diff --git a/doc/en/scripting/events.md b/doc/en/scripting/events.md index cc820898..21e1cee5 100644 --- a/doc/en/scripting/events.md +++ b/doc/en/scripting/events.md @@ -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. diff --git a/doc/ru/scripting/events.md b/doc/ru/scripting/events.md index a5cc3e72..c7781879 100644 --- a/doc/ru/scripting/events.md +++ b/doc/ru/scripting/events.md @@ -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`. diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index c8e0de9b..e4360bdc 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -109,6 +109,10 @@ struct WorldFuncsSet { bool onblockbroken; bool onblockinteract; bool onplayertick; + bool onchunkpresent; + bool onchunkremove; + bool oninventoryopen; + bool oninventoryclosed; }; class ContentPackRuntime { diff --git a/src/frontend/hud.cpp b/src/frontend/hud.cpp index 3bf7cab2..29b5df05 100644 --- a/src/frontend/hud.cpp +++ b/src/frontend/hud.cpp @@ -413,6 +413,7 @@ std::shared_ptr 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,18 @@ 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) { diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index f83363cd..cefb61d5 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -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(); diff --git a/src/logic/LevelController.cpp b/src/logic/LevelController.cpp index 15ed2eb8..06fd6e96 100644 --- a/src/logic/LevelController.cpp +++ b/src/logic/LevelController.cpp @@ -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(*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( level->content, *clientPlayer->chunks diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index b8f00d3f..d58879d3 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -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( diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index fb552f66..b447de28 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -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 diff --git a/src/voxels/Chunks.cpp b/src/voxels/Chunks.cpp index f65c1863..bf9b2990 100644 --- a/src/voxels/Chunks.cpp +++ b/src/voxels/Chunks.cpp @@ -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) { 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; } diff --git a/src/voxels/GlobalChunks.cpp b/src/voxels/GlobalChunks.cpp index 8f8e05bb..1e67a374 100644 --- a/src/voxels/GlobalChunks.cpp +++ b/src/voxels/GlobalChunks.cpp @@ -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 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; } diff --git a/src/world/Level.cpp b/src/world/Level.cpp index 7ea871ee..460b34a4 100644 --- a/src/world/Level.cpp +++ b/src/world/Level.cpp @@ -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)); }); diff --git a/src/world/LevelEvents.hpp b/src/world/LevelEvents.hpp index f7bbaa7f..8096428b 100644 --- a/src/world/LevelEvents.hpp +++ b/src/world/LevelEvents.hpp @@ -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;