Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>dev.espi</groupId>
<artifactId>protectionstones</artifactId>
<version>2.10.6</version>
<version>2.10.7</version>
<name>ProtectionStones</name>
<description>A grief prevention plugin for Spigot Minecraft servers.</description>
<url>https://github.com/espidev/ProtectionStones</url>
Expand Down Expand Up @@ -207,7 +207,7 @@
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.21.10-R0.1-SNAPSHOT</version>
<version>1.21.11-R0.2-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> 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 {
Expand All @@ -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");
Expand All @@ -87,72 +91,79 @@ static boolean argumentAdminCleanup(CommandSender p, String[] preParseArgs) {
}

RegionManager rgm = WGUtils.getRegionManagerWithWorld(w);
Map<String, ProtectedRegion> regions = rgm.getRegions();
if (rgm == null) {
PSL.msg(p, PSL.INVALID_WORLD.msg());
return true;
}

// async cleanup task
Map<String, ProtectedRegion> 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<UUID> relevantPlayers = new HashSet<>();
List<PSRegion> 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<UUID> 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<PSRegion> 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);
}
}
}

// start recursive iteration to delete a region each tick
Iterator<PSRegion> deleteRegionsIterator = toDelete.iterator();
regionLoop(deleteRegionsIterator, p, cleanupOperation.equalsIgnoreCase("remove"));
});
return true;
return false;
}

static private void regionLoop(Iterator<PSRegion> 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).");
Expand All @@ -165,31 +176,22 @@ static private void regionLoop(Iterator<PSRegion> 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<PSRegion> 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) {
e.printStackTrace();
}
}

// go to next region
regionLoop(deleteRegionsIterator, p, isRemoveOperation);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<OfflinePlayer> {
@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());
Expand All @@ -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()
Expand All @@ -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) {
Expand All @@ -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<UUID> ownerUuids = collectRegionOwnerUuids();
List<LastLogonEntry> 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<LastLogonEntry> 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<UUID> collectRegionOwnerUuids() {
Set<UUID> owners = new HashSet<>();
for (Map.Entry<World, RegionManager> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/dev/espi/protectionstones/commands/ArgView.java
Original file line number Diff line number Diff line change
Expand Up @@ -131,19 +131,19 @@ public List<String> 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;
}
}
Loading