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: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ This repository is TF-Minecraft's fork of CoreProtect, developed by [PlayPro](ht
- **Detailed activity searches** — narrow results by player, time, action, location, and affected materials.
- **Targeted recovery** — roll back selected changes and restore previously rolled-back activity.
- **Recovery previews** — review the effect of a rollback or restore before applying it.
- **Broad world coverage** — logging includes player block changes, container contents, explosions, fire, liquids, many entity interactions, skills players use and where they teleport them, and where each online player is once a minute.
- **Broad world coverage** — logging includes player block changes, container contents, explosions, fire, liquids, many entity interactions, skills players use and where they teleport them, where each online player is once a minute, and every hit, knockout and kill between two players.
- **Custom item and mob labels** — item and chest lookups show saved MMOItems names and IDs; kill lookups show recorded mob names and, for new MythicMobs deaths, the internal mob ID.
- **Staff investigation tools** — paginated lookups, inventory history, and WorldEdit selection support help investigate incidents of different sizes.

Expand Down
1 change: 1 addition & 0 deletions lang/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ 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_PING: "{0} was online here."
LOOKUP_PVP: "{0} {hit|knocked out|killed} {1}{2}."
LOOKUP_PAGE: "Page {0}"
LOOKUP_PROJECTILE: "{0} {threw|shot} {1} {2}."
LOOKUP_ROWS_FOUND: "{0} {row|rows} found."
Expand Down
46 changes: 45 additions & 1 deletion src/main/java/net/tfminecraft/coreprotect/CoreProtectAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
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.PvpEvent;
import net.tfminecraft.coreprotect.listener.player.PlayerPvpListener;
import net.tfminecraft.coreprotect.model.action.LookupActions;
import net.tfminecraft.coreprotect.utility.Chat;
import net.tfminecraft.coreprotect.utility.MaterialUtils;
Expand Down Expand Up @@ -103,7 +105,7 @@ else if (value instanceof Integer) {
}

private static boolean isSupportedLegacyAction(Integer action) {
return action != null && (action == LookupActions.BLOCK_BREAK || action == LookupActions.BLOCK_PLACE || action == LookupActions.INTERACTION || action == LookupActions.ENTITY_KILL || action == LookupActions.ENTITY_SPAWN);
return action != null && (action == LookupActions.BLOCK_BREAK || action == LookupActions.BLOCK_PLACE || action == LookupActions.INTERACTION || action == LookupActions.ENTITY_KILL || action == LookupActions.ENTITY_SPAWN || LookupActions.isPvp(action));
}

/**
Expand Down Expand Up @@ -531,6 +533,48 @@ public boolean logLockChange(String user, Location location, String lockState, b
return Queue.queueLockChange(user, location.getBlock().getState(), location.getBlock().getType(), new LockChange(lockState, staffOverride));
}

/**
* Logs a nonlethal knockout of one player by another. Use this when a plugin cancels the
* knockout blow instead of letting the victim die, since cancelled hits are not logged.
*
* @param attacker
* The player who landed the knockout
* @param victim
* The player who was knocked out
* @return True if the knockout was queued for logging
*/
public boolean logPvpKnockout(Player attacker, Player victim) {
if (attacker == null || victim == null) {
return false;
}

return logPvpKnockout(attacker.getName(), victim.getName(), victim.getLocation(), PlayerPvpListener.heldItem(attacker));
}

/**
* Logs a nonlethal knockout of one player by another.
*
* @param attacker
* The attacker's username
* @param victim
* The victim's username
* @param location
* Where the victim was knocked out
* @param weapon
* The item that landed the knockout, for example "iron_sword", or null for an empty hand
* @return True if the knockout was queued for logging
*/
public boolean logPvpKnockout(String attacker, String victim, Location location, String weapon) {
if (!isEnabled() || !isValidUserAndLocation(attacker, location) || victim == null || victim.isEmpty() || attacker.equalsIgnoreCase(victim)) {
return false;
}
if (!Config.getConfig(location.getWorld()).PLAYER_PVP) {
return false;
}

return Queue.queuePlayerPvp(attacker, location, victim, LookupActions.PVP_KNOCKOUT, new PvpEvent(weapon, 0, "knockout"));
}

