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.37.0: umbrellas inside out, hats
- A strong gust can blow an open umbrella inside out for a moment; the third time (or in a storm) it's broken for good - Hats: Toni and Kurti wear caps, Jonas a beanie. The wind can take a cap off; it bowls along the ground, they chase it and put it back on (pulled down tighter each time)
commit_message.txt | 15 +++--- entropie/Makefile | 2 +- entropie/README.md | 2 +- entropie/STATE.md | 16 +++++- entropie/assets/shaders/figure.frag | 32 ++++++++++-- entropie/commit_message.txt | 15 +++--- entropie/src/game/save.c | 4 +- entropie/src/people/ai.c | 4 +- entropie/src/render/figure.c | 6 +++ entropie/src/render/figure.h | 5 ++ entropie/src/render/renderer.c | 4 ++ entropie/src/render/renderer.h | 2 +- entropie/src/render/shapes.h | 6 ++- entropie/src/render/shapes_props.c | 27 ++++++++-- entropie/src/world/object.c | 27 ++++++++-- entropie/src/world/object.h | 7 ++- entropie/src/world/scene.c | 4 ++ entropie/src/world/world.c | 101 ++++++++++++++++++++++++++++++++++-- 18 files changed, 236 insertions(+), 43 deletions(-) diff --git a/commit_message.txt b/commit_message.txt index cbec05c..a418266 100644 --- a/commit_message.txt +++ b/commit_message.txt @@ -1,10 +1,7 @@ -entropie 2.36.0: rain together, steady view on slopes +entropie 2.37.0: umbrellas inside out, hats -- No more shaking view walking down steep slopes (the bank by your garages) -- The pullover tied round the waist no longer goes through the legs -- Umbrellas held properly: open, up in front of the shoulder with the shaft - leaning over the head; folded, carried by the middle, tip down -- People without an umbrella ask to come under someone's (yours too) and - walk along side by side; the umbrella leans over both -- In hard rain the guests sit it out in your garages' kitchen (chairs, a - new log bench, or the floor) and get up when it stops +- A strong gust can blow an open umbrella inside out for a moment; the + third time (or in a storm) it's broken for good +- Hats: Toni and Kurti wear caps, Jonas a beanie. The wind can take a cap + off; it bowls along the ground, they chase it and put it back on + (pulled down tighter each time) diff --git a/entropie/Makefile b/entropie/Makefile index 537b19e..0c05bb2 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 2.36.0 +VERSION := 2.37.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/README.md b/entropie/README.md index 5baee17..3fc8f72 100644 --- a/entropie/README.md +++ b/entropie/README.md @@ -124,5 +124,5 @@ Dependencies point downward: `main → game → world/people → render → core ## Version -`make version` prints e.g. `entropie 2.36.0 (build 25ed4f6)`. The build id is +`make version` prints e.g. `entropie 2.37.0 (build 1a6ad0e)`. The build id is an md5 of all sources and assets. diff --git a/entropie/STATE.md b/entropie/STATE.md index 125b23a..bffff4d 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -286,6 +286,13 @@ Painted in the rotoscope style of *Waking Life* (2001): it starts to rain (if they're not busy working) and stay out under it; the careful put it back after. Open, it's held up in front of the shoulder, the shaft leaning in over the head; folded, carried by the middle, tip down +- A strong gust may blow an open umbrella inside out (no use for a moment, + then pushed back right); the third time, or in a storm, the ribs bend and + it's broken (the others drop theirs and get in out of the rain) +- Some wear a hat: Toni a red cap, Kurti an old green one, Jonas a beanie. + A gust can take a cap off (a beanie hardly ever, none under a hood): it + bowls along the ground, they run after it and put it back on - pulled down + tighter each time - No umbrella of their own: somebody near under one gets asked ("Room under there for me?") and they walk along side by side under it (yours too); it leans over the two of them @@ -1218,6 +1225,11 @@ Round 2.36 (rain together, view on the slope): - Sharing one (`ai.c` umbrellaToShare / walkUnder): out in the rain without one, somebody within SHARE_SEE 12 m holding one up with nobody under it: asked, then along at their side (SHARE_GAP 0.53 m, the umbrella side), keeping pace (close: stepping straight onto the spot, no circling), for up to SHARE_MAX 4 min; the holder's `umbrella_guest` leans the canopy over both; `world.c` underUmbrella counts a head within 0.9 x its radius under anybody's open umbrella as dry. Checked: under yours, standing and walking: 0.6 m beside you, dry. - Sitting out heavy rain (`ai.c` rainRoom, `Site_rainRoom`): the guests go to the middle of your garages' kitchen; from RAIN_SIT 0.45 they sit down on a free chair or the new log bench by its back wall (else the floor) and stay till the rain is nearly over (half of SHELTER_RAIN). Checked: two on the chairs at the table for 100 s of hard rain, up again when it stopped. +Round 2.37 (umbrellas blown inside out, hats): +- Inside out (`world.c` windUmbrella): an open umbrella held in a gust (FLIP_GUST 0.4) over FLIP_WIND 10 m/s: FLIP_RATE 0.3/s for every 3 m/s over it x gust x carelessness (drunk, careless more) -> `umbrella_flip` FLIP_TIME 2.2 s inside out (`Shapes_umbrella` UMBRELLA_INSIDE_OUT: the rim up above the tip, ragged; no cover meanwhile), a rustle. The FLIP_RUIN 3rd time, or over RUIN_WIND 16 m/s: `umbrella_ruined` (stays inside out, never opens; saved; NPCs drop it, don't fetch it, shelter). Checked: at wind level 0.7 in the rain - flips at 28 and 47 s, broken at 126 s; dry under it otherwise. +- Hats (`FigureLook.hat` HAT_CAP / HAT_BEANIE, `hat_color`; `figure.frag` hatDistance: a cap's crown and a brim tipped down over the brow, a beanie down to the ears with a knot and ribs; under a hood: none): Toni a red cap, Kurti an old green one, Jonas a blue beanie (`Figure_wardrobe` by index % 7: 3, 4, 6). Saved with the person. +- Blown off (`world.c` windHat): in a gust (HAT_GUST 0.4) over HAT_WIND 9 m/s, HAT_RATE 0.3/s for every 3 m/s over it x gust (a beanie a tenth), divided by 1 + 3 x `hats_lost` (pulled down tighter each time): an OBJ_HAT (`Shapes_hat`, its colour, `owner`) flies off with the wind and bowls along like paper (`windBlows`); NPCs go after it (`ai.blown`, as for paper); picked up by its owner it goes back on (`hatBackOn`). Checked: Toni at wind level 0.8 - off, chased 7-14 m, back on in 3-14 s; less often each time. + ## Open tasks Most important first. @@ -1240,7 +1252,7 @@ Most important first. - "No shirt no service" policy (decision open) - Clothing, still to do: more outfits for extra people (only the performance test has any: they all wear trousers) - Tents, still to do: NPCs taking theirs down on their own (leaving) -- Weather, still to do: hats (nobody wears one yet) blown off; umbrellas while working (now only idle people fetch one); an umbrella blown inside out; more roofed places to sit out the rain together (outdoors: a shelter, a pavilion - `Site_rainRoom` is the one place now) +- Weather, still to do: umbrellas while working (now only idle people fetch one); more roofed places to sit out the rain together (outdoors: a shelter, a pavilion - `Site_rainRoom` is the one place now) - Sound, still to do: the house's doors (they have no doors that open yet) **Saves, still to do** @@ -1253,4 +1265,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.36.0 (build 25ed4f6)` +State of `entropie 2.37.0 (build 1a6ad0e)` diff --git a/entropie/assets/shaders/figure.frag b/entropie/assets/shaders/figure.frag index 3debd18..f247e7f 100644 --- a/entropie/assets/shaders/figure.frag +++ b/entropie/assets/shaders/figure.frag @@ -43,6 +43,8 @@ uniform float streamWidth; uniform int garment; // 0 trousers, 1 shorts, 2 skirt, 3 dress uniform float soaked; // 0..1 wet from the rain: clothes and hair darker uniform bool hood; // the jacket's hood up over the head (rain) +uniform int hat; // 0 none, 1 a cap (a brim in front), 2 a beanie (knitted, a turned-up cuff) +uniform vec3 hatColor; 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; @@ -66,7 +68,7 @@ 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, M_JACKET = 9, M_URINELLA = 10; -const int M_FUR = 11, M_FUR_DARK = 12, M_NOSE = 13; +const int M_FUR = 11, M_FUR_DARK = 12, M_NOSE = 13, M_HAT = 14; const float FIGURE_LIGHT_WRAP = 0.6; // soft, flat faces instead of a hard light split const float SELF_SHADOW_SKIP = 0.10; // meters @@ -297,6 +299,23 @@ float hairDistance(vec3 world_p) { return d; } +// A hat on the head (head-local): a cap's crown and its brim out in front, or a beanie down to the ears. +float hatDistance(vec3 world_p) { + vec3 p = toHead(world_p); + if (hat == 2) { + float crown = length(p - vec3(0.0, 0.028, -0.014)) - 0.112; + float rim = dot(p - vec3(0.0, -0.012, 0.0), normalize(vec3(0.0, -1.0, 0.25))); // (the cuff's lower edge, a bit lower behind) + float top = length(p - vec3(0.0, 0.128, -0.02)) - 0.018; // (a little knot on top) + return min(max(crown, rim), top); + } + float crown = max(length(p - vec3(0.0, 0.026, -0.012)) - 0.108, dot(p - vec3(0.0, 0.020, 0.0), normalize(vec3(0.0, -1.0, 0.15)))); + vec3 b = p - vec3(0.0, 0.026, 0.115); // the brim: a thin slab out over the brow + b.y += 0.12 * b.z; // (tipped down a little) + vec3 q = abs(b) - vec3(0.075, 0.004, 0.055); + float brim = length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0) - 0.004; + return min(crown, brim); +} + // The hood up: a shell round the head, open for the face, down the back of the neck. float hoodDistance(vec3 world_p) { vec3 p = toHead(world_p); @@ -322,6 +341,11 @@ float body(vec3 p, out int mat) { float dh = hood ? hoodDistance(p) : hairDistance(p); if (dh < nearest + 0.004) mat = hood ? M_JACKET : M_HAIR; // hair (or the hood) covers the scalp d = smin(d, dh, 0.008); + if (hat > 0 && !hood) { // a hat over the hair + float dhat = hatDistance(p); + if (dhat < min(nearest, dh) + 0.003) mat = M_HAT; + d = min(d, dhat); + } } return d; } @@ -411,6 +435,7 @@ vec3 clothDetail(int mat, vec3 base, vec3 p) { vec3 materialColor(int mat) { if (mat == M_HAIR) return hairColor; + if (mat == M_HAT) return hatColor; if (mat == M_SHIRT) return shirtColor; if (mat == M_PANTS) return pantsColor; if (mat == M_SHOES) return shoeColor; @@ -470,7 +495,7 @@ void main() { if (length(cross(c - origin, ray)) < r) candidates |= 1 << i; } vec3 head = onHead(vec3(0.0, hairStyle == 1 ? -0.08 : 0.0, -0.02)); - hairCandidate = species == 0 && length(cross(head - origin, ray)) < (hairStyle == 1 || hood ? 0.22 : 0.15); + hairCandidate = species == 0 && length(cross(head - origin, ray)) < (hairStyle == 1 || hood ? 0.22 : hat == 1 ? 0.2 : 0.15); if (candidates == 0 && !hairCandidate) discard; // March the ray through the body @@ -505,7 +530,8 @@ void main() { } else if (mat == M_HAIR) base = hairDetail(base, p); else if (mat < M_PEE) base = clothDetail(mat, base, p); - if (species == 0 && (mat == M_SHIRT || mat == M_PANTS || mat == M_JACKET || mat == M_HAIR || mat == M_SHOES)) + if (mat == M_HAT && hat == 2) base *= 0.9 + 0.1 * step(0.5, fract(dot(toHead(p), vec3(60.0, 0.0, 0.0)) + 0.25)); // (knitted ribs) + if (species == 0 && (mat == M_SHIRT || mat == M_PANTS || mat == M_JACKET || mat == M_HAIR || mat == M_SHOES || mat == M_HAT)) base *= 1.0 - 0.34 * soaked; // (wet cloth: darker) if (mat == M_JACKET) { // the zip down the front, a darker collar vec3 rel = p - joints[J_CHEST]; diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt index cbec05c..a418266 100644 --- a/entropie/commit_message.txt +++ b/entropie/commit_message.txt @@ -1,10 +1,7 @@ -entropie 2.36.0: rain together, steady view on slopes +entropie 2.37.0: umbrellas inside out, hats -- No more shaking view walking down steep slopes (the bank by your garages) -- The pullover tied round the waist no longer goes through the legs -- Umbrellas held properly: open, up in front of the shoulder with the shaft - leaning over the head; folded, carried by the middle, tip down -- People without an umbrella ask to come under someone's (yours too) and - walk along side by side; the umbrella leans over both -- In hard rain the guests sit it out in your garages' kitchen (chairs, a - new log bench, or the floor) and get up when it stops +- A strong gust can blow an open umbrella inside out for a moment; the + third time (or in a storm) it's broken for good +- Hats: Toni and Kurti wear caps, Jonas a beanie. The wind can take a cap + off; it bowls along the ground, they chase it and put it back on + (pulled down tighter each time) diff --git a/entropie/src/game/save.c b/entropie/src/game/save.c index 27cb1c5..76fcde3 100644 --- a/entropie/src/game/save.c +++ b/entropie/src/game/save.c @@ -43,7 +43,7 @@ static const Field THING[] = { FL("wake", wake), IN("stored", stored), FL("work", work), FL("cut_at", cut_at), FL("mark", mark), VE("mark_at", mark_at), VE("mark_out", mark_out), IN("branches", branches), BO("trunk_cut", trunk_cut), BO("toppled", toppled), FL("topple", topple), VE("fall_dir", fall_dir), FL("fall_base_y", fall_base_y), VE("fall_origin", fall_origin), FL("fall_ground_y", fall_ground_y), - FL("fall_slide", fall_slide), VE("fell_plan", fell_plan), BO("fell_blocked", fell_blocked), BO("keep", keep), FL("charred", charred), + FL("fall_slide", fall_slide), VE("fell_plan", fell_plan), BO("fell_blocked", fell_blocked), BO("keep", keep), FL("charred", charred), BO("umbrella_ruined", umbrella_ruined), VE("hat_tint", look.hat_color), IN("umbrella_flips", umbrella_flips), FL("glow", glow), FL("fuel", fuel), BO("burning", burning), FL("water", water), FL("grill", grill), BO("grilled", grilled), BO("burnt", burnt), FL("extend", extend), FL("raised", raised), BO("leaning", leaning), VE("lean_normal", lean_normal), FL("lean_top", lean_top), FL("stability", stability), IN("crate_empties", crate_empties), IN("tent_stage", tent_stage), @@ -64,7 +64,7 @@ static const Field PERSON[] = { VE("skin", look.skin), VE("hair", look.hair), VE("shirt", look.shirt), VE("pants", look.pants), VE("shoes", look.shoes), FL("height", look.height), IN("hair_style", look.hair_style), BO("long_sleeves", look.long_sleeves), IN("garment", look.garment), IN("top", look.top), VE("jacket", look.jacket), BO("jacket_tied", look.jacket_tied), FL("belly", look.belly), - BO("keeps_shirt", look.keeps_shirt), FL("wet", look.wet), BO("hoodie", look.hoodie), + BO("keeps_shirt", look.keeps_shirt), FL("wet", look.wet), BO("hoodie", look.hoodie), IN("hat", look.hat), IN("hats_lost", hats_lost), VE("hat_color", look.hat_color), FL("ladder_skill", ladder_skill), FL("fitness", fitness), FL("wits", wits), FL("worn", worn), FL("height_fear", height_fear), FL("top_speed", top_speed), FL("jump_speed", jump_speed), FL("pace", pace), FL("tempo", tempo), IN("main_hand", main_hand), BO("female", female), BO("urinella", urinella), IN("urinella_tried", urinella_tried), IN("urinellas", urinellas), diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c index 2b0ca3f..e7e46cb 100644 --- a/entropie/src/people/ai.c +++ b/entropie/src/people/ai.c @@ -2062,7 +2062,7 @@ static bool goInside(Object *self, Object *const *objects, int count, float dt) static bool holdsUmbrella(const Object *self) { for (int h = 0; h < 2; h++) - if (self->hands.item[h] && self->hands.item[h]->type == OBJ_UMBRELLA && self->hands.state[h] == HAND_HOLDING) return true; + if (self->hands.item[h] && self->hands.item[h]->type == OBJ_UMBRELLA && !self->hands.item[h]->umbrella_ruined && self->hands.state[h] == HAND_HOLDING) return true; return false; } @@ -2230,7 +2230,7 @@ static bool shelter(Object *self, Object *const *objects, int count, float dt) { float best_d = UMBRELLA_FETCH; for (int i = 0; i < count; i++) { Object *o = objects[i]; - if (o->type != OBJ_UMBRELLA || o->is_held || ai_skipped(ai, o)) continue; + if (o->type != OBJ_UMBRELLA || o->is_held || o->umbrella_ruined || ai_skipped(ai, o)) continue; bool claimed = false; for (int j = 0; j < count && !claimed; j++) claimed = objects[j] != self && objects[j]->is_character && objects[j]->ai.umbrella == o; float d = flatDistance(o->position, self->position); diff --git a/entropie/src/render/figure.c b/entropie/src/render/figure.c index 3d1ae76..9532c29 100644 --- a/entropie/src/render/figure.c +++ b/entropie/src/render/figure.c @@ -27,6 +27,12 @@ FigureLook Figure_wardrobe(int index) { FigureLook look = WARDROBE[((index % n) + n) % n]; look.jacket = JACKETS[((index % 5) + 5) % 5]; look.hoodie = index % 5 == 1 || index % 5 == 3 || index % 5 == 4; /* (the parka, the hoodie, the red rain jacket) */ + switch (((index % 7) + 7) % 7) { /* hats: Toni a red cap, Kurti an old green one, Jonas a beanie (and so on for more people) */ + case 3: look.hat = HAT_CAP; look.hat_color = vec3(0.72f, 0.13f, 0.12f); break; + case 4: look.hat = HAT_CAP; look.hat_color = vec3(0.26f, 0.32f, 0.22f); break; + case 6: look.hat = HAT_BEANIE; look.hat_color = vec3(0.20f, 0.28f, 0.46f); break; + default: break; + } return look; } diff --git a/entropie/src/render/figure.h b/entropie/src/render/figure.h index e5492c5..9ff8823 100644 --- a/entropie/src/render/figure.h +++ b/entropie/src/render/figure.h @@ -35,10 +35,15 @@ typedef struct { float wet; /* 0 .. 1: clothes and hair wet from the rain (darker) */ bool hoodie; /* the jacket has a hood ... */ bool hood_up; /* ... pulled up over the head (rain) */ + int hat; /* HAT_*: on the head (off under a hood) ... */ + Vec3 hat_color; /* ... its colour */ } FigureLook; enum { TOP_SHIRT, TOP_BARE, TOP_CROP, TOP_JACKET }; +/* A cap (a brim in front: the wind takes it easily) or a knitted beanie (stays on). */ +enum { HAT_NONE, HAT_CAP, HAT_BEANIE }; + /* 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 e8a86e4..0822181 100644 --- a/entropie/src/render/renderer.c +++ b/entropie/src/render/renderer.c @@ -108,6 +108,8 @@ static bool loadShaders(Renderer *r) { f->belly = glGetUniformLocation(f->program, "belly"); f->wet = glGetUniformLocation(f->program, "soaked"); f->hood = glGetUniformLocation(f->program, "hood"); + f->hat = glGetUniformLocation(f->program, "hat"); + f->hat_color = glGetUniformLocation(f->program, "hatColor"); f->cig_a = glGetUniformLocation(f->program, "cigA"); f->uri_a = glGetUniformLocation(f->program, "uriA"); f->uri_b = glGetUniformLocation(f->program, "uriB"); @@ -575,6 +577,8 @@ void Renderer_drawFigure(Renderer *r, const FigurePose *pose) { glUniform1f(f->belly, pose->look.belly); glUniform1f(f->wet, pose->look.wet); glUniform1i(f->hood, pose->look.hoodie && pose->look.hood_up && pose->look.top == TOP_JACKET ? 1 : 0); + glUniform1i(f->hat, pose->look.hat); + glUniform3f(f->hat_color, pose->look.hat_color.x, pose->look.hat_color.y, pose->look.hat_color.z); setVec3(f->cig_a, sk->cig_a); setVec3(f->uri_a, sk->uri_a); setVec3(f->uri_b, sk->uri_b); diff --git a/entropie/src/render/renderer.h b/entropie/src/render/renderer.h index 5f62c53..9bb0bd5 100644 --- a/entropie/src/render/renderer.h +++ b/entropie/src/render/renderer.h @@ -72,7 +72,7 @@ typedef struct { GLint skin, hair, shirt, pants, shoes, hair_style, long_sleeves; GLint head_forward, head_up, chest_forward, waist_bend, face, expression; GLint stream, stream_kind, stream_width; - GLint garment, undress, top, jacket_tied, belly, wet, hood; + GLint garment, undress, top, jacket_tied, belly, wet, hood, hat, hat_color; GLint cig_a, cig_tip, cig_glow, jacket, uri_a, uri_b, uri; GLint species, tail_a, tail_b; GLint view_pos, light_dir, light_color, shadow_color, sky_top, sky_horizon; diff --git a/entropie/src/render/shapes.h b/entropie/src/render/shapes.h index f2cb342..198f1d4 100644 --- a/entropie/src/render/shapes.h +++ b/entropie/src/render/shapes.h @@ -152,7 +152,11 @@ enum { BOTTLE_KIND_NONE, BOTTLE_KIND_BEER, BOTTLE_KIND_WATER, BOTTLE_KIND_COLA, Mesh Shapes_crate(int variant, const unsigned char *slots, int n); /* An umbrella along Y, centred, length long: folded (a crook at the bottom), or open (the canopy over the top end, radius across). */ -Mesh Shapes_umbrella(float length, float radius, bool open, Vec3 color); +/* shape: UMBRELLA_FOLDED, UMBRELLA_OPEN, UMBRELLA_INSIDE_OUT (blown: the canopy up like a cup, ribs bent) */ +enum { UMBRELLA_FOLDED, UMBRELLA_OPEN, UMBRELLA_INSIDE_OUT }; +Mesh Shapes_umbrella(float length, float radius, int shape, Vec3 color); +/* A hat lying about (HAT_CAP, HAT_BEANIE), its crown up, the cap's brim along +Z. */ +Mesh Shapes_hat(int kind, Vec3 color); /* Camping things (centred, lying along X): kind 0 a sleeping bag in its sack, 1 a foam mat rolled up, 2 a small tent in its bag, 3 a big one. */ Mesh Shapes_gear(int kind, Vec3 color); diff --git a/entropie/src/render/shapes_props.c b/entropie/src/render/shapes_props.c index 6ff81e7..7e62f17 100644 --- a/entropie/src/render/shapes_props.c +++ b/entropie/src/render/shapes_props.c @@ -1073,7 +1073,8 @@ Mesh Shapes_crate(int variant, const unsigned char *slots, int n) { return Mesh_fromBuilder(&b); } -Mesh Shapes_umbrella(float length, float radius, bool open, Vec3 color) { +Mesh Shapes_umbrella(float length, float radius, int shape, Vec3 color) { + const bool open = shape != UMBRELLA_FOLDED, inside_out = shape == UMBRELLA_INSIDE_OUT; MeshBuilder b; MeshBuilder_init(&b); const float half = length * 0.5f; @@ -1091,8 +1092,11 @@ Mesh Shapes_umbrella(float length, float radius, bool open, Vec3 color) { Vec3 tip = vec3(0.0f, half + 0.02f, 0.0f); for (int k = 0; k < panels; k++) { float a0 = 2.0f * PI_F * (float)k / (float)panels, a1 = 2.0f * PI_F * (float)(k + 1) / (float)panels; - Vec3 r0 = vec3(cosf(a0) * radius, half - 0.2f * radius, sinf(a0) * radius); - Vec3 r1 = vec3(cosf(a1) * radius, half - 0.2f * radius, sinf(a1) * radius); + /* (inside out: the rim blown up above the tip, narrower, a bit ragged) */ + float rr0 = inside_out ? radius * (0.8f + 0.1f * (float)(k % 3)) : radius, rr1 = inside_out ? radius * (0.8f + 0.1f * (float)((k + 1) % 3)) : radius; + float lift = inside_out ? 0.55f * radius : -0.2f * radius; + Vec3 r0 = vec3(cosf(a0) * rr0, half + lift + (inside_out ? 0.05f * (float)(k % 2) : 0.0f), sinf(a0) * rr0); + Vec3 r1 = vec3(cosf(a1) * rr1, half + lift + (inside_out ? 0.05f * (float)((k + 1) % 2) : 0.0f), sinf(a1) * rr1); Vec3 c = k % 2 ? vec3_scale(color, 0.82f) : color; Vec3 n = vec3_normalize(vec3_cross(vec3_sub(r1, tip), vec3_sub(r0, tip))); GLuint t0 = MeshBuilder_vertex(&b, tip, n, c), i0 = MeshBuilder_vertex(&b, r0, n, c), i1 = MeshBuilder_vertex(&b, r1, n, c); @@ -1108,6 +1112,23 @@ Mesh Shapes_umbrella(float length, float radius, bool open, Vec3 color) { return Mesh_fromBuilder(&b); } +/* A hat: a dome of rings (a cap: a brim out in front; a beanie: taller, a cuff). */ +Mesh Shapes_hat(int kind, Vec3 color) { + MeshBuilder b; + MeshBuilder_init(&b); + const bool beanie = kind == 2; + const float r = beanie ? 0.105f : 0.1f, h = beanie ? 0.12f : 0.085f; + const int rings = 5; + for (int k = 0; k < rings; k++) { /* the crown: a dome */ + float a0 = 0.5f * PI_F * (float)k / (float)rings, a1 = 0.5f * PI_F * (float)(k + 1) / (float)rings; + shapes_addTube(&b, -0.04f + h * sinf(a0), r * cosf(a0), fmaxf(r * cosf(a1), 0.004f), h * (sinf(a1) - sinf(a0)), 12, + vec3_scale(color, 0.92f + 0.08f * (float)k / (float)rings), k == rings - 1); + } + if (beanie) shapes_addTube(&b, -0.045f, r * 1.04f, r * 1.04f, 0.035f, 12, vec3_scale(color, 0.85f), false); /* the cuff */ + else addBlock(&b, vec3(0.0f, -0.036f, r + 0.045f), vec3(0.15f, 0.008f, 0.11f), vec3_scale(color, 0.8f)); /* the brim */ + return Mesh_fromBuilder(&b); +} + /* Camping things, centred, lying along X (see shapes.h). */ Mesh Shapes_gear(int kind, Vec3 color) { MeshBuilder b; diff --git a/entropie/src/world/object.c b/entropie/src/world/object.c index f4821b4..ea64f3d 100644 --- a/entropie/src/world/object.c +++ b/entropie/src/world/object.c @@ -390,12 +390,20 @@ Object *Object_create(ObjectType type, Palette palette, Vec3 position, Vec3 size obj->size = vec3(0.08f, UMBRELLA_LENGTH, 0.08f); obj->weight = 0.5f; obj->variant = (int)weight % 3; - obj->mesh = Shapes_umbrella(UMBRELLA_LENGTH, UMBRELLA_RADIUS, false, COLORS[obj->variant]); + obj->mesh = Shapes_umbrella(UMBRELLA_LENGTH, UMBRELLA_RADIUS, UMBRELLA_FOLDED, COLORS[obj->variant]); obj->pivot = vec3(0.0f, UMBRELLA_LENGTH * 0.5f, 0.0f); addCollider(obj, vec3_zero(), obj->size, vec3_zero()); break; } + case OBJ_HAT: /* (grey here: its colour once it's known - world.c, showProps) */ + obj->size = vec3(0.21f, 0.1f, 0.24f); + obj->weight = 0.1f; + obj->variant = (int)weight == 2 ? 2 : 1; + obj->mesh = Shapes_hat(obj->variant, vec3(0.5f, 0.5f, 0.5f)); + addCollider(obj, vec3(0.0f, 0.0f, 0.02f), obj->size, vec3_zero()); + break; + case OBJ_TENT: { /* (see tent.c: its mesh by how far it's up) */ obj->variant = weight > 0.5f ? 1 : 0; obj->weight = 50.0f; @@ -1369,10 +1377,11 @@ static const char *bottleName(const Object *b) { void Object_showUmbrella(Object *u) { static const Vec3 COLORS[] = { { 0.15f, 0.20f, 0.42f }, { 0.70f, 0.14f, 0.16f }, { 0.20f, 0.32f, 0.22f } }; - if (u->type != OBJ_UMBRELLA || u->shown == (u->umbrella_open ? 1 : 0)) return; + int want = u->umbrella_flip > 0.0f || u->umbrella_ruined ? UMBRELLA_INSIDE_OUT : u->umbrella_open ? UMBRELLA_OPEN : UMBRELLA_FOLDED; + if (u->type != OBJ_UMBRELLA || u->shown == want) return; Mesh_destroy(&u->mesh); - u->mesh = Shapes_umbrella(UMBRELLA_LENGTH, UMBRELLA_RADIUS, u->umbrella_open, COLORS[u->variant % 3]); - u->shown = u->umbrella_open ? 1 : 0; + u->mesh = Shapes_umbrella(UMBRELLA_LENGTH, UMBRELLA_RADIUS, want, COLORS[u->variant % 3]); + u->shown = want; } void Object_setDrink(Object *b, int drink) { @@ -1495,8 +1504,16 @@ void Object_describe(const Object *obj, char *out, size_t size) { else snprintf(out, size, "%s's tent - %s", obj->owner ? obj->owner->name : "Somebody", STAGE[obj->tent_stage % 5]); break; } + case OBJ_HAT: { + const char *what = obj->variant == 2 ? "beanie" : "cap"; + if (obj->owner && obj->owner->type == OBJ_PLAYER) snprintf(out, size, "Your %s - pick it up to put it back on", what); + else if (obj->owner) snprintf(out, size, "%s's %s (blown off)", obj->owner->name, what); + else snprintf(out, size, "A %s", what); + break; + } case OBJ_UMBRELLA: - snprintf(out, size, obj->umbrella_open ? "Umbrella - open" : "Umbrella (carried, it opens in the rain)"); + snprintf(out, size, obj->umbrella_ruined ? "A broken umbrella - blown inside out, the ribs bent" : obj->umbrella_flip > 0.0f ? "Umbrella - blown inside out!" : + obj->umbrella_open ? "Umbrella - open" : "Umbrella (carried, it opens in the rain)"); break; case OBJ_GEAR: { static const char *const WHAT[] = { "sleeping bag", "sleeping mat", "tent", "tent (the big one)" }; diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h index e6e58cd..0f63d48 100644 --- a/entropie/src/world/object.h +++ b/entropie/src/world/object.h @@ -88,6 +88,7 @@ typedef enum { OBJ_GEAR, /* camping things: variant = GearKind (see car.h), owner = whose */ OBJ_TENT, /* static: a dome tent, weight 0 small / 1 big (see tent.h); stored = pegs in */ OBJ_UMBRELLA, /* an umbrella: size.y long (folded); opens held in the rain (umbrella_open); variant = colour */ + OBJ_HAT, /* a hat off somebody's head (blown off): variant = HAT_*, look.hat_color, owner = whose */ } ObjectType; /* What a body needs over the day. All 0..1; 0 is fine (stamina: 1 is fine). */ @@ -189,7 +190,11 @@ typedef struct Object { unsigned char crate_slot[CRATE_SLOTS]; struct Object *umbrella_guest; /* people: somebody under their umbrella with them (the canopy leans their way) ... */ float umbrella_guest_t; /* ... kept up by them this long */ - bool umbrella_open; /* umbrellas: open (held up in the rain) */ /* ... and what's in each slot (CRATE_SLOT: kind and sips; see car.c Crate_*) */ + bool umbrella_open; /* umbrellas: open (held up in the rain) */ + float umbrella_flip; /* umbrellas: s left blown inside out by a gust (0: as it should be) ... */ + int umbrella_flips; /* ... how often that happened ... */ + int hats_lost; /* people: how often the wind took their cap (they pull it down tighter each time) */ + bool umbrella_ruined; /* ... once too often (or in a storm): the ribs bent, it's no use any more */ /* ... and what's in each slot (CRATE_SLOT: kind and sips; see car.c Crate_*) */ int tent_stage; /* tents: TentStage ... */ float tent_work; /* ... how far the stage is (0..1) ... */ float tent_idle; /* ... seconds since anybody worked on it */ diff --git a/entropie/src/world/scene.c b/entropie/src/world/scene.c index de04371..ac46e75 100644 --- a/entropie/src/world/scene.c +++ b/entropie/src/world/scene.c @@ -243,9 +243,13 @@ Object *Scene_load(World *world) { if (characters < (int)(sizeof(LOOKS) / sizeof(LOOKS[0]))) { /* (their own clothes; extra people: the wardrobe) */ Vec3 jacket = obj->look.jacket; bool hoodie = obj->look.hoodie; + int hat = obj->look.hat; + Vec3 hat_color = obj->look.hat_color; obj->look = LOOKS[characters]; obj->look.jacket = jacket; obj->look.hoodie = hoodie; + obj->look.hat = hat; + obj->look.hat_color = hat_color; } obj->look.garment = GARMENT[characters % TEMPERAMENT_COUNT]; Object_setPerson(obj, NAMES[characters % TEMPERAMENT_COUNT], MAIN_HAND[characters % TEMPERAMENT_COUNT]); diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c index 425f2a6..12e531a 100644 --- a/entropie/src/world/world.c +++ b/entropie/src/world/world.c @@ -776,9 +776,9 @@ static bool underUmbrella(World *world, Object *o, float dt) { for (int h = 0; h < 2; h++) { Object *u = o->hands.item[h]; if (!u || u->type != OBJ_UMBRELLA || o->hands.state[h] != HAND_HOLDING) continue; - u->umbrella_open = world->rain > 0.05f && !o->in_tent && o->indoors <= 0.0f && !o->riding && + u->umbrella_open = world->rain > 0.05f && !o->in_tent && o->indoors <= 0.0f && !o->riding && !u->umbrella_ruined && !Weather_covered(world, vec3_add(o->position, vec3(0.0f, o->look.height + 0.3f, 0.0f))); - over = over || u->umbrella_open; + over = over || (u->umbrella_open && u->umbrella_flip <= 0.0f); /* (blown inside out: no use a moment) */ } if (over || o->is_held) return over; /* under somebody else's (walking along with them) */ @@ -787,7 +787,7 @@ static bool underUmbrella(World *world, Object *o, float dt) { if (p == o || !p->is_character) continue; for (int h = 0; h < 2 && !over; h++) { const Object *u = p->hands.item[h]; - if (!u || u->type != OBJ_UMBRELLA || !u->umbrella_open || p->hands.state[h] != HAND_HOLDING) continue; + if (!u || u->type != OBJ_UMBRELLA || !u->umbrella_open || u->umbrella_flip > 0.0f || p->hands.state[h] != HAND_HOLDING) continue; Vec3 top = vec3_add(Object_colliderCenter(u, 0), vec3_scale(u->colliders[0].axes[1], UMBRELLA_LENGTH * 0.5f)); float dx = top.x - o->position.x, dz = top.z - o->position.z; over = dx * dx + dz * dz < (UMBRELLA_COVERS * UMBRELLA_RADIUS) * (UMBRELLA_COVERS * UMBRELLA_RADIUS) && top.y > o->position.y + o->look.height; @@ -889,6 +889,12 @@ static void showProps(World *world) { if (open != f->heard_open) World_noise(world, f->type == OBJ_CAR ? NOISE_SLAM : NOISE_CREAK, f->position, 0.8f); f->heard_open = open; } + if (f->type == OBJ_HAT && !f->broken && f->shown != 1) { /* (its look: the colour it had on the head) */ + Mesh_destroy(&f->mesh); + f->mesh = Shapes_hat(f->variant, f->look.hat_color); + f->shown = 1; + continue; + } if (f->type == OBJ_UMBRELLA) { /* (folded once it's put down) */ if (!f->is_held) f->umbrella_open = false; Object_showUmbrella(f); @@ -1379,6 +1385,7 @@ static void emptyTray(World *world, Object *o) { #define BLOW_SHOVE 0.9f /* m/s^2 for each m/s over it (against the ground's grip) */ static bool paperLike(const Object *o) { + if (o->type == OBJ_HAT) return true; /* (light, and a cap catches the wind: it bowls along) */ return o->type == OBJ_TRASH && o->size.x > 0.0f && ((o->variant & 3) == PALETTE_WARM || (o->variant & 3) == PALETTE_STANDARD); } @@ -1422,6 +1429,91 @@ static void windSnatches(World *world, Object *o, float dt) { } } +/* An open umbrella in a gust: blown inside out now and then (no use a moment, then + pushed back right); the third time, or in a storm, the ribs bend and it's done for. */ +#define FLIP_WIND 10.0f /* m/s in a gust: an open umbrella may blow inside out ... */ +#define FLIP_GUST 0.4f /* ... a gust this strong (0..1) ... */ +#define FLIP_RATE 0.3f /* ... chance a second for every 3 m/s over it, in a full gust */ +#define FLIP_TIME 2.2f /* s till it's pushed back the right way */ +#define FLIP_RUIN 3 /* blown inside out this often: broken */ +#define RUIN_WIND 16.0f /* m/s: a gust like that breaks it at once */ + +static void windUmbrella(World *world, Object *o, float dt) { + for (int h = 0; h < 2; h++) { + Object *u = o->hands.item[h]; + if (!u || u->type != OBJ_UMBRELLA || o->hands.state[h] != HAND_HOLDING || u->umbrella_ruined) continue; + if (u->umbrella_flip > 0.0f) { + if ((u->umbrella_flip -= dt) <= 0.0f) { + u->umbrella_flip = 0.0f; + if (o->type != OBJ_PLAYER && dice(world) < 0.4f) Ai_say(o, "There - right way round again."); + } + continue; + } + if (!u->umbrella_open || world->gust < FLIP_GUST) continue; + float speed = hypotf(world->wind.x, world->wind.z), over = speed - FLIP_WIND; + if (over <= 0.0f) continue; + float careless = 0.6f + fminf(o->drunk, 1.0f) + 0.6f * (1.0f - Object_caution(o)); /* (the careful turn it into the wind) */ + if (dice(world) >= FLIP_RATE * over / 3.0f * world->gust * careless * dt) continue; + u->umbrella_flip = FLIP_TIME; + u->umbrella_flips++; + World_noiseOn(world, NOISE_RUSTLE, u->position, 0.6f, 0); /* (the canopy snapping over) */ + if (u->umbrella_flips >= FLIP_RUIN || speed > RUIN_WIND) { + u->umbrella_ruined = true; + u->umbrella_open = false; + if (o->type == OBJ_PLAYER) Ai_say(o, "The umbrella's had it - the ribs are bent."); + else { /* (no use: down it goes, and in out of the rain) */ + Ai_say(o, dice(world) < 0.5f ? "Oh no - my umbrella's had it!" : "Great. Broken."); + Hands_drop(o, h); + } + } else { + Ai_say(o, dice(world) < 0.5f ? "Argh - inside out!" : "Whoa - the umbrella!"); + } + } +} + +/* A gust takes a cap off a head (a beanie hardly ever; not under a hood): it flies + off with the wind and bowls along the ground; whoever's it is goes after it. + Picked up by its owner, it goes back on. */ +#define HAT_WIND 9.0f /* m/s in a gust: a cap may go from this ... */ +#define HAT_GUST 0.4f +#define HAT_RATE 0.3f /* ... chance a second for every 3 m/s over it, in a full gust (a beanie: a tenth) */ + +static void windHat(World *world, Object *o, float dt) { + if (!o->look.hat || o->look.hood_up || o->indoors > 0.0f || o->in_tent || o->riding || world->gust < HAT_GUST) return; + float speed = hypotf(world->wind.x, world->wind.z), over = speed - HAT_WIND; + if (over <= 0.0f) return; + float rate = HAT_RATE * over / 3.0f * world->gust * (o->look.hat == HAT_BEANIE ? 0.1f : 1.0f) / (1.0f + 3.0f * (float)o->hats_lost); /* (lost it before: pulled down tight) */ + if (dice(world) >= rate * dt) return; + Vec3 head = vec3_add(o->position, vec3(0.0f, o->look.height + 0.05f, 0.0f)); + Object *hat = Object_create(OBJ_HAT, PALETTE_STANDARD, head, vec3(0.2f, 0.1f, 0.2f), (float)o->look.hat); + if (!hat) return; + hat->look.hat_color = o->look.hat_color; + hat->owner = o; + hat->rotation.y = o->rotation.y; + Object_syncColliders(hat); + if (!World_add(world, hat)) return; + hat->velocity = vec3(world->wind.x * 0.55f, 1.8f, world->wind.z * 0.55f); + hat->angular_velocity = vec3(3.0f, 1.0f, 2.0f); + hat->is_grounded = false; + o->look.hat = HAT_NONE; + o->hats_lost++; + if (o->type != OBJ_PLAYER) { o->ai.blown = hat; o->ai.blown_t = 0.0f; } + Ai_say(o, o->type == OBJ_PLAYER ? "My cap - the wind took it!" : dice(world) < 0.5f ? "Hey - my cap!" : "Oi! Come back here, cap!"); +} + +/* Their own hat in hand: back on the head. */ +static void hatBackOn(Object *o) { + for (int h = 0; h < 2; h++) { + Object *hat = o->hands.item[h]; + if (!hat || hat->type != OBJ_HAT || hat->owner != o || o->look.hat) continue; + Hands_drop(o, h); + o->look.hat = hat->variant; + o->look.hat_color = hat->look.hat_color; + Object_useUp(hat); + if (o->type == OBJ_PLAYER) Ai_say(o, "Cap back on."); + } +} + void World_update(World *world, float dt, float game_dt) { Object_indexKinds(world->objects, world->count); world->dt_props = dt; @@ -1479,6 +1571,9 @@ void World_update(World *world, float dt, float game_dt) { Object *o = world->objects[i]; windBlows(world, o, dt); windSnatches(world, o, dt); + windUmbrella(world, o, dt); + windHat(world, o, dt); + hatBackOn(o); Physics_step(o, world->objects, world->count, &world->terrain, dt); landThrown(world, o); }