I wrote a very simple Minecraft Paper plugin that logs every player interaction with vanilla containers (barrels, chests, copper chests, ender chests, shulker boxes, dispensers, droppers, and hoppers) to the server log. This is useful for small to medium servers that want to keep track of items without the overhead of a full logging/anti-cheat plugin.
You can download the .jar from the plugin's
CurseForge page
to add it to your server. Find the source code on the
GitHub repository.
The plugin's functionality is extremely simple. It takes a snapshot of
the container's contents on every InventoryClickEvent and
then compares the snapshot to the container's contents during tick
N + 1, when the changes from the event have been applied.
The logic for taking the difference between the two snapshots and
logging it can be seen here:
...
private void diffAndLog(ItemStack[] before, ItemStack[] after, Inventory chest, Player player) {
Map<ItemType, Integer> diffs = new HashMap<>();
String chestType = chest.getType().toString();
Location chestLoc = chest.getLocation();
if (chestLoc == null) {
return;
}
String chestLocStr = "(" + chestLoc.getBlockX()
+ ", " + chestLoc.getBlockY()
+ ", " + chestLoc.getBlockZ() + ")";
for (int i = 0; i < before.length; i++) {
ItemStack bef = before[i];
ItemStack aft = after[i];
if (isEmpty(bef) && isEmpty(aft)) {
continue;
}
if (isEmpty(bef)) {
addToDiff(aft.getType().asItemType(), aft.getAmount(), diffs);
continue;
}
if (isEmpty(aft)) {
addToDiff(bef.getType().asItemType(), (-bef.getAmount()), diffs);
continue;
}
if (bef.getType().equals(aft.getType()) && bef.getAmount() == aft.getAmount()) {
continue;
}
addToDiff(aft.getType().asItemType(), aft.getAmount(), diffs);
addToDiff(bef.getType().asItemType(), (-bef.getAmount()), diffs);
}
diffs.forEach((k, v) -> {
if (v > 0) {
plugin.getLogger().info(player.getName() + " put " + v + "x " + k.getKey().getKey()
+ " in " + chestType + " at " + chestLocStr);
} else if (v < 0) {
plugin.getLogger().info(player.getName() + " took " + (-v) + "x " + k.getKey().getKey()
+ " from " + chestType + " at " + chestLocStr);
}
});
}
...
The plugin also handles InventoryDragEvent, which is
actually easier, as the event provides the raw slot IDs of the slots
that will be changed by the event. The handler for these events can be
seen here:
...
@EventHandler
public void onInventoryDrag(InventoryDragEvent event) {
if (!(event.getWhoClicked() instanceof Player player) || plugin.isOff()) {
return;
}
Inventory chest = event.getView().getTopInventory();
if (chest.getSize() <= 0 || isNotContainer(chest.getType())) {
return;
}
Map<Integer, ItemStack> newItems = event.getNewItems();
if (newItems.isEmpty()) {
return;
}
ItemStack[] before = new ItemStack[chest.getSize()];
ItemStack[] after = new ItemStack[chest.getSize()];
newItems.forEach((k, v) -> {
if (k < chest.getSize()) {
after[k] = v;
}
});
diffAndLog(before, after, chest, player);
}
...
SimpleChestLogging requires no configuration and can be dropped into
the plugins folder of any Paper server on a stable release for
versions 1.21.11 through 26.2 of Minecraft.
- /simplecl on: requires operator - turns
SimpleChestLogging on
- /simplecl off: requires operator - turns
SimpleChestLogging off
And that's it! Chest interactions will show up in the logs in the following format:
Notch put 3x iron_ingot in CHEST at (-640, 63, 216)
Notch took 64x cooked_beef from SHULKER_BOX at (-88, 65, -192)