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/src/game/game_bigtree.c raw
/*
* The big old tree (world/bigtree.h): what the game does around Enrico's work
* (ai_jobs.c bigTree) and the pullers (ai_onRope).
*
* - The throw line: lying at the tree's foot. Thrown (T, wound up, looking up)
* so it touches the crown, it's caught over a branch where it touched: the
* higher, the better it pulls. Left click at the rope's end: pulled down
* again to try once more (before the pulling starts).
* - The pulling: when the notch is in, the crew nearest are called to the
* rope's end (four at most); you can take hold of it too (left click near
* the end). Enrico cuts once three or more are on it.
* - Which way it goes: the pull (how many, how strong, how high the rope) and
* the notch against the lean (towards the road) and a gust. Way off, it
* comes down across the road onto the phone line: torn, a bill from the
* phone company, Kurti cross. Anybody it comes down on: injured (Forestry).
*/
#include <math.h>
#include "game/game_internal.h"
#include "world/bigtree.h"
#include "world/cast.h"
#include "world/forestry.h"
#include "world/injury.h"
#include "world/weather.h"
#define LINE_BILL 1500.0f /* EUR: the phone company puts the line back up */
#define PULL_EACH 0.35f /* each on the rope (x how strong: fitness) ... */
#define NOTCH_PULL 0.45f /* ... the notch steering it too ... */
#define LEAN 1.15f /* ... against the lean, and a gust on top */
#define GUST_LEAN 0.8f
#define ROPE_TOP 16.0f /* m: caught higher than this is no better */
#define CALL_NEAR 160.0f /* m: the crew this near get called to the rope */
#define ON_ROPE 3.0f /* m: you near the rope's end: on it (left click) */
#define PLACE_NEAR 1.2f /* m: a puller at their place counts */
#define STORM_WIND 11.0f /* m/s: wind like this can bring the old tree down ... */
#define STORM_FALLS 0.3f /* ... this likely a game hour */
static Object *named(const Game *game, const char *name) {
for (int i = 0; i < game->world.count; i++) {
Object *o = game->world.objects[i];
if (o->is_character && !strcmp(o->name, name)) return o;
}
return NULL;
}
/* ---------- The throw line ---------- */
static void throwLine(Game *game) {
World *w = &game->world;
BigTree *bt = &w->bigtree;
Object *tree = BigTree_tree(w);
if (!tree || tree->toppled || bt->step < BT_ROPE || bt->rope_h > 0.0f || tree->collider_count < 2) return;
for (int i = 0; i < w->count; i++) {
Object *bag = w->objects[i];
if (bag->type != OBJ_ROPE || bag->broken || bag->is_held || bag->is_grounded) continue;
Vec3 c = Object_colliderCenter(tree, 1), half = tree->colliders[1].half_extents;
Vec3 p = bag->position;
if (fabsf(p.x - c.x) > half.x + 0.1f || fabsf(p.z - c.z) > half.z + 0.1f || p.y < c.y - half.y || p.y > c.y + half.y) continue;
bt->rope_h = fminf(p.y - Terrain_height(&w->terrain, tree->position.x, tree->position.z), ROPE_TOP);
bt->rope_h = fmaxf(bt->rope_h, 4.5f);
Object_useUp(bag);
static char line[128];
snprintf(line, sizeof line, "Caught over a branch - %.1f m up!%s", bt->rope_h,
bt->rope_h < 7.0f ? " A bit low (left click the rope's end: pull it down, throw again)" : "");
say(game, line);
game->message_time = 5.0f;
Object *e = named(game, "Enrico");
if (e && !Game_inBerlin(e)) Ai_say(e, bt->rope_h >= 10.0f ? "Yes! Way up there - that pulls like a dream." : bt->rope_h >= 7.0f ? "Good throw! That'll do nicely."
: "Hm, low. It'll hold - but higher pulls better.");
return;
}
}
/* ---------- The rope, as it's seen ---------- */
static void showRope(Game *game) {
World *w = &game->world;
BigTree *bt = &w->bigtree;
int shown = bt->rope_h <= 0.0f || bt->step >= BT_FALLING ? 0 : bt->step >= BT_PULL ? 2 : 1;
int key = shown * 1000 + (int)(bt->rope_h * 10.0f);
if (key == game->rope_shown) return;
game->rope_shown = key;
if (game->rope_look) { Object_useUp(game->rope_look); game->rope_look = NULL; }
if (!shown) return;
Vec3 a = BigTree_attach(w);
Vec3 end = shown == 2 ? BigTree_ropeEnd(w) : vec3_add(BigTree_notchSpot(w), vec3_scale(Site_bigTreeSafe(), 1.0f));
if (shown == 1) end.y = Terrain_height(&w->terrain, end.x, end.z) + 0.02f;
Mesh m = Shapes_rope(vec3_zero(), vec3_sub(end, a), shown == 2 ? 0.3f : 0.0f, shown == 1);
if (!m.vao) return;
Object *look = Object_createStructure(m, a, 0.0f, vec3(BT_ROPE_OUT, 12.0f, BT_ROPE_OUT));
if (look && World_add(w, look)) game->rope_look = look;
}
bool game_bigTreeClick(Game *game) {
World *w = &game->world;
BigTree *bt = &w->bigtree;
Object *p = game->player;
if (bt->rope_h <= 0.0f || bt->step >= BT_FALLING || Hands_freeHand(p) < 0) return false;
if (bt->step < BT_PULL) { /* the rope hanging down by the trunk: pulled down again, to throw it once more */
Vec3 end = vec3_add(BigTree_notchSpot(w), vec3_scale(Site_bigTreeSafe(), 1.0f));
if (hypotf(end.x - p->position.x, end.z - p->position.z) > 2.0f) return false;
bt->rope_h = 0.0f;
bt->rope_wait = 0.0f;
if (bt->step == BT_NOTCH && bt->work <= 0.0f) bt->step = BT_ROPE;
Vec3 at = vec3_add(p->position, vec3_scale(Object_facing(p), 0.6f));
at.y = World_groundHeight(w, at.x, at.z, p->position.y + 1.0f) + 0.02f;
Object *bag = Object_create(OBJ_ROPE, PALETTE_WARM, at, vec3_zero(), 0.0f);
if (bag && !World_add(w, bag)) bag = NULL;
say(game, "You pull the line down again - pick it up and throw it once more");
return true;
}
Vec3 end = BigTree_ropeEnd(w); /* the pulling: hold of the rope's end */
if (hypotf(end.x - p->position.x, end.z - p->position.z) > ON_ROPE + 3.0f) return false;
game->on_rope = !game->on_rope;
say(game, game->on_rope ? "You take hold of the rope - when it goes, you pull (walk off: let go)" : "You let go of the rope");
return true;
}
/* ---------- The pulling ---------- */
static bool canPull(const Object *o) {
return o->type == OBJ_HUMAN && o->cast_id > 0 && strcmp(o->name, "Enrico") && strcmp(o->name, "Kurti") && !o->injured && o->hospital_until <= 0.0f
&& Injury_castKind(o) == INJ_NONE && !Body_isDown(&o->body) && !o->in_tent && o->indoors <= 0.0f && !o->riding && !o->ai.meeting
&& !Game_inBerlin(o) && o->drunk < 0.6f;
}
static void pullers(Game *game, const Object *enrico) {
World *w = &game->world;
BigTree *bt = &w->bigtree;
bool on = ((bt->step == BT_PULL || bt->step == BT_CUT) && enrico && enrico->ai.on_project) || bt->step == BT_FALLING; /* (falling: they hold on till it's down) */
int taken[BT_PULLERS] = { 0 }, n = 0;
for (int i = 0; i < w->count; i++) {
Object *o = w->objects[i];
if (o->puller <= 0.0f) continue;
if (!on || !canPull(o)) { o->puller = 0.0f; continue; }
taken[(int)o->puller - 1] = 1;
if (vec3_length(vec3_sub(o->position, BigTree_pullerSpot(w, (int)o->puller - 1))) < PLACE_NEAR) n++;
}
Object *p = game->player;
if (game->on_rope) {
Vec3 end = BigTree_ropeEnd(w);
if (!on && bt->step != BT_FALLING) game->on_rope = false;
else if (hypotf(end.x - p->position.x, end.z - p->position.z) > ON_ROPE + 3.0f || Body_isDown(&p->body)) { game->on_rope = false; say(game, "You let go of the rope"); }
else {
n++;
p->hands.rope = bt->step == BT_CUT && bt->work >= BT_CUT_GOES - 3.0f ? 1.0f : 0.05f;
p->hands.rope_at = BigTree_ropeAt(w, p->position);
p->hands.rope_dir = vec3_normalize(vec3_sub(BigTree_ropeEnd(w), BigTree_attach(w)));
}
}
if (bt->step != BT_FALLING) bt->pullers = n;
if (!on) return;
for (int k = 0; k < BT_PULLERS; k++) { /* free places: the nearest who can come */
if (taken[k]) continue;
Object *best = NULL;
float best_d = CALL_NEAR;
for (int i = 0; i < w->count; i++) {
Object *o = w->objects[i];
if (o->puller > 0.0f || !canPull(o) || o->ai.mode == AI_FOLLOW || o->ai.talking) continue;
float d = hypotf(o->position.x - Site_bigTree().x, o->position.z - Site_bigTree().z);
if (d < best_d) { best_d = d; best = o; }
}
if (!best) break;
best->puller = (float)(k + 1);
best->ai.on_project = false;
best->ai.mode = AI_IDLE;
Ai_say(best, k == 0 ? "Coming - I'll take the end of the rope!" : "I'm on it!");
}
}
/* ---------- Down it comes ---------- */
static float gauss(World *w) { /* (-1..1, mostly near 0) */
float s = 0.0f;
for (int k = 0; k < 3; k++) { w->rng = w->rng * 1664525u + 1013904223u; s += (float)(w->rng >> 8) / 16777216.0f; }
return (s - 1.5f) / 1.5f;
}
static void fell(Game *game, Object *tree, Object *enrico) {
World *w = &game->world;
BigTree *bt = &w->bigtree;
float pull = 0.0f;
for (int i = 0; i < w->count; i++) {
const Object *o = w->objects[i];
if (o->puller > 0.0f && vec3_length(vec3_sub(o->position, BigTree_pullerSpot(w, (int)o->puller - 1))) < PLACE_NEAR + 0.5f)
pull += PULL_EACH * (0.7f + 0.6f * o->fitness);
}
if (game->on_rope) pull += PULL_EACH * (0.7f + 0.6f * game->player->fitness);
float leverage = clampf(bt->rope_h / 10.0f, 0.3f, 1.15f);
float notch = NOTCH_PULL * (enrico ? 0.6f + 0.4f * enrico->skill[ORDER_TREES] : 0.7f);
float margin = pull * leverage + notch - LEAN - GUST_LEAN * w->gust;
float side = gauss(w) >= 0.0f ? 1.0f : -1.0f;
float off = margin >= 0.3f ? 6.0f * gauss(w)
: margin >= 0.0f ? (6.0f + (0.3f - margin) * 80.0f) * gauss(w)
: side * fminf(180.0f, -margin / 0.5f * 180.0f + 25.0f * fabsf(gauss(w))); /* (not enough: round towards the lean) */
bt->off = off;
Vec3 safe = Site_bigTreeSafe();
float a = off * PI_F / 180.0f;
Vec3 dir = vec3(safe.x * cosf(a) - safe.z * sinf(a), 0.0f, safe.x * sinf(a) + safe.z * cosf(a));
Forestry_fell(w, tree, dir, 0.6f);
for (int i = 0; i < w->count; i++) if (w->objects[i]->puller > 0.0f) w->objects[i]->ai.speech_time = 0.0f;
if (enrico) { /* (out of the way: back and to the side) */
enrico->ai.errand = true;
enrico->ai.errand_there = false;
enrico->ai.errand_to = BigTree_escapeSpot(w, off > 0.0f ? -1.0f : 1.0f); /* (away from the way it's turning) */
enrico->ai.errand_with = NULL;
enrico->carving = false;
Ai_say(enrico, fabsf(off) < 30.0f ? "TIMBEEER!" : "Watch out - it's turning! Get away!");
}
say(game, "TIMBEEER! The big tree comes down");
}
static bool crosses(Vec3 a, Vec3 b, Vec3 c, Vec3 d) { /* (two stretches on the ground cross) */
float d1 = (b.x - a.x) * (c.z - a.z) - (b.z - a.z) * (c.x - a.x), d2 = (b.x - a.x) * (d.z - a.z) - (b.z - a.z) * (d.x - a.x);
float d3 = (d.x - c.x) * (a.z - c.z) - (d.z - c.z) * (a.x - c.x), d4 = (d.x - c.x) * (b.z - c.z) - (d.z - c.z) * (b.x - c.x);
return d1 * d2 < 0.0f && d3 * d4 < 0.0f;
}
static void down(Game *game, Object *tree) {
World *w = &game->world;
BigTree *bt = &w->bigtree;
Vec3 t = Site_bigTree(), tip = vec3_add(t, vec3_scale(tree->fall_dir, 21.0f)), safe = Site_bigTreeSafe();
bt->off = acosf(clampf(vec3_dot(tree->fall_dir, safe), -1.0f, 1.0f)) * 180.0f / PI_F; /* (however it was felled) */
Vec3 pole[4];
int n = Site_phoneLine(pole, 4);
for (int k = 0; k + 1 < n; k++) if (crosses(t, tip, pole[k], pole[k + 1])) bt->lines_down = true;
for (int k = 0; k < n; k++) /* (into a pole) */
if (vec3_length(vec3_cross(vec3_sub(vec3(pole[k].x, 0, pole[k].z), vec3(t.x, 0, t.z)), tree->fall_dir)) < 1.2f
&& vec3_dot(vec3_sub(pole[k], t), tree->fall_dir) > 0.0f && vec3_dot(vec3_sub(pole[k], t), tree->fall_dir) < 21.0f) bt->lines_down = true;
bt->step = BT_DOWN;
bt->told = true;
for (int i = 0; i < w->count; i++) {
Object *o = w->objects[i];
if (o->puller > 0.0f) o->puller = 0.0f;
if (o->is_character && o->cast_id > 0 && !strcmp(o->name, "Enrico")) o->ai.errand = false;
}
game->on_rope = false;
static char line[192];
Object *kurti = named(game, "Kurti");
if (bt->lines_down) {
Site_phoneWiresDown(w, true);
w->cash -= LINE_BILL;
for (int i = 0; i < w->count; i++) {
Object *o = w->objects[i];
if (o->is_character && o->cast_id > 0) o->commitment = clampf(o->commitment - (o == kurti ? 0.25f : 0.05f), 0.0f, 1.0f);
}
if (kurti) Ai_say(kurti, "My phone line! Forty years that line stood. Who pays for that?!");
snprintf(line, sizeof line, "It came down across the road - right through the phone line. The phone company puts it back up: %d EUR from the cash box",
(int)LINE_BILL);
} else {
for (int i = 0; i < w->count; i++) {
Object *o = w->objects[i];
if (o->is_character && o->cast_id > 0) o->commitment = fminf(1.0f, o->commitment + (!strcmp(o->name, "Enrico") ? 0.1f : 0.04f));
}
if (kurti && fabsf(bt->off) < 40.0f) Ai_say(kurti, "Hah! Right where you said. Well pulled, all of you.");
snprintf(line, sizeof line, fabsf(bt->off) < 15.0f ? "Down - right where it should be. Well pulled!" : "Down - %.0f degrees off, but clear of the phone line. Phew.",
fabsf(bt->off));
}
say(game, line);
game->message_time = 8.0f;
}
/* A tree felled that wasn't to be: the fine from the cash box (forestry.c counted it). */
static void treeFine(Game *game) {
World *w = &game->world;
if (w->tree_fine <= 0) return;
w->cash -= (float)w->tree_fine;
Object *kurti = named(game, "Kurti");
static char line[160];
if (w->tree_fine_kind == 1) {
if (kurti) { kurti->commitment = fmaxf(0.0f, kurti->commitment - 0.3f); Ai_say(kurti, "My apple tree! My father planted that one. Unbelievable."); }
snprintf(line, sizeof line, "One of Kurti's apple trees - %d EUR from the cash box for a new one, and Kurti's furious", w->tree_fine);
} else snprintf(line, sizeof line, "That tree was protected - the town's fine: %d EUR from the cash box", w->tree_fine);
say(game, line);
game->message_time = 6.0f;
w->tree_fine = 0;
}
void game_bigTree(Game *game, float dt) {
World *w = &game->world;
BigTree *bt = &w->bigtree;
(void)dt;
treeFine(game);
if (bt->lines_down != game->wires_shown) { Site_phoneWiresDown(w, bt->lines_down); game->wires_shown = bt->lines_down; }
Object *tree = BigTree_tree(w);
if (!tree) return;
Object *enrico = named(game, "Enrico");
throwLine(game);
pullers(game, enrico);
if (tree->toppled && bt->step < BT_FALLING) { /* (felled some other way: by you without the rope, or the storm) */
bt->step = BT_FALLING;
for (int i = 0; i < w->count; i++) w->objects[i]->puller = 0.0f;
}
if (!tree->toppled && bt->step < BT_CUT && Weather_windSpeed(w) > STORM_WIND) { /* old and half dead: a storm can bring it down */
w->rng = w->rng * 1664525u + 1013904223u;
if ((float)(w->rng >> 8) / 16777216.0f < STORM_FALLS * w->game_dt / 3600.0f) {
Forestry_fell(w, tree, Site_bigTreeLean(), 0.3f);
say(game, "CRACK! The big old tree gives way in the storm - it's coming down!");
game->message_time = 6.0f;
}
}
if (bt->step == BT_FALLING && !tree->toppled) fell(game, tree, enrico);
if (bt->step == BT_FALLING && tree->toppled && tree->topple >= 1.0f) down(game, tree);
showRope(game);
}
const char *game_bigTreeStatus(const Game *game) {
const BigTree *bt = &game->world.bigtree;
static char text[96];
switch (bt->step) {
case BT_STANDING: return "standing (Enrico hasn't started)";
case BT_ROPE: return "the rope up (throw the line from its foot)";
case BT_NOTCH: snprintf(text, sizeof text, "the notch, %d%% (rope %.1f m up)", (int)(100.0f * bt->work / BT_NOTCH_WORK), bt->rope_h); return text;
case BT_PULL: snprintf(text, sizeof text, "on the rope: %d of 3 (rope %.1f m up)", bt->pullers, bt->rope_h); return text;
case BT_CUT: case BT_FALLING: return "coming down!";
default: return bt->lines_down ? "down - and the phone line too" : "down, the phone line fine";
}
}
/* Dev: on to the next step (Enrico here and at it; the rope up; the notch in). */
void game_bigTreeJump(Game *game) {
World *w = &game->world;
BigTree *bt = &w->bigtree;
Object *e = named(game, "Enrico");
if (!e) {
for (int id = 1; id < Cast_count() && !e; id++)
if (!strcmp(Cast_get(id)->name, "Enrico")) {
Vec3 at = BigTree_notchSpot(w);
at.x += 2.0f;
e = Cast_create(id, at);
if (e && !World_add(w, e)) e = NULL;
}
}
if (e && Game_inBerlin(e)) return;
if (e) { e->ai.on_project = true; e->ai.project_off = 0.0f; e->ai.greet = false; }
if (bt->step == BT_STANDING) return; /* (he starts it: the throw line put down) */
if (bt->step == BT_ROPE && bt->rope_h <= 0.0f) {
for (int i = 0; i < w->count; i++) if (w->objects[i]->type == OBJ_ROPE && !w->objects[i]->broken) Object_useUp(w->objects[i]);
bt->rope_h = 10.0f;
return;
}
if (bt->step == BT_NOTCH) bt->work = BT_NOTCH_WORK;
}