From 42f9f2e8f174de9a57e1872903ac25370ece53a8 Mon Sep 17 00:00:00 2001 From: Yangyangzhong82 Date: Sun, 15 Feb 2026 03:46:34 +0800 Subject: [PATCH 1/3] feat(events): add onPortalTrySpawnPigZombie event --- docs/apis/EventAPI/EntityEvents.md | 14 +++++++++++++- docs/apis/EventAPI/EntityEvents.zh.md | 14 +++++++++++++- src/legacy/api/EventAPI.cpp | 4 ++++ src/legacy/api/EventAPI.h | 1 + src/lse/events/EntityEvents.cpp | 27 +++++++++++++++++++++++++++ src/lse/events/EntityEvents.h | 3 ++- src/tests/LSETests/EventTests.js | 3 ++- 7 files changed, 62 insertions(+), 4 deletions(-) diff --git a/docs/apis/EventAPI/EntityEvents.md b/docs/apis/EventAPI/EntityEvents.md index cd34749b..7820276e 100644 --- a/docs/apis/EventAPI/EntityEvents.md +++ b/docs/apis/EventAPI/EntityEvents.md @@ -119,6 +119,18 @@ Here are values of `ActorDamageCause`: You can use entity.despawn() or entity.remove() to intercept this event. +#### `"onPortalTrySpawnPigZombie"` - Nether Portal Try Spawn Zombified Piglin Event + +- Listener function prototype + `function(pos,axis)` +- Parameters: + - pos : `IntPos` + The portal block coordinates where the spawn attempt happened. + - axis : `Integer` + Portal axis enum value (`0` = Unknown, `1` = X, `2` = Z). + +- Intercept events: function returns `false` + #### `"onProjectileHitEntity"` - Entity Hit by Projectile Event - Listener function prototype @@ -261,4 +273,4 @@ the transition is destroyed quickly. Pick up the Block - pos : `BlockPos` The coordinates of the block. -- Intercept events: function returns `false` \ No newline at end of file +- Intercept events: function returns `false` diff --git a/docs/apis/EventAPI/EntityEvents.zh.md b/docs/apis/EventAPI/EntityEvents.zh.md index f5492c0e..0a35ea9c 100644 --- a/docs/apis/EventAPI/EntityEvents.zh.md +++ b/docs/apis/EventAPI/EntityEvents.zh.md @@ -119,6 +119,18 @@ ActorDamageCause 为伤害原因枚举,枚举值如下,有问号的待验证 此事件为实体成功生成后触发,不可直接拦截,如需拦截请使用entity.despawn()或entity.remove() +#### `"onPortalTrySpawnPigZombie"` - 下界传送门尝试生成僵尸猪灵 + +- 监听函数原型 + `function(pos,axis)` +- 参数: + - pos : `IntPos` + 发生尝试生成的位置(传送门方块坐标) + - axis : `Integer` + 传送门朝向枚举值(`0`=Unknown,`1`=X,`2`=Z) + +- 拦截事件:函数返回`false` + #### `"onProjectileHitEntity"` - 实体被弹射物击中 - 监听函数原型 @@ -271,4 +283,4 @@ ActorDamageCause 为伤害原因枚举,枚举值如下,有问号的待验证 被搬运的方块 - pos : `BlockPos` 被搬运的方块坐标 -- 拦截事件:函数返回`false` \ No newline at end of file +- 拦截事件:函数返回`false` diff --git a/src/legacy/api/EventAPI.cpp b/src/legacy/api/EventAPI.cpp index b4bdddf4..a68fd284 100644 --- a/src/legacy/api/EventAPI.cpp +++ b/src/legacy/api/EventAPI.cpp @@ -772,6 +772,10 @@ void EnableEventListener(int eventId) { }); break; + case EVENT_TYPES::onPortalTrySpawnPigZombie: + lse::events::entity::PortalTrySpawnPigZombieEvent(); + break; + case EVENT_TYPES::onExperienceAdd: bus.emplaceListener([](PlayerAddExperienceEvent& ev) { IF_LISTENED(EVENT_TYPES::onExperienceAdd) { diff --git a/src/legacy/api/EventAPI.h b/src/legacy/api/EventAPI.h index 6fd65f96..3beebdf8 100644 --- a/src/legacy/api/EventAPI.h +++ b/src/legacy/api/EventAPI.h @@ -74,6 +74,7 @@ enum class EVENT_TYPES : int { onEntityTransformation, onMobTrySpawn, onMobSpawned, + onPortalTrySpawnPigZombie, onNpcCmd, onEndermanTakeBlock, /* Block Events */ diff --git a/src/lse/events/EntityEvents.cpp b/src/lse/events/EntityEvents.cpp index 6e919cfe..2c0e2b09 100644 --- a/src/lse/events/EntityEvents.cpp +++ b/src/lse/events/EntityEvents.cpp @@ -33,6 +33,7 @@ #include "mc/world/level/BedrockSpawner.h" #include "mc/world/level/BlockSource.h" #include "mc/world/level/Level.h" +#include "mc/world/level/block/PortalBlock.h" #include "mc/world/phys/AABB.h" #include "mc/world/phys/HitResult.h" @@ -129,6 +130,31 @@ LL_TYPE_INSTANCE_HOOK( origin(item, player, durationLeft); } +LL_TYPE_STATIC_HOOK( + PortalTrySpawnPigZombieHook, + HookPriority::Normal, + PortalBlock, + &PortalBlock::trySpawnPigZombie, + void, + BlockSource& region, + BlockPos const& pos, + PortalAxis axis +) { + IF_LISTENED(EVENT_TYPES::onPortalTrySpawnPigZombie) { + if (checkClientIsServerThread()) { + if (!CallEvent( + EVENT_TYPES::onPortalTrySpawnPigZombie, + IntPos::newPos(pos, region.getDimensionId()), + Number::newNumber(static_cast(axis)) + )) { + return; + } + } + } + IF_LISTENED_END(EVENT_TYPES::onPortalTrySpawnPigZombie); + origin(region, pos, axis); +} + LL_TYPE_INSTANCE_HOOK(ActorRideHook, HookPriority::Normal, Actor, &Actor::$canAddPassenger, bool, Actor& passenger) { IF_LISTENED(EVENT_TYPES::onRide) { if (checkClientIsServerThread()) { @@ -436,6 +462,7 @@ void ProjectileSpawnEvent() { ProjectileSpawnHook2::hook(); ProjectileSpawnHook3::hook(); }; +void PortalTrySpawnPigZombieEvent() { PortalTrySpawnPigZombieHook::hook(); } void ProjectileCreatedEvent() { ProjectileSpawnHook1::hook(); }; void ActorRideEvent() { ActorRideHook::hook(); } void WitherDestroyEvent() { WitherDestroyHook::hook(); } diff --git a/src/lse/events/EntityEvents.h b/src/lse/events/EntityEvents.h index cabec7a0..61a65913 100644 --- a/src/lse/events/EntityEvents.h +++ b/src/lse/events/EntityEvents.h @@ -12,4 +12,5 @@ void NpcCommandEvent(); void EndermanTakeBlockEvent(); void EffectUpdateEvent(); void TransformationEvent(); -} // namespace lse::events::entity \ No newline at end of file +void PortalTrySpawnPigZombieEvent(); +} // namespace lse::events::entity diff --git a/src/tests/LSETests/EventTests.js b/src/tests/LSETests/EventTests.js index 8023c54c..21de3d0b 100644 --- a/src/tests/LSETests/EventTests.js +++ b/src/tests/LSETests/EventTests.js @@ -50,6 +50,7 @@ export const events = [ "onBlockChanged", "onBlockExplode", "onRespawnAnchorExplode", + "onPortalTrySpawnPigZombie", "onBlockExploded", "onFireSpread", "onCmdBlockExecute", @@ -95,4 +96,4 @@ export function RegisterEvents() { logger.info(`${triggeredEvents.size}/${events.length} events called`); }); }); -} \ No newline at end of file +} From c6dc2f486c56d2661459245e58c93969cc8acd97 Mon Sep 17 00:00:00 2001 From: Yangyangzhong82 Date: Sun, 15 Feb 2026 14:27:17 +0800 Subject: [PATCH 2/3] feat(events): add onPortalTrySpawn event #318 #306 --- CHANGELOG.md | 5 +++++ docs/apis/EventAPI/BlockEvents.md | 10 ++++++++++ docs/apis/EventAPI/BlockEvents.zh.md | 10 ++++++++++ src/legacy/api/EventAPI.cpp | 4 ++++ src/legacy/api/EventAPI.h | 1 + src/lse/events/BlockEvents.cpp | 22 ++++++++++++++++++++++ src/lse/events/BlockEvents.h | 3 ++- src/tests/LSETests/EventTests.js | 1 + 8 files changed, 55 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfb4240e..7c389abe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Added `onPortalTrySpawn` event [#318] [#306] @yangyangzhong82 +- Added `onPortalTrySpawnPigZombie` event @yangyangzhong82 + ## [0.17.2] - 2026-02-04 ### Fixed diff --git a/docs/apis/EventAPI/BlockEvents.md b/docs/apis/EventAPI/BlockEvents.md index 300a025f..d2017246 100644 --- a/docs/apis/EventAPI/BlockEvents.md +++ b/docs/apis/EventAPI/BlockEvents.md @@ -75,6 +75,16 @@ Intercept events have no effect on chests, shulker boxes, and workbenches. +#### `"onPortalTrySpawn"` - Nether Portal Try Spawn Event + +- Listener function prototype + `function(pos)` +- Parameters: + - pos : `IntPos` + The coordinates where portal generation is attempted. +- Intercept events: function returns `false` + + #### `"onBlockExploded"` - Block Destroyed by Explosion Event - Listener function prototype diff --git a/docs/apis/EventAPI/BlockEvents.zh.md b/docs/apis/EventAPI/BlockEvents.zh.md index 117abc9b..0b6a560e 100644 --- a/docs/apis/EventAPI/BlockEvents.zh.md +++ b/docs/apis/EventAPI/BlockEvents.zh.md @@ -77,6 +77,16 @@ +#### `"onPortalTrySpawn"` - 下界传送门尝试生成 + +- 监听函数原型 + `function(pos)` +- 参数: + - pos : `IntPos` + 尝试生成传送门的位置坐标 +- 拦截事件:函数返回`false` + + #### `"onBlockExploded"` - 方块被爆炸破坏 - 监听函数原型 diff --git a/src/legacy/api/EventAPI.cpp b/src/legacy/api/EventAPI.cpp index a68fd284..cda29b30 100644 --- a/src/legacy/api/EventAPI.cpp +++ b/src/legacy/api/EventAPI.cpp @@ -576,6 +576,10 @@ void EnableEventListener(int eventId) { lse::events::block::RespawnAnchorExplodeEvent(); break; + case EVENT_TYPES::onPortalTrySpawn: + lse::events::block::PortalSpawnEvent(); + break; + case EVENT_TYPES::onBlockExploded: lse::events::block::BlockExplodedEvent(); break; diff --git a/src/legacy/api/EventAPI.h b/src/legacy/api/EventAPI.h index 3beebdf8..06053c63 100644 --- a/src/legacy/api/EventAPI.h +++ b/src/legacy/api/EventAPI.h @@ -82,6 +82,7 @@ enum class EVENT_TYPES : int { onBlockChanged, onBlockExplode, onRespawnAnchorExplode, + onPortalTrySpawn, onBlockExploded, onFireSpread, onCmdBlockExecute, diff --git a/src/lse/events/BlockEvents.cpp b/src/lse/events/BlockEvents.cpp index 86a22c3e..99a694c0 100644 --- a/src/lse/events/BlockEvents.cpp +++ b/src/lse/events/BlockEvents.cpp @@ -37,6 +37,7 @@ #include "mc/world/level/block/LiquidBlock.h" #include "mc/world/level/block/NoteBlock.h" #include "mc/world/level/block/PoweredRailBlock.h" +#include "mc/world/level/block/PortalBlock.h" #include "mc/world/level/block/RedStoneWireBlock.h" #include "mc/world/level/block/RedstoneLampBlock.h" #include "mc/world/level/block/RedstoneTorchBlock.h" @@ -268,6 +269,26 @@ LL_TYPE_STATIC_HOOK( origin(player, pos, region, level); } +LL_TYPE_STATIC_HOOK( + PortalSpawnHook, + HookPriority::Normal, + PortalBlock, + &PortalBlock::trySpawnPortal, + bool, + BlockSource& region, + BlockPos const& pos +) { + IF_LISTENED(EVENT_TYPES::onPortalTrySpawn) { + if (checkClientIsServerThread()) { + if (!CallEvent(EVENT_TYPES::onPortalTrySpawn, IntPos::newPos(pos, region.getDimensionId()))) { + return false; + } + } + } + IF_LISTENED_END(EVENT_TYPES::onPortalTrySpawn); + return origin(region, pos); +} + LL_TYPE_INSTANCE_HOOK( BlockExplodedHook, HookPriority::Normal, @@ -587,6 +608,7 @@ void FarmDecayEvent() { FarmDecayHook::hook(); } void PistonPushEvent() { PistonPushHook::hook(); } void ExplodeEvent() { ExplodeHook::hook(); } void RespawnAnchorExplodeEvent() { RespawnAnchorExplodeHook::hook(); } +void PortalSpawnEvent() { PortalSpawnHook::hook(); } void BlockExplodedEvent() { BlockExplodedHook ::hook(); } void RedstoneUpdateEvent() { redstone::RedstoneTorchBlockHook::hook(); diff --git a/src/lse/events/BlockEvents.h b/src/lse/events/BlockEvents.h index 15000aa9..760dc6c8 100644 --- a/src/lse/events/BlockEvents.h +++ b/src/lse/events/BlockEvents.h @@ -8,9 +8,10 @@ void FarmDecayEvent(); void PistonPushEvent(); void ExplodeEvent(); void RespawnAnchorExplodeEvent(); +void PortalSpawnEvent(); void BlockExplodedEvent(); void RedstoneUpdateEvent(); void LiquidFlowEvent(); void CommandBlockExecuteEvent(); void HopperEvent(bool pullIn); -} \ No newline at end of file +} diff --git a/src/tests/LSETests/EventTests.js b/src/tests/LSETests/EventTests.js index 21de3d0b..72731d7a 100644 --- a/src/tests/LSETests/EventTests.js +++ b/src/tests/LSETests/EventTests.js @@ -50,6 +50,7 @@ export const events = [ "onBlockChanged", "onBlockExplode", "onRespawnAnchorExplode", + "onPortalTrySpawn", "onPortalTrySpawnPigZombie", "onBlockExploded", "onFireSpread", From 92c4ded7798d3a562ec560f33647ea22289cde38 Mon Sep 17 00:00:00 2001 From: Yangyangzhong82 Date: Sun, 15 Feb 2026 17:41:51 +0800 Subject: [PATCH 3/3] feat(events): add onDispenseItem event #318 --- CHANGELOG.md | 1 + docs/apis/EventAPI/BlockEvents.md | 20 ++++++++++++++++ docs/apis/EventAPI/BlockEvents.zh.md | 20 ++++++++++++++++ src/legacy/api/EventAPI.cpp | 4 ++++ src/legacy/api/EventAPI.h | 1 + src/lse/events/BlockEvents.cpp | 36 ++++++++++++++++++++++++++++ src/lse/events/BlockEvents.h | 1 + src/tests/LSETests/EventTests.js | 1 + xmake.lua | 4 ++-- 9 files changed, 86 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c389abe..be660670 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `onPortalTrySpawn` event [#318] [#306] @yangyangzhong82 - Added `onPortalTrySpawnPigZombie` event @yangyangzhong82 +- Added `onDispenseItem` event [#318] @yangyangzhong82 ## [0.17.2] - 2026-02-04 diff --git a/docs/apis/EventAPI/BlockEvents.md b/docs/apis/EventAPI/BlockEvents.md index d2017246..206ca0eb 100644 --- a/docs/apis/EventAPI/BlockEvents.md +++ b/docs/apis/EventAPI/BlockEvents.md @@ -154,6 +154,26 @@ There are many different combinations of old item objects and new item objects, - Replacement Item: Old Item Object's typenot equal to the new item object `type`, and neither `item` object is empty. +#### `"onDispenseItem"` - Dispenser / Dropper Dispense Item Event + +- Listener function prototype + `function(pos,item,slot,face,container)` +- Parameters: + - pos : `FloatPos` + Spawn position of the dispensed item. + - item : `Item` + The dispensed item object. + - slot : `Integer` + Source slot index in the container. + - face : `Integer` + Facing direction value of the dispenser/dropper at dispense time. + - container : `Container` + The dispenser/dropper container object. +- Intercept events: function returns `false` + +Only triggers when dispenser/dropper ejects normal items (non-projectiles). + + #### `"onProjectileHitBlock"` - Block Hit by Projectile Event diff --git a/docs/apis/EventAPI/BlockEvents.zh.md b/docs/apis/EventAPI/BlockEvents.zh.md index 0b6a560e..e97d87c4 100644 --- a/docs/apis/EventAPI/BlockEvents.zh.md +++ b/docs/apis/EventAPI/BlockEvents.zh.md @@ -156,6 +156,26 @@ - 替换物品:旧物品对象的`type` 不等于 新物品对象的`type`,且两物品对象均不为空 +#### `"onDispenseItem"` - 发射器/投掷器发射普通物品 + +- 监听函数原型 + `function(pos,item,slot,face,container)` +- 参数: + - pos : `FloatPos` + 发射物品出现的位置 + - item : `Item` + 被发射的物品对象 + - slot : `Integer` + 容器中被取出物品的槽位索引 + - face : `Integer` + 发射时方块朝向数值 + - container : `Container` + 发射器/投掷器的容器对象 +- 拦截事件:函数返回`false` + +仅在发射器/投掷器发射普通物品(非弹射物)时触发。 + + #### `"onProjectileHitBlock"` - 方块被弹射物击中 diff --git a/src/legacy/api/EventAPI.cpp b/src/legacy/api/EventAPI.cpp index cda29b30..ad622a8a 100644 --- a/src/legacy/api/EventAPI.cpp +++ b/src/legacy/api/EventAPI.cpp @@ -446,6 +446,10 @@ void EnableEventListener(int eventId) { lse::events::block::ContainerChangeEvent(); break; + case EVENT_TYPES::onDispenseItem: + lse::events::block::DispenseItemEvent(); + break; + case EVENT_TYPES::onChangeArmorStand: lse::events::block::ArmorStandSwapItemEvent(); break; diff --git a/src/legacy/api/EventAPI.h b/src/legacy/api/EventAPI.h index 06053c63..a0d9fa76 100644 --- a/src/legacy/api/EventAPI.h +++ b/src/legacy/api/EventAPI.h @@ -87,6 +87,7 @@ enum class EVENT_TYPES : int { onFireSpread, onCmdBlockExecute, onContainerChange, + onDispenseItem, onProjectileHitBlock, onRedStoneUpdate, onHopperSearchItem, diff --git a/src/lse/events/BlockEvents.cpp b/src/lse/events/BlockEvents.cpp index 99a694c0..c22b19e1 100644 --- a/src/lse/events/BlockEvents.cpp +++ b/src/lse/events/BlockEvents.cpp @@ -1,5 +1,6 @@ #include "legacy/api/BaseAPI.h" #include "legacy/api/BlockAPI.h" +#include "legacy/api/ContainerAPI.h" #include "legacy/api/EntityAPI.h" #include "legacy/api/EventAPI.h" #include "legacy/api/ItemAPI.h" @@ -518,6 +519,40 @@ LL_TYPE_INSTANCE_HOOK( return origin(region, commandOrigin, markForSaving); } +namespace dispenser { +LL_TYPE_INSTANCE_HOOK( + DispenserEjectItemHook, + HookPriority::Normal, + DispenserBlock, + &DispenserBlock::ejectItem, + void, + BlockSource& region, + Vec3 const& pos, + uchar face, + ItemStack const& item, + Container& container, + int slot, + int countLimit +) { + IF_LISTENED(EVENT_TYPES::onDispenseItem) { + if (checkClientIsServerThread()) { + if (!CallEvent( + EVENT_TYPES::onDispenseItem, + FloatPos::newPos(pos, region.getDimensionId()), + ItemClass::newItem(&const_cast(item)), + Number::newNumber(slot), + Number::newNumber((int)face), + ContainerClass::newContainer(&container) + )) { + return; + } + } + } + IF_LISTENED_END(EVENT_TYPES::onDispenseItem); + origin(region, pos, face, item, container, slot, countLimit); +} +} // namespace dispenser + namespace hopper { enum class HopperStatus { None, PullIn, PullOut } hopperStatus = HopperStatus::None; Vec3 hopperPos; @@ -633,6 +668,7 @@ void RedstoneUpdateEvent() { } void LiquidFlowEvent() { LiquidFlowHook::hook(); } void CommandBlockExecuteEvent() { CommandBlockExecuteHook::hook(); } +void DispenseItemEvent() { dispenser::DispenserEjectItemHook::hook(); } void HopperEvent(bool pullIn) { hopper::HopperAddItemHook::hook(); if (pullIn) { diff --git a/src/lse/events/BlockEvents.h b/src/lse/events/BlockEvents.h index 760dc6c8..aa5ed3ac 100644 --- a/src/lse/events/BlockEvents.h +++ b/src/lse/events/BlockEvents.h @@ -11,6 +11,7 @@ void RespawnAnchorExplodeEvent(); void PortalSpawnEvent(); void BlockExplodedEvent(); void RedstoneUpdateEvent(); +void DispenseItemEvent(); void LiquidFlowEvent(); void CommandBlockExecuteEvent(); void HopperEvent(bool pullIn); diff --git a/src/tests/LSETests/EventTests.js b/src/tests/LSETests/EventTests.js index 72731d7a..61027873 100644 --- a/src/tests/LSETests/EventTests.js +++ b/src/tests/LSETests/EventTests.js @@ -56,6 +56,7 @@ export const events = [ "onFireSpread", "onCmdBlockExecute", "onContainerChange", + "onDispenseItem", "onProjectileHitBlock", "onRedStoneUpdate", "onHopperSearchItem", diff --git a/xmake.lua b/xmake.lua index c3a58b62..b5dc9596 100644 --- a/xmake.lua +++ b/xmake.lua @@ -3,9 +3,9 @@ add_rules("mode.debug", "mode.release") add_repositories("levimc-repo " .. (get_config("levimc_repo") or "https://github.com/LiteLDev/xmake-repo.git")) if is_config("target_type", "server") then - add_requires("levilamina 1.9.4", {configs = {target_type = "server"}}) + add_requires("levilamina 1.9.5", {configs = {target_type = "server"}}) else - add_requires("levilamina 1.9.4", {configs = {target_type = "client"}}) + add_requires("levilamina 1.9.5", {configs = {target_type = "client"}}) end add_requires("levibuildscript")