Merge branch 'MihailRis:main' into main

This commit is contained in:
Xertis 2025-01-14 00:05:07 +03:00 committed by GitHub
commit a83b63db6c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 491 additions and 209 deletions

3
.cmake-format.py Normal file
View File

@ -0,0 +1,3 @@
tab_size = 4
enable_sort = True
autosort = True

View File

@ -32,11 +32,11 @@ jobs:
# install EnTT
git clone https://github.com/skypjack/entt.git
cd entt/build
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
cmake -DCMAKE_BUILD_TYPE=Release ..
sudo make install
cd ../..
- name: Configure
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DVOXELENGINE_BUILD_APPDIR=1 -DVOXELENGINE_BUILD_TESTS=ON
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_APPDIR=1 -DVOXELENGINE_BUILD_TESTS=ON
- name: Build
run: cmake --build build -t install
- name: Run tests

View File

@ -8,18 +8,20 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
# We use two types linking: for clang build is static (vcpkg triplet x64-windows-static)
# and for msvc build is dynamic linking (vcpkg triplet x64-windows)
# By default CMAKE_MSVC_RUNTIME_LIBRARY set by MultiThreaded$<$<CONFIG:Debug>:Debug>DLL
if (VCPKG_TARGET_TRIPLET MATCHES "static")
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
# We use two types linking: for clang build is static (vcpkg triplet
# x64-windows-static) and for msvc build is dynamic linking (vcpkg triplet
# x64-windows) By default CMAKE_MSVC_RUNTIME_LIBRARY set by
# MultiThreaded$<$<CONFIG:Debug>:Debug>DLL
if(VCPKG_TARGET_TRIPLET MATCHES "static")
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()
endif()
add_subdirectory(src)
add_executable(${PROJECT_NAME} src/main.cpp)
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_include_directories(${PROJECT_NAME}
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
if(VOXELENGINE_BUILD_APPDIR)
include(${CMAKE_CURRENT_SOURCE_DIR}/dev/cmake/BuildAppdir.cmake)
@ -27,33 +29,44 @@ endif()
if(MSVC)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
set(CMAKE_BUILD_TYPE
Release
CACHE STRING "Build type" FORCE)
endif()
if((CMAKE_BUILD_TYPE EQUAL "Release") OR (CMAKE_BUILD_TYPE EQUAL "RelWithDebInfo"))
target_compile_options(${PROJECT_NAME} PRIVATE /W4 /MT /O2)
if((CMAKE_BUILD_TYPE EQUAL "Release") OR (CMAKE_BUILD_TYPE EQUAL
"RelWithDebInfo"))
target_compile_options(${PROJECT_NAME} PRIVATE /W4 /MT /O2)
else()
target_compile_options(${PROJECT_NAME} PRIVATE /W4)
target_compile_options(${PROJECT_NAME} PRIVATE /W4)
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /source-charset:UTF-8 /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR")
set(CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} /source-charset:UTF-8 /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR"
)
else()
target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra
# additional warnings
-Wformat-nonliteral -Wcast-align
-Wpointer-arith -Wundef
-Wwrite-strings -Wno-unused-parameter)
if (CMAKE_BUILD_TYPE MATCHES "Debug")
target_compile_options(${PROJECT_NAME} PRIVATE -Og)
endif()
if (WIN32)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
endif()
target_compile_options(
${PROJECT_NAME}
PRIVATE -Wall
-Wextra
# additional warnings
-Wformat-nonliteral
-Wcast-align
-Wpointer-arith
-Wundef
-Wwrite-strings
-Wno-unused-parameter)
if(CMAKE_BUILD_TYPE MATCHES "Debug")
target_compile_options(${PROJECT_NAME} PRIVATE -Og)
endif()
if(WIN32)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
endif()
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -no-pie -lstdc++fs")
endif()
if (WIN32)
if(WIN32)
target_link_libraries(${PROJECT_NAME} VoxelEngineSrc winmm)
endif()
@ -61,14 +74,13 @@ target_link_libraries(${PROJECT_NAME} VoxelEngineSrc ${CMAKE_DL_LIBS})
# Deploy res to build dir
add_custom_command(
TARGET ${PROJECT_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory_if_different
${CMAKE_CURRENT_SOURCE_DIR}/res
$<TARGET_FILE_DIR:${PROJECT_NAME}>/res
)
TARGET ${PROJECT_NAME}
POST_BUILD
COMMAND
${CMAKE_COMMAND} -E copy_directory_if_different
${CMAKE_CURRENT_SOURCE_DIR}/res $<TARGET_FILE_DIR:${PROJECT_NAME}>/res)
if (VOXELENGINE_BUILD_TESTS)
if(VOXELENGINE_BUILD_TESTS)
enable_testing()
add_subdirectory(test)
endif()

View File

@ -5,6 +5,7 @@ app.reconfig_packs({"base"}, {})
app.new_world("demo", "2019", "core:default")
assert(world.is_open())
assert(world.get_generator() == "core:default")
assert(app.is_content_loaded())
app.sleep(1)
assert(world.get_total_time() > 0.0)
print(world.get_total_time())
@ -12,6 +13,7 @@ print(world.get_total_time())
-- Close
app.close_world(true)
assert(not world.is_open())
assert(not app.is_content_loaded())
-- Reopen
app.open_world("demo")

