Nimbin[12]?Games / Entropie / commits / 5c30ea5

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.1.0: new people, rolling kit, your pockets

Christian Immanuel · 2026-10-19 20:23 · 5c30ea5ae0380e93d8c438615c1b88bf4eaac8f4

- New cast: you came with Volkram, Jana and Toni; the house belongs to
  Kurti (74, slow on his feet) and his sons Martin (~30) and Jonas (~25).
  Jonas, Ida, Mila and Ben are gone
- Most roll their own: tobacco (a pouch rolls about 75), papers (50 a
  packet), filter tips (120 a bag). Short of something they ask whoever is
  near for a paper, a filter or a bit of tobacco - you too
- B opens your pockets (work trousers: 8 of them): Up / Down, Enter takes
  it out - looking at someone you give it to them, else you roll one
 entropie/Makefile                |   2 +-
 entropie/README.md               |  40 ++++++----
 entropie/STATE.md                |  46 ++++++-----
 entropie/commit_message.txt      |  14 ++--
 entropie/src/core/input.c        |   1 +
 entropie/src/core/input.h        |   1 +
 entropie/src/game/game.c         |   4 +-
 entropie/src/game/game.h         |   6 ++
 entropie/src/game/game_actions.c | 167 ++++++++++++++++++++++++++++++++++++++-
 entropie/src/main.c              |  19 +++++
 entropie/src/people/ai.c         |  29 ++++++-
 entropie/src/people/ai.h         |   2 +
 entropie/src/people/pockets.c    |  79 +++++++++++++++++-
 entropie/src/people/pockets.h    |  27 +++++--
 entropie/src/render/ui.h         |   2 +-
 entropie/src/world/object.h      |  10 ++-
 entropie/src/world/scene.c       | 127 ++++++++++++++++++++---------
 17 files changed, 480 insertions(+), 96 deletions(-)

diff --git a/entropie/Makefile b/entropie/Makefile
index eb73309..8b828a8 100644
--- a/entropie/Makefile
+++ b/entropie/Makefile
@@ -1,5 +1,5 @@
 APP       := entropie
-VERSION   := 2.0.0
+VERSION   := 2.1.0
 
 PREFIX    ?= /usr
 BINDIR    ?= $(PREFIX)/bin
diff --git a/entropie/README.md b/entropie/README.md
index a427630..6a4e565 100644
--- a/entropie/README.md
+++ b/entropie/README.md
@@ -265,7 +265,7 @@ and satellite pictures (about 340 x 340 m).
   pushes off and the front one lands first
 - Drunk, walking into a wall or a person easily ends on the ground
 - Now and then they grab a beer, take a sip or two and put it down
-- Tidy people (Ida) pick up trash they pass and take it to the container
+- Tidy people (Jana) pick up trash they pass and take it to the container
 - Press G and the nearest one helps: walks beside you, finds a free spot
   at what you push and pushes along. Press again for more helpers, H sends them off
 - A helper standing where the load wants to go notices it, lets go and comes
@@ -373,10 +373,9 @@ Hold the left button to keep working.
 - The chainsaw is carried relaxed in one hand; sawing takes both. Nobody
   sprints flat out with it
 - Strokes keep hitting a cut that was started, whoever swings
-- Everyone works their own way: Jonas is quick, gets right to it and takes
-  no breaks; Ida thinks things over first and takes a little walk now and
-  then; Mila stops for a chat with someone; Ben is slow, ponders and sits
-  down a lot. Now and then they take another kind of break (a coffee at the
+- Everyone works their own way: Martin is quick, gets right to it and takes
+  no breaks; Kurti is old and slow, ponders and sits down a lot; Jana and
+  Jonas stop for a chat; Toni takes a little walk; Volkram has a coffee. Now and then they take another kind of break (a coffee at the
   stove, a walk, a chat, a sit), short or long, and put the tool down first
 - Called away from a job to help you, they go back to it after; the
   forgetful wait to be reminded ("Back to the firewood, please")
@@ -432,7 +431,7 @@ and they snap off. Without branches it moves much more easily.
 - The others can slip off a wobbly ladder too
 - Helpers follow you up a leaning ladder onto a roof or wall and down again;
   without a ladder they pull themselves up after you (if they reach) and
-  jump down when you're down. Ida is afraid of heights and stays down. Up
+  jump down when you're down. Kurti stays off ladders these days. Up
   there they go slowly and carefully, arms out; drunk, the sharp ones are
   extra careful, the dim ones stroll about and stumble
 - Click a leaning ladder to climb: W / S up and down, at the top W gets you
@@ -460,9 +459,12 @@ say no more often and don't come to you for a bit.
 
 - "Could you do something for me?": make firewood, cut trees, collect the
   trash, or look after the fire
-- Everyone is their own person ("Who are you?"): Jonas loves the chainsaw
-  and hates tidying up, Ida likes things clean but fears chainsaws, Mila
-  lives for campfire nights, Ben mostly wants to relax
+- Everyone is their own person ("Who are you?"). With you came Volkram (calm,
+  splits wood to clear his head), his girlfriend Jana (the fire and the
+  people, likes it tidy) and Toni (first time out here, keen). The house is
+  Kurti's: an old man (74) who has felled more trees than anyone, with his
+  sons Martin (about 30, chainsaw, no brooms) and Jonas (about 25, helpful,
+  talks a lot)
 - They may say no: to jobs they don't like, when tired or drunk, or when it
   isn't needed ("We've got plenty of firewood")
 - They come to you with jobs that need doing and that they like: low on
@@ -500,9 +502,9 @@ say no more often and don't come to you for a bit.
 - Trash: they take a garbage bag, bag piece after piece and empty full bags
   in the container; a bag is carried by its neck, the arm a little out
 - The fire: they keep wood in it, light it when it gets dark and sit by it.
-  No lighter (Ida, Mila): they borrow one from somebody near, maybe you
-- Pockets: you, Jonas and Ben have a lighter. It runs empty over time. A
-  borrowed one goes back when they think of it; owners ask for theirs
+  No lighter (Jana, Jonas): they borrow one from somebody near, maybe you
+- Pockets: most have a lighter. It runs empty over time. A borrowed one
+  goes back when they think of it; owners ask for theirs
 - 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),
@@ -514,8 +516,16 @@ say no more often and don't come to you for a bit.
 
 ## Smoking
 
-- Ben and Jonas roll their own (tobacco), you and Ben have a pack, Mila
-  smokes now and then and bums them, Ida doesn't smoke
+- Most roll their own: tobacco (a pouch rolls about 75), papers (a packet
+  of 50), filter tips (a bag of 120; Kurti never uses one). Martin smokes a
+  pack, Jana only now and then, Jonas doesn't smoke
+- Short of something, they ask: "Got a paper for me?", a filter, a bit of
+  tobacco - from whoever is near, you too. Toni forgot his papers, Jana is
+  out of filters. No filter to be had: they roll one without
+- B: your pockets - what's in which (work trousers: 8 pockets, normal ones
+  4, a skirt 2). Up / Down picks, Enter takes it out: looking at someone
+  you give it (a cigarette, a pinch of tobacco, 3 papers, 5 filters, lend
+  the lighter), else you roll one and light it
 - Smokers get a craving over time (dying for one: restless). In a quiet moment (idle, a break, by
   the fire) they light up: rolling first if it's tobacco (both hands),
   then the lighter to it - theirs, or "Got a light?" from somebody near
@@ -558,7 +568,7 @@ say no more often and don't come to you for a bit.
   one hand holds it (or him), the other stays at the belt
   A used Urinella goes into the bag (tidy), is dropped (careless) or flung
   away (drunk); now and then it ends up on the shoes
-- Clothes: trousers, shorts (Jonas), a skirt (Ida) or a dress (Mila)
+- Clothes: trousers, shorts (Toni, Jonas) or a skirt (Jana)
 - The others use the toilet when it's free and not too far; busy (they wait
   a while, less when it's urgent) or far: a decently hidden spot behind a
   bush, tree, wall or car, away from the others; spots others used are
diff --git a/entropie/STATE.md b/entropie/STATE.md
index 27c8533..7686990 100644
--- a/entropie/STATE.md
+++ b/entropie/STATE.md
@@ -5,24 +5,27 @@ Updated with every version (see the version line at the end).
 
 ## People
 
