#pragma once // Player movement, run identically by the server (authoritative) and the // client (prediction + reconciliation). Pure function of state + input. #include "Protocol.h" #include "Zone.h" namespace bs { constexpr float kWalkSpeed = 4.0f; // world units / second constexpr float kRunSpeed = 6.6f; struct MoveState { glm::vec2 pos{0.0f}; float yaw = 0.0f; bool moving = false; bool running = false; }; inline void stepMovement(const Zone& zone, MoveState& s, const InputCmd& in, float dt) { glm::vec2 dir = in.move(); float amount = std::min(glm::length(dir), 1.0f); s.moving = amount > 0.05f; s.running = s.moving && in.run(); if (!s.moving) return; dir = glm::normalize(dir) * amount; s.yaw = yawFromDir(dir); glm::vec2 step = dir * (s.running ? kRunSpeed : kWalkSpeed) * dt; // Axis-separated resolution slides along obstacles. glm::vec2 p = s.pos; if (zone.walkable(p, p + glm::vec2(step.x, 0.0f))) p.x += step.x; if (zone.walkable(p, p + glm::vec2(0.0f, step.y))) p.y += step.y; s.pos = p; } // Quantizes a world direction exactly like the network does, so prediction // uses the same input the server will see. inline InputCmd makeInput(uint32_t seq, glm::vec2 worldDir, bool run) { InputCmd c; c.seq = seq; float len = glm::length(worldDir); if (len > 1.0f) worldDir /= len; c.moveX = int8_t(std::lround(glm::clamp(worldDir.x, -1.0f, 1.0f) * 127.0f)); c.moveZ = int8_t(std::lround(glm::clamp(worldDir.y, -1.0f, 1.0f) * 127.0f)); c.flags = run ? 1 : 0; return c; } } // namespace bs