/* * 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 #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; }