Merge branch 'master' of github.com:PlayPro/CoreProtect
# Conflicts: # README.md # pom.xml # src/main/java/net/coreprotect/config/ConfigHandler.java
This commit is contained in:
commit
df6f20ef97
14 changed files with 923 additions and 16 deletions
|
|
@ -15,7 +15,7 @@ Maven
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.zhdev.griefus</groupId>
|
<groupId>org.zhdev.griefus</groupId>
|
||||||
<artifactId>griefus</artifactId>
|
<artifactId>griefus</artifactId>
|
||||||
<version>23.0</version>
|
<version>23.1</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ The CoreProtect API enables you to log your own block changes, perform lookups,
|
||||||
|
|
||||||
| API Details | |
|
| API Details | |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| **API Version:** | 10 |
|
| **API Version:** | 11 |
|
||||||
| **Plugin Version:** | v22.4+ |
|
| **Plugin Version:** | v23.1+ |
|
||||||
| **Maven:** | [maven.playpro.com](https://maven.playpro.com) |
|
| **Maven:** | [maven.playpro.com](https://maven.playpro.com) |
|
||||||
|
|
||||||
*Documentation for the API version 10 can be found [here](/api/version/v10/).*
|
*Documentation for the API version 10 can be found [here](/api/version/v11/).*
|
||||||
|
|
@ -5,7 +5,7 @@ The CoreProtect API enables you to log your own block changes, perform lookups,
|
||||||
| API Details | |
|
| API Details | |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| **API Version:** | 11 |
|
| **API Version:** | 11 |
|
||||||
| **Plugin Version:** | v24.0+ |
|
| **Plugin Version:** | v23.1+ |
|
||||||
| **Maven:** | [maven.playpro.com](https://maven.playpro.com) |
|
| **Maven:** | [maven.playpro.com](https://maven.playpro.com) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -28,8 +28,8 @@ CoreProtectPreLogEvent(String user)
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
Ensure you're using CoreProtect 24.0 or higher. Add it as an external jar to your plugin in your IDE.
|
Ensure you're using CoreProtect 23.1 or higher. Add it as an external jar to your plugin in your IDE.
|
||||||
Alternatively, if using Maven, you can add it via the repository https://maven.playpro.com (net.coreprotect, 24.0).
|
Alternatively, if using Maven, you can add it via the repository https://maven.playpro.com (net.coreprotect, 23.1).
|
||||||
|
|
||||||
The first thing you need to do is get access to CoreProtect. You can do this by using code similar to the following:
|
The first thing you need to do is get access to CoreProtect. You can do this by using code similar to the following:
|
||||||
|
|
||||||
|
|
|
||||||
2
pom.xml
2
pom.xml
|
|
@ -2,7 +2,7 @@
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.zhdev.griefus</groupId>
|
<groupId>org.zhdev.griefus</groupId>
|
||||||
<artifactId>griefus</artifactId>
|
<artifactId>griefus</artifactId>
|
||||||
<version>23.0-SNAPSHOT</version>
|
<version>23.1-SNAPSHOT</version>
|
||||||
<properties>
|
<properties>
|
||||||
<project.branch>master</project.branch>
|
<project.branch>master</project.branch>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
|
|
||||||
|
|
@ -366,8 +366,18 @@ public class BukkitAdapter implements BukkitInterface {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShelf(Material material){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<Material> copperChestMaterials() {
|
public Set<Material> copperChestMaterials() {
|
||||||
return EMPTY_SET;
|
return EMPTY_SET;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Material> shelfMaterials() {
|
||||||
|
return EMPTY_SET;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,17 @@ public interface BukkitInterface {
|
||||||
*/
|
*/
|
||||||
boolean isChiseledBookshelf(Material material);
|
boolean isChiseledBookshelf(Material material);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a material is a shelf of any wood kind.
|
||||||
|
*
|
||||||
|
* @param material
|
||||||
|
* The material to check
|
||||||
|
* @return true if the material is a shelf, false otherwise
|
||||||
|
*/
|
||||||
|
boolean isShelf(Material material);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a material is a bookshelf book.
|
* Checks if a material is a bookshelf book.
|
||||||
*
|
*
|
||||||
|
|
@ -441,4 +452,6 @@ public interface BukkitInterface {
|
||||||
|
|
||||||
Set<Material> copperChestMaterials();
|
Set<Material> copperChestMaterials();
|
||||||
|
|
||||||
|
Set<Material> shelfMaterials();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,10 @@ import net.coreprotect.model.BlockGroup;
|
||||||
* - Registry handling for named objects
|
* - Registry handling for named objects
|
||||||
* - Updated interaction blocks
|
* - Updated interaction blocks
|
||||||
*/
|
*/
|
||||||
public class Bukkit_v1_21 extends Bukkit_v1_20 implements BukkitInterface {
|
public class Bukkit_v1_21 extends Bukkit_v1_20 {
|
||||||
|
|
||||||
public static Set<Material> COPPER_CHESTS = new HashSet<>(Arrays.asList());
|
public static Set<Material> COPPER_CHESTS = new HashSet<>(Arrays.asList());
|
||||||
|
public static Set<Material> SHELVES = new HashSet<>(Arrays.asList());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the Bukkit_v1_21 adapter with 1.21-specific block groups and mappings.
|
* Initializes the Bukkit_v1_21 adapter with 1.21-specific block groups and mappings.
|
||||||
|
|
@ -37,6 +38,7 @@ public class Bukkit_v1_21 extends Bukkit_v1_20 implements BukkitInterface {
|
||||||
BlockGroup.INTERACT_BLOCKS.addAll(copperChestMaterials());
|
BlockGroup.INTERACT_BLOCKS.addAll(copperChestMaterials());
|
||||||
BlockGroup.CONTAINERS.addAll(copperChestMaterials());
|
BlockGroup.CONTAINERS.addAll(copperChestMaterials());
|
||||||
BlockGroup.UPDATE_STATE.addAll(copperChestMaterials());
|
BlockGroup.UPDATE_STATE.addAll(copperChestMaterials());
|
||||||
|
BlockGroup.CONTAINERS.addAll(shelfMaterials());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -111,6 +113,7 @@ public class Bukkit_v1_21 extends Bukkit_v1_20 implements BukkitInterface {
|
||||||
return ((Keyed) value).getKey().toString();
|
return ((Keyed) value).getKey().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a registry value from a key string and class.
|
* Gets a registry value from a key string and class.
|
||||||
* Used for deserializing registry objects.
|
* Used for deserializing registry objects.
|
||||||
|
|
@ -177,6 +180,11 @@ public class Bukkit_v1_21 extends Bukkit_v1_20 implements BukkitInterface {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShelf(Material material) {
|
||||||
|
return SHELVES.contains(material);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<Material> copperChestMaterials() {
|
public Set<Material> copperChestMaterials() {
|
||||||
if (COPPER_CHESTS.isEmpty()) {
|
if (COPPER_CHESTS.isEmpty()) {
|
||||||
|
|
@ -198,4 +206,16 @@ public class Bukkit_v1_21 extends Bukkit_v1_20 implements BukkitInterface {
|
||||||
|
|
||||||
return COPPER_CHESTS;
|
return COPPER_CHESTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Material> shelfMaterials() {
|
||||||
|
if (SHELVES.isEmpty()) {
|
||||||
|
Material shelf = Material.getMaterial("OAK_SHELF");
|
||||||
|
if (shelf != null) {
|
||||||
|
SHELVES.addAll(Tag.WOODEN_SHELVES.getValues());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SHELVES;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,8 +51,8 @@ public class ConfigHandler extends Queue {
|
||||||
public static final String EDITION_NAME = VersionUtils.getPluginName();
|
public static final String EDITION_NAME = VersionUtils.getPluginName();
|
||||||
public static final String JAVA_VERSION = "11.0";
|
public static final String JAVA_VERSION = "11.0";
|
||||||
public static final String MINECRAFT_VERSION = "1.16";
|
public static final String MINECRAFT_VERSION = "1.16";
|
||||||
public static final String PATCH_VERSION = "23.0";
|
public static final String PATCH_VERSION = "23.1";
|
||||||
public static final String LATEST_VERSION = "1.21.10";
|
public static final String LATEST_VERSION = "1.21.11";
|
||||||
public static String path = "plugins/Griefus/";
|
public static String path = "plugins/Griefus/";
|
||||||
public static String sqlite = "database.db";
|
public static String sqlite = "database.db";
|
||||||
public static String host = "127.0.0.1";
|
public static String host = "127.0.0.1";
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ public class ContainerLogger extends Queue {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is a dispenser with no actual changes
|
// Check if this is a dispenser with no actual changes
|
||||||
if (player.equals("#dispenser") && ItemUtils.compareContainers(oldInventory, newInventory)) {
|
if ("#dispenser".equals(player) && ItemUtils.compareContainers(oldInventory, newInventory)) {
|
||||||
// No changes detected, mark this dispenser in the dispenserNoChange map
|
// No changes detected, mark this dispenser in the dispenserNoChange map
|
||||||
// Extract the location key from the loggingContainerId
|
// Extract the location key from the loggingContainerId
|
||||||
// Format: #dispenser.x.y.z
|
// Format: #dispenser.x.y.z
|
||||||
|
|
@ -95,7 +95,7 @@ public class ContainerLogger extends Queue {
|
||||||
|
|
||||||
// If we reach here, the dispenser event resulted in changes
|
// If we reach here, the dispenser event resulted in changes
|
||||||
// Remove any pending event for this dispenser
|
// Remove any pending event for this dispenser
|
||||||
if (player.equals("#dispenser")) {
|
if ("#dispenser".equals(player)) {
|
||||||
String[] parts = loggingContainerId.split("\\.");
|
String[] parts = loggingContainerId.split("\\.");
|
||||||
if (parts.length >= 4) {
|
if (parts.length >= 4) {
|
||||||
int x = Integer.parseInt(parts[1]);
|
int x = Integer.parseInt(parts[1]);
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ import net.coreprotect.listener.world.LeavesDecayListener;
|
||||||
import net.coreprotect.listener.world.PortalCreateListener;
|
import net.coreprotect.listener.world.PortalCreateListener;
|
||||||
import net.coreprotect.listener.world.StructureGrowListener;
|
import net.coreprotect.listener.world.StructureGrowListener;
|
||||||
import net.coreprotect.paper.listener.BlockPreDispenseListener;
|
import net.coreprotect.paper.listener.BlockPreDispenseListener;
|
||||||
|
import net.coreprotect.paper.listener.CopperGolemChestListener;
|
||||||
import net.coreprotect.paper.listener.PaperChatListener;
|
import net.coreprotect.paper.listener.PaperChatListener;
|
||||||
|
|
||||||
public final class ListenerHandler {
|
public final class ListenerHandler {
|
||||||
|
|
@ -72,6 +73,14 @@ public final class ListenerHandler {
|
||||||
BlockPreDispenseListener.useBlockPreDispenseEvent = false;
|
BlockPreDispenseListener.useBlockPreDispenseEvent = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class.forName("io.papermc.paper.event.entity.ItemTransportingEntityValidateTargetEvent"); // Paper 1.21.10+
|
||||||
|
pluginManager.registerEvents(new CopperGolemChestListener(plugin), plugin);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
// Ignore registration failures to remain compatible with older servers.
|
||||||
|
}
|
||||||
|
|
||||||
// Block Listeners
|
// Block Listeners
|
||||||
pluginManager.registerEvents(new BlockBreakListener(), plugin);
|
pluginManager.registerEvents(new BlockBreakListener(), plugin);
|
||||||
pluginManager.registerEvents(new BlockBurnListener(), plugin);
|
pluginManager.registerEvents(new BlockBurnListener(), plugin);
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,14 @@ import org.bukkit.block.Jukebox;
|
||||||
import org.bukkit.block.Sign;
|
import org.bukkit.block.Sign;
|
||||||
import org.bukkit.block.data.Bisected;
|
import org.bukkit.block.data.Bisected;
|
||||||
import org.bukkit.block.data.Bisected.Half;
|
import org.bukkit.block.data.Bisected.Half;
|
||||||
|
import org.bukkit.block.data.SideChaining.ChainPart;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
import org.bukkit.block.data.Lightable;
|
import org.bukkit.block.data.Lightable;
|
||||||
import org.bukkit.block.data.Waterlogged;
|
import org.bukkit.block.data.Waterlogged;
|
||||||
import org.bukkit.block.data.type.Bed;
|
import org.bukkit.block.data.type.Bed;
|
||||||
import org.bukkit.block.data.type.Bed.Part;
|
import org.bukkit.block.data.type.Bed.Part;
|
||||||
import org.bukkit.block.data.type.Cake;
|
import org.bukkit.block.data.type.Cake;
|
||||||
|
import org.bukkit.block.data.type.Shelf;
|
||||||
import org.bukkit.entity.EnderCrystal;
|
import org.bukkit.entity.EnderCrystal;
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
@ -37,6 +39,7 @@ import org.bukkit.inventory.BlockInventoryHolder;
|
||||||
import org.bukkit.inventory.EquipmentSlot;
|
import org.bukkit.inventory.EquipmentSlot;
|
||||||
import org.bukkit.inventory.InventoryHolder;
|
import org.bukkit.inventory.InventoryHolder;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
import net.coreprotect.CoreProtect;
|
import net.coreprotect.CoreProtect;
|
||||||
import net.coreprotect.bukkit.BukkitAdapter;
|
import net.coreprotect.bukkit.BukkitAdapter;
|
||||||
|
|
@ -465,6 +468,49 @@ public final class PlayerInteractListener extends Queue implements Listener {
|
||||||
InventoryChangeListener.inventoryTransaction(player.getName(), blockState.getLocation(), null);
|
InventoryChangeListener.inventoryTransaction(player.getName(), blockState.getLocation(), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (BukkitAdapter.ADAPTER.isShelf(type) ){
|
||||||
|
BlockData blockState = block.getBlockData();
|
||||||
|
if (blockState instanceof Shelf){
|
||||||
|
Shelf shelf = (Shelf) blockState;
|
||||||
|
|
||||||
|
// ignore clicking on the back face
|
||||||
|
if (event.getBlockFace() != shelf.getFacing()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shelf.getSideChain() == ChainPart.UNCONNECTED){
|
||||||
|
InventoryChangeListener.inventoryTransaction(player.getName(), block.getLocation(), null);
|
||||||
|
} else {
|
||||||
|
Block center = block;
|
||||||
|
Vector direction = shelf.getFacing().getDirection();
|
||||||
|
|
||||||
|
if (shelf.getSideChain() == ChainPart.LEFT){
|
||||||
|
center = center.getRelative(direction.getBlockZ(), 0, -direction.getBlockX());
|
||||||
|
} else if (shelf.getSideChain() == ChainPart.RIGHT){
|
||||||
|
center = center.getRelative(-direction.getBlockZ(), 0, direction.getBlockX());
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockData centerBlockData = center.getBlockData();
|
||||||
|
if (centerBlockData instanceof Shelf){
|
||||||
|
// log center
|
||||||
|
InventoryChangeListener.inventoryTransaction(player.getName(), center.getLocation(), null);
|
||||||
|
|
||||||
|
if (((Shelf)centerBlockData).getSideChain() != ChainPart.CENTER){
|
||||||
|
// if it's not the center it's just a chain of 2
|
||||||
|
InventoryChangeListener.inventoryTransaction(player.getName(), block.getLocation(), null);
|
||||||
|
} else {
|
||||||
|
Block left = center.getRelative(-direction.getBlockZ(), 0, direction.getBlockX());
|
||||||
|
InventoryChangeListener.inventoryTransaction(player.getName(), left.getLocation(), null);
|
||||||
|
|
||||||
|
Block right = center.getRelative(direction.getBlockZ(), 0, -direction.getBlockX());
|
||||||
|
InventoryChangeListener.inventoryTransaction(player.getName(), right.getLocation(), null);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// fallback if invalid block is found just log clicked shelf
|
||||||
|
InventoryChangeListener.inventoryTransaction(player.getName(), block.getLocation(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (BukkitAdapter.ADAPTER.isDecoratedPot(type)) {
|
else if (BukkitAdapter.ADAPTER.isDecoratedPot(type)) {
|
||||||
BlockState blockState = block.getState();
|
BlockState blockState = block.getState();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,805 @@
|
||||||
|
package net.coreprotect.paper.listener;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.bukkit.GameEvent;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.block.BlockState;
|
||||||
|
import org.bukkit.block.DoubleChest;
|
||||||
|
import org.bukkit.entity.CopperGolem;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.world.GenericGameEvent;
|
||||||
|
import org.bukkit.inventory.EntityEquipment;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.InventoryHolder;
|
||||||
|
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.utility.ItemUtils;
|
||||||
|
|
||||||
|
public final class CopperGolemChestListener implements Listener {
|
||||||
|
|
||||||
|
private static final String USERNAME = "#copper_golem";
|
||||||
|
private static final long OPEN_INTERACTION_TIMEOUT_MILLIS = 20000L;
|
||||||
|
private static final long CLEANUP_INTERVAL_MILLIS = 60000L;
|
||||||
|
private static final long EMPTY_COPPER_CHEST_SKIP_TTL_MILLIS = 6000L;
|
||||||
|
private static final long CLOSE_FINALIZE_DELAY_TICKS = 1L;
|
||||||
|
private static final int CLOSE_FINALIZE_MAX_ATTEMPTS = 3;
|
||||||
|
private static final int CLOSE_FALLBACK_MAX_ATTEMPTS = 2;
|
||||||
|
|
||||||
|
private final CoreProtect plugin;
|
||||||
|
private final Map<UUID, OpenInteraction> openInteractions = new ConcurrentHashMap<>();
|
||||||
|
private final Map<UUID, RecentEmptyCopperChestSkip> recentEmptyCopperChestSkips = new ConcurrentHashMap<>();
|
||||||
|
private final Map<TransactionKey, OpenInteractionIndex> openInteractionIndexByContainerKey = new ConcurrentHashMap<>();
|
||||||
|
private final Map<TransactionKey, Set<UUID>> emptySkipGolemsByContainerKey = new ConcurrentHashMap<>();
|
||||||
|
private volatile long lastCleanupMillis;
|
||||||
|
|
||||||
|
public CopperGolemChestListener(CoreProtect plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||||
|
public void onGenericGameEvent(GenericGameEvent event) {
|
||||||
|
if (event == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameEvent gameEvent = event.getEvent();
|
||||||
|
if (gameEvent != GameEvent.CONTAINER_OPEN && gameEvent != GameEvent.CONTAINER_CLOSE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity entity = event.getEntity();
|
||||||
|
if (gameEvent == GameEvent.CONTAINER_OPEN) {
|
||||||
|
if (!(entity instanceof CopperGolem)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (entity != null && !(entity instanceof CopperGolem)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Location eventLocation = event.getLocation();
|
||||||
|
if (eventLocation == null || eventLocation.getWorld() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Config.getConfig(eventLocation.getWorld()).ITEM_TRANSACTIONS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockState blockState = eventLocation.getBlock().getState();
|
||||||
|
if (!(blockState instanceof InventoryHolder)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location containerLocation = blockState.getLocation();
|
||||||
|
if (containerLocation == null || containerLocation.getWorld() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Material containerType = blockState.getType();
|
||||||
|
boolean isCopperChest = isCopperChest(containerType);
|
||||||
|
boolean isStandardChest = containerType == Material.CHEST || containerType == Material.TRAPPED_CHEST;
|
||||||
|
if (!isCopperChest && !isStandardChest) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
cleanupOpenInteractions(now);
|
||||||
|
|
||||||
|
InventoryHolder inventoryHolder = (InventoryHolder) blockState;
|
||||||
|
Inventory inventory = inventoryHolder.getInventory();
|
||||||
|
Location canonicalLocation = getCanonicalContainerLocation(containerLocation, inventory);
|
||||||
|
TransactionKey containerKey = TransactionKey.of(canonicalLocation);
|
||||||
|
|
||||||
|
if (gameEvent == GameEvent.CONTAINER_OPEN) {
|
||||||
|
handleContainerOpen((CopperGolem) entity, canonicalLocation, containerKey, containerType, inventoryHolder, now);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (entity instanceof CopperGolem) {
|
||||||
|
handleContainerClose((CopperGolem) entity, canonicalLocation, containerKey, containerType, inventoryHolder, now);
|
||||||
|
}
|
||||||
|
else if (entity == null) {
|
||||||
|
handleContainerCloseWithoutEntity(containerKey, containerType, now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Location getCanonicalContainerLocation(Location containerLocation, Inventory inventory) {
|
||||||
|
if (containerLocation == null || containerLocation.getWorld() == null || inventory == null) {
|
||||||
|
return containerLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
InventoryHolder holder = inventory.getHolder();
|
||||||
|
if (!(holder instanceof DoubleChest)) {
|
||||||
|
return containerLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location doubleChestLocation = ((DoubleChest) holder).getLocation();
|
||||||
|
if (doubleChestLocation == null) {
|
||||||
|
return containerLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location canonical = new Location(containerLocation.getWorld(), doubleChestLocation.getBlockX(), doubleChestLocation.getBlockY(), doubleChestLocation.getBlockZ());
|
||||||
|
if (canonical.getWorld() == null) {
|
||||||
|
return containerLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
return canonical;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isCopperChest(Material material) {
|
||||||
|
return BukkitAdapter.ADAPTER != null && BukkitAdapter.ADAPTER.isCopperChest(material);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleContainerOpen(CopperGolem golem, Location containerLocation, TransactionKey containerKey, Material containerType, InventoryHolder inventoryHolder, long nowMillis) {
|
||||||
|
Inventory inventory = inventoryHolder.getInventory();
|
||||||
|
if (inventory == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HeldItemSnapshot held = getHeldItemSnapshot(golem);
|
||||||
|
boolean isCopperChest = isCopperChest(containerType);
|
||||||
|
if (isCopperChest) {
|
||||||
|
if (held.material != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (held.material == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack[] contents = inventory.getContents();
|
||||||
|
if (contents == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCopperChest) {
|
||||||
|
if (isInventoryEmpty(contents)) {
|
||||||
|
UUID golemId = golem.getUniqueId();
|
||||||
|
RecentEmptyCopperChestSkip previous = recentEmptyCopperChestSkips.put(golemId, new RecentEmptyCopperChestSkip(containerKey, nowMillis));
|
||||||
|
if (previous != null) {
|
||||||
|
unindexEmptySkip(previous.containerKey, golemId);
|
||||||
|
}
|
||||||
|
indexEmptySkip(containerKey, golemId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!isInventoryEmpty(contents) && !containsOnlyMaterial(contents, held.material)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasSpaceForMaterial(contents, held.material)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack[] baseline = ItemUtils.getContainerState(contents);
|
||||||
|
if (baseline == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Material heldMaterial = isCopperChest ? null : held.material;
|
||||||
|
int heldAmount = isCopperChest ? 0 : held.amount;
|
||||||
|
OpenInteraction interaction = new OpenInteraction(containerKey, containerLocation.clone(), containerType, baseline, heldMaterial, heldAmount, nowMillis);
|
||||||
|
UUID golemId = golem.getUniqueId();
|
||||||
|
RecentEmptyCopperChestSkip removedSkip = recentEmptyCopperChestSkips.remove(golemId);
|
||||||
|
if (removedSkip != null) {
|
||||||
|
unindexEmptySkip(removedSkip.containerKey, golemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenInteraction previous = openInteractions.put(golemId, interaction);
|
||||||
|
if (previous != null) {
|
||||||
|
openInteractionIndexByContainerKey.remove(previous.containerKey, new OpenInteractionIndex(golemId, previous.openedAtMillis));
|
||||||
|
}
|
||||||
|
openInteractionIndexByContainerKey.put(containerKey, new OpenInteractionIndex(golemId, nowMillis));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleContainerClose(CopperGolem golem, Location containerLocation, TransactionKey containerKey, Material containerType, InventoryHolder inventoryHolder, long nowMillis) {
|
||||||
|
UUID golemId = golem.getUniqueId();
|
||||||
|
OpenInteraction interaction = openInteractions.get(golemId);
|
||||||
|
if (interaction == null) {
|
||||||
|
if (isCopperChest(containerType)) {
|
||||||
|
RecentEmptyCopperChestSkip emptySkip = recentEmptyCopperChestSkips.get(golemId);
|
||||||
|
if (emptySkip != null && emptySkip.containerKey.equals(containerKey) && (nowMillis - emptySkip.skippedAtMillis) <= EMPTY_COPPER_CHEST_SKIP_TTL_MILLIS) {
|
||||||
|
if (recentEmptyCopperChestSkips.remove(golemId, emptySkip)) {
|
||||||
|
unindexEmptySkip(emptySkip.containerKey, golemId);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleUntrackedCopperChestCloseFinalize(golemId, containerLocation.clone(), containerType, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nowMillis - interaction.openedAtMillis > OPEN_INTERACTION_TIMEOUT_MILLIS) {
|
||||||
|
if (openInteractions.remove(golemId, interaction)) {
|
||||||
|
openInteractionIndexByContainerKey.remove(interaction.containerKey, new OpenInteractionIndex(golemId, interaction.openedAtMillis));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!interaction.containerKey.equals(containerKey)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
openInteractions.remove(golemId, interaction);
|
||||||
|
openInteractionIndexByContainerKey.remove(interaction.containerKey, new OpenInteractionIndex(golemId, interaction.openedAtMillis));
|
||||||
|
scheduleCloseFinalize(golemId, interaction, containerKey, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleContainerCloseWithoutEntity(TransactionKey containerKey, Material containerType, long nowMillis) {
|
||||||
|
if (containerKey == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCopperChest(containerType)) {
|
||||||
|
clearRecentEmptyCopperChestSkipsByKey(containerKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenInteractionIndex index = openInteractionIndexByContainerKey.get(containerKey);
|
||||||
|
if (index == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID golemId = index.golemId;
|
||||||
|
OpenInteraction interaction = openInteractions.get(golemId);
|
||||||
|
if (interaction == null) {
|
||||||
|
openInteractionIndexByContainerKey.remove(containerKey, index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (interaction.openedAtMillis != index.openedAtMillis || !interaction.containerKey.equals(containerKey)) {
|
||||||
|
openInteractionIndexByContainerKey.remove(containerKey, index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (nowMillis - interaction.openedAtMillis > OPEN_INTERACTION_TIMEOUT_MILLIS) {
|
||||||
|
if (openInteractions.remove(golemId, interaction)) {
|
||||||
|
openInteractionIndexByContainerKey.remove(containerKey, index);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openInteractions.remove(golemId, interaction)) {
|
||||||
|
openInteractionIndexByContainerKey.remove(containerKey, index);
|
||||||
|
scheduleCloseFinalize(golemId, interaction, containerKey, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearRecentEmptyCopperChestSkipsByKey(TransactionKey containerKey) {
|
||||||
|
Set<UUID> golemIds = emptySkipGolemsByContainerKey.remove(containerKey);
|
||||||
|
if (golemIds == null || golemIds.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (UUID golemId : golemIds) {
|
||||||
|
RecentEmptyCopperChestSkip skip = recentEmptyCopperChestSkips.get(golemId);
|
||||||
|
if (skip != null && skip.containerKey.equals(containerKey)) {
|
||||||
|
recentEmptyCopperChestSkips.remove(golemId, skip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void indexEmptySkip(TransactionKey containerKey, UUID golemId) {
|
||||||
|
emptySkipGolemsByContainerKey.computeIfAbsent(containerKey, key -> ConcurrentHashMap.newKeySet()).add(golemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unindexEmptySkip(TransactionKey containerKey, UUID golemId) {
|
||||||
|
if (containerKey == null || golemId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Set<UUID> golemIds = emptySkipGolemsByContainerKey.get(containerKey);
|
||||||
|
if (golemIds == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
golemIds.remove(golemId);
|
||||||
|
if (golemIds.isEmpty()) {
|
||||||
|
emptySkipGolemsByContainerKey.remove(containerKey, golemIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleCloseFinalize(UUID golemId, OpenInteraction interaction, TransactionKey containerKey, int attempt) {
|
||||||
|
plugin.getServer().getScheduler().runTaskLater(plugin, () -> finalizeContainerClose(golemId, interaction, containerKey, attempt), CLOSE_FINALIZE_DELAY_TICKS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleUntrackedCopperChestCloseFinalize(UUID golemId, Location containerLocation, Material containerType, int attempt) {
|
||||||
|
plugin.getServer().getScheduler().runTaskLater(plugin, () -> finalizeUntrackedCopperChestClose(golemId, containerLocation, containerType, attempt), CLOSE_FINALIZE_DELAY_TICKS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finalizeContainerClose(UUID golemId, OpenInteraction interaction, TransactionKey containerKey, int attempt) {
|
||||||
|
Entity entity = plugin.getServer().getEntity(golemId);
|
||||||
|
if (!(entity instanceof CopperGolem)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockState blockState = interaction.location.getBlock().getState();
|
||||||
|
if (!(blockState instanceof InventoryHolder)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Inventory inventory = ((InventoryHolder) blockState).getInventory();
|
||||||
|
if (inventory == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack[] currentContents = inventory.getContents();
|
||||||
|
if (currentContents == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean changed = hasInventoryChanged(interaction.baselineState, currentContents);
|
||||||
|
if (!changed) {
|
||||||
|
if (attempt < CLOSE_FINALIZE_MAX_ATTEMPTS) {
|
||||||
|
scheduleCloseFinalize(golemId, interaction, containerKey, attempt + 1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CopperGolem golem = (CopperGolem) entity;
|
||||||
|
if (!isAttributableToGolem(golem, interaction, currentContents)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
recordForcedContainerState(interaction.location, currentContents);
|
||||||
|
InventoryChangeListener.inventoryTransaction(USERNAME, interaction.location, interaction.baselineState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finalizeUntrackedCopperChestClose(UUID golemId, Location containerLocation, Material containerType, int attempt) {
|
||||||
|
Entity entity = plugin.getServer().getEntity(golemId);
|
||||||
|
if (!(entity instanceof CopperGolem)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCopperChest(containerType)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CopperGolem golem = (CopperGolem) entity;
|
||||||
|
ItemStack heldNowStack = getHeldItemStack(golem);
|
||||||
|
if (isEmptyItem(heldNowStack) || heldNowStack == null || heldNowStack.getAmount() <= 0) {
|
||||||
|
if (attempt < CLOSE_FALLBACK_MAX_ATTEMPTS) {
|
||||||
|
scheduleUntrackedCopperChestCloseFinalize(golemId, containerLocation, containerType, attempt + 1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int heldAmount = heldNowStack.getAmount();
|
||||||
|
if (heldAmount > 16) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockState blockState = containerLocation.getBlock().getState();
|
||||||
|
if (!(blockState instanceof InventoryHolder) || !isCopperChest(blockState.getType())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Inventory inventory = ((InventoryHolder) blockState).getInventory();
|
||||||
|
if (inventory == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack[] currentContents = inventory.getContents();
|
||||||
|
if (currentContents == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack[] baselineCandidate = reconstructCopperChestBaseline(currentContents, heldNowStack);
|
||||||
|
if (baselineCandidate == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
recordForcedContainerState(containerLocation, currentContents);
|
||||||
|
InventoryChangeListener.inventoryTransaction(USERNAME, containerLocation, baselineCandidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAttributableToGolem(CopperGolem golem, OpenInteraction interaction, ItemStack[] currentContents) {
|
||||||
|
boolean isCopperChest = isCopperChest(interaction.containerType);
|
||||||
|
HeldItemSnapshot heldNow = getHeldItemSnapshot(golem);
|
||||||
|
|
||||||
|
if (isCopperChest) {
|
||||||
|
if (heldNow.material == null || heldNow.amount <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int baselineTotal = sumAllItems(interaction.baselineState);
|
||||||
|
int currentTotal = sumAllItems(currentContents);
|
||||||
|
int totalRemoved = baselineTotal - currentTotal;
|
||||||
|
if (totalRemoved != heldNow.amount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int removedSameMaterial = sumMaterialAmount(interaction.baselineState, heldNow.material) - sumMaterialAmount(currentContents, heldNow.material);
|
||||||
|
return removedSameMaterial == heldNow.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
Material heldMaterial = interaction.heldMaterial;
|
||||||
|
if (heldMaterial == null || interaction.heldAmount <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!containsOnlyMaterial(currentContents, heldMaterial)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int baselineCount = sumMaterialAmount(interaction.baselineState, heldMaterial);
|
||||||
|
int currentCount = sumMaterialAmount(currentContents, heldMaterial);
|
||||||
|
int added = currentCount - baselineCount;
|
||||||
|
if (added <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int heldAfter = (heldNow.material == heldMaterial ? heldNow.amount : 0);
|
||||||
|
int removedFromGolem = interaction.heldAmount - heldAfter;
|
||||||
|
return added == removedFromGolem;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack getHeldItemStack(CopperGolem copperGolem) {
|
||||||
|
if (copperGolem == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
EntityEquipment equipment = copperGolem.getEquipment();
|
||||||
|
if (equipment == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ItemStack mainHand = equipment.getItemInMainHand();
|
||||||
|
if (isEmptyItem(mainHand)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return mainHand.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack[] reconstructCopperChestBaseline(ItemStack[] currentContents, ItemStack heldNowStack) {
|
||||||
|
if (currentContents == null || heldNowStack == null || isEmptyItem(heldNowStack) || heldNowStack.getAmount() <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack[] baseline = ItemUtils.getContainerState(currentContents);
|
||||||
|
if (baseline == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int addAmount = heldNowStack.getAmount();
|
||||||
|
int maxStack = heldNowStack.getMaxStackSize();
|
||||||
|
|
||||||
|
for (int i = 0; i < baseline.length; i++) {
|
||||||
|
ItemStack item = baseline[i];
|
||||||
|
if (item == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (item.isSimilar(heldNowStack)) {
|
||||||
|
if (item.getAmount() + addAmount > maxStack) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
item.setAmount(item.getAmount() + addAmount);
|
||||||
|
return baseline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < baseline.length; i++) {
|
||||||
|
ItemStack item = baseline[i];
|
||||||
|
if (isEmptyItem(item)) {
|
||||||
|
ItemStack placed = heldNowStack.clone();
|
||||||
|
placed.setAmount(addAmount);
|
||||||
|
baseline[i] = placed;
|
||||||
|
return baseline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recordForcedContainerState(Location location, ItemStack[] contents) {
|
||||||
|
if (location == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack[] snapshot = ItemUtils.getContainerState(contents);
|
||||||
|
if (snapshot == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String loggingContainerId = USERNAME + "." + location.getBlockX() + "." + location.getBlockY() + "." + location.getBlockZ();
|
||||||
|
|
||||||
|
List<ItemStack[]> forceList = ConfigHandler.forceContainer.get(loggingContainerId);
|
||||||
|
List<ItemStack[]> oldList = ConfigHandler.oldContainer.get(loggingContainerId);
|
||||||
|
|
||||||
|
boolean hasPendingBaseline = oldList != null && !oldList.isEmpty();
|
||||||
|
boolean hasStaleForceSnapshots = forceList != null && !forceList.isEmpty() && (forceList.get(0) == null || forceList.get(0).length != snapshot.length);
|
||||||
|
|
||||||
|
if (!hasPendingBaseline || hasStaleForceSnapshots) {
|
||||||
|
ConfigHandler.forceContainer.remove(loggingContainerId);
|
||||||
|
forceList = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forceList == null) {
|
||||||
|
forceList = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
ConfigHandler.forceContainer.put(loggingContainerId, forceList);
|
||||||
|
}
|
||||||
|
|
||||||
|
forceList.add(snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cleanupOpenInteractions(long nowMillis) {
|
||||||
|
long last = lastCleanupMillis;
|
||||||
|
if (nowMillis - last < CLEANUP_INTERVAL_MILLIS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastCleanupMillis = nowMillis;
|
||||||
|
|
||||||
|
for (Map.Entry<UUID, OpenInteraction> entry : openInteractions.entrySet()) {
|
||||||
|
UUID golemId = entry.getKey();
|
||||||
|
OpenInteraction interaction = entry.getValue();
|
||||||
|
if (interaction == null || nowMillis - interaction.openedAtMillis > OPEN_INTERACTION_TIMEOUT_MILLIS || plugin.getServer().getEntity(golemId) == null) {
|
||||||
|
if (openInteractions.remove(golemId, interaction) && interaction != null) {
|
||||||
|
openInteractionIndexByContainerKey.remove(interaction.containerKey, new OpenInteractionIndex(golemId, interaction.openedAtMillis));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<UUID, RecentEmptyCopperChestSkip> entry : recentEmptyCopperChestSkips.entrySet()) {
|
||||||
|
UUID golemId = entry.getKey();
|
||||||
|
RecentEmptyCopperChestSkip skip = entry.getValue();
|
||||||
|
if (skip == null || nowMillis - skip.skippedAtMillis > EMPTY_COPPER_CHEST_SKIP_TTL_MILLIS || plugin.getServer().getEntity(golemId) == null) {
|
||||||
|
if (recentEmptyCopperChestSkips.remove(golemId, skip) && skip != null) {
|
||||||
|
unindexEmptySkip(skip.containerKey, golemId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasInventoryChanged(ItemStack[] previousState, ItemStack[] currentState) {
|
||||||
|
if (previousState == null || currentState == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (previousState.length != currentState.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < previousState.length; i++) {
|
||||||
|
ItemStack previousItem = previousState[i];
|
||||||
|
ItemStack currentItem = currentState[i];
|
||||||
|
|
||||||
|
if (previousItem == null && currentItem == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousItem == null || currentItem == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!previousItem.equals(currentItem)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isInventoryEmpty(ItemStack[] state) {
|
||||||
|
if (state == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ItemStack item : state) {
|
||||||
|
if (!isEmptyItem(item)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEmptyItem(ItemStack item) {
|
||||||
|
return item == null || item.getType().isAir() || item.getAmount() <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean containsOnlyMaterial(ItemStack[] state, Material material) {
|
||||||
|
if (state == null || material == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (ItemStack item : state) {
|
||||||
|
if (isEmptyItem(item)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (item.getType() != material) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasSpaceForMaterial(ItemStack[] state, Material material) {
|
||||||
|
if (state == null || material == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int maxStackSize = material.getMaxStackSize();
|
||||||
|
for (ItemStack item : state) {
|
||||||
|
if (isEmptyItem(item)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (item.getType() == material && item.getAmount() < maxStackSize) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int sumMaterialAmount(ItemStack[] state, Material material) {
|
||||||
|
if (state == null || material == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int total = 0;
|
||||||
|
for (ItemStack item : state) {
|
||||||
|
if (!isEmptyItem(item) && item.getType() == material) {
|
||||||
|
total += item.getAmount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int sumAllItems(ItemStack[] state) {
|
||||||
|
if (state == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int total = 0;
|
||||||
|
for (ItemStack item : state) {
|
||||||
|
if (!isEmptyItem(item)) {
|
||||||
|
total += item.getAmount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HeldItemSnapshot getHeldItemSnapshot(CopperGolem copperGolem) {
|
||||||
|
if (copperGolem == null) {
|
||||||
|
return HeldItemSnapshot.EMPTY;
|
||||||
|
}
|
||||||
|
EntityEquipment equipment = copperGolem.getEquipment();
|
||||||
|
if (equipment == null) {
|
||||||
|
return HeldItemSnapshot.EMPTY;
|
||||||
|
}
|
||||||
|
ItemStack mainHand = equipment.getItemInMainHand();
|
||||||
|
if (isEmptyItem(mainHand)) {
|
||||||
|
return HeldItemSnapshot.EMPTY;
|
||||||
|
}
|
||||||
|
return new HeldItemSnapshot(mainHand.getType(), mainHand.getAmount());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class OpenInteraction {
|
||||||
|
|
||||||
|
private final TransactionKey containerKey;
|
||||||
|
private final Location location;
|
||||||
|
private final Material containerType;
|
||||||
|
private final ItemStack[] baselineState;
|
||||||
|
private final Material heldMaterial;
|
||||||
|
private final int heldAmount;
|
||||||
|
private final long openedAtMillis;
|
||||||
|
|
||||||
|
private OpenInteraction(TransactionKey containerKey, Location location, Material containerType, ItemStack[] baselineState, Material heldMaterial, int heldAmount, long openedAtMillis) {
|
||||||
|
this.containerKey = containerKey;
|
||||||
|
this.location = location;
|
||||||
|
this.containerType = containerType;
|
||||||
|
this.baselineState = baselineState;
|
||||||
|
this.heldMaterial = heldMaterial;
|
||||||
|
this.heldAmount = heldAmount;
|
||||||
|
this.openedAtMillis = openedAtMillis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class RecentEmptyCopperChestSkip {
|
||||||
|
|
||||||
|
private final TransactionKey containerKey;
|
||||||
|
private final long skippedAtMillis;
|
||||||
|
|
||||||
|
private RecentEmptyCopperChestSkip(TransactionKey containerKey, long skippedAtMillis) {
|
||||||
|
this.containerKey = containerKey;
|
||||||
|
this.skippedAtMillis = skippedAtMillis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class HeldItemSnapshot {
|
||||||
|
|
||||||
|
private static final HeldItemSnapshot EMPTY = new HeldItemSnapshot(null, 0);
|
||||||
|
|
||||||
|
private final Material material;
|
||||||
|
private final int amount;
|
||||||
|
|
||||||
|
private HeldItemSnapshot(Material material, int amount) {
|
||||||
|
this.material = material;
|
||||||
|
this.amount = amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class TransactionKey {
|
||||||
|
|
||||||
|
private final UUID worldId;
|
||||||
|
private final int x;
|
||||||
|
private final int y;
|
||||||
|
private final int z;
|
||||||
|
|
||||||
|
private TransactionKey(UUID worldId, int x, int y, int z) {
|
||||||
|
this.worldId = worldId;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.z = z;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TransactionKey of(Location location) {
|
||||||
|
if (location == null || location.getWorld() == null) {
|
||||||
|
throw new IllegalArgumentException("Location must have world");
|
||||||
|
}
|
||||||
|
return new TransactionKey(location.getWorld().getUID(), location.getBlockX(), location.getBlockY(), location.getBlockZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof TransactionKey)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
TransactionKey other = (TransactionKey) obj;
|
||||||
|
return worldId.equals(other.worldId) && x == other.x && y == other.y && z == other.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(worldId, x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class OpenInteractionIndex {
|
||||||
|
|
||||||
|
private final UUID golemId;
|
||||||
|
private final long openedAtMillis;
|
||||||
|
|
||||||
|
private OpenInteractionIndex(UUID golemId, long openedAtMillis) {
|
||||||
|
this.golemId = golemId;
|
||||||
|
this.openedAtMillis = openedAtMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof OpenInteractionIndex)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
OpenInteractionIndex other = (OpenInteractionIndex) obj;
|
||||||
|
return openedAtMillis == other.openedAtMillis && golemId.equals(other.golemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(golemId, openedAtMillis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,6 @@ import org.bukkit.Location;
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.scheduler.BukkitTask;
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
|
||||||
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
|
|
||||||
import net.coreprotect.CoreProtect;
|
import net.coreprotect.CoreProtect;
|
||||||
import net.coreprotect.config.ConfigHandler;
|
import net.coreprotect.config.ConfigHandler;
|
||||||
|
|
||||||
|
|
@ -116,8 +115,8 @@ public class Scheduler {
|
||||||
|
|
||||||
public static void cancelTask(Object task) {
|
public static void cancelTask(Object task) {
|
||||||
if (ConfigHandler.isFolia) {
|
if (ConfigHandler.isFolia) {
|
||||||
if (task instanceof ScheduledTask) {
|
if (task instanceof io.papermc.paper.threadedregions.scheduler.ScheduledTask) {
|
||||||
ScheduledTask scheduledTask = (ScheduledTask) task;
|
io.papermc.paper.threadedregions.scheduler.ScheduledTask scheduledTask = (io.papermc.paper.threadedregions.scheduler.ScheduledTask) task;
|
||||||
scheduledTask.cancel();
|
scheduledTask.cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -196,4 +196,9 @@ public final class WorldEditBlockState implements BlockState {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSuffocating() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue