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.19.0: saves keep the hose and sitting; felling hint
- Saves keep the hose (where the rope lies, melted and taped bits) and who sits where (they sit down again after loading) - Felling yourself: the hint line says where the tree comes down and what it would hit; nearly through, people in the way step aside
commit_message.txt | 14 +++++--------- entropie/ARCHITECTURE.md | 2 +- entropie/Makefile | 2 +- entropie/README.md | 2 ++ entropie/STATE.md | 10 +++++++--- entropie/src/game/game_actions.c | 35 +++++++++++++++++++++++++++++++++++ entropie/src/game/save.c | 36 ++++++++++++++++++++++++++++++++++-- entropie/src/people/ai.h | 4 ++++ entropie/src/people/ai_work.c | 29 +++++++++++++++++++++-------- 9 files changed, 110 insertions(+), 24 deletions(-) diff --git a/commit_message.txt b/commit_message.txt index bd1ec9d..ee54708 100644 --- a/commit_message.txt +++ b/commit_message.txt @@ -1,10 +1,6 @@ -entropie 2.18.0: saving and loading, start menu +entropie 2.19.0: saves keep the hose and sitting; felling hint -- Start menu: continue (the autosave), new game, load a save, quit -- Esc menu: save as (type a name), quick save, load a save -- F5 quick save, F9 quick load; autosave every 5 minutes and on quit -- Saves: a versioned text file plus the ground (worn paths, wet ground); - the world is rebuilt and everything put back (people, pockets, clothes, - tents, crates, felled trees, the car, the weather, the time) -- Format version and migration steps so old saves keep working -- Fix: a freed people list could be used after rebuilding the world +- Saves keep the hose (where the rope lies, melted and taped bits) and + who sits where (they sit down again after loading) +- Felling yourself: the hint line says where the tree comes down and what + it would hit; nearly through, people in the way step aside diff --git a/entropie/ARCHITECTURE.md b/entropie/ARCHITECTURE.md index ff35b49..03091c7 100644 --- a/entropie/ARCHITECTURE.md +++ b/entropie/ARCHITECTURE.md @@ -88,7 +88,7 @@ of view (objects, 8×8 terrain tiles). - A key renamed or its meaning changed: raise `SAVE_FORMAT` (save.h) and add a step to `migrate()` that rewrites the old lines. - A save from a newer format is refused, never guessed at. - A thing the scene no longer makes is dropped; one it newly makes keeps its fresh state. -- Not kept (comes back fresh): what anybody was in the middle of (AI plans, paths, poses; sitting: they stand), the hose (lies at its tap), sparks and smoke. +- Not kept (comes back fresh): what anybody was in the middle of (AI plans, paths, poses), sparks and smoke. Kept specially: the hose's rope (`rope` line: every point, melted, taped), who sits where (`sit` line: sat down again, staying a while). ## Versions diff --git a/entropie/Makefile b/entropie/Makefile index 49a6ec8..197ffe1 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 2.18.0 +VERSION := 2.19.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/README.md b/entropie/README.md index 3fa2578..2d9f51a 100644 --- a/entropie/README.md +++ b/entropie/README.md @@ -460,6 +460,8 @@ Hold the left button to keep working. - Felling, they look where the tree will come down first: never onto a house, wall, car, tent, table or the fire (no room anywhere: they leave it). Nearly through, anybody in the way hears "Timber!" and steps aside; they wait for you +- You felling: the hint line says where it comes down and what's in the way + there; nearly through, the others step out of the way - Using or taking something shows a short line about it Logs weigh what their wood weighs. Click one to hold on, call helpers and diff --git a/entropie/STATE.md b/entropie/STATE.md index f940ffe..22cd0f8 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -272,6 +272,10 @@ Round 2.18: - Menus (`main.c`): a start menu over the waiting world (Continue: the autosave, New game, Load a save, Quit); Esc: Resume, Save as (`Platform_typing`, `Input.text` / `erase`: letters, digits, - _), Quick save, Load a save, Keys, Quit (autosaves). F5 quick save, F9 quick load. Autosave every AUTOSAVE_EVERY 300 s of play and on quit / closing the window. Folder: `Platform_saveDir` (SDL_GetPrefPath + "saves"). - Tested in the harness: a save written and read back (the same process, and another one): people, pockets, a tent half taken down, crates, rain, the wet ground, a pullover put down, a felled tree with fewer branches; under AddressSanitizer. The menus themselves aren't run here (no SDL library to link): compiled with all warnings only. +Round 2.19: +- Saves: the hose's rope (`rope` length + every node: where, melted, taped; was laid out fresh from the tap) and the nozzle where it was (OBJ_HOSE no longer "fixed"); people sitting (`sit` seat + facing) sit down again on loading and stay 20-35 s (they stood up and walked off at once). +- Felling, you (`game_actions.c` fellingHint, before the ladder hint): an axe or the chainsaw at a standing tree: the hint line says it falls away from you, or "Careful: it would come down on the car - fell it from another side" (`Ai_fallsOnto`: the felling-room rays, the first thing hit). Over 0.6 through and somebody where it'll come down: "Timber! ..." and they step aside (`Ai_warnFellingRoom`, now with the way it falls; NPC fellers use their plan). + 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). @@ -413,7 +417,7 @@ 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: trash jobs; walking a heavy piece round tight corners; the felling room when you fell (a hint where it'll come down) +- The work place, still to do: trash jobs; walking a heavy piece round tight corners - Ladders, still to do: a holder for a ladder standing free; NPCs taking a ladder somewhere for their own work - Smoking, still to do: paying for a pack (no money in the game yet) - "No shirt no service" policy (decision open) @@ -422,7 +426,7 @@ Most important first. - Weather, still to do: a sound for thunder, wind and rain (no sound in the game yet); a hat or anything held blown out of the hands **Saves, still to do** -- The hose (its rope) and things being done (sitting, a job half way, a ladder being climbed) come back fresh, not as they were +- Things being done (a job half way, a ladder being climbed, carrying something together) come back fresh, not as they were **Tools for us** - Optional local AI (llama.cpp, small model) for talking and tasks; the menu stays as fallback @@ -431,4 +435,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.18.0 (build a8a2692)` +State of `entropie 2.19.0 (build 04c05e7)` diff --git a/entropie/src/game/game_actions.c b/entropie/src/game/game_actions.c index de3c32d..d7d14cd 100644 --- a/entropie/src/game/game_actions.c +++ b/entropie/src/game/game_actions.c @@ -984,6 +984,40 @@ static const Object *askingYou(const Game *game) { return NULL; } +/* What a tree would come down on (for the hint line). */ +static const char *thingName(const Object *o) { + switch (o->type) { + case OBJ_HOUSE: return "house"; + case OBJ_BARN: return "barn"; + case OBJ_STRUCTURE: case OBJ_WALL: return "building"; + case OBJ_CAR: return "car"; + case OBJ_TENT: return "tent"; + case OBJ_FIREPLACE: return "fire"; + case OBJ_WOODPILE: return "woodpile"; + case OBJ_FURNITURE: return o->variant == FURN_BENCH ? "bench" : o->variant == FURN_TABLE ? "table" : o->variant == FURN_CHAIR ? "chair" : "furniture"; + default: return "something"; + } +} + +/* At a tree with an axe or the chainsaw: where it'll come down (away from + you), what's in the way there, and nearly through: everybody out of it. */ +static bool fellingHint(Game *game, char *out, size_t size) { + Object *p = game->player; + ToolKind tool = Tools_held(p, NULL); + if (tool != TOOL_AXE && tool != TOOL_CHAINSAW) return false; + Object *tree = Tools_target(p, game->world.objects, game->world.count); + if (!tree || tree->type != OBJ_TREE || tree->toppled) return false; + Vec3 dir = vec3_sub(tree->position, p->position); + dir.y = 0.0f; + if (vec3_length(dir) < 1e-3f) return false; + const Object *onto = Ai_fallsOnto(tree, dir); + bool someone = Tools_fellDone(tree) > 0.6f && Ai_warnFellingRoom(p, game->world.objects, game->world.count, tree, dir); + if (someone) snprintf(out, size, "Timber! Somebody's where it'll come down - wait till they're out of the way"); + else if (onto) snprintf(out, size, "Careful: it would come down on the %s - fell it from another side", thingName(onto)); + else snprintf(out, size, "It falls away from you - clear there"); + return true; +} + const char *Game_hint(Game *game) { const Object *shown = follower(game, SHOW_PLACE), *watcher = follower(game, SHOW_HOW), *ladder, *food, *asking; Object *fire = NULL; @@ -1010,6 +1044,7 @@ const char *Game_hint(Game *game) { } else if (fire && fire->burning && Hands_bottleHand(game->player) >= 0 && game->player->hands.item[Hands_bottleHand(game->player)]->sips > 0) snprintf(game->hint, sizeof(game->hint), "The fire - C: pour your drink on it"); + else if (fellingHint(game, game->hint, sizeof(game->hint))) {} else if (!game->player->on_ladder && !game->player->climbing && (ladder = Ladder_near(&game->world, game->player, &at_top))) snprintf(game->hint, sizeof(game->hint), at_top ? "Ladder: left click to climb down" : "Ladder: left click to climb - E / Q or right click: take it down"); diff --git a/entropie/src/game/save.c b/entropie/src/game/save.c index 3975626..be17474 100644 --- a/entropie/src/game/save.c +++ b/entropie/src/game/save.c @@ -9,6 +9,7 @@ #include "world/weather.h" #include "world/wetness.h" #include "people/hands.h" +#include "world/hose.h" #include "build_info.h" #include <dirent.h> @@ -83,7 +84,7 @@ static const Field PERSON[] = { static bool fixedThing(const Object *o) { switch (o->type) { case OBJ_HOUSE: case OBJ_BARN: case OBJ_WALL: case OBJ_STRUCTURE: case OBJ_DECAL: case OBJ_BUSH: case OBJ_BIN: - case OBJ_BOX_STATIC: case OBJ_CYLINDER_STATIC: case OBJ_SUPPLY: case OBJ_HOSE: + case OBJ_BOX_STATIC: case OBJ_CYLINDER_STATIC: case OBJ_SUPPLY: return o->scene_id > 0; default: return false; @@ -202,11 +203,22 @@ bool Save_write(const Game *game, const char *dir, const char *name, char *err, fprintf(f, "\n"); if (o->keys) fprintf(f, "keys %s\n", o->keys); if (o->body.asleep) fprintf(f, "asleep 1\n"); + if (o->sitting && !o->riding) /* (where they sit: on what, facing where) */ + fprintf(f, "sit %d %.9g %.9g %.9g\n", idOf(&ids, o->seat), (double)o->sit_dir.x, (double)o->sit_dir.y, (double)o->sit_dir.z); for (int h = 0; h < 2; h++) if (o->hands.item[h] && o->hands.state[h] == HAND_HOLDING) fprintf(f, "hand %d %d\n", h, idOf(&ids, o->hands.item[h])); } else { writeFields(f, o, THING, COUNT(THING), &ids); if (o->cloth_where) fprintf(f, "where %s\n", o->cloth_where); + if (o->type == OBJ_HOSE) { /* its rope: where every bit lies, melted, taped */ + const HoseRope *rope = Hose_of((World *)w, o); + if (rope && rope->ready) { + fprintf(f, "rope %.9g %d", (double)rope->length, HOSE_NODES); + for (int k = 0; k < HOSE_NODES; k++) + fprintf(f, " %.6g %.6g %.6g %.4g %d", (double)rope->p[k].x, (double)rope->p[k].y, (double)rope->p[k].z, (double)rope->melt[k], rope->taped[k]); + fprintf(f, "\n"); + } + } if (o->type == OBJ_CAR && o->load) { fprintf(f, "load %d", o->load->n); for (int k = 0; k < o->load->n; k++) fprintf(f, " %d %d", o->load->kind[k], idOf(&ids, o->load->owner[k])); @@ -496,6 +508,21 @@ bool Save_read(Game *game, const char *dir, const char *name, char *err, size_t else if (!strcmp(key, "keys")) o->keys = keysText(s); else if (!strcmp(key, "where")) o->cloth_where = Clothes_whereText(s); else if (!strcmp(key, "name")) snprintf(o->name, sizeof o->name, "%s", s); + else if (!strcmp(key, "rope") && o->type == OBJ_HOSE) { + HoseRope *rope = Hose_of(w, o); + float len = numF(&s); + int n = numI(&s); + if (rope && n == HOSE_NODES) { + rope->length = len; + for (int q = 0; q < HOSE_NODES; q++) { + rope->p[q] = rope->prev[q] = numV(&s); + rope->melt[q] = numF(&s); + rope->taped[q] = numI(&s) != 0; + rope->heat[q] = 0.0f; + } + rope->ready = true; + } + } else if (!strcmp(key, "load") && o->type == OBJ_CAR && o->load) { int n = numI(&s); o->load->n = 0; @@ -550,12 +577,15 @@ bool Save_read(Game *game, const char *dir, const char *name, char *err, size_t for (int q = 0; q < AI_SKIPS; q++) o->ai.skip[q] = NULL; if (o->ai.mode != AI_IDLE && o->ai.mode != AI_WANDER && o->ai.mode != AI_WORK) o->ai.mode = AI_IDLE; Object_syncColliders(o); - bool asleep = false; + bool asleep = false, sits = false; + Object *seat = NULL; + Vec3 sit_dir = vec3(0.0f, 0.0f, 1.0f); for (int i = r->first; i < r->first + r->lines; i++) { char buf[256], *s = buf; snprintf(buf, sizeof buf, "%s", p.line[i]); char *key = word(&s); if (!strcmp(key, "asleep")) asleep = true; + else if (!strcmp(key, "sit")) { sits = true; seat = byId(&p, numI(&s)); sit_dir = numV(&s); } else if (!strcmp(key, "hand")) { int h = numI(&s); Object *item = byId(&p, numI(&s)); @@ -575,6 +605,8 @@ bool Save_read(Game *game, const char *dir, const char *name, char *err, size_t o->in_tent = NULL; Tent_crawlIn(tent, o); } else if (asleep) Body_lieDown(&o->body); + else if (sits && Object_sit(o, seat, vec3_add(o->position, vec3_scale(sit_dir, 3.0f)))) /* (sat down again where they sat ... */ + o->ai.timer = 20.0f + 0.37f * (float)(k % 41); /* ... and stay a while) */ } for (int k = 0; k < p.recs; k++) /* (pullovers last: they need their owners' look) */ if (p.rec[k].obj && p.rec[k].obj->type == OBJ_CLOTHES) Clothes_rebuild(p.rec[k].obj); diff --git a/entropie/src/people/ai.h b/entropie/src/people/ai.h index c506779..50d3789 100644 --- a/entropie/src/people/ai.h +++ b/entropie/src/people/ai.h @@ -255,6 +255,10 @@ bool Ai_resumePaused(struct Object *self); void Ai_say(struct Object *self, const char *text); /* Starts working on an order around area (they must know how). */ void Ai_order(struct Object *self, Order order, Vec3 area); +/* A tree felled falling that way: the first thing it would come down on (a house, a car, a tent ...), or NULL. */ +const struct Object *Ai_fallsOnto(const struct Object *tree, Vec3 dir); +/* Somebody where it'll come down (falling that way): told to get out (they step aside). True if anybody is there. */ +bool Ai_warnFellingRoom(struct Object *feller, struct Object *const *objects, int count, struct Object *tree, Vec3 dir); void Ai_stopWork(struct Object *self); /* Follows the player to be shown how to do the pending order, or where the woodpile goes. */ void Ai_show(struct Object *self, Showing what, const struct Object *player); diff --git a/entropie/src/people/ai_work.c b/entropie/src/people/ai_work.c index e2921a6..9f9a350 100644 --- a/entropie/src/people/ai_work.c +++ b/entropie/src/people/ai_work.c @@ -38,8 +38,9 @@ static bool notInTheWay(const Object *o, const void *tree) { } } -/* What it would come down on, falling that way (counted). */ -static int fallHits(const Object *tree, Vec3 dir) { +/* What it would come down on, falling that way (counted; the first in *first). */ +static int fallHits(const Object *tree, Vec3 dir, Object **first) { + if (first) *first = NULL; if (!g_world) return 0; Vec3 side = vec3(-dir.z, 0.0f, dir.x); float tall = treeTall(tree), r = crownR(tree); @@ -49,7 +50,11 @@ static int fallHits(const Object *tree, Vec3 dir) { float w = (along > tall * 0.45f ? r : 0.4f) * (float)s * 0.8f; /* (the trunk narrow, the crown wide) */ Vec3 p = vec3_add(tree->position, vec3_add(vec3_scale(dir, along), vec3_scale(side, w))); p.y = tree->position.y + 14.0f; - if (World_rayCast(g_world, p, vec3(0.0f, -1.0f, 0.0f), 14.5f, notInTheWay, tree, NULL, NULL) >= 0.0f) hits++; + Object *hit = NULL; + if (World_rayCast(g_world, p, vec3(0.0f, -1.0f, 0.0f), 14.5f, notInTheWay, tree, &hit, NULL) >= 0.0f) { + hits++; + if (first && !*first) *first = hit; + } } return hits; } @@ -63,7 +68,7 @@ static Vec3 fellPlan(Object *tree, Vec3 from) { for (int k = 0; k < FELL_DIRS; k++) { /* (nearest first: 0, +30, -30, +60 ...) */ float a = (float)((k + 1) / 2) * (2.0f * PI_F / (float)FELL_DIRS) * (k % 2 ? 1.0f : -1.0f); Vec3 d = vec3(want.x * cosf(a) - want.z * sinf(a), 0.0f, want.x * sinf(a) + want.z * cosf(a)); - int hits = fallHits(tree, d); + int hits = fallHits(tree, d, NULL); if (hits < best_hits) { best_hits = hits; best = d; } if (hits == 0) break; } @@ -72,9 +77,17 @@ static Vec3 fellPlan(Object *tree, Vec3 from) { return best; } +const Object *Ai_fallsOnto(const Object *tree, Vec3 dir) { + Object *first = NULL; + dir = vec3_normalize(vec3(dir.x, 0.0f, dir.z)); + fallHits(tree, dir, &first); + return first; +} + /* Anybody standing where it'll come down: warned off (they step out sideways). */ -static bool clearFellingRoom(Object *self, Object *const *objects, int count, Object *tree) { - Vec3 dir = tree->fell_plan, side = vec3(-dir.z, 0.0f, dir.x); +bool Ai_warnFellingRoom(Object *self, Object *const *objects, int count, Object *tree, Vec3 dir) { + dir = vec3_normalize(vec3(dir.x, 0.0f, dir.z)); + Vec3 side = vec3(-dir.z, 0.0f, dir.x); float tall = treeTall(tree) + 1.5f, r = crownR(tree) + 0.6f; bool told = false, anybody = false; for (int i = 0; i < count; i++) { @@ -84,7 +97,7 @@ static bool clearFellingRoom(Object *self, Object *const *objects, int count, Ob float along = vec3_dot(rel, dir), across = vec3_dot(rel, side); if (along < -0.5f || along > tall || fabsf(across) > r) continue; anybody = true; - if (!told && self->ai.speech_time <= 0.0f) { + if (!told && self->type != OBJ_PLAYER && self->ai.speech_time <= 0.0f) { Ai_say(self, o->type == OBJ_PLAYER ? "Timber! Get out of the way - it's coming down there!" : "Timber! Out of the way!"); told = true; } @@ -1341,7 +1354,7 @@ void ai_updateWork(Object *self, Object *const *objects, int count, float dt) { skipTarget(ai, t.target); return; } - ai->timber = Tools_fellDone(t.target) > FELL_WARN && !Tools_isWorking(self) && clearFellingRoom(self, objects, count, t.target); + ai->timber = Tools_fellDone(t.target) > FELL_WARN && !Tools_isWorking(self) && Ai_warnFellingRoom(self, objects, count, t.target, t.target->fell_plan); if (ai->timber) { stand(self); return; } /* (not the last strokes with somebody under it) */ } if (prepareSpot(self, objects, count, &t, dt)) return;