* 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 org.bukkit.Bukkit;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.craftbukkit.v.block.CraftBlock;
|
||||
import org.bukkit.craftbukkit.v.event.CraftEventFactory;
|
||||
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.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
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"))
|
||||
public void arclight$resetBlockBreak(BlockPos pos, CallbackInfoReturnable<Boolean> cir) {
|
||||
List<ItemEntity> blockDrops = ArclightCaptures.getBlockDrops();
|
||||
org.bukkit.block.BlockState state = ArclightCaptures.getBlockBreakPlayerState();
|
||||
BlockBreakEvent breakEvent = ArclightCaptures.resetBlockBreakPlayer();
|
||||
ArclightCaptures.BlockBreakEventContext breakEventContext = ArclightCaptures.popPrimaryBlockBreakEvent();
|
||||
|
||||
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())) {
|
||||
CraftBlock craftBlock = CraftBlock.at(this.level, pos);
|
||||
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"))
|
||||
private void arclight$handleBlockDrops(Level worldIn, Player player, BlockPos pos, BlockState blockState, BlockEntity te, ItemStack stack, CallbackInfo ci) {
|
||||
List<ItemEntity> blockDrops = ArclightCaptures.getBlockDrops();
|
||||
org.bukkit.block.BlockState state = ArclightCaptures.getBlockBreakPlayerState();
|
||||
BlockBreakEvent breakEvent = ArclightCaptures.resetBlockBreakPlayer();
|
||||
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);
|
||||
ArclightCaptures.BlockBreakEventContext breakEventContext = ArclightCaptures.popPrimaryBlockBreakEvent();
|
||||
|
||||
if (breakEventContext != null) {
|
||||
BlockBreakEvent breakEvent = breakEventContext.getEvent();
|
||||
List<ItemEntity> blockDrops = breakEventContext.getBlockDrops();
|
||||
org.bukkit.block.BlockState state = breakEventContext.getBlockBreakPlayerState();
|
||||
|
||||
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;
|
||||
|
||||
import io.izzel.arclight.common.mod.ArclightConstants;
|
||||
import io.izzel.arclight.common.mod.ArclightMod;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
|
@ -18,6 +19,7 @@ import org.bukkit.event.entity.EntityPotionEffectEvent;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
public class ArclightCaptures {
|
||||
|
||||
|
@ -35,35 +37,72 @@ public class ArclightCaptures {
|
|||
}
|
||||
}
|
||||
|
||||
private static BlockBreakEvent blockBreakEvent;
|
||||
private static List<ItemEntity> blockDrops;
|
||||
private static BlockState blockBreakPlayerState;
|
||||
/**
|
||||
* Indicates that next BlockBreakEvent is fired directly by ServerPlayerGameMode#destroyBlock
|
||||
* 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) {
|
||||
blockBreakEvent = event;
|
||||
blockDrops = new ArrayList<>();
|
||||
blockBreakPlayerState = event.getBlock().getState();
|
||||
}
|
||||
|
||||
public static BlockBreakEvent getBlockBreakPlayer() {
|
||||
return blockBreakEvent;
|
||||
}
|
||||
|
||||
public static BlockState getBlockBreakPlayerState() {
|
||||
return blockBreakPlayerState;
|
||||
blockBreakEventStack.push(new BlockBreakEventContext(event, isPrimaryEvent));
|
||||
isPrimaryEvent = false;
|
||||
}
|
||||
|
||||
public static List<ItemEntity> getBlockDrops() {
|
||||
return blockDrops;
|
||||
if (!blockBreakEventStack.empty()) {
|
||||
return blockBreakEventStack.peek().getBlockDrops();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static BlockBreakEvent resetBlockBreakPlayer() {
|
||||
try {
|
||||
return blockBreakEvent;
|
||||
} finally {
|
||||
blockBreakEvent = null;
|
||||
blockDrops = null;
|
||||
blockBreakPlayerState = null;
|
||||
public static BlockBreakEventContext popPrimaryBlockBreakEvent() {
|
||||
if (blockBreakEventStack.size() > 0) {
|
||||
BlockBreakEventContext eventContext = blockBreakEventStack.pop();
|
||||
|
||||
// deal with unhandled secondary events
|
||||
// should never happen, but just in case
|
||||
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);
|
||||
}
|
||||
|
||||
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