diff --git a/.gitignore b/.gitignore index f507bae9..732e31c6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,6 @@ /.classpath /.project /dependency-reduced-pom.xml -/*.iml \ No newline at end of file +/*.iml +/dev.zip +/src.zip diff --git a/pom.xml b/pom.xml index 3c7a417c..a05f6ad8 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 dev.espi protectionstones - 2.10.6 + 2.10.8 ProtectionStones A grief prevention plugin for Spigot Minecraft servers. https://github.com/espidev/ProtectionStones @@ -141,19 +141,19 @@ - - org.apache.maven.plugins - maven-javadoc-plugin - 3.2.0 - - - attach-javadocs - - jar - - - - + + org.apache.maven.plugins + maven-javadoc-plugin + 3.2.0 + + + attach-javadocs + + jar + + + + org.sonatype.plugins @@ -207,7 +207,7 @@ org.spigotmc spigot-api - 1.21.10-R0.1-SNAPSHOT + 26.2-R0.1-SNAPSHOT provided diff --git a/src/main/java/dev/espi/protectionstones/commands/ArgAdminCleanup.java b/src/main/java/dev/espi/protectionstones/commands/ArgAdminCleanup.java index 4736246a..315c74ba 100644 --- a/src/main/java/dev/espi/protectionstones/commands/ArgAdminCleanup.java +++ b/src/main/java/dev/espi/protectionstones/commands/ArgAdminCleanup.java @@ -37,30 +37,27 @@ class ArgAdminCleanup { private static File previewFile; private static FileWriter previewFileOutputStream; - // /ps admin cleanup [remove/preview] static boolean argumentAdminCleanup(CommandSender p, String[] preParseArgs) { if (preParseArgs.length < 3 || !Arrays.asList("remove", "preview").contains(preParseArgs[2].toLowerCase())) { PSL.msg(p, ArgAdmin.getCleanupHelp()); return true; } - String cleanupOperation = preParseArgs[2].toLowerCase(); // [remove|preview] + String cleanupOperation = preParseArgs[2].toLowerCase(); World w; String alias = null; List args = new ArrayList<>(); - // determine if there is an alias flag selected, and remove [-t typealias] if there is for (int i = 3; i < preParseArgs.length; i++) { - if (preParseArgs[i].equals("-t") && i != preParseArgs.length-1) { + if (preParseArgs[i].equals("-t") && i != preParseArgs.length - 1) { alias = preParseArgs[++i]; } else { args.add(preParseArgs[i]); } } - // the args array should consist of: [days, world (optional)] if (args.size() > 1 && Bukkit.getWorld(args.get(1)) != null) { w = Bukkit.getWorld(args.get(1)); } else { @@ -72,7 +69,14 @@ static boolean argumentAdminCleanup(CommandSender p, String[] preParseArgs) { } } - // create preview file + int days; + try { + days = (args.size() > 0) ? Integer.parseInt(args.get(0)) : 30; + } catch (Exception e) { + PSL.msg(p, PSL.ADMIN_ERROR_PARSING.msg()); + return true; + } + if (cleanupOperation.equals("preview")) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd H-m-s"); previewFile = new File(ProtectionStones.getInstance().getDataFolder().getAbsolutePath() + "/" + LocalDateTime.now().format(formatter) + " cleanup preview.txt"); @@ -80,56 +84,65 @@ static boolean argumentAdminCleanup(CommandSender p, String[] preParseArgs) { previewFile.createNewFile(); previewFileOutputStream = new FileWriter(previewFile); } catch (IOException e) { - e.printStackTrace(); + PSL.; p.sendMessage(ChatColor.RED + "Internal error, please check the console logs."); return true; } } RegionManager rgm = WGUtils.getRegionManagerWithWorld(w); - Map regions = rgm.getRegions(); + if (rgm == null) { + PSL.msg(p, PSL.INVALID_WORLD.msg()); + return true; + } - // async cleanup task + Map regions = new HashMap<>(rgm.getRegions()); String finalAlias = alias; - Bukkit.getScheduler().runTaskAsynchronously(ProtectionStones.getInstance(), () -> { - int days = (args.size() > 0) ? Integer.parseInt(args.get(0)) : 30; // 30 days is default if days aren't specified + int finalDays = days; + Bukkit.getScheduler().runTaskAsynchronously(ProtectionStones.getInstance(), () -> { PSL.msg(p, PSL.ADMIN_CLEANUP_HEADER.msg() .replace("%arg%", cleanupOperation) - .replace("%days%", "" + days)); + .replace("%days%", "" + finalDays)); + + HashSet relevantPlayers = new HashSet<>(); + List psRegions = new ArrayList<>(); + + for (ProtectedRegion protectedRegion : regions.values()) { + PSRegion r = PSRegion.fromWGRegion(w, protectedRegion); + if (r == null) { + continue; + } + + if (finalAlias != null && (r.getTypeOptions() == null || !r.getTypeOptions().alias.equals(finalAlias))) { + continue; + } + + psRegions.add(r); + relevantPlayers.addAll(r.getOwners()); + relevantPlayers.addAll(r.getMembers()); + } HashSet activePlayers = new HashSet<>(); + long now = System.currentTimeMillis(); - // loop over offline players and add to list if they haven't joined recently - for (OfflinePlayer op : Bukkit.getServer().getOfflinePlayers()) { - long lastPlayed = (System.currentTimeMillis() - op.getLastPlayed()) / 86400000L; + for (UUID uuid : relevantPlayers) { try { - // a player is active if they have joined within the days - if (lastPlayed < days) { - activePlayers.add(op.getUniqueId()); + OfflinePlayer op = Bukkit.getOfflinePlayer(uuid); + long lastPlayed = (now - op.getLastPlayed()) / 86400000L; + if (lastPlayed < finalDays) { + activePlayers.add(uuid); } } catch (Exception e) { e.printStackTrace(); } } - // loop over all regions async and find regions to delete List toDelete = new ArrayList<>(); - for (String regionId : regions.keySet()) { - PSRegion r = PSRegion.fromWGRegion(w, regions.get(regionId)); - if (r == null) { // not a ps region (unconfigured types still count as ps regions) - continue; - } - - // if an alias is specified, skip regions that aren't of the type - if (finalAlias != null && (r.getTypeOptions() == null || !r.getTypeOptions().alias.equals(finalAlias))) { - continue; - } - + for (PSRegion r : psRegions) { long numOfActiveOwners = r.getOwners().stream().filter(activePlayers::contains).count(); long numOfActiveMembers = r.getMembers().stream().filter(activePlayers::contains).count(); - // remove region if there are no owners left if (numOfActiveOwners == 0) { if (ProtectionStones.getInstance().getConfigOptions().cleanupDeleteRegionsWithMembersButNoOwners || numOfActiveMembers == 0) { toDelete.add(r); @@ -137,22 +150,20 @@ static boolean argumentAdminCleanup(CommandSender p, String[] preParseArgs) { } } - // start recursive iteration to delete a region each tick Iterator deleteRegionsIterator = toDelete.iterator(); regionLoop(deleteRegionsIterator, p, cleanupOperation.equalsIgnoreCase("remove")); }); - return true; + return false; } static private void regionLoop(Iterator deleteRegionsIterator, CommandSender p, boolean isRemoveOperation) { if (deleteRegionsIterator.hasNext()) { Bukkit.getScheduler().runTaskLater(ProtectionStones.getInstance(), () -> processRegion(deleteRegionsIterator, p, isRemoveOperation), 1); - } else { // finished region iteration + } else { PSL.msg(p, PSL.ADMIN_CLEANUP_FOOTER.msg() .replace("%arg%", isRemoveOperation ? "remove" : "preview")); - // flush and close preview file if (!isRemoveOperation) { try { p.sendMessage(ChatColor.YELLOW + "Dumped the list regions that can be deleted in " + previewFile.getName() + " (in the plugin folder)."); @@ -165,23 +176,15 @@ static private void regionLoop(Iterator deleteRegionsIterator, Command } } - // Process a region, and then iterate to the next region on the next tick. - // This is to prevent the server from pausing for the entire duration of the cleanup. - // (lag from loading chunks to remove protection blocks) static private void processRegion(Iterator deleteRegionsIterator, CommandSender p, boolean isRemoveOperation) { PSRegion r = deleteRegionsIterator.next(); - if (isRemoveOperation) { // delete - + if (isRemoveOperation) { p.sendMessage(ChatColor.YELLOW + "Removed region " + r.getId() + " due to inactive owners."); - - // must be sync r.deleteRegion(true); - } else { // preview - + } else { p.sendMessage(ChatColor.YELLOW + "Found region " + r.getId() + " that can be deleted."); - // adds region id to preview file try { previewFileOutputStream.write(r.getId() + "\n"); } catch (IOException e) { @@ -189,7 +192,6 @@ static private void processRegion(Iterator deleteRegionsIterator, Comm } } - // go to next region regionLoop(deleteRegionsIterator, p, isRemoveOperation); } } diff --git a/src/main/java/dev/espi/protectionstones/commands/ArgAdminLastlogon.java b/src/main/java/dev/espi/protectionstones/commands/ArgAdminLastlogon.java index e91aa61d..dfe1ff84 100644 --- a/src/main/java/dev/espi/protectionstones/commands/ArgAdminLastlogon.java +++ b/src/main/java/dev/espi/protectionstones/commands/ArgAdminLastlogon.java @@ -15,23 +15,33 @@ package dev.espi.protectionstones.commands; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; import dev.espi.protectionstones.PSL; +import dev.espi.protectionstones.PSRegion; +import dev.espi.protectionstones.ProtectionStones; +import dev.espi.protectionstones.utils.UUIDCache; +import dev.espi.protectionstones.utils.WGUtils; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; +import org.bukkit.World; import org.bukkit.command.CommandSender; -import java.util.Arrays; -import java.util.Comparator; +import java.util.*; class ArgAdminLastlogon { - // /ps admin lastlogon + private static final int LINES_PER_TICK = 100; - static class PlayerComparator implements Comparator { - @Override - public int compare(OfflinePlayer o1, OfflinePlayer o2) { - return o1.getName().compareTo(o2.getName()); + private static final class LastLogonEntry { + private final String playerName; + private final long daysSinceLastPlayed; + + private LastLogonEntry(String playerName, long daysSinceLastPlayed) { + this.playerName = playerName; + this.daysSinceLastPlayed = daysSinceLastPlayed; } } + static boolean argumentAdminLastLogon(CommandSender p, String[] args) { if (args.length < 3) { p.sendMessage(PSL.COMMAND_REQUIRES_PLAYER_NAME.msg()); @@ -44,7 +54,7 @@ static boolean argumentAdminLastLogon(CommandSender p, String[] args) { PSL.msg(p, PSL.ADMIN_LAST_LOGON.msg() .replace("%player%", playerName) - .replace("%days%", "" +lastPlayed)); + .replace("%days%", "" + lastPlayed)); if (op.isBanned()) { PSL.msg(p, PSL.ADMIN_IS_BANNED.msg() @@ -54,7 +64,6 @@ static boolean argumentAdminLastLogon(CommandSender p, String[] args) { return true; } - // /ps admin lastlogons static boolean argumentAdminLastLogons(CommandSender p, String[] args) { int days = 0; if (args.length > 2) { @@ -65,27 +74,79 @@ static boolean argumentAdminLastLogons(CommandSender p, String[] args) { return true; } } - OfflinePlayer[] offlinePlayerList = Bukkit.getServer().getOfflinePlayers().clone(); - int playerCounter = 0; + + final int minDays = days; PSL.msg(p, PSL.ADMIN_LASTLOGONS_HEADER.msg() - .replace("%days%", "" + days)); - - Arrays.sort(offlinePlayerList, new PlayerComparator()); - for (OfflinePlayer offlinePlayer : offlinePlayerList) { - long lastPlayed = (System.currentTimeMillis() - offlinePlayer.getLastPlayed()) / 86400000L; - if (lastPlayed >= days) { - playerCounter++; - PSL.msg(p, PSL.ADMIN_LASTLOGONS_LINE.msg() - .replace("%player%", offlinePlayer.getName()) - .replace("%time%", "" + lastPlayed)); + .replace("%days%", "" + minDays)); + + Bukkit.getScheduler().runTaskAsynchronously(ProtectionStones.getInstance(), () -> { + Set ownerUuids = collectRegionOwnerUuids(); + List matchingEntries = new ArrayList<>(); + long now = System.currentTimeMillis(); + + for (UUID uuid : ownerUuids) { + try { + OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid); + long lastPlayed = (now - offlinePlayer.getLastPlayed()) / 86400000L; + if (lastPlayed >= minDays) { + matchingEntries.add(new LastLogonEntry(resolvePlayerName(uuid), lastPlayed)); + } + } catch (Exception e) { + e.printStackTrace(); + } } - } - PSL.msg(p, PSL.ADMIN_LASTLOGONS_FOOTER.msg() - .replace("%count%", "" + playerCounter) - .replace("%checked%", "" + offlinePlayerList.length)); + matchingEntries.sort(Comparator.comparing(entry -> entry.playerName, String.CASE_INSENSITIVE_ORDER)); + + Bukkit.getScheduler().runTask(ProtectionStones.getInstance(), () -> + sendLastLogonEntries(p, matchingEntries, ownerUuids.size(), 0)); + }); return true; } + private static void sendLastLogonEntries(CommandSender sender, List entries, int checkedCount, int startIndex) { + int endIndex = Math.min(entries.size(), startIndex + LINES_PER_TICK); + for (int i = startIndex; i < endIndex; i++) { + LastLogonEntry entry = entries.get(i); + PSL.msg(sender, PSL.ADMIN_LASTLOGONS_LINE.msg() + .replace("%player%", entry.playerName) + .replace("%time%", "" + entry.daysSinceLastPlayed)); + } + + if (endIndex < entries.size()) { + int nextIndex = endIndex; + Bukkit.getScheduler().runTaskLater(ProtectionStones.getInstance(), + () -> sendLastLogonEntries(sender, entries, checkedCount, nextIndex), 1L); + return; + } + + PSL.msg(sender, PSL.ADMIN_LASTLOGONS_FOOTER.msg() + .replace("%count%", "" + entries.size()) + .replace("%checked%", "" + checkedCount)); + } + + private static Set collectRegionOwnerUuids() { + Set owners = new HashSet<>(); + for (Map.Entry entry : WGUtils.getAllRegionManagers().entrySet()) { + World world = entry.getKey(); + RegionManager regionManager = entry.getValue(); + if (world == null || regionManager == null) continue; + + for (ProtectedRegion protectedRegion : regionManager.getRegions().values()) { + PSRegion region = PSRegion.fromWGRegion(world, protectedRegion); + if (region == null) continue; + owners.addAll(region.getOwners()); + } + } + return owners; + } + + private static String resolvePlayerName(UUID uuid) { + String name = UUIDCache.getNameFromUUID(uuid); + if (name == null || name.isEmpty() || name.equalsIgnoreCase("null")) { + name = uuid.toString().substring(0, 8); + } + return name; + } } diff --git a/src/main/java/dev/espi/protectionstones/commands/ArgTp.java b/src/main/java/dev/espi/protectionstones/commands/ArgTp.java index 5261ffcb..5517cf53 100644 --- a/src/main/java/dev/espi/protectionstones/commands/ArgTp.java +++ b/src/main/java/dev/espi/protectionstones/commands/ArgTp.java @@ -146,7 +146,7 @@ static void teleportPlayer(Player p, PSRegion r) { Bukkit.getScheduler().runTaskLater(ProtectionStones.getInstance(), () -> { PSL.msg(p, PSL.TPING.msg()); p.teleport(r.getHome()); - }, 20 * r.getTypeOptions().tpWaitingSeconds); + }, 20L * r.getTypeOptions().tpWaitingSeconds); } else {// delay and not allowed to move PSL.msg(p, PSL.TP_IN_SECONDS.msg().replace("%seconds%", "" + r.getTypeOptions().tpWaitingSeconds)); diff --git a/src/main/java/dev/espi/protectionstones/commands/ArgView.java b/src/main/java/dev/espi/protectionstones/commands/ArgView.java index 4d5991f6..64dd2aa4 100644 --- a/src/main/java/dev/espi/protectionstones/commands/ArgView.java +++ b/src/main/java/dev/espi/protectionstones/commands/ArgView.java @@ -131,19 +131,19 @@ public List tabComplete(CommandSender sender, String alias, String[] arg private static boolean handlePinkParticle(Player p, Location l) { if (p.getLocation().distance(l) > PARTICLE_VIEW_DISTANCE_LIMIT || Math.abs(l.getY()-p.getLocation().getY()) > 30) return false; - ParticlesUtil.persistRedstoneParticle(p, l, new Particle.DustOptions(Color.fromRGB(233, 30, 99), 2), 30); + ParticlesUtil.persistRedstoneParticle(p, l, new Particle.DustOptions(Color.fromRGB(233, 30, 99), 2.0f), 30); return true; } private static boolean handleBlueParticle(Player p, Location l) { if (p.getLocation().distance(l) > PARTICLE_VIEW_DISTANCE_LIMIT || Math.abs(l.getY()-p.getLocation().getY()) > 30) return false; - ParticlesUtil.persistRedstoneParticle(p, l, new Particle.DustOptions(Color.fromRGB(0, 255, 255), 2), 30); + ParticlesUtil.persistRedstoneParticle(p, l, new Particle.DustOptions(Color.fromRGB(0, 255, 255), 2.0f), 30); return true; } private static boolean handlePurpleParticle(Player p, Location l) { if (p.getLocation().distance(l) > PARTICLE_VIEW_DISTANCE_LIMIT || Math.abs(l.getY()-p.getLocation().getY()) > 30) return false; - ParticlesUtil.persistRedstoneParticle(p, l, new Particle.DustOptions(Color.fromRGB(255, 0, 255), 10), 30); + ParticlesUtil.persistRedstoneParticle(p, l, new Particle.DustOptions(Color.fromRGB(255, 0, 255), 4.0f), 30); return true; } }