Nimbin[12]?Games / Entropie / entropie/src/game/game_touch.c

Entropie git · main

Entropie festival game; first steps

sdl c game entropie cmake · first commit 2026-10-10 · last commit 2026-11-02 (18 hours ago) · synced 10 hours ago

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.gz
entropie/src/game/game_touch.c 11.6 KB · 245 lines 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;
}