diff --git a/src/main/java/xyz/nucleoid/stimuli/StimuliInitializer.java b/src/main/java/xyz/nucleoid/stimuli/StimuliInitializer.java index 0cedd84..821e02c 100644 --- a/src/main/java/xyz/nucleoid/stimuli/StimuliInitializer.java +++ b/src/main/java/xyz/nucleoid/stimuli/StimuliInitializer.java @@ -48,7 +48,7 @@ public void onInitialize() { UseBlockCallback.EVENT.register((player, world, hand, hitResult) -> { if (player instanceof ServerPlayerEntity serverPlayer) { try (var invokers = Stimuli.select().forEntityAt(player, hitResult.getBlockPos())) { - return invokers.get(BlockUseEvent.EVENT).onUse(serverPlayer, hand, hitResult); + return invokers.get(BlockUseEvent.INTERACT).onBlockInteraction(serverPlayer, hand, hitResult); } } return ActionResult.PASS; diff --git a/src/main/java/xyz/nucleoid/stimuli/event/block/BlockUseEvent.java b/src/main/java/xyz/nucleoid/stimuli/event/block/BlockUseEvent.java index cd3554a..190e4cf 100644 --- a/src/main/java/xyz/nucleoid/stimuli/event/block/BlockUseEvent.java +++ b/src/main/java/xyz/nucleoid/stimuli/event/block/BlockUseEvent.java @@ -1,27 +1,35 @@ package xyz.nucleoid.stimuli.event.block; +import net.minecraft.block.BlockState; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemUsageContext; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.util.ActionResult; import net.minecraft.util.Hand; import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; import xyz.nucleoid.stimuli.event.StimulusEvent; -/** - * Called when a {@link ServerPlayerEntity} attempts to use a block by interacting. - * - *

Upon return: - *

- *

- * If all listeners return {@link ActionResult#PASS}, the use succeeds and proceeds with normal logic. - */ -public interface BlockUseEvent { - StimulusEvent EVENT = StimulusEvent.create(BlockUseEvent.class, ctx -> (player, hand, hitResult) -> { +public final class BlockUseEvent { + /** + * Called when a {@link ServerPlayerEntity} attempts to interact with a block. + * + *

This is before the game tries to use the block, or tries to use an item on the block. + * + *

Upon return: + *

+ *

+ * If all listeners return {@link ActionResult#PASS}, the use succeeds and proceeds with normal logic. + */ + public static final StimulusEvent INTERACT = StimulusEvent.create(Interact.class, ctx -> (player, hand, hitResult) -> { try { for (var listener : ctx.getListeners()) { - var result = listener.onUse(player, hand, hitResult); + var result = listener.onBlockInteraction(player, hand, hitResult); if (result != ActionResult.PASS) { return result; } @@ -32,5 +40,71 @@ public interface BlockUseEvent { return ActionResult.PASS; }); - ActionResult onUse(ServerPlayerEntity player, Hand hand, BlockHitResult hitResult); + /** + * Called when a {@link ServerPlayerEntity} attempts to use a block. + * + *

Upon return: + *

+ *

+ * If all listeners return {@link ActionResult#PASS}, the use succeeds and proceeds with normal logic. + */ + public static final StimulusEvent USE = StimulusEvent.create(Use.class, ctx -> (state, world, pos, player, hand, hit) -> { + try { + for (var listener : ctx.getListeners()) { + var result = listener.onBlockUse(state, world, pos, player, hand, hit); + if (result != ActionResult.PASS) { + return result; + } + } + } catch (Throwable t) { + ctx.handleException(t); + } + return ActionResult.PASS; + }); + + /** + * Called when a {@link ServerPlayerEntity} attempts to use an item on a block. + * + *

Upon return: + *

+ *

+ * If all listeners return {@link ActionResult#PASS}, the use succeeds and proceeds with normal logic. + */ + public static final StimulusEvent USE_ITEM = StimulusEvent.create(UseItem.class, ctx -> (stack, context) -> { + try { + for (var listener : ctx.getListeners()) { + var result = listener.onItemUseOnBlock(stack, context); + if (result != ActionResult.PASS) { + return result; + } + } + } catch (Throwable t) { + ctx.handleException(t); + } + return ActionResult.PASS; + }); + + public interface Interact { + ActionResult onBlockInteraction(ServerPlayerEntity player, Hand hand, BlockHitResult hitResult); + } + + public interface Use { + ActionResult onBlockUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit); + } + + public interface UseItem { + ActionResult onItemUseOnBlock(ItemStack stack, ItemUsageContext context); + } + + /** + * @deprecated Use {@link #INTERACT} instead. + */ + @Deprecated + public static final StimulusEvent EVENT = INTERACT; } diff --git a/src/main/java/xyz/nucleoid/stimuli/mixin/block/AbstractBlockStateMixin.java b/src/main/java/xyz/nucleoid/stimuli/mixin/block/AbstractBlockStateMixin.java new file mode 100644 index 0000000..fd4832d --- /dev/null +++ b/src/main/java/xyz/nucleoid/stimuli/mixin/block/AbstractBlockStateMixin.java @@ -0,0 +1,39 @@ +package xyz.nucleoid.stimuli.mixin.block; + +import net.minecraft.block.AbstractBlock; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.world.World; +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 xyz.nucleoid.stimuli.Stimuli; +import xyz.nucleoid.stimuli.event.block.BlockUseEvent; + +@Mixin(AbstractBlock.AbstractBlockState.class) +public class AbstractBlockStateMixin { + @Inject(method = "onUse", at = @At("HEAD"), cancellable = true) + private void onUse(World world, PlayerEntity player, Hand hand, BlockHitResult hit, CallbackInfoReturnable cir) { + if (!world.isClient()) { + var events = Stimuli.select(); + try (var invokers = events.forEntityAt(player, hit.getBlockPos())) { + var state = world.getBlockState(hit.getBlockPos()); + var result = invokers.get(BlockUseEvent.USE).onBlockUse(state, world, hit.getBlockPos(), player, hand, hit); + + if (result == ActionResult.FAIL) { + // notify the client that this action did not go through + int slot = hand == Hand.MAIN_HAND ? player.getInventory().selectedSlot : 40; + var stack = player.getStackInHand(hand); + ((ServerPlayerEntity) player).networkHandler.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(ScreenHandlerSlotUpdateS2CPacket.UPDATE_PLAYER_INVENTORY_SYNC_ID, 0, slot, stack)); + + cir.setReturnValue(ActionResult.FAIL); + } + } + } + } +} diff --git a/src/main/java/xyz/nucleoid/stimuli/mixin/block/ItemStackMixin.java b/src/main/java/xyz/nucleoid/stimuli/mixin/block/ItemStackMixin.java new file mode 100644 index 0000000..f48888f --- /dev/null +++ b/src/main/java/xyz/nucleoid/stimuli/mixin/block/ItemStackMixin.java @@ -0,0 +1,39 @@ +package xyz.nucleoid.stimuli.mixin.block; + +import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemUsageContext; +import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +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 xyz.nucleoid.stimuli.Stimuli; +import xyz.nucleoid.stimuli.event.block.BlockUseEvent; + +@Mixin(ItemStack.class) +public class ItemStackMixin { + @Inject(method = "useOnBlock", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;useOnBlock(Lnet/minecraft/item/ItemUsageContext;)Lnet/minecraft/util/ActionResult;"), cancellable = true) + private void useOnBlock(ItemUsageContext context, CallbackInfoReturnable cir) { + var world = context.getWorld(); + if (!context.getWorld().isClient()) { + var events = Stimuli.select(); + var player = context.getPlayer(); + var pos = context.getBlockPos(); + try (var invokers = player == null ? events.at(world, pos) : events.forEntityAt(player, pos)) { + var result = invokers.get(BlockUseEvent.USE_ITEM).onItemUseOnBlock((ItemStack) (Object) this, context); + + if (result == ActionResult.FAIL) { + // notify the client that this action did not go through + int slot = context.getHand() == Hand.MAIN_HAND ? player.getInventory().selectedSlot : 40; + var stack = context.getStack(); + ((ServerPlayerEntity) player).networkHandler.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(ScreenHandlerSlotUpdateS2CPacket.UPDATE_PLAYER_INVENTORY_SYNC_ID, 0, slot, stack)); + + cir.setReturnValue(ActionResult.FAIL); + } + } + } + } +}