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_touch.c raw
/*
* Your hands and other people (people/touch.h, hands.h):
*
* - E / Q: that hand reaches out where you look and takes hold of whatever
* comes in reach - something to carry, something heavy (push, pull, lift
* together), something fixed (a post, a tree, a railing: you can't walk
* off further than your arm), or somebody, at the part of them you got.
* Again: let go (or put down what it carries).
* - Holding somebody: answers 1 - 5 - pull them closer, push them away, take
* them along (by the hand: hand in hand), calm them, let go. How they take
* it goes with how they feel about you (Object.like, love).
* - Who sees it: kind acts lift how the place looks out for each other
* (World.aware) and how they see you; harassment sinks it - and the more
* aware the place, the likelier somebody steps in (the awareness team: all
* the more). The place's awareness drifts with the mood of everybody and,
* at the festival, how good the floors are.
*/
#include <math.h>
#include "game/game_internal.h"
#include "people/touch.h"
#include "world/why.h"
#define SEE_NEAR 16.0f /* m: who notices a touch */
#define ARM_REACH 0.72f /* m (scaled) from the shoulder: holding on to something fixed, no further */
#define HOLD_BREAK 0.45f /* m past that: you lose your grip (somebody pulled away, you were pushed) */
#define AWARE_EASE 0.004f /* a real second: the place's awareness drifting to how it is */
#define AWARE_KIND 0.01f /* a kind act seen */
#define AWARE_HARASS 0.03f /* harassment seen */
static Object *heldPerson(const Game *game, int *hand) {
const Object *p = game->player;
for (int i = 0; i < 2; i++)
if (p->hands.state[i] == HAND_HOLD && p->hands.hold[i] && p->hands.hold[i]->is_character) { if (hand) *hand = i; return p->hands.hold[i]; }
return NULL;
}
static Vec3 shoulderOf(const Object *o, int hand) {
return Figure_toWorld(o->position, Object_facing(o), o->skeleton.joints[hand == HAND_LEFT ? JOINT_SHOULDER_L : JOINT_SHOULDER_R]);
}
static void letGo(Game *game, int hand) {
Object *p = game->player, *o = p->hands.hold[hand];
if (o && o->is_character && o->led_by == p) o->led_by = NULL;
Hands_letGo(p, hand);
}
/* E / Q: used[hand] - handled here (else the old way: picked up in front, or put down: hands.c). */
void game_touchKeys(Game *game, const Input *input, bool used[2]) {
Object *p = game->player;
bool keys[2] = { input->use_left, input->use_right };
for (int hand = 0; hand < 2; hand++) {
used[hand] = false;
if (!keys[hand]) continue;
int state = p->hands.state[hand];
if (state == HAND_READY || state == HAND_HOLD) { letGo(game, hand); say(game, "You let go"); used[hand] = true; continue; }
if (state != HAND_FREE || p->hands.grabbed) continue;
if (Hands_inFront(p, game->world.objects, game->world.count)) continue; /* (something right there to take: taken, as ever) */
Hands_reachOut(p, hand);
say(game, hand == HAND_LEFT ? "Left hand out - it takes hold of whatever comes in reach (Q again: never mind)"
: "Right hand out - it takes hold of whatever comes in reach (E again: never mind)");
used[hand] = true;
}
}
/* ---------- Who saw it ---------- */
static void noticed(Game *game, Object *by, Object *whom, int what, int part) {
World *w = &game->world;
bool you = by->type == OBJ_PLAYER;
bool kind = what == TOUCHED_WELCOME && (part < 0 || part == TOUCH_HAND || part == TOUCH_SHOULDER || part == TOUCH_BACK || part == TOUCH_ARM);
if (kind) w->aware = fminf(1.0f, w->aware + AWARE_KIND);
if (what == TOUCHED_HARASSED) w->aware = fmaxf(0.0f, w->aware - AWARE_HARASS);
Object *stepper = NULL;
float best = 1e9f;
for (int i = 0; i < w->count; i++) {
Object *o = w->objects[i];
if (o == by || o == whom || o->type != OBJ_HUMAN || o->body.asleep || o->in_tent || o->indoors > 0.0f || Game_inBerlin(o) != Game_inBerlin(whom)) continue;
float d = hypotf(o->position.x - whom->position.x, o->position.z - whom->position.z);
if (d > SEE_NEAR) continue;
if (you && kind) o->like = fminf(1.0f, o->like + 0.01f); /* (seen being kind) */
if (you && what == TOUCHED_UNWELCOME) o->like = fmaxf(0.0f, o->like - 0.02f);
if (what != TOUCHED_HARASSED) continue;
if (you) o->like = fmaxf(0.0f, o->like - 0.12f);
o->ai.annoyed = fminf(1.0f, o->ai.annoyed + 0.3f);
/* stepping in: the place's awareness, their own nerve, the awareness team on it */
bool team = w->fest.awareness && Why_knows(o, WHY_AWARENESS);
float chance = w->aware * (0.7f - 0.3f * o->temperament.nerves + 0.2f * o->temperament.pride) + (team ? 0.4f : 0.0f) - 0.1f * fminf(o->drunk, 1.0f);
w->rng = w->rng * 1664525u + 1013904223u;
if ((float)(w->rng >> 8) / 16777216.0f < chance && d < best && !o->ai.step_in) { best = d; stepper = o; }
}
if (what != TOUCHED_HARASSED) return;
if (game->festival.stage) game->festival.harassed++;
if (stepper) {
stepper->ai.step_in = by;
stepper->ai.step_for = whom;
stepper->ai.step_t = 0.0f;
if (game->festival.stage) game->festival.stepped++;
if (you) {
static char line[128];
snprintf(line, sizeof line, "%s saw that - and is coming over", stepper->name);
say(game, line);
}
} else if (you) say(game, "Nobody said anything. But they saw.");
}
/* ---------- Holding somebody: what you do ---------- */
int game_touchChoices(const Game *game, const char *out[TALK_CHOICES]) {
int hand;
const Object *o = heldPerson(game, &hand);
if (!o) return 0;
static char take[48];
int part = game->player->hands.hold_part[hand];
snprintf(take, sizeof take, part == TOUCH_HAND ? "Walk with me (hand in hand)" : "Come with me");
out[0] = "Pull them closer";
out[1] = "Push them away";
out[2] = o->led_by == game->player ? "Stop here" : take;
out[3] = "Easy now... (calm them)";
out[4] = "Let go";
return 5;
}
static void answer(Game *game, int choice) {
int hand;
Object *o = heldPerson(game, &hand), *p = game->player;
if (!o) return;
World *w = &game->world;
static char line[128];
int part = p->hands.hold_part[hand];
switch (choice) {
case 1: { /* closer: that's closeness */
int got = Touch_react(w, o, p, part == TOUCH_HAND || part == TOUCH_ARM || part == TOUCH_SHOULDER ? TOUCH_WAIST : part);
if (got <= TOUCHED_TOLERATED) {
Vec3 to = vec3_sub(p->position, o->position);
to.y = 0.0f;
float d = vec3_length(to);
if (d > 0.55f) { o->ai.dodge_to = vec3_add(o->position, vec3_scale(to, (d - 0.45f) / d)); o->ai.dodge_t = 1.2f; }
o->love = fminf(1.0f, o->love + 0.02f);
} else letGo(game, hand);
break;
}
case 2: { /* away: a push */
Vec3 away = vec3_sub(o->position, p->position);
away.y = 0.0f;
Body_stagger(&o->body, vec3_scale(vec3_length(away) > 1e-3f ? vec3_normalize(away) : Object_facing(p), 1.6f));
o->like = fmaxf(0.0f, o->like - 0.1f);
o->ai.annoyed = fminf(1.0f, o->ai.annoyed + 0.5f);
if (o->type == OBJ_HUMAN) Ai_say(o, "Hey! What's that for?");
letGo(game, hand);
noticed(game, p, o, TOUCHED_UNWELCOME, part);
break;
}
case 3:
if (o->led_by == p) { o->led_by = NULL; if (o->type == OBJ_HUMAN) Ai_say(o, "Here? Okay."); break; }
if (Touch_react(w, o, p, part == TOUCH_HAND ? TOUCH_HAND : TOUCH_ARM) == TOUCHED_WELCOME) {
o->led_by = p;
o->ai.mode = AI_IDLE;
o->ai.on_project = false;
snprintf(line, sizeof line, part == TOUCH_HAND ? "%s walks along with you, hand in hand" : "%s comes along with you", o->name);
say(game, line);
} else {
if (o->type == OBJ_HUMAN) Ai_say(o, "I'd rather not, thanks.");
letGo(game, hand);
}
break;
case 4:
if (!Touch_calm(w, o, p, part)) letGo(game, hand);
break;
default:
letGo(game, hand);
break;
}
}
void game_touchAnswer(Game *game, int choice) { answer(game, choice); }
/* ---------- Every frame ---------- */
static float placeMood(const Game *game) {
const World *w = &game->world;
float sum = 0.0f;
int n = 0;
for (int i = 0; i < w->count; i++) {
const Object *o = w->objects[i];
if (o->type != OBJ_HUMAN || o->gone_home || Game_inBerlin(o)) continue;
sum += (o->guest ? o->fun : o->commitment) - 0.3f * o->ai.annoyed;
n++;
}
float mood = n ? sum / (float)n : 0.5f, place = 0.5f;
if (w->fest.stage >= FEST_OPEN) { /* (the festival: how good the floors are) */
place = 0.0f;
for (int k = 0; k < FEST_FLOORS; k++) place += w->fest.good[k] / (float)FEST_FLOORS;
}
return clampf(0.2f + 0.45f * mood + 0.15f * place + (w->fest.awareness ? 0.15f : 0.0f), 0.0f, 1.0f);
}
void game_touch(Game *game, float dt) {
World *w = &game->world;
Object *p = game->player;
if (w->aware <= 0.0f) w->aware = 0.5f;
w->aware += (placeMood(game) - w->aware) * fminf(1.0f, AWARE_EASE * dt);
for (int hand = 0; hand < 2; hand++) {
int state = p->hands.state[hand];
if (state == HAND_READY) { /* in reach now? */
bool person = false;
Object *got = Hands_catch(p, hand, w->objects, w->count, &person);
if (!got) continue;
if (person) {
int part = Touch_part(got, p->hands.hold_joint[hand], p->body.hand_target[hand]);
p->hands.hold_part[hand] = part;
int how = Touch_react(w, got, p, part);
static char line[128];
snprintf(line, sizeof line, "You take hold of %s's %s - 1 - 5: what you do", got->name, Touch_partName(part));
say(game, line);
if (how >= TOUCHED_UNWELCOME) { /* (they pull away) */
letGo(game, hand);
Vec3 away = vec3_sub(got->position, p->position);
away.y = 0.0f;
if (vec3_length(away) > 1e-3f) { got->ai.dodge_to = vec3_add(got->position, vec3_scale(vec3_normalize(away), 1.2f)); got->ai.dodge_t = 1.5f; }
}
} else if (p->hands.state[hand] == HAND_HOLD) say(game, "You hold on to it (E / Q: let go)");
else tell(game, got);
} else if (state == HAND_HOLD) { /* held: no further than the arm; too far, the grip's gone */
Object *o = p->hands.hold[hand];
float s = p->look.height / 1.75f;
Vec3 at = Hands_holdPoint(p, hand), sh = shoulderOf(p, hand);
float d = vec3_length(vec3_sub(at, sh)), reach = ARM_REACH * s;
if (o && o->led_by == p) reach += 1.0f; /* (hand in hand: the arm swings out and back, they catch up) */
if (d > reach + HOLD_BREAK) { letGo(game, hand); say(game, o && o->is_character ? "They're out of your reach - you let go" : "You lose your grip"); continue; }
if (o && !o->is_character && d > reach) { /* (walking off: held back) */
Vec3 out = vec3_sub(sh, at);
out.y = 0.0f;
if (vec3_length(out) > 1e-3f) {
out = vec3_normalize(out);
float away = vec3_dot(p->movement, out);
if (away > 0.0f) p->movement = vec3_sub(p->movement, vec3_scale(out, away));
}
}
}
}
for (int k = 0; k < w->touched_n; k++) noticed(game, w->touched[k].by, w->touched[k].whom, w->touched[k].what, w->touched[k].part);
w->touched_n = 0;
}