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_actions.c raw
/*
* What the player's keys and clicks do: climbing, the ladder, taking,
* using, throwing, talking.
*/
#include "game/game_internal.h"
#include "world/field.h"
#include "world/stage.h"
#include "people/pockets.h"
#include "world/clothes.h"
#include "world/car.h"
#include "world/tent.h"
#include "people/climb.h"
#include <stdlib.h>
#define SUPPLY_REACH 0.8f
#define ONE_HAND_LADDER_KG 5.0f /* up a ladder one-handed with this much in the other hand at most */
#define APPLE_REACH 1.6f /* from the trunk of an apple tree */
#define BAG_INTERVAL 0.35f /* holding the button: the next piece into the bag */
enum { CLICK_NONE, CLICK_WORK, CLICK_DRINK, CLICK_BAG, CLICK_WATER, CLICK_TENT, CLICK_STAGE, CLICK_MOW };
#define BUSH_OVER 1.3f /* bushes lower than this (scaled): you try to get over, and land in it */
#define CLIMB_PUSH 0.3f /* walking against it this long starts the climb */
#define JUST_OUT_OF_REACH 0.35f /* an edge this much higher than the hands get: they still reach for it */
/* Jumping at an edge just too high: the hands reach for it at the top of the jump, and you drop back. */
static void reachForIt(Game *game, Object *p, float s) {
if (p->velocity.y > 0.6f) return; /* (near the top of the jump) */
Vec3 f = Object_facing(p);
float reach = p->position.y + CLIMB_REACH * s;
for (float d = 0.3f * s; d < 0.75f * s; d += 0.05f) {
Vec3 q = vec3_add(p->position, vec3_scale(f, d));
float top = World_groundHeight(&game->world, q.x, q.z, reach + JUST_OUT_OF_REACH * s);
if (top <= p->position.y + 0.3f * s) continue;
if (top <= reach || top > reach + JUST_OUT_OF_REACH * s) return;
Vec3 face = vec3_add(p->position, vec3_scale(f, fmaxf(d - 0.06f * s, 0.2f * s)));
face.y = fminf(reach, top - 0.05f);
Hands_holdEdge(p, face, vec3_normalize(vec3_cross(f, vec3(0.0f, 1.0f, 0.0f))), 0.0f);
if (game->message_time <= 0.0f) say(game, "Just out of reach - your fingertips touch the edge");
return;
}
}
#define BRACE_FROM 0.7f /* walking up to something this high (scaled, above the feet) ... */
#define BRACE_NEAR 0.6f /* ... this close: the hands go onto its edge */
/* Walking up to something you could climb: the hands come up onto its edge
(instead of swinging into it), ready to pull up. */
static void braceOnEdge(Game *game, Object *p, const Input *in, float s, float dt) {
game->brace_hold = fmaxf(0.0f, game->brace_hold - dt); /* (kept up a moment: no hands up, down, up) */
if (game->brace_hold > 0.0f) Hands_holdEdge(p, game->brace_grip, vec3_normalize(vec3_cross(Object_facing(p), vec3(0.0f, 1.0f, 0.0f))), 0.0f);
if (in->move_forward < 0.3f || !p->is_grounded) return;
Vec3 f = Object_facing(p);
float near = (BRACE_NEAR + 0.35f * fminf(p->ground_speed, 4.0f)) * s; /* (quicker: the hands go up sooner) */
for (float d = 0.1f * s; d < near; d += 0.03f) {
Vec3 q = vec3_add(p->position, vec3_scale(f, d));
float h = World_groundHeight(&game->world, q.x, q.z, p->position.y + (CLIMB_REACH + 0.05f) * s) - p->position.y;
if (h < 0.25f * s) continue;
if (h < BRACE_FROM * s || h > CLIMB_REACH * s) return; /* (low: stepped over; too high: nothing to hold) */
Vec3 grip = vec3_add(p->position, vec3_scale(f, d + 0.1f * s));
grip.y = World_groundHeight(&game->world, grip.x, grip.z, p->position.y + (CLIMB_REACH + 0.05f) * s) + 0.01f;
if (grip.y < p->position.y + BRACE_FROM * s) return;
Hands_holdEdge(p, grip, vec3_normalize(vec3_cross(f, vec3(0.0f, 1.0f, 0.0f))), 0.0f);
game->brace_grip = grip;
game->brace_hold = 0.3f;
return;
}
}
/* Pulls you up onto the edge at top (people/climb.c), or says why not. */
static bool startClimb(Game *game, float top, Vec3 probe) {
int how = Climb_start(&game->world, game->player, top, probe);
static const char *const WHY[] = { [CLIMB_NO_HANDS] = "You need both hands free to pull yourself up there",
[CLIMB_NO_STRENGTH] = "Too worn out to pull yourself up",
[CLIMB_NO_GRIP] = "You hang on a moment... and drop again" };
if (how >= CLIMB_NO_HANDS) {
if (game->message_time <= 0.0f) say(game, WHY[how]);
return false;
}
game->climb_push = 0.0f;
say(game, how == CLIMB_HARD ? "You grab the edge and pull yourself up" : "You pull yourself up");
return true;
}
/* Walking (or jumping) against something: as long as the hands reach its
edge you can get up there. */
void game_climb(Game *game, const Input *in, float dt) {
Object *p = game->player;
if (p->on_ladder) return; /* see game_climbLadder */
const float s = p->look.height / 1.75f;
if (p->climbing) { /* pulling up (people/climb.c) */
Climb_update(&game->world, p, dt);
return;
}
if (Body_isBusy(&p->body) || Hands_isGrabbing(p) || p->sitting) { game->climb_push = 0.0f; return; }
braceOnEdge(game, p, in, s, dt);
Vec3 probe;
/* In the air (jumping at it): grab the edge as soon as the hands reach it */
if (!p->is_grounded) {
game->climb_push = 0.0f;
/* (walking at it, or holding Space: trying to grab it) */
if ((in->move_forward < 0.3f && !in->jump) || p->body.air_time < 0.08f) return;
float top = Climb_ledgeAhead(&game->world, p, &probe);
if (top > -1e8f && top > p->position.y + 0.3f * s) { startClimb(game, top, probe); return; }
reachForIt(game, p, s);
return;
}
if (in->move_forward < 0.5f || vec3_length(vec3(p->body.velocity.x, 0.0f, p->body.velocity.z)) > 0.6f) { /* not blocked */
game->climb_push = 0.0f;
return;
}
if ((game->climb_push += dt) < CLIMB_PUSH) return;
/* A bush in the way: over it, or rather into it - down you go, twigs snap */
Object *bush = Camp_inFront(p, game->world.objects, game->world.count, OBJ_BUSH);
if (bush && bush->size.y < BUSH_OVER * s) {
game->climb_push = 0.0f;
int snapped = Forestry_intoBush(&game->world, p, bush);
say(game, snapped ? "You try to step over the bush... and end up in it. Twigs snap" : "You end up in the bush");
return;
}
float top = Climb_ledgeAhead(&game->world, p, &probe);
if (top < -1e8f) {
/* just out of reach: say so (once) */
if (game->climb_push < 1.0f || game->climb_push - dt >= 1.0f) return;
Vec3 f = Object_facing(p), at = vec3_add(p->position, vec3_scale(f, 0.45f * s));
float high = World_groundHeight(&game->world, at.x, at.z, p->position.y + (CLIMB_REACH + 1.0f) * s);
if (high - p->position.y > CLIMB_REACH * s && World_groundHeight(&game->world, at.x, at.z, high + 1.8f * s) <= high + 0.05f)
say(game, "Too high to reach - jump for it");
return;
}
if (top - p->position.y > CLIMB_EASY * s && game->climb_push < 2.0f * CLIMB_PUSH) return; /* (high up: a moment longer) */
startClimb(game, top, probe);
}
/* On a ladder: W / S climb, at the top W gets you over the edge, Space jumps off. */
/* K: a cigarette (rolled if all you have is tobacco: both hands free), lit with
your lighter or somebody's next to you; K again puts it out. */
static void smokeOrStop(Game *game) {
Object *p = game->player;
if (Body_isSmoking(&p->body)) {
Body_stopSmoking(&p->body);
say(game, "You put the cigarette out");
return;
}
if (!Pockets_hasSmoke(p)) {
say(game, p->tobacco > 0 ? "No papers - ask somebody for one" : p->papers > 0 ? "No tobacco left - ask somebody for a bit"
: "Nothing to smoke - ask somebody for one (talk to them)");
return;
}
Object *light = p->lighter && p->lighter_uses > 0 ? p
: Pockets_lighterNear(p, game->world.objects, game->world.count, 2.5f);
if (!light) { say(game, p->lighter ? "Your lighter is empty - stand next to somebody with one" : "No lighter - stand next to somebody with one"); return; }
bool both = p->hands.state[HAND_LEFT] == HAND_FREE && p->hands.state[HAND_RIGHT] == HAND_FREE;
bool rolled = false;
if (!Pockets_takeSmoke(p, both, &rolled)) { say(game, "Both hands free to roll one"); return; }
int hand = p->hands.state[p->main_hand] == HAND_FREE ? p->main_hand : Hands_freeHand(p);
if (!Body_smoke(&p->body, rolled, hand >= 0 ? hand : 2)) {
if (rolled) { p->tobacco++; p->papers++; } else p->cigarettes++;
return;
}
Pockets_light(light);
if (light != p && light->type == OBJ_HUMAN) Ai_say(light, "Here, a light.");
say(game, rolled ? "You roll a cigarette" : light != p ? "Somebody gives you a light" : "You light a cigarette");
}
/* ---------- Your pockets (B): what's in them, taken out, given ---------- */
enum { ROW_CIGS, ROW_TOBACCO, ROW_PAPERS, ROW_FILTERS, ROW_LIGHTER, ROW_ASHTRAY, ROW_KEYS, ROW_URINELLAS, ROW_PULLOVER, ROW_PUT_DOWN, POCKET_ROWS };
static bool rowHas(const Object *p, int row) {
switch (row) {
case ROW_CIGS: return p->cigarettes > 0;
case ROW_TOBACCO: return p->tobacco > 0;
case ROW_PAPERS: return p->papers > 0;
case ROW_FILTERS: return p->filters > 0;
case ROW_LIGHTER: return p->lighter;
case ROW_ASHTRAY: return p->pocket_tray || p->pocket_butts > 0;
case ROW_KEYS: return p->keys != NULL;
case ROW_URINELLAS: return p->urinellas > 0;
case ROW_PULLOVER: return true;
case ROW_PUT_DOWN: return !p->pullover_down;
}
return false;
}
static void rowText(const Object *p, int row, char *out, size_t size) {
switch (row) {
case ROW_CIGS: snprintf(out, size, "Cigarettes - %d", p->cigarettes); break;
case ROW_TOBACCO: snprintf(out, size, "Tobacco - enough for about %d", p->tobacco); break;
case ROW_PAPERS: snprintf(out, size, "Rolling papers - %d", p->papers); break;
case ROW_FILTERS: snprintf(out, size, "Filter tips - %d", p->filters); break;
case ROW_LIGHTER: snprintf(out, size, "%s - %s", p->lighter_of ? "A borrowed lighter" : "Lighter",
p->lighter_uses > 0 ? "works" : "empty"); break;
case ROW_ASHTRAY: snprintf(out, size, p->pocket_tray ? "Pocket ashtray - %d butts" : "Butts wrapped up - %d", p->pocket_butts); break;
case ROW_KEYS: snprintf(out, size, "%s", p->keys); break;
case ROW_URINELLAS: snprintf(out, size, "Urinellas - %d", p->urinellas); break;
case ROW_PULLOVER: snprintf(out, size, "Pullover - %s", p->pullover_down ? Clothes_where(p) : p->look.top == TOP_JACKET ? "on"
: "tied round your hips"); break;
case ROW_PUT_DOWN: snprintf(out, size, "Put your pullover down"); break;
default: out[0] = 0; break;
}
}
/* Where your pullover would go if put down now (where you look). */
static bool pulloverSpot(Game *game, ClothSpot *spot) {
return Clothes_spot(&game->world, game->player, game->camera.front, spot);
}
/* The row picked, kept on something that's there. */
static int pocketRow(Game *game) {
const Object *p = game->player;
for (int k = 0; k < POCKET_ROWS; k++) {
int r = (game->pocket_row + k) % POCKET_ROWS;
if (rowHas(p, r)) return game->pocket_row = r;
}
return -1;
}
/* The tin, a bin or a burning fire within reach: where butts go. */
static Object *buttPlaceNear(const Game *game) {
const Object *p = game->player;
for (int i = 0; i < game->world.count; i++) {
Object *o = game->world.objects[i];
bool takes = (o->type == OBJ_FURNITURE && o->variant == FURN_ASHTRAY) || o->type == OBJ_BIN || (o->type == OBJ_FIREPLACE && o->burning);
if (takes && Hands_gapFrom(p->position, p->look.height, o) < 1.0f) return o;
}
return NULL;
}
static const char *placeName(const Object *o) {
return o->type == OBJ_BIN ? "bin" : o->type == OBJ_FIREPLACE ? "fire" : "tin";
}
/* Your pocket ashtray (or wrapped-up butts) emptied into the tin, a bin or the fire. */
static void emptyButts(Game *game) {
Object *p = game->player, *place = buttPlaceNear(game);
if (!place || p->pocket_butts == 0) { say(game, p->pocket_butts ? "No bin, tin or fire right here to empty it" : "It's empty"); return; }
if (place->type == OBJ_FURNITURE) place->stored += p->pocket_butts;
Hands_touch(p, Object_colliderCenter(place, 0));
p->pocket_butts = 0;
snprintf(game->info, sizeof(game->info), "You empty your butts into the %s", placeName(place));
game->info_about = NULL;
say(game, game->info);
}
/* What Enter does with that row now: given to who's in front, or used. */
static void pocketVerb(Game *game, int row, char *out, size_t size) {
Object *person = game_personInFront(game);
const char *name = person ? person->name : NULL;
switch (row) {
case ROW_CIGS: snprintf(out, size, name ? "Enter: give %s a cigarette" : "Enter: smoke one", name); break;
case ROW_TOBACCO: snprintf(out, size, name ? "Enter: give %s a pinch" : "Enter: roll one and light it", name); break;
case ROW_PAPERS: snprintf(out, size, name ? "Enter: give %s a few papers" : "Enter: roll one and light it", name); break;
case ROW_FILTERS: snprintf(out, size, name ? "Enter: give %s a few filters" : "Enter: roll one and light it", name); break;
case ROW_LIGHTER: snprintf(out, size, name ? "Enter: lend %s your lighter" : "Enter: click - does it work?", name); break;
case ROW_ASHTRAY: {
const Object *place = buttPlaceNear(game);
if (person && person->butt_held && game->player->pocket_tray) snprintf(out, size, "Enter: hold it out for %s's butt", name);
else if (place && game->player->pocket_butts > 0) snprintf(out, size, "Enter: empty it into the %s", placeName(place));
else snprintf(out, size, "Your butts go in here when there's no bin near");
break;
}
case ROW_KEYS: snprintf(out, size, "Keys stay in your pocket"); break;
case ROW_URINELLAS: snprintf(out, size, name ? "Enter: give %s one" : "They're for peeing standing (P)", name); break;
case ROW_PULLOVER:
snprintf(out, size, "%s", game->player->pullover_down ? "Look at it and click to take it"
: game->player->look.top == TOP_JACKET ? "Enter: take it off (round your hips)" : "Enter: put it on");
break;
case ROW_PUT_DOWN: {
ClothSpot spot;
if (!pulloverSpot(game, &spot)) snprintf(out, size, "Nowhere to put it here");
else if (game->player->sitting && !game->player->seat) snprintf(out, size, "Enter: spread it out and sit on it");
else snprintf(out, size, "Enter: put it %s", spot.where);
break;
}
default: out[0] = 0; break;
}
}
static void smokeOrStop(Game *game);
static void usePocket(Game *game, int row) {
Object *p = game->player, *person = game_personInFront(game);
char line[96];
if (row == ROW_PULLOVER) {
if (p->pullover_down) { say(game, "Look at it and click to take it"); return; }
bool on = p->look.top != TOP_JACKET;
if (on) Clothes_takeBack(p, true);
else { p->look.top = TOP_SHIRT; p->look.jacket_tied = true; }
p->jacket_told = true; /* (your choice now, this cold spell) */
say(game, on ? "You pull your pullover on" : "You take your pullover off and tie it round your hips");
return;
}
if (row == ROW_PUT_DOWN) {
ClothSpot spot;
if (!pulloverSpot(game, &spot)) { say(game, "Nowhere to put it here"); return; }
bool under = p->sitting && !p->seat;
if (!Clothes_putDown(&game->world, p, &spot)) return;
p->jacket_told = true;
snprintf(game->info, sizeof(game->info), under ? "You spread your pullover out and sit on it" : "You put your pullover %s", spot.where);
game->info_about = NULL;
say(game, game->info);
return;
}
if (!person || person->type != OBJ_HUMAN) {
if (row == ROW_CIGS || row == ROW_TOBACCO || row == ROW_PAPERS || row == ROW_FILTERS) {
if (!Body_isSmoking(&p->body)) smokeOrStop(game);
else say(game, "You're smoking one already");
} else if (row == ROW_LIGHTER) {
say(game, p->lighter_uses > 0 ? "Click - a flame. It works" : "Click, click... it's empty");
} else if (row == ROW_ASHTRAY) {
emptyButts(game);
}
return;
}
int given = 0;
PocketNeed what = POCKET_NOTHING;
switch (row) {
case ROW_CIGS:
if (p->cigarettes > 0) { p->cigarettes--; person->cigarettes++; given = 1; }
snprintf(line, sizeof line, "You give %s a cigarette", person->name);
break;
case ROW_TOBACCO: what = POCKET_TOBACCO; given = Pockets_give(p, person, what); snprintf(line, sizeof line, "You give %s a pinch of tobacco", person->name); break;
case ROW_PAPERS: what = POCKET_PAPERS; given = Pockets_give(p, person, what); snprintf(line, sizeof line, "You give %s %d papers", person->name, given); break;
case ROW_FILTERS: what = POCKET_FILTERS; given = Pockets_give(p, person, what); snprintf(line, sizeof line, "You give %s %d filters", person->name, given); break;
case ROW_LIGHTER:
if (person->lighter) { say(game, "They have one"); return; }
Pockets_passLighter(p, person);
given = 1;
snprintf(line, sizeof line, "You lend %s your lighter", person->name);
break;
case ROW_ASHTRAY:
if (!p->pocket_tray || !person->butt_held) { emptyButts(game); return; }
what = POCKET_ASHTRAY;
given = Pockets_give(p, person, what);
snprintf(line, sizeof line, "%s puts the butt in your pocket ashtray", person->name);
break;
case ROW_URINELLAS:
if (p->urinellas > 0) { p->urinellas--; person->urinellas++; given = 1; }
snprintf(line, sizeof line, "You give %s a Urinella", person->name);
break;
default:
say(game, "Your keys stay in your pocket");
return;
}
if (!given) { say(game, "Not enough to give any"); return; }
snprintf(game->info, sizeof(game->info), "%s", line); /* (say keeps the pointer: not a stack buffer) */
game->info_about = NULL;
say(game, game->info);
if (person->ai.asking_you && (person->ai.asking_you == (int)what || row == ROW_CIGS)) { /* (what they asked for) */
person->ai.asking_you = 0;
if (what != POCKET_ASHTRAY) person->smoke_ask = 2.0f; /* (and rolls one in a moment) */
Ai_say(person, what == POCKET_ASHTRAY ? "Thanks - much better than on the ground." : "Thanks, you're a star!");
} else if (person->ai.speech_time <= 0.0f) {
Ai_say(person, "Oh - thanks.");
}
}
/* B: open or shut your pockets; Up / Down picks, Enter takes it out (gives it to who's in front). */
static void game_pockets(Game *game, const Input *input) {
if (input->pockets) game->show_pockets = !game->show_pockets;
if (input->notes) { game->show_notes = !game->show_notes; if (game->show_notes) game->show_crew = false; }
if (!game->show_pockets || game->show_dev) return;
if (input->menu_move) {
for (int k = 1; k <= POCKET_ROWS; k++) { /* (to the next one with something in it) */
int r = ((game->pocket_row + input->menu_move * k) % POCKET_ROWS + POCKET_ROWS) % POCKET_ROWS;
if (rowHas(game->player, r)) { game->pocket_row = r; break; }
}
}
int row = pocketRow(game);
if (input->enter && row >= 0) usePocket(game, row);
}
int Game_pocketLines(Game *game, char lines[][96], int max, int *picked) {
const Object *p = game->player;
int n = 0, row = pocketRow(game);
*picked = -1;
if (max < 3) return 0;
snprintf(lines[n++], 96, "Pockets (%s): %d of %d used - B closes",
p->look.garment == GARMENT_TROUSERS && p->work_trousers ? "work trousers" : "trousers",
Pockets_used(p), Pockets_slots(p));
for (int r = 0; r < POCKET_ROWS && n < max - 1; r++) {
if (!rowHas(p, r)) continue;
if (r == row) *picked = n;
rowText(p, r, lines[n++], 96);
}
if (row < 0) snprintf(lines[n++], 96, "Nothing in them");
else pocketVerb(game, row, lines[n++], 96);
return n;
}
/* Climbing a ladder with one hand (something in the other): the unpractised,
clumsy, unfit, tired or drunk may slip - a wobble first, then off they go.
Every climb like that is practice. */
static float upAlongY(const Object *l) { return l->colliders[0].axes[1].y; } /* (sin of how steep it stands) */
/* An unsteady ladder (see Ladder_steadiness) may slip when you climb it: a wobble
first, then over it goes - sideways, or back off the wall when it stands too steep.
The higher up, the worse. Somebody holding it: steady. */
static bool ladderSlips(Game *game, Object *p, Object *l, bool moving, float dt) {
float steady = Ladder_steadiness(l);
if (!moving || steady >= 0.98f || p->ladder_off > 0.0f) return false;
float up = clampf(p->ladder_at / 3.0f, 0.0f, 1.0f);
float risk = 0.9f * powf(1.0f - steady, 1.5f) * (0.3f + up) * (1.0f + fminf(p->drunk, 1.0f));
if ((float)rand() / (float)RAND_MAX >= risk * dt) return false;
if (l->ladder_wobble <= 0.0f) {
l->ladder_wobble = 30.0f; /* (the next one is no wobble) */
l->shake = 1.2f;
say(game, "The ladder wobbles under you! (somebody to hold it: G)");
return false;
}
Vec3 n = l->lean_normal, across = vec3_cross(n, vec3(0.0f, 1.0f, 0.0f));
bool steep = upAlongY(l) > 0.985f;
Vec3 dir = steep ? n : vec3_scale(across, rand() % 2 ? 1.0f : -1.0f);
Ladder_topple(&game->world, l, dir);
p->velocity = vec3_add(vec3_scale(dir, 1.2f), vec3(0.0f, 0.5f, 0.0f));
if (p->ladder_at > 0.6f) Body_fall(&p->body, dir);
say(game, "The ladder slips - down you go!");
return true;
}
static bool oneHandSlip(Game *game, Object *p, Object *l, bool moving, float dt) {
p->ladder_wobble = fmaxf(0.0f, p->ladder_wobble - dt);
bool one_hand = (p->hands.state[HAND_LEFT] == HAND_HOLDING) != (p->hands.state[HAND_RIGHT] == HAND_HOLDING);
if (!one_hand || !moving || p->ladder_off > 0.0f) return false;
p->ladder_skill = fminf(1.0f, p->ladder_skill + 0.01f * dt);
float risk = 0.16f * (1.3f - p->fitness) * (0.6f + Body_clumsiness(&p->body)) * (1.0f + 3.0f * fminf(p->drunk, 1.0f))
* (1.5f - p->needs.stamina) * (1.0f - 0.9f * p->ladder_skill);
if ((float)rand() / (float)RAND_MAX >= risk * dt) return false;
if (p->ladder_wobble <= 0.0f) { /* first a wobble: caught it */
p->ladder_wobble = 3.0f;
say(game, "Whoa - you nearly slip!");
return false;
}
Vec3 n = l->lean_normal;
bool low = p->ladder_at < 0.6f;
Ladder_getOff(p);
p->velocity = vec3(n.x * 1.5f, 0.5f, n.z * 1.5f);
if (!low) Body_fall(&p->body, n);
say(game, low ? "You slip off the bottom rung" : "You slip and fall off the ladder!");
return true;
}
void game_climbLadder(Game *game, const Input *in, float dt) {
Object *p = game->player;
Object *l = p->on_ladder;
if (!l) return;
if (in->jump || Body_isDown(&p->body)) {
Vec3 n = l->lean_normal;
Ladder_getOff(p);
p->velocity = vec3(n.x * 2.0f, 1.5f, n.z * 2.0f);
say(game, "You jump off the ladder");
return;
}
l->ladder_wobble = fmaxf(0.0f, l->ladder_wobble - dt);
if (ladderSlips(game, p, l, in->move_forward != 0.0f, dt)) return;
if (oneHandSlip(game, p, l, in->move_forward != 0.0f, dt)) return;
if (Ladder_climb(p, in->move_forward, dt) != CLIMB_AT_EDGE) return;
/* Over the edge onto whatever it leans on, if there's room to stand */
if (!Climb_overLadderTop(&game->world, p, l)) {
if (game->message_time <= 0.0f) say(game, "Nothing to stand on up there");
return;
}
say(game, "You climb over the edge");
}
/* The ladder carried in a hand, or NULL. */
static Object *ladderInHand(const Object *p) {
for (int h = 0; h < 2; h++)
if (p->hands.state[h] == HAND_HOLDING && p->hands.item[h] && p->hands.item[h]->type == OBJ_LADDER) return p->hands.item[h];
return NULL;
}
/* Ladder in hand: slide it out, lean it against what's in front, or slide it
together again. A leaning ladder in front (or its top, from up there): game_climb on. */
static bool useLadder(Game *game) {
Object *p = game->player;
Object *ladder = ladderInHand(p);
if (ladder) {
const char *why = NULL;
/* (together still, at a wall: out it slides and up against it, in one go) */
bool was_in = ladder->extend_goal < 0.5f;
float extend_was = ladder->extend;
int lean = Ladder_lean(&game->world, p, ladder, was_in, &why);
if (was_in && lean != LEAN_DONE) { Ladder_setExtend(ladder, extend_was); ladder->extend_goal = 0.0f; }
if (lean == LEAN_DONE) game->putting_up = ladder;
const bool fold = ladder->variant == LADDER_FOLDING;
if (lean == LEAN_DONE && ladder->variant == LADDER_STEP)
say(game, ladder->lean_top > 0.0f ? "You stand the step ladder there - climb it to get up there"
: "You stand the step ladder up - click it to climb");
else if (lean == LEAN_DONE && ladder->place_top < 0.0f)
say(game, "You stand it up on its own - nothing holds it...");
else if (lean == LEAN_DONE) {
say(game, ladder->lean_top > 0.0f ? (was_in ? (fold ? "You unfold the ladder and lean it there - click it to climb"
: "You slide the ladder out and lean it there - click it to climb")
: "The ladder leans there - click it to climb")
: "It leans there, but doesn't reach the top");
if (ladder->stability < 0.5f) tellMore(game, "very wobbly: get somebody to hold it (G)");
else if (ladder->stability < 0.85f) tellMore(game, "a bit wobbly");
}
else if (lean == LEAN_NO) say(game, why);
else {
Ladder_slide(ladder);
if (fold) say(game, ladder->extend_goal > 0.5f ? "You unfold the ladder - click where it should lean" : "You fold the ladder up");
else say(game, ladder->extend_goal > 0.5f ? "You slide the ladder out - click at a wall to lean it there"
: "You slide the ladder together");
}
return true;
}
bool at_top = false;
ladder = Ladder_near(&game->world, p, &at_top);
if (!ladder) return false;
/* one hand free is enough for the skilled (the other holds something small): the others may slip */
Object *in_hand = p->hands.item[HAND_LEFT] ? p->hands.item[HAND_LEFT] : p->hands.item[HAND_RIGHT];
bool free_l = p->hands.state[HAND_LEFT] == HAND_FREE, free_r = p->hands.state[HAND_RIGHT] == HAND_FREE;
bool one_hand = free_l != free_r && in_hand && !p->hands.grabbed &&
p->hands.state[free_l ? HAND_RIGHT : HAND_LEFT] == HAND_HOLDING && in_hand->weight <= ONE_HAND_LADDER_KG;
if (!(free_l && free_r) && !one_hand) {
say(game, in_hand && in_hand->weight > ONE_HAND_LADDER_KG ? "Too heavy to take up the ladder" : "You need a hand free to climb");
return true;
}
Ladder_mount(p, ladder, at_top);
if (one_hand) say(game, "One-handed up the ladder - careful! W / S, Space: jump off");
else say(game, at_top ? "Down the ladder: S" : "Up the ladder: W, down: S, Space: jump off");
return true;
}
/* E / Q (or right click, empty-handed) at the foot of a leaning ladder: take it down. */
static bool takeLadder(Game *game) {
Object *p = game->player;
bool at_top = false;
Object *ladder = Ladder_near(&game->world, p, &at_top);
if (!ladder || at_top || Hands_freeHand(p) < 0) return false;
if (Ladder_take(p, ladder)) say(game, ladder->variant == LADDER_STEP ? "You fold the step ladder up" : "You take the ladder down");
return true;
}
/* The item a hand is reaching for right now, if any. */
static const Object *reachingFor(const Object *p) {
for (int i = 0; i < 2; i++)
if (p->hands.state[i] == HAND_REACHING) return p->hands.item[i];
return NULL;
}
/* Lift what we hold on to together with whoever helps, or put it down. */
static void liftTogether(Game *game) {
int lift = Hands_toggleLift(game->player, game->world.objects, game->world.count);
if (lift == -3) say(game, "Too heavy - take it at one end to lift that end, or get help (G)");
else if (lift < 0) say(game, "Too heavy - get more people to help (G)");
if (lift == 2) say(game, "You lift one end - the other drags along (more help: carry all of it)");
}
/* A supply in front (apple crate, water barrel): hands one out. */
static bool takeFromSupply(Game *game) {
Object *p = game->player;
Vec3 facing = Object_facing(p);
for (int i = 0; i < game->world.count; i++) {
Object *o = game->world.objects[i];
if ((o->type != OBJ_SUPPLY && (o->type != OBJ_CRATE || o->is_held)) || Hands_gapFrom(p->position, p->look.height, o) > SUPPLY_REACH) continue;
Vec3 to = vec3_sub(o->position, p->position);
to.y = 0.0f;
if (vec3_length(to) > 1e-3f && vec3_dot(vec3_normalize(to), facing) < 0.3f) continue;
if (o->dispenses == OBJ_URINELLA) { /* into the pocket, not the hand */
if (!p->female) { say(game, "You pee standing anyway"); return true; }
int got = Pockets_refill(p);
p->urinella = true;
if (got == 0) say(game, "Your pocket is full of Urinellas already");
else {
snprintf(game->info, sizeof(game->info), "%d Urinella%s into your pocket (%d now)", got, got == 1 ? "" : "s", p->urinellas);
say(game, game->info);
}
return true;
}
int hand = p->hands.state[HAND_RIGHT] == HAND_FREE ? HAND_RIGHT
: p->hands.state[HAND_LEFT] == HAND_FREE ? HAND_LEFT : -1;
if (o->type == OBJ_CRATE) { /* a bottle in hand that's empty (or no hand free): back in it goes */
for (int h = 0; h < 2; h++) {
Object *it = p->hands.item[h];
if (!it || p->hands.state[h] != HAND_HOLDING || !Crate_bottleKind(it) || it->broken || (it->sips > 0 && hand >= 0)) continue;
if (o->stored >= Crate_bottles(o->variant)) { say(game, "The crate is full"); return true; }
Hands_drop(p, h);
Hands_touch(p, vec3_add(Object_colliderCenter(o, 0), vec3(0.0f, 0.2f, 0.0f)));
bool empty = it->sips <= 0, own = Crate_belongs(o, Crate_bottleKind(it));
Crate_putBottle(o, it);
World_noise(&game->world, NOISE_CLINK, o->position, 0.6f);
say(game, !own ? "In it goes - but it doesn't belong in this crate" : empty ? "The empty back into the crate" : "The bottle back into the crate");
return true;
}
}
if (hand < 0) { say(game, "Your hands are full"); return true; }
if (o->type == OBJ_CRATE) { /* a bottle out of the crate (E / Q: the crate itself) */
if (o->stored <= 0) { say(game, "The crate is empty"); return true; }
if (o->stored <= o->crate_empties) { say(game, "Only empties left in this one"); return true; }
Object *bottle = Crate_takeBottle(&game->world, o);
if (bottle && Hands_pickUpObject(p, hand, bottle)) { tell(game, bottle); World_noise(&game->world, NOISE_CLINK, o->position, 0.5f); }
return true;
}
if (o->stock < 0) { say(game, o->dispenses == OBJ_APPLE ? "The food crate is empty - time for a shopping run" : "The cool box is empty"); return true; }
if (o->stock > 0 && --o->stock == 0) o->stock = -1; /* (bought: it runs out) */
ObjectType what = o->dispenses;
Vec3 top = vec3_add(Object_colliderCenter(o, 0), vec3(0.0f, o->colliders[0].half_extents.y + 0.1f, 0.0f));
Vec3 size = what == OBJ_APPLE ? vec3(0.08f, 0.08f, 0.08f) : what == OBJ_SAUSAGE ? vec3(0.0f, 0.16f, 0.0f)
: what == OBJ_BAG ? vec3_zero() : vec3(0.07f, 0.25f, 0.0f);
float kg = what == OBJ_APPLE ? 0.2f : what == OBJ_SAUSAGE ? 0.12f : what == OBJ_BAG ? 0.05f : 0.6f;
Object *item = Object_create(what, PALETTE_WARM, top, size, kg);
if (item && World_add(&game->world, item) && Hands_pickUpObject(p, hand, item)) tell(game, item);
return true;
}
return false;
}
/* An apple tree in front: pick an apple. */
static bool pickApple(Game *game) {
Object *p = game->player;
for (int i = 0; i < game->world.count; i++) {
Object *o = game->world.objects[i];
if (o->type != OBJ_TREE || !o->variant || o->toppled) continue;
Vec3 to = vec3_sub(o->position, p->position);
to.y = 0.0f;
float d = vec3_length(to);
if (d > APPLE_REACH || (d > 1e-3f && vec3_dot(vec3_scale(to, 1.0f / d), Object_facing(p)) < 0.3f)) continue;
int hand = Hands_freeHand(p);
if (hand < 0) { say(game, "Your hands are full"); return true; }
Vec3 at = vec3_add(p->position, vec3(0.0f, p->look.height * 1.05f, 0.0f));
Object *apple = Object_create(OBJ_APPLE, PALETTE_WARM, at, vec3(0.08f, 0.08f, 0.08f), 0.2f);
if (apple && World_add(&game->world, apple) && Hands_pickUpObject(p, hand, apple)) say(game, "You pick an apple");
return true;
}
return false;
}
/* ---------- The side jobs: the scythe, the snails ---------- */
#define MOW_SWING_YOU 1.3f /* s a swing of the scythe */
#define MOW_AHEAD 0.9f /* m in front: where the blade goes through */
#define SNAIL_REACH 1.3f /* m: a snail this near in front is picked up */
#define REFUGE_YOU 3.0f /* m from the refuge: your snails go free */
static Object *scytheIn(const Object *p) {
for (int h = 0; h < 2; h++)
if (p->hands.item[h] && p->hands.item[h]->type == OBJ_SCYTHE && p->hands.state[h] == HAND_HOLDING) return p->hands.item[h];
return NULL;
}
/* Left click with the scythe in the field: mowing (hold it and walk along). */
static int startMowing(Game *game) {
Object *p = game->player;
if (!scytheIn(p)) return -1;
Vec3 ahead = vec3_add(p->position, vec3_scale(Object_facing(p), MOW_AHEAD));
if (!Field_inside(ahead.x, ahead.z)) { say(game, "The scythe's for the Kartoffelacker's long grass - out behind the road to the chicken farm"); return CLICK_NONE; }
game->mow_t = 0.0f;
return CLICK_MOW;
}
static bool mowStep(Game *game) {
Object *p = game->player;
if (!scytheIn(p)) return false;
Vec3 ahead = vec3_add(p->position, vec3_scale(Object_facing(p), MOW_AHEAD));
if (!Field_inside(ahead.x, ahead.z)) return false;
game->mow_t += game->dt;
p->mowing = 0.001f + 3.1416f * game->mow_t / MOW_SWING_YOU;
if (game->mow_t < MOW_SWING_YOU) return true;
game->mow_t = 0.0f;
int killed = 0;
Field_mow(&game->world, ahead, 0.8f, &killed);
World_noise(&game->world, NOISE_RUSTLE, p->position, 0.4f);
p->needs.stamina = fmaxf(0.0f, p->needs.stamina - 0.004f);
if (killed) { say(game, "Crunch - a snail in the grass! They're protected: pick them up before you mow"); return true; }
snprintf(game->info, sizeof game->info, "Mowing the Kartoffelacker - %d%% done (walk on as you swing)", (int)(Field_done(&game->world) * 100.0f));
game->info_about = NULL;
say(game, game->info);
return true;
}
/* A vineyard snail in front: into your bucket. */
static bool pickSnail(Game *game) {
Object *p = game->player, *best = NULL;
float best_d = SNAIL_REACH;
for (int i = 0; i < game->world.count; i++) {
Object *o = game->world.objects[i];
if (o->type != OBJ_SNAIL || o->variant || o->broken || o->is_held) continue;
Vec3 to = vec3(o->position.x - p->position.x, 0.0f, o->position.z - p->position.z);
float d = vec3_length(to);
if (d > 0.3f && vec3_dot(vec3_scale(to, 1.0f / d), Object_facing(p)) < 0.5f) continue;
if (d < best_d) { best_d = d; best = o; }
}
if (!best) return false;
Hands_touch(p, best->position);
if (!Field_pickSnail(&game->world, p, best)) { say(game, "Your bucket's full - let them go by the wood next to the Kartoffelacker"); return true; }
snprintf(game->info, sizeof game->info, "A vineyard snail into your bucket (%d) - let them go by the wood next to the Kartoffelacker", p->snails);
game->info_about = NULL;
say(game, game->info);
return true;
}
/* At the refuge with snails: off they go. */
static void freeSnails(Game *game) {
Object *p = game->player;
if (p->snails <= 0) return;
Vec3 r = Site_snailRefuge();
if (hypotf(r.x - p->position.x, r.z - p->position.z) > REFUGE_YOU) return;
int n = Field_release(&game->world, p);
snprintf(game->info, sizeof game->info, "You let %d snail%s go in the long grass by the wood - safe here (%d rescued so far)", n, n == 1 ? "" : "s",
game->world.snails_rescued);
game->info_about = NULL;
say(game, game->info);
}
static void takeInFront(Game *game) {
if (pickSnail(game)) return;
if (takeFromSupply(game)) return;
if (!Hands_inFront(game->player, game->world.objects, game->world.count) && pickApple(game)) return;
Object *p = game->player;
int taken = Hands_takeInFront(p, game->world.objects, game->world.count);
if (taken == -1) say(game, "Your hands are full");
if (taken == 1) tell(game, reachingFor(p));
if (taken == 2) {
/* Too big for the hands: hold on to it, and lift it if you're strong enough */
game->holding = true;
tell(game, p->hands.grabbed);
int lift = Hands_toggleLift(p, game->world.objects, game->world.count);
if (lift == -3) tellMore(game, "too heavy alone: take it at one end to lift that end, or call help (G)");
else if (lift < 0) tellMore(game, "too heavy alone: push it, or call help (G)");
if (lift == 2) tellMore(game, "one end up, the other drags along");
}
}
/* A sip or a bite of what's in the hand; tells what's left after it. */
static bool drinkNow(Game *game) {
Object *p = game->player;
int hand = Hands_bottleHand(p);
if (hand < 0 || !Hands_drink(p)) return false;
Object after = *p->hands.item[hand];
after.sips--;
tell(game, &after);
game->info_about = p->hands.item[hand];
return true;
}
/* A stroke with the tool; tells about the thing it works on the first time. */
static void work(Game *game) {
Object *p = game->player;
Object *target = Tools_target(p, game->world.objects, game->world.count);
if (Tools_use(p, game->world.objects, game->world.count)) {
if (target && target != game->info_about) tell(game, target);
} else if (Tools_held(p, NULL) == TOOL_CHAINSAW && Hands_freeHand(p) < 0) {
say(game, "The chainsaw needs both hands");
}
}
/* A piece of furniture of this kind within reach (in front or not). */
static Object *furnitureNear(const Game *game, FurnitureKind kind, float reach) {
const Object *p = game->player;
for (int i = 0; i < game->world.count; i++) {
Object *o = game->world.objects[i];
if (o->type == OBJ_FURNITURE && o->variant == (int)kind &&
Hands_gapFrom(p->position, p->look.height, o) < reach) return o;
}
return NULL;
}
/* At a gate: open it, or shut it (both leaves of a double gate go together). */
static bool swingGate(Game *game) {
Object *g = furnitureNear(game, FURN_GATE, 1.2f);
if (!g) return false;
bool open = !g->gate_open;
for (int i = 0; i < game->world.count; i++) { /* (its other leaf: the hinges 6 m apart at most, the same way out) */
Object *o = game->world.objects[i];
if (o->type == OBJ_FURNITURE && o->variant == FURN_GATE && vec3_length(vec3_sub(o->hinge, g->hinge)) < 5.1f &&
vec3_dot(o->gate_out, g->gate_out) > 0.9f && fabsf(vec3_dot(vec3_sub(o->hinge, g->hinge), g->gate_shut)) <= g->size.x * 2.0f + 0.2f)
o->gate_open = open;
}
say(game, open ? "You open the gate" : "You shut the gate");
return true;
}
/* Looking at your pullover (put down) within reach: back round your hips. Somebody else's: whose. */
static bool takePullover(Game *game) {
Object *p = game->player;
Object *c = Clothes_alongRay(&game->world, game->camera.position, game->camera.front, 2.3f);
if (!c) return false;
Vec3 to = vec3_sub(c->position, p->position);
to.y = 0.0f;
if (vec3_length(to) > 1.3f) return false;
if (c->cloth_of != p) {
snprintf(game->info, sizeof(game->info), "That's %s's pullover", c->cloth_of ? c->cloth_of->name : "somebody");
game->info_about = NULL;
say(game, game->info);
return true;
}
Clothes_takeBack(p, false);
say(game, "You take your pullover and tie it round your hips");
return true;
}
/* ---------- Tents ---------- */
#define TENT_REACH 1.2f /* m: a tent this near to work on it */
/* A packed tent (its bag lying) or a tent not up yet (or a standing one), near and in front. */
static Object *tentFind(const Game *game, bool packed, bool standing) {
const Object *p = game->player;
Vec3 facing = Object_facing(p);
Object *best = NULL;
float best_gap = packed ? 0.9f : standing ? 0.6f : TENT_REACH; /* (a standing one: right at it, so a click near it still takes things) */
for (int i = 0; i < game->world.count; i++) {
Object *o = game->world.objects[i];
bool fits = packed ? o->type == OBJ_GEAR && o->variant >= GEAR_TENT_SMALL && !o->is_held && !o->broken
: o->type == OBJ_TENT && !o->broken && (standing ? o->tent_stage >= TENT_UP && !o->tent_down : o->tent_stage < TENT_UP || o->tent_down);
if (!fits) continue;
Vec3 to = vec3_sub(o->position, p->position);
to.y = 0.0f;
float gap = Hands_gapFrom(p->position, p->look.height, o);
if (gap < best_gap && (gap < 0.3f || vec3_dot(vec3_normalize(to), facing) > 0.2f)) { best = o; best_gap = gap; }
}
return best;
}
static Object *tentNear(const Game *game, bool packed) { return tentFind(game, packed, false); }
/* Left click: a packed tent in front spread out (the door towards you), or a tent going up: work on it (held). */
static int tentClick(Game *game) {
Object *p = game->player, *bag = tentNear(game, true);
if (bag) {
Vec3 door = vec3_sub(p->position, bag->position);
Object *tent = Tent_unpack(&game->world, bag, door);
if (tent) {
Hands_touch(p, tent->position);
say(game, Tent_isTheirs(tent, p) ? "You spread your tent out - hold left click to put it up"
: "You spread the tent out - hold left click to put it up");
}
return CLICK_NONE;
}
Object *tent = tentNear(game, false);
if (!tent) tent = tentFind(game, false, true); /* (standing: held a moment, it comes down) */
if (!tent) return -1;
game->tent_hold = 0.0f;
if (p->hands.state[HAND_LEFT] != HAND_FREE && p->hands.state[HAND_RIGHT] != HAND_FREE) { say(game, "A hand free for that"); return CLICK_NONE; }
game->tent = tent;
return CLICK_TENT;
}
/* A floor's stage you're at (beside it or on its deck, facing it): left click held, you build on it. */
#define STAGE_REACH 1.3f /* m out from its edge */
#define STAGE_TIRES 0.0015f /* stamina a real second of building */
static Object *stageAt(const Game *game) {
const Object *p = game->player;
for (int i = 0; i < game->world.count; i++) {
Object *s = game->world.objects[i];
if (s->type != OBJ_STAGE) continue;
Vec3 f = Object_facing(s), r = vec3(-f.z, 0.0f, f.x), d = vec3_sub(p->position, s->position);
float x = vec3_dot(d, r), z = vec3_dot(d, f);
if (fabsf(x) < s->size.x * 0.5f + STAGE_REACH && fabsf(z) < s->size.z * 0.5f + STAGE_REACH) return s;
}
return NULL;
}
static bool workStage(Game *game) {
Object *p = game->player, *s = game->stage;
if (!s || stageAt(game) != s) return false;
if (s->build_stage >= BUILD_DONE) { say(game, "It's built!"); return false; }
Hands_touch(p, Stage_handsAt(s, p->position, p->body.clock));
float handy = 0.5f * (p->skill[ORDER_FIREWOOD] + p->skill[ORDER_TREES]);
int got = Stage_work(&game->world, s, (0.6f + 0.8f * handy) * game->world.game_dt / 3600.0f);
p->needs.stamina = fmaxf(0.0f, p->needs.stamina - STAGE_TIRES * game->dt);
if (got == -2) {
char what[96];
Stage_missing(&game->world, s, what, sizeof what);
snprintf(game->info, sizeof game->info, s->build_stage == BUILD_FRAME ? "The frame needs %s lying by it (carry some here: F with a helper)"
: "It needs %s lying by it (the shopping: ask whoever has the car keys)", what);
say(game, game->info);
return false;
}
if (got > 0) {
snprintf(game->info, sizeof game->info, "The %s stage: %s - next: %s", Cast_projectName((Project)s->variant),
got == BUILD_DONE ? "built!" : "a step done", Stage_stepName(got));
say(game, game->info);
} else if (game->message_time <= 0.0f) tell(game, s);
return true;
}
/* Left button held at a tent: the next bit of it. */
#define TAKE_DOWN_HOLD 1.0f /* s: held this long at a standing tent, you start taking it down */
static bool workTent(Game *game) {
Object *p = game->player, *tent = game->tent;
if (!tent || tent->broken || Hands_gapFrom(p->position, p->look.height, tent) > TENT_REACH + 0.6f) return false;
if (tent->tent_stage >= TENT_UP && !tent->tent_down) { /* standing: a moment first (no accidents) */
if (game->tent_hold == 0.0f) say(game, "Keep holding to take the tent down");
if ((game->tent_hold += game->dt) < TAKE_DOWN_HOLD) return true;
Tent_takeDown(game->world.objects, game->world.count, tent);
say(game, Tent_isTheirs(tent, p) || !tent->owner ? "You start taking the tent down - pegs out first"
: "You start taking their tent down - pegs out first");
}
Hands_touch(p, Tent_handsAt(tent, p, tent->tent_work * 18.0f));
int got = Tent_work(tent, p, game->dt);
static const char *const SAID[] = { "", "Spread out - now the poles in and up", "The poles are in - now pull the cover over it",
"The cover's on - now peg it down (a stone helps)", "All pegged - the tent stands" };
static const char *const DOWN[] = { "Poles down - now fold it up", "The cover's off - now the poles down", "The pegs are out - now pull the cover off" };
if (got == TENT_DOWN_PACKED) { say(game, "Folded up and in its bag - E / Q: carry it"); return false; }
if (got >= TENT_DOWN) say(game, DOWN[(got - TENT_DOWN) % 3]);
else if (got >= 0) say(game, SAID[got % 5]);
return tent->tent_stage < TENT_UP || tent->tent_down;
}
/* F held right at a tent with no pegs in it: it comes along (in front of
you, turning with you), set down on the ground where you let go. */
static bool moveTent(Game *game, bool held) {
Object *p = game->player, *t = game->tent_drag;
if (t && (!held || t->broken)) {
t->tent_dragged = false;
t->tent_idle = 0.0f;
game->tent_drag = NULL;
say(game, "Put down here - hold left click to peg it again");
return true;
}
if (!t) {
if (!held || Hands_isGrabbing(p) || game->holding) return false;
t = tentFind(game, false, false);
if (!t) t = tentFind(game, false, true);
if (!t) return false;
if (!Tent_movable(t)) {
static float told;
if (game->daycycle.seconds - told > 4.0f) { say(game, "Its pegs are in - hold left click to take it down (the pegs come out first)"); told = game->daycycle.seconds; }
return false;
}
Tent_startMove(t);
Vec3 f = Object_facing(p), r = vec3(-f.z, 0.0f, f.x), rel = vec3_sub(t->position, p->position);
game->drag_off = vec3(vec3_dot(rel, r), 0.0f, vec3_dot(rel, f));
game->drag_turn = t->rotation.y - p->rotation.y;
game->tent_drag = t;
say(game, "You pick the tent up - walk to move it, let go of F to set it down");
}
Vec3 f = Object_facing(p), r = vec3(-f.z, 0.0f, f.x);
Vec3 at = vec3_add(p->position, vec3_add(vec3_scale(r, game->drag_off.x), vec3_scale(f, game->drag_off.z)));
at.y = Terrain_height(&game->world.terrain, at.x, at.z);
t->position = at;
Object_setRotation(t, vec3(0.0f, p->rotation.y + game->drag_turn, 0.0f));
Object_syncColliders(t);
t->tent_idle = 0.0f;
Hands_touch(p, vec3_add(at, vec3(0.0f, 0.6f, 0.0f)));
return false; /* (walking on as usual) */
}
/* At the cigarette machine: a pack. */
static bool smokesMachine(Game *game) {
Object *m = furnitureNear(game, FURN_SMOKES, 0.9f);
if (!m) return false;
Object *p = game->player;
Hands_touch(p, vec3_add(Object_colliderCenter(m, 0), vec3(0.0f, 0.2f, 0.0f)));
p->cigarettes += SMOKES_PACKS;
say(game, "*clunk* - a pack of cigarettes from the machine");
return true;
}
/* The hose or the watering can in a hand, or NULL. */
static Object *waterInHand(const Object *p) {
for (int h = 0; h < 2; h++)
if (p->hands.item[h] && (p->hands.item[h]->type == OBJ_HOSE || p->hands.item[h]->type == OBJ_CAN))
return p->hands.item[h];
return NULL;
}
/* Holding the hose: it only goes so far. */
void game_holdHose(Game *game) {
Object *p = game->player, *hose = waterInHand(p);
if (!hose || hose->type != OBJ_HOSE || !hose->hose_tap) return;
if (Hose_burnedThrough(&game->world, hose, NULL)) return; /* (burned through: the end you hold is loose) */
Vec3 tap = hose->hose_tap->position, to = vec3_sub(p->position, tap);
to.y = 0.0f;
float d = vec3_length(to), reach = Hose_length(&game->world, hose);
if (d <= reach) return;
Vec3 back = vec3_scale(to, reach / d);
p->position.x = tap.x + back.x;
p->position.z = tap.z + back.z;
Object_syncColliders(p);
if (game->message_time <= 0.0f) say(game, "The hose doesn't reach any further - the watering can does");
}
#define CAN_LITERS 8.0f
/* Duct tape or shears in hand, looking at a melted bit of the hose: tape it up,
or cut the burnt bit out and join the ends. */
#define MEND_REACH 2.2f
static bool mendHose(Game *game) {
Object *p = game->player, *with = NULL;
for (int h = 0; h < 2; h++)
if (p->hands.item[h] && (p->hands.item[h]->type == OBJ_TAPE || p->hands.item[h]->type == OBJ_SHEARS)) with = p->hands.item[h];
if (!with || vec3_length(p->body.look_dir) < 0.5f) return false;
Vec3 eye = vec3_add(p->position, vec3(0.0f, 0.93f * p->look.height, 0.0f));
int k = -1;
HoseRope *rope = Hose_aimed(&game->world, eye, vec3_normalize(p->body.look_dir), MEND_REACH, &k);
if (!rope || Hose_damageNear(rope, k) < 0) return false; /* (shears: nothing to mend here, so on to the branches) */
if (rope->heat[Hose_damageNear(rope, k)] > 0.15f) { say(game, "Too hot - pull it out of the fire first"); return true; }
static char line[96];
if (with->type == OBJ_TAPE) {
if (Hose_tape(rope, k)) say(game, "You tape up the melted bit - it holds");
else say(game, "Burned right through - tape won't hold that. Cut the burnt bit out with the shears");
return true;
}
float cut = Hose_cutOut(rope, k);
if (cut <= 0.0f) { say(game, "Not much hose left to cut"); return true; }
snprintf(line, sizeof(line), "You cut out the burnt bit and join the ends - the hose is %.1f m now", rope->length);
say(game, line);
return true;
}
/* Hose: spray. Watering can: fill it at the tap, else pour. Kitchen sink: wash. */
static int useWater(Game *game) {
Object *p = game->player;
Object *tool = waterInHand(p);
if (!tool) {
if (furnitureNear(game, FURN_COUNTER, 0.7f)) {
p->dirt *= 0.4f;
say(game, p->dirt > 0.05f ? "You wash up a bit at the sink" : "You wash up - clean again");
return CLICK_NONE;
}
return -1;
}
if (tool->type == OBJ_CAN && furnitureNear(game, FURN_TAP, 1.0f)) {
tool->weight += CAN_LITERS - tool->water;
tool->water = CAN_LITERS;
say(game, "You fill the watering can");
return CLICK_NONE;
}
if (tool->type == OBJ_CAN && tool->water <= 0.05f) {
say(game, "The watering can is empty - fill it at the tap");
return CLICK_NONE;
}
if (tool->type == OBJ_HOSE && Hose_burnedThrough(&game->world, tool, NULL))
say(game, "Nothing comes out - the hose is burned through. Cut the burnt bit out with the shears");
else if (tool->type == OBJ_HOSE && Hose_flow(&game->world, tool, 0.0f) < 0.95f)
say(game, "The hose leaks where it's melted - duct tape would mend it");
p->spraying = true;
return CLICK_WATER;
}
/* At the car's open boot, or carrying something out of it: what to do with it. */
static bool game_bootHint(Game *game, char *out, size_t size) {
const Object *p = game->player, *car = game->arrival.car;
for (int h = 0; h < 2; h++) {
const Object *it = p->hands.item[h];
if (!it || p->hands.state[h] != HAND_HOLDING) continue;
if (it->type == OBJ_CRATE) { snprintf(out, size, "The crates go into the kitchen in your garages, along the wall - right click: put it down"); return true; }
if (it->type == OBJ_GEAR && it->variant <= GEAR_MAT) { /* (its owner's tent up: in at its door) */
const Object *tent = Tent_of(game->world.objects, game->world.count, it->owner);
if (tent && tent->tent_stage >= TENT_COVER) {
snprintf(out, size, "Put it down at %s tent's door (right click there): it goes in", it->owner == p ? "your" : "their");
return true;
}
}
if (it->type == OBJ_GEAR) {
snprintf(out, size, "%s - up to the meadow behind your garages (the path left of them), where the tents go - right click: put it down",
it->owner == p ? "Yours" : it->owner ? it->owner->name : "Somebody's");
return true;
}
}
if (!car || !car->boot_open || !car->load || car->load->n == 0) return false;
Vec3 boot = Car_bootSpot(car);
if (hypotf(boot.x - p->position.x, boot.z - p->position.z) > 1.6f) return false;
snprintf(out, size, "The boot: left click - take out %s%s", Car_nextName(car), Car_nextHeavy(car) ? " (both hands)" : "");
return true;
}
/* At a tent: what to do with it now. */
static bool tentHint(Game *game, char *out, size_t size) {
Object *p = game->player, *bag = tentNear(game, true), *tent = tentNear(game, false);
if (bag) { snprintf(out, size, "A tent, packed - left click: spread it out here (the door towards you) - E / Q: carry it"); return true; }
if (game->tent_drag) { snprintf(out, size, "Moving the tent - let go of F to set it down here"); return true; }
if (tent) { snprintf(out, size, "The tent: hold left click - %s%s%s", Tent_job(tent), tent->tent_stage == TENT_PEGS && !tent->tent_down ? " (a stone in the other hand: quicker)" : "",
Tent_movable(tent) ? " - F (hold): move it" : ""); return true; }
Object *mine = Tent_of(game->world.objects, game->world.count, p), *standing = tentFind(game, false, true);
Vec3 door = mine ? Tent_door(mine) : p->position;
bool at_door = mine && mine->tent_stage >= TENT_COVER && hypotf(door.x - p->position.x, door.z - p->position.z) <= 1.6f;
if (at_door && Hands_carries(p, OBJ_GEAR)) { snprintf(out, size, "Your tent's door: right click - put it down here, it goes in"); return true; }
if (at_door && mine->tent_stage >= TENT_UP && !mine->tent_down) {
snprintf(out, size, "Your tent%s - Z: crawl in and sleep%s", Tent_hasBed(mine, p) ? "" : " (your sleeping bag isn't in it: put it down at the door)",
standing == mine ? " - hold left click: take it down" : "");
return true;
}
if (standing) {
snprintf(out, size, "%s%s tent - hold left click: take it down", standing->owner == p ? "Your" : standing->owner ? standing->owner->name : "A",
standing->owner && standing->owner != p ? "'s" : "");
return true;
}
return false;
}
/* Somebody near who asked you for a paper, a filter or tobacco (and is still waiting). */
static const Object *askingYou(const Game *game) {
const Object *p = game->player;
for (int i = 0; i < game->world.count; i++) {
Object *o = game->world.objects[i];
if (o->type != OBJ_HUMAN || !o->ai.asking_you) continue;
if (o->ai.asking_you == POCKET_ASHTRAY ? !o->butt_held : Pockets_hasSmoke(o) && Pockets_need(o) == POCKET_NOTHING) {
o->ai.asking_you = 0; /* (got it elsewhere) */
continue;
}
if (hypotf(o->position.x - p->position.x, o->position.z - p->position.z) > 8.0f) { o->ai.asking_you = 0; continue; } /* (walked off) */
return o;
}
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 (tree->variant == 1) snprintf(out, size, "One of Kurti's apple trees - felling it costs 300 EUR, and Kurti won't forgive it");
else if (tree->variant == 2) snprintf(out, size, "No rope on it: it'll go the way it leans - across the road, onto the phone line");
else if (tree->keep) snprintf(out, size, "A protected tree - felling it costs a fine of 1000 EUR");
else 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;
bool at_top;
if (game->player->on_fire > 0.0f)
snprintf(game->hint, sizeof(game->hint), "On fire! C: drop and roll - left click: pour your drink over you - water or pee puts it out");
else if ((asking = askingYou(game)))
snprintf(game->hint, sizeof(game->hint), "%s asks you for %s - B: your pockets, look at %s, Enter: %s",
asking->name, Pockets_needName((PocketNeed)asking->ai.asking_you), asking->female ? "her" : "him",
asking->ai.asking_you == POCKET_ASHTRAY ? "hold it out" : "give it");
else if (game->player->riding)
snprintf(game->hint, sizeof(game->hint), "Nearly there - Homburgsh\xC3\xB6he. Look around (mouse)");
else if (game_bootHint(game, game->hint, sizeof(game->hint))) {}
else if (tentHint(game, game->hint, sizeof(game->hint))) {}
else if (shown)
snprintf(game->hint, sizeof(game->hint), "%s follows you: walk to where the firewood should go and left click - Backspace: never mind", shown->name);
else if (watcher)
snprintf(game->hint, sizeof(game->hint), "%s watches you: cut or split some wood to show how it's done - Backspace: never mind", watcher->name);
else if ((fire = Camp_inFront(game->player, game->world.objects, game->world.count, OBJ_FIREPLACE)) &&
(food = Camp_onGrill(game->world.objects, game->world.count, fire))) {
char what[96];
Object_describe(food, what, sizeof what);
snprintf(game->hint, sizeof(game->hint), "On the grill: %s - left click (free hand): take it off", what);
} 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");
else
return game_task(game);
return game->hint;
}
/* A drink over someone burning (yourself first). */
static bool douse(Game *game, Object *person) {
Object *p = game->player;
int hand = Hands_bottleHand(p);
if (hand < 0 || !Object_isBottle(p->hands.item[hand])) return false;
Object *target = p->on_fire > 0.0f ? p : person;
if (!target || !Camp_douse(target, p->hands.item[hand])) return false;
say(game, target == p ? "You pour it over yourself. Phew!" : "You put the fire out");
return true;
}
/* C right at a burning fire with a drink in hand: pour it on. */
static bool pourOnFire(Game *game) {
Object *p = game->player;
Object *fire = Camp_inFront(p, game->world.objects, game->world.count, OBJ_FIREPLACE);
int hand = Hands_bottleHand(p);
if (!fire || !fire->burning || hand < 0) return false;
switch (Camp_pourOnFire(fire, p->hands.item[hand])) {
case POURED_OUT: say(game, "Pssshhh - you pour your drink on it and the fire goes out"); break;
case POURED_HISS: say(game, "It hisses and smokes - a drink isn't enough for a fire this big"); break;
case POURED_FLARE: say(game, "WHOOSH! The vodka flares up - that didn't help"); break;
default: return false;
}
return true;
}
/* The fireplace in front: wood in, or light it. True if it took the click. */
static bool tendFire(Game *game) {
Object *p = game->player;
Object *fire = Camp_inFront(p, game->world.objects, game->world.count, OBJ_FIREPLACE);
if (!fire) return false;
int fed = Camp_feed(p, fire);
if (fed == FED_GRILL) {
say(game, fire->burning ? "On the grill - click the fire again to take it off when it's done"
: "On the grill - light the fire to grill it");
return true;
}
Object *food = fed == FED_NOTHING ? Camp_takeOff(p, fire, game->world.objects, game->world.count) : NULL;
if (food) {
tell(game, food);
return true;
}
if (fed == FED_WOOD) {
Ai_practice(p, ORDER_FIRE, 0.015f);
say(game, fire->burning ? "More wood on the fire" : "Wood in the fireplace - click again to light it");
return true;
}
if (!fire->burning) {
if (fire->fuel <= 0.0f) say(game, "Put firewood or branches in first");
else if (!p->lighter) say(game, "No lighter in your pocket - ask somebody (talk to them)");
else if (!Pockets_light(p)) say(game, "Your lighter is empty - click, click, nothing");
else if (Camp_light(fire)) {
say(game, p->lighter_uses == 0 ? "You light the fire - that was the lighter's last flame" : "You light the fire");
Ai_practice(p, ORDER_FIRE, 0.03f);
}
return true;
}
tell(game, fire);
return true;
}
/* The waste container in front: in with trash and bags from the hands. */
static bool throwAway(Game *game) {
Object *p = game->player;
if (!Camp_inFront(p, game->world.objects, game->world.count, OBJ_BIN)) return false;
int gone = Camp_throwAway(p);
static char line[64];
if (gone > 0) {
snprintf(line, sizeof(line), "Thrown away: %d piece%s of trash", gone, gone == 1 ? "" : "s");
say(game, line);
} else {
say(game, "Waste container - bring trash or a garbage bag");
}
return true;
}
/* With a garbage bag in hand: the trash in front goes into it. */
static bool bagTrash(Game *game) {
Object *p = game->player;
Object *bag = Camp_bagInHand(p);
if (!bag) return false;
Object *trash = Camp_trashInFront(p, game->world.objects, game->world.count);
if (!trash) return false;
Hands_touch(p, trash->position);
if (!Camp_bag(bag, trash)) say(game, "The bag is full - take it to the container");
else { tell(game, bag); Ai_practice(p, ORDER_TRASH, 0.01f); }
game->bag_timer = BAG_INTERVAL;
return true;
}
/* Drunk empty (and nothing new to take in a free hand): where it goes, the
way you are (see Camp_doneWithBottle). */
static bool doneWithBottle(Game *game) {
Object *p = game->player;
int hand = -1;
for (int i = 0; i < 2; i++) {
Object *o = p->hands.item[i];
if (p->hands.state[i] == HAND_HOLDING && o && Object_isBottle(o) && o->sips <= 0) hand = i;
}
if (hand < 0) return false;
if (Hands_freeHand(p) >= 0 && Hands_inFront(p, game->world.objects, game->world.count)) return false;
float roll = (float)(rand() % 1000) / 1000.0f;
switch (Camp_doneWithBottle(p, hand, game->world.objects, game->world.count, roll)) {
case DONE_BIN: say(game, "Empty - into the container"); break;
case DONE_PLACED: say(game, "Empty - you stand it up there"); break;
case DONE_WITH_OTHERS: say(game, "Empty - with the other empties"); break;
case DONE_DOWN: say(game, "Empty - you put it down"); break;
case DONE_TOSSED: say(game, "Empty - away with it"); break;
default: return false;
}
return true;
}
/* Left click: work with a tool in hand (or drink, if there's no work for it),
lift what you hold on to, drink or eat what's in the hand, else take what's in
front. Returns what holding the button keeps doing. */
static int leftClick(Game *game) {
Object *p = game->player;
Object *const *objects = game->world.objects;
const int count = game->world.count;
/* Alice right in front: pet her */
for (int i = 0; i < count; i++) {
Object *o = objects[i];
if (o->type != OBJ_DOG || Hands_gapFrom(p->position, p->look.height, o) > 0.9f) continue;
Vec3 to = vec3_sub(o->position, p->position);
to.y = 0.0f;
if (vec3_length(to) > 1e-3f && vec3_dot(vec3_normalize(to), Object_facing(p)) < 0.5f) continue;
Dog_pet(o);
static char line[48];
snprintf(line, sizeof(line), "%s wags her tail", o->name);
say(game, line);
return CLICK_NONE;
}
Object *person = game_personInFront(game);
if ((p->on_fire > 0.0f || (person && person->on_fire > 0.0f)) && douse(game, person)) return CLICK_NONE;
/* Being shown where the firewood goes: here */
Object *shown = follower(game, SHOW_PLACE);
if (shown && shown != person && !Talk_active(&game->talk)) {
Talk_showHere(&game->world, p, shown);
return CLICK_NONE;
}
int tent = tentClick(game);
if (tent >= 0) return tent;
if (Hands_freeHand(p) >= 0 && !Hands_inFront(p, objects, count) && (game->stage = stageAt(game)) != NULL) { /* (a hand free at a stage: building) */
tell(game, game->stage);
return CLICK_STAGE;
}
if (pickSnail(game)) return CLICK_NONE; /* (a snail in the grass: picked up, not mown) */
int mow = startMowing(game);
if (mow >= 0) return mow;
if (game_unloadCar(game) || game_bigTreeClick(game) || mendHose(game) || useLadder(game) || tendFire(game) || throwAway(game) || takePullover(game) || smokesMachine(game) || swingGate(game)) return CLICK_NONE;
int watering = useWater(game);
if (watering >= 0) return watering;
if (bagTrash(game)) return CLICK_BAG;
ToolKind tool = Tools_held(p, NULL);
if (tool != TOOL_NONE && tool != TOOL_HANDS) {
/* Nothing to work on: drink from the other hand, or take what's in front -
but not at the work (a step away from the tree, say): the tool comes first */
if (!Tools_hasJob(p, objects, count)) {
if (Tools_jobNearby(p, objects, count)) {
if (game->message_time <= 0.0f) say(game, "Step up to it (and face it) to work");
return CLICK_NONE;
}
if (drinkNow(game)) return CLICK_DRINK;
if (Hands_freeHand(p) >= 0 && Hands_inFront(p, objects, count)) {
takeInFront(game);
return CLICK_NONE;
}
}
work(game);
return CLICK_WORK;
}
if (Hands_isGrabbing(p)) {
liftTogether(game);
return CLICK_NONE;
}
if (drinkNow(game)) return CLICK_DRINK;
if (doneWithBottle(game)) return CLICK_NONE;
takeInFront(game);
return CLICK_NONE;
}
/* Left button held: the next stroke or sip as soon as the last one is done. */
static void keepClicking(Game *game) {
Object *p = game->player;
switch (game->click_action) {
case CLICK_WORK:
if (!Tools_isWorking(p) && !Hands_isDrinking(p)) work(game);
break;
case CLICK_DRINK:
if (!Hands_isDrinking(p) && !drinkNow(game)) game->click_action = CLICK_NONE;
break;
case CLICK_BAG:
if (game->bag_timer <= 0.0f && !bagTrash(game)) game->click_action = CLICK_NONE;
break;
case CLICK_WATER:
p->spraying = true; /* see the world: the water arc */
break;
case CLICK_TENT:
if (!workTent(game)) game->click_action = CLICK_NONE;
break;
case CLICK_STAGE:
if (!workStage(game)) game->click_action = CLICK_NONE;
break;
case CLICK_MOW:
if (!mowStep(game)) game->click_action = CLICK_NONE;
break;
default:
break;
}
}
/* Right click: put down what is lifted, let go, or drop what is in the hands. */
/* A crate right in front (or beside you), within reach: NULL if none. */
#define CRATE_REACH 1.4f
static Object *crateAhead(Game *game) {
Object *p = game->player, *best = NULL;
float best_d = CRATE_REACH;
Vec3 f = Object_facing(p);
for (int i = 0; i < game->world.count; i++) {
Object *c = game->world.objects[i];
if (c->type != OBJ_CRATE || c->is_held) continue;
Vec3 to = vec3(c->position.x - p->position.x, 0.0f, c->position.z - p->position.z);
float d = vec3_length(to);
if (d > 0.4f && vec3_dot(vec3_scale(to, 1.0f / d), f) < -0.2f) continue; /* (not behind you) */
if (d < best_d) { best_d = d; best = c; }
}
return best;
}
/* A bottle that comes in crates, put down next to one: into it (its own crate if one is there). */
static bool bottleIntoCrate(Game *game, int hand) {
Object *p = game->player, *it = p->hands.item[hand];
if (!it || p->hands.state[hand] != HAND_HOLDING || !Crate_bottleKind(it) || it->broken) return false;
Object *crate = Crate_forBottle(game->world.objects, game->world.count, it, p->position, CRATE_REACH);
if (!crate) {
if (crateAhead(game)) say(game, "The crate is full");
return crateAhead(game) != NULL;
}
Hands_drop(p, hand);
Hands_touch(p, vec3_add(Object_colliderCenter(crate, 0), vec3(0.0f, 0.2f, 0.0f)));
bool empty = it->sips <= 0, own = Crate_belongs(crate, Crate_bottleKind(it));
Crate_putBottle(crate, it);
World_noise(&game->world, NOISE_CLINK, crate->position, 0.6f);
char line[96];
snprintf(line, sizeof line, "%s into the %s crate%s", empty ? "The empty" : "The bottle", Crate_name(crate->variant),
own ? "" : " - it doesn't belong there (sort it later: right click at the crate)");
say(game, line);
return true;
}
/* Hands empty at a crate: out comes a bottle that doesn't belong in it. */
static bool wrongOutOfCrate(Game *game) {
Object *p = game->player, *crate = crateAhead(game);
int hand = Hands_freeHand(p);
if (!crate || hand < 0) return false;
if (!Crate_wrong(crate)) { say(game, "Everything in this crate belongs there"); return true; }
Object *b = Crate_takeWrong(&game->world, crate);
if (b && Hands_receive(p, hand, b)) { /* (straight out of the crate into the hand) */
World_noise(&game->world, NOISE_CLINK, crate->position, 0.5f);
tell(game, b);
}
return true;
}
static void putDown(Game *game) {
Object *p = game->player;
if (Hands_isGrabbing(p)) {
if (p->hands.grabbed->lifted) Hands_toggleLift(p, game->world.objects, game->world.count);
Hands_release(p);
game->holding = false;
return;
}
int hand = p->hands.state[HAND_RIGHT] != HAND_FREE ? HAND_RIGHT
: p->hands.state[HAND_LEFT] != HAND_FREE ? HAND_LEFT : -1;
if (hand >= 0 && !bottleIntoCrate(game, hand)) Hands_drop(p, hand);
}
/* Z: lie down to sleep when tired enough, or get up again. */
static void sleepOrWake(Game *game) {
Object *p = game->player;
Body *b = &p->body;
if (b->asleep) {
if (p->passed_out) say(game, "Too drunk to get up");
else Body_getUp(b);
return;
}
if (Body_isBusy(b)) return;
if (p->needs.sleepiness < 0.3f) { say(game, "Not tired"); return; }
Object *tent = Tent_of(game->world.objects, game->world.count, p);
if (tent && tent->tent_stage >= TENT_UP) {
Vec3 door = Tent_door(tent);
if (hypotf(door.x - p->position.x, door.z - p->position.z) < 1.5f) {
Tent_crawlIn(tent, p);
say(game, Tent_hasBed(tent, p) ? "You crawl into your tent and your sleeping bag" : "You crawl into your tent (no sleeping bag in it: a cold night)");
return;
}
}
Body_lieDown(b);
}
/* Backspace: never mind - whoever you were showing how or where goes back to what they did. */
static void callOff(Game *game) {
int n = 0;
for (int i = 0; i < game->world.count; i++) {
Object *o = game->world.objects[i];
if (o->type != OBJ_HUMAN || o->ai.mode != AI_FOLLOW) continue;
Ai_stopShowing(o);
Ai_say(o, "Okay, never mind.");
n++;
}
say(game, n ? "Never mind - called off" : "Nothing to call off");
}
/* Hands: left click does the obvious thing (see leftClick), E / Q pick up or
drop with one hand, F holds on to push and pull. */
void game_handleActions(Game *game, const Input *input) {
Object *p = game->player;
Object *const *objects = game->world.objects;
int count = game->world.count;
game_dev(game, input); /* (the dev menus: in the car too) */
if (p->riding) { /* (in the car: looking about only) */
game->click_action = CLICK_NONE;
if (input->show_keys) game->show_keys = !game->show_keys;
return;
}
if (input->toggle_view) game->third_person = !game->third_person;
if (input->show_keys) game->show_keys = !game->show_keys;
if (input->show_stats) game->show_stats = !game->show_stats;
game_pockets(game, input);
if (input->page && game->show_stats && !game->show_dev && !game->show_crew) game->stats_page = (game->stats_page + input->page + STATS_PAGES) % STATS_PAGES;
if (input->call_help) game_callHelp(game);
if (input->dismiss_help) dismissHelp(game);
if (input->cancel) callOff(game);
if (input->call) game_callOver(game);
if (input->sleep) sleepOrWake(game);
/* C: sit down (squat), e.g. by the fire; walking or jumping gets you up */
if (input->sit && p->on_fire > 0.0f && !p->on_ladder) Body_dropAndRoll(&p->body, Object_facing(p)); /* drop and roll */
else if (input->sit && pourOnFire(game)) {} /* (right at a burning fire with a drink: onto it) */
else if (input->sit && !Body_isBusy(&p->body) && !p->on_ladder) {
if (p->sitting) p->sitting = false;
else {
if (!Object_sit(p, Object_seatNear(game->world.objects, game->world.count, p->position, 1.0f), p->position))
say(game, "No room there - somebody's sitting on that spot");
Object *c = p->pullover;
if (!p->seat && p->pullover_down && c && c->variant == CLOTH_SPREAD &&
vec3_length(vec3(c->position.x - p->position.x, 0.0f, c->position.z - p->position.z)) < 0.6f)
say(game, "You sit down on your pullover");
}
}
if (input->move_forward != 0.0f || input->move_right != 0.0f || input->jump) p->sitting = false;
if (p->on_ladder) { /* climbing: hands on the rungs */
game->click_action = CLICK_NONE;
p->spraying = false;
return;
}
p->work_state.aim = game->camera.front; /* strokes go where you look */
p->body.pee_aim = game->camera.front; /* ... and the stream */
p->body.pee_power = input->sprint ? 1.0f : 0.0f; /* Shift: pushing harder, further */
if (Body_isBusy(&p->body)) { /* lying on the ground, being sick, peeing */
game->holding = false;
game->click_action = CLICK_NONE;
return;
}
for (int k = 0; k < 2; k++) { /* done peeing: what you put down goes back into your hands */
Object *item = p->ai.set_aside[k];
if (!item) continue;
p->ai.set_aside[k] = NULL;
if (item->is_held || vec3_length(vec3_sub(item->position, p->position)) > 1.8f) continue;
int hand = p->hands.state[k] == HAND_FREE ? k : Hands_freeHand(p);
if (hand >= 0) Hands_pickUpObject(p, hand, item);
}
/* E / Q while holding on to something: everybody holding it lifts it (or puts it down) */
if ((input->use_left || input->use_right) && Hands_isGrabbing(p)) {
liftTogether(game);
} else if ((input->use_left || input->use_right) && takeLadder(game)) {
/* (taken down) */
} else {
bool used[2];
game_touchKeys(game, input, used); /* (reaching out, letting go) */
if (input->use_left && !used[HAND_LEFT] && !bottleIntoCrate(game, HAND_LEFT)) Hands_pickUpOrDrop(p, HAND_LEFT, objects, count);
if (input->use_right && !used[HAND_RIGHT] && !bottleIntoCrate(game, HAND_RIGHT)) Hands_pickUpOrDrop(p, HAND_RIGHT, objects, count);
if ((input->use_left && !used[HAND_LEFT]) || (input->use_right && !used[HAND_RIGHT])) tell(game, reachingFor(p));
}
/* Left click (held: again and again), right click puts down */
p->spraying = false;
game->player->mowing = 0.0f; /* (swinging the scythe: only while it's held down) */
freeSnails(game);
if (input->click) game->click_action = leftClick(game);
else if (input->click_held) keepClicking(game);
else game->click_action = CLICK_NONE;
if (input->right_click && !Talk_active(&game->talk) && !game->shouting) { /* (empty-handed at a leaning ladder: take it down) */
bool empty = !p->hands.item[HAND_LEFT] && !p->hands.item[HAND_RIGHT];
if (!empty || (!takeLadder(game) && !wrongOutOfCrate(game))) putDown(game);
}
/* F (held) at a tent with no pegs in: move it */
if (moveTent(game, input->grab)) return;
/* F (held) or a click on something heavy: hold on to push, pull or lift */
bool grab = input->grab || game->holding;
if (grab && !Hands_isGrabbing(p)) {
if (!Hands_grab(p, objects, count)) game->holding = false;
}
if (!grab && Hands_isGrabbing(p)) {
if (p->hands.grabbed->lifted) Hands_toggleLift(p, objects, count);
Hands_release(p);
}
if (!Hands_isGrabbing(p)) game->holding = false;
/* T: tap to toss it, hold to wind up (overhand, harder), let go to throw */
if (input->throw_held && (input->throw_item || Hands_isWindingUp(p))) Hands_windUp(p);
else if (Hands_isWindingUp(p) || input->throw_item) Hands_throw(p, game->camera.front);
if (input->smoke) smokeOrStop(game);
if (input->pee) {
Object *toilet = furnitureNear(game, FURN_TOILET, 1.3f);
if (p->needs.bladder <= 0.15f) say(game, "No need right now");
else if (toilet && toilet->dog.mode) say(game, "Occupied!");
else if (toilet) { Object_useToilet(p, toilet); say(game, "*in the toilet*"); }
else {
bool full = p->hands.item[HAND_LEFT] || p->hands.item[HAND_RIGHT];
Hands_setAside(p, p->ai.set_aside); /* (hands free: the dress up, the fly down; taken up again after) */
Body_pee(&p->body, Object_peeTime(p));
if (full) say(game, "You put your things down first");
}
}
}