From 68694a9c9d7d27d3b79c7b96bb67f56db2f75c45 Mon Sep 17 00:00:00 2001 From: untodesu Date: Thu, 11 Sep 2025 18:18:08 +0500 Subject: Metadata voxels! --- game/shared/game_voxels.cc | 141 +++++++++------------ game/shared/game_voxels.hh | 29 +++-- game/shared/world/chunk.cc | 25 ++-- game/shared/world/chunk.hh | 9 +- game/shared/world/dimension.cc | 30 +++-- game/shared/world/dimension.hh | 11 +- game/shared/world/feature.cc | 7 +- game/shared/world/feature.hh | 7 +- game/shared/world/ray_dda.cc | 2 +- game/shared/world/ray_dda.hh | 3 +- game/shared/world/voxel.cc | 120 ++++++++++++++++++ game/shared/world/voxel.hh | 236 ++++++++++++++++++++++++++++++++++++ game/shared/world/voxel_registry.cc | 194 +++++------------------------ game/shared/world/voxel_registry.hh | 137 ++------------------- game/shared/world/voxels/generic.hh | 27 +++++ 15 files changed, 555 insertions(+), 423 deletions(-) create mode 100644 game/shared/world/voxel.cc create mode 100644 game/shared/world/voxel.hh create mode 100644 game/shared/world/voxels/generic.hh (limited to 'game') diff --git a/game/shared/game_voxels.cc b/game/shared/game_voxels.cc index 51de1a1..59d035d 100644 --- a/game/shared/game_voxels.cc +++ b/game/shared/game_voxels.cc @@ -2,41 +2,71 @@ #include "shared/game_voxels.hh" +#include "shared/world/voxels/generic.hh" + #include "shared/world/voxel_registry.hh" -voxel_id game_voxels::cobblestone = NULL_VOXEL_ID; -voxel_id game_voxels::dirt = NULL_VOXEL_ID; -voxel_id game_voxels::grass = NULL_VOXEL_ID; -voxel_id game_voxels::stone = NULL_VOXEL_ID; -voxel_id game_voxels::vtest = NULL_VOXEL_ID; -voxel_id game_voxels::vtest_ck = NULL_VOXEL_ID; -voxel_id game_voxels::oak_leaves = NULL_VOXEL_ID; -voxel_id game_voxels::oak_planks = NULL_VOXEL_ID; -voxel_id game_voxels::oak_log = NULL_VOXEL_ID; -voxel_id game_voxels::glass = NULL_VOXEL_ID; -voxel_id game_voxels::slime = NULL_VOXEL_ID; -voxel_id game_voxels::mud = NULL_VOXEL_ID; +const world::Voxel* game_voxels::cobblestone = nullptr; +const world::Voxel* game_voxels::dirt = nullptr; +const world::Voxel* game_voxels::grass = nullptr; +const world::Voxel* game_voxels::stone = nullptr; +const world::Voxel* game_voxels::vtest = nullptr; +const world::Voxel* game_voxels::vtest_ck = nullptr; +const world::Voxel* game_voxels::oak_leaves = nullptr; +const world::Voxel* game_voxels::oak_planks = nullptr; +const world::Voxel* game_voxels::oak_log = nullptr; +const world::Voxel* game_voxels::glass = nullptr; +const world::Voxel* game_voxels::slime = nullptr; +const world::Voxel* game_voxels::mud = nullptr; void game_voxels::populate(void) { - // Stone; the backbone of the generated world - game_voxels::stone = - world::voxel_registry::construct("stone", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/stone_01.png") - .add_texture_default("textures/voxel/stone_02.png") - .add_texture_default("textures/voxel/stone_03.png") - .add_texture_default("textures/voxel/stone_04.png") - .set_surface(world::voxel_surface::STONE) - .build(); + using namespace world; + using namespace world::voxels; + using world::voxel_registry::register_voxel; + + stone = register_voxel(GenericCube("stone", VRENDER_OPAQUE, false, VMAT_STONE, VTOUCH_SOLID, glm::fvec3(0.0f, 0.0f, 0.0f), + "textures/voxel/stone_01.png", "textures/voxel/stone_02.png", "textures/voxel/stone_03.png", "textures/voxel/stone_04.png")); + + cobblestone = register_voxel(GenericCube("cobblestone", VRENDER_OPAQUE, false, VMAT_STONE, VTOUCH_SOLID, glm::fvec3(0.0f, 0.0f, 0.0f), + "textures/voxel/cobblestone_01.png", "textures/voxel/cobblestone_02.png")); + + vtest = register_voxel(GenericCube("vtest", VRENDER_OPAQUE, true, VMAT_DEFAULT, VTOUCH_SOLID, glm::fvec3(0.0f, 0.0f, 0.0f), + "textures/voxel/vtest_F1.png", "textures/voxel/vtest_F2.png", "textures/voxel/vtest_F3.png", "textures/voxel/vtest_F4.png")); + + vtest_ck = register_voxel(GenericCube("vtest_ck", VRENDER_OPAQUE, false, VMAT_DEFAULT, VTOUCH_SOLID, glm::fvec3(0.0f, 0.0f, 0.0f), + "textures/voxel/chromakey.png")); + + oak_leaves = register_voxel(GenericCube("oak_leaves", VRENDER_BLEND, false, VMAT_GRASS, VTOUCH_SOLID, glm::fvec3(0.0f, 0.0f, 0.0f), + "textures/voxel/oak_leaves.png")); - // Cobblestone; should drop when a stone is broken, might also be present in surface features - game_voxels::cobblestone = - world::voxel_registry::construct("cobblestone", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/cobblestone_01.png") - .add_texture_default("textures/voxel/cobblestone_02.png") - .set_surface(world::voxel_surface::STONE) + oak_planks = register_voxel(GenericCube("oak_planks", VRENDER_OPAQUE, false, VMAT_WOOD, VTOUCH_SOLID, glm::fvec3(0.0f, 0.0f, 0.0f), + "textures/voxel/oak_planks_01.png", "textures/voxel/oak_planks_02.png")); + + glass = register_voxel(GenericCube("glass", VRENDER_BLEND, false, VMAT_GLASS, VTOUCH_SOLID, glm::fvec3(0.0f, 0.0f, 0.0f), + "textures/voxel/glass_01.png")); + + slime = register_voxel(GenericCube("slime", VRENDER_BLEND, false, VMAT_SLOSH, VTOUCH_BOUNCE, glm::fvec3(0.00f, 0.60f, 0.00f), + "textures/voxel/slime_01.png")); + +#if 0 + + // Oak logs; greenery. TODO: add trees as surface features + game_voxels::oak_log = + world::voxel_registry::construct("oak_log", world::voxel_type::CUBE, false, false) + .add_texture_default("textures/voxel/oak_wood_01.png") + .add_texture_default("textures/voxel/oak_wood_02.png") + .add_texture(world::voxel_face::CUBE_BOTTOM, "textures/voxel/oak_wood_top.png") + .add_texture(world::voxel_face::CUBE_TOP, "textures/voxel/oak_wood_top.png") + .set_surface(world::voxel_surface::WOOD) .build(); + // Glass; blend rendering test + game_voxels::glass = world::voxel_registry::construct("glass", world::voxel_type::CUBE, false, true) + .add_texture_default("textures/voxel/glass_01.png") + .set_surface(world::voxel_surface::GLASS) + .build(); + // Dirt with a grass layer on top; the top layer of plains biome game_voxels::grass = world::voxel_registry::construct("grass", world::voxel_type::CUBE, false, false) @@ -60,60 +90,5 @@ void game_voxels::populate(void) .add_texture_default("textures/voxel/dirt_04.png") .set_surface(world::voxel_surface::DIRT) .build(); - - // VTest; a test voxel to ensure animations work - game_voxels::vtest = world::voxel_registry::construct("vtest", world::voxel_type::CUBE, true, false) - .add_texture_default("textures/voxel/vtest_F1.png") - .add_texture_default("textures/voxel/vtest_F2.png") - .add_texture_default("textures/voxel/vtest_F3.png") - .add_texture_default("textures/voxel/vtest_F4.png") - .build(); - - // VTest-CK; a pure blue chromakey I used to make the game's logo - game_voxels::vtest_ck = world::voxel_registry::construct("vtest_ck", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/chromakey.png") - .build(); - - // Oak leaves; greenery. TODO: add trees as surface features - game_voxels::oak_leaves = world::voxel_registry::construct("oak_leaves", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/oak_leaves.png") - .set_surface(world::voxel_surface::GRASS) - .build(); - - // Oak planks; the thing that comes out of oak logs - game_voxels::oak_planks = world::voxel_registry::construct("oak_planks", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/oak_planks_01.png") - .add_texture_default("textures/voxel/oak_planks_02.png") - .set_surface(world::voxel_surface::WOOD) - .build(); - - // Oak logs; greenery. TODO: add trees as surface features - game_voxels::oak_log = - world::voxel_registry::construct("oak_log", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/oak_wood_01.png") - .add_texture_default("textures/voxel/oak_wood_02.png") - .add_texture(world::voxel_face::CUBE_BOTTOM, "textures/voxel/oak_wood_top.png") - .add_texture(world::voxel_face::CUBE_TOP, "textures/voxel/oak_wood_top.png") - .set_surface(world::voxel_surface::WOOD) - .build(); - - // Glass; blend rendering test - game_voxels::glass = world::voxel_registry::construct("glass", world::voxel_type::CUBE, false, true) - .add_texture_default("textures/voxel/glass_01.png") - .set_surface(world::voxel_surface::GLASS) - .build(); - - // Slime; it's bouncy! - game_voxels::slime = world::voxel_registry::construct("slime", world::voxel_type::CUBE, false, true) - .set_touch(world::voxel_touch::BOUNCE, glm::fvec3(0.00f, 0.60f, 0.00f)) - .add_texture_default("textures/voxel/slime_01.png") - .build(); - - // Mud; you sink in it - game_voxels::mud = world::voxel_registry::construct("mud", world::voxel_type::CUBE, false, false) - .set_touch(world::voxel_touch::SINK, glm::fvec3(0.50f, 0.75f, 0.50f)) - .add_texture_default("textures/voxel/mud_01.png") - .add_texture_default("textures/voxel/mud_02.png") - .set_surface(world::voxel_surface::DIRT) - .build(); +#endif } diff --git a/game/shared/game_voxels.hh b/game/shared/game_voxels.hh index 21102d0..7b24ec4 100644 --- a/game/shared/game_voxels.hh +++ b/game/shared/game_voxels.hh @@ -2,22 +2,25 @@ #define SHARED_GAME_VOXELS 1 #pragma once -#include "shared/types.hh" +namespace world +{ +class Voxel; +} // namespace world namespace game_voxels { -extern voxel_id cobblestone; -extern voxel_id dirt; -extern voxel_id grass; -extern voxel_id stone; -extern voxel_id vtest; -extern voxel_id vtest_ck; -extern voxel_id oak_leaves; -extern voxel_id oak_planks; -extern voxel_id oak_log; -extern voxel_id glass; -extern voxel_id slime; -extern voxel_id mud; +extern const world::Voxel* cobblestone; +extern const world::Voxel* dirt; +extern const world::Voxel* grass; +extern const world::Voxel* stone; +extern const world::Voxel* vtest; +extern const world::Voxel* vtest_ck; +extern const world::Voxel* oak_leaves; +extern const world::Voxel* oak_planks; +extern const world::Voxel* oak_log; +extern const world::Voxel* glass; +extern const world::Voxel* slime; +extern const world::Voxel* mud; } // namespace game_voxels namespace game_voxels diff --git a/game/shared/world/chunk.cc b/game/shared/world/chunk.cc index e59b68d..b1b19a6 100644 --- a/game/shared/world/chunk.cc +++ b/game/shared/world/chunk.cc @@ -2,6 +2,8 @@ #include "shared/world/chunk.hh" +#include "shared/world/voxel_registry.hh" + #include "shared/coord.hh" world::Chunk::Chunk(entt::entity entity, Dimension* dimension) @@ -12,30 +14,35 @@ world::Chunk::Chunk(entt::entity entity, Dimension* dimension) m_biome = BIOME_VOID; } -voxel_id world::Chunk::get_voxel(const local_pos& lpos) const +const world::Voxel* world::Chunk::get_voxel(const local_pos& lpos) const { return get_voxel(coord::to_index(lpos)); } -voxel_id world::Chunk::get_voxel(const std::size_t index) const +const world::Voxel* world::Chunk::get_voxel(const std::size_t index) const { if(index >= CHUNK_VOLUME) { - return NULL_VOXEL_ID; - } - else { - return m_voxels[index]; + return nullptr; } + + return voxel_registry::find(m_voxels[index]); } -void world::Chunk::set_voxel(voxel_id voxel, const local_pos& lpos) +void world::Chunk::set_voxel(const Voxel* voxel, const local_pos& lpos) { set_voxel(voxel, coord::to_index(lpos)); } -void world::Chunk::set_voxel(voxel_id voxel, const std::size_t index) +void world::Chunk::set_voxel(const Voxel* voxel, const std::size_t index) { if(index < CHUNK_VOLUME) { - m_voxels[index] = voxel; + if(voxel == nullptr) { + m_voxels[index] = NULL_VOXEL_ID; + return; + } + + m_voxels[index] = voxel->get_id(); + return; } } diff --git a/game/shared/world/chunk.hh b/game/shared/world/chunk.hh index c5bba12..4a1e557 100644 --- a/game/shared/world/chunk.hh +++ b/game/shared/world/chunk.hh @@ -9,6 +9,7 @@ constexpr static unsigned int BIOME_VOID = 0U; namespace world { class Dimension; +class Voxel; } // namespace world namespace world @@ -18,11 +19,11 @@ public: explicit Chunk(entt::entity entity, Dimension* dimension); virtual ~Chunk(void) = default; - voxel_id get_voxel(const local_pos& lpos) const; - voxel_id get_voxel(const std::size_t index) const; + const Voxel* get_voxel(const local_pos& lpos) const; + const Voxel* get_voxel(const std::size_t index) const; - void set_voxel(voxel_id voxel, const local_pos& lpos); - void set_voxel(voxel_id voxel, const std::size_t index); + void set_voxel(const Voxel* voxel, const local_pos& lpos); + void set_voxel(const Voxel* voxel, const std::size_t index); const VoxelStorage& get_voxels(void) const; void set_voxels(const VoxelStorage& voxels); diff --git a/game/shared/world/dimension.cc b/game/shared/world/dimension.cc index dd28449..31a19af 100644 --- a/game/shared/world/dimension.cc +++ b/game/shared/world/dimension.cc @@ -3,6 +3,7 @@ #include "shared/world/dimension.hh" #include "shared/world/chunk.hh" +#include "shared/world/voxel_registry.hh" #include "shared/coord.hh" #include "shared/globals.hh" @@ -107,7 +108,7 @@ void world::Dimension::remove_chunk(Chunk* chunk) } } -voxel_id world::Dimension::get_voxel(const voxel_pos& vpos) const +const world::Voxel* world::Dimension::get_voxel(const voxel_pos& vpos) const { auto cpos = coord::to_chunk(vpos); auto lpos = coord::to_local(vpos); @@ -115,12 +116,11 @@ voxel_id world::Dimension::get_voxel(const voxel_pos& vpos) const if(auto chunk = find_chunk(cpos)) { return chunk->get_voxel(lpos); } - else { - return NULL_VOXEL_ID; - } + + return nullptr; } -voxel_id world::Dimension::get_voxel(const chunk_pos& cpos, const local_pos& lpos) const +const world::Voxel* world::Dimension::get_voxel(const chunk_pos& cpos, const local_pos& lpos) const { // This allows accessing get_voxel with negative // local coordinates that usually would result in an @@ -128,19 +128,33 @@ voxel_id world::Dimension::get_voxel(const chunk_pos& cpos, const local_pos& lpo return get_voxel(coord::to_voxel(cpos, lpos)); } -bool world::Dimension::set_voxel(voxel_id voxel, const voxel_pos& vpos) +bool world::Dimension::set_voxel(const Voxel* voxel, const voxel_pos& vpos) { auto cpos = coord::to_chunk(vpos); auto lpos = coord::to_local(vpos); if(auto chunk = find_chunk(cpos)) { + if(auto old_voxel = chunk->get_voxel(lpos)) { + if(old_voxel != voxel) { + // Notify the old voxel that it is + // being replaced with a different voxel + old_voxel->on_remove(this, vpos); + } + } + chunk->set_voxel(voxel, lpos); + if(voxel) { + // If we're not placing air, notify the + // new voxel that it has been placed + voxel->on_place(this, vpos); + } + VoxelSetEvent event; event.dimension = this; + event.voxel = voxel; event.cpos = cpos; event.lpos = lpos; - event.voxel = voxel; event.chunk = chunk; globals::dispatcher.trigger(event); @@ -151,7 +165,7 @@ bool world::Dimension::set_voxel(voxel_id voxel, const voxel_pos& vpos) return false; } -bool world::Dimension::set_voxel(voxel_id voxel, const chunk_pos& cpos, const local_pos& lpos) +bool world::Dimension::set_voxel(const Voxel* voxel, const chunk_pos& cpos, const local_pos& lpos) { // This allows accessing set_voxel with negative // local coordinates that usually would result in an diff --git a/game/shared/world/dimension.hh b/game/shared/world/dimension.hh index bf9bfe1..58e0765 100644 --- a/game/shared/world/dimension.hh +++ b/game/shared/world/dimension.hh @@ -11,6 +11,7 @@ class ConfigMap; namespace world { class Chunk; +class Voxel; class VoxelStorage; } // namespace world @@ -40,11 +41,11 @@ public: void remove_chunk(Chunk* chunk); public: - voxel_id get_voxel(const voxel_pos& vpos) const; - voxel_id get_voxel(const chunk_pos& cpos, const local_pos& lpos) const; + const Voxel* get_voxel(const voxel_pos& vpos) const; + const Voxel* get_voxel(const chunk_pos& cpos, const local_pos& lpos) const; - bool set_voxel(voxel_id voxel, const voxel_pos& vpos); - bool set_voxel(voxel_id voxel, const chunk_pos& cpos, const local_pos& lpos); + bool set_voxel(const Voxel* voxel, const voxel_pos& vpos); + bool set_voxel(const Voxel* voxel, const chunk_pos& cpos, const local_pos& lpos); public: virtual void init(io::ConfigMap& config); @@ -92,9 +93,9 @@ struct ChunkUpdateEvent final { struct VoxelSetEvent final { Dimension* dimension; + const Voxel* voxel; chunk_pos cpos; local_pos lpos; - voxel_id voxel; Chunk* chunk; }; } // namespace world diff --git a/game/shared/world/feature.cc b/game/shared/world/feature.cc index 4212043..cbf660e 100644 --- a/game/shared/world/feature.cc +++ b/game/shared/world/feature.cc @@ -4,7 +4,6 @@ #include "shared/world/chunk.hh" #include "shared/world/dimension.hh" -#include "shared/world/voxel_storage.hh" #include "shared/coord.hh" @@ -30,7 +29,7 @@ void world::Feature::place(const voxel_pos& vpos, Dimension* dimension) const } } -void world::Feature::place(const voxel_pos& vpos, const chunk_pos& cpos, VoxelStorage& voxels) const +void world::Feature::place(const voxel_pos& vpos, const chunk_pos& cpos, Chunk& chunk) const { for(const auto [rpos, voxel, overwrite] : (*this)) { auto it_vpos = vpos + rpos; @@ -40,14 +39,14 @@ void world::Feature::place(const voxel_pos& vpos, const chunk_pos& cpos, VoxelSt auto it_lpos = coord::to_local(it_vpos); auto it_index = coord::to_index(it_lpos); - if(voxels[it_index] && !overwrite) { + if(chunk.get_voxel(it_index) && !overwrite) { // There is something in the way // and the called intentionally requested // we do not force feature to overwrite voxels continue; } - voxels[it_index] = voxel; + chunk.set_voxel(voxel, it_index); } } } diff --git a/game/shared/world/feature.hh b/game/shared/world/feature.hh index 20a809b..09b913e 100644 --- a/game/shared/world/feature.hh +++ b/game/shared/world/feature.hh @@ -4,19 +4,20 @@ namespace world { +class Chunk; class Dimension; -class VoxelStorage; +class Voxel; } // namespace world namespace world { -class Feature final : public std::vector> { +class Feature final : public std::vector> { public: Feature(void) = default; virtual ~Feature(void) = default; public: void place(const voxel_pos& vpos, Dimension* dimension) const; - void place(const voxel_pos& vpos, const chunk_pos& cpos, VoxelStorage& voxels) const; + void place(const voxel_pos& vpos, const chunk_pos& cpos, Chunk& chunk) const; }; } // namespace world diff --git a/game/shared/world/ray_dda.cc b/game/shared/world/ray_dda.cc index b337e9d..7bb1068 100644 --- a/game/shared/world/ray_dda.cc +++ b/game/shared/world/ray_dda.cc @@ -71,7 +71,7 @@ void world::RayDDA::reset(const world::Dimension& dimension, const chunk_pos& st reset(&dimension, start_chunk, start_fpos, direction); } -voxel_id world::RayDDA::step(void) +const world::Voxel* world::RayDDA::step(void) { if(side_dist.x < side_dist.z) { if(side_dist.x < side_dist.y) { diff --git a/game/shared/world/ray_dda.hh b/game/shared/world/ray_dda.hh index 54bd48a..3071be2 100644 --- a/game/shared/world/ray_dda.hh +++ b/game/shared/world/ray_dda.hh @@ -7,6 +7,7 @@ namespace world { class Dimension; +class Voxel; } // namespace world namespace world @@ -20,7 +21,7 @@ public: void reset(const Dimension* dimension, const chunk_pos& start_chunk, const glm::fvec3& start_fpos, const glm::fvec3& direction); void reset(const Dimension& dimension, const chunk_pos& start_chunk, const glm::fvec3& start_fpos, const glm::fvec3& direction); - voxel_id step(void); + const Voxel* step(void); public: const Dimension* dimension; diff --git a/game/shared/world/voxel.cc b/game/shared/world/voxel.cc new file mode 100644 index 0000000..d51b5a1 --- /dev/null +++ b/game/shared/world/voxel.cc @@ -0,0 +1,120 @@ +#include "shared/pch.hh" + +#include "shared/world/voxel.hh" + +#include "core/math/crc64.hh" + +#include "shared/world/dimension.hh" + +void world::Voxel::on_place(Dimension* dimension, const voxel_pos& vpos) const +{ + // empty +} + +void world::Voxel::on_remove(Dimension* dimension, const voxel_pos& vpos) const +{ + // empty +} + +void world::Voxel::on_tick(Dimension* dimension, const voxel_pos& vpos) const +{ + // empty +} + +void world::Voxel::set_id(voxel_id id) noexcept +{ + m_id = id; +} + +std::size_t world::Voxel::get_random_texture_index(VoxelFace face, const voxel_pos& vpos) const +{ + const auto& textures = get_face_textures(face); + + assert(textures.size()); + + std::uint64_t hash = 0U; + hash = math::crc64(&vpos.x, sizeof(vpos.x), hash); + hash = math::crc64(&vpos.y, sizeof(vpos.y), hash); + hash = math::crc64(&vpos.z, sizeof(vpos.z), hash); + + return static_cast(hash % textures.size()); +} + +void world::Voxel::set_face_cache(VoxelFace face, std::size_t offset, std::size_t plane) +{ + assert(face < m_cached_face_offsets.size()); + assert(face < m_cached_face_planes.size()); + + m_cached_face_offsets[face] = offset; + m_cached_face_planes[face] = plane; +} + +std::uint64_t world::Voxel::calculate_checksum(std::uint64_t combine) const +{ + combine = math::crc64(m_name.data(), m_name.size(), combine); + combine += static_cast(m_shape); + combine += static_cast(m_render_mode); + return combine; +} + +std::shared_ptr world::Voxel::clone(void) const +{ + return std::make_shared(*this); +} + +void world::Voxel::set_name(std::string_view name) noexcept +{ + assert(name.size()); + + m_name = name; +} + +void world::Voxel::set_render_mode(VoxelRender mode) noexcept +{ + m_render_mode = mode; +} + +void world::Voxel::set_shape(VoxelShape shape) noexcept +{ + m_shape = shape; +} + +void world::Voxel::set_animated(bool animated) noexcept +{ + m_animated = animated; +} + +void world::Voxel::set_touch_type(VoxelTouch type) noexcept +{ + m_touch_type = type; +} + +void world::Voxel::set_touch_values(const glm::fvec3& values) noexcept +{ + m_touch_values = values; +} + +void world::Voxel::set_surface_material(VoxelMaterial material) noexcept +{ + m_surface_material = material; +} + +void world::Voxel::set_collision(const math::AABBf& box) noexcept +{ + m_collision = box; +} + +void world::Voxel::add_default_texture(std::string_view path) +{ + assert(path.size()); + + m_default_textures.emplace_back(path); +} + +void world::Voxel::add_face_texture(VoxelFace face, std::string_view path) +{ + assert(face < m_face_textures.size()); + assert(path.size()); + + m_face_textures[face].emplace_back(path); +} diff --git a/game/shared/world/voxel.hh b/game/shared/world/voxel.hh new file mode 100644 index 0000000..0bd83b7 --- /dev/null +++ b/game/shared/world/voxel.hh @@ -0,0 +1,236 @@ +#pragma once + +#include "core/math/aabb.hh" + +#include "shared/types.hh" + +namespace world +{ +class Dimension; +} // namespace world + +namespace world +{ +enum VoxelRender : unsigned short { + VRENDER_NONE = 0U, ///< The voxel is not rendered at all + VRENDER_OPAQUE, ///< The voxel is fully opaque + VRENDER_BLEND, ///< The voxel is blended (e.g. water, glass) +}; + +enum VoxelShape : unsigned short { + VSHAPE_CUBE = 0U, ///< Full cube shape + VSHAPE_CROSS, ///< TODO: Cross shape + VSHAPE_MODEL, ///< TODO: Custom model shape +}; + +enum VoxelFace : unsigned short { + VFACE_NORTH = 0U, ///< Positive Z face + VFACE_SOUTH, ///< Negative Z face + VFACE_EAST, ///< Positive X face + VFACE_WEST, ///< Negative X face + VFACE_TOP, ///< Positive Y face + VFACE_BOTTOM, ///< Negative Y face + VFACE_CROSS_NWSE, ///< Diagonal cross face northwest-southeast + VFACE_CROSS_NESW, ///< Diagonal cross face northeast-southwest + VFACE_COUNT +}; + +enum VoxelTouch : unsigned short { + VTOUCH_NONE = 0xFFFFU, + VTOUCH_SOLID = 0U, ///< The entity is stopped in its tracks + VTOUCH_BOUNCE, ///< The entity bounces back with some energy loss + VTOUCH_SINK, ///< The entity phases/sinks through the voxel +}; + +enum VoxelMaterial : unsigned short { + VMAT_DEFAULT = 0U, + VMAT_STONE, + VMAT_DIRT, + VMAT_GLASS, + VMAT_GRASS, + VMAT_GRAVEL, + VMAT_METAL, + VMAT_SAND, + VMAT_WOOD, + VMAT_SLOSH, + VMAT_COUNT +}; + +enum VoxelVisBits : unsigned short { + VVIS_NORTH = 1U << VFACE_NORTH, ///< Positive Z + VVIS_SOUTH = 1U << VFACE_SOUTH, ///< Negative Z + VVIS_EAST = 1U << VFACE_EAST, ///< Positive X + VVIS_WEST = 1U << VFACE_WEST, ///< Negative X + VVIS_UP = 1U << VFACE_TOP, ///< Positive Y + VVIS_DOWN = 1U << VFACE_BOTTOM, ///< Negative Y +}; +} // namespace world + +namespace world +{ +class Voxel { +public: + /// Called when the voxel is placed in the world + /// @param dimension The dimension the voxel is placed in + /// @param vpos The absolute voxel position where the voxel is placed + virtual void on_place(Dimension* dimension, const voxel_pos& vpos) const; + + /// Called when the voxel is removed from the world + /// @param dimension The dimension the voxel is removed from + /// @param vpos The absolute voxel position where the voxel is removed + virtual void on_remove(Dimension* dimension, const voxel_pos& vpos) const; + + /// Called when the voxel is ticked by something + /// @param dimension The dimension the voxel is ticked in + /// @param vpos The absolute voxel position where the voxel is ticked + virtual void on_tick(Dimension* dimension, const voxel_pos& vpos) const; + + constexpr std::string_view get_name(void) const noexcept; + constexpr voxel_id get_id(void) const noexcept; + void set_id(voxel_id id) noexcept; + + constexpr VoxelRender get_render_mode(void) const noexcept; + constexpr VoxelShape get_shape(void) const noexcept; + constexpr bool is_animated(void) const noexcept; + + constexpr VoxelTouch get_touch_type(void) const noexcept; + constexpr const glm::fvec3& get_touch_values(void) const noexcept; + constexpr VoxelMaterial get_surface_material(void) const noexcept; + + constexpr const math::AABBf& get_collision(void) const noexcept; + + constexpr const std::vector& get_default_textures(void) const noexcept; + constexpr const std::vector& get_face_textures(VoxelFace face) const noexcept; + constexpr std::size_t get_cached_face_offset(VoxelFace face) const noexcept; + constexpr std::size_t get_cached_face_plane(VoxelFace face) const noexcept; + + /// Non-model voxel shapes support texture variation based on the + /// voxel position on the world; this method handles the math behind this + /// @param face The face of the voxel to get the texture index for + /// @param vpos The absolute voxel position to get the texture index for + /// @return The index of the texture to use for the given face at the given position + /// @remarks On client-side: plane[get_cached_face_plane][get_cached_face_offset + thisFunctionResult] + std::size_t get_random_texture_index(VoxelFace face, const voxel_pos& vpos) const; + + /// Assign cached plane index and plane offset for a given face + /// @param face The face to assign the cache for + /// @param offset The offset to assign to the face + /// @param plane The plane index to assign to the face + void set_face_cache(VoxelFace face, std::size_t offset, std::size_t plane); + + /// Calculate a checksum for the voxel's properties + /// @param combine An optional initial checksum to combine with + /// @return The calculated checksum + std::uint64_t calculate_checksum(std::uint64_t combine = 0U) const; + + /// Produce a copy of itself as a shared pointer; the voxel registry + /// does not change the owner of a voxel instance it registers + virtual std::shared_ptr clone(void) const; + +protected: + void set_name(std::string_view name) noexcept; + + void set_render_mode(VoxelRender mode) noexcept; + void set_shape(VoxelShape shape) noexcept; + void set_animated(bool animated) noexcept; + + void set_touch_type(VoxelTouch type) noexcept; + void set_touch_values(const glm::fvec3& values) noexcept; + void set_surface_material(VoxelMaterial material) noexcept; + + void set_collision(const math::AABBf& box) noexcept; + + void add_default_texture(std::string_view path); + void add_face_texture(VoxelFace face, std::string_view path); + +private: + std::string m_name; + voxel_id m_id { NULL_VOXEL_ID }; + + VoxelRender m_render_mode { VRENDER_OPAQUE }; + VoxelShape m_shape { VSHAPE_CUBE }; + bool m_animated { false }; + + VoxelTouch m_touch_type { VTOUCH_SOLID }; + glm::fvec3 m_touch_values { 0.0f }; + VoxelMaterial m_surface_material { VMAT_DEFAULT }; + + math::AABBf m_collision { { 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f } }; + + std::vector m_default_textures; + std::array, VFACE_COUNT> m_face_textures; + std::array m_cached_face_offsets; + std::array m_cached_face_planes; +}; +} // namespace world + +constexpr std::string_view world::Voxel::get_name(void) const noexcept +{ + return m_name; +} + +constexpr voxel_id world::Voxel::get_id(void) const noexcept +{ + return m_id; +} + +constexpr world::VoxelRender world::Voxel::get_render_mode(void) const noexcept +{ + return m_render_mode; +} + +constexpr world::VoxelShape world::Voxel::get_shape(void) const noexcept +{ + return m_shape; +} + +constexpr bool world::Voxel::is_animated(void) const noexcept +{ + return m_animated; +} + +constexpr world::VoxelTouch world::Voxel::get_touch_type(void) const noexcept +{ + return m_touch_type; +} + +constexpr const glm::fvec3& world::Voxel::get_touch_values(void) const noexcept +{ + return m_touch_values; +} + +constexpr world::VoxelMaterial world::Voxel::get_surface_material(void) const noexcept +{ + return m_surface_material; +} + +constexpr const math::AABBf& world::Voxel::get_collision(void) const noexcept +{ + return m_collision; +} + +constexpr const std::vector& world::Voxel::get_default_textures(void) const noexcept +{ + return m_default_textures; +} + +constexpr const std::vector& world::Voxel::get_face_textures(VoxelFace face) const noexcept +{ + assert(face <= m_face_textures.size()); + + return m_face_textures[face]; +} + +constexpr std::size_t world::Voxel::get_cached_face_offset(VoxelFace face) const noexcept +{ + assert(face <= m_cached_face_offsets.size()); + + return m_cached_face_offsets[face]; +} + +constexpr std::size_t world::Voxel::get_cached_face_plane(VoxelFace face) const noexcept +{ + assert(face <= m_cached_face_planes.size()); + + return m_cached_face_planes[face]; +} diff --git a/game/shared/world/voxel_registry.cc b/game/shared/world/voxel_registry.cc index b44845e..4c2f360 100644 --- a/game/shared/world/voxel_registry.cc +++ b/game/shared/world/voxel_registry.cc @@ -2,195 +2,65 @@ #include "shared/world/voxel_registry.hh" -#include "core/math/crc64.hh" +static std::uint64_t registry_checksum = 0U; +emhash8::HashMap world::voxel_registry::names; +std::vector> world::voxel_registry::voxels; -std::unordered_map world::voxel_registry::builders = {}; -std::unordered_map world::voxel_registry::names = {}; -std::vector> world::voxel_registry::voxels = {}; - -world::VoxelInfoBuilder::VoxelInfoBuilder(std::string_view name, voxel_type type, bool animated, bool blending) -{ - prototype.name = name; - prototype.type = type; - prototype.animated = animated; - prototype.blending = blending; - - switch(type) { - case voxel_type::CUBE: - prototype.textures.resize(static_cast(voxel_face::CUBE__NR)); - break; - case voxel_type::CROSS: - prototype.textures.resize(static_cast(voxel_face::CROSS__NR)); - break; - case voxel_type::MODEL: - // Custom models should use a different texture - // resource management that is not a voxel atlas - // TODO: actually implement custom models lol - prototype.textures.resize(0); - break; - default: - // Something really bad should happen if we end up here. - // The outside code would static_cast an int to VoxelType - // and possibly fuck a lot of things up to cause this - spdlog::critical("voxel_registry: {}: unknown voxel type {}", name, static_cast(type)); - std::terminate(); - } - - // Physics properties - prototype.touch_type = voxel_touch::SOLID; - prototype.touch_values = glm::fvec3(0.0f, 0.0f, 0.0f); - prototype.surface = voxel_surface::DEFAULT; - - // Things set in future by item_def - prototype.item_pick = NULL_ITEM_ID; -} - -world::VoxelInfoBuilder& world::VoxelInfoBuilder::add_texture_default(std::string_view texture) +static void recalculate_checksum(void) { - default_texture.paths.push_back(std::string(texture)); - return *this; -} + registry_checksum = 0U; -world::VoxelInfoBuilder& world::VoxelInfoBuilder::add_texture(voxel_face face, std::string_view texture) -{ - const auto index = static_cast(face); - prototype.textures[index].paths.push_back(std::string(texture)); - return *this; -} - -world::VoxelInfoBuilder& world::VoxelInfoBuilder::set_touch(voxel_touch type, const glm::fvec3& values) -{ - prototype.touch_type = type; - prototype.touch_values = values; - return *this; + for(const auto& voxel : world::voxel_registry::voxels) { + registry_checksum = voxel->calculate_checksum(registry_checksum); + } } -world::VoxelInfoBuilder& world::VoxelInfoBuilder::set_surface(voxel_surface surface) +world::Voxel* world::voxel_registry::register_voxel(const Voxel& voxel_template) { - prototype.surface = surface; - return *this; -} + assert(voxel_template.get_name().size()); + assert(nullptr == find(voxel_template.get_name())); -voxel_id world::VoxelInfoBuilder::build(void) const -{ - const auto it = world::voxel_registry::names.find(prototype.name); + const auto id = static_cast(voxels.size()); - if(it != world::voxel_registry::names.cend()) { - spdlog::warn("voxel_registry: cannot build {}: name already present", prototype.name); - return it->second; - } + auto voxel = voxel_template.clone(); + voxel->set_id(id); - std::size_t state_count; - - switch(prototype.type) { - case voxel_type::CUBE: - case voxel_type::CROSS: - case voxel_type::MODEL: - state_count = 1; - break; - default: - // Something really bad should happen if we end up here. - // The outside code would static_cast an int to VoxelType - // and possibly fuck a lot of things up to cause this - spdlog::critical("voxel_registry: {}: unknown voxel type {}", prototype.name, static_cast(prototype.type)); - std::terminate(); - } + names.emplace(std::string(voxel_template.get_name()), id); + voxels.push_back(std::move(voxel)); - if((world::voxel_registry::voxels.size() + state_count) >= MAX_VOXEL_ID) { - spdlog::critical("voxel_registry: voxel registry overflow"); - std::terminate(); - } + recalculate_checksum(); - auto new_info = std::make_shared(); - new_info->name = prototype.name; - new_info->type = prototype.type; - new_info->animated = prototype.animated; - new_info->blending = prototype.blending; - - new_info->textures.resize(prototype.textures.size()); - - for(std::size_t i = 0; i < prototype.textures.size(); ++i) { - if(prototype.textures[i].paths.empty()) { - new_info->textures[i].paths = default_texture.paths; - new_info->textures[i].cached_offset = SIZE_MAX; - new_info->textures[i].cached_plane = SIZE_MAX; - } - else { - new_info->textures[i].paths = prototype.textures[i].paths; - new_info->textures[i].cached_offset = SIZE_MAX; - new_info->textures[i].cached_plane = SIZE_MAX; - } - } - - // Physics properties - new_info->touch_type = prototype.touch_type; - new_info->touch_values = prototype.touch_values; - new_info->surface = prototype.surface; - - // Things set in future by item_def - new_info->item_pick = prototype.item_pick; - - // Base voxel identifier offset - new_info->base_voxel = world::voxel_registry::voxels.size() + 1; - - for(std::size_t i = 0; i < state_count; ++i) - world::voxel_registry::voxels.push_back(new_info); - world::voxel_registry::names.insert_or_assign(new_info->name, new_info->base_voxel); - - return new_info->base_voxel; -} - -world::VoxelInfoBuilder& world::voxel_registry::construct(std::string_view name, voxel_type type, bool animated, bool blending) -{ - const auto it = world::voxel_registry::builders.find(std::string(name)); - - if(it != world::voxel_registry::builders.cend()) { - return it->second; - } - else { - return world::voxel_registry::builders.emplace(std::string(name), VoxelInfoBuilder(name, type, animated, blending)).first->second; - } + return voxels.back().get(); } -world::VoxelInfo* world::voxel_registry::find(std::string_view name) +world::Voxel* world::voxel_registry::find(std::string_view name) { - const auto it = world::voxel_registry::names.find(std::string(name)); + const auto it = names.find(name); - if(it != world::voxel_registry::names.cend()) { - return world::voxel_registry::find(it->second); - } - else { + if(it == names.end()) { return nullptr; } + + return voxels[it->second].get(); } -world::VoxelInfo* world::voxel_registry::find(const voxel_id voxel) +world::Voxel* world::voxel_registry::find(voxel_id id) { - if((voxel != NULL_VOXEL_ID) && (voxel <= world::voxel_registry::voxels.size())) { - return world::voxel_registry::voxels[voxel - 1].get(); - } - else { + if(id >= voxels.size()) { return nullptr; } + + return voxels[id].get(); } void world::voxel_registry::purge(void) { - world::voxel_registry::builders.clear(); - world::voxel_registry::names.clear(); - world::voxel_registry::voxels.clear(); + registry_checksum = 0U; + voxels.clear(); + names.clear(); } -std::uint64_t world::voxel_registry::calculate_checksum(void) +std::uint64_t world::voxel_registry::get_checksum(void) { - std::uint64_t result = 0; - - for(const std::shared_ptr& info : world::voxel_registry::voxels) { - result = math::crc64(info->name, result); - result += static_cast(info->type); - result += static_cast(info->base_voxel); - result += info->blending ? 256 : 1; - } - - return result; + return registry_checksum; } diff --git a/game/shared/world/voxel_registry.hh b/game/shared/world/voxel_registry.hh index a75f59f..b9d9a34 100644 --- a/game/shared/world/voxel_registry.hh +++ b/game/shared/world/voxel_registry.hh @@ -1,141 +1,18 @@ #pragma once -#include "shared/types.hh" - -namespace world -{ -enum class voxel_face : unsigned short { - CUBE_NORTH = 0x0000, - CUBE_SOUTH = 0x0001, - CUBE_EAST = 0x0002, - CUBE_WEST = 0x0003, - CUBE_TOP = 0x0004, - CUBE_BOTTOM = 0x0005, - CUBE__NR = 0x0006, - - CROSS_NESW = 0x0000, - CROSS_NWSE = 0x0001, - CROSS__NR = 0x0002, -}; - -enum class voxel_type : unsigned short { - CUBE = 0x0000, - CROSS = 0x0001, // TODO - MODEL = 0x0002, // TODO -}; - -enum class voxel_facing : unsigned short { - NORTH = 0x0000, - SOUTH = 0x0001, - EAST = 0x0002, - WEST = 0x0003, - UP = 0x0004, - DOWN = 0x0005, - NESW = 0x0006, - NWSE = 0x0007, -}; - -enum class voxel_touch : unsigned short { - SOLID = 0x0000, // The entity is stopped in its tracks - BOUNCE = 0x0001, // The entity bounces back with some energy loss - SINK = 0x0002, // The entity phases/sinks through the voxel - NOTHING = 0xFFFF, -}; - -enum class voxel_surface : unsigned short { - DEFAULT = 0x0000, - STONE = 0x0001, - DIRT = 0x0002, - GLASS = 0x0003, - GRASS = 0x0004, - GRAVEL = 0x0005, - METAL = 0x0006, - SAND = 0x0007, - WOOD = 0x0008, - SLOSH = 0x0009, - COUNT = 0x000A, - UNKNOWN = 0xFFFF, -}; - -using voxel_vis = unsigned short; -constexpr static voxel_vis VIS_NORTH = 1 << static_cast(voxel_facing::NORTH); -constexpr static voxel_vis VIS_SOUTH = 1 << static_cast(voxel_facing::SOUTH); -constexpr static voxel_vis VIS_EAST = 1 << static_cast(voxel_facing::EAST); -constexpr static voxel_vis VIS_WEST = 1 << static_cast(voxel_facing::WEST); -constexpr static voxel_vis VIS_UP = 1 << static_cast(voxel_facing::UP); -constexpr static voxel_vis VIS_DOWN = 1 << static_cast(voxel_facing::DOWN); -} // namespace world - -namespace world -{ -struct VoxelTexture final { - std::vector paths; - std::size_t cached_offset; // client-side only - std::size_t cached_plane; // client-side only -}; - -struct VoxelInfo final { - std::string name; - voxel_type type; - bool animated; - bool blending; - - std::vector textures; - - // Physics properties go here - // TODO: player_move friction modifiers - // that would make the voxel very sticky or - // very slippery to walk on - voxel_touch touch_type; - glm::fvec3 touch_values; - voxel_surface surface; - - // Some voxel types might occupy multiple voxel_id - // values that reference to the exact same VoxelInfo - // structure; the actual numeric state is figured out by - // subtracting base_voxel from the checking voxel_id - voxel_id base_voxel; - - // These will be set by item_registry - // and by default set to NULL_ITEM_ID - item_id item_pick; -}; -} // namespace world - -namespace world -{ -class VoxelInfoBuilder final { -public: - explicit VoxelInfoBuilder(std::string_view name, voxel_type type, bool animated, bool blending); - virtual ~VoxelInfoBuilder(void) = default; - -public: - VoxelInfoBuilder& add_texture_default(std::string_view texture); - VoxelInfoBuilder& add_texture(voxel_face face, std::string_view texture); - VoxelInfoBuilder& set_touch(voxel_touch type, const glm::fvec3& values); - VoxelInfoBuilder& set_surface(voxel_surface surface); - -public: - voxel_id build(void) const; - -private: - VoxelTexture default_texture; - VoxelInfo prototype; -}; -} // namespace world +#include "shared/world/voxel.hh" namespace world::voxel_registry { -extern std::unordered_map builders; -extern std::unordered_map names; -extern std::vector> voxels; +extern emhash8::HashMap names; +extern std::vector> voxels; } // namespace world::voxel_registry namespace world::voxel_registry { -VoxelInfoBuilder& construct(std::string_view name, voxel_type type, bool animated, bool blending); -VoxelInfo* find(std::string_view name); -VoxelInfo* find(const voxel_id voxel); +Voxel* register_voxel(const Voxel& voxel_template); +Voxel* find(std::string_view name); +Voxel* find(voxel_id id); } // namespace world::voxel_registry namespace world::voxel_registry @@ -145,5 +22,5 @@ void purge(void); namespace world::voxel_registry { -std::uint64_t calculate_checksum(void); +std::uint64_t get_checksum(void); } // namespace world::voxel_registry diff --git a/game/shared/world/voxels/generic.hh b/game/shared/world/voxels/generic.hh new file mode 100644 index 0000000..a661792 --- /dev/null +++ b/game/shared/world/voxels/generic.hh @@ -0,0 +1,27 @@ +#pragma once + +#include "shared/world/voxel.hh" + +namespace world::voxels +{ +class GenericCube final : public Voxel { +public: + template + requires(std::is_convertible_v && ...) + explicit GenericCube(std::string_view name, VoxelRender render_mode, bool animated, VoxelMaterial surface_material, VoxelTouch touch, + const glm::fvec3& touch_values, TexturesT&&... textures) noexcept + { + set_name(name); + + set_shape(VoxelShape::CUBE); + set_render_mode(render_mode); + set_animated(animated); + + set_surface_material(surface_material); + set_touch_values(touch_values); + set_touch_type(touch); + + add_texture_default(std::forward(textures)...); + } +}; +} // namespace world::voxels -- cgit From e9076f22fe2a49d1cd8933e54b7b00c5dd943269 Mon Sep 17 00:00:00 2001 From: untodesu Date: Fri, 12 Sep 2025 13:33:52 +0500 Subject: It compiles --- game/client/experiments.cc | 6 +- game/client/game.cc | 36 +++++-- game/client/session.cc | 10 +- game/client/world/chunk_mesher.cc | 192 +++++++++++++++-------------------- game/client/world/chunk_quad.hh | 6 +- game/client/world/player_target.cc | 15 +-- game/client/world/player_target.hh | 3 +- game/client/world/voxel_sounds.cc | 53 +++++----- game/client/world/voxel_sounds.hh | 6 +- game/server/receive.cc | 5 +- game/server/sessions.cc | 4 +- game/server/world/overworld.cc | 12 ++- game/shared/entity/collision.cc | 36 ++++--- game/shared/entity/grounded.hh | 2 +- game/shared/game_voxels.cc | 193 ++++++++++++++++++++++-------------- game/shared/game_voxels.hh | 1 - game/shared/world/CMakeLists.txt | 4 +- game/shared/world/feature.cc | 28 ++++++ game/shared/world/feature.hh | 2 + game/shared/world/item_registry.cc | 9 +- game/shared/world/item_registry.hh | 9 +- game/shared/world/voxel.cc | 95 ++++++++++++++---- game/shared/world/voxel.hh | 130 ++++++++++++++++-------- game/shared/world/voxel_registry.cc | 22 ++-- game/shared/world/voxel_registry.hh | 4 +- game/shared/world/voxels/generic.hh | 27 ----- 26 files changed, 524 insertions(+), 386 deletions(-) delete mode 100644 game/shared/world/voxels/generic.hh (limited to 'game') diff --git a/game/client/experiments.cc b/game/client/experiments.cc index 2b9fe89..b947012 100644 --- a/game/client/experiments.cc +++ b/game/client/experiments.cc @@ -22,7 +22,7 @@ static void on_glfw_mouse_button(const io::GlfwMouseButtonEvent& event) { if(!globals::gui_screen && session::is_ingame()) { - if((event.action == GLFW_PRESS) && (world::player_target::voxel != NULL_VOXEL_ID)) { + if((event.action == GLFW_PRESS) && world::player_target::voxel) { if(event.button == GLFW_MOUSE_BUTTON_LEFT) { experiments::attack(); return; @@ -68,13 +68,13 @@ void experiments::update_late(void) void experiments::attack(void) { - globals::dimension->set_voxel(NULL_VOXEL_ID, world::player_target::coord); + globals::dimension->set_voxel(nullptr, world::player_target::coord); } void experiments::interact(void) { if(auto info = world::item_registry::find(gui::hotbar::slots[gui::hotbar::active_slot])) { - if(info->place_voxel != NULL_VOXEL_ID) { + if(info->place_voxel) { globals::dimension->set_voxel(info->place_voxel, world::player_target::coord + world::player_target::normal); } } diff --git a/game/client/game.cc b/game/client/game.cc index a59aec3..257bd2d 100644 --- a/game/client/game.cc +++ b/game/client/game.cc @@ -372,24 +372,40 @@ void client_game::init_late(void) // NOTE: this is very debug, early and a quite // conservative limit choice; there must be a better // way to make this limit way smaller than it currently is - for(const std::shared_ptr& info : world::voxel_registry::voxels) { - for(const world::VoxelTexture& vtex : info->textures) { - max_texture_count += vtex.paths.size(); - } + for(const auto& voxel : world::voxel_registry::voxels) { + max_texture_count += voxel->get_default_textures().size(); + max_texture_count += voxel->get_face_textures(world::VFACE_NORTH).size(); + max_texture_count += voxel->get_face_textures(world::VFACE_SOUTH).size(); + max_texture_count += voxel->get_face_textures(world::VFACE_EAST).size(); + max_texture_count += voxel->get_face_textures(world::VFACE_WEST).size(); + max_texture_count += voxel->get_face_textures(world::VFACE_TOP).size(); + max_texture_count += voxel->get_face_textures(world::VFACE_BOTTOM).size(); + max_texture_count += voxel->get_face_textures(world::VFACE_CROSS_NWSE).size(); + max_texture_count += voxel->get_face_textures(world::VFACE_CROSS_NESW).size(); } // UNDONE: asset packs for non-16x16 stuff world::voxel_atlas::create(16, 16, max_texture_count); - for(std::shared_ptr& info : world::voxel_registry::voxels) { - for(world::VoxelTexture& vtex : info->textures) { - if(auto strip = world::voxel_atlas::find_or_load(vtex.paths)) { - vtex.cached_offset = strip->offset; - vtex.cached_plane = strip->plane; + for(auto& voxel : world::voxel_registry::voxels) { + constexpr std::array faces = { + world::VFACE_NORTH, + world::VFACE_SOUTH, + world::VFACE_EAST, + world::VFACE_WEST, + world::VFACE_TOP, + world::VFACE_BOTTOM, + world::VFACE_CROSS_NWSE, + world::VFACE_CROSS_NESW, + }; + + for(auto face : faces) { + if(auto strip = world::voxel_atlas::find_or_load(voxel->get_face_textures(face))) { + voxel->set_face_cache(face, strip->offset, strip->plane); continue; } - spdlog::critical("client_gl: {}: failed to load atlas strips", info->name); + spdlog::critical("client_gl: {}: failed to load atlas strips", voxel->get_name()); std::terminate(); } } diff --git a/game/client/session.cc b/game/client/session.cc index 7e0d36c..c33b4d9 100644 --- a/game/client/session.cc +++ b/game/client/session.cc @@ -98,8 +98,10 @@ static void on_set_voxel_packet(const protocol::SetVoxel& packet) auto index = coord::to_index(lpos); if(auto chunk = globals::dimension->find_chunk(cpos)) { - if(chunk->get_voxel(index) != packet.voxel) { - chunk->set_voxel(packet.voxel, index); + auto packet_voxel = world::voxel_registry::find(packet.voxel); + + if(chunk->get_voxel(index) != packet_voxel) { + chunk->set_voxel(packet_voxel, index); world::ChunkUpdateEvent event; event.dimension = globals::dimension; @@ -125,7 +127,7 @@ static void on_voxel_set(const world::VoxelSetEvent& event) // FIXME: should we also validate things here or wait for the server to do so protocol::SetVoxel packet; packet.vpos = coord::to_voxel(event.cpos, event.lpos); - packet.voxel = event.voxel; + packet.voxel = event.voxel->get_id(); protocol::send(session::peer, protocol::encode(packet)); } @@ -283,7 +285,7 @@ void session::send_login_request(void) { protocol::LoginRequest packet; packet.version = protocol::VERSION; - packet.voxel_registry_checksum = world::voxel_registry::calculate_checksum(); + packet.voxel_registry_checksum = world::voxel_registry::get_checksum(); packet.item_registry_checksum = world::item_registry::calculate_checksum(); packet.password_hash = server_password_hash; packet.username = client_game::username.get(); diff --git a/game/client/world/chunk_mesher.cc b/game/client/world/chunk_mesher.cc index a8ee817..bc90a03 100644 --- a/game/client/world/chunk_mesher.cc +++ b/game/client/world/chunk_mesher.cc @@ -8,6 +8,7 @@ #include "shared/world/chunk.hh" #include "shared/world/dimension.hh" +#include "shared/world/voxel.hh" #include "shared/world/voxel_registry.hh" #include "shared/coord.hh" @@ -56,37 +57,6 @@ static const CachedChunkCoord get_cached_cpos(const chunk_pos& pivot, const chun return CPOS_ITSELF; } -static world::voxel_facing get_facing(world::voxel_face face, world::voxel_type type) -{ - if(type == world::voxel_type::CROSS) { - switch(face) { - case world::voxel_face::CROSS_NESW: - return world::voxel_facing::NESW; - case world::voxel_face::CROSS_NWSE: - return world::voxel_facing::NWSE; - default: - return world::voxel_facing::NORTH; - } - } - - switch(face) { - case world::voxel_face::CUBE_NORTH: - return world::voxel_facing::NORTH; - case world::voxel_face::CUBE_SOUTH: - return world::voxel_facing::SOUTH; - case world::voxel_face::CUBE_EAST: - return world::voxel_facing::EAST; - case world::voxel_face::CUBE_WEST: - return world::voxel_facing::WEST; - case world::voxel_face::CUBE_TOP: - return world::voxel_facing::UP; - case world::voxel_face::CUBE_BOTTOM: - return world::voxel_facing::DOWN; - default: - return world::voxel_facing::NORTH; - } -} - class GL_MeshingTask final : public Task { public: explicit GL_MeshingTask(entt::entity entity, const chunk_pos& cpos); @@ -95,11 +65,10 @@ public: virtual void finalize(void) override; private: - bool vis_test(voxel_id voxel, const world::VoxelInfo* info, const local_pos& lpos) const; - void push_quad_a(const world::VoxelInfo* info, const glm::fvec3& pos, const glm::fvec2& size, world::voxel_face face); - void push_quad_v(const world::VoxelInfo* info, const glm::fvec3& pos, const glm::fvec2& size, world::voxel_face face, - std::size_t entropy); - void make_cube(voxel_id voxel, const world::VoxelInfo* info, const local_pos& lpos, world::voxel_vis vis, std::size_t entropy); + bool vis_test(const world::Voxel* voxel, const local_pos& lpos) const; + void push_quad_a(const world::Voxel* voxel, const glm::fvec3& pos, const glm::fvec2& size, world::VoxelFace face); + void push_quad_v(const world::Voxel* voxel, const glm::fvec3& pos, const glm::fvec2& size, world::VoxelFace face, std::size_t entropy); + void make_cube(const world::Voxel* voxel, const local_pos& lpos, world::VoxelVisBits vis, std::size_t entropy); void cache_chunk(const chunk_pos& cpos); private: @@ -138,41 +107,39 @@ void GL_MeshingTask::process(void) return; } - const auto voxel = voxels[i]; const auto lpos = coord::to_local(i); + const auto voxel = world::voxel_registry::find(voxels[i]); - const auto info = world::voxel_registry::find(voxel); - - if(info == nullptr) { + if(voxel == nullptr) { // Either a NULL_VOXEL_ID or something went // horribly wrong and we don't what this is continue; } - world::voxel_vis vis = 0; + unsigned int vis = 0U; - if(vis_test(voxel, info, lpos + DIR_NORTH)) { - vis |= world::VIS_NORTH; + if(vis_test(voxel, lpos + DIR_NORTH)) { + vis |= world::VVIS_NORTH; } - if(vis_test(voxel, info, lpos + DIR_SOUTH)) { - vis |= world::VIS_SOUTH; + if(vis_test(voxel, lpos + DIR_SOUTH)) { + vis |= world::VVIS_SOUTH; } - if(vis_test(voxel, info, lpos + DIR_EAST)) { - vis |= world::VIS_EAST; + if(vis_test(voxel, lpos + DIR_EAST)) { + vis |= world::VVIS_EAST; } - if(vis_test(voxel, info, lpos + DIR_WEST)) { - vis |= world::VIS_WEST; + if(vis_test(voxel, lpos + DIR_WEST)) { + vis |= world::VVIS_WEST; } - if(vis_test(voxel, info, lpos + DIR_UP)) { - vis |= world::VIS_UP; + if(vis_test(voxel, lpos + DIR_UP)) { + vis |= world::VVIS_UP; } - if(vis_test(voxel, info, lpos + DIR_DOWN)) { - vis |= world::VIS_DOWN; + if(vis_test(voxel, lpos + DIR_DOWN)) { + vis |= world::VVIS_DOWN; } const auto vpos = coord::to_voxel(m_cpos, lpos); @@ -180,7 +147,7 @@ void GL_MeshingTask::process(void) const auto entropy = math::crc64(&entropy_src, sizeof(entropy_src)); // FIXME: handle different voxel types - make_cube(voxel, info, lpos, vis, entropy); + make_cube(voxel, lpos, world::VoxelVisBits(vis), entropy); } } @@ -254,7 +221,7 @@ void GL_MeshingTask::finalize(void) } } -bool GL_MeshingTask::vis_test(voxel_id voxel, const world::VoxelInfo* info, const local_pos& lpos) const +bool GL_MeshingTask::vis_test(const world::Voxel* voxel, const local_pos& lpos) const { const auto pvpos = coord::to_voxel(m_cpos, lpos); const auto pcpos = coord::to_chunk(pvpos); @@ -263,26 +230,18 @@ bool GL_MeshingTask::vis_test(voxel_id voxel, const world::VoxelInfo* info, cons const auto cached_cpos = get_cached_cpos(m_cpos, pcpos); const auto& voxels = m_cache.at(cached_cpos); - const auto neighbour = voxels[index]; + const auto neighbour = world::voxel_registry::find(voxels[index]); bool result; - if(neighbour == NULL_VOXEL_ID) { + if(neighbour == nullptr) { result = true; } else if(neighbour == voxel) { result = false; } - else if(auto neighbour_info = world::voxel_registry::find(neighbour)) { - if(neighbour_info->blending != info->blending) { - // Voxel types that use blending are semi-transparent; - // this means they're rendered using a different setup - // and they must have visible faces with opaque voxels - result = neighbour_info->blending; - } - else { - result = false; - } + else if(neighbour->get_render_mode() != voxel->get_render_mode()) { + result = true; } else { result = false; @@ -291,88 +250,95 @@ bool GL_MeshingTask::vis_test(voxel_id voxel, const world::VoxelInfo* info, cons return result; } -void GL_MeshingTask::push_quad_a(const world::VoxelInfo* info, const glm::fvec3& pos, const glm::fvec2& size, world::voxel_face face) +void GL_MeshingTask::push_quad_a(const world::Voxel* voxel, const glm::fvec3& pos, const glm::fvec2& size, world::VoxelFace face) { - const world::voxel_facing facing = get_facing(face, info->type); - const world::VoxelTexture& vtex = info->textures[static_cast(face)]; - - if(info->blending) { - m_quads_b[vtex.cached_plane].push_back(make_chunk_quad(pos, size, facing, vtex.cached_offset, vtex.paths.size())); - } - else { - m_quads_s[vtex.cached_plane].push_back(make_chunk_quad(pos, size, facing, vtex.cached_offset, vtex.paths.size())); + auto cached_offset = voxel->get_cached_face_offset(face); + auto cached_plane = voxel->get_cached_face_plane(face); + auto& textures = voxel->get_face_textures(face); + + switch(voxel->get_render_mode()) { + case world::VRENDER_OPAQUE: + m_quads_s[cached_plane].push_back(make_chunk_quad(pos, size, face, cached_offset, textures.size())); + break; + + case world::VRENDER_BLEND: + m_quads_b[cached_plane].push_back(make_chunk_quad(pos, size, face, cached_offset, textures.size())); + break; } } -void GL_MeshingTask::push_quad_v(const world::VoxelInfo* info, const glm::fvec3& pos, const glm::fvec2& size, world::voxel_face face, +void GL_MeshingTask::push_quad_v(const world::Voxel* voxel, const glm::fvec3& pos, const glm::fvec2& size, world::VoxelFace face, std::size_t entropy) { - const world::voxel_facing facing = get_facing(face, info->type); - const world::VoxelTexture& vtex = info->textures[static_cast(face)]; - const std::size_t entropy_mod = entropy % vtex.paths.size(); - - if(info->blending) { - m_quads_b[vtex.cached_plane].push_back(make_chunk_quad(pos, size, facing, vtex.cached_offset + entropy_mod, 0)); - } - else { - m_quads_s[vtex.cached_plane].push_back(make_chunk_quad(pos, size, facing, vtex.cached_offset + entropy_mod, 0)); + auto cached_offset = voxel->get_cached_face_offset(face); + auto cached_plane = voxel->get_cached_face_plane(face); + auto& textures = voxel->get_face_textures(face); + auto index = entropy % textures.size(); + + switch(voxel->get_render_mode()) { + case world::VRENDER_OPAQUE: + m_quads_s[cached_plane].push_back(make_chunk_quad(pos, size, face, cached_offset + index, 0)); + break; + + case world::VRENDER_BLEND: + m_quads_b[cached_plane].push_back(make_chunk_quad(pos, size, face, cached_offset + index, 0)); + break; } } -void GL_MeshingTask::make_cube(voxel_id voxel, const world::VoxelInfo* info, const local_pos& lpos, world::voxel_vis vis, - std::size_t entropy) +void GL_MeshingTask::make_cube(const world::Voxel* voxel, const local_pos& lpos, world::VoxelVisBits vis, std::size_t entropy) { const glm::fvec3 fpos = glm::fvec3(lpos); const glm::fvec2 fsize = glm::fvec2(1.0f, 1.0f); - if(info->animated) { - if(vis & world::VIS_NORTH) { - push_quad_a(info, fpos, fsize, world::voxel_face::CUBE_NORTH); + if(voxel->is_animated()) { + if(vis & world::VVIS_NORTH) { + push_quad_a(voxel, fpos, fsize, world::VFACE_NORTH); } - if(vis & world::VIS_SOUTH) { - push_quad_a(info, fpos, fsize, world::voxel_face::CUBE_SOUTH); + if(vis & world::VVIS_SOUTH) { + push_quad_a(voxel, fpos, fsize, world::VFACE_SOUTH); } - if(vis & world::VIS_EAST) { - push_quad_a(info, fpos, fsize, world::voxel_face::CUBE_EAST); + if(vis & world::VVIS_EAST) { + push_quad_a(voxel, fpos, fsize, world::VFACE_EAST); } - if(vis & world::VIS_WEST) { - push_quad_a(info, fpos, fsize, world::voxel_face::CUBE_WEST); + if(vis & world::VVIS_WEST) { + push_quad_a(voxel, fpos, fsize, world::VFACE_WEST); } - if(vis & world::VIS_UP) { - push_quad_a(info, fpos, fsize, world::voxel_face::CUBE_TOP); + if(vis & world::VVIS_UP) { + push_quad_a(voxel, fpos, fsize, world::VFACE_TOP); } - if(vis & world::VIS_DOWN) { - push_quad_a(info, fpos, fsize, world::voxel_face::CUBE_BOTTOM); + if(vis & world::VVIS_DOWN) { + push_quad_a(voxel, fpos, fsize, world::VFACE_BOTTOM); } } else { - if(vis & world::VIS_NORTH) { - push_quad_v(info, fpos, fsize, world::voxel_face::CUBE_NORTH, entropy); + if(vis & world::VVIS_NORTH) { + push_quad_v(voxel, fpos, fsize, world::VFACE_NORTH, entropy); } - if(vis & world::VIS_SOUTH) { - push_quad_v(info, fpos, fsize, world::voxel_face::CUBE_SOUTH, entropy); + if(vis & world::VVIS_SOUTH) { + push_quad_v(voxel, fpos, fsize, world::VFACE_SOUTH, entropy); } - if(vis & world::VIS_EAST) { - push_quad_v(info, fpos, fsize, world::voxel_face::CUBE_EAST, entropy); + if(vis & world::VVIS_EAST) { + push_quad_v(voxel, fpos, fsize, world::VFACE_EAST, entropy); } - if(vis & world::VIS_WEST) { - push_quad_v(info, fpos, fsize, world::voxel_face::CUBE_WEST, entropy); + if(vis & world::VVIS_WEST) { + push_quad_v(voxel, fpos, fsize, world::VFACE_WEST, entropy); } - if(vis & world::VIS_UP) { - push_quad_v(info, fpos, fsize, world::voxel_face::CUBE_TOP, entropy); + if(vis & world::VVIS_UP) { + push_quad_v(voxel, fpos, fsize, world::VFACE_TOP, entropy); } - if(vis & world::VIS_DOWN) { - push_quad_v(info, fpos, fsize, world::voxel_face::CUBE_BOTTOM, entropy); + if(vis & world::VVIS_DOWN) { + push_quad_v(voxel, fpos, fsize, world::VFACE_BOTTOM, entropy); } } } diff --git a/game/client/world/chunk_quad.hh b/game/client/world/chunk_quad.hh index c15bb7a..d68977e 100644 --- a/game/client/world/chunk_quad.hh +++ b/game/client/world/chunk_quad.hh @@ -13,8 +13,8 @@ using ChunkQuad = std::array; namespace world { -constexpr inline static ChunkQuad make_chunk_quad(const glm::fvec3& position, const glm::fvec2& size, voxel_facing facing, - std::size_t texture, std::size_t frames) +constexpr inline static ChunkQuad make_chunk_quad(const glm::fvec3& position, const glm::fvec2& size, VoxelFace face, std::size_t texture, + std::size_t frames) { ChunkQuad result = {}; result[0] = 0x00000000; @@ -30,7 +30,7 @@ constexpr inline static ChunkQuad make_chunk_quad(const glm::fvec3& position, co result[0] |= (0x0000000FU & static_cast(size.y * 16.0f - 1.0f)); // [1] FFFF---------------------------- - result[1] |= (0x0000000FU & static_cast(facing)) << 28U; + result[1] |= (0x0000000FU & static_cast(face)) << 28U; // [1] ----TTTTTTTTTTTAAAAA------------ result[1] |= (0x000007FFU & static_cast(texture)) << 17U; diff --git a/game/client/world/player_target.cc b/game/client/world/player_target.cc index f0550c0..3ede47e 100644 --- a/game/client/world/player_target.cc +++ b/game/client/world/player_target.cc @@ -16,17 +16,15 @@ constexpr static float MAX_REACH = 16.0f; -voxel_id world::player_target::voxel; voxel_pos world::player_target::coord; voxel_pos world::player_target::normal; -const world::VoxelInfo* world::player_target::info; +const world::Voxel* world::player_target::voxel; void world::player_target::init(void) { - world::player_target::voxel = NULL_VOXEL_ID; world::player_target::coord = voxel_pos(); world::player_target::normal = voxel_pos(); - world::player_target::info = nullptr; + world::player_target::voxel = nullptr; } void world::player_target::update(void) @@ -37,29 +35,26 @@ void world::player_target::update(void) do { world::player_target::voxel = ray.step(); - if(world::player_target::voxel != NULL_VOXEL_ID) { + if(world::player_target::voxel) { world::player_target::coord = ray.vpos; world::player_target::normal = ray.vnormal; - world::player_target::info = world::voxel_registry::find(world::player_target::voxel); break; } world::player_target::coord = voxel_pos(); world::player_target::normal = voxel_pos(); - world::player_target::info = nullptr; } while(ray.distance < MAX_REACH); } else { - world::player_target::voxel = NULL_VOXEL_ID; + world::player_target::voxel = nullptr; world::player_target::coord = voxel_pos(); world::player_target::normal = voxel_pos(); - world::player_target::info = nullptr; } } void world::player_target::render(void) { - if((world::player_target::voxel != NULL_VOXEL_ID) && !client_game::hide_hud) { + if(world::player_target::voxel && !client_game::hide_hud) { auto cpos = coord::to_chunk(world::player_target::coord); auto fpos = coord::to_local(world::player_target::coord); diff --git a/game/client/world/player_target.hh b/game/client/world/player_target.hh index f0db9be..34532c3 100644 --- a/game/client/world/player_target.hh +++ b/game/client/world/player_target.hh @@ -4,10 +4,9 @@ namespace world::player_target { -extern voxel_id voxel; extern voxel_pos coord; extern voxel_pos normal; -extern const VoxelInfo* info; +extern const Voxel* voxel; } // namespace world::player_target namespace world::player_target diff --git a/game/client/world/voxel_sounds.cc b/game/client/world/voxel_sounds.cc index 481e615..fe91f01 100644 --- a/game/client/world/voxel_sounds.cc +++ b/game/client/world/voxel_sounds.cc @@ -4,24 +4,21 @@ #include "client/resource/sound_effect.hh" -constexpr static std::size_t NUM_SURFACES = static_cast(world::voxel_surface::COUNT); - -static std::vector> footsteps_sounds[NUM_SURFACES]; +static std::vector> footsteps_sounds[world::VMAT_COUNT]; static std::mt19937_64 randomizer; -static void add_footsteps_effect(world::voxel_surface surface, std::string_view name) +static void add_footsteps_effect(world::VoxelMaterial material, std::string_view name) { if(auto effect = resource::load(name)) { - auto surface_index = static_cast(surface); - footsteps_sounds[surface_index].push_back(effect); + footsteps_sounds[material].push_back(effect); } } -static resource_ptr get_footsteps_effect(world::voxel_surface surface) +static resource_ptr get_footsteps_effect(world::VoxelMaterial material) { - auto surface_index = static_cast(surface); + auto surface_index = static_cast(material); - if(surface_index >= NUM_SURFACES) { + if(surface_index >= world::VMAT_COUNT) { // Surface index out of range return nullptr; } @@ -39,48 +36,48 @@ static resource_ptr get_footsteps_effect(world::voxel_surface surfa void world::voxel_sounds::init(void) { - add_footsteps_effect(voxel_surface::DEFAULT, "sounds/surface/default1.wav"); - add_footsteps_effect(voxel_surface::DEFAULT, "sounds/surface/default2.wav"); - add_footsteps_effect(voxel_surface::DEFAULT, "sounds/surface/default3.wav"); - add_footsteps_effect(voxel_surface::DEFAULT, "sounds/surface/default4.wav"); + add_footsteps_effect(VMAT_DEFAULT, "sounds/surface/default1.wav"); + add_footsteps_effect(VMAT_DEFAULT, "sounds/surface/default2.wav"); + add_footsteps_effect(VMAT_DEFAULT, "sounds/surface/default3.wav"); + add_footsteps_effect(VMAT_DEFAULT, "sounds/surface/default4.wav"); - add_footsteps_effect(voxel_surface::DIRT, "sounds/surface/dirt1.wav"); + add_footsteps_effect(VMAT_DIRT, "sounds/surface/dirt1.wav"); - add_footsteps_effect(voxel_surface::GRASS, "sounds/surface/grass1.wav"); - add_footsteps_effect(voxel_surface::GRASS, "sounds/surface/grass2.wav"); - add_footsteps_effect(voxel_surface::GRASS, "sounds/surface/grass3.wav"); + add_footsteps_effect(VMAT_GRASS, "sounds/surface/grass1.wav"); + add_footsteps_effect(VMAT_GRASS, "sounds/surface/grass2.wav"); + add_footsteps_effect(VMAT_GRASS, "sounds/surface/grass3.wav"); - add_footsteps_effect(voxel_surface::GRAVEL, "sounds/surface/gravel1.wav"); + add_footsteps_effect(VMAT_GRAVEL, "sounds/surface/gravel1.wav"); - add_footsteps_effect(voxel_surface::SAND, "sounds/surface/sand1.wav"); - add_footsteps_effect(voxel_surface::SAND, "sounds/surface/sand2.wav"); + add_footsteps_effect(VMAT_SAND, "sounds/surface/sand1.wav"); + add_footsteps_effect(VMAT_SAND, "sounds/surface/sand2.wav"); - add_footsteps_effect(voxel_surface::WOOD, "sounds/surface/wood1.wav"); - add_footsteps_effect(voxel_surface::WOOD, "sounds/surface/wood2.wav"); - add_footsteps_effect(voxel_surface::WOOD, "sounds/surface/wood3.wav"); + add_footsteps_effect(VMAT_WOOD, "sounds/surface/wood1.wav"); + add_footsteps_effect(VMAT_WOOD, "sounds/surface/wood2.wav"); + add_footsteps_effect(VMAT_WOOD, "sounds/surface/wood3.wav"); } void world::voxel_sounds::shutdown(void) { - for(std::size_t i = 0; i < NUM_SURFACES; ++i) { + for(std::size_t i = 0; i < world::VMAT_COUNT; ++i) { footsteps_sounds[i].clear(); } } -resource_ptr world::voxel_sounds::get_footsteps(voxel_surface surface) +resource_ptr world::voxel_sounds::get_footsteps(world::VoxelMaterial material) { - if(auto effect = get_footsteps_effect(surface)) { + if(auto effect = get_footsteps_effect(material)) { return effect; } - if(auto effect = get_footsteps_effect(voxel_surface::DEFAULT)) { + if(auto effect = get_footsteps_effect(VMAT_DEFAULT)) { return effect; } return nullptr; } -resource_ptr world::voxel_sounds::get_placebreak(voxel_surface surface) +resource_ptr world::voxel_sounds::get_placebreak(world::VoxelMaterial material) { return nullptr; } diff --git a/game/client/world/voxel_sounds.hh b/game/client/world/voxel_sounds.hh index 09f5e2e..d0f3e07 100644 --- a/game/client/world/voxel_sounds.hh +++ b/game/client/world/voxel_sounds.hh @@ -2,7 +2,7 @@ #include "core/resource/resource.hh" -#include "shared/world/voxel_registry.hh" +#include "shared/world/voxel.hh" struct SoundEffect; @@ -14,6 +14,6 @@ void shutdown(void); namespace world::voxel_sounds { -resource_ptr get_footsteps(voxel_surface surface); -resource_ptr get_placebreak(voxel_surface surface); +resource_ptr get_footsteps(VoxelMaterial material); +resource_ptr get_placebreak(VoxelMaterial material); } // namespace world::voxel_sounds diff --git a/game/server/receive.cc b/game/server/receive.cc index 75ac3a6..612674e 100644 --- a/game/server/receive.cc +++ b/game/server/receive.cc @@ -10,6 +10,7 @@ #include "shared/world/chunk_aabb.hh" #include "shared/world/dimension.hh" +#include "shared/world/voxel_registry.hh" #include "shared/coord.hh" #include "shared/protocol.hh" @@ -83,7 +84,7 @@ static void on_entity_head_packet(const protocol::EntityHead& packet) static void on_set_voxel_packet(const protocol::SetVoxel& packet) { if(auto session = sessions::find(packet.peer)) { - if(session->dimension && !session->dimension->set_voxel(packet.voxel, packet.vpos)) { + if(session->dimension && !session->dimension->set_voxel(world::voxel_registry::find(packet.voxel), packet.vpos)) { auto cpos = coord::to_chunk(packet.vpos); auto lpos = coord::to_local(packet.vpos); auto index = coord::to_index(lpos); @@ -102,7 +103,7 @@ static void on_set_voxel_packet(const protocol::SetVoxel& packet) return; } - chunk->set_voxel(packet.voxel, index); + chunk->set_voxel(world::voxel_registry::find(packet.voxel), index); session->dimension->chunks.emplace_or_replace(chunk->get_entity()); diff --git a/game/server/sessions.cc b/game/server/sessions.cc index c06ec3e..32b96a3 100644 --- a/game/server/sessions.cc +++ b/game/server/sessions.cc @@ -65,7 +65,7 @@ static void on_login_request_packet(const protocol::LoginRequest& packet) // FIXME: calculate voxel registry checksum ahead of time // instead of figuring it out every time a new player connects - if(packet.voxel_registry_checksum != world::voxel_registry::calculate_checksum()) { + if(packet.voxel_registry_checksum != world::voxel_registry::get_checksum()) { protocol::Disconnect response; response.reason = "protocol.voxel_registry_checksum"; protocol::send(packet.peer, protocol::encode(response)); @@ -241,7 +241,7 @@ static void on_voxel_set(const world::VoxelSetEvent& event) { protocol::SetVoxel packet; packet.vpos = coord::to_voxel(event.cpos, event.lpos); - packet.voxel = event.voxel; + packet.voxel = event.voxel ? event.voxel->get_id() : NULL_VOXEL_ID; packet.flags = 0U; // UNDONE protocol::broadcast(globals::server_host, protocol::encode(packet)); } diff --git a/game/server/world/overworld.cc b/game/server/world/overworld.cc index eb801de..43059d8 100644 --- a/game/server/world/overworld.cc +++ b/game/server/world/overworld.cc @@ -4,13 +4,15 @@ #include "core/math/vectors.hh" +#include "shared/world/voxel.hh" #include "shared/world/voxel_storage.hh" #include "shared/coord.hh" #include "shared/game_voxels.hh" // FIXME: load these from a file -static void compute_tree_feature(unsigned int height, world::Feature& feature, voxel_id log_voxel, voxel_id leaves_voxel) +static void compute_tree_feature(unsigned int height, world::Feature& feature, const world::Voxel* log_voxel, + const world::Voxel* leaves_voxel) { // Ensure the tree height is too small height = math::max(height, 4U); @@ -251,12 +253,12 @@ void world::Overworld::generate_terrain(const chunk_pos& cpos, VoxelStorage& vox } if(vpos.y < -variation) { - voxels[i] = game_voxels::stone; + voxels[i] = game_voxels::stone->get_id(); continue; } if(is_inside_terrain(vpos)) { - voxels[i] = game_voxels::stone; + voxels[i] = game_voxels::stone->get_id(); continue; } } @@ -308,10 +310,10 @@ void world::Overworld::generate_surface(const chunk_pos& cpos, VoxelStorage& vox if(depth < 5U) { if(depth == 0U) { - voxels[i] = game_voxels::grass; + voxels[i] = game_voxels::grass->get_id(); } else { - voxels[i] = game_voxels::dirt; + voxels[i] = game_voxels::dirt->get_id(); } } } diff --git a/game/shared/entity/collision.cc b/game/shared/entity/collision.cc index 24f5d49..6b9f063 100644 --- a/game/shared/entity/collision.cc +++ b/game/shared/entity/collision.cc @@ -16,7 +16,7 @@ #include "shared/globals.hh" static int vgrid_collide(const world::Dimension* dimension, int d, entity::Collision& collision, entity::Transform& transform, - entity::Velocity& velocity, world::voxel_surface& touch_surface) + entity::Velocity& velocity, world::VoxelMaterial& touch_surface) { const auto move = globals::fixed_frametime * velocity.value[d]; const auto move_sign = math::sign(move); @@ -57,9 +57,9 @@ static int vgrid_collide(const world::Dimension* dimension, int d, entity::Colli dmax = lpos_min[d]; } - world::voxel_touch latch_touch = world::voxel_touch::NOTHING; + world::VoxelTouch latch_touch = world::VTOUCH_NONE; glm::fvec3 latch_values = glm::fvec3(0.0f, 0.0f, 0.0f); - world::voxel_surface latch_surface = world::voxel_surface::UNKNOWN; + world::VoxelMaterial latch_surface = world::VMAT_UNKNOWN; math::AABBf latch_vbox; for(auto i = dmin; i != dmax; i += ddir) { @@ -70,18 +70,16 @@ static int vgrid_collide(const world::Dimension* dimension, int d, entity::Colli lpos[u] = j; lpos[v] = k; - const auto vpos = coord::to_voxel(transform.chunk, lpos); - const auto info = world::voxel_registry::find(dimension->get_voxel(vpos)); + auto vpos = coord::to_voxel(transform.chunk, lpos); + auto voxel = dimension->get_voxel(vpos); - if(info == nullptr) { + if(voxel == nullptr) { // Don't collide with something // that we assume to be nothing continue; } - math::AABBf vbox; - vbox.min = glm::fvec3(lpos); - vbox.max = glm::fvec3(lpos) + 1.0f; + math::AABBf vbox(voxel->get_collision().push(lpos)); if(!next_aabb.intersect(vbox)) { // No intersection between the voxel @@ -89,29 +87,29 @@ static int vgrid_collide(const world::Dimension* dimension, int d, entity::Colli continue; } - if(info->touch_type == world::voxel_touch::SOLID) { + if(voxel->is_touch_type()) { // Solid touch type makes a collision // response whenever it is encountered velocity.value[d] = 0.0f; - touch_surface = info->surface; + touch_surface = voxel->get_surface_material(); return move_sign; } // In case of other touch types, they // are latched and the last ever touch // type is then responded to - if(info->touch_type != world::voxel_touch::NOTHING) { - latch_touch = info->touch_type; - latch_values = info->touch_values; - latch_surface = info->surface; + if(voxel->get_touch_type() != world::VTOUCH_NONE) { + latch_touch = voxel->get_touch_type(); + latch_values = voxel->get_touch_values(); + latch_surface = voxel->get_surface_material(); latch_vbox = vbox; continue; } } } - if(latch_touch != world::voxel_touch::NOTHING) { - if(latch_touch == world::voxel_touch::BOUNCE) { + if(latch_touch != world::VTOUCH_NONE) { + if(latch_touch == world::VTOUCH_BOUNCE) { const auto move_distance = math::abs(current_aabb.min[d] - next_aabb.min[d]); const auto threshold = 2.0f * globals::fixed_frametime; @@ -127,7 +125,7 @@ static int vgrid_collide(const world::Dimension* dimension, int d, entity::Colli return move_sign; } - if(latch_touch == world::voxel_touch::SINK) { + if(latch_touch == world::VTOUCH_SINK) { velocity.value[d] *= latch_values[d]; touch_surface = latch_surface; return move_sign; @@ -151,7 +149,7 @@ void entity::Collision::fixed_update(world::Dimension* dimension) auto group = dimension->entities.group(entt::get); for(auto [entity, collision, transform, velocity] : group.each()) { - auto surface = world::voxel_surface::UNKNOWN; + auto surface = world::VMAT_UNKNOWN; auto vertical_move = vgrid_collide(dimension, 1, collision, transform, velocity, surface); if(dimension->entities.any_of(entity)) { diff --git a/game/shared/entity/grounded.hh b/game/shared/entity/grounded.hh index e86e3a8..940cebe 100644 --- a/game/shared/entity/grounded.hh +++ b/game/shared/entity/grounded.hh @@ -9,7 +9,7 @@ namespace entity // Assigned to entities which are grounded // according to the collision and gravity system struct Grounded final { - world::voxel_surface surface; + world::VoxelMaterial surface; }; } // namespace entity diff --git a/game/shared/game_voxels.cc b/game/shared/game_voxels.cc index 59d035d..75c56c7 100644 --- a/game/shared/game_voxels.cc +++ b/game/shared/game_voxels.cc @@ -2,10 +2,11 @@ #include "shared/game_voxels.hh" -#include "shared/world/voxels/generic.hh" - +#include "shared/world/dimension.hh" #include "shared/world/voxel_registry.hh" +#include "shared/const.hh" + const world::Voxel* game_voxels::cobblestone = nullptr; const world::Voxel* game_voxels::dirt = nullptr; const world::Voxel* game_voxels::grass = nullptr; @@ -17,78 +18,124 @@ const world::Voxel* game_voxels::oak_planks = nullptr; const world::Voxel* game_voxels::oak_log = nullptr; const world::Voxel* game_voxels::glass = nullptr; const world::Voxel* game_voxels::slime = nullptr; -const world::Voxel* game_voxels::mud = nullptr; + +static void dirt_tick(world::Dimension* dimension, const voxel_pos& vpos) +{ + auto grass_found = false; + auto air_above = false; + + for(voxel_pos::value_type dx = -1; dx <= 1 && !grass_found; ++dx) { + for(voxel_pos::value_type dy = -1; dy <= 1 && !grass_found; ++dy) { + for(voxel_pos::value_type dz = -1; dz <= 1 && !grass_found; ++dz) { + if(dx == 0 && dy == 0 && dz == 0) { + // Skip self + continue; + } + + auto neighbour_vpos = vpos + voxel_pos(dx, dy, dz); + auto neighbour_voxel = dimension->get_voxel(neighbour_vpos); + + // Voxel pointers returned by get_voxel() are the exact same + // returned by the voxel registry, so we can compare pointers directly + // and not bother with voxel_id property comparisons + if(neighbour_voxel == game_voxels::grass) { + grass_found = true; + break; + } + } + } + } + + auto above_vpos = vpos + voxel_pos(0, 1, 0); + auto above_voxel = dimension->get_voxel(above_vpos); + + if(above_voxel == nullptr || above_voxel->is_surface_material()) { + air_above = true; + } + + if(grass_found && air_above) { + // Replace itself with the grass voxel + dimension->set_voxel(game_voxels::grass, vpos); + } +} void game_voxels::populate(void) { - using namespace world; - using namespace world::voxels; - using world::voxel_registry::register_voxel; - - stone = register_voxel(GenericCube("stone", VRENDER_OPAQUE, false, VMAT_STONE, VTOUCH_SOLID, glm::fvec3(0.0f, 0.0f, 0.0f), - "textures/voxel/stone_01.png", "textures/voxel/stone_02.png", "textures/voxel/stone_03.png", "textures/voxel/stone_04.png")); - - cobblestone = register_voxel(GenericCube("cobblestone", VRENDER_OPAQUE, false, VMAT_STONE, VTOUCH_SOLID, glm::fvec3(0.0f, 0.0f, 0.0f), - "textures/voxel/cobblestone_01.png", "textures/voxel/cobblestone_02.png")); - - vtest = register_voxel(GenericCube("vtest", VRENDER_OPAQUE, true, VMAT_DEFAULT, VTOUCH_SOLID, glm::fvec3(0.0f, 0.0f, 0.0f), - "textures/voxel/vtest_F1.png", "textures/voxel/vtest_F2.png", "textures/voxel/vtest_F3.png", "textures/voxel/vtest_F4.png")); - - vtest_ck = register_voxel(GenericCube("vtest_ck", VRENDER_OPAQUE, false, VMAT_DEFAULT, VTOUCH_SOLID, glm::fvec3(0.0f, 0.0f, 0.0f), - "textures/voxel/chromakey.png")); - - oak_leaves = register_voxel(GenericCube("oak_leaves", VRENDER_BLEND, false, VMAT_GRASS, VTOUCH_SOLID, glm::fvec3(0.0f, 0.0f, 0.0f), - "textures/voxel/oak_leaves.png")); - - oak_planks = register_voxel(GenericCube("oak_planks", VRENDER_OPAQUE, false, VMAT_WOOD, VTOUCH_SOLID, glm::fvec3(0.0f, 0.0f, 0.0f), - "textures/voxel/oak_planks_01.png", "textures/voxel/oak_planks_02.png")); - - glass = register_voxel(GenericCube("glass", VRENDER_BLEND, false, VMAT_GLASS, VTOUCH_SOLID, glm::fvec3(0.0f, 0.0f, 0.0f), - "textures/voxel/glass_01.png")); - - slime = register_voxel(GenericCube("slime", VRENDER_BLEND, false, VMAT_SLOSH, VTOUCH_BOUNCE, glm::fvec3(0.00f, 0.60f, 0.00f), - "textures/voxel/slime_01.png")); - -#if 0 - - // Oak logs; greenery. TODO: add trees as surface features - game_voxels::oak_log = - world::voxel_registry::construct("oak_log", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/oak_wood_01.png") - .add_texture_default("textures/voxel/oak_wood_02.png") - .add_texture(world::voxel_face::CUBE_BOTTOM, "textures/voxel/oak_wood_top.png") - .add_texture(world::voxel_face::CUBE_TOP, "textures/voxel/oak_wood_top.png") - .set_surface(world::voxel_surface::WOOD) - .build(); - - // Glass; blend rendering test - game_voxels::glass = world::voxel_registry::construct("glass", world::voxel_type::CUBE, false, true) - .add_texture_default("textures/voxel/glass_01.png") - .set_surface(world::voxel_surface::GLASS) - .build(); - - // Dirt with a grass layer on top; the top layer of plains biome - game_voxels::grass = - world::voxel_registry::construct("grass", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/grass_side_01.png") - .add_texture_default("textures/voxel/grass_side_02.png") - .add_texture(world::voxel_face::CUBE_BOTTOM, "textures/voxel/dirt_01.png") - .add_texture(world::voxel_face::CUBE_BOTTOM, "textures/voxel/dirt_02.png") - .add_texture(world::voxel_face::CUBE_BOTTOM, "textures/voxel/dirt_03.png") - .add_texture(world::voxel_face::CUBE_BOTTOM, "textures/voxel/dirt_04.png") - .add_texture(world::voxel_face::CUBE_TOP, "textures/voxel/grass_01.png") - .add_texture(world::voxel_face::CUBE_TOP, "textures/voxel/grass_02.png") - .set_surface(world::voxel_surface::GRASS) - .build(); - - // Dirt; the under-surface layer of some biomes - game_voxels::dirt = - world::voxel_registry::construct("dirt", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/dirt_01.png") - .add_texture_default("textures/voxel/dirt_02.png") - .add_texture_default("textures/voxel/dirt_03.png") - .add_texture_default("textures/voxel/dirt_04.png") - .set_surface(world::voxel_surface::DIRT) - .build(); -#endif + world::VoxelBuilder builder; + + builder = world::VoxelBuilder("stone"); + builder.add_default_texture("textures/voxel/stone_01.png") + .add_default_texture("textures/voxel/stone_02.png") + .add_default_texture("textures/voxel/stone_03.png") + .add_default_texture("textures/voxel/stone_04.png"); + stone = world::voxel_registry::register_voxel(builder); + + builder = world::VoxelBuilder("cobblestone"); + builder.add_default_texture("textures/voxel/cobblestone_01.png").add_default_texture("textures/voxel/cobblestone_02.png"); + cobblestone = world::voxel_registry::register_voxel(builder); + + builder = world::VoxelBuilder("dirt"); + builder.add_default_texture("textures/voxel/dirt_01.png") + .add_default_texture("textures/voxel/dirt_02.png") + .add_default_texture("textures/voxel/dirt_03.png") + .add_default_texture("textures/voxel/dirt_04.png"); + builder.set_surface_material(world::VMAT_DIRT); + builder.set_on_tick(&dirt_tick); + dirt = world::voxel_registry::register_voxel(builder); + + builder = world::VoxelBuilder("grass"); + builder.add_default_texture("textures/voxel/grass_side_01.png") + .add_default_texture("textures/voxel/grass_side_02.png") + .add_face_texture(world::VFACE_BOTTOM, "textures/voxel/dirt_01.png") + .add_face_texture(world::VFACE_BOTTOM, "textures/voxel/dirt_02.png") + .add_face_texture(world::VFACE_BOTTOM, "textures/voxel/dirt_03.png") + .add_face_texture(world::VFACE_BOTTOM, "textures/voxel/dirt_04.png") + .add_face_texture(world::VFACE_TOP, "textures/voxel/grass_01.png") + .add_face_texture(world::VFACE_TOP, "textures/voxel/grass_02.png"); + builder.set_surface_material(world::VMAT_GRASS); + grass = world::voxel_registry::register_voxel(builder); + + builder = world::VoxelBuilder("vtest"); + builder.add_default_texture("textures/voxel/vtest_F1.png") + .add_default_texture("textures/voxel/vtest_F2.png") + .add_default_texture("textures/voxel/vtest_F3.png") + .add_default_texture("textures/voxel/vtest_F4.png"); + builder.set_animated(true); + vtest = world::voxel_registry::register_voxel(builder); + + builder = world::VoxelBuilder("vtest_ck"); + builder.add_default_texture("textures/voxel/chromakey.png"); + vtest_ck = world::voxel_registry::register_voxel(builder); + + builder = world::VoxelBuilder("oak_leaves"); + builder.add_default_texture("textures/voxel/oak_leaves.png"); + builder.set_render_mode(world::VRENDER_BLEND); + builder.set_surface_material(world::VMAT_GRASS); + oak_leaves = world::voxel_registry::register_voxel(builder); + + builder = world::VoxelBuilder("oak_planks"); + builder.add_default_texture("textures/voxel/oak_planks_01.png").add_default_texture("textures/voxel/oak_planks_02.png"); + builder.set_surface_material(world::VMAT_WOOD); + oak_planks = world::voxel_registry::register_voxel(builder); + + builder = world::VoxelBuilder("oak_log"); + builder.add_default_texture("textures/voxel/oak_wood_01.png") + .add_default_texture("textures/voxel/oak_wood_02.png") + .add_face_texture(world::VFACE_BOTTOM, "textures/voxel/oak_wood_top.png") + .add_face_texture(world::VFACE_TOP, "textures/voxel/oak_wood_top.png"); + builder.set_surface_material(world::VMAT_WOOD); + oak_log = world::voxel_registry::register_voxel(builder); + + builder = world::VoxelBuilder("glass"); + builder.add_default_texture("textures/voxel/glass_01.png"); + builder.set_render_mode(world::VRENDER_BLEND); + builder.set_surface_material(world::VMAT_GLASS); + glass = world::voxel_registry::register_voxel(builder); + + builder = world::VoxelBuilder("slime"); + builder.add_default_texture("textures/voxel/slime_01.png"); + builder.set_render_mode(world::VRENDER_BLEND); + builder.set_surface_material(world::VMAT_SLOSH); + builder.set_touch_type(world::VTOUCH_BOUNCE).set_touch_values({ 0.00f, 0.60f, 0.00f }); + slime = world::voxel_registry::register_voxel(builder); } diff --git a/game/shared/game_voxels.hh b/game/shared/game_voxels.hh index 7b24ec4..68e599e 100644 --- a/game/shared/game_voxels.hh +++ b/game/shared/game_voxels.hh @@ -20,7 +20,6 @@ extern const world::Voxel* oak_planks; extern const world::Voxel* oak_log; extern const world::Voxel* glass; extern const world::Voxel* slime; -extern const world::Voxel* mud; } // namespace game_voxels namespace game_voxels diff --git a/game/shared/world/CMakeLists.txt b/game/shared/world/CMakeLists.txt index 15d5b59..cc7ed90 100644 --- a/game/shared/world/CMakeLists.txt +++ b/game/shared/world/CMakeLists.txt @@ -13,4 +13,6 @@ target_sources(shared PRIVATE "${CMAKE_CURRENT_LIST_DIR}/voxel_registry.cc" "${CMAKE_CURRENT_LIST_DIR}/voxel_registry.hh" "${CMAKE_CURRENT_LIST_DIR}/voxel_storage.cc" - "${CMAKE_CURRENT_LIST_DIR}/voxel_storage.hh") + "${CMAKE_CURRENT_LIST_DIR}/voxel_storage.hh" + "${CMAKE_CURRENT_LIST_DIR}/voxel.cc" + "${CMAKE_CURRENT_LIST_DIR}/voxel.hh") diff --git a/game/shared/world/feature.cc b/game/shared/world/feature.cc index cbf660e..e704ced 100644 --- a/game/shared/world/feature.cc +++ b/game/shared/world/feature.cc @@ -4,6 +4,7 @@ #include "shared/world/chunk.hh" #include "shared/world/dimension.hh" +#include "shared/world/voxel.hh" #include "shared/coord.hh" @@ -50,3 +51,30 @@ void world::Feature::place(const voxel_pos& vpos, const chunk_pos& cpos, Chunk& } } } + +void world::Feature::place(const voxel_pos& vpos, const chunk_pos& cpos, VoxelStorage& voxels) const +{ + for(const auto [rpos, voxel, overwrite] : (*this)) { + auto it_vpos = vpos + rpos; + auto it_cpos = coord::to_chunk(it_vpos); + + if(it_cpos == cpos) { + auto it_lpos = coord::to_local(it_vpos); + auto it_index = coord::to_index(it_lpos); + + if(voxels[it_index] && !overwrite) { + // There is something in the way + // and the called intentionally requested + // we do not force feature to overwrite voxels + continue; + } + + if(voxel == nullptr) { + voxels[it_index] = NULL_VOXEL_ID; + } + else { + voxels[it_index] = voxel->get_id(); + } + } + } +} diff --git a/game/shared/world/feature.hh b/game/shared/world/feature.hh index 09b913e..a543632 100644 --- a/game/shared/world/feature.hh +++ b/game/shared/world/feature.hh @@ -7,6 +7,7 @@ namespace world class Chunk; class Dimension; class Voxel; +class VoxelStorage; } // namespace world namespace world @@ -19,5 +20,6 @@ public: public: void place(const voxel_pos& vpos, Dimension* dimension) const; void place(const voxel_pos& vpos, const chunk_pos& cpos, Chunk& chunk) const; + void place(const voxel_pos& vpos, const chunk_pos& cpos, VoxelStorage& voxels) const; }; } // namespace world diff --git a/game/shared/world/item_registry.cc b/game/shared/world/item_registry.cc index d1b9ff4..783c85e 100644 --- a/game/shared/world/item_registry.cc +++ b/game/shared/world/item_registry.cc @@ -14,7 +14,7 @@ world::ItemInfoBuilder::ItemInfoBuilder(std::string_view name) { prototype.name = name; prototype.texture = std::string(); - prototype.place_voxel = NULL_VOXEL_ID; + prototype.place_voxel = nullptr; prototype.cached_texture = nullptr; } @@ -25,7 +25,7 @@ world::ItemInfoBuilder& world::ItemInfoBuilder::set_texture(std::string_view tex return *this; } -world::ItemInfoBuilder& world::ItemInfoBuilder::set_place_voxel(voxel_id place_voxel) +world::ItemInfoBuilder& world::ItemInfoBuilder::set_place_voxel(const Voxel* place_voxel) { prototype.place_voxel = place_voxel; return *this; @@ -99,7 +99,10 @@ std::uint64_t world::item_registry::calculate_checksum(void) for(const auto& info : world::item_registry::items) { result = math::crc64(info->name, result); - result += static_cast(info->place_voxel); + + if(info->place_voxel) { + result += static_cast(info->place_voxel->get_id()); + } } return result; diff --git a/game/shared/world/item_registry.hh b/game/shared/world/item_registry.hh index c3e6cf9..2274da2 100644 --- a/game/shared/world/item_registry.hh +++ b/game/shared/world/item_registry.hh @@ -9,12 +9,17 @@ // anywhere else in the shared and server code struct TextureGUI; +namespace world +{ +class Voxel; +} // namespace world + namespace world { struct ItemInfo final { std::string name; std::string texture; - voxel_id place_voxel; + const Voxel* place_voxel; resource_ptr cached_texture; // Client-side only }; @@ -29,7 +34,7 @@ public: public: ItemInfoBuilder& set_texture(std::string_view texture); - ItemInfoBuilder& set_place_voxel(voxel_id place_voxel); + ItemInfoBuilder& set_place_voxel(const Voxel* place_voxel); public: item_id build(void) const; diff --git a/game/shared/world/voxel.cc b/game/shared/world/voxel.cc index d51b5a1..1cd1504 100644 --- a/game/shared/world/voxel.cc +++ b/game/shared/world/voxel.cc @@ -6,24 +6,30 @@ #include "shared/world/dimension.hh" -void world::Voxel::on_place(Dimension* dimension, const voxel_pos& vpos) const +world::Voxel::Voxel(const Voxel& source, voxel_id id) noexcept : Voxel(source) { - // empty + m_id = id; } -void world::Voxel::on_remove(Dimension* dimension, const voxel_pos& vpos) const +void world::Voxel::on_place(Dimension* dimension, const voxel_pos& vpos) const { - // empty + if(m_on_place) { + m_on_place(dimension, vpos); + } } -void world::Voxel::on_tick(Dimension* dimension, const voxel_pos& vpos) const +void world::Voxel::on_remove(Dimension* dimension, const voxel_pos& vpos) const { - // empty + if(m_on_remove) { + m_on_remove(dimension, vpos); + } } -void world::Voxel::set_id(voxel_id id) noexcept +void world::Voxel::on_tick(Dimension* dimension, const voxel_pos& vpos) const { - m_id = id; + if(m_on_tick) { + m_on_tick(dimension, vpos); + } } std::size_t world::Voxel::get_random_texture_index(VoxelFace face, const voxel_pos& vpos) const @@ -57,64 +63,113 @@ std::uint64_t world::Voxel::calculate_checksum(std::uint64_t combine) const return combine; } -std::shared_ptr world::Voxel::clone(void) const +world::VoxelBuilder::VoxelBuilder(std::string_view name) +{ + set_name(name); +} + +world::VoxelBuilder& world::VoxelBuilder::set_on_place(VoxelOnPlaceFunc func) noexcept +{ + m_on_place = std::move(func); + + return *this; +} + +world::VoxelBuilder& world::VoxelBuilder::set_on_remove(VoxelOnRemoveFunc func) noexcept +{ + m_on_remove = std::move(func); + + return *this; +} + +world::VoxelBuilder& world::VoxelBuilder::set_on_tick(VoxelOnTickFunc func) noexcept { - return std::make_shared(*this); + m_on_tick = std::move(func); + + return *this; } -void world::Voxel::set_name(std::string_view name) noexcept +world::VoxelBuilder& world::VoxelBuilder::set_name(std::string_view name) noexcept { assert(name.size()); m_name = name; + + return *this; } -void world::Voxel::set_render_mode(VoxelRender mode) noexcept +world::VoxelBuilder& world::VoxelBuilder::set_render_mode(VoxelRender mode) noexcept { m_render_mode = mode; + + return *this; } -void world::Voxel::set_shape(VoxelShape shape) noexcept +world::VoxelBuilder& world::VoxelBuilder::set_shape(VoxelShape shape) noexcept { m_shape = shape; + + return *this; } -void world::Voxel::set_animated(bool animated) noexcept +world::VoxelBuilder& world::VoxelBuilder::set_animated(bool animated) noexcept { m_animated = animated; + + return *this; } -void world::Voxel::set_touch_type(VoxelTouch type) noexcept +world::VoxelBuilder& world::VoxelBuilder::set_touch_type(VoxelTouch type) noexcept { m_touch_type = type; + + return *this; } -void world::Voxel::set_touch_values(const glm::fvec3& values) noexcept +world::VoxelBuilder& world::VoxelBuilder::set_touch_values(const glm::fvec3& values) noexcept { m_touch_values = values; + + return *this; } -void world::Voxel::set_surface_material(VoxelMaterial material) noexcept +world::VoxelBuilder& world::VoxelBuilder::set_surface_material(VoxelMaterial material) noexcept { m_surface_material = material; + + return *this; } -void world::Voxel::set_collision(const math::AABBf& box) noexcept +world::VoxelBuilder& world::VoxelBuilder::set_collision(const math::AABBf& box) noexcept { m_collision = box; + + return *this; } -void world::Voxel::add_default_texture(std::string_view path) +world::VoxelBuilder& world::VoxelBuilder::add_default_texture(std::string_view path) { assert(path.size()); m_default_textures.emplace_back(path); + + return *this; } -void world::Voxel::add_face_texture(VoxelFace face, std::string_view path) +world::VoxelBuilder& world::VoxelBuilder::add_face_texture(VoxelFace face, std::string_view path) { assert(face < m_face_textures.size()); assert(path.size()); m_face_textures[face].emplace_back(path); + + return *this; +} + +std::unique_ptr world::VoxelBuilder::build(voxel_id id) const +{ + assert(m_name.size()); + assert(id); + + return std::make_unique(*this, id); } diff --git a/game/shared/world/voxel.hh b/game/shared/world/voxel.hh index 0bd83b7..9cc0e4c 100644 --- a/game/shared/world/voxel.hh +++ b/game/shared/world/voxel.hh @@ -11,19 +11,19 @@ class Dimension; namespace world { -enum VoxelRender : unsigned short { +enum VoxelRender : unsigned int { VRENDER_NONE = 0U, ///< The voxel is not rendered at all VRENDER_OPAQUE, ///< The voxel is fully opaque VRENDER_BLEND, ///< The voxel is blended (e.g. water, glass) }; -enum VoxelShape : unsigned short { +enum VoxelShape : unsigned int { VSHAPE_CUBE = 0U, ///< Full cube shape VSHAPE_CROSS, ///< TODO: Cross shape VSHAPE_MODEL, ///< TODO: Custom model shape }; -enum VoxelFace : unsigned short { +enum VoxelFace : unsigned int { VFACE_NORTH = 0U, ///< Positive Z face VFACE_SOUTH, ///< Negative Z face VFACE_EAST, ///< Positive X face @@ -35,14 +35,15 @@ enum VoxelFace : unsigned short { VFACE_COUNT }; -enum VoxelTouch : unsigned short { +enum VoxelTouch : unsigned int { VTOUCH_NONE = 0xFFFFU, VTOUCH_SOLID = 0U, ///< The entity is stopped in its tracks VTOUCH_BOUNCE, ///< The entity bounces back with some energy loss VTOUCH_SINK, ///< The entity phases/sinks through the voxel }; -enum VoxelMaterial : unsigned short { +enum VoxelMaterial : unsigned int { + VMAT_UNKNOWN = 0xFFFFU, VMAT_DEFAULT = 0U, VMAT_STONE, VMAT_DIRT, @@ -56,7 +57,7 @@ enum VoxelMaterial : unsigned short { VMAT_COUNT }; -enum VoxelVisBits : unsigned short { +enum VoxelVisBits : unsigned int { VVIS_NORTH = 1U << VFACE_NORTH, ///< Positive Z VVIS_SOUTH = 1U << VFACE_SOUTH, ///< Negative Z VVIS_EAST = 1U << VFACE_EAST, ///< Positive X @@ -66,28 +67,26 @@ enum VoxelVisBits : unsigned short { }; } // namespace world +namespace world +{ +using VoxelOnPlaceFunc = std::function; +using VoxelOnRemoveFunc = std::function; +using VoxelOnTickFunc = std::function; +} // namespace world + namespace world { class Voxel { public: - /// Called when the voxel is placed in the world - /// @param dimension The dimension the voxel is placed in - /// @param vpos The absolute voxel position where the voxel is placed - virtual void on_place(Dimension* dimension, const voxel_pos& vpos) const; - - /// Called when the voxel is removed from the world - /// @param dimension The dimension the voxel is removed from - /// @param vpos The absolute voxel position where the voxel is removed - virtual void on_remove(Dimension* dimension, const voxel_pos& vpos) const; + Voxel(void) = default; + explicit Voxel(const Voxel& source, voxel_id id) noexcept; - /// Called when the voxel is ticked by something - /// @param dimension The dimension the voxel is ticked in - /// @param vpos The absolute voxel position where the voxel is ticked - virtual void on_tick(Dimension* dimension, const voxel_pos& vpos) const; + void on_place(Dimension* dimension, const voxel_pos& vpos) const; + void on_remove(Dimension* dimension, const voxel_pos& vpos) const; + void on_tick(Dimension* dimension, const voxel_pos& vpos) const; constexpr std::string_view get_name(void) const noexcept; constexpr voxel_id get_id(void) const noexcept; - void set_id(voxel_id id) noexcept; constexpr VoxelRender get_render_mode(void) const noexcept; constexpr VoxelShape get_shape(void) const noexcept; @@ -104,6 +103,15 @@ public: constexpr std::size_t get_cached_face_offset(VoxelFace face) const noexcept; constexpr std::size_t get_cached_face_plane(VoxelFace face) const noexcept; + template + constexpr bool is_render_mode(void) const noexcept; + template + constexpr bool is_shape(void) const noexcept; + template + constexpr bool is_touch_type(void) const noexcept; + template + constexpr bool is_surface_material(void) const noexcept; + /// Non-model voxel shapes support texture variation based on the /// voxel position on the world; this method handles the math behind this /// @param face The face of the voxel to get the texture index for @@ -123,27 +131,7 @@ public: /// @return The calculated checksum std::uint64_t calculate_checksum(std::uint64_t combine = 0U) const; - /// Produce a copy of itself as a shared pointer; the voxel registry - /// does not change the owner of a voxel instance it registers - virtual std::shared_ptr clone(void) const; - protected: - void set_name(std::string_view name) noexcept; - - void set_render_mode(VoxelRender mode) noexcept; - void set_shape(VoxelShape shape) noexcept; - void set_animated(bool animated) noexcept; - - void set_touch_type(VoxelTouch type) noexcept; - void set_touch_values(const glm::fvec3& values) noexcept; - void set_surface_material(VoxelMaterial material) noexcept; - - void set_collision(const math::AABBf& box) noexcept; - - void add_default_texture(std::string_view path); - void add_face_texture(VoxelFace face, std::string_view path); - -private: std::string m_name; voxel_id m_id { NULL_VOXEL_ID }; @@ -161,6 +149,40 @@ private: std::array, VFACE_COUNT> m_face_textures; std::array m_cached_face_offsets; std::array m_cached_face_planes; + + VoxelOnPlaceFunc m_on_place; + VoxelOnRemoveFunc m_on_remove; + VoxelOnTickFunc m_on_tick; +}; +} // namespace world + +namespace world +{ +class VoxelBuilder final : public Voxel { +public: + VoxelBuilder(void) = default; + explicit VoxelBuilder(std::string_view name); + + VoxelBuilder& set_on_place(VoxelOnPlaceFunc func) noexcept; + VoxelBuilder& set_on_remove(VoxelOnRemoveFunc func) noexcept; + VoxelBuilder& set_on_tick(VoxelOnTickFunc func) noexcept; + + VoxelBuilder& set_name(std::string_view name) noexcept; + + VoxelBuilder& set_render_mode(VoxelRender mode) noexcept; + VoxelBuilder& set_shape(VoxelShape shape) noexcept; + VoxelBuilder& set_animated(bool animated) noexcept; + + VoxelBuilder& set_touch_type(VoxelTouch type) noexcept; + VoxelBuilder& set_touch_values(const glm::fvec3& values) noexcept; + VoxelBuilder& set_surface_material(VoxelMaterial material) noexcept; + + VoxelBuilder& set_collision(const math::AABBf& box) noexcept; + + VoxelBuilder& add_default_texture(std::string_view path); + VoxelBuilder& add_face_texture(VoxelFace face, std::string_view path); + + std::unique_ptr build(voxel_id id) const; }; } // namespace world @@ -218,6 +240,10 @@ constexpr const std::vector& world::Voxel::get_face_textures(VoxelF { assert(face <= m_face_textures.size()); + if(m_face_textures[face].empty()) { + return m_default_textures; + } + return m_face_textures[face]; } @@ -234,3 +260,27 @@ constexpr std::size_t world::Voxel::get_cached_face_plane(VoxelFace face) const return m_cached_face_planes[face]; } + +template +constexpr bool world::Voxel::is_render_mode(void) const noexcept +{ + return m_render_mode == RenderMode; +} + +template +constexpr bool world::Voxel::is_shape(void) const noexcept +{ + return m_shape == Shape; +} + +template +constexpr bool world::Voxel::is_touch_type(void) const noexcept +{ + return m_touch_type == TouchType; +} + +template +constexpr bool world::Voxel::is_surface_material(void) const noexcept +{ + return m_surface_material == Material; +} diff --git a/game/shared/world/voxel_registry.cc b/game/shared/world/voxel_registry.cc index 4c2f360..97485e6 100644 --- a/game/shared/world/voxel_registry.cc +++ b/game/shared/world/voxel_registry.cc @@ -4,7 +4,7 @@ static std::uint64_t registry_checksum = 0U; emhash8::HashMap world::voxel_registry::names; -std::vector> world::voxel_registry::voxels; +std::vector> world::voxel_registry::voxels; static void recalculate_checksum(void) { @@ -15,17 +15,15 @@ static void recalculate_checksum(void) } } -world::Voxel* world::voxel_registry::register_voxel(const Voxel& voxel_template) +world::Voxel* world::voxel_registry::register_voxel(const VoxelBuilder& builder) { - assert(voxel_template.get_name().size()); - assert(nullptr == find(voxel_template.get_name())); + assert(builder.get_name().size()); + assert(nullptr == find(builder.get_name())); - const auto id = static_cast(voxels.size()); + const auto id = static_cast(1 + voxels.size()); - auto voxel = voxel_template.clone(); - voxel->set_id(id); - - names.emplace(std::string(voxel_template.get_name()), id); + std::unique_ptr voxel(builder.build(id)); + names.emplace(std::string(builder.get_name()), id); voxels.push_back(std::move(voxel)); recalculate_checksum(); @@ -35,7 +33,7 @@ world::Voxel* world::voxel_registry::register_voxel(const Voxel& voxel_template) world::Voxel* world::voxel_registry::find(std::string_view name) { - const auto it = names.find(name); + const auto it = names.find(std::string(name)); if(it == names.end()) { return nullptr; @@ -46,11 +44,11 @@ world::Voxel* world::voxel_registry::find(std::string_view name) world::Voxel* world::voxel_registry::find(voxel_id id) { - if(id >= voxels.size()) { + if(id == NULL_VOXEL_ID || id > voxels.size()) { return nullptr; } - return voxels[id].get(); + return voxels[id - 1].get(); } void world::voxel_registry::purge(void) diff --git a/game/shared/world/voxel_registry.hh b/game/shared/world/voxel_registry.hh index b9d9a34..5ba2aed 100644 --- a/game/shared/world/voxel_registry.hh +++ b/game/shared/world/voxel_registry.hh @@ -5,12 +5,12 @@ namespace world::voxel_registry { extern emhash8::HashMap names; -extern std::vector> voxels; +extern std::vector> voxels; } // namespace world::voxel_registry namespace world::voxel_registry { -Voxel* register_voxel(const Voxel& voxel_template); +Voxel* register_voxel(const VoxelBuilder& builder); Voxel* find(std::string_view name); Voxel* find(voxel_id id); } // namespace world::voxel_registry diff --git a/game/shared/world/voxels/generic.hh b/game/shared/world/voxels/generic.hh deleted file mode 100644 index a661792..0000000 --- a/game/shared/world/voxels/generic.hh +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include "shared/world/voxel.hh" - -namespace world::voxels -{ -class GenericCube final : public Voxel { -public: - template - requires(std::is_convertible_v && ...) - explicit GenericCube(std::string_view name, VoxelRender render_mode, bool animated, VoxelMaterial surface_material, VoxelTouch touch, - const glm::fvec3& touch_values, TexturesT&&... textures) noexcept - { - set_name(name); - - set_shape(VoxelShape::CUBE); - set_render_mode(render_mode); - set_animated(animated); - - set_surface_material(surface_material); - set_touch_values(touch_values); - set_touch_type(touch); - - add_texture_default(std::forward(textures)...); - } -}; -} // namespace world::voxels -- cgit From 73cbcdd6e8c849e32abbf9757e603e6a6654e870 Mon Sep 17 00:00:00 2001 From: untodesu Date: Fri, 12 Sep 2025 14:09:34 +0500 Subject: Metaitems --- game/client/experiments.cc | 9 ++- game/client/game.cc | 4 +- game/client/gui/hotbar.cc | 19 +++-- game/client/gui/hotbar.hh | 9 ++- game/client/gui/status_lines.cc | 4 +- game/client/gui/status_lines.hh | 2 +- game/client/session.cc | 4 +- game/server/sessions.cc | 2 +- game/shared/game_items.cc | 91 +++++++++++---------- game/shared/game_items.hh | 24 +++--- game/shared/game_voxels.cc | 154 ++++++++++++++++++------------------ game/shared/world/CMakeLists.txt | 2 + game/shared/world/chunk.cc | 7 +- game/shared/world/feature.cc | 7 +- game/shared/world/item.cc | 55 +++++++++++++ game/shared/world/item.hh | 84 ++++++++++++++++++++ game/shared/world/item_registry.cc | 101 +++++++---------------- game/shared/world/item_registry.hh | 55 ++----------- game/shared/world/voxel.cc | 54 ++++--------- game/shared/world/voxel.hh | 28 +++---- game/shared/world/voxel_registry.cc | 4 +- 21 files changed, 373 insertions(+), 346 deletions(-) create mode 100644 game/shared/world/item.cc create mode 100644 game/shared/world/item.hh (limited to 'game') diff --git a/game/client/experiments.cc b/game/client/experiments.cc index b947012..8b0b526 100644 --- a/game/client/experiments.cc +++ b/game/client/experiments.cc @@ -73,9 +73,12 @@ void experiments::attack(void) void experiments::interact(void) { - if(auto info = world::item_registry::find(gui::hotbar::slots[gui::hotbar::active_slot])) { - if(info->place_voxel) { - globals::dimension->set_voxel(info->place_voxel, world::player_target::coord + world::player_target::normal); + auto active_item = gui::hotbar::slots[gui::hotbar::active_slot]; + + if(active_item) { + if(auto place_voxel = active_item->get_place_voxel()) { + globals::dimension->set_voxel(place_voxel, world::player_target::coord + world::player_target::normal); + return; } } } diff --git a/game/client/game.cc b/game/client/game.cc index 257bd2d..d61ce84 100644 --- a/game/client/game.cc +++ b/game/client/game.cc @@ -412,8 +412,8 @@ void client_game::init_late(void) world::voxel_atlas::generate_mipmaps(); - for(std::shared_ptr& info : world::item_registry::items) { - info->cached_texture = resource::load(info->texture.c_str(), TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T); + for(auto& item : world::item_registry::items) { + item->set_cached_texture(resource::load(item->get_texture(), TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T)); } experiments::init_late(); diff --git a/game/client/gui/hotbar.cc b/game/client/gui/hotbar.cc index 806d82b..e9458dd 100644 --- a/game/client/gui/hotbar.cc +++ b/game/client/gui/hotbar.cc @@ -25,7 +25,7 @@ constexpr static float SELECTOR_PADDING = 1.0f; constexpr static float HOTBAR_PADDING = 2.0f; unsigned int gui::hotbar::active_slot = 0U; -item_id gui::hotbar::slots[HOTBAR_SIZE]; +std::array gui::hotbar::slots = {}; static config::KeyBind hotbar_keys[HOTBAR_SIZE]; @@ -40,14 +40,13 @@ static ImU32 get_color_alpha(ImGuiCol style_color, float alpha) static void update_hotbar_item(void) { - if(gui::hotbar::slots[gui::hotbar::active_slot] == NULL_ITEM_ID) { + auto current_item = gui::hotbar::slots[gui::hotbar::active_slot]; + + if(current_item == nullptr) { gui::status_lines::unset(gui::STATUS_HOTBAR); - return; } - - if(auto info = world::item_registry::find(gui::hotbar::slots[gui::hotbar::active_slot])) { - gui::status_lines::set(gui::STATUS_HOTBAR, info->name, ImVec4(1.0f, 1.0f, 1.0f, 1.0f), 5.0f); - return; + else { + gui::status_lines::set(gui::STATUS_HOTBAR, current_item->get_name(), ImVec4(1.0f, 1.0f, 1.0f, 1.0f), 5.0f); } } @@ -154,9 +153,9 @@ void gui::hotbar::layout(void) // Draw individual item textures in the hotbar for(std::size_t i = 0; i < HOTBAR_SIZE; ++i) { - const auto info = world::item_registry::find(gui::hotbar::slots[i]); + auto item = gui::hotbar::slots[i]; - if((info == nullptr) || (info->cached_texture == nullptr)) { + if((item == nullptr) || (item->get_cached_texture() == nullptr)) { // There's either no item in the slot // or the item doesn't have a texture continue; @@ -164,7 +163,7 @@ void gui::hotbar::layout(void) const auto item_start = ImVec2(background_start.x + i * item_size + item_padding_a, background_start.y + item_padding_a); const auto item_end = ImVec2(item_start.x + item_size - item_padding_b, item_start.y + item_size - item_padding_b); - draw_list->AddImage(info->cached_texture->handle, item_start, item_end); + draw_list->AddImage(item->get_cached_texture()->handle, item_start, item_end); } } diff --git a/game/client/gui/hotbar.hh b/game/client/gui/hotbar.hh index 88ce791..223dbc9 100644 --- a/game/client/gui/hotbar.hh +++ b/game/client/gui/hotbar.hh @@ -1,16 +1,19 @@ #pragma once -#include "shared/types.hh" - // TODO: design an inventory system and an item // registry and integrate the hotbar into that system +namespace world +{ +class Item; +} // namespace world + constexpr static unsigned int HOTBAR_SIZE = 9U; namespace gui::hotbar { extern unsigned int active_slot; -extern item_id slots[HOTBAR_SIZE]; +extern std::array slots; } // namespace gui::hotbar namespace gui::hotbar diff --git a/game/client/gui/status_lines.cc b/game/client/gui/status_lines.cc index d1a919a..c146478 100644 --- a/game/client/gui/status_lines.cc +++ b/game/client/gui/status_lines.cc @@ -65,11 +65,11 @@ void gui::status_lines::layout(void) } } -void gui::status_lines::set(unsigned int line, const std::string& text, const ImVec4& color, float fadeout) +void gui::status_lines::set(unsigned int line, std::string_view text, const ImVec4& color, float fadeout) { line_text_colors[line] = ImVec4(color.x, color.y, color.z, color.w); line_shadow_colors[line] = ImVec4(color.x * 0.1f, color.y * 0.1f, color.z * 0.1f, color.w); - line_strings[line] = std::string(text); + line_strings[line] = text; line_spawns[line] = globals::curtime; line_fadeouts[line] = fadeout; } diff --git a/game/client/gui/status_lines.hh b/game/client/gui/status_lines.hh index 7245d68..f694fd3 100644 --- a/game/client/gui/status_lines.hh +++ b/game/client/gui/status_lines.hh @@ -16,6 +16,6 @@ void layout(void); namespace gui::status_lines { -void set(unsigned int line, const std::string& text, const ImVec4& color, float fadeout); +void set(unsigned int line, std::string_view text, const ImVec4& color, float fadeout); void unset(unsigned int line); } // namespace gui::status_lines diff --git a/game/client/session.cc b/game/client/session.cc index c33b4d9..907b789 100644 --- a/game/client/session.cc +++ b/game/client/session.cc @@ -127,7 +127,7 @@ static void on_voxel_set(const world::VoxelSetEvent& event) // FIXME: should we also validate things here or wait for the server to do so protocol::SetVoxel packet; packet.vpos = coord::to_voxel(event.cpos, event.lpos); - packet.voxel = event.voxel->get_id(); + packet.voxel = event.voxel ? event.voxel->get_id() : NULL_VOXEL_ID; protocol::send(session::peer, protocol::encode(packet)); } @@ -286,7 +286,7 @@ void session::send_login_request(void) protocol::LoginRequest packet; packet.version = protocol::VERSION; packet.voxel_registry_checksum = world::voxel_registry::get_checksum(); - packet.item_registry_checksum = world::item_registry::calculate_checksum(); + packet.item_registry_checksum = world::item_registry::get_checksum(); packet.password_hash = server_password_hash; packet.username = client_game::username.get(); diff --git a/game/server/sessions.cc b/game/server/sessions.cc index 32b96a3..2ab9d74 100644 --- a/game/server/sessions.cc +++ b/game/server/sessions.cc @@ -72,7 +72,7 @@ static void on_login_request_packet(const protocol::LoginRequest& packet) return; } - if(packet.item_registry_checksum != world::item_registry::calculate_checksum()) { + if(packet.item_registry_checksum != world::item_registry::get_checksum()) { protocol::Disconnect response; response.reason = "protocol.item_registry_checksum"; protocol::send(packet.peer, protocol::encode(response)); diff --git a/game/shared/game_items.cc b/game/shared/game_items.cc index 6073117..cad73da 100644 --- a/game/shared/game_items.cc +++ b/game/shared/game_items.cc @@ -6,61 +6,60 @@ #include "shared/game_voxels.hh" -item_id game_items::stone = NULL_ITEM_ID; -item_id game_items::cobblestone = NULL_ITEM_ID; -item_id game_items::dirt = NULL_ITEM_ID; -item_id game_items::grass = NULL_ITEM_ID; -item_id game_items::oak_leaves = NULL_ITEM_ID; -item_id game_items::oak_planks = NULL_ITEM_ID; -item_id game_items::oak_log = NULL_ITEM_ID; -item_id game_items::glass = NULL_ITEM_ID; -item_id game_items::slime = NULL_ITEM_ID; -item_id game_items::mud = NULL_ITEM_ID; +const world::Item* game_items::stone = nullptr; +const world::Item* game_items::cobblestone = nullptr; +const world::Item* game_items::dirt = nullptr; +const world::Item* game_items::grass = nullptr; +const world::Item* game_items::oak_leaves = nullptr; +const world::Item* game_items::oak_planks = nullptr; +const world::Item* game_items::oak_log = nullptr; +const world::Item* game_items::glass = nullptr; +const world::Item* game_items::slime = nullptr; void game_items::populate(void) { - // Stone; a hardened slate rock - game_items::stone = - world::item_registry::construct("stone").set_texture("textures/item/stone.png").set_place_voxel(game_voxels::stone).build(); + auto stone_builder = world::ItemBuilder("stone"); + stone_builder.set_texture("textures/item/stone.png"); + stone_builder.set_place_voxel(game_voxels::stone); + stone = world::item_registry::register_item(stone_builder); - // Cobblestone; a bunch of small stones - game_items::cobblestone = world::item_registry::construct("cobblestone") - .set_texture("textures/item/cobblestone.png") - .set_place_voxel(game_voxels::cobblestone) - .build(); + auto cobblestone_builder = world::ItemBuilder("cobblestone"); + cobblestone_builder.set_texture("textures/item/cobblestone.png"); + cobblestone_builder.set_place_voxel(game_voxels::cobblestone); + cobblestone = world::item_registry::register_item(cobblestone_builder); - // Dirt; it's very dirty - game_items::dirt = - world::item_registry::construct("dirt").set_texture("textures/item/dirt.png").set_place_voxel(game_voxels::dirt).build(); + auto dirt_builder = world::ItemBuilder("dirt"); + dirt_builder.set_texture("textures/item/dirt.png"); + dirt_builder.set_place_voxel(game_voxels::dirt); + dirt = world::item_registry::register_item(dirt_builder); - // Grass; literally just grassy dirt - game_items::grass = - world::item_registry::construct("grass").set_texture("textures/item/grass.png").set_place_voxel(game_voxels::grass).build(); + auto grass_builder = world::ItemBuilder("grass"); + grass_builder.set_texture("textures/item/grass.png"); + grass_builder.set_place_voxel(game_voxels::grass); + grass = world::item_registry::register_item(grass_builder); - // Oak leaves; they're bushy! - game_items::oak_leaves = world::item_registry::construct("oak_leaves") - .set_texture("textures/item/oak_leaves.png") - .set_place_voxel(game_voxels::oak_leaves) - .build(); + auto oak_leaves_builder = world::ItemBuilder("oak_leaves"); + oak_leaves_builder.set_texture("textures/item/oak_leaves.png"); + oak_leaves_builder.set_place_voxel(game_voxels::oak_leaves); + oak_leaves = world::item_registry::register_item(oak_leaves_builder); - // Oak planks; watch for splinters! - game_items::oak_planks = world::item_registry::construct("oak_planks") - .set_texture("textures/item/oak_planks.png") - .set_place_voxel(game_voxels::oak_planks) - .build(); + auto oak_planks_builder = world::ItemBuilder("oak_planks"); + oak_planks_builder.set_texture("textures/item/oak_planks.png"); + oak_planks_builder.set_place_voxel(game_voxels::oak_planks); + oak_planks = world::item_registry::register_item(oak_planks_builder); - // Oak log; a big wad of wood - game_items::oak_log = - world::item_registry::construct("oak_log").set_texture("textures/item/oak_log.png").set_place_voxel(game_voxels::oak_log).build(); + auto oak_log_builder = world::ItemBuilder("oak_log"); + oak_log_builder.set_texture("textures/item/oak_log.png"); + oak_log_builder.set_place_voxel(game_voxels::oak_log); + oak_log = world::item_registry::register_item(oak_log_builder); - // Glass; used for windowing - game_items::glass = - world::item_registry::construct("glass").set_texture("textures/item/glass.png").set_place_voxel(game_voxels::glass).build(); + auto glass_builder = world::ItemBuilder("glass"); + glass_builder.set_texture("textures/item/glass.png"); + glass_builder.set_place_voxel(game_voxels::glass); + glass = world::item_registry::register_item(glass_builder); - // Slime; it's bouncy! - game_items::slime = - world::item_registry::construct("slime").set_texture("textures/item/slime.png").set_place_voxel(game_voxels::slime).build(); - - // Mud; you sink in it! - game_items::mud = world::item_registry::construct("mud").set_texture("textures/item/mud.png").build(); + auto slime_builder = world::ItemBuilder("slime"); + slime_builder.set_texture("textures/item/slime.png"); + slime_builder.set_place_voxel(game_voxels::slime); + slime = world::item_registry::register_item(slime_builder); } diff --git a/game/shared/game_items.hh b/game/shared/game_items.hh index 099923f..88952fc 100644 --- a/game/shared/game_items.hh +++ b/game/shared/game_items.hh @@ -2,20 +2,22 @@ #define SHARED_GAME_ITEMS 1 #pragma once -#include "shared/types.hh" +namespace world +{ +class Item; +} // namespace world namespace game_items { -extern item_id stone; -extern item_id cobblestone; -extern item_id dirt; -extern item_id grass; -extern item_id oak_leaves; -extern item_id oak_planks; -extern item_id oak_log; -extern item_id glass; -extern item_id slime; -extern item_id mud; +extern const world::Item* stone; +extern const world::Item* cobblestone; +extern const world::Item* dirt; +extern const world::Item* grass; +extern const world::Item* oak_leaves; +extern const world::Item* oak_planks; +extern const world::Item* oak_log; +extern const world::Item* glass; +extern const world::Item* slime; } // namespace game_items namespace game_items diff --git a/game/shared/game_voxels.cc b/game/shared/game_voxels.cc index 75c56c7..f262c23 100644 --- a/game/shared/game_voxels.cc +++ b/game/shared/game_voxels.cc @@ -61,81 +61,81 @@ static void dirt_tick(world::Dimension* dimension, const voxel_pos& vpos) void game_voxels::populate(void) { - world::VoxelBuilder builder; - - builder = world::VoxelBuilder("stone"); - builder.add_default_texture("textures/voxel/stone_01.png") - .add_default_texture("textures/voxel/stone_02.png") - .add_default_texture("textures/voxel/stone_03.png") - .add_default_texture("textures/voxel/stone_04.png"); - stone = world::voxel_registry::register_voxel(builder); - - builder = world::VoxelBuilder("cobblestone"); - builder.add_default_texture("textures/voxel/cobblestone_01.png").add_default_texture("textures/voxel/cobblestone_02.png"); - cobblestone = world::voxel_registry::register_voxel(builder); - - builder = world::VoxelBuilder("dirt"); - builder.add_default_texture("textures/voxel/dirt_01.png") - .add_default_texture("textures/voxel/dirt_02.png") - .add_default_texture("textures/voxel/dirt_03.png") - .add_default_texture("textures/voxel/dirt_04.png"); - builder.set_surface_material(world::VMAT_DIRT); - builder.set_on_tick(&dirt_tick); - dirt = world::voxel_registry::register_voxel(builder); - - builder = world::VoxelBuilder("grass"); - builder.add_default_texture("textures/voxel/grass_side_01.png") - .add_default_texture("textures/voxel/grass_side_02.png") - .add_face_texture(world::VFACE_BOTTOM, "textures/voxel/dirt_01.png") - .add_face_texture(world::VFACE_BOTTOM, "textures/voxel/dirt_02.png") - .add_face_texture(world::VFACE_BOTTOM, "textures/voxel/dirt_03.png") - .add_face_texture(world::VFACE_BOTTOM, "textures/voxel/dirt_04.png") - .add_face_texture(world::VFACE_TOP, "textures/voxel/grass_01.png") - .add_face_texture(world::VFACE_TOP, "textures/voxel/grass_02.png"); - builder.set_surface_material(world::VMAT_GRASS); - grass = world::voxel_registry::register_voxel(builder); - - builder = world::VoxelBuilder("vtest"); - builder.add_default_texture("textures/voxel/vtest_F1.png") - .add_default_texture("textures/voxel/vtest_F2.png") - .add_default_texture("textures/voxel/vtest_F3.png") - .add_default_texture("textures/voxel/vtest_F4.png"); - builder.set_animated(true); - vtest = world::voxel_registry::register_voxel(builder); - - builder = world::VoxelBuilder("vtest_ck"); - builder.add_default_texture("textures/voxel/chromakey.png"); - vtest_ck = world::voxel_registry::register_voxel(builder); - - builder = world::VoxelBuilder("oak_leaves"); - builder.add_default_texture("textures/voxel/oak_leaves.png"); - builder.set_render_mode(world::VRENDER_BLEND); - builder.set_surface_material(world::VMAT_GRASS); - oak_leaves = world::voxel_registry::register_voxel(builder); - - builder = world::VoxelBuilder("oak_planks"); - builder.add_default_texture("textures/voxel/oak_planks_01.png").add_default_texture("textures/voxel/oak_planks_02.png"); - builder.set_surface_material(world::VMAT_WOOD); - oak_planks = world::voxel_registry::register_voxel(builder); - - builder = world::VoxelBuilder("oak_log"); - builder.add_default_texture("textures/voxel/oak_wood_01.png") - .add_default_texture("textures/voxel/oak_wood_02.png") - .add_face_texture(world::VFACE_BOTTOM, "textures/voxel/oak_wood_top.png") - .add_face_texture(world::VFACE_TOP, "textures/voxel/oak_wood_top.png"); - builder.set_surface_material(world::VMAT_WOOD); - oak_log = world::voxel_registry::register_voxel(builder); - - builder = world::VoxelBuilder("glass"); - builder.add_default_texture("textures/voxel/glass_01.png"); - builder.set_render_mode(world::VRENDER_BLEND); - builder.set_surface_material(world::VMAT_GLASS); - glass = world::voxel_registry::register_voxel(builder); - - builder = world::VoxelBuilder("slime"); - builder.add_default_texture("textures/voxel/slime_01.png"); - builder.set_render_mode(world::VRENDER_BLEND); - builder.set_surface_material(world::VMAT_SLOSH); - builder.set_touch_type(world::VTOUCH_BOUNCE).set_touch_values({ 0.00f, 0.60f, 0.00f }); - slime = world::voxel_registry::register_voxel(builder); + auto stone_builder = world::VoxelBuilder("stone"); + stone_builder.add_default_texture("textures/voxel/stone_01.png"); + stone_builder.add_default_texture("textures/voxel/stone_02.png"); + stone_builder.add_default_texture("textures/voxel/stone_03.png"); + stone_builder.add_default_texture("textures/voxel/stone_04.png"); + stone = world::voxel_registry::register_voxel(stone_builder); + + auto cobblestone_builder = world::VoxelBuilder("cobblestone"); + cobblestone_builder.add_default_texture("textures/voxel/cobblestone_01.png"); + cobblestone_builder.add_default_texture("textures/voxel/cobblestone_02.png"); + cobblestone = world::voxel_registry::register_voxel(cobblestone_builder); + + auto dirt_builder = world::VoxelBuilder("dirt"); + dirt_builder.add_default_texture("textures/voxel/dirt_01.png"); + dirt_builder.add_default_texture("textures/voxel/dirt_02.png"); + dirt_builder.add_default_texture("textures/voxel/dirt_03.png"); + dirt_builder.add_default_texture("textures/voxel/dirt_04.png"); + dirt_builder.set_surface_material(world::VMAT_DIRT); + dirt_builder.set_on_tick(&dirt_tick); + dirt = world::voxel_registry::register_voxel(dirt_builder); + + auto grass_builder = world::VoxelBuilder("grass"); + grass_builder.add_default_texture("textures/voxel/grass_side_01.png"); + grass_builder.add_default_texture("textures/voxel/grass_side_02.png"); + grass_builder.add_face_texture(world::VFACE_BOTTOM, "textures/voxel/dirt_01.png"); + grass_builder.add_face_texture(world::VFACE_BOTTOM, "textures/voxel/dirt_02.png"); + grass_builder.add_face_texture(world::VFACE_BOTTOM, "textures/voxel/dirt_03.png"); + grass_builder.add_face_texture(world::VFACE_BOTTOM, "textures/voxel/dirt_04.png"); + grass_builder.add_face_texture(world::VFACE_TOP, "textures/voxel/grass_01.png"); + grass_builder.add_face_texture(world::VFACE_TOP, "textures/voxel/grass_02.png"); + grass_builder.set_surface_material(world::VMAT_GRASS); + grass = world::voxel_registry::register_voxel(grass_builder); + + auto vtest_builder = world::VoxelBuilder("vtest"); + vtest_builder.add_default_texture("textures/voxel/vtest_F1.png"); + vtest_builder.add_default_texture("textures/voxel/vtest_F2.png"); + vtest_builder.add_default_texture("textures/voxel/vtest_F3.png"); + vtest_builder.add_default_texture("textures/voxel/vtest_F4.png"); + vtest_builder.set_animated(true); + vtest = world::voxel_registry::register_voxel(vtest_builder); + + auto vtest_ck_builder = world::VoxelBuilder("vtest_ck"); + vtest_ck_builder.add_default_texture("textures/voxel/chromakey.png"); + vtest_ck = world::voxel_registry::register_voxel(vtest_ck_builder); + + auto oak_leaves_builder = world::VoxelBuilder("oak_leaves"); + oak_leaves_builder.add_default_texture("textures/voxel/oak_leaves.png"); + oak_leaves_builder.set_surface_material(world::VMAT_GRASS); + oak_leaves = world::voxel_registry::register_voxel(oak_leaves_builder); + + auto oak_planks_builder = world::VoxelBuilder("oak_planks"); + oak_planks_builder.add_default_texture("textures/voxel/oak_planks_01.png"); + oak_planks_builder.add_default_texture("textures/voxel/oak_planks_02.png"); + oak_planks_builder.set_surface_material(world::VMAT_WOOD); + oak_planks = world::voxel_registry::register_voxel(oak_planks_builder); + + auto oak_log_builder = world::VoxelBuilder("oak_log"); + oak_log_builder.add_default_texture("textures/voxel/oak_wood_01.png"); + oak_log_builder.add_default_texture("textures/voxel/oak_wood_02.png"); + oak_log_builder.add_face_texture(world::VFACE_BOTTOM, "textures/voxel/oak_wood_top.png"); + oak_log_builder.add_face_texture(world::VFACE_TOP, "textures/voxel/oak_wood_top.png"); + oak_log_builder.set_surface_material(world::VMAT_WOOD); + oak_log = world::voxel_registry::register_voxel(oak_log_builder); + + auto glass_builder = world::VoxelBuilder("glass"); + glass_builder.add_default_texture("textures/voxel/glass_01.png"); + glass_builder.set_render_mode(world::VRENDER_BLEND); + glass_builder.set_surface_material(world::VMAT_GLASS); + glass = world::voxel_registry::register_voxel(glass_builder); + + auto slime_builder = world::VoxelBuilder("slime"); + slime_builder.add_default_texture("textures/voxel/slime_01.png"); + slime_builder.set_render_mode(world::VRENDER_BLEND); + slime_builder.set_surface_material(world::VMAT_SLOSH); + slime_builder.set_touch_type(world::VTOUCH_BOUNCE); + slime_builder.set_touch_values({ 0.00f, 0.60f, 0.00f }); + slime = world::voxel_registry::register_voxel(slime_builder); } diff --git a/game/shared/world/CMakeLists.txt b/game/shared/world/CMakeLists.txt index cc7ed90..db3f370 100644 --- a/game/shared/world/CMakeLists.txt +++ b/game/shared/world/CMakeLists.txt @@ -8,6 +8,8 @@ target_sources(shared PRIVATE "${CMAKE_CURRENT_LIST_DIR}/feature.hh" "${CMAKE_CURRENT_LIST_DIR}/item_registry.cc" "${CMAKE_CURRENT_LIST_DIR}/item_registry.hh" + "${CMAKE_CURRENT_LIST_DIR}/item.cc" + "${CMAKE_CURRENT_LIST_DIR}/item.hh" "${CMAKE_CURRENT_LIST_DIR}/ray_dda.cc" "${CMAKE_CURRENT_LIST_DIR}/ray_dda.hh" "${CMAKE_CURRENT_LIST_DIR}/voxel_registry.cc" diff --git a/game/shared/world/chunk.cc b/game/shared/world/chunk.cc index b1b19a6..f8f7b93 100644 --- a/game/shared/world/chunk.cc +++ b/game/shared/world/chunk.cc @@ -36,12 +36,7 @@ void world::Chunk::set_voxel(const Voxel* voxel, const local_pos& lpos) void world::Chunk::set_voxel(const Voxel* voxel, const std::size_t index) { if(index < CHUNK_VOLUME) { - if(voxel == nullptr) { - m_voxels[index] = NULL_VOXEL_ID; - return; - } - - m_voxels[index] = voxel->get_id(); + m_voxels[index] = voxel ? voxel->get_id() : NULL_VOXEL_ID; return; } } diff --git a/game/shared/world/feature.cc b/game/shared/world/feature.cc index e704ced..8fe95f1 100644 --- a/game/shared/world/feature.cc +++ b/game/shared/world/feature.cc @@ -69,12 +69,7 @@ void world::Feature::place(const voxel_pos& vpos, const chunk_pos& cpos, VoxelSt continue; } - if(voxel == nullptr) { - voxels[it_index] = NULL_VOXEL_ID; - } - else { - voxels[it_index] = voxel->get_id(); - } + voxels[it_index] = voxel ? voxel->get_id() : NULL_VOXEL_ID; } } } diff --git a/game/shared/world/item.cc b/game/shared/world/item.cc new file mode 100644 index 0000000..5e60609 --- /dev/null +++ b/game/shared/world/item.cc @@ -0,0 +1,55 @@ +#include "shared/pch.hh" + +#include "shared/world/item.hh" + +#include "core/math/crc64.hh" + +#include "shared/world/voxel.hh" + +world::Item::Item(const Item& source, item_id id) noexcept : Item(source) +{ + m_id = id; +} + +void world::Item::set_cached_texture(resource_ptr texture) const noexcept +{ + m_cached_texture = std::move(texture); +} + +std::uint64_t world::Item::get_checksum(std::uint64_t combine) const +{ + combine = math::crc64(m_name.data(), m_name.size(), combine); + combine = math::crc64(m_texture.data(), m_texture.size(), combine); + + std::uint32_t id = m_place_voxel ? m_place_voxel->get_id() : NULL_VOXEL_ID; + combine = math::crc64(&id, sizeof(id), combine); + + return combine; +} + +world::ItemBuilder::ItemBuilder(std::string_view name) +{ + set_name(name); +} + +void world::ItemBuilder::set_name(std::string_view name) +{ + assert(name.size()); + + m_name = name; +} + +void world::ItemBuilder::set_texture(std::string_view texture) +{ + m_texture = texture; +} + +void world::ItemBuilder::set_place_voxel(const Voxel* place_voxel) +{ + m_place_voxel = place_voxel; +} + +std::unique_ptr world::ItemBuilder::build(item_id id) const +{ + return std::make_unique(*this, id); +} diff --git a/game/shared/world/item.hh b/game/shared/world/item.hh new file mode 100644 index 0000000..ffa7f5c --- /dev/null +++ b/game/shared/world/item.hh @@ -0,0 +1,84 @@ +#pragma once + +#include "core/resource/resource.hh" + +#include "shared/types.hh" + +// This resource is only defined client-side and +// resource_ptr should remain set to null +// anywhere else in the shared and server code +struct TextureGUI; + +namespace world +{ +class Voxel; +} // namespace world + +namespace world +{ +class Item { +public: + Item(void) = default; + explicit Item(const Item& source, item_id id) noexcept; + + constexpr std::string_view get_name(void) const noexcept; + constexpr item_id get_id(void) const noexcept; + + constexpr std::string_view get_texture(void) const noexcept; + constexpr const Voxel* get_place_voxel(void) const noexcept; + + constexpr resource_ptr& get_cached_texture(void) const noexcept; + void set_cached_texture(resource_ptr texture) const noexcept; + + std::uint64_t get_checksum(std::uint64_t combine = 0U) const; + +protected: + std::string m_name; + item_id m_id { NULL_ITEM_ID }; + + std::string m_texture; + const Voxel* m_place_voxel { nullptr }; + + mutable resource_ptr m_cached_texture; // Client-side only +}; +} // namespace world + +namespace world +{ +class ItemBuilder final : public Item { +public: + explicit ItemBuilder(std::string_view name); + + void set_name(std::string_view name); + + void set_texture(std::string_view texture); + void set_place_voxel(const Voxel* place_voxel); + + std::unique_ptr build(item_id id) const; +}; +} // namespace world + +constexpr std::string_view world::Item::get_name(void) const noexcept +{ + return m_name; +} + +constexpr item_id world::Item::get_id(void) const noexcept +{ + return m_id; +} + +constexpr std::string_view world::Item::get_texture(void) const noexcept +{ + return m_texture; +} + +constexpr const world::Voxel* world::Item::get_place_voxel(void) const noexcept +{ + return m_place_voxel; +} + +constexpr resource_ptr& world::Item::get_cached_texture(void) const noexcept +{ + return m_cached_texture; +} diff --git a/game/shared/world/item_registry.cc b/game/shared/world/item_registry.cc index 783c85e..4e0932c 100644 --- a/game/shared/world/item_registry.cc +++ b/game/shared/world/item_registry.cc @@ -6,104 +6,63 @@ #include "shared/world/voxel_registry.hh" -std::unordered_map world::item_registry::builders = {}; +static std::uint64_t registry_checksum = 0U; std::unordered_map world::item_registry::names = {}; -std::vector> world::item_registry::items = {}; +std::vector> world::item_registry::items = {}; -world::ItemInfoBuilder::ItemInfoBuilder(std::string_view name) +static void recalculate_checksum(void) { - prototype.name = name; - prototype.texture = std::string(); - prototype.place_voxel = nullptr; - prototype.cached_texture = nullptr; -} + registry_checksum = 0U; -world::ItemInfoBuilder& world::ItemInfoBuilder::set_texture(std::string_view texture) -{ - prototype.texture = texture; - prototype.cached_texture = nullptr; - return *this; + for(const auto& item : world::item_registry::items) { + registry_checksum = item->get_checksum(registry_checksum); + } } -world::ItemInfoBuilder& world::ItemInfoBuilder::set_place_voxel(const Voxel* place_voxel) +world::Item* world::item_registry::register_item(const ItemBuilder& builder) { - prototype.place_voxel = place_voxel; - return *this; -} + assert(builder.get_name().size()); + assert(nullptr == find(builder.get_name())); -item_id world::ItemInfoBuilder::build(void) const -{ - const auto it = world::item_registry::names.find(prototype.name); + const auto id = static_cast(1 + items.size()); - if(it != world::item_registry::names.cend()) { - spdlog::warn("item_registry: cannot build {}: name already present", prototype.name); - return it->second; - } + std::unique_ptr item(builder.build(id)); + names.emplace(std::string(builder.get_name()), id); + items.push_back(std::move(item)); - auto new_info = std::make_shared(); - new_info->name = prototype.name; - new_info->texture = prototype.texture; - new_info->place_voxel = prototype.place_voxel; - new_info->cached_texture = nullptr; + recalculate_checksum(); - world::item_registry::items.push_back(new_info); - world::item_registry::names.insert_or_assign(prototype.name, static_cast(world::item_registry::items.size())); - - return static_cast(world::item_registry::items.size()); + return items.back().get(); } -world::ItemInfoBuilder& world::item_registry::construct(std::string_view name) +world::Item* world::item_registry::find(std::string_view name) { - const auto it = world::item_registry::builders.find(std::string(name)); - - if(it != world::item_registry::builders.cend()) { - return it->second; - } - else { - return world::item_registry::builders.emplace(std::string(name), ItemInfoBuilder(name)).first->second; - } -} + const auto it = names.find(std::string(name)); -world::ItemInfo* world::item_registry::find(std::string_view name) -{ - const auto it = world::item_registry::names.find(std::string(name)); - - if(it != world::item_registry::names.cend()) { - return world::item_registry::find(it->second); - } - else { + if(it == names.end()) { return nullptr; } + + return items[it->second - 1].get(); } -world::ItemInfo* world::item_registry::find(const item_id item) +world::Item* world::item_registry::find(const item_id item) { - if((item != NULL_ITEM_ID) && (item <= world::item_registry::items.size())) { - return world::item_registry::items[item - 1].get(); - } - else { + if(item == NULL_ITEM_ID || item > items.size()) { return nullptr; } + + return items[item - 1].get(); } void world::item_registry::purge(void) { - world::item_registry::builders.clear(); - world::item_registry::names.clear(); - world::item_registry::items.clear(); + registry_checksum = 0U; + items.clear(); + names.clear(); } -std::uint64_t world::item_registry::calculate_checksum(void) +std::uint64_t world::item_registry::get_checksum(void) { - std::uint64_t result = 0; - - for(const auto& info : world::item_registry::items) { - result = math::crc64(info->name, result); - - if(info->place_voxel) { - result += static_cast(info->place_voxel->get_id()); - } - } - - return result; + return registry_checksum; } diff --git a/game/shared/world/item_registry.hh b/game/shared/world/item_registry.hh index 2274da2..b4c9fda 100644 --- a/game/shared/world/item_registry.hh +++ b/game/shared/world/item_registry.hh @@ -1,61 +1,18 @@ #pragma once -#include "core/resource/resource.hh" - -#include "shared/types.hh" - -// This resource is only defined client-side and -// resource_ptr should remain set to null -// anywhere else in the shared and server code -struct TextureGUI; - -namespace world -{ -class Voxel; -} // namespace world - -namespace world -{ -struct ItemInfo final { - std::string name; - std::string texture; - const Voxel* place_voxel; - - resource_ptr cached_texture; // Client-side only -}; -} // namespace world - -namespace world -{ -class ItemInfoBuilder final { -public: - explicit ItemInfoBuilder(std::string_view name); - virtual ~ItemInfoBuilder(void) = default; - -public: - ItemInfoBuilder& set_texture(std::string_view texture); - ItemInfoBuilder& set_place_voxel(const Voxel* place_voxel); - -public: - item_id build(void) const; - -private: - ItemInfo prototype; -}; -} // namespace world +#include "shared/world/item.hh" namespace world::item_registry { -extern std::unordered_map builders; extern std::unordered_map names; -extern std::vector> items; +extern std::vector> items; } // namespace world::item_registry namespace world::item_registry { -ItemInfoBuilder& construct(std::string_view name); -ItemInfo* find(std::string_view name); -ItemInfo* find(const item_id item); +Item* register_item(const ItemBuilder& builder); +Item* find(std::string_view name); +Item* find(const item_id item); } // namespace world::item_registry namespace world::item_registry @@ -65,5 +22,5 @@ void purge(void); namespace world::item_registry { -std::uint64_t calculate_checksum(void); +std::uint64_t get_checksum(void); } // namespace world::item_registry diff --git a/game/shared/world/voxel.cc b/game/shared/world/voxel.cc index 1cd1504..21fe62c 100644 --- a/game/shared/world/voxel.cc +++ b/game/shared/world/voxel.cc @@ -55,7 +55,7 @@ void world::Voxel::set_face_cache(VoxelFace face, std::size_t offset, std::size_ m_cached_face_planes[face] = plane; } -std::uint64_t world::Voxel::calculate_checksum(std::uint64_t combine) const +std::uint64_t world::Voxel::get_checksum(std::uint64_t combine) const { combine = math::crc64(m_name.data(), m_name.size(), combine); combine += static_cast(m_shape); @@ -68,102 +68,76 @@ world::VoxelBuilder::VoxelBuilder(std::string_view name) set_name(name); } -world::VoxelBuilder& world::VoxelBuilder::set_on_place(VoxelOnPlaceFunc func) noexcept +void world::VoxelBuilder::set_on_place(VoxelOnPlaceFunc func) noexcept { m_on_place = std::move(func); - - return *this; } -world::VoxelBuilder& world::VoxelBuilder::set_on_remove(VoxelOnRemoveFunc func) noexcept +void world::VoxelBuilder::set_on_remove(VoxelOnRemoveFunc func) noexcept { m_on_remove = std::move(func); - - return *this; } -world::VoxelBuilder& world::VoxelBuilder::set_on_tick(VoxelOnTickFunc func) noexcept +void world::VoxelBuilder::set_on_tick(VoxelOnTickFunc func) noexcept { m_on_tick = std::move(func); - - return *this; } -world::VoxelBuilder& world::VoxelBuilder::set_name(std::string_view name) noexcept +void world::VoxelBuilder::set_name(std::string_view name) noexcept { assert(name.size()); m_name = name; - - return *this; } -world::VoxelBuilder& world::VoxelBuilder::set_render_mode(VoxelRender mode) noexcept +void world::VoxelBuilder::set_render_mode(VoxelRender mode) noexcept { m_render_mode = mode; - - return *this; } -world::VoxelBuilder& world::VoxelBuilder::set_shape(VoxelShape shape) noexcept +void world::VoxelBuilder::set_shape(VoxelShape shape) noexcept { m_shape = shape; - - return *this; } -world::VoxelBuilder& world::VoxelBuilder::set_animated(bool animated) noexcept +void world::VoxelBuilder::set_animated(bool animated) noexcept { m_animated = animated; - - return *this; } -world::VoxelBuilder& world::VoxelBuilder::set_touch_type(VoxelTouch type) noexcept +void world::VoxelBuilder::set_touch_type(VoxelTouch type) noexcept { m_touch_type = type; - - return *this; } -world::VoxelBuilder& world::VoxelBuilder::set_touch_values(const glm::fvec3& values) noexcept +void world::VoxelBuilder::set_touch_values(const glm::fvec3& values) noexcept { m_touch_values = values; - - return *this; } -world::VoxelBuilder& world::VoxelBuilder::set_surface_material(VoxelMaterial material) noexcept +void world::VoxelBuilder::set_surface_material(VoxelMaterial material) noexcept { m_surface_material = material; - - return *this; } -world::VoxelBuilder& world::VoxelBuilder::set_collision(const math::AABBf& box) noexcept +void world::VoxelBuilder::set_collision(const math::AABBf& box) noexcept { m_collision = box; - - return *this; } -world::VoxelBuilder& world::VoxelBuilder::add_default_texture(std::string_view path) +void world::VoxelBuilder::add_default_texture(std::string_view path) { assert(path.size()); m_default_textures.emplace_back(path); - - return *this; } -world::VoxelBuilder& world::VoxelBuilder::add_face_texture(VoxelFace face, std::string_view path) +void world::VoxelBuilder::add_face_texture(VoxelFace face, std::string_view path) { assert(face < m_face_textures.size()); assert(path.size()); m_face_textures[face].emplace_back(path); - - return *this; } std::unique_ptr world::VoxelBuilder::build(voxel_id id) const diff --git a/game/shared/world/voxel.hh b/game/shared/world/voxel.hh index 9cc0e4c..6013962 100644 --- a/game/shared/world/voxel.hh +++ b/game/shared/world/voxel.hh @@ -129,7 +129,7 @@ public: /// Calculate a checksum for the voxel's properties /// @param combine An optional initial checksum to combine with /// @return The calculated checksum - std::uint64_t calculate_checksum(std::uint64_t combine = 0U) const; + std::uint64_t get_checksum(std::uint64_t combine = 0U) const; protected: std::string m_name; @@ -163,24 +163,24 @@ public: VoxelBuilder(void) = default; explicit VoxelBuilder(std::string_view name); - VoxelBuilder& set_on_place(VoxelOnPlaceFunc func) noexcept; - VoxelBuilder& set_on_remove(VoxelOnRemoveFunc func) noexcept; - VoxelBuilder& set_on_tick(VoxelOnTickFunc func) noexcept; + void set_on_place(VoxelOnPlaceFunc func) noexcept; + void set_on_remove(VoxelOnRemoveFunc func) noexcept; + void set_on_tick(VoxelOnTickFunc func) noexcept; - VoxelBuilder& set_name(std::string_view name) noexcept; + void set_name(std::string_view name) noexcept; - VoxelBuilder& set_render_mode(VoxelRender mode) noexcept; - VoxelBuilder& set_shape(VoxelShape shape) noexcept; - VoxelBuilder& set_animated(bool animated) noexcept; + void set_render_mode(VoxelRender mode) noexcept; + void set_shape(VoxelShape shape) noexcept; + void set_animated(bool animated) noexcept; - VoxelBuilder& set_touch_type(VoxelTouch type) noexcept; - VoxelBuilder& set_touch_values(const glm::fvec3& values) noexcept; - VoxelBuilder& set_surface_material(VoxelMaterial material) noexcept; + void set_touch_type(VoxelTouch type) noexcept; + void set_touch_values(const glm::fvec3& values) noexcept; + void set_surface_material(VoxelMaterial material) noexcept; - VoxelBuilder& set_collision(const math::AABBf& box) noexcept; + void set_collision(const math::AABBf& box) noexcept; - VoxelBuilder& add_default_texture(std::string_view path); - VoxelBuilder& add_face_texture(VoxelFace face, std::string_view path); + void add_default_texture(std::string_view path); + void add_face_texture(VoxelFace face, std::string_view path); std::unique_ptr build(voxel_id id) const; }; diff --git a/game/shared/world/voxel_registry.cc b/game/shared/world/voxel_registry.cc index 97485e6..573a606 100644 --- a/game/shared/world/voxel_registry.cc +++ b/game/shared/world/voxel_registry.cc @@ -11,7 +11,7 @@ static void recalculate_checksum(void) registry_checksum = 0U; for(const auto& voxel : world::voxel_registry::voxels) { - registry_checksum = voxel->calculate_checksum(registry_checksum); + registry_checksum = voxel->get_checksum(registry_checksum); } } @@ -39,7 +39,7 @@ world::Voxel* world::voxel_registry::find(std::string_view name) return nullptr; } - return voxels[it->second].get(); + return voxels[it->second - 1].get(); } world::Voxel* world::voxel_registry::find(voxel_id id) -- cgit From f210a86c1406ccc6dfd6f14181dd7a1274ee0de4 Mon Sep 17 00:00:00 2001 From: untodesu Date: Fri, 12 Sep 2025 15:09:01 +0500 Subject: Random ticking? In my game?! Hell yeah! --- game/client/io/glfw.hh | 4 ---- game/server/game.cc | 7 +++++++ game/server/world/CMakeLists.txt | 2 ++ game/server/world/random_tick.cc | 40 ++++++++++++++++++++++++++++++++++++++++ game/server/world/random_tick.hh | 14 ++++++++++++++ game/shared/entity/grounded.hh | 6 +----- game/shared/game.hh | 4 ---- game/shared/game_items.hh | 4 ---- game/shared/game_voxels.cc | 13 ++++++++++++- game/shared/game_voxels.hh | 4 ---- game/shared/world/chunk_aabb.hh | 4 ---- game/shared/world/ray_dda.hh | 4 ---- 12 files changed, 76 insertions(+), 30 deletions(-) create mode 100644 game/server/world/random_tick.cc create mode 100644 game/server/world/random_tick.hh (limited to 'game') diff --git a/game/client/io/glfw.hh b/game/client/io/glfw.hh index bbd767a..7697d97 100644 --- a/game/client/io/glfw.hh +++ b/game/client/io/glfw.hh @@ -1,5 +1,3 @@ -#ifndef CLIENTFW -#define CLIENTFW 1 #pragma once namespace io @@ -36,5 +34,3 @@ struct GlfwScrollEvent final { float dy; }; } // namespace io - -#endif // CLIENTFW diff --git a/game/server/game.cc b/game/server/game.cc index 8624670..f9802ae 100644 --- a/game/server/game.cc +++ b/game/server/game.cc @@ -28,6 +28,7 @@ #include "shared/protocol.hh" #include "shared/splash.hh" +#include "server/world/random_tick.hh" #include "server/world/universe.hh" #include "server/world/unloader.hh" #include "server/world/worldgen.hh" @@ -69,6 +70,8 @@ void server_game::init(void) world::unloader::init(); world::universe::init(); + + world::random_tick::init(); } void server_game::init_late(void) @@ -128,6 +131,10 @@ void server_game::fixed_update(void) entity::Transform::fixed_update(dimension.second); entity::Gravity::fixed_update(dimension.second); entity::Stasis::fixed_update(dimension.second); + + for(auto [entity, component] : dimension.second->chunks.view().each()) { + world::random_tick::tick(component.cpos, component.chunk); + } } } diff --git a/game/server/world/CMakeLists.txt b/game/server/world/CMakeLists.txt index e8fd4be..58a2216 100644 --- a/game/server/world/CMakeLists.txt +++ b/game/server/world/CMakeLists.txt @@ -2,6 +2,8 @@ target_sources(vserver PRIVATE "${CMAKE_CURRENT_LIST_DIR}/inhabited.hh" "${CMAKE_CURRENT_LIST_DIR}/overworld.cc" "${CMAKE_CURRENT_LIST_DIR}/overworld.hh" + "${CMAKE_CURRENT_LIST_DIR}/random_tick.cc" + "${CMAKE_CURRENT_LIST_DIR}/random_tick.hh" "${CMAKE_CURRENT_LIST_DIR}/universe.cc" "${CMAKE_CURRENT_LIST_DIR}/universe.hh" "${CMAKE_CURRENT_LIST_DIR}/unloader.cc" diff --git a/game/server/world/random_tick.cc b/game/server/world/random_tick.cc new file mode 100644 index 0000000..c5fa47c --- /dev/null +++ b/game/server/world/random_tick.cc @@ -0,0 +1,40 @@ +#include "server/pch.hh" + +#include "server/world/random_tick.hh" + +#include "core/config/number.hh" + +#include "core/io/config_map.hh" + +#include "shared/world/chunk.hh" +#include "shared/world/dimension.hh" +#include "shared/world/voxel.hh" + +#include "shared/coord.hh" + +#include "server/globals.hh" + +static config::Int random_tick_speed(2, 1, 1000); +static std::mt19937_64 random_source; + +void world::random_tick::init(void) +{ + globals::server_config.add_value("world.random_tick_speed", random_tick_speed); + + random_source.seed(std::random_device {}()); +} + +void world::random_tick::tick(const chunk_pos& cpos, Chunk* chunk) +{ + assert(chunk); + + for(int i = 0; i < random_tick_speed.get_value(); ++i) { + auto voxel_index = random_source() % CHUNK_VOLUME; + auto lpos = coord::to_local(voxel_index); + auto vpos = coord::to_voxel(cpos, lpos); + + if(auto voxel = chunk->get_voxel(lpos)) { + voxel->on_tick(chunk->get_dimension(), vpos); + } + } +} diff --git a/game/server/world/random_tick.hh b/game/server/world/random_tick.hh new file mode 100644 index 0000000..4ef1691 --- /dev/null +++ b/game/server/world/random_tick.hh @@ -0,0 +1,14 @@ +#pragma once + +#include "shared/types.hh" + +namespace world +{ +class Chunk; +} // namespace world + +namespace world::random_tick +{ +void init(void); +void tick(const chunk_pos& cpos, Chunk* chunk); +} // namespace world::random_tick diff --git a/game/shared/entity/grounded.hh b/game/shared/entity/grounded.hh index 940cebe..34a0f9e 100644 --- a/game/shared/entity/grounded.hh +++ b/game/shared/entity/grounded.hh @@ -1,8 +1,6 @@ -#ifndef SHARED_ENTITY_GROUNDED -#define SHARED_ENTITY_GROUNDED 1 #pragma once -#include "shared/world/voxel_registry.hh" +#include "shared/world/voxel.hh" namespace entity { @@ -12,5 +10,3 @@ struct Grounded final { world::VoxelMaterial surface; }; } // namespace entity - -#endif // SHARED_ENTITY_GROUNDED diff --git a/game/shared/game.hh b/game/shared/game.hh index 21286e8..0dfbadb 100644 --- a/game/shared/game.hh +++ b/game/shared/game.hh @@ -1,5 +1,3 @@ -#ifndef SHARED_GAME -#define SHARED_GAME 1 #pragma once namespace shared_game @@ -7,5 +5,3 @@ namespace shared_game void init(int argc, char** argv); void shutdown(void); } // namespace shared_game - -#endif // SHARED_GAME diff --git a/game/shared/game_items.hh b/game/shared/game_items.hh index 88952fc..6f8eac9 100644 --- a/game/shared/game_items.hh +++ b/game/shared/game_items.hh @@ -1,5 +1,3 @@ -#ifndef SHARED_GAME_ITEMS -#define SHARED_GAME_ITEMS 1 #pragma once namespace world @@ -24,5 +22,3 @@ namespace game_items { void populate(void); } // namespace game_items - -#endif // SHARED_GAME_ITEMS diff --git a/game/shared/game_voxels.cc b/game/shared/game_voxels.cc index f262c23..51cf873 100644 --- a/game/shared/game_voxels.cc +++ b/game/shared/game_voxels.cc @@ -54,11 +54,21 @@ static void dirt_tick(world::Dimension* dimension, const voxel_pos& vpos) } if(grass_found && air_above) { - // Replace itself with the grass voxel dimension->set_voxel(game_voxels::grass, vpos); } } +static void grass_tick(world::Dimension* dimension, const voxel_pos& vpos) +{ + auto above_vpos = vpos + voxel_pos(0, 1, 0); + auto above_voxel = dimension->get_voxel(above_vpos); + + if(above_voxel && !above_voxel->is_surface_material()) { + // Decay into dirt if something is blocking airflow + dimension->set_voxel(game_voxels::dirt, vpos); + } +} + void game_voxels::populate(void) { auto stone_builder = world::VoxelBuilder("stone"); @@ -92,6 +102,7 @@ void game_voxels::populate(void) grass_builder.add_face_texture(world::VFACE_TOP, "textures/voxel/grass_01.png"); grass_builder.add_face_texture(world::VFACE_TOP, "textures/voxel/grass_02.png"); grass_builder.set_surface_material(world::VMAT_GRASS); + grass_builder.set_on_tick(&grass_tick); grass = world::voxel_registry::register_voxel(grass_builder); auto vtest_builder = world::VoxelBuilder("vtest"); diff --git a/game/shared/game_voxels.hh b/game/shared/game_voxels.hh index 68e599e..2211619 100644 --- a/game/shared/game_voxels.hh +++ b/game/shared/game_voxels.hh @@ -1,5 +1,3 @@ -#ifndef SHARED_GAME_VOXELS -#define SHARED_GAME_VOXELS 1 #pragma once namespace world @@ -26,5 +24,3 @@ namespace game_voxels { void populate(void); } // namespace game_voxels - -#endif // SHARED_GAME_VOXELS diff --git a/game/shared/world/chunk_aabb.hh b/game/shared/world/chunk_aabb.hh index 3a2d26f..d926b55 100644 --- a/game/shared/world/chunk_aabb.hh +++ b/game/shared/world/chunk_aabb.hh @@ -1,5 +1,3 @@ -#ifndef SHARED_CHUNK_AABB -#define SHARED_CHUNK_AABB 1 #pragma once #include "core/math/aabb.hh" @@ -10,5 +8,3 @@ namespace world { using ChunkAABB = math::AABB; } // namespace world - -#endif // SHARED_CHUNK_AABB diff --git a/game/shared/world/ray_dda.hh b/game/shared/world/ray_dda.hh index 3071be2..110e3d4 100644 --- a/game/shared/world/ray_dda.hh +++ b/game/shared/world/ray_dda.hh @@ -1,5 +1,3 @@ -#ifndef SHARED_RAY_DDA -#define SHARED_RAY_DDA 1 #pragma once #include "shared/types.hh" @@ -38,5 +36,3 @@ public: voxel_pos vpos; }; } // namespace world - -#endif // SHARED_RAY_DDA -- cgit From 522a7514012da86f7b9643179f0763746f3b232e Mon Sep 17 00:00:00 2001 From: untodesu Date: Fri, 12 Sep 2025 16:15:32 +0500 Subject: Protocol and versioning changes --- game/client/gui/bother.cc | 8 +++- game/client/gui/bother.hh | 4 +- game/client/gui/play_menu.cc | 92 +++++++++++++++++++++++++++-------------- game/client/gui/window_title.cc | 11 +---- game/client/session.cc | 6 ++- game/server/sessions.cc | 24 ++++++++++- game/server/status.cc | 6 ++- game/shared/protocol.cc | 20 ++++++--- game/shared/protocol.hh | 11 +++-- 9 files changed, 124 insertions(+), 58 deletions(-) (limited to 'game') diff --git a/game/client/gui/bother.cc b/game/client/gui/bother.cc index 1bb7097..e87b9ff 100644 --- a/game/client/gui/bother.cc +++ b/game/client/gui/bother.cc @@ -2,6 +2,8 @@ #include "client/gui/bother.hh" +#include "core/version.hh" + #include "shared/protocol.hh" #include "client/globals.hh" @@ -29,10 +31,12 @@ static void on_status_response_packet(const protocol::StatusResponse& packet) gui::BotherResponseEvent event; event.identity = identity; event.is_server_unreachable = false; - event.protocol_version = packet.version; event.num_players = packet.num_players; event.max_players = packet.max_players; event.motd = packet.motd; + event.game_version_major = packet.game_version_major; + event.game_version_minor = packet.game_version_minor; + event.game_version_patch = packet.game_version_patch; globals::dispatcher.trigger(event); enet_peer_disconnect(packet.peer, protocol::CHANNEL); @@ -89,7 +93,7 @@ void gui::bother::update_late(void) if(0 < enet_host_service(bother_host, &enet_event, 0)) { if(enet_event.type == ENET_EVENT_TYPE_CONNECT) { protocol::StatusRequest packet; - packet.version = protocol::VERSION; + packet.game_version_major = version::major; protocol::send(enet_event.peer, protocol::encode(packet)); return; } diff --git a/game/client/gui/bother.hh b/game/client/gui/bother.hh index fc5bab4..75e56d1 100644 --- a/game/client/gui/bother.hh +++ b/game/client/gui/bother.hh @@ -5,9 +5,11 @@ namespace gui struct BotherResponseEvent final { unsigned int identity; bool is_server_unreachable; - std::uint32_t protocol_version; std::uint16_t num_players; std::uint16_t max_players; + std::uint32_t game_version_major; + std::uint32_t game_version_minor; + std::uint32_t game_version_patch; std::string motd; }; } // namespace gui diff --git a/game/client/gui/play_menu.cc b/game/client/gui/play_menu.cc index 5b9887e..ad85141 100644 --- a/game/client/gui/play_menu.cc +++ b/game/client/gui/play_menu.cc @@ -10,6 +10,8 @@ #include "core/utils/string.hh" +#include "core/version.hh" + #include "shared/protocol.hh" #include "client/gui/bother.hh" @@ -25,9 +27,8 @@ constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration; constexpr static std::string_view DEFAULT_SERVER_NAME = "Voxelius Server"; constexpr static std::string_view SERVERS_TXT = "servers.txt"; -constexpr static std::string_view WARNING_TOAST = "[!]"; -constexpr static std::size_t MAX_SERVER_ITEM_NAME = 24; +constexpr static std::size_t MAX_SERVER_ITEM_NAME = 18; enum class item_status : unsigned int { UNKNOWN = 0x0000U, @@ -43,10 +44,12 @@ struct ServerStatusItem final { std::uint16_t port; // Things pulled from bother events - std::uint32_t protocol_version; std::uint16_t num_players; std::uint16_t max_players; std::string motd; + std::uint16_t game_version_major; + std::uint16_t game_version_minor; + std::uint16_t game_version_patch; // Unique identifier that monotonically // grows with each new server added and @@ -69,9 +72,6 @@ static std::string str_status_init; static std::string str_status_ping; static std::string str_status_fail; -static std::string str_outdated_client; -static std::string str_outdated_server; - static std::string input_itemname; static std::string input_hostname; static std::string input_password; @@ -106,11 +106,13 @@ static void add_new_server(void) { auto item = new ServerStatusItem(); item->port = protocol::PORT; - item->protocol_version = protocol::VERSION; item->max_players = UINT16_MAX; item->num_players = UINT16_MAX; item->identity = next_identity; item->status = item_status::UNKNOWN; + item->game_version_major = 0U; + item->game_version_minor = 0U; + item->game_version_patch = 0U; next_identity += 1U; @@ -202,9 +204,6 @@ static void on_language_set(const gui::LanguageSetEvent& event) str_status_init = gui::language::resolve("play_menu.status.init"); str_status_ping = gui::language::resolve("play_menu.status.ping"); str_status_fail = gui::language::resolve("play_menu.status.fail"); - - str_outdated_client = gui::language::resolve("play_menu.outdated_client"); - str_outdated_server = gui::language::resolve("play_menu.outdated_server"); } static void on_bother_response(const gui::BotherResponseEvent& event) @@ -212,18 +211,22 @@ static void on_bother_response(const gui::BotherResponseEvent& event) for(auto item : servers_deque) { if(item->identity == event.identity) { if(event.is_server_unreachable) { - item->protocol_version = 0U; item->num_players = UINT16_MAX; item->max_players = UINT16_MAX; item->motd = str_status_fail; item->status = item_status::FAILURE; + item->game_version_major = 0U; + item->game_version_minor = 0U; + item->game_version_patch = 0U; } else { - item->protocol_version = event.protocol_version; item->num_players = event.num_players; item->max_players = event.max_players; item->motd = event.motd; item->status = item_status::REACHED; + item->game_version_major = event.game_version_major; + item->game_version_minor = event.game_version_minor; + item->game_version_patch = event.game_version_patch; } break; @@ -264,30 +267,50 @@ static void layout_server_item(ServerStatusItem* item) if(item->status == item_status::REACHED) { auto stats = std::format("{}/{}", item->num_players, item->max_players); - auto stats_width = ImGui::CalcTextSize(stats.c_str(), stats.c_str() + stats.size()).x; - auto stats_pos = ImVec2(cursor.x + item_width - stats_width - padding.x, cursor.y + padding.y); + auto stats_size = ImGui::CalcTextSize(stats.c_str(), stats.c_str() + stats.size()); + auto stats_pos = ImVec2(cursor.x + item_width - stats_size.x - padding.x, cursor.y + padding.y); draw_list->AddText(stats_pos, ImGui::GetColorU32(ImGuiCol_TextDisabled), stats.c_str(), stats.c_str() + stats.size()); - if(item->protocol_version != protocol::VERSION) { - auto warning_size = ImGui::CalcTextSize(WARNING_TOAST.data(), WARNING_TOAST.data() + WARNING_TOAST.size()); - auto warning_pos = ImVec2(stats_pos.x - warning_size.x - padding.x - 4.0f * globals::gui_scale, cursor.y + padding.y); - auto warning_end = ImVec2(warning_pos.x + warning_size.x, warning_pos.y + warning_size.y); - draw_list->AddText(warning_pos, ImGui::GetColorU32(ImGuiCol_DragDropTarget), WARNING_TOAST.data(), - WARNING_TOAST.data() + WARNING_TOAST.size()); + auto major_version_mismatch = item->game_version_major != version::major; + auto minor_version_mismatch = item->game_version_minor != version::minor; + auto patch_version_mismatch = item->game_version_patch != version::patch; - if(ImGui::IsMouseHoveringRect(warning_pos, warning_end)) { - ImGui::BeginTooltip(); + ImU32 version_color; - if(item->protocol_version < protocol::VERSION) { - ImGui::TextUnformatted(str_outdated_server.c_str(), str_outdated_server.c_str() + str_outdated_server.size()); - } - else { - ImGui::TextUnformatted(str_outdated_client.c_str(), str_outdated_client.c_str() + str_outdated_client.size()); - } + if(major_version_mismatch || minor_version_mismatch || patch_version_mismatch) { + version_color = ImGui::GetColorU32(major_version_mismatch ? ImGuiCol_PlotLinesHovered : ImGuiCol_DragDropTarget); + } + else { + version_color = ImGui::GetColorU32(ImGuiCol_PlotHistogram); + } - ImGui::EndTooltip(); - } + ImGui::PushFont(globals::font_unscii8, 4.0f); + + std::string version_toast; + + if(item->game_version_major < 16U) { + // Pre v16.x.x servers didn't send minor and patch versions + // and also used a different versioning scheme; post v16 the + // major version became the protocol version and the semver lost the tweak part + version_toast = std::string("0.0.1"); + } + else { + version_toast = std::format("{}.{}.{}", item->game_version_major, item->game_version_minor, item->game_version_patch); } + + auto version_size = ImGui::CalcTextSize(version_toast.c_str(), version_toast.c_str() + version_toast.size()); + auto version_pos = ImVec2(stats_pos.x - version_size.x - padding.x - 4.0f * globals::gui_scale, + cursor.y + padding.y + 0.5f * (stats_size.y - version_size.y)); + auto version_end = ImVec2(version_pos.x + version_size.x, version_pos.y + version_size.y); + + auto outline_pos = ImVec2(version_pos.x - 2U * globals::gui_scale, version_pos.y - 2U * globals::gui_scale); + auto outline_end = ImVec2(version_end.x + 2U * globals::gui_scale, version_end.y + 2U * globals::gui_scale); + auto outline_thickness = math::max(1.0f, 0.5f * static_cast(globals::gui_scale)); + + draw_list->AddRect(outline_pos, outline_end, version_color, 0.0f, 0, outline_thickness); + draw_list->AddText(version_pos, version_color, version_toast.c_str(), version_toast.c_str() + version_toast.size()); + + ImGui::PopFont(); } ImU32 motd_color = {}; @@ -460,11 +483,13 @@ void gui::play_menu::init(void) auto item = new ServerStatusItem(); item->port = protocol::PORT; - item->protocol_version = protocol::VERSION; item->max_players = UINT16_MAX; item->num_players = UINT16_MAX; item->identity = next_identity; item->status = item_status::UNKNOWN; + item->game_version_major = version::major; + item->game_version_minor = version::minor; + item->game_version_patch = version::patch; next_identity += 1U; @@ -543,6 +568,11 @@ void gui::play_menu::layout(void) ImGui::EndTabItem(); } + if(ImGui::BeginTabItem("debug###play_menu.debug.child")) { + ImGui::ShowStyleEditor(); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); } diff --git a/game/client/gui/window_title.cc b/game/client/gui/window_title.cc index 6e2387c..2f96205 100644 --- a/game/client/gui/window_title.cc +++ b/game/client/gui/window_title.cc @@ -10,14 +10,5 @@ void gui::window_title::update(void) { - std::string title; - - if(globals::sound_ctx && globals::sound_dev) { - title = std::format("Voxelius {}: {}", version::semver, splash::get()); - } - else { - title = std::format("Voxelius {}: {} [NOSOUND]", version::semver, splash::get()); - } - - glfwSetWindowTitle(globals::window, title.c_str()); + glfwSetWindowTitle(globals::window, std::format("Voxelius {}: {}", version::semver, splash::get()).c_str()); } diff --git a/game/client/session.cc b/game/client/session.cc index 907b789..ce3d616 100644 --- a/game/client/session.cc +++ b/game/client/session.cc @@ -6,6 +6,8 @@ #include "core/math/crc64.hh" +#include "core/version.hh" + #include "shared/entity/head.hh" #include "shared/entity/player.hh" #include "shared/entity/transform.hh" @@ -284,11 +286,13 @@ void session::disconnect(std::string_view reason) void session::send_login_request(void) { protocol::LoginRequest packet; - packet.version = protocol::VERSION; + packet.game_version_major = version::major; packet.voxel_registry_checksum = world::voxel_registry::get_checksum(); packet.item_registry_checksum = world::item_registry::get_checksum(); packet.password_hash = server_password_hash; packet.username = client_game::username.get(); + packet.game_version_minor = version::minor; + packet.game_version_patch = version::patch; protocol::send(session::peer, protocol::encode(packet)); diff --git a/game/server/sessions.cc b/game/server/sessions.cc index 2ab9d74..6758648 100644 --- a/game/server/sessions.cc +++ b/game/server/sessions.cc @@ -12,6 +12,8 @@ #include "core/utils/string.hh" +#include "core/version.hh" + #include "shared/entity/factory.hh" #include "shared/entity/head.hh" #include "shared/entity/player.hh" @@ -42,6 +44,8 @@ private: config::Unsigned sessions::max_players(8U, 1U, 128U); unsigned int sessions::num_players = 0U; +static config::Boolean strict_version_matching(true); + static emhash8::HashMap username_map; static emhash8::HashMap identity_map; static std::vector dimension_listeners; @@ -49,20 +53,36 @@ static std::vector sessions_vector; static void on_login_request_packet(const protocol::LoginRequest& packet) { - if(packet.version > protocol::VERSION) { + if(packet.game_version_major > version::major) { protocol::Disconnect response; response.reason = "protocol.outdated_server"; protocol::send(packet.peer, protocol::encode(response)); return; } - if(packet.version < protocol::VERSION) { + if(packet.game_version_minor < version::minor) { protocol::Disconnect response; response.reason = "protocol.outdated_client"; protocol::send(packet.peer, protocol::encode(response)); return; } + if(strict_version_matching.get_value()) { + if(packet.game_version_minor > version::minor || packet.game_version_patch > version::patch) { + protocol::Disconnect response; + response.reason = "protocol.outdated_server"; + protocol::send(packet.peer, protocol::encode(response)); + return; + } + + if(packet.game_version_minor < version::minor || packet.game_version_patch < version::patch) { + protocol::Disconnect response; + response.reason = "protocol.outdated_client"; + protocol::send(packet.peer, protocol::encode(response)); + return; + } + } + // FIXME: calculate voxel registry checksum ahead of time // instead of figuring it out every time a new player connects if(packet.voxel_registry_checksum != world::voxel_registry::get_checksum()) { diff --git a/game/server/status.cc b/game/server/status.cc index ba1d59d..0edd0a0 100644 --- a/game/server/status.cc +++ b/game/server/status.cc @@ -4,6 +4,8 @@ #include "core/config/number.hh" +#include "core/version.hh" + #include "shared/protocol.hh" #include "shared/splash.hh" @@ -13,10 +15,12 @@ static void on_status_request_packet(const protocol::StatusRequest& packet) { protocol::StatusResponse response; - response.version = protocol::VERSION; + response.game_version_major = version::major; response.max_players = sessions::max_players.get_value(); response.num_players = sessions::num_players; response.motd = splash::get(); + response.game_version_minor = version::minor; + response.game_version_patch = version::patch; protocol::send(packet.peer, protocol::encode(response)); } diff --git a/game/shared/protocol.cc b/game/shared/protocol.cc index 576502e..7115807 100644 --- a/game/shared/protocol.cc +++ b/game/shared/protocol.cc @@ -21,7 +21,7 @@ ENetPacket* protocol::encode(const protocol::StatusRequest& packet, enet_uint32 { write_buffer.reset(); write_buffer.write(protocol::StatusRequest::ID); - write_buffer.write(packet.version); + write_buffer.write(packet.game_version_major); return write_buffer.to_packet(flags); } @@ -29,10 +29,12 @@ ENetPacket* protocol::encode(const protocol::StatusResponse& packet, enet_uint32 { write_buffer.reset(); write_buffer.write(protocol::StatusResponse::ID); - write_buffer.write(packet.version); + write_buffer.write(packet.game_version_major); write_buffer.write(packet.max_players); write_buffer.write(packet.num_players); write_buffer.write(packet.motd); + write_buffer.write(packet.game_version_minor); + write_buffer.write(packet.game_version_patch); return write_buffer.to_packet(flags); } @@ -40,11 +42,13 @@ ENetPacket* protocol::encode(const protocol::LoginRequest& packet, enet_uint32 f { write_buffer.reset(); write_buffer.write(protocol::LoginRequest::ID); - write_buffer.write(packet.version); + write_buffer.write(packet.game_version_major); write_buffer.write(packet.voxel_registry_checksum); write_buffer.write(packet.item_registry_checksum); write_buffer.write(packet.password_hash); write_buffer.write(packet.username.substr(0, protocol::MAX_USERNAME)); + write_buffer.write(packet.game_version_minor); + write_buffer.write(packet.game_version_patch); return write_buffer.to_packet(flags); } @@ -268,26 +272,30 @@ void protocol::decode(entt::dispatcher& dispatcher, const ENetPacket* packet, EN switch(id) { case protocol::StatusRequest::ID: status_request.peer = peer; - status_request.version = read_buffer.read(); + status_request.game_version_major = read_buffer.read(); dispatcher.trigger(status_request); break; case protocol::StatusResponse::ID: status_response.peer = peer; - status_response.version = read_buffer.read(); + status_response.game_version_major = read_buffer.read(); status_response.max_players = read_buffer.read(); status_response.num_players = read_buffer.read(); status_response.motd = read_buffer.read(); + status_response.game_version_minor = read_buffer.read(); + status_response.game_version_patch = read_buffer.read(); dispatcher.trigger(status_response); break; case protocol::LoginRequest::ID: login_request.peer = peer; - login_request.version = read_buffer.read(); + login_request.game_version_major = read_buffer.read(); login_request.voxel_registry_checksum = read_buffer.read(); login_request.item_registry_checksum = read_buffer.read(); login_request.password_hash = read_buffer.read(); login_request.username = read_buffer.read(); + login_request.game_version_minor = read_buffer.read(); + login_request.game_version_patch = read_buffer.read(); dispatcher.trigger(login_request); break; diff --git a/game/shared/protocol.hh b/game/shared/protocol.hh index 3133275..f0bdff6 100644 --- a/game/shared/protocol.hh +++ b/game/shared/protocol.hh @@ -14,7 +14,6 @@ constexpr static std::size_t MAX_USERNAME = 64; constexpr static std::size_t MAX_SOUNDNAME = 1024; constexpr static std::uint16_t TICKRATE = 60; constexpr static std::uint16_t PORT = 43103; -constexpr static std::uint32_t VERSION = 15; constexpr static std::uint8_t CHANNEL = 0; } // namespace protocol @@ -107,22 +106,26 @@ ENetPacket* make_dimension_info(const world::Dimension* dimension); } // namespace protocol::utils struct protocol::StatusRequest final : public protocol::Base<0x0000> { - std::uint32_t version; + std::uint32_t game_version_major; // renamed from 'version' in v16.x.x }; struct protocol::StatusResponse final : public protocol::Base<0x0001> { - std::uint32_t version; + std::uint32_t game_version_major; // renamed from 'version' in v16.x.x std::uint16_t max_players; std::uint16_t num_players; std::string motd; + std::uint32_t game_version_minor { UINT32_MAX }; // added in v16.x.x + std::uint32_t game_version_patch { UINT32_MAX }; }; struct protocol::LoginRequest final : public protocol::Base<0x0002> { - std::uint32_t version; + std::uint32_t game_version_major; // renamed from 'version' in v16.x.x std::uint64_t voxel_registry_checksum; std::uint64_t item_registry_checksum; std::uint64_t password_hash; std::string username; + std::uint32_t game_version_minor; // added in v16.x.x + std::uint32_t game_version_patch; }; struct protocol::LoginResponse final : public protocol::Base<0x0003> { -- cgit