View File

@ -69,6 +69,12 @@ app.config_packs(
Updates the packs configuration, automatically removing unspecified ones, adding those missing in the previous configuration.
Uses app.reconfig_packs.
```lua
app.is_content_loaded() -> bool
```
Checks if content is loaded.
```lua
app.new_world(
-- world name

View File

@ -47,4 +47,18 @@ world.is_night() -> bool
-- Returns the total number of chunks loaded into memory
world.count_chunks() -> int
-- Returns the compressed chunk data to send.
-- Currently includes:
-- 1. Voxel data (id and state)
-- 2. Voxel metadata (fields)
world.get_chunk_data(x: int, z: int) -> Bytearray
-- Modifies the chunk based on the compressed data.
-- Returns true if the chunk exists.
world.set_chunk_data(
x: int, z: int,
-- compressed chunk data
data: Bytearray
) -> bool
```

View File

@ -126,6 +126,34 @@ function on_block_interact(blockid, x, y, z, playerid) -> bool
Called on block RMB click interaction. Prevents block placing if **true** returned.
### Chunk Events (world.lua)
```lua
function on_chunk_present(x: int, z: int, loaded: bool)
```
Called after a chunk is generated/loaded. If a previously saved chunk is loaded, `loaded` will be true.
```lua
function on_chunk_remove(x: int, z: int)
```
Called when a chunk is unloaded from the world.
### Inventory Events (world.lua)
```lua
function on_inventory_open(invid: int, playerid: int)
```
Called when the inventory is opened. If the inventory was not opened directly by the player, playerid will be -1.
```lua
function on_inventory_closed(invid: int, playerid: int)
```
Called when the inventory is closed.
## Layout events
Script *layouts/layout_name.xml.lua* events.

View File

@ -69,6 +69,12 @@ app.config_packs(
Обновляет конфигурацию паков, автоматически удаляя лишние, добавляя отсутствующие в прошлой конфигурации.
Использует app.reconfig_packs.
```lua
app.is_content_loaded() -> bool
```
Проверяет, загружен ли контент.
```lua
app.new_world(
-- название мира

View File

@ -46,4 +46,18 @@ world.is_night() -> bool
-- Возвращает общее количество загруженных в память чанков
world.count_chunks() -> int
-- Возвращает сжатые данные чанка для отправки.
-- На данный момент включает:
-- 1. Данные вокселей (id и состояние)
-- 2. Метаданные (поля) вокселей
world.get_chunk_data(x: int, z: int) -> Bytearray
-- Изменяет чанк на основе сжатых данных.
-- Возвращает true если чанк существует.
world.set_chunk_data(
x: int, z: int,
-- сжатые данные чанка
data: Bytearray
) -> bool
```

View File

@ -126,6 +126,35 @@ function on_block_interact(blockid, x, y, z, playerid) -> bool
Вызывается при нажатии на блок ПКМ. Предотвращает установку блоков, если возвращает `true`
### События чанков (world.lua)
```lua
function on_chunk_present(x: int, z: int, loaded: bool)
```
Вызывается после генерации/загрузки чанка. В случае загрузки ранее сохраненного чанка `loaded` будет истинным.
```lua
function on_chunk_remove(x: int, z: int)
```
Вызывается при выгрузке чанка из мира.
### События инвентарей (world.lua)
```lua
function on_inventory_open(invid: int, playerid: int)
```
Вызывается при открытии инвентаря. Если инвентарь был открыт не напрямую игроком, playerid будет равен -1.
```lua
function on_inventory_closed(invid: int, playerid: int)
```
Вызывается при закрытии инвентаря.
## События макета
События прописываются в файле `layouts/имя_макета.xml.lua`.

View File

@ -19,3 +19,4 @@ player.pick="mouse:middle"
player.drop="key:q"
player.fast_interaction="key:x"
hud.inventory="key:tab"
hud.chat="key:t"

View File

@ -39,6 +39,7 @@ local function complete_app_lib(app)
app.get_setting_info = core.get_setting_info
app.load_content = core.load_content
app.reset_content = core.reset_content
app.is_content_loaded = core.is_content_loaded
function app.config_packs(packs_list)
-- Check if packs are valid and add dependencies to the configuration
@ -376,6 +377,14 @@ function __vc_on_hud_open()
hud.show_overlay("core:console", false, {"console"})
end)
end)
input.add_callback("hud.chat", function()
if hud.is_paused() then
return
end
time.post_runnable(function()
hud.show_overlay("core:console", false, {"chat"})
end)
end)
end
local RULES_FILE = "world:rules.toml"

View File

@ -33,6 +33,7 @@ movement.sprint=Sprint
movement.crouch=Crouch
movement.cheat=Cheat
hud.inventory=Inventory
hud.chat=Chat
player.pick=Pick Block
player.attack=Attack
player.destroy=Destroy

View File

