diff --git a/src/main/java/net/coreprotect/listener/ListenerHandler.java b/src/main/java/net/coreprotect/listener/ListenerHandler.java index 52447d49c..cf1a97a8d 100644 --- a/src/main/java/net/coreprotect/listener/ListenerHandler.java +++ b/src/main/java/net/coreprotect/listener/ListenerHandler.java @@ -60,6 +60,7 @@ import net.coreprotect.listener.world.StructureGrowListener; import net.coreprotect.paper.listener.BlockPreDispenseListener; import net.coreprotect.paper.listener.CopperGolemChestListener; +import net.coreprotect.paper.listener.CopperGolemTargetListener; import net.coreprotect.paper.listener.FlowerPotManipulateListener; import net.coreprotect.paper.listener.LegacyTNTPrimeListener; @@ -79,8 +80,17 @@ public ListenerHandler(CoreProtect plugin) { } try { - Class.forName("io.papermc.paper.event.entity.ItemTransportingEntityValidateTargetEvent"); // Paper 1.21.10+ - pluginManager.registerEvents(new CopperGolemChestListener(plugin), plugin); + Class.forName("org.bukkit.entity.CopperGolem"); + CopperGolemChestListener copperGolemListener = new CopperGolemChestListener(plugin); + pluginManager.registerEvents(copperGolemListener, plugin); + + try { + Class.forName("io.papermc.paper.event.entity.ItemTransportingEntityValidateTargetEvent"); + pluginManager.registerEvents(new CopperGolemTargetListener(copperGolemListener), plugin); + } + catch (Exception e) { + // Paper 26.1.x: GenericGameEvent fallback remains active. + } } catch (Exception e) { // Ignore registration failures to remain compatible with older servers. diff --git a/src/main/java/net/coreprotect/paper/listener/CopperGolemChestListener.java b/src/main/java/net/coreprotect/paper/listener/CopperGolemChestListener.java index 791999657..e5326a52e 100644 --- a/src/main/java/net/coreprotect/paper/listener/CopperGolemChestListener.java +++ b/src/main/java/net/coreprotect/paper/listener/CopperGolemChestListener.java @@ -31,6 +31,7 @@ import net.coreprotect.config.ConfigHandler; import net.coreprotect.listener.player.InventoryChangeListener; import net.coreprotect.thread.Scheduler; +import net.coreprotect.utility.HopperTransactionUtils; import net.coreprotect.utility.ItemUtils; public final class CopperGolemChestListener implements Listener { @@ -54,6 +55,80 @@ public CopperGolemChestListener(CoreProtect plugin) { this.plugin = plugin; } + void captureTarget(CopperGolem golem, BlockState blockState) { + if (!(blockState instanceof InventoryHolder)) { + return; + } + + Location containerLocation = blockState.getLocation(); + if (containerLocation.getWorld() == null || !Config.getConfig(containerLocation.getWorld()).ITEM_TRANSACTIONS) { + return; + } + + Material containerType = blockState.getType(); + if (!isCopperChest(containerType) && containerType != Material.CHEST && containerType != Material.TRAPPED_CHEST) { + return; + } + + InventoryHolder inventoryHolder = (InventoryHolder) blockState; + Inventory inventory = inventoryHolder.getInventory(); + Location canonicalLocation = getCanonicalContainerLocation(containerLocation, inventory); + long now = System.currentTimeMillis(); + cleanupOpenInteractions(now); + TransactionKey containerKey = TransactionKey.of(canonicalLocation); + UUID golemId = golem.getUniqueId(); + OpenInteraction existing = openInteractions.get(golemId); + if (existing != null && existing.containerKey.equals(containerKey) && now - existing.openedAtMillis <= OPEN_INTERACTION_TIMEOUT_MILLIS) { + return; + } + if (existing != null) { + finalizeCapturedInteraction(golemId, golem, existing); + } + handleContainerOpen(golem, canonicalLocation, containerKey, containerType, inventoryHolder, now); + OpenInteraction captured = openInteractions.get(golemId); + if (captured != null && captured.containerKey.equals(containerKey)) { + scheduleTargetWatch(golemId, captured, 1); + } + } + + private void scheduleTargetWatch(UUID golemId, OpenInteraction interaction, int attempt) { + Scheduler.scheduleSyncDelayedTask(plugin, () -> watchTarget(golemId, interaction, attempt), interaction.location, 1); + } + + private void watchTarget(UUID golemId, OpenInteraction interaction, int attempt) { + if (openInteractions.get(golemId) != interaction) { + return; + } + + Entity entity = plugin.getServer().getEntity(golemId); + if (entity instanceof CopperGolem && finalizeCapturedInteraction(golemId, (CopperGolem) entity, interaction)) { + return; + } + + if (attempt < 80) { + scheduleTargetWatch(golemId, interaction, attempt + 1); + } + } + + private boolean finalizeCapturedInteraction(UUID golemId, CopperGolem golem, OpenInteraction interaction) { + BlockState blockState = interaction.location.getBlock().getState(); + if (!(blockState instanceof InventoryHolder)) { + return false; + } + + ItemStack[] currentContents = ((InventoryHolder) blockState).getInventory().getContents(); + if (!hasInventoryChanged(interaction.baselineState, currentContents) || !isAttributableToGolem(golem, interaction, currentContents)) { + return false; + } + + if (openInteractions.remove(golemId, interaction)) { + openInteractionIndexByContainerKey.remove(interaction.containerKey, new OpenInteractionIndex(golemId, interaction.openedAtMillis)); + recordForcedContainerState(interaction.location, currentContents); + InventoryChangeListener.inventoryTransaction(USERNAME, interaction.location, interaction.baselineState); + } + return true; + } + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onGenericGameEvent(GenericGameEvent event) { if (event == null) { @@ -522,7 +597,7 @@ private void recordForcedContainerState(Location location, ItemStack[] contents) return; } - String loggingContainerId = USERNAME + "." + location.getBlockX() + "." + location.getBlockY() + "." + location.getBlockZ(); + String loggingContainerId = HopperTransactionUtils.getLoggingId(USERNAME, location); List forceList = ConfigHandler.forceContainer.get(loggingContainerId); List oldList = ConfigHandler.oldContainer.get(loggingContainerId); diff --git a/src/main/java/net/coreprotect/paper/listener/CopperGolemTargetListener.java b/src/main/java/net/coreprotect/paper/listener/CopperGolemTargetListener.java new file mode 100644 index 000000000..b9babb351 --- /dev/null +++ b/src/main/java/net/coreprotect/paper/listener/CopperGolemTargetListener.java @@ -0,0 +1,25 @@ +package net.coreprotect.paper.listener; + +import org.bukkit.entity.CopperGolem; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; + +import io.papermc.paper.event.entity.ItemTransportingEntityValidateTargetEvent; + +/** Paper 26.2+ target-event bridge, kept separate for 26.1.x compatibility. */ +public final class CopperGolemTargetListener implements Listener { + + private final CopperGolemChestListener delegate; + + public CopperGolemTargetListener(CopperGolemChestListener delegate) { + this.delegate = delegate; + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onValidateTarget(ItemTransportingEntityValidateTargetEvent event) { + if (event.isAllowed() && event.getEntity() instanceof CopperGolem) { + delegate.captureTarget((CopperGolem) event.getEntity(), event.getBlock().getState()); + } + } +}