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 1.78.0: jackets at night, your shirt off, trying a Urinella
- Cold nights: away from the fire a jacket goes on (each their own colour) - Working hard in the midday sun your shirt comes off too - Women who never tried a Urinella may try one: some like it, some don't - Dying for a smoke makes restless; a wobbling ladder rocks - Moving heavy wood: tested with a called helper; the player is asked when nobody else is around Includes 1.77.0 (if not committed separately): - Ladders: carried nearly upright when out; stood up on their own they fall over; leaned, how steady they stand depends on angle, ground, feet and what holds them; a wobbly one wobbles, then slips and falls with you - Somebody holds it (hands on the rails, a foot on the bottom rung): helpers (G) when you climb, helpful people when it looks wobbly - The work place: the tidy clear away what lies where they'd stand; no room to swing, they take the piece somewhere with room (carried, or dragged with somebody called to help) - The sporty pull up and climb ladders much quicker Includes 1.76.0 (if not committed separately): - Smoking: packs and tobacco in the pockets (used up); smokers get a craving and light up in a quiet moment - rolling one first, the lighter to it, drags (the tip glows), smoke breathed out, the butt flicked away - Shared: "Got a light?", bumming a cigarette (or a no); talk to ask for one or offer one; K lights yours / puts it out; Tab shows your pockets - Shadows: soft, smooth edges instead of stair steps Includes 1.75.0 (if not committed separately): - Urinellas: one-time use, up to 4 in the pocket; a box by the toilet refills it (the others fetch more when they run out, you click it) - Peeing: what's in the hands is put down first (the others take it up again); standing, the fly is open or the skirt or dress lifted - Lighter: needed to light the fire, runs empty; borrowed when there's none, given back (or not), owners ask for it; talk: borrow, give back, ask for yours back - Working hard in the midday sun the shirt comes off (women keep a top) - Dropping from high up: knees pulled up, legs reach for the ground, deep into the knees, a roll or a crash - Ladders: one-handed climbing (you may slip), a lying ladder is stepped over, kicked it hardly moves and your toe hurts - Dev menu: teleport to the parkour / yard; the player spawns 1 m up Includes 1.74.0 (if not committed separately): - Fix: legs showed through a skirt or dress standing normally; the thighs under it are cloth now and it's wider over the hips - Mila's Urinellas come in a pack of 3 (then she squats); now and then a mishap (on the shoes); used ones go into the bag, are dropped or flung away (drunk) as trash Includes 1.73.0 (if not committed separately): - Clothes: trousers, shorts, skirt, dress; squatting to pee trousers come down, a skirt or dress is gathered up Includes 1.72.0 (if not committed separately): - Fix: walking off the other way from something half stepped over left one foot planted behind, sliding along Includes 1.71.0 (if not committed separately): - Two-handed fidgets: rubbing the hands, hands behind the head, both hands on the hips Includes 1.70.0 (if not committed separately): - More one-hand gestures (neck, nose, ear, hair, thumb in the belt, hand on the chest, shading the eyes); mood and personal habits pick them Includes 1.69.0 (if not committed separately): - Faster with a crowd: 750 people 22 -> 14 ms a frame (kind index, cell grid for company, no foot checks far from you) Includes 1.68.0 (if not committed separately): - The walking map is updated in place when fixed things change (0.3 ms instead of 10 ms) Includes 1.67.0 (if not committed separately): - Pour your drink on the fire (C): a small fire goes out, a big one hisses, vodka flares up Includes 1.66.0 (if not committed separately): - Careful people sort the woodpile by length (front even) and put the branches lying about onto a heap at the end of the woodpile Includes 1.65.0 (if not committed separately): - Smooth off the ladder (a step back, one foot after the other) and taking it down (the hand reaches for the rail at chest height) Includes 1.64.0 (if not committed separately): - Smooth ladder and climbs: getting on, over the edge at the top, feet from where they are; the easy 1 m climb no longer drops you off again Includes 1.63.0 (if not committed separately): - The hose melts in the fire (leaks, burns through); duct tape and the shears mend it Includes 1.62.0 (if not committed separately): - Smooth pull-ups: no hop at the end, feet and knees ease over the edge, elbows and hands taking hold blend in (audit walk: joint jumps 161 -> 58) Includes 1.61.0 (if not committed separately): - A step ladder in the tool room: stands on its own, at a wall with its back to it; climb onto the wall from its top Includes 1.60.0 (if not committed separately): - A folding ladder (two 2.5 m halves on a hinge, 5 m open) beside the telescopic one; one click at a wall unfolds it and leans it there Includes 1.59.0 (if not committed separately): - Stepping up onto blocks above knee high; set moves and jumps only for things standing on the ground in front of you Includes 1.58.0 (if not committed separately): - Walking over knee-high things is one smooth set move that always finishes; at a run: jump up onto blocks, catch climbable edges
entropie/Makefile | 2 +- entropie/README.md | 10 +++++++--- entropie/STATE.md | 18 +++++++++++------- entropie/assets/shaders/figure.frag | 31 +++++++++++++++++++++---------- entropie/commit_message.txt | 10 +++++++++- entropie/src/game/game.c | 19 +++++++++++++++++++ entropie/src/game/game_actions.c | 1 + entropie/src/people/ai.c | 10 +++++++--- entropie/src/people/ai_work.c | 5 ++++- entropie/src/people/condition.c | 3 +++ entropie/src/render/figure.c | 8 +++++++- entropie/src/render/figure.h | 5 +++-- entropie/src/render/renderer.c | 2 ++ entropie/src/render/renderer.h | 2 +- entropie/src/world/ladder.c | 8 ++++++++ entropie/src/world/object.h | 3 +++ entropie/src/world/scene.c | 1 + entropie/src/world/world.c | 29 +++++++++++++++++++++++++++++ entropie/src/world/world.h | 1 + 19 files changed, 138 insertions(+), 30 deletions(-) diff --git a/entropie/Makefile b/entropie/Makefile index 191e610..3c5b0a3 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 1.77.0 +VERSION := 1.78.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/README.md b/entropie/README.md index bfff08a..6e90bf0 100644 --- a/entropie/README.md +++ b/entropie/README.md @@ -354,7 +354,7 @@ A hand that can't reach its grip lets go and takes hold again. - It leans on whatever is there. How steady it stands depends on the angle (about 75 degrees is best), soft or muddy ground, its feet not level and what holds it (a bush, something light). The message says when it's wobbly -- An unsteady ladder may slip when you climb it: a wobble first, then it +- An unsteady ladder may slip when you climb it: a wobble first (it rocks), then it falls over and you with it (the higher, the harder you land) - Somebody holding it (hands on the rails, a foot on the bottom rung) keeps it steady: helpers (G) hold it when you climb, helpful people come over @@ -425,14 +425,16 @@ say no more often and don't come to you for a bit. - 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 - Working hard in the midday sun the shirt comes off (women keep a top on; - the shy keep it all on); it goes back on when they've cooled down + the shy keep it all on), yours too; it goes back on when cooled down +- Cold nights: away from the fire a jacket goes on (each their own colour), + by the fire or in the day it comes off - Ask them what they're doing, send them back to helping you, or give them a break ## 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 -- Smokers get a craving over time. In a quiet moment (idle, a break, by +- 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 - A drag now and then (the tip glows), smoke breathed out, the hand up in @@ -459,6 +461,8 @@ say no more often and don't come to you for a bit. in the pocket. Out of them, they fetch more from the box by the toilet (you too: click it). Squatting, trousers come down to the knees, a skirt or dress is gathered up; standing, the fly is open or the skirt lifted +- Women who never tried a Urinella may try one from the box: some like it, + some say "not for me" and squat again - Before peeing, what's in the hands is put down beside you (the others take it up again after) A used Urinella goes into the bag (tidy), is dropped (careless) or flung diff --git a/entropie/STATE.md b/entropie/STATE.md index 0ecdaa4..4530ef6 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -66,6 +66,10 @@ Smoke puffs (`chips.c` Chips_puff): small blocks drifting up, growing, drawn wit Shadows: 3x3 PCF with each tap blended bilinearly between its 4 texels (`common.glsl` shadowVisibility, 16 lookups): no stair steps at the edges. +Night clothes (`world.c` dressForNight): `chill` 0..1 grows 1/60 s at night (21:00-7:00) away from a burning fire (drunk: slower), falls by a fire (1/60 s) or in the day (2/60 s); over 0.6 the jacket (`look.top` TOP_JACKET, colour per person: `Figure_wardrobe`) goes on, under 0.15 off. Drawn a bit bulkier, long sleeves, a zip and a darker collar. The sun logic only takes off / puts on the shirt, never the jacket. You too (`game.c` sunShirt: working = a stroke in the last 3 s). + +Urinella, trying: a woman who never had one (`urinella_tried` 0), bladder 0.35-0.65, chance 0.1 + 0.2 energy per think: fetches some from the box (30 m). The first one used decides: like = 0.6 - 0.3 shyness - 0.2 (pride over 0.5) + 0.2 energy; not liked: back to squatting ("not for me"). Craving: dying for a smoke (0.8-1) adds up to 0.5 x smoker to the nerves shown. A wobble rocks the ladder (`shake`, 1.2 s). A heavy piece to drag and nobody to call: the player is asked ("grab it with F"). + Shirt off (`ai_work.c` ai_sunShirt): `sun_heat` grows 11:00-17:30 while felling or making firewood (or helping with a tool), faster when tired; at 90 s the shirt comes off (`look.top`: TOP_BARE men, TOP_CROP women; not the shy, pride under -0.3), under 40 it goes back on (cools fast after 17:30). Policy "no shirt no problem" for now; maybe "no shirt no service" later. Getting about: straight at it, round things in the way (local look-ahead); leads into a corner: a way round from the walking map (`src/world/nav.c`, 0.4 m grid of fixed things, A*). When fixed things change (cheap signature over all of them) only the cells round what moved, turned, came or went are done again (a table of each fixed thing as last built, by pointer; more than 32 changes at once: the whole map): a felled tree 0.3 ms instead of 10 ms, the same cells as a whole rebuild. They remember 4 things they couldn't get at. @@ -179,13 +183,13 @@ Most important first. - More gestures and feelings to pick personalities from: the lists in Body / Fidgets and Gestures can grow **People** -- The work place, still to do: dragging a heavy piece with a called helper is untested in play; other jobs than split / cut (felling room, trash); the player asked to help move it -- Ladders, still to do: the ladder visibly shaking on a wobble; a holder for a ladder standing free; NPCs climbing -- Smoking, still to do: buying more (a kiosk), the mood (nerves) from a craving, an ashtray -- Peeing, still to do: some women don't like the Urinella; a Urinella in the hand (not drawn); the player-woman picks up her things again herself -- Shirt off for you (now only the others); "no shirt no service" policy (decision open) +- The work place, still to do: other jobs than split / cut (felling room, trash); walking a heavy piece round tight corners +- Ladders, still to do: a holder for a ladder standing free; NPCs climbing +- Smoking, still to do: buying more (a kiosk), an ashtray +- Peeing, still to do: a Urinella in the hand (not drawn); the player-woman picks up her things again herself +- "No shirt no service" policy (decision open) - NPCs on ladders (only you climb them) -- Clothing, still to do: more outfits for extra people (they all wear trousers), jackets at night +- Clothing, still to do: more outfits for extra people (they all wear trousers); fetching the jacket from somewhere **Tools for us** - Optional local AI (llama.cpp, small model) for talking and tasks; the menu stays as fallback @@ -194,4 +198,4 @@ Most important first. - Drawing many figures on a real graphics card: not measured (software GL: 300 people cost 3x the rest of the scene) --- -State of `entropie 1.77.0 (build 208a5af)` +State of `entropie 1.78.0 (build 9a82b8f)` diff --git a/entropie/assets/shaders/figure.frag b/entropie/assets/shaders/figure.frag index 3e46783..dd702d8 100644 --- a/entropie/assets/shaders/figure.frag +++ b/entropie/assets/shaders/figure.frag @@ -32,6 +32,7 @@ uniform vec2 expression; // smile (-1 .. 1), gaze (-1 down .. 1 up) uniform vec3 skinColor; uniform vec3 hairColor; uniform vec3 shirtColor; +uniform vec3 jacketColor; // (top == 3: a jacket over it) uniform vec3 pantsColor; uniform vec3 shoeColor; uniform vec3 stream[4]; // pee or sick: an arc from the body to the ground (figure space) @@ -41,7 +42,7 @@ uniform int garment; // 0 trousers, 1 shorts, 2 skirt, 3 dress uniform float undress; // to pee: squatting trousers down / skirt up (0..1); standing fly open / skirt lifted in front (-1..0) uniform vec3 cigA, cigTip; // a cigarette (figure space): the end at the hand / lips, the burning end uniform float cigGlow; // -1 none, 0 not lit .. 1 a drag -uniform int top; // 0 a shirt, 1 bare-chested, 2 just a top (shirt off in the sun) +uniform int top; // 0 a shirt, 1 bare-chested, 2 just a top (shirt off in the sun), 3 a jacket (cold night) uniform mat4 viewProj; uniform mat4 view; @@ -56,7 +57,7 @@ uniform float shadowStrength; uniform float boilTime; uniform sampler2D shadowMap; -const int M_SKIN = 0, M_HAIR = 1, M_SHIRT = 2, M_PANTS = 3, M_SHOES = 4, M_PEE = 5, M_SICK = 6, M_WATER = 7, M_CIG = 8; +const int M_SKIN = 0, M_HAIR = 1, M_SHIRT = 2, M_PANTS = 3, M_SHOES = 4, M_PEE = 5, M_SICK = 6, M_WATER = 7, M_CIG = 8, M_JACKET = 9; const float FIGURE_LIGHT_WRAP = 0.6; // soft, flat faces instead of a hard light split const float SELF_SHADOW_SKIP = 0.10; // meters @@ -90,13 +91,15 @@ void addPart(vec3 a, vec3 b, float r1, float r2, float blend, float depth, int m void buildBody() { vec3 pelvis = joints[J_PELVIS], chest = joints[J_CHEST], neck = joints[J_NECK]; vec3 waist = mix(pelvis, chest, 0.45) + waistBend; - int forearm = longSleeves && top == 0 ? M_SHIRT : M_SKIN; - int sleeve = top == 0 ? M_SHIRT : M_SKIN; + bool jacket = top == 3; + int forearm = jacket ? M_JACKET : longSleeves && top == 0 ? M_SHIRT : M_SKIN; + int sleeve = jacket ? M_JACKET : top == 0 ? M_SHIRT : M_SKIN; + float puff = jacket ? 0.008 : 0.0; // (a jacket is a bit bulkier) - // Torso: belly into chest, shoulders across (the shirt off: skin, or just a top) - addPart(pelvis + spineDir * 0.05, waist, 0.130, 0.135, 0.04, 0.70, sleeve); - addPart(waist, chest + spineDir * 0.03 - chestForward * 0.005, 0.135, 0.160, 0.04, 0.66, top == 1 ? M_SKIN : M_SHIRT); - addPart(joints[J_SHOULDER_L], joints[J_SHOULDER_R], 0.050, 0.050, 0.05, 0.75, sleeve); + // Torso: belly into chest, shoulders across (the shirt off: skin, or just a top; a jacket over it) + addPart(pelvis + spineDir * (jacket ? 0.0 : 0.05), waist, 0.130 + puff, 0.135 + puff, 0.04, 0.70, sleeve); + addPart(waist, chest + spineDir * 0.03 - chestForward * 0.005, 0.135 + puff, 0.160 + puff, 0.04, 0.66, jacket ? M_JACKET : top == 1 ? M_SKIN : M_SHIRT); + addPart(joints[J_SHOULDER_L], joints[J_SHOULDER_R], 0.050 + puff, 0.050 + puff, 0.05, 0.75, sleeve); // What's on the legs: trousers, shorts, a skirt or a dress (squatting to pee: // trousers pulled down to the knees, a skirt or dress gathered up) @@ -136,8 +139,8 @@ void buildBody() { // Arms and hands for (int s = 0; s < 2; s++) { int o = s * 4; - addPart(joints[J_SHOULDER_L + o], joints[J_ELBOW_L + o], 0.055, 0.045, 0.035, 1.0, sleeve); - addPart(joints[J_ELBOW_L + o], joints[J_WRIST_L + o], 0.044, 0.034, 0.02, 1.0, forearm); + addPart(joints[J_SHOULDER_L + o], joints[J_ELBOW_L + o], 0.055 + puff, 0.045 + puff, 0.035, 1.0, sleeve); + addPart(joints[J_ELBOW_L + o], joints[J_WRIST_L + o], 0.044 + puff, 0.034 + puff * 0.5, 0.02, 1.0, forearm); addPart(joints[J_WRIST_L + o], joints[J_HAND_L + o], 0.033, 0.036, 0.015, 1.0, M_SKIN); } @@ -309,6 +312,7 @@ vec3 materialColor(int mat) { if (mat == M_SICK) return vec3(0.70, 0.66, 0.30); if (mat == M_WATER) return vec3(0.78, 0.90, 0.96); if (mat == M_CIG) return vec3(0.96, 0.95, 0.92); + if (mat == M_JACKET) return jacketColor; return skinColor; } @@ -385,6 +389,13 @@ void main() { if (mat == M_SKIN) base = faceDetail(base, p); else if (mat == M_HAIR) base = hairDetail(base, p); else if (mat < M_PEE) base = clothDetail(mat, base, p); + if (mat == M_JACKET) { // the zip down the front, a darker collar + vec3 rel = p - joints[J_CHEST]; + vec3 side = cross(spineDir, chestForward); + float along = dot(rel, spineDir); + if (dot(rel, chestForward) > 0.0 && abs(dot(rel, side)) < 0.006) base *= 0.6; + if (along > 0.04) base *= 0.85; + } float ember = 0.0; if (mat == M_CIG) { // filter at the hand end, ash and ember at the other float along = dot(p - cigA, normalize(cigTip - cigA)), left = length(cigTip - cigA) - along; diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt index a66b65d..d8e8141 100644 --- a/entropie/commit_message.txt +++ b/entropie/commit_message.txt @@ -1,5 +1,13 @@ -entropie 1.77.0: steady or wobbly ladders, holding them, the work place +entropie 1.78.0: jackets at night, your shirt off, trying a Urinella +- Cold nights: away from the fire a jacket goes on (each their own colour) +- Working hard in the midday sun your shirt comes off too +- Women who never tried a Urinella may try one: some like it, some don't +- Dying for a smoke makes restless; a wobbling ladder rocks +- Moving heavy wood: tested with a called helper; the player is asked when + nobody else is around + +Includes 1.77.0 (if not committed separately): - Ladders: carried nearly upright when out; stood up on their own they fall over; leaned, how steady they stand depends on angle, ground, feet and what holds them; a wobbly one wobbles, then slips and falls with you diff --git a/entropie/src/game/game.c b/entropie/src/game/game.c index 5fcf13a..d330724 100644 --- a/entropie/src/game/game.c +++ b/entropie/src/game/game.c @@ -112,6 +112,23 @@ void Game_needsText(const Game *game, char *out, size_t size) { if (words[i] && k >= 0 && (size_t)k < size) k += snprintf(out + k, size - (size_t)k, "%s%s", k ? " " : "", words[i]); } +/* You, working hard in the midday sun: off with the shirt after a while (a top + if you're a woman), on again when you've cooled down (as the others do). */ +static void sunShirt(Game *game, float dt) { + Object *p = game->player; + float hour = game->daycycle.seconds / 3600.0f; + bool sunny = hour >= 11.0f && hour < 17.5f, working = p->work_state.since < 3.0f; + float warm = sunny && working ? 1.0f + 0.5f * (1.0f - p->needs.stamina) : sunny ? -0.5f : -3.0f; + p->ai.sun_heat = clampf(p->ai.sun_heat + warm * dt, 0.0f, 135.0f); + if (p->look.top == TOP_SHIRT && p->ai.sun_heat >= 90.0f) { + p->look.top = p->female ? TOP_CROP : TOP_BARE; + say(game, "Hot work - you take your shirt off"); + } else if ((p->look.top == TOP_BARE || p->look.top == TOP_CROP) && p->ai.sun_heat < 40.0f) { + p->look.top = TOP_SHIRT; + say(game, "You put your shirt back on"); + } +} + int Game_stats(const Game *game, StatBar *bars, int max, const char **title, char *about, size_t size) { const Object *p = game->player; const Needs *n = &p->needs; @@ -246,6 +263,8 @@ void Game_update(Game *game, const Input *input, float dt, float aspect) { say(game, "Pssssshhh... the fire is out"); } Ai_setHour(game->daycycle.seconds / 3600.0f); + game->world.hour = game->daycycle.seconds / 3600.0f; + sunShirt(game, dt); if (game->player->on_fire > 0.0f && (game->fire_nag -= dt) <= 0.0f) { say(game, "You're on fire!"); game->fire_nag = 4.0f; diff --git a/entropie/src/game/game_actions.c b/entropie/src/game/game_actions.c index 74e9c9e..0dec5fa 100644 --- a/entropie/src/game/game_actions.c +++ b/entropie/src/game/game_actions.c @@ -384,6 +384,7 @@ static bool ladderSlips(Game *game, Object *p, Object *l, bool moving, float dt) if ((float)rand() / (float)RAND_MAX >= risk * dt) return false; if (l->ladder_wobble <= 0.0f) { l->ladder_wobble = 30.0f; /* (the next one is no wobble) */ + l->shake = 1.2f; say(game, "The ladder wobbles under you! (somebody to hold it: G)"); return false; } diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c index 62b1d14..6b26780 100644 --- a/entropie/src/people/ai.c +++ b/entropie/src/people/ai.c @@ -403,9 +403,12 @@ static void updateIdle(Object *self, Object *const *objects, int count, float dt } if (hands_free && n->thirst > 0.5f && seekNeed(self, objects, count, WANT_DRINK)) return; if (hands_free && n->hunger > 0.5f && seekNeed(self, objects, count, WANT_FOOD)) return; - if (self->urinella && self->urinellas == 0 && n->bladder < 0.6f) { /* out of Urinellas: more from the box */ + bool curious = self->female && !self->urinella && self->urinella_tried == 0 && n->bladder > 0.35f && n->bladder < 0.65f && + random01(ai) < 0.1f + 0.2f * fmaxf(self->temperament.energy, 0.0f); /* (never tried one: why not) */ + if ((self->urinella && self->urinellas == 0 && n->bladder < 0.6f) || curious) { /* out of Urinellas (or curious): the box */ Object *box = ai_nearestOf(self, objects, count, OBJ_SUPPLY, OBJ_URINELLA); - if (box && flatDistance(box->position, self->position) < 80.0f) { + if (box && flatDistance(box->position, self->position) < (curious ? 30.0f : 80.0f)) { + if (curious) Ai_say(self, "Maybe I'll try one of those Urinellas."); ai->mode = AI_SUPPLY; ai->target = box; ai->timer = 0.0f; @@ -566,7 +569,8 @@ static void updateSupply(Object *self, Object *const *objects, int count, float Hands_touch(self, Object_colliderCenter(supply, 0)); if ((ai->timer -= dt) > 0.0f) return; Pockets_refill(self); - Ai_say(self, "Some Urinellas for later."); + Ai_say(self, self->urinella ? "Some Urinellas for later." : "Let's see how these work."); + self->urinella = true; /* (to try, or more of them) */ ai->target = NULL; rest(ai, 1.0f, 2.0f); return; diff --git a/entropie/src/people/ai_work.c b/entropie/src/people/ai_work.c index 8074c58..5450d67 100644 --- a/entropie/src/people/ai_work.c +++ b/entropie/src/people/ai_work.c @@ -458,7 +458,7 @@ void ai_sunShirt(Object *self, float dt) { if (self->look.top == TOP_SHIRT && ai->sun_heat >= SUN_SHIRT_OFF && self->temperament.pride > -0.3f) { self->look.top = self->female ? TOP_CROP : TOP_BARE; if (ai->speech_time <= 0.0f) Ai_say(self, "Phew, it's hot. Off with the shirt."); - } else if (self->look.top != TOP_SHIRT && ai->sun_heat < SUN_SHIRT_ON) { + } else if ((self->look.top == TOP_BARE || self->look.top == TOP_CROP) && ai->sun_heat < SUN_SHIRT_ON) { self->look.top = TOP_SHIRT; } } @@ -1090,6 +1090,9 @@ static bool prepareSpot(Object *self, Object *const *objects, int count, const W if (Hands_pickUpObject(self, HAND_RIGHT, piece) || Hands_pickUpObject(self, HAND_LEFT, piece)) return true; if (Hands_grabObject(self, piece)) { /* too big to lift: drag it, and call somebody to help */ Object *helper = piece->weight > 30.0f ? helperFor(self, objects, count) : NULL; + Object *player = piece->weight > 30.0f && !helper ? findPlayer(objects, count) : NULL; + if (player && flatDistance(player->position, self->position) < PREP_HELP) + Ai_say(self, "Can you give me a hand moving this? (grab it with F)"); if (helper) { static char line[AI_SPEECH_MAX]; snprintf(line, sizeof line, "%s, give me a hand moving this?", helper->name); diff --git a/entropie/src/people/condition.c b/entropie/src/people/condition.c index 2ff36b9..734dec8 100644 --- a/entropie/src/people/condition.c +++ b/entropie/src/people/condition.c @@ -155,6 +155,9 @@ static void showMood(Object *self, Object *const *objects, int count) { if (self->temperament.pride > 0.0f) m.pride += 0.15f; m.nerves += 0.15f; } + /* Dying for a smoke: restless (nicotine) - a cigarette calms them down */ + if (self->smoker > 0.0f && !Body_isSmoking(&self->body)) + m.nerves += 0.5f * smoothstepf(0.8f, 1.0f, self->craving) * self->smoker; self->body.mood = (Mood){ .pride = clampf(m.pride, -1.0f, 1.0f), .energy = clampf(m.energy, -1.0f, 1.0f), diff --git a/entropie/src/render/figure.c b/entropie/src/render/figure.c index 8be2b86..3577be0 100644 --- a/entropie/src/render/figure.c +++ b/entropie/src/render/figure.c @@ -18,9 +18,15 @@ static const FigureLook WARDROBE[] = { .hair_style = HAIR_LONG, .long_sleeves = false }, }; +/* Jackets for the night: denim, olive, brown leather, a grey hoodie, red. */ +static const Vec3 JACKETS[] = { { 0.24f, 0.33f, 0.52f }, { 0.36f, 0.40f, 0.22f }, { 0.42f, 0.26f, 0.16f }, + { 0.55f, 0.55f, 0.57f }, { 0.62f, 0.16f, 0.15f } }; + FigureLook Figure_wardrobe(int index) { const int n = (int)(sizeof(WARDROBE) / sizeof(WARDROBE[0])); - return WARDROBE[((index % n) + n) % n]; + FigureLook look = WARDROBE[((index % n) + n) % n]; + look.jacket = JACKETS[((index % 5) + 5) % 5]; + return look; } static Vec3 rightOf(Vec3 facing) { diff --git a/entropie/src/render/figure.h b/entropie/src/render/figure.h index 2454205..2a87254 100644 --- a/entropie/src/render/figure.h +++ b/entropie/src/render/figure.h @@ -27,10 +27,11 @@ typedef struct { HairStyle hair_style; bool long_sleeves; int garment; /* GARMENT_*: what's on the legs */ - int top; /* TOP_*: the shirt on, or off (working in the sun) */ + int top; /* TOP_*: the shirt on, or off (working in the sun), or a jacket over it (cold nights) */ + Vec3 jacket; /* the jacket's colour */ } FigureLook; -enum { TOP_SHIRT, TOP_BARE, TOP_CROP }; +enum { TOP_SHIRT, TOP_BARE, TOP_CROP, TOP_JACKET }; /* Trousers (pants colour), shorts (knee-length), a skirt (pants colour) or a dress (shirt colour). */ enum { GARMENT_TROUSERS, GARMENT_SHORTS, GARMENT_SKIRT, GARMENT_DRESS }; diff --git a/entropie/src/render/renderer.c b/entropie/src/render/renderer.c index 171fc6f..9b6282f 100644 --- a/entropie/src/render/renderer.c +++ b/entropie/src/render/renderer.c @@ -82,6 +82,7 @@ static bool loadShaders(Renderer *r) { f->skin = glGetUniformLocation(f->program, "skinColor"); f->hair = glGetUniformLocation(f->program, "hairColor"); f->shirt = glGetUniformLocation(f->program, "shirtColor"); + f->jacket = glGetUniformLocation(f->program, "jacketColor"); f->pants = glGetUniformLocation(f->program, "pantsColor"); f->shoes = glGetUniformLocation(f->program, "shoeColor"); f->hair_style = glGetUniformLocation(f->program, "hairStyle"); @@ -502,6 +503,7 @@ void Renderer_drawFigure(Renderer *r, const FigurePose *pose) { setVec3(f->skin, pose->look.skin); setVec3(f->hair, pose->look.hair); setVec3(f->shirt, pose->look.shirt); + setVec3(f->jacket, pose->look.jacket); setVec3(f->pants, pose->look.pants); setVec3(f->shoes, pose->look.shoes); glUniform1i(f->hair_style, (GLint)pose->look.hair_style); diff --git a/entropie/src/render/renderer.h b/entropie/src/render/renderer.h index 8c58413..2f5d92e 100644 --- a/entropie/src/render/renderer.h +++ b/entropie/src/render/renderer.h @@ -68,7 +68,7 @@ typedef struct { GLint head_forward, head_up, chest_forward, waist_bend, face, expression; GLint stream, stream_kind, stream_width; GLint garment, undress, top; - GLint cig_a, cig_tip, cig_glow; + GLint cig_a, cig_tip, cig_glow, jacket; GLint view_pos, light_dir, light_color, shadow_color, sky_top, sky_horizon; GLint shadow_strength, shadow_map, fire_light; } FigureShader; diff --git a/entropie/src/world/ladder.c b/entropie/src/world/ladder.c index f83fb7a..51a3b8c 100644 --- a/entropie/src/world/ladder.c +++ b/entropie/src/world/ladder.c @@ -190,6 +190,14 @@ Vec3 Ladder_holdSpot(const Object *l, Vec3 *facing) { void Ladder_update(Object *l, float dt) { if (l->placing > 0.0f) { putUp(l, dt); return; } if (l->tip_v > 0.0f) { fallOver(l, dt); return; } + if (l->shake > 0.0f && l->leaning) { /* wobbling: the top rocks from side to side, dying away */ + l->shake = fmaxf(0.0f, l->shake - dt); + Vec3 axis = l->place_axis[1], across = vec3_normalize(vec3_cross(l->lean_normal, UP)); + float a = 0.05f * sinf(l->shake * 38.0f) * l->shake; + Vec3 rocked = vec3_normalize(vec3_add(vec3_scale(axis, cosf(a)), vec3_scale(across, sinf(a)))); + l->rotation = euler_yxz_from_axes(rocked, l->place_face[1]); + Object_syncColliders(l); + } if (l->raised > 0.0f) l->raised += clampf(l->raise_goal - l->raised, -RAISE_SPEED * dt, RAISE_SPEED * dt); if (l->extend == l->extend_goal) return; float step = (stepLadder(l) ? STEP_FOLD_SPEED : SLIDE_SPEED) * dt; diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h index 51ddfea..cb8cc95 100644 --- a/entropie/src/world/object.h +++ b/entropie/src/world/object.h @@ -168,6 +168,7 @@ typedef struct Object { float pour; /* characters: 0 = can hanging .. 1 = lifted and tipped, pouring */ bool female; /* characters: squats to pee, unless ... */ bool urinella; /* ... she has one of these (a pack of them) ... */ + int urinella_tried; /* women: 0 never tried one, 1 likes it, -1 not for her */ int urinellas; /* ... fresh ones in the pocket (none: she squats like the others, and gets more) */ bool lighter; /* characters: a lighter in the pocket ... */ struct Object *lighter_of;/* ... someone else's (borrowed), or NULL: their own ... */ @@ -237,6 +238,8 @@ typedef struct Object { float ladder_wobble; /* ... nearly slipped: the next slip is a fall (seconds this counts) */ Vec3 lean_normal; /* ladders: away from what it leans on */ bool spot_checked; /* wood: somebody looked whether there's room to work on it (once) */ + float chill; /* characters: 0..1 cold (night, away from a fire): a jacket on over 0.6 */ + float shake; /* ladders: wobbling (seconds left) */ float stability; /* ladders: how steady it stands, leaning (0..1; somebody holding it: steady) */ struct Object *held_by; /* ladders: somebody holding it steady at its foot ... */ struct Object *holding_ladder; /* characters: ... this one */ diff --git a/entropie/src/world/scene.c b/entropie/src/world/scene.c index edf9752..8225027 100644 --- a/entropie/src/world/scene.c +++ b/entropie/src/world/scene.c @@ -141,6 +141,7 @@ Object *Scene_load(World *world) { 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->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]; diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c index a95e152..476cfab 100644 --- a/entropie/src/world/world.c +++ b/entropie/src/world/world.c @@ -192,6 +192,17 @@ static void usedUrinella(World *world, Object *o) { o->urinellas--; const float drunk = fminf(o->drunk, 1.0f), clumsy = Body_clumsiness(&o->body); const bool talks = o->type == OBJ_HUMAN; + if (o->urinella_tried == 0 && o->type == OBJ_HUMAN) { /* the first one: she likes it, or not (the shy and the proud less) */ + float like = 0.6f - 0.3f * fmaxf(-o->temperament.pride, 0.0f) - 0.2f * fmaxf(o->temperament.pride - 0.5f, 0.0f) + 0.2f * o->temperament.energy; + o->urinella_tried = worldDice(world) < like ? 1 : -1; + if (o->urinella_tried < 0) { + o->urinella = false; /* (squatting again; the rest stays in the pocket) */ + Ai_say(o, "Hm. Not for me - I'll stick to squatting."); + return; + } + Ai_say(o, "Oh, that's handy!"); + return; + } if (worldDice(world) < 0.04f + 0.25f * drunk + 0.12f * clumsy) { o->dirt = fminf(1.0f, o->dirt + 0.12f); if (talks) Ai_say(o, "Oops... that went on my shoes."); @@ -623,6 +634,22 @@ static void smoke(World *world, Object *o, Vec3 facing, float dt) { } } +/* Cold nights: away from a burning fire a chill builds up and the jacket goes + on; by the fire, in the day (or warm with drink) it comes off again. */ +static void dressForNight(World *world, Object *o, float dt) { + if (o->type == OBJ_DOG) return; + bool night = world->hour >= 21.0f || world->hour < 7.0f; + bool by_fire = Camp_fireNear(world->objects, world->count, o->position, 4.0f, true) != NULL; + float cold = !night ? -2.0f : by_fire ? -1.0f : 1.0f - 0.8f * fminf(o->drunk, 1.0f); + o->chill = clampf(o->chill + cold * dt / 60.0f, 0.0f, 1.0f); /* (about a minute to feel it) */ + if (o->look.top != TOP_JACKET && o->chill > 0.6f) { + o->look.top = TOP_JACKET; + if (o->type == OBJ_HUMAN && o->ai.speech_time <= 0.0f && worldDice(world) < 0.3f) Ai_say(o, "Brr, getting cold."); + } else if (o->look.top == TOP_JACKET && o->chill < 0.15f) { + o->look.top = TOP_SHIRT; + } +} + /* Down from high up: into the knees, deeper, a roll or a crash (see Body_land). */ static void landFromHigh(World *world, Object *o) { float speed = o->air_fall; @@ -709,6 +736,7 @@ static void animateCharacter(World *world, Object *o, float dt) { if (o->spraying) water(world, o, dt); if (Body_isDown(&o->body)) o->dirt = fminf(1.0f, o->dirt + 0.003f * dt); /* lying in the dirt */ smoke(world, o, frame.facing, dt); + dressForNight(world, o, dt); } /* ---------- What people carry bumps into things ---------- */ @@ -838,6 +866,7 @@ static void pushOffCarried(World *world, Object *who) { void World_init(World *world) { memset(world, 0, sizeof(*world)); world->rng = 0x2545F491u; + world->hour = 12.0f; } void World_destroy(World *world) { diff --git a/entropie/src/world/world.h b/entropie/src/world/world.h index 9baaedc..cf798c9 100644 --- a/entropie/src/world/world.h +++ b/entropie/src/world/world.h @@ -26,6 +26,7 @@ typedef struct World { HoseRope hoses[HOSE_MAX]; Chips chips; /* wood chips and sawdust flying off work */ Grid grid; /* where things are, by ground cell (built each frame) */ + float hour; /* time of day (set by the game) */ } World; void World_init(World *world);