summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--draw/anim.cpp86
-rw-r--r--draw/anim.hpp8
-rw-r--r--editor/app.cpp2
-rw-r--r--editor/app.hpp7
-rw-r--r--editor/character.cpp137
-rw-r--r--editor/character.hpp28
-rw-r--r--editor/draw.cpp8
-rw-r--r--editor/imgui-editors.cpp2
-rw-r--r--editor/imgui-inspect.cpp22
-rw-r--r--editor/imgui.cpp21
-rw-r--r--editor/inspect-types.cpp59
-rw-r--r--editor/scenery-editor.cpp17
-rw-r--r--editor/update.cpp67
-rw-r--r--main/clickable.hpp9
-rw-r--r--main/draw.cpp8
-rw-r--r--serialize/scenery.cpp49
-rw-r--r--serialize/world-impl.hpp2
-rw-r--r--serialize/world-reader.cpp47
-rw-r--r--serialize/world-writer.cpp30
-rw-r--r--src/character.cpp121
-rw-r--r--src/character.hpp32
-rw-r--r--src/chunk-collision.cpp43
-rw-r--r--src/chunk-render.cpp88
-rw-r--r--src/chunk.cpp64
-rw-r--r--src/chunk.hpp37
-rw-r--r--src/chunk.inl17
-rw-r--r--src/entity.cpp199
-rw-r--r--src/entity.hpp80
-rw-r--r--src/pass-mode.hpp1
-rw-r--r--src/scenery.cpp134
-rw-r--r--src/scenery.hpp104
-rw-r--r--src/tile.cpp15
-rw-r--r--src/tile.hpp8
-rw-r--r--src/world.cpp29
-rw-r--r--src/world.hpp26
-rw-r--r--test/app.hpp7
-rw-r--r--test/entity.cpp5
-rw-r--r--test/main.cpp2
-rw-r--r--test/serializer.cpp33
-rw-r--r--userconfig-sthalik@Windows-Clang.cmake1
40 files changed, 969 insertions, 686 deletions
diff --git a/draw/anim.cpp b/draw/anim.cpp
index 44be4129..df67585b 100644
--- a/draw/anim.cpp
+++ b/draw/anim.cpp
@@ -25,24 +25,20 @@ std::array<UnsignedShort, 6> anim_mesh::make_index_array()
}};
}
-void anim_mesh::add_clickable(tile_shader& shader, const Vector2i& win_size,
- chunk_coords c, std::uint8_t i, const std::shared_ptr<anim_atlas>& atlas, scenery& s,
- std::vector<clickable>& list)
+void anim_mesh::add_clickable(tile_shader& shader, const Vector2i& win_size, const std::shared_ptr<entity>& s, std::vector<clickable>& list)
{
- const local_coords xy{i};
- const auto& a = *atlas;
- const auto& g = a.group(s.r);
- const auto& f = a.frame(s.r, s.frame);
- const auto world_pos = TILE_SIZE20 * Vector3(xy) + Vector3(g.offset) + Vector3(Vector2(s.offset), 0);
+ const auto& a = *s->atlas;
+ const auto& g = a.group(s->r);
+ const auto& f = a.frame(s->r, s->frame);
+ const auto world_pos = TILE_SIZE20 * Vector3(s->coord.local()) + Vector3(g.offset) + Vector3(Vector2(s->offset), 0);
const Vector2i offset((Vector2(shader.camera_offset()) + Vector2(win_size)*.5f)
+ shader.project(world_pos) - Vector2(f.ground));
if (offset < win_size && offset + Vector2i(f.size) >= Vector2i())
{
clickable item = {
{ f.offset, f.offset + f.size }, { offset, offset + Vector2i(f.size) },
- a.bitmask(), tile_shader::depth_value(xy, tile_shader::scenery_depth_offset),
+ a.bitmask(), s, s->ordinal(),
a.info().pixel_size[0],
- c, xy,
!g.mirror_from.isEmpty(),
};
list.push_back(item);
@@ -52,12 +48,14 @@ void anim_mesh::add_clickable(tile_shader& shader, const Vector2i& win_size,
void anim_mesh::draw(tile_shader& shader, chunk& c)
{
constexpr auto quad_index_count = 6;
- auto [mesh_, ids, size] = c.ensure_scenery_mesh();
+ auto [mesh_] = c.ensure_scenery_mesh();
+ const auto& es = c.entities();
GL::MeshView mesh{mesh_};
+ [[maybe_unused]] std::size_t draw_count = 0;
anim_atlas* bound = nullptr;
- [[maybe_unused]] std::size_t draw_count = 0;
- fm_debug_assert(std::size_t(mesh_.count()) == size*quad_index_count);
+ const auto size = es.size();
+ const auto max_index = std::uint32_t(size*quad_index_count - 1);
const auto do_draw = [&](std::size_t from, std::size_t to, anim_atlas* atlas, std::uint32_t max_index) {
if (atlas != bound)
@@ -69,48 +67,40 @@ void anim_mesh::draw(tile_shader& shader, chunk& c)
draw_count++;
};
- struct last_ { anim_atlas* atlas = nullptr; std::size_t run_from = 0; };
- Optional<last_> last;
- const auto max_index = std::uint32_t(size*quad_index_count - 1);
+ fm_debug_assert(std::size_t(mesh_.count()) <= size*quad_index_count);
+
+ struct last_ {
+ anim_atlas* atlas = nullptr; std::size_t run_from = 0;
+ operator bool() const { return atlas; }
+ last_& operator=(std::nullptr_t) { atlas = nullptr; return *this; }
+ } last;
+ std::size_t i = 0;
- auto last_id = 0_uz;
for (auto k = 0_uz; k < size; k++)
{
- auto id = ids[k];
- auto [atlas, s] = c[id].scenery();
- for (auto i = last_id+1; i < id; i++)
- if (auto [atlas, s] = c[i].scenery();
- atlas && atlas->info().fps > 0)
+ const auto& e = *es[k];
+ auto& atlas = *e.atlas;
+ if (atlas.info().fps > 0)
+ {
+ if (last)
+ do_draw(last.run_from, i, last.atlas, max_index);
+ draw(shader, atlas, e.r, e.frame, e.coord.local(), e.offset, tile_shader::scenery_depth_offset);
+ last = nullptr;
+ }
+ else
+ {
+ if (last && &atlas != last.atlas)
{
- if (last)
- {
- do_draw(last->run_from, k, last->atlas, max_index);
- last = NullOpt;
- }
- bound = nullptr;
- draw(shader, *atlas, s.r, s.frame, local_coords{i}, s.offset, tile_shader::scenery_depth_offset);
+ do_draw(last.run_from, i, last.atlas, max_index);
+ last = nullptr;
}
- last_id = id;
- if (last && atlas && &*atlas != last->atlas)
- {
- do_draw(last->run_from, k, last->atlas, max_index);
- last = NullOpt;
+ if (!last)
+ last = { &atlas, i };
+ i++;
}
- if (!last)
- last = { InPlaceInit, &*atlas, k };
- }
- if (size > 0)
- {
- if (last)
- do_draw(last->run_from, size, last->atlas, max_index);
- for (std::size_t i = ids[size-1]+1; i < TILE_COUNT; i++)
- if (auto [atlas, s] = c[i].scenery(); atlas && atlas->info().fps > 0)
- draw(shader, *atlas, s.r, s.frame, local_coords{i}, s.offset, tile_shader::scenery_depth_offset);
}
- else
- for (auto i = 0_uz; i < TILE_COUNT; i++)
- if (auto [atlas, s] = c[i].scenery(); atlas)
- draw(shader, *atlas, s.r, s.frame, local_coords{i}, s.offset, tile_shader::scenery_depth_offset);
+ if (last)
+ do_draw(last.run_from, size, last.atlas, max_index);
//#define FM_DEBUG_DRAW_COUNT
#ifdef FM_DEBUG_DRAW_COUNT
diff --git a/draw/anim.hpp b/draw/anim.hpp
index 4a3e4c40..b7d0a0cd 100644
--- a/draw/anim.hpp
+++ b/draw/anim.hpp
@@ -1,5 +1,6 @@
#pragma once
#include "local-coords.hpp"
+#include "rotation.hpp"
#include <array>
#include <Corrade/Containers/ArrayViewStl.h>
#include <Magnum/Magnum.h>
@@ -7,7 +8,6 @@
#include <Magnum/Math/Vector3.h>
#include <Magnum/GL/Mesh.h>
#include <Magnum/GL/Buffer.h>
-#include "src/scenery.hpp"
#include "main/clickable.hpp"
//namespace floormat::Serialize { struct anim_frame; }
@@ -18,7 +18,7 @@ struct tile_shader;
struct anim_atlas;
struct chunk;
struct clickable;
-struct scenery;
+struct entity;
struct anim_mesh final
{
@@ -27,9 +27,7 @@ struct anim_mesh final
void draw(tile_shader& shader, chunk& c);
void draw(tile_shader& shader, anim_atlas& atlas, rotation r, std::size_t frame, const Vector3& pos, float depth);
void draw(tile_shader& shader, anim_atlas& atlas, rotation r, std::size_t frame, local_coords xy, Vector2b offset, float depth_offset);
- static void add_clickable(tile_shader& shader, const Vector2i& win_size,
- chunk_coords c, std::uint8_t i, const std::shared_ptr<anim_atlas>& atlas, scenery& s,
- std::vector<clickable>& list);
+ static void add_clickable(tile_shader& shader, const Vector2i& win_size, const std::shared_ptr<entity>& s, std::vector<clickable>& list);
private:
struct vertex_data final {
diff --git a/editor/app.cpp b/editor/app.cpp
index 80599ed1..aa8608c5 100644
--- a/editor/app.cpp
+++ b/editor/app.cpp
@@ -33,7 +33,7 @@ app::app(fm_settings&& opts) :
maybe_initialize_chunk_(coord, w[coord]);
reset_camera_offset();
inspectors.reserve(16);
- _character = std::make_unique<character_wip>();
+ _character = w.make_entity<character>(global_coords{});
}
app::~app()
diff --git a/editor/app.hpp b/editor/app.hpp
index 56660845..9e6b1312 100644
--- a/editor/app.hpp
+++ b/editor/app.hpp
@@ -23,7 +23,7 @@ struct tile_atlas;
struct tile_editor;
struct fm_settings;
struct anim_atlas;
-struct character_wip;
+struct character;
struct cursor_state final {
Optional<Vector2i> pixel;
@@ -44,8 +44,7 @@ enum class popup_target_type : unsigned char {
};
struct popup_target final {
- chunk_coords c;
- local_coords pos;
+ std::shared_ptr<entity> e;
popup_target_type target = popup_target_type::none;
bool operator==(const popup_target&) const;
};
@@ -150,7 +149,7 @@ private:
key_set keys;
std::array<int, key_set::COUNT> key_modifiers = {};
std::vector<popup_target> inspectors;
- std::unique_ptr<character_wip> _character;
+ std::shared_ptr<character> _character;
cursor_state cursor;
popup_target _popup_target;
diff --git a/editor/character.cpp b/editor/character.cpp
deleted file mode 100644
index 845f8345..00000000
--- a/editor/character.cpp
+++ /dev/null
@@ -1,137 +0,0 @@
-#include "character.hpp"
-#include "src/anim-atlas.hpp"
-#include "loader/loader.hpp"
-#include "src/world.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 inv_tile_size = 1 / TILE_SIZE2;
-constexpr Vector2b bbox_size(12);
-
-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_wip::character_wip() :
- walk_anim{loader.anim_atlas("npc-walk", loader.ANIM_PATH)}
-{
-}
-
-character_wip::~character_wip() = default;
-
-int character_wip::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_wip::move_vec(int left_right, int top_bottom)
-{
- constexpr auto c = move_speed * frame_time;
- return c * Vector2(sgn(left_right), sgn(top_bottom)).normalized();
-}
-
-void character_wip::tick(world& w, float dt, bool L, bool R, bool U, bool D)
-{
- auto [lr, ud, rot] = arrows_to_dir(L, R, U, D);
-
- if (!lr & !ud)
- {
- delta = 0;
- return;
- }
-
- int nframes = allocate_frame_time(dt);
-
- if (!nframes)
- return;
-
- const auto vec = move_vec(lr, ud);
- r = rot;
-
- for (int i = 0; i < nframes; i++)
- {
- auto pos_ = pos;
- Vector2 offset_ = offset;
- offset_ += vec;
- auto pos_1 = Vector2i(offset_ * inv_tile_size);
- pos_ += pos_1;
- offset_ = Vector2(std::fmod(offset_[0], TILE_SIZE2[0]), std::fmod(offset_[1], TILE_SIZE2[1]));
- constexpr auto half_tile = TILE_SIZE2/2;
- if (auto off = offset_[0]; std::fabs(off) > half_tile[0])
- {
- pos_ += Vector2i(offset_[0] < 0 ? -1 : 1, 0);
- offset_[0] = std::copysign(TILE_SIZE[0] - std::fabs(offset_[0]), -off);
- }
- if (auto off = offset_[1]; std::fabs(off) > half_tile[1])
- {
- pos_ += Vector2i(0, offset_[1] < 0 ? -1 : 1);
- offset_[1] = std::copysign(TILE_SIZE[1] - std::fabs(offset_[1]), -off);
- }
- auto [c, t] = w[pos_];
- const auto& r = c.rtree();
- auto center = Vector2(pos_.local()) * TILE_SIZE2 + offset_;
- auto half_bbox = Vector2(bbox_size)*.5f;
- auto min = center - half_bbox;
- auto max = center + half_bbox;
- bool is_blocked = false;
- r->Search(min.data(), max.data(), [&](const std::uint64_t data, const auto&) {
- auto cdata = std::bit_cast<collision_data>(data);
- is_blocked |= (pass_mode)cdata.pass != pass_mode::pass;
- return !is_blocked;
- });
- if (is_blocked)
- break;
- pos = pos_;
- offset = offset_;
- ++frame %= walk_anim->info().nframes;
- }
- //Debug{} << "pos" << Vector2i(pos.local());
-}
-
-} // namespace floormat
diff --git a/editor/character.hpp b/editor/character.hpp
deleted file mode 100644
index 7172415c..00000000
--- a/editor/character.hpp
+++ /dev/null
@@ -1,28 +0,0 @@
-#pragma once
-#include "src/global-coords.hpp"
-#include "src/rotation.hpp"
-#include <memory>
-
-namespace floormat {
-
-struct anim_atlas;
-struct world;
-
-struct character_wip final
-{
- character_wip();
- ~character_wip();
- void tick(world& w, float dt, bool L, bool R, bool U, bool D);
-
- std::shared_ptr<anim_atlas> walk_anim;
- global_coords pos;
- Vector2 offset;
- std::uint16_t delta = 0, frame = 0;
- rotation r = rotation::NE;
-
-private:
- int allocate_frame_time(float dt);
- static Vector2 move_vec(int left_right, int top_bottom);
-};
-
-} // namespace floormat
diff --git a/editor/draw.cpp b/editor/draw.cpp
index 7ce5c7bf..dd286edf 100644
--- a/editor/draw.cpp
+++ b/editor/draw.cpp
@@ -54,7 +54,7 @@ void app::draw_cursor()
shader.set_tint({1, 1, 1, 0.75f});
auto [_f, _w, anim_mesh] = M->meshes();
const auto pos = cursor.tile->to_signed3()*iTILE_SIZE;
- anim_mesh.draw(shader, *sel.atlas, sel.frame.r, sel.frame.frame, Vector3(pos), 1);
+ anim_mesh.draw(shader, *sel.atlas, sel.r, sel.frame, Vector3(pos), 1);
}
}
@@ -153,9 +153,9 @@ void app::draw_character()
auto& c = *_character;
const auto [minx, maxx, miny, maxy] = M->get_draw_bounds();
- const with_shifted_camera_offset o{shader, c.pos.chunk(), {minx, miny}, {maxx, maxy}};
+ const with_shifted_camera_offset o{shader, c.coord.chunk(), {minx, miny}, {maxx, maxy}};
if (floormat_main::check_chunk_visible(shader.camera_offset(), sz))
- mesh.draw(shader, *c.walk_anim, c.r, c.frame, c.pos.local(), Vector2b(c.offset), tile_shader::character_depth_offset);
+ mesh.draw(shader, *c.atlas, c.r, c.frame, c.coord.local(), Vector2b(c.offset), tile_shader::character_depth_offset);
GL::Renderer::setDepthMask(true);
GL::Renderer::disable(GL::Renderer::Feature::DepthTest);
@@ -163,7 +163,7 @@ void app::draw_character()
void app::draw()
{
- draw_character();
+ //draw_character();
if (_render_bboxes)
draw_collision_boxes();
if (_editor.current_tile_editor() || _editor.current_scenery_editor())
diff --git a/editor/imgui-editors.cpp b/editor/imgui-editors.cpp
index cdc99c69..77658680 100644
--- a/editor/imgui-editors.cpp
+++ b/editor/imgui-editors.cpp
@@ -69,7 +69,7 @@ void app::draw_editor_scenery_pane(scenery_editor& ed)
}
if (ImGui::TableSetColumnIndex(2))
{
- switch (scenery.proto.frame.type)
+ switch (scenery.proto.sc_type)
{
case scenery_type::none: text("none"); break;
case scenery_type::generic: text("generic"); break;
diff --git a/editor/imgui-inspect.cpp b/editor/imgui-inspect.cpp
index b673c9d5..0d1ab6cf 100644
--- a/editor/imgui-inspect.cpp
+++ b/editor/imgui-inspect.cpp
@@ -29,15 +29,11 @@ void app::draw_inspector()
for (auto i = inspectors.size()-1; i != -1_uz; i--)
{
- auto [ch, pos, target] = inspectors[i];
- auto [c, t] = w[{ch, pos}];
- auto s = t.scenery();
-
- if (!s)
- {
- inspectors.erase(inspectors.begin() + (int)i);
- continue;
- }
+ auto [e, target] = inspectors[i];
+ auto& s = *e;
+ chunk_coords ch = e->coord.chunk();
+ local_coords pos = e->coord.local();
+ auto& c = w[ch];
char buf[128];
snformat(buf, "i-{}-{}x{}-{}x{}"_cf, (int)target, ch.x, ch.y, (int)pos.x, (int)pos.y);
@@ -48,7 +44,13 @@ void app::draw_inspector()
snformat(buf, "{} ({}x{} -> {}x{})"_cf, name, ch.x, ch.y, (int)pos.x, (int)pos.y);
bool is_open = true;
if (auto b2 = begin_window(buf, &is_open))
- c.with_scenery_update(s.index(), [&] { return entities::inspect_type(s); });
+ {
+ if (s.type == entity_type::scenery)
+ {
+ auto& s2 = static_cast<scenery&>(s);
+ c.with_scenery_update(s, [&] { return entities::inspect_type(s2); });
+ }
+ }
if (!is_open)
inspectors.erase(inspectors.begin() + (int)i);
}
diff --git a/editor/imgui.cpp b/editor/imgui.cpp
index 753475d3..4ce19403 100644
--- a/editor/imgui.cpp
+++ b/editor/imgui.cpp
@@ -138,19 +138,14 @@ bool app::check_inspector_exists(popup_target p)
void app::do_popup_menu()
{
fm_assert(_popup_target.target != popup_target_type::none);
- auto& w = M->world();
-
auto b0 = push_id(SCENERY_POPUP_NAME);
-
- const auto [ch, pos, type] = _popup_target;
- auto [c, t] = w[{ch, pos}];
+ const auto [sc, target] = _popup_target;
//if (_popup_target.target != popup_target_type::scenery) {...}
- auto sc = t.scenery();
if (_pending_popup)
{
_pending_popup = false;
- fm_assert(type != popup_target_type::none);
+ fm_assert(target != popup_target_type::none);
//if (type != popup_target_type::scenery) {...}
if (sc)
ImGui::OpenPopup(SCENERY_POPUP_NAME.data());
@@ -158,11 +153,13 @@ void app::do_popup_menu()
if (auto b1 = begin_popup(SCENERY_POPUP_NAME))
{
- if (ImGui::MenuItem("Activate", nullptr, false, sc.can_activate()))
- sc.activate();
- if (auto next_rot = sc.atlas->next_rotation_from(sc.frame.r);
- ImGui::MenuItem("Rotate", nullptr, false, next_rot != sc.frame.r))
- sc.rotate(next_rot);
+ auto iter = sc->iter();
+ auto& c = sc->chunk();
+ if (ImGui::MenuItem("Activate", nullptr, false, sc->can_activate(iter, c)))
+ sc->activate(iter, c);
+ if (auto next_rot = sc->atlas->next_rotation_from(sc->r);
+ ImGui::MenuItem("Rotate", nullptr, false, next_rot != sc->r))
+ sc->rotate(iter, c, next_rot);
ImGui::Separator();
diff --git a/editor/inspect-types.cpp b/editor/inspect-types.cpp
index 110ebabf..9b14c132 100644
--- a/editor/inspect-types.cpp
+++ b/editor/inspect-types.cpp
@@ -20,53 +20,52 @@ static Corrade::Containers::String my_str;
namespace floormat::entities {
template<>
-struct entity_accessors<scenery_ref> {
+struct entity_accessors<scenery> {
static constexpr auto accessors()
{
- using entity = Entity<scenery_ref>;
- using frame_t = scenery::frame_t;
+ using entity = Entity<scenery>;
return std::tuple{
entity::type<StringView>::field{"name"_s,
- [](const scenery_ref& x) { return loader.strip_prefix(x.atlas->name()); },
- [](scenery_ref&, StringView) {},
+ [](const scenery& x) { return loader.strip_prefix(x.atlas->name()); },
+ [](scenery&, StringView) {},
constantly(field_status::readonly),
},
entity::type<rotation>::field{"rotation"_s,
- [](const scenery_ref& x) { return x.frame.r; },
- [](scenery_ref& x, rotation r) { x.rotate(r); },
+ [](const scenery& x) { return x.r; },
+ [](scenery& x, rotation r) { x.rotate(x.iter(), x.chunk(), r); },
},
- entity::type<scenery::frame_t>::field{"frame"_s,
- [](const scenery_ref& x) { return x.frame.frame; },
- [](scenery_ref& x, frame_t value) { x.frame.frame = value; },
- [](const scenery_ref& x) { return constraints::range<frame_t>{0, !x.atlas ? frame_t(0) : frame_t(x.atlas->info().nframes-1)}; }
+ entity::type<std::uint16_t>::field{"frame"_s,
+ [](const scenery& x) { return x.frame; },
+ [](scenery& x, std::uint16_t value) { x.frame = value; },
+ [](const scenery& x) { return constraints::range<std::uint16_t>{0, !x.atlas ? std::uint16_t(0) : std::uint16_t(x.atlas->info().nframes-1)}; }
},
entity::type<Vector2b>::field{"offset"_s,
- [](const scenery_ref& x) { return x.frame.offset; },
- [](scenery_ref& x, Vector2b value) { x.frame.offset = value; },
+ [](const scenery& x) { return x.offset; },
+ [](scenery& x, Vector2b value) { x.offset = value; },
constantly(constraints::range{Vector2b(iTILE_SIZE2/-2), Vector2b(iTILE_SIZE2/2)})
},
entity::type<pass_mode>::field{"pass-mode"_s,
- [](const scenery_ref& x) { return x.frame.passability; },
- [](scenery_ref& x, pass_mode value) { x.chunk().with_scenery_update(x.index(), [&] { x.frame.passability = value; }); },
+ [](const scenery& x) { return x.pass; },
+ [](scenery& x, pass_mode value) { x.chunk().with_scenery_update(x, [&] { x.pass = value; }); },
},
entity::type<Vector2b>::field{"bbox-offset"_s,
- [](const scenery_ref& x) { return x.frame.bbox_offset; },
- [](scenery_ref& x, Vector2b value) { x.chunk().with_scenery_update(x.index(), [&] { x.frame.bbox_offset = value; }); },
- [](const scenery_ref& x) { return x.frame.passability == pass_mode::pass ? field_status::readonly : field_status::enabled; },
+ [](const scenery& x) { return x.bbox_offset; },
+ [](scenery& x, Vector2b value) { x.chunk().with_scenery_update(x, [&] { x.bbox_offset = value; }); },
+ [](const scenery& x) { return x.pass == pass_mode::pass ? field_status::readonly : field_status::enabled; },
},
entity::type<Vector2ub>::field{"bbox-size"_s,
- [](const scenery_ref& x) { return x.frame.bbox_size; },
- [](scenery_ref& x, Vector2ub value) { x.chunk().with_scenery_update(x.index(), [&] { x.frame.bbox_size = value; }); },
- [](const scenery_ref& x) { return x.frame.passability == pass_mode::pass ? field_status::readonly : field_status::enabled; },
+ [](const scenery& x) { return x.bbox_size; },
+ [](scenery& x, Vector2ub value) { x.chunk().with_scenery_update(x, [&] { x.bbox_size = value; }); },
+ [](const scenery& x) { return x.pass == pass_mode::pass ? field_status::readonly : field_status::enabled; },
},
entity::type<bool>::field{"interactive"_s,
- [](const scenery_ref& x) { return x.frame.interactive; },
- [](scenery_ref& x, bool value) { x.frame.interactive = value; }
+ [](const scenery& x) { return x.interactive; },
+ [](scenery& x, bool value) { x.interactive = value; }
},
#ifdef TEST_STR
entity::type<String>::field{"string"_s,
- [](const scenery_ref&) { return my_str; },
- [](scenery_ref&, String value) { my_str = std::move(value); },
+ [](const scenery&) { return my_str; },
+ [](scenery&, String value) { my_str = std::move(value); },
constantly(constraints::max_length{8}),
},
#endif
@@ -75,8 +74,8 @@ struct entity_accessors<scenery_ref> {
};
template<typename T, typename = void> struct has_anim_atlas : std::false_type {};
-template<> struct has_anim_atlas<scenery_ref> : std::true_type {
- static const anim_atlas& get_atlas(const scenery_ref& x) {
+template<> struct has_anim_atlas<scenery> : std::true_type {
+ static const anim_atlas& get_atlas(const scenery& x) {
fm_assert(x.atlas);
return *x.atlas;
}
@@ -135,12 +134,12 @@ struct enum_values<rotation, U> : std::false_type {
};
template<>
-bool inspect_type<scenery_ref>(scenery_ref& x)
+bool inspect_type<scenery>(scenery& x)
{
bool ret = false;
visit_tuple([&](const auto& field) {
using type = typename std::decay_t<decltype(field)>::FieldType;
- using enum_type = enum_values<type, scenery_ref>;
+ using enum_type = enum_values<type, scenery>;
if constexpr(enum_type::value)
{
constexpr auto list = enum_type::get();
@@ -151,7 +150,7 @@ bool inspect_type<scenery_ref>(scenery_ref& x)
const auto& list = enum_type::get(x);
ret |= inspect_field<type>(&x, field.erased(), list);
}
- }, entity_metadata<scenery_ref>::accessors);
+ }, entity_metadata<scenery>::accessors);
return ret;
}
diff --git a/editor/scenery-editor.cpp b/editor/scenery-editor.cpp
index 1d0635a6..9e9aa47b 100644
--- a/editor/scenery-editor.cpp
+++ b/editor/scenery-editor.cpp
@@ -22,7 +22,7 @@ scenery_editor::scenery_editor() noexcept
void scenery_editor::set_rotation(rotation_ r)
{
- auto& s = _selected.proto.frame;
+ auto& s = _selected.proto;
s.bbox_offset = rotate_point(s.bbox_offset, s.r, r);
s.bbox_size = rotate_size(s.bbox_size, s.r, r);
s.r = r;
@@ -30,24 +30,24 @@ void scenery_editor::set_rotation(rotation_ r)
rotation_ scenery_editor::rotation() const
{
- return _selected.proto.frame.r;
+ return _selected.proto.r;
}
void scenery_editor::next_rotation()
{
- set_rotation(_selected.proto.atlas->next_rotation_from(_selected.proto.frame.r));
+ set_rotation(_selected.proto.atlas->next_rotation_from(_selected.proto.r));
}
void scenery_editor::prev_rotation()
{
- set_rotation(_selected.proto.atlas->prev_rotation_from(_selected.proto.frame.r));
+ set_rotation(_selected.proto.atlas->prev_rotation_from(_selected.proto.r));
}
void scenery_editor::select_tile(const scenery_& s)
{
- const auto r = s.proto.atlas && s.proto.atlas->check_rotation(_selected.proto.frame.r)
- ? _selected.proto.frame.r
- : s.proto.frame.r;
+ const auto r = s.proto.atlas && s.proto.atlas->check_rotation(_selected.proto.r)
+ ? _selected.proto.r
+ : s.proto.r;
_selected = s;
set_rotation(r);
}
@@ -80,7 +80,8 @@ bool scenery_editor::is_anything_selected() const
void scenery_editor::place_tile(world& w, global_coords pos, const scenery_& s)
{
auto [c, t] = w[pos];
- t.scenery() = s.proto;
+ // todo check collision at pos
+ auto sc = w.make_entity<scenery>(pos, s.proto);
c.mark_scenery_modified();
}
diff --git a/editor/update.cpp b/editor/update.cpp
index e9f178a1..24fd3086 100644
--- a/editor/update.cpp
+++ b/editor/update.cpp
@@ -69,28 +69,12 @@ void app::do_mouse_up_down(std::uint8_t button, bool is_down, int mods)
if (button == mouse_button_left)
{
if (auto* cl = find_clickable_scenery(*cursor.pixel))
- {
- auto [c, t] = w[{cl->chunk, cl->pos}];
- if (auto s = t.scenery())
- return (void)s.activate();
- }
+ return (void)cl->e->activate(cl->e->iter(), cl->e->chunk());
}
// TODO it should open on mouseup if still on the same item as on mousedown
else if (button == mouse_button_right)
- {
if (auto* cl = find_clickable_scenery(*cursor.pixel))
- {
- auto [c, t] = w[{cl->chunk, cl->pos}];
- if (auto s = t.scenery())
- {
- _popup_target = {
- .c = cl->chunk, .pos = cl->pos,
- .target = popup_target_type::scenery,
- };
- do_open_popup();
- }
- }
- }
+ _popup_target = { .e = cl->e, .target = popup_target_type::scenery, };
break;
case editor_mode::floor:
case editor_mode::walls:
@@ -120,18 +104,14 @@ void app::do_rotate(bool backward)
{
if (ed->is_anything_selected())
backward ? ed->prev_rotation() : ed->next_rotation();
- else if (cursor.tile)
+ else if (auto* cl = find_clickable_scenery(*cursor.pixel))
{
- auto [c, t] = M->world()[*cursor.tile];
- if (auto sc = t.scenery())
+ auto& e = *cl->e;
+ auto r = backward ? e.atlas->prev_rotation_from(e.r) : e.atlas->next_rotation_from(e.r);
+ if (r != e.r)
{
- auto [atlas, s] = sc;
- auto r = backward ? atlas->prev_rotation_from(s.r) : atlas->next_rotation_from(s.r);
- if (r != s.r)
- {
- sc.rotate(r);
- c.mark_scenery_modified();
- }
+ e.rotate(e.iter(), e.chunk(), r);
+ e.chunk().mark_scenery_modified();
}
}
}
@@ -204,16 +184,24 @@ void app::update_world(float dt)
minx--; miny--; maxx++; maxy++;
for (std::int16_t y = miny; y <= maxy; y++)
for (std::int16_t x = minx; x <= maxx; x++)
- for (auto& c = world[chunk_coords{x, y}]; auto [x, k, pt] : c)
- if (auto sc = x.scenery(); sc && sc.can_activate())
- c.with_scenery_update(sc.index(), [&] { return sc.update(dt); });
+ {
+ auto& c = world[chunk_coords{x, y}];
+ const auto& es = c.entities();
+ const auto size = es.size();
+ for (auto i = size-1; i != (std::size_t)-1; i--)
+ {
+ auto iter = es.cbegin() + std::ptrdiff_t(i);
+ auto& e = *es[i];
+ c.with_scenery_update(e, [&] { return e.update(iter, c, dt); });
+ }
+ }
}
-void app::update_character(float dt)
+void app::update_character([[maybe_unused]] float dt)
{
- _character->tick(M->world(), dt, keys[key_left], keys[key_right], keys[key_up], keys[key_down]);
+ _character->set_keys(keys[key_left], keys[key_right], keys[key_up], keys[key_down]);
}
void app::set_cursor()
@@ -221,16 +209,9 @@ void app::set_cursor()
if (!cursor.in_imgui)
{
if (auto* cl = find_clickable_scenery(cursor.pixel))
- {
- auto& w = M->world();
- auto [c, t] = w[{cl->chunk, cl->pos}];
- if (auto sc = t.scenery())
- {
- M->set_cursor(std::uint32_t(Cursor::Hand));
- return;
- }
- }
- M->set_cursor(std::uint32_t(Cursor::Arrow));
+ M->set_cursor(std::uint32_t(Cursor::Hand));
+ else
+ M->set_cursor(std::uint32_t(Cursor::Arrow));
}
else
set_cursor_from_imgui();
diff --git a/main/clickable.hpp b/main/clickable.hpp
index 3ca8ccb6..c7502f33 100644
--- a/main/clickable.hpp
+++ b/main/clickable.hpp
@@ -1,18 +1,19 @@
#pragma once
#include "src/global-coords.hpp"
+#include <memory>
#include <Corrade/Containers/BitArrayView.h>
#include <Magnum/Math/Range.h>
namespace floormat {
+struct entity;
+
struct clickable final {
Math::Range2D<unsigned> src;
Math::Range2D<int> dest;
BitArrayView bitmask;
- float depth = 0;
- std::uint32_t stride;
- chunk_coords chunk;
- local_coords pos;
+ std::shared_ptr<entity> e;
+ std::uint32_t depth, stride;
bool mirrored;
};
diff --git a/main/draw.cpp b/main/draw.cpp
index 57623eef..84d90f65 100644
--- a/main/draw.cpp
+++ b/main/draw.cpp
@@ -136,12 +136,8 @@ void main_impl::draw_world() noexcept
if (check_chunk_visible(_shader.camera_offset(), sz))
{
_anim_mesh.draw(_shader, c);
- for (auto i = 0_uz; i < TILE_COUNT; i++)
- {
- const local_coords xy{i};
- if (auto [atlas, s] = c[xy].scenery(); atlas)
- _anim_mesh.add_clickable(_shader, window_size(), pos, std::uint8_t(i), atlas, s, _clickable_scenery);
- }
+ for (const auto& e : c.entities())
+ _anim_mesh.add_clickable(_shader, window_size(), e, _clickable_scenery);
}
}
diff --git a/serialize/scenery.cpp b/serialize/scenery.cpp
index 4eeb95f7..3cd084cd 100644
--- a/serialize/scenery.cpp
+++ b/serialize/scenery.cpp
@@ -80,20 +80,19 @@ void adl_serializer<rotation>::from_json(const json& j, rotation& val)
val = foo_from_string(j, rotation_map, "rotation");
}
-void adl_serializer<scenery_proto>::to_json(json& j, const scenery_proto& val)
+void adl_serializer<scenery_proto>::to_json(json& j, const scenery_proto& f)
{
- fm_assert(val.atlas);
- constexpr scenery default_scenery;
- const scenery& f = val.frame;
+ fm_assert(f.atlas);
+ const scenery_proto default_scenery;
if (f.type != default_scenery.type)
j["type"] = f.type;
- j["atlas-name"] = val.atlas->name();
+ j["atlas-name"] = f.atlas->name();
if (f.frame != default_scenery.frame)
j["frame"] = f.frame;
if (f.r != default_scenery.r)
j["rotation"] = f.r;
- if (f.passability != default_scenery.passability)
- j["pass-mode"] = f.passability;
+ if (f.pass != default_scenery.pass)
+ j["pass-mode"] = f.pass;
if (f.active != default_scenery.active)
j["active"] = f.active;
if (f.interactive != default_scenery.interactive)
@@ -106,7 +105,7 @@ void adl_serializer<scenery_proto>::to_json(json& j, const scenery_proto& val)
j["bbox-size"] = Vector2ui(f.bbox_size);
}
-void adl_serializer<scenery_proto>::from_json(const json& j, scenery_proto& val)
+void adl_serializer<scenery_proto>::from_json(const json& j, scenery_proto& f)
{
const auto get = [&](const StringView& name, auto& value)
{
@@ -117,14 +116,13 @@ void adl_serializer<scenery_proto>::from_json(const json& j, scenery_proto& val)
StringView atlas_name = j["atlas-name"];
fm_soft_assert(!atlas_name.isEmpty());
- val.atlas = loader.anim_atlas(atlas_name, loader_::SCENERY_PATH);
- auto& f = val.frame;
f = {};
+ f.atlas = loader.anim_atlas(atlas_name, loader_::SCENERY_PATH);
auto type = scenery_type::generic; get("type", type);
auto frame = f.frame; get("frame", frame);
- auto r = val.atlas->first_rotation(); get("rotation", r);
- pass_mode pass = f.passability; get("pass-mode", pass);
+ auto r = f.atlas->first_rotation(); get("rotation", r);
+ pass_mode pass = f.pass; get("pass-mode", pass);
bool active = f.active; get("active", active);
bool interactive = f.interactive; get("interactive", interactive);
auto offset = Vector2i(f.offset); get("offset", offset);
@@ -139,13 +137,30 @@ void adl_serializer<scenery_proto>::from_json(const json& j, scenery_proto& val)
default:
fm_throw("unhandled scenery type '{}'"_cf, (unsigned)type);
case scenery_type::generic:
- f = { scenery::generic, *val.atlas, r, frame, pass,
- active, interactive,
- Vector2b(offset), Vector2b(bbox_offset), Vector2ub(bbox_size) };
+ f.type = entity_type::scenery;
+ f.sc_type = scenery_type::generic;
+ f.r = r;
+ f.frame = frame;
+ f.pass = pass;
+ f.active = active;
+ f.interactive = interactive;
+ f.offset = Vector2b(offset);
+ f.bbox_offset = Vector2b(bbox_offset);
+ f.bbox_size = Vector2ub(bbox_size);
break;
case scenery_type::door:
- f = { scenery::door, *val.atlas, r, false,
- Vector2b(offset), Vector2b(bbox_offset), Vector2ub(bbox_size), };
+ fm_assert(f.atlas->info().fps > 0 && f.atlas->info().nframes > 0);
+ f.type = entity_type::scenery;
+ f.sc_type = scenery_type::door;
+ f.r = r;
+ f.frame = std::uint16_t(f.atlas->group(r).frames.size()-1);
+ f.pass = pass_mode::blocked;
+ f.interactive = true;
+ f.closing = false;
+ f.offset = Vector2b(offset);
+ f.bbox_offset = Vector2b(bbox_offset);
+ f.bbox_size = Vector2ub(bbox_size);
+ break;
}
}
diff --git a/serialize/world-impl.hpp b/serialize/world-impl.hpp
index 5a43654e..c8282141 100644
--- a/serialize/world-impl.hpp
+++ b/serialize/world-impl.hpp
@@ -4,6 +4,8 @@
#pragma once
#include "src/tile.hpp"
+#include "src/pass-mode.hpp"
+#include "src/rotation.hpp"
#include <bit>
#include <cstddef>
#include <cstdint>
diff --git a/serialize/world-reader.cpp b/serialize/world-reader.cpp
index 3060579f..83a77ffb 100644
--- a/serialize/world-reader.cpp
+++ b/serialize/world-reader.cpp
@@ -2,6 +2,7 @@
#include "world-impl.hpp"
#include "binary-reader.inl"
#include "src/world.hpp"
+#include "src/scenery.hpp"
#include "loader/loader.hpp"
#include "loader/scenery.hpp"
#include "src/tile-atlas.hpp"
@@ -52,10 +53,10 @@ void reader_state::read_atlases(reader_t& s)
}
template<typename T>
-bool read_scenery_flags(binary_reader<T>& s, scenery& sc)
+bool read_scenery_flags(binary_reader<T>& s, scenery_proto& sc)
{
std::uint8_t flags; flags << s;
- sc.passability = pass_mode(flags & pass_mask);
+ sc.pass = pass_mode(flags & pass_mask);
sc.active = !!(flags & 1 << 2);
sc.closing = !!(flags & 1 << 3);
sc.interactive = !!(flags & 1 << 4);
@@ -79,20 +80,19 @@ void reader_state::read_sceneries(reader_t& s)
std::uint8_t num; num << s;
fm_soft_assert(num > 0);
auto str = s.read_asciiz_string<atlas_name_max>();
- const auto sc_ = loader.scenery(str);
+ auto sc = loader.scenery(str);
for (auto n = 0_uz; n < num; n++)
{
atlasid id; id << s;
fm_soft_assert(id < sz);
fm_soft_assert(!sceneries[id]);
- scenery_proto sc = sc_;
- bool short_frame = read_scenery_flags(s, sc.frame);
+ bool short_frame = read_scenery_flags(s, sc);
fm_debug_assert(sc.atlas != nullptr);
if (short_frame)
- sc.frame.frame = s.read<std::uint8_t>();
+ sc.frame = s.read<std::uint8_t>();
else
- sc.frame.frame << s;
- fm_soft_assert(sc.frame.frame < sc.atlas->info().nframes);
+ sc.frame << s;
+ fm_soft_assert(sc.frame < sc.atlas->info().nframes);
sceneries[id] = sc;
}
i += num;
@@ -165,39 +165,42 @@ void reader_state::read_chunks(reader_t& s)
id &= ~scenery_id_flag_mask;
auto sc = lookup_scenery(id);
(void)sc.atlas->group(r);
- sc.frame.r = r;
+ sc.r = r;
if (!exact)
{
- if (read_scenery_flags(s, sc.frame))
- sc.frame.frame = s.read<std::uint8_t>();
+ if (read_scenery_flags(s, sc))
+ sc.frame = s.read<std::uint8_t>();
else
- sc.frame.frame << s;
+ sc.frame << s;
if (PROTO >= 5) [[likely]]
{
- sc.frame.offset[0] << s;
- sc.frame.offset[1] << s;
+ sc.offset[0] << s;
+ sc.offset[1] << s;
}
if (PROTO >= 6) [[likely]]
{
- sc.frame.bbox_size[0] << s;
- sc.frame.bbox_size[1] << s;
+ sc.bbox_size[0] << s;
+ sc.bbox_size[1] << s;
}
if (PROTO >= 7) [[likely]]
{
- sc.frame.bbox_offset[0] << s;
- sc.frame.bbox_offset[1] << s;
+ sc.bbox_offset[0] << s;
+ sc.bbox_offset[1] << s;
}
- if (sc.frame.active)
+ if (sc.active)
{
if (PROTO >= 4) [[likely]]
- sc.frame.delta << s;
+ sc.delta << s;
else
- sc.frame.delta = (std::uint16_t)Math::clamp(int(s.read<float>() * 65535), 0, 65535);
+ sc.delta = (std::uint16_t)Math::clamp(int(s.read<float>() * 65535), 0, 65535);
}
}
- t.scenery() = sc;
+
+ auto e = _world->make_entity<scenery>({ coord, local_coords{i} }, sc);
+ chunk.add_entity_unsorted(e);
}
}
+ chunk.sort_entities();
}
}
diff --git a/serialize/world-writer.cpp b/serialize/world-writer.cpp
index 2a58a51b..413a9566 100644
--- a/serialize/world-writer.cpp
+++ b/serialize/world-writer.cpp
@@ -115,7 +115,6 @@ void writer_state::load_scenery_1(const serialized_scenery& s)
scenery_map[ptr] = { { &s, null_atlas } };
else
{
- fm_assert(s.proto.frame.delta == 0);
auto& vec = scenery_map[ptr];
for (const auto& x : vec)
if (s.proto.frame == x.s->proto.frame)
@@ -140,8 +139,8 @@ scenery_pair writer_state::intern_scenery(scenery_proto s, bool create)
interned_scenery *ret = nullptr, *ret2 = nullptr;
for (interned_scenery& x : vec)
{
- fm_debug_assert(s.frame.type == x.s->proto.frame.type);
- s.frame.r = x.s->proto.frame.r;
+ fm_debug_assert(s.type == x.s->proto.type);
+ s.r = x.s->proto.r;
if (x.s->proto.frame == s.frame)
{
if (x.index != null_atlas)
@@ -178,10 +177,10 @@ scenery_pair writer_state::maybe_intern_scenery(const scenery_proto& s, bool cre
}
template<typename T>
-void write_scenery_flags(binary_writer<T>& s, const scenery& proto)
+void write_scenery_flags(binary_writer<T>& s, const scenery_proto& proto)
{
std::uint8_t flags = 0;
- flags |= pass_mode_(proto.passability) & pass_mask;
+ flags |= pass_mode_(proto.pass) & pass_mask;
flags |= (1 << 2) * proto.active;
flags |= (1 << 3) * proto.closing;
flags |= (1 << 4) * proto.interactive;
@@ -272,12 +271,11 @@ void writer_state::serialize_scenery()
s.write_asciiz_string(sc->name);
}
s << idx;
- const auto& fr = sc->proto.frame;
- write_scenery_flags(s, sc->proto.frame);
- if (sc->proto.frame.frame <= 0xff)
- s << (std::uint8_t)fr.frame;
+ write_scenery_flags(s, sc->proto);
+ if (sc->proto.frame <= 0xff)
+ s << (std::uint8_t)sc->proto.frame;
else
- s << fr.frame;
+ s << sc->proto.frame;
}
scenery_buf.resize(s.bytes_written());
@@ -296,27 +294,29 @@ void writer_state::serialize_chunk(const chunk& c, chunk_coords coord)
{
const tile_proto x = c[i];
const auto ground = x.ground(), wall_north = x.wall_north(), wall_west = x.wall_west();
- const auto scenery = x.scenery_frame;
+ //const auto scenery = x.scenery_frame;
fm_debug_assert(s.bytes_written() + tile_size <= chunkbuf_size);
auto img_g = maybe_intern_atlas(ground);
auto img_n = maybe_intern_atlas(wall_north);
auto img_w = maybe_intern_atlas(wall_west);
- auto [sc, img_s, sc_exact] = maybe_intern_scenery(x.scenery(), true);
+ //auto [sc, img_s, sc_exact] = maybe_intern_scenery(x.scenery(), true);
+#if 0
if (sc_exact && sc)
{
sc_exact = scenery.offset == sc->proto.frame.offset &&
scenery.bbox_size == sc->proto.frame.bbox_size &&
scenery.bbox_offset == sc->proto.frame.bbox_offset;
}
+#endif
tilemeta flags = {};
flags |= meta_ground * (img_g != null_atlas);
flags |= meta_wall_n * (img_n != null_atlas);
flags |= meta_wall_w * (img_w != null_atlas);
- flags |= meta_scenery * (img_s != null_atlas);
+ //flags |= meta_scenery * (img_s != null_atlas);
using uchar = std::uint8_t;
@@ -353,6 +353,7 @@ void writer_state::serialize_chunk(const chunk& c, chunk_coords coord)
write(img_n, wall_north.variant);
if (img_w != null_atlas)
write(img_w, wall_west.variant);
+#if 0
if (img_s != null_atlas)
{
atlasid id = img_s;
@@ -381,6 +382,7 @@ void writer_state::serialize_chunk(const chunk& c, chunk_coords coord)
s << scenery.delta;
}
}
+#endif
}
const auto nbytes = s.bytes_written();
@@ -403,9 +405,11 @@ ArrayView<const char> writer_state::serialize_world()
{
load_scenery();
+#if 0
for (const auto& [_, c] : _world->chunks())
for (auto [x, _k, _pt] : c)
maybe_intern_scenery(x.scenery(), false);
+#endif
for (const auto& [pos, c] : _world->chunks())
{
diff --git a/src/character.cpp b/src/character.cpp
new file mode 100644
index 00000000..18472954
--- /dev/null
+++ b/src/character.cpp
@@ -0,0 +1,121 @@
+#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 world& w, entity_type type) : entity{id, w, type}
+{
+ atlas = loader.anim_atlas("npc-walk", loader.ANIM_PATH);
+ bbox_size = {12, 12};
+}
+
+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(sgn(left_right), 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(It it, struct chunk& c, float dt)
+{
+ auto [lr, ud, rot] = 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);
+ r = rot;
+ c.ensure_passability();
+
+ for (int i = 0; i < nframes; i++)
+ {
+ constexpr auto frac = Vector2(32767);
+ constexpr auto inv_frac = Vector2(1.f/32767);
+ 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, c))
+ entity::move(it, off_i, c);
+ ++frame %= atlas->info().nframes;
+ }
+ //Debug{} << "pos" << Vector2i(pos.local());
+ return true;
+}
+
+} // namespace floormat
diff --git a/src/character.hpp b/src/character.hpp
new file mode 100644
index 00000000..d69b98dc
--- /dev/null
+++ b/src/character.hpp
@@ -0,0 +1,32 @@
+#pragma once
+#include "src/global-coords.hpp"
+#include "src/rotation.hpp"
+#include "src/entity.hpp"
+#include <memory>
+
+namespace floormat {
+
+struct anim_atlas;
+struct world;
+
+struct character final : entity
+{
+ ~character() override;
+ void set_keys(bool L, bool R, bool U, bool D);
+ bool update(It it, struct chunk& c, float dt) override;
+
+private:
+ int allocate_frame_time(float dt);
+ static Vector2 move_vec(int left_right, int top_bottom);
+ character(std::uint64_t id, struct world& w, entity_type type);
+
+ friend struct world;
+
+ Vector2s offset_frac;
+
+ bool b_L : 1 = false, b_R : 1 = false, b_U : 1 = false, b_D : 1 = false;
+};
+
+template<> struct entity_type_<struct character> : std::integral_constant<entity_type, entity_type::character> {};
+
+} // namespace floormat
diff --git a/src/chunk-collision.cpp b/src/chunk-collision.cpp
index 3cf772ef..032c9d7d 100644
--- a/src/chunk-collision.cpp
+++ b/src/chunk-collision.cpp
@@ -18,10 +18,9 @@ constexpr Vector2 tile_start(std::size_t k)
return TILE_SIZE2 * Vector2(coord) - half_tile;
}
-constexpr Pair<Vector2i, Vector2i> scenery_tile(std::size_t k, const scenery& sc)
+Pair<Vector2i, Vector2i> scenery_tile(const entity& sc)
{
- const local_coords coord{k};
- auto center = iTILE_SIZE2 * Vector2i(coord) + Vector2i(sc.offset) + Vector2i(sc.bbox_offset);
+ auto center = iTILE_SIZE2 * Vector2i(sc.coord.local()) + Vector2i(sc.offset) + Vector2i(sc.bbox_offset);
auto start = center - Vector2i(sc.bbox_size);
auto size = Vector2i(sc.bbox_size)*2;
return { start, start + size, };
@@ -60,15 +59,13 @@ void chunk::ensure_passability() noexcept
_rtree.RemoveAll();
- for (auto i = 0_uz; i < TILE_COUNT; i++)
+ for (const std::shared_ptr<entity>& s : entities())
{
- if (auto s = operator[](i).scenery())
- {
- bbox box;
- if (_bbox_for_scenery(i, box))
- _add_bbox(box);
- }
+ bbox box;
+ if (_bbox_for_scenery(*s, box))
+ _add_bbox(box);
}
+
for (auto i = 0_uz; i < TILE_COUNT; i++)
{
if (const auto* atlas = ground_atlas_at(i))
@@ -99,24 +96,26 @@ void chunk::ensure_passability() noexcept
}
}
-bool chunk::_bbox_for_scenery(std::size_t i, bbox& value) noexcept
+bool chunk::_bbox_for_scenery(const entity& s, bbox& value) noexcept
{
- fm_debug_assert(i < TILE_COUNT);
- auto [atlas, s] = operator[](i).scenery();
- auto [start, end] = scenery_tile(i, s);
- auto id = make_id(collision_type::scenery, s.passability, i);
+ auto [start, end] = scenery_tile(s);
+ auto id = make_id(collision_type::scenery, s.pass, s.id);
value = { .id = id, .start = start, .end = end };
- return atlas && s.passability != pass_mode::pass && Vector2i(s.bbox_size).product() > 0;
+ return s.atlas && s.pass != pass_mode::pass;
}
void chunk::_remove_bbox(const bbox& x)
{
+ if (_scenery_modified)
+ return;
auto start = Vector2(x.start), end = Vector2(x.end);
_rtree.Remove(start.data(), end.data(), x.id);
}
void chunk::_add_bbox(const bbox& x)
{
+ if (_scenery_modified)
+ return;
auto start = Vector2(x.start), end = Vector2(x.end);
_rtree.Insert(start.data(), end.data(), x.id);
}
@@ -145,4 +144,16 @@ void chunk::_replace_bbox(const bbox& x0, const bbox& x1, bool b0, bool b1)
CORRADE_ASSUME(false);
}
+bool chunk::can_place_entity(const entity_proto& proto, local_coords pos)
+{
+ (void)ensure_scenery_mesh();
+
+ const auto center = Vector2(pos)*TILE_SIZE2 + Vector2(proto.offset) + Vector2(proto.bbox_offset),
+ half_bbox = Vector2(proto.bbox_size)*.5f,
+ min = center - half_bbox, max = center + half_bbox;
+ bool ret = true;
+ _rtree.Search(min.data(), max.data(), [&](auto, const auto&) { return ret = false; });
+ return ret;
+}
+
} // namespace floormat
diff --git a/src/chunk-render.cpp b/src/chunk-render.cpp
index bc22ef9b..e370239f 100644
--- a/src/chunk-render.cpp
+++ b/src/chunk-render.cpp
@@ -1,7 +1,8 @@
#include "chunk.hpp"
#include "tile-atlas.hpp"
-#include "anim-atlas.hpp"
#include "shaders/tile.hpp"
+#include "entity.hpp"
+#include "anim-atlas.hpp"
#include <algorithm>
#include <Corrade/Containers/ArrayViewStl.h>
#include <Magnum/GL/Buffer.h>
@@ -17,12 +18,6 @@ static auto make_index_array(std::size_t max)
return array;
}
-struct vertex {
- Vector3 position;
- Vector2 texcoords;
- float depth = -1;
-};
-
auto chunk::ensure_ground_mesh() noexcept -> ground_mesh_tuple
{
if (!_ground_modified)
@@ -111,48 +106,47 @@ auto chunk::ensure_wall_mesh() noexcept -> wall_mesh_tuple
auto chunk::ensure_scenery_mesh() noexcept -> scenery_mesh_tuple
{
- if (!_scenery_modified)
- return { scenery_mesh, scenery_indexes, std::size_t(scenery_mesh.count()/6) };
- _scenery_modified = false;
-
- std::size_t count = 0;
- for (auto i = 0_uz; i < TILE_COUNT; i++)
- if (const auto& atlas = _scenery_atlases[i]; atlas && atlas->info().fps == 0)
- scenery_indexes[count++] = std::uint8_t(i);
-
-#if 0
- std::sort(scenery_indexes.begin(), scenery_indexes.begin() + count,
- [this](std::uint8_t a, std::uint8_t b) {
- return _scenery_atlases[a] < _scenery_atlases[b];
- });
-#endif
- std::array<std::array<vertex, 4>, TILE_COUNT> vertexes;
- for (auto k = 0_uz; k < count; k++)
+ if (_scenery_modified)
{
- const std::uint8_t i = scenery_indexes[k];
- const local_coords pos{i};
- const auto& atlas = _scenery_atlases[i];
- const auto& fr = _scenery_variants[i];
- const auto coord = Vector3(pos) * TILE_SIZE + Vector3(Vector2(fr.offset), 0);
- const auto quad = atlas->frame_quad(coord, fr.r, fr.frame);
- const auto& group = atlas->group(fr.r);
- const auto texcoords = atlas->texcoords_for_frame(fr.r, fr.frame, !group.mirror_from.isEmpty());
- const float depth = tile_shader::depth_value(pos, tile_shader::scenery_depth_offset);
- auto& v = vertexes[k];
- for (auto j = 0_uz; j < 4; j++)
- v[j] = { quad[j], texcoords[j], depth };
+ _scenery_modified = false;
+ const auto count = fm_begin(
+ std::size_t ret = 0;
+ for (const auto& e : _entities)
+ ret += e->atlas->info().fps == 0;
+ return ret;
+ );
+
+ scenery_indexes.clear();
+ scenery_indexes.reserve(count);
+ scenery_vertexes.clear();
+ scenery_vertexes.reserve(count);
+
+ for (const auto& e : _entities)
+ {
+ const auto i = scenery_indexes.size();
+ scenery_indexes.emplace_back();
+ scenery_indexes.back() = tile_atlas::indices(i);
+ const auto& atlas = e->atlas;
+ const auto& fr = *e;
+ const auto pos = e->coord.local();
+ const auto coord = Vector3(pos) * TILE_SIZE + Vector3(Vector2(fr.offset), 0);
+ const auto quad = atlas->frame_quad(coord, fr.r, fr.frame);
+ const auto& group = atlas->group(fr.r);
+ const auto texcoords = atlas->texcoords_for_frame(fr.r, fr.frame, !group.mirror_from.isEmpty());
+ const float depth = tile_shader::depth_value(pos, tile_shader::scenery_depth_offset);
+ scenery_vertexes.emplace_back();
+ auto& v = scenery_vertexes.back();
+ for (auto j = 0_uz; j < 4; j++)
+ v[j] = { quad[j], texcoords[j], depth };
+ }
+
+ GL::Mesh mesh{GL::MeshPrimitive::Triangles};
+ mesh.addVertexBuffer(GL::Buffer{scenery_vertexes}, 0, tile_shader::Position{}, tile_shader::TextureCoordinates{}, tile_shader::Depth{})
+ .setIndexBuffer(GL::Buffer{scenery_indexes}, 0, GL::MeshIndexType::UnsignedShort)
+ .setCount(std::int32_t(6 * count));
+ scenery_mesh = Utility::move(mesh);
}
-
- const auto indexes = make_index_array(count);
- const auto vertex_view = ArrayView{vertexes.data(), count};
- const auto vert_index_view = ArrayView{indexes.data(), count};
-
- GL::Mesh mesh{GL::MeshPrimitive::Triangles};
- mesh.addVertexBuffer(GL::Buffer{vertex_view}, 0, tile_shader::Position{}, tile_shader::TextureCoordinates{}, tile_shader::Depth{})
- .setIndexBuffer(GL::Buffer{vert_index_view}, 0, GL::MeshIndexType::UnsignedShort)
- .setCount(std::int32_t(6 * count));
- scenery_mesh = Utility::move(mesh);
- return { scenery_mesh, scenery_indexes, count };
+ return { scenery_mesh, };
}
} // namespace floormat
diff --git a/src/chunk.cpp b/src/chunk.cpp
index 6cc7ee78..6edced05 100644
--- a/src/chunk.cpp
+++ b/src/chunk.cpp
@@ -1,5 +1,7 @@
#include "chunk.hpp"
#include "src/tile-atlas.hpp"
+#include "anim-atlas.hpp"
+#include <algorithm>
#include <Magnum/GL/Context.h>
namespace floormat {
@@ -22,17 +24,16 @@ bool chunk::empty(bool force) const noexcept
if (!force && !_maybe_empty)
return false;
for (auto i = 0_uz; i < TILE_COUNT; i++)
- if (_ground_atlases[i] || _wall_atlases[i*2 + 0] || _wall_atlases[i*2 + 1] || _scenery_atlases[i])
+ if (_ground_atlases[i] || _wall_atlases[i*2 + 0] || _wall_atlases[i*2 + 1] || !_entities.empty())
return _maybe_empty = false;
+ if (!_entities.empty())
+ return false;
return true;
}
tile_atlas* chunk::ground_atlas_at(std::size_t i) const noexcept { return _ground_atlases[i].get(); }
tile_atlas* chunk::wall_atlas_at(std::size_t i) const noexcept { return _wall_atlases[i].get(); }
-std::shared_ptr<anim_atlas>& chunk::scenery_atlas_at(std::size_t i) noexcept { return _scenery_atlases[i]; }
-scenery& chunk::scenery_at(std::size_t i) noexcept { return _scenery_variants[i]; }
-
tile_ref chunk::operator[](std::size_t idx) noexcept { return { *this, std::uint8_t(idx) }; }
tile_proto chunk::operator[](std::size_t idx) const noexcept { return tile_proto(tile_ref { *const_cast<chunk*>(this), std::uint8_t(idx) }); }
tile_ref chunk::operator[](local_coords xy) noexcept { return operator[](xy.to_index()); }
@@ -89,11 +90,64 @@ void chunk::mark_modified() noexcept
}
chunk::chunk() noexcept = default;
-chunk::~chunk() noexcept = default;
+chunk::~chunk() noexcept
+{
+ _teardown = true;
+ _entities.clear();
+ _rtree.RemoveAll();
+}
chunk::chunk(chunk&&) noexcept = default;
chunk& chunk::operator=(chunk&&) noexcept = default;
bool chunk::bbox::operator==(const bbox& other) const noexcept = default;
+void chunk::add_entity_unsorted(const std::shared_ptr<entity>& e)
+{
+ if (e->atlas->info().fps == 0)
+ mark_scenery_modified(false);
+ if (bbox bb; _bbox_for_scenery(*e, bb))
+ _add_bbox(bb);
+ _entities.push_back(e);
+}
+
+void chunk::sort_entities()
+{
+ mark_scenery_modified(false);
+
+ std::sort(_entities.begin(), _entities.end(), [](const auto& a, const auto& b) {
+ return a->ordinal() < b->ordinal();
+ });
+}
+
+void chunk::add_entity(const std::shared_ptr<entity>& e)
+{
+ if (e->atlas->info().fps == 0)
+ mark_scenery_modified(false);
+ if (bbox bb; _bbox_for_scenery(*e, bb))
+ _add_bbox(bb);
+
+ _entities.reserve(_entities.size() + 1);
+ auto it = std::lower_bound(_entities.cbegin(), _entities.cend(), e, [ord = e->ordinal()](const auto& a, const auto&) {
+ return a->ordinal() < ord;
+ });
+ _entities.insert(it, e);
+}
+
+void chunk::remove_entity(entity_const_iterator it)
+{
+ const auto& e = *it;
+ if (e->atlas->info().fps == 0)
+ mark_scenery_modified(false);
+ if (bbox bb; _bbox_for_scenery(*e, bb))
+ _remove_bbox(bb);
+
+ _entities.erase(it);
+}
+
+const std::vector<std::shared_ptr<entity>>& chunk::entities() const
+{
+ return _entities;
+}
+
} // namespace floormat
diff --git a/src/chunk.hpp b/src/chunk.hpp
index a2fdf53f..a5eba19e 100644
--- a/src/chunk.hpp
+++ b/src/chunk.hpp
@@ -11,6 +11,7 @@
namespace floormat {
struct anim_atlas;
+struct entity;
enum class collision : std::uint8_t {
view, shoot, move,
@@ -29,6 +30,7 @@ struct collision_data final {
struct chunk final
{
friend struct tile_ref;
+ friend struct entity;
tile_ref operator[](std::size_t idx) noexcept;
tile_proto operator[](std::size_t idx) const noexcept;
@@ -76,8 +78,12 @@ struct chunk final
struct scenery_mesh_tuple final {
GL::Mesh& mesh;
- const ArrayView<const std::uint8_t> ids;
- const std::size_t size;
+ };
+
+ struct vertex {
+ Vector3 position;
+ Vector2 texcoords;
+ float depth = -1;
};
ground_mesh_tuple ensure_ground_mesh() noexcept;
@@ -87,8 +93,6 @@ struct chunk final
tile_atlas* wall_atlas_at(std::size_t i) const noexcept;
scenery_mesh_tuple ensure_scenery_mesh() noexcept;
- std::shared_ptr<anim_atlas>& scenery_atlas_at(std::size_t i) noexcept;
- scenery& scenery_at(std::size_t i) noexcept;
void ensure_passability() noexcept;
@@ -99,7 +103,18 @@ struct chunk final
template<typename F>
requires requires(F fun) { fun(); }
- void with_scenery_update(std::size_t idx, F&& fun);
+ void with_scenery_update(entity& e, F&& fun);
+
+ using entity_vector = std::vector<std::shared_ptr<entity>>;
+ using entity_const_iterator = typename entity_vector::const_iterator;
+
+ [[nodiscard]] bool can_place_entity(const entity_proto& proto, local_coords pos);
+
+ void add_entity(const std::shared_ptr<entity>& e);
+ void add_entity_unsorted(const std::shared_ptr<entity>& e);
+ void sort_entities();
+ void remove_entity(entity_const_iterator it);
+ const std::vector<std::shared_ptr<entity>>& entities() const;
private:
std::array<std::shared_ptr<tile_atlas>, TILE_COUNT> _ground_atlases;
@@ -108,9 +123,10 @@ private:
std::array<std::shared_ptr<tile_atlas>, TILE_COUNT*2> _wall_atlases;
std::array<std::uint16_t, TILE_COUNT*2> wall_indexes = {};
std::array<variant_t, TILE_COUNT*2> _wall_variants = {};
- std::array<std::shared_ptr<anim_atlas>, TILE_COUNT> _scenery_atlases;
- std::array<std::uint8_t, TILE_COUNT> scenery_indexes = {};
- std::array<scenery, TILE_COUNT> _scenery_variants = {};
+ std::vector<std::shared_ptr<entity>> _entities;
+
+ std::vector<std::array<UnsignedShort, 6>> scenery_indexes;
+ std::vector<std::array<vertex, 4>> scenery_vertexes;
GL::Mesh ground_mesh{NoCreate}, wall_mesh{NoCreate}, scenery_mesh{NoCreate};
@@ -120,7 +136,8 @@ private:
_ground_modified : 1 = true,
_walls_modified : 1 = true,
_scenery_modified : 1 = true,
- _pass_modified : 1 = true;
+ _pass_modified : 1 = true,
+ _teardown : 1 = false;
struct bbox final // NOLINT(cppcoreguidelines-pro-type-member-init)
{
@@ -129,7 +146,7 @@ private:
bool operator==(const bbox& other) const noexcept;
};
- bool _bbox_for_scenery(std::size_t i, bbox& value) noexcept;
+ bool _bbox_for_scenery(const entity& s, bbox& value) noexcept;
void _remove_bbox(const bbox& x);
void _add_bbox(const bbox& x);
void _replace_bbox(const bbox& x0, const bbox& x, bool b0, bool b);
diff --git a/src/chunk.inl b/src/chunk.inl
index 23013099..3d40c30e 100644
--- a/src/chunk.inl
+++ b/src/chunk.inl
@@ -1,16 +1,20 @@
#pragma once
#include "chunk.hpp"
+#include "anim-atlas.hpp"
namespace floormat {
template<typename F>
requires requires(F fun) { fun(); }
-void chunk::with_scenery_update(std::size_t idx, F&& fun)
+void chunk::with_scenery_update(entity& s, F&& fun)
{
static_assert(std::is_convertible_v<decltype(fun()), bool> || std::is_same_v<void, decltype(fun())>);
- const auto& s = scenery_at(idx), s0 = s;
- bbox bb0; bool b0 = _bbox_for_scenery(idx, bb0);
+ // todo handle coord & offset fields
+
+ auto ch = s.coord.chunk();
+ entity_proto s0 = s;
+ bbox bb0; bool b0 = _bbox_for_scenery(s, bb0);
bool modified = true;
if constexpr(!std::is_same_v<void, std::decay_t<decltype(fun())>>)
@@ -20,10 +24,13 @@ void chunk::with_scenery_update(std::size_t idx, F&& fun)
if (!modified)
return;
+ if (s.coord.chunk() != ch) // todo
+ return;
+
if (bbox bb; !is_passability_modified())
- if (bool b = _bbox_for_scenery(idx, bb); b != b0 || bb != bb0)
+ if (bool b = _bbox_for_scenery(s, bb); b != b0 || bb != bb0)
_replace_bbox(bb0, bb, b0, b);
- if (!is_scenery_modified() && scenery::is_mesh_modified(s0, s))
+ if (!is_scenery_modified() && s.atlas->info().fps == 0 && s != s0)
mark_scenery_modified(false);
}
diff --git a/src/entity.cpp b/src/entity.cpp
new file mode 100644
index 00000000..07289291
--- /dev/null
+++ b/src/entity.cpp
@@ -0,0 +1,199 @@
+#include "entity.hpp"
+#include "world.hpp"
+#include "rotation.inl"
+#include "chunk.inl"
+#include "RTree.hpp"
+#include <algorithm>
+
+namespace floormat {
+
+bool entity_proto::operator==(const entity_proto&) const = default;
+entity_proto& entity_proto::operator=(const entity_proto&) = default;
+entity_proto::~entity_proto() noexcept = default;
+entity_proto::entity_proto() = default;
+entity_proto::entity_proto(const entity_proto&) = default;
+
+entity::entity(std::uint64_t id, struct world& w, entity_type type) noexcept :
+ id{id}, w{w}, type{type}
+{
+}
+
+entity::entity(std::uint64_t id, struct world& w, entity_type type, const entity_proto& proto) noexcept :
+ id{id}, w{w}, atlas{proto.atlas},
+ offset{proto.offset}, bbox_offset{proto.bbox_offset},
+ bbox_size{proto.bbox_size}, delta{proto.delta},
+ frame{proto.frame}, type{type}, r{proto.r}, pass{proto.pass}
+{
+ fm_assert(type == proto.type);
+}
+
+entity::~entity() noexcept
+{
+ fm_debug_assert(id);
+ if (w.is_teardown()) [[unlikely]]
+ return;
+ auto& c = chunk();
+ if (!c._teardown) [[likely]]
+ {
+ if (chunk::bbox bb; c._bbox_for_scenery(*this, bb))
+ c._remove_bbox(bb);
+ auto it = std::find_if(c._entities.cbegin(), c._entities.cend(), [id = id](const auto& x) { return x->id == id; });
+ fm_debug_assert(it != c._entities.cend());
+ c._entities.erase(it);
+ }
+ w.do_kill_entity(id);
+}
+
+std::uint32_t entity_proto::ordinal(local_coords local) const
+{
+ return entity::ordinal(local, offset);
+}
+
+std::uint32_t entity::ordinal() const
+{
+ return ordinal(coord.local(), offset);
+}
+
+std::uint32_t entity::ordinal(local_coords xy, Vector2b offset)
+{
+ constexpr auto x_size = (std::uint32_t)TILE_MAX_DIM * (std::uint32_t)iTILE_SIZE[0];
+ auto vec = Vector2ui(xy) * Vector2ui(iTILE_SIZE2) + Vector2ui(offset);
+ return vec[1] * x_size + vec[0];
+}
+
+struct chunk& entity::chunk() const
+{
+ return w[coord.chunk()];
+}
+
+auto entity::iter() const -> It
+{
+ auto& c = chunk();
+ auto it = std::find_if(c._entities.cbegin(), c._entities.cend(), [id = id](const auto& x) { return x->id == id; });
+ fm_assert(it != c._entities.cend());
+ return it;
+}
+
+bool entity::operator==(const entity_proto& o) const
+{
+ return atlas.get() == o.atlas.get() &&
+ type == o.type && frame == o.frame && r == o.r && pass == o.pass &&
+ offset == o.offset && bbox_offset == o.bbox_offset &&
+ bbox_size == o.bbox_size && delta == o.delta;
+}
+
+void entity::rotate(It, struct chunk&, rotation new_r)
+{
+ w[coord.chunk()].with_scenery_update(*this, [&]() {
+ bbox_offset = rotate_point(bbox_offset, r, new_r);
+ bbox_size = rotate_size(bbox_size, r, new_r);
+ r = new_r;
+ });
+}
+
+Pair<global_coords, Vector2b> entity::normalize_coords(global_coords coord, Vector2b cur_offset, Vector2i new_offset)
+{
+ auto tmp = Vector2i(cur_offset) + new_offset;
+ auto new_off = tmp % iTILE_SIZE2;
+ auto tile = tmp / iTILE_SIZE2;
+ return { coord + tile, Vector2b(new_off) };
+}
+
+bool entity::can_move_to(Vector2i delta, struct chunk& c)
+{
+ auto [coord_, offset_] = normalize_coords(coord, offset, delta);
+ auto& c_ = coord.chunk() == coord_.chunk() ? c : w[coord_.chunk()];
+
+ const auto center = Vector2(coord_.local())*TILE_SIZE2 + Vector2(offset_) + Vector2(bbox_offset),
+ half_bbox = Vector2(bbox_size)*.5f,
+ min = center - half_bbox, max = center + half_bbox;
+
+ bool ret = true;
+ c_.rtree()->Search(min.data(), max.data(), [&](std::uint64_t data, const auto&) {
+ auto id2 = std::bit_cast<collision_data>(data).data;
+ if (id2 != id)
+ return ret = false;
+ else
+ return true;
+ });
+ return ret;
+}
+
+void entity::move(It it, Vector2i delta, struct chunk& c)
+{
+ auto e_ = *it;
+ auto& e = *e_;
+ auto& w = e.w;
+ auto& coord = e.coord;
+ auto& offset = e.offset;
+
+ auto& es = c._entities;
+ auto [coord_, offset_] = normalize_coords(coord, offset, delta);
+
+ if (coord_ == coord && offset_ == offset)
+ return;
+
+ if (e.atlas->info().fps == 0)
+ c.mark_scenery_modified(false);
+
+ if (coord_ != coord)
+ Debug{} << "coord" << Vector2i(coord_.chunk()) << Vector2i(coord_.local());
+
+ bool same_chunk = coord_.chunk() == coord.chunk();
+ chunk::bbox bb0, bb1;
+ bool b0 = c._bbox_for_scenery(e, bb0);
+ coord = coord_; offset = offset_;
+ bool b1 = c._bbox_for_scenery(e, bb1);
+
+ if (same_chunk)
+ {
+ c._replace_bbox(bb0, bb1, b0, b1);
+ auto it_ = std::lower_bound(es.cbegin(), es.cend(), e_, [ord = e.ordinal()](const auto& a, const auto&) { return a->ordinal() < ord; });
+ if (it_ != it)
+ {
+ auto pos0 = std::distance(es.cbegin(), it), pos1 = std::distance(es.cbegin(), it_);
+ if (pos1 > pos0)
+ {
+ Debug{} << "decr";
+ pos1--;
+ }
+ else
+ Debug{} << "no decr";
+ es.erase(it);
+ [[maybe_unused]] auto size = es.size();
+ es.insert(es.cbegin() + pos1, std::move(e_));
+ }
+ }
+ else
+ {
+ auto& c2 = w[coord.chunk()];
+ if (e.atlas->info().fps == 0)
+ c2.mark_scenery_modified(false);
+ c._remove_bbox(bb0);
+ c2._add_bbox(bb1);
+ c.remove_entity(it);
+ auto it_ = std::lower_bound(c2._entities.cbegin(), c2._entities.cend(), e_,
+ [ord = e.ordinal()](const auto& a, const auto&) { return a->ordinal() < ord; });
+ c2._entities.insert(it_, std::move(e_));
+ }
+}
+
+entity::operator entity_proto() const
+{
+ entity_proto x;
+ x.atlas = atlas;
+ x.offset = offset;
+ x.bbox_offset = bbox_offset;
+ x.bbox_size = bbox_size;
+ x.delta = delta;
+ x.frame = frame;
+ x.type = type;
+ x.r = r;
+ x.pass = pass;
+ return x;
+}
+
+bool entity::can_activate(It, struct chunk&) const { return false; }
+bool entity::activate(It, struct chunk&) { return false; }
+
+} // namespace floormat
diff --git a/src/entity.hpp b/src/entity.hpp
new file mode 100644
index 00000000..18c588bd
--- /dev/null
+++ b/src/entity.hpp
@@ -0,0 +1,80 @@
+#pragma once
+#include "compat/defs.hpp"
+#include "src/global-coords.hpp"
+#include "src/rotation.hpp"
+#include "src/pass-mode.hpp"
+#include <memory>
+#include <vector>
+
+namespace floormat {
+
+template<typename T> struct entity_type_;
+struct anim_atlas;
+struct world;
+
+enum class entity_type : std::uint8_t {
+ none, character, scenery,
+};
+
+struct entity_proto
+{
+ std::shared_ptr<anim_atlas> atlas;
+ Vector2b offset, bbox_offset;
+ Vector2ub bbox_size = Vector2ub(iTILE_SIZE2);
+ std::uint16_t delta = 0, frame = 0;
+ entity_type type = entity_type::none;
+ rotation r : rotation_BITS = rotation::N;
+ pass_mode pass : pass_mode_BITS = pass_mode::see_through;
+
+ std::uint32_t ordinal(local_coords coord) const;
+ entity_proto& operator=(const entity_proto&);
+ entity_proto();
+ entity_proto(const entity_proto&);
+
+ bool operator==(const entity_proto&) const;
+ virtual ~entity_proto() noexcept;
+};
+
+struct entity
+{
+ fm_DECLARE_DELETED_COPY_ASSIGNMENT(entity);
+ using It = typename std::vector<std::shared_ptr<entity>>::const_iterator;
+
+ const std::uint64_t id = 0;
+ world& w;
+ std::shared_ptr<anim_atlas> atlas;
+ global_coords coord;
+ Vector2b offset, bbox_offset;
+ Vector2ub bbox_size;
+ std::uint16_t delta = 0, frame = 0;
+ const entity_type type;
+ rotation r : rotation_BITS = rotation::N;
+ pass_mode pass : pass_mode_BITS = pass_mode::see_through;
+
+ virtual ~entity() noexcept;
+
+ std::uint32_t ordinal() const;
+ static std::uint32_t ordinal(local_coords xy, Vector2b offset);
+ struct chunk& chunk() const;
+ It iter() const;
+
+ virtual bool operator==(const entity_proto& e0) const;
+ operator entity_proto() const;
+
+ virtual bool can_activate(It it, struct chunk& c) const;
+ virtual bool activate(It it, struct chunk& c);
+ virtual bool update(It it, struct chunk& c, float dt) = 0;
+ virtual void rotate(It it, struct chunk& c, rotation r);
+
+ static Pair<global_coords, Vector2b> normalize_coords(global_coords coord, Vector2b cur_offset, Vector2i delta);
+ [[nodiscard]] virtual bool can_move_to(Vector2i delta, struct chunk& c);
+ static void move(It it, Vector2i delta, struct chunk& c);
+
+ friend struct world;
+
+protected:
+ entity(std::uint64_t id, struct world& w, entity_type type) noexcept;
+ entity(std::uint64_t id, struct world& w, entity_type type, const entity_proto& proto) noexcept;
+};
+
+} // namespace floormat
diff --git a/src/pass-mode.hpp b/src/pass-mode.hpp
index 112aafcc..accefaae 100644
--- a/src/pass-mode.hpp
+++ b/src/pass-mode.hpp
@@ -5,5 +5,6 @@ namespace floormat {
enum class pass_mode : std::uint8_t { blocked, see_through, shoot_through, pass, };
constexpr inline std::uint8_t pass_mode_COUNT = 4;
+constexpr inline std::uint8_t pass_mode_BITS = 2;
} // namespace floormat
diff --git a/src/scenery.cpp b/src/scenery.cpp
index e2ebdb49..cf58a8de 100644
--- a/src/scenery.cpp
+++ b/src/scenery.cpp
@@ -2,91 +2,30 @@
#include "anim-atlas.hpp"
#include "chunk.hpp"
#include "compat/assert.hpp"
-#include "rotation.inl"
-#include "chunk.inl"
+#include "world.hpp"
#include <algorithm>
namespace floormat {
-scenery_proto::scenery_proto() noexcept = default;
-scenery_proto::scenery_proto(const std::shared_ptr<anim_atlas>& atlas, const scenery& frame) noexcept :
- atlas{atlas}, frame{frame}
-{}
+scenery_proto::scenery_proto() = default;
-scenery_proto& scenery_proto::operator=(const scenery_proto&) noexcept = default;
-scenery_proto::scenery_proto(const scenery_proto&) noexcept = default;
-scenery_proto::operator bool() const noexcept { return atlas != nullptr; }
+scenery_proto& scenery_proto::operator=(const scenery_proto&) = default;
+scenery_proto::scenery_proto(const scenery_proto&) = default;
+scenery_proto::~scenery_proto() noexcept = default;
+scenery_proto::operator bool() const { return atlas != nullptr; }
-scenery_ref::scenery_ref(struct chunk& c, std::size_t i) noexcept :
- atlas{c.scenery_atlas_at(i)}, frame{c.scenery_at(i)},
- c{&c}, idx{std::uint8_t(i)}
-{}
-scenery_ref::scenery_ref(const scenery_ref&) noexcept = default;
-scenery_ref::scenery_ref(scenery_ref&&) noexcept = default;
-struct chunk& scenery_ref::chunk() noexcept { return *c; }
-std::uint8_t scenery_ref::index() const noexcept { return idx; }
-
-scenery_ref& scenery_ref::operator=(const scenery_proto& proto) noexcept
-{
- atlas = proto.atlas;
- frame = proto.frame;
- return *this;
-}
-
-scenery_ref::operator scenery_proto() const noexcept { return { atlas, frame }; }
-scenery_ref::operator bool() const noexcept { return atlas != nullptr; }
-
-scenery::scenery(generic_tag_t, const anim_atlas& atlas, rotation r, frame_t frame,
- pass_mode passability, bool active, bool interactive,
- Vector2b offset, Vector2b bbox_offset, Vector2ub bbox_size) :
- frame{frame},
- offset{offset},
- bbox_offset{rotate_point(bbox_offset, atlas.first_rotation(), r)},
- bbox_size{rotate_size(bbox_size, atlas.first_rotation(), r)},
- r{r}, type{scenery_type::generic},
- passability{passability},
- active{active}, interactive{interactive}
-{
- fm_assert(r < rotation_COUNT);
- fm_assert(frame < atlas.group(r).frames.size());
-}
-
-scenery::scenery(door_tag_t, const anim_atlas& atlas, rotation r, bool is_open,
- Vector2b offset, Vector2b bbox_offset, Vector2ub bbox_size) :
- frame{frame_t(is_open ? 0 : atlas.group(r).frames.size()-1)},
- offset{offset},
- bbox_offset{rotate_point(bbox_offset, atlas.first_rotation(), r)},
- bbox_size{rotate_size(bbox_size, atlas.first_rotation(), r)},
- r{r}, type{scenery_type::door},
- passability{is_open ? pass_mode::pass : pass_mode::blocked},
- interactive{true}
-{
- fm_assert(r < rotation_COUNT);
- fm_assert(atlas.group(r).frames.size() >= 2);
-}
-
-void scenery_ref::rotate(rotation new_r)
-{
- c->with_scenery_update(idx, [&]() {
- auto& s = frame;
- s.bbox_offset = rotate_point(s.bbox_offset, s.r, new_r);
- s.bbox_size = rotate_size(s.bbox_size, s.r, new_r);
- s.r = new_r;
- });
-}
-
-bool scenery_ref::can_activate() const noexcept
+bool scenery::can_activate(It, struct chunk&) const
{
- return atlas && frame.interactive;
+ return atlas && interactive;
}
-bool scenery_ref::update(float dt)
+bool scenery::update(It, struct chunk&, float dt)
{
- auto& s = frame;
+ auto& s = *this;
if (!s.active)
return false;
- switch (s.type)
+ switch (s.sc_type)
{
default:
case scenery_type::none:
@@ -109,25 +48,25 @@ bool scenery_ref::update(float dt)
const int fr = s.frame + dir*n;
s.active = fr > 0 && fr < nframes-1;
if (fr <= 0)
- s.passability = pass_mode::pass;
+ s.pass = pass_mode::pass;
else if (fr >= nframes-1)
- s.passability = pass_mode::blocked;
+ s.pass = pass_mode::blocked;
else
- s.passability = pass_mode::see_through;
- s.frame = (scenery::frame_t)std::clamp(fr, 0, nframes-1);
+ s.pass = pass_mode::see_through;
+ s.frame = (std::uint16_t)std::clamp(fr, 0, nframes-1);
if (!s.active)
s.delta = s.closing = 0;
return true;
}
}
-bool scenery_ref::activate()
+bool scenery::activate(It, struct chunk&)
{
- auto& s = frame;
- if (!*this || s.active)
+ auto& s = *this;
+ if (s.active)
return false;
- switch (s.type)
+ switch (s.sc_type)
{
default:
case scenery_type::none:
@@ -143,22 +82,29 @@ bool scenery_ref::activate()
return false;
}
-bool scenery::is_mesh_modified(const scenery& s0, const scenery& s)
+bool scenery::operator==(const entity_proto& e0) const
{
- if (s.interactive != s0.interactive || s.type != s0.type)
- return true;
- if (!s.interactive)
- return s.r != s0.r || s.offset != s0.offset || s0.frame != s.frame || s.type != s0.type;
- return false;
+ if (!entity::operator==(e0))
+ return false;
+
+ const auto& s0 = static_cast<const scenery_proto&>(e0);
+ return sc_type == s0.sc_type && active == s0.active &&
+ closing == s0.closing && interactive == s0.interactive;
}
-#ifdef __GNUG__
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wfloat-equal"
-#endif
-bool scenery::operator==(const scenery&) const noexcept = default;
-#ifdef __GNUG__
-#pragma GCC diagnostic pop
-#endif
+scenery::scenery(std::uint64_t id, struct world& w, entity_type type, const scenery_proto& proto) :
+ entity{id, w, type}, sc_type{proto.sc_type}, active{proto.active},
+ closing{proto.closing}, interactive{proto.interactive}
+{
+ fm_assert(type == proto.type);
+ atlas = proto.atlas;
+ offset = proto.offset;
+ bbox_offset = proto.bbox_offset;
+ bbox_size = proto.bbox_size;
+ delta = proto.delta;
+ frame = proto.frame;
+ r = proto.r;
+ pass = proto.pass;
+}
} // namespace floormat
diff --git a/src/scenery.hpp b/src/scenery.hpp
index 9d9ad6e6..6f8afadd 100644
--- a/src/scenery.hpp
+++ b/src/scenery.hpp
@@ -2,6 +2,7 @@
#include "pass-mode.hpp"
#include "tile-defs.hpp"
#include "rotation.hpp"
+#include "entity.hpp"
#include <cstdint>
#include <memory>
#include <type_traits>
@@ -12,104 +13,43 @@ namespace floormat {
struct chunk;
struct anim_atlas;
+struct world;
enum class scenery_type : std::uint8_t {
none, generic, door,
};
-struct scenery final
+struct scenery_proto : entity_proto
{
- struct none_tag_t final {};
- struct generic_tag_t final {};
- struct door_tag_t final {};
-
- static constexpr auto none = none_tag_t{};
- static constexpr auto generic = generic_tag_t{};
- static constexpr auto door = door_tag_t{};
-
- using frame_t = std::uint16_t;
-
- std::uint16_t delta = 0;
- frame_t frame = 0;
- Vector2b offset, bbox_offset;
- Vector2ub bbox_size{usTILE_SIZE2/2};
- rotation r : 3 = rotation::N;
- scenery_type type : 3 = scenery_type::none;
- pass_mode passability : 2 = pass_mode::shoot_through;
+ scenery_type sc_type : 3 = scenery_type::none;
std::uint8_t active : 1 = false;
std::uint8_t closing : 1 = false;
std::uint8_t interactive : 1 = false;
- constexpr scenery() noexcept;
- constexpr scenery(none_tag_t) noexcept;
- scenery(generic_tag_t, const anim_atlas& atlas, rotation r, frame_t frame,
- pass_mode passability, bool active, bool interactive,
- Vector2b offset, Vector2b bbox_offset, Vector2ub bbox_size);
- scenery(door_tag_t, const anim_atlas& atlas, rotation r, bool is_open,
- Vector2b offset, Vector2b bbox_offset, Vector2ub bbox_size);
-
- static bool is_mesh_modified(const scenery& s1, const scenery& s2);
-
- bool operator==(const scenery&) const noexcept;
+ scenery_proto();
+ scenery_proto(const scenery_proto&);
+ ~scenery_proto() noexcept override;
+ scenery_proto& operator=(const scenery_proto&);
+ operator bool() const;
};
-constexpr scenery::scenery() noexcept : scenery{scenery::none_tag_t{}} {}
-constexpr scenery::scenery(none_tag_t) noexcept {}
-
-struct scenery_proto final
+struct scenery final : entity
{
- std::shared_ptr<anim_atlas> atlas;
- scenery frame;
-
- scenery_proto() noexcept;
- scenery_proto(const std::shared_ptr<anim_atlas>& atlas, const scenery& frame) noexcept;
- scenery_proto& operator=(const scenery_proto&) noexcept;
- scenery_proto(const scenery_proto&) noexcept;
-
- template<typename... Ts>
- scenery_proto(scenery::generic_tag_t, const std::shared_ptr<anim_atlas>& atlas, Ts&&... args) :
- atlas{atlas}, frame{scenery::generic, *atlas, std::forward<Ts>(args)...}
- {}
-
- template<typename... Ts>
- scenery_proto(scenery::door_tag_t, const std::shared_ptr<anim_atlas>& atlas, Ts&&... args) :
- atlas{atlas}, frame{scenery::door, *atlas, std::forward<Ts>(args)...}
- {}
-
- operator bool() const noexcept;
-};
-
-struct scenery_ref final {
- scenery_ref(struct chunk& c, std::size_t i) noexcept;
- scenery_ref(const scenery_ref&) noexcept;
- scenery_ref(scenery_ref&&) noexcept;
- scenery_ref& operator=(const scenery_ref&) = delete;
- scenery_ref& operator=(const scenery_proto& proto) noexcept;
-
- operator scenery_proto() const noexcept;
- operator bool() const noexcept;
-
- struct chunk& chunk() noexcept;
- std::uint8_t index() const noexcept;
-
- template<std::size_t N> std::tuple_element_t<N, scenery_ref>& get() & { if constexpr(N == 0) return atlas; else return frame; }
- template<std::size_t N> std::tuple_element_t<N, scenery_ref>& get() && { if constexpr(N == 0) return atlas; else return frame; }
-
- std::shared_ptr<anim_atlas>& atlas;
- scenery& frame;
+ scenery_type sc_type : 3 = scenery_type::none;
+ std::uint8_t active : 1 = false;
+ std::uint8_t closing : 1 = false;
+ std::uint8_t interactive : 1 = false;
- bool can_activate() const noexcept;
- bool activate();
- bool update(float dt);
- void rotate(rotation r);
+ bool can_activate(It it, struct chunk& c) const override;
+ bool activate(It it, struct chunk& c) override;
+ bool update(It it, struct chunk& c, float dt) override;
+ bool operator==(const entity_proto& p) const override;
private:
- struct chunk* c;
- std::uint8_t idx;
+ friend struct world;
+ scenery(std::uint64_t id, struct world& w, entity_type type, const scenery_proto& proto);
};
-} // namespace floormat
+template<> struct entity_type_<scenery> : std::integral_constant<entity_type, entity_type::scenery> {};
-template<> struct std::tuple_size<floormat::scenery_ref> final : std::integral_constant<std::size_t, 2> {};
-template<> struct std::tuple_element<0, floormat::scenery_ref> final { using type = std::shared_ptr<floormat::anim_atlas>; };
-template<> struct std::tuple_element<1, floormat::scenery_ref> final { using type = floormat::scenery; };
+} // namespace floormat
diff --git a/src/tile.cpp b/src/tile.cpp
index 25b413a9..75ef71e7 100644
--- a/src/tile.cpp
+++ b/src/tile.cpp
@@ -6,42 +6,36 @@ namespace floormat {
bool operator==(const tile_proto& a, const tile_proto& b) noexcept {
return a.ground() == b.ground() &&
a.wall_north() == b.wall_north() &&
- a.wall_west() == b.wall_west() &&
- a.scenery() == b.scenery();
+ a.wall_west() == b.wall_west();
};
tile_image_proto tile_proto::ground() const noexcept { return { ground_atlas, ground_variant }; }
tile_image_proto tile_proto::wall_north() const noexcept { return { wall_north_atlas, wall_north_variant }; }
tile_image_proto tile_proto::wall_west() const noexcept { return { wall_west_atlas, wall_west_variant }; }
-scenery_proto tile_proto::scenery() const noexcept { return { scenery_atlas, scenery_frame }; }
tile_ref::tile_ref(struct chunk& c, std::uint8_t i) noexcept : _chunk{&c}, i{i} {}
std::shared_ptr<tile_atlas> tile_ref::ground_atlas() noexcept { return _chunk->_ground_atlases[i]; }
std::shared_ptr<tile_atlas> tile_ref::wall_north_atlas() noexcept { return _chunk->_wall_atlases[i*2+0]; }
std::shared_ptr<tile_atlas> tile_ref::wall_west_atlas() noexcept { return _chunk->_wall_atlases[i*2+1]; }
-std::shared_ptr<anim_atlas> tile_ref::scenery_atlas() noexcept { return _chunk->_scenery_atlases[i]; }
std::shared_ptr<const tile_atlas> tile_ref::ground_atlas() const noexcept { return _chunk->_ground_atlases[i]; }
std::shared_ptr<const tile_atlas> tile_ref::wall_north_atlas() const noexcept { return _chunk->_wall_atlases[i*2+0]; }
std::shared_ptr<const tile_atlas> tile_ref::wall_west_atlas() const noexcept { return _chunk->_wall_atlases[i*2+1]; }
-std::shared_ptr<const anim_atlas> tile_ref::scenery_atlas() const noexcept { return _chunk->_scenery_atlases[i]; }
tile_image_ref tile_ref::ground() noexcept { return {_chunk->_ground_atlases[i], _chunk->_ground_variants[i] }; }
tile_image_ref tile_ref::wall_north() noexcept { return {_chunk->_wall_atlases[i*2+0], _chunk->_wall_variants[i*2+0] }; }
tile_image_ref tile_ref::wall_west() noexcept { return {_chunk->_wall_atlases[i*2+1], _chunk->_wall_variants[i*2+1] }; }
-scenery_ref tile_ref::scenery() noexcept { return { *_chunk, i }; }
tile_image_proto tile_ref::ground() const noexcept { return { _chunk->_ground_atlases[i], _chunk->_ground_variants[i] }; }
tile_image_proto tile_ref::wall_north() const noexcept { return { _chunk->_wall_atlases[i*2+0], _chunk->_wall_variants[i*2+0] }; }
tile_image_proto tile_ref::wall_west() const noexcept { return { _chunk->_wall_atlases[i*2+1], _chunk->_wall_variants[i*2+1] }; }
-scenery_proto tile_ref::scenery() const noexcept { return { _chunk->_scenery_atlases[i], _chunk->_scenery_variants[i] }; }
tile_ref::operator tile_proto() const noexcept
{
return {
- _chunk->_ground_atlases[i], _chunk->_wall_atlases[i*2+0], _chunk->_wall_atlases[i*2+1], _chunk->_scenery_atlases[i],
- _chunk->_ground_variants[i], _chunk->_wall_variants[i*2+0], _chunk->_wall_variants[i*2+1], _chunk->_scenery_variants[i],
+ _chunk->_ground_atlases[i], _chunk->_wall_atlases[i*2+0], _chunk->_wall_atlases[i*2+1],
+ _chunk->_ground_variants[i], _chunk->_wall_variants[i*2+0], _chunk->_wall_variants[i*2+1],
};
}
@@ -52,8 +46,7 @@ bool operator==(const tile_ref& a, const tile_ref& b) noexcept
else
return a.ground() == b.ground() &&
a.wall_north() == b.wall_north() &&
- a.wall_west() == b.wall_west() &&
- a.scenery() == b.scenery();
+ a.wall_west() == b.wall_west();
}
} // namespace floormat
diff --git a/src/tile.hpp b/src/tile.hpp
index 7a6604dd..5eed7633 100644
--- a/src/tile.hpp
+++ b/src/tile.hpp
@@ -1,6 +1,5 @@
#pragma once
#include "tile-image.hpp"
-#include "scenery.hpp"
namespace floormat {
@@ -10,14 +9,11 @@ struct anim_atlas;
struct tile_proto final
{
std::shared_ptr<tile_atlas> ground_atlas, wall_north_atlas, wall_west_atlas;
- std::shared_ptr<anim_atlas> scenery_atlas;
variant_t ground_variant = 0, wall_north_variant = 0, wall_west_variant = 0;
- struct scenery scenery_frame;
tile_image_proto ground() const noexcept;
tile_image_proto wall_north() const noexcept;
tile_image_proto wall_west() const noexcept;
- scenery_proto scenery() const noexcept;
friend bool operator==(const tile_proto& a, const tile_proto& b) noexcept;
};
@@ -29,22 +25,18 @@ struct tile_ref final
tile_image_ref ground() noexcept;
tile_image_ref wall_north() noexcept;
tile_image_ref wall_west() noexcept;
- scenery_ref scenery() noexcept;
tile_image_proto ground() const noexcept;
tile_image_proto wall_north() const noexcept;
tile_image_proto wall_west() const noexcept;
- scenery_proto scenery() const noexcept;
std::shared_ptr<tile_atlas> ground_atlas() noexcept;
std::shared_ptr<tile_atlas> wall_north_atlas() noexcept;
std::shared_ptr<tile_atlas> wall_west_atlas() noexcept;
- std::shared_ptr<anim_atlas> scenery_atlas() noexcept;
std::shared_ptr<const tile_atlas> ground_atlas() const noexcept;
std::shared_ptr<const tile_atlas> wall_north_atlas() const noexcept;
std::shared_ptr<const tile_atlas> wall_west_atlas() const noexcept;
- std::shared_ptr<const anim_atlas> scenery_atlas() const noexcept;
explicit operator tile_proto() const noexcept;
diff --git a/src/world.cpp b/src/world.cpp
index 1fcee1d8..ea767422 100644
--- a/src/world.cpp
+++ b/src/world.cpp
@@ -1,5 +1,6 @@
#include "world.hpp"
#include "chunk.hpp"
+#include "entity.hpp"
namespace floormat {
@@ -7,6 +8,14 @@ world::world() : world{initial_capacity}
{
}
+world::~world() noexcept
+{
+ _teardown = true;
+ _last_chunk = {};
+ _chunks.clear();
+ _entities.clear();
+}
+
world::world(std::size_t capacity) : _chunks{capacity, hasher}
{
_chunks.max_load_factor(max_load_factor);
@@ -69,4 +78,24 @@ void world::collect(bool force)
fm_debug("world: collected %zu/%zu chunks", len, len0);
}
+static constexpr std::uint64_t min_id = 1u << 16;
+std::uint64_t world::entity_counter = min_id;
+
+void world::do_make_entity(const std::shared_ptr<entity>& e, global_coords pos)
+{
+ fm_debug_assert(e->id > min_id && &e->w == this);
+ fm_assert(Vector2ui(e->bbox_size).product() > 0);
+ fm_assert(e->type != entity_type::none);
+ e->coord = pos;
+ _entities[e->id] = e;
+ operator[](pos.chunk()).add_entity(e);
+}
+
+void world::do_kill_entity(std::uint64_t id)
+{
+ fm_debug_assert(id > min_id);
+ auto cnt = _entities.erase(id);
+ fm_debug_assert(cnt > 0);
+}
+
} // namespace floormat
diff --git a/src/world.hpp b/src/world.hpp
index 36703fd3..6781ee3c 100644
--- a/src/world.hpp
+++ b/src/world.hpp
@@ -9,6 +9,9 @@
namespace floormat {
+struct entity;
+template<typename T> struct entity_type_;
+
struct world final
{
private:
@@ -24,13 +27,24 @@ private:
return int_hash((std::size_t)c.y << 16 | (std::size_t)c.x);
};
std::unordered_map<chunk_coords, chunk, decltype(hasher)> _chunks;
+ std::unordered_map<std::uint64_t, std::weak_ptr<entity>> _entities;
std::size_t _last_collection = 0;
std::size_t _collect_every = 64;
+ bool _teardown : 1 = false;
+
+ static std::uint64_t entity_counter;
+
explicit world(std::size_t capacity);
+ void do_make_entity(const std::shared_ptr<entity>& e, global_coords pos);
+ void do_kill_entity(std::uint64_t id);
+
+ friend struct entity;
+
public:
explicit world();
+ ~world() noexcept;
struct pair final { chunk& c; tile_ref t; }; // NOLINT
@@ -52,6 +66,18 @@ public:
void set_collect_threshold(std::size_t value) { _collect_every = value; }
std::size_t collect_threshold() const noexcept { return _collect_every; }
+ template<typename T, typename... Xs>
+ requires requires { T{std::uint64_t(), std::declval<world&>(), entity_type(), std::declval<Xs>()...}; }
+ std::shared_ptr<T> make_entity(global_coords pos, Xs&&... xs)
+ {
+ static_assert(std::is_base_of_v<entity, T>);
+ auto ret = std::shared_ptr<T>(new T{++entity_counter, *this, entity_type_<T>::value, std::forward<Xs>(xs)...});
+ do_make_entity(std::static_pointer_cast<entity>(ret), pos);
+ return ret;
+ }
+
+ bool is_teardown() const { return _teardown; }
+
fm_DECLARE_DEPRECATED_COPY_ASSIGNMENT(world);
fm_DECLARE_DEFAULT_MOVE_ASSIGNMENT_(world);
};
diff --git a/test/app.hpp b/test/app.hpp
index 894401c8..2152e2e5 100644
--- a/test/app.hpp
+++ b/test/app.hpp
@@ -1,4 +1,5 @@
#pragma once
+#include "src/world.hpp"
#include <Magnum/Magnum.h>
#ifdef __APPLE__
@@ -22,13 +23,15 @@ struct test_app final : private FM_APPLICATION
explicit test_app(const Arguments& arguments);
~test_app();
int exec() override;
- static chunk make_test_chunk();
+ chunk& make_test_chunk(chunk_coords ch);
static void test_json();
static void test_tile_iter();
static void test_const_math();
- static void test_serializer();
+ void test_serializer();
static void test_entity();
static void test_loader();
static void test_bitmask();
+
+ world w;
};
} // namespace floormat
diff --git a/test/entity.cpp b/test/entity.cpp
index a7810863..c1489723 100644
--- a/test/entity.cpp
+++ b/test/entity.cpp
@@ -96,6 +96,7 @@ constexpr bool test_visitor()
}
void test_fun2() {
+ using entity = Entity<TestAccessors>;
static constexpr auto read_fn = [](const TestAccessors& x) constexpr { return x.bar(); };
static constexpr auto write_fn = [](TestAccessors& x, int value) constexpr { x.set_bar(value); };
constexpr auto read_bar = fu2::function_view<int(const TestAccessors&) const>{read_fn};
@@ -156,6 +157,7 @@ void test_type_name()
[[maybe_unused]] constexpr void test_null_writer()
{
+ using entity = Entity<TestAccessors>;
constexpr auto foo = entity::type<int>::field{"foo"_s, &TestAccessors::foo, nullptr};
static_assert(foo.writer == nullptr);
static_assert(!foo.can_write);
@@ -164,6 +166,7 @@ void test_type_name()
void test_predicate()
{
+ using entity = Entity<TestAccessors>;
constexpr TestAccessors x{0, 0, 0};
constexpr auto foo = entity::type<int>::field{"foo"_s, &TestAccessors::foo, &TestAccessors::foo,
[](const TestAccessors&) { return field_status::hidden; }};
@@ -199,6 +202,7 @@ constexpr bool test_names()
constexpr void test_constraints()
{
+ using entity = Entity<TestAccessors>;
constexpr auto x = TestAccessors{};
constexpr auto foo = entity::type<int>::field {
"foo"_s, &TestAccessors::foo, &TestAccessors::foo,
@@ -226,6 +230,7 @@ constexpr void test_constraints()
void test_erased_constraints()
{
+ using entity = Entity<TestAccessors>;
static constexpr auto foo = entity::type<int>::field{
"foo"_s, &TestAccessors::foo, &TestAccessors::foo,
constantly(constraints::max_length{42}),
diff --git a/test/main.cpp b/test/main.cpp
index 0d7ddf84..05ecd7fe 100644
--- a/test/main.cpp
+++ b/test/main.cpp
@@ -24,10 +24,10 @@ int test_app::exec()
test_json();
test_tile_iter();
test_const_math();
- test_serializer();
test_entity();
test_loader();
test_bitmask();
+ test_serializer();
return 0;
}
diff --git a/test/serializer.cpp b/test/serializer.cpp
index 71b1a3f1..c74f4ddf 100644
--- a/test/serializer.cpp
+++ b/test/serializer.cpp
@@ -9,40 +9,50 @@ namespace floormat {
namespace Path = Corrade::Utility::Path;
-chunk test_app::make_test_chunk()
+chunk& test_app::make_test_chunk(chunk_coords ch)
{
+ chunk& c = w[ch];
+ c.mark_modified();
auto metal1 = loader.tile_atlas("metal1", {2, 2}, pass_mode::pass),
metal2 = loader.tile_atlas("metal2", {2, 2}, pass_mode::blocked),
tiles = loader.tile_atlas("tiles", {8, 5}, pass_mode::pass);
constexpr auto N = TILE_MAX_DIM;
- chunk c;
for (auto [x, k, pt] : c)
x.ground() = { tiles, variant_t(k % tiles->num_tiles()) };
auto door = loader.scenery("door1"),
table = loader.scenery("table1"),
control_panel = loader.scenery("control panel (wall) 1");
- control_panel.frame.r = rotation::W;
+ control_panel.r = rotation::W;
constexpr auto K = N/2;
c[{K, K }].wall_north() = { metal1, 0 };
c[{K, K }].wall_west() = { metal2, 0 };
c[{K, K+1}].wall_north() = { metal1, 0 };
c[{K+1, K }].wall_west() = { metal2, 0 };
- c[{K+3, K+1}].scenery() = door;
- c[{ 3, 4 }].scenery() = table;
- c[{K, K+1}].scenery() = control_panel;
- c[{K, K+1}].scenery().frame.bbox_size = {99, 88};
- c.mark_modified();
+ w.make_entity<scenery>({ch, {K+3, K+1}}, door);
+ w.make_entity<scenery>({ch, {3, 4}}, table);
+ w.make_entity<scenery>({ch, {K, K+1}}, control_panel);
return c;
}
static bool chunks_equal(const chunk& a, const chunk& b)
{
+ if (a.entities().size() != b.entities().size())
+ return false;
+
for (auto i = 0_uz; i < TILE_COUNT; i++)
{
const auto &a1 = a[i], &b1 = b[i];
if (a1 != b1)
return false;
}
+
+ for (auto i = 0_uz; i < a.entities().size(); i++)
+ {
+ auto &ae = *a.entities()[i], &be = *b.entities()[i];
+ if (entity_proto(ae) != entity_proto(be))
+ return false;
+ }
+
return true;
}
@@ -51,13 +61,12 @@ void test_app::test_serializer()
constexpr auto filename = "../test/test-serializer1.dat";
if (Path::exists(filename))
Path::remove(filename);
- world w;
const chunk_coords coord{1, 1};
- w[coord] = make_test_chunk();
+ auto& c = make_test_chunk(coord);
w.serialize(filename);
auto w2 = world::deserialize(filename);
- auto &c1 = w[coord], &c2 = w2[coord];
- fm_assert(chunks_equal(c1, c2));
+ auto& c2 = w2[coord];
+ fm_assert(chunks_equal(c, c2));
}
} // namespace floormat
diff --git a/userconfig-sthalik@Windows-Clang.cmake b/userconfig-sthalik@Windows-Clang.cmake
index 7c75c204..2413704a 100644
--- a/userconfig-sthalik@Windows-Clang.cmake
+++ b/userconfig-sthalik@Windows-Clang.cmake
@@ -89,6 +89,7 @@ function(fm-userconfig-src)
-Wno-exit-time-destructors
-Wno-implicit-int-float-conversion
-Wno-shadow-field-in-constructor
+ -Wno-shadow-field
-Wno-shadow
-Wno-ctad-maybe-unsupported
-Wno-documentation-unknown-command