summaryrefslogtreecommitdiffhomepage
path: root/src/critter.cpp
diff options
context:
space:
mode:
authorStanislaw Halik <sthalik@misaki.pl>2023-09-01 22:36:26 +0200
committerStanislaw Halik <sthalik@misaki.pl>2023-09-01 22:36:26 +0200
commitd90c38ce59d406cdfd4303c836ee524a71ad0979 (patch)
tree27c7bae59fbea84529977f588f050d5ce09e14ea /src/critter.cpp
parent31fd5bbc08234686cf798a93a18e0bb73615d1bf (diff)
rename character -> critter
Diffstat (limited to 'src/critter.cpp')
-rw-r--r--src/critter.cpp220
1 files changed, 220 insertions, 0 deletions
diff --git a/src/critter.cpp b/src/critter.cpp
new file mode 100644
index 00000000..9111aaf6
--- /dev/null
+++ b/src/critter.cpp
@@ -0,0 +1,220 @@
+#include "critter.hpp"
+#include "src/anim-atlas.hpp"
+#include "loader/loader.hpp"
+#include "src/world.hpp"
+#include "src/object.hpp"
+#include "shaders/shader.hpp"
+#include "src/RTree-search.hpp"
+#include "compat/exception.hpp"
+#include <cmath>
+#include <memory>
+#include <utility>
+#include <algorithm>
+
+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 * 3, move_speed = tile_size_1 * 2 * 3;
+constexpr float frame_time = 1.f/framerate;
+
+constexpr auto arrows_to_dir(bool left, bool right, bool up, bool down)
+{
+ if (left == right)
+ left = right = false;
+ if (up == down)
+ up = down = false;
+
+ const auto bits = unsigned(left << 3 | right << 2 | up << 1 | down << 0);
+ constexpr unsigned L = 1 << 3, R = 1 << 2, U = 1 << 1, D = 1 << 0;
+
+ switch (bits)
+ {
+ using enum rotation;
+ case 0: return rotation{rotation_COUNT};
+ case L | U: return W;
+ case L | D: return S;
+ case R | U: return N;
+ case R | D: return E;
+ case L: return SW;
+ case D: return SE;
+ case R: return NE;
+ case U: return NW;
+ }
+ std::unreachable();
+}
+
+constexpr Vector2i rotation_to_vec(rotation r)
+{
+ CORRADE_ASSUME(r < rotation_COUNT);
+ switch (r)
+ {
+ using enum rotation;
+ case N: return { 0, -1 };
+ case NE: return { 1, -1 };
+ case E: return { 1, 0 };
+ case SE: return { 1, 1 };
+ case S: return { 0, 1 };
+ case SW: return { -1, 1 };
+ case W: return { -1, 0 };
+ case NW: return { -1, -1 };
+ }
+ std::unreachable();
+}
+
+constexpr std::array<rotation, 3> rotation_to_similar(rotation r)
+{
+ CORRADE_ASSUME(r < rotation_COUNT);
+ switch (r)
+ {
+ using enum rotation;
+ case N: return { N, NW, NE };
+ case NE: return { NE, N, E };
+ case E: return { E, NE, SE };
+ case SE: return { SE, E, S };
+ case S: return { S, SE, SW };
+ case SW: return { SW, S, W };
+ case W: return { W, SW, NW };
+ case NW: return { NW, W, N };
+ }
+ std::unreachable();
+}
+
+} // namespace
+
+critter_proto::critter_proto(const critter_proto&) = default;
+critter_proto::~critter_proto() noexcept = default;
+critter_proto& critter_proto::operator=(const critter_proto&) = default;
+
+critter_proto::critter_proto()
+{
+ type = object_type::critter;
+ atlas = loader.anim_atlas("npc-walk", loader.ANIM_PATH);
+}
+
+bool critter_proto::operator==(const object_proto& e0) const
+{
+ if (type != e0.type)
+ return false;
+
+ if (!object_proto::operator==(e0))
+ return false;
+
+ const auto& s0 = static_cast<const critter_proto&>(e0);
+ return name == s0.name && playable == s0.playable;
+}
+
+int critter::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 = (uint16_t)std::clamp(d - ret*65535LL, 0LL, 65535LL);
+ return ret;
+}
+
+Vector2 critter::move_vec(Vector2i vec)
+{
+ const int left_right = vec[0], top_bottom = vec[1];
+ constexpr auto c = move_speed * frame_time;
+ return c * Vector2((float)sgn(left_right), (float)sgn(top_bottom)).normalized();
+}
+
+void critter::set_keys(bool L, bool R, bool U, bool D)
+{
+ b_L = L;
+ b_R = R;
+ b_U = U;
+ b_D = D;
+}
+
+float critter::depth_offset() const
+{
+ return tile_shader::character_depth_offset;
+}
+
+Vector2 critter::ordinal_offset(Vector2b offset) const
+{
+ (void)offset;
+ return Vector2(offset);
+}
+
+void critter::update(size_t i, float dt)
+{
+ const auto new_r = arrows_to_dir(b_L, b_R, b_U, b_D);
+ if (new_r == rotation{rotation_COUNT})
+ {
+ delta = 0;
+ return;
+ }
+
+ int nframes = allocate_frame_time(dt);
+
+ if (nframes == 0)
+ return;
+
+ auto [_0, _1, _2] = rotation_to_similar(r);
+ const Vector2 move_vecs[] = {
+ move_vec(rotation_to_vec(_0)),
+ move_vec(rotation_to_vec(_1)),
+ move_vec(rotation_to_vec(_2)),
+ };
+
+
+ if (r != new_r)
+ if (is_dynamic())
+ rotate(i, new_r);
+
+ c->ensure_passability();
+
+ for (int k = 0; k < nframes; k++)
+ {
+ for (auto j = 0uz; j < 3; j++)
+ {
+ auto vec = move_vecs[j];
+ 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))
+ {
+ move_to(i, off_i, new_r);
+ ++frame %= atlas->info().nframes;
+ goto done;
+ }
+ }
+ delta = 0;
+ break;
+done:
+ (void)0;
+ }
+}
+
+object_type critter::type() const noexcept { return object_type::critter; }
+
+critter::operator critter_proto() const
+{
+ critter_proto ret;
+ static_cast<object_proto&>(ret) = object::operator object_proto();
+ ret.name = name;
+ ret.playable = playable;
+ return ret;
+}
+
+critter::critter(object_id id, struct chunk& c, const critter_proto& proto) :
+ object{id, c, proto},
+ name{proto.name},
+ playable{proto.playable}
+{
+ if (!name)
+ name = "(Unnamed)"_s;
+ fm_soft_assert(atlas->check_rotation(r));
+ object::set_bbox_(offset, bbox_offset, Vector2ub(iTILE_SIZE2/2), pass);
+}
+
+} // namespace floormat