diff options
| author | untodesu <kirill@untode.su> | 2025-09-11 15:48:53 +0500 |
|---|---|---|
| committer | untodesu <kirill@untode.su> | 2025-09-11 15:48:53 +0500 |
| commit | d0fbd68055e3f4a796330cc8acc6c0954b5327ff (patch) | |
| tree | e581014ea02711efa5e71f00f9862e5bca58f2ed /game/server | |
| parent | cbd823aa2154a956e7da4319eecbf7afc10441ae (diff) | |
| download | voxelius-d0fbd68055e3f4a796330cc8acc6c0954b5327ff.tar.bz2 voxelius-d0fbd68055e3f4a796330cc8acc6c0954b5327ff.zip | |
Run clang-format across the project
Diffstat (limited to 'game/server')
| -rw-r--r-- | game/server/chat.cc | 110 | ||||
| -rw-r--r-- | game/server/chat.hh | 24 | ||||
| -rw-r--r-- | game/server/game.cc | 312 | ||||
| -rw-r--r-- | game/server/game.hh | 50 | ||||
| -rw-r--r-- | game/server/globals.cc | 36 | ||||
| -rw-r--r-- | game/server/globals.hh | 54 | ||||
| -rw-r--r-- | game/server/main.cc | 216 | ||||
| -rw-r--r-- | game/server/pch.hh | 6 | ||||
| -rw-r--r-- | game/server/receive.cc | 348 | ||||
| -rw-r--r-- | game/server/receive.hh | 12 | ||||
| -rw-r--r-- | game/server/sessions.cc | 862 | ||||
| -rw-r--r-- | game/server/sessions.hh | 110 | ||||
| -rw-r--r-- | game/server/status.cc | 52 | ||||
| -rw-r--r-- | game/server/status.hh | 12 | ||||
| -rw-r--r-- | game/server/whitelist.cc | 202 | ||||
| -rw-r--r-- | game/server/whitelist.hh | 52 | ||||
| -rw-r--r-- | game/server/world/inhabited.hh | 12 | ||||
| -rw-r--r-- | game/server/world/overworld.cc | 766 | ||||
| -rw-r--r-- | game/server/world/overworld.hh | 136 | ||||
| -rw-r--r-- | game/server/world/universe.cc | 442 | ||||
| -rw-r--r-- | game/server/world/universe.hh | 50 | ||||
| -rw-r--r-- | game/server/world/unloader.cc | 156 | ||||
| -rw-r--r-- | game/server/world/unloader.hh | 26 | ||||
| -rw-r--r-- | game/server/world/worldgen.cc | 302 | ||||
| -rw-r--r-- | game/server/world/worldgen.hh | 42 |
25 files changed, 2195 insertions, 2195 deletions
diff --git a/game/server/chat.cc b/game/server/chat.cc index a0ceba8..fadfaa2 100644 --- a/game/server/chat.cc +++ b/game/server/chat.cc @@ -1,55 +1,55 @@ -#include "server/pch.hh" - -#include "server/chat.hh" - -#include "server/globals.hh" -#include "server/sessions.hh" -#include "shared/protocol.hh" - -static void on_chat_message_packet(const protocol::ChatMessage& packet) -{ - if(packet.type == protocol::ChatMessage::TEXT_MESSAGE) { - if(auto session = sessions::find(packet.peer)) { - server_chat::broadcast(packet.message.c_str(), session->client_username.c_str()); - } - else { - server_chat::broadcast(packet.message.c_str(), packet.sender.c_str()); - } - } -} - -void server_chat::init(void) -{ - globals::dispatcher.sink<protocol::ChatMessage>().connect<&on_chat_message_packet>(); -} - -void server_chat::broadcast(std::string_view message) -{ - server_chat::broadcast(message, "server"); -} - -void server_chat::broadcast(std::string_view message, std::string_view sender) -{ - protocol::ChatMessage packet; - packet.type = protocol::ChatMessage::TEXT_MESSAGE; - packet.message = message; - packet.sender = sender; - - protocol::broadcast(globals::server_host, protocol::encode(packet)); - - spdlog::info("<{}> {}", sender, message); -} - -void server_chat::send(Session* session, std::string_view message) -{ - server_chat::send(session, message, "server"); -} - -void server_chat::send(Session* session, std::string_view message, std::string_view sender) -{ - protocol::ChatMessage packet; - packet.type = protocol::ChatMessage::TEXT_MESSAGE; - packet.message = message; - packet.sender = sender; - protocol::broadcast(globals::server_host, protocol::encode(packet)); -} +#include "server/pch.hh"
+
+#include "server/chat.hh"
+
+#include "server/globals.hh"
+#include "server/sessions.hh"
+#include "shared/protocol.hh"
+
+static void on_chat_message_packet(const protocol::ChatMessage& packet)
+{
+ if(packet.type == protocol::ChatMessage::TEXT_MESSAGE) {
+ if(auto session = sessions::find(packet.peer)) {
+ server_chat::broadcast(packet.message.c_str(), session->client_username.c_str());
+ }
+ else {
+ server_chat::broadcast(packet.message.c_str(), packet.sender.c_str());
+ }
+ }
+}
+
+void server_chat::init(void)
+{
+ globals::dispatcher.sink<protocol::ChatMessage>().connect<&on_chat_message_packet>();
+}
+
+void server_chat::broadcast(std::string_view message)
+{
+ server_chat::broadcast(message, "server");
+}
+
+void server_chat::broadcast(std::string_view message, std::string_view sender)
+{
+ protocol::ChatMessage packet;
+ packet.type = protocol::ChatMessage::TEXT_MESSAGE;
+ packet.message = message;
+ packet.sender = sender;
+
+ protocol::broadcast(globals::server_host, protocol::encode(packet));
+
+ spdlog::info("<{}> {}", sender, message);
+}
+
+void server_chat::send(Session* session, std::string_view message)
+{
+ server_chat::send(session, message, "server");
+}
+
+void server_chat::send(Session* session, std::string_view message, std::string_view sender)
+{
+ protocol::ChatMessage packet;
+ packet.type = protocol::ChatMessage::TEXT_MESSAGE;
+ packet.message = message;
+ packet.sender = sender;
+ protocol::broadcast(globals::server_host, protocol::encode(packet));
+}
diff --git a/game/server/chat.hh b/game/server/chat.hh index 1b3e11b..76ddbbb 100644 --- a/game/server/chat.hh +++ b/game/server/chat.hh @@ -1,12 +1,12 @@ -#pragma once - -struct Session; - -namespace server_chat -{ -void init(void); -void broadcast(std::string_view message); -void broadcast(std::string_view message, std::string_view sender); -void send(Session* session, std::string_view message); -void send(Session* session, std::string_view message, std::string_view sender); -} // namespace server_chat +#pragma once
+
+struct Session;
+
+namespace server_chat
+{
+void init(void);
+void broadcast(std::string_view message);
+void broadcast(std::string_view message, std::string_view sender);
+void send(Session* session, std::string_view message);
+void send(Session* session, std::string_view message, std::string_view sender);
+} // namespace server_chat
diff --git a/game/server/game.cc b/game/server/game.cc index 441a2cb..8624670 100644 --- a/game/server/game.cc +++ b/game/server/game.cc @@ -1,156 +1,156 @@ -#include "server/pch.hh" - -#include "server/game.hh" - -#include "core/config/number.hh" -#include "core/config/string.hh" - -#include "core/io/cmdline.hh" -#include "core/io/config_map.hh" - -#include "core/math/constexpr.hh" -#include "core/math/crc64.hh" - -#include "core/utils/epoch.hh" - -#include "shared/entity/collision.hh" -#include "shared/entity/gravity.hh" -#include "shared/entity/head.hh" -#include "shared/entity/player.hh" -#include "shared/entity/stasis.hh" -#include "shared/entity/transform.hh" -#include "shared/entity/velocity.hh" - -#include "shared/world/dimension.hh" - -#include "shared/game_items.hh" -#include "shared/game_voxels.hh" -#include "shared/protocol.hh" -#include "shared/splash.hh" - -#include "server/world/universe.hh" -#include "server/world/unloader.hh" -#include "server/world/worldgen.hh" - -#include "server/chat.hh" -#include "server/globals.hh" -#include "server/receive.hh" -#include "server/sessions.hh" -#include "server/status.hh" -#include "server/whitelist.hh" - -config::Unsigned server_game::view_distance(4U, 4U, 32U); - -std::uint64_t server_game::password_hash = UINT64_MAX; - -static config::Number<enet_uint16> listen_port(protocol::PORT, 1024U, UINT16_MAX); -static config::Unsigned status_peers(2U, 1U, 16U); -static config::String password_string(""); - -void server_game::init(void) -{ - globals::server_config.add_value("game.listen_port", listen_port); - globals::server_config.add_value("game.status_peers", status_peers); - globals::server_config.add_value("game.password", password_string); - globals::server_config.add_value("game.view_distance", server_game::view_distance); - - sessions::init(); - - whitelist::init(); - - splash::init_server(); - - status::init(); - - server_chat::init(); - server_recieve::init(); - - world::worldgen::init(); - - world::unloader::init(); - world::universe::init(); -} - -void server_game::init_late(void) -{ - server_game::password_hash = math::crc64(password_string.get_value()); - - sessions::init_late(); - - whitelist::init_late(); - - ENetAddress address; - address.host = ENET_HOST_ANY; - address.port = listen_port.get_value(); - - globals::server_host = enet_host_create(&address, sessions::max_players.get_value() + status_peers.get_value(), 1, 0, 0); - - if(!globals::server_host) { - spdlog::critical("game: unable to setup an ENet host"); - std::terminate(); - } - - spdlog::info("game: host: {} player + {} status peers", sessions::max_players.get_value(), status_peers.get_value()); - spdlog::info("game: host: listening on UDP port {}", address.port); - - game_voxels::populate(); - game_items::populate(); - - world::unloader::init_late(); - world::universe::init_late(); - - sessions::init_post_universe(); -} - -void server_game::shutdown(void) -{ - protocol::Disconnect packet; - packet.reason = "protocol.server_shutdown"; - protocol::broadcast(globals::server_host, protocol::encode(packet)); - - whitelist::shutdown(); - - sessions::shutdown(); - - enet_host_flush(globals::server_host); - enet_host_service(globals::server_host, nullptr, 500); - enet_host_destroy(globals::server_host); - - world::universe::shutdown(); -} - -void server_game::fixed_update(void) -{ - // FIXME: threading - for(auto dimension : globals::dimensions) { - entity::Collision::fixed_update(dimension.second); - entity::Velocity::fixed_update(dimension.second); - entity::Transform::fixed_update(dimension.second); - entity::Gravity::fixed_update(dimension.second); - entity::Stasis::fixed_update(dimension.second); - } -} - -void server_game::fixed_update_late(void) -{ - ENetEvent enet_event; - - while(0 < enet_host_service(globals::server_host, &enet_event, 0)) { - if(enet_event.type == ENET_EVENT_TYPE_DISCONNECT) { - sessions::destroy(sessions::find(enet_event.peer)); - sessions::refresh_scoreboard(); - continue; - } - - if(enet_event.type == ENET_EVENT_TYPE_RECEIVE) { - protocol::decode(globals::dispatcher, enet_event.packet, enet_event.peer); - enet_packet_destroy(enet_event.packet); - continue; - } - } - - // FIXME: threading - for(auto dimension : globals::dimensions) { - world::unloader::fixed_update_late(dimension.second); - } -} +#include "server/pch.hh"
+
+#include "server/game.hh"
+
+#include "core/config/number.hh"
+#include "core/config/string.hh"
+
+#include "core/io/cmdline.hh"
+#include "core/io/config_map.hh"
+
+#include "core/math/constexpr.hh"
+#include "core/math/crc64.hh"
+
+#include "core/utils/epoch.hh"
+
+#include "shared/entity/collision.hh"
+#include "shared/entity/gravity.hh"
+#include "shared/entity/head.hh"
+#include "shared/entity/player.hh"
+#include "shared/entity/stasis.hh"
+#include "shared/entity/transform.hh"
+#include "shared/entity/velocity.hh"
+
+#include "shared/world/dimension.hh"
+
+#include "shared/game_items.hh"
+#include "shared/game_voxels.hh"
+#include "shared/protocol.hh"
+#include "shared/splash.hh"
+
+#include "server/world/universe.hh"
+#include "server/world/unloader.hh"
+#include "server/world/worldgen.hh"
+
+#include "server/chat.hh"
+#include "server/globals.hh"
+#include "server/receive.hh"
+#include "server/sessions.hh"
+#include "server/status.hh"
+#include "server/whitelist.hh"
+
+config::Unsigned server_game::view_distance(4U, 4U, 32U);
+
+std::uint64_t server_game::password_hash = UINT64_MAX;
+
+static config::Number<enet_uint16> listen_port(protocol::PORT, 1024U, UINT16_MAX);
+static config::Unsigned status_peers(2U, 1U, 16U);
+static config::String password_string("");
+
+void server_game::init(void)
+{
+ globals::server_config.add_value("game.listen_port", listen_port);
+ globals::server_config.add_value("game.status_peers", status_peers);
+ globals::server_config.add_value("game.password", password_string);
+ globals::server_config.add_value("game.view_distance", server_game::view_distance);
+
+ sessions::init();
+
+ whitelist::init();
+
+ splash::init_server();
+
+ status::init();
+
+ server_chat::init();
+ server_recieve::init();
+
+ world::worldgen::init();
+
+ world::unloader::init();
+ world::universe::init();
+}
+
+void server_game::init_late(void)
+{
+ server_game::password_hash = math::crc64(password_string.get_value());
+
+ sessions::init_late();
+
+ whitelist::init_late();
+
+ ENetAddress address;
+ address.host = ENET_HOST_ANY;
+ address.port = listen_port.get_value();
+
+ globals::server_host = enet_host_create(&address, sessions::max_players.get_value() + status_peers.get_value(), 1, 0, 0);
+
+ if(!globals::server_host) {
+ spdlog::critical("game: unable to setup an ENet host");
+ std::terminate();
+ }
+
+ spdlog::info("game: host: {} player + {} status peers", sessions::max_players.get_value(), status_peers.get_value());
+ spdlog::info("game: host: listening on UDP port {}", address.port);
+
+ game_voxels::populate();
+ game_items::populate();
+
+ world::unloader::init_late();
+ world::universe::init_late();
+
+ sessions::init_post_universe();
+}
+
+void server_game::shutdown(void)
+{
+ protocol::Disconnect packet;
+ packet.reason = "protocol.server_shutdown";
+ protocol::broadcast(globals::server_host, protocol::encode(packet));
+
+ whitelist::shutdown();
+
+ sessions::shutdown();
+
+ enet_host_flush(globals::server_host);
+ enet_host_service(globals::server_host, nullptr, 500);
+ enet_host_destroy(globals::server_host);
+
+ world::universe::shutdown();
+}
+
+void server_game::fixed_update(void)
+{
+ // FIXME: threading
+ for(auto dimension : globals::dimensions) {
+ entity::Collision::fixed_update(dimension.second);
+ entity::Velocity::fixed_update(dimension.second);
+ entity::Transform::fixed_update(dimension.second);
+ entity::Gravity::fixed_update(dimension.second);
+ entity::Stasis::fixed_update(dimension.second);
+ }
+}
+
+void server_game::fixed_update_late(void)
+{
+ ENetEvent enet_event;
+
+ while(0 < enet_host_service(globals::server_host, &enet_event, 0)) {
+ if(enet_event.type == ENET_EVENT_TYPE_DISCONNECT) {
+ sessions::destroy(sessions::find(enet_event.peer));
+ sessions::refresh_scoreboard();
+ continue;
+ }
+
+ if(enet_event.type == ENET_EVENT_TYPE_RECEIVE) {
+ protocol::decode(globals::dispatcher, enet_event.packet, enet_event.peer);
+ enet_packet_destroy(enet_event.packet);
+ continue;
+ }
+ }
+
+ // FIXME: threading
+ for(auto dimension : globals::dimensions) {
+ world::unloader::fixed_update_late(dimension.second);
+ }
+}
diff --git a/game/server/game.hh b/game/server/game.hh index 1dbe4b8..99ba3d6 100644 --- a/game/server/game.hh +++ b/game/server/game.hh @@ -1,25 +1,25 @@ -#pragma once - -namespace config -{ -class Unsigned; -} // namespace config - -namespace server_game -{ -extern config::Unsigned view_distance; -} // namespace server_game - -namespace server_game -{ -extern std::uint64_t password_hash; -} // namespace server_game - -namespace server_game -{ -void init(void); -void init_late(void); -void shutdown(void); -void fixed_update(void); -void fixed_update_late(void); -} // namespace server_game +#pragma once
+
+namespace config
+{
+class Unsigned;
+} // namespace config
+
+namespace server_game
+{
+extern config::Unsigned view_distance;
+} // namespace server_game
+
+namespace server_game
+{
+extern std::uint64_t password_hash;
+} // namespace server_game
+
+namespace server_game
+{
+void init(void);
+void init_late(void);
+void shutdown(void);
+void fixed_update(void);
+void fixed_update_late(void);
+} // namespace server_game
diff --git a/game/server/globals.cc b/game/server/globals.cc index 7d79e4d..9e1875d 100644 --- a/game/server/globals.cc +++ b/game/server/globals.cc @@ -1,18 +1,18 @@ -#include "server/pch.hh" - -#include "server/globals.hh" - -#include "core/io/config_map.hh" - -#include "shared/protocol.hh" - -io::ConfigMap globals::server_config; - -ENetHost* globals::server_host; - -bool globals::is_running; -unsigned int globals::tickrate; -std::uint64_t globals::tickrate_dt; - -world::Dimension* globals::spawn_dimension; -std::unordered_map<std::string, world::Dimension*> globals::dimensions; +#include "server/pch.hh"
+
+#include "server/globals.hh"
+
+#include "core/io/config_map.hh"
+
+#include "shared/protocol.hh"
+
+io::ConfigMap globals::server_config;
+
+ENetHost* globals::server_host;
+
+bool globals::is_running;
+unsigned int globals::tickrate;
+std::uint64_t globals::tickrate_dt;
+
+world::Dimension* globals::spawn_dimension;
+std::unordered_map<std::string, world::Dimension*> globals::dimensions;
diff --git a/game/server/globals.hh b/game/server/globals.hh index b684d3b..e9aa174 100644 --- a/game/server/globals.hh +++ b/game/server/globals.hh @@ -1,27 +1,27 @@ -#pragma once - -#include "shared/globals.hh" - -namespace io -{ -class ConfigMap; -} // namespace io - -namespace world -{ -class Dimension; -} // namespace world - -namespace globals -{ -extern io::ConfigMap server_config; - -extern ENetHost* server_host; - -extern bool is_running; -extern unsigned int tickrate; -extern std::uint64_t tickrate_dt; - -extern world::Dimension* spawn_dimension; -extern std::unordered_map<std::string, world::Dimension*> dimensions; -} // namespace globals +#pragma once
+
+#include "shared/globals.hh"
+
+namespace io
+{
+class ConfigMap;
+} // namespace io
+
+namespace world
+{
+class Dimension;
+} // namespace world
+
+namespace globals
+{
+extern io::ConfigMap server_config;
+
+extern ENetHost* server_host;
+
+extern bool is_running;
+extern unsigned int tickrate;
+extern std::uint64_t tickrate_dt;
+
+extern world::Dimension* spawn_dimension;
+extern std::unordered_map<std::string, world::Dimension*> dimensions;
+} // namespace globals
diff --git a/game/server/main.cc b/game/server/main.cc index 9d7026f..3316407 100644 --- a/game/server/main.cc +++ b/game/server/main.cc @@ -1,108 +1,108 @@ -#include "server/pch.hh" - -#include "core/config/number.hh" - -#include "core/io/cmdline.hh" -#include "core/io/config_map.hh" - -#include "core/math/constexpr.hh" - -#include "core/resource/image.hh" -#include "core/resource/resource.hh" - -#include "core/utils/epoch.hh" - -#include "core/threading.hh" -#include "core/version.hh" - -#include "shared/game.hh" -#include "shared/protocol.hh" - -#include "server/game.hh" -#include "server/globals.hh" - -static config::Unsigned server_tickrate(protocol::TICKRATE, 10U, 300U); - -static void on_termination_signal(int) -{ - spdlog::warn("server: received termination signal"); - globals::is_running = false; -} - -int main(int argc, char** argv) -{ - io::cmdline::create(argc, argv); - - shared_game::init(argc, argv); - - spdlog::info("Voxelius Server {}", version::semver); - - globals::fixed_frametime = 0.0f; - globals::fixed_frametime_avg = 0.0f; - globals::fixed_frametime_us = 0; - globals::fixed_framecount = 0; - - globals::curtime = utils::unix_microseconds(); - - globals::is_running = true; - - std::signal(SIGINT, &on_termination_signal); - std::signal(SIGTERM, &on_termination_signal); - - Image::register_resource(); - - server_game::init(); - - threading::init(); - - globals::server_config.add_value("server.tickrate", server_tickrate); - globals::server_config.load_file("server.conf"); - globals::server_config.load_cmdline(); - - globals::tickrate = server_tickrate.get_value(); - globals::tickrate_dt = static_cast<std::uint64_t>(1000000.0f / static_cast<float>(globals::tickrate)); - - server_game::init_late(); - - std::uint64_t last_curtime = globals::curtime; - - while(globals::is_running) { - globals::curtime = utils::unix_microseconds(); - - globals::fixed_frametime_us = globals::curtime - last_curtime; - globals::fixed_frametime = static_cast<float>(globals::fixed_frametime_us) / 1000000.0f; - globals::fixed_frametime_avg += globals::fixed_frametime; - globals::fixed_frametime_avg *= 0.5f; - - last_curtime = globals::curtime; - - server_game::fixed_update(); - server_game::fixed_update_late(); - - globals::dispatcher.update(); - - globals::fixed_framecount += 1; - - std::this_thread::sleep_for(std::chrono::microseconds(globals::tickrate_dt)); - - resource::soft_cleanup(); - - threading::update(); - } - - server_game::shutdown(); - - resource::hard_cleanup(); - - threading::shutdown(); - - spdlog::info("server: shutdown after {} frames", globals::fixed_framecount); - spdlog::info("server: average framerate: {:.03f} TPS", 1.0f / globals::fixed_frametime_avg); - spdlog::info("server: average frametime: {:.03f} MSPT", 1000.0f * globals::fixed_frametime_avg); - - globals::server_config.save_file("server.conf"); - - shared_game::shutdown(); - - return EXIT_SUCCESS; -} +#include "server/pch.hh"
+
+#include "core/config/number.hh"
+
+#include "core/io/cmdline.hh"
+#include "core/io/config_map.hh"
+
+#include "core/math/constexpr.hh"
+
+#include "core/resource/image.hh"
+#include "core/resource/resource.hh"
+
+#include "core/utils/epoch.hh"
+
+#include "core/threading.hh"
+#include "core/version.hh"
+
+#include "shared/game.hh"
+#include "shared/protocol.hh"
+
+#include "server/game.hh"
+#include "server/globals.hh"
+
+static config::Unsigned server_tickrate(protocol::TICKRATE, 10U, 300U);
+
+static void on_termination_signal(int)
+{
+ spdlog::warn("server: received termination signal");
+ globals::is_running = false;
+}
+
+int main(int argc, char** argv)
+{
+ io::cmdline::create(argc, argv);
+
+ shared_game::init(argc, argv);
+
+ spdlog::info("Voxelius Server {}", version::semver);
+
+ globals::fixed_frametime = 0.0f;
+ globals::fixed_frametime_avg = 0.0f;
+ globals::fixed_frametime_us = 0;
+ globals::fixed_framecount = 0;
+
+ globals::curtime = utils::unix_microseconds();
+
+ globals::is_running = true;
+
+ std::signal(SIGINT, &on_termination_signal);
+ std::signal(SIGTERM, &on_termination_signal);
+
+ Image::register_resource();
+
+ server_game::init();
+
+ threading::init();
+
+ globals::server_config.add_value("server.tickrate", server_tickrate);
+ globals::server_config.load_file("server.conf");
+ globals::server_config.load_cmdline();
+
+ globals::tickrate = server_tickrate.get_value();
+ globals::tickrate_dt = static_cast<std::uint64_t>(1000000.0f / static_cast<float>(globals::tickrate));
+
+ server_game::init_late();
+
+ std::uint64_t last_curtime = globals::curtime;
+
+ while(globals::is_running) {
+ globals::curtime = utils::unix_microseconds();
+
+ globals::fixed_frametime_us = globals::curtime - last_curtime;
+ globals::fixed_frametime = static_cast<float>(globals::fixed_frametime_us) / 1000000.0f;
+ globals::fixed_frametime_avg += globals::fixed_frametime;
+ globals::fixed_frametime_avg *= 0.5f;
+
+ last_curtime = globals::curtime;
+
+ server_game::fixed_update();
+ server_game::fixed_update_late();
+
+ globals::dispatcher.update();
+
+ globals::fixed_framecount += 1;
+
+ std::this_thread::sleep_for(std::chrono::microseconds(globals::tickrate_dt));
+
+ resource::soft_cleanup();
+
+ threading::update();
+ }
+
+ server_game::shutdown();
+
+ resource::hard_cleanup();
+
+ threading::shutdown();
+
+ spdlog::info("server: shutdown after {} frames", globals::fixed_framecount);
+ spdlog::info("server: average framerate: {:.03f} TPS", 1.0f / globals::fixed_frametime_avg);
+ spdlog::info("server: average frametime: {:.03f} MSPT", 1000.0f * globals::fixed_frametime_avg);
+
+ globals::server_config.save_file("server.conf");
+
+ shared_game::shutdown();
+
+ return EXIT_SUCCESS;
+}
diff --git a/game/server/pch.hh b/game/server/pch.hh index 6f42c17..e90ebbd 100644 --- a/game/server/pch.hh +++ b/game/server/pch.hh @@ -1,3 +1,3 @@ -#pragma once - -#include <shared/pch.hh> +#pragma once
+
+#include <shared/pch.hh>
diff --git a/game/server/receive.cc b/game/server/receive.cc index 5c56872..75ac3a6 100644 --- a/game/server/receive.cc +++ b/game/server/receive.cc @@ -1,174 +1,174 @@ -#include "server/pch.hh" - -#include "server/receive.hh" - -#include "core/config/number.hh" - -#include "shared/entity/head.hh" -#include "shared/entity/transform.hh" -#include "shared/entity/velocity.hh" - -#include "shared/world/chunk_aabb.hh" -#include "shared/world/dimension.hh" - -#include "shared/coord.hh" -#include "shared/protocol.hh" - -#include "server/world/inhabited.hh" -#include "server/world/universe.hh" -#include "server/world/worldgen.hh" - -#include "server/game.hh" -#include "server/globals.hh" -#include "server/sessions.hh" - -static void on_entity_transform_packet(const protocol::EntityTransform& packet) -{ - if(auto session = sessions::find(packet.peer)) { - if(session->dimension && session->dimension->entities.valid(session->player_entity)) { - auto& component = session->dimension->entities.emplace_or_replace<entity::Transform>(session->player_entity); - component.angles = packet.angles; - component.chunk = packet.chunk; - component.local = packet.local; - - protocol::EntityTransform response; - response.entity = session->player_entity; - response.angles = component.angles; - response.chunk = component.chunk; - response.local = component.local; - - // Propagate changes to the rest of the world - // except the peer that has sent the packet in the first place - sessions::broadcast(session->dimension, protocol::encode(response), session->peer); - } - } -} - -static void on_entity_velocity_packet(const protocol::EntityVelocity& packet) -{ - if(auto session = sessions::find(packet.peer)) { - if(session->dimension && session->dimension->entities.valid(session->player_entity)) { - auto& component = session->dimension->entities.emplace_or_replace<entity::Velocity>(session->player_entity); - component.value = packet.value; - - protocol::EntityVelocity response; - response.entity = session->player_entity; - response.value = component.value; - - // Propagate changes to the rest of the world - // except the peer that has sent the packet in the first place - sessions::broadcast(session->dimension, protocol::encode(response), session->peer); - } - } -} - -static void on_entity_head_packet(const protocol::EntityHead& packet) -{ - if(auto session = sessions::find(packet.peer)) { - if(session->dimension && session->dimension->entities.valid(session->player_entity)) { - auto& component = session->dimension->entities.emplace_or_replace<entity::Head>(session->player_entity); - component.angles = packet.angles; - - protocol::EntityHead response; - response.entity = session->player_entity; - response.angles = component.angles; - - // Propagate changes to the rest of the world - // except the peer that has sent the packet in the first place - sessions::broadcast(session->dimension, protocol::encode(response), session->peer); - } - } -} - -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)) { - auto cpos = coord::to_chunk(packet.vpos); - auto lpos = coord::to_local(packet.vpos); - auto index = coord::to_index(lpos); - - if(world::worldgen::is_generating(session->dimension, cpos)) { - // The chunk is currently being generated; - // ignore all requests from players to build there - return; - } - - auto chunk = session->dimension->find_chunk(cpos); - - if(chunk == nullptr) { - // The chunk is not loaded, so we must - // ignore any requests from players to build there - return; - } - - chunk->set_voxel(packet.voxel, index); - - session->dimension->chunks.emplace_or_replace<world::Inhabited>(chunk->get_entity()); - - protocol::SetVoxel response; - response.vpos = packet.vpos; - response.voxel = packet.voxel; - sessions::broadcast(session->dimension, protocol::encode(response), session->peer); - - return; - } - } -} - -static void on_request_chunk_packet(const protocol::RequestChunk& packet) -{ - if(auto session = sessions::find(packet.peer)) { - if(!session->dimension || !session->dimension->entities.valid(session->player_entity)) { - // De-spawned sessions cannot request - // chunks from the server; that's cheating!!! - return; - } - - if(auto transform = session->dimension->entities.try_get<entity::Transform>(session->player_entity)) { - world::ChunkAABB view_box; - view_box.min = transform->chunk - static_cast<chunk_pos::value_type>(server_game::view_distance.get_value()); - view_box.max = transform->chunk + static_cast<chunk_pos::value_type>(server_game::view_distance.get_value()); - - if(view_box.contains(packet.cpos)) { - if(auto chunk = world::universe::load_chunk(session->dimension, packet.cpos)) { - protocol::ChunkVoxels response; - response.chunk = packet.cpos; - response.voxels = chunk->get_voxels(); - protocol::send(packet.peer, protocol::encode(response)); - } - else { - world::worldgen::request_chunk(session, packet.cpos); - } - } - } - } -} - -static void on_entity_sound_packet(const protocol::EntitySound& packet) -{ - if(auto session = sessions::find(packet.peer)) { - if(!session->dimension || !session->dimension->entities.valid(session->player_entity)) { - // De-spawned sessions cannot play sounds - return; - } - - protocol::EntitySound response; - response.entity = session->player_entity; - response.sound = packet.sound; - response.looping = packet.looping; - response.pitch = packet.pitch; - - sessions::broadcast(session->dimension, protocol::encode(response), packet.peer); - } -} - -void server_recieve::init(void) -{ - globals::dispatcher.sink<protocol::EntityTransform>().connect<&on_entity_transform_packet>(); - globals::dispatcher.sink<protocol::EntityVelocity>().connect<&on_entity_velocity_packet>(); - globals::dispatcher.sink<protocol::EntityHead>().connect<&on_entity_head_packet>(); - globals::dispatcher.sink<protocol::SetVoxel>().connect<&on_set_voxel_packet>(); - globals::dispatcher.sink<protocol::RequestChunk>().connect<&on_request_chunk_packet>(); - globals::dispatcher.sink<protocol::EntitySound>().connect<&on_entity_sound_packet>(); -} +#include "server/pch.hh"
+
+#include "server/receive.hh"
+
+#include "core/config/number.hh"
+
+#include "shared/entity/head.hh"
+#include "shared/entity/transform.hh"
+#include "shared/entity/velocity.hh"
+
+#include "shared/world/chunk_aabb.hh"
+#include "shared/world/dimension.hh"
+
+#include "shared/coord.hh"
+#include "shared/protocol.hh"
+
+#include "server/world/inhabited.hh"
+#include "server/world/universe.hh"
+#include "server/world/worldgen.hh"
+
+#include "server/game.hh"
+#include "server/globals.hh"
+#include "server/sessions.hh"
+
+static void on_entity_transform_packet(const protocol::EntityTransform& packet)
+{
+ if(auto session = sessions::find(packet.peer)) {
+ if(session->dimension && session->dimension->entities.valid(session->player_entity)) {
+ auto& component = session->dimension->entities.emplace_or_replace<entity::Transform>(session->player_entity);
+ component.angles = packet.angles;
+ component.chunk = packet.chunk;
+ component.local = packet.local;
+
+ protocol::EntityTransform response;
+ response.entity = session->player_entity;
+ response.angles = component.angles;
+ response.chunk = component.chunk;
+ response.local = component.local;
+
+ // Propagate changes to the rest of the world
+ // except the peer that has sent the packet in the first place
+ sessions::broadcast(session->dimension, protocol::encode(response), session->peer);
+ }
+ }
+}
+
+static void on_entity_velocity_packet(const protocol::EntityVelocity& packet)
+{
+ if(auto session = sessions::find(packet.peer)) {
+ if(session->dimension && session->dimension->entities.valid(session->player_entity)) {
+ auto& component = session->dimension->entities.emplace_or_replace<entity::Velocity>(session->player_entity);
+ component.value = packet.value;
+
+ protocol::EntityVelocity response;
+ response.entity = session->player_entity;
+ response.value = component.value;
+
+ // Propagate changes to the rest of the world
+ // except the peer that has sent the packet in the first place
+ sessions::broadcast(session->dimension, protocol::encode(response), session->peer);
+ }
+ }
+}
+
+static void on_entity_head_packet(const protocol::EntityHead& packet)
+{
+ if(auto session = sessions::find(packet.peer)) {
+ if(session->dimension && session->dimension->entities.valid(session->player_entity)) {
+ auto& component = session->dimension->entities.emplace_or_replace<entity::Head>(session->player_entity);
+ component.angles = packet.angles;
+
+ protocol::EntityHead response;
+ response.entity = session->player_entity;
+ response.angles = component.angles;
+
+ // Propagate changes to the rest of the world
+ // except the peer that has sent the packet in the first place
+ sessions::broadcast(session->dimension, protocol::encode(response), session->peer);
+ }
+ }
+}
+
+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)) {
+ auto cpos = coord::to_chunk(packet.vpos);
+ auto lpos = coord::to_local(packet.vpos);
+ auto index = coord::to_index(lpos);
+
+ if(world::worldgen::is_generating(session->dimension, cpos)) {
+ // The chunk is currently being generated;
+ // ignore all requests from players to build there
+ return;
+ }
+
+ auto chunk = session->dimension->find_chunk(cpos);
+
+ if(chunk == nullptr) {
+ // The chunk is not loaded, so we must
+ // ignore any requests from players to build there
+ return;
+ }
+
+ chunk->set_voxel(packet.voxel, index);
+
+ session->dimension->chunks.emplace_or_replace<world::Inhabited>(chunk->get_entity());
+
+ protocol::SetVoxel response;
+ response.vpos = packet.vpos;
+ response.voxel = packet.voxel;
+ sessions::broadcast(session->dimension, protocol::encode(response), session->peer);
+
+ return;
+ }
+ }
+}
+
+static void on_request_chunk_packet(const protocol::RequestChunk& packet)
+{
+ if(auto session = sessions::find(packet.peer)) {
+ if(!session->dimension || !session->dimension->entities.valid(session->player_entity)) {
+ // De-spawned sessions cannot request
+ // chunks from the server; that's cheating!!!
+ return;
+ }
+
+ if(auto transform = session->dimension->entities.try_get<entity::Transform>(session->player_entity)) {
+ world::ChunkAABB view_box;
+ view_box.min = transform->chunk - static_cast<chunk_pos::value_type>(server_game::view_distance.get_value());
+ view_box.max = transform->chunk + static_cast<chunk_pos::value_type>(server_game::view_distance.get_value());
+
+ if(view_box.contains(packet.cpos)) {
+ if(auto chunk = world::universe::load_chunk(session->dimension, packet.cpos)) {
+ protocol::ChunkVoxels response;
+ response.chunk = packet.cpos;
+ response.voxels = chunk->get_voxels();
+ protocol::send(packet.peer, protocol::encode(response));
+ }
+ else {
+ world::worldgen::request_chunk(session, packet.cpos);
+ }
+ }
+ }
+ }
+}
+
+static void on_entity_sound_packet(const protocol::EntitySound& packet)
+{
+ if(auto session = sessions::find(packet.peer)) {
+ if(!session->dimension || !session->dimension->entities.valid(session->player_entity)) {
+ // De-spawned sessions cannot play sounds
+ return;
+ }
+
+ protocol::EntitySound response;
+ response.entity = session->player_entity;
+ response.sound = packet.sound;
+ response.looping = packet.looping;
+ response.pitch = packet.pitch;
+
+ sessions::broadcast(session->dimension, protocol::encode(response), packet.peer);
+ }
+}
+
+void server_recieve::init(void)
+{
+ globals::dispatcher.sink<protocol::EntityTransform>().connect<&on_entity_transform_packet>();
+ globals::dispatcher.sink<protocol::EntityVelocity>().connect<&on_entity_velocity_packet>();
+ globals::dispatcher.sink<protocol::EntityHead>().connect<&on_entity_head_packet>();
+ globals::dispatcher.sink<protocol::SetVoxel>().connect<&on_set_voxel_packet>();
+ globals::dispatcher.sink<protocol::RequestChunk>().connect<&on_request_chunk_packet>();
+ globals::dispatcher.sink<protocol::EntitySound>().connect<&on_entity_sound_packet>();
+}
diff --git a/game/server/receive.hh b/game/server/receive.hh index 4150226..a8d531e 100644 --- a/game/server/receive.hh +++ b/game/server/receive.hh @@ -1,6 +1,6 @@ -#pragma once - -namespace server_recieve -{ -void init(void); -} // namespace server_recieve +#pragma once
+
+namespace server_recieve
+{
+void init(void);
+} // namespace server_recieve
diff --git a/game/server/sessions.cc b/game/server/sessions.cc index 0bddcfe..c06ec3e 100644 --- a/game/server/sessions.cc +++ b/game/server/sessions.cc @@ -1,431 +1,431 @@ -#include "server/pch.hh" - -#include "server/sessions.hh" - -#include "core/config/boolean.hh" -#include "core/config/number.hh" - -#include "core/io/config_map.hh" - -#include "core/math/constexpr.hh" -#include "core/math/crc64.hh" - -#include "core/utils/string.hh" - -#include "shared/entity/factory.hh" -#include "shared/entity/head.hh" -#include "shared/entity/player.hh" -#include "shared/entity/transform.hh" -#include "shared/entity/velocity.hh" - -#include "shared/world/chunk.hh" -#include "shared/world/dimension.hh" -#include "shared/world/item_registry.hh" -#include "shared/world/voxel_registry.hh" - -#include "shared/coord.hh" -#include "shared/protocol.hh" - -#include "server/game.hh" -#include "server/globals.hh" -#include "server/whitelist.hh" - -class DimensionListener final { -public: - explicit DimensionListener(world::Dimension* dimension); - void on_destroy_entity(const entt::registry& registry, entt::entity entity); - -private: - world::Dimension* dimension; -}; - -config::Unsigned sessions::max_players(8U, 1U, 128U); -unsigned int sessions::num_players = 0U; - -static emhash8::HashMap<std::string, Session*> username_map; -static emhash8::HashMap<std::uint64_t, Session*> identity_map; -static std::vector<DimensionListener> dimension_listeners; -static std::vector<Session> sessions_vector; - -static void on_login_request_packet(const protocol::LoginRequest& packet) -{ - if(packet.version > protocol::VERSION) { - protocol::Disconnect response; - response.reason = "protocol.outdated_server"; - protocol::send(packet.peer, protocol::encode(response)); - return; - } - - if(packet.version < protocol::VERSION) { - 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()) { - 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()) { - protocol::Disconnect response; - response.reason = "protocol.item_registry_checksum"; - protocol::send(packet.peer, protocol::encode(response)); - return; - } - - // Don't assign new usernames and just kick the player if - // an another client using the same username is already connected - // and playing; since we have a whitelist, adding "(1)" isn't feasible anymore - if(username_map.contains(packet.username)) { - protocol::Disconnect response; - response.reason = "protocol.username_taken"; - protocol::send(packet.peer, protocol::encode(response)); - return; - } - - if(whitelist::enabled.get_value()) { - if(!whitelist::contains(packet.username.c_str())) { - protocol::Disconnect response; - response.reason = "protocol.not_whitelisted"; - protocol::send(packet.peer, protocol::encode(response)); - return; - } - - if(!whitelist::matches(packet.username.c_str(), packet.password_hash)) { - protocol::Disconnect response; - response.reason = "protocol.password_incorrect"; - protocol::send(packet.peer, protocol::encode(response)); - return; - } - } - else if(packet.password_hash != server_game::password_hash) { - protocol::Disconnect response; - response.reason = "protocol.password_incorrect"; - protocol::send(packet.peer, protocol::encode(response)); - return; - } - - if(Session* session = sessions::create(packet.peer, packet.username.c_str())) { - protocol::LoginResponse response; - response.client_index = session->client_index; - response.client_identity = session->client_identity; - response.server_tickrate = globals::tickrate; - protocol::send(packet.peer, protocol::encode(response)); - - protocol::DimensionInfo dim_info; - dim_info.name = globals::spawn_dimension->get_name(); - dim_info.gravity = globals::spawn_dimension->get_gravity(); - protocol::send(packet.peer, protocol::encode(dim_info)); - - spdlog::info("sessions: {} [{}] logged in with client_index={} in {}", session->client_username, session->client_identity, - session->client_index, globals::spawn_dimension->get_name()); - - // FIXME: only send entities that are present within the current - // player's view bounding box; this also would mean we're not sending - // anything here and just straight up spawing the player and await them - // to receive all the chunks and entites they feel like requesting - for(auto entity : globals::spawn_dimension->entities.view<entt::entity>()) { - if(const auto head = globals::spawn_dimension->entities.try_get<entity::Head>(entity)) { - protocol::EntityHead head_packet; - head_packet.entity = entity; - head_packet.angles = head->angles; - protocol::send(session->peer, protocol::encode(head_packet)); - } - - if(const auto transform = globals::spawn_dimension->entities.try_get<entity::Transform>(entity)) { - protocol::EntityTransform transform_packet; - transform_packet.entity = entity; - transform_packet.angles = transform->angles; - transform_packet.chunk = transform->chunk; - transform_packet.local = transform->local; - protocol::send(session->peer, protocol::encode(transform_packet)); - } - - if(const auto velocity = globals::spawn_dimension->entities.try_get<entity::Velocity>(entity)) { - protocol::EntityVelocity velocity_packet; - velocity_packet.entity = entity; - velocity_packet.value = velocity->value; - protocol::send(session->peer, protocol::encode(velocity_packet)); - } - - if(globals::spawn_dimension->entities.all_of<entity::Player>(entity)) { - protocol::EntityPlayer player_packet; - player_packet.entity = entity; - protocol::send(session->peer, protocol::encode(player_packet)); - } - } - - session->dimension = globals::spawn_dimension; - session->player_entity = globals::spawn_dimension->entities.create(); - entity::shared::create_player(globals::spawn_dimension, session->player_entity); - - const auto& head = globals::spawn_dimension->entities.get<entity::Head>(session->player_entity); - const auto& transform = globals::spawn_dimension->entities.get<entity::Transform>(session->player_entity); - const auto& velocity = globals::spawn_dimension->entities.get<entity::Velocity>(session->player_entity); - - protocol::EntityHead head_packet; - head_packet.entity = session->player_entity; - head_packet.angles = head.angles; - - protocol::EntityTransform transform_packet; - transform_packet.entity = session->player_entity; - transform_packet.angles = transform.angles; - transform_packet.chunk = transform.chunk; - transform_packet.local = transform.local; - - protocol::EntityVelocity velocity_packet; - velocity_packet.entity = session->player_entity; - velocity_packet.value = velocity.value; - - protocol::EntityPlayer player_packet; - player_packet.entity = session->player_entity; - - protocol::broadcast(globals::server_host, protocol::encode(head_packet)); - protocol::broadcast(globals::server_host, protocol::encode(transform_packet)); - protocol::broadcast(globals::server_host, protocol::encode(velocity_packet)); - protocol::broadcast(globals::server_host, protocol::encode(player_packet)); - - protocol::SpawnPlayer spawn_packet; - spawn_packet.entity = session->player_entity; - - // SpawnPlayer serves a different purpose compared to EntityPlayer - // The latter is used to construct entities (as in "attach a component") - // whilst the SpawnPlayer packet is used to notify client-side that the - // entity identifier in the packet is to be treated as the local player entity - protocol::send(session->peer, protocol::encode(spawn_packet)); - - protocol::ChatMessage message; - message.type = protocol::ChatMessage::PLAYER_JOIN; - message.sender = session->client_username; - message.message = std::string(); - - protocol::broadcast(globals::server_host, protocol::encode(message)); - - sessions::refresh_scoreboard(); - - return; - } - - protocol::Disconnect response; - response.reason = "protocol.server_full"; - protocol::send(packet.peer, protocol::encode(response)); -} - -static void on_disconnect_packet(const protocol::Disconnect& packet) -{ - if(Session* session = sessions::find(packet.peer)) { - protocol::ChatMessage message; - message.type = protocol::ChatMessage::PLAYER_LEAVE; - message.sender = session->client_username; - message.message = packet.reason; - - protocol::broadcast(globals::server_host, protocol::encode(message), session->peer); - - spdlog::info("{} disconnected ({})", session->client_username, packet.reason); - - sessions::destroy(session); - sessions::refresh_scoreboard(); - } -} - -// NOTE: [sessions] is a good place for this since [receive] -// handles entity data sent by players and [sessions] handles -// everything else network related that is not player movement -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.flags = 0U; // UNDONE - protocol::broadcast(globals::server_host, protocol::encode(packet)); -} - -DimensionListener::DimensionListener(world::Dimension* dimension) -{ - this->dimension = dimension; -} - -void DimensionListener::on_destroy_entity(const entt::registry& registry, entt::entity entity) -{ - protocol::RemoveEntity packet; - packet.entity = entity; - sessions::broadcast(dimension, protocol::encode(packet)); -} - -void sessions::init(void) -{ - globals::server_config.add_value("sessions.max_players", sessions::max_players); - - globals::dispatcher.sink<protocol::LoginRequest>().connect<&on_login_request_packet>(); - globals::dispatcher.sink<protocol::Disconnect>().connect<&on_disconnect_packet>(); - - globals::dispatcher.sink<world::VoxelSetEvent>().connect<&on_voxel_set>(); -} - -void sessions::init_late(void) -{ - sessions::num_players = 0U; - - username_map.clear(); - identity_map.clear(); - sessions_vector.resize(sessions::max_players.get_value(), Session()); - - for(unsigned int i = 0U; i < sessions::max_players.get_value(); ++i) { - sessions_vector[i].client_index = UINT16_MAX; - sessions_vector[i].client_identity = UINT64_MAX; - sessions_vector[i].client_username = std::string(); - sessions_vector[i].player_entity = entt::null; - sessions_vector[i].peer = nullptr; - } -} - -void sessions::init_post_universe(void) -{ - for(auto& dimension : globals::dimensions) { - dimension_listeners.push_back(DimensionListener(dimension.second)); - dimension.second->entities.on_destroy<entt::entity>().connect<&DimensionListener::on_destroy_entity>(dimension_listeners.back()); - } -} - -void sessions::shutdown(void) -{ - username_map.clear(); - identity_map.clear(); - sessions_vector.clear(); - dimension_listeners.clear(); -} - -Session* sessions::create(ENetPeer* peer, std::string_view client_username) -{ - for(unsigned int i = 0U; i < sessions::max_players.get_value(); ++i) { - if(!sessions_vector[i].peer) { - std::uint64_t client_identity = math::crc64(client_username.data(), client_username.size()); - - sessions_vector[i].client_index = i; - sessions_vector[i].client_identity = client_identity; - sessions_vector[i].client_username = client_username; - sessions_vector[i].player_entity = entt::null; - sessions_vector[i].peer = peer; - - username_map[std::string(client_username)] = &sessions_vector[i]; - identity_map[client_identity] = &sessions_vector[i]; - - peer->data = &sessions_vector[i]; - - sessions::num_players += 1U; - - return &sessions_vector[i]; - } - } - - return nullptr; -} - -Session* sessions::find(std::string_view client_username) -{ - const auto it = username_map.find(std::string(client_username)); - if(it != username_map.cend()) { - return it->second; - } - else { - return nullptr; - } -} - -Session* sessions::find(std::uint16_t client_index) -{ - if(client_index < sessions_vector.size()) { - if(!sessions_vector[client_index].peer) { - return nullptr; - } - else { - return &sessions_vector[client_index]; - } - } - - return nullptr; -} - -Session* sessions::find(std::uint64_t client_identity) -{ - const auto it = identity_map.find(client_identity); - - if(it != identity_map.cend()) { - return it->second; - } - else { - return nullptr; - } -} - -Session* sessions::find(ENetPeer* peer) -{ - if(peer != nullptr) { - return reinterpret_cast<Session*>(peer->data); - } - else { - return nullptr; - } -} - -void sessions::destroy(Session* session) -{ - if(session) { - if(session->peer) { - // Make sure we don't leave a mark - session->peer->data = nullptr; - } - - if(session->dimension) { - session->dimension->entities.destroy(session->player_entity); - } - - username_map.erase(session->client_username); - identity_map.erase(session->client_identity); - - session->client_index = UINT16_MAX; - session->client_identity = UINT64_MAX; - session->client_username = std::string(); - session->player_entity = entt::null; - session->peer = nullptr; - - sessions::num_players -= 1U; - } -} - -void sessions::broadcast(const world::Dimension* dimension, ENetPacket* packet) -{ - for(const auto& session : sessions_vector) { - if(session.peer && (session.dimension == dimension)) { - enet_peer_send(session.peer, protocol::CHANNEL, packet); - } - } -} - -void sessions::broadcast(const world::Dimension* dimension, ENetPacket* packet, ENetPeer* except) -{ - for(const auto& session : sessions_vector) { - if(session.peer && (session.peer != except)) { - enet_peer_send(session.peer, protocol::CHANNEL, packet); - } - } -} - -void sessions::refresh_scoreboard(void) -{ - protocol::ScoreboardUpdate packet; - - for(std::size_t i = 0; i < sessions::max_players.get_value(); ++i) { - if(sessions_vector[i].peer) { - packet.names.push_back(sessions_vector[i].client_username); - } - } - - protocol::broadcast(globals::server_host, protocol::encode(packet)); -} +#include "server/pch.hh"
+
+#include "server/sessions.hh"
+
+#include "core/config/boolean.hh"
+#include "core/config/number.hh"
+
+#include "core/io/config_map.hh"
+
+#include "core/math/constexpr.hh"
+#include "core/math/crc64.hh"
+
+#include "core/utils/string.hh"
+
+#include "shared/entity/factory.hh"
+#include "shared/entity/head.hh"
+#include "shared/entity/player.hh"
+#include "shared/entity/transform.hh"
+#include "shared/entity/velocity.hh"
+
+#include "shared/world/chunk.hh"
+#include "shared/world/dimension.hh"
+#include "shared/world/item_registry.hh"
+#include "shared/world/voxel_registry.hh"
+
+#include "shared/coord.hh"
+#include "shared/protocol.hh"
+
+#include "server/game.hh"
+#include "server/globals.hh"
+#include "server/whitelist.hh"
+
+class DimensionListener final {
+public:
+ explicit DimensionListener(world::Dimension* dimension);
+ void on_destroy_entity(const entt::registry& registry, entt::entity entity);
+
+private:
+ world::Dimension* dimension;
+};
+
+config::Unsigned sessions::max_players(8U, 1U, 128U);
+unsigned int sessions::num_players = 0U;
+
+static emhash8::HashMap<std::string, Session*> username_map;
+static emhash8::HashMap<std::uint64_t, Session*> identity_map;
+static std::vector<DimensionListener> dimension_listeners;
+static std::vector<Session> sessions_vector;
+
+static void on_login_request_packet(const protocol::LoginRequest& packet)
+{
+ if(packet.version > protocol::VERSION) {
+ protocol::Disconnect response;
+ response.reason = "protocol.outdated_server";
+ protocol::send(packet.peer, protocol::encode(response));
+ return;
+ }
+
+ if(packet.version < protocol::VERSION) {
+ 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()) {
+ 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()) {
+ protocol::Disconnect response;
+ response.reason = "protocol.item_registry_checksum";
+ protocol::send(packet.peer, protocol::encode(response));
+ return;
+ }
+
+ // Don't assign new usernames and just kick the player if
+ // an another client using the same username is already connected
+ // and playing; since we have a whitelist, adding "(1)" isn't feasible anymore
+ if(username_map.contains(packet.username)) {
+ protocol::Disconnect response;
+ response.reason = "protocol.username_taken";
+ protocol::send(packet.peer, protocol::encode(response));
+ return;
+ }
+
+ if(whitelist::enabled.get_value()) {
+ if(!whitelist::contains(packet.username.c_str())) {
+ protocol::Disconnect response;
+ response.reason = "protocol.not_whitelisted";
+ protocol::send(packet.peer, protocol::encode(response));
+ return;
+ }
+
+ if(!whitelist::matches(packet.username.c_str(), packet.password_hash)) {
+ protocol::Disconnect response;
+ response.reason = "protocol.password_incorrect";
+ protocol::send(packet.peer, protocol::encode(response));
+ return;
+ }
+ }
+ else if(packet.password_hash != server_game::password_hash) {
+ protocol::Disconnect response;
+ response.reason = "protocol.password_incorrect";
+ protocol::send(packet.peer, protocol::encode(response));
+ return;
+ }
+
+ if(Session* session = sessions::create(packet.peer, packet.username.c_str())) {
+ protocol::LoginResponse response;
+ response.client_index = session->client_index;
+ response.client_identity = session->client_identity;
+ response.server_tickrate = globals::tickrate;
+ protocol::send(packet.peer, protocol::encode(response));
+
+ protocol::DimensionInfo dim_info;
+ dim_info.name = globals::spawn_dimension->get_name();
+ dim_info.gravity = globals::spawn_dimension->get_gravity();
+ protocol::send(packet.peer, protocol::encode(dim_info));
+
+ spdlog::info("sessions: {} [{}] logged in with client_index={} in {}", session->client_username, session->client_identity,
+ session->client_index, globals::spawn_dimension->get_name());
+
+ // FIXME: only send entities that are present within the current
+ // player's view bounding box; this also would mean we're not sending
+ // anything here and just straight up spawing the player and await them
+ // to receive all the chunks and entites they feel like requesting
+ for(auto entity : globals::spawn_dimension->entities.view<entt::entity>()) {
+ if(const auto head = globals::spawn_dimension->entities.try_get<entity::Head>(entity)) {
+ protocol::EntityHead head_packet;
+ head_packet.entity = entity;
+ head_packet.angles = head->angles;
+ protocol::send(session->peer, protocol::encode(head_packet));
+ }
+
+ if(const auto transform = globals::spawn_dimension->entities.try_get<entity::Transform>(entity)) {
+ protocol::EntityTransform transform_packet;
+ transform_packet.entity = entity;
+ transform_packet.angles = transform->angles;
+ transform_packet.chunk = transform->chunk;
+ transform_packet.local = transform->local;
+ protocol::send(session->peer, protocol::encode(transform_packet));
+ }
+
+ if(const auto velocity = globals::spawn_dimension->entities.try_get<entity::Velocity>(entity)) {
+ protocol::EntityVelocity velocity_packet;
+ velocity_packet.entity = entity;
+ velocity_packet.value = velocity->value;
+ protocol::send(session->peer, protocol::encode(velocity_packet));
+ }
+
+ if(globals::spawn_dimension->entities.all_of<entity::Player>(entity)) {
+ protocol::EntityPlayer player_packet;
+ player_packet.entity = entity;
+ protocol::send(session->peer, protocol::encode(player_packet));
+ }
+ }
+
+ session->dimension = globals::spawn_dimension;
+ session->player_entity = globals::spawn_dimension->entities.create();
+ entity::shared::create_player(globals::spawn_dimension, session->player_entity);
+
+ const auto& head = globals::spawn_dimension->entities.get<entity::Head>(session->player_entity);
+ const auto& transform = globals::spawn_dimension->entities.get<entity::Transform>(session->player_entity);
+ const auto& velocity = globals::spawn_dimension->entities.get<entity::Velocity>(session->player_entity);
+
+ protocol::EntityHead head_packet;
+ head_packet.entity = session->player_entity;
+ head_packet.angles = head.angles;
+
+ protocol::EntityTransform transform_packet;
+ transform_packet.entity = session->player_entity;
+ transform_packet.angles = transform.angles;
+ transform_packet.chunk = transform.chunk;
+ transform_packet.local = transform.local;
+
+ protocol::EntityVelocity velocity_packet;
+ velocity_packet.entity = session->player_entity;
+ velocity_packet.value = velocity.value;
+
+ protocol::EntityPlayer player_packet;
+ player_packet.entity = session->player_entity;
+
+ protocol::broadcast(globals::server_host, protocol::encode(head_packet));
+ protocol::broadcast(globals::server_host, protocol::encode(transform_packet));
+ protocol::broadcast(globals::server_host, protocol::encode(velocity_packet));
+ protocol::broadcast(globals::server_host, protocol::encode(player_packet));
+
+ protocol::SpawnPlayer spawn_packet;
+ spawn_packet.entity = session->player_entity;
+
+ // SpawnPlayer serves a different purpose compared to EntityPlayer
+ // The latter is used to construct entities (as in "attach a component")
+ // whilst the SpawnPlayer packet is used to notify client-side that the
+ // entity identifier in the packet is to be treated as the local player entity
+ protocol::send(session->peer, protocol::encode(spawn_packet));
+
+ protocol::ChatMessage message;
+ message.type = protocol::ChatMessage::PLAYER_JOIN;
+ message.sender = session->client_username;
+ message.message = std::string();
+
+ protocol::broadcast(globals::server_host, protocol::encode(message));
+
+ sessions::refresh_scoreboard();
+
+ return;
+ }
+
+ protocol::Disconnect response;
+ response.reason = "protocol.server_full";
+ protocol::send(packet.peer, protocol::encode(response));
+}
+
+static void on_disconnect_packet(const protocol::Disconnect& packet)
+{
+ if(Session* session = sessions::find(packet.peer)) {
+ protocol::ChatMessage message;
+ message.type = protocol::ChatMessage::PLAYER_LEAVE;
+ message.sender = session->client_username;
+ message.message = packet.reason;
+
+ protocol::broadcast(globals::server_host, protocol::encode(message), session->peer);
+
+ spdlog::info("{} disconnected ({})", session->client_username, packet.reason);
+
+ sessions::destroy(session);
+ sessions::refresh_scoreboard();
+ }
+}
+
+// NOTE: [sessions] is a good place for this since [receive]
+// handles entity data sent by players and [sessions] handles
+// everything else network related that is not player movement
+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.flags = 0U; // UNDONE
+ protocol::broadcast(globals::server_host, protocol::encode(packet));
+}
+
+DimensionListener::DimensionListener(world::Dimension* dimension)
+{
+ this->dimension = dimension;
+}
+
+void DimensionListener::on_destroy_entity(const entt::registry& registry, entt::entity entity)
+{
+ protocol::RemoveEntity packet;
+ packet.entity = entity;
+ sessions::broadcast(dimension, protocol::encode(packet));
+}
+
+void sessions::init(void)
+{
+ globals::server_config.add_value("sessions.max_players", sessions::max_players);
+
+ globals::dispatcher.sink<protocol::LoginRequest>().connect<&on_login_request_packet>();
+ globals::dispatcher.sink<protocol::Disconnect>().connect<&on_disconnect_packet>();
+
+ globals::dispatcher.sink<world::VoxelSetEvent>().connect<&on_voxel_set>();
+}
+
+void sessions::init_late(void)
+{
+ sessions::num_players = 0U;
+
+ username_map.clear();
+ identity_map.clear();
+ sessions_vector.resize(sessions::max_players.get_value(), Session());
+
+ for(unsigned int i = 0U; i < sessions::max_players.get_value(); ++i) {
+ sessions_vector[i].client_index = UINT16_MAX;
+ sessions_vector[i].client_identity = UINT64_MAX;
+ sessions_vector[i].client_username = std::string();
+ sessions_vector[i].player_entity = entt::null;
+ sessions_vector[i].peer = nullptr;
+ }
+}
+
+void sessions::init_post_universe(void)
+{
+ for(auto& dimension : globals::dimensions) {
+ dimension_listeners.push_back(DimensionListener(dimension.second));
+ dimension.second->entities.on_destroy<entt::entity>().connect<&DimensionListener::on_destroy_entity>(dimension_listeners.back());
+ }
+}
+
+void sessions::shutdown(void)
+{
+ username_map.clear();
+ identity_map.clear();
+ sessions_vector.clear();
+ dimension_listeners.clear();
+}
+
+Session* sessions::create(ENetPeer* peer, std::string_view client_username)
+{
+ for(unsigned int i = 0U; i < sessions::max_players.get_value(); ++i) {
+ if(!sessions_vector[i].peer) {
+ std::uint64_t client_identity = math::crc64(client_username.data(), client_username.size());
+
+ sessions_vector[i].client_index = i;
+ sessions_vector[i].client_identity = client_identity;
+ sessions_vector[i].client_username = client_username;
+ sessions_vector[i].player_entity = entt::null;
+ sessions_vector[i].peer = peer;
+
+ username_map[std::string(client_username)] = &sessions_vector[i];
+ identity_map[client_identity] = &sessions_vector[i];
+
+ peer->data = &sessions_vector[i];
+
+ sessions::num_players += 1U;
+
+ return &sessions_vector[i];
+ }
+ }
+
+ return nullptr;
+}
+
+Session* sessions::find(std::string_view client_username)
+{
+ const auto it = username_map.find(std::string(client_username));
+ if(it != username_map.cend()) {
+ return it->second;
+ }
+ else {
+ return nullptr;
+ }
+}
+
+Session* sessions::find(std::uint16_t client_index)
+{
+ if(client_index < sessions_vector.size()) {
+ if(!sessions_vector[client_index].peer) {
+ return nullptr;
+ }
+ else {
+ return &sessions_vector[client_index];
+ }
+ }
+
+ return nullptr;
+}
+
+Session* sessions::find(std::uint64_t client_identity)
+{
+ const auto it = identity_map.find(client_identity);
+
+ if(it != identity_map.cend()) {
+ return it->second;
+ }
+ else {
+ return nullptr;
+ }
+}
+
+Session* sessions::find(ENetPeer* peer)
+{
+ if(peer != nullptr) {
+ return reinterpret_cast<Session*>(peer->data);
+ }
+ else {
+ return nullptr;
+ }
+}
+
+void sessions::destroy(Session* session)
+{
+ if(session) {
+ if(session->peer) {
+ // Make sure we don't leave a mark
+ session->peer->data = nullptr;
+ }
+
+ if(session->dimension) {
+ session->dimension->entities.destroy(session->player_entity);
+ }
+
+ username_map.erase(session->client_username);
+ identity_map.erase(session->client_identity);
+
+ session->client_index = UINT16_MAX;
+ session->client_identity = UINT64_MAX;
+ session->client_username = std::string();
+ session->player_entity = entt::null;
+ session->peer = nullptr;
+
+ sessions::num_players -= 1U;
+ }
+}
+
+void sessions::broadcast(const world::Dimension* dimension, ENetPacket* packet)
+{
+ for(const auto& session : sessions_vector) {
+ if(session.peer && (session.dimension == dimension)) {
+ enet_peer_send(session.peer, protocol::CHANNEL, packet);
+ }
+ }
+}
+
+void sessions::broadcast(const world::Dimension* dimension, ENetPacket* packet, ENetPeer* except)
+{
+ for(const auto& session : sessions_vector) {
+ if(session.peer && (session.peer != except)) {
+ enet_peer_send(session.peer, protocol::CHANNEL, packet);
+ }
+ }
+}
+
+void sessions::refresh_scoreboard(void)
+{
+ protocol::ScoreboardUpdate packet;
+
+ for(std::size_t i = 0; i < sessions::max_players.get_value(); ++i) {
+ if(sessions_vector[i].peer) {
+ packet.names.push_back(sessions_vector[i].client_username);
+ }
+ }
+
+ protocol::broadcast(globals::server_host, protocol::encode(packet));
+}
diff --git a/game/server/sessions.hh b/game/server/sessions.hh index 656b76d..14d50e1 100644 --- a/game/server/sessions.hh +++ b/game/server/sessions.hh @@ -1,55 +1,55 @@ -#pragma once - -namespace world -{ -class Dimension; -} // namespace world - -namespace config -{ -class Unsigned; -} // namespace config - -struct Session final { - std::uint16_t client_index; - std::uint64_t client_identity; - std::string client_username; - entt::entity player_entity; - world::Dimension* dimension; - ENetPeer* peer; -}; - -namespace sessions -{ -extern config::Unsigned max_players; -extern unsigned int num_players; -} // namespace sessions - -namespace sessions -{ -void init(void); -void init_late(void); -void init_post_universe(void); -void shutdown(void); -} // namespace sessions - -namespace sessions -{ -Session* create(ENetPeer* peer, std::string_view client_username); -Session* find(std::string_view client_username); -Session* find(std::uint16_t client_index); -Session* find(std::uint64_t client_identity); -Session* find(ENetPeer* peer); -void destroy(Session* session); -} // namespace sessions - -namespace sessions -{ -void broadcast(const world::Dimension* dimension, ENetPacket* packet); -void broadcast(const world::Dimension* dimension, ENetPacket* packet, ENetPeer* except); -} // namespace sessions - -namespace sessions -{ -void refresh_scoreboard(void); -} // namespace sessions +#pragma once
+
+namespace world
+{
+class Dimension;
+} // namespace world
+
+namespace config
+{
+class Unsigned;
+} // namespace config
+
+struct Session final {
+ std::uint16_t client_index;
+ std::uint64_t client_identity;
+ std::string client_username;
+ entt::entity player_entity;
+ world::Dimension* dimension;
+ ENetPeer* peer;
+};
+
+namespace sessions
+{
+extern config::Unsigned max_players;
+extern unsigned int num_players;
+} // namespace sessions
+
+namespace sessions
+{
+void init(void);
+void init_late(void);
+void init_post_universe(void);
+void shutdown(void);
+} // namespace sessions
+
+namespace sessions
+{
+Session* create(ENetPeer* peer, std::string_view client_username);
+Session* find(std::string_view client_username);
+Session* find(std::uint16_t client_index);
+Session* find(std::uint64_t client_identity);
+Session* find(ENetPeer* peer);
+void destroy(Session* session);
+} // namespace sessions
+
+namespace sessions
+{
+void broadcast(const world::Dimension* dimension, ENetPacket* packet);
+void broadcast(const world::Dimension* dimension, ENetPacket* packet, ENetPeer* except);
+} // namespace sessions
+
+namespace sessions
+{
+void refresh_scoreboard(void);
+} // namespace sessions
diff --git a/game/server/status.cc b/game/server/status.cc index ed73b23..ba1d59d 100644 --- a/game/server/status.cc +++ b/game/server/status.cc @@ -1,26 +1,26 @@ -#include "server/pch.hh" - -#include "server/status.hh" - -#include "core/config/number.hh" - -#include "shared/protocol.hh" -#include "shared/splash.hh" - -#include "server/globals.hh" -#include "server/sessions.hh" - -static void on_status_request_packet(const protocol::StatusRequest& packet) -{ - protocol::StatusResponse response; - response.version = protocol::VERSION; - response.max_players = sessions::max_players.get_value(); - response.num_players = sessions::num_players; - response.motd = splash::get(); - protocol::send(packet.peer, protocol::encode(response)); -} - -void status::init(void) -{ - globals::dispatcher.sink<protocol::StatusRequest>().connect<&on_status_request_packet>(); -} +#include "server/pch.hh"
+
+#include "server/status.hh"
+
+#include "core/config/number.hh"
+
+#include "shared/protocol.hh"
+#include "shared/splash.hh"
+
+#include "server/globals.hh"
+#include "server/sessions.hh"
+
+static void on_status_request_packet(const protocol::StatusRequest& packet)
+{
+ protocol::StatusResponse response;
+ response.version = protocol::VERSION;
+ response.max_players = sessions::max_players.get_value();
+ response.num_players = sessions::num_players;
+ response.motd = splash::get();
+ protocol::send(packet.peer, protocol::encode(response));
+}
+
+void status::init(void)
+{
+ globals::dispatcher.sink<protocol::StatusRequest>().connect<&on_status_request_packet>();
+}
diff --git a/game/server/status.hh b/game/server/status.hh index 35370a0..d298827 100644 --- a/game/server/status.hh +++ b/game/server/status.hh @@ -1,6 +1,6 @@ -#pragma once - -namespace status -{ -void init(void); -} // namespace status +#pragma once
+
+namespace status
+{
+void init(void);
+} // namespace status
diff --git a/game/server/whitelist.cc b/game/server/whitelist.cc index 0807c19..053809e 100644 --- a/game/server/whitelist.cc +++ b/game/server/whitelist.cc @@ -1,101 +1,101 @@ -#include "server/pch.hh" - -#include "server/whitelist.hh" - -#include "core/config/boolean.hh" -#include "core/config/string.hh" - -#include "core/io/config_map.hh" - -#include "core/math/crc64.hh" - -#include "core/utils/string.hh" - -#include "server/game.hh" -#include "server/globals.hh" - -constexpr static std::string_view DEFAULT_FILENAME = "whitelist.txt"; -constexpr static char SEPARATOR_CHAR = ':'; - -config::Boolean whitelist::enabled(false); -config::String whitelist::filename(DEFAULT_FILENAME); - -static emhash8::HashMap<std::string, std::uint64_t> whitelist_map; - -void whitelist::init(void) -{ - globals::server_config.add_value("whitelist.enabled", whitelist::enabled); - globals::server_config.add_value("whitelist.filename", whitelist::filename); -} - -void whitelist::init_late(void) -{ - whitelist_map.clear(); - - if(!whitelist::enabled.get_value()) { - // Not enabled, shouldn't - // even bother with parsing - // the whitelist file - return; - } - - if(utils::is_whitespace(whitelist::filename.get_value())) { - spdlog::warn("whitelist: enabled but filename is empty, using default ({})", DEFAULT_FILENAME); - whitelist::filename.set(DEFAULT_FILENAME); - } - - PHYSFS_File* file = PHYSFS_openRead(whitelist::filename.c_str()); - - if(file == nullptr) { - spdlog::warn("whitelist: {}: {}", whitelist::filename.get(), PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - whitelist::enabled.set_value(false); - return; - } - - auto source = std::string(PHYSFS_fileLength(file), char(0x00)); - PHYSFS_readBytes(file, source.data(), source.size()); - PHYSFS_close(file); - - std::istringstream stream(source); - std::string line; - - while(std::getline(stream, line)) { - const auto location = line.find_last_of(SEPARATOR_CHAR); - - if(location == std::string::npos) { - // Entries that don't define a password field default - // to the global server password; this allows easier adding - // of guest accounts which can later be edited to use a better password - whitelist_map[line] = server_game::password_hash; - } - else { - const auto username = line.substr(0, location); - const auto password = line.substr(location + 1); - whitelist_map[username] = math::crc64(password); - } - } - - PHYSFS_close(file); -} - -void whitelist::shutdown(void) -{ - // UNDONE: implement saving -} - -bool whitelist::contains(std::string_view username) -{ - return whitelist_map.contains(std::string(username)); -} - -bool whitelist::matches(std::string_view username, std::uint64_t password_hash) -{ - const auto it = whitelist_map.find(std::string(username)); - - if(it == whitelist_map.cend()) { - // Not whitelisted, no match - return false; - } - - return it->second == password_hash; -} +#include "server/pch.hh"
+
+#include "server/whitelist.hh"
+
+#include "core/config/boolean.hh"
+#include "core/config/string.hh"
+
+#include "core/io/config_map.hh"
+
+#include "core/math/crc64.hh"
+
+#include "core/utils/string.hh"
+
+#include "server/game.hh"
+#include "server/globals.hh"
+
+constexpr static std::string_view DEFAULT_FILENAME = "whitelist.txt";
+constexpr static char SEPARATOR_CHAR = ':';
+
+config::Boolean whitelist::enabled(false);
+config::String whitelist::filename(DEFAULT_FILENAME);
+
+static emhash8::HashMap<std::string, std::uint64_t> whitelist_map;
+
+void whitelist::init(void)
+{
+ globals::server_config.add_value("whitelist.enabled", whitelist::enabled);
+ globals::server_config.add_value("whitelist.filename", whitelist::filename);
+}
+
+void whitelist::init_late(void)
+{
+ whitelist_map.clear();
+
+ if(!whitelist::enabled.get_value()) {
+ // Not enabled, shouldn't
+ // even bother with parsing
+ // the whitelist file
+ return;
+ }
+
+ if(utils::is_whitespace(whitelist::filename.get_value())) {
+ spdlog::warn("whitelist: enabled but filename is empty, using default ({})", DEFAULT_FILENAME);
+ whitelist::filename.set(DEFAULT_FILENAME);
+ }
+
+ PHYSFS_File* file = PHYSFS_openRead(whitelist::filename.c_str());
+
+ if(file == nullptr) {
+ spdlog::warn("whitelist: {}: {}", whitelist::filename.get(), PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ whitelist::enabled.set_value(false);
+ return;
+ }
+
+ auto source = std::string(PHYSFS_fileLength(file), char(0x00));
+ PHYSFS_readBytes(file, source.data(), source.size());
+ PHYSFS_close(file);
+
+ std::istringstream stream(source);
+ std::string line;
+
+ while(std::getline(stream, line)) {
+ const auto location = line.find_last_of(SEPARATOR_CHAR);
+
+ if(location == std::string::npos) {
+ // Entries that don't define a password field default
+ // to the global server password; this allows easier adding
+ // of guest accounts which can later be edited to use a better password
+ whitelist_map[line] = server_game::password_hash;
+ }
+ else {
+ const auto username = line.substr(0, location);
+ const auto password = line.substr(location + 1);
+ whitelist_map[username] = math::crc64(password);
+ }
+ }
+
+ PHYSFS_close(file);
+}
+
+void whitelist::shutdown(void)
+{
+ // UNDONE: implement saving
+}
+
+bool whitelist::contains(std::string_view username)
+{
+ return whitelist_map.contains(std::string(username));
+}
+
+bool whitelist::matches(std::string_view username, std::uint64_t password_hash)
+{
+ const auto it = whitelist_map.find(std::string(username));
+
+ if(it == whitelist_map.cend()) {
+ // Not whitelisted, no match
+ return false;
+ }
+
+ return it->second == password_hash;
+}
diff --git a/game/server/whitelist.hh b/game/server/whitelist.hh index 4695d16..990b15a 100644 --- a/game/server/whitelist.hh +++ b/game/server/whitelist.hh @@ -1,26 +1,26 @@ -#pragma once - -namespace config -{ -class Boolean; -class String; -} // namespace config - -namespace whitelist -{ -extern config::Boolean enabled; -extern config::String filename; -} // namespace whitelist - -namespace whitelist -{ -void init(void); -void init_late(void); -void shutdown(void); -} // namespace whitelist - -namespace whitelist -{ -bool contains(std::string_view username); -bool matches(std::string_view username, std::uint64_t password_hash); -} // namespace whitelist +#pragma once
+
+namespace config
+{
+class Boolean;
+class String;
+} // namespace config
+
+namespace whitelist
+{
+extern config::Boolean enabled;
+extern config::String filename;
+} // namespace whitelist
+
+namespace whitelist
+{
+void init(void);
+void init_late(void);
+void shutdown(void);
+} // namespace whitelist
+
+namespace whitelist
+{
+bool contains(std::string_view username);
+bool matches(std::string_view username, std::uint64_t password_hash);
+} // namespace whitelist
diff --git a/game/server/world/inhabited.hh b/game/server/world/inhabited.hh index 57008e9..5eba9ef 100644 --- a/game/server/world/inhabited.hh +++ b/game/server/world/inhabited.hh @@ -1,6 +1,6 @@ -#pragma once - -namespace world -{ -struct Inhabited final {}; -} // namespace world +#pragma once
+
+namespace world
+{
+struct Inhabited final {};
+} // namespace world
diff --git a/game/server/world/overworld.cc b/game/server/world/overworld.cc index fca3edf..eb801de 100644 --- a/game/server/world/overworld.cc +++ b/game/server/world/overworld.cc @@ -1,383 +1,383 @@ -#include "server/pch.hh" - -#include "server/world/overworld.hh" - -#include "core/math/vectors.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) -{ - // Ensure the tree height is too small - height = math::max<unsigned int>(height, 4U); - - // Put down a single piece of dirt - feature.push_back({ voxel_pos(0, -1, 0), game_voxels::dirt, true }); - - // Generate tree stem - for(unsigned int i = 0; i < height; ++i) { - feature.push_back({ voxel_pos(0, i, 0), log_voxel, true }); - } - - auto leaves_start = height - 3U; - auto leaves_thick_end = height - 2U; - auto leaves_thin_end = height - 1U; - - // Generate the thin 3x3 layer of leaves that - // starts from leaves_start and ends at leaves_thin_end - for(unsigned int i = leaves_start; i <= leaves_thin_end; ++i) { - feature.push_back({ local_pos(-1, i, -1), leaves_voxel, false }); - feature.push_back({ local_pos(-1, i, +0), leaves_voxel, false }); - feature.push_back({ local_pos(-1, i, +1), leaves_voxel, false }); - feature.push_back({ local_pos(+0, i, -1), leaves_voxel, false }); - feature.push_back({ local_pos(+0, i, +1), leaves_voxel, false }); - feature.push_back({ local_pos(+1, i, -1), leaves_voxel, false }); - feature.push_back({ local_pos(+1, i, +0), leaves_voxel, false }); - feature.push_back({ local_pos(+1, i, +1), leaves_voxel, false }); - } - - // Generate the tree cap; a 3x3 patch of leaves - // that is slapped right on top of the thin 3x3 layer - feature.push_back({ local_pos(-1, height, +0), leaves_voxel, false }); - feature.push_back({ local_pos(+0, height, -1), leaves_voxel, false }); - feature.push_back({ local_pos(+0, height, +0), leaves_voxel, false }); - feature.push_back({ local_pos(+0, height, +1), leaves_voxel, false }); - feature.push_back({ local_pos(+1, height, +0), leaves_voxel, false }); - - // Generate the thin 5x5 layer of leaves that - // starts from leaves_start and ends at leaves_thin_end - for(unsigned int i = leaves_start; i <= leaves_thick_end; ++i) { - feature.push_back({ local_pos(-1, i, -2), leaves_voxel, false }); - feature.push_back({ local_pos(-1, i, +2), leaves_voxel, false }); - feature.push_back({ local_pos(-2, i, -1), leaves_voxel, false }); - feature.push_back({ local_pos(-2, i, -2), leaves_voxel, false }); - feature.push_back({ local_pos(-2, i, +0), leaves_voxel, false }); - feature.push_back({ local_pos(-2, i, +1), leaves_voxel, false }); - feature.push_back({ local_pos(-2, i, +2), leaves_voxel, false }); - feature.push_back({ local_pos(+0, i, -2), leaves_voxel, false }); - feature.push_back({ local_pos(+0, i, +2), leaves_voxel, false }); - feature.push_back({ local_pos(+1, i, -2), leaves_voxel, false }); - feature.push_back({ local_pos(+1, i, +2), leaves_voxel, false }); - feature.push_back({ local_pos(+2, i, -1), leaves_voxel, false }); - feature.push_back({ local_pos(+2, i, -2), leaves_voxel, false }); - feature.push_back({ local_pos(+2, i, +0), leaves_voxel, false }); - feature.push_back({ local_pos(+2, i, +1), leaves_voxel, false }); - feature.push_back({ local_pos(+2, i, +2), leaves_voxel, false }); - } -} - -world::Overworld::Overworld(std::string_view name) : Dimension(name, -30.0f) -{ - m_bottommost_chunk.set_limits(-64, -4); - m_terrain_variation.set_limits(16, 256); - - compute_tree_feature(4U, m_feat_tree[0], game_voxels::oak_log, game_voxels::oak_leaves); - compute_tree_feature(5U, m_feat_tree[1], game_voxels::oak_log, game_voxels::oak_leaves); - compute_tree_feature(6U, m_feat_tree[2], game_voxels::oak_log, game_voxels::oak_leaves); - compute_tree_feature(8U, m_feat_tree[3], game_voxels::oak_log, game_voxels::oak_leaves); -} - -void world::Overworld::init(io::ConfigMap& config) -{ - m_terrain_variation.set_value(64); - m_bottommost_chunk.set_value(-4); - - config.add_value("overworld.terrain_variation", m_terrain_variation); - config.add_value("overworld.bottommost_chunk", m_bottommost_chunk); -} - -void world::Overworld::init_late(std::uint64_t global_seed) -{ - std::mt19937 twister(global_seed); - - m_fnl_variation = fnlCreateState(); - m_fnl_variation.seed = static_cast<int>(twister()); - m_fnl_variation.noise_type = FNL_NOISE_PERLIN; - m_fnl_variation.frequency = 0.001f; - - m_fnl_terrain = fnlCreateState(); - m_fnl_terrain.seed = static_cast<int>(twister()); - m_fnl_terrain.noise_type = FNL_NOISE_OPENSIMPLEX2S; - m_fnl_terrain.fractal_type = FNL_FRACTAL_FBM; - m_fnl_terrain.frequency = 0.005f; - m_fnl_terrain.octaves = 4; - - m_fnl_caves_a = fnlCreateState(); - m_fnl_caves_a.seed = static_cast<int>(twister()); - m_fnl_caves_a.noise_type = FNL_NOISE_PERLIN; - m_fnl_caves_a.fractal_type = FNL_FRACTAL_RIDGED; - m_fnl_caves_a.frequency = 0.0125f; - m_fnl_caves_a.octaves = 1; - - m_fnl_caves_b = fnlCreateState(); - m_fnl_caves_b.seed = static_cast<int>(twister()); - m_fnl_caves_b.noise_type = FNL_NOISE_OPENSIMPLEX2S; - m_fnl_caves_b.fractal_type = FNL_FRACTAL_RIDGED; - m_fnl_caves_b.frequency = 0.0125f; - m_fnl_caves_b.octaves = 1; - - m_fnl_nvdi = fnlCreateState(); - m_fnl_nvdi.seed = static_cast<int>(twister()); - m_fnl_nvdi.noise_type = FNL_NOISE_OPENSIMPLEX2S; - m_fnl_nvdi.frequency = 1.0f; - - m_metamap.clear(); -} - -bool world::Overworld::generate(const chunk_pos& cpos, VoxelStorage& voxels) -{ - if(cpos.y <= m_bottommost_chunk.get_value()) { - // If the player asks the generator - // to generate a lot of stuff below - // the surface, it will happily chew - // through all the server threads - return false; - } - - voxels.fill(NULL_VOXEL_ID); - - m_mutex.lock(); - generate_terrain(cpos, voxels); - m_mutex.unlock(); - - m_mutex.lock(); - generate_surface(cpos, voxels); - m_mutex.unlock(); - - m_mutex.lock(); - generate_caves(cpos, voxels); - m_mutex.unlock(); - - m_mutex.lock(); - generate_features(cpos, voxels); - m_mutex.unlock(); - - return true; -} - -bool world::Overworld::is_inside_cave(const voxel_pos& vpos) -{ - auto noise_a = fnlGetNoise3D(&m_fnl_caves_a, vpos.x, vpos.y * 2.0f, vpos.z); - auto noise_b = fnlGetNoise3D(&m_fnl_caves_b, vpos.x, vpos.y * 2.0f, vpos.z); - return (noise_a > 0.95f) && (noise_b > 0.85f); -} - -bool world::Overworld::is_inside_terrain(const voxel_pos& vpos) -{ - auto variation_noise = fnlGetNoise3D(&m_fnl_terrain, vpos.x, vpos.y, vpos.z); - auto variation = m_terrain_variation.get_value() * (1.0f - (variation_noise * variation_noise)); - auto noise = variation * fnlGetNoise3D(&m_fnl_terrain, vpos.x, vpos.y, vpos.z) - vpos.y; - return noise > 0.0f; -} - -const world::Overworld_Metadata& world::Overworld::get_or_create_metadata(const chunk_pos_xz& cpos) -{ - auto it = m_metamap.find(cpos); - - if(it != m_metamap.cend()) { - // Metadata is present - return it->second; - } - - auto& metadata = m_metamap.insert_or_assign(cpos, Overworld_Metadata()).first->second; - metadata.entropy.fill(std::numeric_limits<std::uint64_t>::max()); - metadata.heightmap.fill(std::numeric_limits<voxel_pos::value_type>::min()); - - auto twister = std::mt19937_64(std::hash<chunk_pos_xz>()(cpos)); - auto variation = m_terrain_variation.get_value(); - - // Generator might need some randomness - // that depends on 2D coordinates, so we - // generate this entropy ahead of time - for(int i = 0; i < CHUNK_AREA; ++i) { - metadata.entropy[i] = twister(); - } - - // Generate speculative heightmap; - // Cave generation might have issues with placing - // surface features such as trees but I genuinely don't give a shit - for(int lx = 0; lx < CHUNK_SIZE; lx += 1) { - for(int lz = 0; lz < CHUNK_SIZE; lz += 1) { - auto hdx = static_cast<std::size_t>(lx + lz * CHUNK_SIZE); - auto vpos = coord::to_voxel(chunk_pos(cpos.x, 0, cpos.y), local_pos(lx, 0, lz)); - - for(vpos.y = variation; vpos.y >= -variation; vpos.y -= 1) { - if(is_inside_terrain(vpos)) { - metadata.heightmap[hdx] = vpos.y; - break; - } - } - } - } - - auto nvdi_value = 0.5f + 0.5f * fnlGetNoise2D(&m_fnl_nvdi, cpos.x, cpos.y); - auto tree_density = (nvdi_value >= 0.33f) ? math::floor<unsigned int>(nvdi_value * 4.0f) : 0U; - - for(unsigned int i = 0U; i < tree_density; ++i) { - auto lpos = local_pos((twister() % CHUNK_SIZE), (twister() % OW_NUM_TREES), (twister() % CHUNK_SIZE)); - auto is_unique = true; - - for(const auto& check_lpos : metadata.trees) { - if(math::distance2(check_lpos, lpos) <= 9) { - is_unique = false; - break; - } - } - - if(is_unique) { - metadata.trees.push_back(lpos); - } - } - - return metadata; -} - -void world::Overworld::generate_terrain(const chunk_pos& cpos, VoxelStorage& voxels) -{ - auto& metadata = get_or_create_metadata(chunk_pos_xz(cpos.x, cpos.z)); - auto variation = m_terrain_variation.get_value(); - - for(unsigned long i = 0; i < CHUNK_VOLUME; ++i) { - auto lpos = coord::to_local(i); - auto vpos = coord::to_voxel(cpos, lpos); - - if(vpos.y > variation) { - voxels[i] = NULL_VOXEL_ID; - continue; - } - - if(vpos.y < -variation) { - voxels[i] = game_voxels::stone; - continue; - } - - if(is_inside_terrain(vpos)) { - voxels[i] = game_voxels::stone; - continue; - } - } -} - -void world::Overworld::generate_surface(const chunk_pos& cpos, VoxelStorage& voxels) -{ - auto& metadata = get_or_create_metadata(chunk_pos_xz(cpos.x, cpos.z)); - auto variation = m_terrain_variation.get_value(); - - for(unsigned long i = 0; i < CHUNK_VOLUME; ++i) { - auto lpos = coord::to_local(i); - auto vpos = coord::to_voxel(cpos, lpos); - auto hdx = static_cast<std::size_t>(lpos.x + lpos.z * CHUNK_SIZE); - - if((vpos.y > variation) || (vpos.y < -variation)) { - // Speculative optimization - continue; - } - - if(voxels[i] == NULL_VOXEL_ID) { - // Surface voxel checks only apply for solid voxels; - // it's kind of obvious you can't replace air with grass - continue; - } - - unsigned int depth = 0U; - - for(unsigned int dy = 0U; dy < 5U; dy += 1U) { - auto d_lpos = local_pos(lpos.x, lpos.y + dy + 1, lpos.z); - auto d_vpos = coord::to_voxel(cpos, d_lpos); - auto d_index = coord::to_index(d_lpos); - - if(d_lpos.y >= CHUNK_SIZE) { - if(!is_inside_terrain(d_vpos)) { - break; - } - - depth += 1U; - } - else { - if(voxels[d_index] == NULL_VOXEL_ID) { - break; - } - - depth += 1U; - } - } - - if(depth < 5U) { - if(depth == 0U) { - voxels[i] = game_voxels::grass; - } - else { - voxels[i] = game_voxels::dirt; - } - } - } -} - -void world::Overworld::generate_caves(const chunk_pos& cpos, VoxelStorage& voxels) -{ - auto& metadata = get_or_create_metadata(chunk_pos_xz(cpos.x, cpos.z)); - auto variation = m_terrain_variation.get_value(); - - for(unsigned long i = 0U; i < CHUNK_VOLUME; ++i) { - auto lpos = coord::to_local(i); - auto vpos = coord::to_voxel(cpos, lpos); - - if(vpos.y > variation) { - // Speculative optimization - there's no solid - // terrain above variation to carve caves out from - continue; - } - - if(is_inside_cave(vpos)) { - voxels[i] = NULL_VOXEL_ID; - continue; - } - } -} - -void world::Overworld::generate_features(const chunk_pos& cpos, VoxelStorage& voxels) -{ - const chunk_pos_xz tree_chunks[] = { - chunk_pos_xz(cpos.x - 0, cpos.z - 1), - chunk_pos_xz(cpos.x - 1, cpos.z - 1), - chunk_pos_xz(cpos.x - 1, cpos.z + 0), - chunk_pos_xz(cpos.x - 1, cpos.z + 1), - chunk_pos_xz(cpos.x + 0, cpos.z + 0), - chunk_pos_xz(cpos.x + 0, cpos.z + 1), - chunk_pos_xz(cpos.x + 1, cpos.z - 1), - chunk_pos_xz(cpos.x + 1, cpos.z + 0), - chunk_pos_xz(cpos.x + 1, cpos.z + 1), - }; - - for(unsigned int i = 0U; i < math::array_size(tree_chunks); ++i) { - const auto& cpos_xz = tree_chunks[i]; - const auto& metadata = get_or_create_metadata(cpos_xz); - - for(const auto& tree_info : metadata.trees) { - auto hdx = static_cast<std::size_t>(tree_info.x + tree_info.z * CHUNK_SIZE); - auto height = metadata.heightmap[hdx]; - - if(height == std::numeric_limits<voxel_pos::value_type>::min()) { - // What happened? Cave happened - continue; - } - - auto cpos_xyz = chunk_pos(cpos_xz.x, 0, cpos_xz.y); - auto lpos_xyz = local_pos(tree_info.x, 0, tree_info.z); - - auto vpos = coord::to_voxel(cpos_xyz, lpos_xyz); - vpos.y = height; - - if(is_inside_cave(vpos)) { - // Cave is in the way - continue; - } - - m_feat_tree[tree_info.y].place(vpos + DIR_UP<voxel_pos::value_type>, cpos, voxels); - } - } -} +#include "server/pch.hh"
+
+#include "server/world/overworld.hh"
+
+#include "core/math/vectors.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)
+{
+ // Ensure the tree height is too small
+ height = math::max<unsigned int>(height, 4U);
+
+ // Put down a single piece of dirt
+ feature.push_back({ voxel_pos(0, -1, 0), game_voxels::dirt, true });
+
+ // Generate tree stem
+ for(unsigned int i = 0; i < height; ++i) {
+ feature.push_back({ voxel_pos(0, i, 0), log_voxel, true });
+ }
+
+ auto leaves_start = height - 3U;
+ auto leaves_thick_end = height - 2U;
+ auto leaves_thin_end = height - 1U;
+
+ // Generate the thin 3x3 layer of leaves that
+ // starts from leaves_start and ends at leaves_thin_end
+ for(unsigned int i = leaves_start; i <= leaves_thin_end; ++i) {
+ feature.push_back({ local_pos(-1, i, -1), leaves_voxel, false });
+ feature.push_back({ local_pos(-1, i, +0), leaves_voxel, false });
+ feature.push_back({ local_pos(-1, i, +1), leaves_voxel, false });
+ feature.push_back({ local_pos(+0, i, -1), leaves_voxel, false });
+ feature.push_back({ local_pos(+0, i, +1), leaves_voxel, false });
+ feature.push_back({ local_pos(+1, i, -1), leaves_voxel, false });
+ feature.push_back({ local_pos(+1, i, +0), leaves_voxel, false });
+ feature.push_back({ local_pos(+1, i, +1), leaves_voxel, false });
+ }
+
+ // Generate the tree cap; a 3x3 patch of leaves
+ // that is slapped right on top of the thin 3x3 layer
+ feature.push_back({ local_pos(-1, height, +0), leaves_voxel, false });
+ feature.push_back({ local_pos(+0, height, -1), leaves_voxel, false });
+ feature.push_back({ local_pos(+0, height, +0), leaves_voxel, false });
+ feature.push_back({ local_pos(+0, height, +1), leaves_voxel, false });
+ feature.push_back({ local_pos(+1, height, +0), leaves_voxel, false });
+
+ // Generate the thin 5x5 layer of leaves that
+ // starts from leaves_start and ends at leaves_thin_end
+ for(unsigned int i = leaves_start; i <= leaves_thick_end; ++i) {
+ feature.push_back({ local_pos(-1, i, -2), leaves_voxel, false });
+ feature.push_back({ local_pos(-1, i, +2), leaves_voxel, false });
+ feature.push_back({ local_pos(-2, i, -1), leaves_voxel, false });
+ feature.push_back({ local_pos(-2, i, -2), leaves_voxel, false });
+ feature.push_back({ local_pos(-2, i, +0), leaves_voxel, false });
+ feature.push_back({ local_pos(-2, i, +1), leaves_voxel, false });
+ feature.push_back({ local_pos(-2, i, +2), leaves_voxel, false });
+ feature.push_back({ local_pos(+0, i, -2), leaves_voxel, false });
+ feature.push_back({ local_pos(+0, i, +2), leaves_voxel, false });
+ feature.push_back({ local_pos(+1, i, -2), leaves_voxel, false });
+ feature.push_back({ local_pos(+1, i, +2), leaves_voxel, false });
+ feature.push_back({ local_pos(+2, i, -1), leaves_voxel, false });
+ feature.push_back({ local_pos(+2, i, -2), leaves_voxel, false });
+ feature.push_back({ local_pos(+2, i, +0), leaves_voxel, false });
+ feature.push_back({ local_pos(+2, i, +1), leaves_voxel, false });
+ feature.push_back({ local_pos(+2, i, +2), leaves_voxel, false });
+ }
+}
+
+world::Overworld::Overworld(std::string_view name) : Dimension(name, -30.0f)
+{
+ m_bottommost_chunk.set_limits(-64, -4);
+ m_terrain_variation.set_limits(16, 256);
+
+ compute_tree_feature(4U, m_feat_tree[0], game_voxels::oak_log, game_voxels::oak_leaves);
+ compute_tree_feature(5U, m_feat_tree[1], game_voxels::oak_log, game_voxels::oak_leaves);
+ compute_tree_feature(6U, m_feat_tree[2], game_voxels::oak_log, game_voxels::oak_leaves);
+ compute_tree_feature(8U, m_feat_tree[3], game_voxels::oak_log, game_voxels::oak_leaves);
+}
+
+void world::Overworld::init(io::ConfigMap& config)
+{
+ m_terrain_variation.set_value(64);
+ m_bottommost_chunk.set_value(-4);
+
+ config.add_value("overworld.terrain_variation", m_terrain_variation);
+ config.add_value("overworld.bottommost_chunk", m_bottommost_chunk);
+}
+
+void world::Overworld::init_late(std::uint64_t global_seed)
+{
+ std::mt19937 twister(global_seed);
+
+ m_fnl_variation = fnlCreateState();
+ m_fnl_variation.seed = static_cast<int>(twister());
+ m_fnl_variation.noise_type = FNL_NOISE_PERLIN;
+ m_fnl_variation.frequency = 0.001f;
+
+ m_fnl_terrain = fnlCreateState();
+ m_fnl_terrain.seed = static_cast<int>(twister());
+ m_fnl_terrain.noise_type = FNL_NOISE_OPENSIMPLEX2S;
+ m_fnl_terrain.fractal_type = FNL_FRACTAL_FBM;
+ m_fnl_terrain.frequency = 0.005f;
+ m_fnl_terrain.octaves = 4;
+
+ m_fnl_caves_a = fnlCreateState();
+ m_fnl_caves_a.seed = static_cast<int>(twister());
+ m_fnl_caves_a.noise_type = FNL_NOISE_PERLIN;
+ m_fnl_caves_a.fractal_type = FNL_FRACTAL_RIDGED;
+ m_fnl_caves_a.frequency = 0.0125f;
+ m_fnl_caves_a.octaves = 1;
+
+ m_fnl_caves_b = fnlCreateState();
+ m_fnl_caves_b.seed = static_cast<int>(twister());
+ m_fnl_caves_b.noise_type = FNL_NOISE_OPENSIMPLEX2S;
+ m_fnl_caves_b.fractal_type = FNL_FRACTAL_RIDGED;
+ m_fnl_caves_b.frequency = 0.0125f;
+ m_fnl_caves_b.octaves = 1;
+
+ m_fnl_nvdi = fnlCreateState();
+ m_fnl_nvdi.seed = static_cast<int>(twister());
+ m_fnl_nvdi.noise_type = FNL_NOISE_OPENSIMPLEX2S;
+ m_fnl_nvdi.frequency = 1.0f;
+
+ m_metamap.clear();
+}
+
+bool world::Overworld::generate(const chunk_pos& cpos, VoxelStorage& voxels)
+{
+ if(cpos.y <= m_bottommost_chunk.get_value()) {
+ // If the player asks the generator
+ // to generate a lot of stuff below
+ // the surface, it will happily chew
+ // through all the server threads
+ return false;
+ }
+
+ voxels.fill(NULL_VOXEL_ID);
+
+ m_mutex.lock();
+ generate_terrain(cpos, voxels);
+ m_mutex.unlock();
+
+ m_mutex.lock();
+ generate_surface(cpos, voxels);
+ m_mutex.unlock();
+
+ m_mutex.lock();
+ generate_caves(cpos, voxels);
+ m_mutex.unlock();
+
+ m_mutex.lock();
+ generate_features(cpos, voxels);
+ m_mutex.unlock();
+
+ return true;
+}
+
+bool world::Overworld::is_inside_cave(const voxel_pos& vpos)
+{
+ auto noise_a = fnlGetNoise3D(&m_fnl_caves_a, vpos.x, vpos.y * 2.0f, vpos.z);
+ auto noise_b = fnlGetNoise3D(&m_fnl_caves_b, vpos.x, vpos.y * 2.0f, vpos.z);
+ return (noise_a > 0.95f) && (noise_b > 0.85f);
+}
+
+bool world::Overworld::is_inside_terrain(const voxel_pos& vpos)
+{
+ auto variation_noise = fnlGetNoise3D(&m_fnl_terrain, vpos.x, vpos.y, vpos.z);
+ auto variation = m_terrain_variation.get_value() * (1.0f - (variation_noise * variation_noise));
+ auto noise = variation * fnlGetNoise3D(&m_fnl_terrain, vpos.x, vpos.y, vpos.z) - vpos.y;
+ return noise > 0.0f;
+}
+
+const world::Overworld_Metadata& world::Overworld::get_or_create_metadata(const chunk_pos_xz& cpos)
+{
+ auto it = m_metamap.find(cpos);
+
+ if(it != m_metamap.cend()) {
+ // Metadata is present
+ return it->second;
+ }
+
+ auto& metadata = m_metamap.insert_or_assign(cpos, Overworld_Metadata()).first->second;
+ metadata.entropy.fill(std::numeric_limits<std::uint64_t>::max());
+ metadata.heightmap.fill(std::numeric_limits<voxel_pos::value_type>::min());
+
+ auto twister = std::mt19937_64(std::hash<chunk_pos_xz>()(cpos));
+ auto variation = m_terrain_variation.get_value();
+
+ // Generator might need some randomness
+ // that depends on 2D coordinates, so we
+ // generate this entropy ahead of time
+ for(int i = 0; i < CHUNK_AREA; ++i) {
+ metadata.entropy[i] = twister();
+ }
+
+ // Generate speculative heightmap;
+ // Cave generation might have issues with placing
+ // surface features such as trees but I genuinely don't give a shit
+ for(int lx = 0; lx < CHUNK_SIZE; lx += 1) {
+ for(int lz = 0; lz < CHUNK_SIZE; lz += 1) {
+ auto hdx = static_cast<std::size_t>(lx + lz * CHUNK_SIZE);
+ auto vpos = coord::to_voxel(chunk_pos(cpos.x, 0, cpos.y), local_pos(lx, 0, lz));
+
+ for(vpos.y = variation; vpos.y >= -variation; vpos.y -= 1) {
+ if(is_inside_terrain(vpos)) {
+ metadata.heightmap[hdx] = vpos.y;
+ break;
+ }
+ }
+ }
+ }
+
+ auto nvdi_value = 0.5f + 0.5f * fnlGetNoise2D(&m_fnl_nvdi, cpos.x, cpos.y);
+ auto tree_density = (nvdi_value >= 0.33f) ? math::floor<unsigned int>(nvdi_value * 4.0f) : 0U;
+
+ for(unsigned int i = 0U; i < tree_density; ++i) {
+ auto lpos = local_pos((twister() % CHUNK_SIZE), (twister() % OW_NUM_TREES), (twister() % CHUNK_SIZE));
+ auto is_unique = true;
+
+ for(const auto& check_lpos : metadata.trees) {
+ if(math::distance2(check_lpos, lpos) <= 9) {
+ is_unique = false;
+ break;
+ }
+ }
+
+ if(is_unique) {
+ metadata.trees.push_back(lpos);
+ }
+ }
+
+ return metadata;
+}
+
+void world::Overworld::generate_terrain(const chunk_pos& cpos, VoxelStorage& voxels)
+{
+ auto& metadata = get_or_create_metadata(chunk_pos_xz(cpos.x, cpos.z));
+ auto variation = m_terrain_variation.get_value();
+
+ for(unsigned long i = 0; i < CHUNK_VOLUME; ++i) {
+ auto lpos = coord::to_local(i);
+ auto vpos = coord::to_voxel(cpos, lpos);
+
+ if(vpos.y > variation) {
+ voxels[i] = NULL_VOXEL_ID;
+ continue;
+ }
+
+ if(vpos.y < -variation) {
+ voxels[i] = game_voxels::stone;
+ continue;
+ }
+
+ if(is_inside_terrain(vpos)) {
+ voxels[i] = game_voxels::stone;
+ continue;
+ }
+ }
+}
+
+void world::Overworld::generate_surface(const chunk_pos& cpos, VoxelStorage& voxels)
+{
+ auto& metadata = get_or_create_metadata(chunk_pos_xz(cpos.x, cpos.z));
+ auto variation = m_terrain_variation.get_value();
+
+ for(unsigned long i = 0; i < CHUNK_VOLUME; ++i) {
+ auto lpos = coord::to_local(i);
+ auto vpos = coord::to_voxel(cpos, lpos);
+ auto hdx = static_cast<std::size_t>(lpos.x + lpos.z * CHUNK_SIZE);
+
+ if((vpos.y > variation) || (vpos.y < -variation)) {
+ // Speculative optimization
+ continue;
+ }
+
+ if(voxels[i] == NULL_VOXEL_ID) {
+ // Surface voxel checks only apply for solid voxels;
+ // it's kind of obvious you can't replace air with grass
+ continue;
+ }
+
+ unsigned int depth = 0U;
+
+ for(unsigned int dy = 0U; dy < 5U; dy += 1U) {
+ auto d_lpos = local_pos(lpos.x, lpos.y + dy + 1, lpos.z);
+ auto d_vpos = coord::to_voxel(cpos, d_lpos);
+ auto d_index = coord::to_index(d_lpos);
+
+ if(d_lpos.y >= CHUNK_SIZE) {
+ if(!is_inside_terrain(d_vpos)) {
+ break;
+ }
+
+ depth += 1U;
+ }
+ else {
+ if(voxels[d_index] == NULL_VOXEL_ID) {
+ break;
+ }
+
+ depth += 1U;
+ }
+ }
+
+ if(depth < 5U) {
+ if(depth == 0U) {
+ voxels[i] = game_voxels::grass;
+ }
+ else {
+ voxels[i] = game_voxels::dirt;
+ }
+ }
+ }
+}
+
+void world::Overworld::generate_caves(const chunk_pos& cpos, VoxelStorage& voxels)
+{
+ auto& metadata = get_or_create_metadata(chunk_pos_xz(cpos.x, cpos.z));
+ auto variation = m_terrain_variation.get_value();
+
+ for(unsigned long i = 0U; i < CHUNK_VOLUME; ++i) {
+ auto lpos = coord::to_local(i);
+ auto vpos = coord::to_voxel(cpos, lpos);
+
+ if(vpos.y > variation) {
+ // Speculative optimization - there's no solid
+ // terrain above variation to carve caves out from
+ continue;
+ }
+
+ if(is_inside_cave(vpos)) {
+ voxels[i] = NULL_VOXEL_ID;
+ continue;
+ }
+ }
+}
+
+void world::Overworld::generate_features(const chunk_pos& cpos, VoxelStorage& voxels)
+{
+ const chunk_pos_xz tree_chunks[] = {
+ chunk_pos_xz(cpos.x - 0, cpos.z - 1),
+ chunk_pos_xz(cpos.x - 1, cpos.z - 1),
+ chunk_pos_xz(cpos.x - 1, cpos.z + 0),
+ chunk_pos_xz(cpos.x - 1, cpos.z + 1),
+ chunk_pos_xz(cpos.x + 0, cpos.z + 0),
+ chunk_pos_xz(cpos.x + 0, cpos.z + 1),
+ chunk_pos_xz(cpos.x + 1, cpos.z - 1),
+ chunk_pos_xz(cpos.x + 1, cpos.z + 0),
+ chunk_pos_xz(cpos.x + 1, cpos.z + 1),
+ };
+
+ for(unsigned int i = 0U; i < math::array_size(tree_chunks); ++i) {
+ const auto& cpos_xz = tree_chunks[i];
+ const auto& metadata = get_or_create_metadata(cpos_xz);
+
+ for(const auto& tree_info : metadata.trees) {
+ auto hdx = static_cast<std::size_t>(tree_info.x + tree_info.z * CHUNK_SIZE);
+ auto height = metadata.heightmap[hdx];
+
+ if(height == std::numeric_limits<voxel_pos::value_type>::min()) {
+ // What happened? Cave happened
+ continue;
+ }
+
+ auto cpos_xyz = chunk_pos(cpos_xz.x, 0, cpos_xz.y);
+ auto lpos_xyz = local_pos(tree_info.x, 0, tree_info.z);
+
+ auto vpos = coord::to_voxel(cpos_xyz, lpos_xyz);
+ vpos.y = height;
+
+ if(is_inside_cave(vpos)) {
+ // Cave is in the way
+ continue;
+ }
+
+ m_feat_tree[tree_info.y].place(vpos + DIR_UP<voxel_pos::value_type>, cpos, voxels);
+ }
+ }
+}
diff --git a/game/server/world/overworld.hh b/game/server/world/overworld.hh index f3fc8cf..a8112cf 100644 --- a/game/server/world/overworld.hh +++ b/game/server/world/overworld.hh @@ -1,68 +1,68 @@ -#pragma once - -#include "core/config/number.hh" - -#include "core/io/config_map.hh" - -#include "shared/world/dimension.hh" -#include "shared/world/feature.hh" - -#include "shared/const.hh" - -constexpr static unsigned int OW_NUM_TREES = 4U; - -namespace world -{ -struct Overworld_Metadata final { - world::dimension_entropy_map entropy; - world::dimension_height_map heightmap; - std::vector<local_pos> trees; -}; -} // namespace world - -namespace world -{ -class Overworld final : public Dimension { -public: - explicit Overworld(std::string_view name); - virtual ~Overworld(void) = default; - -public: - virtual void init(io::ConfigMap& config) override; - virtual void init_late(std::uint64_t global_seed) override; - virtual bool generate(const chunk_pos& cpos, VoxelStorage& voxels) override; - -private: - bool is_inside_cave(const voxel_pos& vpos); - bool is_inside_terrain(const voxel_pos& vpos); - -private: - const Overworld_Metadata& get_or_create_metadata(const chunk_pos_xz& cpos); - -private: - void generate_terrain(const chunk_pos& cpos, VoxelStorage& voxels); - void generate_surface(const chunk_pos& cpos, VoxelStorage& voxels); - void generate_caves(const chunk_pos& cpos, VoxelStorage& voxels); - void generate_features(const chunk_pos& cpos, VoxelStorage& voxels); - -private: - config::Int m_terrain_variation; - config::Int m_bottommost_chunk; - -private: - emhash8::HashMap<chunk_pos_xz, Overworld_Metadata> m_metamap; - -private: - fnl_state m_fnl_variation; - fnl_state m_fnl_terrain; - fnl_state m_fnl_caves_a; - fnl_state m_fnl_caves_b; - fnl_state m_fnl_nvdi; - -private: - Feature m_feat_tree[OW_NUM_TREES]; - -private: - std::mutex m_mutex; -}; -} // namespace world +#pragma once
+
+#include "core/config/number.hh"
+
+#include "core/io/config_map.hh"
+
+#include "shared/world/dimension.hh"
+#include "shared/world/feature.hh"
+
+#include "shared/const.hh"
+
+constexpr static unsigned int OW_NUM_TREES = 4U;
+
+namespace world
+{
+struct Overworld_Metadata final {
+ world::dimension_entropy_map entropy;
+ world::dimension_height_map heightmap;
+ std::vector<local_pos> trees;
+};
+} // namespace world
+
+namespace world
+{
+class Overworld final : public Dimension {
+public:
+ explicit Overworld(std::string_view name);
+ virtual ~Overworld(void) = default;
+
+public:
+ virtual void init(io::ConfigMap& config) override;
+ virtual void init_late(std::uint64_t global_seed) override;
+ virtual bool generate(const chunk_pos& cpos, VoxelStorage& voxels) override;
+
+private:
+ bool is_inside_cave(const voxel_pos& vpos);
+ bool is_inside_terrain(const voxel_pos& vpos);
+
+private:
+ const Overworld_Metadata& get_or_create_metadata(const chunk_pos_xz& cpos);
+
+private:
+ void generate_terrain(const chunk_pos& cpos, VoxelStorage& voxels);
+ void generate_surface(const chunk_pos& cpos, VoxelStorage& voxels);
+ void generate_caves(const chunk_pos& cpos, VoxelStorage& voxels);
+ void generate_features(const chunk_pos& cpos, VoxelStorage& voxels);
+
+private:
+ config::Int m_terrain_variation;
+ config::Int m_bottommost_chunk;
+
+private:
+ emhash8::HashMap<chunk_pos_xz, Overworld_Metadata> m_metamap;
+
+private:
+ fnl_state m_fnl_variation;
+ fnl_state m_fnl_terrain;
+ fnl_state m_fnl_caves_a;
+ fnl_state m_fnl_caves_b;
+ fnl_state m_fnl_nvdi;
+
+private:
+ Feature m_feat_tree[OW_NUM_TREES];
+
+private:
+ std::mutex m_mutex;
+};
+} // namespace world
diff --git a/game/server/world/universe.cc b/game/server/world/universe.cc index 278d0a9..2dd6053 100644 --- a/game/server/world/universe.cc +++ b/game/server/world/universe.cc @@ -1,221 +1,221 @@ -#include "server/pch.hh" - -#include "server/world/universe.hh" - -#include "core/config/number.hh" -#include "core/config/string.hh" - -#include "core/io/buffer.hh" -#include "core/io/config_map.hh" - -#include "core/utils/epoch.hh" - -#include "shared/world/chunk.hh" -#include "shared/world/dimension.hh" - -#include "server/world/inhabited.hh" -#include "server/world/overworld.hh" - -#include "server/globals.hh" - -struct DimensionMetadata final { - std::string config_path; - std::string zvox_dir; - io::ConfigMap config; -}; - -static config::String universe_name("save"); - -static io::ConfigMap universe_config; -static config::Unsigned64 universe_config_seed; -static config::String universe_spawn_dimension("world"); - -static std::string universe_config_path; -static std::unordered_map<world::Dimension*, DimensionMetadata*> metadata_map; - -static std::string make_chunk_filename(const DimensionMetadata* metadata, const chunk_pos& cpos) -{ - const auto unsigned_x = static_cast<std::uint32_t>(cpos.x); - const auto unsigned_y = static_cast<std::uint32_t>(cpos.y); - const auto unsigned_z = static_cast<std::uint32_t>(cpos.z); - return std::format("{}/{:08X}-{:08X}-{:08X}.zvox", metadata->zvox_dir, unsigned_x, unsigned_y, unsigned_z); -} - -static void add_new_dimension(world::Dimension* dimension) -{ - if(globals::dimensions.count(std::string(dimension->get_name()))) { - spdlog::critical("universe: dimension named {} already exists", dimension->get_name()); - std::terminate(); - } - - auto dimension_dir = std::format("{}/{}", universe_name.get(), dimension->get_name()); - - if(!PHYSFS_mkdir(dimension_dir.c_str())) { - spdlog::critical("universe: {}: {}", dimension_dir, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - std::terminate(); - } - - auto metadata = new DimensionMetadata; - metadata->config_path = std::format("{}/dimension.conf", dimension_dir); - metadata->zvox_dir = std::format("{}/chunk", dimension_dir); - - if(!PHYSFS_mkdir(metadata->zvox_dir.c_str())) { - spdlog::critical("universe: {}: {}", metadata->zvox_dir, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - std::terminate(); - } - - globals::dimensions.insert_or_assign(std::string(dimension->get_name()), dimension); - - auto& mapped_metadata = metadata_map.insert_or_assign(dimension, metadata).first->second; - - dimension->init(mapped_metadata->config); - - mapped_metadata->config.load_file(mapped_metadata->config_path.c_str()); - - dimension->init_late(universe_config_seed.get_value()); -} - -static void internal_save_chunk(const DimensionMetadata* metadata, const world::Dimension* dimension, const chunk_pos& cpos, - const world::Chunk* chunk) -{ - auto path = make_chunk_filename(metadata, cpos); - - io::WriteBuffer buffer; - chunk->get_voxels().serialize(buffer); - - if(auto file = buffer.to_file(path.c_str())) { - PHYSFS_close(file); - return; - } -} - -void world::universe::init(void) -{ - // If the world is newly created, the seed will - // be chosed based on the current system's view on UNIX time - universe_config_seed.set_value(utils::unix_microseconds()); - - // We're going to read files from directory named with - // the value of this config value. Since config is also - // read from command line, the [--universe <name>] parameter still works - globals::server_config.add_value("universe", universe_name); - - universe_config.add_value("global_seed", universe_config_seed); - universe_config.add_value("spawn_dimension", universe_spawn_dimension); -} - -void world::universe::init_late(void) -{ - const auto universe_dir = std::string(universe_name.get()); - - if(!PHYSFS_mkdir(universe_dir.c_str())) { - spdlog::critical("universe: {}: {}", universe_dir, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - std::terminate(); - } - - universe_config_path = std::format("{}/universe.conf", universe_dir); - universe_config.load_file(universe_config_path.c_str()); - - add_new_dimension(new Overworld("world")); - - // UNDONE: lua scripts to setup dimensions - if(globals::dimensions.empty()) { - spdlog::critical("universe: no dimensions"); - std::terminate(); - } - - auto spawn_dimension = globals::dimensions.find(universe_spawn_dimension.get_value()); - - if(spawn_dimension == globals::dimensions.cend()) { - spdlog::critical("universe: {} is not a valid dimension name", universe_spawn_dimension.get()); - std::terminate(); - } - - globals::spawn_dimension = spawn_dimension->second; -} - -void world::universe::shutdown(void) -{ - for(const auto metadata : metadata_map) { - metadata.second->config.save_file(metadata.second->config_path.c_str()); - delete metadata.second; - } - - metadata_map.clear(); - - for(const auto dimension : globals::dimensions) { - world::universe::save_all_chunks(dimension.second); - delete dimension.second; - } - - globals::dimensions.clear(); - globals::spawn_dimension = nullptr; - - universe_config.save_file(universe_config_path.c_str()); -} - -world::Chunk* world::universe::load_chunk(Dimension* dimension, const chunk_pos& cpos) -{ - if(auto chunk = dimension->find_chunk(cpos)) { - // Just return the existing chunk which is - // most probable to be up to date compared to - // whatever the hell is currently stored on disk - return chunk; - } - - auto metadata = metadata_map.find(dimension); - - if(metadata == metadata_map.cend()) { - // The dimension is for sure a weird one - return nullptr; - } - - if(auto file = PHYSFS_openRead(make_chunk_filename(metadata->second, cpos).c_str())) { - VoxelStorage voxels; - io::ReadBuffer buffer(file); - voxels.deserialize(buffer); - - PHYSFS_close(file); - - auto chunk = dimension->create_chunk(cpos); - chunk->set_voxels(voxels); - - // Make sure we're going to save it later - dimension->chunks.emplace_or_replace<Inhabited>(chunk->get_entity()); - - return chunk; - } - - return nullptr; -} - -void world::universe::save_chunk(Dimension* dimension, const chunk_pos& cpos) -{ - auto metadata = metadata_map.find(dimension); - - if(metadata == metadata_map.cend()) { - // Cannot save a chunk in a dimension - // that doesn't have a metadata struct - return; - } - - if(auto chunk = dimension->find_chunk(cpos)) { - internal_save_chunk(metadata->second, dimension, cpos, chunk); - } -} - -void world::universe::save_all_chunks(Dimension* dimension) -{ - auto group = dimension->chunks.group(entt::get<ChunkComponent, Inhabited>); - auto metadata = metadata_map.find(dimension); - - if(metadata == metadata_map.cend()) { - // Cannot save a chunk in a dimension - // that doesn't have a metadata struct - return; - } - - for(auto [entity, chunk] : group.each()) { - internal_save_chunk(metadata->second, dimension, chunk.cpos, chunk.chunk); - } -} +#include "server/pch.hh"
+
+#include "server/world/universe.hh"
+
+#include "core/config/number.hh"
+#include "core/config/string.hh"
+
+#include "core/io/buffer.hh"
+#include "core/io/config_map.hh"
+
+#include "core/utils/epoch.hh"
+
+#include "shared/world/chunk.hh"
+#include "shared/world/dimension.hh"
+
+#include "server/world/inhabited.hh"
+#include "server/world/overworld.hh"
+
+#include "server/globals.hh"
+
+struct DimensionMetadata final {
+ std::string config_path;
+ std::string zvox_dir;
+ io::ConfigMap config;
+};
+
+static config::String universe_name("save");
+
+static io::ConfigMap universe_config;
+static config::Unsigned64 universe_config_seed;
+static config::String universe_spawn_dimension("world");
+
+static std::string universe_config_path;
+static std::unordered_map<world::Dimension*, DimensionMetadata*> metadata_map;
+
+static std::string make_chunk_filename(const DimensionMetadata* metadata, const chunk_pos& cpos)
+{
+ const auto unsigned_x = static_cast<std::uint32_t>(cpos.x);
+ const auto unsigned_y = static_cast<std::uint32_t>(cpos.y);
+ const auto unsigned_z = static_cast<std::uint32_t>(cpos.z);
+ return std::format("{}/{:08X}-{:08X}-{:08X}.zvox", metadata->zvox_dir, unsigned_x, unsigned_y, unsigned_z);
+}
+
+static void add_new_dimension(world::Dimension* dimension)
+{
+ if(globals::dimensions.count(std::string(dimension->get_name()))) {
+ spdlog::critical("universe: dimension named {} already exists", dimension->get_name());
+ std::terminate();
+ }
+
+ auto dimension_dir = std::format("{}/{}", universe_name.get(), dimension->get_name());
+
+ if(!PHYSFS_mkdir(dimension_dir.c_str())) {
+ spdlog::critical("universe: {}: {}", dimension_dir, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ std::terminate();
+ }
+
+ auto metadata = new DimensionMetadata;
+ metadata->config_path = std::format("{}/dimension.conf", dimension_dir);
+ metadata->zvox_dir = std::format("{}/chunk", dimension_dir);
+
+ if(!PHYSFS_mkdir(metadata->zvox_dir.c_str())) {
+ spdlog::critical("universe: {}: {}", metadata->zvox_dir, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ std::terminate();
+ }
+
+ globals::dimensions.insert_or_assign(std::string(dimension->get_name()), dimension);
+
+ auto& mapped_metadata = metadata_map.insert_or_assign(dimension, metadata).first->second;
+
+ dimension->init(mapped_metadata->config);
+
+ mapped_metadata->config.load_file(mapped_metadata->config_path.c_str());
+
+ dimension->init_late(universe_config_seed.get_value());
+}
+
+static void internal_save_chunk(const DimensionMetadata* metadata, const world::Dimension* dimension, const chunk_pos& cpos,
+ const world::Chunk* chunk)
+{
+ auto path = make_chunk_filename(metadata, cpos);
+
+ io::WriteBuffer buffer;
+ chunk->get_voxels().serialize(buffer);
+
+ if(auto file = buffer.to_file(path.c_str())) {
+ PHYSFS_close(file);
+ return;
+ }
+}
+
+void world::universe::init(void)
+{
+ // If the world is newly created, the seed will
+ // be chosed based on the current system's view on UNIX time
+ universe_config_seed.set_value(utils::unix_microseconds());
+
+ // We're going to read files from directory named with
+ // the value of this config value. Since config is also
+ // read from command line, the [--universe <name>] parameter still works
+ globals::server_config.add_value("universe", universe_name);
+
+ universe_config.add_value("global_seed", universe_config_seed);
+ universe_config.add_value("spawn_dimension", universe_spawn_dimension);
+}
+
+void world::universe::init_late(void)
+{
+ const auto universe_dir = std::string(universe_name.get());
+
+ if(!PHYSFS_mkdir(universe_dir.c_str())) {
+ spdlog::critical("universe: {}: {}", universe_dir, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ std::terminate();
+ }
+
+ universe_config_path = std::format("{}/universe.conf", universe_dir);
+ universe_config.load_file(universe_config_path.c_str());
+
+ add_new_dimension(new Overworld("world"));
+
+ // UNDONE: lua scripts to setup dimensions
+ if(globals::dimensions.empty()) {
+ spdlog::critical("universe: no dimensions");
+ std::terminate();
+ }
+
+ auto spawn_dimension = globals::dimensions.find(universe_spawn_dimension.get_value());
+
+ if(spawn_dimension == globals::dimensions.cend()) {
+ spdlog::critical("universe: {} is not a valid dimension name", universe_spawn_dimension.get());
+ std::terminate();
+ }
+
+ globals::spawn_dimension = spawn_dimension->second;
+}
+
+void world::universe::shutdown(void)
+{
+ for(const auto metadata : metadata_map) {
+ metadata.second->config.save_file(metadata.second->config_path.c_str());
+ delete metadata.second;
+ }
+
+ metadata_map.clear();
+
+ for(const auto dimension : globals::dimensions) {
+ world::universe::save_all_chunks(dimension.second);
+ delete dimension.second;
+ }
+
+ globals::dimensions.clear();
+ globals::spawn_dimension = nullptr;
+
+ universe_config.save_file(universe_config_path.c_str());
+}
+
+world::Chunk* world::universe::load_chunk(Dimension* dimension, const chunk_pos& cpos)
+{
+ if(auto chunk = dimension->find_chunk(cpos)) {
+ // Just return the existing chunk which is
+ // most probable to be up to date compared to
+ // whatever the hell is currently stored on disk
+ return chunk;
+ }
+
+ auto metadata = metadata_map.find(dimension);
+
+ if(metadata == metadata_map.cend()) {
+ // The dimension is for sure a weird one
+ return nullptr;
+ }
+
+ if(auto file = PHYSFS_openRead(make_chunk_filename(metadata->second, cpos).c_str())) {
+ VoxelStorage voxels;
+ io::ReadBuffer buffer(file);
+ voxels.deserialize(buffer);
+
+ PHYSFS_close(file);
+
+ auto chunk = dimension->create_chunk(cpos);
+ chunk->set_voxels(voxels);
+
+ // Make sure we're going to save it later
+ dimension->chunks.emplace_or_replace<Inhabited>(chunk->get_entity());
+
+ return chunk;
+ }
+
+ return nullptr;
+}
+
+void world::universe::save_chunk(Dimension* dimension, const chunk_pos& cpos)
+{
+ auto metadata = metadata_map.find(dimension);
+
+ if(metadata == metadata_map.cend()) {
+ // Cannot save a chunk in a dimension
+ // that doesn't have a metadata struct
+ return;
+ }
+
+ if(auto chunk = dimension->find_chunk(cpos)) {
+ internal_save_chunk(metadata->second, dimension, cpos, chunk);
+ }
+}
+
+void world::universe::save_all_chunks(Dimension* dimension)
+{
+ auto group = dimension->chunks.group(entt::get<ChunkComponent, Inhabited>);
+ auto metadata = metadata_map.find(dimension);
+
+ if(metadata == metadata_map.cend()) {
+ // Cannot save a chunk in a dimension
+ // that doesn't have a metadata struct
+ return;
+ }
+
+ for(auto [entity, chunk] : group.each()) {
+ internal_save_chunk(metadata->second, dimension, chunk.cpos, chunk.chunk);
+ }
+}
diff --git a/game/server/world/universe.hh b/game/server/world/universe.hh index 285911e..966ac70 100644 --- a/game/server/world/universe.hh +++ b/game/server/world/universe.hh @@ -1,25 +1,25 @@ -#pragma once - -#include "shared/types.hh" - -namespace world -{ -class Chunk; -class Dimension; -} // namespace world - -class Session; - -namespace world::universe -{ -void init(void); -void init_late(void); -void shutdown(void); -} // namespace world::universe - -namespace world::universe -{ -Chunk* load_chunk(Dimension* dimension, const chunk_pos& cpos); -void save_chunk(Dimension* dimension, const chunk_pos& cpos); -void save_all_chunks(Dimension* dimension); -} // namespace world::universe +#pragma once
+
+#include "shared/types.hh"
+
+namespace world
+{
+class Chunk;
+class Dimension;
+} // namespace world
+
+class Session;
+
+namespace world::universe
+{
+void init(void);
+void init_late(void);
+void shutdown(void);
+} // namespace world::universe
+
+namespace world::universe
+{
+Chunk* load_chunk(Dimension* dimension, const chunk_pos& cpos);
+void save_chunk(Dimension* dimension, const chunk_pos& cpos);
+void save_all_chunks(Dimension* dimension);
+} // namespace world::universe
diff --git a/game/server/world/unloader.cc b/game/server/world/unloader.cc index 4a3f4e1..371a96f 100644 --- a/game/server/world/unloader.cc +++ b/game/server/world/unloader.cc @@ -1,78 +1,78 @@ -#include "server/pch.hh" - -#include "server/world/unloader.hh" - -#include "core/config/number.hh" - -#include "shared/entity/player.hh" -#include "shared/entity/transform.hh" - -#include "shared/world/chunk.hh" -#include "shared/world/chunk_aabb.hh" -#include "shared/world/dimension.hh" - -#include "server/world/inhabited.hh" -#include "server/world/universe.hh" - -#include "server/game.hh" -#include "server/globals.hh" - -static void on_chunk_update(const world::ChunkUpdateEvent& event) -{ - event.dimension->chunks.emplace_or_replace<world::Inhabited>(event.chunk->get_entity()); -} - -static void on_voxel_set(const world::VoxelSetEvent& event) -{ - event.dimension->chunks.emplace_or_replace<world::Inhabited>(event.chunk->get_entity()); -} - -void world::unloader::init(void) -{ - globals::dispatcher.sink<world::ChunkUpdateEvent>().connect<&on_chunk_update>(); - globals::dispatcher.sink<world::VoxelSetEvent>().connect<&on_voxel_set>(); -} - -void world::unloader::init_late(void) -{ -} - -void world::unloader::fixed_update_late(Dimension* dimension) -{ - auto group = dimension->entities.group(entt::get<entity::Player, entity::Transform>); - auto boxes = std::vector<ChunkAABB>(); - - for(const auto [entity, transform] : group.each()) { - ChunkAABB aabb; - aabb.min = transform.chunk - static_cast<chunk_pos::value_type>(server_game::view_distance.get_value()); - aabb.max = transform.chunk + static_cast<chunk_pos::value_type>(server_game::view_distance.get_value()); - boxes.push_back(aabb); - } - - auto view = dimension->chunks.view<ChunkComponent>(); - auto chunk_in_view = false; - - for(const auto [entity, chunk] : view.each()) { - chunk_in_view = false; - - for(const auto& aabb : boxes) { - if(aabb.contains(chunk.cpos)) { - chunk_in_view = true; - break; - } - } - - if(chunk_in_view) { - // The chunk is within view box of at least - // a single player; we shouldn't unload it now - continue; - } - - if(dimension->chunks.any_of<Inhabited>(entity)) { - // Only store inhabited chunks on disk - world::universe::save_chunk(dimension, chunk.cpos); - } - - dimension->remove_chunk(entity); - } -} +#include "server/pch.hh"
+
+#include "server/world/unloader.hh"
+
+#include "core/config/number.hh"
+
+#include "shared/entity/player.hh"
+#include "shared/entity/transform.hh"
+
+#include "shared/world/chunk.hh"
+#include "shared/world/chunk_aabb.hh"
+#include "shared/world/dimension.hh"
+
+#include "server/world/inhabited.hh"
+#include "server/world/universe.hh"
+
+#include "server/game.hh"
+#include "server/globals.hh"
+
+static void on_chunk_update(const world::ChunkUpdateEvent& event)
+{
+ event.dimension->chunks.emplace_or_replace<world::Inhabited>(event.chunk->get_entity());
+}
+
+static void on_voxel_set(const world::VoxelSetEvent& event)
+{
+ event.dimension->chunks.emplace_or_replace<world::Inhabited>(event.chunk->get_entity());
+}
+
+void world::unloader::init(void)
+{
+ globals::dispatcher.sink<world::ChunkUpdateEvent>().connect<&on_chunk_update>();
+ globals::dispatcher.sink<world::VoxelSetEvent>().connect<&on_voxel_set>();
+}
+
+void world::unloader::init_late(void)
+{
+}
+
+void world::unloader::fixed_update_late(Dimension* dimension)
+{
+ auto group = dimension->entities.group(entt::get<entity::Player, entity::Transform>);
+ auto boxes = std::vector<ChunkAABB>();
+
+ for(const auto [entity, transform] : group.each()) {
+ ChunkAABB aabb;
+ aabb.min = transform.chunk - static_cast<chunk_pos::value_type>(server_game::view_distance.get_value());
+ aabb.max = transform.chunk + static_cast<chunk_pos::value_type>(server_game::view_distance.get_value());
+ boxes.push_back(aabb);
+ }
+
+ auto view = dimension->chunks.view<ChunkComponent>();
+ auto chunk_in_view = false;
+
+ for(const auto [entity, chunk] : view.each()) {
+ chunk_in_view = false;
+
+ for(const auto& aabb : boxes) {
+ if(aabb.contains(chunk.cpos)) {
+ chunk_in_view = true;
+ break;
+ }
+ }
+
+ if(chunk_in_view) {
+ // The chunk is within view box of at least
+ // a single player; we shouldn't unload it now
+ continue;
+ }
+
+ if(dimension->chunks.any_of<Inhabited>(entity)) {
+ // Only store inhabited chunks on disk
+ world::universe::save_chunk(dimension, chunk.cpos);
+ }
+
+ dimension->remove_chunk(entity);
+ }
+}
diff --git a/game/server/world/unloader.hh b/game/server/world/unloader.hh index 9682de6..a5b1da1 100644 --- a/game/server/world/unloader.hh +++ b/game/server/world/unloader.hh @@ -1,13 +1,13 @@ -#pragma once - -namespace world -{ -class Dimension; -} // namespace world - -namespace world::unloader -{ -void init(void); -void init_late(void); -void fixed_update_late(Dimension* dimension); -} // namespace world::unloader +#pragma once
+
+namespace world
+{
+class Dimension;
+} // namespace world
+
+namespace world::unloader
+{
+void init(void);
+void init_late(void);
+void fixed_update_late(Dimension* dimension);
+} // namespace world::unloader
diff --git a/game/server/world/worldgen.cc b/game/server/world/worldgen.cc index 8b02b52..3d8154c 100644 --- a/game/server/world/worldgen.cc +++ b/game/server/world/worldgen.cc @@ -1,151 +1,151 @@ -#include "server/pch.hh" - -#include "server/world/worldgen.hh" - -#include "core/io/cmdline.hh" - -#include "core/threading.hh" - -#include "shared/world/chunk.hh" -#include "shared/world/dimension.hh" - -#include "shared/protocol.hh" - -#include "server/world/inhabited.hh" - -#include "server/globals.hh" -#include "server/sessions.hh" - -static bool aggressive_caching; - -static emhash8::HashMap<world::Dimension*, emhash8::HashMap<chunk_pos, std::unordered_set<Session*>>> active_tasks; - -class WorldgenTask final : public Task { -public: - explicit WorldgenTask(world::Dimension* dimension, const chunk_pos& cpos); - virtual ~WorldgenTask(void) = default; - virtual void process(void) override; - virtual void finalize(void) override; - -private: - world::Dimension* m_dimension; - world::VoxelStorage m_voxels; - chunk_pos m_cpos; -}; - -WorldgenTask::WorldgenTask(world::Dimension* dimension, const chunk_pos& cpos) -{ - m_dimension = dimension; - m_voxels.fill(rand()); // trolling - m_cpos = cpos; -} - -void WorldgenTask::process(void) -{ - if(!m_dimension->generate(m_cpos, m_voxels)) { - set_status(task_status::CANCELLED); - } -} - -void WorldgenTask::finalize(void) -{ - auto dim_tasks = active_tasks.find(m_dimension); - - if(dim_tasks == active_tasks.cend()) { - // Normally this should never happen but - // one can never be sure about anything - // when that anything is threaded out - return; - } - - auto it = dim_tasks->second.find(m_cpos); - - if(it == dim_tasks->second.cend()) { - // Normally this should never happen but - // one can never be sure about anything - // when that anything is threaded out - return; - } - - auto chunk = m_dimension->create_chunk(m_cpos); - chunk->set_voxels(m_voxels); - - if(aggressive_caching) { - // Marking the chunk with InhabitedComponent makes - // it so that it is saved regardles of whether it was - // modified by players or not. This isn't particularly - // good for server-side disk usage but it might improve performance - m_dimension->chunks.emplace<world::Inhabited>(chunk->get_entity()); - } - - protocol::ChunkVoxels response; - response.voxels = m_voxels; - response.chunk = m_cpos; - - auto packet = protocol::encode(response); - - for(auto session : it->second) { - if(session->peer) { - // Respond with the voxels to every session - // that has requested this specific chunk for this dimension - enet_peer_send(session->peer, protocol::CHANNEL, packet); - } - } - - dim_tasks->second.erase(it); - - if(dim_tasks->second.empty()) { - // There are no more requests - // to generate a chunk for that - // dimension, at least for now - active_tasks.erase(dim_tasks); - } -} - -void world::worldgen::init(void) -{ - aggressive_caching = io::cmdline::contains("aggressive-caching"); -} - -bool world::worldgen::is_generating(Dimension* dimension, const chunk_pos& cpos) -{ - auto dim_tasks = active_tasks.find(dimension); - - if(dim_tasks == active_tasks.cend()) { - // No tasks for this dimension - return false; - } - - auto it = dim_tasks->second.find(cpos); - - if(it == dim_tasks->second.cend()) { - // Not generating this chunk - return false; - } - - return true; -} - -void world::worldgen::request_chunk(Session* session, const chunk_pos& cpos) -{ - if(session->dimension) { - auto dim_tasks = active_tasks.find(session->dimension); - - if(dim_tasks == active_tasks.cend()) { - dim_tasks = active_tasks.emplace(session->dimension, emhash8::HashMap<chunk_pos, std::unordered_set<Session*>>()).first; - } - - auto it = dim_tasks->second.find(cpos); - - if(it == dim_tasks->second.cend()) { - auto& sessions = dim_tasks->second.insert_or_assign(cpos, std::unordered_set<Session*>()).first->second; - sessions.insert(session); - - threading::submit<WorldgenTask>(session->dimension, cpos); - - return; - } - - it->second.insert(session); - } -} +#include "server/pch.hh"
+
+#include "server/world/worldgen.hh"
+
+#include "core/io/cmdline.hh"
+
+#include "core/threading.hh"
+
+#include "shared/world/chunk.hh"
+#include "shared/world/dimension.hh"
+
+#include "shared/protocol.hh"
+
+#include "server/world/inhabited.hh"
+
+#include "server/globals.hh"
+#include "server/sessions.hh"
+
+static bool aggressive_caching;
+
+static emhash8::HashMap<world::Dimension*, emhash8::HashMap<chunk_pos, std::unordered_set<Session*>>> active_tasks;
+
+class WorldgenTask final : public Task {
+public:
+ explicit WorldgenTask(world::Dimension* dimension, const chunk_pos& cpos);
+ virtual ~WorldgenTask(void) = default;
+ virtual void process(void) override;
+ virtual void finalize(void) override;
+
+private:
+ world::Dimension* m_dimension;
+ world::VoxelStorage m_voxels;
+ chunk_pos m_cpos;
+};
+
+WorldgenTask::WorldgenTask(world::Dimension* dimension, const chunk_pos& cpos)
+{
+ m_dimension = dimension;
+ m_voxels.fill(rand()); // trolling
+ m_cpos = cpos;
+}
+
+void WorldgenTask::process(void)
+{
+ if(!m_dimension->generate(m_cpos, m_voxels)) {
+ set_status(task_status::CANCELLED);
+ }
+}
+
+void WorldgenTask::finalize(void)
+{
+ auto dim_tasks = active_tasks.find(m_dimension);
+
+ if(dim_tasks == active_tasks.cend()) {
+ // Normally this should never happen but
+ // one can never be sure about anything
+ // when that anything is threaded out
+ return;
+ }
+
+ auto it = dim_tasks->second.find(m_cpos);
+
+ if(it == dim_tasks->second.cend()) {
+ // Normally this should never happen but
+ // one can never be sure about anything
+ // when that anything is threaded out
+ return;
+ }
+
+ auto chunk = m_dimension->create_chunk(m_cpos);
+ chunk->set_voxels(m_voxels);
+
+ if(aggressive_caching) {
+ // Marking the chunk with InhabitedComponent makes
+ // it so that it is saved regardles of whether it was
+ // modified by players or not. This isn't particularly
+ // good for server-side disk usage but it might improve performance
+ m_dimension->chunks.emplace<world::Inhabited>(chunk->get_entity());
+ }
+
+ protocol::ChunkVoxels response;
+ response.voxels = m_voxels;
+ response.chunk = m_cpos;
+
+ auto packet = protocol::encode(response);
+
+ for(auto session : it->second) {
+ if(session->peer) {
+ // Respond with the voxels to every session
+ // that has requested this specific chunk for this dimension
+ enet_peer_send(session->peer, protocol::CHANNEL, packet);
+ }
+ }
+
+ dim_tasks->second.erase(it);
+
+ if(dim_tasks->second.empty()) {
+ // There are no more requests
+ // to generate a chunk for that
+ // dimension, at least for now
+ active_tasks.erase(dim_tasks);
+ }
+}
+
+void world::worldgen::init(void)
+{
+ aggressive_caching = io::cmdline::contains("aggressive-caching");
+}
+
+bool world::worldgen::is_generating(Dimension* dimension, const chunk_pos& cpos)
+{
+ auto dim_tasks = active_tasks.find(dimension);
+
+ if(dim_tasks == active_tasks.cend()) {
+ // No tasks for this dimension
+ return false;
+ }
+
+ auto it = dim_tasks->second.find(cpos);
+
+ if(it == dim_tasks->second.cend()) {
+ // Not generating this chunk
+ return false;
+ }
+
+ return true;
+}
+
+void world::worldgen::request_chunk(Session* session, const chunk_pos& cpos)
+{
+ if(session->dimension) {
+ auto dim_tasks = active_tasks.find(session->dimension);
+
+ if(dim_tasks == active_tasks.cend()) {
+ dim_tasks = active_tasks.emplace(session->dimension, emhash8::HashMap<chunk_pos, std::unordered_set<Session*>>()).first;
+ }
+
+ auto it = dim_tasks->second.find(cpos);
+
+ if(it == dim_tasks->second.cend()) {
+ auto& sessions = dim_tasks->second.insert_or_assign(cpos, std::unordered_set<Session*>()).first->second;
+ sessions.insert(session);
+
+ threading::submit<WorldgenTask>(session->dimension, cpos);
+
+ return;
+ }
+
+ it->second.insert(session);
+ }
+}
diff --git a/game/server/world/worldgen.hh b/game/server/world/worldgen.hh index 8ffec36..eeb3e19 100644 --- a/game/server/world/worldgen.hh +++ b/game/server/world/worldgen.hh @@ -1,21 +1,21 @@ -#pragma once - -#include "shared/types.hh" - -namespace world -{ -class Dimension; -} // namespace world - -class Session; - -namespace world::worldgen -{ -void init(void); -} // namespace world::worldgen - -namespace world::worldgen -{ -bool is_generating(Dimension* dimension, const chunk_pos& cpos); -void request_chunk(Session* session, const chunk_pos& cpos); -} // namespace world::worldgen +#pragma once
+
+#include "shared/types.hh"
+
+namespace world
+{
+class Dimension;
+} // namespace world
+
+class Session;
+
+namespace world::worldgen
+{
+void init(void);
+} // namespace world::worldgen
+
+namespace world::worldgen
+{
+bool is_generating(Dimension* dimension, const chunk_pos& cpos);
+void request_chunk(Session* session, const chunk_pos& cpos);
+} // namespace world::worldgen
|