/**
* Logs a container transaction by a user at a location.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ public enum CoreProtectAction {
SIGN(LookupActions.SIGN),
ITEM(LookupActions.ITEM),
UNKNOWN(-1),
ENTITY_SPAWN(LookupActions.ENTITY_SPAWN);
ENTITY_SPAWN(LookupActions.ENTITY_SPAWN),
PVP_HIT(LookupActions.PVP_HIT),
PVP_KNOCKOUT(LookupActions.PVP_KNOCKOUT),
PVP_KILL(LookupActions.PVP_KILL);

private final int id;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,10 @@ else if (resultc > 1) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION));
return;
}
if (argAction.stream().anyMatch(LookupActions::isPvp) && !player.hasPermission("coreprotect.lookup.pvp")) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION));
return;
}
if (argAction.contains(LookupActions.ENTITY_SPAWN) && !player.hasPermission("coreprotect.lookup.spawn")) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION));
return;
Expand Down Expand Up @@ -580,6 +584,11 @@ else if (type == 4 || type == 5) {
argExclude = ConfigHandler.lookupElist.get(player.getName());
argExcludeUsers = ConfigHandler.lookupEUserlist.get(player.getName());
argAction = ConfigHandler.lookupAlist.get(player.getName());
// A page lookup reuses the saved actions, so check them against the current permissions
if (argAction != null && argAction.stream().anyMatch(LookupActions::isPvp) && !player.hasPermission("coreprotect.lookup.pvp")) {
Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION));
return;
}
argEntityActionFilter = ConfigHandler.lookupEntityActionFilter.getOrDefault(player.getName(), EntityActionFilter.DEFAULT);
argFilters = ConfigHandler.lookupFlist.getOrDefault(player.getName(), Collections.emptyList());
argRadius = ConfigHandler.lookupRadius.get(player.getName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ public void run() {
else if (PurgePolicy.isPurgeable(table)) {
String blockRestriction = "(";
if (hasBlockRestriction && PurgePolicy.supportsBlockRestriction(table)) {
blockRestriction = "action IN(" + LookupActions.ENTITY_KILL + "," + LookupActions.ENTITY_SPAWN + ") OR type NOT IN(" + includeBlockFinal + ") OR (type IN(" + includeBlockFinal + ") AND ";
blockRestriction = "action IN(" + LookupActions.NON_BLOCK_ACTIONS + ") OR type NOT IN(" + includeBlockFinal + ") OR (type IN(" + includeBlockFinal + ") AND ";
}
else if (hasBlockRestriction) {
purge = false;
Expand Down Expand Up @@ -574,7 +574,7 @@ else if (argWid == 0 && purge) {

String blockRestriction = "";
if (hasBlockRestriction && PurgePolicy.supportsBlockRestriction(table)) {
blockRestriction = "action NOT IN(" + LookupActions.ENTITY_KILL + "," + LookupActions.ENTITY_SPAWN + ") AND type IN(" + includeBlockFinal + ") AND ";
blockRestriction = "action NOT IN(" + LookupActions.NON_BLOCK_ACTIONS + ") AND type IN(" + includeBlockFinal + ") AND ";
}
else if (hasBlockRestriction) {
purge = false;
Expand Down Expand Up @@ -646,7 +646,7 @@ else if (argWid > 0) {

String blockRestriction = "";
if (hasBlockRestriction && PurgePolicy.supportsBlockRestriction(table)) {
blockRestriction = "action NOT IN(" + LookupActions.ENTITY_KILL + "," + LookupActions.ENTITY_SPAWN + ") AND type IN(" + includeBlockFinal + ") AND ";
blockRestriction = "action NOT IN(" + LookupActions.NON_BLOCK_ACTIONS + ") AND type IN(" + includeBlockFinal + ") AND ";
}
else if (hasBlockRestriction) {
purge = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class TabHandler implements TabCompleter {
// private static String[] COMMANDS = new String[] { "help", "inspect", "rollback", "restore", "lookup", "purge", "reload", "status", "near", "undo" }; // max 10!
private static final String[] HELP = new String[] { "inspect", "rollback", "restore", "lookup", "purge", "teleport", "reload", "status", "params", "users", "time", "radius", "action", "include", "exclude", "filter" };
private static final String[] PARAMS = new String[] { "user:", "time:", "radius:", "action:", "include:", "exclude:", "filter:", "#container" };
private static final String[] ACTIONS = new String[] { "block", "+block", "-block", "click", "kill", "spawn", "+container", "-container", "container", "chat", "command", "skill", "+inventory", "-inventory", "inventory", "item", "+item", "-item", "sign", "session", "+session", "-session", "ping", "username" };
private static final String[] ACTIONS = new String[] { "block", "+block", "-block", "click", "kill", "pvp", "hit", "knockout", "pvp-kill", "spawn", "+container", "-container", "container", "chat", "command", "skill", "+inventory", "-inventory", "inventory", "item", "+item", "-item", "sign", "session", "+session", "-session", "ping", "username" };
private static final String[] NUMBERS = new String[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
private static final String[] TIMES = new String[] { "w", "d", "h", "m", "s" };
private static ArrayList<String> materials = null;
Expand Down Expand Up @@ -337,7 +337,7 @@ private List<String> handleActionParamCompletions(CommandSender sender, String c
for (String action : ACTIONS) {
String category = action.replaceFirst("^[+-]", "");
if (lookup) {
String permission = category.equals("skill") ? "command" : category.equals("ping") ? "session" : category;
String permission = category.equals("skill") ? "command" : category.equals("ping") ? "session" : List.of("hit", "knockout", "pvp-kill").contains(category) ? "pvp" : category;
if (!sender.hasPermission("coreprotect.lookup." + permission)) {
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
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.PvpEvent;
import net.tfminecraft.coreprotect.model.action.MythicIdentity;
import net.tfminecraft.coreprotect.database.statement.EntityStatement;
import net.tfminecraft.coreprotect.utility.AuditLabels;
Expand Down Expand Up @@ -557,6 +558,11 @@ else if (daction == ItemTransactionActions.SELL || daction == ItemTransactionAct
if (entityInteraction) {
dname = EntityInteractionLookup.entityName(dtype);
}
else if (LookupActions.isPvp(daction) && amount == -1) {
// PvP rows keep the victim's user id in the data column
dname = UserStatement.getName(connection, ddata);
isPlayer = true;
}
else if ((daction == LookupActions.ENTITY_KILL || daction == LookupActions.ENTITY_SPAWN) && !actions.contains(LookupActions.ITEM) && amount == -1) {
if (daction == LookupActions.ENTITY_KILL && dtype == 0) {
dname = UserStatement.getName(connection, ddata);
Expand Down Expand Up @@ -590,6 +596,7 @@ else if ((daction == LookupActions.ENTITY_KILL || daction == LookupActions.ENTIT
String selector = Selector.FIRST;
String action = "a:block";
LockChange lockChange = null;
PvpEvent pvpEvent = 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);
dname = AuditLabels.item(metadata, dtype, amount, dname);
Expand Down Expand Up @@ -636,6 +643,13 @@ else if (daction == LookupActions.ENTITY_SPAWN) {
tag = Color.GREEN + "+";
action = placedEntitySpawn ? "a:block" : "a:spawn";
}
else if (LookupActions.isPvp(daction)) {
pvpEvent = PvpEvent.fromLookupString(data[11]);
phrase = Phrase.LOOKUP_PVP; // {hit|knocked out|killed}
selector = (daction == LookupActions.PVP_HIT ? Selector.FIRST : daction == LookupActions.PVP_KNOCKOUT ? Selector.SECOND : Selector.THIRD);
tag = Color.RED + "-";
action = "a:" + LookupActions.getActionString(daction);
}
else if (daction == LookupActions.INTERACTION || daction == LookupActions.ENTITY_KILL) {
if (placedEntityKill) {
phrase = Phrase.LOOKUP_BLOCK;
Expand All @@ -658,7 +672,11 @@ else if (daction == LookupActions.INTERACTION || daction == LookupActions.ENTITY
}

String message;
if (lockChange != null) {
if (phrase == Phrase.LOOKUP_PVP) {
String detail = pvpEvent == null ? "" : Color.WHITE + rbd + pvpEvent.describe();
message = Phrase.build(phrase, Color.DARK_AQUA + rbd + dplayer + Color.WHITE + rbd, Color.DARK_AQUA + rbd + dname + Color.WHITE + rbd, detail, selector);
}
else 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,18 @@ else if (argument.equals("click") || argument.equals("clicks") || argument.equal
else if (argument.equals("death") || argument.equals("deaths") || argument.equals("entity-death") || argument.equals("entity-deaths") || argument.equals("kill") || argument.equals("kills") || argument.equals("entity-kill") || argument.equals("entity-kills")) {
result.add(LookupActions.ENTITY_KILL);
}
else if (argument.equals("pvp") || argument.equals("combat") || argument.equals("fight") || argument.equals("fights")) {
result.addAll(LookupActions.PVP_ACTIONS);
}
else if (argument.equals("hit") || argument.equals("hits") || argument.equals("attack") || argument.equals("attacks") || argument.equals("damage") || argument.equals("pvp-hit") || argument.equals("pvp-hits")) {
result.add(LookupActions.PVP_HIT);
}
else if (argument.equals("knockout") || argument.equals("knockouts") || argument.equals("ko") || argument.equals("pvp-knockout") || argument.equals("pvp-knockouts")) {
result.add(LookupActions.PVP_KNOCKOUT);
}
else if (argument.equals("pvp-kill") || argument.equals("pvp-kills") || argument.equals("player-kill") || argument.equals("player-kills")) {
result.add(LookupActions.PVP_KILL);
}
else if (argument.equals("spawn") || argument.equals("spawns") || argument.equals("entity-spawn") || argument.equals("entity-spawns")) {
result.add(LookupActions.ENTITY_SPAWN);
entityActionFilter = EntityActionFilter.SPAWNED;
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/net/tfminecraft/coreprotect/config/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ public class Config extends Language {
public int DUCKDB_THREADS;
public int MAX_RADIUS;
public int PLAYER_PINGS;
public boolean PLAYER_PVP;

static {
DEFAULT_VALUES.put("donation-key", "");
Expand Down Expand Up @@ -176,6 +177,7 @@ public class Config extends Language {
DEFAULT_VALUES.put("player-skills", "true");
DEFAULT_VALUES.put("player-sessions", "true");
DEFAULT_VALUES.put("player-pings", "60");
DEFAULT_VALUES.put("player-pvp", "true");
DEFAULT_VALUES.put("username-changes", "true");
DEFAULT_VALUES.put("worldedit", "true");

Expand Down Expand Up @@ -228,6 +230,7 @@ public class Config extends Language {
HEADERS.put("player-skills", new String[] { "# Logs MythicLib skills that players activate, and where skills teleport them.", "# Search them with \"/co lookup a:skill\"." });
HEADERS.put("player-sessions", new String[] { "# Logs the logins and logouts of players." });
HEADERS.put("player-pings", new String[] { "# Logs the position of each online player at this interval, in seconds.", "# Search them with \"/co lookup a:ping\". Set to \"0\" to disable." });
HEADERS.put("player-pvp", new String[] { "# Logs every hit, knockout and kill between two players, with the weapon used.", "# Search them with \"/co lookup a:pvp\", or \"a:hit\", \"a:knockout\" and \"a:pvp-kill\"." });
HEADERS.put("username-changes", new String[] { "# Logs when a player changes their Minecraft username." });
HEADERS.put("worldedit", new String[] { "# Logs changes made via the plugin \"WorldEdit\" if it's in use on your server." });
}
Expand Down Expand Up @@ -309,6 +312,7 @@ private void readValues() {
this.PLAYER_SKILLS = this.getBoolean("player-skills");
this.PLAYER_SESSIONS = this.getBoolean("player-sessions");
this.PLAYER_PINGS = this.getInt("player-pings");
this.PLAYER_PVP = this.getBoolean("player-pvp");
this.USERNAME_CHANGES = this.getBoolean("username-changes");
this.WORLDEDIT = this.getBoolean("worldedit");
}
Expand Down
Loading