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/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/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/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); }