From 518d5d5ae4bc407676b5f6bc5f71a6f576aa2753 Mon Sep 17 00:00:00 2001 From: Xertis <118364459+Xertis@users.noreply.github.com> Date: Sat, 4 Apr 2026 01:53:49 +0300 Subject: [PATCH 1/3] Add on_entity_spawned and on_entity_despawned events --- doc/en/scripting/ecs.md | 4 +++ doc/en/scripting/events.md | 11 ++++++++ doc/ru/scripting/ecs.md | 4 +++ doc/ru/scripting/events.md | 12 ++++++++ src/content/ContentPack.hpp | 2 ++ src/logic/scripting/scripting.cpp | 32 ++++++++++++++++++++++ src/logic/scripting/scripting.hpp | 3 ++ src/logic/scripting/scripting_entities.cpp | 8 +++++- 8 files changed, 75 insertions(+), 1 deletion(-) diff --git a/doc/en/scripting/ecs.md b/doc/en/scripting/ecs.md index 7b1f87a1a..878452928 100644 --- a/doc/en/scripting/ecs.md +++ b/doc/en/scripting/ecs.md @@ -180,6 +180,10 @@ rig:get_color() -> vec3 rig:set_color(color: vec3) ``` +> [!WARNING] +> When an entity moves outside the loaded area, it is removed, triggering the event. +> When it re-enters the loaded area, the entity is spawned again. + ## Component events ```lua diff --git a/doc/en/scripting/events.md b/doc/en/scripting/events.md index fea9de7df..f475dc1c1 100644 --- a/doc/en/scripting/events.md +++ b/doc/en/scripting/events.md @@ -152,6 +152,17 @@ function on_block_interact(blockid, x, y, z, playerid) -> bool Called on block RMB click interaction. Prevents block placing if **true** returned. +```lua +function on_entity_spawned(uid) +``` +Called after an entity is spawned. + +```lua +function on_entity_despawned(uid) +``` + +Called after an entity is removed. At the time of the call, the entity with the given uid has already been deleted. + ### Chunk Events (world.lua) ```lua diff --git a/doc/ru/scripting/ecs.md b/doc/ru/scripting/ecs.md index 4f9d45f08..1a5f6ccf3 100644 --- a/doc/ru/scripting/ecs.md +++ b/doc/ru/scripting/ecs.md @@ -180,6 +180,10 @@ rig:get_color() -> vec3 rig:set_color(color: vec3) ``` +> [!WARNING] +> При выходе сущности за пределы зоны прогрузки она удаляется, вызывая события. +> При повторном попадании в зону прогрузки сущность спавнится заново. + ## События компонента ```lua diff --git a/doc/ru/scripting/events.md b/doc/ru/scripting/events.md index 7dbe7bc07..fa20e4030 100644 --- a/doc/ru/scripting/events.md +++ b/doc/ru/scripting/events.md @@ -156,6 +156,18 @@ function on_block_interact(blockid, x, y, z, playerid) -> bool Вызывается при нажатии на блок ПКМ. Предотвращает установку блоков, если возвращает `true` +```lua +function on_entity_spawned(uid) +``` + +Вызывается после спавна сущности + +```lua +function on_entity_despawned(uid) +``` + +Вызывается после удаления сущности, на момент вызова события сущность с переданным uid уже удалена + ```lua function on_player_tick(playerid: int, tps: int) ``` diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index a4548d038..bd0ccc79f 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -127,6 +127,8 @@ struct WorldFuncsSet { bool onchunkremove; bool oninventoryopen; bool oninventoryclosed; + bool onentityspawned; + bool onentitydespawned; }; class ContentPackRuntime { diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index bba9f6837..232c72796 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -555,6 +555,34 @@ void scripting::on_player_tick(Player* player, int tps) { } } +void scripting::on_entity_spawned(entityid_t eid) { + auto L = lua::get_main_state(); + for (auto& pack : content_control->getAllContentPacks()) { + lua::emit_event( + L, + pack.id + ":.entityspawned", + [&](lua::State* L) { + lua::pushinteger(L, eid); + return 1; + } + ); + } +} + +void scripting::on_entity_despawned(entityid_t eid) { + auto L = lua::get_main_state(); + for (auto& pack : content_control->getAllContentPacks()) { + lua::emit_event( + L, + pack.id + ":.entitydespawned", + [&](lua::State* L) { + lua::pushinteger(L, eid); + return 1; + } + ); + } +} + bool scripting::on_item_use(Player* player, const ItemDef& item) { std::string name = item.name + ".use"; return lua::emit_event( @@ -781,6 +809,10 @@ void scripting::load_world_script( register_event(env, "on_inventory_open", prefix + ":.inventoryopen"); funcsset.oninventoryclosed = register_event(env, "on_inventory_closed", prefix + ":.inventoryclosed"); + funcsset.onentityspawned = + register_event(env, "on_entity_spawned", prefix + ":.entityspawned"); + funcsset.onentitydespawned = + register_event(env, "on_entity_despawned", prefix + ":.entitydespawned"); } void scripting::load_content_script( diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index 9d29528b8..3d9737c6a 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -112,6 +112,9 @@ namespace scripting { void on_player_tick(Player* player, int tps); + void on_entity_spawned(entityid_t eid); + void on_entity_despawned(entityid_t eid); + /// @brief Called on RMB click with the item selected /// @return true if prevents default action bool on_item_use(Player* player, const ItemDef& item); diff --git a/src/logic/scripting/scripting_entities.cpp b/src/logic/scripting/scripting_entities.cpp index c068d7007..0be289038 100644 --- a/src/logic/scripting/scripting_entities.cpp +++ b/src/logic/scripting/scripting_entities.cpp @@ -129,6 +129,8 @@ void scripting::on_entity_spawn( for (auto& component : components) { create_component(L, -1, *component, args, saved); } + + scripting::on_entity_spawned(eid); } static void process_entity_callback( @@ -171,9 +173,13 @@ void scripting::on_entity_despawn(const Entity& entity) { entity, "on_despawn", &EntityFuncsSet::on_despawn, nullptr ); auto L = lua::get_main_state(); + entityid_t uid = entity.getUID(); + lua::get_from(L, "stdcomp", "remove_Entity", true); - lua::pushinteger(L, entity.getUID()); + lua::pushinteger(L, uid); lua::call(L, 1, 0); + + scripting::on_entity_despawned(uid); } void scripting::on_entity_grounded(const Entity& entity, float force) { From acf8112644952559f8d21e9ba6902aa5a8423762 Mon Sep 17 00:00:00 2001 From: Xertis <118364459+Xertis@users.noreply.github.com> Date: Wed, 8 Apr 2026 21:44:17 +0300 Subject: [PATCH 2/3] rename events + fix --- doc/en/scripting/events.md | 6 ++-- doc/ru/scripting/events.md | 6 ++-- src/content/ContentPack.hpp | 4 +-- src/logic/scripting/scripting.cpp | 36 +++------------------- src/logic/scripting/scripting.hpp | 3 -- src/logic/scripting/scripting_entities.cpp | 26 ++++++++++++++-- 6 files changed, 35 insertions(+), 46 deletions(-) diff --git a/doc/en/scripting/events.md b/doc/en/scripting/events.md index f475dc1c1..75180edc3 100644 --- a/doc/en/scripting/events.md +++ b/doc/en/scripting/events.md @@ -153,15 +153,15 @@ function on_block_interact(blockid, x, y, z, playerid) -> bool Called on block RMB click interaction. Prevents block placing if **true** returned. ```lua -function on_entity_spawned(uid) +function on_entity_spawn(uid) ``` Called after an entity is spawned. ```lua -function on_entity_despawned(uid) +function on_entity_despawn(uid) ``` -Called after an entity is removed. At the time of the call, the entity with the given uid has already been deleted. +Called before an entity is despawned. ### Chunk Events (world.lua) diff --git a/doc/ru/scripting/events.md b/doc/ru/scripting/events.md index fa20e4030..ff4bfb456 100644 --- a/doc/ru/scripting/events.md +++ b/doc/ru/scripting/events.md @@ -157,16 +157,16 @@ function on_block_interact(blockid, x, y, z, playerid) -> bool Вызывается при нажатии на блок ПКМ. Предотвращает установку блоков, если возвращает `true` ```lua -function on_entity_spawned(uid) +function on_entity_spawn(uid) ``` Вызывается после спавна сущности ```lua -function on_entity_despawned(uid) +function on_entity_despawn(uid) ``` -Вызывается после удаления сущности, на момент вызова события сущность с переданным uid уже удалена +Вызывается перед удалением сущности ```lua function on_player_tick(playerid: int, tps: int) diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index bd0ccc79f..06a8cf4dc 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -127,8 +127,8 @@ struct WorldFuncsSet { bool onchunkremove; bool oninventoryopen; bool oninventoryclosed; - bool onentityspawned; - bool onentitydespawned; + bool onentityspawn; + bool onentitydespawn; }; class ContentPackRuntime { diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 232c72796..341470c17 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -555,34 +555,6 @@ void scripting::on_player_tick(Player* player, int tps) { } } -void scripting::on_entity_spawned(entityid_t eid) { - auto L = lua::get_main_state(); - for (auto& pack : content_control->getAllContentPacks()) { - lua::emit_event( - L, - pack.id + ":.entityspawned", - [&](lua::State* L) { - lua::pushinteger(L, eid); - return 1; - } - ); - } -} - -void scripting::on_entity_despawned(entityid_t eid) { - auto L = lua::get_main_state(); - for (auto& pack : content_control->getAllContentPacks()) { - lua::emit_event( - L, - pack.id + ":.entitydespawned", - [&](lua::State* L) { - lua::pushinteger(L, eid); - return 1; - } - ); - } -} - bool scripting::on_item_use(Player* player, const ItemDef& item) { std::string name = item.name + ".use"; return lua::emit_event( @@ -809,10 +781,10 @@ void scripting::load_world_script( register_event(env, "on_inventory_open", prefix + ":.inventoryopen"); funcsset.oninventoryclosed = register_event(env, "on_inventory_closed", prefix + ":.inventoryclosed"); - funcsset.onentityspawned = - register_event(env, "on_entity_spawned", prefix + ":.entityspawned"); - funcsset.onentitydespawned = - register_event(env, "on_entity_despawned", prefix + ":.entitydespawned"); + funcsset.onentityspawn = + register_event(env, "on_entity_spawn", prefix + ":.entityspawn"); + funcsset.onentitydespawn = + register_event(env, "on_entity_despawn", prefix + ":.entitydespawn"); } void scripting::load_content_script( diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index 3d9737c6a..9d29528b8 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -112,9 +112,6 @@ namespace scripting { void on_player_tick(Player* player, int tps); - void on_entity_spawned(entityid_t eid); - void on_entity_despawned(entityid_t eid); - /// @brief Called on RMB click with the item selected /// @return true if prevents default action bool on_item_use(Player* player, const ItemDef& item); diff --git a/src/logic/scripting/scripting_entities.cpp b/src/logic/scripting/scripting_entities.cpp index 0be289038..afed77e85 100644 --- a/src/logic/scripting/scripting_entities.cpp +++ b/src/logic/scripting/scripting_entities.cpp @@ -6,6 +6,7 @@ #include "objects/Entity.hpp" #include "objects/Player.hpp" #include "util/stringutil.hpp" +#include "content/ContentControl.hpp" using namespace scripting; @@ -130,7 +131,16 @@ void scripting::on_entity_spawn( create_component(L, -1, *component, args, saved); } - scripting::on_entity_spawned(eid); + for (auto& pack : scripting::content_control->getAllContentPacks()) { + lua::emit_event( + L, + pack.id + ":.entityspawn", + [&](lua::State* L) { + lua::pushinteger(L, eid); + return 1; + } + ); + } } static void process_entity_callback( @@ -172,14 +182,24 @@ void scripting::on_entity_despawn(const Entity& entity) { process_entity_callback( entity, "on_despawn", &EntityFuncsSet::on_despawn, nullptr ); + auto L = lua::get_main_state(); entityid_t uid = entity.getUID(); + for (auto& pack : content_control->getAllContentPacks()) { + lua::emit_event( + L, + pack.id + ":.entitydespawn", + [&](lua::State* L) { + lua::pushinteger(L, uid); + return 1; + } + ); + } + lua::get_from(L, "stdcomp", "remove_Entity", true); lua::pushinteger(L, uid); lua::call(L, 1, 0); - - scripting::on_entity_despawned(uid); } void scripting::on_entity_grounded(const Entity& entity, float force) { From 2dd8024b7e0f82bfc79cd6e5e389c3dd6a5a700c Mon Sep 17 00:00:00 2001 From: Xertis <118364459+Xertis@users.noreply.github.com> Date: Wed, 8 Apr 2026 22:59:47 +0300 Subject: [PATCH 3/3] fix entity spawn/despawn --- src/logic/scripting/scripting_entities.cpp | 27 ++++++++++------------ 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/logic/scripting/scripting_entities.cpp b/src/logic/scripting/scripting_entities.cpp index afed77e85..32a396b25 100644 --- a/src/logic/scripting/scripting_entities.cpp +++ b/src/logic/scripting/scripting_entities.cpp @@ -6,7 +6,8 @@ #include "objects/Entity.hpp" #include "objects/Player.hpp" #include "util/stringutil.hpp" -#include "content/ContentControl.hpp" +#include "content/Content.hpp" +#include "content/ContentPack.hpp" using namespace scripting; @@ -131,15 +132,13 @@ void scripting::on_entity_spawn( create_component(L, -1, *component, args, saved); } - for (auto& pack : scripting::content_control->getAllContentPacks()) { - lua::emit_event( - L, - pack.id + ":.entityspawn", - [&](lua::State* L) { + for (auto& [packid, pack] : content->getPacks()) { + if (pack->worldfuncsset.onentityspawn) { + lua::emit_event(L, packid + ":.entityspawn", [&](lua::State* L) { lua::pushinteger(L, eid); return 1; - } - ); + }); + } } } @@ -186,15 +185,13 @@ void scripting::on_entity_despawn(const Entity& entity) { auto L = lua::get_main_state(); entityid_t uid = entity.getUID(); - for (auto& pack : content_control->getAllContentPacks()) { - lua::emit_event( - L, - pack.id + ":.entitydespawn", - [&](lua::State* L) { + for (auto& [packid, pack] : content->getPacks()) { + if (pack->worldfuncsset.onentitydespawn) { + lua::emit_event(L, packid + ":.entitydespawn", [&](lua::State* L) { lua::pushinteger(L, uid); return 1; - } - ); + }); + } } lua::get_from(L, "stdcomp", "remove_Entity", true);