summaryrefslogtreecommitdiffstats
path: root/game/server/world
diff options
context:
space:
mode:
authoruntodesu <kirill@untode.su>2025-09-11 15:48:53 +0500
committeruntodesu <kirill@untode.su>2025-09-11 15:48:53 +0500
commitd0fbd68055e3f4a796330cc8acc6c0954b5327ff (patch)
treee581014ea02711efa5e71f00f9862e5bca58f2ed /game/server/world
parentcbd823aa2154a956e7da4319eecbf7afc10441ae (diff)
downloadvoxelius-d0fbd68055e3f4a796330cc8acc6c0954b5327ff.tar.bz2
voxelius-d0fbd68055e3f4a796330cc8acc6c0954b5327ff.zip
Run clang-format across the project
Diffstat (limited to 'game/server/world')
-rw-r--r--game/server/world/inhabited.hh12
-rw-r--r--game/server/world/overworld.cc766
-rw-r--r--game/server/world/overworld.hh136
-rw-r--r--game/server/world/universe.cc442
-rw-r--r--game/server/world/universe.hh50
-rw-r--r--game/server/world/unloader.cc156
-rw-r--r--game/server/world/unloader.hh26
-rw-r--r--game/server/world/worldgen.cc302
-rw-r--r--game/server/world/worldgen.hh42
9 files changed, 966 insertions, 966 deletions
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