added antiGrief command

This commit is contained in:
2025-09-28 12:59:18 +02:00
parent d7cc141b94
commit 32a20cd4c5
3 changed files with 92 additions and 49 deletions

View File

@@ -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<Long, Double> scores = new ConcurrentSkipListMap<>();
public final NavigableMap<Long, Double> scores = new ConcurrentSkipListMap<>();
/** Incidents per Bucket */
final Map<Long, List<GriefIncident>> incidentsByBucket = new ConcurrentHashMap<>();
public final Map<Long, List<GriefIncident>> 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<UUID, Set<GriefIncident>> 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<AreaState> 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<ApplianceCommand<?>> commands() {
return List.of(
new AntiGriefCommand()
);
}
}

View File

@@ -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<AntiGrief> {
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<AntiGrief.AreaState> 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<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(args.length == 1) return List.of("currentChunk", "topChunks");
return null;
}
}

View File

@@ -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<AntiGrief> {
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<TextComponent> entries = griefEntry.getValue().stream()
.map(incident -> Component.text(incident.x()))
.toList();
ComponentBuilder<TextComponent, TextComponent.Builder> builder = Component.text()
.append(Component.text(griefEntry.getKey().toString()))
.append(Component.text(": "));
entries.forEach(builder::append);
return builder.build();
})
.reduce(ScopedComponent::append)
.orElseThrow()
);
}
}