/* * 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 #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; }