mirror of
https://github.com/PlayPro/CoreProtect.git
synced 2026-10-28 09:44:56 +00:00
Added entity interaction logging
This commit is contained in:
1 parent
c25621622e
commit
3d1594e116
31 files changed
+1332
-51
No files matched your search
@@ -10,6 +10,7 @@ import org.bukkit.entity.EntityType;
|
||||
import net.coreprotect.api.SessionLookup;
|
||||
import net.coreprotect.model.action.LookupActions;
|
||||
import net.coreprotect.model.action.SessionActions;
|
||||
import net.coreprotect.model.item.InventorySources;
|
||||
import net.coreprotect.utility.BlockUtils;
|
||||
import net.coreprotect.utility.BlockTypeUtils;
|
||||
import net.coreprotect.utility.EntityUtils;
|
||||
@@ -137,6 +138,9 @@ public class ParseResult implements CoreProtectResult {
|
||||
}
|
||||
|
||||
private boolean isEntityAction(int actionId) {
|
||||
return actionId == LookupActions.ENTITY_KILL || actionId == LookupActions.ENTITY_SPAWN;
|
||||
if (actionId == LookupActions.ENTITY_KILL || actionId == LookupActions.ENTITY_SPAWN) {
|
||||
return true;
|
||||
}
|
||||
return actionId == LookupActions.INTERACTION && parse.length > 14 && parse[13] != null && Integer.parseInt(parse[13]) == InventorySources.ENTITY_INTERACTION;
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import org.bukkit.entity.Player;
|
||||
|
||||
import net.coreprotect.command.lookup.BlockLookupThread;
|
||||
import net.coreprotect.command.lookup.ChestTransactionLookupThread;
|
||||
import net.coreprotect.command.lookup.EntityInteractionLookupThread;
|
||||
import net.coreprotect.command.lookup.StandardLookupThread;
|
||||
import net.coreprotect.command.parser.ActionParser;
|
||||
import net.coreprotect.command.parser.MessageFilterParser;
|
||||
@@ -89,7 +90,7 @@ public class LookupCommand {
|
||||
argAction.add(LookupActions.ENTITY_KILL);
|
||||
argAction.add(LookupActions.ENTITY_SPAWN);
|
||||
}
|
||||
else if (!argEntityActionFilter.includesAnyEntity(argAction, true)) {
|
||||
else if (!argEntityActionFilter.includesAnyEntity(argAction, true) && !argAction.contains(LookupActions.INTERACTION)) {
|
||||
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.INVALID_INCLUDE_COMBO));
|
||||
return;
|
||||
}
|
||||
@@ -107,7 +108,7 @@ public class LookupCommand {
|
||||
argAction.add(LookupActions.ENTITY_KILL);
|
||||
argAction.add(LookupActions.ENTITY_SPAWN);
|
||||
}
|
||||
else if (!argEntityActionFilter.includesAnyEntity(argAction, true)) {
|
||||
else if (!argEntityActionFilter.includesAnyEntity(argAction, true) && !argAction.contains(LookupActions.INTERACTION)) {
|
||||
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.INVALID_INCLUDE_COMBO));
|
||||
return;
|
||||
}
|
||||
@@ -367,7 +368,7 @@ public class LookupCommand {
|
||||
Thread thread = new Thread(runnable);
|
||||
thread.start();
|
||||
}
|
||||
else if (type == 2 || type == 3 || type == 7 || type == 8) {
|
||||
else if (type == 2 || type == 3 || type == 7 || type == 8 || type == 9) {
|
||||
boolean defaultRe = true;
|
||||
int page = 1;
|
||||
int re = 7;
|
||||
@@ -409,6 +410,13 @@ public class LookupCommand {
|
||||
}
|
||||
|
||||
// String bc = x+"."+y+"."+z+"."+wid+"."+rstring+"."+lookup_user;
|
||||
if (type == 9) {
|
||||
Runnable runnable = new EntityInteractionLookupThread(player, command, page, re);
|
||||
Thread thread = new Thread(runnable);
|
||||
thread.start();
|
||||
return;
|
||||
}
|
||||
|
||||
String lcommand = ConfigHandler.lookupCommand.get(player.getName());
|
||||
String[] data = lcommand.split("\\.");
|
||||
int x = Integer.parseInt(data[0]);
|
||||
|
||||
@@ -37,7 +37,7 @@ import net.coreprotect.utility.ErrorReporter;
|
||||
|
||||
public class PurgeCommand extends Consumer {
|
||||
|
||||
public static final List<String> PURGE_TABLES = Arrays.asList("sign", "container", "entity_container", "item", "skull", "session", "chat", "command", "entity", "block");
|
||||
public static final List<String> PURGE_TABLES = Arrays.asList("sign", "container", "entity_container", "entity_interaction", "item", "skull", "session", "chat", "command", "entity", "block");
|
||||
|
||||
private static String findUnsupportedPurgeArgument(String[] args) {
|
||||
boolean includeContinuation = false;
|
||||
@@ -340,7 +340,7 @@ public class PurgeCommand extends Consumer {
|
||||
Database.createDatabaseTables(purgePrefix, false, null, Config.getGlobal().MYSQL, true);
|
||||
}
|
||||
|
||||
List<String> worldTables = Arrays.asList("sign", "container", "entity_container", "item", "session", "chat", "command", "block");
|
||||
List<String> worldTables = Arrays.asList("sign", "container", "entity_container", "entity_interaction", "item", "session", "chat", "command", "block");
|
||||
List<String> restrictTables = Arrays.asList("block");
|
||||
List<String> excludeTables = Arrays.asList("database_lock"); // don't insert data into these tables
|
||||
for (String table : ConfigHandler.databaseTables) {
|
||||
@@ -375,7 +375,7 @@ public class PurgeCommand extends Consumer {
|
||||
boolean purge = true;
|
||||
String timeLimit = "";
|
||||
if (table.equals("entity_spawn")) {
|
||||
timeLimit = " WHERE removed=0 OR block_rowid IN(SELECT rowid FROM " + purgePrefix + "block) OR kill_rowid IN(SELECT rowid FROM " + purgePrefix + "entity) OR rowid IN(SELECT entity_spawn_rowid FROM " + purgePrefix + "entity_container)";
|
||||
timeLimit = " WHERE removed=0 OR block_rowid IN(SELECT rowid FROM " + purgePrefix + "block) OR kill_rowid IN(SELECT rowid FROM " + purgePrefix + "entity) OR rowid IN(SELECT entity_spawn_rowid FROM " + purgePrefix + "entity_container) OR rowid IN(SELECT entity_spawn_rowid FROM " + purgePrefix + "entity_interaction)";
|
||||
}
|
||||
else if (PURGE_TABLES.contains(table)) {
|
||||
String blockRestriction = "(";
|
||||
@@ -387,7 +387,7 @@ public class PurgeCommand extends Consumer {
|
||||
}
|
||||
|
||||
if (argWid > 0 && worldTables.contains(table)) {
|
||||
if (table.equals("entity_container")) {
|
||||
if (table.equals("entity_container") || table.equals("entity_interaction")) {
|
||||
if (purge) {
|
||||
String worldMatch = "(wid = '" + argWid + "' OR entity_spawn_rowid IN(SELECT rowid FROM " + ConfigHandler.prefix + "entity_spawn WHERE current_wid = '" + argWid + "'))";
|
||||
timeLimit = " WHERE (" + worldMatch + " AND (time >= '" + timeEnd + "' OR time < '" + timeStart + "')) OR NOT " + worldMatch;
|
||||
@@ -462,7 +462,7 @@ public class PurgeCommand extends Consumer {
|
||||
|
||||
String worldRestriction = "";
|
||||
if (argWid > 0 && worldTables.contains(table)) {
|
||||
if (table.equals("entity_container")) {
|
||||
if (table.equals("entity_container") || table.equals("entity_interaction")) {
|
||||
worldRestriction = " AND (wid = '" + argWid + "' OR entity_spawn_rowid IN(SELECT rowid FROM " + ConfigHandler.prefix + "entity_spawn WHERE current_wid = '" + argWid + "'))";
|
||||
}
|
||||
else {
|
||||
@@ -534,7 +534,7 @@ public class PurgeCommand extends Consumer {
|
||||
|
||||
String worldRestriction = "";
|
||||
if (argWid > 0 && worldTables.contains(table)) {
|
||||
if (table.equals("entity_container")) {
|
||||
if (table.equals("entity_container") || table.equals("entity_interaction")) {
|
||||
worldRestriction = " AND (wid = '" + argWid + "' OR entity_spawn_rowid IN(SELECT rowid FROM " + ConfigHandler.prefix + "entity_spawn WHERE current_wid = '" + argWid + "'))";
|
||||
}
|
||||
else {
|
||||
@@ -575,7 +575,19 @@ public class PurgeCommand extends Consumer {
|
||||
preparedStmt.executeUpdate();
|
||||
preparedStmt.close();
|
||||
|
||||
query = "DELETE FROM " + retainedPrefix + "entity_spawn WHERE removed=1 AND block_rowid IS NULL AND kill_rowid IS NULL AND NOT EXISTS (SELECT 1 FROM " + retainedPrefix + "entity_container WHERE " + retainedPrefix + "entity_container.entity_spawn_rowid=" + retainedPrefix + "entity_spawn.rowid)";
|
||||
query = "DELETE FROM " + retainedPrefix + "entity_interaction WHERE NOT EXISTS (SELECT 1 FROM " + retainedPrefix + "entity_spawn WHERE " + retainedPrefix + "entity_spawn.rowid=" + retainedPrefix + "entity_interaction.entity_spawn_rowid)";
|
||||
preparedStmt = connection.prepareStatement(query);
|
||||
preparedStmt.executeUpdate();
|
||||
removed = removed + preparedStmt.getUpdateCount();
|
||||
preparedStmt.close();
|
||||
|
||||
query = "DELETE FROM " + retainedPrefix + "entity_container WHERE NOT EXISTS (SELECT 1 FROM " + retainedPrefix + "entity_spawn WHERE " + retainedPrefix + "entity_spawn.rowid=" + retainedPrefix + "entity_container.entity_spawn_rowid)";
|
||||
preparedStmt = connection.prepareStatement(query);
|
||||
preparedStmt.executeUpdate();
|
||||
removed = removed + preparedStmt.getUpdateCount();
|
||||
preparedStmt.close();
|
||||
|
||||
query = "DELETE FROM " + retainedPrefix + "entity_spawn WHERE removed=1 AND block_rowid IS NULL AND kill_rowid IS NULL AND NOT EXISTS (SELECT 1 FROM " + retainedPrefix + "entity_container WHERE " + retainedPrefix + "entity_container.entity_spawn_rowid=" + retainedPrefix + "entity_spawn.rowid) AND NOT EXISTS (SELECT 1 FROM " + retainedPrefix + "entity_interaction WHERE " + retainedPrefix + "entity_interaction.entity_spawn_rowid=" + retainedPrefix + "entity_spawn.rowid)";
|
||||
preparedStmt = connection.prepareStatement(query);
|
||||
preparedStmt.executeUpdate();
|
||||
removed = removed + preparedStmt.getUpdateCount();
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package net.coreprotect.command.lookup;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.Statement;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import net.coreprotect.config.ConfigHandler;
|
||||
import net.coreprotect.database.Database;
|
||||
import net.coreprotect.database.lookup.EntityInteractionLookup;
|
||||
import net.coreprotect.language.Phrase;
|
||||
import net.coreprotect.utility.Chat;
|
||||
import net.coreprotect.utility.Color;
|
||||
import net.coreprotect.utility.ErrorReporter;
|
||||
|
||||
public final class EntityInteractionLookupThread implements Runnable {
|
||||
private final CommandSender player;
|
||||
private final Command command;
|
||||
private final int page;
|
||||
private final int limit;
|
||||
|
||||
public EntityInteractionLookupThread(CommandSender player, Command command, int page, int limit) {
|
||||
this.player = player;
|
||||
this.command = command;
|
||||
this.page = page;
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try (Connection connection = Database.getConnection(true)) {
|
||||
ConfigHandler.lookupThrottle.put(player.getName(), new Object[] { true, System.currentTimeMillis() });
|
||||
if (connection == null) {
|
||||
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.DATABASE_BUSY));
|
||||
return;
|
||||
}
|
||||
|
||||
Integer entitySpawnRowId = ConfigHandler.lookupEntityInteraction.get(player.getName());
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
List<String> results = EntityInteractionLookup.performLookup(command.getName(), statement, player, page, limit, entitySpawnRowId);
|
||||
for (String result : results) {
|
||||
Chat.sendComponent(player, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
ErrorReporter.report(e);
|
||||
}
|
||||
finally {
|
||||
ConfigHandler.lookupThrottle.put(player.getName(), new Object[] { false, System.currentTimeMillis() });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import com.google.common.base.Strings;
|
||||
import net.coreprotect.config.ConfigHandler;
|
||||
import net.coreprotect.database.Database;
|
||||
import net.coreprotect.database.Lookup;
|
||||
import net.coreprotect.database.lookup.EntityInteractionLookup;
|
||||
import net.coreprotect.database.lookup.PlayerLookup;
|
||||
import net.coreprotect.database.statement.EntitySpawnStatement;
|
||||
import net.coreprotect.database.statement.UserStatement;
|
||||
@@ -126,6 +127,7 @@ public class StandardLookupThread implements Runnable {
|
||||
|
||||
List<String> uuidList = new ArrayList<>();
|
||||
Integer entityContainerId = actions.contains(5) ? ConfigHandler.lookupEntityContainer.get(player.getName()) : null;
|
||||
ConfigHandler.lookupEntityInteraction.remove(player.getName());
|
||||
if (!actions.contains(5)) {
|
||||
ConfigHandler.lookupEntityContainer.remove(player.getName());
|
||||
}
|
||||
@@ -212,8 +214,9 @@ public class StandardLookupThread implements Runnable {
|
||||
Set<UUID> loadedEntityCandidates = Collections.emptySet();
|
||||
boolean includeEntitySpawns = entityActionFilter.includesAnySpawn(actions, true);
|
||||
boolean includeEntityContainers = entityContainerId != null || actions.contains(LookupActions.CONTAINER) || LookupActions.isInventoryLookup(actions) || actions.isEmpty();
|
||||
if ((includeEntitySpawns || includeEntityContainers) && radius != null && finalLocation != null && finalLocation.getWorld() != null) {
|
||||
Set<UUID> databaseCandidates = includeEntityContainers ? EntitySpawnStatement.loadActiveUuids(connection, finalLocation, radius) : EntitySpawnStatement.loadActiveUuids(connection, finalLocation, radius, timeStart, timeEnd);
|
||||
boolean includeEntityInteractions = actions.isEmpty() || actions.contains(LookupActions.INTERACTION);
|
||||
if ((includeEntitySpawns || includeEntityContainers || includeEntityInteractions) && radius != null && finalLocation != null && finalLocation.getWorld() != null) {
|
||||
Set<UUID> databaseCandidates = includeEntityContainers || includeEntityInteractions ? EntitySpawnStatement.loadActiveUuids(connection, finalLocation, radius) : EntitySpawnStatement.loadActiveUuids(connection, finalLocation, radius, timeStart, timeEnd);
|
||||
EntitySpawnTracking.LoadedEntityRadius loadedEntities = EntitySpawnTracking.findLoadedEntities(finalLocation, radius, databaseCandidates);
|
||||
loadedEntityUuids = loadedEntities.getInside();
|
||||
loadedEntityCandidates = loadedEntities.getLoadedCandidates();
|
||||
@@ -289,6 +292,9 @@ public class StandardLookupThread implements Runnable {
|
||||
if (data.length > 14 && data[13] != null && data[14] != null && Integer.parseInt(data[13]) == InventorySources.ENTITY_CONTAINER) {
|
||||
entitySpawnRowIds.add(Integer.parseInt(data[14]));
|
||||
}
|
||||
if (data.length > 14 && data[13] != null && data[14] != null && Integer.parseInt(data[13]) == InventorySources.ENTITY_INTERACTION) {
|
||||
entitySpawnRowIds.add(Integer.parseInt(data[14]));
|
||||
}
|
||||
}
|
||||
if (!entitySpawnRowIds.isEmpty()) {
|
||||
entitySpawnRecords = EntitySpawnStatement.loadLocationRecords(connection, entitySpawnRowIds);
|
||||
@@ -462,6 +468,7 @@ public class StandardLookupThread implements Runnable {
|
||||
int dtype = Integer.parseInt(data[5]);
|
||||
int ddata = Integer.parseInt(data[6]);
|
||||
int daction = Integer.parseInt(data[7]);
|
||||
boolean entityInteraction = data[13] != null && Integer.parseInt(data[13]) == InventorySources.ENTITY_INTERACTION;
|
||||
boolean placedEntitySpawn = daction == LookupActions.ENTITY_SPAWN && EntitySpawnTracking.isPlacedEntityType(EntityUtils.getEntityType(dtype));
|
||||
boolean placedEntityKill = daction == LookupActions.ENTITY_KILL && EntitySpawnTracking.isPlacedEntityType(EntityUtils.getEntityType(dtype));
|
||||
int wid = Integer.parseInt(data[9]);
|
||||
@@ -487,7 +494,10 @@ public class StandardLookupThread implements Runnable {
|
||||
|
||||
String dname = "";
|
||||
boolean isPlayer = false;
|
||||
if ((daction == LookupActions.ENTITY_KILL || daction == LookupActions.ENTITY_SPAWN) && !actions.contains(LookupActions.ITEM) && amount == -1) {
|
||||
if (entityInteraction) {
|
||||
dname = EntityInteractionLookup.entityName(dtype);
|
||||
}
|
||||
else if ((daction == LookupActions.ENTITY_KILL || daction == LookupActions.ENTITY_SPAWN) && !actions.contains(LookupActions.ITEM) && amount == -1) {
|
||||
if (daction == LookupActions.ENTITY_KILL && dtype == 0) {
|
||||
if (ConfigHandler.playerIdCacheReversed.get(ddata) == null) {
|
||||
UserStatement.loadName(connection, ddata);
|
||||
@@ -551,7 +561,12 @@ public class StandardLookupThread implements Runnable {
|
||||
PluginChannelListener.getInstance().sendData(player, Integer.parseInt(time), phrase, selector, dplayer, dname, (tag.contains("+") ? 1 : -1), dataX, dataY, dataZ, wid, rbd, action.contains("container"), tag.contains("+"));
|
||||
}
|
||||
else {
|
||||
if (daction == LookupActions.ENTITY_SPAWN) {
|
||||
if (entityInteraction) {
|
||||
phrase = Phrase.LOOKUP_ENTITY_INTERACTION;
|
||||
selector = EntityInteractionLookup.actionSelector(ddata);
|
||||
action = "a:click";
|
||||
}
|
||||
else if (daction == LookupActions.ENTITY_SPAWN) {
|
||||
phrase = placedEntitySpawn ? Phrase.LOOKUP_BLOCK : Phrase.LOOKUP_ENTITY_SPAWN;
|
||||
selector = Selector.FIRST;
|
||||
tag = Color.GREEN + "+";
|
||||
@@ -669,7 +684,7 @@ public class StandardLookupThread implements Runnable {
|
||||
}
|
||||
|
||||
int trackingRowId = 0;
|
||||
if (data[13] != null && Integer.parseInt(data[13]) == InventorySources.ENTITY_CONTAINER && data[14] != null) {
|
||||
if (data[13] != null && (Integer.parseInt(data[13]) == InventorySources.ENTITY_CONTAINER || Integer.parseInt(data[13]) == InventorySources.ENTITY_INTERACTION) && data[14] != null) {
|
||||
trackingRowId = Integer.parseInt(data[14]);
|
||||
}
|
||||
else if (data[6] != null && Integer.parseInt(data[7]) == LookupActions.ENTITY_SPAWN) {
|
||||
@@ -696,7 +711,8 @@ public class StandardLookupThread implements Runnable {
|
||||
currentZ = loadedLocation.getBlockZ();
|
||||
}
|
||||
|
||||
return new EntityDisplayLocation(currentWorldId, currentX, currentY, currentZ, Integer.parseInt(data[9]), Integer.parseInt(data[2]), Integer.parseInt(data[3]), Integer.parseInt(data[4]));
|
||||
boolean interactionOrigin = data[13] != null && Integer.parseInt(data[13]) == InventorySources.ENTITY_INTERACTION;
|
||||
return new EntityDisplayLocation(currentWorldId, currentX, currentY, currentZ, Integer.parseInt(data[9]), Integer.parseInt(data[2]), Integer.parseInt(data[3]), Integer.parseInt(data[4]), interactionOrigin);
|
||||
}
|
||||
|
||||
private static final class EntityDisplayLocation {
|
||||
@@ -708,8 +724,9 @@ public class StandardLookupThread implements Runnable {
|
||||
private final int originX;
|
||||
private final int originY;
|
||||
private final int originZ;
|
||||
private final boolean interactionOrigin;
|
||||
|
||||
private EntityDisplayLocation(int worldId, int x, int y, int z, int originWorldId, int originX, int originY, int originZ) {
|
||||
private EntityDisplayLocation(int worldId, int x, int y, int z, int originWorldId, int originX, int originY, int originZ, boolean interactionOrigin) {
|
||||
this.worldId = worldId;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
@@ -718,13 +735,15 @@ public class StandardLookupThread implements Runnable {
|
||||
this.originX = originX;
|
||||
this.originY = originY;
|
||||
this.originZ = originZ;
|
||||
this.interactionOrigin = interactionOrigin;
|
||||
}
|
||||
|
||||
private String getOriginTooltip() {
|
||||
if (worldId == originWorldId && x == originX && y == originY && z == originZ) {
|
||||
return "";
|
||||
}
|
||||
return ChatUtils.getCoordinateTooltip(originWorldId, originX, originY, originZ, Phrase.build(Phrase.LOOKUP_ENTITY_ORIGIN), true);
|
||||
Phrase originPhrase = interactionOrigin ? Phrase.LOOKUP_ENTITY_INTERACTION_ORIGIN : Phrase.LOOKUP_ENTITY_ORIGIN;
|
||||
return ChatUtils.getCoordinateTooltip(originWorldId, originX, originY, originZ, Phrase.build(originPhrase), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,6 +136,7 @@ public class ConfigHandler extends Queue {
|
||||
public static Map<String, LookupOutputMode> lookupOutputMode = syncMap();
|
||||
public static Map<String, String> lookupCommand = syncMap();
|
||||
public static Map<String, Integer> lookupEntityContainer = syncMap();
|
||||
public static Map<String, Integer> lookupEntityInteraction = syncMap();
|
||||
public static Map<String, List<Object>> lookupBlist = syncMap();
|
||||
public static Map<String, Map<Object, Boolean>> lookupElist = syncMap();
|
||||
public static Map<String, List<String>> lookupEUserlist = syncMap();
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.bukkit.block.data.Bisected.Half;
|
||||
import org.bukkit.block.data.Waterlogged;
|
||||
import org.bukkit.block.data.type.Bed;
|
||||
import org.bukkit.block.data.type.Bed.Part;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
@@ -30,13 +31,17 @@ import net.coreprotect.listener.block.BlockUtil;
|
||||
import net.coreprotect.model.BlockGroup;
|
||||
import net.coreprotect.model.entity.EntityContainerRollbackUpdate;
|
||||
import net.coreprotect.model.entity.EntityContainerTransaction;
|
||||
import net.coreprotect.model.entity.EntityInteraction;
|
||||
import net.coreprotect.model.entity.EntityInteractionAction;
|
||||
import net.coreprotect.model.entity.EntityInteractionOrigin;
|
||||
import net.coreprotect.model.entity.EntitySpawnData;
|
||||
import net.coreprotect.thread.Scheduler;
|
||||
import net.coreprotect.thread.CacheHandler;
|
||||
import net.coreprotect.thread.Scheduler;
|
||||
import net.coreprotect.utility.BlockUtils;
|
||||
import net.coreprotect.utility.EntitySpawnTracking;
|
||||
import net.coreprotect.utility.EntityUtils;
|
||||
import net.coreprotect.utility.WorldUtils;
|
||||
import net.coreprotect.utility.ErrorReporter;
|
||||
import net.coreprotect.utility.WorldUtils;
|
||||
|
||||
public class Queue {
|
||||
|
||||
@@ -331,6 +336,30 @@ public class Queue {
|
||||
queueStandardData(new Object[] { null, Process.ENTITY_CONTAINER_TRANSACTION, null, 0, null, 0, 0, null }, new String[] { user, null }, transaction, false, Consumer.reserveConsumer());
|
||||
}
|
||||
|
||||
public static void queueEntityInteraction(String user, Entity entity, EntityInteractionAction action) {
|
||||
queueEntityInteraction(user, entity, action, null);
|
||||
}
|
||||
|
||||
public static void queueEntityInteraction(String user, Entity entity, EntityInteractionAction action, byte[] metadata) {
|
||||
if (user == null || user.trim().isEmpty() || entity == null || action == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Location currentLocation = entity.getLocation();
|
||||
EntityInteractionOrigin origin = EntitySpawnTracking.getOrCreateInteractionOrigin(entity);
|
||||
if (origin == null || currentLocation.getWorld() == null) {
|
||||
return;
|
||||
}
|
||||
queueEntityInteraction(user, new EntityInteraction(entity.getUniqueId(), entity.getType(), origin, currentLocation, action, metadata));
|
||||
}
|
||||
|
||||
public static void queueEntityInteraction(String user, EntityInteraction interaction) {
|
||||
if (user == null || user.trim().isEmpty() || interaction == null) {
|
||||
return;
|
||||
}
|
||||
queueStandardData(new Object[] { null, Process.ENTITY_INTERACTION, null, 0, null, 0, 0, null }, new String[] { user, null }, interaction, false, Consumer.reserveConsumer());
|
||||
}
|
||||
|
||||
protected static void queueItemTransaction(String user, Location location, int time, int offset, int itemId) {
|
||||
queueStandardData(new Object[] { null, Process.ITEM_TRANSACTION, null, offset, null, time, itemId, null }, new String[] { user, null }, location, false, Consumer.reserveConsumer());
|
||||
}
|
||||
|
||||
@@ -22,10 +22,13 @@ import net.coreprotect.config.ConfigHandler;
|
||||
import net.coreprotect.consumer.Consumer;
|
||||
import net.coreprotect.consumer.Queue;
|
||||
import net.coreprotect.database.Database;
|
||||
import net.coreprotect.database.logger.EntityInteractionLogger;
|
||||
import net.coreprotect.database.statement.EntityInteractionStatement;
|
||||
import net.coreprotect.database.statement.EntitySpawnStatement;
|
||||
import net.coreprotect.database.statement.UserStatement;
|
||||
import net.coreprotect.model.entity.EntityContainerRollbackUpdate;
|
||||
import net.coreprotect.model.entity.EntityContainerTransaction;
|
||||
import net.coreprotect.model.entity.EntityInteraction;
|
||||
import net.coreprotect.model.entity.EntitySpawnData;
|
||||
import net.coreprotect.model.entity.EntitySpawnIdentity;
|
||||
import net.coreprotect.model.rollback.RollbackUpdateTargets;
|
||||
@@ -67,6 +70,7 @@ public class Process {
|
||||
public static final int ENTITY_CONTAINER_TRANSACTION = 32;
|
||||
public static final int ENTITY_CONTAINER_ROLLBACK_UPDATE = 33;
|
||||
public static final int ENTITY_CONTAINER_TRANSITION_UPDATE = 34;
|
||||
public static final int ENTITY_INTERACTION = 35;
|
||||
|
||||
public static int lastLockUpdate = 0;
|
||||
private static volatile int currentConsumerSize = 0;
|
||||
@@ -104,6 +108,9 @@ public class Process {
|
||||
protected static void processConsumer(int processId, boolean lastRun) {
|
||||
Map<UUID, Location> pendingEntitySpawnLogs = new LinkedHashMap<>();
|
||||
Map<UUID, EntitySpawnIdentity> entitySpawnIdentities = new LinkedHashMap<>();
|
||||
Map<UUID, Location> pendingEntityIdentityConfirmations = new LinkedHashMap<>();
|
||||
Set<UUID> promotedEntityIdentities = new HashSet<>();
|
||||
List<PendingEntityInteraction> pendingEntityInteractions = new ArrayList<>();
|
||||
List<EntityContainerRollbackRetry> pendingEntityContainerRollbacks = new ArrayList<>();
|
||||
EntitySpawnStatement.Updates entitySpawnUpdates = null;
|
||||
ArrayList<Object[]> consumerData = null;
|
||||
@@ -138,6 +145,7 @@ public class Process {
|
||||
boolean hasEntitySpawnUpdates = false;
|
||||
boolean hasEntityKills = false;
|
||||
boolean hasEntityContainerTransactions = false;
|
||||
boolean hasEntityInteractions = false;
|
||||
Set<UUID> entityIdentityUuids = new HashSet<>();
|
||||
Set<Integer> entityIdentityRowIds = new HashSet<>();
|
||||
for (int index = 0; index < consumerDataSize; index++) {
|
||||
@@ -150,6 +158,7 @@ public class Process {
|
||||
hasEntitySpawnUpdates |= action == Process.ENTITY_SPAWN_UPDATE || action == Process.ENTITY_CONTAINER_TRANSITION_UPDATE;
|
||||
hasEntityKills |= action == Process.ENTITY_KILL;
|
||||
hasEntityContainerTransactions |= action == Process.ENTITY_CONTAINER_TRANSACTION;
|
||||
hasEntityInteractions |= action == Process.ENTITY_INTERACTION;
|
||||
|
||||
if (action == Process.ENTITY_CONTAINER_TRANSACTION) {
|
||||
Object object = consumerObject.get((int) data[0]);
|
||||
@@ -157,6 +166,12 @@ public class Process {
|
||||
entityIdentityUuids.add(((EntityContainerTransaction) object).getEntityUuid());
|
||||
}
|
||||
}
|
||||
else if (action == Process.ENTITY_INTERACTION) {
|
||||
Object object = consumerObject.get((int) data[0]);
|
||||
if (object instanceof EntityInteraction) {
|
||||
entityIdentityUuids.add(((EntityInteraction) object).getEntityUuid());
|
||||
}
|
||||
}
|
||||
else if (action == Process.ENTITY_SPAWN_UPDATE || action == Process.ENTITY_CONTAINER_TRANSITION_UPDATE) {
|
||||
Object object = consumerObject.get((int) data[0]);
|
||||
EntitySpawnData spawnData = getEntitySpawnUpdate(object);
|
||||
@@ -195,7 +210,7 @@ public class Process {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasEntityContainerTransactions) {
|
||||
if (hasEntityContainerTransactions || hasEntityInteractions) {
|
||||
entitySpawnIdentities.putAll(EntitySpawnStatement.loadIdentities(connection, entityIdentityUuids));
|
||||
Map<Integer, EntitySpawnIdentity> identitiesByRowId = EntitySpawnStatement.loadIdentitiesByRowIds(connection, entityIdentityRowIds);
|
||||
bindPendingEntitySpawnIdentities(consumerData, consumerObject, entitySpawnIdentities, identitiesByRowId);
|
||||
@@ -216,8 +231,10 @@ public class Process {
|
||||
PreparedStatement preparedStmtArt = Database.prepareStatement(connection, Database.ART, false);
|
||||
PreparedStatement preparedStmtEntity = Database.prepareStatement(connection, Database.ENTITY_MAP, false);
|
||||
PreparedStatement preparedStmtBlockdata = Database.prepareStatement(connection, Database.BLOCKDATA, false);
|
||||
PreparedStatement preparedStmtEntitySpawns = hasEntitySpawnLogs ? Database.prepareStatement(connection, Database.ENTITY_SPAWN, true) : null;
|
||||
PreparedStatement preparedStmtEntitySpawns = hasEntitySpawnLogs || hasEntityInteractions ? Database.prepareStatement(connection, Database.ENTITY_SPAWN, true) : null;
|
||||
PreparedStatement preparedStmtEntityContainers = hasEntityContainerTransactions ? Database.prepareStatement(connection, Database.ENTITY_CONTAINER, false) : null;
|
||||
PreparedStatement preparedStmtEntityInteractions = hasEntityInteractions ? Database.prepareStatement(connection, Database.ENTITY_INTERACTION, false) : null;
|
||||
PreparedStatement preparedStmtEntityInteractionCheckpoints = hasEntityInteractions ? EntityInteractionStatement.prepareCheckpoint(connection) : null;
|
||||
PreparedStatement preparedStmtEntitySpawnBlocks = hasEntitySpawnLogs ? Database.prepareStatement(connection, Database.BLOCK, true) : null;
|
||||
PreparedStatement preparedStmtEntitySpawnLinks = hasEntitySpawnLogs ? EntitySpawnStatement.prepareBlockLink(connection) : null;
|
||||
PreparedStatement preparedStmtEntityKillLinks = hasEntityKills ? EntitySpawnStatement.prepareKillLink(connection) : null;
|
||||
@@ -272,6 +289,36 @@ public class Process {
|
||||
retryEntityContainerTransaction(user, transaction);
|
||||
}
|
||||
break;
|
||||
case Process.ENTITY_INTERACTION:
|
||||
EntityInteraction interaction = (EntityInteraction) object;
|
||||
EntityInteractionLogger.LogContext logContext = EntityInteractionLogger.prepare(user, interaction);
|
||||
if (logContext == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
EntitySpawnIdentity existingIdentity = entitySpawnIdentities.get(interaction.getEntityUuid());
|
||||
EntitySpawnIdentity[] loggedIdentity = { existingIdentity };
|
||||
try {
|
||||
Database.executeSavepoint(statement, "entity_interaction_log", () -> {
|
||||
if (loggedIdentity[0] == null) {
|
||||
int time = (int) (System.currentTimeMillis() / 1000L);
|
||||
loggedIdentity[0] = EntitySpawnStatement.insertIdentity(preparedStmtEntitySpawns, time, interaction.getEntityUuid(), interaction.getOrigin(), interaction.getCurrentLocation());
|
||||
}
|
||||
EntityInteractionLogger.log(preparedStmtEntityInteractions, preparedStmtEntityInteractionCheckpoints, loggedIdentity[0], interaction, logContext);
|
||||
});
|
||||
}
|
||||
catch (Exception e) {
|
||||
retryEntityInteraction(user, interaction);
|
||||
throw e;
|
||||
}
|
||||
|
||||
entitySpawnIdentities.put(interaction.getEntityUuid(), loggedIdentity[0]);
|
||||
pendingEntityInteractions.add(new PendingEntityInteraction(user, interaction));
|
||||
if (existingIdentity == null) {
|
||||
promotedEntityIdentities.add(interaction.getEntityUuid());
|
||||
}
|
||||
pendingEntityIdentityConfirmations.put(interaction.getEntityUuid(), interaction.getCurrentLocation());
|
||||
break;
|
||||
case Process.ITEM_TRANSACTION:
|
||||
ItemTransactionProcess.process(preparedStmtItems, i, processId, id, forceData, replaceData, blockData, user, object);
|
||||
break;
|
||||
@@ -379,6 +426,7 @@ public class Process {
|
||||
entitySpawnUpdates.afterCommit(false);
|
||||
}
|
||||
retryEntityContainerRollbacks(pendingEntityContainerRollbacks, false);
|
||||
completeEntityInteractions(pendingEntityInteractions, pendingEntityIdentityConfirmations, promotedEntityIdentities, entitySpawnIdentities, false);
|
||||
discardProcessedConsumerData(processId, consumerData, users, consumerObject, i + 1);
|
||||
deferConsumerRetry();
|
||||
completeEntitySpawnLogs(pendingEntitySpawnLogs, false);
|
||||
@@ -392,6 +440,7 @@ public class Process {
|
||||
entitySpawnUpdates.afterCommit(committed);
|
||||
}
|
||||
retryEntityContainerRollbacks(pendingEntityContainerRollbacks, committed);
|
||||
completeEntityInteractions(pendingEntityInteractions, pendingEntityIdentityConfirmations, promotedEntityIdentities, entitySpawnIdentities, committed);
|
||||
completeEntitySpawnLogs(pendingEntitySpawnLogs, committed);
|
||||
processedThrough = i + 1;
|
||||
Thread.sleep(500);
|
||||
@@ -420,6 +469,7 @@ public class Process {
|
||||
}
|
||||
finally {
|
||||
retryEntityContainerRollbacks(pendingEntityContainerRollbacks, committed);
|
||||
completeEntityInteractions(pendingEntityInteractions, pendingEntityIdentityConfirmations, promotedEntityIdentities, entitySpawnIdentities, committed);
|
||||
completeEntitySpawnLogs(pendingEntitySpawnLogs, committed);
|
||||
}
|
||||
}
|
||||
@@ -436,6 +486,10 @@ public class Process {
|
||||
if (preparedStmtEntityContainers != null) {
|
||||
preparedStmtEntityContainers.close();
|
||||
}
|
||||
if (preparedStmtEntityInteractions != null) {
|
||||
preparedStmtEntityInteractions.close();
|
||||
preparedStmtEntityInteractionCheckpoints.close();
|
||||
}
|
||||
preparedStmtItems.close();
|
||||
preparedStmtWorlds.close();
|
||||
preparedStmtChat.close();
|
||||
@@ -448,8 +502,10 @@ public class Process {
|
||||
preparedStmtBlockdata.close();
|
||||
if (preparedStmtEntitySpawns != null) {
|
||||
preparedStmtEntitySpawns.close();
|
||||
preparedStmtEntitySpawnBlocks.close();
|
||||
preparedStmtEntitySpawnLinks.close();
|
||||
if (preparedStmtEntitySpawnBlocks != null) {
|
||||
preparedStmtEntitySpawnBlocks.close();
|
||||
preparedStmtEntitySpawnLinks.close();
|
||||
}
|
||||
}
|
||||
if (preparedStmtEntityKillLinks != null) {
|
||||
preparedStmtEntityKillLinks.close();
|
||||
@@ -467,6 +523,7 @@ public class Process {
|
||||
entitySpawnUpdates.afterCommit(false);
|
||||
}
|
||||
retryEntityContainerRollbacks(pendingEntityContainerRollbacks, false);
|
||||
completeEntityInteractions(pendingEntityInteractions, pendingEntityIdentityConfirmations, promotedEntityIdentities, entitySpawnIdentities, false);
|
||||
clearConsumerData(processId, consumerData, users, consumerObject);
|
||||
consumerDataCleared = true;
|
||||
}
|
||||
@@ -486,6 +543,7 @@ public class Process {
|
||||
}
|
||||
}
|
||||
}
|
||||
completeEntityInteractions(pendingEntityInteractions, pendingEntityIdentityConfirmations, promotedEntityIdentities, entitySpawnIdentities, false);
|
||||
completeEntitySpawnLogs(pendingEntitySpawnLogs, false);
|
||||
|
||||
if (consumerDataCleared) {
|
||||
@@ -547,6 +605,40 @@ public class Process {
|
||||
}
|
||||
}
|
||||
|
||||
private static void retryEntityInteraction(String user, EntityInteraction interaction) {
|
||||
EntityInteraction retry = interaction.retry();
|
||||
if (retry != null) {
|
||||
Queue.queueEntityInteraction(user, retry);
|
||||
}
|
||||
else {
|
||||
ErrorReporter.report(new IllegalStateException("Dropped entity interaction after repeated persistence failures: " + interaction.getEntityUuid()), ConfigHandler.EDITION_BRANCH.contains("-dev"));
|
||||
}
|
||||
}
|
||||
|
||||
private static void completeEntityInteractions(List<PendingEntityInteraction> interactions, Map<UUID, Location> identityConfirmations, Set<UUID> promotedIdentities, Map<UUID, EntitySpawnIdentity> identities, boolean committed) {
|
||||
if (committed) {
|
||||
for (Map.Entry<UUID, Location> entry : identityConfirmations.entrySet()) {
|
||||
EntitySpawnTracking.confirmDatabaseIdentity(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (PendingEntityInteraction pending : interactions) {
|
||||
try {
|
||||
retryEntityInteraction(pending.user, pending.interaction);
|
||||
}
|
||||
catch (Exception e) {
|
||||
ErrorReporter.report(e);
|
||||
}
|
||||
}
|
||||
for (UUID uuid : promotedIdentities) {
|
||||
identities.remove(uuid);
|
||||
}
|
||||
}
|
||||
interactions.clear();
|
||||
identityConfirmations.clear();
|
||||
promotedIdentities.clear();
|
||||
}
|
||||
|
||||
private static void retryEntityContainerRollbacks(List<EntityContainerRollbackRetry> updates, boolean committed) {
|
||||
if (!committed) {
|
||||
for (EntityContainerRollbackRetry update : updates) {
|
||||
@@ -626,8 +718,11 @@ public class Process {
|
||||
}
|
||||
|
||||
private static void completeEntitySpawnLogs(Map<UUID, Location> pendingEntitySpawnLogs, boolean committed) {
|
||||
if (!committed) {
|
||||
for (Map.Entry<UUID, Location> entry : pendingEntitySpawnLogs.entrySet()) {
|
||||
for (Map.Entry<UUID, Location> entry : pendingEntitySpawnLogs.entrySet()) {
|
||||
if (committed) {
|
||||
EntitySpawnTracking.confirmDatabaseIdentity(entry.getKey(), entry.getValue());
|
||||
}
|
||||
else {
|
||||
try {
|
||||
EntitySpawnTracking.reverifyDatabaseRow(entry.getKey(), entry.getValue());
|
||||
}
|
||||
@@ -698,4 +793,15 @@ public class Process {
|
||||
this.inventoryRollback = inventoryRollback;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PendingEntityInteraction {
|
||||
|
||||
private final String user;
|
||||
private final EntityInteraction interaction;
|
||||
|
||||
private PendingEntityInteraction(String user, EntityInteraction interaction) {
|
||||
this.user = user;
|
||||
this.interaction = interaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,7 @@ public class Database extends Queue {
|
||||
public static final int ITEM = 13;
|
||||
public static final int ENTITY_SPAWN = 14;
|
||||
public static final int ENTITY_CONTAINER = 15;
|
||||
public static final int ENTITY_INTERACTION = 16;
|
||||
|
||||
private static final int ROLLED_BACK_UPDATE_BATCH_SIZE = 1000;
|
||||
|
||||
@@ -73,6 +74,7 @@ public class Database extends Queue {
|
||||
SQL_QUERIES.put(BLOCKDATA, "INSERT INTO %sprefix%blockdata_map (id, data) VALUES (?, ?)");
|
||||
SQL_QUERIES.put(ENTITY_SPAWN, "INSERT INTO %sprefix%entity_spawn (time, uuid, wid, current_wid, origin_x, origin_y, origin_z, x, y, z, yaw, pitch, data, removed) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
SQL_QUERIES.put(ENTITY_CONTAINER, "INSERT INTO %sprefix%entity_container (time, user, entity_spawn_rowid, wid, x, y, z, type, data, amount, metadata, action, rolled_back) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
SQL_QUERIES.put(ENTITY_INTERACTION, "INSERT INTO %sprefix%entity_interaction (time, user, entity_spawn_rowid, wid, x, y, z, type, action, metadata, rolled_back) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
}
|
||||
|
||||
public static void beginTransaction(Statement statement, boolean isMySQL) throws SQLException {
|
||||
@@ -404,7 +406,7 @@ public class Database extends Queue {
|
||||
}
|
||||
}
|
||||
|
||||
private static final List<String> DATABASE_TABLES = Arrays.asList("art_map", "block", "chat", "command", "container", "entity_container", "item", "database_lock", "entity", "entity_spawn", "entity_map", "material_map", "blockdata_map", "session", "sign", "skull", "user", "username_log", "version", "world");
|
||||
private static final List<String> DATABASE_TABLES = Arrays.asList("art_map", "block", "chat", "command", "container", "entity_container", "entity_interaction", "item", "database_lock", "entity", "entity_spawn", "entity_map", "material_map", "blockdata_map", "session", "sign", "skull", "user", "username_log", "version", "world");
|
||||
|
||||
public static void createDatabaseTables(String prefix, boolean forcePrefix, Connection forceConnection, boolean mySQL, boolean purge) {
|
||||
ConfigHandler.databaseTables.clear();
|
||||
@@ -467,6 +469,10 @@ public class Database extends Queue {
|
||||
index = ", INDEX(wid,x,z,time), INDEX(entity_spawn_rowid,time), INDEX(user,time), INDEX(type,time)";
|
||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS " + prefix + "entity_container(rowid int NOT NULL AUTO_INCREMENT,PRIMARY KEY(rowid), time int, user int, entity_spawn_rowid int NOT NULL, wid int, x int, y int, z int, type int, data int, amount int, metadata mediumblob, action tinyint, rolled_back tinyint" + index + ") ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4");
|
||||
|
||||
// Entity interaction
|
||||
index = ", INDEX(wid,x,z,time), INDEX(entity_spawn_rowid,time), INDEX(user,time), INDEX(type,time), INDEX(action,time)";
|
||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS " + prefix + "entity_interaction(rowid int NOT NULL AUTO_INCREMENT,PRIMARY KEY(rowid), time int, user int, entity_spawn_rowid int NOT NULL, wid int, x int, y int, z int, type int, action tinyint, metadata mediumblob, rolled_back tinyint" + index + ") ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4");
|
||||
|
||||
// Item
|
||||
index = ", INDEX(wid,x,z,time), INDEX(user,time), INDEX(type,time)";
|
||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS " + prefix + "item(rowid int NOT NULL AUTO_INCREMENT,PRIMARY KEY(rowid), time int, user int, wid int, x int, y int, z int, type int, data mediumblob, amount int, action tinyint, rolled_back tinyint" + index + ") ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4");
|
||||
@@ -532,6 +538,11 @@ public class Database extends Queue {
|
||||
ensureMySQLIndex(statement, prefix + "entity_container", "entity_spawn_rowid", "time");
|
||||
ensureMySQLIndex(statement, prefix + "entity_container", "user", "time");
|
||||
ensureMySQLIndex(statement, prefix + "entity_container", "type", "time");
|
||||
ensureMySQLIndex(statement, prefix + "entity_interaction", "wid", "x", "z", "time");
|
||||
ensureMySQLIndex(statement, prefix + "entity_interaction", "entity_spawn_rowid", "time");
|
||||
ensureMySQLIndex(statement, prefix + "entity_interaction", "user", "time");
|
||||
ensureMySQLIndex(statement, prefix + "entity_interaction", "type", "time");
|
||||
ensureMySQLIndex(statement, prefix + "entity_interaction", "action", "time");
|
||||
ensureMySQLIndex(statement, prefix + "item", "wid", "x", "z", "time");
|
||||
ensureMySQLIndex(statement, prefix + "item", "user", "time");
|
||||
ensureMySQLIndex(statement, prefix + "item", "type", "time");
|
||||
@@ -688,6 +699,9 @@ public class Database extends Queue {
|
||||
if (!tableData.contains(prefix + "entity_container")) {
|
||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS " + prefix + "entity_container (time INTEGER, user INTEGER, entity_spawn_rowid INTEGER NOT NULL, wid INTEGER, x INTEGER, y INTEGER, z INTEGER, type INTEGER, data INTEGER, amount INTEGER, metadata BLOB, action INTEGER, rolled_back INTEGER);");
|
||||
}
|
||||
if (!tableData.contains(prefix + "entity_interaction")) {
|
||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS " + prefix + "entity_interaction (time INTEGER, user INTEGER, entity_spawn_rowid INTEGER NOT NULL, wid INTEGER, x INTEGER, y INTEGER, z INTEGER, type INTEGER, action INTEGER, metadata BLOB, rolled_back INTEGER);");
|
||||
}
|
||||
if (!tableData.contains(prefix + "item")) {
|
||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS " + prefix + "item (time INTEGER, user INTEGER, wid INTEGER, x INTEGER, y INTEGER, z INTEGER, type INTEGER, data BLOB, amount INTEGER, action INTEGER, rolled_back INTEGER);");
|
||||
}
|
||||
@@ -752,6 +766,11 @@ public class Database extends Queue {
|
||||
createSQLiteIndex(statement, indexData, attachDatabase, "entity_container_spawn_index", prefix + "entity_container(entity_spawn_rowid,time)");
|
||||
createSQLiteIndex(statement, indexData, attachDatabase, "entity_container_user_index", prefix + "entity_container(user,time)");
|
||||
createSQLiteIndex(statement, indexData, attachDatabase, "entity_container_type_index", prefix + "entity_container(type,time)");
|
||||
createSQLiteIndex(statement, indexData, attachDatabase, "entity_interaction_index", prefix + "entity_interaction(wid,x,z,time)");
|
||||
createSQLiteIndex(statement, indexData, attachDatabase, "entity_interaction_spawn_index", prefix + "entity_interaction(entity_spawn_rowid,time)");
|
||||
createSQLiteIndex(statement, indexData, attachDatabase, "entity_interaction_user_index", prefix + "entity_interaction(user,time)");
|
||||
createSQLiteIndex(statement, indexData, attachDatabase, "entity_interaction_type_index", prefix + "entity_interaction(type,time)");
|
||||
createSQLiteIndex(statement, indexData, attachDatabase, "entity_interaction_action_index", prefix + "entity_interaction(action,time)");
|
||||
createSQLiteIndex(statement, indexData, attachDatabase, "item_index", prefix + "item(wid,x,z,time)");
|
||||
createSQLiteIndex(statement, indexData, attachDatabase, "item_user_index", prefix + "item(user,time)");
|
||||
createSQLiteIndex(statement, indexData, attachDatabase, "item_type_index", prefix + "item(type,time)");
|
||||
|
||||
@@ -54,7 +54,9 @@ public class Lookup extends Queue {
|
||||
while (results.next()) {
|
||||
int resultTable = results.getInt("tbl");
|
||||
long count = results.getLong("count");
|
||||
rowData[resultTable] = count;
|
||||
if (rowData != null && resultTable >= 0 && resultTable < rowData.length) {
|
||||
rowData[resultTable] = count;
|
||||
}
|
||||
rows += count;
|
||||
}
|
||||
results.close();
|
||||
|
||||
@@ -30,11 +30,11 @@ import net.coreprotect.model.action.LookupActions;
|
||||
import net.coreprotect.model.action.SignActions;
|
||||
import net.coreprotect.model.item.InventorySources;
|
||||
import net.coreprotect.model.item.ItemTransactionActions;
|
||||
import net.coreprotect.utility.EntityUtils;
|
||||
import net.coreprotect.utility.EntitySpawnTracking;
|
||||
import net.coreprotect.utility.EntityUtils;
|
||||
import net.coreprotect.utility.ErrorReporter;
|
||||
import net.coreprotect.utility.MaterialUtils;
|
||||
import net.coreprotect.utility.WorldUtils;
|
||||
import net.coreprotect.utility.ErrorReporter;
|
||||
|
||||
public class LookupRaw extends Queue {
|
||||
|
||||
@@ -208,7 +208,7 @@ public class LookupRaw extends Queue {
|
||||
int resultWorldId = results.getInt("wid");
|
||||
|
||||
boolean hasTbl = false;
|
||||
if ((lookup && actionList.size() == 0) || actionList.contains(LookupActions.CONTAINER) || actionList.contains(5) || actionList.contains(LookupActions.ITEM)) {
|
||||
if ((lookup && actionList.size() == 0) || actionList.contains(LookupActions.INTERACTION) || actionList.contains(LookupActions.CONTAINER) || actionList.contains(5) || actionList.contains(LookupActions.ITEM)) {
|
||||
resultData = results.getInt("data");
|
||||
resultAmount = results.getInt("amount");
|
||||
resultMeta = results.getBytes("metadata");
|
||||
@@ -283,10 +283,13 @@ public class LookupRaw extends Queue {
|
||||
}
|
||||
|
||||
boolean inventoryQuery = LookupActions.isInventoryLookup(actionList);
|
||||
boolean standardActionLookup = !actionList.contains(LookupActions.CONTAINER) && !actionList.contains(5) && !actionList.contains(LookupActions.ITEM) && !actionList.contains(LookupActions.CHAT) && !actionList.contains(LookupActions.COMMAND) && !actionList.contains(LookupActions.SESSION) && !actionList.contains(LookupActions.USERNAME) && !actionList.contains(LookupActions.SIGN);
|
||||
boolean includeEntityInteractions = !summary && lookup && standardActionLookup && (actionList.isEmpty() || actionList.contains(LookupActions.INTERACTION));
|
||||
boolean includeEntityContainers = entityContainerId != null || actionList.contains(LookupActions.CONTAINER) || inventoryQuery || (lookup && actionList.isEmpty());
|
||||
boolean includeEntitySpawnLocations = entityActionFilter.includesAnySpawn(actionList, lookup || Config.getGlobal().ROLLBACK_ENTITIES);
|
||||
boolean entitySpawnLocation = restrictWorld && location != null && location.getWorld() != null && includeEntitySpawnLocations;
|
||||
boolean entityContainerLocation = restrictWorld && location != null && location.getWorld() != null && includeEntityContainers;
|
||||
boolean entityInteractionLocation = restrictWorld && location != null && location.getWorld() != null && includeEntityInteractions;
|
||||
boolean entitySpawnRadius = entitySpawnLocation && radius != null;
|
||||
boolean entityContainerRadius = entityContainerLocation && radius != null;
|
||||
boolean currentEntityRadius = !lookup && (entitySpawnRadius || entityContainerRadius);
|
||||
@@ -309,6 +312,7 @@ public class LookupRaw extends Queue {
|
||||
String query = "";
|
||||
String entitySpawnLocationQuery = "";
|
||||
String entityContainerLocationQuery = "";
|
||||
String entityInteractionLocationQuery = "";
|
||||
String standardLocationQuery = "";
|
||||
List<String> messageFilterBindings = new ArrayList<>();
|
||||
|
||||
@@ -550,7 +554,7 @@ public class LookupRaw extends Queue {
|
||||
}
|
||||
}
|
||||
|
||||
if (entitySpawnLocation || entityContainerLocation) {
|
||||
if (entitySpawnLocation || entityContainerLocation || entityInteractionLocation) {
|
||||
int wid = WorldUtils.getWorldId(location.getWorld().getName());
|
||||
String originalLocation = "(wid=" + wid + (bounds.isEmpty() ? "" : " AND " + bounds) + ")";
|
||||
String entityBounds = "";
|
||||
@@ -593,6 +597,12 @@ public class LookupRaw extends Queue {
|
||||
entityContainerLocationQuery = lookup ? "(" + originalLocation + " OR " + currentLocation + ")" : currentLocation;
|
||||
}
|
||||
|
||||
if (entityInteractionLocation) {
|
||||
String entitySpawnRows = "SELECT rowid FROM " + ConfigHandler.prefix + "entity_spawn WHERE (" + databaseLocation + ")";
|
||||
String currentLocation = "entity_spawn_rowid IN(" + entitySpawnRows + ")";
|
||||
entityInteractionLocationQuery = "(" + originalLocation + " OR " + currentLocation + ")";
|
||||
}
|
||||
|
||||
standardLocationQuery = originalLocation;
|
||||
queryBlock = queryBlock + " " + (entitySpawnLocation ? entitySpawnLocationQuery : originalLocation) + " AND";
|
||||
}
|
||||
@@ -616,8 +626,10 @@ public class LookupRaw extends Queue {
|
||||
queryBlock = queryBlock + " wid=" + worldId + " AND (x = '" + x + "' OR x = '" + x2 + "') AND (z = '" + z + "' OR z = '" + z2 + "') AND y = '" + location.getBlockY() + "' AND";
|
||||
}
|
||||
|
||||
String actionPredicate = "";
|
||||
if (validAction) {
|
||||
queryBlock = queryBlock + " " + buildActionPredicate(action, actionList, entityActionFilter) + " AND";
|
||||
actionPredicate = buildActionPredicate(action, actionList, entityActionFilter);
|
||||
queryBlock = queryBlock + " " + actionPredicate + " AND";
|
||||
}
|
||||
else if (inventoryQuery || actionExclude.length() > 0 || includeBlock.length() > 0 || includeEntity.length() > 0 || excludeBlock.length() > 0 || excludeEntity.length() > 0) {
|
||||
queryBlock = queryBlock + " action NOT IN(-1) AND";
|
||||
@@ -682,8 +694,19 @@ public class LookupRaw extends Queue {
|
||||
if (excludeBlock.length() > 0 || excludeEntity.length() > 0) {
|
||||
queryEntity = queryEntity.replace("type NOT IN(" + (excludeBlock.length() > 0 ? excludeBlock : "0") + ")", "type NOT IN(" + (excludeEntity.length() > 0 ? excludeEntity : "0") + ")");
|
||||
}
|
||||
String queryEntityInteraction = queryEntity;
|
||||
if (!entitySpawnLocationQuery.isEmpty()) {
|
||||
queryEntityInteraction = queryEntityInteraction.replace(entitySpawnLocationQuery, standardLocationQuery);
|
||||
}
|
||||
if (!entityInteractionLocationQuery.isEmpty()) {
|
||||
queryEntityInteraction = queryEntityInteraction.replace(standardLocationQuery, entityInteractionLocationQuery);
|
||||
}
|
||||
if (!actionPredicate.isEmpty()) {
|
||||
queryEntityInteraction = queryEntityInteraction.replace(actionPredicate, "1");
|
||||
}
|
||||
|
||||
String baseQuery = ((!includeEntity.isEmpty() || !excludeEntity.isEmpty()) ? queryEntity : queryBlock);
|
||||
String blockSourceQuery = actionList.size() == 1 && actionList.contains(LookupActions.INTERACTION) ? queryBlock : baseQuery;
|
||||
if (!summary && limitOffset > -1 && limitCount > -1) {
|
||||
queryLimit = " LIMIT " + limitOffset + ", " + limitCount + "";
|
||||
unionLimit = " ORDER BY time DESC, id DESC LIMIT " + (limitOffset + limitCount) + "";
|
||||
@@ -726,7 +749,7 @@ public class LookupRaw extends Queue {
|
||||
|
||||
if (count) {
|
||||
rows = "COUNT(*) as count";
|
||||
queryLimit = " LIMIT 0, 4";
|
||||
queryLimit = " LIMIT 0, 5";
|
||||
queryOrder = "";
|
||||
unionLimit = "";
|
||||
}
|
||||
@@ -856,6 +879,23 @@ public class LookupRaw extends Queue {
|
||||
}
|
||||
}
|
||||
|
||||
if (includeEntityInteractions) {
|
||||
if (query.isEmpty()) {
|
||||
if (!count) {
|
||||
rows = "rowid as id,time,user,wid,x,y,z,type,meta as metadata,data,-1 as amount,action,rolled_back,0 as entity_spawn_rowid";
|
||||
}
|
||||
query = unionSelect + "SELECT '0' as tbl," + rows + " FROM " + ConfigHandler.prefix + "block " + index + "WHERE" + blockSourceQuery + unionLimit + ")";
|
||||
}
|
||||
|
||||
if (!count) {
|
||||
rows = "rowid as id,time,user,wid,x,y,z,type,metadata,action as data,-1 as amount," + LookupActions.INTERACTION + " as action,rolled_back,entity_spawn_rowid";
|
||||
}
|
||||
query += " UNION ALL " + unionSelect + "SELECT '" + InventorySources.ENTITY_INTERACTION + "' as tbl," + rows + " FROM " + ConfigHandler.prefix + "entity_interaction WHERE" + queryEntityInteraction + unionLimit + ")";
|
||||
if (!count) {
|
||||
queryOrder = " ORDER BY time DESC, tbl DESC, id DESC";
|
||||
}
|
||||
}
|
||||
|
||||
if (query.length() == 0) {
|
||||
if (actionExclude.length() > 0) {
|
||||
baseQuery = baseQuery.replace("action NOT IN(-1)", "action NOT IN(" + actionExclude + ")");
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package net.coreprotect.database.logger;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
|
||||
import net.coreprotect.CoreProtect;
|
||||
import net.coreprotect.config.Config;
|
||||
import net.coreprotect.config.ConfigHandler;
|
||||
import net.coreprotect.database.statement.EntityInteractionStatement;
|
||||
import net.coreprotect.database.statement.UserStatement;
|
||||
import net.coreprotect.event.CoreProtectPreLogEvent;
|
||||
import net.coreprotect.model.action.LookupActions;
|
||||
import net.coreprotect.model.entity.EntityInteraction;
|
||||
import net.coreprotect.model.entity.EntitySpawnIdentity;
|
||||
|
||||
public final class EntityInteractionLogger {
|
||||
|
||||
private EntityInteractionLogger() {
|
||||
throw new IllegalStateException("Database class");
|
||||
}
|
||||
|
||||
public static LogContext prepare(String user, EntityInteraction interaction) {
|
||||
if (interaction == null || ConfigHandler.isBlacklisted(user) || ConfigHandler.isBlacklisted(user, interaction.getEntityType().getKey().toString())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Location currentLocation = interaction.getCurrentLocation();
|
||||
CoreProtectPreLogEvent event = new CoreProtectPreLogEvent(user, currentLocation.clone(), CoreProtectPreLogEvent.Action.ENTITY_INTERACTION, LookupActions.INTERACTION, null, interaction.getEntityType(), interaction.getAction().name());
|
||||
if (Config.getGlobal().API_ENABLED && !Bukkit.isPrimaryThread()) {
|
||||
CoreProtect.getInstance().getServer().getPluginManager().callEvent(event);
|
||||
}
|
||||
return event.isCancelled() ? null : new LogContext(event);
|
||||
}
|
||||
|
||||
public static void log(PreparedStatement interactionStatement, PreparedStatement checkpointStatement, EntitySpawnIdentity identity, EntityInteraction interaction, LogContext context) throws Exception {
|
||||
Location currentLocation = interaction.getCurrentLocation();
|
||||
EntityInteractionStatement.checkpoint(checkpointStatement, identity, currentLocation);
|
||||
|
||||
int worldId = identity.getOriginalWorldId();
|
||||
int x = identity.getOriginalX();
|
||||
int y = identity.getOriginalY();
|
||||
int z = identity.getOriginalZ();
|
||||
|
||||
int userId = UserStatement.getId(interactionStatement, context.event.getUser(), true);
|
||||
int time = (int) (System.currentTimeMillis() / 1000L);
|
||||
EntityInteractionStatement.insert(interactionStatement, time, userId, identity, worldId, x, y, z, interaction);
|
||||
}
|
||||
|
||||
public static final class LogContext {
|
||||
|
||||
private final CoreProtectPreLogEvent event;
|
||||
|
||||
private LogContext(CoreProtectPreLogEvent event) {
|
||||
this.event = event;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package net.coreprotect.database.lookup;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.EntityType;
|
||||
|
||||
import net.coreprotect.config.ConfigHandler;
|
||||
import net.coreprotect.database.statement.EntitySpawnStatement;
|
||||
import net.coreprotect.database.statement.UserStatement;
|
||||
import net.coreprotect.language.Phrase;
|
||||
import net.coreprotect.language.Selector;
|
||||
import net.coreprotect.listener.channel.PluginChannelListener;
|
||||
import net.coreprotect.model.entity.EntityInteractionAction;
|
||||
import net.coreprotect.model.entity.EntitySpawnRecord;
|
||||
import net.coreprotect.utility.ChatUtils;
|
||||
import net.coreprotect.utility.Color;
|
||||
import net.coreprotect.utility.EntitySpawnTracking;
|
||||
import net.coreprotect.utility.EntityUtils;
|
||||
import net.coreprotect.utility.ErrorReporter;
|
||||
import net.coreprotect.utility.WorldUtils;
|
||||
|
||||
public final class EntityInteractionLookup {
|
||||
|
||||
private EntityInteractionLookup() {
|
||||
throw new IllegalStateException("Lookup class");
|
||||
}
|
||||
|
||||
public static List<String> performLookup(String command, Statement statement, CommandSender commandSender, int page, int limit, Integer entitySpawnRowId) {
|
||||
return performLookup(command, statement, commandSender, page, limit, entitySpawnRowId, null);
|
||||
}
|
||||
|
||||
public static List<String> performLookup(String command, Statement statement, CommandSender commandSender, int page, int limit, Integer entitySpawnRowId, Location liveLocation) {
|
||||
List<String> result = new ArrayList<>();
|
||||
if (entitySpawnRowId == null) {
|
||||
clearLookup(commandSender);
|
||||
result.add(noData());
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
String resolvedCommand = resolveCommand(command, commandSender);
|
||||
int currentTime = (int) (System.currentTimeMillis() / 1000L);
|
||||
int rowMax = page * limit;
|
||||
int pageStart = rowMax - limit;
|
||||
int count;
|
||||
String where = "entity_spawn_rowid='" + entitySpawnRowId + "'";
|
||||
|
||||
try (ResultSet results = statement.executeQuery("SELECT COUNT(*) AS count FROM " + ConfigHandler.prefix + "entity_interaction WHERE " + where)) {
|
||||
count = results.next() ? results.getInt("count") : 0;
|
||||
}
|
||||
|
||||
EntitySpawnRecord record = EntitySpawnStatement.loadLocationRecords(statement.getConnection(), Collections.singleton(entitySpawnRowId)).get(entitySpawnRowId);
|
||||
DisplayLocation displayLocation = resolveDisplayLocation(record, liveLocation);
|
||||
boolean found = false;
|
||||
String query = "SELECT time,user,wid,x,y,z,type,action,rolled_back FROM " + ConfigHandler.prefix + "entity_interaction WHERE " + where + " ORDER BY time DESC,rowid DESC LIMIT " + pageStart + ", " + limit;
|
||||
try (ResultSet results = statement.executeQuery(query)) {
|
||||
while (results.next()) {
|
||||
int userId = results.getInt("user");
|
||||
String resultUser = ConfigHandler.playerIdCacheReversed.get(userId);
|
||||
if (resultUser == null) {
|
||||
resultUser = UserStatement.loadName(statement.getConnection(), userId);
|
||||
}
|
||||
|
||||
int originWorldId = results.getInt("wid");
|
||||
int originX = results.getInt("x");
|
||||
int originY = results.getInt("y");
|
||||
int originZ = results.getInt("z");
|
||||
int displayWorldId = displayLocation == null ? originWorldId : displayLocation.worldId;
|
||||
int displayX = displayLocation == null ? originX : displayLocation.x;
|
||||
int displayY = displayLocation == null ? originY : displayLocation.y;
|
||||
int displayZ = displayLocation == null ? originZ : displayLocation.z;
|
||||
if (!found) {
|
||||
result.add(Color.WHITE + "----- " + Color.DARK_AQUA + Phrase.build(Phrase.INTERACTIONS_HEADER) + Color.WHITE + " ----- " + ChatUtils.getCoordinates(resolvedCommand, displayWorldId, displayX, displayY, displayZ, false, false));
|
||||
}
|
||||
found = true;
|
||||
|
||||
int actionId = results.getInt("action");
|
||||
int typeId = results.getInt("type");
|
||||
int rolledBack = results.getInt("rolled_back");
|
||||
long resultTime = results.getLong("time");
|
||||
String rollbackFormat = rolledBack == 1 || rolledBack == 3 ? Color.STRIKETHROUGH : "";
|
||||
String target = entityName(typeId);
|
||||
String coordinateInfo = displayWorldId == originWorldId && displayX == originX && displayY == originY && displayZ == originZ ? "" : ChatUtils.getCoordinateTooltip(originWorldId, originX, originY, originZ, Phrase.build(Phrase.LOOKUP_ENTITY_INTERACTION_ORIGIN), true);
|
||||
String selector = actionSelector(actionId);
|
||||
String timeAgo = ChatUtils.getTimeSince(resultTime, currentTime, true);
|
||||
result.add(timeAgo + " " + Color.WHITE + "- " + Phrase.build(Phrase.LOOKUP_ENTITY_INTERACTION, Color.DARK_AQUA + rollbackFormat + resultUser + Color.WHITE + rollbackFormat, Color.DARK_AQUA + rollbackFormat + target + Color.WHITE + coordinateInfo, selector));
|
||||
PluginChannelListener.getInstance().sendData(commandSender, resultTime, Phrase.LOOKUP_ENTITY_INTERACTION, selector, resultUser, target, -1, displayX, displayY, displayZ, displayWorldId, rollbackFormat, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (found && count > limit) {
|
||||
int totalPages = (int) Math.ceil(count / (limit + 0.0));
|
||||
result.add(Color.WHITE + "-----");
|
||||
result.add(ChatUtils.getPageNavigation(resolvedCommand, page, totalPages));
|
||||
}
|
||||
else if (!found) {
|
||||
result.add(rowMax > count && count > 0 ? Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_RESULTS_PAGE, Selector.SECOND) : noData());
|
||||
}
|
||||
|
||||
ConfigHandler.lookupEntityInteraction.put(commandSender.getName(), entitySpawnRowId);
|
||||
ConfigHandler.lookupType.put(commandSender.getName(), 9);
|
||||
ConfigHandler.lookupPage.put(commandSender.getName(), page);
|
||||
ConfigHandler.lookupCommand.put(commandSender.getName(), displayLocation == null ? "0.0.0.0.9." + limit : displayLocation.x + "." + displayLocation.y + "." + displayLocation.z + "." + displayLocation.worldId + ".9." + limit);
|
||||
}
|
||||
catch (Exception e) {
|
||||
ErrorReporter.report(e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String actionSelector(int actionId) {
|
||||
switch (EntityInteractionAction.fromId(actionId)) {
|
||||
case SHEAR:
|
||||
return Selector.SECOND;
|
||||
case LEASH:
|
||||
return Selector.THIRD;
|
||||
case UNLEASH:
|
||||
return Selector.FOURTH;
|
||||
case GENERIC:
|
||||
default:
|
||||
return Selector.FIRST;
|
||||
}
|
||||
}
|
||||
|
||||
public static String entityName(int typeId) {
|
||||
EntityType type = EntityUtils.getEntityType(typeId);
|
||||
return type == null ? "unknown" : type.name().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private static DisplayLocation resolveDisplayLocation(EntitySpawnRecord record, Location liveLocation) throws Exception {
|
||||
if (liveLocation != null && liveLocation.getWorld() != null) {
|
||||
return new DisplayLocation(WorldUtils.getWorldId(liveLocation.getWorld().getName()), liveLocation.getBlockX(), liveLocation.getBlockY(), liveLocation.getBlockZ());
|
||||
}
|
||||
if (record == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int worldId = record.getWorldId();
|
||||
int x = (int) Math.floor(record.getX());
|
||||
int y = (int) Math.floor(record.getY());
|
||||
int z = (int) Math.floor(record.getZ());
|
||||
Map<UUID, Location> loadedLocations = EntitySpawnTracking.findLoadedLocations(Collections.singleton(record.getUuid()));
|
||||
Location loaded = loadedLocations.get(record.getUuid());
|
||||
if (loaded != null && loaded.getWorld() != null) {
|
||||
worldId = WorldUtils.getWorldId(loaded.getWorld().getName());
|
||||
x = loaded.getBlockX();
|
||||
y = loaded.getBlockY();
|
||||
z = loaded.getBlockZ();
|
||||
}
|
||||
return new DisplayLocation(worldId, x, y, z);
|
||||
}
|
||||
|
||||
private static String resolveCommand(String command, CommandSender commandSender) {
|
||||
if (command != null) {
|
||||
return command;
|
||||
}
|
||||
if (commandSender.hasPermission("coreprotect.co")) {
|
||||
return "co";
|
||||
}
|
||||
if (commandSender.hasPermission("coreprotect.core")) {
|
||||
return "core";
|
||||
}
|
||||
return commandSender.hasPermission("coreprotect.coreprotect") ? "coreprotect" : "co";
|
||||
}
|
||||
|
||||
private static String noData() {
|
||||
return Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_DATA_LOCATION, Selector.THIRD);
|
||||
}
|
||||
|
||||
private static void clearLookup(CommandSender commandSender) {
|
||||
ConfigHandler.lookupEntityInteraction.remove(commandSender.getName());
|
||||
ConfigHandler.lookupType.remove(commandSender.getName());
|
||||
ConfigHandler.lookupCommand.remove(commandSender.getName());
|
||||
}
|
||||
|
||||
private static final class DisplayLocation {
|
||||
private final int worldId;
|
||||
private final int x;
|
||||
private final int y;
|
||||
private final int z;
|
||||
|
||||
private DisplayLocation(int worldId, int x, int y, int z) {
|
||||
this.worldId = worldId;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package net.coreprotect.database.statement;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
|
||||
import org.bukkit.Location;
|
||||
|
||||
import net.coreprotect.config.ConfigHandler;
|
||||
import net.coreprotect.model.entity.EntityInteraction;
|
||||
import net.coreprotect.model.entity.EntitySpawnIdentity;
|
||||
import net.coreprotect.utility.EntityUtils;
|
||||
import net.coreprotect.utility.WorldUtils;
|
||||
|
||||
public final class EntityInteractionStatement {
|
||||
|
||||
private EntityInteractionStatement() {
|
||||
throw new IllegalStateException("Database class");
|
||||
}
|
||||
|
||||
public static PreparedStatement prepareCheckpoint(Connection connection) throws SQLException {
|
||||
return connection.prepareStatement("UPDATE " + ConfigHandler.prefix + "entity_spawn SET current_wid=?,x=?,y=?,z=?,yaw=?,pitch=? WHERE rowid=? AND removed=0");
|
||||
}
|
||||
|
||||
public static void checkpoint(PreparedStatement statement, EntitySpawnIdentity identity, Location location) throws SQLException {
|
||||
statement.setInt(1, WorldUtils.getWorldId(location.getWorld().getName()));
|
||||
statement.setDouble(2, location.getX());
|
||||
statement.setDouble(3, location.getY());
|
||||
statement.setDouble(4, location.getZ());
|
||||
statement.setFloat(5, location.getYaw());
|
||||
statement.setFloat(6, location.getPitch());
|
||||
statement.setInt(7, identity.getRowId());
|
||||
if (statement.executeUpdate() != 1) {
|
||||
throw new SQLException("Entity interaction could not checkpoint its tracking row");
|
||||
}
|
||||
}
|
||||
|
||||
public static void insert(PreparedStatement statement, int time, int userId, EntitySpawnIdentity identity, int worldId, int x, int y, int z, EntityInteraction interaction) throws SQLException {
|
||||
statement.setInt(1, time);
|
||||
statement.setInt(2, userId);
|
||||
statement.setInt(3, identity.getRowId());
|
||||
statement.setInt(4, worldId);
|
||||
statement.setInt(5, x);
|
||||
statement.setInt(6, y);
|
||||
statement.setInt(7, z);
|
||||
statement.setInt(8, EntityUtils.getEntityId(interaction.getEntityType()));
|
||||
statement.setInt(9, interaction.getAction().getId());
|
||||
byte[] metadata = interaction.getMetadata();
|
||||
if (metadata == null) {
|
||||
statement.setNull(10, Types.BLOB);
|
||||
}
|
||||
else {
|
||||
statement.setBytes(10, metadata);
|
||||
}
|
||||
statement.setInt(11, 0);
|
||||
if (statement.executeUpdate() != 1) {
|
||||
throw new SQLException("Entity interaction insert did not insert one row");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import net.coreprotect.database.Database;
|
||||
import net.coreprotect.database.rollback.EntitySpawnRollbackHandler;
|
||||
import net.coreprotect.model.action.LookupActions;
|
||||
import net.coreprotect.model.entity.EntityContainerRollbackUpdate;
|
||||
import net.coreprotect.model.entity.EntityInteractionOrigin;
|
||||
import net.coreprotect.model.entity.EntitySpawnData;
|
||||
import net.coreprotect.model.entity.EntitySpawnIdentity;
|
||||
import net.coreprotect.model.entity.EntitySpawnRecord;
|
||||
@@ -76,6 +77,45 @@ public final class EntitySpawnStatement {
|
||||
}
|
||||
}
|
||||
|
||||
public static EntitySpawnIdentity insertIdentity(PreparedStatement statement, int time, UUID uuid, EntityInteractionOrigin origin, Location currentLocation) throws Exception {
|
||||
statement.setInt(1, time);
|
||||
statement.setString(2, uuid.toString());
|
||||
statement.setInt(3, origin.getWorldId());
|
||||
statement.setInt(4, WorldUtils.getWorldId(currentLocation.getWorld().getName()));
|
||||
statement.setDouble(5, origin.getX());
|
||||
statement.setDouble(6, origin.getY());
|
||||
statement.setDouble(7, origin.getZ());
|
||||
statement.setDouble(8, currentLocation.getX());
|
||||
statement.setDouble(9, currentLocation.getY());
|
||||
statement.setDouble(10, currentLocation.getZ());
|
||||
statement.setFloat(11, currentLocation.getYaw());
|
||||
statement.setFloat(12, currentLocation.getPitch());
|
||||
statement.setNull(13, Types.BLOB);
|
||||
statement.setInt(14, 0);
|
||||
|
||||
int rowId;
|
||||
if (Database.hasReturningKeys()) {
|
||||
try (ResultSet resultSet = statement.executeQuery()) {
|
||||
if (!resultSet.next()) {
|
||||
throw new SQLException("Entity identity insert did not return a row id");
|
||||
}
|
||||
rowId = resultSet.getInt(1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (statement.executeUpdate() != 1) {
|
||||
throw new SQLException("Entity identity insert did not insert one row");
|
||||
}
|
||||
try (ResultSet resultSet = statement.getGeneratedKeys()) {
|
||||
if (!resultSet.next()) {
|
||||
throw new SQLException("Entity identity insert did not generate a row id");
|
||||
}
|
||||
rowId = resultSet.getInt(1);
|
||||
}
|
||||
}
|
||||
return new EntitySpawnIdentity(rowId, uuid, origin.getWorldId(), origin.getX(), origin.getY(), origin.getZ());
|
||||
}
|
||||
|
||||
public static PreparedStatement prepareBlockLink(Connection connection) throws SQLException {
|
||||
return connection.prepareStatement("UPDATE " + ConfigHandler.prefix + "entity_spawn SET block_rowid=? WHERE rowid=? AND block_rowid IS NULL");
|
||||
}
|
||||
@@ -306,8 +346,8 @@ public final class EntitySpawnStatement {
|
||||
location = connection.prepareStatement("UPDATE " + ConfigHandler.prefix + "entity_spawn SET current_wid=?,x=?,y=?,z=?,yaw=?,pitch=? WHERE uuid=? AND removed=0");
|
||||
removed = connection.prepareStatement("UPDATE " + ConfigHandler.prefix + "entity_spawn SET current_wid=?,x=?,y=?,z=?,yaw=?,pitch=?,data=NULL,removed=1 WHERE uuid=? AND removed=0");
|
||||
revived = connection.prepareStatement("UPDATE " + ConfigHandler.prefix + "entity_spawn SET uuid=?,current_wid=?,x=?,y=?,z=?,yaw=?,pitch=?,data=NULL,removed=0 WHERE uuid=? AND removed=1");
|
||||
rollback = connection.prepareStatement("UPDATE " + ConfigHandler.prefix + "entity_spawn SET current_wid=?,x=?,y=?,z=?,yaw=?,pitch=?,data=?,removed=1 WHERE rowid=?");
|
||||
restore = connection.prepareStatement("UPDATE " + ConfigHandler.prefix + "entity_spawn SET uuid=?,current_wid=?,x=?,y=?,z=?,yaw=?,pitch=?,data=NULL,removed=0 WHERE rowid=?");
|
||||
rollback = connection.prepareStatement("UPDATE " + ConfigHandler.prefix + "entity_spawn SET current_wid=?,x=?,y=?,z=?,yaw=?,pitch=?,data=?,kill_rowid=NULL,removed=1 WHERE rowid=?");
|
||||
restore = connection.prepareStatement("UPDATE " + ConfigHandler.prefix + "entity_spawn SET uuid=?,current_wid=?,x=?,y=?,z=?,yaw=?,pitch=?,data=NULL,kill_rowid=NULL,removed=0 WHERE rowid=?");
|
||||
killRollback = connection.prepareStatement("UPDATE " + ConfigHandler.prefix + "entity_spawn SET uuid=?,current_wid=?,x=?,y=?,z=?,yaw=?,pitch=?,data=NULL,removed=0 WHERE rowid=? AND kill_rowid=?");
|
||||
killRestore = connection.prepareStatement("UPDATE " + ConfigHandler.prefix + "entity_spawn SET current_wid=?,x=?,y=?,z=?,yaw=?,pitch=?,data=NULL,removed=1 WHERE rowid=? AND kill_rowid=?");
|
||||
compositeRollback = connection.prepareStatement("UPDATE " + ConfigHandler.prefix + "entity_spawn SET current_wid=?,x=?,y=?,z=?,yaw=?,pitch=?,data=?,removed=1 WHERE rowid=? AND kill_rowid=?");
|
||||
|
||||
@@ -20,7 +20,8 @@ public class CoreProtectPreLogEvent extends Event implements Cancellable {
|
||||
CONTAINER_TRANSACTION,
|
||||
ITEM_TRANSACTION,
|
||||
PLAYER_COMMAND,
|
||||
ENTITY_SPAWN
|
||||
ENTITY_SPAWN,
|
||||
ENTITY_INTERACTION
|
||||
}
|
||||
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
@@ -154,6 +154,8 @@ public class Language {
|
||||
phrases.put(Phrase.LOOKUP_CONTAINER, "{0} {added|removed} {1} {2}.");
|
||||
phrases.put(Phrase.LOOKUP_HEADER, "{0} Lookup Results");
|
||||
phrases.put(Phrase.LOOKUP_INTERACTION, "{0} {clicked|killed} {1}.");
|
||||
phrases.put(Phrase.LOOKUP_ENTITY_INTERACTION, "{0} {clicked|sheared|leashed|unleashed} {1}.");
|
||||
phrases.put(Phrase.LOOKUP_ENTITY_INTERACTION_ORIGIN, "Origin location");
|
||||
phrases.put(Phrase.LOOKUP_ENTITY_ORIGIN, "Spawn location");
|
||||
phrases.put(Phrase.LOOKUP_ENTITY_SPAWN, "{0} spawned {1}.");
|
||||
phrases.put(Phrase.LOOKUP_ITEM, "{0} {picked up|dropped} {1} {2}.");
|
||||
|
||||
@@ -234,7 +234,9 @@ public enum Phrase {
|
||||
VERSION_REQUIRED,
|
||||
WORLD_NOT_FOUND,
|
||||
LOOKUP_ENTITY_SPAWN,
|
||||
LOOKUP_ENTITY_ORIGIN;
|
||||
LOOKUP_ENTITY_ORIGIN,
|
||||
LOOKUP_ENTITY_INTERACTION,
|
||||
LOOKUP_ENTITY_INTERACTION_ORIGIN;
|
||||
|
||||
final private static Set<Phrase> HEADERS = new HashSet<>(Arrays.asList(Phrase.CONTAINER_HEADER, Phrase.HELP_HEADER, Phrase.INTERACTIONS_HEADER, Phrase.LOOKUP_HEADER, Phrase.SIGN_HEADER, Phrase.UPDATE_HEADER));
|
||||
final private static Set<String> COLORS = new HashSet<>(Arrays.asList(Color.WHITE, Color.DARK_AQUA));
|
||||
|
||||
@@ -42,6 +42,7 @@ import net.coreprotect.listener.entity.TrackedEntityRemoveListener;
|
||||
import net.coreprotect.listener.entity.TrackedEntityTeleportListener;
|
||||
import net.coreprotect.listener.player.ArmorStandManipulateListener;
|
||||
import net.coreprotect.listener.player.CraftItemListener;
|
||||
import net.coreprotect.listener.player.EntityInteractionListener;
|
||||
import net.coreprotect.listener.player.FoodLevelChangeListener;
|
||||
import net.coreprotect.listener.player.InventoryChangeListener;
|
||||
import net.coreprotect.listener.player.InventoryClickListener;
|
||||
@@ -181,6 +182,7 @@ public final class ListenerHandler {
|
||||
// Player Listeners
|
||||
pluginManager.registerEvents(new ArmorStandManipulateListener(), plugin);
|
||||
pluginManager.registerEvents(new CraftItemListener(), plugin);
|
||||
pluginManager.registerEvents(new EntityInteractionListener(), plugin);
|
||||
pluginManager.registerEvents(new FoodLevelChangeListener(), plugin);
|
||||
pluginManager.registerEvents(new InventoryChangeListener(), plugin);
|
||||
pluginManager.registerEvents(new InventoryClickListener(), plugin);
|
||||
|
||||
@@ -43,6 +43,9 @@ public final class CreatureSpawnListener extends Queue implements Listener {
|
||||
Queue.queueEntitySpawnLog(user, event.getEntity().getUniqueId(), event.getEntityType(), event.getEntity().getLocation());
|
||||
}
|
||||
}
|
||||
if (Config.getConfig(world).PLAYER_INTERACTIONS) {
|
||||
EntitySpawnTracking.seedOrigin(event.getEntity());
|
||||
}
|
||||
|
||||
if (!event.getEntityType().equals(EntityType.ARMOR_STAND)) {
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
package net.coreprotect.listener.player;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.entity.ArmorStand;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.ItemFrame;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.PlayerLeashEntityEvent;
|
||||
import org.bukkit.event.player.PlayerArmorStandManipulateEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||
import org.bukkit.event.player.PlayerShearEntityEvent;
|
||||
|
||||
import net.coreprotect.CoreProtect;
|
||||
import net.coreprotect.config.Config;
|
||||
import net.coreprotect.config.ConfigHandler;
|
||||
import net.coreprotect.consumer.Queue;
|
||||
import net.coreprotect.model.entity.EntityInteractionAction;
|
||||
import net.coreprotect.thread.Scheduler;
|
||||
|
||||
public final class EntityInteractionListener extends Queue implements Listener {
|
||||
|
||||
private static final int MAX_PENDING_INTERACTIONS = 4096;
|
||||
private static final long STALE_INTERACTION_NANOS = 1_000_000_000L;
|
||||
private final Map<InteractionKey, PendingInteraction> pendingInteractions = new HashMap<>();
|
||||
private long nextCleanup;
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
|
||||
if (event instanceof PlayerArmorStandManipulateEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = event.getPlayer();
|
||||
Entity entity = event.getRightClicked();
|
||||
if (!shouldLog(player, entity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
capture(player, entity, EntityInteractionAction.GENERIC, isLeashed(entity));
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlayerShearEntity(PlayerShearEntityEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
Entity entity = event.getEntity();
|
||||
if (shouldLog(player, entity)) {
|
||||
capture(player, entity, EntityInteractionAction.SHEAR, false);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlayerLeashEntity(PlayerLeashEntityEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
Entity entity = event.getEntity();
|
||||
if (shouldLog(player, entity)) {
|
||||
capture(player, entity, EntityInteractionAction.LEASH, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void capture(Player player, Entity entity, EntityInteractionAction action, boolean unleashCandidate) {
|
||||
InteractionKey key = new InteractionKey(player.getUniqueId(), entity.getUniqueId());
|
||||
PendingInteraction interaction;
|
||||
boolean scheduleFlush = false;
|
||||
long now = System.nanoTime();
|
||||
|
||||
synchronized (pendingInteractions) {
|
||||
cleanup(now);
|
||||
interaction = pendingInteractions.get(key);
|
||||
if (interaction == null) {
|
||||
if (pendingInteractions.size() >= MAX_PENDING_INTERACTIONS) {
|
||||
return;
|
||||
}
|
||||
|
||||
interaction = new PendingInteraction(player.getName(), entity, action, unleashCandidate, now);
|
||||
pendingInteractions.put(key, interaction);
|
||||
scheduleFlush = true;
|
||||
}
|
||||
else {
|
||||
interaction.merge(action, unleashCandidate);
|
||||
}
|
||||
}
|
||||
|
||||
if (scheduleFlush) {
|
||||
PendingInteraction scheduledInteraction = interaction;
|
||||
try {
|
||||
Scheduler.scheduleSyncDelayedTask(CoreProtect.getInstance(),
|
||||
() -> flush(key, scheduledInteraction),
|
||||
() -> discard(key, scheduledInteraction), entity, 1);
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
discard(key, scheduledInteraction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void flush(InteractionKey key, PendingInteraction interaction) {
|
||||
synchronized (pendingInteractions) {
|
||||
if (!pendingInteractions.remove(key, interaction)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
EntityInteractionAction action = interaction.action;
|
||||
if (action == EntityInteractionAction.GENERIC
|
||||
&& interaction.unleashCandidate
|
||||
&& interaction.entity.isValid()
|
||||
&& !isLeashed(interaction.entity)) {
|
||||
action = EntityInteractionAction.UNLEASH;
|
||||
}
|
||||
Queue.queueEntityInteraction(interaction.user, interaction.entity, action);
|
||||
}
|
||||
|
||||
private void discard(InteractionKey key, PendingInteraction interaction) {
|
||||
synchronized (pendingInteractions) {
|
||||
pendingInteractions.remove(key, interaction);
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanup(long now) {
|
||||
if (now < nextCleanup) {
|
||||
return;
|
||||
}
|
||||
|
||||
nextCleanup = now + STALE_INTERACTION_NANOS;
|
||||
Iterator<PendingInteraction> iterator = pendingInteractions.values().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
if (now - iterator.next().createdAt >= STALE_INTERACTION_NANOS) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldLog(Player player, Entity entity) {
|
||||
return !Boolean.TRUE.equals(ConfigHandler.inspecting.get(player.getName()))
|
||||
&& Config.getConfig(player.getWorld()).PLAYER_INTERACTIONS
|
||||
&& !(entity instanceof Player)
|
||||
&& !(entity instanceof ArmorStand)
|
||||
&& !(entity instanceof ItemFrame);
|
||||
}
|
||||
|
||||
private static boolean isLeashed(Entity entity) {
|
||||
if (!(entity instanceof LivingEntity)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((LivingEntity) entity).isLeashed();
|
||||
}
|
||||
|
||||
private static final class InteractionKey {
|
||||
|
||||
private final UUID playerId;
|
||||
private final UUID entityId;
|
||||
|
||||
private InteractionKey(UUID playerId, UUID entityId) {
|
||||
this.playerId = playerId;
|
||||
this.entityId = entityId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
}
|
||||
if (!(object instanceof InteractionKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
InteractionKey other = (InteractionKey) object;
|
||||
return playerId.equals(other.playerId) && entityId.equals(other.entityId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * playerId.hashCode() + entityId.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PendingInteraction {
|
||||
|
||||
private final String user;
|
||||
private final Entity entity;
|
||||
private final long createdAt;
|
||||
private EntityInteractionAction action;
|
||||
private boolean unleashCandidate;
|
||||
|
||||
private PendingInteraction(String user, Entity entity, EntityInteractionAction action, boolean unleashCandidate, long createdAt) {
|
||||
this.user = user;
|
||||
this.entity = entity;
|
||||
this.action = action;
|
||||
this.unleashCandidate = unleashCandidate;
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
private void merge(EntityInteractionAction action, boolean unleashCandidate) {
|
||||
if (action != EntityInteractionAction.GENERIC) {
|
||||
this.action = action;
|
||||
}
|
||||
this.unleashCandidate |= unleashCandidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import net.coreprotect.consumer.Queue;
|
||||
import net.coreprotect.database.logger.ItemLogger;
|
||||
import net.coreprotect.language.Phrase;
|
||||
import net.coreprotect.listener.player.inspector.ContainerInspector;
|
||||
import net.coreprotect.listener.player.inspector.EntityInteractionInspector;
|
||||
import net.coreprotect.model.BlockGroup;
|
||||
import net.coreprotect.utility.Chat;
|
||||
import net.coreprotect.utility.Color;
|
||||
@@ -35,6 +36,7 @@ import net.coreprotect.utility.ItemUtils;
|
||||
public final class PlayerInteractEntityListener extends Queue implements Listener {
|
||||
|
||||
private final ContainerInspector containerInspector = new ContainerInspector();
|
||||
private final EntityInteractionInspector entityInteractionInspector = new EntityInteractionInspector();
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
protected void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
|
||||
@@ -44,7 +46,7 @@ public final class PlayerInteractEntityListener extends Queue implements Listene
|
||||
|
||||
Player player = event.getPlayer();
|
||||
final Entity entity = event.getRightClicked(); // change item in ItemFrame, etc
|
||||
if (Boolean.TRUE.equals(ConfigHandler.inspecting.get(player.getName())) && EntitySpawnTracking.isPlacedEntity(entity) && entity instanceof InventoryHolder) {
|
||||
if (Boolean.TRUE.equals(ConfigHandler.inspecting.get(player.getName())) && EntitySpawnTracking.isPlacedEntity(entity) && entity instanceof InventoryHolder && Config.getConfig(player.getWorld()).ITEM_TRANSACTIONS) {
|
||||
String playerUuid = player.getUniqueId().toString();
|
||||
long now = System.currentTimeMillis();
|
||||
Object[] previousInspection = PlayerInteractListener.lastInspectorEvent.get(playerUuid);
|
||||
@@ -60,11 +62,9 @@ public final class PlayerInteractEntityListener extends Queue implements Listene
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
if (Config.getConfig(player.getWorld()).ITEM_TRANSACTIONS) {
|
||||
containerInspector.performEntityContainerLookup(player, entity.getUniqueId(), entity.getLocation());
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
containerInspector.performEntityContainerLookup(player, entity.getUniqueId(), entity.getLocation());
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity instanceof ItemFrame) {
|
||||
@@ -115,6 +115,23 @@ public final class PlayerInteractEntityListener extends Queue implements Listene
|
||||
queueContainerSpecifiedItems(player.getName(), Material.ITEM_FRAME, new Object[] { oldState, newState, frame.getFacing() }, frame.getLocation(), false);
|
||||
}
|
||||
}
|
||||
else if (Boolean.TRUE.equals(ConfigHandler.inspecting.get(player.getName()))) {
|
||||
String playerUuid = player.getUniqueId().toString();
|
||||
long now = System.currentTimeMillis();
|
||||
Object[] previousInspection = PlayerInteractListener.lastInspectorEvent.get(playerUuid);
|
||||
if (previousInspection == null || now - (long) previousInspection[0] >= 100L) {
|
||||
PlayerInteractListener.lastInspectorEvent.put(playerUuid, new Object[] { now, event.getHand() });
|
||||
if (!player.hasPermission("coreprotect.inspect")) {
|
||||
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION));
|
||||
ConfigHandler.inspecting.put(player.getName(), false);
|
||||
}
|
||||
else {
|
||||
entityInteractionInspector.performLookup(player, entity.getUniqueId(), entity.getLocation());
|
||||
}
|
||||
}
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
else if (!event.isCancelled() && entity instanceof Creature && entity.getType().name().equals("ALLAY")) {
|
||||
ItemStack handItem = new ItemStack(Material.AIR);
|
||||
ItemStack mainHand = player.getInventory().getItemInMainHand();
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package net.coreprotect.listener.player.inspector;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.Statement;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import net.coreprotect.config.ConfigHandler;
|
||||
import net.coreprotect.database.lookup.EntityInteractionLookup;
|
||||
import net.coreprotect.utility.Chat;
|
||||
import net.coreprotect.utility.ErrorReporter;
|
||||
|
||||
public final class EntityInteractionInspector extends BaseInspector {
|
||||
|
||||
public void performLookup(Player player, UUID entityUuid, Location location) {
|
||||
ConfigHandler.lookupEntityInteraction.remove(player.getName());
|
||||
ConfigHandler.lookupEntityContainer.remove(player.getName());
|
||||
ConfigHandler.lookupType.remove(player.getName());
|
||||
|
||||
Thread thread = new Thread(() -> {
|
||||
try {
|
||||
checkPreconditions(player);
|
||||
try (Connection connection = getDatabaseConnection(player)) {
|
||||
Integer entitySpawnRowId = findEntitySpawnRowId(connection, entityUuid);
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
List<String> results = EntityInteractionLookup.performLookup(null, statement, player, 1, 7, entitySpawnRowId, location);
|
||||
for (String result : results) {
|
||||
Chat.sendComponent(player, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InspectionException e) {
|
||||
Chat.sendMessage(player, e.getMessage());
|
||||
}
|
||||
catch (Exception e) {
|
||||
ErrorReporter.report(e);
|
||||
}
|
||||
finally {
|
||||
finishInspection(player);
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private Integer findEntitySpawnRowId(Connection connection, UUID entityUuid) throws Exception {
|
||||
String query = "SELECT rowid AS id FROM " + ConfigHandler.prefix + "entity_spawn WHERE uuid=? LIMIT 1";
|
||||
try (PreparedStatement statement = connection.prepareStatement(query)) {
|
||||
statement.setString(1, entityUuid.toString());
|
||||
try (ResultSet resultSet = statement.executeQuery()) {
|
||||
return resultSet.next() ? resultSet.getInt("id") : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package net.coreprotect.model.entity;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.EntityType;
|
||||
|
||||
public final class EntityInteraction {
|
||||
|
||||
private static final int MAX_IDENTITY_RESOLUTION_ATTEMPTS = 3;
|
||||
|
||||
private final UUID entityUuid;
|
||||
private final EntityType entityType;
|
||||
private final EntityInteractionOrigin origin;
|
||||
private final Location currentLocation;
|
||||
private final EntityInteractionAction action;
|
||||
private final byte[] metadata;
|
||||
private final int identityResolutionAttempts;
|
||||
|
||||
public EntityInteraction(UUID entityUuid, EntityType entityType, EntityInteractionOrigin origin, Location currentLocation, EntityInteractionAction action, byte[] metadata) {
|
||||
this(entityUuid, entityType, origin, currentLocation, action, metadata, 0);
|
||||
}
|
||||
|
||||
private EntityInteraction(UUID entityUuid, EntityType entityType, EntityInteractionOrigin origin, Location currentLocation, EntityInteractionAction action, byte[] metadata, int identityResolutionAttempts) {
|
||||
if (entityUuid == null || entityType == null || origin == null || currentLocation == null || currentLocation.getWorld() == null || action == null) {
|
||||
throw new IllegalArgumentException("Invalid entity interaction");
|
||||
}
|
||||
this.entityUuid = entityUuid;
|
||||
this.entityType = entityType;
|
||||
this.origin = origin;
|
||||
this.currentLocation = currentLocation.clone();
|
||||
this.action = action;
|
||||
this.metadata = metadata == null ? null : metadata.clone();
|
||||
this.identityResolutionAttempts = identityResolutionAttempts;
|
||||
}
|
||||
|
||||
public UUID getEntityUuid() {
|
||||
return entityUuid;
|
||||
}
|
||||
|
||||
public EntityType getEntityType() {
|
||||
return entityType;
|
||||
}
|
||||
|
||||
public EntityInteractionOrigin getOrigin() {
|
||||
return origin;
|
||||
}
|
||||
|
||||
public Location getCurrentLocation() {
|
||||
return currentLocation.clone();
|
||||
}
|
||||
|
||||
public EntityInteractionAction getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public byte[] getMetadata() {
|
||||
return metadata == null ? null : metadata.clone();
|
||||
}
|
||||
|
||||
public EntityInteraction retry() {
|
||||
if (!canRetry()) {
|
||||
return null;
|
||||
}
|
||||
return new EntityInteraction(entityUuid, entityType, origin, currentLocation, action, metadata, identityResolutionAttempts + 1);
|
||||
}
|
||||
|
||||
public boolean canRetry() {
|
||||
return identityResolutionAttempts < MAX_IDENTITY_RESOLUTION_ATTEMPTS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package net.coreprotect.model.entity;
|
||||
|
||||
public enum EntityInteractionAction {
|
||||
|
||||
GENERIC(0, "clicked"),
|
||||
SHEAR(1, "sheared"),
|
||||
LEASH(2, "leashed"),
|
||||
UNLEASH(3, "unleashed");
|
||||
|
||||
private final int id;
|
||||
private final String displayVerb;
|
||||
|
||||
EntityInteractionAction(int id, String displayVerb) {
|
||||
this.id = id;
|
||||
this.displayVerb = displayVerb;
|
||||
}
|
||||
|
||||
public static EntityInteractionAction fromId(int id) {
|
||||
for (EntityInteractionAction action : values()) {
|
||||
if (action.id == id) {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
return GENERIC;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getDisplayVerb() {
|
||||
return displayVerb;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package net.coreprotect.model.entity;
|
||||
|
||||
public final class EntityInteractionOrigin {
|
||||
|
||||
private final int worldId;
|
||||
private final double x;
|
||||
private final double y;
|
||||
private final double z;
|
||||
|
||||
public EntityInteractionOrigin(int worldId, double x, double y, double z) {
|
||||
if (worldId < 0) {
|
||||
throw new IllegalArgumentException("Invalid entity interaction origin");
|
||||
}
|
||||
this.worldId = worldId;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public int getWorldId() {
|
||||
return worldId;
|
||||
}
|
||||
|
||||
public double getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public double getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public double getZ() {
|
||||
return z;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ public final class InventorySources {
|
||||
public static final int CONTAINER = 1;
|
||||
public static final int ITEM = 2;
|
||||
public static final int ENTITY_CONTAINER = 3;
|
||||
public static final int ENTITY_INTERACTION = 4;
|
||||
|
||||
private InventorySources() {
|
||||
throw new IllegalStateException("Model class");
|
||||
@@ -19,6 +20,8 @@ public final class InventorySources {
|
||||
return "container";
|
||||
case ITEM:
|
||||
return "item";
|
||||
case ENTITY_INTERACTION:
|
||||
return "interaction";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
@@ -11,15 +11,16 @@ public class __2_24_1 {
|
||||
try {
|
||||
Integer[] last_version = Patch.getDatabaseVersion(statement.getConnection(), true);
|
||||
if (last_version[0] == 2 && last_version[1] == 24 && last_version[2] == 0) {
|
||||
return __2_24_0.updateItemMetadataColumns(statement) && __2_24_0.updateSkullSkinColumn(statement);
|
||||
if (!__2_24_0.updateItemMetadataColumns(statement) || !__2_24_0.updateSkullSkinColumn(statement)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception e) {
|
||||
ErrorReporter.report(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,7 +16,8 @@ public class __2_25_0 {
|
||||
|
||||
protected static boolean patch(Statement statement) {
|
||||
if (Config.getGlobal().MYSQL) {
|
||||
if (!createMySQLPrefixIndex(statement, ConfigHandler.prefix + "chat", "message_prefix_index", "message(16)")
|
||||
if (!createEntityInteractionTable(statement)
|
||||
|| !createMySQLPrefixIndex(statement, ConfigHandler.prefix + "chat", "message_prefix_index", "message(16)")
|
||||
|| !createMySQLPrefixIndex(statement, ConfigHandler.prefix + "command", "message_prefix_index", "message(16)")) {
|
||||
return false;
|
||||
}
|
||||
@@ -34,6 +35,10 @@ public class __2_25_0 {
|
||||
connection = statement.getConnection();
|
||||
autoCommit = connection.getAutoCommit();
|
||||
connection.setAutoCommit(false);
|
||||
if (!createEntityInteractionTable(statement)) {
|
||||
connection.rollback();
|
||||
return false;
|
||||
}
|
||||
String[] indexQueries = new String[10];
|
||||
indexQueries[0] = "CREATE INDEX IF NOT EXISTS chat_message_prefix_index ON " + ConfigHandler.prefix + "chat(substr(message,1,16) COLLATE NOCASE)";
|
||||
indexQueries[1] = "CREATE INDEX IF NOT EXISTS command_message_prefix_index ON " + ConfigHandler.prefix + "command(substr(message,1,16) COLLATE NOCASE)";
|
||||
@@ -60,6 +65,29 @@ public class __2_25_0 {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean createEntityInteractionTable(Statement statement) {
|
||||
String table = ConfigHandler.prefix + "entity_interaction";
|
||||
try {
|
||||
if (Config.getGlobal().MYSQL) {
|
||||
String indexes = ", INDEX(wid,x,z,time), INDEX(entity_spawn_rowid,time), INDEX(user,time), INDEX(type,time), INDEX(action,time)";
|
||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS " + table + "(rowid int NOT NULL AUTO_INCREMENT,PRIMARY KEY(rowid), time int, user int, entity_spawn_rowid int NOT NULL, wid int, x int, y int, z int, type int, action tinyint, metadata mediumblob, rolled_back tinyint" + indexes + ") ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4");
|
||||
}
|
||||
else {
|
||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS " + table + " (time INTEGER, user INTEGER, entity_spawn_rowid INTEGER NOT NULL, wid INTEGER, x INTEGER, y INTEGER, z INTEGER, type INTEGER, action INTEGER, metadata BLOB, rolled_back INTEGER)");
|
||||
statement.executeUpdate("CREATE INDEX IF NOT EXISTS entity_interaction_index ON " + table + "(wid,x,z,time)");
|
||||
statement.executeUpdate("CREATE INDEX IF NOT EXISTS entity_interaction_spawn_index ON " + table + "(entity_spawn_rowid,time)");
|
||||
statement.executeUpdate("CREATE INDEX IF NOT EXISTS entity_interaction_user_index ON " + table + "(user,time)");
|
||||
statement.executeUpdate("CREATE INDEX IF NOT EXISTS entity_interaction_type_index ON " + table + "(type,time)");
|
||||
statement.executeUpdate("CREATE INDEX IF NOT EXISTS entity_interaction_action_index ON " + table + "(action,time)");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception e) {
|
||||
ErrorReporter.report(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean createMySQLPrefixIndex(Statement statement, String table, String index, String column) {
|
||||
try {
|
||||
statement.executeUpdate("CREATE INDEX " + index + " ON " + table + "(" + column + ")");
|
||||
|
||||
@@ -16,6 +16,10 @@ public class Scheduler {
|
||||
}
|
||||
|
||||
public static void scheduleSyncDelayedTask(CoreProtect plugin, Runnable task, Object regionData, int delay) {
|
||||
scheduleSyncDelayedTask(plugin, task, null, regionData, delay);
|
||||
}
|
||||
|
||||
public static void scheduleSyncDelayedTask(CoreProtect plugin, Runnable task, Runnable retiredTask, Object regionData, int delay) {
|
||||
if (ConfigHandler.isFolia) {
|
||||
if (regionData instanceof Location) {
|
||||
Location location = (Location) regionData;
|
||||
@@ -29,10 +33,10 @@ public class Scheduler {
|
||||
else if (regionData instanceof Entity) {
|
||||
Entity entity = (Entity) regionData;
|
||||
if (delay == 0) {
|
||||
entity.getScheduler().run(plugin, value -> task.run(), null);
|
||||
entity.getScheduler().run(plugin, value -> task.run(), retiredTask);
|
||||
}
|
||||
else {
|
||||
entity.getScheduler().runDelayed(plugin, value -> task.run(), null, delay);
|
||||
entity.getScheduler().runDelayed(plugin, value -> task.run(), retiredTask, delay);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.coreprotect.utility;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -19,11 +20,13 @@ import org.bukkit.Location;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.TreeSpecies;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.ArmorStand;
|
||||
import org.bukkit.entity.Boat;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.entity.Minecart;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.EntityEquipment;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
@@ -33,6 +36,7 @@ import org.bukkit.persistence.PersistentDataType;
|
||||
import net.coreprotect.CoreProtect;
|
||||
import net.coreprotect.config.ConfigHandler;
|
||||
import net.coreprotect.consumer.Queue;
|
||||
import net.coreprotect.model.entity.EntityInteractionOrigin;
|
||||
import net.coreprotect.model.entity.EntitySpawnData;
|
||||
import net.coreprotect.paper.PaperAdapter;
|
||||
import net.coreprotect.thread.Scheduler;
|
||||
@@ -40,6 +44,8 @@ import net.coreprotect.thread.Scheduler;
|
||||
public final class EntitySpawnTracking {
|
||||
|
||||
private static final String TRACKING_KEY = "spawn";
|
||||
private static final String ORIGIN_SEED_KEY = "entity_origin";
|
||||
private static final int ORIGIN_SEED_SIZE = Integer.BYTES + Double.BYTES * 3;
|
||||
private static final int KILL_LOCATION_INDEX = 8;
|
||||
private static final long PENDING_CLEAR_TTL_MILLIS = 300_000L;
|
||||
private static final long UNLOADED_CACHE_RETENTION_MILLIS = 300_000L;
|
||||
@@ -49,10 +55,12 @@ public final class EntitySpawnTracking {
|
||||
private static final long ENTITY_SCAN_TIMEOUT_SECONDS = 30L;
|
||||
private static final Map<UUID, TrackedLocation> trackedLocations = new ConcurrentHashMap<>();
|
||||
private static final Map<UUID, PendingClear> pendingClear = new ConcurrentHashMap<>();
|
||||
private static final Map<UUID, PendingIdentityConfirmation> pendingIdentityConfirmations = new ConcurrentHashMap<>();
|
||||
private static final Set<UUID> coreProtectRemovals = ConcurrentHashMap.newKeySet();
|
||||
private static final AtomicLong verificationEpoch = new AtomicLong();
|
||||
private static final AtomicLong nextCleanup = new AtomicLong();
|
||||
private static volatile NamespacedKey trackingKey;
|
||||
private static volatile NamespacedKey originSeedKey;
|
||||
|
||||
private EntitySpawnTracking() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
@@ -99,10 +107,98 @@ public final class EntitySpawnTracking {
|
||||
UUID uuid = entity.getUniqueId();
|
||||
Location location = entity.getLocation();
|
||||
pendingClear.remove(uuid);
|
||||
pendingIdentityConfirmations.remove(uuid);
|
||||
entity.getPersistentDataContainer().set(getKey(), PersistentDataType.BYTE, (byte) 1);
|
||||
entity.getPersistentDataContainer().remove(getOriginSeedKey());
|
||||
trackedLocations.compute(uuid, (key, previous) -> TrackedLocation.from(location, true, verificationEpoch.get()));
|
||||
}
|
||||
|
||||
public static void seedOrigin(Entity entity) {
|
||||
if (!isEligibleInteractionEntity(entity) || isTracked(entity)) {
|
||||
return;
|
||||
}
|
||||
byte[] existingSeed = entity.getPersistentDataContainer().get(getOriginSeedKey(), PersistentDataType.BYTE_ARRAY);
|
||||
if (existingSeed != null && existingSeed.length == ORIGIN_SEED_SIZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
Location location = entity.getLocation();
|
||||
if (location.getWorld() == null) {
|
||||
return;
|
||||
}
|
||||
int worldId = WorldUtils.getWorldId(location.getWorld().getName());
|
||||
if (worldId < 0) {
|
||||
return;
|
||||
}
|
||||
ByteBuffer buffer = ByteBuffer.allocate(ORIGIN_SEED_SIZE);
|
||||
buffer.putInt(worldId);
|
||||
buffer.putDouble(location.getX());
|
||||
buffer.putDouble(location.getY());
|
||||
buffer.putDouble(location.getZ());
|
||||
entity.getPersistentDataContainer().set(getOriginSeedKey(), PersistentDataType.BYTE_ARRAY, buffer.array());
|
||||
}
|
||||
|
||||
public static EntityInteractionOrigin getOrCreateInteractionOrigin(Entity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
seedOrigin(entity);
|
||||
byte[] data = entity.getPersistentDataContainer().get(getOriginSeedKey(), PersistentDataType.BYTE_ARRAY);
|
||||
if (data == null || data.length != ORIGIN_SEED_SIZE) {
|
||||
Location location = entity.getLocation();
|
||||
if (location.getWorld() == null) {
|
||||
return null;
|
||||
}
|
||||
int worldId = WorldUtils.getWorldId(location.getWorld().getName());
|
||||
return worldId < 0 ? null : new EntityInteractionOrigin(worldId, location.getX(), location.getY(), location.getZ());
|
||||
}
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(data);
|
||||
int worldId = buffer.getInt();
|
||||
double x = buffer.getDouble();
|
||||
double y = buffer.getDouble();
|
||||
double z = buffer.getDouble();
|
||||
return new EntityInteractionOrigin(worldId, x, y, z);
|
||||
}
|
||||
|
||||
public static boolean isEligibleInteractionEntity(Entity entity) {
|
||||
return entity instanceof LivingEntity && !(entity instanceof Player) && !(entity instanceof ArmorStand);
|
||||
}
|
||||
|
||||
public static void confirmDatabaseIdentity(UUID uuid, Location location) {
|
||||
if (uuid == null || location == null || location.getWorld() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
scheduleCleanup();
|
||||
PendingIdentityConfirmation confirmation = new PendingIdentityConfirmation(location, System.currentTimeMillis() + PENDING_CLEAR_TTL_MILLIS);
|
||||
pendingIdentityConfirmations.put(uuid, confirmation);
|
||||
CoreProtect plugin = CoreProtect.getInstance();
|
||||
if (plugin == null || !plugin.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Scheduler.runTask(plugin, () -> {
|
||||
Entity entity = Bukkit.getEntity(uuid);
|
||||
if (entity == null) {
|
||||
return;
|
||||
}
|
||||
Runnable confirm = () -> applyIdentityConfirmation(entity, confirmation);
|
||||
if (ConfigHandler.isFolia && !PaperAdapter.ADAPTER.isOwnedByCurrentRegion(entity)) {
|
||||
PaperAdapter.ADAPTER.executeEntityTask(plugin, entity, confirm, () -> {
|
||||
});
|
||||
}
|
||||
else {
|
||||
confirm.run();
|
||||
}
|
||||
}, location);
|
||||
}
|
||||
catch (Exception e) {
|
||||
ErrorReporter.report(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void trackRevivedEntity(Entity entity, UUID previousUuid) {
|
||||
track(entity);
|
||||
Queue.queueEntitySpawnRevived(previousUuid, entity.getUniqueId(), entity.getLocation());
|
||||
@@ -112,6 +208,10 @@ public final class EntitySpawnTracking {
|
||||
if (clearPendingTracking(entity)) {
|
||||
return;
|
||||
}
|
||||
PendingIdentityConfirmation confirmation = pendingIdentityConfirmations.get(entity.getUniqueId());
|
||||
if (confirmation != null && !confirmation.isExpired(System.currentTimeMillis())) {
|
||||
applyIdentityConfirmation(entity, confirmation);
|
||||
}
|
||||
if (isTracked(entity)) {
|
||||
observe(entity, true);
|
||||
}
|
||||
@@ -485,6 +585,7 @@ public final class EntitySpawnTracking {
|
||||
|
||||
public static void clearTracking(UUID uuid) {
|
||||
scheduleCleanup();
|
||||
pendingIdentityConfirmations.remove(uuid);
|
||||
PendingClear request = new PendingClear(System.currentTimeMillis() + PENDING_CLEAR_TTL_MILLIS);
|
||||
pendingClear.put(uuid, request);
|
||||
Location location = getCachedLocation(uuid);
|
||||
@@ -855,6 +956,40 @@ public final class EntitySpawnTracking {
|
||||
return key;
|
||||
}
|
||||
|
||||
private static NamespacedKey getOriginSeedKey() {
|
||||
NamespacedKey key = originSeedKey;
|
||||
if (key == null) {
|
||||
synchronized (EntitySpawnTracking.class) {
|
||||
key = originSeedKey;
|
||||
if (key == null) {
|
||||
key = new NamespacedKey(CoreProtect.getInstance(), ORIGIN_SEED_KEY);
|
||||
originSeedKey = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
private static void applyIdentityConfirmation(Entity entity, PendingIdentityConfirmation confirmation) {
|
||||
UUID uuid = entity.getUniqueId();
|
||||
if (!pendingIdentityConfirmations.remove(uuid, confirmation)) {
|
||||
return;
|
||||
}
|
||||
if (pendingClear.containsKey(uuid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
entity.getPersistentDataContainer().set(getKey(), PersistentDataType.BYTE, (byte) 1);
|
||||
entity.getPersistentDataContainer().remove(getOriginSeedKey());
|
||||
Location location = entity.getLocation();
|
||||
long epoch = verificationEpoch.get();
|
||||
boolean locationConfirmed = TrackedLocation.from(confirmation.location, true, epoch).matches(location);
|
||||
trackedLocations.put(uuid, TrackedLocation.from(location, true, locationConfirmed ? epoch : -1L));
|
||||
if (!locationConfirmed) {
|
||||
Queue.queueEntitySpawnLocation(uuid, location, epoch);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean clearPendingTracking(Entity entity) {
|
||||
UUID uuid = entity.getUniqueId();
|
||||
PendingClear request = pendingClear.get(uuid);
|
||||
@@ -869,6 +1004,7 @@ public final class EntitySpawnTracking {
|
||||
return false;
|
||||
}
|
||||
|
||||
pendingIdentityConfirmations.remove(uuid);
|
||||
entity.getPersistentDataContainer().remove(getKey());
|
||||
forget(uuid);
|
||||
return true;
|
||||
@@ -1000,6 +1136,26 @@ public final class EntitySpawnTracking {
|
||||
trackedLocations.remove(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
for (Map.Entry<UUID, PendingIdentityConfirmation> entry : pendingIdentityConfirmations.entrySet()) {
|
||||
if (entry.getValue().isExpired(now)) {
|
||||
pendingIdentityConfirmations.remove(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PendingIdentityConfirmation {
|
||||
|
||||
private final Location location;
|
||||
private final long expiry;
|
||||
|
||||
private PendingIdentityConfirmation(Location location, long expiry) {
|
||||
this.location = location.clone();
|
||||
this.expiry = expiry;
|
||||
}
|
||||
|
||||
private boolean isExpired(long now) {
|
||||
return expiry < now;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TrackedLocation {
|
||||
|
||||
Reference in new issue
Block a user