summaryrefslogtreecommitdiffhomepage
path: root/src/character.cpp
blob: 67b6ca720e849abaf58ad2a2b01f92a2607209b8 (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
#include "character.hpp"
#include "src/anim-atlas.hpp"
#include "loader/loader.hpp"
#include "src/world.hpp"
#include "src/entity.hpp"
#include "src/RTree.hpp"
#include <cmath>

namespace floormat {

namespace {

template <typename T> constexpr T sgn(T val) { return T(T(0) < val) - T(val < T(0)); }

constexpr int tile_size_1 = iTILE_SIZE2.sum()/2,
              framerate = 96, move_speed = tile_size_1 * 2;
constexpr float frame_time = 1.f/framerate;

constexpr auto arrows_to_dir(bool L, bool R, bool U, bool D)
{
    if (L == R)
        L = R = false;
    if (U == D)
        U = D = false;

    using enum rotation;
    struct {
        int lr = 0, ud = 0;
        rotation r = N;
    } dir;

    if (L && U)
        dir = { -1,  0,  W };
    else if (L && D)
        dir = {  0,  1,  S };
    else if (R && U)
        dir = {  0, -1,  N };
    else if (R && D)
        dir = {  1,  0,  E };
    else if (L)
        dir = { -1,  1, SW };
    else if (D)
        dir = {  1,  1, SE };
    else if (R)
        dir = {  1, -1, NE };
    else if (U)
        dir = { -1, -1, NW };

    return dir;
}

} // namespace

character::character(std::uint64_t id, struct chunk& c, entity_type type, const character_proto& proto) :
    entity{id, c, type, proto},
    name{proto.name},
    playable{proto.playable}
{
    if (!atlas)
        atlas = loader.anim_atlas("npc-walk", loader.ANIM_PATH);
    entity::set_bbox_(offset, bbox_offset, Vector2ub(iTILE_SIZE2/2), pass);
}

character::~character() = default;

int character::allocate_frame_time(float dt)
{
    int d = int(delta) + int(65535u * dt);
    constexpr int framerate_ = 65535/framerate;
    static_assert(framerate_ > 0);
    auto ret = d / framerate_;
    delta = (std::uint16_t)std::clamp(d - ret*65535, 0, 65535);
    return ret;
}

Vector2 character::move_vec(int left_right, int top_bottom)
{
    constexpr auto c = move_speed * frame_time;
    return c * Vector2((float)sgn(left_right), (float)sgn(top_bottom)).normalized();
}

void character::set_keys(bool L, bool R, bool U, bool D)
{
    b_L = L;
    b_R = R;
    b_U = U;
    b_D = D;
}

bool character::update(std::size_t i, float dt)
{
    auto [lr, ud, new_r] = arrows_to_dir(b_L, b_R, b_U, b_D);

    if (!lr & !ud)
    {
        delta = 0;
        return false;
    }

    int nframes = allocate_frame_time(dt);

    if (!nframes)
        return false;

    const auto vec = move_vec(lr, ud);
    c->ensure_passability();

    for (int k = 0; k < nframes; k++)
    {
        constexpr auto frac = Vector2(32767);
        constexpr auto inv_frac = 1.f / frac;
        auto offset_ = vec + Vector2(offset_frac) * inv_frac;
        offset_frac = Vector2s(Vector2(std::fmod(offset_[0], 1.f), std::fmod(offset_[1], 1.f)) * frac);
        auto off_i = Vector2i(offset_);
        if (can_move_to(off_i))
            i = move(i, off_i, new_r);
        ++frame %= atlas->info().nframes;
    }
    //Debug{} << "pos" << Vector2i(pos.local());
    return true;
}

} // namespace floormat