summaryrefslogtreecommitdiffhomepage
path: root/src/scenery.cpp
blob: ed5b643fcd50c0cf2f7f5693ef50e9a5ba0a6076 (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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
#include "scenery.hpp"
#include "tile-constants.hpp"
#include "anim-atlas.hpp"
#include "chunk.hpp"
#include "compat/assert.hpp"
#include "world.hpp"
#include "shaders/shader.hpp"
#include "src/rotation.inl"
#include "compat/exception.hpp"
#include <algorithm>

namespace floormat {

namespace {

template<typename... Ts> struct [[maybe_unused]] overloaded : Ts... { using Ts::operator()...; };

#if defined __GNUG__ && !defined __clang__
#pragma GCC diagnostic push // gcc doesn't support [[attributes]] on deduction guides
#pragma GCC diagnostic ignored "-Wunused"
#endif
template<typename... Ts>
#ifdef __clang__
[[maybe_unused]]
#endif
overloaded(Ts...) -> overloaded<Ts...>;
#if defined __GNUG__ && !defined __clang__
#pragma GCC diagnostic pop
#endif

template<typename T> struct proto_to_scenery_;
template<> struct proto_to_scenery_<generic_scenery_proto> { using type = generic_scenery; };
template<> struct proto_to_scenery_<door_scenery_proto> { using type = door_scenery; };
template<typename T> using proto_to_scenery = typename proto_to_scenery_<T>::type;

template<typename T> struct scenery_to_proto_;
template<> struct scenery_to_proto_<generic_scenery> { using type = generic_scenery_proto; };
template<> struct scenery_to_proto_<door_scenery> { using type = door_scenery_proto; };
template<typename T> using scenery_to_proto = typename scenery_to_proto_<T>::type;

} // namespace

scenery_proto::scenery_proto() noexcept { type = object_type::scenery; }

scenery_proto& scenery_proto::operator=(const scenery_proto&) noexcept = default;
scenery_proto::scenery_proto(const scenery_proto&) noexcept = default;
scenery_proto::~scenery_proto() noexcept = default;
scenery_proto::operator bool() const { return atlas != nullptr; }

bool generic_scenery_proto::operator==(const generic_scenery_proto& p) const = default;
enum scenery_type generic_scenery_proto::scenery_type() const { return scenery_type::generic; }

void generic_scenery::update(scenery&, size_t, float) {}
Vector2 generic_scenery::ordinal_offset(const scenery&, Vector2b offset) const { return Vector2(offset); }
bool generic_scenery::can_activate(const scenery&, size_t) const { return interactive; }
bool generic_scenery::activate(floormat::scenery&, size_t) { return false; }
enum scenery_type generic_scenery::scenery_type() const { return scenery_type::generic; }

enum scenery_type scenery_proto::scenery_type() const
{
    return std::visit(
        [&]<typename T>(const T& x) { return x.scenery_type(); },
        subtype
    );
}

generic_scenery::operator generic_scenery_proto() const { return { .active = active, .interactive = interactive, }; }

generic_scenery::generic_scenery(object_id, class chunk&, const generic_scenery_proto& p) :
    active{p.active}, interactive{p.interactive}
{}

bool door_scenery_proto::operator==(const door_scenery_proto& p) const = default;
enum scenery_type door_scenery_proto::scenery_type() const { return scenery_type::door; }

enum scenery_type door_scenery::scenery_type() const { return scenery_type::door; }
door_scenery::operator door_scenery_proto() const { return { .active = active, .interactive = interactive, .closing = closing, }; }

door_scenery::door_scenery(object_id, class chunk&, const door_scenery_proto& p) :
    closing{p.closing}, active{p.active}, interactive{p.interactive}
{}

void door_scenery::update(scenery& s, size_t, float dt)
{
    if (!s.atlas || !active)
        return;

    fm_assert(s.atlas);
    auto& anim = *s.atlas;
    const auto hz = uint8_t(s.atlas->info().fps);
    const auto nframes = (int)anim.info().nframes;
    fm_debug_assert(anim.info().fps > 0 && anim.info().fps <= 0xff);

    auto delta_ = int(s.delta) + int(65535u * dt);
    delta_ = std::min(65535, delta_);
    const auto frame_time = int(1.f/hz * 65535);
    const auto n = (uint8_t)std::clamp(delta_ / frame_time, 0, 255);
    s.delta = (uint16_t)std::clamp(delta_ - frame_time*n, 0, 65535);
    fm_debug_assert(s.delta >= 0);
    if (n == 0)
        return;
    const int8_t dir = closing ? 1 : -1;
    const int fr = s.frame + dir*n;
    active = fr > 0 && fr < nframes-1;
    pass_mode p;
    if (fr <= 0)
        p = pass_mode::pass;
    else if (fr >= nframes-1)
        p = pass_mode::blocked;
    else
        p = pass_mode::see_through;
    s.set_bbox(s.offset, s.bbox_offset, s.bbox_size, p);
    const auto new_frame = (uint16_t)std::clamp(fr, 0, nframes-1);
    //Debug{} << "frame" << new_frame << nframes-1;
    s.frame = new_frame;
    if (!active)
        s.delta = closing = 0;
    //if ((p == pass_mode::pass) != (old_pass == pass_mode::pass)) Debug{} << "update: need reposition" << (s.frame == 0 ? "-1" : "1");
}

Vector2 door_scenery::ordinal_offset(const scenery& s, Vector2b offset) const
{
    constexpr auto bTILE_SIZE = Vector2b(iTILE_SIZE2);

    constexpr auto off_closed_ = Vector2b(0, -bTILE_SIZE[1]/2+2);
    constexpr auto off_opened_ = Vector2b(-bTILE_SIZE[0]+2, -bTILE_SIZE[1]/2+2);
    const auto off_closed = rotate_point(off_closed_, rotation::N, s.r);
    const auto off_opened = rotate_point(off_opened_, rotation::N, s.r);
    const auto vec = s.frame == s.atlas->info().nframes-1 ? off_closed : off_opened;
    return Vector2(offset) + Vector2(vec);
}

bool door_scenery::can_activate(const scenery&, size_t) const { return interactive; }

bool door_scenery::activate(scenery& s, size_t)
{
    if (active)
        return false;
    fm_assert(s.frame == 0 || s.frame == s.atlas->info().nframes-1);
    closing = s.frame == 0;
    s.frame += closing ? 1 : -1;
    active = true;
    return true;
}

bool scenery::can_activate(size_t i) const
{
    if (!atlas)
        return false;

    return std::visit(
        [&]<typename T>(const T& sc) { return sc.can_activate(*this, i); },
        subtype
    );
}

void scenery::update(size_t i, float dt)
{
    return std::visit(
        [&]<typename T>(T& sc) { sc.update(*this, i, dt); },
        subtype
    );
}

Vector2 scenery::ordinal_offset(Vector2b offset) const
{
    return std::visit(
        [&]<typename T>(const T& sc) { return sc.ordinal_offset(*this, offset); },
        subtype
    );
}

float scenery::depth_offset() const
{
    constexpr auto inv_tile_size = 1.f/TILE_SIZE2;
    Vector2 offset;
    offset += Vector2(atlas->group(r).depth_offset) * inv_tile_size;
    float ret = 0;
    ret += offset[1]*TILE_MAX_DIM + offset[0];
    ret += tile_shader::scenery_depth_offset;

    return ret;
}

bool scenery::activate(size_t i)
{
    return std::visit(
        [&]<typename T>(T& sc) { return sc.activate(*this, i); },
        subtype
    );
}

bool scenery_proto::operator==(const object_proto& e0) const
{
    if (type != e0.type)
        return false;

    if (!object_proto::operator==(e0))
        return false;

    const auto& sc = static_cast<const scenery_proto&>(e0);

    if (subtype.index() != sc.subtype.index())
        return false;

    return std::visit(
        [](const auto& a, const auto& b) {
            if constexpr(std::is_same_v< std::decay_t<decltype(a)>, std::decay_t<decltype(b)> >)
                return a == b;
            else
            {
                std::unreachable();
                return false;
            }
        },
        subtype, sc.subtype
    );
}

object_type scenery::type() const noexcept { return object_type::scenery; }

scenery::operator scenery_proto() const
{
    scenery_proto ret;
    static_cast<object_proto&>(ret) = object::operator object_proto();
    std::visit(
        [&]<typename T>(const T& s) { ret.subtype = scenery_to_proto<T>(s); },
        subtype
    );
    return ret;
}

enum scenery_type scenery::scenery_type() const
{
    return std::visit(
        [&]<typename T>(const T& sc) { return sc.scenery_type(); },
        subtype
    );
}

scenery_variants scenery::subtype_from_proto(object_id id, class chunk& c, const scenery_proto_variants& variant)
{
    return std::visit(
        [&]<typename T>(const T& p) {
            return scenery_variants { std::in_place_type_t<proto_to_scenery<T>>{}, id, c, p };
        },
        variant
    );
}

scenery_variants scenery::subtype_from_scenery_type(object_id id, class chunk& c, enum scenery_type type)
{
    switch (type)
    {
    case scenery_type::generic:
        return generic_scenery{id, c, {}};
    case scenery_type::door:
        return door_scenery{id, c, {}};
    case scenery_type::none:
        break;
    }
    fm_throw("invalid scenery type"_cf, (int)type);
}

scenery::scenery(object_id id, class chunk& c, const scenery_proto& proto) :
    object{id, c, proto}, subtype{ subtype_from_proto(id, c, proto.subtype) }
{
#ifndef FM_NO_DEBUG
    if (id != 0)
        fm_debug_assert(atlas); // todo add placeholder graphic
#endif
}

} // namespace floormat