diff --git a/docs/scarpet/api/Events.md b/docs/scarpet/api/Events.md index 67fb1586..3a3f491e 100644 --- a/docs/scarpet/api/Events.md +++ b/docs/scarpet/api/Events.md @@ -22,6 +22,14 @@ partially handle the event before it happens and handle the rest after. While in confusion (like handling the respawn event still referring to player's original position and dimension), but gives much more control over these events. +Some events also provide the ability to cancel minecraft's processing of the event by returning `'cancel'` from the event handler. +This only works for particular events that are triggered before they take an effect in the game. +However, cancelling the event will also stop events from subsequent apps from triggering. +The order of events being executed can be changed by specifying an `'event_priority'` in the app config, +with the highest value being executed first. +Note that cancelling some events might introduce a desynchronization to the client from the server, +creating ghost items or blocks. This can be solved by updating the inventory or block to the client, by using `inventory_set` or `set`. + Programmers can also define their own events and signal other events, including built-in events, and across all loaded apps. ## App scopes and event distribution @@ -142,6 +150,8 @@ Triggers with a right click action. Event is triggered right after a server rece game manages to do anything about it. Event triggers when player starts eating food, or starts drawing a bow. Use `player_finishes_using_item`, or `player_releases_item` to capture the end of these events. +This event can be cancelled by returning `'cancel'`, which prevents the item from being used. + Event is not triggered when a player places a block, for that use `player_right_clicks_block` or `player_places_block` event. @@ -156,21 +166,34 @@ Player using of an item is done. This is controlled server side and is responsib eating. The event is triggered after confirming that the action is valid, and sending the feedback back to the client, but before triggering it and its effects in game. +This event can be cancelled by returning `'cancel'`, which prevents the player from finishing using the item. + ### `__on_player_clicks_block(player, block, face)` Representing left-click attack on a block, usually signifying start of breaking of a block. Triggers right after the server -receives a client packet, before anything happens on the server side. +receives a client packet, before anything happens on the server side. + +This event can be cancelled by returning `'cancel'`, which stops the player from breaking a block. ### `__on_player_breaks_block(player, block)` Called when player breaks a block, right before any changes to the world are done, but the decision is made to remove the block. +This event can be cancelled by returning `'cancel'`, which prevents the block from being placed. + ### `__on_player_right_clicks_block(player, item_tuple, hand, block, face, hitvec)` Called when player right clicks on a block with anything, or interacts with a block. This event is triggered right before other interaction events, like `'player_interacts_with_block'` or `'player_places_block'`. + +This event can be cancelled by returning `'cancel'`, which prevents the player interaction. ### `__on_player_interacts_with_block(player, hand, block, face, hitvec)` Called when player successfully interacted with a block, which resulted in activation of said block, right after this happened. + +### `__on_player_placing_block(player, item_tuple, hand, block)` +Triggered when player places a block, before block is placed in the world. + +This event can be cancelled by returning `'cancel'`, which prevents the block from being placed. ### `__on_player_places_block(player, item_tuple, hand, block)` Triggered when player places a block, after block is placed in the world, but before scoreboard is triggered or player inventory @@ -181,6 +204,8 @@ Triggered when player right clicks (interacts) with an entity, even if the entit the item they are holding. The event is invoked after receiving a packet from the client, before anything happens server side with that interaction. +This event can be cancelled by returning `'cancel'`, which prevents the player interacting with the entity. + ### `__on_player_trades(player, entity, buy_left, buy_right, sell)` Triggered when player trades with a merchant. The event is invoked after the server allow the trade, but before the inventory changes and merchant updates its trade-uses counter. @@ -195,6 +220,8 @@ on the player. Triggered when a player clicks a recipe in the crafting window from the crafting book, after server received a client request, but before any items are moved from its inventory to the crafting menu. +This event can be cancelled by returning `'cancel'`, which prevents the recipe from being moved into the crafting grid. + ### `__on_player_switches_slot(player, from, to)` Triggered when a player changes their selected hotbar slot. Applied right after the server receives the message to switch the slot. @@ -202,6 +229,8 @@ the slot. ### `__on_player_swaps_hands(player)` Triggered when a player sends a command to swap their offhand item. Executed before the effect is applied on the server. +This event can be cancelled by returning `'cancel'`, which prevents the hands from being swapped. + ### `__on_player_swings_hand(player, hand)` Triggered when a player starts swinging their hand. The event typically triggers after a corresponding event that caused it (`player_uses_item`, `player_breaks_block`, etc.), but it triggers also after some failed events, like attacking the air. When @@ -210,15 +239,21 @@ swinging continues as an effect of an action, no new swinging events will be iss ### `__on_player_attacks_entity(player, entity)` Triggered when a player attacks entity, right before it happens server side. +This event can be cancelled by returning `'cancel'`, which prevents the player from attacking the entity. + ### `__on_player_takes_damage(player, amount, source, source_entity)` Triggered when a player is taking damage. Event is executed right after potential absorbtion was applied and before the actual damage is applied to the player. +This event can be cancelled by returning `'cancel'`, which prevents the player from taking damage. + ### `__on_player_deals_damage(player, amount, entity)` Triggered when a player deals damage to another entity. Its applied in the same moment as `player_takes_damage` if both sides of the event are players, and similar for all other entities, just their absorbtion is taken twice, just noone ever notices that ¯\_(ツ)_/¯ +This event can be cancelled by returning `'cancel'`, which prevents the damage from being dealt. + ### `__on_player_dies(player)` Triggered when a player dies. Player is already dead, so don't revive them then. Event applied before broadcasting messages about players death and applying external effects (like mob anger etc). @@ -267,6 +302,8 @@ Four events triggered when player controls for sneaking and sprinting toggle. Triggered when the game receives the request from a player to drop one item or full stack from its inventory. Event happens before anything is changed server side. +These events can be cancelled by returning `'cancel'`, which prevents the player dropping the items. + ### `__on_player_picks_up_item(player, item)` Triggered AFTER a player successfully ingested an item in its inventory. Item represents the total stack of items ingested by the player. The exact position of these items is unknown as technically these @@ -281,6 +318,8 @@ Triggered when a player sends a disconnect package or is forcefully disconnected ### `__on_player_message(player, message)` Triggered when a player sends a chat message or runs a command. +This event can be cancelled by returning `'cancel'`, which prevents the message from being sent. + ### `__on_statistic(player, category, event, value)` Triggered when a player statistic changes. Doesn't notify on periodic an rhythmic events, i.e. `time_since_death`, `time_since_rest`, and `played_one_minute` since these are triggered every tick. Event diff --git a/docs/scarpet/api/Overview.md b/docs/scarpet/api/Overview.md index 733b6a03..890246a8 100644 --- a/docs/scarpet/api/Overview.md +++ b/docs/scarpet/api/Overview.md @@ -60,6 +60,8 @@ To access global/server state for a player app, which you shouldn't do, you need so either use a command block, or any arbitrary entity: `/execute as @e[type=bat,limit=1] run script in globals` for instance, however running anything in the global scope for a `'player'` scoped app is not intended. +* `'event_priority'`: defaults to `0`. This specifies the order in which events will be run, from highest to lowest. +This is need since cancelling an event will stop executing the event in subsequent apps with lower priority. * `'stay_loaded'`: defaults to `true`. If true, and `/carpet scriptsAutoload` is turned on, the following apps will stay loaded after startup. Otherwise, after reading the app the first time, and fetching the config, server will drop them down. WARNING: all apps will run once at startup anyways, so be aware that their actions that are called diff --git a/docs/scarpet/resources/editors/idea/4.txt b/docs/scarpet/resources/editors/idea/4.txt index 3bf2504e..d17f121d 100644 --- a/docs/scarpet/resources/editors/idea/4.txt +++ b/docs/scarpet/resources/editors/idea/4.txt @@ -17,6 +17,7 @@ __on_player_interacts_with_block(player, hand, block, face, hitvec) -> __on_player_interacts_with_entity(player, entity, hand) -> __on_player_jumps(player) -> __on_player_picks_up_item(player, item) -> +__on_player_placing_block(player, item_tuple, hand, block) -> __on_player_places_block(player, item_tuple, hand, block) -> __on_player_releases_item(player, item_tuple, hand) -> __on_player_rides(player, forward, strafe, jumping, sneaking) -> diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml index 8730b818..951afa03 100644 --- a/docs/scarpet/resources/editors/npp/scarpet.xml +++ b/docs/scarpet/resources/editors/npp/scarpet.xml @@ -120,6 +120,7 @@ __on_player_interacts_with_entity(player, entity, hand) -> __on_player_jumps(player) -> __on_player_picks_up_item(player, item) -> + __on_player_placing_block(player, item_tuple, hand, block) -> __on_player_places_block(player, item_tuple, hand, block) -> __on_player_releases_item(player, item_tuple, hand) -> __on_player_rides(player, forward, strafe, jumping, sneaking) -> diff --git a/src/main/java/carpet/mixins/BlockItem_scarpetEventMixin.java b/src/main/java/carpet/mixins/BlockItem_scarpetEventMixin.java index c3516759..2b34ecac 100644 --- a/src/main/java/carpet/mixins/BlockItem_scarpetEventMixin.java +++ b/src/main/java/carpet/mixins/BlockItem_scarpetEventMixin.java @@ -1,11 +1,13 @@ package carpet.mixins; +import net.minecraft.world.level.block.state.BlockState; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import static carpet.script.CarpetEventServer.Event.PLAYER_PLACES_BLOCK; +import static carpet.script.CarpetEventServer.Event.PLAYER_PLACING_BLOCK; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionResult; @@ -25,4 +27,14 @@ public class BlockItem_scarpetEventMixin if (context.getPlayer() instanceof ServerPlayer && PLAYER_PLACES_BLOCK.isNeeded()) PLAYER_PLACES_BLOCK.onBlockPlaced((ServerPlayer) context.getPlayer(), context.getClickedPos(), context.getHand(), context.getItemInHand()); } + + @Inject(method = "placeBlock", at = @At("HEAD"), cancellable = true) + private void beforePlacement(BlockPlaceContext blockPlaceContext, BlockState blockState, CallbackInfoReturnable cir) { + if (blockPlaceContext.getPlayer() instanceof ServerPlayer && PLAYER_PLACING_BLOCK.isNeeded()) { + if (PLAYER_PLACING_BLOCK.onBlockPlaced((ServerPlayer) blockPlaceContext.getPlayer(), blockPlaceContext.getClickedPos(), blockPlaceContext.getHand(), blockPlaceContext.getItemInHand())) { + cir.setReturnValue(false); + cir.cancel(); + } + } + } } diff --git a/src/main/java/carpet/mixins/LivingEntity_scarpetEventsMixin.java b/src/main/java/carpet/mixins/LivingEntity_scarpetEventsMixin.java index 1cf2ca2c..57c66d84 100644 --- a/src/main/java/carpet/mixins/LivingEntity_scarpetEventsMixin.java +++ b/src/main/java/carpet/mixins/LivingEntity_scarpetEventsMixin.java @@ -37,7 +37,7 @@ public abstract class LivingEntity_scarpetEventsMixin extends Entity implements ((EntityInterface)this).getEventContainer().onEvent(EntityEventsGroup.Event.ON_DEATH, damageSource_1.msgId); } - @Inject(method = "actuallyHurt", locals = LocalCapture.CAPTURE_FAILHARD, at = @At( + @Inject(method = "actuallyHurt", cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD, at = @At( value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;getDamageAfterArmorAbsorb(Lnet/minecraft/world/damagesource/DamageSource;F)F", shift = At.Shift.BEFORE @@ -52,7 +52,9 @@ public abstract class LivingEntity_scarpetEventsMixin extends Entity implements //} if (source.getEntity() instanceof ServerPlayer && PLAYER_DEALS_DAMAGE.isNeeded()) { - PLAYER_DEALS_DAMAGE.onDamage(this, amount, source); + if(PLAYER_DEALS_DAMAGE.onDamage(this, amount, source)) { + ci.cancel(); + } } } diff --git a/src/main/java/carpet/mixins/Player_scarpetEventsMixin.java b/src/main/java/carpet/mixins/Player_scarpetEventsMixin.java index 7ef8b1ea..6f41f620 100644 --- a/src/main/java/carpet/mixins/Player_scarpetEventsMixin.java +++ b/src/main/java/carpet/mixins/Player_scarpetEventsMixin.java @@ -32,7 +32,7 @@ public abstract class Player_scarpetEventsMixin extends LivingEntity super(type, world); } - @Inject(method = "actuallyHurt", locals = LocalCapture.CAPTURE_FAILHARD, at = @At( + @Inject(method = "actuallyHurt", cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD, at = @At( value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getDamageAfterArmorAbsorb(Lnet/minecraft/world/damagesource/DamageSource;F)F" )) @@ -42,11 +42,15 @@ public abstract class Player_scarpetEventsMixin extends LivingEntity ((EntityInterface)this).getEventContainer().onEvent(EntityEventsGroup.Event.ON_DAMAGE, amount, source); if (PLAYER_TAKES_DAMAGE.isNeeded()) { - PLAYER_TAKES_DAMAGE.onDamage(this, amount, source); + if(PLAYER_TAKES_DAMAGE.onDamage(this, amount, source)) { + ci.cancel(); + } } if (source.getEntity() instanceof ServerPlayer && PLAYER_DEALS_DAMAGE.isNeeded()) { - PLAYER_DEALS_DAMAGE.onDamage(this, amount, source); + if(PLAYER_DEALS_DAMAGE.onDamage(this, amount, source)) { + ci.cancel(); + } } } @@ -59,21 +63,26 @@ public abstract class Player_scarpetEventsMixin extends LivingEntity } } - @Inject(method = "interactOn", at = @At("HEAD")) + @Inject(method = "interactOn", cancellable = true, at = @At("HEAD")) private void doInteract(Entity entity, InteractionHand hand, CallbackInfoReturnable cir) { if (!level.isClientSide && PLAYER_INTERACTS_WITH_ENTITY.isNeeded()) { - PLAYER_INTERACTS_WITH_ENTITY.onEntityHandAction((ServerPlayer) (Object)this, entity, hand); + if(PLAYER_INTERACTS_WITH_ENTITY.onEntityHandAction((ServerPlayer) (Object)this, entity, hand)) { + cir.setReturnValue(InteractionResult.PASS); + cir.cancel(); + } } } - @Inject(method = "attack", at = @At("HEAD")) + @Inject(method = "attack", at = @At("HEAD"), cancellable = true) private void onAttack(Entity target, CallbackInfo ci) { if (!level.isClientSide && PLAYER_ATTACKS_ENTITY.isNeeded() && target.isAttackable()) { - PLAYER_ATTACKS_ENTITY.onEntityHandAction((ServerPlayer) (Object)this, target, null); + if(PLAYER_ATTACKS_ENTITY.onEntityHandAction((ServerPlayer) (Object)this, target, null)) { + ci.cancel(); + } } } } diff --git a/src/main/java/carpet/mixins/ServerGamePacketListenerImpl_scarpetEventsMixin.java b/src/main/java/carpet/mixins/ServerGamePacketListenerImpl_scarpetEventsMixin.java index ee342120..57989181 100644 --- a/src/main/java/carpet/mixins/ServerGamePacketListenerImpl_scarpetEventsMixin.java +++ b/src/main/java/carpet/mixins/ServerGamePacketListenerImpl_scarpetEventsMixin.java @@ -61,7 +61,7 @@ public class ServerGamePacketListenerImpl_scarpetEventsMixin } } - @Inject(method = "handlePlayerAction", at = @At( + @Inject(method = "handlePlayerAction", cancellable = true, at = @At( value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;drop(Z)Z", // dropSelectedItem ordinal = 0, @@ -69,10 +69,12 @@ public class ServerGamePacketListenerImpl_scarpetEventsMixin )) private void onQItem(ServerboundPlayerActionPacket playerActionC2SPacket_1, CallbackInfo ci) { - PLAYER_DROPS_ITEM.onPlayerEvent(player); + if(PLAYER_DROPS_ITEM.onPlayerEvent(player)) { + ci.cancel(); + } } - @Inject(method = "handlePlayerAction", at = @At( + @Inject(method = "handlePlayerAction", cancellable = true, at = @At( value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;getItemInHand(Lnet/minecraft/world/InteractionHand;)Lnet/minecraft/world/item/ItemStack;", ordinal = 0, @@ -80,10 +82,10 @@ public class ServerGamePacketListenerImpl_scarpetEventsMixin )) private void onHandSwap(ServerboundPlayerActionPacket playerActionC2SPacket_1, CallbackInfo ci) { - PLAYER_SWAPS_HANDS.onPlayerEvent(player); + if(PLAYER_SWAPS_HANDS.onPlayerEvent(player)) ci.cancel(); } - @Inject(method = "handlePlayerAction", at = @At( + @Inject(method = "handlePlayerAction", cancellable = true, at = @At( value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;drop(Z)Z", // dropSelectedItem ordinal = 1, @@ -91,7 +93,9 @@ public class ServerGamePacketListenerImpl_scarpetEventsMixin )) private void onCtrlQItem(ServerboundPlayerActionPacket playerActionC2SPacket_1, CallbackInfo ci) { - PLAYER_DROPS_STACK.onPlayerEvent(player); + if(PLAYER_DROPS_STACK.onPlayerEvent(player)) { + ci.cancel(); + } } @@ -104,7 +108,7 @@ public class ServerGamePacketListenerImpl_scarpetEventsMixin PLAYER_JUMPS.onPlayerEvent(player); } - @Inject(method = "handlePlayerAction", at = @At( + @Inject(method = "handlePlayerAction", cancellable = true, at = @At( value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayerGameMode;handleBlockBreakAction(Lnet/minecraft/core/BlockPos;Lnet/minecraft/network/protocol/game/ServerboundPlayerActionPacket$Action;Lnet/minecraft/core/Direction;II)V", shift = At.Shift.BEFORE @@ -112,7 +116,9 @@ public class ServerGamePacketListenerImpl_scarpetEventsMixin private void onClicked(ServerboundPlayerActionPacket packet, CallbackInfo ci) { if (packet.getAction() == ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK) - PLAYER_CLICKS_BLOCK.onBlockAction(player, packet.getPos(), packet.getDirection()); + if(PLAYER_CLICKS_BLOCK.onBlockAction(player, packet.getPos(), packet.getDirection())) { + ci.cancel(); + } } @Redirect(method = "handlePlayerAction", at = @At( @@ -134,7 +140,7 @@ public class ServerGamePacketListenerImpl_scarpetEventsMixin } } - @Inject(method = "handleUseItemOn", at = @At( + @Inject(method = "handleUseItemOn", cancellable = true, at = @At( value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayerGameMode;useItemOn(Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/world/level/Level;Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/InteractionHand;Lnet/minecraft/world/phys/BlockHitResult;)Lnet/minecraft/world/InteractionResult;" )) @@ -144,11 +150,13 @@ public class ServerGamePacketListenerImpl_scarpetEventsMixin { InteractionHand hand = playerInteractBlockC2SPacket_1.getHand(); BlockHitResult hitRes = playerInteractBlockC2SPacket_1.getHitResult(); - PLAYER_RIGHT_CLICKS_BLOCK.onBlockHit(player, hand, hitRes); + if(PLAYER_RIGHT_CLICKS_BLOCK.onBlockHit(player, hand, hitRes)) { + ci.cancel(); + } } } - @Inject(method = "handleUseItem", at = @At( + @Inject(method = "handleUseItem", cancellable = true, at = @At( value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;resetLastActionTime()V" )) @@ -157,7 +165,9 @@ public class ServerGamePacketListenerImpl_scarpetEventsMixin if (PLAYER_USES_ITEM.isNeeded()) { InteractionHand hand = playerInteractItemC2SPacket_1.getHand(); - PLAYER_USES_ITEM.onItemAction(player, hand, player.getItemInHand(hand).copy()); + if(PLAYER_USES_ITEM.onItemAction(player, hand, player.getItemInHand(hand).copy())) { + ci.cancel(); + } } } @@ -251,12 +261,12 @@ public class ServerGamePacketListenerImpl_scarpetEventsMixin // crafts not int the crafting window //CarpetSettings.LOG.error("Player clicks button "+packet.getButtonId()); } - @Inject(method = "handlePlaceRecipe", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;resetLastActionTime()V")) + @Inject(method = "handlePlaceRecipe", cancellable = true, at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;resetLastActionTime()V")) private void onRecipeSelectedInRecipeManager(ServerboundPlaceRecipePacket packet, CallbackInfo ci) { if (PLAYER_CHOOSES_RECIPE.isNeeded()) { - PLAYER_CHOOSES_RECIPE.onRecipeSelected(player, packet.getRecipe(), packet.isShiftDown()); + if(PLAYER_CHOOSES_RECIPE.onRecipeSelected(player, packet.getRecipe(), packet.isShiftDown())) ci.cancel(); } } @@ -283,12 +293,13 @@ public class ServerGamePacketListenerImpl_scarpetEventsMixin } @Inject(method = "handleChat(Lnet/minecraft/network/protocol/game/ServerboundChatPacket;)V", - at = @At(value = "HEAD") + at = @At(value = "HEAD"), + cancellable = true ) private void onChatMessage(ServerboundChatPacket serverboundChatPacket, CallbackInfo ci) { if (PLAYER_MESSAGE.isNeeded()) { - PLAYER_MESSAGE.onPlayerMessage(player, serverboundChatPacket.message()); + if(PLAYER_MESSAGE.onPlayerMessage(player, serverboundChatPacket.message())) ci.cancel(); } } } diff --git a/src/main/java/carpet/mixins/ServerPlayerGameMode_scarpetEventsMixin.java b/src/main/java/carpet/mixins/ServerPlayerGameMode_scarpetEventsMixin.java index 1ad57ea1..98f0ffec 100644 --- a/src/main/java/carpet/mixins/ServerPlayerGameMode_scarpetEventsMixin.java +++ b/src/main/java/carpet/mixins/ServerPlayerGameMode_scarpetEventsMixin.java @@ -38,14 +38,18 @@ public class ServerPlayerGameMode_scarpetEventsMixin implements ServerPlayerInte @Shadow public ServerLevel level; - @Inject(method = "destroyBlock", locals = LocalCapture.CAPTURE_FAILHARD, at = @At( + @Inject(method = "destroyBlock", locals = LocalCapture.CAPTURE_FAILHARD, cancellable = true, at = @At( value = "INVOKE", target = "Lnet/minecraft/world/level/block/Block;playerWillDestroy(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/entity/player/Player;)V", shift = At.Shift.BEFORE )) private void onBlockBroken(BlockPos blockPos_1, CallbackInfoReturnable cir, BlockState blockState_1, BlockEntity be, Block b) { - PLAYER_BREAK_BLOCK.onBlockBroken(player, blockPos_1, blockState_1); + if(PLAYER_BREAK_BLOCK.onBlockBroken(player, blockPos_1, blockState_1)) { + this.level.sendBlockUpdated(blockPos_1, blockState_1, blockState_1, 3); + cir.setReturnValue(false); + cir.cancel(); + } } @Inject(method = "useItemOn", at = @At( diff --git a/src/main/java/carpet/mixins/ServerPlayer_scarpetEventMixin.java b/src/main/java/carpet/mixins/ServerPlayer_scarpetEventMixin.java index 2be1737d..57f38680 100644 --- a/src/main/java/carpet/mixins/ServerPlayer_scarpetEventMixin.java +++ b/src/main/java/carpet/mixins/ServerPlayer_scarpetEventMixin.java @@ -56,9 +56,10 @@ public abstract class ServerPlayer_scarpetEventMixin extends Player implements S if (PLAYER_FINISHED_USING_ITEM.isNeeded()) { InteractionHand hand = getUsedItemHand(); - PLAYER_FINISHED_USING_ITEM.onItemAction((ServerPlayer) (Object)this, hand, getUseItem()); - // do vanilla - super.completeUsingItem(); + if(!PLAYER_FINISHED_USING_ITEM.onItemAction((ServerPlayer) (Object)this, hand, getUseItem())) { + // do vanilla + super.completeUsingItem(); + } } else { diff --git a/src/main/java/carpet/script/CarpetEventServer.java b/src/main/java/carpet/script/CarpetEventServer.java index 194a27f9..09182d38 100644 --- a/src/main/java/carpet/script/CarpetEventServer.java +++ b/src/main/java/carpet/script/CarpetEventServer.java @@ -25,6 +25,7 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; @@ -77,7 +78,7 @@ public class CarpetEventServer public enum CallbackResult { - SUCCESS, PASS, FAIL + SUCCESS, PASS, FAIL, CANCEL } public static class Callback @@ -216,12 +217,13 @@ public class CarpetEventServer * Handles only built-in events from the events system * @param argumentSupplier * @param cmdSourceSupplier + * @return Whether this event call has been cancelled */ - public void call(Supplier> argumentSupplier, Supplier cmdSourceSupplier) + public boolean call(Supplier> argumentSupplier, Supplier cmdSourceSupplier) { if (callList.size() > 0 && CarpetServer.scriptServer != null) { - CarpetServer.scriptServer.events.handleEvents.runIfEnabled( () -> { + return CarpetServer.scriptServer.events.handleEvents.runIfEnabled( () -> { CarpetProfiler.ProfilerToken currentSection = CarpetProfiler.start_section(null, "Scarpet events", CarpetProfiler.TYPE.GENERAL); List argv = argumentSupplier.get(); // empty for onTickDone CommandSourceStack source; @@ -231,10 +233,11 @@ public class CarpetEventServer } catch (NullPointerException noReference) // todo figure out what happens when closing. { - return; + return false; } String nameCheck = perPlayerDistribution ? source.getTextName() : null; assert argv.size() == reqArgs; + boolean cancelled = false; try { // we are ok with list growing in the meantime @@ -246,7 +249,12 @@ public class CarpetEventServer // supressing calls where target player hosts simply don't match // handling global hosts with player targets is left to when the host is resolved (few calls deeper). if (nameCheck != null && call.optionalTarget != null && !nameCheck.equals(call.optionalTarget)) continue; - if (call.execute(source, argv) == CallbackResult.FAIL) removedCalls.add(call); + CallbackResult result = call.execute(source, argv); + if(result == CallbackResult.CANCEL) { + cancelled = true; + break; + } + if (result == CallbackResult.FAIL) removedCalls.add(call); } } finally @@ -256,8 +264,10 @@ public class CarpetEventServer for (Callback call : removedCalls) callList.remove(call); removedCalls.clear(); CarpetProfiler.end_current_section(currentSection); + return cancelled; }); } + return false; } public int signal(CommandSourceStack sender, ServerPlayer optinoalReceipient, List callArg) @@ -363,6 +373,10 @@ public class CarpetEventServer if (inSignal || inCall) callList = new ArrayList<>(); callList.clear(); } + + public void sortByPriority(CarpetScriptServer scriptServer) { + callList.sort(Comparator.comparingDouble(c -> -scriptServer.getAppHostByName(c.host).eventPriority)); + } } public static class Event @@ -483,33 +497,37 @@ public class CarpetEventServer public static final Event PLAYER_JUMPS = new Event("player_jumps", 1, false) { @Override - public void onPlayerEvent(ServerPlayer player) + public boolean onPlayerEvent(ServerPlayer player) { handler.call( () -> Collections.singletonList(new EntityValue(player)), player::createCommandSourceStack); + return false; } }; public static final Event PLAYER_DEPLOYS_ELYTRA = new Event("player_deploys_elytra", 1, false) { @Override - public void onPlayerEvent(ServerPlayer player) + public boolean onPlayerEvent(ServerPlayer player) { handler.call( () -> Collections.singletonList(new EntityValue(player)), player::createCommandSourceStack); + return false; } }; public static final Event PLAYER_WAKES_UP = new Event("player_wakes_up", 1, false) { @Override - public void onPlayerEvent(ServerPlayer player) + public boolean onPlayerEvent(ServerPlayer player) { handler.call( () -> Collections.singletonList(new EntityValue(player)), player::createCommandSourceStack); + return false; } }; public static final Event PLAYER_ESCAPES_SLEEP = new Event("player_escapes_sleep", 1, false) { @Override - public void onPlayerEvent(ServerPlayer player) + public boolean onPlayerEvent(ServerPlayer player) { handler.call( () -> Collections.singletonList(new EntityValue(player)), player::createCommandSourceStack); + return false; } }; public static final Event PLAYER_RIDES = new Event("player_rides", 5, false) @@ -525,9 +543,9 @@ public class CarpetEventServer public static final Event PLAYER_USES_ITEM = new Event("player_uses_item", 3, false) { @Override - public void onItemAction(ServerPlayer player, InteractionHand enumhand, ItemStack itemstack) + public boolean onItemAction(ServerPlayer player, InteractionHand enumhand, ItemStack itemstack) { - handler.call( () -> + return handler.call( () -> { //ItemStack itemstack = player.getStackInHand(enumhand); return Arrays.asList( @@ -541,9 +559,9 @@ public class CarpetEventServer public static final Event PLAYER_CLICKS_BLOCK = new Event("player_clicks_block", 3, false) { @Override - public void onBlockAction(ServerPlayer player, BlockPos blockpos, Direction facing) + public boolean onBlockAction(ServerPlayer player, BlockPos blockpos, Direction facing) { - handler.call( () -> + return handler.call( () -> { return Arrays.asList( new EntityValue(player), @@ -556,9 +574,9 @@ public class CarpetEventServer public static final Event PLAYER_RIGHT_CLICKS_BLOCK = new Event("player_right_clicks_block", 6, false) { @Override - public void onBlockHit(ServerPlayer player, InteractionHand enumhand, BlockHitResult hitRes)//ItemStack itemstack, Hand enumhand, BlockPos blockpos, Direction enumfacing, Vec3d vec3d) + public boolean onBlockHit(ServerPlayer player, InteractionHand enumhand, BlockHitResult hitRes)//ItemStack itemstack, Hand enumhand, BlockPos blockpos, Direction enumfacing, Vec3d vec3d) { - handler.call( () -> + return handler.call( () -> { ItemStack itemstack = player.getItemInHand(enumhand); BlockPos blockpos = hitRes.getBlockPos(); @@ -582,7 +600,7 @@ public class CarpetEventServer public static final Event PLAYER_INTERACTS_WITH_BLOCK = new Event("player_interacts_with_block", 5, false) { @Override - public void onBlockHit(ServerPlayer player, InteractionHand enumhand, BlockHitResult hitRes) + public boolean onBlockHit(ServerPlayer player, InteractionHand enumhand, BlockHitResult hitRes) { handler.call( () -> { @@ -601,14 +619,15 @@ public class CarpetEventServer ) ); }, player::createCommandSourceStack); + return false; } }; - public static final Event PLAYER_PLACES_BLOCK = new Event("player_places_block", 4, false) + public static final Event PLAYER_PLACING_BLOCK = new Event("player_placing_block", 4, false) { @Override - public void onBlockPlaced(ServerPlayer player, BlockPos pos, InteractionHand enumhand, ItemStack itemstack) + public boolean onBlockPlaced(ServerPlayer player, BlockPos pos, InteractionHand enumhand, ItemStack itemstack) { - handler.call( () -> Arrays.asList( + return handler.call( () -> Arrays.asList( new EntityValue(player), ValueConversions.of(itemstack), StringValue.of(enumhand == InteractionHand.MAIN_HAND ? "mainhand" : "offhand"), @@ -616,12 +635,26 @@ public class CarpetEventServer ), player::createCommandSourceStack); } }; + public static final Event PLAYER_PLACES_BLOCK = new Event("player_places_block", 4, false) + { + @Override + public boolean onBlockPlaced(ServerPlayer player, BlockPos pos, InteractionHand enumhand, ItemStack itemstack) + { + handler.call( () -> Arrays.asList( + new EntityValue(player), + ValueConversions.of(itemstack), + StringValue.of(enumhand == InteractionHand.MAIN_HAND ? "mainhand" : "offhand"), + new BlockValue(null, player.getLevel(), pos) + ), player::createCommandSourceStack); + return false; + } + }; public static final Event PLAYER_BREAK_BLOCK = new Event("player_breaks_block", 2, false) { @Override - public void onBlockBroken(ServerPlayer player, BlockPos pos, BlockState previousBS) + public boolean onBlockBroken(ServerPlayer player, BlockPos pos, BlockState previousBS) { - handler.call( + return handler.call( () -> Arrays.asList(new EntityValue(player), new BlockValue(previousBS, player.getLevel(), pos)), player::createCommandSourceStack ); @@ -630,9 +663,9 @@ public class CarpetEventServer public static final Event PLAYER_INTERACTS_WITH_ENTITY = new Event("player_interacts_with_entity", 3, false) { @Override - public void onEntityHandAction(ServerPlayer player, Entity entity, InteractionHand enumhand) + public boolean onEntityHandAction(ServerPlayer player, Entity entity, InteractionHand enumhand) { - handler.call( () -> Arrays.asList( + return handler.call( () -> Arrays.asList( new EntityValue(player), new EntityValue(entity), StringValue.of(enumhand==InteractionHand.MAIN_HAND?"mainhand":"offhand") ), player::createCommandSourceStack); } @@ -654,56 +687,61 @@ public class CarpetEventServer public static final Event PLAYER_PICKS_UP_ITEM = new Event("player_picks_up_item", 2, false) { @Override - public void onItemAction(ServerPlayer player, InteractionHand enumhand, ItemStack itemstack) { + public boolean onItemAction(ServerPlayer player, InteractionHand enumhand, ItemStack itemstack) { handler.call( () -> Arrays.asList(new EntityValue(player), ValueConversions.of(itemstack)), player::createCommandSourceStack); + return false; } }; public static final Event PLAYER_ATTACKS_ENTITY = new Event("player_attacks_entity", 2, false) { @Override - public void onEntityHandAction(ServerPlayer player, Entity entity, InteractionHand enumhand) + public boolean onEntityHandAction(ServerPlayer player, Entity entity, InteractionHand enumhand) { - handler.call( () -> Arrays.asList(new EntityValue(player), new EntityValue(entity)), player::createCommandSourceStack); + return handler.call( () -> Arrays.asList(new EntityValue(player), new EntityValue(entity)), player::createCommandSourceStack); } }; public static final Event PLAYER_STARTS_SNEAKING = new Event("player_starts_sneaking", 1, false) { @Override - public void onPlayerEvent(ServerPlayer player) + public boolean onPlayerEvent(ServerPlayer player) { handler.call( () -> Collections.singletonList(new EntityValue(player)), player::createCommandSourceStack); + return false; } }; public static final Event PLAYER_STOPS_SNEAKING = new Event("player_stops_sneaking", 1, false) { @Override - public void onPlayerEvent(ServerPlayer player) + public boolean onPlayerEvent(ServerPlayer player) { handler.call( () -> Collections.singletonList(new EntityValue(player)), player::createCommandSourceStack); + return false; } }; public static final Event PLAYER_STARTS_SPRINTING = new Event("player_starts_sprinting", 1, false) { @Override - public void onPlayerEvent(ServerPlayer player) + public boolean onPlayerEvent(ServerPlayer player) { handler.call( () -> Collections.singletonList(new EntityValue(player)), player::createCommandSourceStack); + return false; } }; public static final Event PLAYER_STOPS_SPRINTING = new Event("player_stops_sprinting", 1, false) { @Override - public void onPlayerEvent(ServerPlayer player) + public boolean onPlayerEvent(ServerPlayer player) { handler.call( () -> Collections.singletonList(new EntityValue(player)), player::createCommandSourceStack); + return false; } }; public static final Event PLAYER_RELEASED_ITEM = new Event("player_releases_item", 3, false) { @Override - public void onItemAction(ServerPlayer player, InteractionHand enumhand, ItemStack itemstack) + public boolean onItemAction(ServerPlayer player, InteractionHand enumhand, ItemStack itemstack) { // this.getStackInHand(this.getActiveHand()), this.activeItemStack) handler.call( () -> @@ -712,15 +750,16 @@ public class CarpetEventServer ValueConversions.of(itemstack), StringValue.of(enumhand == InteractionHand.MAIN_HAND ? "mainhand" : "offhand") ), player::createCommandSourceStack); + return false; } }; public static final Event PLAYER_FINISHED_USING_ITEM = new Event("player_finishes_using_item", 3, false) { @Override - public void onItemAction(ServerPlayer player, InteractionHand enumhand, ItemStack itemstack) + public boolean onItemAction(ServerPlayer player, InteractionHand enumhand, ItemStack itemstack) { // this.getStackInHand(this.getActiveHand()), this.activeItemStack) - handler.call( () -> + return handler.call( () -> Arrays.asList( new EntityValue(player), ValueConversions.of(itemstack), @@ -731,25 +770,25 @@ public class CarpetEventServer public static final Event PLAYER_DROPS_ITEM = new Event("player_drops_item", 1, false) { @Override - public void onPlayerEvent(ServerPlayer player) + public boolean onPlayerEvent(ServerPlayer player) { - handler.call( () -> Collections.singletonList(new EntityValue(player)), player::createCommandSourceStack); + return handler.call( () -> Collections.singletonList(new EntityValue(player)), player::createCommandSourceStack); } }; public static final Event PLAYER_DROPS_STACK = new Event("player_drops_stack", 1, false) { @Override - public void onPlayerEvent(ServerPlayer player) + public boolean onPlayerEvent(ServerPlayer player) { - handler.call( () -> Collections.singletonList(new EntityValue(player)), player::createCommandSourceStack); + return handler.call( () -> Collections.singletonList(new EntityValue(player)), player::createCommandSourceStack); } }; public static final Event PLAYER_CHOOSES_RECIPE = new Event("player_chooses_recipe", 3, false) { @Override - public void onRecipeSelected(ServerPlayer player, ResourceLocation recipe, boolean fullStack) + public boolean onRecipeSelected(ServerPlayer player, ResourceLocation recipe, boolean fullStack) { - handler.call( () -> + return handler.call( () -> Arrays.asList( new EntityValue(player), StringValue.of(NBTSerializableValue.nameFromRegistryId(recipe)), @@ -774,9 +813,9 @@ public class CarpetEventServer public static final Event PLAYER_SWAPS_HANDS = new Event("player_swaps_hands", 1, false) { @Override - public void onPlayerEvent(ServerPlayer player) + public boolean onPlayerEvent(ServerPlayer player) { - handler.call( () -> Collections.singletonList(new EntityValue(player)), player::createCommandSourceStack); + return handler.call( () -> Collections.singletonList(new EntityValue(player)), player::createCommandSourceStack); } }; public static final Event PLAYER_SWINGS_HAND = new Event("player_swings_hand", 2, false) @@ -794,9 +833,9 @@ public class CarpetEventServer public static final Event PLAYER_TAKES_DAMAGE = new Event("player_takes_damage", 4, false) { @Override - public void onDamage(Entity target, float amount, DamageSource source) + public boolean onDamage(Entity target, float amount, DamageSource source) { - handler.call( () -> + return handler.call( () -> Arrays.asList( new EntityValue(target), new NumericValue(amount), @@ -808,9 +847,9 @@ public class CarpetEventServer public static final Event PLAYER_DEALS_DAMAGE = new Event("player_deals_damage", 3, false) { @Override - public void onDamage(Entity target, float amount, DamageSource source) + public boolean onDamage(Entity target, float amount, DamageSource source) { - handler.call( () -> + return handler.call( () -> Arrays.asList(new EntityValue(source.getEntity()), new NumericValue(amount), new EntityValue(target)), () -> source.getEntity().createCommandSourceStack() ); @@ -819,25 +858,28 @@ public class CarpetEventServer public static final Event PLAYER_COLLIDES_WITH_ENTITY = new Event("player_collides_with_entity", 2, false) { @Override - public void onEntityHandAction(ServerPlayer player, Entity entity, InteractionHand enumhand) { + public boolean onEntityHandAction(ServerPlayer player, Entity entity, InteractionHand enumhand) { handler.call( () -> Arrays.asList(new EntityValue(player), new EntityValue(entity)), player::createCommandSourceStack); + return false; } }; public static final Event PLAYER_DIES = new Event("player_dies", 1, false) { @Override - public void onPlayerEvent(ServerPlayer player) + public boolean onPlayerEvent(ServerPlayer player) { handler.call( () -> Collections.singletonList(new EntityValue(player)), player::createCommandSourceStack); + return false; } }; public static final Event PLAYER_RESPAWNS = new Event("player_respawns", 1, false) { @Override - public void onPlayerEvent(ServerPlayer player) + public boolean onPlayerEvent(ServerPlayer player) { handler.call( () -> Collections.singletonList(new EntityValue(player)), player::createCommandSourceStack); + return false; } }; public static final Event PLAYER_CHANGES_DIMENSION = new Event("player_changes_dimension", 5, false) @@ -856,23 +898,25 @@ public class CarpetEventServer }; public static final Event PLAYER_CONNECTS = new Event("player_connects", 1, false) { @Override - public void onPlayerEvent(ServerPlayer player) + public boolean onPlayerEvent(ServerPlayer player) { handler.call( () -> Collections.singletonList(new EntityValue(player)), player::createCommandSourceStack); + return false; } }; public static final Event PLAYER_DISCONNECTS = new Event("player_disconnects", 2, false) { @Override - public void onPlayerMessage(ServerPlayer player, String message) + public boolean onPlayerMessage(ServerPlayer player, String message) { handler.call( () -> Arrays.asList(new EntityValue(player), new StringValue(message)), player::createCommandSourceStack); + return false; } }; public static final Event PLAYER_MESSAGE = new Event("player_message", 2, false) { @Override - public void onPlayerMessage(ServerPlayer player, String message) { - handler.call( () -> Arrays.asList(new EntityValue(player), new StringValue(message)), player::createCommandSourceStack); + public boolean onPlayerMessage(ServerPlayer player, String message) { + return handler.call( () -> Arrays.asList(new EntityValue(player), new StringValue(message)), player::createCommandSourceStack); } }; @@ -1103,21 +1147,21 @@ public class CarpetEventServer //stubs for calls just to ease calls in vanilla code so they don't need to deal with scarpet value types public void onTick() { } public void onChunkEvent(ServerLevel world, ChunkPos chPos, boolean generated) { } - public void onPlayerEvent(ServerPlayer player) { } - public void onPlayerMessage(ServerPlayer player, String message) { } + public boolean onPlayerEvent(ServerPlayer player) {return false;} + public boolean onPlayerMessage(ServerPlayer player, String message) {return false;} public void onPlayerStatistic(ServerPlayer player, Stat stat, int amount) { } public void onMountControls(ServerPlayer player, float strafeSpeed, float forwardSpeed, boolean jumping, boolean sneaking) { } - public void onItemAction(ServerPlayer player, InteractionHand enumhand, ItemStack itemstack) { } - public void onBlockAction(ServerPlayer player, BlockPos blockpos, Direction facing) { } - public void onBlockHit(ServerPlayer player, InteractionHand enumhand, BlockHitResult hitRes) { } - public void onBlockBroken(ServerPlayer player, BlockPos pos, BlockState previousBS) { } - public void onBlockPlaced(ServerPlayer player, BlockPos pos, InteractionHand enumhand, ItemStack itemstack) { } - public void onEntityHandAction(ServerPlayer player, Entity entity, InteractionHand enumhand) { } + public boolean onItemAction(ServerPlayer player, InteractionHand enumhand, ItemStack itemstack) {return false;} + public boolean onBlockAction(ServerPlayer player, BlockPos blockpos, Direction facing) {return false;} + public boolean onBlockHit(ServerPlayer player, InteractionHand enumhand, BlockHitResult hitRes) {return false;} + public boolean onBlockBroken(ServerPlayer player, BlockPos pos, BlockState previousBS) {return false;} + public boolean onBlockPlaced(ServerPlayer player, BlockPos pos, InteractionHand enumhand, ItemStack itemstack) {return false;} + public boolean onEntityHandAction(ServerPlayer player, Entity entity, InteractionHand enumhand) {return false;} public void onHandAction(ServerPlayer player, InteractionHand enumhand) { } public void onEntityAction(Entity entity, boolean created) { } public void onDimensionChange(ServerPlayer player, Vec3 from, Vec3 to, ResourceKey fromDim, ResourceKey dimTo) {} - public void onDamage(Entity target, float amount, DamageSource source) { } - public void onRecipeSelected(ServerPlayer player, ResourceLocation recipe, boolean fullStack) {} + public boolean onDamage(Entity target, float amount, DamageSource source) {return false;} + public boolean onRecipeSelected(ServerPlayer player, ResourceLocation recipe, boolean fullStack) {return false;} public void onSlotSwitch(ServerPlayer player, int from, int to) {} public void onTrade(ServerPlayer player, Merchant merchant, MerchantOffer tradeOffer) {} @@ -1221,8 +1265,8 @@ public class CarpetEventServer if (executingHost == null) return CallbackResult.FAIL; try { - executingHost.callUDF(source.withPermission(CarpetSettings.runPermissionLevel), udf, argv); - return CallbackResult.SUCCESS; + Value returnValue = executingHost.callUDF(source.withPermission(CarpetSettings.runPermissionLevel), udf, argv); + return returnValue instanceof StringValue && returnValue.getString().equals("cancel") ? CallbackResult.CANCEL : CallbackResult.SUCCESS; } catch (NullPointerException | InvalidCallbackException | IntegrityException error) { @@ -1269,6 +1313,7 @@ public class CarpetEventServer private void onEventAddedToHost(Event event, ScriptHost host) { if (event.deprecated()) host.issueDeprecation(event.name+" event"); + event.handler.sortByPriority(this.scriptServer); //return !(event.globalOnly && (host.perUser || host.parent != null)); } diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java index 92ff6774..76c394cf 100644 --- a/src/main/java/carpet/script/CarpetScriptHost.java +++ b/src/main/java/carpet/script/CarpetScriptHost.java @@ -75,6 +75,7 @@ public class CarpetScriptHost extends ScriptHost private Tag globalState; private int saveTimeout; public boolean persistenceRequired; + public double eventPriority; public Map appConfig; public Map appArgTypes; @@ -298,6 +299,7 @@ public class CarpetScriptHost extends ScriptHost setPerPlayer(config.getOrDefault(new StringValue("scope"), new StringValue("player")).getString().equalsIgnoreCase("player")); persistenceRequired = config.getOrDefault(new StringValue("stay_loaded"), Value.TRUE).getBoolean(); strict = config.getOrDefault(StringValue.of("strict"), Value.FALSE).getBoolean(); + eventPriority = config.getOrDefault(new StringValue("event_priority"), Value.ZERO).readDoubleNumber(); // check requires Value loadRequirements = config.get(new StringValue("requires")); if (loadRequirements instanceof FunctionValue) diff --git a/src/main/java/carpet/script/utils/GlocalFlag.java b/src/main/java/carpet/script/utils/GlocalFlag.java index af9ae8f4..3a7a0c30 100644 --- a/src/main/java/carpet/script/utils/GlocalFlag.java +++ b/src/main/java/carpet/script/utils/GlocalFlag.java @@ -48,21 +48,22 @@ public class GlocalFlag extends ThreadLocal return result; } - public boolean runIfEnabled(Runnable action) + public T runIfEnabled(Supplier action) { synchronized (this) { - if (get() != initial) return false; + if (get() != initial) return null; set(!initial); } + T result; try { - action.run(); + result = action.get(); } finally { set(initial); } - return true; + return result; } } diff --git a/src/main/resources/assets/carpet/scripts/event_test.sc b/src/main/resources/assets/carpet/scripts/event_test.sc index 6d59b1f0..ff47320e 100644 --- a/src/main/resources/assets/carpet/scripts/event_test.sc +++ b/src/main/resources/assets/carpet/scripts/event_test.sc @@ -177,6 +177,20 @@ __on_player_interacts_with_block(player, hand, block, face, hitvec) -> print(' - face: '+face); print(' - hitvec: '+map(hitvec, str('%.2f',_))) ); +__on_player_placing_block(player, item_tuple, hand, block) -> +( + l(item, count, nbt) = item_tuple || l('None', 0, null); + print(''); + print('__on_player_placing_block(player, item_tuple, hand, block)'); + print('player about to place a block:'); + print(' - player: '+player); + print(' - block: '+block+' at '+map(pos(block), str('%.2f',_))); + print(' - hand: '+hand); + print(' - item:'); + print(' > name: '+item); + print(' > count: '+count); + print(' > nbt: '+nbt); +); __on_player_places_block(player, item_tuple, hand, block) -> ( l(item, count, nbt) = item_tuple || l('None', 0, null);