diff --git a/doc/en/scripting/builtins/libworld.md b/doc/en/scripting/builtins/libworld.md index 97c6fdee9..b2453b34b 100644 --- a/doc/en/scripting/builtins/libworld.md +++ b/doc/en/scripting/builtins/libworld.md @@ -49,10 +49,11 @@ world.is_night() -> bool world.count_chunks() -> int -- Returns the compressed chunk data to send. +-- If the chunk is not loaded, returns the saved data. -- Currently includes: -- 1. Voxel data (id and state) -- 2. Voxel metadata (fields) -world.get_chunk_data(x: int, z: int) -> Bytearray +world.get_chunk_data(x: int, z: int) -> Bytearray or nil -- Modifies the chunk based on the compressed data. -- Returns true if the chunk exists. @@ -61,4 +62,12 @@ world.set_chunk_data( -- compressed chunk data data: Bytearray ) -> bool + +-- Saves chunk data to region. +-- Changes will be written to file only on world save. +world.save_chunk_data( + x: int, z: int, + -- compressed chunk data + data: Bytearray +) ``` diff --git a/doc/en/scripting/ui.md b/doc/en/scripting/ui.md index f5bb8401a..441115c8e 100644 --- a/doc/en/scripting/ui.md +++ b/doc/en/scripting/ui.md @@ -156,6 +156,25 @@ Properties: | ----- | ------ | ---- | ----- | ------------ | | src | string | yes | yes | texture name | +## Canvas + +Properties: + +| Title | Type | Read | Write | Description | +|-------|--------| ---- |-------|-------------| +| data | Canvas | yes | no | canvas data | + +Methods: + +| Method | Description | +|----------------------------------------------------------|---------------------------------------------------------| +| data:at(x: int, y: int) | returns an RGBA pixel at the given coordinates | +| data:set(x: int, y: int, rgba: int) | updates an RGBA pixel at the given coordinates | +| data:set(x: int, y: int, r: int, g: int, b: int) | updates an RGBA pixel at the given coordinates | +| data:set(x: int, y: int, r: int, g: int, b: int, a: int) | updates an RGBA pixel at the given coordinates | +| data:update() | applies changes to the canvas and uploads it to the GPU | + + ## Inventory Properties: diff --git a/doc/en/xml-ui-layouts.md b/doc/en/xml-ui-layouts.md index 8856338ad..6f687328d 100644 --- a/doc/en/xml-ui-layouts.md +++ b/doc/en/xml-ui-layouts.md @@ -97,7 +97,11 @@ Inner text is a button text. - `src` - name of an image stored in textures folder. Extension is not specified. Type: string. Example: *gui/error* - ## *textbox* +## *canvas* + +- _No additional attributes_ + +## *textbox* Inner text - initially entered text diff --git a/doc/ru/scripting/builtins/libworld.md b/doc/ru/scripting/builtins/libworld.md index a7e0e5a60..5713a3287 100644 --- a/doc/ru/scripting/builtins/libworld.md +++ b/doc/ru/scripting/builtins/libworld.md @@ -48,10 +48,11 @@ world.is_night() -> bool world.count_chunks() -> int -- Возвращает сжатые данные чанка для отправки. +-- Если чанк не загружен, возвращает сохранённые данные. -- На данный момент включает: -- 1. Данные вокселей (id и состояние) -- 2. Метаданные (поля) вокселей -world.get_chunk_data(x: int, z: int) -> Bytearray +world.get_chunk_data(x: int, z: int) -> Bytearray или nil -- Изменяет чанк на основе сжатых данных. -- Возвращает true если чанк существует. @@ -60,4 +61,12 @@ world.set_chunk_data( -- сжатые данные чанка data: Bytearray ) -> bool + +-- Сохраняет данные чанка в регион. +-- Изменения будет записаны в файл только после сохранения мира. +world.save_chunk_data( + x: int, z: int, + -- сжатые данные чанка + data: Bytearray +) ``` diff --git a/doc/ru/scripting/ui.md b/doc/ru/scripting/ui.md index 7e10a1804..cca9790e2 100644 --- a/doc/ru/scripting/ui.md +++ b/doc/ru/scripting/ui.md @@ -156,6 +156,25 @@ document["worlds-panel"]:clear() | -------- | ------ | ------ | ------ | --------------------- | | src | string | да | да | отображаемая текстура | + +## Холст (canvas) + +Свойства: + +| Название | Тип | Чтение | Запись | Описание | +|----------|--------| ------ |--------|----------------| +| data | Canvas | да | нет | пиксели холста | + +Методы: + +| Метод | Описание | +|----------------------------------------------------------|-----------------------------------------------------| +| data:at(x: int, y: int) | возвращает RGBA пиксель по указанным координатам | +| data:set(x: int, y: int, rgba: int) | изменяет RGBA пиксель по указанным координатам | +| data:set(x: int, y: int, r: int, g: int, b: int) | изменяет RGBA пиксель по указанным координатам | +| data:set(x: int, y: int, r: int, g: int, b: int, a: int) | изменяет RGBA пиксель по указанным координатам | +| data:update() | применяет изменения и загружает холст в видеопамять | + ## Inventory (inventory) Свойства: diff --git a/doc/ru/xml-ui-layouts.md b/doc/ru/xml-ui-layouts.md index 00db07547..30c173156 100644 --- a/doc/ru/xml-ui-layouts.md +++ b/doc/ru/xml-ui-layouts.md @@ -98,6 +98,10 @@ - `src` - имя изображения в папке textures без указания расширения. Тип: строка. Например `gui/error` +## Холст - *canvas* + +- _Нет дополнительных свойств_ + ## Текстовое поле - *textbox* Внутренний текст - изначально введенный текст diff --git a/res/layouts/ingame_chat.xml.lua b/res/layouts/ingame_chat.xml.lua index 52f38ce72..ec4e12b09 100644 --- a/res/layouts/ingame_chat.xml.lua +++ b/res/layouts/ingame_chat.xml.lua @@ -9,7 +9,9 @@ local animation_fps = 30 local function remove_line(line) document[line[1]]:destruct() - time.post_runnable(function() document.root:reposition() end) + time.post_runnable(function() + if world.is_open() then document.root:reposition() end + end) end local function update_line(line, uptime) diff --git a/res/modules/bit_converter.lua b/res/modules/bit_converter.lua index b72dc3114..c2ada355f 100644 --- a/res/modules/bit_converter.lua +++ b/res/modules/bit_converter.lua @@ -313,8 +313,8 @@ function bit_converter.bytes_to_uint16(bytes, order) return bit.bor( - bit.lshift(bytes[2], 8), - bytes[1], 0) + bit.lshift(bytes[1], 8), + bytes[2], 0) end function bit_converter.bytes_to_int64(bytes, order) diff --git a/res/modules/internal/gui_util.lua b/res/modules/internal/gui_util.lua index 3cf84a33b..294a7c79e 100644 --- a/res/modules/internal/gui_util.lua +++ b/res/modules/internal/gui_util.lua @@ -47,7 +47,7 @@ function gui_util.add_page_dispatcher(dispatcher) table.insert(gui_util.local_dispatchers, dispatcher) end -function gui_util.reset_local() +function gui_util.__reset_local() gui_util.local_dispatchers = {} end diff --git a/res/modules/internal/stdcomp.lua b/res/modules/internal/stdcomp.lua index 9e208038a..bdb2468fc 100644 --- a/res/modules/internal/stdcomp.lua +++ b/res/modules/internal/stdcomp.lua @@ -132,5 +132,8 @@ return { end return values end + end, + __reset = function() + entities = {} end } diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 78228254c..9ee20241d 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -343,8 +343,8 @@ function __vc_on_hud_open() end) end) input.add_callback("key:escape", function() - if hud.is_paused() then - hud.resume() + if menu.page ~= "" then + menu:reset() elseif hud.is_inventory_open() then hud.close_inventory() else @@ -375,7 +375,8 @@ end function __vc_on_world_quit() _rules.clear() - gui_util:reset_local() + gui_util:__reset_local() + stdcomp.__reset() end local __vc_coroutines = {} diff --git a/src/frontend/hud.cpp b/src/frontend/hud.cpp index d8f957150..bcf623ced 100644 --- a/src/frontend/hud.cpp +++ b/src/frontend/hud.cpp @@ -687,9 +687,10 @@ void Hud::setPause(bool pause) { } const auto& menu = gui.getMenu(); - if (menu->hasOpenPage()) { + if (!pause && menu->hasOpenPage()) { menu->reset(); - } else { + } + if (pause && !menu->hasOpenPage()) { menu->setPage("pause"); } menu->setVisible(pause); diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp index 0dce2bc03..0e9eb6095 100644 --- a/src/frontend/screens/LevelScreen.cpp +++ b/src/frontend/screens/LevelScreen.cpp @@ -117,7 +117,9 @@ void LevelScreen::initializePack(ContentPackRuntime* pack) { } LevelScreen::~LevelScreen() { - saveWorldPreview(); + if (!controller->getLevel()->getWorld()->isNameless()) { + saveWorldPreview(); + } scripting::on_frontend_close(); // unblock all bindings Events::enableBindings(); diff --git a/src/graphics/ui/elements/Canvas.cpp b/src/graphics/ui/elements/Canvas.cpp new file mode 100644 index 000000000..cf3c2bc6c --- /dev/null +++ b/src/graphics/ui/elements/Canvas.cpp @@ -0,0 +1,19 @@ +#include "Canvas.hpp" + +#include "graphics/core/Batch2D.hpp" +#include "graphics/core/DrawContext.hpp" +#include "graphics/core/Texture.hpp" + +gui::Canvas::Canvas(ImageFormat inFormat, glm::uvec2 inSize) : UINode(inSize) { + ImageData data {inFormat, inSize.x, inSize.y}; + mTexture = Texture::from(&data); +} + +void gui::Canvas::draw(const DrawContext& pctx, const Assets& assets) { + auto pos = calcPos(); + auto col = calcColor(); + + auto batch = pctx.getBatch2D(); + batch->texture(mTexture.get()); + batch->rect(pos.x, pos.y, size.x, size.y, 0, 0, 0, {}, false, true, col); +} diff --git a/src/graphics/ui/elements/Canvas.hpp b/src/graphics/ui/elements/Canvas.hpp new file mode 100644 index 000000000..2be9bf5ca --- /dev/null +++ b/src/graphics/ui/elements/Canvas.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "UINode.hpp" +#include "graphics/core/ImageData.hpp" +#include "graphics/core/Texture.hpp" + +class Texture; + +namespace gui { + class Canvas final : public UINode { + public: + explicit Canvas(ImageFormat inFormat, glm::uvec2 inSize); + + ~Canvas() override = default; + + void draw(const DrawContext& pctx, const Assets& assets) override; + + [[nodiscard]] std::shared_ptr<::Texture> texture() const { + return mTexture; + } + private: + std::shared_ptr<::Texture> mTexture; + std::unique_ptr mData; + }; +} \ No newline at end of file diff --git a/src/graphics/ui/gui_xml.cpp b/src/graphics/ui/gui_xml.cpp index 972b118ff..6bae272da 100644 --- a/src/graphics/ui/gui_xml.cpp +++ b/src/graphics/ui/gui_xml.cpp @@ -4,6 +4,7 @@ #include "elements/Image.hpp" #include "elements/Menu.hpp" #include "elements/Button.hpp" +#include "elements/Canvas.hpp" #include "elements/CheckBox.hpp" #include "elements/TextBox.hpp" #include "elements/TrackBar.hpp" @@ -455,6 +456,18 @@ static std::shared_ptr readImage( return image; } +static std::shared_ptr readCanvas( + const UiXmlReader& reader, const xml::xmlelement& element +) { + auto size = glm::uvec2{32, 32}; + if (element.has("size")) { + size = element.attr("size").asVec2(); + } + auto image = std::make_shared(ImageFormat::rgba8888, size); + _readUINode(reader, element, *image); + return image; +} + static std::shared_ptr readTrackBar( const UiXmlReader& reader, const xml::xmlelement& element ) { @@ -634,6 +647,7 @@ static std::shared_ptr readPageBox(UiXmlReader& reader, const xml::xmlel UiXmlReader::UiXmlReader(const scriptenv& env) : env(env) { contextStack.emplace(""); add("image", readImage); + add("canvas", readCanvas); add("label", readLabel); add("panel", readPanel); add("button", readButton); diff --git a/src/logic/scripting/lua/libs/libgui.cpp b/src/logic/scripting/lua/libs/libgui.cpp index 2947d80c9..995cb625d 100644 --- a/src/logic/scripting/lua/libs/libgui.cpp +++ b/src/logic/scripting/lua/libs/libgui.cpp @@ -3,6 +3,7 @@ #include "engine/Engine.hpp" #include "frontend/locale.hpp" #include "graphics/ui/elements/Button.hpp" +#include "graphics/ui/elements/Canvas.hpp" #include "graphics/ui/elements/CheckBox.hpp" #include "graphics/ui/elements/Image.hpp" #include "graphics/ui/elements/InventoryView.hpp" @@ -323,6 +324,13 @@ static int p_get_src(UINode* node, lua::State* L) { return 0; } +static int p_get_data(UINode* node, lua::State* L) { + if (auto canvas = dynamic_cast(node)) { + return lua::newuserdata(L, canvas->texture()); + } + return 0; +} + static int p_get_add(UINode* node, lua::State* L) { if (dynamic_cast(node)) { return lua::pushcfunction(L, lua::wrap); @@ -464,6 +472,7 @@ static int l_gui_getattr(lua::State* L) { {"inventory", p_get_inventory}, {"focused", p_get_focused}, {"cursor", p_get_cursor}, + {"data", p_get_data}, }; auto func = getters.find(attr); if (func != getters.end()) { diff --git a/src/logic/scripting/lua/libs/libworld.cpp b/src/logic/scripting/lua/libs/libworld.cpp index bbba422df..362d08c79 100644 --- a/src/logic/scripting/lua/libs/libworld.cpp +++ b/src/logic/scripting/lua/libs/libworld.cpp @@ -6,6 +6,7 @@ #include "assets/AssetsLoader.hpp" #include "coders/json.hpp" #include "engine/Engine.hpp" +#include "files/WorldFiles.hpp" #include "files/engine_paths.hpp" #include "files/files.hpp" #include "lighting/Lighting.hpp" @@ -125,11 +126,21 @@ static int l_get_chunk_data(lua::State* L) { int x = static_cast(lua::tointeger(L, 1)); int z = static_cast(lua::tointeger(L, 2)); const auto& chunk = level->chunks->getChunk(x, z); + + std::vector chunkData; if (chunk == nullptr) { - lua::pushnil(L); - return 0; + auto& regions = level->getWorld()->wfile->getRegions(); + auto voxelData = regions.getVoxels(x, z); + if (voxelData == nullptr) { + return 0; + } + static util::Buffer rleBuffer(CHUNK_DATA_LEN * 2); + auto metadata = regions.getBlocksData(x, z); + chunkData = + compressed_chunks::encode(voxelData.get(), metadata, rleBuffer); + } else { + chunkData = compressed_chunks::encode(*chunk); } - auto chunkData = compressed_chunks::encode(*chunk); return lua::newuserdata(L, std::move(chunkData)); } @@ -158,9 +169,14 @@ static void integrate_chunk_client(Chunk& chunk) { } static int l_set_chunk_data(lua::State* L) { + if (level == nullptr) { + throw std::runtime_error("no open world"); + } + int x = static_cast(lua::tointeger(L, 1)); int z = static_cast(lua::tointeger(L, 2)); auto buffer = lua::require_bytearray(L, 3); + auto chunk = level->chunks->getChunk(x, z); if (chunk == nullptr) { return lua::pushboolean(L, false); @@ -175,6 +191,21 @@ static int l_set_chunk_data(lua::State* L) { return lua::pushboolean(L, true); } +static int l_save_chunk_data(lua::State* L) { + if (level == nullptr) { + throw std::runtime_error("no open world"); + } + + int x = static_cast(lua::tointeger(L, 1)); + int z = static_cast(lua::tointeger(L, 2)); + auto buffer = lua::require_bytearray(L, 3); + + compressed_chunks::save( + x, z, std::move(buffer), level->getWorld()->wfile->getRegions() + ); + return 0; +} + static int l_count_chunks(lua::State* L) { if (level == nullptr) { return 0; @@ -197,6 +228,7 @@ const luaL_Reg worldlib[] = { {"exists", lua::wrap}, {"get_chunk_data", lua::wrap}, {"set_chunk_data", lua::wrap}, + {"save_chunk_data", lua::wrap}, {"count_chunks", lua::wrap}, {NULL, NULL} }; diff --git a/src/logic/scripting/lua/lua_custom_types.hpp b/src/logic/scripting/lua/lua_custom_types.hpp index bb8037ee1..56a3c83c5 100644 --- a/src/logic/scripting/lua/lua_custom_types.hpp +++ b/src/logic/scripting/lua/lua_custom_types.hpp @@ -8,6 +8,8 @@ struct fnl_state; class Heightmap; class VoxelFragment; +class Texture; +class ImageData; namespace lua { class Userdata { @@ -90,4 +92,25 @@ namespace lua { inline static std::string TYPENAME = "VoxelFragment"; }; static_assert(!std::is_abstract()); + + class LuaCanvas : public Userdata { + public: + explicit LuaCanvas(std::shared_ptr inTexture); + ~LuaCanvas() override = default; + + const std::string& getTypeName() const override { + return TYPENAME; + } + + [[nodiscard]] Texture& texture() const { return *mTexture; } + + [[nodiscard]] ImageData& data() const { return *mData; } + + static int createMetatable(lua::State*); + inline static std::string TYPENAME = "Canvas"; + private: + std::shared_ptr mTexture; + std::unique_ptr mData; + }; + static_assert(!std::is_abstract()); } diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index 39cb1b8f0..1f4bde7d2 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -115,6 +115,7 @@ void lua::init_state(State* L, StateType stateType) { newusertype(L); newusertype(L); newusertype(L); + newusertype(L); } void lua::initialize(const EnginePaths& paths, const CoreParameters& params) { diff --git a/src/logic/scripting/lua/lua_util.hpp b/src/logic/scripting/lua/lua_util.hpp index 1baf9c357..9539fbbf0 100644 --- a/src/logic/scripting/lua/lua_util.hpp +++ b/src/logic/scripting/lua/lua_util.hpp @@ -274,7 +274,7 @@ namespace lua { inline int newuserdata(lua::State* L, Args&&... args) { const auto& found = usertypeNames.find(typeid(T)); void* ptr = lua_newuserdata(L, sizeof(T)); - new (ptr) T(args...); + new (ptr) T(std::forward(args)...); if (found == usertypeNames.end()) { log_error( diff --git a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp new file mode 100644 index 000000000..b9a205147 --- /dev/null +++ b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp @@ -0,0 +1,140 @@ +#include + +#include "graphics/core/ImageData.hpp" +#include "graphics/core/Texture.hpp" +#include "logic/scripting/lua/lua_custom_types.hpp" +#include "logic/scripting/lua/lua_util.hpp" + +using namespace lua; + +LuaCanvas::LuaCanvas(std::shared_ptr inTexture) + : mTexture(std::move(inTexture)) { + mData = mTexture->readData(); +} + +union RGBA { + uint8_t rgba[4]; + uint32_t raw; +}; + +static RGBA* get_at(const ImageData& data, uint index) { + if (index >= data.getWidth() * data.getHeight()) { + return nullptr; + } + return reinterpret_cast(data.getData() + index * sizeof(RGBA)); +} + +static RGBA* get_at(const ImageData& data, uint x, uint y) { + return get_at(data, y * data.getWidth() + x); +} + +static RGBA* get_at(State* L, uint x, uint y) { + if (auto texture = touserdata(L, 1)) { + return get_at(texture->data(), x, y); + } + return nullptr; +} + +static int l_at(State* L) { + auto x = static_cast(tointeger(L, 2)); + auto y = static_cast(tointeger(L, 3)); + + if (auto pixel = get_at(L, x, y)) { + return pushinteger(L, pixel->raw); + } + + return 0; +} + +static int l_set(State* L) { + auto x = static_cast(tointeger(L, 2)); + auto y = static_cast(tointeger(L, 3)); + + if (auto pixel = get_at(L, x, y)) { + switch (gettop(L)) { + case 4: + pixel->raw = static_cast(tointeger(L, 4)); + return 1; + case 6: + pixel->rgba[0] = static_cast(tointeger(L, 4)); + pixel->rgba[1] = static_cast(tointeger(L, 5)); + pixel->rgba[2] = static_cast(tointeger(L, 6)); + pixel->rgba[3] = 255; + return 1; + case 7: + pixel->rgba[0] = static_cast(tointeger(L, 4)); + pixel->rgba[1] = static_cast(tointeger(L, 5)); + pixel->rgba[2] = static_cast(tointeger(L, 6)); + pixel->rgba[3] = static_cast(tointeger(L, 7)); + return 1; + default: + return 0; + } + } + + return 0; +} + +static int l_update(State* L) { + if (auto texture = touserdata(L, 1)) { + texture->texture().reload(texture->data()); + } + return 0; +} + +static std::unordered_map methods { + {"at", lua::wrap}, + {"set", lua::wrap}, + {"update", lua::wrap} +}; + +static int l_meta_index(State* L) { + auto texture = touserdata(L, 1); + if (texture == nullptr) { + return 0; + } + auto& data = texture->data(); + if (isnumber(L, 2)) { + if (auto pixel = get_at(data, static_cast(tointeger(L, 2)))) { + return pushinteger(L, pixel->raw); + } + } + if (isstring(L, 2)) { + auto name = tostring(L, 2); + if (!strcmp(name, "width")) { + return pushinteger(L, data.getWidth()); + } + if (!strcmp(name, "height")) { + return pushinteger(L, data.getHeight()); + } + if (auto func = methods.find(tostring(L, 2)); func != methods.end()) { + return pushcfunction(L, func->second); + } + } + return 0; +} + +static int l_meta_newindex(State* L) { + auto texture = touserdata(L, 1); + if (texture == nullptr) { + return 0; + } + auto& data = texture->data(); + if (isnumber(L, 2) && isnumber(L, 3)) { + if (auto pixel = get_at(data, static_cast(tointeger(L, 2)))) { + pixel->raw = static_cast(tointeger(L, 3)); + return 1; + } + return 1; + } + return 0; +} + +int LuaCanvas::createMetatable(State* L) { + createtable(L, 0, 3); + pushcfunction(L, lua::wrap); + setfield(L, "__index"); + pushcfunction(L, lua::wrap); + setfield(L, "__newindex"); + return 1; +} diff --git a/src/voxels/compressed_chunks.cpp b/src/voxels/compressed_chunks.cpp index 0e6772af8..24cc02dd9 100644 --- a/src/voxels/compressed_chunks.cpp +++ b/src/voxels/compressed_chunks.cpp @@ -2,27 +2,24 @@ #include "coders/rle.hpp" #include "coders/gzip.hpp" -#include "coders/byte_utils.hpp" -#include "voxels/Chunk.hpp" + +#include "files/WorldFiles.hpp" inline constexpr int HAS_VOXELS = 0x1; inline constexpr int HAS_METADATA = 0x2; -std::vector compressed_chunks::encode(const Chunk& chunk) { - auto data = chunk.encode(); - - /// world.get_chunk_data is only available in the main Lua state - static util::Buffer rleBuffer; - if (rleBuffer.size() < CHUNK_DATA_LEN * 2) { - rleBuffer = util::Buffer(CHUNK_DATA_LEN * 2); - } +std::vector compressed_chunks::encode( + const ubyte* data, + const BlocksMetadata& metadata, + util::Buffer& rleBuffer +) { size_t rleCompressedSize = - extrle::encode16(data.get(), CHUNK_DATA_LEN, rleBuffer.data()); + extrle::encode16(data, CHUNK_DATA_LEN, rleBuffer.data()); const auto gzipCompressedData = gzip::compress( rleBuffer.data(), rleCompressedSize ); - auto metadataBytes = chunk.blocksMetadata.serialize(); + auto metadataBytes = metadata.serialize(); ByteBuilder builder(2 + 8 + gzipCompressedData.size() + metadataBytes.size()); builder.put(HAS_VOXELS | HAS_METADATA); // flags @@ -34,6 +31,23 @@ std::vector compressed_chunks::encode(const Chunk& chunk) { return builder.build(); } +std::vector compressed_chunks::encode(const Chunk& chunk) { + auto data = chunk.encode(); + + /// world.get_chunk_data is only available in the main Lua state + static util::Buffer rleBuffer(CHUNK_DATA_LEN * 2); + return encode(data.get(), chunk.blocksMetadata, rleBuffer); +} + +static void read_voxel_data(ByteReader& reader, util::Buffer& dst) { + size_t gzipCompressedSize = reader.getInt32(); + + auto rleData = gzip::decompress(reader.pointer(), gzipCompressedSize); + reader.skip(gzipCompressedSize); + + extrle::decode16(rleData.data(), rleData.size(), dst.data()); +} + void compressed_chunks::decode(Chunk& chunk, const ubyte* src, size_t size) { ByteReader reader(src, size); @@ -41,14 +55,9 @@ void compressed_chunks::decode(Chunk& chunk, const ubyte* src, size_t size) { reader.skip(1); // reserved byte if (flags & HAS_VOXELS) { - size_t gzipCompressedSize = reader.getInt32(); - - auto rleData = gzip::decompress(reader.pointer(), gzipCompressedSize); - reader.skip(gzipCompressedSize); - /// world.get_chunk_data is only available in the main Lua state static util::Buffer voxelData (CHUNK_DATA_LEN); - extrle::decode16(rleData.data(), rleData.size(), voxelData.data()); + read_voxel_data(reader, voxelData); chunk.decode(voxelData.data()); chunk.updateHeights(); } @@ -59,3 +68,30 @@ void compressed_chunks::decode(Chunk& chunk, const ubyte* src, size_t size) { } chunk.setModifiedAndUnsaved(); } + +void compressed_chunks::save( + int x, int z, std::vector bytes, WorldRegions& regions +) { + ByteReader reader(bytes.data(), bytes.size()); + + ubyte flags = reader.get(); + reader.skip(1); // reserved byte + if (flags & HAS_VOXELS) { + util::Buffer voxelData (CHUNK_DATA_LEN); + read_voxel_data(reader, voxelData); + regions.put( + x, z, REGION_LAYER_VOXELS, voxelData.release(), CHUNK_DATA_LEN + ); + } + if (flags & HAS_METADATA) { + size_t metadataSize = reader.getInt32(); + regions.put( + x, + z, + REGION_LAYER_BLOCKS_DATA, + util::Buffer(reader.pointer(), metadataSize).release(), + metadataSize + ); + reader.skip(metadataSize); + } +} diff --git a/src/voxels/compressed_chunks.hpp b/src/voxels/compressed_chunks.hpp index dc0bc5b6a..dd00bc66d 100644 --- a/src/voxels/compressed_chunks.hpp +++ b/src/voxels/compressed_chunks.hpp @@ -1,12 +1,20 @@ #pragma once #include "typedefs.hpp" +#include "Chunk.hpp" +#include "coders/byte_utils.hpp" #include -class Chunk; +class WorldRegions; namespace compressed_chunks { + std::vector encode( + const ubyte* voxelData, + const BlocksMetadata& metadata, + util::Buffer& rleBuffer + ); std::vector encode(const Chunk& chunk); void decode(Chunk& chunk, const ubyte* src, size_t size); + void save(int x, int z, std::vector bytes, WorldRegions& regions); }