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.56.0: careful people put tools back, practised ones cut even
- Done with a tool, careful people take it back to where it belongs (the tool room); careless ones leave it lying where they stopped - The practised cut even pieces; the unpractised by eye (up to 0.2 m off) Includes 1.55.0 (if not committed separately): - Tools people shouldn't use (chainsaw unpractised, axe or saw drunk): used anyway with close calls; "Leave the chainsaw alone" in talk; proud, unwilling or drunk people may not listen
entropie/Makefile | 2 +- entropie/README.md | 7 ++++ entropie/STATE.md | 9 +++-- entropie/commit_message.txt | 17 ++++----- entropie/src/people/ai.c | 2 + entropie/src/people/ai.h | 11 ++++++ entropie/src/people/ai_help.c | 42 ++++++++++++++++++++- entropie/src/people/ai_internal.h | 1 + entropie/src/people/ai_work.c | 77 ++++++++++++++++++++++++++++++++++++++- entropie/src/people/talk.c | 26 +++++++++++++ entropie/src/world/object.h | 2 + entropie/src/world/world.c | 5 +++ 12 files changed, 186 insertions(+), 15 deletions(-) diff --git a/entropie/Makefile b/entropie/Makefile index 9b30905..49be6ab 100644 --- a/entropie/Makefile +++ b/entropie/Makefile @@ -1,5 +1,5 @@ APP := entropie -VERSION := 1.54.0 +VERSION := 1.56.0 PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin diff --git a/entropie/README.md b/entropie/README.md index a1f5f35..54ecc99 100644 --- a/entropie/README.md +++ b/entropie/README.md @@ -358,6 +358,13 @@ say no more often and don't come to you for a bit. or drunk ones just throw it on, and a high loose pile comes down now and then If you keep the axe, the bold ones ask for it, the others wait until you put it down +- The practised cut even pieces, the others by eye. Careful people take + the tools back to the tool room when they're done; the others leave them + lying where they stopped +- Tools they shouldn't use (the chainsaw without practice, the axe or saw + drunk) they use anyway, with a remark and now and then a close call. Talk + to them: "Leave the chainsaw alone" - proud, unwilling or drunk ones may + not listen - Showing where the woodpile goes: they follow you, a glowing frame on the ground shows the spot, and a left click puts the woodpile there - Trash: they take a garbage bag, bag piece after piece and empty full bags diff --git a/entropie/STATE.md b/entropie/STATE.md index 671d1b0..0579678 100644 --- a/entropie/STATE.md +++ b/entropie/STATE.md @@ -39,6 +39,10 @@ Worn out: "worn out" shows until energy is back above 30%; no running until then ## Behaviours (people on their own) +Taking care: the practised cut even pieces (0.6 m), the unpractised by eye, up to 0.2 m off (firewood skill; `ai_work.c` ai_taskFor). Done with a tool (idle, wandering), careful people (`ai_stackCare` 0.5 and up) take it back to where it was at the start (`has_home`, the tool room, up to 80 m), the others put it down where they are (`ai_help.c` ai_putAway). + +Tools they shouldn't use (`ai_work.c`, Ai_shouldntUse): the chainsaw with tree skill under 0.4, the axe or chainsaw drunk over 0.6. They use them anyway: a remark when they start, a close call (a stagger, "it kicked back") about every 8 s of work with it. Talking to a worker on a wood job: "Leave the <tool in hand, else chainsaw> alone" / "You can use the ... again". Told, they put it down and take another tool; they ignore it with a chance of 0.1 + 0.45 pride + 0.5 (0.5 - willing) + 0.4 drunk ("Pfft. I know what I'm doing"). + | Mode | What they do | |---|---| | Idle / wander | stand about (fidgets, gestures), stroll near home | @@ -134,8 +138,7 @@ Most important first. - More gestures and feelings to pick personalities from (a list to choose from) **People** -- Tools they shouldn't use: they use them anyway, unless told (the ignorant don't listen) -- The skilled take care: clean tools and area, sort wood by length, cut even pieces +- The skilled take care, still to do: sort wood by length on the pile, sweep up chips and twigs - Peeing properly: open the fly; women crouch and pull down / lift the skirt or dress; Urinella (some don't like it; mishaps; tidy, careless or high disposal) - Clothing (trousers, skirt, dress, fly) – needed for the above @@ -151,4 +154,4 @@ Most important first. - The walking map rebuild takes ~9 ms when fixed things change (a felled tree) --- -State of `entropie 1.54.0 (build 5b985f0)` +State of `entropie 1.56.0 (build efaf3bb)` diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt index 61700eb..e66251c 100644 --- a/entropie/commit_message.txt +++ b/entropie/commit_message.txt @@ -1,11 +1,10 @@ -entropie 1.54.0: no more elbow jumps folding the arms on an edge +entropie 1.56.0: careful people put tools back, practised ones cut even -- Limbs between folded and stretched blend the two fits (no jump at the - switch): elbows on an edge popped up to 31 cm, now none -- An elbow swung up over an edge stays solved that way, folded or not -- Just taken hold of an edge: the elbow eases back in, the hand stays +- Done with a tool, careful people take it back to where it belongs (the + tool room); careless ones leave it lying where they stopped +- The practised cut even pieces; the unpractised by eye (up to 0.2 m off) -Includes 1.53.0 (if not committed separately): -- Climbing: legs stay out of what you climb (the 0.8 m walk-in climb no - longer puts the thigh into it); feet keep room for the knee at walls -- Raised ladder aimed too low: leans as usual instead of refusing +Includes 1.55.0 (if not committed separately): +- Tools people shouldn't use (chainsaw unpractised, axe or saw drunk): + used anyway with close calls; "Leave the chainsaw alone" in talk; proud, + unwilling or drunk people may not listen diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c index 6dcfcad..a33fd88 100644 --- a/entropie/src/people/ai.c +++ b/entropie/src/people/ai.c @@ -1150,6 +1150,8 @@ void Ai_update(Object *self, Object *const *objects, int count, float dt) { stand(self); } } + /* Done with a tool: careful people put it back where it belongs, the others leave it lying */ + if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && ai_putAway(self, objects, count, dt)) return; /* Tidy people pick things up on the way */ if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER || (ai->mode == AI_HELP && !ai->pushing && !ai->copied) || ai->mode == AI_SUPPLY || ai->mode == AI_FETCH) && ai_tidyUp(self, objects, count, dt)) diff --git a/entropie/src/people/ai.h b/entropie/src/people/ai.h index f596e17..bc5f7b8 100644 --- a/entropie/src/people/ai.h +++ b/entropie/src/people/ai.h @@ -160,6 +160,10 @@ typedef struct { bool tool_asked; /* asked the player for their axe (once per order) */ float tidy_pause; /* bending down for trash on the way (seconds left) */ bool tool_waiting; /* waiting for the player to put the axe down */ + unsigned char banned; /* tools (1 << ToolKind) they were told to leave alone ... */ + unsigned char defies; /* ... and those they use anyway ("I know what I'm doing") */ + unsigned char warned; /* tools they already said something about (using one they shouldn't) */ + float risky; /* seconds until the next close call with a tool they shouldn't use */ float speech_time; float sniff_cooldown; /* until the next remark about a smell */ AiMode before; /* mode to go back to after being shown something */ @@ -194,6 +198,13 @@ void Ai_setWoodpile(struct Object *self, struct Object *woodpile); /* In a conversation or not; done = it ended (no new chat for a while). */ void Ai_setTalking(struct Object *self, bool talking); +/* Tools: a tool someone shouldn't use (the chainsaw unpractised, the axe or + the saw drunk) they use anyway, unless told not to - and the ignorant + don't listen. tool: a ToolKind. Ai_banTool: true = they'll leave it. */ +bool Ai_shouldntUse(const struct Object *self, int tool); +bool Ai_banTool(struct Object *self, int tool, bool ban); +bool Ai_mayUse(const struct Object *self, int tool); + /* Time of day (hours) for everybody's plans. */ void Ai_setHour(float hour); /* Does this job need doing right now (not much firewood, trash lying around, dark and no fire)? */ diff --git a/entropie/src/people/ai_help.c b/entropie/src/people/ai_help.c index ebd2278..de18de0 100644 --- a/entropie/src/people/ai_help.c +++ b/entropie/src/people/ai_help.c @@ -110,7 +110,7 @@ Object *ai_findTool(const Object *self, Object *const *objects, int count, Job j float best_dist = 60.0f; /* (the tool room may be a walk away) */ for (int i = 0; i < count; i++) { Object *o = objects[i]; - if (o->is_held || !Tools_canDo(toolOf(o), job)) continue; + if (o->is_held || !Tools_canDo(toolOf(o), job) || !Ai_mayUse(self, toolOf(o))) continue; if (ai_skipped(&self->ai, o)) continue; /* couldn't get at it */ float d = flatDistance(o->position, self->position); if (d < best_dist) { @@ -261,6 +261,46 @@ bool ai_tidyUp(Object *self, Object *const *objects, int count, float dt) { return false; } +#define PUT_AWAY_CARE 0.5f /* this careful (see ai_stackCare): tools go back where they belong */ +#define PUT_AWAY_FAR 80.0f /* ... unless that's further than this */ + +/* Holding a tool with nothing to do with it: careful people take it back to + its place (the tool room), the others put it down where they are. */ +bool ai_putAway(Object *self, Object *const *objects, int count, float dt) { + Ai *ai = &self->ai; + int hand = -1; + for (int h = 0; h < 2; h++) { + const Object *it = self->hands.item[h]; + if (self->hands.state[h] == HAND_HOLDING && it && it->has_home && + (it->type == OBJ_AXE || it->type == OBJ_SHEARS || it->type == OBJ_CHAINSAW)) hand = h; + } + if (hand < 0 || ai->talking) return false; + Object *tool = self->hands.item[hand]; + float d = flatDistance(tool->home_spot, self->position); + if (ai_stackCare(self) < PUT_AWAY_CARE || d > PUT_AWAY_FAR) { /* down it goes, right here */ + Hands_drop(self, hand); + return false; + } + if (d > 0.7f) { + Vec3 dir = ai_steer(self, tool, objects, count, tool->home_spot); + if (vec3_length(dir) < 0.5f) { + stand(self); + if (isStuck(ai, d, dt) && ai->stuck > 6.0f) Hands_drop(self, hand); /* (can't get there: here then) */ + } else { + ai_walk(self, dir, WALK_SPEED * 0.8f, dt); + } + return true; + } + stand(self); + Hands_drop(self, hand); + tool->position = tool->home_spot; /* (back on its hook, as it was) */ + tool->rotation = tool->home_rot; + tool->velocity = tool->angular_velocity = vec3_zero(); + Object_syncColliders(tool); + if (random01(ai) < 0.3f) Ai_say(self, "Back where it belongs."); + return true; +} + /* ---------- Doing what the leader does ---------- */ /* What sort of thing it is, for "something like that" (0: nothing to copy). */ diff --git a/entropie/src/people/ai_internal.h b/entropie/src/people/ai_internal.h index dffdf23..0fbc962 100644 --- a/entropie/src/people/ai_internal.h +++ b/entropie/src/people/ai_internal.h @@ -87,6 +87,7 @@ float ai_stackCare(const Object *self); void ai_pileSlid(Object *self, int slid); void ai_updateFollow(Object *self, Object *const *objects, int count, float dt); bool ai_tidyUp(Object *self, Object *const *objects, int count, float dt); +bool ai_putAway(Object *self, Object *const *objects, int count, float dt); void ai_updateHelp(Object *self, Object *const *objects, int count, float dt); void ai_ensureReady(Object *self); diff --git a/entropie/src/people/ai_work.c b/entropie/src/people/ai_work.c index 0316c9b..b601b44 100644 --- a/entropie/src/people/ai_work.c +++ b/entropie/src/people/ai_work.c @@ -7,6 +7,8 @@ /* ---------- Wood work ---------- */ +#define UNEVEN_CUT 0.2f /* m a piece may come out longer or shorter, cut by someone unpractised */ + /* Where to stand for a job on o, and what to face. */ WoodTask ai_taskFor(const Object *self, Object *o, Job job) { WoodTask t = { .job = job, .target = o }; @@ -34,6 +36,12 @@ WoodTask ai_taskFor(const Object *self, Object *o, Job job) { u = vec3_length(u) > 1e-3f ? vec3_normalize(u) : vec3(1.0f, 0.0f, 0.0f); Vec3 v = vec3_normalize(vec3_cross(u, UP)); float along = job == JOB_CUT ? Tools_cutPoint(o, self->position) : length * 0.5f; + if (job == JOB_CUT) { /* the practised cut even pieces; the others by eye, some longer, some shorter */ + unsigned h = (unsigned)(int)(o->position.x * 731.0f) * 2654435761u ^ (unsigned)(int)(o->position.z * 557.0f) * 40503u; + float r = (float)((h >> 8) & 1023u) / 1023.0f * 2.0f - 1.0f; + along += r * UNEVEN_CUT * (1.0f - self->skill[ORDER_FIREWOOD]); + along = clampf(along, 0.3f, length - 0.3f); + } Vec3 point = vec3_add(a, vec3_scale(axis, along)); float side = vec3_dot(vec3_sub(self->position, point), v) < 0.0f ? -1.0f : 1.0f; float reach = o->colliders[0].half_extents.x; @@ -86,12 +94,78 @@ bool ai_nextWoodTask(Object *self, Object *const *objects, int count, Vec3 area, return true; } +/* ---------- Tools they shouldn't use ---------- */ + +#define SAW_PRACTICE 0.4f /* tree skill below this: the chainsaw is too much for them */ +#define AXE_DRUNK 0.6f /* this drunk: hands off the axe and the saw */ +#define CLOSE_CALL 8.0f /* about this many seconds of work between close calls with one */ + +bool Ai_shouldntUse(const Object *self, int tool) { + if (tool == TOOL_CHAINSAW && self->skill[ORDER_TREES] < SAW_PRACTICE) return true; + if ((tool == TOOL_AXE || tool == TOOL_CHAINSAW) && self->drunk > AXE_DRUNK) return true; + return false; +} + +bool Ai_mayUse(const Object *self, int tool) { + unsigned bit = 1u << tool; + return !(self->ai.banned & bit) || (self->ai.defies & bit); +} + +/* How likely they ignore being told: proud, unwilling, drunk. */ +static float ignorance(const Object *self) { + return clampf(0.1f + 0.45f * self->temperament.pride + 0.5f * (0.5f - self->ai.character.willing) + + 0.4f * fminf(self->drunk, 1.0f), 0.0f, 0.9f); +} + +bool Ai_banTool(Object *self, int tool, bool ban) { + unsigned bit = 1u << tool; + Ai *ai = &self->ai; + if (!ban) { + ai->banned &= (unsigned char)~bit; + ai->defies &= (unsigned char)~bit; + return true; + } + ai->banned |= (unsigned char)bit; + if (random01(&self->ai) < ignorance(self)) { /* doesn't listen */ + ai->defies |= (unsigned char)bit; + return false; + } + ai->defies &= (unsigned char)~bit; + for (int h = 0; h < 2; h++) /* put it down right here */ + if (self->hands.state[h] == HAND_HOLDING && toolOf(self->hands.item[h]) == (ToolKind)tool) Hands_drop(self, h); + if (ai->tool && toolOf(ai->tool) == (ToolKind)tool) ai->tool = NULL; + return true; +} + +/* Working with a tool they shouldn't use: a word about it first, and now + and then a close call (it kicks back, the axe glances off): a stagger. */ +static void riskyTool(Object *self, ToolKind held, float dt) { + Ai *ai = &self->ai; + if (!Ai_shouldntUse(self, held)) return; + unsigned bit = 1u << held; + if (!(ai->warned & bit)) { + ai->warned |= (unsigned char)bit; + ai->risky = CLOSE_CALL * (0.5f + random01(&self->ai)); + Ai_say(self, self->drunk > AXE_DRUNK ? "I'm fine, I'm fine! Watch this..." + : held == TOOL_CHAINSAW ? "Never used one of these... how hard can it be?" : "Here goes nothing."); + return; + } + if (!Tools_isWorking(self) || (ai->risky -= dt) > 0.0f) return; + ai->risky = CLOSE_CALL * (0.5f + random01(&self->ai)); + Body_stagger(&self->body, vec3_scale(Object_facing(self), -0.8f)); + Ai_say(self, held == TOOL_CHAINSAW ? "Whoa! It kicked back at me!" : "Whoa, that one glanced off!"); +} + /* Holds a tool that can do the job, fetching one if needed. 0 = ready, 1 = busy getting it, -1 = there is none. */ static int getTool(Object *self, Object *const *objects, int count, Job job, float dt) { Ai *ai = &self->ai; int hand; ToolKind held = Tools_held(self, &hand); + if (held >= TOOL_AXE && !Ai_mayUse(self, held) && hand >= 0) { /* told to leave that one: down it goes */ + Hands_drop(self, hand); + return 1; + } bool saw_ok = held != TOOL_CHAINSAW || (hand >= 0 && self->hands.state[1 - hand] == HAND_FREE); if (Tools_canDo(held, job) && saw_ok) return 0; if (held == TOOL_CHAINSAW && Tools_canDo(held, job)) { /* both hands for the saw */ @@ -136,6 +210,7 @@ void ai_workOn(Object *self, Object *const *objects, int count, const WoodTask * int tool = getTool(self, objects, count, t->job, dt); if (tool < 0) { skipTarget(ai, t->target); stand(self); return; } if (tool > 0) return; + riskyTool(self, Tools_held(self, NULL), dt); if (self->hands.state[0] == HAND_REACHING || self->hands.state[1] == HAND_REACHING) { stand(self); return; } float dist = flatDistance(t->spot, self->position); @@ -724,7 +799,7 @@ void ai_updateWork(Object *self, Object *const *objects, int count, float dt) { if (ai->order != ORDER_FIRE && takeBreak(self, objects, count, dt)) return; /* (tending a fire they sit by it anyway) */ if (ai->order == ORDER_TRASH) { workTrash(self, objects, count, dt); return; } if (ai->order == ORDER_FIRE) { workFire(self, objects, count, dt); return; } - if (Tools_isWorking(self)) { stand(self); return; } + if (Tools_isWorking(self)) { riskyTool(self, Tools_held(self, NULL), dt); stand(self); return; } /* Lively people working near the player like company */ Object *player = findPlayer(objects, count); diff --git a/entropie/src/people/talk.c b/entropie/src/people/talk.c index db038ca..1b4ec0a 100644 --- a/entropie/src/people/talk.c +++ b/entropie/src/people/talk.c @@ -1,5 +1,6 @@ #include "people/talk.h" #include "world/camp.h" +#include "world/tools.h" #include <stdio.h> #include <string.h> @@ -198,6 +199,12 @@ static bool handOver(Object *player, Object *npc, Job job) { return Hands_receive(npc, npc->main_hand, tool); } +/* The tool a worker's talk is about: the one in their hand, else the chainsaw. */ +static int talkTool(const Object *npc) { + ToolKind held = Tools_held(npc, NULL); + return held == TOOL_AXE || held == TOOL_CHAINSAW ? (int)held : (int)TOOL_CHAINSAW; +} + static void answer(Talk *talk, World *world, Object *player, int choice) { Object *npc = talk->with; switch (talk->node) { @@ -238,6 +245,17 @@ static void answer(Talk *talk, World *world, Object *player, int choice) { else if (choice == 2 && woodJob(npc->ai.order)) { Ai_show(npc, SHOW_PLACE, player); wrapUp(talk, "Lead the way."); } else if (choice == 2) { Ai_stopWork(npc); go(talk, NODE_TASKS); } else if (choice == 3) { Ai_stopWork(npc); Ai_help(npc, player); wrapUp(talk, "Sure!"); } + else if (choice == 5) { + int tool = talkTool(npc); + if (!(npc->ai.banned & (1u << tool))) { + bool obeys = Ai_banTool(npc, tool, true); + wrapUp(talk, obeys ? (shy(npc) ? "Oh... okay, sorry." : "Okay, I'll leave it.") + : npc->temperament.pride > 0.3f ? "Pfft. I know what I'm doing." : "Yeah, yeah..."); + } else { + Ai_banTool(npc, tool, false); + wrapUp(talk, "Alright!"); + } + } else { Ai_stopWork(npc); wrapUp(talk, "Thanks, I could use one."); } break; case NODE_HOW: @@ -380,5 +398,13 @@ int Talk_choices(const Talk *talk, const char *out[TALK_CHOICES]) { for (int k = 0; k < TALK_CHOICES; k++) if (CHOICES[talk->node][k]) out[n++] = CHOICES[talk->node][k]; if (talk->node == NODE_WORKER && !woodJob(talk->with->ai.order)) out[1] = "Do something else instead"; + if (talk->node == NODE_WORKER && woodJob(talk->with->ai.order) && n < TALK_CHOICES) { /* about the tool they use */ + static char line[64]; + const Object *npc = talk->with; + int tool = talkTool(npc); + snprintf(line, sizeof line, npc->ai.banned & (1u << tool) ? "You can use the %s again" : "Leave the %s alone", + tool == TOOL_CHAINSAW ? "chainsaw" : "axe"); + out[n++] = line; + } return n; } diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h index 0b3ab8a..c979827 100644 --- a/entropie/src/world/object.h +++ b/entropie/src/world/object.h @@ -101,6 +101,8 @@ typedef struct Object { Vec3 position; /* base point (bottom center) */ Vec3 rotation; /* Euler radians, applied Y, X, Z */ Vec3 pivot; /* mesh origin relative to position */ + bool has_home; /* tools: where they belong (the tool room), put back there by careful people */ + Vec3 home_spot, home_rot; Mesh mesh; /* empty for characters, they are drawn as figures */ Collider colliders[OBJECT_MAX_COLLIDERS]; diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c index d266f65..201fffc 100644 --- a/entropie/src/world/world.c +++ b/entropie/src/world/world.c @@ -712,6 +712,11 @@ bool World_add(World *world, Object *obj) { world->objects = grown; world->capacity = cap; } + if (!obj->has_home && (obj->type == OBJ_AXE || obj->type == OBJ_SHEARS || obj->type == OBJ_CHAINSAW)) { + obj->has_home = true; /* (where it is at first is where it belongs) */ + obj->home_spot = obj->position; + obj->home_rot = obj->rotation; + } world->objects[world->count++] = obj; return true; }