/* * Talking and calling out: X talks to whoever is right in front, or opens the * shout menu (everybody, someone by name, the dog). A shout carries so far, * less over a running chainsaw; whoever hears it may come (see Ai_call). * G / H: helpers, R: a quick call to the one you look at. */ #include "game/game_internal.h" #define HELP_CALL_RANGE 15.0f #define TALK_REACH 2.2f /* someone this close in front: X talks to them */ #define SHOUT_RANGE 35.0f /* a shout carries this far ... */ #define NOISE_RANGE 6.0f /* ... not over a chainsaw this close to the listener */ #define SHOUT_NAMES 3 /* people the menu offers to call by name */ /* G: the nearest person who isn't helping yet comes to help (press again for more). */ void game_callHelp(Game *game) { Object *nearest = NULL; float best = HELP_CALL_RANGE; for (int i = 0; i < game->world.count; i++) { Object *o = game->world.objects[i]; if (o->type != OBJ_HUMAN || Ai_isHelping(o) || Body_isDown(&o->body) || o->lives_here) continue; /* (the owners don't help: your festival) */ float d = vec3_length(vec3_sub(o->position, game->player->position)); if (d < best) { best = d; nearest = o; } } if (nearest) Ai_help(nearest, game->player); } /* Someone right in front of the player, close enough to talk to. */ Object *game_personInFront(const Game *game) { const Object *p = game->player; Vec3 facing = Object_facing(p); Object *best = NULL; float best_dist = TALK_REACH; for (int i = 0; i < game->world.count; i++) { Object *o = game->world.objects[i]; if (o == p || o->type != OBJ_HUMAN || Body_isDown(&o->body)) continue; Vec3 to = vec3_sub(o->position, p->position); to.y = 0.0f; float d = vec3_length(to); if (d < best_dist && d > 1e-3f && vec3_dot(vec3_scale(to, 1.0f / d), facing) > 0.75f) { best_dist = d; best = o; } } return best; } /* Does a shout from the player reach them? Not asleep, not too far; a chainsaw running near them drowns it out; drink makes them hard of hearing. */ static bool hears(const Game *game, const Object *o) { if (o->body.asleep || o->toilet) return false; float range = SHOUT_RANGE * (1.0f - 0.3f * fminf(o->drunk, 1.0f)); for (int i = 0; i < game->world.count; i++) { const Object *w = game->world.objects[i]; if (w->is_character && Tools_isWorking(w) && w->work_state.tool == TOOL_CHAINSAW && vec3_length(vec3_sub(w->position, o->position)) < NOISE_RANGE) range *= 0.3f; } return vec3_length(vec3_sub(o->position, game->player->position)) < range; } /* Calls one of them over: true if they heard it (they come, or say why not). */ static bool callOne(Game *game, Object *o) { if (o->indoors > 0.0f) { /* in the house: out they come */ o->indoors = fminf(o->indoors, 3.0f); Ai_say(o, "Coming!"); } if (!hears(game, o)) return false; if (o->type == OBJ_DOG) Dog_call(o, game->player); else if (!Ai_call(o, game->player)) Ai_say(o, "Not now!"); return true; } /* R: calls the one you look at (or the nearest) over, if they can hear it. */ void game_callOver(Game *game) { Object *p = game->player; Vec3 look = vec3_normalize(vec3(game->camera.front.x, 0.0f, game->camera.front.z)); Object *best = NULL; float best_score = -1e9f; for (int i = 0; i < game->world.count; i++) { Object *o = game->world.objects[i]; if (o == p || (o->type != OBJ_HUMAN && o->type != OBJ_DOG)) continue; Vec3 to = vec3_sub(o->position, p->position); to.y = 0.0f; float d = vec3_length(to); if (d > SHOUT_RANGE || d < 1e-3f) continue; float score = vec3_dot(vec3_scale(to, 1.0f / d), look) * 2.0f - d / SHOUT_RANGE; if (score > best_score) { best_score = score; best = o; } } static char line[64]; if (!best) { say(game, "Hello? ...nobody hears you"); return; } snprintf(line, sizeof(line), "Hey, %s!", best->name); say(game, line); if (!callOne(game, best)) tellMore(game, "no answer"); } /* ---------- The shout menu ---------- */ /* Fills the menu: everybody, the nearest few by name, the dog. */ static void openShout(Game *game) { const Object *p = game->player; game->shout_count = 0; game->shout_dog = NULL; for (int i = 0; i < game->world.count; i++) { Object *o = game->world.objects[i]; if (o->type == OBJ_DOG && !game->shout_dog) game->shout_dog = o; if (o->type != OBJ_HUMAN) continue; /* keep the nearest SHOUT_NAMES, nearest first */ float d = vec3_length(vec3_sub(o->position, p->position)); int at = game->shout_count; while (at > 0 && vec3_length(vec3_sub(game->shout_to[at - 1]->position, p->position)) > d) at--; if (at >= SHOUT_NAMES) continue; int last = game->shout_count < SHOUT_NAMES ? game->shout_count++ : SHOUT_NAMES - 1; for (int k = last; k > at; k--) game->shout_to[k] = game->shout_to[k - 1]; game->shout_to[at] = o; } game->shouting = true; } static void shout(Game *game, int choice) { static char line[64]; game->shouting = false; int heard = 0; bool storm = game->world.fest.stage == FEST_WARNED || game->world.fest.stage == FEST_STORM; if (storm && choice == 1) { /* (the storm coming: that first) */ heard = game_shoutStorm(game); if (!heard) tellMore(game, "nobody near who doesn't know already"); return; } if (storm) choice--; int shown = game->shout_count < TALK_CHOICES - 1 - (storm ? 2 : 1) ? game->shout_count : TALK_CHOICES - 1 - (storm ? 2 : 1); /* (names on the menu) */ if (choice == 1) { say(game, "Hey, everybody! Over here!"); for (int i = 0; i < game->world.count; i++) { /* (your lot: not Kurti and his sons - call them by name) */ Object *o = game->world.objects[i]; if (o->type == OBJ_HUMAN && !o->lives_here && !o->guest && callOne(game, o)) heard++; /* (the crew: not the guests) */ } } else if (choice - 2 < shown) { Object *o = game->shout_to[choice - 2]; snprintf(line, sizeof(line), "%s! Come here!", o->name); say(game, line); heard = callOne(game, o); } else if (game->shout_dog) { snprintf(line, sizeof(line), "%s! Here, girl!", game->shout_dog->name); say(game, line); heard = callOne(game, game->shout_dog); } if (!heard) tellMore(game, "...nobody seems to hear you"); } int Game_choices(const Game *game, const char *out[TALK_CHOICES]) { int meeting = Game_meetingChoices(game, out); /* (a question at a meeting: that first) */ if (meeting) return meeting; if (!game->shouting && !Talk_active(&game->talk)) { int touch = game_touchChoices(game, out); if (touch) return touch; } /* (holding somebody) */ if (!game->shouting) return Talk_choices(&game->talk, out); Game *g = (Game *)game; /* (the lines live in the game) */ int n = 0; if (game->world.fest.stage == FEST_WARNED || game->world.fest.stage == FEST_STORM) out[n++] = "Shout: \"Storm! Everybody inside!\""; out[n++] = "Shout: \"Hey, everybody! Over here!\""; for (int k = 0; k < game->shout_count && n < TALK_CHOICES - 1; k++) { snprintf(g->shout_lines[k], sizeof(g->shout_lines[k]), "Call %s (%.0f m)", game->shout_to[k]->name, vec3_length(vec3_sub(game->shout_to[k]->position, game->player->position))); out[n++] = g->shout_lines[k]; } if (game->shout_dog) { snprintf(g->shout_lines[SHOUT_NAMES], sizeof(g->shout_lines[SHOUT_NAMES]), "Call %s", game->shout_dog->name); out[n++] = g->shout_lines[SHOUT_NAMES]; } return n; } /* Conversations: X talks to whoever is in front (or opens the shout menu); answers with 1-5, right click, X or walking off ends them. People who walked up to say something get to say it. */ void game_talk(Game *game, const Input *input, float dt) { Talk *t = &game->talk; if (game->meeting.kind && game->meeting.asking) return; /* (the keys answer the meeting) */ bool leave = input->right_click; if (input->talk) { if (game->shouting) game->shouting = false; else if (Talk_active(t)) leave = true; else if (!Body_isBusy(&game->player->body)) { Object *person = game_personInFront(game); if (person) Talk_begin(t, &game->world, game->player, person, TOPIC_NONE); else openShout(game); } } if (game->shouting) { if (input->right_click || Talk_active(t)) game->shouting = false; else if (input->choice >= 1) { const char *lines[TALK_CHOICES]; if (input->choice <= Game_choices(game, lines)) shout(game, input->choice); } return; } if (!Talk_active(t) && input->choice >= 1) { /* holding somebody: what you do */ const char *lines[TALK_CHOICES]; if (game_touchChoices(game, lines) >= input->choice) { game_touchAnswer(game, input->choice); return; } } if (!Talk_active(t)) { for (int i = 0; i < game->world.count; i++) { Object *o = game->world.objects[i]; if (o->type == OBJ_HUMAN && o->ai.mode == AI_APPROACH && o->ai.ready_to_talk && !Body_isBusy(&game->player->body)) { Talk_begin(t, &game->world, game->player, o, o->ai.topic); break; } } } Talk_update(t, &game->world, game->player, input->choice, leave, dt); if (t->errand) { /* a shopping run agreed: off they go */ game_shopStart(game, t->errand, t->shop); t->errand = NULL; } }