diff --git a/src/main/java/link/star_dust/MinerTrack/listeners/MiningListener.java b/src/main/java/link/star_dust/MinerTrack/listeners/MiningListener.java index 2853d10..cd15d66 100644 --- a/src/main/java/link/star_dust/MinerTrack/listeners/MiningListener.java +++ b/src/main/java/link/star_dust/MinerTrack/listeners/MiningListener.java @@ -18,6 +18,7 @@ import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; import org.bukkit.block.data.Levelled; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -25,6 +26,7 @@ import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitRunnable; import java.util.function.Consumer; @@ -45,6 +47,7 @@ public class MiningListener implements Listener { private final MinerTrack plugin; // Store mining paths per world: worldName -> (playerUUID -> path) private final Map>> miningPath = new HashMap<>(); + private final Map>> airExposurePath = new HashMap<>(); private final Map lastMiningTime = new HashMap<>(); private final Map violationLevel = new HashMap<>(); private final Map minedVeinCount = new HashMap<>(); @@ -112,6 +115,25 @@ public void onPlayerJoin(PlayerJoinEvent event) { plugin.getVerbosePlayers().remove(playerUUID); } + @EventHandler(ignoreCancelled = true) + public void onPlayerTeleport(PlayerTeleportEvent event) { + Location from = event.getFrom(); + Location to = event.getTo(); + + if (to == null) { + return; + } + + if (Objects.equals(from.getWorld(), to.getWorld()) + && from.getBlockX() == to.getBlockX() + && from.getBlockY() == to.getBlockY() + && from.getBlockZ() == to.getBlockZ()) { + return; + } + + clearPlayerPathTracking(event.getPlayer().getUniqueId()); + } + @EventHandler public void onBlockPlace(BlockPlaceEvent event) { String worldName = event.getBlock().getWorld().getName(); @@ -310,20 +332,28 @@ private void handleXRayDetection(Player player, Material blockType, Location blo // Initialize world's player path information miningPath.putIfAbsent(worldName, new HashMap<>()); Map> worldPlayers = miningPath.get(worldName); + airExposurePath.putIfAbsent(worldName, new HashMap<>()); + Map> worldAirExposure = airExposurePath.get(worldName); worldPlayers.putIfAbsent(playerId, new ArrayList<>()); List path = worldPlayers.get(playerId); + worldAirExposure.putIfAbsent(playerId, new ArrayList<>()); + List exposedPath = worldAirExposure.get(playerId); // Update mining path and time path.add(blockLocation); + exposedPath.add(isExposedToAir(blockLocation)); lastMiningTime.put(playerId, currentTime); if (path.size() > maxPathLength) { path.remove(0); } + if (exposedPath.size() > maxPathLength) { + exposedPath.remove(0); + } // Check new veins - checkForArtificialAir(player, path); + checkForArtificialAir(player, exposedPath); boolean smooth = isSmoothPath(player.getUniqueId(), path); boolean natural = isInNaturalEnvironment(worldName, player, blockLocation, path); @@ -345,38 +375,26 @@ private void handleXRayDetection(Player player, Material blockType, Location blo private void cleanupExpiredPaths() { long now = System.currentTimeMillis(); - // Iterate per world, then per player + Set playersToClear = new HashSet<>(); + for (Map.Entry>> worldEntry : miningPath.entrySet()) { String worldName = worldEntry.getKey(); long traceBackLength = plugin.getConfigManager().traceBackLength(worldName) * 60 * 1000L; // Convert minutes to ms Map> playerMap = worldEntry.getValue(); - - Set playersToClear = new HashSet<>(); + for (Map.Entry> p : playerMap.entrySet()) { UUID playerId = p.getKey(); - List path = p.getValue(); long lastTime = lastMiningTime.getOrDefault(playerId, 0L); if (lastTime > 0 && now - lastTime > traceBackLength) { playersToClear.add(playerId); - } else { - if (lastTime > 0) { - path.removeIf(loc -> now - lastTime > traceBackLength); - } } } - - for (UUID uuid : playersToClear) { - playerMap.remove(uuid); - lastMiningTime.remove(uuid); - lastVeinClusters.remove(uuid); - lastVeinLocation.remove(uuid); - minedVeinCount.remove(uuid); - } } - // Remove empty world entries - miningPath.entrySet().removeIf(e -> e.getValue().isEmpty()); + for (UUID playerId : playersToClear) { + clearPlayerPathTracking(playerId); + } } private boolean isNewVein(UUID playerId, String worldName, Location location, Material oreType) { @@ -650,7 +668,7 @@ private boolean isInNaturalEnvironment(String worldName, Player player, Location } } - if (airCount > airThreshold && plugin.getConfigManager().isCaveSkipVL(worldName) && airViolationLevel.getOrDefault(player, 0) < plugin.getConfigManager().AirMonitorVLT(worldName)) return true; + if (airCount > airThreshold && plugin.getConfigManager().isCaveSkipVL(worldName) && airViolationLevel.getOrDefault(player.getUniqueId(), 0) < plugin.getConfigManager().getAirMonitorViolationThreshold(worldName)) return true; if (waterCount > waterThreshold && plugin.getConfigManager().isSeaSkipVL(worldName)) return true; if (lavaCount > lavaThreshold && plugin.getConfigManager().isLavaSeaSkipVL(worldName)) return true; @@ -743,28 +761,27 @@ private boolean isPathConnected(Location start, Location end, List pat return false; } - private void checkForArtificialAir(Player player, List path) { - if (path == null || path.isEmpty()) return; - String worldName = path.get(0).getWorld().getName(); + private void checkForArtificialAir(Player player, List exposedPath) { + if (exposedPath == null || exposedPath.isEmpty()) return; + String worldName = player.getWorld().getName(); if (!plugin.getConfigManager().isAirMonitorEnabled(worldName)) { return; } int minPathLength = plugin.getConfigManager().getAirMonitorMinPathLength(worldName); - if (path.size() < minPathLength) { + if (exposedPath.size() < minPathLength) { return; } - int airBlockCount = 0; - for (Location loc : path) { - Material type = loc.getBlock().getType(); - if (type == Material.AIR || type == Material.CAVE_AIR) { - airBlockCount++; + int airExposureCount = 0; + for (Boolean exposed : exposedPath) { + if (Boolean.TRUE.equals(exposed)) { + airExposureCount++; } } - double airRatio = (double) airBlockCount / path.size(); + double airRatio = (double) airExposureCount / exposedPath.size(); double threshold = plugin.getConfigManager().getAirMonitorAirRatioThreshold(worldName); if (airRatio > threshold) { @@ -827,6 +844,7 @@ private void checkAndResetPaths() { long traceRemoveMillis = plugin.getConfigManager().getTraceRemoveTime(worldName) * 60 * 1000L; if (lastZeroTime != null && vl == 0 && now - lastZeroTime > traceRemoveMillis) { + clearPlayerPathTracking(playerId); // 从所有世界中移除该玩家的路径 for (Map> playerMap : miningPath.values()) { playerMap.remove(playerId); @@ -851,6 +869,7 @@ private void checkAndResetPaths() { public void checkAndResetPaths(UUID playerId) { if (playerId == null) return; + clearPlayerPathTracking(playerId); // Remove this player from all world maps for (Map> playerMap : miningPath.values()) { playerMap.remove(playerId); @@ -870,6 +889,48 @@ public void checkAndResetPaths(UUID playerId) { yChanges.remove(playerId); } + private void clearPlayerPathTracking(UUID playerId) { + removePlayerPathEntries(miningPath, playerId); + removePlayerPathEntries(airExposurePath, playerId); + lastMiningTime.remove(playerId); + minedVeinCount.remove(playerId); + lastVeinLocation.remove(playerId); + lastVeinClusters.remove(playerId); + totalTurns.remove(playerId); + branchCount.remove(playerId); + yChanges.remove(playerId); + } + + private void removePlayerPathEntries(Map>> trackedPaths, UUID playerId) { + for (Map> playerMap : trackedPaths.values()) { + playerMap.remove(playerId); + } + trackedPaths.entrySet().removeIf(entry -> entry.getValue().isEmpty()); + } + + private boolean isExposedToAir(Location location) { + Block block = location.getBlock(); + + for (BlockFace face : new BlockFace[] { + BlockFace.UP, + BlockFace.DOWN, + BlockFace.NORTH, + BlockFace.SOUTH, + BlockFace.EAST, + BlockFace.WEST + }) { + if (isAir(block.getRelative(face).getType())) { + return true; + } + } + + return false; + } + + private boolean isAir(Material material) { + return material == Material.AIR || material == Material.CAVE_AIR || material == Material.VOID_AIR; + } + private void increaseViolationLevel(Player player, int amount, String blockType, int count, int vein, Location location) { UUID playerId = player.getUniqueId(); violationLevel.put(playerId, violationLevel.getOrDefault(playerId, 0) + amount); diff --git a/src/main/java/link/star_dust/MinerTrack/managers/ConfigManager.java b/src/main/java/link/star_dust/MinerTrack/managers/ConfigManager.java index d34e195..05df9ba 100644 --- a/src/main/java/link/star_dust/MinerTrack/managers/ConfigManager.java +++ b/src/main/java/link/star_dust/MinerTrack/managers/ConfigManager.java @@ -822,7 +822,7 @@ public int AirMonitorVLT() { } public int AirMonitorVLT(String worldName) { - return getIntForWorld(null, "xray.natural-detection.cave.air-monitor.violation-threshold", 5); + return getIntForWorld(worldName, "xray.natural-detection.cave.air-monitor.violation-threshold", 5); } public boolean isAirMonitorEnabled(String worldName) {