From a5618c1e52773266f9290691edbbe8681c8f9b17 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 16 Aug 2026 16:37:40 -0700 Subject: [PATCH] feat: audit log every successful chunk claim Log player name, UUID, position, look direction, action type, whether a block was clicked (vs air ray-trace), sneaking state, target chunk, distance to the nearest border of the claimed chunk, confirmation setting, chunk count, and credit before/after. Motivated by a report of two chunks being claimed while the player was mining a cobble generator "nowhere near the border". The audit trail will reveal the exact trigger (LEFT_CLICK_AIR gap, border distance, sneaking state) if this happens again. Also changes attemptClaim to return ClaimResult so the caller can react to the outcome. Co-Authored-By: Claude Opus 4.6 (1M context) Claude-Session: https://claude.ai/code/session_01B94CWZDoiM9VevWtXb4RxF --- .../listeners/ChunkClaimListener.java | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/main/java/world/bentobox/chunkblock/listeners/ChunkClaimListener.java b/src/main/java/world/bentobox/chunkblock/listeners/ChunkClaimListener.java index 04e0f34..b37a059 100644 --- a/src/main/java/world/bentobox/chunkblock/listeners/ChunkClaimListener.java +++ b/src/main/java/world/bentobox/chunkblock/listeners/ChunkClaimListener.java @@ -140,7 +140,11 @@ public void onBorderHit(PlayerInteractEvent e) { denyClaim(user, island); return; } - attemptClaim(user, island, target[0], target[1]); + long creditBefore = addon.getChunkManager().getCredit(island); + ClaimResult result = attemptClaim(user, island, target[0], target[1]); + if (result == ClaimResult.OK) { + logClaimAudit(player, island, e.getAction(), clicked != null, target[0], target[1], creditBefore); + } } /** @@ -210,7 +214,7 @@ private int[] findTargetLockedChunk(Player player, Island island) { * @param chunkX target world chunk x * @param chunkZ target world chunk z */ - public void attemptClaim(User user, Island island, int chunkX, int chunkZ) { + public ClaimResult attemptClaim(User user, Island island, int chunkX, int chunkZ) { ChunkManager cm = addon.getChunkManager(); // Price the chunk before spending anything: only a claim that would actually go // through is worth asking the player to confirm @@ -218,7 +222,7 @@ public void attemptClaim(User user, Island island, int chunkX, int chunkZ) { && cm.checkGeometry(island, chunkX, chunkZ) == ClaimResult.OK && cm.getCredit(island) >= cm.getChunkCost() && !confirming(user, island, chunkX, chunkZ)) { preview(user, island, chunkX, chunkZ); - return; + return null; } ClaimResult result = cm.claim(island, chunkX, chunkZ); switch (result) { @@ -242,6 +246,7 @@ public void attemptClaim(User user, Island island, int chunkX, int chunkZ) { // Aiming at a diagonal corner or a chunk already owned: no claim, no nag } } + return result; } /** @@ -299,6 +304,27 @@ private void clearPending(UUID uuid) { } } + private void logClaimAudit(Player player, Island island, Action action, boolean clickedBlock, int chunkX, + int chunkZ, long creditBefore) { + ChunkManager cm = addon.getChunkManager(); + Location loc = player.getLocation(); + int cx = island.getCenter().getBlockX() >> 4; + int cz = island.getCenter().getBlockZ() >> 4; + int dx = chunkX - cx; + int dz = chunkZ - cz; + int borderBlockX = dx > 0 ? chunkX << 4 : ((chunkX + 1) << 4) - 1; + int borderBlockZ = dz > 0 ? chunkZ << 4 : ((chunkZ + 1) << 4) - 1; + double dist = Math.min(Math.abs(loc.getX() - borderBlockX), Math.abs(loc.getZ() - borderBlockZ)); + addon.log(String.format( + "CHUNK CLAIM: player=%s uuid=%s at=[%.1f, %.1f, %.1f] yaw=%.0f pitch=%.0f " + + "action=%s clickedBlock=%b sneaking=%b chunk=(%d,%d) offset=(%d,%d) " + + "borderDist=%.1f confirm=%b count=%d credit=%d->%d", + player.getName(), player.getUniqueId(), loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), + loc.getPitch(), action, clickedBlock, player.isSneaking(), chunkX, chunkZ, dx, dz, dist, + addon.getSettings().isRequireClaimConfirmation(), cm.getUnlockedChunkCount(island), creditBefore, + cm.getCredit(island))); + } + private long timeoutMillis() { return addon.getSettings().getClaimConfirmationTimeout() * 1000L; }