Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lang/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ LOOKUP_ENTITY_ORIGIN: "Spawn location"
LOOKUP_ENTITY_SPAWN: "{0} spawned {1}."
LOOKUP_HEADER: "{0} Lookup Results"
LOOKUP_INTERACTION: "{0} {clicked|killed} {1}."
LOOKUP_LOCK_CHANGE: "{0} set {1} lock to {2}."
LOOKUP_LOCK_CHANGE_STAFF: "{0} set {1} lock to {2} (staff override)."
LOOKUP_ITEM: "{0} {picked up|dropped} {1} {2}."
LOOKUP_LOGIN: "{0} logged {in|out}."
LOOKUP_PAGE: "Page {0}"
Expand Down
25 changes: 24 additions & 1 deletion src/main/java/net/tfminecraft/coreprotect/CoreProtectAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import net.tfminecraft.coreprotect.database.rollback.Rollback;
import net.tfminecraft.coreprotect.language.Phrase;
import net.tfminecraft.coreprotect.listener.player.InventoryChangeListener;
import net.tfminecraft.coreprotect.model.action.LockChange;
import net.tfminecraft.coreprotect.model.action.LookupActions;
import net.tfminecraft.coreprotect.utility.Chat;
import net.tfminecraft.coreprotect.utility.MaterialUtils;
Expand All @@ -60,7 +61,7 @@ public class CoreProtectAPI extends Queue {
/**
* Current version of the API
*/
private static final int API_VERSION = 13;
private static final int API_VERSION = 14;
private static final AtomicLong API_ROLLBACK_SEQUENCE = new AtomicLong();

public static class ParseResult extends net.tfminecraft.coreprotect.api.result.ParseResult {
Expand Down Expand Up @@ -508,6 +509,28 @@ public boolean logInteraction(String user, Location location) {
return true;
}

/**
* Logs a lock state change by a user at a location. Lookups show it as
* "user set block lock to state", and it is never rolled back.
*
* @param user
* The username
* @param location
* The location of the locked block
* @param lockState
* The new lock state, as it should be shown to staff (for example "Private")
* @param staffOverride
* True if staff changed a lock owned by another player
* @return True if the change was queued for logging
*/
public boolean logLockChange(String user, Location location, String lockState, boolean staffOverride) {
if (!isEnabled() || !isValidUserAndLocation(user, location) || lockState == null || lockState.isEmpty()) {
return false;
}

return Queue.queueLockChange(user, location.getBlock().getState(), location.getBlock().getType(), new LockChange(lockState, staffOverride));
}

/**
* Logs a container transaction by a user at a location.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import net.tfminecraft.coreprotect.listener.channel.PluginChannelHandshakeListener;
import net.tfminecraft.coreprotect.listener.channel.PluginChannelListener;
import net.tfminecraft.coreprotect.model.action.EntityActionFilter;
import net.tfminecraft.coreprotect.model.action.LockChange;
import net.tfminecraft.coreprotect.model.action.LookupActions;
import net.tfminecraft.coreprotect.model.action.SessionActions;
import net.tfminecraft.coreprotect.model.entity.EntitySpawnRecord;
Expand Down Expand Up @@ -562,6 +563,7 @@ else if ((daction == LookupActions.ENTITY_KILL || daction == LookupActions.ENTIT
Phrase phrase = Phrase.LOOKUP_BLOCK;
String selector = Selector.FIRST;
String action = "a:block";
LockChange lockChange = null;
if (actions.contains(LookupActions.CONTAINER) || actions.contains(5) || actions.contains(LookupActions.ITEM) || amount > -1) {
byte[] metadata = data[11] == null ? null : data[11].getBytes(StandardCharsets.ISO_8859_1);
String tooltip = ItemUtils.getEnchantments(metadata, dtype, amount);
Expand Down Expand Up @@ -615,6 +617,7 @@ else if (daction == LookupActions.INTERACTION || daction == LookupActions.ENTITY
action = "a:block";
}
else {
lockChange = daction == LookupActions.INTERACTION ? LockChange.fromLookupString(data[11]) : null;
phrase = Phrase.LOOKUP_INTERACTION; // {clicked|killed}
selector = (daction != LookupActions.ENTITY_KILL ? Selector.FIRST : Selector.SECOND);
tag = (daction != LookupActions.ENTITY_KILL ? Color.WHITE + "-" : Color.RED + "-");
Expand All @@ -627,7 +630,14 @@ else if (daction == LookupActions.INTERACTION || daction == LookupActions.ENTITY
tag = (daction != LookupActions.BLOCK_BREAK ? Color.GREEN + "+" : Color.RED + "-");
}

Chat.sendComponent(player, timeago + " " + tag + " " + Phrase.build(phrase, Color.DARK_AQUA + rbd + dplayer + Color.WHITE + rbd, Color.DARK_AQUA + rbd + dname + Color.WHITE, selector));
String message;
if (lockChange != null) {
message = Phrase.build(lockChange.isStaffOverride() ? Phrase.LOOKUP_LOCK_CHANGE_STAFF : Phrase.LOOKUP_LOCK_CHANGE, Color.DARK_AQUA + rbd + dplayer + Color.WHITE + rbd, Color.DARK_AQUA + rbd + dname + Color.WHITE + rbd, Color.DARK_AQUA + rbd + lockChange.getLockState() + Color.WHITE);
}
else {
message = Phrase.build(phrase, Color.DARK_AQUA + rbd + dplayer + Color.WHITE + rbd, Color.DARK_AQUA + rbd + dname + Color.WHITE, selector);
}
Chat.sendComponent(player, timeago + " " + tag + " " + message);
PluginChannelListener.getInstance().sendData(player, Integer.parseInt(time), phrase, selector, dplayer, dname, (tag.contains("+") ? 1 : -1), dataX, dataY, dataZ, wid, rbd, false, tag.contains("+"));
}

Expand Down
5 changes: 5 additions & 0 deletions src/main/java/net/tfminecraft/coreprotect/consumer/Queue.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import net.tfminecraft.coreprotect.consumer.process.Process;
import net.tfminecraft.coreprotect.listener.block.BlockUtil;
import net.tfminecraft.coreprotect.model.BlockGroup;
import net.tfminecraft.coreprotect.model.action.LockChange;
import net.tfminecraft.coreprotect.model.entity.EntityContainerRollbackUpdate;
import net.tfminecraft.coreprotect.model.entity.EntityContainerTransaction;
import net.tfminecraft.coreprotect.model.entity.EntityInteraction;
Expand Down Expand Up @@ -508,6 +509,10 @@ protected static void queuePlayerInteraction(String user, BlockState block, Mate
queueStandardData(new Object[] { null, Process.PLAYER_INTERACTION, type, 0, null, 0, 0, null }, new String[] { user, null }, block, false, Consumer.reserveConsumer());
}

protected static boolean queueLockChange(String user, BlockState block, Material type, LockChange lockChange) {
return queueStandardData(new Object[] { null, Process.LOCK_CHANGE, type, 0, null, 0, 0, null }, new String[] { user, null }, new Object[] { block, lockChange }, false, Consumer.reserveConsumer());
}

protected static void queuePlayerKill(String user, Location location, String player) {
queueStandardData(new Object[] { null, Process.PLAYER_KILL, null, 0, null, 0, 0, null }, new String[] { user, null }, new Object[] { getBlockLocation(location), player }, false, Consumer.reserveConsumer());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package net.tfminecraft.coreprotect.consumer.process;

import org.bukkit.Material;
import org.bukkit.block.BlockState;

import net.tfminecraft.coreprotect.database.ConsumerWriteBatch;
import net.tfminecraft.coreprotect.database.logger.PlayerInteractLogger;
import net.tfminecraft.coreprotect.model.action.LockChange;

class LockChangeProcess {

private LockChangeProcess() {
throw new IllegalStateException("Process class");
}

static void process(ConsumerWriteBatch preparedStmt, int batchCount, String user, Object object, Material type) {
if (object instanceof Object[]) {
Object[] values = (Object[]) object;
if (values.length > 1 && values[0] instanceof BlockState && values[1] instanceof LockChange) {
PlayerInteractLogger.log(preparedStmt, batchCount, user, (BlockState) values[0], type, (LockChange) values[1]);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public class Process {
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 final int LOCK_CHANGE = 36;

public static int lastLockUpdate = 0;
private static volatile int currentConsumerSize = 0;
Expand Down Expand Up @@ -294,6 +295,9 @@ else if (action == Process.ENTITY_SPAWN_UPDATE || action == Process.ENTITY_CONTA
case Process.PLAYER_INTERACTION:
PlayerInteractionProcess.process(writeBatch, i, user, object, blockType);
break;
case Process.LOCK_CHANGE:
LockChangeProcess.process(writeBatch, i, user, object, blockType);
break;
case Process.CONTAINER_TRANSACTION:
ContainerTransactionProcess.process(writeBatch, writeBatch, i, processId, id, blockType, forceData, user, object);
break;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package net.tfminecraft.coreprotect.database.logger;

import java.util.List;

import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Location;
Expand All @@ -13,6 +15,7 @@
import net.tfminecraft.coreprotect.database.statement.BlockStatement;
import net.tfminecraft.coreprotect.database.statement.UserStatement;
import net.tfminecraft.coreprotect.event.CoreProtectPreLogEvent;
import net.tfminecraft.coreprotect.model.action.LockChange;
import net.tfminecraft.coreprotect.model.action.LookupActions;
import net.tfminecraft.coreprotect.utility.BlockTypeUtils;
import net.tfminecraft.coreprotect.utility.MaterialUtils;
Expand All @@ -25,6 +28,10 @@ private PlayerInteractLogger() {
}

public static void log(ConsumerWriteBatch preparedStmt, int batchCount, String user, BlockState block, Material blockType) {
log(preparedStmt, batchCount, user, block, blockType, null);
}

public static void log(ConsumerWriteBatch preparedStmt, int batchCount, String user, BlockState block, Material blockType, LockChange lockChange) {
try {
String blockData = block.getBlockData().getAsString();
String blockKey = BlockTypeUtils.getBlockDataKey(blockData);
Expand Down Expand Up @@ -64,7 +71,8 @@ public static void log(ConsumerWriteBatch preparedStmt, int batchCount, String u
int y = eventLocation.getBlockY();
int z = eventLocation.getBlockZ();
int data = 0;
BlockStatement.insert(preparedStmt, batchCount, time, userId, wid, x, y, z, type, data, null, blockData, LookupActions.INTERACTION, 0);
List<Object> meta = lockChange == null ? null : lockChange.toMetadata();
BlockStatement.insert(preparedStmt, batchCount, time, userId, wid, x, y, z, type, data, meta, blockData, LookupActions.INTERACTION, 0);
}
catch (Exception e) {
Database.handleWriteFailure(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import net.tfminecraft.coreprotect.language.Phrase;
import net.tfminecraft.coreprotect.language.Selector;
import net.tfminecraft.coreprotect.listener.channel.PluginChannelListener;
import net.tfminecraft.coreprotect.model.action.LockChange;
import net.tfminecraft.coreprotect.model.action.LookupActions;
import net.tfminecraft.coreprotect.utility.*;
import net.tfminecraft.coreprotect.utility.ErrorReporter;
Expand Down Expand Up @@ -58,15 +59,16 @@ else if (commandSender.hasPermission("coreprotect.coreprotect")) {
}

String blockName = block.getType().name().toLowerCase(Locale.ROOT);
String actionPredicate = "(action IN(0,1," + LookupActions.ENTITY_SPAWN + ") OR (action=" + LookupActions.ENTITY_KILL + " AND type IN(" + placedEntityTypeIds() + ")))";
// Plain clicks have no metadata; interaction rows with metadata are lock changes, which belong in block history.
String actionPredicate = "(action IN(0,1," + LookupActions.ENTITY_SPAWN + ") OR (action=" + LookupActions.ENTITY_KILL + " AND type IN(" + placedEntityTypeIds() + ")) OR (action=" + LookupActions.INTERACTION + " AND meta IS NOT NULL))";

String where = "wid = " + worldId + " AND x = " + x + " AND z = " + z + " AND y = " + y + " AND " + actionPredicate + " AND time >= " + checkTime;
boolean combinedDuckDBPage = ConfigHandler.databaseType.isDuckDB();
String query;
ResultSet results;
if (combinedDuckDBPage) {
String sourceTable = DuckDBLookupQuery.spatialTable(statement.getConnection(), "block", worldId, x, x, z, z, "spatial_rows");
String columns = "data_rows.time,data_rows." + ConfigHandler.databaseType.getUserColumn() + ",data_rows.action,data_rows.type,data_rows.data,data_rows.rolled_back";
String columns = "data_rows.time,data_rows." + ConfigHandler.databaseType.getUserColumn() + ",data_rows.action,data_rows.type,data_rows.data,data_rows.meta,data_rows.rolled_back";
query = DuckDBLookupQuery.pageQuery(sourceTable, ConfigHandler.prefix + "block", where, columns, false, limit, page_start);
results = statement.executeQuery(query);
}
Expand All @@ -77,7 +79,7 @@ else if (commandSender.hasPermission("coreprotect.coreprotect")) {
count = results.getInt("count");
}
results.close();
query = "SELECT time," + ConfigHandler.databaseType.getUserColumn() + ",action,type,data,rolled_back FROM " + ConfigHandler.prefix + "block " + WorldUtils.getWidIndex("block") + "WHERE " + where + " ORDER BY " + ConfigHandler.getDescendingEventOrder() + " LIMIT " + limit + " OFFSET " + page_start;
query = "SELECT time," + ConfigHandler.databaseType.getUserColumn() + ",action,type,data,meta,rolled_back FROM " + ConfigHandler.prefix + "block " + WorldUtils.getWidIndex("block") + "WHERE " + where + " ORDER BY " + ConfigHandler.getDescendingEventOrder() + " LIMIT " + limit + " OFFSET " + page_start;
results = statement.executeQuery(query);
}

Expand All @@ -96,6 +98,7 @@ else if (commandSender.hasPermission("coreprotect.coreprotect")) {
int resultData = results.getInt("data");
long resultTime = results.getLong("time");
int resultRolledBack = results.getInt("rolled_back");
LockChange lockChange = resultAction == LookupActions.INTERACTION ? LockChange.fromMetadata(DatabaseUtils.getBytes(results, "meta")) : null;

String resultUser = UserStatement.getName(statement.getConnection(), resultUserId);
String timeAgo = ChatUtils.getTimeSince(resultTime, time, true);
Expand Down Expand Up @@ -153,7 +156,14 @@ else if (resultAction == 2 || resultAction == 3) {
target = target.split(":")[1];
}

resultTextBuilder.append(timeAgo + " " + tag + " ").append(Phrase.build(phrase, Color.DARK_AQUA + rbFormat + resultUser + Color.WHITE + rbFormat, Color.DARK_AQUA + rbFormat + target + Color.WHITE, selector)).append("\n");
String message;
if (lockChange != null) {
message = Phrase.build(lockChange.isStaffOverride() ? Phrase.LOOKUP_LOCK_CHANGE_STAFF : Phrase.LOOKUP_LOCK_CHANGE, Color.DARK_AQUA + rbFormat + resultUser + Color.WHITE + rbFormat, Color.DARK_AQUA + rbFormat + target + Color.WHITE + rbFormat, Color.DARK_AQUA + rbFormat + lockChange.getLockState() + Color.WHITE);
}
else {
message = Phrase.build(phrase, Color.DARK_AQUA + rbFormat + resultUser + Color.WHITE + rbFormat, Color.DARK_AQUA + rbFormat + target + Color.WHITE, selector);
}
resultTextBuilder.append(timeAgo + " " + tag + " ").append(message).append("\n");
PluginChannelListener.getInstance().sendData(commandSender, resultTime, phrase, selector, resultUser, target, -1, x, y, z, worldId, rbFormat, false, tag.contains("+"));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ public static void loadPhrases() {
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_LOCK_CHANGE, "{0} set {1} lock to {2}.");
phrases.put(Phrase.LOOKUP_LOCK_CHANGE_STAFF, "{0} set {1} lock to {2} (staff override).");
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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ public enum Phrase {
LOOKUP_CONTAINER,
LOOKUP_HEADER,
LOOKUP_INTERACTION,
LOOKUP_LOCK_CHANGE,
LOOKUP_LOCK_CHANGE_STAFF,
LOOKUP_ITEM,
LOOKUP_LOGIN,
LOOKUP_PAGE,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package net.tfminecraft.coreprotect.model.action;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;

import net.tfminecraft.coreprotect.database.statement.BlockStatement;

/**
* A lock state change logged by another plugin. It is stored as an interaction row whose block
* metadata starts with {@link #MARKER}, so rollback, purge and user lookups treat it like any
* other click.
*/
public final class LockChange {

public static final String MARKER = "coreprotect:lock";

private final String lockState;
private final boolean staffOverride;

public LockChange(String lockState, boolean staffOverride) {
this.lockState = lockState;
this.staffOverride = staffOverride;
}

public String getLockState() {
return lockState;
}

public boolean isStaffOverride() {
return staffOverride;
}

public List<Object> toMetadata() {
return Arrays.asList(MARKER, lockState, staffOverride);
}

public static LockChange fromMetadata(List<Object> metadata) {
if (metadata == null || metadata.size() < 3 || !MARKER.equals(metadata.get(0)) || !(metadata.get(1) instanceof String)) {
return null;
}
return new LockChange((String) metadata.get(1), Boolean.TRUE.equals(metadata.get(2)));
}

public static LockChange fromMetadata(byte[] metadata) {
return metadata == null ? null : fromMetadata(BlockStatement.deserializeMetadata(metadata));
}

// Lookup rows carry metadata as ISO-8859-1 strings; see LookupConverter.
public static LockChange fromLookupString(String metadata) {
return metadata == null || metadata.isEmpty() ? null : fromMetadata(metadata.getBytes(StandardCharsets.ISO_8859_1));
}
}