@ -103,6 +103,7 @@ movement.sprint=Ускорение
movement.crouch=Красться
movement.cheat=Чит
hud.inventory=Инвентарь
hud.chat=Чат
player.pick=Подобрать Блок
player.attack=Атаковать
player.destroy=Сломать

View File

@ -10,7 +10,7 @@ add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS})
find_package(OpenGL REQUIRED)
find_package(GLEW REQUIRED)
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
# specific for vcpkg
find_package(OpenAL CONFIG REQUIRED)
set(OPENAL_LIBRARY OpenAL::OpenAL)
@ -20,17 +20,19 @@ endif()
find_package(ZLIB REQUIRED)
find_package(PNG REQUIRED)
find_package(CURL REQUIRED)
if (NOT APPLE)
if(NOT APPLE)
find_package(EnTT REQUIRED)
endif()
set(LIBS "")
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
# Use directly linking to lib instead PkgConfig (because pkg-config dont install on windows as default)
# TODO: Do it with findLua.
if (MSVC)
set(LUA_INCLUDE_DIR "$ENV{VCPKG_ROOT}/packages/luajit_${VCPKG_TARGET_TRIPLET}/include/luajit")
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
# Use directly linking to lib instead PkgConfig (because pkg-config dont
# install on windows as default) TODO: Do it with findLua.
if(MSVC)
set(LUA_INCLUDE_DIR
"$ENV{VCPKG_ROOT}/packages/luajit_${VCPKG_TARGET_TRIPLET}/include/luajit"
)
find_package(Lua REQUIRED)
else()
# Used for mingw-clang cross compiling from msys2
@ -49,7 +51,7 @@ elseif(APPLE)
set(LUA_LIBRARIES "/opt/homebrew/lib/libluajit-5.1.a")
message(STATUS "LUA Libraries: ${LUA_LIBRARIES}")
message(STATUS "LUA Include Dir: ${LUA_INCLUDE_DIR}")
set(VORBISLIB ${VORBIS_LDFLAGS})
message(STATUS "Vorbis Lib: ${VORBIS_LDFLAGS}")
else()
@ -70,4 +72,16 @@ endif()
include_directories(${LUA_INCLUDE_DIR})
include_directories(${CURL_INCLUDE_DIR})
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(${PROJECT_NAME} ${LIBS} glfw OpenGL::GL ${OPENAL_LIBRARY} GLEW::GLEW ZLIB::ZLIB PNG::PNG CURL::libcurl ${VORBISLIB} ${LUA_LIBRARIES} ${CMAKE_DL_LIBS})
target_link_libraries(
${PROJECT_NAME}
${LIBS}
glfw
OpenGL::GL
${OPENAL_LIBRARY}
GLEW::GLEW
ZLIB::ZLIB
PNG::PNG
CURL::libcurl
${VORBISLIB}
${LUA_LIBRARIES}
${CMAKE_DL_LIBS})

View File

