From 3bf42c6ff3805a0d42bbc661794a95ff31bedc26 Mon Sep 17 00:00:00 2001 From: untodesu Date: Sat, 15 Mar 2025 16:22:09 +0500 Subject: Add whatever I was working on for the last month --- game/shared/CMakeLists.txt | 55 +++++ game/shared/chunk.cc | 55 +++++ game/shared/chunk.hh | 33 +++ game/shared/chunk_aabb.cc | 57 +++++ game/shared/chunk_aabb.hh | 30 +++ game/shared/collision.cc | 160 ++++++++++++++ game/shared/collision.hh | 19 ++ game/shared/const.hh | 47 ++++ game/shared/coord.hh | 148 +++++++++++++ game/shared/dimension.cc | 163 ++++++++++++++ game/shared/dimension.hh | 81 +++++++ game/shared/factory.cc | 36 ++++ game/shared/factory.hh | 12 ++ game/shared/game.cc | 118 ++++++++++ game/shared/game.hh | 11 + game/shared/game_items.cc | 78 +++++++ game/shared/game_items.hh | 26 +++ game/shared/game_voxels.cc | 113 ++++++++++ game/shared/game_voxels.hh | 28 +++ game/shared/globals.cc | 11 + game/shared/globals.hh | 23 ++ game/shared/gravity.cc | 17 ++ game/shared/gravity.hh | 12 ++ game/shared/grounded.hh | 13 ++ game/shared/head.hh | 14 ++ game/shared/item_registry.cc | 80 +++++++ game/shared/item_registry.hh | 57 +++++ game/shared/pch.hh | 23 ++ game/shared/player.hh | 7 + game/shared/protocol.cc | 490 ++++++++++++++++++++++++++++++++++++++++++ game/shared/protocol.hh | 213 ++++++++++++++++++ game/shared/ray_dda.cc | 102 +++++++++ game/shared/ray_dda.hh | 35 +++ game/shared/splash.cc | 34 +++ game/shared/splash.hh | 11 + game/shared/stasis.cc | 16 ++ game/shared/stasis.hh | 14 ++ game/shared/threading.cc | 91 ++++++++ game/shared/threading.hh | 46 ++++ game/shared/transform.cc | 31 +++ game/shared/transform.hh | 25 +++ game/shared/types.hh | 44 ++++ game/shared/velocity.cc | 16 ++ game/shared/velocity.hh | 17 ++ game/shared/voxel_registry.cc | 184 ++++++++++++++++ game/shared/voxel_registry.hh | 144 +++++++++++++ game/shared/voxel_storage.cc | 43 ++++ game/shared/voxel_storage.hh | 18 ++ 48 files changed, 3101 insertions(+) create mode 100644 game/shared/CMakeLists.txt create mode 100644 game/shared/chunk.cc create mode 100644 game/shared/chunk.hh create mode 100644 game/shared/chunk_aabb.cc create mode 100644 game/shared/chunk_aabb.hh create mode 100644 game/shared/collision.cc create mode 100644 game/shared/collision.hh create mode 100644 game/shared/const.hh create mode 100644 game/shared/coord.hh create mode 100644 game/shared/dimension.cc create mode 100644 game/shared/dimension.hh create mode 100644 game/shared/factory.cc create mode 100644 game/shared/factory.hh create mode 100644 game/shared/game.cc create mode 100644 game/shared/game.hh create mode 100644 game/shared/game_items.cc create mode 100644 game/shared/game_items.hh create mode 100644 game/shared/game_voxels.cc create mode 100644 game/shared/game_voxels.hh create mode 100644 game/shared/globals.cc create mode 100644 game/shared/globals.hh create mode 100644 game/shared/gravity.cc create mode 100644 game/shared/gravity.hh create mode 100644 game/shared/grounded.hh create mode 100644 game/shared/head.hh create mode 100644 game/shared/item_registry.cc create mode 100644 game/shared/item_registry.hh create mode 100644 game/shared/pch.hh create mode 100644 game/shared/player.hh create mode 100644 game/shared/protocol.cc create mode 100644 game/shared/protocol.hh create mode 100644 game/shared/ray_dda.cc create mode 100644 game/shared/ray_dda.hh create mode 100644 game/shared/splash.cc create mode 100644 game/shared/splash.hh create mode 100644 game/shared/stasis.cc create mode 100644 game/shared/stasis.hh create mode 100644 game/shared/threading.cc create mode 100644 game/shared/threading.hh create mode 100644 game/shared/transform.cc create mode 100644 game/shared/transform.hh create mode 100644 game/shared/types.hh create mode 100644 game/shared/velocity.cc create mode 100644 game/shared/velocity.hh create mode 100644 game/shared/voxel_registry.cc create mode 100644 game/shared/voxel_registry.hh create mode 100644 game/shared/voxel_storage.cc create mode 100644 game/shared/voxel_storage.hh (limited to 'game/shared') diff --git a/game/shared/CMakeLists.txt b/game/shared/CMakeLists.txt new file mode 100644 index 0000000..cd3eed4 --- /dev/null +++ b/game/shared/CMakeLists.txt @@ -0,0 +1,55 @@ +add_library(shared STATIC + "${CMAKE_CURRENT_LIST_DIR}/chunk_aabb.cc" + "${CMAKE_CURRENT_LIST_DIR}/chunk_aabb.hh" + "${CMAKE_CURRENT_LIST_DIR}/chunk.cc" + "${CMAKE_CURRENT_LIST_DIR}/chunk.hh" + "${CMAKE_CURRENT_LIST_DIR}/CMakeLists.txt" + "${CMAKE_CURRENT_LIST_DIR}/collision.cc" + "${CMAKE_CURRENT_LIST_DIR}/collision.hh" + "${CMAKE_CURRENT_LIST_DIR}/const.hh" + "${CMAKE_CURRENT_LIST_DIR}/coord.hh" + "${CMAKE_CURRENT_LIST_DIR}/dimension.cc" + "${CMAKE_CURRENT_LIST_DIR}/dimension.hh" + "${CMAKE_CURRENT_LIST_DIR}/factory.cc" + "${CMAKE_CURRENT_LIST_DIR}/factory.hh" + "${CMAKE_CURRENT_LIST_DIR}/game_items.cc" + "${CMAKE_CURRENT_LIST_DIR}/game_items.hh" + "${CMAKE_CURRENT_LIST_DIR}/game_voxels.cc" + "${CMAKE_CURRENT_LIST_DIR}/game_voxels.hh" + "${CMAKE_CURRENT_LIST_DIR}/game.cc" + "${CMAKE_CURRENT_LIST_DIR}/game.hh" + "${CMAKE_CURRENT_LIST_DIR}/globals.cc" + "${CMAKE_CURRENT_LIST_DIR}/globals.hh" + "${CMAKE_CURRENT_LIST_DIR}/gravity.cc" + "${CMAKE_CURRENT_LIST_DIR}/gravity.hh" + "${CMAKE_CURRENT_LIST_DIR}/grounded.hh" + "${CMAKE_CURRENT_LIST_DIR}/head.hh" + "${CMAKE_CURRENT_LIST_DIR}/item_registry.cc" + "${CMAKE_CURRENT_LIST_DIR}/item_registry.hh" + "${CMAKE_CURRENT_LIST_DIR}/pch.hh" + "${CMAKE_CURRENT_LIST_DIR}/player.hh" + "${CMAKE_CURRENT_LIST_DIR}/protocol.cc" + "${CMAKE_CURRENT_LIST_DIR}/protocol.hh" + "${CMAKE_CURRENT_LIST_DIR}/ray_dda.cc" + "${CMAKE_CURRENT_LIST_DIR}/ray_dda.hh" + "${CMAKE_CURRENT_LIST_DIR}/splash.cc" + "${CMAKE_CURRENT_LIST_DIR}/splash.hh" + "${CMAKE_CURRENT_LIST_DIR}/stasis.cc" + "${CMAKE_CURRENT_LIST_DIR}/stasis.hh" + "${CMAKE_CURRENT_LIST_DIR}/threading.cc" + "${CMAKE_CURRENT_LIST_DIR}/threading.hh" + "${CMAKE_CURRENT_LIST_DIR}/transform.cc" + "${CMAKE_CURRENT_LIST_DIR}/transform.hh" + "${CMAKE_CURRENT_LIST_DIR}/types.hh" + "${CMAKE_CURRENT_LIST_DIR}/velocity.cc" + "${CMAKE_CURRENT_LIST_DIR}/velocity.hh" + "${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") +target_compile_features(shared PUBLIC cxx_std_17) +target_include_directories(shared PUBLIC "${DEPS_INCLUDE_DIR}") +target_include_directories(shared PRIVATE "${PROJECT_SOURCE_DIR}") +target_include_directories(shared PRIVATE "${PROJECT_SOURCE_DIR}/game") +target_precompile_headers(shared PRIVATE "${CMAKE_CURRENT_LIST_DIR}/pch.hh") +target_link_libraries(shared PUBLIC core enet FNL miniz parson) diff --git a/game/shared/chunk.cc b/game/shared/chunk.cc new file mode 100644 index 0000000..c996973 --- /dev/null +++ b/game/shared/chunk.cc @@ -0,0 +1,55 @@ +#include "shared/pch.hh" +#include "shared/chunk.hh" + +#include "shared/coord.hh" + +Chunk::Chunk(entt::entity entity, Dimension *dimension) +{ + m_entity = entity; + m_dimension = dimension; + m_voxels.fill(NULL_VOXEL_ID); +} + +voxel_id Chunk::get_voxel(const local_pos &lpos) const +{ + return get_voxel(coord::to_index(lpos)); +} + +voxel_id Chunk::get_voxel(const std::size_t index) const +{ + if(index >= CHUNK_VOLUME) + return NULL_VOXEL_ID; + return m_voxels[index]; +} + +void Chunk::set_voxel(voxel_id voxel, const local_pos &lpos) +{ + set_voxel(voxel, coord::to_index(lpos)); +} + +void Chunk::set_voxel(voxel_id voxel, const std::size_t index) +{ + if(index >= CHUNK_VOLUME) + return; + m_voxels[index] = voxel; +} + +const VoxelStorage &Chunk::get_voxels(void) const +{ + return m_voxels; +} + +void Chunk::set_voxels(const VoxelStorage &voxels) +{ + m_voxels = voxels; +} + +entt::entity Chunk::get_entity(void) const +{ + return m_entity; +} + +Dimension *Chunk::get_dimension(void) const +{ + return m_dimension; +} diff --git a/game/shared/chunk.hh b/game/shared/chunk.hh new file mode 100644 index 0000000..506d867 --- /dev/null +++ b/game/shared/chunk.hh @@ -0,0 +1,33 @@ +#ifndef SHARED_CHUNK_HH +#define SHARED_CHUNK_HH 1 +#pragma once + +#include "shared/types.hh" +#include "shared/voxel_storage.hh" + +class Dimension; + +class Chunk final { +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; + + void set_voxel(voxel_id voxel, const local_pos &lpos); + void set_voxel(voxel_id voxel, const std::size_t index); + + const VoxelStorage &get_voxels(void) const; + void set_voxels(const VoxelStorage &voxels); + + entt::entity get_entity(void) const; + Dimension *get_dimension(void) const; + +private: + entt::entity m_entity; + Dimension *m_dimension; + VoxelStorage m_voxels; +}; + +#endif /* SHARED_CHUNK_HH */ diff --git a/game/shared/chunk_aabb.cc b/game/shared/chunk_aabb.cc new file mode 100644 index 0000000..b4e5b82 --- /dev/null +++ b/game/shared/chunk_aabb.cc @@ -0,0 +1,57 @@ +#include "shared/pch.hh" +#include "shared/chunk_aabb.hh" + +void ChunkAABB::set_bounds(const chunk_pos &min, const chunk_pos &max) +{ + this->min = min; + this->max = max; +} + +void ChunkAABB::set_offset(const chunk_pos &base, const chunk_pos &size) +{ + this->min = base; + this->max = base + size; +} + +bool ChunkAABB::contains(const chunk_pos &point) const +{ + if((point.x < min.x) || (point.x > max.x)) + return false; + if((point.y < min.y) || (point.y > max.y)) + return false; + if((point.z < min.z) || (point.z > max.z)) + return false; + return true; +} + +bool ChunkAABB::intersect(const ChunkAABB &other_box) const +{ + if((min.x >= other_box.max.x) || (max.x <= other_box.min.x)) + return false; + if((min.y >= other_box.max.y) || (max.y <= other_box.min.y)) + return false; + if((min.z >= other_box.max.z) || (max.z <= other_box.min.z)) + return false; + return true; +} + +ChunkAABB ChunkAABB::combine_with(const ChunkAABB &other_box) const +{ + ChunkAABB result; + result.set_bounds(min, other_box.max); + return result; +} + +ChunkAABB ChunkAABB::multiply_with(const ChunkAABB &other_box) const +{ + ChunkAABB result; + result.set_bounds(other_box.min, max); + return result; +} + +ChunkAABB ChunkAABB::push(const chunk_pos &vector) const +{ + ChunkAABB result; + result.set_bounds(min + vector, max + vector); + return result; +} diff --git a/game/shared/chunk_aabb.hh b/game/shared/chunk_aabb.hh new file mode 100644 index 0000000..68b8701 --- /dev/null +++ b/game/shared/chunk_aabb.hh @@ -0,0 +1,30 @@ +#ifndef SHARED_CHUNK_AABB +#define SHARED_CHUNK_AABB 1 +#pragma once + +#include "shared/types.hh" + +class ChunkAABB final { +public: + explicit ChunkAABB(void) = default; + virtual ~ChunkAABB(void) = default; + + void set_bounds(const chunk_pos &min, const chunk_pos &max); + void set_offset(const chunk_pos &base, const chunk_pos &size); + + const chunk_pos &get_min(void) const; + const chunk_pos &get_max(void) const; + + bool contains(const chunk_pos &point) const; + bool intersect(const ChunkAABB &other_box) const; + + ChunkAABB combine_with(const ChunkAABB &other_box) const; + ChunkAABB multiply_with(const ChunkAABB &other_box) const; + ChunkAABB push(const chunk_pos &vector) const; + +public: + chunk_pos min; + chunk_pos max; +}; + +#endif /* SHARED_CHUNK_AABB */ diff --git a/game/shared/collision.cc b/game/shared/collision.cc new file mode 100644 index 0000000..126d3bb --- /dev/null +++ b/game/shared/collision.cc @@ -0,0 +1,160 @@ +#include "shared/pch.hh" +#include "shared/collision.hh" + +#include "core/constexpr.hh" + +#include "shared/coord.hh" +#include "shared/dimension.hh" +#include "shared/globals.hh" +#include "shared/grounded.hh" +#include "shared/transform.hh" +#include "shared/velocity.hh" +#include "shared/voxel_registry.hh" + +static int vgrid_collide(const Dimension *dimension, int d, CollisionComponent &collision, TransformComponent &transform, VelocityComponent &velocity, voxel_surface &touch_surface) +{ + const auto move = globals::fixed_frametime * velocity.value[d]; + const auto move_sign = cxpr::sign(move); + + const auto &ref_aabb = collision.aabb; + const auto current_aabb = ref_aabb.push(transform.local); + + auto next_aabb = AABB(current_aabb); + next_aabb.min[d] += move; + next_aabb.max[d] += move; + + local_pos lpos_min; + lpos_min.x = cxpr::floor(next_aabb.min.x); + lpos_min.y = cxpr::floor(next_aabb.min.y); + lpos_min.z = cxpr::floor(next_aabb.min.z); + + local_pos lpos_max; + lpos_max.x = cxpr::ceil(next_aabb.max.x); + lpos_max.y = cxpr::ceil(next_aabb.max.y); + lpos_max.z = cxpr::ceil(next_aabb.max.z); + + // Other axes + const int u = (d + 1) % 3; + const int v = (d + 2) % 3; + + local_pos::value_type ddir; + local_pos::value_type dmin; + local_pos::value_type dmax; + + if(move < 0.0f) { + ddir = local_pos::value_type(+1); + dmin = lpos_min[d]; + dmax = lpos_max[d]; + } + else { + ddir = local_pos::value_type(-1); + dmin = lpos_max[d]; + dmax = lpos_min[d]; + } + + voxel_touch latch_touch = voxel_touch::NOTHING; + glm::fvec3 latch_values = glm::fvec3(0.0f, 0.0f, 0.0f); + voxel_surface latch_surface = voxel_surface::UNKNOWN; + AABB latch_vbox; + + for(auto i = dmin; i != dmax; i += ddir) { + for(auto j = lpos_min[u]; j < lpos_max[u]; ++j) + for(auto k = lpos_min[v]; k < lpos_max[v]; ++k) { + local_pos lpos; + lpos[d] = i; + lpos[u] = j; + lpos[v] = k; + + const auto vpos = coord::to_voxel(transform.chunk, lpos); + const auto info = voxel_registry::find(dimension->get_voxel(vpos)); + + if(info == nullptr) { + // Don't collide with something + // that we assume to be nothing + continue; + } + + AABB vbox; + vbox.min = glm::fvec3(lpos); + vbox.max = glm::fvec3(lpos) + 1.0f; + + if(!next_aabb.intersect(vbox)) { + // No intersection between the voxel + // and the entity's collision hull + continue; + } + + if(info->touch_type == voxel_touch::SOLID) { + // Solid touch type makes a collision + // response whenever it is encountered + velocity.value[d] = 0.0f; + touch_surface = info->surface; + 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 != voxel_touch::NOTHING) { + latch_touch = info->touch_type; + latch_values = info->touch_values; + latch_surface = info->surface; + latch_vbox = vbox; + continue; + } + } + } + + if(latch_touch != voxel_touch::NOTHING) { + if(latch_touch == voxel_touch::BOUNCE) { + const auto move_distance = cxpr::abs(current_aabb.min[d] - next_aabb.min[d]); + const auto threshold = 2.0f * globals::fixed_frametime; + + if(move_distance > threshold) + velocity.value[d] *= -latch_values[d]; + else velocity.value[d] = 0.0f; + + touch_surface = latch_surface; + + return move_sign; + } + + if(latch_touch == voxel_touch::SINK) { + velocity.value[d] *= latch_values[d]; + touch_surface = latch_surface; + return move_sign; + } + } + + return 0; +} + +void CollisionComponent::fixed_update(Dimension *dimension) +{ + // FIXME: this isn't particularly accurate considering + // some voxels might be passable and some other voxels + // might apply some slowing factor; what I might do in the + // future is to add a specific value to the voxel registry + // entries that would specify the amount of force we apply + // to prevent player movement inside a specific voxel, plus + // we shouldn't treat all voxels as full cubes if we want + // to support slabs, stairs and non-full liquid voxels in the future + + auto group = dimension->entities.group(entt::get); + + for(auto [entity, collision, transform, velocity] : group.each()) { + auto surface = voxel_surface::UNKNOWN; + auto vertical_move = vgrid_collide(dimension, 1, collision, transform, velocity, surface); + + if(vertical_move == cxpr::sign(dimension->get_gravity())) { + auto &component = dimension->entities.get_or_emplace(entity); + component.surface = surface; + } + else { + dimension->entities.remove(entity); + } + + vgrid_collide(dimension, 0, collision, transform, velocity, surface); + vgrid_collide(dimension, 2, collision, transform, velocity, surface); + } +} diff --git a/game/shared/collision.hh b/game/shared/collision.hh new file mode 100644 index 0000000..3e3643f --- /dev/null +++ b/game/shared/collision.hh @@ -0,0 +1,19 @@ +#ifndef SHARED_COLLISION_HH +#define SHARED_COLLISION_HH 1 +#pragma once + +#include "core/aabb.hh" + +class Dimension; + +struct CollisionComponent final { + AABB aabb; + +public: + // NOTE: CollisionComponent::fixed_update must be called + // before TransformComponent::fixed_update and VelocityComponent::fixed_update + // because both transform and velocity may be updated internally + static void fixed_update(Dimension *dimension); +}; + +#endif /* SHARED_COLLISION_HH */ diff --git a/game/shared/const.hh b/game/shared/const.hh new file mode 100644 index 0000000..8f8f0f9 --- /dev/null +++ b/game/shared/const.hh @@ -0,0 +1,47 @@ +#ifndef SHARED_CONST_HH +#define SHARED_CONST_HH 1 +#pragma once + +#include "core/constexpr.hh" + +constexpr static unsigned int CHUNK_SIZE = 16; +constexpr static unsigned int CHUNK_AREA = CHUNK_SIZE * CHUNK_SIZE; +constexpr static unsigned int CHUNK_VOLUME = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE; +constexpr static unsigned int CHUNK_BITSHIFT = cxpr::log2(CHUNK_SIZE); + +template +constexpr static glm::vec<3, T> DIR_NORTH = glm::vec<3, T>(0, 0, +1); +template +constexpr static glm::vec<3, T> DIR_SOUTH = glm::vec<3, T>(0, 0, -1); +template +constexpr static glm::vec<3, T> DIR_EAST = glm::vec<3, T>(-1, 0, 0); +template +constexpr static glm::vec<3, T> DIR_WEST = glm::vec<3, T>(+1, 0, 0); +template +constexpr static glm::vec<3, T> DIR_DOWN = glm::vec<3, T>(0, -1, 0); +template +constexpr static glm::vec<3, T> DIR_UP = glm::vec<3, T>(0, +1, 0); + +template +constexpr static glm::vec<3, T> DIR_FORWARD = glm::vec<3, T>(0, 0, +1); +template +constexpr static glm::vec<3, T> DIR_BACK = glm::vec<3, T>(0, 0, -1); +template +constexpr static glm::vec<3, T> DIR_LEFT = glm::vec<3, T>(-1, 0, 0); +template +constexpr static glm::vec<3, T> DIR_RIGHT = glm::vec<3, T>(+1, 0, 0); + +template +constexpr static glm::vec<3, T> UNIT_X = glm::vec<3, T>(1, 0, 0); +template +constexpr static glm::vec<3, T> UNIT_Y = glm::vec<3, T>(0, 1, 0); +template +constexpr static glm::vec<3, T> UNIT_Z = glm::vec<3, T>(0, 0, 1); + +template +constexpr static glm::vec<2, T> ZERO_VEC2 = glm::vec<2, T>(0, 0); + +template +constexpr static glm::vec<3, T> ZERO_VEC3 = glm::vec<3, T>(0, 0, 0); + +#endif /* SHARED_CONST_HH */ diff --git a/game/shared/coord.hh b/game/shared/coord.hh new file mode 100644 index 0000000..e51624f --- /dev/null +++ b/game/shared/coord.hh @@ -0,0 +1,148 @@ +#ifndef SHARED_COORD_HH +#define SHARED_COORD_HH 1 +#pragma once + +#include "shared/const.hh" +#include "shared/types.hh" + +namespace coord +{ +constexpr chunk_pos to_chunk(const voxel_pos &vpos); +} // namespace coord + +namespace coord +{ +constexpr local_pos to_local(const voxel_pos &vpos); +constexpr local_pos to_local(const glm::fvec3 &fvec); +constexpr local_pos to_local(std::size_t index); +} // namespace coord + +namespace coord +{ +constexpr voxel_pos to_voxel(const chunk_pos &cpos, const local_pos &lpos); +constexpr voxel_pos to_voxel(const chunk_pos &cpos, const glm::fvec3 &fvec); +} // namespace coord + +namespace coord +{ +constexpr std::size_t to_index(const local_pos &lpos); +} // namespace coord + +namespace coord +{ +constexpr glm::fvec3 to_relative(const chunk_pos &pivot_cpos, const chunk_pos &cpos, const glm::fvec3 &fvec); +constexpr glm::fvec3 to_relative(const chunk_pos &pivot_cpos, const glm::fvec3 &pivot_fvec, const chunk_pos &cpos); +constexpr glm::fvec3 to_relative(const chunk_pos &pivot_cpos, const glm::fvec3 &pivot_fvec, const chunk_pos &cpos, const glm::fvec3 &fvec); +} // namespace coord + +namespace coord +{ +constexpr glm::fvec3 to_fvec3(const chunk_pos &cpos); +constexpr glm::fvec3 to_fvec3(const chunk_pos &cpos, const glm::fvec3 &fpos); +} // namespace coord + +inline constexpr chunk_pos coord::to_chunk(const voxel_pos &vpos) +{ + return chunk_pos { + static_cast(vpos.x >> CHUNK_BITSHIFT), + static_cast(vpos.y >> CHUNK_BITSHIFT), + static_cast(vpos.z >> CHUNK_BITSHIFT), + }; +} + +inline constexpr local_pos coord::to_local(const voxel_pos &vpos) +{ + return local_pos { + static_cast(cxpr::mod_signed(vpos.x, CHUNK_SIZE)), + static_cast(cxpr::mod_signed(vpos.y, CHUNK_SIZE)), + static_cast(cxpr::mod_signed(vpos.z, CHUNK_SIZE)), + }; +} + +inline constexpr local_pos coord::to_local(const glm::fvec3 &fvec) +{ + return local_pos { + static_cast(fvec.x), + static_cast(fvec.y), + static_cast(fvec.z), + }; +} + +inline constexpr local_pos coord::to_local(std::size_t index) +{ + return local_pos { + static_cast((index % CHUNK_SIZE)), + static_cast((index / CHUNK_SIZE) / CHUNK_SIZE), + static_cast((index / CHUNK_SIZE) % CHUNK_SIZE), + }; +} + +inline constexpr voxel_pos coord::to_voxel(const chunk_pos &cpos, const local_pos &lpos) +{ + return voxel_pos { + lpos.x + (static_cast(cpos.x) << CHUNK_BITSHIFT), + lpos.y + (static_cast(cpos.y) << CHUNK_BITSHIFT), + lpos.z + (static_cast(cpos.z) << CHUNK_BITSHIFT), + }; +} + +inline constexpr voxel_pos coord::to_voxel(const chunk_pos &cpos, const glm::fvec3 &fvec) +{ + return voxel_pos { + static_cast(fvec.x) + (static_cast(cpos.x) << CHUNK_BITSHIFT), + static_cast(fvec.y) + (static_cast(cpos.y) << CHUNK_BITSHIFT), + static_cast(fvec.z) + (static_cast(cpos.z) << CHUNK_BITSHIFT), + }; +} + +inline constexpr std::size_t coord::to_index(const local_pos &lpos) +{ + return static_cast((lpos.y * CHUNK_SIZE + lpos.z) * CHUNK_SIZE + lpos.x); +} + +inline constexpr glm::fvec3 coord::to_relative(const chunk_pos &pivot_cpos, const chunk_pos &cpos, const glm::fvec3 &fvec) +{ + return glm::fvec3 { + static_cast((cpos.x - pivot_cpos.x) << CHUNK_BITSHIFT) + fvec.x, + static_cast((cpos.y - pivot_cpos.y) << CHUNK_BITSHIFT) + fvec.y, + static_cast((cpos.z - pivot_cpos.z) << CHUNK_BITSHIFT) + fvec.z, + }; +} + +inline constexpr glm::fvec3 coord::to_relative(const chunk_pos &pivot_cpos, const glm::fvec3 &pivot_fvec, const chunk_pos &cpos) +{ + return glm::fvec3 { + static_cast((cpos.x - pivot_cpos.x) << CHUNK_BITSHIFT) - pivot_fvec.x, + static_cast((cpos.y - pivot_cpos.y) << CHUNK_BITSHIFT) - pivot_fvec.y, + static_cast((cpos.z - pivot_cpos.z) << CHUNK_BITSHIFT) - pivot_fvec.z, + }; +} + +inline constexpr glm::fvec3 coord::to_relative(const chunk_pos &pivot_cpos, const glm::fvec3 &pivot_fvec, const chunk_pos &cpos, const glm::fvec3 &fvec) +{ + return glm::fvec3 { + static_cast((cpos.x - pivot_cpos.x) << CHUNK_BITSHIFT) + (fvec.x - pivot_fvec.x), + static_cast((cpos.y - pivot_cpos.y) << CHUNK_BITSHIFT) + (fvec.y - pivot_fvec.y), + static_cast((cpos.z - pivot_cpos.z) << CHUNK_BITSHIFT) + (fvec.z - pivot_fvec.z), + }; +} + +inline constexpr glm::fvec3 coord::to_fvec3(const chunk_pos &cpos) +{ + return glm::fvec3 { + static_cast(cpos.x << CHUNK_BITSHIFT), + static_cast(cpos.y << CHUNK_BITSHIFT), + static_cast(cpos.z << CHUNK_BITSHIFT), + }; +} + +inline constexpr glm::fvec3 coord::to_fvec3(const chunk_pos &cpos, const glm::fvec3 &fpos) +{ + return glm::fvec3 { + fpos.x + static_cast(cpos.x << CHUNK_BITSHIFT), + fpos.y + static_cast(cpos.y << CHUNK_BITSHIFT), + fpos.z + static_cast(cpos.z << CHUNK_BITSHIFT), + }; +} + +#endif /* SHARED_COORD_HH */ diff --git a/game/shared/dimension.cc b/game/shared/dimension.cc new file mode 100644 index 0000000..378779c --- /dev/null +++ b/game/shared/dimension.cc @@ -0,0 +1,163 @@ +#include "shared/pch.hh" +#include "shared/dimension.hh" + +#include "shared/coord.hh" +#include "shared/globals.hh" +#include "shared/chunk.hh" + +Dimension::Dimension(const char *name, float gravity) +{ + m_name = name; + m_gravity = gravity; +} + +Dimension::~Dimension(void) +{ + for(const auto it : m_hashmap) + delete it.second; + entities.clear(); + chunks.clear(); +} + +const char *Dimension::get_name(void) const +{ + return m_name.c_str(); +} + +float Dimension::get_gravity(void) const +{ + return m_gravity; +} + +Chunk *Dimension::create_chunk(const chunk_pos &cpos) +{ + auto it = m_hashmap.find(cpos); + + if(it != m_hashmap.cend()) { + // Chunk already exists + return it->second; + } + + auto entity = chunks.create(); + auto chunk = new Chunk(entity, this); + + auto &component = chunks.emplace(entity); + component.chunk = chunk; + component.cpos = cpos; + + ChunkCreateEvent event; + event.dimension = this; + event.chunk = chunk; + event.cpos = cpos; + + globals::dispatcher.trigger(event); + + return m_hashmap.insert_or_assign(cpos, std::move(chunk)).first->second; +} + +Chunk *Dimension::find_chunk(entt::entity entity) const +{ + if(chunks.valid(entity)) + return chunks.get(entity).chunk; + return nullptr; +} + +Chunk *Dimension::find_chunk(const chunk_pos &cpos) const +{ + auto it = m_hashmap.find(cpos); + if(it != m_hashmap.cend()) + return it->second; + return nullptr; +} + +void Dimension::remove_chunk(entt::entity entity) +{ + if(chunks.valid(entity)) { + auto &component = chunks.get(entity); + m_hashmap.erase(component.cpos); + chunks.destroy(entity); + } +} + +void Dimension::remove_chunk(const chunk_pos &cpos) +{ + auto it = m_hashmap.find(cpos); + + if(it != m_hashmap.cend()) { + chunks.destroy(it->second->get_entity()); + m_hashmap.erase(it); + } +} + +void Dimension::remove_chunk(Chunk *chunk) +{ + if(chunk) { + const auto &component = chunks.get(chunk->get_entity()); + m_hashmap.erase(component.cpos); + chunks.destroy(chunk->get_entity()); + } +} + +voxel_id Dimension::get_voxel(const voxel_pos &vpos) const +{ + auto cpos = coord::to_chunk(vpos); + auto lpos = coord::to_local(vpos); + + if(auto chunk = find_chunk(cpos)) + return chunk->get_voxel(lpos); + return NULL_VOXEL_ID; +} + +voxel_id 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 + // out-of-range values; this is useful for per-voxel update logic + return get_voxel(coord::to_voxel(cpos, lpos)); +} + +bool Dimension::set_voxel(voxel_id voxel, const voxel_pos &vpos) +{ + auto cpos = coord::to_chunk(vpos); + auto lpos = coord::to_local(vpos); + + if(auto chunk = find_chunk(cpos)) { + chunk->set_voxel(voxel, lpos); + + VoxelSetEvent event; + event.dimension = this; + event.cpos = cpos; + event.lpos = lpos; + event.voxel = voxel; + event.chunk = chunk; + + globals::dispatcher.trigger(event); + + return true; + } + + return false; +} + +bool Dimension::set_voxel(voxel_id voxel, const chunk_pos &cpos, const local_pos &lpos) +{ + // This allows accessing set_voxel with negative + // local coordinates that usually would result in an + // out-of-range values; this is useful for per-voxel update logic + return set_voxel(voxel, coord::to_voxel(cpos, lpos)); +} + +void Dimension::init(Config &config) +{ + +} + +void Dimension::init_late(std::uint64_t global_seed) +{ + +} + +bool Dimension::generate(const chunk_pos &cpos, VoxelStorage &voxels) +{ + return false; +} diff --git a/game/shared/dimension.hh b/game/shared/dimension.hh new file mode 100644 index 0000000..eb6f896 --- /dev/null +++ b/game/shared/dimension.hh @@ -0,0 +1,81 @@ +#ifndef SHARED_DIMENSION_HH +#define SHARED_DIMENSION_HH 1 +#pragma once + +#include "shared/types.hh" + +class Chunk; +class Config; +class VoxelStorage; + +class Dimension { +public: + explicit Dimension(const char *name, float gravity); + virtual ~Dimension(void); + + const char *get_name(void) const; + float get_gravity(void) const; + +public: + Chunk *create_chunk(const chunk_pos &cpos); + Chunk *find_chunk(entt::entity entity) const; + Chunk *find_chunk(const chunk_pos &cpos) const; + + void remove_chunk(entt::entity entity); + void remove_chunk(const chunk_pos &cpos); + 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; + + bool set_voxel(voxel_id voxel, const voxel_pos &vpos); + bool set_voxel(voxel_id voxel, const chunk_pos &cpos, const local_pos &lpos); + +public: + virtual void init(Config &config); + virtual void init_late(std::uint64_t global_seed); + virtual bool generate(const chunk_pos &cpos, VoxelStorage &voxels); + +public: + entt::registry chunks; + entt::registry entities; + +private: + std::string m_name; + emhash8::HashMap m_hashmap; + float m_gravity; +}; + +struct ChunkComponent final { + chunk_pos cpos; + Chunk *chunk; +}; + +struct ChunkCreateEvent final { + Dimension *dimension; + chunk_pos cpos; + Chunk *chunk; +}; + +struct ChunkDestroyEvent final { + Dimension *dimension; + chunk_pos cpos; + Chunk *chunk; +}; + +struct ChunkUpdateEvent final { + Dimension *dimension; + chunk_pos cpos; + Chunk *chunk; +}; + +struct VoxelSetEvent final { + Dimension *dimension; + chunk_pos cpos; + local_pos lpos; + voxel_id voxel; + Chunk *chunk; +}; + +#endif /* SHARED_DIMENSION_HH */ diff --git a/game/shared/factory.cc b/game/shared/factory.cc new file mode 100644 index 0000000..ee09a24 --- /dev/null +++ b/game/shared/factory.cc @@ -0,0 +1,36 @@ +#include "shared/pch.hh" +#include "shared/factory.hh" + +#include "shared/collision.hh" +#include "shared/dimension.hh" +#include "shared/globals.hh" +#include "shared/gravity.hh" +#include "shared/head.hh" +#include "shared/player.hh" +#include "shared/transform.hh" +#include "shared/velocity.hh" + +void shared_factory::create_player(Dimension *dimension, entt::entity entity) +{ + spdlog::debug("factory[{}]: assigning player components to {}", dimension->get_name(), static_cast(entity)); + + auto &collision = dimension->entities.emplace_or_replace(entity); + collision.aabb.min = glm::fvec3(-0.4f, -1.6f, -0.4f); + collision.aabb.max = glm::fvec3(+0.4f, +0.2f, +0.4f); + + dimension->entities.emplace_or_replace(entity); + + auto &head = dimension->entities.emplace_or_replace(entity); + head.angles = glm::fvec3(0.0f, 0.0f, 0.0f); + head.offset = glm::fvec3(0.0f, 0.0f, 0.0f); + + dimension->entities.emplace_or_replace(entity); + + auto &transform = dimension->entities.emplace_or_replace(entity); + transform.chunk = chunk_pos(0, 2, 0); + transform.local = glm::fvec3(0.0f, 0.0f, 0.0f); + transform.angles = glm::fvec3(0.0f, 0.0f, 0.0f); + + auto &velocity = dimension->entities.emplace_or_replace(entity); + velocity.value = glm::fvec3(0.0f, 0.0f, 0.0f); +} diff --git a/game/shared/factory.hh b/game/shared/factory.hh new file mode 100644 index 0000000..79df276 --- /dev/null +++ b/game/shared/factory.hh @@ -0,0 +1,12 @@ +#ifndef SHARED_FACTORY +#define SHARED_FACTORY 1 +#pragma once + +class Dimension; + +namespace shared_factory +{ +void create_player(Dimension *dimension, entt::entity entity); +} // namespace shared_factory + +#endif /* SHARED_FACTORY */ diff --git a/game/shared/game.cc b/game/shared/game.cc new file mode 100644 index 0000000..22693eb --- /dev/null +++ b/game/shared/game.cc @@ -0,0 +1,118 @@ +#include "shared/pch.hh" +#include "shared/game.hh" + +#include "core/cmdline.hh" + +static std::filesystem::path get_gamepath(void) +{ + if(auto gamepath = cmdline::get("gamepath")) { + // Allow users and third-party launchers to override + // content location. Perhaps this would work to allow + // for a Minecraft-like versioning approach? + return std::filesystem::path(gamepath); + } + + return std::filesystem::current_path() / "data"; +} + +static std::filesystem::path get_userpath(void) +{ + if(auto userpath = cmdline::get("userpath")) { + // Allow users and third-party launchers to override + // user data location. Perhaps this would work to allow + // for a Minecraft-like versioning approach? + return std::filesystem::path(userpath); + } + + if(auto win_appdata = std::getenv("APPDATA")) { + // On pre-seven Windows systems it's just AppData + // On post-seven Windows systems it's AppData/Roaming + return std::filesystem::path(win_appdata) / "voxelius"; + } + + if(auto xdg_home = std::getenv("XDG_DATA_HOME")) { + // Systems with an active X11/Wayland session + // theoretically should have this defined, and + // it can be different from ${HOME} (I think). + return std::filesystem::path(xdg_home) / ".voxelius"; + } + + if(auto unix_home = std::getenv("HOME")) { + // Any spherical UNIX/UNIX-like system in vacuum + // has this defined for every single user process. + return std::filesystem::path(unix_home) / ".voxelius"; + } + + // Give up and save stuff into CWD + return std::filesystem::current_path(); +} + +void shared_game::init(int argc, char **argv) +{ + auto logger = spdlog::default_logger(); + auto &logger_sinks = logger->sinks(); + + logger_sinks.clear(); + logger_sinks.push_back(std::make_shared()); + logger_sinks.push_back(std::make_shared("voxelius.log", false)); + +#if defined(NDEBUG) + constexpr auto default_loglevel = spdlog::level::info; +#else + constexpr auto default_loglevel = spdlog::level::trace; +#endif + + if(cmdline::contains("quiet")) + logger->set_level(spdlog::level::warn); + else if(cmdline::contains("verbose")) + logger->set_level(spdlog::level::trace); + else logger->set_level(default_loglevel); + + logger->set_pattern("%H:%M:%S.%e %^[%L]%$ %v"); + logger->flush(); + + if(!PHYSFS_init(argv[0])) { + spdlog::critical("physfs: init failed: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); + std::terminate(); + } + + auto gamepath = get_gamepath(); + auto userpath = get_userpath(); + + spdlog::info("shared_game: set gamepath to {}", gamepath.string()); + spdlog::info("shared_game: set userpath to {}", userpath.string()); + + std::error_code ignore_error = {}; + std::filesystem::create_directories(gamepath, ignore_error); + std::filesystem::create_directories(userpath, ignore_error); + + if(!PHYSFS_mount(gamepath.string().c_str(), nullptr, false)) { + spdlog::critical("physfs: mount {} failed: {}", gamepath.string(), PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); + std::terminate(); + } + + if(!PHYSFS_mount(userpath.string().c_str(), nullptr, false)) { + spdlog::critical("physfs: mount {} failed: {}", userpath.string(), PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); + std::terminate(); + } + + if(!PHYSFS_setWriteDir(userpath.string().c_str())) { + spdlog::critical("physfs: setwritedir {} failed: {}", userpath.string(), PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); + std::terminate(); + } + + if(enet_initialize()) { + spdlog::critical("enet: init failed"); + std::terminate(); + } +} + +void shared_game::deinit(void) +{ + enet_deinitialize(); + + if(!PHYSFS_deinit()) { + spdlog::critical("physfs: deinit failed: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); + std::terminate(); + } +} diff --git a/game/shared/game.hh b/game/shared/game.hh new file mode 100644 index 0000000..f274d25 --- /dev/null +++ b/game/shared/game.hh @@ -0,0 +1,11 @@ +#ifndef SHARED_GAME +#define SHARED_GAME 1 +#pragma once + +namespace shared_game +{ +void init(int argc, char **argv); +void deinit(void); +} // namespace shared_game + +#endif /* SHARED_GAME */ diff --git a/game/shared/game_items.cc b/game/shared/game_items.cc new file mode 100644 index 0000000..dae286f --- /dev/null +++ b/game/shared/game_items.cc @@ -0,0 +1,78 @@ +#include "shared/pch.hh" +#include "shared/game_items.hh" + +#include "shared/game_voxels.hh" +#include "shared/item_registry.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; + +void game_items::populate(void) +{ + // Stone; a hardened slate rock + game_items::stone = item_registry::construct("stone") + .set_texture("textures/item/stone.png") + .set_place_voxel(game_voxels::stone) + .build(); + + // Cobblestone; a bunch of small stones + game_items::cobblestone = item_registry::construct("cobblestone") + .set_texture("textures/item/cobblestone.png") + .set_place_voxel(game_voxels::cobblestone) + .build(); + + // Dirt; it's very dirty + game_items::dirt = item_registry::construct("dirt") + .set_texture("textures/item/dirt.png") + .set_place_voxel(game_voxels::dirt) + .build(); + + // Grass; literally just grassy dirt + game_items::grass = item_registry::construct("grass") + .set_texture("textures/item/grass.png") + .set_place_voxel(game_voxels::grass) + .build(); + + // Oak leaves; they're bushy! + game_items::oak_leaves = item_registry::construct("oak_leaves") + .set_texture("textures/item/oak_leaves.png") + .set_place_voxel(game_voxels::oak_leaves) + .build(); + + // Oak planks; watch for splinters! + game_items::oak_planks = item_registry::construct("oak_planks") + .set_texture("textures/item/oak_planks.png") + .set_place_voxel(game_voxels::oak_planks) + .build(); + + // Oak log; a big wad of wood + game_items::oak_log = item_registry::construct("oak_log") + .set_texture("textures/item/oak_log.png") + .set_place_voxel(game_voxels::oak_log) + .build(); + + // Glass; used for windowing + game_items::glass = item_registry::construct("glass") + .set_texture("textures/item/glass.png") + .set_place_voxel(game_voxels::glass) + .build(); + + // Slime; it's bouncy! + game_items::slime = 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 = item_registry::construct("mud") + .set_texture("textures/item/mud.png") + .build(); +} diff --git a/game/shared/game_items.hh b/game/shared/game_items.hh new file mode 100644 index 0000000..4e4eb81 --- /dev/null +++ b/game/shared/game_items.hh @@ -0,0 +1,26 @@ +#ifndef SHARED_GAME_ITEMS +#define SHARED_GAME_ITEMS 1 +#pragma once + +#include "shared/types.hh" + +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; +} // namespace game_items + +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 new file mode 100644 index 0000000..b72ded4 --- /dev/null +++ b/game/shared/game_voxels.cc @@ -0,0 +1,113 @@ +#include "shared/pch.hh" +#include "shared/game_voxels.hh" + +#include "shared/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; + +void game_voxels::populate(void) +{ + // Stone; the backbone of the generated world + game_voxels::stone = voxel_registry::construct("stone", 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(voxel_surface::STONE) + .build(); + + // Cobblestone; should drop when a stone is broken, might also be present in surface features + game_voxels::cobblestone = voxel_registry::construct("cobblestone", voxel_type::CUBE, false, false) + .add_texture_default("textures/voxel/cobblestone_01.png") + .add_texture_default("textures/voxel/cobblestone_02.png") + .set_surface(voxel_surface::STONE) + .build(); + + // Dirt with a grass layer on top; the top layer of plains biome + game_voxels::grass = voxel_registry::construct("grass", 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(voxel_face::CUBE_BOTTOM, "textures/voxel/dirt_01.png") + .add_texture(voxel_face::CUBE_BOTTOM, "textures/voxel/dirt_02.png") + .add_texture(voxel_face::CUBE_BOTTOM, "textures/voxel/dirt_03.png") + .add_texture(voxel_face::CUBE_BOTTOM, "textures/voxel/dirt_04.png") + .add_texture(voxel_face::CUBE_TOP, "textures/voxel/grass_01.png") + .add_texture(voxel_face::CUBE_TOP, "textures/voxel/grass_02.png") + .set_surface(voxel_surface::GRASS) + .build(); + + // Dirt; the under-surface layer of some biomes + game_voxels::dirt = voxel_registry::construct("dirt", 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(voxel_surface::DIRT) + .build(); + + // VTest; a test voxel to ensure animations work + game_voxels::vtest = voxel_registry::construct("vtest", 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 = voxel_registry::construct("vtest_ck", 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 = voxel_registry::construct("oak_leaves", voxel_type::CUBE, false, false) + .add_texture_default("textures/voxel/oak_leaves.png") + .set_surface(voxel_surface::GRASS) + .build(); + + // Oak planks; the thing that comes out of oak logs + game_voxels::oak_planks = voxel_registry::construct("oak_planks", 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(voxel_surface::WOOD) + .build(); + + // Oak logs; greenery. TODO: add trees as surface features + game_voxels::oak_log = voxel_registry::construct("oak_log", 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(voxel_face::CUBE_BOTTOM, "textures/voxel/oak_wood_top.png") + .add_texture(voxel_face::CUBE_TOP, "textures/voxel/oak_wood_top.png") + .set_surface(voxel_surface::WOOD) + .build(); + + // Glass; blend rendering test + game_voxels::glass = voxel_registry::construct("glass", voxel_type::CUBE, false, true) + .add_texture_default("textures/voxel/glass_01.png") + .set_surface(voxel_surface::GLASS) + .build(); + + // Slime; it's bouncy! + game_voxels::slime = voxel_registry::construct("slime", voxel_type::CUBE, false, true) + .set_touch(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 = voxel_registry::construct("mud", voxel_type::CUBE, false, false) + .set_touch(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(voxel_surface::DIRT) + .build(); +} diff --git a/game/shared/game_voxels.hh b/game/shared/game_voxels.hh new file mode 100644 index 0000000..1b344bf --- /dev/null +++ b/game/shared/game_voxels.hh @@ -0,0 +1,28 @@ +#ifndef SHARED_GAME_VOXELS +#define SHARED_GAME_VOXELS 1 +#pragma once + +#include "shared/types.hh" + +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; +} // namespace game_voxels + +namespace game_voxels +{ +void populate(void); +} // namespace game_voxels + +#endif /* SHARED_GAME_VOXELS */ diff --git a/game/shared/globals.cc b/game/shared/globals.cc new file mode 100644 index 0000000..466398f --- /dev/null +++ b/game/shared/globals.cc @@ -0,0 +1,11 @@ +#include "shared/pch.hh" +#include "shared/globals.hh" + +entt::dispatcher globals::dispatcher; + +float globals::fixed_frametime; +float globals::fixed_frametime_avg; +std::uint64_t globals::fixed_frametime_us; +std::size_t globals::fixed_framecount; + +std::uint64_t globals::curtime; diff --git a/game/shared/globals.hh b/game/shared/globals.hh new file mode 100644 index 0000000..216d341 --- /dev/null +++ b/game/shared/globals.hh @@ -0,0 +1,23 @@ +#ifndef SHARED_GLOBALS_HH +#define SHARED_GLOBALS_HH 1 +#pragma once + +namespace globals +{ +extern entt::dispatcher dispatcher; +} // namespace globals + +namespace globals +{ +extern float fixed_frametime; +extern float fixed_frametime_avg; +extern std::uint64_t fixed_frametime_us; +extern std::size_t fixed_framecount; +} // namespace globals + +namespace globals +{ +extern std::uint64_t curtime; +} // namespace globals + +#endif /* SHARED_GLOBALS_HH */ diff --git a/game/shared/gravity.cc b/game/shared/gravity.cc new file mode 100644 index 0000000..66b6589 --- /dev/null +++ b/game/shared/gravity.cc @@ -0,0 +1,17 @@ +#include "shared/pch.hh" +#include "shared/gravity.hh" + +#include "shared/dimension.hh" +#include "shared/globals.hh" +#include "shared/stasis.hh" +#include "shared/velocity.hh" + +void GravityComponent::fixed_update(Dimension *dimension) +{ + auto fixed_acceleration = globals::fixed_frametime * dimension->get_gravity(); + auto group = dimension->entities.group(entt::get, entt::exclude); + + for(auto [entity, velocity] : group.each()) { + velocity.value.y += fixed_acceleration; + } +} diff --git a/game/shared/gravity.hh b/game/shared/gravity.hh new file mode 100644 index 0000000..06eeae7 --- /dev/null +++ b/game/shared/gravity.hh @@ -0,0 +1,12 @@ +#ifndef SHARED_GRAVITY_HH +#define SHARED_GRAVITY_HH 1 +#pragma once + +class Dimension; + +struct GravityComponent final { +public: + static void fixed_update(Dimension *dimension); +}; + +#endif /* SHARED_GRAVITY_HH */ diff --git a/game/shared/grounded.hh b/game/shared/grounded.hh new file mode 100644 index 0000000..869cca0 --- /dev/null +++ b/game/shared/grounded.hh @@ -0,0 +1,13 @@ +#ifndef SHARED_GROUNDED +#define SHARED_GROUNDED 1 +#pragma once + +#include "shared/voxel_registry.hh" + +// Assigned to entities which are grounded +// according to the collision and gravity system +struct GroundedComponent final { + voxel_surface surface; +}; + +#endif /* SHARED_GROUNDED */ diff --git a/game/shared/head.hh b/game/shared/head.hh new file mode 100644 index 0000000..57dd445 --- /dev/null +++ b/game/shared/head.hh @@ -0,0 +1,14 @@ +#ifndef SHARED_HEAD_HH +#define SHARED_HEAD_HH 1 +#pragma once + +struct HeadComponent { + glm::fvec3 angles; + glm::fvec3 offset; +}; + +// Client-side only - interpolated and previous head +struct HeadComponentIntr final : public HeadComponent {}; +struct HeadComponentPrev final : public HeadComponent {}; + +#endif /* SHARED_HEAD_HH */ diff --git a/game/shared/item_registry.cc b/game/shared/item_registry.cc new file mode 100644 index 0000000..02164bd --- /dev/null +++ b/game/shared/item_registry.cc @@ -0,0 +1,80 @@ +#include "shared/pch.hh" +#include "shared/item_registry.hh" + +#include "shared/voxel_registry.hh" + +std::unordered_map item_registry::builders = {}; +std::unordered_map item_registry::names = {}; +std::vector> item_registry::items = {}; + +ItemInfoBuilder::ItemInfoBuilder(const char *name) +{ + prototype.name = name; + prototype.texture = std::string(); + prototype.place_voxel = NULL_VOXEL_ID; + prototype.cached_texture = nullptr; +} + +ItemInfoBuilder &ItemInfoBuilder::set_texture(const char *texture) +{ + prototype.texture = texture; + prototype.cached_texture = nullptr; + return *this; +} + +ItemInfoBuilder &ItemInfoBuilder::set_place_voxel(voxel_id place_voxel) +{ + prototype.place_voxel = place_voxel; + return *this; +} + +item_id ItemInfoBuilder::build(void) const +{ + const auto it = item_registry::names.find(prototype.name); + + if(it != item_registry::names.cend()) { + spdlog::warn("item_registry: cannot build {}: name already present", prototype.name); + return it->second; + } + + 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; + + item_registry::items.push_back(new_info); + item_registry::names.insert_or_assign(prototype.name, static_cast(item_registry::items.size())); + + return static_cast(item_registry::items.size()); +} + +ItemInfoBuilder &item_registry::construct(const char *name) +{ + const auto it = item_registry::builders.find(name); + if(it != item_registry::builders.cend()) + return it->second; + return item_registry::builders.emplace(name, ItemInfoBuilder(name)).first->second; +} + +ItemInfo *item_registry::find(const char *name) +{ + const auto it = item_registry::names.find(name); + if(it != item_registry::names.cend()) + return item_registry::find(it->second); + return nullptr; +} + +ItemInfo *item_registry::find(const item_id item) +{ + if((item != NULL_ITEM_ID) && (item <= item_registry::items.size())) + return item_registry::items[item - 1].get(); + return nullptr; +} + +void item_registry::purge(void) +{ + item_registry::builders.clear(); + item_registry::names.clear(); + item_registry::items.clear(); +} diff --git a/game/shared/item_registry.hh b/game/shared/item_registry.hh new file mode 100644 index 0000000..7160b0b --- /dev/null +++ b/game/shared/item_registry.hh @@ -0,0 +1,57 @@ +#ifndef SHARED_ITEM_REGISTRY_HH +#define SHARED_ITEM_REGISTRY_HH 1 +#pragma once + +#include "core/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; + +struct ItemInfo final { + std::string name; + std::string texture; + voxel_id place_voxel; + + resource_ptr cached_texture; // Client-side only +}; + +class ItemInfoBuilder final { +public: + explicit ItemInfoBuilder(const char *name); + virtual ~ItemInfoBuilder(void) = default; + +public: + ItemInfoBuilder &set_texture(const char *texture); + ItemInfoBuilder &set_place_voxel(voxel_id place_voxel); + +public: + item_id build(void) const; + +private: + ItemInfo prototype; +}; + +namespace item_registry +{ +extern std::unordered_map builders; +extern std::unordered_map names; +extern std::vector> items; +} // namespace item_registry + +namespace item_registry +{ +ItemInfoBuilder &construct(const char *name); +ItemInfo *find(const char *name); +ItemInfo *find(const item_id item); +} // namespace item_registry + +namespace item_registry +{ +void purge(void); +} // namespace item_registry + +#endif /* SHARED_ITEM_REGISTRY_HH */ diff --git a/game/shared/pch.hh b/game/shared/pch.hh new file mode 100644 index 0000000..3588709 --- /dev/null +++ b/game/shared/pch.hh @@ -0,0 +1,23 @@ +#ifndef SHARED_PCH_HH +#define SHARED_PCH_HH 1 +#pragma once + +#include + +#include + +#include + +#include +#include + +#include + +#include + +#include + +#include +#include + +#endif /* SHARED_PCH_HH */ diff --git a/game/shared/player.hh b/game/shared/player.hh new file mode 100644 index 0000000..a01b4f2 --- /dev/null +++ b/game/shared/player.hh @@ -0,0 +1,7 @@ +#ifndef SHARED_PLAYER_HH +#define SHARED_PLAYER_HH 1 +#pragma once + +struct PlayerComponent final {}; + +#endif /* SHARED_PLAYER_HH */ diff --git a/game/shared/protocol.cc b/game/shared/protocol.cc new file mode 100644 index 0000000..e2c79b5 --- /dev/null +++ b/game/shared/protocol.cc @@ -0,0 +1,490 @@ +#include "shared/pch.hh" +#include "shared/protocol.hh" + +#include "core/buffer.hh" +#include "core/floathacks.hh" + +#include "shared/chunk.hh" +#include "shared/dimension.hh" +#include "shared/globals.hh" +#include "shared/head.hh" +#include "shared/player.hh" +#include "shared/transform.hh" +#include "shared/velocity.hh" + +static ReadBuffer read_buffer; +static WriteBuffer write_buffer; + +ENetPacket *protocol::encode(const protocol::StatusRequest &packet, enet_uint32 flags) +{ + write_buffer.reset(); + write_buffer.write_UI16(protocol::StatusRequest::ID); + write_buffer.write_UI32(packet.version); + return write_buffer.to_packet(flags); +} + +ENetPacket *protocol::encode(const protocol::StatusResponse &packet, enet_uint32 flags) +{ + write_buffer.reset(); + write_buffer.write_UI16(protocol::StatusResponse::ID); + write_buffer.write_UI32(packet.version); + write_buffer.write_UI16(packet.max_players); + write_buffer.write_UI16(packet.num_players); + write_buffer.write_string(packet.motd); + return write_buffer.to_packet(flags); +} + +ENetPacket *protocol::encode(const protocol::LoginRequest &packet, enet_uint32 flags) +{ + write_buffer.reset(); + write_buffer.write_UI16(protocol::LoginRequest::ID); + write_buffer.write_UI32(packet.version); + write_buffer.write_UI64(packet.voxel_def_checksum); + write_buffer.write_UI64(packet.item_def_checksum); + write_buffer.write_UI64(packet.password_hash); + write_buffer.write_string(packet.username.substr(0, protocol::MAX_USERNAME)); + return write_buffer.to_packet(flags); +} + +ENetPacket *protocol::encode(const protocol::LoginResponse &packet, enet_uint32 flags) +{ + write_buffer.reset(); + write_buffer.write_UI16(protocol::LoginResponse::ID); + write_buffer.write_UI16(packet.client_index); + write_buffer.write_UI64(packet.client_identity); + write_buffer.write_UI16(packet.server_tickrate); + return write_buffer.to_packet(flags); +} + +ENetPacket *protocol::encode(const protocol::Disconnect &packet, enet_uint32 flags) +{ + write_buffer.reset(); + write_buffer.write_UI16(protocol::Disconnect::ID); + write_buffer.write_string(packet.reason); + return write_buffer.to_packet(flags); +} + +ENetPacket *protocol::encode(const protocol::ChunkVoxels &packet, enet_uint32 flags) +{ + write_buffer.reset(); + write_buffer.write_UI16(protocol::ChunkVoxels::ID); + write_buffer.write_I32(packet.chunk.x); + write_buffer.write_I32(packet.chunk.y); + write_buffer.write_I32(packet.chunk.z); + packet.voxels.serialize(write_buffer); + return write_buffer.to_packet(flags); +} + +ENetPacket *protocol::encode(const protocol::EntityTransform &packet, enet_uint32 flags) +{ + write_buffer.reset(); + write_buffer.write_UI16(protocol::EntityTransform::ID); + write_buffer.write_UI64(static_cast(packet.entity)); + write_buffer.write_I32(packet.chunk.x); + write_buffer.write_I32(packet.chunk.y); + write_buffer.write_I32(packet.chunk.z); + write_buffer.write_FP32(packet.local.x); + write_buffer.write_FP32(packet.local.y); + write_buffer.write_FP32(packet.local.z); + write_buffer.write_FP32(packet.angles.x); + write_buffer.write_FP32(packet.angles.y); + write_buffer.write_FP32(packet.angles.z); + return write_buffer.to_packet(flags); +} + +ENetPacket *protocol::encode(const protocol::EntityHead &packet, enet_uint32 flags) +{ + write_buffer.reset(); + write_buffer.write_UI16(protocol::EntityHead::ID); + write_buffer.write_UI64(static_cast(packet.entity)); + write_buffer.write_FP32(packet.angles.x); + write_buffer.write_FP32(packet.angles.y); + write_buffer.write_FP32(packet.angles.z); + return write_buffer.to_packet(flags); +} + +ENetPacket *protocol::encode(const protocol::EntityVelocity &packet, enet_uint32 flags) +{ + write_buffer.reset(); + write_buffer.write_UI16(protocol::EntityVelocity::ID); + write_buffer.write_UI64(static_cast(packet.entity)); + write_buffer.write_FP32(packet.value.x); + write_buffer.write_FP32(packet.value.y); + write_buffer.write_FP32(packet.value.z); + return write_buffer.to_packet(flags); +} + +ENetPacket *protocol::encode(const protocol::SpawnPlayer &packet, enet_uint32 flags) +{ + write_buffer.reset(); + write_buffer.write_UI16(protocol::SpawnPlayer::ID); + write_buffer.write_UI64(static_cast(packet.entity)); + return write_buffer.to_packet(flags); +} + +ENetPacket *protocol::encode(const protocol::ChatMessage &packet, enet_uint32 flags) +{ + write_buffer.reset(); + write_buffer.write_UI16(protocol::ChatMessage::ID); + write_buffer.write_UI16(packet.type); + write_buffer.write_string(packet.sender.substr(0, protocol::MAX_USERNAME)); + write_buffer.write_string(packet.message.substr(0, protocol::MAX_CHAT)); + return write_buffer.to_packet(flags); +} + +ENetPacket *protocol::encode(const protocol::SetVoxel &packet, enet_uint32 flags) +{ + write_buffer.reset(); + write_buffer.write_UI16(protocol::SetVoxel::ID); + write_buffer.write_I64(packet.vpos.x); + write_buffer.write_I64(packet.vpos.y); + write_buffer.write_I64(packet.vpos.z); + write_buffer.write_UI16(packet.voxel); + write_buffer.write_UI16(packet.flags); + return write_buffer.to_packet(flags); +} + +ENetPacket *protocol::encode(const protocol::RemoveEntity &packet, enet_uint32 flags) +{ + write_buffer.reset(); + write_buffer.write_UI16(protocol::RemoveEntity::ID); + write_buffer.write_UI64(static_cast(packet.entity)); + return write_buffer.to_packet(flags); +} + +ENetPacket *protocol::encode(const protocol::EntityPlayer &packet, enet_uint32 flags) +{ + write_buffer.reset(); + write_buffer.write_UI16(protocol::EntityPlayer::ID); + write_buffer.write_UI64(static_cast(packet.entity)); + return write_buffer.to_packet(flags); +} + +ENetPacket *protocol::encode(const protocol::ScoreboardUpdate &packet, enet_uint32 flags) +{ + write_buffer.reset(); + write_buffer.write_UI16(protocol::ScoreboardUpdate::ID); + write_buffer.write_UI16(static_cast(packet.names.size())); + for(const std::string &username : packet.names) + write_buffer.write_string(username.substr(0, protocol::MAX_USERNAME)); + return write_buffer.to_packet(flags); +} + +ENetPacket *protocol::encode(const protocol::RequestChunk &packet, enet_uint32 flags) +{ + write_buffer.reset(); + write_buffer.write_UI16(protocol::RequestChunk::ID); + write_buffer.write_I32(packet.cpos.x); + write_buffer.write_I32(packet.cpos.y); + write_buffer.write_I32(packet.cpos.z); + return write_buffer.to_packet(flags); +} + +ENetPacket *protocol::encode(const protocol::GenericSound &packet, enet_uint32 flags) +{ + write_buffer.reset(); + write_buffer.write_UI16(protocol::GenericSound::ID); + write_buffer.write_string(packet.sound.substr(0, protocol::MAX_SOUNDNAME)); + write_buffer.write_UI8(packet.looping); + write_buffer.write_FP32(packet.pitch); + return write_buffer.to_packet(flags); +} + +ENetPacket *protocol::encode(const protocol::EntitySound &packet, enet_uint32 flags) +{ + write_buffer.reset(); + write_buffer.write_UI16(protocol::EntitySound::ID); + write_buffer.write_UI64(static_cast(packet.entity)); + write_buffer.write_string(packet.sound.substr(0, protocol::MAX_SOUNDNAME)); + write_buffer.write_UI8(packet.looping); + write_buffer.write_FP32(packet.pitch); + return write_buffer.to_packet(flags); +} + +ENetPacket *protocol::encode(const protocol::DimensionInfo &packet, enet_uint32 flags) +{ + write_buffer.reset(); + write_buffer.write_UI16(protocol::DimensionInfo::ID); + write_buffer.write_string(packet.name); + write_buffer.write_FP32(packet.gravity); + return write_buffer.to_packet(flags); +} + +void protocol::broadcast(ENetHost *host, ENetPacket *packet) +{ + if(packet) { + enet_host_broadcast(host, protocol::CHANNEL, packet); + } +} + +void protocol::broadcast(ENetHost *host, ENetPacket *packet, ENetPeer *except) +{ + if(packet) { + for(unsigned int i = 0U; i < host->peerCount; ++i) { + if(host->peers[i].state == ENET_PEER_STATE_CONNECTED) { + if(&host->peers[i] == except) + continue; + enet_peer_send(&host->peers[i], protocol::CHANNEL, packet); + } + } + } +} + +void protocol::send(ENetPeer *peer, ENetPacket *packet) +{ + if(packet) { + enet_peer_send(peer, protocol::CHANNEL, packet); + } +} + +void protocol::decode(entt::dispatcher &dispatcher, const ENetPacket *packet, ENetPeer *peer) +{ + read_buffer.reset(packet); + + protocol::StatusRequest status_request; + protocol::StatusResponse status_response; + protocol::LoginRequest login_request; + protocol::LoginResponse login_response; + protocol::Disconnect disconnect; + protocol::ChunkVoxels chunk_voxels; + protocol::EntityTransform entity_transform; + protocol::EntityHead entity_head; + protocol::EntityVelocity entity_velocity; + protocol::SpawnPlayer spawn_player; + protocol::ChatMessage chat_message; + protocol::SetVoxel set_voxel; + protocol::RemoveEntity remove_entity; + protocol::EntityPlayer entity_player; + protocol::ScoreboardUpdate scoreboard_update; + protocol::RequestChunk request_chunk; + protocol::GenericSound generic_sound; + protocol::EntitySound entity_sound; + protocol::DimensionInfo dimension_info; + + auto id = read_buffer.read_UI16(); + + switch(id) { + case protocol::StatusRequest::ID: + status_request.peer = peer; + status_request.version = read_buffer.read_UI32(); + dispatcher.trigger(status_request); + break; + case protocol::StatusResponse::ID: + status_response.peer = peer; + status_response.version = read_buffer.read_UI32(); + status_response.max_players = read_buffer.read_UI16(); + status_response.num_players = read_buffer.read_UI16(); + status_response.motd = read_buffer.read_string(); + dispatcher.trigger(status_response); + break; + case protocol::LoginRequest::ID: + login_request.peer = peer; + login_request.version = read_buffer.read_UI32(); + login_request.voxel_def_checksum = read_buffer.read_UI64(); + login_request.item_def_checksum = read_buffer.read_UI64(); + login_request.password_hash = read_buffer.read_UI64(); + login_request.username = read_buffer.read_string(); + dispatcher.trigger(login_request); + break; + case protocol::LoginResponse::ID: + login_response.peer = peer; + login_response.client_index = read_buffer.read_UI16(); + login_response.client_identity = read_buffer.read_UI64(); + login_response.server_tickrate = read_buffer.read_UI16(); + dispatcher.trigger(login_response); + break; + case protocol::Disconnect::ID: + disconnect.peer = peer; + disconnect.reason = read_buffer.read_string(); + dispatcher.trigger(disconnect); + break; + case protocol::ChunkVoxels::ID: + chunk_voxels.peer = peer; + chunk_voxels.chunk.x = read_buffer.read_I32(); + chunk_voxels.chunk.y = read_buffer.read_I32(); + chunk_voxels.chunk.z = read_buffer.read_I32(); + chunk_voxels.voxels.deserialize(read_buffer); + dispatcher.trigger(chunk_voxels); + break; + case protocol::EntityTransform::ID: + entity_transform.peer = peer; + entity_transform.entity = static_cast(read_buffer.read_UI64()); + entity_transform.chunk.x = read_buffer.read_I32(); + entity_transform.chunk.y = read_buffer.read_I32(); + entity_transform.chunk.z = read_buffer.read_I32(); + entity_transform.local.x = read_buffer.read_FP32(); + entity_transform.local.y = read_buffer.read_FP32(); + entity_transform.local.z = read_buffer.read_FP32(); + entity_transform.angles.x = read_buffer.read_FP32(); + entity_transform.angles.y = read_buffer.read_FP32(); + entity_transform.angles.z = read_buffer.read_FP32(); + dispatcher.trigger(entity_transform); + break; + case protocol::EntityHead::ID: + entity_head.peer = peer; + entity_head.entity = static_cast(read_buffer.read_UI64()); + entity_head.angles[0] = read_buffer.read_FP32(); + entity_head.angles[1] = read_buffer.read_FP32(); + entity_head.angles[2] = read_buffer.read_FP32(); + dispatcher.trigger(entity_head); + break; + case protocol::EntityVelocity::ID: + entity_velocity.peer = peer; + entity_velocity.entity = static_cast(read_buffer.read_UI64()); + entity_velocity.value.x = read_buffer.read_FP32(); + entity_velocity.value.y = read_buffer.read_FP32(); + entity_velocity.value.z = read_buffer.read_FP32(); + dispatcher.trigger(entity_velocity); + break; + case protocol::SpawnPlayer::ID: + spawn_player.peer = peer; + spawn_player.entity = static_cast(read_buffer.read_UI64()); + dispatcher.trigger(spawn_player); + break; + case protocol::ChatMessage::ID: + chat_message.peer = peer; + chat_message.type = read_buffer.read_UI16(); + chat_message.sender = read_buffer.read_string(); + chat_message.message = read_buffer.read_string(); + dispatcher.trigger(chat_message); + break; + case protocol::SetVoxel::ID: + set_voxel.peer = peer; + set_voxel.vpos.x = read_buffer.read_I64(); + set_voxel.vpos.y = read_buffer.read_I64(); + set_voxel.vpos.z = read_buffer.read_I64(); + set_voxel.voxel = read_buffer.read_UI16(); + set_voxel.flags = read_buffer.read_UI16(); + dispatcher.trigger(set_voxel); + break; + case protocol::RemoveEntity::ID: + remove_entity.peer = peer; + remove_entity.entity = static_cast(read_buffer.read_UI64()); + dispatcher.trigger(remove_entity); + break; + case protocol::EntityPlayer::ID: + entity_player.peer = peer; + entity_player.entity = static_cast(read_buffer.read_UI64()); + dispatcher.trigger(entity_player); + break; + case protocol::ScoreboardUpdate::ID: + scoreboard_update.peer = peer; + scoreboard_update.names.resize(read_buffer.read_UI16()); + for(std::size_t i = 0; i < scoreboard_update.names.size(); ++i) + scoreboard_update.names[i] = read_buffer.read_string(); + dispatcher.trigger(scoreboard_update); + break; + case protocol::RequestChunk::ID: + request_chunk.peer = peer; + request_chunk.cpos.x = read_buffer.read_UI32(); + request_chunk.cpos.y = read_buffer.read_UI32(); + request_chunk.cpos.z = read_buffer.read_UI32(); + dispatcher.trigger(request_chunk); + break; + case protocol::GenericSound::ID: + generic_sound.peer = peer; + generic_sound.sound = read_buffer.read_string(); + generic_sound.looping = read_buffer.read_UI8(); + generic_sound.pitch = read_buffer.read_FP32(); + dispatcher.trigger(generic_sound); + break; + case protocol::EntitySound::ID: + entity_sound.peer = peer; + entity_sound.entity = static_cast(read_buffer.read_UI64()); + entity_sound.sound = read_buffer.read_string(); + entity_sound.looping = read_buffer.read_UI8(); + entity_sound.pitch = read_buffer.read_FP32(); + dispatcher.trigger(entity_sound); + break; + case protocol::DimensionInfo::ID: + dimension_info.peer = peer; + dimension_info.name = read_buffer.read_string(); + dimension_info.gravity = read_buffer.read_FP32(); + dispatcher.trigger(dimension_info); + break; + } +} + +ENetPacket *protocol::utils::make_disconnect(const char *reason, enet_uint32 flags) +{ + protocol::Disconnect packet; + packet.reason = std::string(reason); + return protocol::encode(packet, flags); +} + +ENetPacket *protocol::utils::make_chat_message(const char *message, enet_uint32 flags) +{ + protocol::ChatMessage packet; + packet.type = protocol::ChatMessage::TEXT_MESSAGE; + packet.message = std::string(message); + return protocol::encode(packet, flags); +} + +ENetPacket *protocol::utils::make_chunk_voxels(Dimension *dimension, entt::entity entity, enet_uint32 flags) +{ + if(auto component = dimension->chunks.try_get(entity)) { + protocol::ChunkVoxels packet; + packet.chunk = component->cpos; + packet.voxels = component->chunk->get_voxels(); + return protocol::encode(packet, flags); + } + + return nullptr; +} + +ENetPacket *protocol::utils::make_entity_head(Dimension *dimension, entt::entity entity, enet_uint32 flags) +{ + if(auto component = dimension->entities.try_get(entity)) { + protocol::EntityHead packet; + packet.entity = entity; + packet.angles = component->angles; + return protocol::encode(packet, flags); + } + + return nullptr; +} + +ENetPacket *protocol::utils::make_entity_transform(Dimension *dimension, entt::entity entity, enet_uint32 flags) +{ + if(auto component = dimension->entities.try_get(entity)) { + protocol::EntityTransform packet; + packet.entity = entity; + packet.chunk = component->chunk; + packet.local = component->local; + packet.angles = component->angles; + return protocol::encode(packet, flags); + } + + return nullptr; +} + +ENetPacket *protocol::utils::make_entity_velocity(Dimension *dimension, entt::entity entity, enet_uint32 flags) +{ + if(auto component = dimension->entities.try_get(entity)) { + protocol::EntityVelocity packet; + packet.entity = entity; + packet.value = component->value; + return protocol::encode(packet, flags); + } + + return nullptr; +} + +ENetPacket *protocol::utils::make_entity_player(Dimension *dimension, entt::entity entity, enet_uint32 flags) +{ + if(dimension->entities.any_of(entity)) { + protocol::EntityPlayer packet; + packet.entity = entity; + return protocol::encode(packet, flags); + } + + return nullptr; +} + +ENetPacket *protocol::utils::make_dimension_info(const Dimension *dimension) +{ + protocol::DimensionInfo packet; + packet.name = dimension->get_name(); + packet.gravity = dimension->get_gravity(); + return protocol::encode(packet, ENET_PACKET_FLAG_RELIABLE); +} diff --git a/game/shared/protocol.hh b/game/shared/protocol.hh new file mode 100644 index 0000000..0b8b0f1 --- /dev/null +++ b/game/shared/protocol.hh @@ -0,0 +1,213 @@ +#ifndef SHARED_PROTOCOL_HH +#define SHARED_PROTOCOL_HH 1 +#pragma once + +#include "shared/chunk.hh" + +class Dimension; + +namespace protocol +{ +constexpr static std::size_t MAX_CHAT = 16384; +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 + +namespace protocol +{ +template +struct Base { + constexpr static std::uint16_t ID = packet_id; + virtual ~Base(void) = default; + ENetPeer *peer {nullptr}; +}; +} // namespace protocol + +namespace protocol +{ +struct StatusRequest; +struct StatusResponse; +struct LoginRequest; +struct LoginResponse; +struct Disconnect; +struct ChunkVoxels; +struct EntityTransform; +struct EntityHead; +struct EntityVelocity; +struct SpawnPlayer; +struct ChatMessage; +struct SetVoxel; +struct RemoveEntity; +struct EntityPlayer; +struct ScoreboardUpdate; +struct RequestChunk; +struct GenericSound; +struct EntitySound; +struct DimensionInfo; +} // namespace protocol + +namespace protocol +{ +ENetPacket *encode(const StatusRequest &packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *encode(const StatusResponse &packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *encode(const LoginRequest &packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *encode(const LoginResponse &packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *encode(const Disconnect &packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *encode(const ChunkVoxels &packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *encode(const EntityTransform &packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *encode(const EntityHead &packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *encode(const EntityVelocity &packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *encode(const SpawnPlayer &packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *encode(const ChatMessage &packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *encode(const SetVoxel &packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *encode(const RemoveEntity &packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *encode(const EntityPlayer &packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *encode(const ScoreboardUpdate &packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *encode(const RequestChunk &packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *encode(const GenericSound &packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *encode(const EntitySound &packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *encode(const DimensionInfo &packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +} // namespace protocol + +namespace protocol +{ +void broadcast(ENetHost *host, ENetPacket *packet); +void broadcast(ENetHost *host, ENetPacket *packet, ENetPeer *except); +void send(ENetPeer *peer, ENetPacket *packet); +} // namespace protocol + +namespace protocol +{ +void decode(entt::dispatcher &dispatcher, const ENetPacket *packet, ENetPeer *peer); +} // namespace protocol + +namespace protocol::utils +{ +ENetPacket *make_disconnect(const char *reason, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *make_chat_message(const char *message, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +} // namespace protocol::utils + +namespace protocol::utils +{ +ENetPacket *make_chunk_voxels(Dimension *dimension, entt::entity entity, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +} // namespace protocol::utils + +namespace protocol::utils +{ +ENetPacket *make_entity_head(Dimension *dimension, entt::entity entity, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *make_entity_transform(Dimension *dimension, entt::entity entity, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *make_entity_velocity(Dimension *dimension, entt::entity entity, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *make_entity_player(Dimension *dimension, entt::entity entity, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); +ENetPacket *make_dimension_info(const Dimension *dimension); +} // namespace protocol::utils + +struct protocol::StatusRequest final : public protocol::Base<0x0000> { + std::uint32_t version; +}; + +struct protocol::StatusResponse final : public protocol::Base<0x0001> { + std::uint32_t version; + std::uint16_t max_players; + std::uint16_t num_players; + std::string motd; +}; + +struct protocol::LoginRequest final : public protocol::Base<0x0002> { + std::uint32_t version; + std::uint64_t voxel_def_checksum; + std::uint64_t item_def_checksum; + std::uint64_t password_hash; + std::string username; +}; + +struct protocol::LoginResponse final : public protocol::Base<0x0003> { + std::uint16_t client_index; + std::uint64_t client_identity; + std::uint16_t server_tickrate; +}; + +struct protocol::Disconnect final : public protocol::Base<0x0004> { + std::string reason; +}; + +struct protocol::ChunkVoxels final : public protocol::Base<0x0005> { + chunk_pos chunk; + VoxelStorage voxels; +}; + +struct protocol::EntityTransform final : public protocol::Base<0x0006> { + entt::entity entity; + chunk_pos chunk; + glm::fvec3 local; + glm::fvec3 angles; +}; + +struct protocol::EntityHead final : public protocol::Base<0x0007> { + entt::entity entity; + glm::fvec3 angles; +}; + +struct protocol::EntityVelocity final : public protocol::Base<0x0008> { + entt::entity entity; + glm::fvec3 value; +}; + +struct protocol::SpawnPlayer final : public protocol::Base<0x0009> { + entt::entity entity; +}; + +struct protocol::ChatMessage final : public protocol::Base<0x000A> { + constexpr static std::uint16_t TEXT_MESSAGE = 0x0000; + constexpr static std::uint16_t PLAYER_JOIN = 0x0001; + constexpr static std::uint16_t PLAYER_LEAVE = 0x0002; + + std::uint16_t type; + std::string sender; + std::string message; +}; + +struct protocol::SetVoxel final : public protocol::Base<0x000B> { + voxel_pos vpos; + voxel_id voxel; + std::uint16_t flags; +}; + +struct protocol::RemoveEntity final : public protocol::Base<0x000C> { + entt::entity entity; +}; + +struct protocol::EntityPlayer final : public protocol::Base<0x000D> { + entt::entity entity; +}; + +struct protocol::ScoreboardUpdate final : public protocol::Base<0x000E> { + std::vector names; +}; + +struct protocol::RequestChunk final : public protocol::Base<0x000F> { + chunk_pos cpos; +}; + +struct protocol::GenericSound final : public protocol::Base<0x0010> { + std::string sound; + bool looping; + float pitch; +}; + +struct protocol::EntitySound final : public protocol::Base<0x0011> { + entt::entity entity; + std::string sound; + bool looping; + float pitch; +}; + +struct protocol::DimensionInfo final : public protocol::Base<0x0012> { + std::string name; + float gravity; +}; + +#endif /* SHARED_PROTOCOL_HH */ diff --git a/game/shared/ray_dda.cc b/game/shared/ray_dda.cc new file mode 100644 index 0000000..132d05a --- /dev/null +++ b/game/shared/ray_dda.cc @@ -0,0 +1,102 @@ +#include "shared/pch.hh" +#include "shared/ray_dda.hh" + +#include "shared/coord.hh" +#include "shared/dimension.hh" + +RayDDA::RayDDA(const Dimension *dimension, const chunk_pos &start_chunk, const glm::fvec3 &start_fpos, const glm::fvec3 &direction) +{ + reset(dimension, start_chunk, start_fpos, direction); +} + +RayDDA::RayDDA(const Dimension &dimension, const chunk_pos &start_chunk, const glm::fvec3 &start_fpos, const glm::fvec3 &direction) +{ + reset(dimension, start_chunk, start_fpos, direction); +} + +void RayDDA::reset(const Dimension *dimension, const chunk_pos &start_chunk, const glm::fvec3 &start_fpos, const glm::fvec3 &direction) +{ + this->dimension = dimension; + this->start_chunk = start_chunk; + this->start_fpos = start_fpos; + this->direction = direction; + + this->delta_dist.x = direction.x ? cxpr::abs(1.0f / direction.x) : std::numeric_limits::max(); + this->delta_dist.y = direction.y ? cxpr::abs(1.0f / direction.y) : std::numeric_limits::max(); + this->delta_dist.z = direction.z ? cxpr::abs(1.0f / direction.z) : std::numeric_limits::max(); + + this->distance = 0.0f; + this->vpos = coord::to_voxel(start_chunk, start_fpos); + this->vnormal = voxel_pos(0, 0, 0); + + // Need this for initial direction calculations + auto lpos = coord::to_local(start_fpos); + + if(direction.x < 0.0f) { + this->side_dist.x = this->delta_dist.x * (start_fpos.x - lpos.x); + this->vstep.x = voxel_pos::value_type(-1); + } + else { + this->side_dist.x = this->delta_dist.x * (lpos.x + 1.0f - start_fpos.x); + this->vstep.x = voxel_pos::value_type(+1); + } + + if(direction.y < 0.0f) { + this->side_dist.y = this->delta_dist.y * (start_fpos.y - lpos.y); + this->vstep.y = voxel_pos::value_type(-1); + } + else { + this->side_dist.y = this->delta_dist.y * (lpos.y + 1.0f - start_fpos.y); + this->vstep.y = voxel_pos::value_type(+1); + } + + if(direction.z < 0.0f) { + this->side_dist.z = this->delta_dist.z * (start_fpos.z - lpos.z); + this->vstep.z = voxel_pos::value_type(-1); + } + else { + this->side_dist.z = this->delta_dist.z * (lpos.z + 1.0f - start_fpos.z); + this->vstep.z = voxel_pos::value_type(+1); + } +} + +void RayDDA::reset(const Dimension &dimension, const chunk_pos &start_chunk, const glm::fvec3 &start_fpos, const glm::fvec3 &direction) +{ + reset(&dimension, start_chunk, start_fpos, direction); +} + +voxel_id RayDDA::step(void) +{ + if(side_dist.x < side_dist.z) { + if(side_dist.x < side_dist.y) { + vnormal = voxel_pos(-vstep.x, 0, 0); + distance = side_dist.x; + side_dist.x += delta_dist.x; + vpos.x += vstep.x; + } + else { + vnormal = voxel_pos(0, -vstep.y, 0); + distance = side_dist.y; + side_dist.y += delta_dist.y; + vpos.y += vstep.y; + } + } + else { + if(side_dist.z < side_dist.y) { + vnormal = voxel_pos(0, 0, -vstep.z); + distance = side_dist.z; + side_dist.z += delta_dist.z; + vpos.z += vstep.z; + } + else { + vnormal = voxel_pos(0, -vstep.y, 0); + distance = side_dist.y; + side_dist.y += delta_dist.y; + vpos.y += vstep.y; + } + } + + // This is slower than I want it to be + return dimension->get_voxel(vpos); +} + diff --git a/game/shared/ray_dda.hh b/game/shared/ray_dda.hh new file mode 100644 index 0000000..9b4374e --- /dev/null +++ b/game/shared/ray_dda.hh @@ -0,0 +1,35 @@ +#ifndef SHARED_RAY_DDA +#define SHARED_RAY_DDA 1 +#pragma once + +#include "shared/types.hh" + +class Dimension; + +class RayDDA final { +public: + explicit RayDDA(void) = default; + explicit RayDDA(const Dimension *dimension, const chunk_pos &start_chunk, const glm::fvec3 &start_fpos, const glm::fvec3 &direction); + explicit RayDDA(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); + void reset(const Dimension &dimension, const chunk_pos &start_chunk, const glm::fvec3 &start_fpos, const glm::fvec3 &direction); + + voxel_id step(void); + +public: + const Dimension *dimension; + chunk_pos start_chunk; + glm::fvec3 start_fpos; + glm::fvec3 direction; + + glm::fvec3 delta_dist; + glm::fvec3 side_dist; + voxel_pos vstep; + + double distance; + voxel_pos vnormal; + voxel_pos vpos; +}; + +#endif /* SHARED_RAY_DDA */ diff --git a/game/shared/splash.cc b/game/shared/splash.cc new file mode 100644 index 0000000..ec7b3c8 --- /dev/null +++ b/game/shared/splash.cc @@ -0,0 +1,34 @@ +#include "shared/pch.hh" +#include "shared/splash.hh" + +constexpr static const char *SPLASHES_FILENAME = "misc/splashes.txt"; + +static std::mt19937_64 splash_random; +static std::vector splash_lines; + +void splash::init(void) +{ + if(auto file = PHYSFS_openRead(SPLASHES_FILENAME)) { + auto source = std::string(PHYSFS_fileLength(file), char(0x00)); + PHYSFS_readBytes(file, source.data(), source.size()); + PHYSFS_close(file); + + std::string line; + std::istringstream stream(source); + + while(std::getline(stream, line)) { + splash_lines.push_back(line); + } + + splash_random.seed(std::random_device()()); + } + else { + splash_lines.push_back(fmt::format("{}: {}", SPLASHES_FILENAME, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()))); + } +} + +const char* splash::get(void) +{ + std::uniform_int_distribution dist(0, splash_lines.size() - 1); + return splash_lines.at(dist(splash_random)).c_str(); +} diff --git a/game/shared/splash.hh b/game/shared/splash.hh new file mode 100644 index 0000000..2a370f9 --- /dev/null +++ b/game/shared/splash.hh @@ -0,0 +1,11 @@ +#ifndef SHARED_SPLASH_HH +#define SHARED_SPLASH_HH 1 +#pragma once + +namespace splash +{ +void init(void); +const char* get(void); +} // namespace splash + +#endif /* SHARED_SPLASH_HH */ diff --git a/game/shared/stasis.cc b/game/shared/stasis.cc new file mode 100644 index 0000000..4e474af --- /dev/null +++ b/game/shared/stasis.cc @@ -0,0 +1,16 @@ +#include "shared/pch.hh" +#include "shared/stasis.hh" + +#include "shared/dimension.hh" +#include "shared/transform.hh" + +void StasisComponent::fixed_update(Dimension *dimension) +{ + auto view = dimension->entities.view(); + + for(auto [entity, transform] : view.each()) { + if(dimension->find_chunk(transform.chunk)) + dimension->entities.remove(entity); + else dimension->entities.emplace_or_replace(entity); + } +} diff --git a/game/shared/stasis.hh b/game/shared/stasis.hh new file mode 100644 index 0000000..6a3e280 --- /dev/null +++ b/game/shared/stasis.hh @@ -0,0 +1,14 @@ +#ifndef SHARED_STASIS_HH +#define SHARED_STASIS_HH 1 +#pragma once + +class Dimension; + +// Attached to entities with transform values +// out of bounds in a specific dimension +struct StasisComponent final { +public: + static void fixed_update(Dimension *dimension); +}; + +#endif /* SHARED_STASIS_HH */ diff --git a/game/shared/threading.cc b/game/shared/threading.cc new file mode 100644 index 0000000..06a9411 --- /dev/null +++ b/game/shared/threading.cc @@ -0,0 +1,91 @@ +#include "shared/pch.hh" +#include "shared/threading.hh" + +#include "core/cmdline.hh" +#include "core/constexpr.hh" + +static BS::light_thread_pool *thread_pool; +static std::deque task_deque; + +static void task_process(Task *task) +{ + task->set_status(task_status::PROCESSING); + task->process(); + + if(task->get_status() == task_status::PROCESSING) { + // If the task status is still PROCESSING + // it can be deduced it hasn't been cancelled + task->set_status(task_status::COMPLETED); + } +} + +task_status Task::get_status(void) const +{ + return m_status; +} + +void Task::set_status(task_status status) +{ + m_status = status; +} + +void threading::init(void) +{ + auto threads_arg = cmdline::get("threads", "4"); + auto threads_num = cxpr::clamp(std::strtoul(threads_arg, nullptr, 10), 2U, 4U); + thread_pool = new BS::light_thread_pool(threads_num); + task_deque.clear(); +} + +void threading::deinit(void) +{ + for(auto task : task_deque) { + auto status = task->get_status(); + if((status != task_status::CANCELLED) || (status != task_status::COMPLETED)) { + task->set_status(task_status::CANCELLED); + } + } + + thread_pool->purge(); + thread_pool->wait(); + + for(auto task : task_deque) + delete task; + task_deque.clear(); + + delete thread_pool; +} + +void threading::update(void) +{ + auto task_iter = task_deque.cbegin(); + + while(task_iter != task_deque.cend()) { + auto task_ptr = *task_iter; + auto status = task_ptr->get_status(); + + if(status == task_status::CANCELLED) { + delete task_ptr; + task_iter = task_deque.erase(task_iter); + continue; + } + + if(status == task_status::COMPLETED) { + task_ptr->finalize(); + delete task_ptr; + task_iter = task_deque.erase(task_iter); + continue; + } + + task_iter = std::next(task_iter); + } +} + +void threading::submit(Task *task) +{ + task->set_status(task_status::ENQUEUED); + + static_cast(thread_pool->submit_task(std::bind(&task_process, task))); + + task_deque.push_back(task); +} diff --git a/game/shared/threading.hh b/game/shared/threading.hh new file mode 100644 index 0000000..e143f4f --- /dev/null +++ b/game/shared/threading.hh @@ -0,0 +1,46 @@ +#ifndef SHARED_THREADING_HH +#define SHARED_THREADING_HH 1 +#pragma once + +enum class task_status : unsigned int { + ENQUEUED = 0x0000U, + PROCESSING = 0x0001U, + COMPLETED = 0x0002U, + CANCELLED = 0x0004U, +}; + +class Task { +public: + virtual ~Task(void) = default; + virtual void process(void) = 0; + virtual void finalize(void) = 0; + + task_status get_status(void) const; + void set_status(task_status status); + +protected: + std::atomic m_status; + std::future m_future; +}; + +namespace threading +{ +void init(void); +void deinit(void); +void update(void); +void submit(Task *task); +} // namespace threading + +namespace threading +{ +template +void submit(AT &&... args); +} // namespace threading + +template +inline void threading::submit(AT &&... args) +{ + threading::submit(new T(args...)); +} + +#endif /* SHARED_THREADING_HH */ diff --git a/game/shared/transform.cc b/game/shared/transform.cc new file mode 100644 index 0000000..6dc6126 --- /dev/null +++ b/game/shared/transform.cc @@ -0,0 +1,31 @@ +#include "shared/pch.hh" +#include "shared/transform.hh" + +#include "shared/const.hh" +#include "shared/dimension.hh" + +constexpr inline static void update_component(unsigned int dim, TransformComponent &component) +{ + if(component.local[dim] >= CHUNK_SIZE) { + component.local[dim] -= CHUNK_SIZE; + component.chunk[dim] += 1; + return; + } + + if(component.local[dim] < 0.0f) { + component.local[dim] += CHUNK_SIZE; + component.chunk[dim] -= 1; + return; + } +} + +void TransformComponent::fixed_update(Dimension *dimension) +{ + auto view = dimension->entities.view(); + + for(auto [entity, transform] : view.each()) { + update_component(0U, transform); + update_component(1U, transform); + update_component(2U, transform); + } +} diff --git a/game/shared/transform.hh b/game/shared/transform.hh new file mode 100644 index 0000000..0c0cc51 --- /dev/null +++ b/game/shared/transform.hh @@ -0,0 +1,25 @@ +#ifndef SHARED_TRANSFORM_HH +#define SHARED_TRANSFORM_HH 1 +#pragma once + +#include "shared/types.hh" + +class Dimension; + +struct TransformComponent { + chunk_pos chunk; + glm::fvec3 local; + glm::fvec3 angles; + +public: + // Updates TransformComponent values so that + // the local translation field is always within + // local coodrinates; [floating-point precision] + static void fixed_update(Dimension *dimension); +}; + +// Client-side only - interpolated and previous transform +struct TransformComponentIntr final : public TransformComponent {}; +struct TransformComponentPrev final : public TransformComponent {}; + +#endif /* SHARED_TRANSFORM_HH */ diff --git a/game/shared/types.hh b/game/shared/types.hh new file mode 100644 index 0000000..dace85e --- /dev/null +++ b/game/shared/types.hh @@ -0,0 +1,44 @@ +#ifndef SHARED_TYPES_HH +#define SHARED_TYPES_HH 1 +#pragma once + +using item_id = std::uint32_t; +constexpr static item_id NULL_ITEM_ID = UINT32_C(0x00000000); +constexpr static item_id MAX_ITEM_ID = UINT32_C(0xFFFFFFFF); + +using voxel_id = std::uint16_t; +constexpr static voxel_id NULL_VOXEL_ID = UINT16_C(0x0000); +constexpr static voxel_id MAX_VOXEL_ID = UINT16_C(0xFFFF); + +using chunk_pos = glm::vec<3, std::int32_t>; +using local_pos = glm::vec<3, std::int16_t>; +using voxel_pos = glm::vec<3, std::int64_t>; + +// A special 2D chunk coordinate used by world generation code +// to cache things like 2D noise and terrain heightmap for performance reasons +using worldgen_chunk_pos = glm::vec<2, chunk_pos::value_type>; + +template<> +struct std::hash final { + constexpr inline std::size_t operator()(const chunk_pos &cpos) const + { + std::size_t value = 0; + value ^= cpos.x * 73856093; + value ^= cpos.y * 19349663; + value ^= cpos.z * 83492791; + return value; + } +}; + +template<> +struct std::hash final { + constexpr inline std::size_t operator()(const worldgen_chunk_pos &cwpos) const + { + std::size_t value = 0; + value ^= cwpos.x * 73856093; + value ^= cwpos.y * 19349663; + return value; + } +}; + +#endif /* SHARED_TYPES_HH */ diff --git a/game/shared/velocity.cc b/game/shared/velocity.cc new file mode 100644 index 0000000..6305363 --- /dev/null +++ b/game/shared/velocity.cc @@ -0,0 +1,16 @@ +#include "shared/pch.hh" +#include "shared/velocity.hh" + +#include "shared/dimension.hh" +#include "shared/globals.hh" +#include "shared/stasis.hh" +#include "shared/transform.hh" + +void VelocityComponent::fixed_update(Dimension *dimension) +{ + auto group = dimension->entities.group(entt::get, entt::exclude); + + for(auto [entity, velocity, transform] : group.each()) { + transform.local += velocity.value * globals::fixed_frametime; + } +} diff --git a/game/shared/velocity.hh b/game/shared/velocity.hh new file mode 100644 index 0000000..9aafed1 --- /dev/null +++ b/game/shared/velocity.hh @@ -0,0 +1,17 @@ +#ifndef SHARED_VELOCITY_HH +#define SHARED_VELOCITY_HH 1 +#pragma once + +class Dimension; + +struct VelocityComponent final { + glm::fvec3 value; + +public: + // Updates entities TransformComponent values + // according to velocities multiplied by fixed_frametime. + // NOTE: This system was previously called inertial + static void fixed_update(Dimension *dimension); +}; + +#endif /* SHARED_VELOCITY_HH */ diff --git a/game/shared/voxel_registry.cc b/game/shared/voxel_registry.cc new file mode 100644 index 0000000..dea7179 --- /dev/null +++ b/game/shared/voxel_registry.cc @@ -0,0 +1,184 @@ +#include "shared/pch.hh" +#include "shared/voxel_registry.hh" + +#include "core/crc64.hh" + +std::unordered_map voxel_registry::builders = {}; +std::unordered_map voxel_registry::names = {}; +std::vector> voxel_registry::voxels = {}; + +VoxelInfoBuilder::VoxelInfoBuilder(const char *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; +} + +VoxelInfoBuilder &VoxelInfoBuilder::add_texture_default(const char *texture) +{ + default_texture.paths.push_back(texture); + return *this; +} + +VoxelInfoBuilder &VoxelInfoBuilder::add_texture(voxel_face face, const char *texture) +{ + const auto index = static_cast(face); + prototype.textures[index].paths.push_back(texture); + return *this; +} + +VoxelInfoBuilder &VoxelInfoBuilder::set_touch(voxel_touch type, const glm::fvec3 &values) +{ + prototype.touch_type = type; + prototype.touch_values = values; + return *this; +} + +VoxelInfoBuilder &VoxelInfoBuilder::set_surface(voxel_surface surface) +{ + prototype.surface = surface; + return *this; +} + +voxel_id VoxelInfoBuilder::build(void) const +{ + const auto it = voxel_registry::names.find(prototype.name); + + if(it != voxel_registry::names.cend()) { + spdlog::warn("voxel_registry: cannot build {}: name already present", prototype.name); + return it->second; + } + + 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(); + } + + if((voxel_registry::voxels.size() + state_count) >= MAX_VOXEL_ID) { + spdlog::critical("voxel_registry: voxel registry overflow"); + std::terminate(); + } + + 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 = voxel_registry::voxels.size() + 1; + + for(std::size_t i = 0; i < state_count; ++i) + voxel_registry::voxels.push_back(new_info); + voxel_registry::names.insert_or_assign(new_info->name, new_info->base_voxel); + + return new_info->base_voxel; +} + +VoxelInfoBuilder &voxel_registry::construct(const char *name, voxel_type type, bool animated, bool blending) +{ + const auto it = voxel_registry::builders.find(name); + if(it != voxel_registry::builders.cend()) + return it->second; + return voxel_registry::builders.emplace(name, VoxelInfoBuilder(name, type, animated, blending)).first->second; +} + +VoxelInfo *voxel_registry::find(const char *name) +{ + const auto it = voxel_registry::names.find(name); + if(it != voxel_registry::names.cend()) + return voxel_registry::find(it->second); + return nullptr; +} + +VoxelInfo *voxel_registry::find(const voxel_id voxel) +{ + if((voxel != NULL_VOXEL_ID) && (voxel <= voxel_registry::voxels.size())) + return voxel_registry::voxels[voxel - 1].get(); + return nullptr; +} + +void voxel_registry::purge(void) +{ + voxel_registry::builders.clear(); + voxel_registry::names.clear(); + voxel_registry::voxels.clear(); +} + +std::uint64_t voxel_registry::checksum(void) +{ + std::uint64_t result = 0; + + for(const std::shared_ptr &info : voxel_registry::voxels) { + result = crc64::get(info->name, result); + result += static_cast(info->type); + result += static_cast(info->base_voxel); + result += info->blending ? 256 : 1; + } + + return result; +} diff --git a/game/shared/voxel_registry.hh b/game/shared/voxel_registry.hh new file mode 100644 index 0000000..722cec1 --- /dev/null +++ b/game/shared/voxel_registry.hh @@ -0,0 +1,144 @@ +#ifndef SHARED_VOXEL_REGISTRY_HH +#define SHARED_VOXEL_REGISTRY_HH 1 +#pragma once + +#include "shared/types.hh" + +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); + +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; +}; + +class VoxelInfoBuilder final { +public: + explicit VoxelInfoBuilder(const char *name, voxel_type type, bool animated, bool blending); + virtual ~VoxelInfoBuilder(void) = default; + +public: + VoxelInfoBuilder &add_texture_default(const char *texture); + VoxelInfoBuilder &add_texture(voxel_face face, const char *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 voxel_registry +{ +extern std::unordered_map builders; +extern std::unordered_map names; +extern std::vector> voxels; +} // namespace voxel_registry + +namespace voxel_registry +{ +VoxelInfoBuilder &construct(const char *name, voxel_type type, bool animated, bool blending); +VoxelInfo *find(const char *name); +VoxelInfo *find(const voxel_id voxel); +} // namespace voxel_registry + +namespace voxel_registry +{ +void purge(void); +} // namespace voxel_registry + +namespace voxel_registry +{ +std::uint64_t checksum(void); +} // namespace voxel_registry + +#endif /* SHARED_VOXEL_REGISTRY_HH */ diff --git a/game/shared/voxel_storage.cc b/game/shared/voxel_storage.cc new file mode 100644 index 0000000..eb51347 --- /dev/null +++ b/game/shared/voxel_storage.cc @@ -0,0 +1,43 @@ +#include "shared/pch.hh" +#include "shared/voxel_storage.hh" + +#include "core/buffer.hh" + +void VoxelStorage::serialize(WriteBuffer &buffer) const +{ + auto bound = mz_compressBound(sizeof(VoxelStorage)); + auto zdata = std::vector(bound); + + VoxelStorage net_storage; + + for(std::size_t i = 0; i < CHUNK_VOLUME; ++i) { + // Convert voxel indices into network byte order; + // We're going to compress them but we still want + // the order to be consistent across all the platforms + net_storage[i] = ENET_HOST_TO_NET_16(at(i)); + } + + mz_compress(zdata.data(), &bound, reinterpret_cast(net_storage.data()), sizeof(VoxelStorage)); + + buffer.write_UI64(bound); + + // Write all the compressed data into the buffer + for(std::size_t i = 0; i < bound; buffer.write_UI8(zdata[i++])); +} + +void VoxelStorage::deserialize(ReadBuffer &buffer) +{ + auto size = static_cast(sizeof(VoxelStorage)); + auto bound = static_cast(buffer.read_UI64()); + auto zdata = std::vector(bound); + + // Read all the compressed data from the buffer + for(std::size_t i = 0; i < bound; zdata[i++] = buffer.read_UI8()); + + mz_uncompress(reinterpret_cast(data()), &size, zdata.data(), bound); + + for(std::size_t i = 0; i < CHUNK_VOLUME; ++i) { + // Convert voxel indices back into the host byte order + at(i) = ENET_NET_TO_HOST_16(at(i)); + } +} diff --git a/game/shared/voxel_storage.hh b/game/shared/voxel_storage.hh new file mode 100644 index 0000000..08d234d --- /dev/null +++ b/game/shared/voxel_storage.hh @@ -0,0 +1,18 @@ +#ifndef SHARED_VOXEL_STORAGE_HH +#define SHARED_VOXEL_STORAGE_HH 1 +#pragma once + +#include "shared/const.hh" +#include "shared/types.hh" + +class ReadBuffer; +class WriteBuffer; + +class VoxelStorage final : public std::array { +public: + using std::array::array; + void serialize(WriteBuffer &buffer) const; + void deserialize(ReadBuffer &buffer); +}; + +#endif /* SHARED_VOXEL_STORAGE_HH */ -- cgit