mirror of
https://github.com/PlayPro/CoreProtect.git
synced 2026-10-28 09:44:56 +00:00
Additional hopper transaction logging hardening
This commit is contained in:
1 parent
3d1594e116
commit
197e5b34a4
13 files changed
+518
-281
No files matched your search
@@ -181,6 +181,7 @@ CLAUDE.md
|
||||
.clinerules
|
||||
context.json
|
||||
context.yml
|
||||
issues.md
|
||||
run.sh
|
||||
run_*.sh
|
||||
no-log4j2.xml
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.bukkit.entity.ArmorStand;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.ItemFrame;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import net.coreprotect.CoreProtect;
|
||||
@@ -24,6 +25,7 @@ import net.coreprotect.consumer.process.Process;
|
||||
import net.coreprotect.database.rollback.Rollback;
|
||||
import net.coreprotect.database.rollback.RollbackComplete;
|
||||
import net.coreprotect.language.Phrase;
|
||||
import net.coreprotect.listener.player.InventoryChangeListener;
|
||||
import net.coreprotect.model.BlockGroup;
|
||||
import net.coreprotect.thread.Scheduler;
|
||||
import net.coreprotect.utility.BlockUtils;
|
||||
@@ -91,6 +93,9 @@ public class ContainerRollback extends Rollback {
|
||||
|
||||
int modifyCount = 0;
|
||||
if (container != null) {
|
||||
if (container instanceof Inventory) {
|
||||
InventoryChangeListener.flushPendingContainer((Inventory) container, location);
|
||||
}
|
||||
for (Object[] lookupRow : lookupList) {
|
||||
// int unixtimestamp = (int) (System.currentTimeMillis() / 1000L);
|
||||
// int rowId = lookupRow[0];
|
||||
|
||||
@@ -24,11 +24,11 @@ import net.coreprotect.consumer.Consumer;
|
||||
import net.coreprotect.consumer.Queue;
|
||||
import net.coreprotect.consumer.process.Process;
|
||||
import net.coreprotect.language.Phrase;
|
||||
import net.coreprotect.listener.player.InventoryChangeListener;
|
||||
import net.coreprotect.model.BlockGroup;
|
||||
import net.coreprotect.model.rollback.RollbackUpdateTargets;
|
||||
import net.coreprotect.utility.Chat;
|
||||
import net.coreprotect.utility.Color;
|
||||
import net.coreprotect.utility.HopperTransactionUtils;
|
||||
import net.coreprotect.utility.ItemUtils;
|
||||
import net.coreprotect.utility.ErrorReporter;
|
||||
|
||||
@@ -189,15 +189,19 @@ public class Database extends Queue {
|
||||
}
|
||||
|
||||
public static void containerBreakCheck(String user, Material type, Object container, ItemStack[] contents, Location location) {
|
||||
if (BlockGroup.CONTAINERS.contains(type) && !BlockGroup.SHULKER_BOXES.contains(type)) {
|
||||
if (BlockGroup.CONTAINERS.contains(type)) {
|
||||
if (Config.getConfig(location.getWorld()).ITEM_TRANSACTIONS) {
|
||||
try {
|
||||
if (contents == null) {
|
||||
contents = ItemUtils.getContainerContents(type, container, location);
|
||||
}
|
||||
if (contents != null) {
|
||||
setForceContainer(HopperTransactionUtils.getLoggingId(user, location), ItemUtils.getContainerState(contents));
|
||||
Queue.queueContainerBreak(user, location, type, contents);
|
||||
if (BlockGroup.SHULKER_BOXES.contains(type)) {
|
||||
InventoryChangeListener.flushPendingContainer(location, contents);
|
||||
}
|
||||
else {
|
||||
InventoryChangeListener.queueContainerBreak(user, location, type, contents);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
|
||||
@@ -41,6 +41,7 @@ import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import net.coreprotect.bukkit.BukkitAdapter;
|
||||
import net.coreprotect.consumer.Queue;
|
||||
import net.coreprotect.listener.player.InventoryChangeListener;
|
||||
import net.coreprotect.model.BlockGroup;
|
||||
import net.coreprotect.model.PendingBlockChange;
|
||||
import net.coreprotect.paper.PaperAdapter;
|
||||
@@ -168,6 +169,7 @@ public class RollbackBlockHandler extends Queue {
|
||||
if (BlockGroup.CONTAINERS.contains(changeType)) {
|
||||
Inventory inventory = BlockUtils.getContainerInventory(block.getState(), false);
|
||||
if (inventory != null) {
|
||||
InventoryChangeListener.flushPendingContainer(inventory, block.getLocation());
|
||||
inventory.clear();
|
||||
}
|
||||
}
|
||||
@@ -284,6 +286,7 @@ public class RollbackBlockHandler extends Queue {
|
||||
}
|
||||
if (meta != null) {
|
||||
Inventory inventory = BlockUtils.getContainerInventory(block.getState(), false);
|
||||
InventoryChangeListener.flushPendingContainer(inventory, block.getLocation());
|
||||
for (Object value : meta) {
|
||||
ItemStack item = ItemUtils.unserializeItemStackLegacy(value);
|
||||
if (item != null) {
|
||||
|
||||
@@ -24,12 +24,14 @@ import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.ItemFrame;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import net.coreprotect.CoreProtect;
|
||||
import net.coreprotect.bukkit.BukkitAdapter;
|
||||
import net.coreprotect.config.Config;
|
||||
import net.coreprotect.config.ConfigHandler;
|
||||
import net.coreprotect.listener.player.InventoryChangeListener;
|
||||
import net.coreprotect.model.BlockGroup;
|
||||
import net.coreprotect.model.PendingBlockChange;
|
||||
import net.coreprotect.model.item.ItemTransactionActions;
|
||||
@@ -313,6 +315,9 @@ public class RollbackProcessor {
|
||||
}
|
||||
else {
|
||||
container = BlockUtils.getContainerInventory(blockState, false);
|
||||
if (container instanceof Inventory) {
|
||||
InventoryChangeListener.flushPendingContainer((Inventory) container, block.getLocation());
|
||||
}
|
||||
}
|
||||
|
||||
containerType = block.getType();
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package net.coreprotect.listener.player;
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.bukkit.Location;
|
||||
|
||||
import net.coreprotect.CoreProtect;
|
||||
import net.coreprotect.thread.Scheduler;
|
||||
import net.coreprotect.utility.ErrorReporter;
|
||||
import net.coreprotect.utility.HopperTransactionUtils;
|
||||
|
||||
final class ContainerTransactionDispatcher {
|
||||
|
||||
private static final int STRIPE_COUNT = 4;
|
||||
private static final Stripe[] STRIPES = new Stripe[STRIPE_COUNT];
|
||||
|
||||
static {
|
||||
for (int i = 0; i < STRIPE_COUNT; i++) {
|
||||
STRIPES[i] = new Stripe();
|
||||
}
|
||||
}
|
||||
|
||||
private ContainerTransactionDispatcher() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
|
||||
static void submit(Location location, Runnable task) {
|
||||
if (location == null || location.getWorld() == null || task == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String transactionId = HopperTransactionUtils.getTransactionId(location);
|
||||
Stripe stripe = STRIPES[(transactionId.hashCode() & Integer.MAX_VALUE) % STRIPE_COUNT];
|
||||
stripe.submit(task);
|
||||
}
|
||||
|
||||
static int pendingTasks() {
|
||||
int pending = 0;
|
||||
for (Stripe stripe : STRIPES) {
|
||||
pending += stripe.queue.size();
|
||||
}
|
||||
return pending;
|
||||
}
|
||||
|
||||
static void drainForShutdown() {
|
||||
for (Stripe stripe : STRIPES) {
|
||||
stripe.drain();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Stripe {
|
||||
private final ConcurrentLinkedQueue<Runnable> queue = new ConcurrentLinkedQueue<>();
|
||||
private final AtomicBoolean running = new AtomicBoolean(false);
|
||||
|
||||
private void submit(Runnable task) {
|
||||
queue.add(task);
|
||||
start();
|
||||
}
|
||||
|
||||
private void start() {
|
||||
if (!running.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Scheduler.runTaskAsynchronously(CoreProtect.getInstance(), this::drain);
|
||||
}
|
||||
|
||||
private void drain() {
|
||||
synchronized (this) {
|
||||
try {
|
||||
Runnable task;
|
||||
while ((task = queue.poll()) != null) {
|
||||
try {
|
||||
task.run();
|
||||
}
|
||||
catch (Exception e) {
|
||||
ErrorReporter.report(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
running.set(false);
|
||||
if (!queue.isEmpty()) {
|
||||
start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package net.coreprotect.listener.player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
final class HopperPullAccumulator {
|
||||
|
||||
private final List<MoveRun> runs = new ArrayList<>();
|
||||
private long moveCount;
|
||||
|
||||
void add(String user, ItemStack item) {
|
||||
if (user == null || user.isEmpty() || item == null || item.getAmount() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int amount = item.getAmount();
|
||||
MoveRun tail = runs.isEmpty() ? null : runs.get(runs.size() - 1);
|
||||
if (tail != null && tail.user.equals(user) && tail.amount == amount && tail.item.isSimilar(item)) {
|
||||
tail.repetitions++;
|
||||
}
|
||||
else {
|
||||
ItemStack prototype = item.clone();
|
||||
prototype.setAmount(amount);
|
||||
runs.add(new MoveRun(user, prototype, amount));
|
||||
}
|
||||
moveCount++;
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return runs.isEmpty();
|
||||
}
|
||||
|
||||
int runCount() {
|
||||
return runs.size();
|
||||
}
|
||||
|
||||
long moveCount() {
|
||||
return moveCount;
|
||||
}
|
||||
|
||||
void replay(ItemStack[] checkpoint, int inventoryMaxStackSize, MoveConsumer consumer) {
|
||||
if (checkpoint == null || consumer == null || runs.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack[] state = checkpoint.clone();
|
||||
for (int i = runs.size() - 1; i >= 0; i--) {
|
||||
MoveRun run = runs.get(i);
|
||||
state = addToState(state, run.item, run.totalAmount(), inventoryMaxStackSize);
|
||||
}
|
||||
|
||||
for (MoveRun run : runs) {
|
||||
for (long repetition = 0; repetition < run.repetitions; repetition++) {
|
||||
ItemStack movedItem = run.item.clone();
|
||||
movedItem.setAmount(run.amount);
|
||||
consumer.accept(run.user, state, movedItem);
|
||||
state = removeFromState(state, movedItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ItemStack[] addToState(ItemStack[] state, ItemStack item, long amount, int inventoryMaxStackSize) {
|
||||
ItemStack[] result = state.clone();
|
||||
long remaining = amount;
|
||||
int maxStackSize = maxStackSize(item, inventoryMaxStackSize);
|
||||
|
||||
for (int i = 0; i < result.length && remaining > 0; i++) {
|
||||
ItemStack existing = result[i];
|
||||
if (existing == null || existing.getAmount() <= 0 || !existing.isSimilar(item) || existing.getAmount() >= maxStackSize) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int added = (int) Math.min(remaining, maxStackSize - existing.getAmount());
|
||||
ItemStack updated = existing.clone();
|
||||
updated.setAmount(existing.getAmount() + added);
|
||||
result[i] = updated;
|
||||
remaining -= added;
|
||||
}
|
||||
|
||||
for (int i = 0; i < result.length && remaining > 0; i++) {
|
||||
ItemStack existing = result[i];
|
||||
if (existing != null && existing.getAmount() > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int added = (int) Math.min(remaining, maxStackSize);
|
||||
ItemStack inserted = item.clone();
|
||||
inserted.setAmount(added);
|
||||
result[i] = inserted;
|
||||
remaining -= added;
|
||||
}
|
||||
|
||||
if (remaining <= 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
long additionalSlots = (remaining + maxStackSize - 1L) / maxStackSize;
|
||||
if (additionalSlots > Integer.MAX_VALUE - result.length) {
|
||||
throw new IllegalStateException("Hopper pull reconstruction exceeds maximum inventory size");
|
||||
}
|
||||
|
||||
ItemStack[] expanded = new ItemStack[result.length + (int) additionalSlots];
|
||||
System.arraycopy(result, 0, expanded, 0, result.length);
|
||||
int slot = result.length;
|
||||
while (remaining > 0) {
|
||||
int added = (int) Math.min(remaining, maxStackSize);
|
||||
ItemStack inserted = item.clone();
|
||||
inserted.setAmount(added);
|
||||
expanded[slot] = inserted;
|
||||
slot++;
|
||||
remaining -= added;
|
||||
}
|
||||
return expanded;
|
||||
}
|
||||
|
||||
private static ItemStack[] removeFromState(ItemStack[] state, ItemStack item) {
|
||||
ItemStack[] result = state.clone();
|
||||
int remaining = item.getAmount();
|
||||
for (int i = 0; i < result.length && remaining > 0; i++) {
|
||||
ItemStack existing = result[i];
|
||||
if (existing == null || existing.getAmount() <= 0 || !existing.isSimilar(item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int removed = Math.min(remaining, existing.getAmount());
|
||||
if (removed == existing.getAmount()) {
|
||||
result[i] = null;
|
||||
}
|
||||
else {
|
||||
ItemStack updated = existing.clone();
|
||||
updated.setAmount(existing.getAmount() - removed);
|
||||
result[i] = updated;
|
||||
}
|
||||
remaining -= removed;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int maxStackSize(ItemStack item, int inventoryMaxStackSize) {
|
||||
int maxStackSize = item.getMaxStackSize();
|
||||
if (inventoryMaxStackSize > 0 && (inventoryMaxStackSize < maxStackSize || maxStackSize < 1)) {
|
||||
maxStackSize = inventoryMaxStackSize;
|
||||
}
|
||||
return Math.max(1, maxStackSize);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface MoveConsumer {
|
||||
void accept(String user, ItemStack[] sourceBefore, ItemStack movedItem);
|
||||
}
|
||||
|
||||
private static final class MoveRun {
|
||||
private final String user;
|
||||
private final ItemStack item;
|
||||
private final int amount;
|
||||
private long repetitions = 1;
|
||||
|
||||
private MoveRun(String user, ItemStack item, int amount) {
|
||||
this.user = user;
|
||||
this.item = item;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
private long totalAmount() {
|
||||
try {
|
||||
return Math.multiplyExact((long) amount, repetitions);
|
||||
}
|
||||
catch (ArithmeticException e) {
|
||||
throw new IllegalStateException("Hopper pull amount overflow", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package net.coreprotect.listener.player;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
@@ -21,139 +21,157 @@ import net.coreprotect.utility.ErrorReporter;
|
||||
|
||||
public final class HopperPullListener {
|
||||
|
||||
private static final ConcurrentLinkedQueue<Object[]> hopperQueue = new ConcurrentLinkedQueue<>();
|
||||
private static final AtomicBoolean processorRunning = new AtomicBoolean(false);
|
||||
private static final int BATCH_SIZE = 100;
|
||||
private static final int MAX_CONCURRENT_PROCESSORS = 4;
|
||||
private static final AtomicInteger activeProcessors = new AtomicInteger(0);
|
||||
private static final Map<String, PendingPull> pendingPulls = new ConcurrentHashMap<>();
|
||||
|
||||
private HopperPullListener() {
|
||||
throw new IllegalStateException("Listener class");
|
||||
}
|
||||
|
||||
static void processHopperPull(Location location, String user, InventoryHolder sourceHolder, InventoryHolder destinationHolder, ItemStack item) {
|
||||
if (location == null || location.getWorld() == null || sourceHolder == null || destinationHolder == null || item == null || item.getAmount() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Inventory sourceInventory = sourceHolder.getInventory();
|
||||
Inventory destinationInventory = destinationHolder.getInventory();
|
||||
String loggingChestId = HopperTransactionUtils.getHopperPullId(location);
|
||||
Object[] lastAbort = ConfigHandler.hopperAbort.get(loggingChestId);
|
||||
if (lastAbort != null) {
|
||||
ItemStack[] destinationContents = destinationHolder.getInventory().getContents();
|
||||
if (((Set<?>) lastAbort[0]).contains(item) && Arrays.equals(destinationContents, (ItemStack[]) lastAbort[1])) {
|
||||
return;
|
||||
}
|
||||
ItemStack[] destinationContents = destinationInventory.getContents();
|
||||
if (lastAbort != null && ((Set<?>) lastAbort[0]).contains(item) && Arrays.equals(destinationContents, (ItemStack[]) lastAbort[1])) {
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack[] destinationContainer = ItemUtils.getContainerState(destinationHolder.getInventory().getContents());
|
||||
ItemStack[] destinationContainer = ItemUtils.getContainerState(destinationContents);
|
||||
ItemStack movedItem = item.clone();
|
||||
|
||||
// Queue the hopper pull operation instead of creating a new thread immediately
|
||||
hopperQueue.add(new Object[] { location, user, sourceHolder, destinationHolder, movedItem, destinationContainer, loggingChestId, lastAbort });
|
||||
|
||||
// Start the processor if it's not already running
|
||||
if (processorRunning.compareAndSet(false, true)) {
|
||||
startHopperProcessor();
|
||||
}
|
||||
}
|
||||
|
||||
private static void startHopperProcessor() {
|
||||
if (activeProcessors.incrementAndGet() <= MAX_CONCURRENT_PROCESSORS) {
|
||||
Scheduler.runTaskAsynchronously(CoreProtect.getInstance(), () -> {
|
||||
try {
|
||||
// Use the same server running check as Consumer class
|
||||
while (!hopperQueue.isEmpty() && (ConfigHandler.serverRunning || ConfigHandler.converterRunning)) {
|
||||
processHopperBatch();
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
ErrorReporter.report(e);
|
||||
}
|
||||
finally {
|
||||
activeProcessors.decrementAndGet();
|
||||
processorRunning.set(false);
|
||||
|
||||
// If more items were added and we're still running, restart the processor
|
||||
if (!hopperQueue.isEmpty() && (ConfigHandler.serverRunning || ConfigHandler.converterRunning) && activeProcessors.get() == 0) {
|
||||
startHopperProcessor();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
activeProcessors.decrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
private static void processHopperBatch() {
|
||||
int processed = 0;
|
||||
final long taskStarted = InventoryChangeListener.tasksStarted.incrementAndGet();
|
||||
|
||||
while (!hopperQueue.isEmpty() && processed < BATCH_SIZE && (ConfigHandler.serverRunning || ConfigHandler.converterRunning)) {
|
||||
Object[] data = hopperQueue.poll();
|
||||
if (data == null)
|
||||
continue;
|
||||
|
||||
Location location = (Location) data[0];
|
||||
String user = (String) data[1];
|
||||
InventoryHolder sourceHolder = (InventoryHolder) data[2];
|
||||
InventoryHolder destinationHolder = (InventoryHolder) data[3];
|
||||
ItemStack movedItem = (ItemStack) data[4];
|
||||
ItemStack[] destinationContainer = (ItemStack[]) data[5];
|
||||
String loggingChestId = (String) data[6];
|
||||
Object[] lastAbort = (Object[]) data[7];
|
||||
|
||||
processSingleHopperPull(location, user, sourceHolder, destinationHolder, movedItem, destinationContainer, loggingChestId, lastAbort);
|
||||
|
||||
processed++;
|
||||
}
|
||||
|
||||
// Ensure sequential processing just like the original code
|
||||
InventoryChangeListener.checkTasks(taskStarted);
|
||||
}
|
||||
|
||||
private static void processSingleHopperPull(Location location, String user, InventoryHolder sourceHolder, InventoryHolder destinationHolder, ItemStack movedItem, ItemStack[] destinationContainer, String loggingChestId, Object[] lastAbort) {
|
||||
|
||||
if (sourceHolder == null || destinationHolder == null) {
|
||||
if (!ItemUtils.canAddContainer(destinationContainer, movedItem, destinationInventory.getMaxStackSize())) {
|
||||
ConfigHandler.hopperAbort.put(loggingChestId, HopperTransactionUtils.createAbortState(lastAbort, destinationContainer, movedItem));
|
||||
return;
|
||||
}
|
||||
|
||||
boolean abort = false;
|
||||
boolean addedInventory = ItemUtils.canAddContainer(destinationContainer, movedItem, destinationHolder.getInventory().getMaxStackSize());
|
||||
if (!addedInventory) {
|
||||
abort = true;
|
||||
}
|
||||
|
||||
if (abort) {
|
||||
ItemStack[] destinationContents = destinationHolder.getInventory().getContents();
|
||||
ConfigHandler.hopperAbort.put(loggingChestId, HopperTransactionUtils.createAbortState(lastAbort, destinationContents, movedItem));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ConfigHandler.hopperAbort.get(loggingChestId) != null) {
|
||||
ConfigHandler.hopperAbort.remove(loggingChestId);
|
||||
}
|
||||
|
||||
ConfigHandler.hopperAbort.remove(loggingChestId);
|
||||
boolean hopperTransactions = Config.getConfig(location.getWorld()).HOPPER_TRANSACTIONS;
|
||||
if (!hopperTransactions) {
|
||||
HopperTransactionUtils.recordItemRemoved(HopperTransactionUtils.getTransactionId(location), movedItem);
|
||||
return;
|
||||
}
|
||||
|
||||
Location destinationLocation = destinationHolder.getInventory().getLocation();
|
||||
Location destinationLocation = destinationInventory.getLocation();
|
||||
if (destinationLocation == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
HopperTransactionUtils.recordItemAdded(HopperTransactionUtils.getTransactionId(destinationLocation), movedItem);
|
||||
|
||||
if (!Config.getConfig(location.getWorld()).ITEM_TRANSACTIONS) {
|
||||
return;
|
||||
}
|
||||
if (Config.getConfig(location.getWorld()).HOPPER_FILTER_META && !movedItem.hasItemMeta()) {
|
||||
HopperTransactionUtils.recordItemRemoved(HopperTransactionUtils.getTransactionId(location), movedItem);
|
||||
return;
|
||||
}
|
||||
|
||||
Inventory sourceInventory = sourceHolder.getInventory();
|
||||
ItemStack[] inventoryContents = sourceInventory.getContents();
|
||||
ItemStack[] originalSource = new ItemStack[inventoryContents.length + 1];
|
||||
for (int i = 0; i < inventoryContents.length; i++) {
|
||||
ItemStack itemStack = inventoryContents[i];
|
||||
if (itemStack != null) {
|
||||
originalSource[i] = itemStack.clone();
|
||||
String transactionId = HopperTransactionUtils.getTransactionId(location);
|
||||
PendingPull pending = pendingPulls.compute(transactionId, (key, current) -> {
|
||||
PendingPull value = current;
|
||||
if (value == null) {
|
||||
value = new PendingPull(sourceInventory, location.clone(), sourceInventory.getMaxStackSize());
|
||||
}
|
||||
value.accumulator.add(user, movedItem);
|
||||
return value;
|
||||
});
|
||||
|
||||
if (pending.scheduled.compareAndSet(false, true)) {
|
||||
Scheduler.runTask(CoreProtect.getInstance(), () -> capturePendingPull(transactionId, pending), pending.location);
|
||||
}
|
||||
}
|
||||
|
||||
static void flushPendingPull(Location location, Inventory inventory, ItemStack[] checkpoint) {
|
||||
if (location == null || location.getWorld() == null || checkpoint == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
originalSource[inventoryContents.length] = movedItem;
|
||||
InventoryChangeListener.onInventoryInteract(user, sourceInventory, originalSource, null, sourceInventory.getLocation(), true);
|
||||
String transactionId = HopperTransactionUtils.getTransactionId(location);
|
||||
PendingPull pending = pendingPulls.remove(transactionId);
|
||||
if (pending == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Inventory sourceInventory = inventory == null ? pending.inventory : inventory;
|
||||
submitPendingPull(pending, sourceInventory, checkpoint);
|
||||
}
|
||||
|
||||
static void flushPendingPull(Location location, Inventory inventory) {
|
||||
if (location == null || location.getWorld() == null || inventory == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String transactionId = HopperTransactionUtils.getTransactionId(location);
|
||||
PendingPull pending = pendingPulls.get(transactionId);
|
||||
if (pending == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack[] checkpoint = ItemUtils.getContainerState(inventory.getContents());
|
||||
if (pendingPulls.remove(transactionId, pending)) {
|
||||
submitPendingPull(pending, inventory, checkpoint);
|
||||
}
|
||||
}
|
||||
|
||||
static void flushPendingPullsForShutdown() {
|
||||
for (Map.Entry<String, PendingPull> entry : pendingPulls.entrySet()) {
|
||||
PendingPull pending = entry.getValue();
|
||||
try {
|
||||
ItemStack[] checkpoint = ItemUtils.getContainerState(pending.inventory.getContents());
|
||||
if (pendingPulls.remove(entry.getKey(), pending)) {
|
||||
submitPendingPull(pending, pending.inventory, checkpoint);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
ErrorReporter.report(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int pendingLocations() {
|
||||
return pendingPulls.size();
|
||||
}
|
||||
|
||||
static long pendingMoves() {
|
||||
long count = 0;
|
||||
for (PendingPull pending : pendingPulls.values()) {
|
||||
count += pending.accumulator.moveCount();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private static void capturePendingPull(String transactionId, PendingPull pending) {
|
||||
if (!pendingPulls.remove(transactionId, pending)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack[] checkpoint = ItemUtils.getContainerState(pending.inventory.getContents());
|
||||
submitPendingPull(pending, pending.inventory, checkpoint);
|
||||
}
|
||||
|
||||
private static void submitPendingPull(PendingPull pending, Inventory sourceInventory, ItemStack[] checkpoint) {
|
||||
if (pending == null || sourceInventory == null || checkpoint == null || pending.accumulator.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ContainerTransactionDispatcher.submit(pending.location, () -> pending.accumulator.replay(checkpoint, pending.maxStackSize,
|
||||
(user, sourceBefore, movedItem) -> InventoryChangeListener.onHopperInventoryInteract(user, sourceInventory, sourceBefore, pending.location, movedItem)));
|
||||
}
|
||||
|
||||
private static final class PendingPull {
|
||||
private final Inventory inventory;
|
||||
private final Location location;
|
||||
private final int maxStackSize;
|
||||
private final HopperPullAccumulator accumulator = new HopperPullAccumulator();
|
||||
private final AtomicBoolean scheduled = new AtomicBoolean(false);
|
||||
|
||||
private PendingPull(Inventory inventory, Location location, int maxStackSize) {
|
||||
this.inventory = inventory;
|
||||
this.location = location;
|
||||
this.maxStackSize = maxStackSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,161 +2,61 @@ package net.coreprotect.listener.player;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import net.coreprotect.CoreProtect;
|
||||
import net.coreprotect.config.Config;
|
||||
import net.coreprotect.config.ConfigHandler;
|
||||
import net.coreprotect.thread.Scheduler;
|
||||
import net.coreprotect.utility.HopperTransactionUtils;
|
||||
import net.coreprotect.utility.ItemUtils;
|
||||
import net.coreprotect.utility.ErrorReporter;
|
||||
|
||||
public final class HopperPushListener {
|
||||
|
||||
private static final ConcurrentLinkedQueue<Object[]> hopperQueue = new ConcurrentLinkedQueue<>();
|
||||
private static final AtomicBoolean processorRunning = new AtomicBoolean(false);
|
||||
private static final int BATCH_SIZE = 100;
|
||||
private static final int PROCESS_DELAY_TICKS = 1;
|
||||
private static final int MAX_CONCURRENT_PROCESSORS = 4;
|
||||
private static final AtomicInteger activeProcessors = new AtomicInteger(0);
|
||||
private HopperPushListener() {
|
||||
throw new IllegalStateException("Listener class");
|
||||
}
|
||||
|
||||
static void processHopperPush(Location location, String user, InventoryHolder sourceHolder, InventoryHolder destinationHolder, ItemStack item) {
|
||||
Location destinationLocation = destinationHolder.getInventory().getLocation();
|
||||
if (destinationLocation == null) {
|
||||
if (location == null || location.getWorld() == null || sourceHolder == null || destinationHolder == null || item == null || item.getAmount() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Inventory destinationInventory = destinationHolder.getInventory();
|
||||
Location destinationLocation = destinationInventory.getLocation();
|
||||
if (destinationLocation == null || destinationLocation.getWorld() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String loggingChestId = HopperTransactionUtils.getHopperPushId(destinationLocation);
|
||||
Object[] lastAbort = ConfigHandler.hopperAbort.get(loggingChestId);
|
||||
if (lastAbort != null) {
|
||||
ItemStack[] destinationContents = destinationHolder.getInventory().getContents();
|
||||
if (((Set<?>) lastAbort[0]).contains(item) && Arrays.equals(destinationContents, (ItemStack[]) lastAbort[1])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ItemStack[] destinationContainer = ItemUtils.getContainerState(destinationHolder.getInventory().getContents());
|
||||
ItemStack movedItem = item.clone();
|
||||
|
||||
hopperQueue.add(new Object[] { location, user, sourceHolder, destinationHolder, movedItem, destinationContainer, loggingChestId, lastAbort });
|
||||
|
||||
// Start the processor if it's not already running
|
||||
if (processorRunning.compareAndSet(false, true)) {
|
||||
startHopperProcessor();
|
||||
}
|
||||
}
|
||||
|
||||
private static void startHopperProcessor() {
|
||||
if (activeProcessors.incrementAndGet() <= MAX_CONCURRENT_PROCESSORS) {
|
||||
Scheduler.runTaskAsynchronously(CoreProtect.getInstance(), () -> {
|
||||
try {
|
||||
// Use the same server running check as Consumer class
|
||||
while (!hopperQueue.isEmpty() && (ConfigHandler.serverRunning || ConfigHandler.converterRunning)) {
|
||||
processHopperBatch();
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
ErrorReporter.report(e);
|
||||
}
|
||||
finally {
|
||||
activeProcessors.decrementAndGet();
|
||||
processorRunning.set(false);
|
||||
|
||||
// If more items were added and we're still running, restart the processor
|
||||
if (!hopperQueue.isEmpty() && (ConfigHandler.serverRunning || ConfigHandler.converterRunning) && activeProcessors.get() == 0) {
|
||||
startHopperProcessor();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
activeProcessors.decrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
private static void processHopperBatch() {
|
||||
int processed = 0;
|
||||
final long taskStarted = InventoryChangeListener.tasksStarted.incrementAndGet();
|
||||
|
||||
while (!hopperQueue.isEmpty() && processed < BATCH_SIZE && (ConfigHandler.serverRunning || ConfigHandler.converterRunning)) {
|
||||
Object[] data = hopperQueue.poll();
|
||||
if (data == null)
|
||||
continue;
|
||||
|
||||
Location location = (Location) data[0];
|
||||
String user = (String) data[1];
|
||||
InventoryHolder sourceHolder = (InventoryHolder) data[2];
|
||||
InventoryHolder destinationHolder = (InventoryHolder) data[3];
|
||||
ItemStack movedItem = (ItemStack) data[4];
|
||||
ItemStack[] destinationContainer = (ItemStack[]) data[5];
|
||||
String loggingChestId = (String) data[6];
|
||||
Object[] lastAbort = (Object[]) data[7];
|
||||
|
||||
processSingleHopperPush(location, user, sourceHolder, destinationHolder, movedItem, destinationContainer, loggingChestId, lastAbort);
|
||||
|
||||
processed++;
|
||||
}
|
||||
|
||||
InventoryChangeListener.checkTasks(taskStarted);
|
||||
}
|
||||
|
||||
private static void processSingleHopperPush(Location location, String user, InventoryHolder sourceHolder, InventoryHolder destinationHolder, ItemStack movedItem, ItemStack[] destinationContainer, String loggingChestId, Object[] lastAbort) {
|
||||
|
||||
if (sourceHolder == null || destinationHolder == null) {
|
||||
ItemStack[] destinationContents = destinationInventory.getContents();
|
||||
if (lastAbort != null && ((Set<?>) lastAbort[0]).contains(item) && Arrays.equals(destinationContents, (ItemStack[]) lastAbort[1])) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean abort = false;
|
||||
boolean addedInventory = ItemUtils.canAddContainer(destinationContainer, movedItem, destinationHolder.getInventory().getMaxStackSize());
|
||||
if (!addedInventory) {
|
||||
abort = true;
|
||||
}
|
||||
|
||||
if (abort) {
|
||||
ItemStack[] destinationContents = destinationHolder.getInventory().getContents();
|
||||
ConfigHandler.hopperAbort.put(loggingChestId, HopperTransactionUtils.createAbortState(lastAbort, destinationContents, movedItem));
|
||||
ItemStack[] destinationContainer = ItemUtils.getContainerState(destinationContents);
|
||||
ItemStack movedItem = item.clone();
|
||||
if (!ItemUtils.canAddContainer(destinationContainer, movedItem, destinationInventory.getMaxStackSize())) {
|
||||
ConfigHandler.hopperAbort.put(loggingChestId, HopperTransactionUtils.createAbortState(lastAbort, destinationContainer, movedItem));
|
||||
return;
|
||||
}
|
||||
|
||||
HopperTransactionUtils.recordItemRemoved(HopperTransactionUtils.getTransactionId(location), movedItem);
|
||||
|
||||
HopperPullListener.flushPendingPull(destinationLocation, destinationInventory, destinationContainer);
|
||||
if (!Config.getConfig(location.getWorld()).ITEM_TRANSACTIONS) {
|
||||
return;
|
||||
}
|
||||
if (Config.getConfig(location.getWorld()).HOPPER_FILTER_META && !movedItem.hasItemMeta()) {
|
||||
HopperTransactionUtils.recordItemAdded(HopperTransactionUtils.getTransactionId(destinationLocation), movedItem);
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigHandler.hopperSuccess.put(loggingChestId, new Object[] { destinationContainer, movedItem });
|
||||
|
||||
Inventory destinationInventory = destinationHolder.getInventory();
|
||||
ItemStack[] originalDestination = destinationInventory.getContents().clone();
|
||||
int removeAmount = movedItem.getAmount();
|
||||
for (int i = 0; i < originalDestination.length; i++) {
|
||||
if (removeAmount == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
ItemStack itemStack = (originalDestination[i] != null ? originalDestination[i].clone() : null);
|
||||
if (itemStack != null && itemStack.isSimilar(movedItem)) {
|
||||
if (itemStack.getAmount() >= removeAmount) {
|
||||
itemStack.setAmount(itemStack.getAmount() - removeAmount);
|
||||
removeAmount = 0;
|
||||
}
|
||||
else {
|
||||
removeAmount = removeAmount - itemStack.getAmount();
|
||||
itemStack = null;
|
||||
}
|
||||
|
||||
originalDestination[i] = itemStack;
|
||||
}
|
||||
}
|
||||
|
||||
InventoryChangeListener.onHopperInventoryInteract(user, destinationInventory, originalDestination, destinationInventory.getLocation(), movedItem);
|
||||
ContainerTransactionDispatcher.submit(destinationLocation, () -> {
|
||||
ConfigHandler.hopperSuccess.put(loggingChestId, new Object[] { destinationContainer, movedItem });
|
||||
InventoryChangeListener.onHopperInventoryInteract(user, destinationInventory, destinationContainer, destinationLocation, movedItem);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
@@ -53,39 +52,8 @@ import us.lynuxcraft.deadsilenceiv.advancedchests.chest.AdvancedChest;
|
||||
|
||||
public final class InventoryChangeListener extends Queue implements Listener {
|
||||
|
||||
protected static AtomicLong tasksStarted = new AtomicLong();
|
||||
protected static AtomicLong tasksCompleted = new AtomicLong();
|
||||
private static ConcurrentHashMap<String, Boolean> inventoryProcessing = new ConcurrentHashMap<>();
|
||||
private static final Map<UUID, PendingEntityContainerTransaction> pendingEntityTransactions = new HashMap<>();
|
||||
private static final Object taskCompletionLock = new Object();
|
||||
private static final long TASK_WAIT_MAX_MS = 50; // Maximum wait time in milliseconds
|
||||
|
||||
protected static void checkTasks(long taskStarted) {
|
||||
try {
|
||||
// Skip checking if this is the first task or we're already caught up
|
||||
if (taskStarted <= 1 || tasksCompleted.get() >= (taskStarted - 1L)) {
|
||||
tasksCompleted.set(taskStarted);
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to update without waiting if possible
|
||||
if (tasksCompleted.compareAndSet(taskStarted - 1L, taskStarted)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use proper synchronization instead of busy waiting
|
||||
synchronized (taskCompletionLock) {
|
||||
if (tasksCompleted.get() < (taskStarted - 1L)) {
|
||||
taskCompletionLock.wait(TASK_WAIT_MAX_MS);
|
||||
}
|
||||
tasksCompleted.set(taskStarted);
|
||||
taskCompletionLock.notifyAll(); // Notify other waiting threads
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
ErrorReporter.report(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean inventoryTransaction(String user, Location location, ItemStack[] inventoryData) {
|
||||
if (location != null) {
|
||||
@@ -108,6 +76,43 @@ public final class InventoryChangeListener extends Queue implements Listener {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void queueContainerBreak(String user, Location location, Material type, ItemStack[] contents) {
|
||||
if (user == null || user.isEmpty() || location == null || location.getWorld() == null || type == null || contents == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Location capturedLocation = location.clone();
|
||||
Location boundaryLocation = getCanonicalContainerLocation(location);
|
||||
ItemStack[] capturedContents = ItemUtils.getContainerState(contents);
|
||||
HopperPullListener.flushPendingPull(boundaryLocation, null, capturedContents);
|
||||
ContainerTransactionDispatcher.submit(boundaryLocation, () -> {
|
||||
setForceContainer(HopperTransactionUtils.getLoggingId(user, capturedLocation), ItemUtils.getContainerState(capturedContents));
|
||||
Queue.queueContainerBreak(user, capturedLocation, type, capturedContents);
|
||||
});
|
||||
}
|
||||
|
||||
public static void flushPendingContainer(Location location, ItemStack[] contents) {
|
||||
if (location == null || location.getWorld() == null || contents == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
HopperPullListener.flushPendingPull(getCanonicalContainerLocation(location), null, ItemUtils.getContainerState(contents));
|
||||
}
|
||||
|
||||
public static void flushPendingContainer(Inventory inventory, Location location) {
|
||||
if (inventory == null || location == null || location.getWorld() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Location inventoryLocation = inventory.getLocation();
|
||||
HopperPullListener.flushPendingPull(inventoryLocation == null ? location : inventoryLocation, inventory);
|
||||
}
|
||||
|
||||
public static void flushPendingTransactionsForShutdown() {
|
||||
HopperPullListener.flushPendingPullsForShutdown();
|
||||
ContainerTransactionDispatcher.drainForShutdown();
|
||||
}
|
||||
|
||||
static boolean onInventoryInteract(String user, final Inventory inventory, ItemStack[] inventoryData, Material containerType, Location location, boolean aSync) {
|
||||
if (inventory != null && location != null) {
|
||||
World world = location.getWorld();
|
||||
@@ -153,10 +158,18 @@ public final class InventoryChangeListener extends Queue implements Listener {
|
||||
|
||||
if (playerLocation != null) {
|
||||
if (inventoryData == null) {
|
||||
inventoryData = inventory.getContents();
|
||||
inventoryData = ItemUtils.getContainerState(inventory.getContents());
|
||||
}
|
||||
else if (!aSync) {
|
||||
inventoryData = ItemUtils.getContainerState(inventoryData);
|
||||
}
|
||||
|
||||
return queueContainerTransaction(user, playerLocation, type, inventory, inventoryData, null, null);
|
||||
ItemStack[] capturedState = inventoryData;
|
||||
Location capturedLocation = playerLocation.clone();
|
||||
Material capturedType = type;
|
||||
HopperPullListener.flushPendingPull(capturedLocation, inventory, capturedState);
|
||||
ContainerTransactionDispatcher.submit(capturedLocation, () -> queueContainerTransaction(user, capturedLocation, capturedType, inventory, capturedState, null, null));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -285,8 +298,9 @@ public final class InventoryChangeListener extends Queue implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
Location inventoryLocation = location;
|
||||
Location inventoryLocation = location.clone();
|
||||
ItemStack[] containerState = ItemUtils.getContainerState(inventory.getContents());
|
||||
HopperPullListener.flushPendingPull(inventoryLocation, inventory, containerState);
|
||||
|
||||
String loggingChestId = player.getName() + "." + location.getBlockX() + "." + location.getBlockY() + "." + location.getBlockZ();
|
||||
Boolean lastTransaction = inventoryProcessing.get(loggingChestId);
|
||||
@@ -295,13 +309,11 @@ public final class InventoryChangeListener extends Queue implements Listener {
|
||||
}
|
||||
inventoryProcessing.put(loggingChestId, true);
|
||||
|
||||
final long taskStarted = InventoryChangeListener.tasksStarted.incrementAndGet();
|
||||
Scheduler.runTaskAsynchronously(CoreProtect.getInstance(), () -> {
|
||||
Material containerType = (enderChest != true ? null : Material.ENDER_CHEST);
|
||||
ContainerTransactionDispatcher.submit(inventoryLocation, () -> {
|
||||
try {
|
||||
Material containerType = (enderChest != true ? null : Material.ENDER_CHEST);
|
||||
InventoryChangeListener.checkTasks(taskStarted);
|
||||
inventoryProcessing.remove(loggingChestId);
|
||||
onInventoryInteract(player.getName(), inventory, containerState, containerType, inventoryLocation, true);
|
||||
queueContainerTransaction(player.getName(), inventoryLocation, containerType == null ? Material.CHEST : containerType, inventory, containerState, null, null);
|
||||
}
|
||||
catch (Exception e) {
|
||||
ErrorReporter.report(e);
|
||||
@@ -785,7 +797,8 @@ public final class InventoryChangeListener extends Queue implements Listener {
|
||||
onInventoryInteract(user, sourceInventory, sourceBefore, null, sourceLocation, true);
|
||||
}
|
||||
if (destinationBlockContainer && (Validate.isHopper(sourceHolder) || Validate.isDropper(sourceHolder)) && !Validate.isHopper(destinationHolder)) {
|
||||
onHopperInventoryInteract(user, destinationInventory, destinationBefore, destinationLocation, moved);
|
||||
HopperPullListener.flushPendingPull(destinationLocation, destinationInventory, destinationBefore);
|
||||
ContainerTransactionDispatcher.submit(destinationLocation, () -> onHopperInventoryInteract(user, destinationInventory, destinationBefore, destinationLocation, moved));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -852,10 +865,28 @@ public final class InventoryChangeListener extends Queue implements Listener {
|
||||
queueEntityContainerDelta(user, entityContainer, oldContents, newContents);
|
||||
}
|
||||
else {
|
||||
queueContainerTransaction(user, location, Material.HOPPER, newContents, oldContents, newContents, null);
|
||||
Location inventoryLocation = location.clone();
|
||||
String transactionUser = user;
|
||||
ContainerTransactionDispatcher.submit(inventoryLocation, () -> queueContainerTransaction(transactionUser, inventoryLocation, Material.HOPPER, newContents, oldContents, newContents, null));
|
||||
}
|
||||
}
|
||||
|
||||
private static Location getCanonicalContainerLocation(Location location) {
|
||||
try {
|
||||
BlockState state = location.getBlock().getState();
|
||||
if (state instanceof InventoryHolder) {
|
||||
Location inventoryLocation = ((InventoryHolder) state).getInventory().getLocation();
|
||||
if (inventoryLocation != null && inventoryLocation.getWorld() != null) {
|
||||
return inventoryLocation.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
ErrorReporter.report(e);
|
||||
}
|
||||
return location.clone();
|
||||
}
|
||||
|
||||
private static ItemStack[] addPickedItem(ItemStack[] contents, ItemStack itemStack, int inventoryMaxStackSize) {
|
||||
if (contents == null || itemStack == null || itemStack.getAmount() <= 0 || itemStack.getType() == Material.AIR) {
|
||||
return null;
|
||||
|
||||
@@ -13,6 +13,7 @@ import net.coreprotect.consumer.Consumer;
|
||||
import net.coreprotect.consumer.process.Process;
|
||||
import net.coreprotect.language.Phrase;
|
||||
import net.coreprotect.listener.player.PlayerQuitListener;
|
||||
import net.coreprotect.listener.player.InventoryChangeListener;
|
||||
import net.coreprotect.paper.PaperAdapter;
|
||||
import net.coreprotect.utility.Chat;
|
||||
import net.coreprotect.utility.Extensions;
|
||||
@@ -59,6 +60,8 @@ public class ShutdownService {
|
||||
EntitySpawnTracking.queueLoadedLocationsForShutdown();
|
||||
}
|
||||
|
||||
InventoryChangeListener.flushPendingTransactionsForShutdown();
|
||||
|
||||
ConfigHandler.serverRunning = false;
|
||||
long shutdownTime = System.currentTimeMillis();
|
||||
long nextAlertTime = shutdownTime + ALERT_INTERVAL_MS;
|
||||
|
||||
@@ -142,7 +142,7 @@ public final class HopperTransactionUtils {
|
||||
}
|
||||
|
||||
public static void recordItemAdded(String transactionId, ItemStack item) {
|
||||
recordDelta(transactionId, item, false, 1);
|
||||
recordDelta(transactionId, item, false, item == null ? 0 : item.getAmount());
|
||||
}
|
||||
|
||||
public static void recordItemRemoved(String transactionId, ItemStack item) {
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||
|
||||
import net.coreprotect.config.Config;
|
||||
import net.coreprotect.consumer.Queue;
|
||||
import net.coreprotect.listener.player.InventoryChangeListener;
|
||||
import net.coreprotect.model.action.SignActions;
|
||||
import net.coreprotect.utility.BlockUtils;
|
||||
import net.coreprotect.utility.EntityUtils;
|
||||
@@ -100,7 +101,7 @@ public class WorldEditLogger extends Queue {
|
||||
}
|
||||
}
|
||||
if (containerContents != null) {
|
||||
Queue.queueContainerBreak(actor.getName(), location, oldType, containerContents);
|
||||
InventoryChangeListener.queueContainerBreak(actor.getName(), location, oldType, containerContents);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
|
||||
Reference in new issue
Block a user