diff --git a/docs/lua-api-block.md b/docs/lua-api-block.md index 8dc311c35..97ff1ebdc 100644 --- a/docs/lua-api-block.md +++ b/docs/lua-api-block.md @@ -263,6 +263,7 @@ Useful for flora, tallgrass, mushrooms, etc... Parameters: - `pos` (`ivec3`): position of the block +- `block` (`ServerBlock`): block definition - `player` (`ServerPlayer`): player that activated the block - `world` (`ServerWorld`): instance of the world - `client` (`Client`): client that activated the block @@ -290,6 +291,7 @@ Parameters: Parameters: - `pos` (`ivec3`): position of the block +- `block` (`ServerBlock`): block definition - `chunk` (`ServerChunk`): current chunk - `world` (`ServerWorld`): instance of the world diff --git a/docs/lua-api-core.md b/docs/lua-api-core.md index 4c458d523..e92cbc71f 100644 --- a/docs/lua-api-core.md +++ b/docs/lua-api-core.md @@ -35,8 +35,8 @@ end) Possible events: -- `BlockPlaced`: `funcion(pos, block, player, world, client, server)` -- `BlockDigged`: `funcion(pos, block, player, world, client, server)` +- `BlockPlaced`: `funcion(pos, block_state, player, world, client, server)` +- `BlockDigged`: `funcion(pos, block_state, player, world, client, server)` - `BlockActivated`: `function(pos, block, player, world, client, server)` - `ItemActivated`: `function(pos, block, player, world, client, server)` - `PlayerConnected`: `function(pos, player, client, server)` diff --git a/docs/lua-api-cpp.md b/docs/lua-api-cpp.md index 229bb4b12..2059261eb 100644 --- a/docs/lua-api-cpp.md +++ b/docs/lua-api-cpp.md @@ -5,13 +5,10 @@ ## Block - `u16 id()` -- `u16 data()` - `string string_id()` -- `string label()` - `string mod_name()` -- `bool is_opaque()` -- `ItemStack get_item_drop()` - `BlockParam param()` +- `Block get_state()` ## BlockData diff --git a/mods/default/blocks.lua b/mods/default/blocks.lua index bd641366d..6429cab69 100644 --- a/mods/default/blocks.lua +++ b/mods/default/blocks.lua @@ -208,7 +208,7 @@ mod:block { draw_type = "glass", is_opaque = false, - on_block_activated = function(pos, player, world, client, server, screen_width, screen_height, gui_scale) + on_block_activated = function(pos, block, player, world, client, server, screen_width, screen_height, gui_scale) local dim = (player:dimension() + 1) % 2 local pos = { x = math.floor(player:x()), @@ -311,7 +311,7 @@ mod:block { name = "Redstone Lamp", tiles = "redstone_lamp_off.png", - on_block_activated = function(pos, player, world, client, server, screen_width, screen_height, gui_scale) + on_block_activated = function(pos, block, player, world, client, server, screen_width, screen_height, gui_scale) local block = openminer.registry:get_block_from_string("default:redstone_lamp_on") world:set_block(pos.x, pos.y, pos.z, block:id()) end @@ -327,7 +327,7 @@ mod:block { ci_ignore = 1 }, - on_block_activated = function(pos, player, world, client, server, screen_width, screen_height, gui_scale) + on_block_activated = function(pos, block, player, world, client, server, screen_width, screen_height, gui_scale) local block = openminer.registry:get_block_from_string("default:redstone_lamp_off") world:set_block(pos.x, pos.y, pos.z, block:id()) end @@ -356,11 +356,22 @@ mod:block { id = "seeds", name = "Seeds", tiles = "wheat_stage_0.png", - alt_tiles = "wheat_stage_7.png", draw_type = "xshape", inventory_image = "seeds_wheat.png", hardness = 0, + bounding_box = {0, 0, 0, 1, 1, 1.0 / 16.0}, + + states = { + [1] = { tiles = "wheat_stage_1.png", bounding_box = {0, 0, 0, 1, 1, 3.0 / 16.0}, }, + [2] = { tiles = "wheat_stage_2.png", bounding_box = {0, 0, 0, 1, 1, 5.0 / 16.0}, }, + [3] = { tiles = "wheat_stage_3.png", bounding_box = {0, 0, 0, 1, 1, 8.0 / 16.0}, }, + [4] = { tiles = "wheat_stage_4.png", bounding_box = {0, 0, 0, 1, 1, 10.0 / 16.0}, }, + [5] = { tiles = "wheat_stage_5.png", bounding_box = {0, 0, 0, 1, 1, 12.0 / 16.0}, }, + [6] = { tiles = "wheat_stage_6.png", bounding_box = {0, 0, 0, 1, 1, 14.0 / 16.0}, }, + [7] = { tiles = "wheat_stage_7.png", bounding_box = {0, 0, 0, 1, 1, 1}, }, + }, + tick_randomly = true, tick_probability = 0.01, @@ -368,25 +379,20 @@ mod:block { world:add_block_data(pos.x, pos.y, pos.z, 0, 0) end, - on_tick = function(pos, chunk, world) - local data = world:get_block_data(pos.x, pos.y, pos.z) - if not data then return end - - local growth_stage = data.meta:get_int("growth_stage") or 0 - if growth_stage < 7 then - data.use_alt_tiles = true - data.meta:set_int("growth_stage", 7) + on_tick = function(pos, block, chunk, world) + local block_param = world:get_block_param(pos.x, pos.y, pos.z) + local current_state = block:param():get_param(BlockParam.State, block_param) + if current_state < 7 then + world:set_block_param(pos.x, pos.y, pos.z, current_state + 1) end end, - on_block_activated = function(pos, player, world, client, server, screen_width, screen_height, gui_scale) - local data = world:get_block_data(pos.x, pos.y, pos.z) - if not data then return end - - local growth_stage = data.meta:get_int("growth_stage") or 0 - if growth_stage >= 7 then - data.use_alt_tiles = false - data.meta:set_int("growth_stage", 0) + on_block_activated = function(pos, block, player, world, client, server, screen_width, screen_height, gui_scale) + local block_param = world:get_block_param(pos.x, pos.y, pos.z) + local current_state = block:param():get_param(BlockParam.State, block_param) + if current_state >= 7 then + world:set_block_param(pos.x, pos.y, pos.z, + block:param():set_param(BlockParam.State, block_param, 0)) -- FIXME: It should drop the item if 'default:use_item_drops' is enabled local item_stack = ItemStack.new("default:wheat", 1) diff --git a/mods/default/blocks/door.lua b/mods/default/blocks/door.lua index cf4fa9699..ecd9eab44 100644 --- a/mods/default/blocks/door.lua +++ b/mods/default/blocks/door.lua @@ -66,7 +66,7 @@ mod:block { end end, - on_block_activated = function(pos, player, world, client, server, screen_width, screen_height, gui_scale) + on_block_activated = function(pos, block, player, world, client, server, screen_width, screen_height, gui_scale) open_door(pos, world) open_door({x = pos.x, y = pos.y, z = pos.z - 1}, world) end diff --git a/mods/default/blocks/furnace.lua b/mods/default/blocks/furnace.lua index 117e46ca8..776eba4d4 100644 --- a/mods/default/blocks/furnace.lua +++ b/mods/default/blocks/furnace.lua @@ -37,7 +37,7 @@ mod:block { world:add_block_data(pos.x, pos.y, pos.z, 3, 1) end, - on_block_activated = function(pos, player, world, client, server, screen_width, screen_height, gui_scale) + on_block_activated = function(pos, block, player, world, client, server, screen_width, screen_height, gui_scale) local gui = LuaGUI.new() gui:set_size(176, 166) @@ -169,7 +169,7 @@ mod:block { gui:show(client) end, - on_tick = function(pos, chunk, world) + on_tick = function(pos, block, chunk, world) local data = world:get_block_data(pos.x, pos.y, pos.z) if not data then return end diff --git a/mods/default/blocks/workbench.lua b/mods/default/blocks/workbench.lua index 4f4b609f2..ec846ae8c 100644 --- a/mods/default/blocks/workbench.lua +++ b/mods/default/blocks/workbench.lua @@ -35,7 +35,7 @@ mod:block { world:add_block_data(pos.x, pos.y, pos.z, 3, 3) end, - on_block_activated = function(pos, player, world, client, server, screen_width, screen_height, gui_scale) + on_block_activated = function(pos, block, player, world, client, server, screen_width, screen_height, gui_scale) local gui = LuaGUI.new() gui:set_size(176, 166) diff --git a/source/client/graphics/TextureAtlas.cpp b/source/client/graphics/TextureAtlas.cpp index 168df5bce..5196e2d1f 100644 --- a/source/client/graphics/TextureAtlas.cpp +++ b/source/client/graphics/TextureAtlas.cpp @@ -136,11 +136,13 @@ void TextureAtlas::loadFromRegistry(const std::string &texturePack) { else path = "texturepacks/" + texturePack + "/blocks/"; - const TilesDef &tiles = block->tiles(); - for (auto &textureFilename : tiles.textureFilenames()) - addFile(path, textureFilename); - for (auto &textureFilename : tiles.altTextureFilenames()) - addFile(path, textureFilename); + for (auto &state : block->states()) { + const TilesDef &tiles = state.tiles(); + for (auto &textureFilename : tiles.textureFilenames()) + addFile(path, textureFilename); + for (auto &textureFilename : tiles.altTextureFilenames()) + addFile(path, textureFilename); + } } for (auto &item : Registry::getInstance().items()) { diff --git a/source/client/gui/InventoryCube.cpp b/source/client/gui/InventoryCube.cpp index 2cd795992..141526e66 100644 --- a/source/client/gui/InventoryCube.cpp +++ b/source/client/gui/InventoryCube.cpp @@ -58,9 +58,11 @@ InventoryCube::InventoryCube(float size, bool isEntity) using namespace BlockGeometry; -void InventoryCube::updateVertexBuffer(const Block &block) { +void InventoryCube::updateVertexBuffer(const Block &block, u8 state) { if (!block.id()) return; + const BlockState &blockState = block.getState(state); + Vertex vertices[nFaces][nVertsPerFace]; glm::vec3 vertexPos[nVertsPerCube] { @@ -75,7 +77,7 @@ void InventoryCube::updateVertexBuffer(const Block &block) { glm::vec3{m_size, m_size, m_size}, }; - const gk::FloatBox &boundingBox = block.boundingBox(); + const gk::FloatBox &boundingBox = blockState.boundingBox(); constexpr s8f faceValue[nFaces]{2, 2, 4, 4, 3, 3}; @@ -86,7 +88,7 @@ void InventoryCube::updateVertexBuffer(const Block &block) { // U0V0 -> 3 2 <- U1V0 // U0V1 -> 0 1 <- U1V1 float U0, V0, U1, V1; - if (block.drawType() == BlockDrawType::Cactus) { + if (blockState.drawType() == BlockDrawType::Cactus) { U0 = 0.f; V0 = 0.f; U1 = 1.f; @@ -101,10 +103,10 @@ void InventoryCube::updateVertexBuffer(const Block &block) { V1 = (f <= 3) ? 1.f - boundingBox.z : (f == 4) ? boundingBox.y + boundingBox.sizeY : 1.f - boundingBox.y; } - const gk::FloatRect &blockTexCoords = m_textureAtlas->getTexCoords(block.tiles().getTextureForFace(f)); + const gk::FloatRect &blockTexCoords = m_textureAtlas->getTexCoords(blockState.tiles().getTextureForFace(f)); for (u8f v = 0; v < nVertsPerFace; ++v) { - if (block.drawType() == BlockDrawType::Cactus) { + if (blockState.drawType() == BlockDrawType::Cactus) { vertices[f][v].coord3d[0] = vertexPos[cubeVerts[f][v]].x - boundingBox.x * faceNormals[f][0] * m_size; vertices[f][v].coord3d[1] = vertexPos[cubeVerts[f][v]].y - boundingBox.y * faceNormals[f][1] * m_size; vertices[f][v].coord3d[2] = vertexPos[cubeVerts[f][v]].z - boundingBox.z * faceNormals[f][2] * m_size; @@ -121,7 +123,7 @@ void InventoryCube::updateVertexBuffer(const Block &block) { vertices[f][v].texCoord[0] = gk::qlerp(blockTexCoords.x, blockTexCoords.x + blockTexCoords.sizeX, U); vertices[f][v].texCoord[1] = gk::qlerp(blockTexCoords.y, blockTexCoords.y + blockTexCoords.sizeY, V); - const gk::Color &colorMultiplier = block.colorMultiplier(); + const gk::Color &colorMultiplier = blockState.colorMultiplier(); vertices[f][v].color[0] = colorMultiplier.r; vertices[f][v].color[1] = colorMultiplier.g; vertices[f][v].color[2] = colorMultiplier.b; diff --git a/source/client/gui/InventoryCube.hpp b/source/client/gui/InventoryCube.hpp index 51c198f4c..a5cb0fe3e 100644 --- a/source/client/gui/InventoryCube.hpp +++ b/source/client/gui/InventoryCube.hpp @@ -38,7 +38,7 @@ class InventoryCube : public gk::Drawable, public gk::Transformable { public: InventoryCube(float size = 1.0f, bool isEntity = false); - void updateVertexBuffer(const Block &block); + void updateVertexBuffer(const Block &block, u8 state = 0); float size() const { return m_size; } diff --git a/source/client/gui/ItemWidget.cpp b/source/client/gui/ItemWidget.cpp index a3e02c018..f85e0e142 100644 --- a/source/client/gui/ItemWidget.cpp +++ b/source/client/gui/ItemWidget.cpp @@ -42,12 +42,13 @@ ItemWidget::ItemWidget(Inventory &inventory, u16 x, u16 y, Widget *parent) void ItemWidget::update() { if (stack().item().isBlock()) { const Block &block = Registry::getInstance().getBlock(stack().item().id()); - if (block.drawType() != BlockDrawType::XShape && block.inventoryImage().empty()) { + const BlockState &blockState = block.getState(0); // FIXME: Get state from item stack + if (blockState.drawType() != BlockDrawType::XShape && blockState.inventoryImage().empty()) { m_cube.updateVertexBuffer(block); m_isImage = false; } else - updateImage(&block); + updateImage(&blockState); } else updateImage(); @@ -56,7 +57,7 @@ void ItemWidget::update() { m_text.setPosition(16 - 4 - 6 * floor(log10(stack().amount())), 16 - 6, 0); } -void ItemWidget::updateImage(const Block *block) { +void ItemWidget::updateImage(const BlockState *blockState) { if (m_image.width() == 0) { m_image.load(m_textureAtlas.texture()); m_image.setPosition(1, 1, 0); @@ -67,8 +68,8 @@ void ItemWidget::updateImage(const Block *block) { m_image.setClipRect(clipRect.x, clipRect.y, clipRect.sizeX, clipRect.sizeY); m_image.setScale(16.0f / clipRect.sizeX, 16.0f / clipRect.sizeY); - if (block) - m_image.setColor(block->colorMultiplier()); + if (blockState) + m_image.setColor(blockState->colorMultiplier()); m_isImage = true; } diff --git a/source/client/gui/ItemWidget.hpp b/source/client/gui/ItemWidget.hpp index 1d6b1f41b..16b0cc087 100644 --- a/source/client/gui/ItemWidget.hpp +++ b/source/client/gui/ItemWidget.hpp @@ -34,12 +34,14 @@ #include "Text.hpp" #include "Widget.hpp" +class BlockState; + class ItemWidget : public Widget { public: ItemWidget(Inventory &inventory, u16 x, u16 y, Widget *parent = nullptr); void update() override; - void updateImage(const Block *block = nullptr); + void updateImage(const BlockState *blockState = nullptr); const ItemStack &stack() const { return m_inventory.getStack(m_x, m_y); } void setStack(const std::string &name, unsigned int amount = 1); diff --git a/source/client/hud/BlockCursor.cpp b/source/client/hud/BlockCursor.cpp index d74b28903..fddc01e25 100644 --- a/source/client/hud/BlockCursor.cpp +++ b/source/client/hud/BlockCursor.cpp @@ -95,12 +95,14 @@ void BlockCursor::onEvent(const SDL_Event &event, const Hotbar &hotbar) { if (face == 5) --z; // First, we check if the new block is not replacing another block - u32 blockId = m_world.getBlock(x, y, z); - const Block &block = Registry::getInstance().getBlock(blockId); - if (!blockId || block.drawType() == BlockDrawType::Liquid) { + // u32 blockId = m_world.getBlock(x, y, z); + // const Block &block = Registry::getInstance().getBlock(blockId); + const BlockState *blockState = m_world.getBlockState(x, y, z); + if (blockState && (!blockState->block().id() || blockState->drawType() == BlockDrawType::Liquid)) { // Second, we check if the new block is not inside the player const Block &newBlock = Registry::getInstance().getBlock(hotbar.currentItem().id()); - gk::FloatBox boundingBox = newBlock.boundingBox() + gk::Vector3f(x - m_player.x(), y - m_player.y(), z - m_player.z()); + const BlockState &newBlockState = newBlock.getState(0); // FIXME: Get state from item stack + gk::FloatBox boundingBox = newBlockState.boundingBox() + gk::Vector3f(x - m_player.x(), y - m_player.y(), z - m_player.z()); gk::FloatBox playerBoundingBox = m_player.hitbox(); if (!boundingBox.intersects(playerBoundingBox)) { u32 block = hotbar.currentItem().id(); @@ -137,12 +139,14 @@ void BlockCursor::update(const Hotbar &hotbar) { m_selectedBlock = selectedBlock; - m_currentBlock = &Registry::getInstance().getBlock(m_world.getBlock(m_selectedBlock.x, m_selectedBlock.y, m_selectedBlock.z)); + m_currentBlock = m_world.getBlockState(m_selectedBlock.x, m_selectedBlock.y, m_selectedBlock.z); // if (block.boundingBox().intersects(FloatBox{m_selectedBlock.x, m_selectedBlock.y, m_selectedBlock.z, 1, 1, 1})) { // selectedBlockChanged = false; // m_selectedBlock.w = -1; // } + if (!m_currentBlock) return; + u32 ticks = gk::GameClock::getInstance().getTicks(); if (selectedBlockChanged) @@ -167,7 +171,7 @@ void BlockCursor::update(const Hotbar &hotbar) { } } - u8f orientation = m_currentBlock->isRotatable() ? m_world.getData(selectedBlock.x, selectedBlock.y, selectedBlock.z) & 0x1F : 0; + u8f orientation = m_currentBlock->block().isRotatable() ? m_world.getData(selectedBlock.x, selectedBlock.y, selectedBlock.z) & 0x1F : 0; if (m_selectedBlock.w != -1) updateVertexBuffer(*m_currentBlock, orientation); @@ -181,11 +185,11 @@ void BlockCursor::update(const Hotbar &hotbar) { using namespace BlockGeometry; -void BlockCursor::updateVBOCoords(Vertex vertices[nFaces][nVertsPerFace], const Block &block, +void BlockCursor::updateVBOCoords(Vertex vertices[nFaces][nVertsPerFace], const BlockState &blockState, float face, u8f orientation) { - glm::vec3 bottomLeft{block.boundingBox().x, block.boundingBox().y, block.boundingBox().z}; - glm::vec3 topRight{block.boundingBox().sizeX, block.boundingBox().sizeY, block.boundingBox().sizeZ}; + glm::vec3 bottomLeft{blockState.boundingBox().x, blockState.boundingBox().y, blockState.boundingBox().z}; + glm::vec3 topRight{blockState.boundingBox().sizeX, blockState.boundingBox().sizeY, blockState.boundingBox().sizeZ}; topRight += bottomLeft; const glm::mat3 &orientMatrix = orientMatrices[orientation]; @@ -220,18 +224,18 @@ void BlockCursor::updateVBOCoords(Vertex vertices[nFaces][nVertsPerFace], const } } -void BlockCursor::updateVertexBuffer(const Block &block, u8f orientation) { +void BlockCursor::updateVertexBuffer(const BlockState &blockState, u8f orientation) { Vertex vertices[nFaces][nVertsPerFace]; - updateVBOCoords(vertices, block, -1, orientation); + updateVBOCoords(vertices, blockState, -1, orientation); gk::VertexBuffer::bind(&m_vbo); m_vbo.setData(sizeof(vertices), vertices, GL_DYNAMIC_DRAW); gk::VertexBuffer::bind(nullptr); } -void BlockCursor::updateAnimationVertexBuffer(const Block &block, u8f orientation, int animationPos) { +void BlockCursor::updateAnimationVertexBuffer(const BlockState &blockState, u8f orientation, int animationPos) { Vertex vertices[nFaces][nVertsPerFace]; - updateVBOCoords(vertices, block, -2, orientation); + updateVBOCoords(vertices, blockState, -2, orientation); GLfloat color[4] = {1, 1, 1, 0.5}; for (u8f f = 0; f < nFaces; ++f) @@ -301,9 +305,8 @@ glm::ivec4 BlockCursor::findSelectedBlock() const { s32f bestZ = s32f(floor(position.z)); // Deal with a degenerate case: camera in the middle of a block - u32f blockID = m_world.getBlock(bestX, bestY, bestZ); - const Block &block = Registry::getInstance().getBlock(blockID); - if (blockID && block.drawType() != BlockDrawType::Liquid) { + const BlockState *blockState = m_world.getBlockState(bestX, bestY, bestZ); + if (blockState && blockState->block().id() && blockState->drawType() != BlockDrawType::Liquid) { // We're inside a node, therefore there's no face, but we still need // to return a valid block. We use face 6 for that. For rightclicks, // it should attempt to place the block on the same node clicked, and @@ -320,9 +323,8 @@ glm::ivec4 BlockCursor::findSelectedBlock() const { if (double(bestX) == position.x && double(bestY) == position.y) { for (int y = -1; y <= 0; y++) { for (int x = -1; x <= 0; x++) { - blockID = m_world.getBlock(bestX + x, bestY + y, bestZ); - const Block &block = Registry::getInstance().getBlock(blockID); - if (blockID && block.drawType() != BlockDrawType::Liquid) { + const BlockState *blockState = m_world.getBlockState(bestX, bestY, bestZ); + if (blockState && blockState->block().id() && blockState->drawType() != BlockDrawType::Liquid) { return glm::ivec4{bestX + x, bestY + y, bestZ, 6}; } } diff --git a/source/client/hud/BlockCursor.hpp b/source/client/hud/BlockCursor.hpp index 81a1cb1d5..83d580eee 100644 --- a/source/client/hud/BlockCursor.hpp +++ b/source/client/hud/BlockCursor.hpp @@ -45,13 +45,13 @@ class BlockCursor : public gk::Drawable { void update(const Hotbar &hotbar); - const Block *currentBlock() const { return m_currentBlock; } + const BlockState *currentBlock() const { return m_currentBlock; } private: - void updateVertexBuffer(const Block &block, const u8f orientation); - void updateAnimationVertexBuffer(const Block &block, const u8f orientation, int animationPos = -1); + void updateVertexBuffer(const BlockState &blockState, const u8f orientation); + void updateAnimationVertexBuffer(const BlockState &blockState, const u8f orientation, int animationPos = -1); void updateVBOCoords(Vertex vertices[BlockGeometry::nFaces][BlockGeometry::nVertsPerFace], - const Block &block, float face, u8f orientation); + const BlockState &blockState, float face, u8f orientation); void draw(gk::RenderTarget &target, gk::RenderStates states) const override; @@ -66,7 +66,7 @@ class BlockCursor : public gk::Drawable { unsigned int m_animationStart = 0; glm::ivec4 m_selectedBlock{0, 0, 0, -1}; - const Block *m_currentBlock = nullptr; + const BlockState *m_currentBlock = nullptr; const ItemStack *m_currentTool = nullptr; gk::Texture *m_blockDestroyTexture = nullptr; diff --git a/source/client/hud/BlockInfoWidget.cpp b/source/client/hud/BlockInfoWidget.cpp index 3c0e0598e..ec6b32cb2 100644 --- a/source/client/hud/BlockInfoWidget.cpp +++ b/source/client/hud/BlockInfoWidget.cpp @@ -40,16 +40,16 @@ void BlockInfoWidget::update() { m_itemWidget.update(); } -void BlockInfoWidget::setCurrentBlock(const Block *block) { - m_currentBlock = block; +void BlockInfoWidget::setCurrentBlock(const BlockState *blockState) { + m_currentBlock = blockState; if (!m_currentBlock) m_isVisible = false; else { m_isVisible = true; - m_text.setString(block->label()); - m_itemWidget.setStack(block->stringID(), 1); + m_text.setString(blockState->label()); + m_itemWidget.setStack(blockState->block().stringID(), 1); } } diff --git a/source/client/hud/BlockInfoWidget.hpp b/source/client/hud/BlockInfoWidget.hpp index 7312d6a41..5af148858 100644 --- a/source/client/hud/BlockInfoWidget.hpp +++ b/source/client/hud/BlockInfoWidget.hpp @@ -37,8 +37,8 @@ class BlockInfoWidget : public Widget { void update() override; - const Block *currentBlock() const { return m_currentBlock; } - void setCurrentBlock(const Block *block); + const BlockState *currentBlock() const { return m_currentBlock; } + void setCurrentBlock(const BlockState *blockState); private: void draw(gk::RenderTarget &target, gk::RenderStates states) const override; @@ -51,7 +51,7 @@ class BlockInfoWidget : public Widget { bool m_isVisible = false; - const Block *m_currentBlock = nullptr; + const BlockState *m_currentBlock = nullptr; }; #endif // BLOCKINFOWIDGET_HPP_ diff --git a/source/client/math/BlockCursorRaycast.cpp b/source/client/math/BlockCursorRaycast.cpp index 155697e37..08e2d2e74 100644 --- a/source/client/math/BlockCursorRaycast.cpp +++ b/source/client/math/BlockCursorRaycast.cpp @@ -117,12 +117,12 @@ void BlockCursorRaycast::rayCastToAxis(const Axis axis, const glm::dvec3 &positi ny = axis == AXIS_Y ? nodeRow : floor(isect.y); nz = axis == AXIS_Z ? nodeRow : floor(isect.z); - u32 blockID = world.getBlock(nx, ny, nz); - const Block &block = Registry::getInstance().getBlock(blockID); + const BlockState *blockState = world.getBlockState(nx, ny, nz); + if (!blockState) continue; - u8f orientation = block.isRotatable() ? world.getData(nx, ny, nz) & 0x1F : 0; + u8f orientation = blockState->block().isRotatable() ? world.getData(nx, ny, nz) & 0x1F : 0; - const gk::FloatBox &boundingBox = block.boundingBox(); + const gk::FloatBox &boundingBox = blockState->boundingBox(); glm::vec3 localCorner1{boundingBox.x, boundingBox.y, boundingBox.z}; glm::vec3 localCorner2{boundingBox.sizeX, boundingBox.sizeY, boundingBox.sizeZ}; localCorner2 += localCorner1; @@ -136,7 +136,7 @@ void BlockCursorRaycast::rayCastToAxis(const Axis axis, const glm::dvec3 &positi if (localCorner2.z < localCorner1.z) std::swap(localCorner1.z, localCorner2.z); } - if (blockID && block.drawType() != BlockDrawType::Liquid) { + if (blockState->block().id() && blockState->drawType() != BlockDrawType::Liquid) { // Check bounding box; this should loop over all selection boxes // when they are implemented const glm::dvec3 cubePos{double(nx), double(ny), double(nz)}; diff --git a/source/client/world/ChunkBuilder.cpp b/source/client/world/ChunkBuilder.cpp index 2c152961a..f663e3367 100644 --- a/source/client/world/ChunkBuilder.cpp +++ b/source/client/world/ChunkBuilder.cpp @@ -43,22 +43,26 @@ std::array ChunkBuilder::buildChunk(const Cli for (s8f z = 0 ; z < CHUNK_HEIGHT ; z++) { for (s8f y = 0 ; y < CHUNK_DEPTH ; y++) { for (s8f x = 0 ; x < CHUNK_WIDTH ; x++) { - const Block &block = Registry::getInstance().getBlock(chunk.getBlock(x, y, z)); - if (!block.id()) continue; - if (!chunk.getBlock(x, y, z)) continue; + u16 blockID = chunk.getBlock(x, y, z); + const Block &block = Registry::getInstance().getBlock(blockID); + if (!blockID) continue; - const gk::FloatBox &boundingBox = block.boundingBox(); + u16 blockParam = chunk.getData(x, y, z); + const BlockState &blockState = block.getState(block.param().hasParam(BlockParam::State) + ? block.param().getParam(BlockParam::State, blockParam) : 0); + + const gk::FloatBox &boundingBox = blockState.boundingBox(); u8f orientation = block.isRotatable() - ? block.param().getParam(BlockParam::Rotation, chunk.getData(x, y, z)) : 0; + ? block.param().getParam(BlockParam::Rotation, blockParam) : 0; const glm::mat3 &orientMatrix = orientMatrices[orientation]; - if (block.drawType() == BlockDrawType::Solid - || block.drawType() == BlockDrawType::Leaves - || block.drawType() == BlockDrawType::Liquid - || block.drawType() == BlockDrawType::Glass - || block.drawType() == BlockDrawType::Cactus - || block.drawType() == BlockDrawType::BoundingBox) + if (blockState.drawType() == BlockDrawType::Solid + || blockState.drawType() == BlockDrawType::Leaves + || blockState.drawType() == BlockDrawType::Liquid + || blockState.drawType() == BlockDrawType::Glass + || blockState.drawType() == BlockDrawType::Cactus + || blockState.drawType() == BlockDrawType::BoundingBox) { glm::vec3 vertexPos[nVertsPerCube]{ // Order is important. It matches the bit order defined in BlockGeometry::cubeVerts. @@ -72,7 +76,7 @@ std::array ChunkBuilder::buildChunk(const Cli {boundingBox.x + boundingBox.sizeX, boundingBox.y + boundingBox.sizeY, boundingBox.z + boundingBox.sizeZ}, }; - if (block.drawType() == BlockDrawType::Cactus) { + if (blockState.drawType() == BlockDrawType::Cactus) { // Ignore bounding box, initialize it to full node coordinates for (u8f i = 0; i < nVertsPerCube; ++i) { vertexPos[i].x = (i >> 0) & 1; @@ -115,10 +119,10 @@ std::array ChunkBuilder::buildChunk(const Cli const gk::Vector3i *vFaceNeighbours[nVertsPerFace]{&corner0, &corner1, &corner2, &corner3}; - addFace(x, y, z, f, chunk, block, normal, faceVerts, vFaceNeighbours); + addFace(x, y, z, f, chunk, blockState, normal, faceVerts, vFaceNeighbours); } } - else if (block.drawType() == BlockDrawType::XShape) { + else if (blockState.drawType() == BlockDrawType::XShape) { glm::vec3 vertexPos[nVertsPerCube]{ {0, 0, 0}, {1, 0, 0}, @@ -135,7 +139,7 @@ std::array ChunkBuilder::buildChunk(const Cli {&vertexPos[crossVerts[1][0]], &vertexPos[crossVerts[1][1]], &vertexPos[crossVerts[1][2]], &vertexPos[crossVerts[1][3]]}, }; - addCross(x, y, z, chunk, block, faceVertices); + addCross(x, y, z, chunk, blockState, faceVertices); } } } @@ -157,26 +161,25 @@ std::array ChunkBuilder::buildChunk(const Cli return verticesCount; } -inline void ChunkBuilder::addFace(s8f x, s8f y, s8f z, s8f f, const ClientChunk &chunk, const Block &block, +inline void ChunkBuilder::addFace(s8f x, s8f y, s8f z, s8f f, const ClientChunk &chunk, const BlockState &blockState, const gk::Vector3i &normal, const glm::vec3 *const vertexPos[nVertsPerFace], const gk::Vector3i *const neighbourOfs[nVertsPerFace]) { // Get surrounding block for the face - u16 surroundingBlockID = chunk.getBlock(x + normal.x, y + normal.y, z + normal.z); - const Block *surroundingBlock = &Registry::getInstance().getBlock(surroundingBlockID); + const BlockState *surroundingBlockState = chunk.getBlockState(x + normal.x, y + normal.y, z + normal.z); // Skip hidden faces - if (surroundingBlock && surroundingBlock->id() - && ((block.drawType() == BlockDrawType::Solid && surroundingBlock->drawType() == BlockDrawType::Solid && surroundingBlock->isOpaque()) - || (block.id() == surroundingBlock->id() && (block.drawType() == BlockDrawType::Liquid || block.drawType() == BlockDrawType::Glass)) - || (block.drawType() == BlockDrawType::Liquid && surroundingBlock->drawType() == BlockDrawType::Solid) - || (block.drawType() == BlockDrawType::Cactus && surroundingBlock->id() == block.id() && f > 3))) + if (surroundingBlockState && surroundingBlockState->block().id() + && ((blockState.drawType() == BlockDrawType::Solid && surroundingBlockState->drawType() == BlockDrawType::Solid && surroundingBlockState->isOpaque()) + || (blockState.block().id() == surroundingBlockState->block().id() && (blockState.drawType() == BlockDrawType::Liquid || blockState.drawType() == BlockDrawType::Glass)) + || (blockState.drawType() == BlockDrawType::Liquid && surroundingBlockState->drawType() == BlockDrawType::Solid) + || (blockState.drawType() == BlockDrawType::Cactus && surroundingBlockState->block().id() == blockState.block().id() && f > 3))) return; - const gk::FloatBox &boundingBox = block.boundingBox(); + const gk::FloatBox &boundingBox = blockState.boundingBox(); const BlockData *blockData = chunk.getBlockData(x, y, z); - const std::string &texture = block.tiles().getTextureForFace(f, blockData ? blockData->useAltTiles : false); + const std::string &texture = blockState.tiles().getTextureForFace(f, blockData ? blockData->useAltTiles : false); const gk::FloatRect &blockTexCoords = m_textureAtlas.getTexCoords(texture); // Calculate UV's @@ -185,7 +188,7 @@ inline void ChunkBuilder::addFace(s8f x, s8f y, s8f z, s8f f, const ClientChunk // U0V0 -> 3 2 <- U1V0 // U0V1 -> 0 1 <- U1V1 float U0, V0, U1, V1; - if (block.drawType() == BlockDrawType::Cactus) { + if (blockState.drawType() == BlockDrawType::Cactus) { U0 = 0.f; V0 = 0.f; U1 = 1.f; @@ -203,14 +206,14 @@ inline void ChunkBuilder::addFace(s8f x, s8f y, s8f z, s8f f, const ClientChunk // Prepare vertex information for VBO Vertex vertices[nVertsPerFace]; for (s8f v = 0; v < nVertsPerFace; ++v) { - if (block.drawType() == BlockDrawType::Cactus) { + if (blockState.drawType() == BlockDrawType::Cactus) { vertices[v].coord3d[0] = x + vertexPos[v]->x - boundingBox.x * normal.x; vertices[v].coord3d[1] = y + vertexPos[v]->y - boundingBox.y * normal.y; vertices[v].coord3d[2] = z + vertexPos[v]->z - boundingBox.z * normal.z; } else { float blockHeight = vertexPos[v]->z; - if (block.drawType() == BlockDrawType::Liquid && (f != BlockFace::Bottom || !surroundingBlock || !surroundingBlock->id())) { + if (blockState.drawType() == BlockDrawType::Liquid && (f != BlockFace::Bottom || !surroundingBlockState || !surroundingBlockState->block().id())) { if (f == BlockFace::Bottom) blockHeight = vertexPos[v]->z - 2.f / 16.f; else @@ -228,7 +231,7 @@ inline void ChunkBuilder::addFace(s8f x, s8f y, s8f z, s8f f, const ClientChunk vertices[v].normal[1] = normal.y; vertices[v].normal[2] = normal.z; - const gk::Color colorMultiplier = block.colorMultiplier(); + const gk::Color colorMultiplier = blockState.colorMultiplier(); vertices[v].color[0] = colorMultiplier.r; vertices[v].color[1] = colorMultiplier.g; vertices[v].color[2] = colorMultiplier.b; @@ -239,13 +242,13 @@ inline void ChunkBuilder::addFace(s8f x, s8f y, s8f z, s8f f, const ClientChunk vertices[v].texCoord[0] = gk::qlerp(blockTexCoords.x, blockTexCoords.x + blockTexCoords.sizeX, U); vertices[v].texCoord[1] = gk::qlerp(blockTexCoords.y, blockTexCoords.y + blockTexCoords.sizeY, V); - if (Config::isSmoothLightingEnabled && block.drawType() != BlockDrawType::Liquid) + if (Config::isSmoothLightingEnabled && blockState.drawType() != BlockDrawType::Liquid) vertices[v].lightValue[0] = getLightForVertex(Light::Sun, x, y, z, *neighbourOfs[v], normal, chunk); else vertices[v].lightValue[0] = chunk.lightmap().getSunlight(x + normal.x, y + normal.y, z + normal.z); int torchlight = chunk.lightmap().getTorchlight(x, y, z); - if (Config::isSmoothLightingEnabled && torchlight == 0 && block.drawType() != BlockDrawType::Liquid) + if (Config::isSmoothLightingEnabled && torchlight == 0 && blockState.drawType() != BlockDrawType::Liquid) vertices[v].lightValue[1] = getLightForVertex(Light::Torch, x, y, z, *neighbourOfs[v], normal, chunk); else vertices[v].lightValue[1] = chunk.lightmap().getTorchlight(x + normal.x, y + normal.y, z + normal.z); @@ -257,11 +260,11 @@ inline void ChunkBuilder::addFace(s8f x, s8f y, s8f z, s8f f, const ClientChunk if (!Config::isAmbientOcclusionEnabled || Config::isSmoothLightingEnabled) vertices[v].ambientOcclusion = 5; - if (block.drawType() == BlockDrawType::Liquid) + if (blockState.drawType() == BlockDrawType::Liquid) m_vertices[Layer::Liquid].emplace_back(vertices[v]); - else if (block.drawType() == BlockDrawType::Glass) + else if (blockState.drawType() == BlockDrawType::Glass) m_vertices[Layer::Glass].emplace_back(vertices[v]); - else if (block.colorMultiplier() != gk::Color::White) + else if (blockState.colorMultiplier() != gk::Color::White) m_vertices[Layer::NoMipMap].emplace_back(vertices[v]); else m_vertices[Layer::Solid].emplace_back(vertices[v]); @@ -286,9 +289,9 @@ inline void ChunkBuilder::addFace(s8f x, s8f y, s8f z, s8f f, const ClientChunk } } -inline void ChunkBuilder::addCross(s8f x, s8f y, s8f z, const ClientChunk &chunk, const Block &block, const glm::vec3 *const vertexPos[nCrossFaces][nVertsPerFace]) { +inline void ChunkBuilder::addCross(s8f x, s8f y, s8f z, const ClientChunk &chunk, const BlockState &blockState, const glm::vec3 *const vertexPos[nCrossFaces][nVertsPerFace]) { const BlockData *blockData = chunk.getBlockData(x, y, z); - const std::string &texture = block.tiles().getTextureForFace(0, blockData ? blockData->useAltTiles : false); + const std::string &texture = blockState.tiles().getTextureForFace(0, blockData ? blockData->useAltTiles : false); const gk::FloatRect &blockTexCoords = m_textureAtlas.getTexCoords(texture); float faceTexCoords[nVertsPerFace][nCoordsPerUV] = { @@ -310,7 +313,7 @@ inline void ChunkBuilder::addCross(s8f x, s8f y, s8f z, const ClientChunk &chunk vertices[v].normal[1] = 0; vertices[v].normal[2] = 0; - const gk::Color colorMultiplier = block.colorMultiplier(); + const gk::Color colorMultiplier = blockState.colorMultiplier(); vertices[v].color[0] = colorMultiplier.r; vertices[v].color[1] = colorMultiplier.g; vertices[v].color[2] = colorMultiplier.b; @@ -342,18 +345,18 @@ inline u8 ChunkBuilder::getAmbientOcclusion(s8f x, s8f y, s8f z, const gk::Vecto (normal.z != 0) ? offset.z : 0 }; - const Block *blocks[4] = { - &Registry::getInstance().getBlock(chunk.getBlock(x + minOffset.x, y + minOffset.y, z + offset.z)), - &Registry::getInstance().getBlock(chunk.getBlock(x + offset.x, y + minOffset.y, z + minOffset.z)), - &Registry::getInstance().getBlock(chunk.getBlock(x + minOffset.x, y + offset.y, z + minOffset.z)), - &Registry::getInstance().getBlock(chunk.getBlock(x + offset.x, y + offset.y, z + offset.z)) + const BlockState *blocks[4] = { + chunk.getBlockState(x + minOffset.x, y + minOffset.y, z + offset.z), + chunk.getBlockState(x + offset.x, y + minOffset.y, z + minOffset.z), + chunk.getBlockState(x + minOffset.x, y + offset.y, z + minOffset.z), + chunk.getBlockState(x + offset.x, y + offset.y, z + offset.z) }; bool blockPresence[4] = { - blocks[0]->id() != 0 && blocks[0]->isOpaque(), - blocks[1]->id() != 0 && blocks[1]->isOpaque(), - blocks[2]->id() != 0 && blocks[2]->isOpaque(), - blocks[3]->id() != 0 && blocks[3]->isOpaque() + blocks[0] && blocks[0]->block().id() != 0 && blocks[0]->isOpaque(), + blocks[1] && blocks[1]->block().id() != 0 && blocks[1]->isOpaque(), + blocks[2] && blocks[2]->block().id() != 0 && blocks[2]->isOpaque(), + blocks[3] && blocks[3]->block().id() != 0 && blocks[3]->isOpaque() }; bool side1 = blockPresence[(minOffset.x != 0) ? 2 : 1]; diff --git a/source/client/world/ChunkBuilder.hpp b/source/client/world/ChunkBuilder.hpp index 18427107a..c37ce0f19 100644 --- a/source/client/world/ChunkBuilder.hpp +++ b/source/client/world/ChunkBuilder.hpp @@ -37,7 +37,7 @@ #include "Vertex.hpp" -class Block; +class BlockState; class ClientChunk; class TextureAtlas; @@ -58,11 +58,11 @@ class ChunkBuilder { }; private: - void addFace(s8f x, s8f y, s8f z, s8f f, const ClientChunk &chunk, const Block &block, + void addFace(s8f x, s8f y, s8f z, s8f f, const ClientChunk &chunk, const BlockState &blockState, const gk::Vector3i &normal, const glm::vec3 *const vertexPos[4], const gk::Vector3i *const neighbourOfs[4]); - void addCross(s8f x, s8f y, s8f z, const ClientChunk &chunk, const Block &block, + void addCross(s8f x, s8f y, s8f z, const ClientChunk &chunk, const BlockState &blockState, const glm::vec3 *const vertexPos[2][4]); enum class Light { diff --git a/source/client/world/ClientPlayer.cpp b/source/client/world/ClientPlayer.cpp index 453762fdc..f1f86e16e 100644 --- a/source/client/world/ClientPlayer.cpp +++ b/source/client/world/ClientPlayer.cpp @@ -144,12 +144,11 @@ void ClientPlayer::updatePosition(const ClientWorld &world) { m_velocity.z = 0.f; // Checking to block at camera position to enable specific effects - u16 blockID = world.getBlock(m_camera.getDPosition().x, m_camera.getDPosition().y, m_camera.getDPosition().z); - const Block &block = Registry::getInstance().getBlock(blockID); - if (block.fogDepth() != 0) { + const BlockState *blockState = world.getBlockState(m_camera.getDPosition().x, m_camera.getDPosition().y, m_camera.getDPosition().z); + if (blockState && blockState->fogDepth() != 0) { GameConfig::currentScreenEffect = 1; - GameConfig::fogDepth = block.fogDepth(); - GameConfig::fogColor = block.fogColor(); + GameConfig::fogDepth = blockState->fogDepth(); + GameConfig::fogColor = blockState->fogColor(); } else { GameConfig::currentScreenEffect = 0; @@ -186,9 +185,8 @@ void ClientPlayer::checkCollisions(const ClientWorld &world) { } bool passable(const ClientWorld &world, double x, double y, double z) { - u32 blockID = world.getBlock(floor(x), floor(y), floor(z)); - const Block &block = Registry::getInstance().getBlock(blockID); - return !blockID || block.drawType() == BlockDrawType::Liquid || block.drawType() == BlockDrawType::XShape; + const BlockState *blockState = world.getBlockState(floor(x), floor(y), floor(z)); + return !blockState || !blockState->block().id() || blockState->drawType() == BlockDrawType::Liquid || blockState->drawType() == BlockDrawType::XShape; } void ClientPlayer::testPoint(const ClientWorld &world, double x, double y, double z, glm::vec3 &vel) { diff --git a/source/common/core/Registry.hpp b/source/common/core/Registry.hpp index 5920af446..674512e3e 100644 --- a/source/common/core/Registry.hpp +++ b/source/common/core/Registry.hpp @@ -47,10 +47,10 @@ class Registry : public gk::ISerializable { public: template - auto registerBlock(const TilesDef &tiles, const std::string &stringID, const std::string &label) -> typename std::enable_if::value, T&>::type { + auto registerBlock(const std::string &stringID) -> typename std::enable_if::value, T&>::type { u32 id = m_blocks.size(); m_blocksID.emplace(stringID, id); - m_blocks.emplace_back(std::make_unique(id, tiles, stringID, label)); + m_blocks.emplace_back(std::make_unique(id, stringID)); return *static_cast(m_blocks.back().get()); } diff --git a/source/common/core/TilesDef.cpp b/source/common/core/TilesDef.cpp index 2a8b9ffc7..e5a1350be 100644 --- a/source/common/core/TilesDef.cpp +++ b/source/common/core/TilesDef.cpp @@ -67,13 +67,7 @@ void TilesDef::deserialize(sf::Packet &packet) { packet >> m_textureFilenames >> m_altTextureFilenames; } -void TilesDef::loadFromLuaTable(const sol::table &table) { - if (table["tiles"].get_type() == sol::type::table) { - m_textureFilenames = table["tiles"].get>(); - } - else - m_textureFilenames.emplace_back(table["tiles"].get()); - +bool TilesDef::loadFromLuaTable(const sol::table &table) { if (table["alt_tiles"].get_type() != sol::type::none) { if (table["alt_tiles"].get_type() == sol::type::table) { m_altTextureFilenames = table["alt_tiles"].get>(); @@ -81,5 +75,17 @@ void TilesDef::loadFromLuaTable(const sol::table &table) { else m_altTextureFilenames.emplace_back(table["alt_tiles"].get()); } + + if (table["tiles"].get_type() == sol::type::table) { + m_textureFilenames = table["tiles"].get>(); + return true; + } + else if (table["tiles"].get_type() == sol::type::string) { + m_textureFilenames.emplace_back(table["tiles"].get()); + return true; + } + else { + return false; + } } diff --git a/source/common/core/TilesDef.hpp b/source/common/core/TilesDef.hpp index 9b9486682..d78a6911d 100644 --- a/source/common/core/TilesDef.hpp +++ b/source/common/core/TilesDef.hpp @@ -56,7 +56,7 @@ class TilesDef : public gk::ISerializable { void serialize(sf::Packet &packet) const override; void deserialize(sf::Packet &packet) override; - void loadFromLuaTable(const sol::table &table); + bool loadFromLuaTable(const sol::table &table); const std::vector &textureFilenames() const { return m_textureFilenames; }; const std::vector &altTextureFilenames() const { return m_altTextureFilenames; } diff --git a/source/common/world/Block.cpp b/source/common/world/Block.cpp index 872b867e4..f84cd9e37 100644 --- a/source/common/world/Block.cpp +++ b/source/common/world/Block.cpp @@ -29,50 +29,75 @@ #include "Player.hpp" #include "World.hpp" -Block::Block(u32 id, const TilesDef &tiles, const std::string &stringID, const std::string &label) { +Block::Block(u32 id, const std::string &stringID) { m_id = id; - m_tiles = tiles; - m_stringID = stringID; - m_label = label; - m_itemDrop = stringID; - m_itemDropAmount = 1; + BlockState &defaultState = addState(); + defaultState.itemDrop(stringID); + defaultState.itemDropAmount(1); +} + +const TilesDef &Block::tiles(u16 stateID) const { + if (stateID >= m_states.size()) { + gkError() << ("Failed to get tiles for block '" + m_stringID + "': State").c_str() << stateID << "doesn't exist"; + return m_states[0].tiles(); + } + + return m_states[stateID].tiles(); } void Block::serialize(sf::Packet &packet) const { - packet << u32(m_id) << m_stringID << m_label << u8(m_drawType) - << m_hardness << m_harvestRequirements << m_itemDrop << m_itemDropAmount << m_tiles - << m_boundingBox << m_isOpaque << m_isLightSource << m_canUpdate << m_canBeActivated - << m_colorMultiplier << m_isRotatable << m_inventoryImage << m_groups - << m_fogDepth << m_fogColor << m_param; + packet << u32(m_id) << m_stringID << m_canUpdate << m_canBeActivated + << m_isRotatable << m_groups << m_states << m_param; } void Block::deserialize(sf::Packet &packet) { u32 id; - u8 drawType; + packet >> id >> m_stringID >> m_canUpdate >> m_canBeActivated + >> m_isRotatable >> m_groups >> m_states >> m_param; + m_id = id; - packet >> id >> m_stringID >> m_label >> drawType - >> m_hardness >> m_harvestRequirements >> m_itemDrop >> m_itemDropAmount >> m_tiles - >> m_boundingBox >> m_isOpaque >> m_isLightSource >> m_canUpdate >> m_canBeActivated - >> m_colorMultiplier >> m_isRotatable >> m_inventoryImage >> m_groups - >> m_fogDepth >> m_fogColor >> m_param; + for (auto &it : m_states) { + it.setBlock(this); + it.setDefaultState(&m_states.at(0)); + } +} - m_id = id; - m_drawType = BlockDrawType(drawType); +BlockState &Block::addState() { + if (!m_states.empty()) + m_states.emplace_back(m_states.size(), this, &getState(0)); + else + m_states.emplace_back(m_states.size(), this, nullptr); + return m_states.back(); +} + +BlockState &Block::getState(u16 id) { + if (id >= m_states.size()) { + gkError() << "Failed to get state" << id << "in block" << m_stringID; + return m_states.at(0); + } + + return m_states.at(id); +} + +const BlockState &Block::getState(u16 id) const { + if (id >= m_states.size()) { + gkError() << "Failed to get state" << id << "in block" << m_stringID; + return m_states.at(0); + } + + return m_states.at(id); } // Please update 'docs/lua-api-cpp.md' if you change this void Block::initUsertype(sol::state &lua) { lua.new_usertype("Block", "id", &Block::id, - "data", &Block::data, "string_id", &Block::stringID, - "label", &Block::label, "mod_name", &Block::modName, - "is_opaque", &Block::isOpaque, - "get_item_drop", &Block::getItemDrop, - "param", (const BlockParam &(Block::*)() const)&Block::param + "param", (const BlockParam &(Block::*)() const)&Block::param, + "get_state", (const BlockState &(Block::*)(u16) const)&Block::getState ); } diff --git a/source/common/world/Block.hpp b/source/common/world/Block.hpp index 987c95481..e55579e9d 100644 --- a/source/common/world/Block.hpp +++ b/source/common/world/Block.hpp @@ -37,6 +37,7 @@ #include #include "BlockParam.hpp" +#include "BlockState.hpp" #include "ItemStack.hpp" #include "TilesDef.hpp" @@ -44,72 +45,28 @@ class Chunk; class Player; class World; -enum class BlockDrawType : u8 { - Solid = 0, - XShape = 1, - Leaves = 2, - Liquid = 3, - Glass = 4, - Cactus = 5, - BoundingBox = 6, // FIXME: Temporary -}; - class Block : public gk::ISerializable { public: Block() = default; - Block(u32 id, const TilesDef &tiles, const std::string &stringID, const std::string &label); + Block(u32 id, const std::string &stringID); virtual ~Block() = default; void serialize(sf::Packet &packet) const override; void deserialize(sf::Packet &packet) override; - u16 id() const { return m_id & 0xffff; } - u16 data() const { return (m_id >> 16) & 0xffff; } - const TilesDef &tiles() const { return m_tiles; } + u16 id() const { return m_id; } 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::string modName() const { return m_stringID.substr(0, m_stringID.find_first_of(":")); } - bool isOpaque() const { return m_id != 0 && m_isOpaque && m_drawType != BlockDrawType::XShape; } - void setOpaque(bool isOpaque) { m_isOpaque = isOpaque; } - - ItemStack getItemDrop() const { return ItemStack{m_itemDrop, m_itemDropAmount}; }; - void setItemDrop(const std::string &itemDrop, u16 itemDropAmount = 1) { m_itemDrop = itemDrop; m_itemDropAmount = itemDropAmount; } - - u8 harvestRequirements() const { return m_harvestRequirements; } - void setHarvestRequirements(u8 harvestRequirements) { m_harvestRequirements = harvestRequirements; } - - float hardness() const { return m_hardness; } - void setHardness(float hardness) { m_hardness = hardness; } - - float timeToBreak(u8 harvestCapability, float miningSpeed) const { - return ((m_harvestRequirements & harvestCapability) == m_harvestRequirements) ? 1.5 * m_hardness / miningSpeed : 5 * m_hardness; - } - - const gk::FloatBox &boundingBox() const { return m_boundingBox; } - void setBoundingBox(const gk::FloatBox &boundingBox) { m_boundingBox = boundingBox; } - - BlockDrawType drawType() const { return m_drawType; } - void setDrawType(BlockDrawType drawType) { m_drawType = drawType; } + const TilesDef &tiles(u16 state) const; bool canUpdate() const { return m_canUpdate; } bool canBeActivated() const { return m_canBeActivated; } - bool isLightSource() const { return m_isLightSource; } - void setLightSource(bool isLightSource) { m_isLightSource = isLightSource; } - - const gk::Color &colorMultiplier() const { return m_colorMultiplier; } - void setColorMultiplier(const gk::Color &colorMultiplier) { m_colorMultiplier = colorMultiplier; } - bool isRotatable() const { return m_isRotatable; } void setRotatable(bool isRotatable) { m_isRotatable = isRotatable; } - const std::string &inventoryImage() const { return m_inventoryImage; } - void setInventoryImage(const std::string &inventoryImage) { m_inventoryImage = inventoryImage; } - void addGroup(const std::string &name, u16 value) { m_groups.emplace(name, value); } bool hasGroup(const std::string &name) const { return m_groups.find(name) != m_groups.end(); } @@ -121,15 +78,14 @@ class Block : public gk::ISerializable { return it->second; } - float fogDepth() const { return m_fogDepth; } - void setFogDepth(float fogDepth) { m_fogDepth = fogDepth; } - - const gk::Color &fogColor() const { return m_fogColor; } - void setFogColor(const gk::Color &fogColor) { m_fogColor = fogColor; } - const BlockParam ¶m() const { return m_param; } BlockParam ¶m() { return m_param; } + BlockState &addState(); + BlockState &getState(u16 id); + const BlockState &getState(u16 id) const; + const std::vector &states() const { return m_states; } + static void initUsertype(sol::state &lua); protected: @@ -139,36 +95,14 @@ class Block : public gk::ISerializable { bool m_canBeActivated = false; private: - u32 m_id = 0; - TilesDef m_tiles; - + u16 m_id = 0; std::string m_stringID; - std::string m_label; - - std::string m_itemDrop; - u16 m_itemDropAmount; - - u8 m_harvestRequirements = 0; - float m_hardness = 1.0f; - - gk::FloatBox m_boundingBox{0, 0, 0, 1, 1, 1}; - - BlockDrawType m_drawType = BlockDrawType::Solid; - - bool m_isOpaque = true; - - bool m_isLightSource = false; - - gk::Color m_colorMultiplier = gk::Color::White; bool m_isRotatable = false; - std::string m_inventoryImage; - std::unordered_map m_groups; - float m_fogDepth = 0; - gk::Color m_fogColor = gk::Color::White; + std::vector m_states; BlockParam m_param{*this}; }; diff --git a/source/common/world/BlockParam.cpp b/source/common/world/BlockParam.cpp index 02b07bed8..c99c6b97b 100644 --- a/source/common/world/BlockParam.cpp +++ b/source/common/world/BlockParam.cpp @@ -42,7 +42,7 @@ void BlockParam::deserialize(sf::Packet &packet) { void BlockParam::allocateBits(u8 type, u8 size) { auto it = m_allocatedBits.find(type); if (it != m_allocatedBits.end()) { - gkWarning() << "Can't allocate param type" << getTypeName(type) << "twice in block" << m_block.stringID(); + gkWarning() << "Can't allocate param type" << getTypeName(type) << "twice in block" << m_block->stringID(); } else if (m_totalSize + size <= 16) { m_allocatedBits.emplace(type, Param{m_totalSize, size}); @@ -52,7 +52,7 @@ void BlockParam::allocateBits(u8 type, u8 size) { // gkDebug() << "Allocated" << (int)size << "bits for type" << getTypeName(type) << "in block" << m_block.stringID(); } else { - gkError() << "Failed to allocate bits for param" << getTypeName(type) << "in block" << m_block.stringID(); + gkError() << "Failed to allocate bits for param" << getTypeName(type) << "in block" << m_block->stringID(); gkError() << "Reason: Can't allocate more than 16 bits. Allocated bits:"; for (auto &it : m_allocatedBits) { gkError() << "\t-" << getTypeName(it.first) << "=" << (int)it.second.size; @@ -63,7 +63,7 @@ void BlockParam::allocateBits(u8 type, u8 size) { u16 BlockParam::getParam(u8 type, u16 data) const { auto it = m_allocatedBits.find(type); if (it == m_allocatedBits.end()) { - gkWarning() << "Failed to get param" << getTypeName(type) << "in block" << m_block.stringID(); + gkWarning() << "Failed to get param" << getTypeName(type) << "in block" << m_block->stringID(); return 0; } @@ -73,14 +73,14 @@ u16 BlockParam::getParam(u8 type, u16 data) const { u16 BlockParam::setParam(u8 type, u16 data, u16 param) { auto it = m_allocatedBits.find(type); if (it == m_allocatedBits.end()) { - gkWarning() << "Failed to set param" << getTypeName(type) << "in block" << m_block.stringID(); + gkWarning() << "Failed to set param" << getTypeName(type) << "in block" << m_block->stringID(); return 0; } u16 mask = ~(~0u << it->second.size) << it->second.offset; param <<= it->second.offset; if ((param & ~mask) != 0) - gkWarning() << "Block param overflow for type" << getTypeName(type) << "in block" << m_block.stringID(); + gkWarning() << "Block param overflow for type" << getTypeName(type) << "in block" << m_block->stringID(); return (data & ~mask) | (param & mask); } @@ -88,6 +88,7 @@ u16 BlockParam::setParam(u8 type, u16 data, u16 param) { std::string BlockParam::getTypeName(u8 type) { std::array names = { "Rotation", + "State", }; return names[type]; diff --git a/source/common/world/BlockParam.hpp b/source/common/world/BlockParam.hpp index 322cfee14..5bd48d767 100644 --- a/source/common/world/BlockParam.hpp +++ b/source/common/world/BlockParam.hpp @@ -41,13 +41,14 @@ class Block; class BlockParam : public gk::ISerializable { public: - BlockParam(Block &block) : m_block(block) {} + BlockParam(Block &block) : m_block(&block) {} void serialize(sf::Packet &packet) const override; void deserialize(sf::Packet &packet) override; enum Type { Rotation, + State, Count }; @@ -56,13 +57,14 @@ class BlockParam : public gk::ISerializable { u16 getParam(u8 type, u16 data) const; u16 setParam(u8 type, u16 data, u16 param); + bool hasParam(u8 type) const { return m_allocatedBits.find(type) != m_allocatedBits.end(); } static std::string getTypeName(u8 type); static void initUsertype(sol::state &lua); private: - Block &m_block; + Block *m_block = nullptr; u8 m_totalSize = 0; struct Param : public gk::ISerializable { diff --git a/source/common/world/BlockState.cpp b/source/common/world/BlockState.cpp new file mode 100644 index 000000000..6e28048d5 --- /dev/null +++ b/source/common/world/BlockState.cpp @@ -0,0 +1,58 @@ +/* + * ===================================================================================== + * + * 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 "BlockState.hpp" +#include "Registry.hpp" + +void BlockState::serialize(sf::Packet &packet) const { + packet << m_id << m_label << m_tiles + << m_itemDrop << m_itemDropAmount + << m_hardness << m_harvestRequirements + << m_boundingBox << u8(m_drawType) << m_colorMultiplier + << m_isOpaque << m_isLightSource + << m_inventoryImage << m_fogDepth << m_fogColor << m_attrs; +} + +void BlockState::deserialize(sf::Packet &packet) { + u8 drawType; + + packet >> m_id >> m_label >> m_tiles + >> m_itemDrop >> m_itemDropAmount + >> m_hardness >> m_harvestRequirements + >> m_boundingBox >> drawType >> m_colorMultiplier + >> m_isOpaque >> m_isLightSource + >> m_inventoryImage >> m_fogDepth >> m_fogColor >> m_attrs; + + m_drawType = BlockDrawType(drawType); +} + +// Please update 'docs/lua-api-cpp.md' if you change this +void BlockState::initUsertype(sol::state &lua) { + lua.new_usertype("BlockState", + "get_item_drop", &BlockState::getItemDrop + ); +} + diff --git a/source/common/world/BlockState.hpp b/source/common/world/BlockState.hpp new file mode 100644 index 000000000..63d0bc853 --- /dev/null +++ b/source/common/world/BlockState.hpp @@ -0,0 +1,157 @@ +/* + * ===================================================================================== + * + * 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 BLOCKSTATE_HPP_ +#define BLOCKSTATE_HPP_ + +#include +#include + +#include +#include + +#include "BlockParam.hpp" +#include "ItemStack.hpp" +#include "TilesDef.hpp" + +enum class BlockDrawType : u8 { + Solid = 0, + XShape = 1, + Leaves = 2, + Liquid = 3, + Glass = 4, + Cactus = 5, + BoundingBox = 6, // FIXME: Temporary +}; + +#define BLOCK_ATTR_GETTER(attrName) \ + auto attrName() const -> const std::remove_reference::type & { \ + if (!m_defaultState || m_attrs & BlockAttribute::attr_##attrName) \ + return m_##attrName; \ + else \ + return m_defaultState->m_##attrName; \ + } + +#define BLOCK_ATTR_SETTER(attrName) \ + void attrName(const std::remove_reference::type &value) { \ + m_##attrName = value; \ + m_attrs |= BlockAttribute::attr_##attrName; \ + } + +#define BLOCK_ATTR(type, name) \ + private: \ + type m_##name; \ + public: \ + BLOCK_ATTR_GETTER(name) \ + BLOCK_ATTR_SETTER(name) + +#define BLOCK_ATTR_V(type, name, defaultValue) \ + private: \ + type m_##name = defaultValue; \ + public: \ + BLOCK_ATTR_GETTER(name) \ + BLOCK_ATTR_SETTER(name) + + +class BlockState : public gk::ISerializable { + public: + BlockState() = default; + BlockState(u16 id, const Block *block, const BlockState *defaultState = nullptr) + : m_block(block), m_defaultState(defaultState), m_id(id) {} + + void serialize(sf::Packet &packet) const override; + void deserialize(sf::Packet &packet) override; + + const Block &block() const { return *m_block; } + + u16 id() const { return m_id; } + + float timeToBreak(u8 harvestCapability, float miningSpeed) const { + return ((m_harvestRequirements & harvestCapability) == m_harvestRequirements) ? 1.5 * m_hardness / miningSpeed : 5 * m_hardness; + } + + ItemStack getItemDrop() const { return {m_itemDrop, m_itemDropAmount}; } + + void setBlock(const Block *block) { m_block = block; } + void setDefaultState(const BlockState *defaultState) { m_defaultState = defaultState; } + + static void initUsertype(sol::state &lua); + + private: + const Block *m_block = nullptr; + const BlockState *m_defaultState = nullptr; + + u16 m_id = 0; + + enum BlockAttribute : u32 { + attr_tiles, + attr_label, + attr_itemDrop, + attr_itemDropAmount, + attr_harvestRequirements, + attr_hardness, + attr_boundingBox, + attr_drawType, + attr_colorMultiplier, + attr_isOpaque, + attr_isLightSource, + attr_inventoryImage, + attr_fogDepth, + attr_fogColor, + attr_param, + }; + + BLOCK_ATTR(std::string, label); + BLOCK_ATTR(TilesDef, tiles); + + BLOCK_ATTR(std::string, itemDrop); + BLOCK_ATTR_V(u16, itemDropAmount, 1); + + BLOCK_ATTR_V(u8, harvestRequirements, 0); + BLOCK_ATTR_V(float, hardness, 1.0f); + + BLOCK_ATTR_V(gk::FloatBox, boundingBox, (gk::FloatBox{0, 0, 0, 1, 1, 1})); + + BLOCK_ATTR_V(BlockDrawType, drawType, BlockDrawType::Solid); + + BLOCK_ATTR_V(gk::Color, colorMultiplier, gk::Color::White); + + BLOCK_ATTR_V(bool, isOpaque, true); + BLOCK_ATTR_V(bool, isLightSource, false); + + BLOCK_ATTR(std::string, inventoryImage); + + BLOCK_ATTR_V(float, fogDepth, 0); + BLOCK_ATTR_V(gk::Color, fogColor, gk::Color::White); + + u32 m_attrs = 0; +}; + +#undef BLOCK_ATTR +#undef BLOCK_ATTR_GETTER +#undef BLOCK_ATTR_SETTER + +#endif // BLOCKSTATE_HPP_ diff --git a/source/common/world/Chunk.cpp b/source/common/world/Chunk.cpp index 7ea63b7be..a32d9ef46 100644 --- a/source/common/world/Chunk.cpp +++ b/source/common/world/Chunk.cpp @@ -71,8 +71,12 @@ void Chunk::setBlock(int x, int y, int z, u16 type) { if ((m_data[z][y][x] & 0xffff) == type) return; + u16 blockParam = getData(x, y, z); const Block &block = Registry::getInstance().getBlock(type); - if (block.isLightSource()) + const BlockState &blockState = block.getState(block.param().hasParam(BlockParam::State) + ? block.param().getParam(BlockParam::State, blockParam) : 0); + + if (blockState.isLightSource()) m_lightmap.addTorchlight(x, y, z, 14); else { m_lightmap.removeTorchlight(x, y, z); @@ -82,8 +86,8 @@ void Chunk::setBlock(int x, int y, int z, u16 type) { onBlockPlaced(x, y, z, block); m_world.onBlockPlaced(x + m_x * width, y + m_y * depth, z + m_z * height, block); - if (m_data[z][y][x] != 0) { - const Block &oldBlock = Registry::getInstance().getBlock(m_data[z][y][x]); + if ((m_data[z][y][x] & 0xffff) != 0) { + const Block &oldBlock = Registry::getInstance().getBlock(m_data[z][y][x] & 0xffff); onBlockDestroyed(x, y, z, oldBlock); } @@ -133,6 +137,23 @@ void Chunk::setBlockRaw(int x, int y, int z, u16 type) { m_hasChanged = true; } +const BlockState *Chunk::getBlockState(int x, int y, int z) const { + if(x < 0) return m_surroundingChunks[0] ? m_surroundingChunks[0]->getBlockState(x + Chunk::width, y, z) : nullptr; + if(x >= Chunk::width) return m_surroundingChunks[1] ? m_surroundingChunks[1]->getBlockState(x - Chunk::width, y, z) : nullptr; + if(y < 0) return m_surroundingChunks[2] ? m_surroundingChunks[2]->getBlockState(x, y + Chunk::depth, z) : nullptr; + if(y >= Chunk::depth) return m_surroundingChunks[3] ? m_surroundingChunks[3]->getBlockState(x, y - Chunk::depth, z) : nullptr; + if(z < 0) return m_surroundingChunks[4] ? m_surroundingChunks[4]->getBlockState(x, y, z + Chunk::height) : nullptr; + if(z >= Chunk::height) return m_surroundingChunks[5] ? m_surroundingChunks[5]->getBlockState(x, y, z - Chunk::height) : nullptr; + + u16 blockID = getBlock(x, y, z); + u16 blockParam = getData(x, y, z); + const Block &block = Registry::getInstance().getBlock(blockID); + if (!block.param().hasParam(BlockParam::State)) + return &block.getState(0); + + return &block.getState(block.param().getParam(BlockParam::State, blockParam)); +} + BlockData *Chunk::getBlockData(int x, int y, int z) const { if(x < 0) return m_surroundingChunks[0] ? m_surroundingChunks[0]->getBlockData(x + CHUNK_WIDTH, y, z) : 0; if(x >= CHUNK_WIDTH) return m_surroundingChunks[1] ? m_surroundingChunks[1]->getBlockData(x - CHUNK_WIDTH, y, z) : 0; diff --git a/source/common/world/Chunk.hpp b/source/common/world/Chunk.hpp index f90d15984..a52d7dc94 100644 --- a/source/common/world/Chunk.hpp +++ b/source/common/world/Chunk.hpp @@ -64,6 +64,8 @@ class Chunk : public gk::NonCopyable { void setBlockRaw(int x, int y, int z, u16 block); + const BlockState *getBlockState(int x, int y, int z) const; + virtual void onBlockPlaced(int, int, int, const Block &) {} virtual void onBlockDestroyed(int, int, int, const Block &) {} diff --git a/source/common/world/ChunkLightmap.cpp b/source/common/world/ChunkLightmap.cpp index 2d24be8db..ce1b4528c 100644 --- a/source/common/world/ChunkLightmap.cpp +++ b/source/common/world/ChunkLightmap.cpp @@ -129,9 +129,8 @@ bool ChunkLightmap::updateTorchlight() { m_torchlightBfsQueue.pop(); // If this block is opaque, don't propagate the light - u16 blockID = m_chunk->getBlock(node.x, node.y, node.z); - const Block &block = Registry::getInstance().getBlock(blockID); - if (block.isOpaque() && !block.isLightSource()) { + const BlockState *blockState = m_chunk->getBlockState(node.x, node.y, node.z); + if (blockState && blockState->isOpaque() && !blockState->isLightSource()) { setTorchlight(node.x, node.y, node.z, 0); // FIXME: This only reverts an addTorchlight that added light in a non-generated chunk @@ -153,9 +152,8 @@ bool ChunkLightmap::updateTorchlight() { u8 lightLevel = getTorchlight(node.x, node.y, node.z); for (const gk::Vector3i &surroundingNode : surroundingNodes) { if (getTorchlight(surroundingNode.x, surroundingNode.y, surroundingNode.z) + 2 <= lightLevel) { - u16 blockID = m_chunk->getBlock(surroundingNode.x, surroundingNode.y, surroundingNode.z); - const Block &block = Registry::getInstance().getBlock(blockID); - if (!block.isOpaque()) { + const BlockState *blockState = m_chunk->getBlockState(surroundingNode.x, surroundingNode.y, surroundingNode.z); + if (blockState && blockState->isOpaque()) { addTorchlight(surroundingNode.x, surroundingNode.y, surroundingNode.z, lightLevel - 1); lightUpdated = true; @@ -205,9 +203,8 @@ bool ChunkLightmap::updateSunlight() { m_sunlightBfsQueue.pop(); // If this block is opaque, don't propagate the light - u16 blockID = m_chunk->getBlock(node.x, node.y, node.z); - const Block &block = Registry::getInstance().getBlock(blockID); - if (block.isOpaque()) { + const BlockState *blockState = m_chunk->getBlockState(node.x, node.y, node.z); + if (blockState && blockState->isOpaque()) { setSunlight(node.x, node.y, node.z, 0); // FIXME: This only reverts an addSunlight that added light in a non-generated chunk @@ -231,10 +228,9 @@ bool ChunkLightmap::updateSunlight() { u8 neighbourSunlightLevel = getSunlight(surroundingNode.x, surroundingNode.y, surroundingNode.z); if (neighbourSunlightLevel + 2 <= sunlightLevel || (sunlightLevel == 15 && neighbourSunlightLevel != 15 && surroundingNode.z == node.z - 1)) { - u16 blockID = m_chunk->getBlock(surroundingNode.x, surroundingNode.y, surroundingNode.z); - const Block &block = Registry::getInstance().getBlock(blockID); - if (!block.isOpaque()) { - if (sunlightLevel == 15 && surroundingNode.z == node.z - 1 && (!blockID || block.drawType() == BlockDrawType::Glass || block.drawType() == BlockDrawType::XShape)) { + const BlockState *blockState = m_chunk->getBlockState(surroundingNode.x, surroundingNode.y, surroundingNode.z); + if (blockState && blockState->isOpaque()) { + if (sunlightLevel == 15 && surroundingNode.z == node.z - 1 && (!blockState->block().id() || blockState->drawType() == BlockDrawType::Glass || blockState->drawType() == BlockDrawType::XShape)) { addSunlight(surroundingNode.x, surroundingNode.y, surroundingNode.z, sunlightLevel); lightUpdated = true; diff --git a/source/common/world/World.cpp b/source/common/world/World.cpp index 7d3261d4e..e9ab22308 100644 --- a/source/common/world/World.cpp +++ b/source/common/world/World.cpp @@ -58,6 +58,14 @@ BlockData *World::addBlockData(int x, int y, int z, int inventoryWidth, int inve return nullptr; } +const BlockState *World::getBlockState(int x, int y, int z) const { + Chunk *chunk = getChunkAtBlockPos(x, y, z); + if (chunk) + return chunk->getBlockState(x & (CHUNK_WIDTH - 1), y & (CHUNK_DEPTH - 1), z & (CHUNK_HEIGHT - 1)); + + return nullptr; +} + u16 World::getBlock(int x, int y, int z) const { Chunk *chunk = getChunkAtBlockPos(x, y, z); if (chunk) diff --git a/source/common/world/World.hpp b/source/common/world/World.hpp index ceb480255..2dd578a69 100644 --- a/source/common/world/World.hpp +++ b/source/common/world/World.hpp @@ -41,6 +41,8 @@ class World { BlockData *getBlockData(int x, int y, int z) const; BlockData *addBlockData(int x, int y, int z, int inventoryWidth = 0, int inventoryHeight = 0) const; + const BlockState *getBlockState(int x, int y, int z) const; + u16 getBlock(int x, int y, int z) const; void setBlock(int x, int y, int z, u16 id) const; u16 getData(int x, int y, int z) const; diff --git a/source/server/core/ServerApplication.cpp b/source/server/core/ServerApplication.cpp index f59161a95..9bbd72e1a 100644 --- a/source/server/core/ServerApplication.cpp +++ b/source/server/core/ServerApplication.cpp @@ -117,7 +117,7 @@ bool ServerApplication::init() { Registry::setInstance(m_registry); // The id "_:air" is used in CraftingRecipe, update it there if it changes - m_registry.registerBlock({}, "_:air", "Air"); + m_registry.registerBlock("_:air").getState(0).label("Air"); m_registry.registerItem({}, "_:air", "Air").setIsBlock(true); m_modLoader.loadMods(); diff --git a/source/server/lua/loader/LuaBlockLoader.cpp b/source/server/lua/loader/LuaBlockLoader.cpp index 76b4859f8..7294afa6e 100644 --- a/source/server/lua/loader/LuaBlockLoader.cpp +++ b/source/server/lua/loader/LuaBlockLoader.cpp @@ -32,54 +32,61 @@ #include "ServerBlock.hpp" void LuaBlockLoader::loadBlock(const sol::table &table) const { - TilesDef tiles; - tiles.loadFromLuaTable(table); - std::string stringID = m_mod.id() + ":" + table["id"].get(); - std::string label = table["name"].get(); - ServerBlock &block = Registry::getInstance().registerBlock(tiles, stringID, label); + ServerBlock &block = Registry::getInstance().registerBlock(stringID); + block.setRotatable(table["is_rotatable"].get_or(false)); + + BlockState &defaultState = block.getState(0); + loadBlockState(defaultState, table, block); +} + +void LuaBlockLoader::loadBlockState(BlockState &state, const sol::table &table, ServerBlock &block) const { + TilesDef tiles; + tiles.loadFromLuaTable(table); + state.tiles(tiles); - loadProperties(block, table); - loadBoundingBox(block, table); - loadDrawType(block, table); - loadItemDrop(block, table); - loadColorMultiplier(block, table); + loadProperties(state, table); + loadBoundingBox(state, table); + loadDrawType(state, table, block); + loadItemDrop(state, table); + loadColorMultiplier(state, table); Item *item = nullptr; - if (!block.inventoryImage().empty()) { - item = &Registry::getInstance().registerItem(TilesDef{block.inventoryImage()}, stringID, label); + if (!state.inventoryImage().empty()) { + item = &Registry::getInstance().registerItem(TilesDef{state.inventoryImage()}, block.stringID(), state.label()); } else { - item = &Registry::getInstance().registerItem(block.tiles(), stringID, label); + item = &Registry::getInstance().registerItem(state.tiles(), block.stringID(), state.label()); } item->setIsBlock(true); - loadGroups(block, *item, table); + loadGroups(block, table, item); loadParams(block); } -inline void LuaBlockLoader::loadProperties(ServerBlock &block, const sol::table &table) const { - block.setHarvestRequirements(table["harvest_requirements"].get_or(0)); - block.setHardness(table["hardness"].get_or(1.0f)); - block.setOpaque(table["is_opaque"].get_or(true)); - block.setLightSource(table["is_light_source"].get_or(false)); - block.setOnBlockActivated(table["on_block_activated"]); - block.setOnTick(table["on_tick"]); - block.setOnBlockPlaced(table["on_block_placed"]); - block.setOnBlockDestroyed(table["on_block_destroyed"]); - block.setRotatable(table["is_rotatable"].get_or(false)); - block.setInventoryImage(table["inventory_image"].get_or("")); - block.setFogDepth(table["fog_depth"].get_or(0)); - block.setTickRandomly(table["tick_randomly"].get_or(false)); - block.setTickProbability(table["tick_probability"].get_or(0.f)); - - if (block.fogDepth()) { +inline void LuaBlockLoader::loadProperties(BlockState &state, const sol::table &table) const { + state.label(table["name"].get()); + state.harvestRequirements(table["harvest_requirements"].get_or(0)); + state.hardness(table["hardness"].get_or(1.0f)); + state.isOpaque(table["is_opaque"].get_or(true)); + state.isLightSource(table["is_light_source"].get_or(false)); + state.inventoryImage(table["inventory_image"].get_or("")); + state.fogDepth(table["fog_depth"].get_or(0)); + + // state.onBlockActivated(table["on_block_activated"]); + // state.onTick(table["on_tick"]); + // state.onBlockPlaced(table["on_block_placed"]); + // state.onBlockDestroyed(table["on_block_destroyed"]); + // state.setTickRandomly(table["tick_randomly"].get_or(false)); + // state.setTickProbability(table["tick_probability"].get_or(0.f)); + + if (state.fogDepth()) { sol::optional fogColor = table["fog_color"]; if (fogColor != sol::nullopt) { - block.setFogColor(gk::Color{ + state.fogColor(gk::Color{ fogColor.value().get(1), fogColor.value().get(2), fogColor.value().get(3), @@ -88,10 +95,10 @@ inline void LuaBlockLoader::loadProperties(ServerBlock &block, const sol::table } } -inline void LuaBlockLoader::loadBoundingBox(ServerBlock &block, const sol::table &table) const { +inline void LuaBlockLoader::loadBoundingBox(BlockState &state, const sol::table &table) const { sol::optional boundingBox = table["bounding_box"]; if (boundingBox != sol::nullopt) { - block.setBoundingBox(gk::FloatBox{ + state.boundingBox(gk::FloatBox{ boundingBox.value().get(1), boundingBox.value().get(2), boundingBox.value().get(3), @@ -102,7 +109,7 @@ inline void LuaBlockLoader::loadBoundingBox(ServerBlock &block, const sol::table } } -inline void LuaBlockLoader::loadDrawType(ServerBlock &block, const sol::table &table) const { +inline void LuaBlockLoader::loadDrawType(BlockState &state, const sol::table &table, const ServerBlock &block) const { sol::object drawTypeObject = table["draw_type"]; if (drawTypeObject.valid()) { if (drawTypeObject.get_type() == sol::type::string) { @@ -117,8 +124,9 @@ inline void LuaBlockLoader::loadDrawType(ServerBlock &block, const sol::table &t }; auto it = drawTypes.find(drawTypeObject.as()); - if (it != drawTypes.end()) - block.setDrawType(it->second); + if (it != drawTypes.end()) { + state.drawType(it->second); + } else gkError() << "In" << block.stringID() << " definition: Block draw type invalid"; } @@ -127,19 +135,18 @@ inline void LuaBlockLoader::loadDrawType(ServerBlock &block, const sol::table &t } } -inline void LuaBlockLoader::loadItemDrop(ServerBlock &block, const sol::table &table) const { +inline void LuaBlockLoader::loadItemDrop(BlockState &state, const sol::table &table) const { sol::optional itemDrop = table["item_drop"]; if (itemDrop != sol::nullopt) { - std::string dropID = itemDrop.value()["id"]; - u16 dropAmount = itemDrop.value()["amount"]; - block.setItemDrop(dropID, dropAmount); + state.itemDrop(itemDrop.value()["id"]); + state.itemDropAmount(itemDrop.value()["amount"]); } } -inline void LuaBlockLoader::loadColorMultiplier(ServerBlock &block, const sol::table &table) const { +inline void LuaBlockLoader::loadColorMultiplier(BlockState &state, const sol::table &table) const { sol::optional colorMultiplier = table["color_multiplier"]; if (colorMultiplier != sol::nullopt) { - block.setColorMultiplier(gk::Color{ + state.colorMultiplier(gk::Color{ colorMultiplier.value().get(1), colorMultiplier.value().get(2), colorMultiplier.value().get(3), @@ -148,7 +155,7 @@ inline void LuaBlockLoader::loadColorMultiplier(ServerBlock &block, const sol::t } } -inline void LuaBlockLoader::loadGroups(ServerBlock &block, Item &item, const sol::table &table) const { +inline void LuaBlockLoader::loadGroups(ServerBlock &block, const sol::table &table, Item *item) const { sol::object groupsObject = table["groups"]; if (groupsObject.valid()) { if (groupsObject.get_type() == sol::type::table) { @@ -158,7 +165,8 @@ inline void LuaBlockLoader::loadGroups(ServerBlock &block, Item &item, const sol u16 groupValue = groupObject.second.as(); block.addGroup(groupName, groupValue); - item.addGroup(groupName, groupValue); + if (item) + item->addGroup(groupName, groupValue); } } else diff --git a/source/server/lua/loader/LuaBlockLoader.hpp b/source/server/lua/loader/LuaBlockLoader.hpp index eb0392d56..a7872a9ae 100644 --- a/source/server/lua/loader/LuaBlockLoader.hpp +++ b/source/server/lua/loader/LuaBlockLoader.hpp @@ -31,6 +31,7 @@ class Item; class LuaMod; +class BlockState; class ServerBlock; class LuaBlockLoader { @@ -40,12 +41,13 @@ class LuaBlockLoader { void loadBlock(const sol::table &table) const; private: - void loadProperties(ServerBlock &block, const sol::table &table) const; - void loadBoundingBox(ServerBlock &block, const sol::table &table) const; - void loadDrawType(ServerBlock &block, const sol::table &table) const; - void loadItemDrop(ServerBlock &block, const sol::table &table) const; - void loadColorMultiplier(ServerBlock &block, const sol::table &table) const; - void loadGroups(ServerBlock &block, Item &item, const sol::table &table) const; + void loadBlockState(BlockState &state, const sol::table &table, ServerBlock &block) const; + void loadProperties(BlockState &state, const sol::table &table) const; + void loadBoundingBox(BlockState &state, const sol::table &table) const; + void loadDrawType(BlockState &state, const sol::table &table, const ServerBlock &block) const; + void loadItemDrop(BlockState &state, const sol::table &table) const; + void loadColorMultiplier(BlockState &state, const sol::table &table) const; + void loadGroups(ServerBlock &block, const sol::table &table, Item *item = nullptr) const; void loadParams(ServerBlock &block) const; LuaMod &m_mod; diff --git a/source/server/network/ServerCommandHandler.cpp b/source/server/network/ServerCommandHandler.cpp index 8781c26fa..4de4e4a6d 100644 --- a/source/server/network/ServerCommandHandler.cpp +++ b/source/server/network/ServerCommandHandler.cpp @@ -272,9 +272,11 @@ void ServerCommandHandler::setupCallbacks() { ServerWorld &world = getWorldForClient(client.id); world.setData(x, y, z, block >> 16); world.setBlock(x, y, z, block & 0xffff); - const Block &blockDef = Registry::getInstance().getBlock(block & 0xffff); - m_scriptEngine.luaCore().onEvent(LuaEventType::BlockPlaced, glm::ivec3{x, y, z}, blockDef, *player, world, client, *this); + const BlockState *blockState = world.getBlockState(x, y, z); + if (!blockState) return; + + m_scriptEngine.luaCore().onEvent(LuaEventType::BlockPlaced, glm::ivec3{x, y, z}, blockState, *player, world, client, *this); Network::Packet answer; answer << Network::Command::BlockUpdate << x << y << z << block; @@ -291,10 +293,11 @@ void ServerCommandHandler::setupCallbacks() { packet >> x >> y >> z; ServerWorld &world = getWorldForClient(client.id); - const Block &blockDef = Registry::getInstance().getBlock(world.getBlock(x, y, z)); + const BlockState *blockState = world.getBlockState(x, y, z); + if (!blockState) return; world.setBlock(x, y, z, 0); - m_scriptEngine.luaCore().onEvent(LuaEventType::BlockDigged, glm::ivec3{x, y, z}, blockDef, *player, world, client, *this); + m_scriptEngine.luaCore().onEvent(LuaEventType::BlockDigged, glm::ivec3{x, y, z}, blockState, *player, world, client, *this); Network::Packet answer; answer << Network::Command::BlockUpdate << x << y << z << u32(0); diff --git a/source/server/world/ServerBlock.cpp b/source/server/world/ServerBlock.cpp index 666627590..28e79f605 100644 --- a/source/server/world/ServerBlock.cpp +++ b/source/server/world/ServerBlock.cpp @@ -35,7 +35,7 @@ void ServerBlock::onTick(const glm::ivec3 &pos, ServerChunk &chunk, ServerWorld &world, ServerCommandHandler &server) const { if (m_onTickEnabled && m_onTick) { try { - m_onTick(pos, chunk, world); + m_onTick(pos, *this, chunk, world); BlockData *blockData = world.getBlockData(pos.x, pos.y, pos.z); if (blockData) { @@ -59,7 +59,7 @@ void ServerBlock::onTick(const glm::ivec3 &pos, ServerChunk &chunk, ServerWorld bool ServerBlock::onBlockActivated(const glm::ivec3 &pos, ServerPlayer &player, ServerWorld &world, ClientInfo &client, ServerCommandHandler &server, u16 screenWidth, u16 screenHeight, u8 guiScale) const { try { if (m_onBlockActivated) { - m_onBlockActivated(pos, player, world, client, server, screenWidth, screenHeight, guiScale); + m_onBlockActivated(pos, *this, player, world, client, server, screenWidth, screenHeight, guiScale); // FIXME: Check if data changed before sending BlockData *blockData = world.getBlockData(pos.x, pos.y, pos.z); diff --git a/source/server/world/ServerBlock.hpp b/source/server/world/ServerBlock.hpp index a408cab84..671e5bc9a 100644 --- a/source/server/world/ServerBlock.hpp +++ b/source/server/world/ServerBlock.hpp @@ -37,8 +37,8 @@ class ServerWorld; class ServerBlock : public Block { public: - ServerBlock(u32 id, const TilesDef &tiles, const std::string &name, const std::string &label) - : Block(id, tiles, name, label) {} + ServerBlock(u32 id, const std::string &name) + : Block(id, name) {} void onTick(const glm::ivec3 &pos, ServerChunk &chunk, ServerWorld &world, ServerCommandHandler &server) const; bool onBlockActivated(const glm::ivec3 &pos, ServerPlayer &player, ServerWorld &world, ClientInfo &client, ServerCommandHandler &server, u16 screenWidth, u16 screenHeight, u8 guiScale) const;