* Fix ArclightCaptures reset before event being handled (IzzelAliz#674) * Fix ArclightCaptures reset before being handled by event stack (IzzelAliz#674) * Fix ArclightCaptures reset before being handled by event stack (IzzelAliz#674) - resetBlockBreakPlayer -> popPrimaryBlockBreakEvent - handleBlockBreak -> handleBlockDrop - isPrimaryEvent: int -> boolean, as it does not work as intended - Add warnings - Add cleaning to avoid memory leak
This commit is contained in:
parent
3623739e84
commit
592b141d13
|
@ -31,6 +31,7 @@ import net.minecraft.world.phys.BlockHitResult;
|
||||||
import net.minecraftforge.common.ForgeHooks;
|
import net.minecraftforge.common.ForgeHooks;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.GameMode;
|
import org.bukkit.GameMode;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
import org.bukkit.craftbukkit.v.block.CraftBlock;
|
import org.bukkit.craftbukkit.v.block.CraftBlock;
|
||||||
import org.bukkit.craftbukkit.v.event.CraftEventFactory;
|
import org.bukkit.craftbukkit.v.event.CraftEventFactory;
|
||||||
import org.bukkit.event.Event;
|
import org.bukkit.event.Event;
|
||||||
|
@ -45,6 +46,7 @@ import org.spongepowered.asm.mixin.Overwrite;
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -210,11 +212,42 @@ public abstract class ServerPlayerGameModeMixin implements PlayerInteractionMana
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Inject(method = "destroyBlock", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/common/ForgeHooks;onBlockBreakEvent(Lnet/minecraft/world/level/Level;Lnet/minecraft/world/level/GameType;Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/core/BlockPos;)I"))
|
||||||
|
public void arclight$beforePrimaryEventFired(BlockPos pos, CallbackInfoReturnable<Boolean> cir) {
|
||||||
|
ArclightCaptures.captureNextBlockBreakEventAsPrimaryEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "destroyBlock", at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraftforge/common/ForgeHooks;onBlockBreakEvent(Lnet/minecraft/world/level/Level;Lnet/minecraft/world/level/GameType;Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/core/BlockPos;)I"))
|
||||||
|
public void arclight$handleSecondaryBlockBreakEvents(BlockPos pos, CallbackInfoReturnable<Boolean> cir) {
|
||||||
|
ArclightCaptures.BlockBreakEventContext breakEventContext = ArclightCaptures.popSecondaryBlockBreakEvent();
|
||||||
|
while (breakEventContext != null) {
|
||||||
|
Block block = breakEventContext.getEvent().getBlock();
|
||||||
|
handleBlockDrop(breakEventContext, new BlockPos(block.getX(), block.getY(), block.getZ()));
|
||||||
|
breakEventContext = ArclightCaptures.popSecondaryBlockBreakEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Inject(method = "destroyBlock", at = @At("RETURN"))
|
@Inject(method = "destroyBlock", at = @At("RETURN"))
|
||||||
public void arclight$resetBlockBreak(BlockPos pos, CallbackInfoReturnable<Boolean> cir) {
|
public void arclight$resetBlockBreak(BlockPos pos, CallbackInfoReturnable<Boolean> cir) {
|
||||||
List<ItemEntity> blockDrops = ArclightCaptures.getBlockDrops();
|
ArclightCaptures.BlockBreakEventContext breakEventContext = ArclightCaptures.popPrimaryBlockBreakEvent();
|
||||||
org.bukkit.block.BlockState state = ArclightCaptures.getBlockBreakPlayerState();
|
|
||||||
BlockBreakEvent breakEvent = ArclightCaptures.resetBlockBreakPlayer();
|
if (breakEventContext != null) {
|
||||||
|
handleBlockDrop(breakEventContext, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = {"tick", "destroyAndAck"}, at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayerGameMode;destroyBlock(Lnet/minecraft/core/BlockPos;)Z"))
|
||||||
|
public void arclight$clearCaptures(CallbackInfo ci) {
|
||||||
|
// clear the event stack in case that interrupted events are left here unhandled
|
||||||
|
// it should be a new event capture session each time destroyBlock is called from these two contexts
|
||||||
|
ArclightCaptures.clearBlockBreakEventContexts();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleBlockDrop(ArclightCaptures.BlockBreakEventContext breakEventContext, BlockPos pos) {
|
||||||
|
BlockBreakEvent breakEvent = breakEventContext.getEvent();
|
||||||
|
List<ItemEntity> blockDrops = breakEventContext.getBlockDrops();
|
||||||
|
org.bukkit.block.BlockState state = breakEventContext.getBlockBreakPlayerState();
|
||||||
|
|
||||||
if (blockDrops != null && (breakEvent == null || breakEvent.isDropItems())) {
|
if (blockDrops != null && (breakEvent == null || breakEvent.isDropItems())) {
|
||||||
CraftBlock craftBlock = CraftBlock.at(this.level, pos);
|
CraftBlock craftBlock = CraftBlock.at(this.level, pos);
|
||||||
CraftEventFactory.handleBlockDropItemEvent(craftBlock, state, this.player, blockDrops);
|
CraftEventFactory.handleBlockDropItemEvent(craftBlock, state, this.player, blockDrops);
|
||||||
|
|
|
@ -94,13 +94,18 @@ public abstract class BlockMixin extends BlockBehaviourMixin implements BlockBri
|
||||||
|
|
||||||
@Inject(method = "playerDestroy", at = @At("RETURN"))
|
@Inject(method = "playerDestroy", at = @At("RETURN"))
|
||||||
private void arclight$handleBlockDrops(Level worldIn, Player player, BlockPos pos, BlockState blockState, BlockEntity te, ItemStack stack, CallbackInfo ci) {
|
private void arclight$handleBlockDrops(Level worldIn, Player player, BlockPos pos, BlockState blockState, BlockEntity te, ItemStack stack, CallbackInfo ci) {
|
||||||
List<ItemEntity> blockDrops = ArclightCaptures.getBlockDrops();
|
ArclightCaptures.BlockBreakEventContext breakEventContext = ArclightCaptures.popPrimaryBlockBreakEvent();
|
||||||
org.bukkit.block.BlockState state = ArclightCaptures.getBlockBreakPlayerState();
|
|
||||||
BlockBreakEvent breakEvent = ArclightCaptures.resetBlockBreakPlayer();
|
if (breakEventContext != null) {
|
||||||
if (player instanceof ServerPlayer && blockDrops != null && (breakEvent == null || breakEvent.isDropItems())
|
BlockBreakEvent breakEvent = breakEventContext.getEvent();
|
||||||
&& DistValidate.isValid(worldIn)) {
|
List<ItemEntity> blockDrops = breakEventContext.getBlockDrops();
|
||||||
CraftBlock craftBlock = CraftBlock.at(((CraftWorld) state.getWorld()).getHandle(), pos);
|
org.bukkit.block.BlockState state = breakEventContext.getBlockBreakPlayerState();
|
||||||
CraftEventFactory.handleBlockDropItemEvent(craftBlock, state, ((ServerPlayer) player), blockDrops);
|
|
||||||
|
if (player instanceof ServerPlayer && blockDrops != null && (breakEvent == null || breakEvent.isDropItems())
|
||||||
|
&& DistValidate.isValid(worldIn)) {
|
||||||
|
CraftBlock craftBlock = CraftBlock.at(((CraftWorld) state.getWorld()).getHandle(), pos);
|
||||||
|
CraftEventFactory.handleBlockDropItemEvent(craftBlock, state, ((ServerPlayer) player), blockDrops);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package io.izzel.arclight.common.mod.util;
|
package io.izzel.arclight.common.mod.util;
|
||||||
|
|
||||||
import io.izzel.arclight.common.mod.ArclightConstants;
|
import io.izzel.arclight.common.mod.ArclightConstants;
|
||||||
|
import io.izzel.arclight.common.mod.ArclightMod;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.core.Direction;
|
import net.minecraft.core.Direction;
|
||||||
import net.minecraft.world.InteractionHand;
|
import net.minecraft.world.InteractionHand;
|
||||||
|
@ -18,6 +19,7 @@ import org.bukkit.event.entity.EntityPotionEffectEvent;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
public class ArclightCaptures {
|
public class ArclightCaptures {
|
||||||
|
|
||||||
|
@ -35,35 +37,72 @@ public class ArclightCaptures {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BlockBreakEvent blockBreakEvent;
|
/**
|
||||||
private static List<ItemEntity> blockDrops;
|
* Indicates that next BlockBreakEvent is fired directly by ServerPlayerGameMode#destroyBlock
|
||||||
private static BlockState blockBreakPlayerState;
|
* and need to be captured as primary event.
|
||||||
|
*
|
||||||
|
* @see net.minecraft.server.level.ServerPlayerGameMode#destroyBlock(BlockPos)
|
||||||
|
*/
|
||||||
|
public static boolean isPrimaryEvent = false;
|
||||||
|
public static Stack<BlockBreakEventContext> blockBreakEventStack = new Stack<>();
|
||||||
|
|
||||||
|
public static void captureNextBlockBreakEventAsPrimaryEvent() {
|
||||||
|
// fix #674, some mod will implement their own "destroyBlock(...)"
|
||||||
|
// and its context cannot be tracked by Arclight directly.
|
||||||
|
// This is used to tell whether the event is fired by vanilla destroyBlock.
|
||||||
|
isPrimaryEvent = true;
|
||||||
|
}
|
||||||
|
|
||||||
public static void captureBlockBreakPlayer(BlockBreakEvent event) {
|
public static void captureBlockBreakPlayer(BlockBreakEvent event) {
|
||||||
blockBreakEvent = event;
|
blockBreakEventStack.push(new BlockBreakEventContext(event, isPrimaryEvent));
|
||||||
blockDrops = new ArrayList<>();
|
isPrimaryEvent = false;
|
||||||
blockBreakPlayerState = event.getBlock().getState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BlockBreakEvent getBlockBreakPlayer() {
|
|
||||||
return blockBreakEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BlockState getBlockBreakPlayerState() {
|
|
||||||
return blockBreakPlayerState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<ItemEntity> getBlockDrops() {
|
public static List<ItemEntity> getBlockDrops() {
|
||||||
return blockDrops;
|
if (!blockBreakEventStack.empty()) {
|
||||||
|
return blockBreakEventStack.peek().getBlockDrops();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BlockBreakEvent resetBlockBreakPlayer() {
|
public static BlockBreakEventContext popPrimaryBlockBreakEvent() {
|
||||||
try {
|
if (blockBreakEventStack.size() > 0) {
|
||||||
return blockBreakEvent;
|
BlockBreakEventContext eventContext = blockBreakEventStack.pop();
|
||||||
} finally {
|
|
||||||
blockBreakEvent = null;
|
// deal with unhandled secondary events
|
||||||
blockDrops = null;
|
// should never happen, but just in case
|
||||||
blockBreakPlayerState = null;
|
ArrayList<BlockBreakEventContext> unhandledEvents = new ArrayList<>();
|
||||||
|
while (!blockBreakEventStack.empty() && !eventContext.isPrimary()) {
|
||||||
|
unhandledEvents.add(eventContext);
|
||||||
|
eventContext = blockBreakEventStack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unhandledEvents.size() > 0) {
|
||||||
|
ArclightMod.LOGGER.warn("Unhandled secondary block break event");
|
||||||
|
eventContext.mergeAllDrops(unhandledEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
return eventContext;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BlockBreakEventContext popSecondaryBlockBreakEvent() {
|
||||||
|
if (blockBreakEventStack.size() > 0) {
|
||||||
|
BlockBreakEventContext eventContext = blockBreakEventStack.peek();
|
||||||
|
if (!eventContext.isPrimary()) {
|
||||||
|
return blockBreakEventStack.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clearBlockBreakEventContexts() {
|
||||||
|
if (!blockBreakEventStack.empty()) {
|
||||||
|
ArclightMod.LOGGER.warn("Unhandled block break event");
|
||||||
|
blockBreakEventStack.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,4 +314,39 @@ public class ArclightCaptures {
|
||||||
throw new IllegalStateException("Recapturing " + type);
|
throw new IllegalStateException("Recapturing " + type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class BlockBreakEventContext {
|
||||||
|
final private BlockBreakEvent blockBreakEvent;
|
||||||
|
final private ArrayList<ItemEntity> blockDrops;
|
||||||
|
final private BlockState blockBreakPlayerState;
|
||||||
|
final private boolean primary;
|
||||||
|
|
||||||
|
public BlockBreakEventContext(BlockBreakEvent event, boolean primary) {
|
||||||
|
this.blockBreakEvent = event;
|
||||||
|
this.blockDrops = new ArrayList<>();
|
||||||
|
this.blockBreakPlayerState = event.getBlock().getState();
|
||||||
|
this.primary = primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockBreakEvent getEvent() {
|
||||||
|
return blockBreakEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<ItemEntity> getBlockDrops() {
|
||||||
|
return blockDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockState getBlockBreakPlayerState() {
|
||||||
|
return blockBreakPlayerState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mergeAllDrops(List<BlockBreakEventContext> others) {
|
||||||
|
for (BlockBreakEventContext other : others) {
|
||||||
|
this.getBlockDrops().addAll(other.getBlockDrops());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPrimary() {
|
||||||
|
return primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user