aboutsummaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
authorNicolas James <Eele1Ephe7uZahRie@tutanota.com>2025-02-12 21:57:46 +1100
committerNicolas James <Eele1Ephe7uZahRie@tutanota.com>2025-02-12 21:57:46 +1100
commite4483eca01b48b943cd0461e24a74ae1a3139ed4 (patch)
treeed58c3c246e3af1af337697695d780aa31f6ad9a /src/server
parent1cc08c51eb4b0f95c30c0a98ad1fc5ad3459b2df (diff)
Update to most recent version (old initial commit)
Diffstat (limited to 'src/server')
-rw-r--r--src/server/CMakeLists.txt50
-rw-r--r--src/server/chunk_data.hh6
-rw-r--r--src/server/client.cc14
-rw-r--r--src/server/client.hh15
-rw-r--r--src/server/database.cc77
-rw-r--r--src/server/database.hh2
-rw-r--r--src/server/init.cc45
-rw-r--r--src/server/init.hh15
-rw-r--r--src/server/movement.cc87
-rw-r--r--src/server/movement/movement.cc98
-rw-r--r--src/server/movement/movement.hh (renamed from src/server/movement.hh)8
-rw-r--r--src/server/resources.cc1
-rw-r--r--src/server/resources.hh5
-rw-r--r--src/server/server.cc470
-rw-r--r--src/server/server.hh6
-rw-r--r--src/server/shared.hh2
-rw-r--r--src/server/world.cc53
-rw-r--r--src/server/world.hh59
-rw-r--r--src/server/world/chunk.cc26
-rw-r--r--src/server/world/chunk.hh46
20 files changed, 688 insertions, 397 deletions
diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt
new file mode 100644
index 0000000..7ac16c2
--- /dev/null
+++ b/src/server/CMakeLists.txt
@@ -0,0 +1,50 @@
+cmake_minimum_required(VERSION 3.18)
+
+project(server)
+
+file (GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS
+ "*.cc"
+)
+file (GLOB_RECURSE HEADER_FILES CONFIGURE_DEPENDS
+ "*.hh"
+ "../shared/*.hh"
+)
+add_library(${PROJECT_NAME} STATIC
+ ${SOURCE_FILES}
+)
+
+find_library(LIB_SQLITE3 sqlite3 SQLITE3 REQUIRED)
+find_library(LIB_PROTOBUF protobuf libprotobuf REQUIRED)
+find_package(Boost COMPONENTS iostreams REQUIRED)
+find_package(Threads REQUIRED)
+find_package(Backtrace REQUIRED)
+
+target_compile_options(${PROJECT_NAME} PRIVATE
+ -Wall -Wextra -Wshadow -Wdouble-promotion -Wformat=2 -Wundef -fno-common
+ -Wconversion -Wpedantic -std=c++20 -O2
+ -Wno-exceptions
+ -Wno-missing-field-initializers -Wno-unknown-pragmas
+)
+if (${IS_DEBUG})
+ target_compile_options(${PROJECT_NAME} PRIVATE
+ ¦ -fstack-protector-strong -fno-omit-frame-pointer -fsanitize=undefined
+ )
+ target_link_options(${PROJECT_NAME} PRIVATE
+ ¦ -fstack-protector-strong -fsanitize=undefined
+ )
+endif()
+target_include_directories(${PROJECT_NAME} PRIVATE
+ "${PROJECT_SOURCE_DIR}/../../src"
+)
+target_link_libraries(${PROJECT_NAME} PRIVATE
+ ${LIB_SQLITE3}
+ ${LIB_PROTOBUF}
+ ${Backtrace_LIBRARIES}
+ ${Threads_LIBRARIES}
+ ${Boost_LIBRARIES}
+ ${FREETYPE_LIBRARIES}
+ shared
+)
+target_precompile_headers(${PROJECT_NAME} PRIVATE
+ ${HEADER_FILES}
+)
diff --git a/src/server/chunk_data.hh b/src/server/chunk_data.hh
index eea5cef..349d4dc 100644
--- a/src/server/chunk_data.hh
+++ b/src/server/chunk_data.hh
@@ -5,8 +5,8 @@
#include <optional>
#include <unordered_set>
-#include "server/world.hh"
-#include "shared/player.hh"
+#include "server/world/chunk.hh"
+#include "shared/entity/player.hh"
namespace server {
@@ -15,7 +15,7 @@ public:
// nullopt = constructing/destructing via future operations in thread pool
// we use shared_ptr here to avoid complex moves in boost::asio::post.
// There is no good reason to use shared_ptr over unique_ptr, other than
- // boost::asio::post requiring copy_constructable args in std::bind.
+ // boost::asio::post requiring copy_constructable args in std::bind.
std::optional<std::shared_ptr<server::world::chunk>> chunk;
// players associated with the chunk
std::unordered_set<shared::player::index_t> players;
diff --git a/src/server/client.cc b/src/server/client.cc
index 0197f90..6d3b5b0 100644
--- a/src/server/client.cc
+++ b/src/server/client.cc
@@ -1 +1,15 @@
#include "server/client.hh"
+
+namespace server {
+
+bool client::is_in_pvs(const client& other) const noexcept {
+ if (!other.has_initialised()) {
+ return false;
+ }
+ if (!this->chunks.contains(other.get_player().get_chunk_pos())) {
+ return false;
+ }
+ return true;
+}
+
+} // namespace server
diff --git a/src/server/client.hh b/src/server/client.hh
index 5866035..4a2c1fd 100644
--- a/src/server/client.hh
+++ b/src/server/client.hh
@@ -9,10 +9,10 @@
#include <unordered_set>
#include "server/chunk_data.hh"
-#include "server/world.hh"
+#include "server/world/chunk.hh"
+#include "shared/entity/player.hh"
#include "shared/net/connection.hh"
-#include "shared/player.hh"
-#include "shared/world.hh"
+#include "shared/world/chunk.hh"
namespace server {
@@ -44,14 +44,21 @@ public:
decltype(&shared::world::chunk::equal)>
chunks{4096, shared::world::chunk::hash, shared::world::chunk::equal};
+ // sequence of the client's last commands, used for prediction
+ shared::tick_t sequence = 0u;
+
public:
client(shared::net::connection&& con, const shared::player::index_t& index)
: connection(std::move(con)), index(index) {}
+ bool is_in_pvs(const client& other) const noexcept;
+
+ // Not safe to use getter functions without checking has_initalised.
bool has_initialised() const noexcept {
return this->player_info.has_value();
}
- shared::player& get_player() noexcept {
+
+ shared::player& get_player() const noexcept {
return (*this->player_info)->player;
}
const std::string& get_username() const noexcept {
diff --git a/src/server/database.cc b/src/server/database.cc
index 077c86c..37f5757 100644
--- a/src/server/database.cc
+++ b/src/server/database.cc
@@ -3,14 +3,25 @@
namespace server {
namespace database {
+// because of all the things not to work, bit cast doesn't
+template <typename T, typename U>
+static T bit_cast(const U& src) noexcept {
+ static_assert(sizeof(T) == sizeof(U));
+
+ T dest;
+ std::memcpy(&dest, &src, sizeof(U));
+ return dest;
+}
+
sqlite3* get_database() noexcept {
static sqlite3* const database = []() -> sqlite3* {
std::filesystem::create_directory(server::state.directory);
const std::string path = server::state.directory + "world.dat";
if (!std::filesystem::exists(path)) {
- shared::print::warn(
- "server: regenerating non-existent world data\n");
+ shared::print::warn
+ << shared::print::time
+ << "server: regenerating non-existent world data\n";
}
sqlite3* database = nullptr;
@@ -59,8 +70,8 @@ static void finalise(sqlite3_stmt*& statement) noexcept {
static void bind_int64(sqlite3_stmt*& statement, const int index,
const std::uint64_t& key) noexcept {
- if (const int status = sqlite3_bind_int64(statement, index,
- std::bit_cast<std::int64_t>(key));
+ if (const int status =
+ sqlite3_bind_int64(statement, index, bit_cast<std::int64_t>(key));
status != SQLITE_OK) {
throw std::runtime_error(std::string{"sqlite bind int error \""} +
sqlite3_errstr(status) + '\"');
@@ -121,8 +132,7 @@ void quit() noexcept {
// We have to store our key in network byte order!
static std::uint64_t make_key(const shared::math::coords& coords) noexcept {
const auto to_64 = [](const std::int32_t& val, const int lshift) {
- return static_cast<std::uint64_t>(
- htonl(std::bit_cast<std::uint32_t>(val)))
+ return static_cast<std::uint64_t>(htonl(bit_cast<std::uint32_t>(val)))
<< lshift;
};
@@ -145,14 +155,21 @@ maybe_read_chunk(const shared::math::coords& pos) noexcept {
std::string blocks{addr, addr + sqlite3_column_bytes(statement, 0)};
finalise(statement);
- shared::decompress_string(blocks);
- proto::chunk chunk;
- if (!chunk.ParseFromString(blocks)) {
+ std::optional<proto::chunk> chunk = [&]() -> std::optional<proto::chunk> {
+ const auto decompress = shared::maybe_decompress_string(blocks);
+ if (!decompress.has_value()) {
+ return std::nullopt;
+ }
- shared::print::fault("server: chunk [" + std::to_string(pos.x) + ", " +
- std::to_string(pos.z) +
- "] failed to parse and was evicted\n");
+ proto::chunk ret;
+ ret.ParseFromString(*decompress);
+ return ret;
+ }();
+ if (!chunk.has_value()) {
+ shared::print::fault << shared::print::time << "server: chunk ["
+ << pos.x << ", " << pos.z
+ << "] failed to parse and was evicted\n";
statement = nullptr;
prepare(statement, "DELETE FROM Chunks WHERE Chunks.coords = ?;");
bind_int64(statement, 1, make_key(pos));
@@ -160,14 +177,14 @@ maybe_read_chunk(const shared::math::coords& pos) noexcept {
finalise(statement);
return std::nullopt;
}
- return chunk;
+ return *chunk;
}
void write_chunk(const shared::math::coords& pos,
const proto::chunk& chunk) noexcept {
std::string blocks;
chunk.SerializeToString(&blocks);
- shared::compress_string(blocks);
+ blocks = shared::compress_string(blocks);
sqlite3_stmt* statement = nullptr;
@@ -195,17 +212,30 @@ maybe_read_player(const std::string& username) noexcept {
const char* plr_adr =
static_cast<const char*>(sqlite3_column_blob(statement, 0));
- std::string player_bytes{plr_adr, plr_adr + sqlite3_column_bytes(statement, 0)};
+ std::string player_bytes{plr_adr,
+ plr_adr + sqlite3_column_bytes(statement, 0)};
const unsigned char* pass_adr = sqlite3_column_text(statement, 1);
- std::string pass_bytes{pass_adr, pass_adr + sqlite3_column_bytes(statement, 1)};
+ std::string pass_bytes{pass_adr,
+ pass_adr + sqlite3_column_bytes(statement, 1)};
finalise(statement);
- shared::decompress_string(player_bytes);
- proto::player player;
- if (!player.ParseFromString(player_bytes)) {
- shared::print::fault("server: player \"" + username +
- "\" failed to parse and was evicted\n");
+ const std::optional<proto::player> player =
+ [&]() -> std::optional<proto::player> {
+ const auto decompress = shared::maybe_decompress_string(player_bytes);
+ if (!decompress.has_value()) {
+ return std::nullopt;
+ }
+
+ proto::player ret;
+ ret.ParseFromString(*decompress);
+ return ret;
+ }();
+
+ if (!player.has_value()) {
+ shared::print::fault << shared::print::time << "server: player \""
+ << username
+ << "\" failed to parse and was evicted\n";
statement = nullptr;
prepare(statement, "DELETE FROM Players WHERE Players.username = ?;");
@@ -214,14 +244,15 @@ maybe_read_player(const std::string& username) noexcept {
finalise(statement);
return std::nullopt;
}
- return std::make_pair(player, pass_bytes);
+
+ return std::make_pair(*player, pass_bytes);
}
void write_player(const std::string& username, const std::string& password,
const proto::player& player) noexcept {
std::string player_bytes;
player.SerializeToString(&player_bytes);
- shared::compress_string(player_bytes);
+ player_bytes = shared::compress_string(player_bytes);
sqlite3_stmt* statement = nullptr;
prepare(statement, "INSERT OR REPLACE INTO Players VALUES(?, ?, ?);");
diff --git a/src/server/database.hh b/src/server/database.hh
index 154cdf2..13798a1 100644
--- a/src/server/database.hh
+++ b/src/server/database.hh
@@ -13,7 +13,7 @@
#include "server/shared.hh"
#include "shared/net/net.hh"
#include "shared/shared.hh"
-#include "shared/world.hh"
+#include "shared/world/chunk.hh"
namespace server {
namespace database {
diff --git a/src/server/init.cc b/src/server/init.cc
new file mode 100644
index 0000000..31dca46
--- /dev/null
+++ b/src/server/init.cc
@@ -0,0 +1,45 @@
+#include "server/init.hh"
+
+namespace server {
+
+const shared::args_t& get_options() {
+ static shared::args_t ret{
+ {.name = "seed", .desc = "manually set the worldseed", .val = "S:"},
+ {.name = "directory",
+ .desc = "manually set the directory",
+ .val = "d:"},
+ {.name = "tickrate",
+ .desc = "override the default tickrate",
+ .val = "t:"},
+ {.name = "distance",
+ .desc = "limit the max chunk distance the server provides",
+ .val = "D:"},
+ };
+ return ret;
+}
+
+bool parse_arg(const int& c, const char* const arg) {
+ switch (c) {
+ case 'S':
+ server::state.seed =
+ boost::lexical_cast<decltype(server::state.seed)>(arg);
+ break;
+ case 'd':
+ server::state.directory =
+ boost::lexical_cast<decltype(server::state.directory)>(arg);
+ break;
+ case 't':
+ server::state.tickrate =
+ boost::lexical_cast<decltype(server::state.tickrate)>(arg);
+ break;
+ case 'D':
+ server::state.draw_distance =
+ boost::lexical_cast<decltype(server::state.draw_distance)>(arg);
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+} // namespace server
diff --git a/src/server/init.hh b/src/server/init.hh
new file mode 100644
index 0000000..2dcd2b0
--- /dev/null
+++ b/src/server/init.hh
@@ -0,0 +1,15 @@
+#ifndef SERVER_INIT_HH_
+#define SERVER_INIT_HH_
+
+#include "server/shared.hh"
+#include "shared/init.hh"
+
+namespace server {
+
+const shared::args_t& get_options();
+
+bool parse_arg(const int& c, const char* const arg);
+
+} // namespace server
+
+#endif
diff --git a/src/server/movement.cc b/src/server/movement.cc
deleted file mode 100644
index 42b17f1..0000000
--- a/src/server/movement.cc
+++ /dev/null
@@ -1,87 +0,0 @@
-#include "server/movement.hh"
-
-// Gets blocks from chunks, returning nullopt if it doesn't exist.
-static std::optional<shared::world::block>
-get_block(const shared::math::coords& pos, server::resources::chunk_map& chunks,
- const glm::ivec3& block_pos) noexcept {
-
- const auto find_it = chunks.find(pos);
- if (find_it == std::end(chunks)) {
- return std::nullopt;
- }
- auto& chunk_data = find_it->second;
- if (!chunk_data->has_initialised()) {
- return std::nullopt;
- }
-
- return chunk_data->get_chunk().get_block(block_pos);
-}
-
-static std::optional<std::vector<shared::movement::blockinfo>>
-make_blockinfos(server::client& client,
- server::resources::chunk_map& chunks) noexcept {
-
- std::vector<shared::movement::blockinfo> blockinfos;
-
- constexpr int width = shared::movement::move_width;
- constexpr int height = shared::movement::move_height;
- for (int x = -width; x <= width; ++x) {
- for (int y = -height; y <= height; ++y) {
- for (int z = -width; z <= width; ++z) {
-
- const glm::ivec3 rel_pos =
- glm::ivec3{x, y, z} +
- glm::ivec3{client.get_player().local_pos};
-
- if (rel_pos.y < 0 ||
- rel_pos.y >= shared::world::chunk::HEIGHT) {
- continue;
- }
-
- const shared::math::coords norm_chunk_pos =
- shared::world::chunk::get_normalised_chunk(
- client.get_player().chunk_pos, rel_pos.x, rel_pos.z);
- const glm::ivec3 norm_pos =
- shared::world::chunk::get_normalised_coords(rel_pos);
-
- const auto block = get_block(norm_chunk_pos, chunks, norm_pos);
-
- if (!block.has_value()) {
- return std::nullopt;
- }
-
- const glm::vec3 pos =
- glm::vec3{rel_pos} - client.get_player().local_pos;
-
- const shared::movement::aabb aabb = {
- .min = glm::vec3{0.0f, 0.0f, 0.0f} + pos,
- .max = glm::vec3{1.0f, 1.0f, 1.0f} + pos};
- blockinfos.push_back(
- shared::movement::blockinfo{.block = *block,
- .aabb = aabb,
- .chunk_pos = norm_chunk_pos,
- .pos = norm_pos});
- }
- }
- }
-
- return blockinfos;
-}
-
-void server::movement::move(server::client& client,
- server::resources::chunk_map& chunks) noexcept {
-
- if (!client.has_initialised()) {
- return;
- }
-
- const auto blockinfos = make_blockinfos(client, chunks);
-
- if (!blockinfos.has_value()) {
- return;
- }
-
- const float deltatime = 1.0f / static_cast<float>(server::state.tickrate);
-
- shared::movement::move(client.get_player(), *blockinfos, deltatime);
-}
diff --git a/src/server/movement/movement.cc b/src/server/movement/movement.cc
new file mode 100644
index 0000000..0dd3f28
--- /dev/null
+++ b/src/server/movement/movement.cc
@@ -0,0 +1,98 @@
+#include "server/movement/movement.hh"
+
+namespace server {
+namespace movement {
+
+// Gets blocks from chunks, returning nullopt if it doesn't exist.
+static std::optional<shared::world::block>
+maybe_get_block(const shared::math::coords& pos,
+ server::resources::chunk_map& chunks,
+ const glm::ivec3& block_pos) noexcept {
+
+ const auto find_it = chunks.find(pos);
+ if (find_it == std::end(chunks)) {
+ return std::nullopt;
+ }
+ auto& chunk_data = find_it->second;
+ if (!chunk_data->has_initialised()) {
+ return std::nullopt;
+ }
+
+ return chunk_data->get_chunk().get_block(block_pos);
+}
+
+static std::optional<shared::movement::blocks>
+maybe_make_blocks(server::client& client,
+ server::resources::chunk_map& chunks) noexcept {
+
+ shared::movement::blocks blocks;
+
+ const auto xy = shared::movement::get_move_xy(server::state.tickrate,
+ client.get_player());
+ for (int x = -xy.x; x <= xy.x; ++x) {
+ for (int y = -xy.y; y <= xy.y; ++y) {
+ for (int z = -xy.x; z <= xy.x; ++z) {
+
+ const glm::ivec3 rel_pos =
+ glm::ivec3{x, y, z} +
+ glm::ivec3{client.get_player().get_local_pos()};
+
+ if (rel_pos.y < 0 ||
+ rel_pos.y >= shared::world::chunk::HEIGHT) {
+ continue;
+ }
+
+ const shared::math::coords norm_chunk_pos =
+ shared::world::chunk::get_normalised_chunk(
+ client.get_player().get_chunk_pos(), rel_pos.x,
+ rel_pos.z);
+ const glm::ivec3 norm_pos =
+ shared::world::chunk::get_normalised_coords(rel_pos);
+
+ const auto block =
+ maybe_get_block(norm_chunk_pos, chunks, norm_pos);
+ if (!block.has_value()) {
+ return std::nullopt;
+ }
+
+ const glm::vec3 pos =
+ glm::vec3{rel_pos} - client.get_player().get_local_pos();
+
+ const shared::movement::aabb aabb = {
+ .min = glm::vec3{0.0f, 0.0f, 0.0f} + pos,
+ .max = glm::vec3{1.0f, 1.0f, 1.0f} + pos};
+ blocks.push_back(
+ shared::movement::block{.block = *block,
+ .aabb = aabb,
+ .chunk_pos = norm_chunk_pos,
+ .pos = norm_pos});
+ }
+ }
+ }
+
+ return blocks;
+}
+
+void move(client& client, resources::chunk_map& chunks) noexcept {
+
+ if (!client.has_initialised()) {
+ return;
+ }
+
+ const auto blocks = maybe_make_blocks(client, chunks);
+ if (!blocks.has_value()) {
+ return;
+ }
+
+ auto& player = client.get_player();
+
+ const shared::animate result =
+ shared::movement::move(player, *blocks, state.tickrate);
+
+ player.get_mutable_velocity() = result.get_velocity();
+ player.get_mutable_local_pos() = result.get_local_pos();
+ player.get_mutable_chunk_pos() = result.get_chunk_pos();
+}
+
+} // namespace movement
+} // namespace server
diff --git a/src/server/movement.hh b/src/server/movement/movement.hh
index 2c647b3..0b09a24 100644
--- a/src/server/movement.hh
+++ b/src/server/movement/movement.hh
@@ -1,10 +1,10 @@
-#ifndef SERVER_MOVEMENT_HH_
-#define SERVER_MOVEMENT_HH_
+#ifndef SERVER_MOVEMENT_MOVEMENT_HH_
+#define SERVER_MOVEMENT_MOVEMENT_HH_
#include "server/client.hh"
#include "server/resources.hh"
-#include "server/world.hh"
-#include "shared/movement.hh"
+#include "server/world/chunk.hh"
+#include "shared/movement/movement.hh"
namespace server {
diff --git a/src/server/resources.cc b/src/server/resources.cc
index 7eb3d8b..9b4a1b8 100644
--- a/src/server/resources.cc
+++ b/src/server/resources.cc
@@ -46,6 +46,7 @@ void quit() noexcept {
sleep();
continue;
}
+
while (!get_resources_lock()->chunks.empty()) {
sleep();
continue;
diff --git a/src/server/resources.hh b/src/server/resources.hh
index 578a248..a7d2faf 100644
--- a/src/server/resources.hh
+++ b/src/server/resources.hh
@@ -12,9 +12,8 @@
#include "server/chunk_data.hh"
#include "server/client.hh"
#include "server/database.hh"
-#include "server/world.hh"
-#include "shared/player.hh"
-#include "shared/world.hh"
+#include "shared/entity/player.hh"
+#include "shared/world/chunk.hh"
namespace server {
namespace resources {
diff --git a/src/server/server.cc b/src/server/server.cc
index 72faa03..4592334 100644
--- a/src/server/server.cc
+++ b/src/server/server.cc
@@ -2,25 +2,39 @@
namespace server {
-static proto::packet
-make_init_packet(const resources::client_map_value& client) noexcept {
- proto::packet packet;
-
- const auto init_packet = packet.mutable_init_packet();
- init_packet->set_seed(state.seed);
- init_packet->set_draw_distance(state.draw_distance);
- shared::net::set_player(*init_packet->mutable_localplayer(),
- client->get_player());
+static std::uint32_t& get_tick() noexcept {
+ static std::uint32_t ret = 0;
+ return ret;
+}
+static proto::player make_player_packet(const shared::player& player) noexcept {
+ proto::player packet;
+ player.pack(&packet);
return packet;
}
-static proto::packet make_player_packet(const shared::player& player) noexcept {
+static proto::packet
+make_animate_update_packet(const shared::animate& animate, const std::optional<shared::tick_t>& sequence) noexcept {
proto::packet packet;
+ const auto animate_update = packet.mutable_animate_update_packet();
+ animate.pack(animate_update->mutable_animate());
+ animate_update->set_tick(get_tick());
+ if (sequence.has_value()) {
+ animate_update->set_sequence(*sequence);
+ }
+ return packet;
+}
- const auto player_packet = packet.mutable_player_packet();
- shared::net::set_player(*player_packet, player);
+static proto::packet
+make_init_packet(const resources::client_map_value& client) noexcept {
+ proto::packet packet;
+ const auto init_packet = packet.mutable_init_packet();
+ init_packet->set_seed(state.seed);
+ init_packet->set_draw_distance(state.draw_distance);
+ init_packet->set_tickrate(state.tickrate);
+ init_packet->set_tick(get_tick());
+ (*client->player_info)->player.pack(init_packet->mutable_localplayer());
return packet;
}
@@ -39,7 +53,7 @@ static proto::packet
make_remove_packet(const resources::client_map_value& client) noexcept {
proto::packet packet;
- const auto remove_packet = packet.mutable_remove_player_packet();
+ const auto remove_packet = packet.mutable_remove_entity_packet();
remove_packet->set_index(client->index);
return packet;
@@ -62,12 +76,11 @@ static proto::packet make_chunk_packet(const world::chunk& chunk) noexcept {
static void block_by_tickrate() noexcept {
const auto tickrate = server::state.tickrate;
- static const auto ratetime = std::chrono::milliseconds(
- static_cast<int>((1.0 / static_cast<double>(tickrate)) * 1000.0));
- static auto prev = std::chrono::steady_clock::now();
- const auto now = std::chrono::steady_clock::now();
- std::this_thread::sleep_for(ratetime - (now - prev));
- prev = std::chrono::steady_clock::now();
+ static const auto ratetime = std::chrono::microseconds(
+ static_cast<int>((1.0 / static_cast<double>(tickrate)) * 1'000'000));
+ static auto wanted = std::chrono::steady_clock::now();
+ std::this_thread::sleep_until(wanted);
+ wanted += ratetime;
}
// Creates, binds, listens on new nonblocking socket.
@@ -89,14 +102,52 @@ static std::optional<shared::net::connection> make_connection(const int sock) {
if (!accept.has_value()) {
return std::nullopt;
}
- return shared::net::connection(accept->socket);
+
+ try {
+ return shared::net::connection(accept->socket);
+ } catch (const std::runtime_error& e) {
+#ifndef NDEBUG
+ shared::print::debug
+ << shared::print::time
+ << "server: constructor for client connection failed; what(): "
+ << e.what() << '\n';
+#endif
+ }
+
+ return std::nullopt;
+}
+
+static void move_client(resources::client_map_value& client,
+ resources::chunk_map& chunks) noexcept {
+ movement::move(*client, chunks);
}
static void handle_move_packet(const proto::move& packet,
- resources::client_map_value& client) noexcept {
- client->get_player().viewangles = {.pitch = packet.viewangles().pitch(),
- .yaw = packet.viewangles().yaw()};
- client->get_player().commands = packet.commands();
+ resources::client_map_value& client,
+ resources::chunk_map& chunks) noexcept {
+ if (!client->has_initialised()) {
+ return;
+ }
+
+ const shared::tick_t sequence = packet.sequence();
+ if (sequence <= client->sequence) { // Packet is late, drop it.
+#ifndef NDEBUG
+ shared::print::debug << shared::print::time
+ << "server: client sent late tick " << sequence
+ << " <= " << client->sequence << '\n';
+#endif
+ return;
+ }
+ client->sequence = sequence;
+
+ auto& player = client->get_player();
+ player.get_mutable_angles() = {.pitch = packet.viewangles().pitch(),
+ .yaw = packet.viewangles().yaw()};
+ player.get_mutable_angles().clamp();
+ player.get_mutable_commands() = packet.commands();
+ player.get_mutable_active_item() = packet.active_item();
+
+ move_client(client, chunks);
}
static void handle_say_packet(const proto::say& packet,
@@ -104,18 +155,24 @@ static void handle_say_packet(const proto::say& packet,
server::resources::client_map& clients) noexcept {
if (std::size(packet.text()) > shared::MAX_SAY_LENGTH) {
#ifndef NDEBUG
- shared::print::warn(
- "server: client tried to say a message that was too long, size: " +
- std::to_string(std::size(packet.text())) + '\n');
+ shared::print::debug
+ << shared::print::time
+ << "server: client tried to say a message that was too long, size: "
+ << std::size(packet.text()) << '\n';
#endif
return;
}
- shared::print::message("server: player " + std::to_string(client->index) +
- " said \"" + packet.text() + "\"\n");
- const auto hear_packet = make_hear_packet(packet.text(), client->index);
+ shared::print::message << shared::print::time << "server: player "
+ << client->index << " said \"" << packet.text()
+ << "\"\n";
+ const auto hear_packet = std::make_shared<shared::net::rpacket>(
+ make_hear_packet(packet.text(), client->index));
for (auto& [index, client_ptr] : clients) {
+ if (!client_ptr->is_in_pvs(*client)) {
+ continue;
+ }
client_ptr->connection.rsend_packet(hear_packet);
}
}
@@ -145,9 +202,9 @@ static void send_chunk_associated(resources::chunk_map_value& chunk_data,
// correctly.
if (find_it == std::end(clients)) {
#ifndef NDEBUG
- shared::print::debug(
- "client index " + std::to_string(client_index) +
- " was associated with a chunk, but not found\n");
+ shared::print::debug
+ << shared::print::time << "client index " << client_index
+ << " was associated with a chunk, but not found\n";
#endif
continue;
}
@@ -209,33 +266,45 @@ static void handle_request_chunk_packet(const proto::request_chunk& packet,
resources::client_map_value& client,
resources::chunk_map& chunks,
resources::pool_t& pool) noexcept {
+ if (!client->has_initialised()) {
+ return;
+ }
const shared::math::coords coords{packet.chunk_pos().x(),
packet.chunk_pos().z()};
if (const auto find_it = chunks.find(coords); find_it != std::end(chunks)) {
auto& chunk_data = find_it->second;
+
+ // Associate client, then post sending of chunk to client.
resources::associate_client_chunk(coords, client, chunk_data);
boost::asio::post(
- pool,
- std::bind(
- [](const shared::math::coords coords,
- const shared::player::index_t index) {
- auto res_lock = resources::get_resources_lock();
- auto& chunk_data = res_lock->chunks.find(coords)->second;
-
- if (!chunk_data->has_initialised()) {
- return; // will be sent on construction
- }
-
- const auto& client_it = res_lock->clients.find(index);
- if (client_it == std::end(res_lock->clients)) {
- return;
- }
- auto& client = client_it->second;
- send_chunk(chunk_data->get_chunk(), client);
- maybe_post_chunk_rm(coords, chunk_data, res_lock->pool);
- },
- coords, client->index));
+ pool, std::bind(
+ [](const shared::math::coords coords,
+ const shared::player::index_t index) {
+ auto res_lock = resources::get_resources_lock();
+
+ // Client has requested a chunk removed before
+ // receiving it?
+ const auto find_it = res_lock->chunks.find(coords);
+ if (find_it == std::end(res_lock->chunks)) {
+ return;
+ }
+ auto& chunk_data = find_it->second;
+
+ if (!chunk_data->has_initialised()) {
+ return; // will be sent on construction
+ }
+
+ const auto& client_it = res_lock->clients.find(index);
+ if (client_it == std::end(res_lock->clients)) {
+ return;
+ }
+ auto& client = client_it->second;
+ send_chunk(chunk_data->get_chunk(), client);
+ maybe_post_chunk_rm(coords, chunk_data,
+ res_lock->pool);
+ },
+ coords, client->index));
return;
}
@@ -247,19 +316,26 @@ static void handle_request_chunk_packet(const proto::request_chunk& packet,
resources::associate_client_chunk(coords, client, data);
boost::asio::post(
- pool,
- std::bind(
- [](const shared::math::coords coords) {
- server::world::chunk chunk{server::state.seed, coords};
+ pool, std::bind(
+ [](const shared::math::coords coords) {
+ auto chunk =
+ [&coords]() -> std::shared_ptr<server::world::chunk> {
+ auto maybe_chunk = database::maybe_read_chunk(coords);
+ if (maybe_chunk.has_value()) {
+ return std::make_shared<server::world::chunk>(
+ server::state.seed, *maybe_chunk);
+ }
+ return std::make_shared<server::world::chunk>(
+ server::state.seed, coords);
+ }();
- auto res_lock = resources::get_resources_lock();
- auto& chunk_data = res_lock->chunks.find(coords)->second;
- chunk_data->chunk.emplace(
- std::make_unique<server::world::chunk>(std::move(chunk)));
- send_chunk_associated(chunk_data, res_lock->clients);
- maybe_post_chunk_rm(coords, chunk_data, res_lock->pool);
- },
- coords));
+ auto res_lock = resources::get_resources_lock();
+ auto& chunk_data = res_lock->chunks.find(coords)->second;
+ chunk_data->chunk.emplace(std::move(chunk));
+ send_chunk_associated(chunk_data, res_lock->clients);
+ maybe_post_chunk_rm(coords, chunk_data, res_lock->pool);
+ },
+ coords));
}
// Disassociates a client with a chunk, while performing necessary cleanups.
@@ -310,17 +386,21 @@ static void post_chunk_update(const shared::math::coords& coords,
coords));
}
-static void modify_block(const enum shared::world::block::type block_type,
- const glm::ivec3& block_pos,
- const shared::math::coords& coords,
- resources::chunk_map& chunks) noexcept {
+static void
+handle_add_block_packet(const proto::add_block& packet,
+ [[maybe_unused]] resources::client_map_value& client,
+ server::resources::chunk_map& chunks,
+ resources::pool_t& pool) noexcept {
+
+ const auto coords = shared::net::get_coords(packet.chunk_pos());
+ const auto pos = shared::net::get_ivec3(packet.block_pos());
+ const auto active_item = packet.active_item();
const auto find_it = chunks.find(coords);
if (find_it == std::end(chunks)) {
return;
}
-
- if (shared::world::chunk::is_outside_chunk(block_pos)) {
+ if (shared::world::chunk::is_outside_chunk(pos)) {
return;
}
@@ -329,22 +409,24 @@ static void modify_block(const enum shared::world::block::type block_type,
return;
}
- chunk_data->get_chunk().get_block(block_pos) = block_type;
- chunk_data->get_chunk().arm_should_update();
-}
+ auto& inventory = client->get_player().inventory;
+ if (active_item < 0 || active_item >= std::size(inventory.contents)) {
+ return;
+ }
-static void
-handle_add_block_packet(const proto::add_block& packet,
- [[maybe_unused]] resources::client_map_value& client,
- server::resources::chunk_map& chunks,
- resources::pool_t& pool) noexcept {
- const shared::math::coords coords{packet.chunk_pos().x(),
- packet.chunk_pos().z()};
- const glm::ivec3 block_pos{packet.block_pos().x(), packet.block_pos().y(),
- packet.block_pos().z()};
- const auto block =
- static_cast<enum shared::world::block::type>(packet.block());
- modify_block(block, block_pos, coords, chunks);
+ const auto& item = inventory.contents[active_item];
+ if (item == nullptr) {
+ return;
+ }
+ const auto block_ptr = dynamic_cast<shared::item::block*>(&*item);
+ if (block_ptr == nullptr) {
+ return;
+ }
+
+ chunk_data->get_chunk().get_block(pos) = block_ptr->type;
+ inventory.decrement(active_item);
+
+ chunk_data->get_chunk().arm_should_update();
post_chunk_update(coords, pool);
}
@@ -353,11 +435,30 @@ handle_remove_block_packet(const proto::remove_block& packet,
[[maybe_unused]] resources::client_map_value& client,
resources::chunk_map& chunks,
resources::pool_t& pool) noexcept {
- const shared::math::coords coords{packet.chunk_pos().x(),
- packet.chunk_pos().z()};
- const glm::ivec3 block_pos{packet.block_pos().x(), packet.block_pos().y(),
- packet.block_pos().z()};
- modify_block(shared::world::block::type::air, block_pos, coords, chunks);
+ const auto coords = shared::net::get_coords(packet.chunk_pos());
+ const auto pos = shared::net::get_ivec3(packet.block_pos());
+
+ const auto find_it = chunks.find(coords);
+ if (find_it == std::end(chunks)) {
+ return;
+ }
+ if (shared::world::chunk::is_outside_chunk(pos)) {
+ return;
+ }
+
+ auto& chunk_data = find_it->second;
+ if (!chunk_data->has_initialised()) {
+ return;
+ }
+
+ auto& block = chunk_data->get_chunk().get_block(pos);
+
+ auto& inventory = client->get_player().inventory;
+ inventory.maybe_add(shared::item::block::get_type(block.type), 1);
+
+ block = shared::world::block::type::air;
+
+ chunk_data->get_chunk().arm_should_update();
post_chunk_update(coords, pool);
}
@@ -379,47 +480,58 @@ static void handle_auth_packet(const proto::auth& packet,
auto& client = find_it->second;
// find if we're already associated, eventually kick our client
- // it would be nice if this wasn't a find_if
- if (std::find_if(std::begin(res_lock->clients),
- std::end(res_lock->clients),
- [&](const auto& it) {
- auto& client = it.second;
- if (!client->has_initialised()) {
- return false;
- }
- return client->index != client_index &&
- client->get_username() == user;
- }) != std::end(res_lock->clients)) {
-
+ if (std::ranges::any_of(res_lock->clients,
+ [&client_index, &user](const auto& it) {
+ const auto& [idx, c] = it;
+ if (idx == client_index) {
+ return false;
+ }
+ if (!c->has_initialised()) {
+ return false;
+ }
+ if (c->get_username() != user) {
+ return false;
+ }
+ return true;
+ })) {
client->disconnect_reason.emplace("user already in server");
return;
}
- struct client::player_info player_info {
- .username = user, .password = pass
- };
- if (db_plr.has_value()) {
- auto& [player, db_password] = *db_plr;
+ const auto in_db = db_plr.has_value();
+ if (in_db && db_plr->second != pass) {
+ client->disconnect_reason.emplace("bad password");
+ return;
+ }
- if (db_password != pass) {
- client->disconnect_reason.emplace("bad password");
- return;
+ shared::player player = [&]() -> shared::player {
+ if (in_db) {
+ // update to new client_index before returning
+ shared::player player{db_plr->first};
+ player.get_mutable_index() = client_index;
+ return player;
}
-
- player_info.player = shared::net::get_player(player);
- player_info.player.index = client_index;
- } else {
// TODO: Find a random spawn chunk and put the player
// on the highest block. Because we don't want to gen chunks
// while blocking, we can't do this here. For now, we'll
// just put the player at a high spot.
- player_info.player =
- shared::player{.index = client_index,
- .local_pos = {0.0f, 140.0f, 0.0f}};
- }
+ return shared::player{
+ shared::item::items{},
+ 0u,
+ shared::math::angles{0.0f, 0.0f},
+ glm::vec3{0.0f, 0.0f, 0.0f},
+ 0u,
+ client_index,
+ shared::math::coords{0, 0},
+ glm::vec3{0.0f, 120.0f, 0.0f},
+ };
+ }();
+
+ using pi = struct client::player_info;
client->player_info.emplace(
- std::make_shared<struct client::player_info>(
- std::move(player_info)));
+ std::make_shared<pi>(pi{.username = user,
+ .password = pass,
+ .player = std::move(player)}));
client->connection.rsend_packet(make_init_packet(client));
},
@@ -427,6 +539,30 @@ static void handle_auth_packet(const proto::auth& packet,
std::move(packet.password())));
}
+static void
+handle_item_swap_packet(const proto::item_swap& proto,
+ const resources::client_map_value& client) noexcept {
+ auto& inventory = client->get_player().inventory;
+
+ const auto a = proto.index_a();
+ const auto b = proto.index_b();
+
+ if (a == b) {
+ return;
+ }
+ const auto in_range = [&](const auto val) {
+ const auto MAX_SIZE =
+ static_cast<std::uint32_t>(std::size(inventory.contents));
+ return std::clamp(val, 0u, MAX_SIZE) == val;
+ };
+
+ if (!in_range(a) || !in_range(b)) {
+ return;
+ }
+
+ std::swap(inventory.contents[a], inventory.contents[b]);
+}
+
// Get new packets from clients, this will change client data and worldata.
static void parse_client_packets(resources::client_map_value& client,
resources::resources& res) noexcept {
@@ -434,7 +570,7 @@ static void parse_client_packets(resources::client_map_value& client,
if (packet->has_auth_packet()) {
handle_auth_packet(packet->auth_packet(), client, res.pool);
} else if (packet->has_move_packet()) {
- handle_move_packet(packet->move_packet(), client);
+ handle_move_packet(packet->move_packet(), client, res.chunks);
} else if (packet->has_say_packet()) {
handle_say_packet(packet->say_packet(), client, res.clients);
} else if (packet->has_request_chunk_packet()) {
@@ -449,10 +585,13 @@ static void parse_client_packets(resources::client_map_value& client,
} else if (packet->has_remove_block_packet()) {
handle_remove_block_packet(packet->remove_block_packet(), client,
res.chunks, res.pool);
+ } else if (packet->has_item_swap_packet()) {
+ handle_item_swap_packet(packet->item_swap_packet(), client);
}
#ifndef NDEBUG
else {
- shared::print::warn("server: unhandled packet type\n");
+ shared::print::debug << shared::print::time
+ << "server: unhandled packet type\n";
}
#endif
}
@@ -462,7 +601,8 @@ static void parse_client_packets(resources::client_map_value& client,
static void
send_remove_packets(server::resources::client_map_value& remove_client,
server::resources::client_map& clients) noexcept {
- const auto remove_packet = make_remove_packet(remove_client);
+ const auto remove_packet = std::make_shared<shared::net::rpacket>(
+ make_remove_packet(remove_client));
for (auto& [index, client_ptr] : clients) {
client_ptr->connection.rsend_packet(remove_packet);
}
@@ -470,8 +610,9 @@ send_remove_packets(server::resources::client_map_value& remove_client,
static void handle_new_connections(shared::net::connection& connection,
resources::resources& res) noexcept {
- shared::print::message("server: got connection from " +
- connection.get_address() + '\n');
+ shared::print::message << shared::print::time
+ << "server: got connection from "
+ << connection.get_address() << '\n';
// Add the client, waiting for an auth packet.
static uint32_t index = 0;
@@ -481,25 +622,25 @@ static void handle_new_connections(shared::net::connection& connection,
++index;
}
-static void move_client(resources::client_map_value& client,
- resources::chunk_map& chunks) noexcept {
- // shared::movement::fly(client.player, client.commands);
- movement::move(*client, chunks);
-}
-
static void send_client_packets(resources::client_map_value& client,
resources::resources& res) noexcept {
- // TODO PVS, as simple as checking if a client has our chunk pos in their
- // associated chunks list
+
+ if (!client->has_initialised()) {
+ return;
+ }
+ const auto non_local_packet = std::make_shared<shared::net::upacket>(
+ make_animate_update_packet(client->get_player(), std::nullopt));
for (auto& [index, c] : res.clients) {
- if (!c->has_initialised()) {
- continue;
- }
- if (!client->chunks.contains(c->get_player().chunk_pos)) {
+ if (!c->is_in_pvs(*client)) {
continue;
}
- client->connection.usend_packet(make_player_packet(c->get_player()));
+ // only network the last received sequence if sending an animate packet for the client's local player
+ if (c->index == client->index) {
+ c->connection.usend_packet( make_animate_update_packet(client->get_player(), client->sequence) );
+ } else {
+ c->connection.usend_packet(non_local_packet);
+ }
}
}
@@ -510,15 +651,17 @@ get_sent_disconnect_reason(const resources::client_map_value& client) noexcept {
return client->connection.get_bad_reason();
}
- if (client->chunks.size() >
- unsigned(state.draw_distance * state.draw_distance * 4)) {
+ if (static const unsigned max = static_cast<unsigned>(
+ state.draw_distance * state.draw_distance * 4);
+ client->chunks.size() > max) {
return "too many chunks associated with client";
}
return client->disconnect_reason;
}
-static void remove_bad_clients(resources::resources& res) noexcept {
+static void remove_bad_clients(resources::resources& res,
+ const bool send_remove = true) noexcept {
for (auto& [index, client] : res.clients) {
const auto reason = get_sent_disconnect_reason(client);
if (reason == std::nullopt) {
@@ -530,11 +673,18 @@ static void remove_bad_clients(resources::resources& res) noexcept {
}
client->disconnecting = true;
- shared::print::message("server: dropped " +
- client->connection.get_address() + " for \"" +
- *reason + "\"\n");
- send_message("disconnected for " + *reason, client, true);
- send_remove_packets(client, res.clients);
+ shared::print::message << shared::print::time << "server: dropped "
+ << client->connection.get_address() << " for \""
+ << *reason << "\"\n";
+ send_message(*reason, client, true);
+ if (send_remove) {
+ send_remove_packets(client, res.clients);
+ }
+
+ // Close early so the client may reconnect and receive a better message
+ // than a generic errno error if they connect too fast later.
+ client->connection.poll();
+ client->connection.close();
boost::asio::post(
res.pool,
@@ -546,8 +696,7 @@ static void remove_bad_clients(resources::resources& res) noexcept {
if (plr_info.has_value()) {
database::write_player(
(*plr_info)->username, (*plr_info)->password,
- make_player_packet((*plr_info)->player)
- .player_packet());
+ make_player_packet((*plr_info)->player));
}
// cleanup associated chunks
@@ -583,9 +732,10 @@ static void process_resources(resources::resources& res) noexcept {
}
// Move clients via their (hopefully updated) command.
- for (auto& [index, client] : res.clients) {
+ // CHANGED: we now do this when we get move packets
+ /*for (auto& [index, client] : res.clients) {
move_client(client, res.chunks);
- }
+ }*/
// Send packets which are sent once per tick, as of now the player
// struct of other clients.
@@ -593,9 +743,15 @@ static void process_resources(resources::resources& res) noexcept {
send_client_packets(client, res);
}
+ // Send queued packets.
+ for (auto& [index, client] : res.clients) {
+ client->connection.poll();
+ }
+
// Delete bad connections, print if it happens. Also sends a remove
// to all clients per client removed.
remove_bad_clients(res);
+ ++get_tick();
}
void main(const std::string_view address, const std::string_view port) {
@@ -603,8 +759,8 @@ void main(const std::string_view address, const std::string_view port) {
server::resources::init();
has_initialised = true;
- shared::print::notify("server: started at " + std::string{address} + ':' +
- std::string{port} + '\n');
+ shared::print::notify << shared::print::time << "server: started at "
+ << address << ':' << port << '\n';
// Server has a tickrate, we will use non-blocking polling at this
// tickrate.
@@ -629,12 +785,14 @@ void main(const std::string_view address, const std::string_view port) {
const auto& client = it.second;
client->disconnect_reason.emplace("server shutting down");
});
- remove_bad_clients(*res_lock);
+ remove_bad_clients(*res_lock, false);
}
- shared::print::notify("server: writing world data\n");
+ shared::print::notify << shared::print::time
+ << "server: writing world data\n";
server::resources::quit();
- shared::print::notify("server: gracefully exited\n");
+ shared::print::notify << shared::print::time
+ << "server: gracefully exited\n";
}
} // namespace server
diff --git a/src/server/server.hh b/src/server/server.hh
index bd2d5fd..d40827e 100644
--- a/src/server/server.hh
+++ b/src/server/server.hh
@@ -20,12 +20,12 @@
#include "server/client.hh"
#include "server/database.hh"
-#include "server/movement.hh"
+#include "server/movement/movement.hh"
#include "server/resources.hh"
-#include "server/world.hh"
+#include "server/world/chunk.hh"
+#include "shared/entity/player.hh"
#include "shared/net/net.hh"
#include "shared/net/proto.hh"
-#include "shared/player.hh"
#include "shared/shared.hh"
namespace server {
diff --git a/src/server/shared.hh b/src/server/shared.hh
index ffbf0f9..608e25e 100644
--- a/src/server/shared.hh
+++ b/src/server/shared.hh
@@ -10,7 +10,7 @@ struct state {
int draw_distance = 32;
std::uint64_t seed = 123456789;
std::string directory = "world/";
- std::uint32_t tickrate = 144;
+ std::uint32_t tickrate = 20;
};
inline state state;
diff --git a/src/server/world.cc b/src/server/world.cc
deleted file mode 100644
index 6527d6c..0000000
--- a/src/server/world.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-#include "server/world.hh"
-
-namespace server {
-namespace world {
-
-proto::packet chunk::make_chunk_packet() const noexcept {
-
- proto::packet ret_packet;
-
- auto chunk_packet = ret_packet.mutable_chunk_packet();
-
- auto chunk_pos = chunk_packet->mutable_chunk_pos();
- const shared::math::coords this_chunk_pos = this->get_pos();
- chunk_pos->set_x(this_chunk_pos.x);
- chunk_pos->set_z(this_chunk_pos.z);
-
- // Since protobuf can store at minimum uint32, we mash four of our
- // uint_8 chunk blocks into a single uint32.
- static_assert(shared::world::chunk::VOLUME % 4 == 0);
- for (unsigned i = 0u; i < shared::world::chunk::VOLUME / 4u; ++i) {
- std::uint32_t packed_blocks = 0u;
-
- for (unsigned j = 0; j < 4; ++j) {
- const auto block =
- static_cast<std::uint8_t>(this->blocks[i * 4 + j].type);
- packed_blocks |= static_cast<unsigned>(block << j * 8);
- }
-
- chunk_packet->add_blocks(packed_blocks);
- }
-
- return ret_packet;
-}
-
-static std::optional<shared::world::chunk::block_array>
-maybe_get_blocks(const shared::math::coords& coords) {
- const auto chunk = database::maybe_read_chunk(coords);
- if (!chunk.has_value()) {
- return std::nullopt;
- }
- return shared::world::chunk::make_blocks_from_chunk(chunk.value());
-}
-
-chunk::chunk(const std::uint64_t& seed,
- const shared::math::coords& coords) noexcept
- : shared::world::chunk(seed, coords, maybe_get_blocks(coords)) {
- this->update();
-}
-
-chunk::~chunk() noexcept { this->write(); }
-
-} // namespace world
-} // namespace server
diff --git a/src/server/world.hh b/src/server/world.hh
deleted file mode 100644
index 134f63b..0000000
--- a/src/server/world.hh
+++ /dev/null
@@ -1,59 +0,0 @@
-#ifndef SERVER_WORLD_HH_
-#define SERVER_WORLD_HH_
-
-#include <cstdint>
-
-#include "server/database.hh"
-#include "server/shared.hh"
-#include "shared/math.hh"
-#include "shared/net/net.hh"
-#include "shared/net/proto.hh"
-#include "shared/player.hh"
-#include "shared/world.hh"
-
-namespace server {
-namespace world {
-
-class chunk : public shared::world::chunk {
-private:
- bool should_write = false;
- bool should_update = true;
-
-public:
- proto::packet packet; // Packet ready for sending, updated in update().
- void arm_should_update() noexcept {
- this->should_update = this->should_write = true;
- }
- bool get_should_update() noexcept { return this->should_update; }
-
-private:
- proto::packet make_chunk_packet() const noexcept;
-
-public:
- // Attempt to read the file using protobuf, otherwise create a new chunk.
- // chunk(const chunk&) = delete;
- chunk(const uint64_t& seed, const shared::math::coords& coords) noexcept;
- ~chunk() noexcept;
-
- // Update the chunk_packet associated with the chunk if necessary.
- void update() noexcept {
- if (!this->should_update) {
- return;
- }
- this->packet = make_chunk_packet();
- this->should_update = false;
- }
- // calling .write before the destrutor will not result in a double write
- void write() noexcept {
- if (!this->should_write) {
- return;
- }
- server::database::write_chunk(this->pos, this->packet.chunk_packet());
- this->should_write = false;
- }
-};
-
-} // namespace world
-} // namespace server
-
-#endif
diff --git a/src/server/world/chunk.cc b/src/server/world/chunk.cc
new file mode 100644
index 0000000..80a3da9
--- /dev/null
+++ b/src/server/world/chunk.cc
@@ -0,0 +1,26 @@
+#include "server/world/chunk.hh"
+
+namespace server {
+namespace world {
+
+void chunk::update() noexcept {
+ if (!this->should_update) {
+ return;
+ }
+ this->packet.clear_chunk_packet();
+ this->pack(packet.mutable_chunk_packet());
+ this->should_update = false;
+}
+
+void chunk::write() noexcept {
+ if (!this->should_write) {
+ return;
+ }
+ server::database::write_chunk(this->pos, this->packet.chunk_packet());
+ this->should_write = false;
+}
+
+chunk::~chunk() noexcept { this->write(); }
+
+} // namespace world
+} // namespace server
diff --git a/src/server/world/chunk.hh b/src/server/world/chunk.hh
new file mode 100644
index 0000000..b8fc73c
--- /dev/null
+++ b/src/server/world/chunk.hh
@@ -0,0 +1,46 @@
+#ifndef SERVER_WORLD_CHUNK_HH_
+#define SERVER_WORLD_CHUNK_HH_
+
+#include <cstdint>
+
+#include "server/database.hh"
+#include "server/shared.hh"
+#include "shared/entity/player.hh"
+#include "shared/math/math.hh"
+#include "shared/net/net.hh"
+#include "shared/net/proto.hh"
+#include "shared/world/chunk.hh"
+
+namespace server {
+namespace world {
+
+class chunk : public shared::world::chunk {
+private:
+ bool should_write = false;
+ bool should_update = true;
+
+public:
+ proto::packet packet; // Packet ready for sending, updated in update().
+ void arm_should_update() noexcept {
+ this->should_update = this->should_write = true;
+ }
+
+public:
+ template <typename... Args>
+ chunk(Args&&... args) noexcept
+ : shared::world::chunk(std::forward<Args>(args)...) {
+ this->pack(packet.mutable_chunk_packet());
+ }
+ virtual ~chunk() noexcept;
+
+public:
+ // Update the chunk_packet associated with the chunk if necessary.
+ void update() noexcept;
+ // calling .write before the destrutor will not result in a double write
+ void write() noexcept;
+};
+
+} // namespace world
+} // namespace server
+
+#endif