#include "game/player.h" #define WALK_SPEED 2.2f /* brisk walk */ #define JOG_SPEED 3.6f /* Shift held */ #define TAP_TIME 0.6f /* Shift pressed again within this: faster */ #define TAP_BOOST 0.45f /* ... by this much of the way to a sprint */ #define BOOST_DECAY 0.8f /* per second with Shift up */ #define ACCELERATION 3.5f /* m/s per second speeding up */ #define DECELERATION 7.0f #define RUN_JUMP_BONUS 0.25f /* a jump at full speed goes this much higher (take-off speed) */ #define CAREFUL_SPEED 4.4f /* carrying a chainsaw: no faster than this */ #define SIDEWAYS_FACTOR 0.65f /* side steps are slower than walking forward */ #define DRUNK_VEER 0.55f /* radians the walk direction wanders when very drunk */ #define DRUNK_SLOWDOWN 0.25f /* Drunk people don't walk quite where they want to. */ static Vec3 veer(const Object *player, Vec3 move) { float d = player->drunk; if (d <= 0.0f) return move; float t = player->body.clock; float angle = (sinf(t * 0.7f) + 0.6f * sinf(t * 1.9f + 1.3f)) * DRUNK_VEER * d; float c = cosf(angle), s = sinf(angle); return vec3(move.x * c - move.z * s, move.y, move.x * s + move.z * c); } /* Speed the player wants: walking, jogging (Shift held) or sprinting (Shift tapped again and again); eased so it never jumps from walk to sprint. */ static float runSpeed(Object *player, const Input *input, bool can_run, bool moving, float dt) { player->tap_timer += dt; if (input->sprint_tap) { if (player->tap_timer < TAP_TIME) player->sprint_boost = fminf(1.0f, player->sprint_boost + TAP_BOOST); player->tap_timer = 0.0f; } bool running = input->sprint && can_run && moving; if (!running) player->sprint_boost = fmaxf(0.0f, player->sprint_boost - BOOST_DECAY * dt); float top = fmaxf(Object_topSpeed(player), JOG_SPEED); if (Hands_carries(player, OBJ_CHAINSAW)) top = fminf(top, CAREFUL_SPEED); /* careful with that */ float goal = running ? lerpf(JOG_SPEED, top, player->sprint_boost) : WALK_SPEED; float speed = player->run_speed; speed = goal > speed ? fminf(goal, speed + ACCELERATION * dt) : fmaxf(goal, speed - DECELERATION * dt); player->run_speed = fmaxf(speed, WALK_SPEED * 0.5f); return player->run_speed; } void Player_update(Object *player, const Input *input, const Camera *camera, float dt) { /* The head follows where you look */ player->body.look_dir = camera->front; player->body.look_weight = 1.0f; player->body.urge = clampf(input->move_forward, 0.0f, 1.0f); /* W gets you up sooner after a fall */ if (Body_isBusy(&player->body) || player->on_ladder || player->riding) { /* (climbing: see the game; in the car) */ player->movement = vec3_zero(); player->move_local = vec3_zero(); return; } /* Move on the XZ plane. While grabbing, directions follow the grab heading (forward pushes, backward pulls) instead of the camera. */ bool grabbing = Hands_isGrabbing(player); Vec3 forward = grabbing ? player->hands.grab_heading : vec3_normalize(vec3(camera->front.x, 0.0f, camera->front.z)); Vec3 right = vec3_normalize(vec3_cross(forward, vec3(0, 1, 0))); Vec3 move = vec3_add(vec3_scale(forward, input->move_forward), vec3_scale(right, input->move_right * SIDEWAYS_FACTOR)); /* Pushing, pulling or carrying with both hands slows you down */ bool busy = grabbing || Hands_moveFactor(player) < 1.0f; /* too exhausted to run: until you got your breath back a bit (no on-off-on) */ static bool spent = false; if (player->needs.stamina < 0.1f) spent = true; else if (player->needs.stamina > 0.3f) spent = false; bool can_run = !busy && !spent; bool moving = input->move_forward != 0.0f || input->move_right != 0.0f; float speed = runSpeed(player, input, can_run, moving, dt) * Hands_moveFactor(player) * Object_vigor(player); speed *= 1.0f - DRUNK_SLOWDOWN * fminf(player->drunk, 1.0f); float amount = fminf(vec3_length(move), 1.0f); /* sideways input stays slower */ if (!grabbing) move = veer(player, move); player->movement = vec3_scale(vec3_normalize(move), speed * amount); player->move_local = vec3(input->move_right, 0.0f, input->move_forward); if (input->jump && player->is_grounded && !grabbing) { /* Everybody jumps their own height; a run-up adds a little */ float run_up = clampf((player->run_speed - WALK_SPEED) / (CAREFUL_SPEED - WALK_SPEED), 0.0f, 1.0f); player->velocity.y = Object_jumpSpeed(player) * (1.0f + RUN_JUMP_BONUS * run_up * amount); player->is_grounded = false; } if (!player->sitting) /* sitting, the body stays put while you look around */ player->rotation.y = atan2f(forward.z, forward.x) + PI_F / 2.0f; }