@ -109,6 +109,10 @@ struct WorldFuncsSet {
bool onblockbroken;
bool onblockinteract;
bool onplayertick;
bool onchunkpresent;
bool onchunkremove;
bool oninventoryopen;
bool oninventoryclosed;
};
class ContentPackRuntime {

View File

@ -70,7 +70,7 @@ void ServerMainloop::run() {
begin = system_clock::now();
}
}
logger.info() << "test finished";
logger.info() << "script finished";
}
void ServerMainloop::setLevel(std::unique_ptr<Level> level) {

View File

@ -413,6 +413,7 @@ std::shared_ptr<Inventory> Hud::openInventory(
}
secondInvView->bind(inv, &content);
add(HudElement(hud_element_mode::inventory_bound, doc, secondUI, false));
scripting::on_inventory_open(&player, *inv);
return inv;
}
@ -447,6 +448,8 @@ void Hud::openInventory(
blockPos = block;
currentblockid = chunks.require(block.x, block.y, block.z).id;
add(HudElement(hud_element_mode::inventory_bound, doc, blockUI, false));
scripting::on_inventory_open(&player, *blockinv);
}
void Hud::showExchangeSlot() {
@ -461,7 +464,6 @@ void Hud::showExchangeSlot() {
exchangeSlot->setInteractive(false);
exchangeSlot->setZIndex(1);
gui.store(SlotView::EXCHANGE_SLOT_NAME, exchangeSlot);
}
void Hud::showOverlay(
@ -517,13 +519,19 @@ void Hud::dropExchangeSlot() {
}
void Hud::closeInventory() {
if (blockUI) {
scripting::on_inventory_closed(&player, *blockUI->getInventory());
blockUI = nullptr;
}
if (secondInvView) {
scripting::on_inventory_closed(&player, *secondInvView->getInventory());
}
dropExchangeSlot();
gui.remove(SlotView::EXCHANGE_SLOT_NAME);
exchangeSlot = nullptr;
exchangeSlotInv = nullptr;
inventoryOpen = false;
inventoryView = nullptr;
blockUI = nullptr;
secondUI = nullptr;
for (auto& element : elements) {

View File

@ -135,7 +135,7 @@ const Mesh* ChunksRenderer::getOrRender(
if (found == meshes.end()) {
return render(chunk, important);
}
if (chunk->flags.modified) {
if (chunk->flags.modified && chunk->flags.lighted) {
render(chunk, important);
}
return found->second.mesh.get();
@ -149,9 +149,17 @@ const Mesh* ChunksRenderer::retrieveChunk(
size_t index, const Camera& camera, Shader& shader, bool culling
) {
auto chunk = chunks.getChunks()[index];
if (chunk == nullptr || !chunk->flags.lighted) {
if (chunk == nullptr) {
return nullptr;
}
if (!chunk->flags.lighted) {
const auto& found = meshes.find({chunk->x, chunk->z});
if (found == meshes.end()) {
return nullptr;
} else {
return found->second.mesh.get();
}
}
float distance = glm::distance(
camera.position,
glm::vec3(

View File

@ -89,7 +89,7 @@ WorldRenderer::WorldRenderer(
) {
auto& settings = engine.getSettings();
level.events->listen(
EVT_CHUNK_HIDDEN,
LevelEventType::CHUNK_HIDDEN,
[this](LevelEventType, Chunk* chunk) { chunks->unload(chunk); }
);
auto assets = engine.getAssets();

View File

@ -14,6 +14,7 @@
#include "scripting/scripting.hpp"
#include "lighting/Lighting.hpp"
#include "settings.hpp"
#include "world/LevelEvents.hpp"
#include "world/Level.hpp"
#include "world/World.hpp"
@ -26,6 +27,14 @@ LevelController::LevelController(
level(std::move(levelPtr)),
chunks(std::make_unique<ChunksController>(*level)),
playerTickClock(20, 3) {
level->events->listen(LevelEventType::CHUNK_PRESENT, [](auto, Chunk* chunk) {
scripting::on_chunk_present(*chunk, chunk->flags.loaded);
});
level->events->listen(LevelEventType::CHUNK_UNLOAD, [](auto, Chunk* chunk) {
scripting::on_chunk_remove(*chunk);
});
if (clientPlayer) {
chunks->lighting = std::make_unique<Lighting>(
level->content, *clientPlayer->chunks
@ -99,6 +108,10 @@ void LevelController::update(float delta, bool pause) {
void LevelController::saveWorld() {
auto world = level->getWorld();
if (world->isNameless()) {
logger.info() << "nameless world will not be saved";
return;
}
logger.info() << "writing world '" << world->getName() << "'";
world->wfile->createDirectories();
scripting::on_world_save();

View File

@ -43,6 +43,10 @@ static int l_reset_content(lua::State* L) {
return 0;
}
static int l_is_content_loaded(lua::State* L) {
return lua::pushboolean(L, content != nullptr);
}
/// @brief Creating new world
/// @param name Name world
/// @param seed Seed world
@ -51,6 +55,9 @@ static int l_new_world(lua::State* L) {
auto name = lua::require_string(L, 1);
auto seed = lua::require_string(L, 2);
auto generator = lua::require_string(L, 3);
if (level != nullptr) {
throw std::runtime_error("world must be closed before");
}
auto controller = engine->getController();
controller->createWorld(name, seed, generator);
return 0;
@ -60,7 +67,9 @@ static int l_new_world(lua::State* L) {
/// @param name Name world
static int l_open_world(lua::State* L) {
auto name = lua::require_string(L, 1);
if (level != nullptr) {
throw std::runtime_error("world must be closed before");
}
auto controller = engine->getController();
controller->openWorld(name, false);
return 0;
@ -258,6 +267,7 @@ const luaL_Reg corelib[] = {
{"get_version", lua::wrap<l_get_version>},
{"load_content", lua::wrap<l_load_content>},
{"reset_content", lua::wrap<l_reset_content>},
{"is_content_loaded", lua::wrap<l_is_content_loaded>},
{"new_world", lua::wrap<l_new_world>},
{"open_world", lua::wrap<l_open_world>},
{"reopen_world", lua::wrap<l_reopen_world>},

View File

@ -4,8 +4,6 @@
#include "api_lua.hpp"
#include "assets/AssetsLoader.hpp"
#include "coders/compression.hpp"
#include "coders/gzip.hpp"
#include "coders/json.hpp"
#include "engine/Engine.hpp"
#include "files/engine_paths.hpp"
@ -14,6 +12,7 @@
#include "voxels/Chunk.hpp"
#include "voxels/Chunks.hpp"
#include "voxels/GlobalChunks.hpp"
#include "voxels/compressed_chunks.hpp"
#include "world/Level.hpp"
#include "world/World.hpp"
#include "logic/LevelController.hpp"
@ -123,122 +122,57 @@ static int l_get_generator(lua::State* L) {
}
static int l_get_chunk_data(lua::State* L) {
int x = (int)lua::tointeger(L, 1);
int y = (int)lua::tointeger(L, 2);
const auto& chunk = level->chunks->getChunk(x, y);
int x = static_cast<int>(lua::tointeger(L, 1));
int z = static_cast<int>(lua::tointeger(L, 2));
const auto& chunk = level->chunks->getChunk(x, z);
if (chunk == nullptr) {
lua::pushnil(L);
return 0;
}
auto chunkData = compressed_chunks::encode(*chunk);
return lua::newuserdata<lua::LuaBytearray>(L, std::move(chunkData));
}
bool compress = false;
if (lua::gettop(L) >= 3) {
compress = lua::toboolean(L, 3);
static void integrate_chunk_client(Chunk& chunk) {
int x = chunk.x;
int z = chunk.z;
auto chunksController = controller->getChunksController();
Lighting& lighting = *chunksController->lighting;
chunk.flags.loadedLights = false;
chunk.flags.lighted = false;
Lighting::prebuildSkyLight(chunk, *indices);
lighting.onChunkLoaded(x, z, true);
for (int lz = -1; lz <= 1; lz++) {
for (int lx = -1; lx <= 1; lx++) {
if (std::abs(lx) + std::abs(lz) != 1) {
continue;
}
if (auto other = level->chunks->getChunk(x + lx, z + lz)) {
other->flags.modified = true;
lighting.onChunkLoaded(x - 1, z, true);
}
}
}
std::vector<ubyte> chunk_data;
if (compress) {
size_t rle_compressed_size;
size_t gzip_compressed_size;
const auto& data_ptr = chunk->encode();
ubyte* data = data_ptr.get();
const auto& rle_compressed_data_ptr = compression::compress(
data,
CHUNK_DATA_LEN,
rle_compressed_size,
compression::Method::EXTRLE16
);
const auto& gzip_compressed_data = compression::compress(
rle_compressed_data_ptr.get(),
rle_compressed_size,
gzip_compressed_size,
compression::Method::GZIP
);
auto tmp = dataio::h2le(rle_compressed_size);
chunk_data.reserve(gzip_compressed_size + sizeof(tmp));
chunk_data.insert(
chunk_data.begin() + 0, (char*)&tmp, ((char*)&tmp) + sizeof(tmp)
);
chunk_data.insert(
chunk_data.begin() + sizeof(tmp),
gzip_compressed_data.get(),
gzip_compressed_data.get() + gzip_compressed_size
);
} else {
const auto& data = chunk->encode();
chunk_data.reserve(CHUNK_DATA_LEN);
chunk_data.insert(
chunk_data.begin(), data.get(), data.get() + CHUNK_DATA_LEN
);
}
return lua::newuserdata<lua::LuaBytearray>(L, chunk_data);
}
static int l_set_chunk_data(lua::State* L) {
int x = (int)lua::tointeger(L, 1);
int y = (int)lua::tointeger(L, 2);
int x = static_cast<int>(lua::tointeger(L, 1));
int z = static_cast<int>(lua::tointeger(L, 2));
auto buffer = lua::touserdata<lua::LuaBytearray>(L, 3);
bool is_compressed = false;
if (lua::gettop(L) >= 4) {
is_compressed = lua::toboolean(L, 4);
}
auto chunk = level->chunks->getChunk(x, y);
auto chunk = level->chunks->getChunk(x, z);
if (chunk == nullptr) {
return 0;
}
if (is_compressed) {
std::vector<ubyte>& raw_data = buffer->data();
size_t gzip_decompressed_size =
dataio::le2h(*(size_t*)(raw_data.data()));
const auto& rle_data = compression::decompress(
raw_data.data() + sizeof(gzip_decompressed_size),
buffer->data().size() - sizeof(gzip_decompressed_size),
gzip_decompressed_size,
compression::Method::GZIP
);
const auto& data = compression::decompress(
rle_data.get(),
gzip_decompressed_size,
CHUNK_DATA_LEN,
compression::Method::EXTRLE16
);
chunk->decode(data.get());
} else {
chunk->decode(buffer->data().data());
compressed_chunks::decode(
*chunk, buffer->data().data(), buffer->data().size()
);
if (controller->getChunksController()->lighting == nullptr) {
return lua::pushboolean(L, true);
}
auto chunksController = controller->getChunksController();
if (chunksController == nullptr) {
return 1;
}
Lighting& lighting = *chunksController->lighting;
chunk->updateHeights();
lighting.buildSkyLight(x, y);
chunk->flags.modified = true;
lighting.onChunkLoaded(x, y, true);
chunk = level->chunks->getChunk(x - 1, y);
if (chunk != nullptr) {
chunk->flags.modified = true;
lighting.onChunkLoaded(x - 1, y, true);
}
chunk = level->chunks->getChunk(x + 1, y);
if (chunk != nullptr) {
chunk->flags.modified = true;
lighting.onChunkLoaded(x + 1, y, true);
}
chunk = level->chunks->getChunk(x, y - 1);
if (chunk != nullptr) {
chunk->flags.modified = true;
lighting.onChunkLoaded(x, y - 1, true);
}
chunk = level->chunks->getChunk(x, y + 1);
if (chunk != nullptr) {
chunk->flags.modified = true;
lighting.onChunkLoaded(x, y + 1, true);
}
return 1;
integrate_chunk_client(*chunk);
return lua::pushboolean(L, true);
}
static int l_count_chunks(lua::State* L) {

View File

@ -268,9 +268,9 @@ KeyCallback lua::create_simple_handler(State* L) {
scripting::common_func lua::create_lambda(State* L) {
auto funcptr = create_lambda_handler(L);
return [=](const std::vector<dv::value>& args) -> dv::value {
int top = gettop(L) + 1;
if (!get_from(L, LAMBDAS_TABLE, *funcptr, false))
return nullptr;
int top = gettop(L) + 1;
for (const auto& arg : args) {
pushvalue(L, arg);
}
@ -290,9 +290,9 @@ scripting::common_func lua::create_lambda(State* L) {
scripting::common_func lua::create_lambda_nothrow(State* L) {
auto funcptr = create_lambda_handler(L);
return [=](const std::vector<dv::value>& args) -> dv::value {
int top = gettop(L) - 1;
if (!get_from(L, LAMBDAS_TABLE, *funcptr, false))
return nullptr;
int top = gettop(L) - 1;
for (const auto& arg : args) {
pushvalue(L, arg);
}

View File

@ -21,6 +21,11 @@ namespace lua {
}
inline void pop(lua::State* L, int n = 1) {
#ifndef NDEBUG
if (lua_gettop(L) < n) {
abort();
}
#endif
lua_pop(L, n);
}
inline void insert(lua::State* L, int idx) {

View File

@ -24,6 +24,7 @@
#include "util/stringutil.hpp"
#include "util/timeutil.hpp"
#include "voxels/Block.hpp"
#include "voxels/Chunk.hpp"
#include "world/Level.hpp"
#include "interfaces/Process.hpp"
@ -411,6 +412,65 @@ bool scripting::on_block_interact(
);
}
void scripting::on_chunk_present(const Chunk& chunk, bool loaded) {
auto args = [&chunk, loaded](lua::State* L) {
lua::pushvec_stack<2>(L, {chunk.x, chunk.z});
lua::pushboolean(L, loaded);
return 3;
};
for (auto& [packid, pack] : content->getPacks()) {
if (pack->worldfuncsset.onchunkpresent) {
lua::emit_event(
lua::get_main_state(), packid + ":.chunkpresent", args
);
}
}
}
void scripting::on_chunk_remove(const Chunk& chunk) {
auto args = [&chunk](lua::State* L) {
lua::pushvec_stack<2>(L, {chunk.x, chunk.z});
return 2;
};
for (auto& [packid, pack] : content->getPacks()) {
if (pack->worldfuncsset.onchunkremove) {
lua::emit_event(
lua::get_main_state(), packid + ":.chunkremove", args
);
}
}
}
void scripting::on_inventory_open(const Player* player, const Inventory& inventory) {
auto args = [player, &inventory](lua::State* L) {
lua::pushinteger(L, inventory.getId());
lua::pushinteger(L, player ? player->getId() : -1);
return 2;
};
for (auto& [packid, pack] : content->getPacks()) {
if (pack->worldfuncsset.oninventoryopen) {
lua::emit_event(
lua::get_main_state(), packid + ":.inventoryopen", args
);
}
}
}
void scripting::on_inventory_closed(const Player* player, const Inventory& inventory) {
auto args = [player, &inventory](lua::State* L) {
lua::pushinteger(L, inventory.getId());
lua::pushinteger(L, player ? player->getId() : -1);
return 2;
};
for (auto& [packid, pack] : content->getPacks()) {
if (pack->worldfuncsset.oninventoryclosed) {
lua::emit_event(
lua::get_main_state(), packid + ":.inventoryclosed", args
);
}
}
}
void scripting::on_player_tick(Player* player, int tps) {
auto args = [=](lua::State* L) {
lua::pushinteger(L, player ? player->getId() : -1);
@ -837,6 +897,14 @@ void scripting::load_world_script(
register_event(env, "on_block_interact", prefix + ":.blockinteract");
funcsset.onplayertick =
register_event(env, "on_player_tick", prefix + ":.playertick");
funcsset.onchunkpresent =
register_event(env, "on_chunk_present", prefix + ":.chunkpresent");
funcsset.onchunkremove =
register_event(env, "on_chunk_remove", prefix + ":.chunkremove");
funcsset.oninventoryopen =
register_event(env, "on_inventory_open", prefix + ":.inventoryopen");
funcsset.oninventoryclosed =
register_event(env, "on_inventory_closed", prefix + ":.inventoryclosed");
}
void scripting::load_layout_script(

View File

@ -17,6 +17,7 @@ struct ContentPack;
class ContentIndices;
class Level;
class Block;
class Chunk;
class Player;
struct ItemDef;
class Inventory;
@ -84,6 +85,13 @@ namespace scripting {
Player* player, const Block& block, const glm::ivec3& pos
);
bool on_block_interact(Player* player, const Block& block, const glm::ivec3& pos);
void on_chunk_present(const Chunk& chunk, bool loaded);
void on_chunk_remove(const Chunk& chunk);
void on_inventory_open(const Player* player, const Inventory& inventory);
void on_inventory_closed(const Player* player, const Inventory& inventory);
void on_player_tick(Player* player, int tps);
/// @brief Called on RMB click with the item selected

View File

@ -1,7 +1,9 @@
#pragma once
#include <mutex>
#include <iostream>
#include <vector>
#include <algorithm>
#include <unordered_map>
#include "typedefs.hpp"
#include "delegates.hpp"

View File

@ -13,19 +13,6 @@ Chunk::Chunk(int xpos, int zpos) : x(xpos), z(zpos) {
top = CHUNK_H;
}
bool Chunk::isEmpty() const {
int id = -1;
for (uint i = 0; i < CHUNK_VOL; i++) {
if (voxels[i].id != id) {
if (id != -1)
return false;
else
id = voxels[i].id;
}
}
return true;
}
void Chunk::updateHeights() {
for (uint i = 0; i < CHUNK_VOL; i++) {
if (voxels[i].id != 0) {

View File

@ -11,6 +11,7 @@
#include "maths/aabb.hpp"
#include "voxel.hpp"
/// @brief Total bytes number of chunk voxel data
inline constexpr int CHUNK_DATA_LEN = CHUNK_VOL * 4;
class ContentReport;
@ -45,8 +46,7 @@ public:
Chunk(int x, int z);
bool isEmpty() const;
/// @brief Refresh `bottom` and `top` values
void updateHeights();
// unused

View File

@ -34,7 +34,7 @@ Chunks::Chunks(
areaMap(w, d) {
areaMap.setCenter(ox-w/2, oz-d/2);
areaMap.setOutCallback([this](int, int, const auto& chunk) {
this->events->trigger(EVT_CHUNK_HIDDEN, chunk.get());
this->events->trigger(LevelEventType::CHUNK_HIDDEN, chunk.get());
});
}
@ -323,7 +323,7 @@ void Chunks::resize(uint32_t newW, uint32_t newD) {
bool Chunks::putChunk(const std::shared_ptr<Chunk>& chunk) {
if (areaMap.set(chunk->x, chunk->z, chunk)) {
if (events) {
events->trigger(LevelEventType::EVT_CHUNK_SHOWN, chunk.get());
events->trigger(LevelEventType::CHUNK_SHOWN, chunk.get());
}
return true;
}

View File

@ -12,6 +12,7 @@
#include "objects/Entities.hpp"
#include "voxels/blocks_agent.hpp"
#include "typedefs.hpp"
#include "world/LevelEvents.hpp"
#include "world/Level.hpp"
#include "world/World.hpp"
#include "Block.hpp"
@ -125,6 +126,8 @@ std::shared_ptr<Chunk> GlobalChunks::create(int x, int z) {
chunk->flags.loadedLights = true;
}
chunk->blocksMetadata = regions.getBlocksData(chunk->x, chunk->z);
level.events->trigger(LevelEventType::CHUNK_PRESENT, chunk.get());
return chunk;
}

View File

@ -0,0 +1,61 @@
#include "compressed_chunks.hpp"
#include "coders/rle.hpp"
#include "coders/gzip.hpp"
#include "coders/byte_utils.hpp"
#include "voxels/Chunk.hpp"
inline constexpr int HAS_VOXELS = 0x1;
inline constexpr int HAS_METADATA = 0x2;
std::vector<ubyte> 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<ubyte> rleBuffer;
if (rleBuffer.size() < CHUNK_DATA_LEN * 2) {
rleBuffer = util::Buffer<ubyte>(CHUNK_DATA_LEN * 2);
}
size_t rleCompressedSize =
extrle::encode16(data.get(), CHUNK_DATA_LEN, rleBuffer.data());
const auto gzipCompressedData = gzip::compress(
rleBuffer.data(), rleCompressedSize
);
auto metadataBytes = chunk.blocksMetadata.serialize();
ByteBuilder builder(2 + 8 + gzipCompressedData.size() + metadataBytes.size());
builder.put(HAS_VOXELS | HAS_METADATA); // flags
builder.put(0); // reserved
builder.putInt32(gzipCompressedData.size());
builder.put(gzipCompressedData.data(), gzipCompressedData.size());
builder.putInt32(metadataBytes.size());
builder.put(metadataBytes.data(), metadataBytes.size());
return builder.build();
}
void compressed_chunks::decode(Chunk& chunk, const ubyte* src, size_t size) {
ByteReader reader(src, size);
ubyte flags = reader.get();
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<ubyte> voxelData (CHUNK_DATA_LEN);
extrle::decode16(rleData.data(), rleData.size(), voxelData.data());
chunk.decode(voxelData.data());
chunk.updateHeights();
}
if (flags & HAS_METADATA) {
size_t metadataSize = reader.getInt32();
chunk.blocksMetadata.deserialize(reader.pointer(), metadataSize);
reader.skip(metadataSize);
}
chunk.setModifiedAndUnsaved();
}

View File

@ -0,0 +1,12 @@
#pragma once
#include "typedefs.hpp"
#include <vector>
class Chunk;
namespace compressed_chunks {
std::vector<ubyte> encode(const Chunk& chunk);
void decode(Chunk& chunk, const ubyte* src, size_t size);
}

View File

@ -52,13 +52,14 @@ Level::Level(
entities->setNextID(worldInfo.nextEntityId);
}
events->listen(LevelEventType::EVT_CHUNK_SHOWN, [this](LevelEventType, Chunk* chunk) {
events->listen(LevelEventType::CHUNK_SHOWN, [this](LevelEventType, Chunk* chunk) {
chunks->incref(chunk);
});
events->listen(LevelEventType::EVT_CHUNK_HIDDEN, [this](LevelEventType, Chunk* chunk) {
events->listen(LevelEventType::CHUNK_HIDDEN, [this](LevelEventType, Chunk* chunk) {
chunks->decref(chunk);
});
chunks->setOnUnload([this](const Chunk& chunk) {
chunks->setOnUnload([this](Chunk& chunk) {
events->trigger(LevelEventType::CHUNK_UNLOAD, &chunk);
AABB aabb = chunk.getAABB();
entities->despawn(entities->getAllInside(aabb));
});

View File

@ -6,9 +6,11 @@
class Chunk;
enum LevelEventType {
EVT_CHUNK_SHOWN,
EVT_CHUNK_HIDDEN,
enum class LevelEventType {
CHUNK_SHOWN,
CHUNK_HIDDEN,
CHUNK_PRESENT,
CHUNK_UNLOAD,
};
using ChunkEventFunc = std::function<void(LevelEventType, Chunk*)>;

View File

@ -94,7 +94,12 @@ std::unique_ptr<Level> World::create(
content,
packs
);
logger.info() << "created world '" << name << "' (" << directory.u8string() << ")";
if (name.empty()) {
logger.info() << "created nameless world";
} else {
logger.info() << "created world '" << name << "' ("
<< directory.u8string() << ")";
}
logger.info() << "world seed: " << seed << " generator: " << generator;
return std::make_unique<Level>(std::move(world), content, settings);
}

View File

@ -140,6 +140,10 @@ public:
/// @brief Get world generator id
std::string getGenerator() const;
bool isNameless() const {
return info.name.empty();
}
WorldInfo& getInfo() {
return info;
}

View File

@ -8,23 +8,19 @@ find_package(GTest)
add_executable(${PROJECT_NAME} ${SOURCES})
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../src)
target_link_libraries(
${PROJECT_NAME}
VoxelEngineSrc
GTest::gtest_main
)
target_include_directories(${PROJECT_NAME}
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../src)
target_link_libraries(${PROJECT_NAME} VoxelEngineSrc GTest::gtest_main)
# HACK: copy res to test/ folder for fixing problem compatibility MultiConfig and non
# MultiConfig builds. Delete in future and use only root res folder
# Also this resolve problem with ctests, because it set cwd to CMAKE_CURRENT_BINARY_DIR
# HACK: copy res to test/ folder for fixing problem compatibility MultiConfig
# and non MultiConfig builds. Delete in future and use only root res folder Also
# this resolve problem with ctests, because it set cwd to
# CMAKE_CURRENT_BINARY_DIR
add_custom_command(
TARGET ${PROJECT_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory_if_different
${CMAKE_SOURCE_DIR}/res
${CMAKE_CURRENT_BINARY_DIR}/res
)
TARGET ${PROJECT_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory_if_different
${CMAKE_SOURCE_DIR}/res ${CMAKE_CURRENT_BINARY_DIR}/res)
include(GoogleTest)
gtest_discover_tests(${PROJECT_NAME})

View File

@ -8,24 +8,35 @@ add_executable(${PROJECT_NAME} ${SOURCES})
if(MSVC)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
set(CMAKE_BUILD_TYPE
Release
CACHE STRING "Build type" FORCE)
endif()
if((CMAKE_BUILD_TYPE EQUAL "Release") OR (CMAKE_BUILD_TYPE EQUAL "RelWithDebInfo"))
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Release>:Release>")
target_compile_options(${PROJECT_NAME} PRIVATE /W4 /MT /O2)
if((CMAKE_BUILD_TYPE EQUAL "Release") OR (CMAKE_BUILD_TYPE EQUAL
"RelWithDebInfo"))
set(CMAKE_MSVC_RUNTIME_LIBRARY
"MultiThreaded$<$<CONFIG:Release>:Release>")
target_compile_options(${PROJECT_NAME} PRIVATE /W4 /MT /O2)
else()
target_compile_options(${PROJECT_NAME} PRIVATE /W4)
target_compile_options(${PROJECT_NAME} PRIVATE /W4)
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
else()
target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra
-Wformat-nonliteral -Wcast-align
-Wpointer-arith -Wundef
-Wwrite-strings -Wno-unused-parameter)
target_compile_options(
${PROJECT_NAME}
PRIVATE -Wall
-Wextra
-Wformat-nonliteral
-Wcast-align
-Wpointer-arith
-Wundef
-Wwrite-strings
-Wno-unused-parameter)
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -no-pie -lstdc++fs")
endif()
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../src ${CMAKE_DL_LIBS})
target_include_directories(
${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../src ${CMAKE_DL_LIBS})