aboutsummaryrefslogtreecommitdiff
path: root/src/client/entity/animate.cc
blob: 9142b558abb4bb2a3da39197a462f8a100c4eba2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#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<std::int64_t>(tick) -
                               static_cast<std::int64_t>(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<float>(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<float>(tick) + TARGET_TICKS_AHEAD) -
        (static_cast<float>(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<float>({"engine", "interp"}, 0.25f);
        const float ret = base * static_cast<float>(state::tickrate);
        return std::clamp(static_cast<unsigned>(ret), 0u, state::tickrate);
    }();

    return state::delta_ticks + static_cast<float>(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<unsigned>(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<float>(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