Entropie git · main
Entropie festival game; first steps
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.gzentropie 2.1.0: new people, rolling kit, your pockets
- 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++; }