Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ 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
- Added `onDispenseItem` event [#318] @yangyangzhong82

## [0.17.2] - 2026-02-04

### Fixed
Expand Down
30 changes: 30 additions & 0 deletions docs/apis/EventAPI/BlockEvents.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -144,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

Expand Down
30 changes: 30 additions & 0 deletions docs/apis/EventAPI/BlockEvents.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@



#### `"onPortalTrySpawn"` - 下界传送门尝试生成

- 监听函数原型
`function(pos)`
- 参数:
- pos : `IntPos`
尝试生成传送门的位置坐标
- 拦截事件:函数返回`false`


#### `"onBlockExploded"` - 方块被爆炸破坏

- 监听函数原型
Expand Down Expand Up @@ -146,6 +156,26 @@
- 替换物品:旧物品对象的`type` 不等于 新物品对象的`type`,且两物品对象均不为空


#### `"onDispenseItem"` - 发射器/投掷器发射普通物品

- 监听函数原型
`function(pos,item,slot,face,container)`
- 参数:
- pos : `FloatPos`
发射物品出现的位置
- item : `Item`
被发射的物品对象
- slot : `Integer`
容器中被取出物品的槽位索引
- face : `Integer`
发射时方块朝向数值
- container : `Container`
发射器/投掷器的容器对象
- 拦截事件:函数返回`false`

仅在发射器/投掷器发射普通物品(非弹射物)时触发。



#### `"onProjectileHitBlock"` - 方块被弹射物击中

Expand Down
14 changes: 13 additions & 1 deletion docs/apis/EventAPI/EntityEvents.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`
- Intercept events: function returns `false`
14 changes: 13 additions & 1 deletion docs/apis/EventAPI/EntityEvents.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"` - 实体被弹射物击中

- 监听函数原型
Expand Down Expand Up @@ -271,4 +283,4 @@ ActorDamageCause 为伤害原因枚举,枚举值如下,有问号的待验证
被搬运的方块
- pos : `BlockPos`
被搬运的方块坐标
- 拦截事件:函数返回`false`
- 拦截事件:函数返回`false`
12 changes: 12 additions & 0 deletions src/legacy/api/EventAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -576,6 +580,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;
Expand Down Expand Up @@ -772,6 +780,10 @@ void EnableEventListener(int eventId) {
});
break;

case EVENT_TYPES::onPortalTrySpawnPigZombie:
lse::events::entity::PortalTrySpawnPigZombieEvent();
break;

case EVENT_TYPES::onExperienceAdd:
bus.emplaceListener<PlayerAddExperienceEvent>([](PlayerAddExperienceEvent& ev) {
IF_LISTENED(EVENT_TYPES::onExperienceAdd) {
Expand Down
3 changes: 3 additions & 0 deletions src/legacy/api/EventAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,20 @@ enum class EVENT_TYPES : int {
onEntityTransformation,
onMobTrySpawn,
onMobSpawned,
onPortalTrySpawnPigZombie,
onNpcCmd,
onEndermanTakeBlock,
/* Block Events */
onBlockInteracted,
onBlockChanged,
onBlockExplode,
onRespawnAnchorExplode,
onPortalTrySpawn,
onBlockExploded,
onFireSpread,
onCmdBlockExecute,
onContainerChange,
onDispenseItem,
onProjectileHitBlock,
onRedStoneUpdate,
onHopperSearchItem,
Expand Down
58 changes: 58 additions & 0 deletions src/lse/events/BlockEvents.cpp
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -37,6 +38,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"
Expand Down Expand Up @@ -268,6 +270,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,
Expand Down Expand Up @@ -497,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<ItemStack&>(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;
Expand Down Expand Up @@ -587,6 +643,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();
Expand All @@ -611,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) {
Expand Down
4 changes: 3 additions & 1 deletion src/lse/events/BlockEvents.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ void FarmDecayEvent();
void PistonPushEvent();
void ExplodeEvent();
void RespawnAnchorExplodeEvent();
void PortalSpawnEvent();
void BlockExplodedEvent();
void RedstoneUpdateEvent();
void DispenseItemEvent();
void LiquidFlowEvent();
void CommandBlockExecuteEvent();
void HopperEvent(bool pullIn);
}
}
27 changes: 27 additions & 0 deletions src/lse/events/EntityEvents.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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<int>(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()) {
Expand Down Expand Up @@ -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(); }
Expand Down
3 changes: 2 additions & 1 deletion src/lse/events/EntityEvents.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ void NpcCommandEvent();
void EndermanTakeBlockEvent();
void EffectUpdateEvent();
void TransformationEvent();
} // namespace lse::events::entity
void PortalTrySpawnPigZombieEvent();
} // namespace lse::events::entity
5 changes: 4 additions & 1 deletion src/tests/LSETests/EventTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,13 @@ export const events = [
"onBlockChanged",
"onBlockExplode",
"onRespawnAnchorExplode",
"onPortalTrySpawn",
"onPortalTrySpawnPigZombie",
"onBlockExploded",
"onFireSpread",
"onCmdBlockExecute",
"onContainerChange",
"onDispenseItem",
"onProjectileHitBlock",
"onRedStoneUpdate",
"onHopperSearchItem",
Expand Down Expand Up @@ -95,4 +98,4 @@ export function RegisterEvents() {
logger.info(`${triggeredEvents.size}/${events.length} events called`);
});
});
}
}
Loading