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_talk.c raw
/*
* 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;
}
}