From 32a20cd4c57124c0a3dae9038016fc63637d007b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Sun, 28 Sep 2025 12:59:18 +0200 Subject: [PATCH] added antiGrief command --- .../security/antiGrief/AntiGrief.java | 39 +++++++++--- .../antiGrief/commands/AntiGriefCommand.java | 63 +++++++++++++++++++ .../commands/GriefOverviewCommand.java | 39 ------------ 3 files changed, 92 insertions(+), 49 deletions(-) create mode 100644 craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/security/antiGrief/commands/AntiGriefCommand.java delete mode 100644 craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/security/antiGrief/commands/GriefOverviewCommand.java diff --git a/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/security/antiGrief/AntiGrief.java b/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/security/antiGrief/AntiGrief.java index 5865e06..e638b1e 100644 --- a/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/security/antiGrief/AntiGrief.java +++ b/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/security/antiGrief/AntiGrief.java @@ -2,7 +2,9 @@ package eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief; import eu.mhsl.craftattack.spawn.core.Main; import eu.mhsl.craftattack.spawn.core.appliance.Appliance; +import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.core.util.NumberUtil; +import eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.commands.AntiGriefCommand; import eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.listener.*; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; @@ -79,17 +81,17 @@ public class AntiGrief extends Appliance { } } - private static final class AreaState { - final UUID worldId; - final int chunkX, chunkZ; + public static final class AreaState { + public final UUID worldId; + public final int chunkX, chunkZ; /** Rolling bucket scores for Spike Detection. */ - final NavigableMap scores = new ConcurrentSkipListMap<>(); + public final NavigableMap scores = new ConcurrentSkipListMap<>(); /** Incidents per Bucket */ - final Map> incidentsByBucket = new ConcurrentHashMap<>(); + public final Map> incidentsByBucket = new ConcurrentHashMap<>(); - volatile double ema = 0.0; - volatile long lastAlertAt = 0L; + public volatile double ema = 0.0; + public volatile long lastAlertAt = 0L; AreaState(UUID worldId, int chunkX, int chunkZ) { this.worldId = worldId; @@ -97,7 +99,7 @@ public class AntiGrief extends Appliance { this.chunkZ = chunkZ; } - long getInhabitedTime() { + public long getInhabitedTime() { return Objects.requireNonNull(Bukkit.getWorld(this.worldId)) .getChunkAt(this.chunkX, this.chunkX).getInhabitedTime(); } @@ -314,8 +316,18 @@ public class AntiGrief extends Appliance { ); } - public Map> getDirectGriefRegistry() { - return this.directGriefRegistry; + public @Nullable AreaState getInfoAtChunk(Chunk chunk) { + long areaKey = this.packArea(chunk.getWorld().getUID(), chunk.getX(), chunk.getZ()); + return this.areas.get(areaKey); + } + + public List getHighesScoredChunks(int limit) { + long nowBucket = this.bucketIdx(System.currentTimeMillis()); + + return this.areas.values().stream() + .sorted(Comparator.comparingDouble((AreaState st) -> st.currentScore(nowBucket)).reversed()) + .limit(limit) + .toList(); } @Override @@ -328,4 +340,11 @@ public class AntiGrief extends Appliance { new EntityRelatedGriefListener() ); } + + @Override + protected @NotNull List> commands() { + return List.of( + new AntiGriefCommand() + ); + } } diff --git a/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/security/antiGrief/commands/AntiGriefCommand.java b/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/security/antiGrief/commands/AntiGriefCommand.java new file mode 100644 index 0000000..2f38e29 --- /dev/null +++ b/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/security/antiGrief/commands/AntiGriefCommand.java @@ -0,0 +1,63 @@ +package eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.commands; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand; +import eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.AntiGrief; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class AntiGriefCommand extends ApplianceCommand.PlayerChecked { + private final Gson prettyGson = new GsonBuilder().setPrettyPrinting().create(); + + public AntiGriefCommand() { + super("antiGrief"); + } + + @Override + protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception { + if(args.length != 1) throw new Error("One argument expected"); + switch(args[0]) { + case "currentChunk": { + AntiGrief.AreaState state = this.getAppliance().getInfoAtChunk(this.getPlayer().getChunk()); + if(state == null) throw new Error("The current chunk does not have a Score!"); + sender.sendMessage(this.areaStateDisplay(state)); + sender.sendMessage(String.format("ChunkLoaded: %ds", state.getInhabitedTime() / 1000)); + break; + } + case "topChunks": { + List states = this.getAppliance().getHighesScoredChunks(10); + sender.sendMessage(Component.empty().append( + states.stream().map(state -> this.areaStateDisplay(state).appendNewline()).toList() + )); + break; + } + default: throw new Error("No such option!"); + } + } + + private Component areaStateDisplay(AntiGrief.AreaState state) { + var object = Component.text("[\uD83D\uDCC2]", NamedTextColor.GRAY) + .append(Component.text(" - ", NamedTextColor.GOLD)) + .hoverEvent(HoverEvent.showText(Component.text(this.prettyGson.toJson(state)))); + var location = Component.text(String.format("[%d,%d]", state.chunkX, state.chunkZ), NamedTextColor.YELLOW) + .append(Component.text(" > ", NamedTextColor.DARK_GRAY)); + int incidentCount = state.incidentsByBucket.values().stream().map(List::size).mapToInt(Integer::intValue).sum(); + var total = Component.text(String.format("ema:%.2f, totalIncidents:%d", state.ema, incidentCount), NamedTextColor.GRAY); + + return Component.empty().append(object, location, total); + } + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if(args.length == 1) return List.of("currentChunk", "topChunks"); + return null; + } +} diff --git a/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/security/antiGrief/commands/GriefOverviewCommand.java b/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/security/antiGrief/commands/GriefOverviewCommand.java deleted file mode 100644 index 700d8ba..0000000 --- a/craftattack/src/main/java/eu/mhsl/craftattack/spawn/craftattack/appliances/security/antiGrief/commands/GriefOverviewCommand.java +++ /dev/null @@ -1,39 +0,0 @@ -package eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.commands; - -import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand; -import eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.AntiGrief; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.ComponentBuilder; -import net.kyori.adventure.text.ScopedComponent; -import net.kyori.adventure.text.TextComponent; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -public class GriefOverviewCommand extends ApplianceCommand { - public GriefOverviewCommand() { - super("griefOverview"); - } - - @Override - protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception { - sender.sendMessage( - this.getAppliance().getDirectGriefRegistry().entrySet().stream() - .map(griefEntry -> { - List entries = griefEntry.getValue().stream() - .map(incident -> Component.text(incident.x())) - .toList(); - ComponentBuilder builder = Component.text() - .append(Component.text(griefEntry.getKey().toString())) - .append(Component.text(": ")); - entries.forEach(builder::append); - - return builder.build(); - }) - .reduce(ScopedComponent::append) - .orElseThrow() - ); - } -}