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