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;
}
}