-| | You | Jonas | Ida | Mila | Ben |
-|---|---|---|---|---|---|
-| Weight | 75 kg | 85 kg | 55 kg | 68 kg | 80 kg |
-| Bearing (pride / energy / nerves) | relaxed 0 / .2 / .1 | proud .8 / .3 / 0 | shy -.8 / -.1 / .6 | lively .2 / .8 / .3 | tired -.2 / -.7 / 0 |
-| Sporty (0..1) | .5 | .75 | .35 | .85 | .15 |
-| Holds drink | 1.0 | 1.3 | 0.7 | 1.1 | 0.9 |
-| Likes: firewood / trees / trash / fire | – | .5 / .9 / -.8 / .2 | -.2 / -.9 / .8 / .3 | .6 / .2 / 0 / .9 | .1 / -.4 / .3 / .6 |
-| Willing (does what's asked) | – | .45 | .9 | .7 | .35 |
-| Skills to start: firewood / trees / trash / fire | .3 each | .7 / .8 / .2 / .4 | .2 / .1 / .6 / .3 | .5 / .3 / .3 / .7 | .3 / .2 / .3 / .5 |
-| Tempo / thinks first (s) | 1 / 0 | 1.25 / .2 | .95 / 1.6 | 1.1 / .6 | .75 / 2.4 |
-| Break | – | none | a little walk | a chat | sits down |
-| Main hand | right | right | left | right | right |
-| Pees | standing | standing | squatting | standing (Urinella, 3 in the pocket) | standing |
-| Lighter in the pocket (lights) | 20 | 40 | – | – | 40 |
-| Smokes (craving speed) / pack / roll-ups | .3 / 10 / 0 | .6 / 0 / 12 | – | .25 / 0 / 0 | .9 / 6 / 20 |
-| Wits | .6 | .45 | .8 | .6 | .25 |
-| Afraid of heights | .2 | .1 | .85 | .15 | .5 |
-| Worn from the days before (start) | .15 | .2 | .1 | .25 | .55 |
+| | You | Volkram | Jana | Toni | Kurti | Martin | Jonas |
+|---|---|---|---|---|---|---|---|
+| Who | – | your friend, Jana's boyfriend | your friend | your friend, first time | the house is his, 74 | his eldest, ~30 | his youngest, ~25 |
+| Weight | 75 kg | 88 kg | 60 kg | 70 kg | 72 kg | 92 kg | 76 kg |
+| Bearing (pride / energy / nerves) | 0 / .2 / .1 | .5 / .1 / 0 | .1 / .7 / .3 | -.3 / .4 / .6 | .3 / -.6 / 0 | .8 / .3 / .1 | -.1 / .6 / .2 |
+| Sporty (0..1) | .5 | .55 | .7 | .8 | .12 (old legs: x.52 speed) | .6 | .85 |
+| Holds drink | 1.0 | 1.2 | 0.8 | 0.9 | 1.4 | 1.3 | 1.0 |
+| Likes: firewood / trees / trash / fire | – | .8 / .3 / 0 / .6 | 0 / -.5 / .6 / .9 | .4 / .6 / -.4 / .3 | .6 / .7 / .2 / .8 | .4 / .9 / -.6 / .3 | .6 / .4 / .3 / .5 |
+| Willing | – | .7 | .8 | .5 | .4 | .5 | .85 |
+| Skills: firewood / trees / trash / fire | .3 each | .6 / .4 / .3 / .6 | .3 / .2 / .5 / .6 | .4 / .4 / .2 / .3 | .8 / .9 / .4 / .8 | .7 / .8 / .2 / .4 | .4 / .4 / .4 / .4 |
+| Tempo / thinks first (s) | 1 / 0 | 1 / .8 | 1.1 / .5 | 1.2 / .3 | .7 / 2.2 | 1.2 / .2 | 1.15 / .4 |
+| Break | – | coffee | a chat | a little walk | sits down | none | a chat |
+| Main hand | right | right | left | right | right | right | left |
+| Pees | standing | standing | standing (Urinella) | standing | standing | standing | standing |
+| Lighter | yes | yes | – | yes | yes | yes | – |
+| Smokes (craving speed) | .3 | .6 | .2 | .7 | .8 | .4 | – |
+| Pockets: cigarettes / tobacco / papers / filters | 0 / 75 / 50 / 120 | 0 / 40 / 20 / 60 | 0 / 10 / 5 / 0 | 0 / 25 / 0 / 15 | 0 / 60 / 30 / never | 12 / – / – / – | – |
+| Keys | car | – | – | – | house | – | – |
+| Wits | .6 | .7 | .65 | .5 | .75 | .55 | .45 |
+| Afraid of heights | .2 | .15 | .5 | .1 | .7 | .05 | .2 |
+| Worn from the days before (start) | .15 | .2 | .1 | .35 | .4 | .2 | .1 |
 
 Alice the dog: follows, can be petted and called.
 
@@ -149,6 +152,11 @@ Round 1.99:
 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.
 
+Round 2.1:
+- Cast (`scene.c`): you; Volkram, Jana, Toni (your friends); Kurti (74, the house is his) and his sons Martin (~30) and Jonas (~25). Their own clothes (`LOOKS`, the wardrobe for anyone extra), AGE (past 45 slower: top and jump speed x (1 - (age - 45) / 60), at least 0.5).
+- Rolling kit (`pockets.c`): `tobacco` (roll-ups' worth, a pouch POUCH_ROLLUPS 75), `papers` (PAPERS_PACK 50), `filters` (FILTERS_BAG 120), `filter_habit`; a roll-up takes tobacco, a paper and a filter if there is one. `Pockets_need`: papers, tobacco, or (filter habit) a filter missing. `ai.c` smokeBreak: asks whoever near has it to spare (6 m): NPCs give (3 papers, 5 filters, a pinch for 2) or say no; the player is asked (`asking_you`, a hint line until given, or they walk off). A filter is asked for once a smoke (`filter_asked`), then they roll without.
+- Your pockets (B, `game_actions.c` game_pockets / Game_pocketLines, drawn on the right in `main.c`): what's in them, how many pockets are used (`Pockets_slots`: work trousers 8 - you -, trousers and shorts 4, a skirt 2, a dress 1). Up / Down picks, Enter: looking at someone you give it (cigarette, pinch, papers, filters, lend the lighter, a Urinella; what they asked for: "Thanks, you're a star!"), else you roll one and light it. `keys` ("Car keys", Kurti's "House keys").
+
 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.
@@ -301,4 +309,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.0.0 (build 20324f8)`
+State of `entropie 2.1.0 (build 7cb9179)`
diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt
index b7df0a9..0cfcc17 100644
--- a/entropie/commit_message.txt
+++ b/entropie/commit_message.txt
@@ -1,6 +1,10 @@
-entropie 2.0.0: a cigarette machine
+entropie 2.1.0: new people, rolling kit, your pockets
 
-- 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
+- New cast: you came with Volkram, Jana and Toni; the house belongs to
+  Kurti (74, slow on his feet) and his sons Martin (~30) and Jonas (~25).
+  Jonas, Ida, Mila and Ben are gone
+- Most roll their own: tobacco (a pouch rolls about 75), papers (50 a
+  packet), filter tips (120 a bag). Short of something they ask whoever is
+  near for a paper, a filter or a bit of tobacco - you too
+- B opens your pockets (work trousers: 8 of them): Up / Down, Enter takes
+  it out - looking at someone you give it to them, else you roll one
diff --git a/entropie/src/core/input.c b/entropie/src/core/input.c
index 487cc08..b5c5bc0 100644
--- a/entropie/src/core/input.c
+++ b/entropie/src/core/input.c
@@ -31,6 +31,7 @@ void Input_poll(Input *input) {
         case SDL_SCANCODE_TAB:    input->show_stats = true;  break;
         case SDL_SCANCODE_P:      input->pee = true;         break;
         case SDL_SCANCODE_K:      input->smoke = true;       break;
+        case SDL_SCANCODE_B:      input->pockets = true;     break;
         case SDL_SCANCODE_Z:      input->sleep = true;       break;
         case SDL_SCANCODE_LSHIFT: input->sprint_tap = true;  break;
         case SDL_SCANCODE_1:      input->choice = 1;         break;
diff --git a/entropie/src/core/input.h b/entropie/src/core/input.h
index d7aa749..9bcd5cd 100644
--- a/entropie/src/core/input.h
+++ b/entropie/src/core/input.h
@@ -42,6 +42,7 @@ typedef struct {
     int   page;          /* Right / Left arrow pressed: next / previous page (stats) */
     int   menu_move;     /* Down / Up arrow pressed: next / previous line (dev menu) */
     bool  dev_menu;      /* F1 pressed: dev menu on / off */
+    bool  pockets;       /* B pressed: your pockets open / shut */
 } Input;
 
 /* Pumps SDL events and fills in the current frame's input. */
diff --git a/entropie/src/game/game.c b/entropie/src/game/game.c
index 2be9401..3a624b6 100644
--- a/entropie/src/game/game.c
+++ b/entropie/src/game/game.c
@@ -195,8 +195,8 @@ int Game_stats(const Game *game, StatBar *bars, int max, const char **title, cha
     if (skill_page) {
         char lighter[24] = "no lighter";
         if (p->lighter) snprintf(lighter, sizeof lighter, p->lighter_uses > 0 ? "lighter (%d)" : "empty lighter", p->lighter_uses);
-        snprintf(about, size, "Pockets: %d cigarettes, tobacco for %d, %s, %d Urinellas",
-                 p->cigarettes, p->tobacco, lighter, p->urinellas);
+        snprintf(about, size, "Pockets: %d cigarettes, tobacco for %d, %d papers, %d filters, %s",
+                 p->cigarettes, p->tobacco, p->papers, p->filters, lighter);
     } else {
         float jump = Object_jumpSpeed(p);
         snprintf(about, size, "Sprint %.1f m/s   Jump %d cm   %d kg   %s-handed",
diff --git a/entropie/src/game/game.h b/entropie/src/game/game.h
index a1641a8..73f28b6 100644
--- a/entropie/src/game/game.h
+++ b/entropie/src/game/game.h
@@ -42,6 +42,8 @@ typedef struct {
     bool     show_dev;      /* dev menu (F1): change anybody's stats ... */
     int      dev_row, dev_who; /* ... the line picked, whose */
     int      stats_page;    /* ... which page (arrow keys): body, skills */
+    bool     show_pockets;  /* your pockets (B) ... */
+    int      pocket_row;    /* ... the thing picked */
     float    fire_nag;      /* until the next "you're on fire" reminder */
     char     hint[192];     /* what to do now (being followed, watched, at the grill, at a ladder) */
     Mesh     marker;        /* where a woodpile would go, shown on the ground */
@@ -92,6 +94,10 @@ const char *Game_status(const Game *game);
 /* Shadow pass and scene pass for all world objects. */
 void Game_render(Game *game, Renderer *renderer);
 
+#define POCKET_LINES 10
+/* Your pockets as lines of text: a heading, what's in them, what Enter does; *picked: the line picked. */
+int Game_pocketLines(Game *game, char lines[][96], int max, int *picked);
+
 #define DEV_LINES 13
 /* The dev menu as lines of text; *picked: the line picked. Returns how many. */
 int Game_devLines(const Game *game, char lines[][96], int max, int *picked);
diff --git a/entropie/src/game/game_actions.c b/entropie/src/game/game_actions.c
index 323477c..e87121d 100644
--- a/entropie/src/game/game_actions.c
+++ b/entropie/src/game/game_actions.c
@@ -140,7 +140,11 @@ static void smokeOrStop(Game *game) {
         say(game, "You put the cigarette out");
         return;
     }
-    if (!Pockets_hasSmoke(p)) { say(game, "Nothing to smoke - ask somebody for one (talk to them)"); return; }
+    if (!Pockets_hasSmoke(p)) {
+        say(game, p->tobacco > 0 ? "No papers - ask somebody for one" : p->papers > 0 ? "No tobacco left - ask somebody for a bit"
+                                                                       : "Nothing to smoke - ask somebody for one (talk to them)");
+        return;
+    }
     Object *light = p->lighter && p->lighter_uses > 0 ? p
                   : Pockets_lighterNear(p, game->world.objects, game->world.count, 2.5f);
     if (!light) { say(game, p->lighter ? "Your lighter is empty - stand next to somebody with one" : "No lighter - stand next to somebody with one"); return; }
@@ -149,7 +153,7 @@ static void smokeOrStop(Game *game) {
     if (!Pockets_takeSmoke(p, both, &rolled)) { say(game, "Both hands free to roll one"); return; }
     int hand = p->hands.state[p->main_hand] == HAND_FREE ? p->main_hand : Hands_freeHand(p);
     if (!Body_smoke(&p->body, rolled, hand >= 0 ? hand : 2)) {
-        if (rolled) p->tobacco++; else p->cigarettes++;
+        if (rolled) { p->tobacco++; p->papers++; } else p->cigarettes++;
         return;
     }
     Pockets_light(light);
@@ -157,6 +161,146 @@ static void smokeOrStop(Game *game) {
     say(game, rolled ? "You roll a cigarette" : light != p ? "Somebody gives you a light" : "You light a cigarette");
 }
 
+/* ---------- Your pockets (B): what's in them, taken out, given ---------- */
+
+enum { ROW_CIGS, ROW_TOBACCO, ROW_PAPERS, ROW_FILTERS, ROW_LIGHTER, ROW_KEYS, ROW_URINELLAS, POCKET_ROWS };
+
+static bool rowHas(const Object *p, int row) {
+    switch (row) {
+    case ROW_CIGS:      return p->cigarettes > 0;
+    case ROW_TOBACCO:   return p->tobacco > 0;
+    case ROW_PAPERS:    return p->papers > 0;
+    case ROW_FILTERS:   return p->filters > 0;
+    case ROW_LIGHTER:   return p->lighter;
+    case ROW_KEYS:      return p->keys != NULL;
+    case ROW_URINELLAS: return p->urinellas > 0;
+    }
+    return false;
+}
+
+static void rowText(const Object *p, int row, char *out, size_t size) {
+    switch (row) {
+    case ROW_CIGS:      snprintf(out, size, "Cigarettes - %d", p->cigarettes); break;
+    case ROW_TOBACCO:   snprintf(out, size, "Tobacco - enough for about %d", p->tobacco); break;
+    case ROW_PAPERS:    snprintf(out, size, "Rolling papers - %d", p->papers); break;
+    case ROW_FILTERS:   snprintf(out, size, "Filter tips - %d", p->filters); break;
+    case ROW_LIGHTER:   snprintf(out, size, "%s - %s", p->lighter_of ? "A borrowed lighter" : "Lighter",
+                                 p->lighter_uses > 0 ? "works" : "empty"); break;
+    case ROW_KEYS:      snprintf(out, size, "%s", p->keys); break;
+    case ROW_URINELLAS: snprintf(out, size, "Urinellas - %d", p->urinellas); break;
+    default:            out[0] = 0; break;
+    }
+}
+
+/* The row picked, kept on something that's there. */
+static int pocketRow(Game *game) {
+    const Object *p = game->player;
+    for (int k = 0; k < POCKET_ROWS; k++) {
+        int r = (game->pocket_row + k) % POCKET_ROWS;
+        if (rowHas(p, r)) return game->pocket_row = r;
+    }
+    return -1;
+}
+
+/* What Enter does with that row now: given to who's in front, or used. */
+static void pocketVerb(Game *game, int row, char *out, size_t size) {
+    Object *person = game_personInFront(game);
+    const char *name = person ? person->name : NULL;
+    switch (row) {
+    case ROW_CIGS:      snprintf(out, size, name ? "Enter: give %s a cigarette" : "Enter: smoke one", name); break;
+    case ROW_TOBACCO:   snprintf(out, size, name ? "Enter: give %s a pinch" : "Enter: roll one and light it", name); break;
+    case ROW_PAPERS:    snprintf(out, size, name ? "Enter: give %s a few papers" : "Enter: roll one and light it", name); break;
+    case ROW_FILTERS:   snprintf(out, size, name ? "Enter: give %s a few filters" : "Enter: roll one and light it", name); break;
+    case ROW_LIGHTER:   snprintf(out, size, name ? "Enter: lend %s your lighter" : "Enter: click - does it work?", name); break;
+    case ROW_KEYS:      snprintf(out, size, "Keys stay in your pocket"); break;
+    case ROW_URINELLAS: snprintf(out, size, name ? "Enter: give %s one" : "They're for peeing standing (P)", name); break;
+    default:            out[0] = 0; break;
+    }
+}
+
+static void smokeOrStop(Game *game);
+
+static void usePocket(Game *game, int row) {
+    Object *p = game->player, *person = game_personInFront(game);
+    char line[96];
+    if (!person || person->type != OBJ_HUMAN) {
+        if (row == ROW_CIGS || row == ROW_TOBACCO || row == ROW_PAPERS || row == ROW_FILTERS) {
+            if (!Body_isSmoking(&p->body)) smokeOrStop(game);
+            else say(game, "You're smoking one already");
+        } else if (row == ROW_LIGHTER) {
+            say(game, p->lighter_uses > 0 ? "Click - a flame. It works" : "Click, click... it's empty");
+        }
+        return;
+    }
+    int given = 0;
+    PocketNeed what = POCKET_NOTHING;
+    switch (row) {
+    case ROW_CIGS:
+        if (p->cigarettes > 0) { p->cigarettes--; person->cigarettes++; given = 1; }
+        snprintf(line, sizeof line, "You give %s a cigarette", person->name);
+        break;
+    case ROW_TOBACCO: what = POCKET_TOBACCO; given = Pockets_give(p, person, what); snprintf(line, sizeof line, "You give %s a pinch of tobacco", person->name); break;
+    case ROW_PAPERS:  what = POCKET_PAPERS;  given = Pockets_give(p, person, what); snprintf(line, sizeof line, "You give %s %d papers", person->name, given); break;
+    case ROW_FILTERS: what = POCKET_FILTERS; given = Pockets_give(p, person, what); snprintf(line, sizeof line, "You give %s %d filters", person->name, given); break;
+    case ROW_LIGHTER:
+        if (person->lighter) { say(game, "They have one"); return; }
+        Pockets_passLighter(p, person);
+        given = 1;
+        snprintf(line, sizeof line, "You lend %s your lighter", person->name);
+        break;
+    case ROW_URINELLAS:
+        if (p->urinellas > 0) { p->urinellas--; person->urinellas++; given = 1; }
+        snprintf(line, sizeof line, "You give %s a Urinella", person->name);
+        break;
+    default:
+        say(game, "Your keys stay in your pocket");
+        return;
+    }
+    if (!given) { say(game, "Not enough to give any"); return; }
+    snprintf(game->info, sizeof(game->info), "%s", line); /* (say keeps the pointer: not a stack buffer) */
+    game->info_about = NULL;
+    say(game, game->info);
+    if (person->ai.asking_you && (person->ai.asking_you == (int)what || row == ROW_CIGS)) { /* (what they asked for) */
+        person->ai.asking_you = 0;
+        person->smoke_ask = 2.0f; /* (and rolls one in a moment) */
+        Ai_say(person, "Thanks, you're a star!");
+    } else if (person->ai.speech_time <= 0.0f) {
+        Ai_say(person, "Oh - thanks.");
+    }
+}
+
+/* B: open or shut your pockets; Up / Down picks, Enter takes it out (gives it to who's in front). */
+static void game_pockets(Game *game, const Input *input) {
+    if (input->pockets) game->show_pockets = !game->show_pockets;
+    if (!game->show_pockets || game->show_dev) return;
+    if (input->menu_move) {
+        for (int k = 1; k <= POCKET_ROWS; k++) { /* (to the next one with something in it) */
+            int r = ((game->pocket_row + input->menu_move * k) % POCKET_ROWS + POCKET_ROWS) % POCKET_ROWS;
+            if (rowHas(game->player, r)) { game->pocket_row = r; break; }
+        }
+    }
+    int row = pocketRow(game);
+    if (input->enter && row >= 0) usePocket(game, row);
+}
+
+int Game_pocketLines(Game *game, char lines[][96], int max, int *picked) {
+    const Object *p = game->player;
+    int n = 0, row = pocketRow(game);
+    *picked = -1;
+    if (max < 3) return 0;
+    snprintf(lines[n++], 96, "Pockets (%s): %d of %d used   -  B closes",
+             p->look.garment == GARMENT_TROUSERS && p->work_trousers ? "work trousers" : "trousers",
+             Pockets_used(p), Pockets_slots(p));
+    for (int r = 0; r < POCKET_ROWS && n < max - 1; r++) {
+        if (!rowHas(p, r)) continue;
+        if (r == row) *picked = n;
+        rowText(p, r, lines[n++], 96);
+    }
+    if (row < 0) snprintf(lines[n++], 96, "Nothing in them");
+    else pocketVerb(game, row, lines[n++], 96);
+    return n;
+}
+
 /* Climbing a ladder with one hand (something in the other): the unpractised,
    clumsy, unfit, tired or drunk may slip - a wobble first, then off they go.
    Every climb like that is practice. */
@@ -552,12 +696,28 @@ static int useWater(Game *game) {
     return CLICK_WATER;
 }
 
+/* Somebody near who asked you for a paper, a filter or tobacco (and is still waiting). */
+static const Object *askingYou(const Game *game) {
+    const Object *p = game->player;
+    for (int i = 0; i < game->world.count; i++) {
+        Object *o = game->world.objects[i];
+        if (o->type != OBJ_HUMAN || !o->ai.asking_you) continue;
+        if (Pockets_hasSmoke(o) && Pockets_need(o) == POCKET_NOTHING) { o->ai.asking_you = 0; continue; } /* (got it elsewhere) */
+        if (hypotf(o->position.x - p->position.x, o->position.z - p->position.z) > 8.0f) { o->ai.asking_you = 0; continue; } /* (walked off) */
+        return o;
+    }
+    return NULL;
+}
+
 const char *Game_hint(Game *game) {
-    const Object *shown = follower(game, SHOW_PLACE), *watcher = follower(game, SHOW_HOW), *ladder, *food;
+    const Object *shown = follower(game, SHOW_PLACE), *watcher = follower(game, SHOW_HOW), *ladder, *food, *asking;
     Object *fire = NULL;
     bool at_top;
     if (game->player->on_fire > 0.0f)
         snprintf(game->hint, sizeof(game->hint), "On fire!  C: drop and roll  -  left click: pour your drink over you  -  water or pee puts it out");
+    else if ((asking = askingYou(game)))
+        snprintf(game->hint, sizeof(game->hint), "%s asks you for %s  -  B: your pockets, look at %s, Enter: give it",
+                 asking->name, Pockets_needName((PocketNeed)asking->ai.asking_you), asking->female ? "her" : "him");
     else if (shown)
         snprintf(game->hint, sizeof(game->hint), "%s follows you: walk to where the firewood should go and left click  -  Backspace: never mind", shown->name);
     else if (watcher)
@@ -824,6 +984,7 @@ void game_handleActions(Game *game, const Input *input) {
     if (input->show_keys) game->show_keys = !game->show_keys;
     if (input->show_stats) game->show_stats = !game->show_stats;
     game_dev(game, input);
+    game_pockets(game, input);
     if (input->page && game->show_stats && !game->show_dev) game->stats_page = (game->stats_page + input->page + STATS_PAGES) % STATS_PAGES;
     if (input->call_help) game_callHelp(game);
     if (input->dismiss_help) dismissHelp(game);
diff --git a/entropie/src/main.c b/entropie/src/main.c
index 8d74f81..baf8c78 100644
--- a/entropie/src/main.c
+++ b/entropie/src/main.c
@@ -32,6 +32,7 @@
 #define STATS_MAX     12
 #define DEV_LABEL     64  /* the dev menu (F1) */
 #define MENU_LABEL    80  /* the menu (Esc) */
+#define POCKET_LABEL  88  /* your pockets (B) */
 
 static const char *const KEYS[] = { /* proportional font: no column padding */
     "W A S D:  walk,   mouse:  look around",
@@ -62,6 +63,7 @@ static const char *const KEYS[] = { /* proportional font: no column padding */
     "I:  show / hide keys",
     "F1:  dev menu (change anybody's stats)",
     "Tab:  your stats (arrow keys: next page)",
+    "K:  smoke;   B:  your pockets (Up / Down, Enter: take out or give)",
     "Esc:  menu (resume, keys, quit)",
 };
 #define KEY_COUNT ((int)(sizeof(KEYS) / sizeof(KEYS[0])))
@@ -110,6 +112,22 @@ static void drawDev(Ui *ui, const Game *game, int width, int height) {
     }
 }
 
+/* Your pockets (B) on a sheet of paper on the right. */
+static void drawPockets(Ui *ui, Game *game, int width, int height) {
+    char lines[POCKET_LINES][96];
+    int chosen = -1;
+    int n = Game_pocketLines(game, lines, POCKET_LINES, &chosen);
+    if (n == 0) return;
+    const float row = 30.0f, pw = 520.0f, ph = 30.0f + row * (float)n;
+    const float x = (float)width - pw - 18.0f, top = (float)height * 0.5f + ph * 0.5f;
+    Ui_drawPatch(ui, x, top - ph, pw, ph, 0.90f, 0.86f, 0.74f, 0.93f, 1.0f, width, height);
+    for (int i = 0; i < n && POCKET_LABEL + i < UI_LABELS; i++) {
+        float y = top - 38.0f - row * (float)i;
+        if (i == chosen) Ui_drawPatch(ui, x + 10.0f, y - 2.0f, pw - 20.0f, row - 2.0f, 0.95f, 0.78f, 0.45f, 0.8f, 0.0f, width, height);
+        Ui_drawTextLeft(ui, POCKET_LABEL + i, lines[i], x + 22.0f, y, width, height);
+    }
+}
+
 /* ---------- The menu (Esc): the game waits meanwhile ---------- */
 
 enum { MENU_RESUME, MENU_KEYS, MENU_QUIT, MENU_COUNT };
@@ -234,6 +252,7 @@ int main(int argc, char **argv) {
         if (game.show_stats) drawStats(&ui, &game, width, height);
         else if (needs_text[0]) Ui_drawTextLine(&ui, NEEDS_LABEL, needs_text, 1, UI_TOP_RIGHT, width, height);
         if (game.show_dev) drawDev(&ui, &game, width, height);
+        if (game.show_pockets) drawPockets(&ui, &game, width, height);
         if (game.show_keys)
             for (int k = 0; k < KEY_COUNT; k++) Ui_drawTextLine(&ui, KEYS_LABEL + k, KEYS[k], k, UI_TOP_LEFT, width, height);
         else
diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c
index 3b9b894..345757e 100644
--- a/entropie/src/people/ai.c
+++ b/entropie/src/people/ai.c
@@ -1529,6 +1529,31 @@ static void smokeBreak(Object *self, Object *const *objects, int count, float dt
     if (!quietMoment(self) && self->craving < 0.98f) return; /* (dying for one: whatever they're doing) */
     int hand = Hands_freeHand(self);
     bool both = self->hands.state[HAND_LEFT] == HAND_FREE && self->hands.state[HAND_RIGHT] == HAND_FREE;
+    /* Rolling their own and something's missing (papers, tobacco, a filter): asked for first */
+    PocketNeed need = Pockets_need(self);
+    if (need != POCKET_NOTHING && !(need == POCKET_FILTERS && ai->filter_asked)) {
+        Object *giver = Pockets_hasNear(self, need, objects, count, 6.0f);
+        if (need == POCKET_FILTERS) ai->filter_asked = true; /* (once: no filter, then without) */
+        if (giver) {
+            char line[80];
+            self->smoke_ask = 30.0f + 30.0f * random01(ai);
+            if (giver->type == OBJ_PLAYER) {
+                snprintf(line, sizeof(line), "Hey - got %s for me?", Pockets_needName(need));
+                Ai_say(self, line);
+                ai->asking_you = (int)need; /* (see the pockets: B, give it) */
+                return;
+            }
+            snprintf(line, sizeof(line), "%s, got %s for me?", giver->name, Pockets_needName(need));
+            Ai_say(self, line);
+            if (giver->type == OBJ_HUMAN && (giver->ai.annoyed > 0.5f || random01(ai) > 0.5f + 0.5f * giver->ai.character.willing)) {
+                Ai_say(giver, need == POCKET_TOBACCO ? "I'm nearly out myself, sorry." : "Sorry, I need them myself.");
+            } else if (Pockets_give(giver, self, need) > 0) {
+                if (giver->type == OBJ_HUMAN) Ai_say(giver, need == POCKET_TOBACCO ? "Here, take a pinch." : "Sure, here.");
+                self->smoke_ask = 2.0f; /* (and rolls one in a moment) */
+            }
+            return;
+        }
+    }
     if (!Pockets_hasSmoke(self)) {
         Object *giver = Pockets_smokesNear(self, objects, count, 6.0f);
         self->smoke_ask = 40.0f + 40.0f * random01(ai);
@@ -1556,6 +1581,8 @@ static void smokeBreak(Object *self, Object *const *objects, int count, float dt
         return;
     }
     ai->asked_you = false; /* (got some) */
+    ai->asking_you = 0;
+    ai->filter_asked = false;
     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?");
@@ -1566,7 +1593,7 @@ static void smokeBreak(Object *self, Object *const *objects, int count, float dt
     self->smoke_ask = 20.0f; /* (whatever happens: not again right away) */
     if (!Pockets_takeSmoke(self, both, &rolled)) return; /* (only tobacco and the hands are busy: later) */
     if (!Body_smoke(&self->body, rolled, hand >= 0 ? (self->hands.state[self->main_hand] == HAND_FREE ? self->main_hand : hand) : 2)) {
-        if (rolled) self->tobacco++; else self->cigarettes++; /* (back in the pocket) */
+        if (rolled) { self->tobacco++; self->papers++; } else self->cigarettes++; /* (back in the pocket) */
         return;
     }
     Pockets_light(light);
diff --git a/entropie/src/people/ai.h b/entropie/src/people/ai.h
index a819e29..c58af24 100644
--- a/entropie/src/people/ai.h
+++ b/entropie/src/people/ai.h
@@ -177,6 +177,8 @@ typedef struct {
     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) */
+    int            asking_you;  /* asked the player for this (PocketNeed: a paper, a filter, tobacco), 0: nothing */
+    bool           filter_asked; /* asked for a filter this time already (none: rolls one without) */
     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/people/pockets.c b/entropie/src/people/pockets.c
index 1217d45..c3776d0 100644
--- a/entropie/src/people/pockets.c
+++ b/entropie/src/people/pockets.c
@@ -43,14 +43,18 @@ int Pockets_refill(Object *who) {
     return got;
 }
 
+static bool canRoll(const Object *who) { return who->tobacco > 0 && who->papers > 0; }
+
 bool Pockets_hasSmoke(const Object *who) {
-    return who->cigarettes > 0 || who->tobacco > 0;
+    return who->cigarettes > 0 || canRoll(who);
 }
 
 bool Pockets_takeSmoke(Object *who, bool can_roll, bool *rolled) {
-    if (who->tobacco > 0 && (can_roll || who->cigarettes <= 0)) {
-        if (!can_roll) return false; /* (only tobacco, and no hands for rolling) */
+    if (canRoll(who) && (can_roll || who->cigarettes <= 0)) {
+        if (!can_roll) return false; /* (only the makings, and no hands for rolling) */
         who->tobacco--;
+        who->papers--;
+        if (who->filters > 0) who->filters--; /* (with a filter if there's one) */
         *rolled = true;
         return true;
     }
@@ -73,8 +77,75 @@ Object *Pockets_smokesNear(Object *who, Object *const *objects, int count, float
 
 bool Pockets_giveSmoke(Object *from, Object *to) {
     if (from->cigarettes > 0) from->cigarettes--;
-    else if (from->tobacco > 0) from->tobacco--;
+    else if (canRoll(from)) { from->tobacco--; from->papers--; if (from->filters > 0) from->filters--; } /* (rolls them one) */
     else return false;
     to->cigarettes++;
     return true;
 }
+
+PocketNeed Pockets_need(const Object *who) {
+    if (who->cigarettes > 0 || (who->tobacco <= 0 && who->papers <= 0)) return POCKET_NOTHING;
+    if (who->papers <= 0) return POCKET_PAPERS;
+    if (who->tobacco <= 0) return POCKET_TOBACCO;
+    if (who->filter_habit && who->filters <= 0) return POCKET_FILTERS;
+    return POCKET_NOTHING;
+}
+
+static int spare(const Object *o, PocketNeed what) {
+    switch (what) {
+    case POCKET_PAPERS:  return o->papers;
+    case POCKET_FILTERS: return o->filters;
+    case POCKET_TOBACCO: return o->tobacco;
+    case POCKET_NOTHING: break;
+    }
+    return 0;
+}
+
+Object *Pockets_hasNear(Object *who, PocketNeed what, Object *const *objects, int count, float range) {
+    Object *best = NULL;
+    for (int i = 0; i < count; i++) {
+        Object *o = objects[i];
+        if (o == who || !o->is_character || o->type == OBJ_DOG || spare(o, what) <= 1) continue;
+        float d = hypotf(o->position.x - who->position.x, o->position.z - who->position.z);
+        if (d < range) { range = d; best = o; }
+    }
+    return best;
+}
+
+int Pockets_give(Object *from, Object *to, PocketNeed what) {
+    static const int SOME[] = { [POCKET_NOTHING] = 0, [POCKET_PAPERS] = 3, [POCKET_FILTERS] = 5, [POCKET_TOBACCO] = 2 };
+    int n = spare(from, what);
+    if (n > SOME[what]) n = SOME[what];
+    if (n <= 0) return 0;
+    switch (what) {
+    case POCKET_PAPERS:  from->papers -= n;  to->papers += n;  break;
+    case POCKET_FILTERS: from->filters -= n; to->filters += n; break;
+    case POCKET_TOBACCO: from->tobacco -= n; to->tobacco += n; break;
+    case POCKET_NOTHING: break;
+    }
+    return n;
+}
+
+const char *Pockets_needName(PocketNeed what) {
+    switch (what) {
+    case POCKET_PAPERS:  return "a paper";
+    case POCKET_FILTERS: return "a filter";
+    case POCKET_TOBACCO: return "a bit of tobacco";
+    case POCKET_NOTHING: break;
+    }
+    return "something";
+}
+
+int Pockets_slots(const Object *who) {
+    switch (who->look.garment) {
+    case GARMENT_TROUSERS: return who->work_trousers ? 8 : 4; /* (work trousers: thigh and leg pockets too) */
+    case GARMENT_SHORTS:   return 4;
+    case GARMENT_SKIRT:    return 2;
+    default:               return 1; /* (a dress: one small one) */
+    }
+}
+
+int Pockets_used(const Object *who) {
+    return (who->cigarettes > 0) + (who->tobacco > 0) + (who->papers > 0) + (who->filters > 0) +
+           (who->lighter ? 1 : 0) + (who->keys ? 1 : 0) + (who->urinellas > 0);
+}
diff --git a/entropie/src/people/pockets.h b/entropie/src/people/pockets.h
index e63c839..7768a4d 100644
--- a/entropie/src/people/pockets.h
+++ b/entropie/src/people/pockets.h
@@ -4,7 +4,8 @@
 /*
  * What people carry in their pockets: Urinellas (one-time use, up to
  * POCKET_URINELLAS), a lighter (it runs empty; it can be lent, and not
- * always given back), cigarettes or tobacco to roll them (shared, used up).
+ * always given back), cigarettes, or the makings to roll them: tobacco,
+ * papers and (for most) filter tips - shared, used up, asked for. Keys.
  */
 
 #include <stdbool.h>
@@ -19,15 +20,31 @@ Object *Pockets_lighterNear(Object *who, Object *const *objects, int count, floa
 void    Pockets_passLighter(Object *from, Object *to);
 /* Someone else's lighter in who's pocket goes back to its owner (if they have none). */
 bool    Pockets_giveBack(Object *who);
-/* Something to smoke in the pocket (a cigarette, or tobacco to roll one). */
+/* Something to smoke in the pocket (a cigarette, or tobacco and a paper to roll one). */
 bool    Pockets_hasSmoke(const Object *who);
-/* Takes one out: tobacco first if they may roll (can_roll: both hands free), else a ready one.
-   *rolled: it has to be rolled first. False: nothing (that they can use now). */
+/* Takes one out: the makings first if they may roll (can_roll: both hands free), else a ready one.
+   *rolled: it has to be rolled first (a paper, tobacco, a filter if there is one). False: nothing (that they can use now). */
 bool    Pockets_takeSmoke(Object *who, bool can_roll, bool *rolled);
 /* Somebody within range with something to smoke (not who). */
 Object *Pockets_smokesNear(Object *who, Object *const *objects, int count, float range);
-/* One cigarette from one pocket to the other (a roll-up for them if it's tobacco). */
+/* One cigarette from one pocket to the other (they roll one if they have no ready ones). */
 bool    Pockets_giveSmoke(Object *from, Object *to);
+
+/* What who lacks to roll one (they have tobacco or papers, not both - or no
+   filter and they use them): POCKET_NOTHING when they can, or have ready ones. */
+typedef enum { POCKET_NOTHING, POCKET_PAPERS, POCKET_FILTERS, POCKET_TOBACCO } PocketNeed;
+PocketNeed  Pockets_need(const Object *who);
+/* Somebody within range who has that to spare (not who). */
+Object     *Pockets_hasNear(Object *who, PocketNeed what, Object *const *objects, int count, float range);
+/* Gives some of it (3 papers, 5 filters, a pinch for 2): how many. */
+int         Pockets_give(Object *from, Object *to, PocketNeed what);
+/* "a paper", "a filter", "a bit of tobacco" */
+const char *Pockets_needName(PocketNeed what);
+
+/* Pockets: how many (by what they wear) and how many have something in them. */
+int     Pockets_slots(const Object *who);
+int     Pockets_used(const Object *who);
+
 /* Takes Urinellas from a box: how many were taken. */
 int     Pockets_refill(Object *who);
 
diff --git a/entropie/src/render/ui.h b/entropie/src/render/ui.h
index b46299a..86aa0d5 100644
--- a/entropie/src/render/ui.h
+++ b/entropie/src/render/ui.h
@@ -9,7 +9,7 @@
 #include "core/gl.h"
 
 #define UI_TEXT_MAX 96
-#define UI_LABELS   88
+#define UI_LABELS   100
 
 typedef enum {
     UI_TOP_LEFT,
diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h
index d60bf79..61b5a62 100644
--- a/entropie/src/world/object.h
+++ b/entropie/src/world/object.h
@@ -104,6 +104,9 @@ typedef struct {
 
 #define POCKET_URINELLAS 4   /* disposable Urinellas a pocket holds (one-time use) */
 #define LIGHTER_USES     60   /* a full lighter lights this many times */
+#define POUCH_ROLLUPS    75   /* a pouch of tobacco rolls about this many */
+#define PAPERS_PACK      50   /* rolling papers in a packet */
+#define FILTERS_BAG      120  /* filter tips in a bag */
 
 /* A pull up in progress (people/climb.c): from where, onto where, over which edge. */
 typedef struct {
@@ -194,7 +197,12 @@ typedef struct Object {
     struct Object *lighter_of;/* ... someone else's (borrowed), or NULL: their own ... */
     int        lighter_uses;  /* ... lights left in it (0: empty) */
     int        cigarettes;    /* characters: ready ones in the pocket (a pack, or roll-ups given) ... */
-    int        tobacco;       /* ... tobacco and papers for this many roll-ups */
+    int        tobacco;       /* ... tobacco for this many roll-ups (a pouch: POUCH_ROLLUPS) ... */
+    int        papers;        /* ... rolling papers (a packet: PAPERS_PACK) ... */
+    int        filters;       /* ... filter tips (a bag: FILTERS_BAG) ... */
+    bool       filter_habit;  /* ... rolls with a filter (without one they ask for one first) */
+    const char *keys;         /* ... keys in the pocket ("Car keys"), or NULL */
+    bool       work_trousers; /* the player: work trousers, more pockets (see Pockets_slots) */
     float      smoker;        /* 0 = doesn't smoke .. 1 = chain smoker: how fast the craving grows */
     float      craving;       /* 0..1: wants a smoke */
     float      puff_timer;    /* smoke breathed out: until the next puff */
diff --git a/entropie/src/world/scene.c b/entropie/src/world/scene.c
index 4a7caab..c8f1deb 100644
--- a/entropie/src/world/scene.c
+++ b/entropie/src/world/scene.c
@@ -22,10 +22,12 @@ typedef struct {
 static const Spawn SCENE[] = {
     /* Characters */
     { OBJ_PLAYER, PALETTE_MODERN, {  1, 1, 0 }, HUMAN_SIZE, 75, NO_ROTATION }, /* (dropped onto the ground: low, no hard landing) */
-    { OBJ_HUMAN,  PALETTE_MODERN, { -1.5f, 1, -3 }, HUMAN_SIZE, 85, { 0, 180, 0 } }, /* facing the player */
-    { OBJ_HUMAN,  PALETTE_MODERN, {  2.5f, 1, -4 }, HUMAN_SIZE, 55, { 0, 160, 0 } },
-    { OBJ_HUMAN,  PALETTE_MODERN, { -5.0f, 1,  4 }, HUMAN_SIZE, 68, { 0, 120, 0 } },
-    { OBJ_HUMAN,  PALETTE_MODERN, {  6.0f, 1,  3 }, HUMAN_SIZE, 80, { 0, 250, 0 } },
+    { OBJ_HUMAN,  PALETTE_MODERN, { -1.5f, 1, -3 }, HUMAN_SIZE, 88, { 0, 180, 0 } }, /* Volkram, facing the player */
+    { OBJ_HUMAN,  PALETTE_MODERN, {  2.5f, 1, -4 }, HUMAN_SIZE, 60, { 0, 160, 0 } }, /* Jana */
+    { OBJ_HUMAN,  PALETTE_MODERN, { -5.0f, 1,  4 }, HUMAN_SIZE, 70, { 0, 120, 0 } }, /* Toni */
+    { OBJ_HUMAN,  PALETTE_MODERN, {  6.0f, 1,  3 }, HUMAN_SIZE, 72, { 0, 250, 0 } }, /* Kurti */
+    { OBJ_HUMAN,  PALETTE_MODERN, {  4.0f, 1, -6 }, HUMAN_SIZE, 92, { 0, 200, 0 } }, /* Martin */
+    { OBJ_HUMAN,  PALETTE_MODERN, { -3.0f, 1, -6 }, HUMAN_SIZE, 76, { 0, 150, 0 } }, /* Jonas */
 
     /* Props (the ground itself is the terrain, see terrain.c) */
     { OBJ_BOX_STATIC, PALETTE_WARM, { -8, 0, 1 }, { 1, 1, 1 }, 20, NO_ROTATION },
@@ -69,53 +71,87 @@ static const Spawn SCENE[] = {
     { OBJ_LOG, PALETTE_WARM, { 5.8f, -0.25f, 0.2f }, { 0.36f, 1.4f, 0.0f }, 0, { 90, 20, 0 } },
 };
 
-/* How each person carries themselves, in spawn order: you, then the others. */
+/* Who's here, in spawn order: you; Volkram, Jana and Toni (your friends, here
+   with you); Kurti (an old man, the house is his) and his sons Martin (about
+   30) and Jonas (about 25). */
 static const Mood TEMPERAMENTS[] = {
     { .pride =  0.0f, .energy =  0.2f, .nerves = 0.1f },  /* you: relaxed */
-    { .pride =  0.8f, .energy =  0.3f, .nerves = 0.0f },  /* proud, chest out */
-    { .pride = -0.8f, .energy = -0.1f, .nerves = 0.6f },  /* shy, a bit nervous */
-    { .pride =  0.2f, .energy =  0.8f, .nerves = 0.3f },  /* lively */
-    { .pride = -0.2f, .energy = -0.7f, .nerves = 0.0f },  /* tired */
+    { .pride =  0.5f, .energy =  0.1f, .nerves = 0.0f },  /* Volkram: calm, sure of himself */
+    { .pride =  0.1f, .energy =  0.7f, .nerves = 0.3f },  /* Jana: lively */
+    { .pride = -0.3f, .energy =  0.4f, .nerves = 0.6f },  /* Toni: restless, a bit unsure */
+    { .pride =  0.3f, .energy = -0.6f, .nerves = 0.0f },  /* Kurti: old and slow, nothing rattles him */
+    { .pride =  0.8f, .energy =  0.3f, .nerves = 0.1f },  /* Martin: proud, chest out */
+    { .pride = -0.1f, .energy =  0.6f, .nerves = 0.2f },  /* Jonas: keen */
 };
 /* Names and main hands, in spawn order. */
-static const char *const NAMES[] = { "You", "Jonas", "Ida", "Mila", "Ben" };
-static const int MAIN_HAND[] = { HAND_RIGHT, HAND_RIGHT, HAND_LEFT, HAND_RIGHT, HAND_RIGHT };
-/* Ida and Mila are women; Mila brought a Urinella (she pees standing). */
-static const bool FEMALE[]   = { false, false, true, true, false };
-static const bool URINELLA[] = { false, false, false, true, false };
-static const bool LIGHTER[]  = { true, true, false, false, true }; /* (a lighter in the pocket: You, Jonas, Ben) */
-/* Smoking: how much (0 = not at all), a pack, tobacco to roll (Mila smokes now and then, and bums them). */
-static const float SMOKER[]     = { 0.3f, 0.6f, 0.0f, 0.25f, 0.9f };
-static const float WITS[]       = { 0.6f, 0.45f, 0.8f, 0.6f, 0.25f }; /* (Ben is slow on the uptake) */
-static const float HEIGHT_FEAR[] = { 0.2f, 0.1f, 0.85f, 0.15f, 0.5f }; /* (Ida won't go up a ladder) */
-static const float YESTERDAY[]   = { 0.15f, 0.2f, 0.1f, 0.25f, 0.55f }; /* worn from the days before (Ben: a long one) */
-static const int   CIGARETTES[] = { 10, 0, 0, 0, 6 };
-static const int   TOBACCO[]    = { 0, 12, 0, 0, 20 };
-static const int  GARMENT[]  = { GARMENT_TROUSERS, GARMENT_SHORTS, GARMENT_SKIRT, GARMENT_DRESS, GARMENT_TROUSERS };
-/* How sporty each is (0..1): not a matter of build (Ida is light but not sporty, Jonas is heavy and fit). */
-static const float FITNESS[] = { 0.5f, 0.75f, 0.35f, 0.85f, 0.15f };
+static const char *const NAMES[] = { "You", "Volkram", "Jana", "Toni", "Kurti", "Martin", "Jonas" };
+static const int MAIN_HAND[] = { HAND_RIGHT, HAND_RIGHT, HAND_LEFT, HAND_RIGHT, HAND_RIGHT, HAND_RIGHT, HAND_LEFT };
+/* Jana is a woman; she brought a Urinella (she pees standing). */
+static const bool FEMALE[]   = { false, false, true, false, false, false, false };
+static const bool URINELLA[] = { false, false, true, false, false, false, false };
+static const bool LIGHTER[]  = { true, true, false, true, true, true, false };
+/* Smoking: how much (0 = not at all), ready ones, tobacco to roll (most roll their own). */
+static const float SMOKER[]     = { 0.3f, 0.6f, 0.2f, 0.7f, 0.8f, 0.4f, 0.0f };
+static const float WITS[]       = { 0.6f, 0.7f, 0.65f, 0.5f, 0.75f, 0.55f, 0.45f };
+static const float HEIGHT_FEAR[] = { 0.2f, 0.15f, 0.5f, 0.1f, 0.7f, 0.05f, 0.2f }; /* (Kurti stays off ladders these days) */
+static const float YESTERDAY[]   = { 0.15f, 0.2f, 0.1f, 0.35f, 0.4f, 0.2f, 0.1f }; /* worn from the days before (you came a long way) */
+static const int   CIGARETTES[] = { 0, 0, 0, 0, 0, 12, 0 };      /* (Martin smokes a pack) */
+static const int   TOBACCO[]    = { POUCH_ROLLUPS, 40, 10, 25, 60, 0, 0 };
+static const int   PAPERS[]     = { PAPERS_PACK, 20, 5, 0, 30, 0, 0 };   /* (Toni forgot his) */
+static const int   FILTERS[]    = { FILTERS_BAG, 60, 0, 15, 0, 0, 0 };   /* (Jana is out) */
+static const bool  FILTER_HABIT[] = { true, true, true, true, false, false, false }; /* (Kurti: none, ever) */
+static const char *const KEYS_IN_POCKET[] = { "Car keys", NULL, NULL, NULL, "House keys", NULL, NULL };
+static const int  GARMENT[]  = { GARMENT_TROUSERS, GARMENT_TROUSERS, GARMENT_SKIRT, GARMENT_SHORTS, GARMENT_TROUSERS, GARMENT_TROUSERS, GARMENT_SHORTS };
+/* Years: past 45 slower on their feet (Kurti is 74). */
+static const int AGE[] = { 28, 31, 29, 26, 74, 30, 25 };
+/* How sporty each is (0..1): not a matter of build. */
+static const float FITNESS[] = { 0.5f, 0.55f, 0.7f, 0.8f, 0.12f, 0.6f, 0.85f };
 /* How well each holds their drink (on top of body weight). */
-static const float TOLERANCE[] = { 1.0f, 1.3f, 0.7f, 1.1f, 0.9f };
+static const float TOLERANCE[] = { 1.0f, 1.2f, 0.8f, 0.9f, 1.4f, 1.3f, 1.0f };
 #define TEMPERAMENT_COUNT ((int)(sizeof(TEMPERAMENTS) / sizeof(TEMPERAMENTS[0])))
 
+/* How they look (skin, hair, shirt, trousers, shoes, height, hair style, long sleeves). */
+static const FigureLook LOOKS[] = {
+    { .skin = { 0.93f, 0.74f, 0.60f }, .hair = { 0.30f, 0.19f, 0.12f }, .shirt = { 0.86f, 0.42f, 0.32f },
+      .pants = { 0.24f, 0.30f, 0.48f }, .shoes = { 0.22f, 0.16f, 0.13f }, .height = 1.78f, .hair_style = HAIR_SHORT, .long_sleeves = false }, /* you */
+    { .skin = { 0.90f, 0.70f, 0.56f }, .hair = { 0.42f, 0.30f, 0.18f }, .shirt = { 0.36f, 0.46f, 0.36f },
+      .pants = { 0.30f, 0.27f, 0.24f }, .shoes = { 0.25f, 0.18f, 0.12f }, .height = 1.86f, .hair_style = HAIR_LONG, .long_sleeves = true }, /* Volkram: tall, long hair */
+    { .skin = { 0.96f, 0.80f, 0.68f }, .hair = { 0.70f, 0.36f, 0.20f }, .shirt = { 0.92f, 0.78f, 0.36f },
+      .pants = { 0.42f, 0.30f, 0.46f }, .shoes = { 0.85f, 0.82f, 0.74f }, .height = 1.66f, .hair_style = HAIR_LONG, .long_sleeves = false }, /* Jana */
+    { .skin = { 0.72f, 0.52f, 0.38f }, .hair = { 0.12f, 0.10f, 0.10f }, .shirt = { 0.30f, 0.62f, 0.60f },
+      .pants = { 0.55f, 0.48f, 0.36f }, .shoes = { 0.32f, 0.22f, 0.16f }, .height = 1.74f, .hair_style = HAIR_SHORT, .long_sleeves = false }, /* Toni */
+    { .skin = { 0.90f, 0.72f, 0.62f }, .hair = { 0.82f, 0.82f, 0.80f }, .shirt = { 0.55f, 0.50f, 0.42f },
+      .pants = { 0.33f, 0.36f, 0.30f }, .shoes = { 0.20f, 0.16f, 0.12f }, .height = 1.70f, .hair_style = HAIR_SHORT, .long_sleeves = true }, /* Kurti: grey, a worn shirt */
+    { .skin = { 0.88f, 0.66f, 0.50f }, .hair = { 0.30f, 0.22f, 0.16f }, .shirt = { 0.20f, 0.24f, 0.40f },
+      .pants = { 0.24f, 0.26f, 0.30f }, .shoes = { 0.18f, 0.14f, 0.12f }, .height = 1.84f, .hair_style = HAIR_SHORT, .long_sleeves = false }, /* Martin: broad, work clothes */
+    { .skin = { 0.90f, 0.70f, 0.55f }, .hair = { 0.36f, 0.26f, 0.18f }, .shirt = { 0.80f, 0.20f, 0.18f },
+      .pants = { 0.50f, 0.46f, 0.38f }, .shoes = { 0.60f, 0.24f, 0.20f }, .height = 1.80f, .hair_style = HAIR_SHORT, .long_sleeves = false }, /* Jonas */
+};
+
 /* Who they are: likes (firewood, trees, trash, fire), how willing, skills, a few words;
    tempo, thinking it over, breaks. */
 #define SKILLS(wood, trees, trash, fire) \
     { [ORDER_FIREWOOD] = wood, [ORDER_TREES] = trees, [ORDER_TRASH] = trash, [ORDER_FIRE] = fire }
 static const Character CHARACTERS[] = {
     { { 0 }, 1.0f, SKILLS(0.3f, 0.3f, 0.3f, 0.3f), NULL, 1.0f, 0.0f, BREAK_NONE, 0.0f, 0.0f }, /* you */
-    { SKILLS(0.5f, 0.9f, -0.8f, 0.2f), 0.45f, SKILLS(0.7f, 0.8f, 0.2f, 0.4f),
-      "Jonas. Grew up on a farm. Give me a chainsaw, not a broom.",
-      1.25f, 0.2f, BREAK_NONE, 0.0f, 0.0f },      /* quick, gets right to it, no breaks */
-    { SKILLS(-0.2f, -0.9f, 0.8f, 0.3f), 0.9f, SKILLS(0.2f, 0.1f, 0.6f, 0.3f),
-      "Ida. I like things tidy. Chainsaws scare me, though.",
-      0.95f, 1.6f, BREAK_WALK, 100.0f, 35.0f },   /* careful: thinks first; a little walk now and then */
-    { SKILLS(0.6f, 0.2f, 0.0f, 0.9f), 0.7f, SKILLS(0.5f, 0.3f, 0.3f, 0.7f),
-      "Mila! I live for campfire nights. And a good axe.",
-      1.1f, 0.6f, BREAK_CHAT, 70.0f, 30.0f },     /* lively: a chat in between */
-    { SKILLS(0.1f, -0.4f, 0.3f, 0.6f), 0.35f, SKILLS(0.3f, 0.2f, 0.3f, 0.5f),
-      "Ben. Here to relax, mostly. I don't mind sitting by a fire.",
-      0.75f, 2.4f, BREAK_SIT, 50.0f, 40.0f },     /* slow, ponders, sits down a lot */
+    { SKILLS(0.8f, 0.3f, 0.0f, 0.6f), 0.7f, SKILLS(0.6f, 0.4f, 0.3f, 0.6f),
+      "Volkram. Jana and I share a tent. Splitting wood clears my head.",
+      1.0f, 0.8f, BREAK_COFFEE, 90.0f, 30.0f },   /* steady; a coffee now and then */
+    { SKILLS(0.0f, -0.5f, 0.6f, 0.9f), 0.8f, SKILLS(0.3f, 0.2f, 0.5f, 0.6f),
+      "Jana! I'm here for the fire and the people. And I like it tidy.",
+      1.1f, 0.5f, BREAK_CHAT, 70.0f, 30.0f },     /* lively: a chat in between */
+    { SKILLS(0.4f, 0.6f, -0.4f, 0.3f), 0.5f, SKILLS(0.4f, 0.4f, 0.2f, 0.3f),
+      "Toni. First time out here. Show me how and I'll do it.",
+      1.2f, 0.3f, BREAK_WALK, 80.0f, 25.0f },     /* quick, a bit careless; a little walk */
+    { SKILLS(0.6f, 0.7f, 0.2f, 0.8f), 0.4f, SKILLS(0.8f, 0.9f, 0.4f, 0.8f),
+      "Kurti. This is my house - you're welcome. I've felled more trees than you've seen.",
+      0.7f, 2.2f, BREAK_SIT, 45.0f, 50.0f },      /* old: slow, thinks it over, sits down a lot */
+    { SKILLS(0.4f, 0.9f, -0.6f, 0.3f), 0.5f, SKILLS(0.7f, 0.8f, 0.2f, 0.4f),
+      "Martin. Kurti's eldest. Give me a chainsaw, not a broom.",
+      1.2f, 0.2f, BREAK_NONE, 0.0f, 0.0f },       /* gets right to it, no breaks */
+    { SKILLS(0.6f, 0.4f, 0.3f, 0.5f), 0.85f, SKILLS(0.4f, 0.4f, 0.4f, 0.4f),
+      "Jonas. The younger one. Dad says I talk too much.",
+      1.15f, 0.4f, BREAK_CHAT, 80.0f, 30.0f },    /* keen, helpful, a chat in between */
 };
 
 /* A ramp to walk up: a plank 20 degrees up, its foot on the ground, its top
@@ -186,13 +222,18 @@ Object *Scene_load(World *world) {
             obj->tolerance   = TOLERANCE[characters % TEMPERAMENT_COUNT];
             obj->fitness     = FITNESS[characters % TEMPERAMENT_COUNT];
             obj->look = Figure_wardrobe(characters);
+            if (characters < (int)(sizeof(LOOKS) / sizeof(LOOKS[0]))) { /* (their own clothes; extra people: the wardrobe) */
+                Vec3 jacket = obj->look.jacket;
+                obj->look = LOOKS[characters];
+                obj->look.jacket = jacket;
+            }
             obj->look.garment = GARMENT[characters % TEMPERAMENT_COUNT];
             Object_setPerson(obj, NAMES[characters % TEMPERAMENT_COUNT], MAIN_HAND[characters % TEMPERAMENT_COUNT]);
             obj->ai.character = CHARACTERS[characters % TEMPERAMENT_COUNT];
             obj->female   = FEMALE[characters % TEMPERAMENT_COUNT];
             obj->urinella = URINELLA[characters % TEMPERAMENT_COUNT];
             obj->urinellas = obj->urinella ? POCKET_URINELLAS - 1 : 0;
-            obj->urinella_tried = obj->urinella ? 1 : 0; /* (Mila knows she likes them) */
+            obj->urinella_tried = obj->urinella ? 1 : 0; /* (Jana knows she likes them) */
             obj->lighter = LIGHTER[characters % TEMPERAMENT_COUNT];
             obj->lighter_uses = obj->lighter ? LIGHTER_USES * (1 + characters % 3) / 3 : 0; /* (some nearly empty) */
             obj->smoker     = SMOKER[characters % TEMPERAMENT_COUNT];
@@ -202,9 +243,17 @@ Object *Scene_load(World *world) {
             obj->jacket_hung = true; /* (on the rack in your garage) */
             obj->cigarettes = CIGARETTES[characters % TEMPERAMENT_COUNT];
             obj->tobacco    = TOBACCO[characters % TEMPERAMENT_COUNT];
+            obj->papers     = PAPERS[characters % TEMPERAMENT_COUNT];
+            obj->filters    = FILTERS[characters % TEMPERAMENT_COUNT];
+            obj->filter_habit = FILTER_HABIT[characters % TEMPERAMENT_COUNT];
+            obj->keys       = KEYS_IN_POCKET[characters % TEMPERAMENT_COUNT];
+            obj->work_trousers = characters == 0; /* (you: work trousers, plenty of pockets) */
             obj->craving    = 0.3f + 0.15f * (float)(characters % 3);
             for (int k = 0; k < ORDER_COUNT; k++) obj->skill[k] = obj->ai.character.skill[k];
             obj->tempo    = obj->ai.character.tempo;
+            float years = clampf(1.0f - (float)(AGE[characters % TEMPERAMENT_COUNT] - 45) / 60.0f, 0.5f, 1.0f); /* (old legs) */
+            obj->top_speed  *= years;
+            obj->jump_speed *= years;
             characters++;
         }