#include "player.hh" namespace client { // We might start receiving data slower or faster. We compensate by adjusting // how fast time progresses on our client.; void animate::update_time_factor(const shared::tick_t& sequence, const shared::tick_t& tick) noexcept { // Check if we're older than the latest, don't update if so. if (std::ranges::any_of(this->updates, [&](const auto& update) { return update.from_server && update.tick_sequence >= sequence; })) { return; } // How many ticks in the future we prefer to be at. If the server is // jittery, we would want this value to be greater, but this should be OK // for most cases. A larger value decreases the max ping we can handle. constexpr float TARGET_TICKS_AHEAD = 1.341519f; if (const auto tick_dist = static_cast(tick) - static_cast(state::tick); tick_dist > 0) { // We're behind, jump ahead. We only touch delta_ticks so extrapolate // is called the correct number of times in client.cc. #ifndef NDEBUG shared::print::debug << shared::print::time << "client: time_factor jumped ahead, got tick " << tick << ", was " << state::tick << " + " << state::delta_ticks << ", time_factor: " << state::time_factor << '\n'; #endif state::delta_ticks = static_cast(tick_dist) + TARGET_TICKS_AHEAD; state::time_factor = 1.0f; return; } // Otherwise we try to move towards the value by adjusting how fast time // progresses. This shouldn't be possible to notice, our constants should // be adjusted if it is. const float time_diff = (static_cast(tick) + TARGET_TICKS_AHEAD) - (static_cast(state::tick) + state::delta_ticks); constexpr float MAX_TIME_FACTOR_DIFF = 0.1f; constexpr float TIME_FACTOR_AGGRESSIVENESS = 0.25f; state::time_factor = std::clamp(1.0f + (time_diff * TIME_FACTOR_AGGRESSIVENESS), 1.0f - MAX_TIME_FACTOR_DIFF, 1.0f + MAX_TIME_FACTOR_DIFF); } void animate::notify(const shared::animate& animate, const shared::tick_t& tick_sequence, const bool from_server) noexcept { // If it's from the server we want to update previously emplaced predicted // (and potentially predicted) values. if (const auto it = std::ranges::find_if(this->updates, [&](const auto& update) { return update.tick_sequence == tick_sequence; }); it != std::end(this->updates)) { if (from_server) { *it = animate_update{.tick_sequence = tick_sequence, .animate = animate, .from_server = from_server}; } return; } this->updates.push_back({.tick_sequence = tick_sequence, .animate = animate, .from_server = from_server}); std::ranges::sort(this->updates, [](const auto& a, const auto& b) { return a.tick_sequence < b.tick_sequence; }); } float animate::get_target_ticks_back() noexcept { // The localplayer is interpolated via the latest tick. const unsigned base_ticks_back = [&]() -> unsigned { if (this->index == *state::localplayer_index) { return 0u; } const float base = settings::get({"engine", "interp"}, 0.25f); const float ret = base * static_cast(state::tickrate); return std::clamp(static_cast(ret), 0u, state::tickrate); }(); return state::delta_ticks + static_cast(base_ticks_back); } void animate::interpolate() noexcept { const float target_ticks_back = this->get_target_ticks_back(); const bool is_localplayer = this->index == *state::localplayer_index; const auto b_it = [&, this]() { if (is_localplayer) { return std::rbegin(this->updates); } return std::ranges::find_if( std::rbegin(this->updates), std::rend(this->updates), [&](const auto& update) { const unsigned target = state::tick - static_cast(target_ticks_back); return update.tick_sequence <= target; }); }(); if (b_it == std::rend(this->updates)) { return; } const auto a_it = std::next(b_it); if (a_it == std::rend(this->updates)) { return; } const glm::vec3 a_pos = a_it->animate.get_local_pos(); const glm::vec3 b_pos = shared::movement::make_relative( a_it->animate.get_chunk_pos(), b_it->animate.get_local_pos(), b_it->animate.get_chunk_pos()); const float b_interp = [&]() { const float diff = is_localplayer ? 1.0f : static_cast(b_it->tick_sequence - a_it->tick_sequence); const float base = std::fmod(target_ticks_back, 1.0f); const float a_dist = diff - (1.0f - base); return a_dist / diff; }(); const float a_interp = (1.0f - b_interp); const auto& a = a_it->animate; const auto& b = b_it->animate; this->local_pos = a_pos * a_interp + b_pos * b_interp; this->chunk_pos = a.get_chunk_pos(); this->velocity = a.get_velocity() * a_interp + b.get_velocity() * b_interp; // Update viewangles if we're not the localplayer, yaw requires special care // because it should snap to the closest difference angle. if (!is_localplayer) { this->viewangles.pitch = a.get_angles().pitch * a_interp + b.get_angles().pitch * b_interp; const float yaw_delta = shared::math::angles::get_yaw_delta( a.get_angles().yaw, b.get_angles().yaw); this->viewangles.yaw = a.get_angles().yaw + yaw_delta * b_interp; this->viewangles.normalise(); } shared::movement::normalise_position(this->local_pos, this->chunk_pos); } } // namespace client