diff --git a/common/source/core/Registry.cpp b/common/source/core/Registry.cpp index 4378adabb..1bc292f9f 100644 --- a/common/source/core/Registry.cpp +++ b/common/source/core/Registry.cpp @@ -86,6 +86,10 @@ void Registry::serialize(sf::Packet &packet) const { packet << u8((it->type() == "craft") ? DataType::CraftingRecipe : DataType::SmeltingRecipe) << *it; } + + for (auto &it : m_biomes) { + packet << u8(DataType::Biome) << *it; + } } void Registry::deserialize(sf::Packet &packet) { @@ -104,6 +108,9 @@ void Registry::deserialize(sf::Packet &packet) { else if (type == u8(DataType::SmeltingRecipe)) { registerRecipe()->deserialize(packet); } + else if (type == u8(DataType::Biome)) { + registerSerializedBiome(packet); + } } } diff --git a/common/source/core/Registry.hpp b/common/source/core/Registry.hpp index 19b666078..6a47ccdc4 100644 --- a/common/source/core/Registry.hpp +++ b/common/source/core/Registry.hpp @@ -35,6 +35,7 @@ #include "Item.hpp" #include "Network.hpp" #include "Recipe.hpp" +#include "Biome.hpp" class Registry : public ISerializable { public: @@ -66,6 +67,25 @@ class Registry : public ISerializable { return m_recipes.back().get(); } + template + auto registerBiome(const std::string &stringID, const std::string &label) -> typename std::enable_if::value, T&>::type { + size_t id = m_biomes.size(); + m_biomesID.emplace(stringID, id); + m_biomes.emplace_back(std::make_unique(id, stringID, label)); + return *static_cast(m_biomes.back().get()); + } + + template + auto registerSerializedBiome(sf::Packet &packet) -> typename std::enable_if::value, T&>::type { + m_biomes.emplace_back(std::make_unique()); + m_biomes.back()->deserialize(packet); + + size_t id = m_biomes.size() - 1; + m_biomesID.emplace(m_biomes.back()->stringID(), id); + + return *static_cast(m_biomes.back().get()); + } + const Block &getBlock(std::size_t id) const { return *m_blocks.at(id).get(); } const Item &getItem(std::size_t id) const { return m_items.at(id); } @@ -74,11 +94,14 @@ class Registry : public ISerializable { const Recipe *getRecipe(const Inventory &inventory) const; + const Biome &getBiome(std::size_t id) const { return *m_biomes.at(id).get(); } + void serialize(sf::Packet &packet) const override; void deserialize(sf::Packet &packet) override; const std::vector> &blocks() const { return m_blocks; } const std::vector &items() const { return m_items; } + const std::vector> &biomes() const { return m_biomes; } static Registry &getInstance() { return *s_instance; } static void setInstance(Registry &instance) { s_instance = &instance; } @@ -89,15 +112,18 @@ class Registry : public ISerializable { std::vector> m_blocks; std::vector m_items; std::vector> m_recipes; + std::vector> m_biomes; std::unordered_map m_blocksID; std::unordered_map m_itemsID; + std::unordered_map m_biomesID; enum class DataType { Block, Item, CraftingRecipe, SmeltingRecipe, + Biome }; }; diff --git a/common/source/world/Biome.cpp b/common/source/world/Biome.cpp new file mode 100644 index 000000000..bcb0d368f --- /dev/null +++ b/common/source/world/Biome.cpp @@ -0,0 +1,43 @@ +/* + * ===================================================================================== + * + * OpenMiner + * + * Copyright (C) 2018-2020 Unarelith, Quentin Bazin + * Copyright (C) 2019-2020 the OpenMiner contributors (see CONTRIBUTORS.md) + * + * This file is part of OpenMiner. + * + * OpenMiner is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * OpenMiner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenMiner; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * ===================================================================================== + */ +#include "Biome.hpp" +#include +#include "NetworkUtils.hpp" + +Biome::Biome(u32 id, const std::string &stringID, const std::string &label) { + m_id = id; + std::string m_stringID; + std::string m_label; +} + +void Biome::serialize(sf::Packet &packet) const { + packet << u32(m_id) << m_stringID << m_label << m_params << m_topBlock << m_groundBlock << m_beachBlock << m_liquidBlock; +} + +void Biome::deserialize(sf::Packet &packet) { + packet >> m_id >> m_stringID >> m_label >> m_params >> m_topBlock >> m_groundBlock >> m_beachBlock >> m_liquidBlock; +} \ No newline at end of file diff --git a/common/source/world/Biome.hpp b/common/source/world/Biome.hpp new file mode 100644 index 000000000..9fb781984 --- /dev/null +++ b/common/source/world/Biome.hpp @@ -0,0 +1,74 @@ +/* + * ===================================================================================== + * + * OpenMiner + * + * Copyright (C) 2018-2020 Unarelith, Quentin Bazin + * Copyright (C) 2019-2020 the OpenMiner contributors (see CONTRIBUTORS.md) + * + * This file is part of OpenMiner. + * + * OpenMiner is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * OpenMiner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenMiner; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * ===================================================================================== + */ +#ifndef BIOME_HPP_ +#define BIOME_HPP_ + +#include +#include +#include +#include "ISerializable.hpp" + +class Biome : public ISerializable { +public: + Biome() = default; + Biome(u32 id, const std::string &stringID, const std::string &label); + ~Biome() = default; + + void serialize(sf::Packet &packet) const override; + void deserialize(sf::Packet &packet) override; + + const std::string &stringID() const { return m_stringID; } + const std::string &label() const { return m_label; } + void setLabel(const std::string &label) { m_label = label; } + + std::vector getParams() { return m_params; } + const u16 getTopBlock() const { return m_topBlock; } + const u16 getGroundBlock() const { return m_groundBlock; } + const u16 getBeachBlock() const { return m_beachBlock; } + const u16 getLiquidBlock() const { return m_liquidBlock; } + + void setParams(std::vector value) { m_params = value; } + void setTopBlock(u16 value) { m_topBlock = value; } + void setGroundBlock(u16 value) { m_groundBlock = value; } + void setBeachBlock(u16 value) { m_beachBlock = value; } + void setLiquidBlock(u16 value) { m_liquidBlock = value; } + +private: + u32 m_id; + std::string m_stringID; + std::string m_label; + + // TODO something to distinguish the worldtype of biome + std::vector m_params; + u16 m_topBlock; + u16 m_groundBlock; + u16 m_beachBlock; + u16 m_liquidBlock; + +}; + +#endif // BIOME_HPP_ \ No newline at end of file diff --git a/mods/default/biomes.lua b/mods/default/biomes.lua new file mode 100644 index 000000000..a41834306 --- /dev/null +++ b/mods/default/biomes.lua @@ -0,0 +1,88 @@ +-- +-- ===================================================================================== +-- +-- OpenMiner +-- +-- Copyright (C) 2018-2020 Unarelith, Quentin Bazin +-- Copyright (C) 2019-2020 the OpenMiner contributors (see CONTRIBUTORS.md) +-- +-- This file is part of OpenMiner. +-- +-- OpenMiner is free software; you can redistribute it and/or +-- modify it under the terms of the GNU Lesser General Public +-- License as published by the Free Software Foundation; either +-- version 2.1 of the License, or (at your option) any later version. +-- +-- OpenMiner is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- Lesser General Public License for more details. +-- +-- You should have received a copy of the GNU Lesser General Public License +-- along with OpenMiner; if not, write to the Free Software Foundation, Inc., +-- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +-- +-- ===================================================================================== +-- + +mod:biome { + id = "default:grassland", + name = "Grassland", + + params = { + temperature = -0.6, + precipitation = 0.4 + }, + + top_block = "default:grass", + ground_block = "default:dirt", + beach_block = "default:sand", + liquid_block = "default:water", + + flora = { + { + block = "default:tallgrass", + spawns_on = "default:grass", + probability = 0.25 + }, + { + block = "default:flower", + spawns_on = "default:grass", + probability = 0.1 + } + } +} + +mod:biome { + id = "default:mountain", + name = "Mountain", + + params = { + temperature = 0.5, + precipitation = 0.3 + }, + + top_block = "default:stone", + ground_block = "default:stone", + beach_block = "default:stone", + liquid_block = "default:water", + + flora = { } +} + +mod:biome { + id = "default:barren", + name = "Barren", + + params = { + temperature = 0.3, + precipitation = -0.7 + }, + + top_block = "default:dirt", + ground_block = "default:dirt", + beach_block = "default:dirt", + liquid_block = "default:water", + + flora = { } +} \ No newline at end of file diff --git a/mods/default/init.lua b/mods/default/init.lua index 6c137fb76..9891e42eb 100644 --- a/mods/default/init.lua +++ b/mods/default/init.lua @@ -29,6 +29,7 @@ mod = LuaMod.new("default") dofile("mods/default/blocks.lua") dofile("mods/default/items.lua") dofile("mods/default/recipes.lua") +dofile("mods/default/biomes.lua") openminer:world():terrain_generator():set_blocks({ dirt = "default:dirt", diff --git a/server/source/lua/LuaMod.cpp b/server/source/lua/LuaMod.cpp index 751d7e865..3e19169c2 100644 --- a/server/source/lua/LuaMod.cpp +++ b/server/source/lua/LuaMod.cpp @@ -30,6 +30,7 @@ #include "LuaMod.hpp" #include "Registry.hpp" #include "ServerBlock.hpp" +#include "Biome.hpp" #include "SmeltingRecipe.hpp" void LuaMod::registerBlock(const sol::table &table) { @@ -142,11 +143,32 @@ void LuaMod::registerSmeltingRecipe(const sol::table &table) { Registry::getInstance().registerRecipe(input, output); } +void LuaMod::registerBiome(const sol::table &table) { + std::string stringID = m_id + ":" + table["id"].get(); + std::string label = table["name"].get(); + + Biome &biome = Registry::getInstance().registerBiome(stringID, label); + + // TODO eventually a WorldType could have a list of biome parameter names in order, and we could use those as the ordered keys. + // Currently hardcoding "temperature" and "precipitation" to get something functional. + size_t nBiomeParams = 2; + std::vector params(nBiomeParams); + params[0] = table["params"]["temperature"]; + params[1] = table["params"]["precipitation"]; + biome.setParams(params); + + biome.setTopBlock(Registry::getInstance().getBlockFromStringID(table["top_block"]).id()); + biome.setGroundBlock(Registry::getInstance().getBlockFromStringID(table["ground_block"]).id()); + biome.setBeachBlock(Registry::getInstance().getBlockFromStringID(table["beach_block"]).id()); + biome.setLiquidBlock(Registry::getInstance().getBlockFromStringID(table["liquid_block"]).id()); +} + void LuaMod::initUsertype(sol::state &lua) { lua.new_usertype("LuaMod", sol::constructors(), "id", &LuaMod::id, "block", &LuaMod::registerBlock, + "biome", &LuaMod::registerBiome, "item", &LuaMod::registerItem, "crafting_recipe", &LuaMod::registerCraftingRecipe, "smelting_recipe", &LuaMod::registerSmeltingRecipe diff --git a/server/source/lua/LuaMod.hpp b/server/source/lua/LuaMod.hpp index 3f855d598..2be987139 100644 --- a/server/source/lua/LuaMod.hpp +++ b/server/source/lua/LuaMod.hpp @@ -38,6 +38,7 @@ class LuaMod { void registerItem(const sol::table &table); void registerCraftingRecipe(const sol::table &table); void registerSmeltingRecipe(const sol::table &table); + void registerBiome(const sol::table &table); const std::string &id() const { return m_id; } diff --git a/server/source/world/TerrainBiomeSampler.cpp b/server/source/world/TerrainBiomeSampler.cpp new file mode 100644 index 000000000..0e87f916b --- /dev/null +++ b/server/source/world/TerrainBiomeSampler.cpp @@ -0,0 +1,78 @@ +/* + * ===================================================================================== + * + * OpenMiner + * + * Copyright (C) 2018-2020 Unarelith, Quentin Bazin + * Copyright (C) 2019-2020 the OpenMiner contributors (see CONTRIBUTORS.md) + * + * This file is part of OpenMiner. + * + * OpenMiner is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * OpenMiner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenMiner; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * ===================================================================================== + */ +#include "TerrainBiomeSampler.hpp" +#include "Registry.hpp" +#include "Biome.hpp" + +TerrainBiomeSampler::TerrainBiomeSampler() { + + paramNoisesPtr = new FastNoise[nBiomeParams]; + for (u8 i = 0; i < nBiomeParams; i++) { + paramNoisesPtr[i].SetNoiseType(FastNoise::NoiseType::SimplexFractal); + paramNoisesPtr[i].SetFrequency(1 / 800.0f); + paramNoisesPtr[i].SetFractalOctaves(5); + paramNoisesPtr[i].SetSeed(i); + } + +} + +TerrainBiomeSampler::~TerrainBiomeSampler() { + delete paramNoisesPtr; +} + +u16 TerrainBiomeSampler::getBiomeIndexAt(s32 x, s32 y) const { + + // Compute noise instances + double* biomeParamsPtr = new double[nBiomeParams]; + for (u8 i = 0; i < nBiomeParams; i++) { + biomeParamsPtr[i] = paramNoisesPtr[i].GetNoise(x, y); + } + + // TODO with a lot of biomes, perhaps we want an R-Tree or similar, instead of a long loop. + // Should also finish solving for analytic blending, or find completely separate solution such as isotropically-modified genlayer + // If we continue with temp/precip/etc params, need to write a weighted lloyd smoother so biomes becone fairly represented. + // True temp/precip values can then be re-interpolated out from the Voronoi diagram using a neighborhood figure "kernel". + // TODO with multiple worldtypes added, need to only consider biomes in one worldtype. + u16 decidedBiomeIndex = 0; + double decidedBiomeDeviation = 0xFFFF; + u16 j = 0; + for (auto &biome : Registry::getInstance().biomes()) { + double deviation = 0; + for (int i = 0; i < nBiomeParams; i++) { + double dp = biomeParamsPtr[i] - biome.get()->getParams()[i]; + deviation += dp * dp; + } + if (deviation < decidedBiomeDeviation) { + decidedBiomeDeviation = deviation; + decidedBiomeIndex = j; + } + j++; + } + + delete biomeParamsPtr; + return decidedBiomeIndex; +} \ No newline at end of file diff --git a/server/source/world/TerrainBiomeSampler.hpp b/server/source/world/TerrainBiomeSampler.hpp new file mode 100644 index 000000000..0a90466a1 --- /dev/null +++ b/server/source/world/TerrainBiomeSampler.hpp @@ -0,0 +1,47 @@ +/* + * ===================================================================================== + * + * OpenMiner + * + * Copyright (C) 2018-2020 Unarelith, Quentin Bazin + * Copyright (C) 2019-2020 the OpenMiner contributors (see CONTRIBUTORS.md) + * + * This file is part of OpenMiner. + * + * OpenMiner is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * OpenMiner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenMiner; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * ===================================================================================== + */ +#ifndef TERRAINBIOMESAMPLER_HPP_ +#define TERRAINBIOMESAMPLER_HPP_ + +#include +#include "FastNoise.hpp" +#include "Biome.hpp" + +class TerrainBiomeSampler { +public: + TerrainBiomeSampler(); // TODO should eventually take a worldtype + ~TerrainBiomeSampler(); + + u16 getBiomeIndexAt(s32 x, s32 y) const; + //std::vector getWeightedBiomeIndicesAt(double x, double y); + +private: + u8 nBiomeParams = 2; // TODO should be defined in the worldtype + FastNoise* paramNoisesPtr; +}; + +#endif // TERRAINBIOMESAMPLER_HPP_ \ No newline at end of file diff --git a/server/source/world/TerrainGenerator.cpp b/server/source/world/TerrainGenerator.cpp index 25e7f7dad..c0da24da7 100644 --- a/server/source/world/TerrainGenerator.cpp +++ b/server/source/world/TerrainGenerator.cpp @@ -63,6 +63,10 @@ void TerrainGenerator::fastNoiseGeneration(ServerChunk &chunk) const { Chunk *topChunk = chunk.getSurroundingChunk(Chunk::Top); for(int y = 0 ; y < CHUNK_DEPTH ; y++) { for(int x = 0 ; x < CHUNK_WIDTH ; x++) { + + u16 biomeIndex = biomeSampler.getBiomeIndexAt(x + chunk.x() * CHUNK_WIDTH, y + chunk.y() * CHUNK_DEPTH); + auto &biome = Registry::getInstance().getBiome(biomeIndex); + // Land height double n = noise.GetNoise(-x - chunk.x() * CHUNK_WIDTH, y + chunk.y() * CHUNK_DEPTH); double h = 10 + n * 20; @@ -115,27 +119,16 @@ void TerrainGenerator::fastNoiseGeneration(ServerChunk &chunk) const { } else { if (z + chunk.z() * CHUNK_HEIGHT >= h - 1 && z + chunk.z() * CHUNK_HEIGHT > SEALEVEL - 1) - chunk.setBlockRaw(x, y, z, m_grassBlockID); + chunk.setBlockRaw(x, y, z, biome.getTopBlock()); else if (z + chunk.z() * CHUNK_HEIGHT <= SEALEVEL - 1 && h < SEALEVEL && z + chunk.z() * CHUNK_HEIGHT > h - 3) - chunk.setBlockRaw(x, y, z, m_sandBlockID); + chunk.setBlockRaw(x, y, z, biome.getBeachBlock()); else if (z + chunk.z() * CHUNK_HEIGHT > h - 3) - chunk.setBlockRaw(x, y, z, m_dirtBlockID); + chunk.setBlockRaw(x, y, z, biome.getGroundBlock()); else chunk.setBlockRaw(x, y, z, m_stoneBlockID); if ((rand() % 4096) == 0) oreFloodFill(chunk, x, y, z, m_stoneBlockID, m_ironOreBlockID, 2); - - // Caves - float n2 = noise2d(-(x + chunk.x() * CHUNK_WIDTH) / 256.0, (y + chunk.y() * CHUNK_DEPTH) / 256.0, 8, 0.3) * 4; - float r2 = noise3d_abs(-(x + chunk.x() * CHUNK_WIDTH) / 512.0f, (z + chunk.z() * CHUNK_HEIGHT) / 512.0f, (y + chunk.y() * CHUNK_DEPTH) / 512.0f, 4, 0.1); - float r3 = noise3d_abs(-(x + chunk.x() * CHUNK_WIDTH) / 512.0f, (z + chunk.z() * CHUNK_HEIGHT) / 128.0f, (y + chunk.y() * CHUNK_DEPTH) / 512.0f, 4, 1); - float r4 = n2 * 5 + r2 * r3 * 20; - if (r4 > 6 && r4 < 8 && h > SEALEVEL) { - chunk.setBlockRaw(x, y, z - 1, 0); - chunk.setBlockRaw(x, y, z, 0); - chunk.setBlockRaw(x, y, z + 1, 0); - } } if (topChunk && topChunk->isInitialized()) { diff --git a/server/source/world/TerrainGenerator.hpp b/server/source/world/TerrainGenerator.hpp index a3e7d1bb8..7d3a61f85 100644 --- a/server/source/world/TerrainGenerator.hpp +++ b/server/source/world/TerrainGenerator.hpp @@ -28,8 +28,8 @@ #define TERRAINGENERATOR_HPP_ #include - #include +#include "TerrainBiomeSampler.hpp" class ServerChunk; @@ -48,6 +48,8 @@ class TerrainGenerator { static float noise2d(double x, double y, int octaves, float persistence); static float noise3d_abs(double x, double y, double z, int octaves, float persistence); + TerrainBiomeSampler biomeSampler; + u16 m_dirtBlockID = 0; u16 m_grassBlockID = 0; u16 m_stoneBlockID = 0;