WIP: AntiGrief

This commit is contained in:
2025-09-21 01:54:20 +02:00
parent 0d18b81399
commit 74f17e1b6d
8 changed files with 705 additions and 33 deletions

View File

@@ -1,33 +1,311 @@
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.craftattack.appliances.security.antiGrief.player.PlayerGriefListener;
import eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.listener.*;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.util.Ticks;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static net.kyori.adventure.text.Component.text;
public class AntiGrief extends Appliance {
public record GriefIncident(Location location, @Nullable Block block) {
public static final Long timestamp = System.currentTimeMillis();
}
private final Map<UUID, List<GriefIncident>> griefRegistry = new HashMap<>();
public void addTracking(Player player, GriefIncident incident) {
this.griefRegistry.computeIfAbsent(player.getUniqueId(), uuid -> new ArrayList<>());
this.griefRegistry.get(player.getUniqueId()).add(incident);
public record GriefIncident(
long timestamp,
UUID worldId,
int x,
int y,
int z,
String event,
String data,
Severity severity
) {
public GriefIncident(Location loc, Event event, @Nullable Object data, Severity severity) {
this(
System.currentTimeMillis(),
loc.getWorld().getUID(),
loc.getBlockX(),
loc.getBlockY(),
loc.getBlockZ(),
event.getEventName(),
String.valueOf(data),
severity
);
}
public Map<UUID, List<GriefIncident>> getGriefRegistry() {
return this.griefRegistry;
public enum Severity {
/**
* No direct severity, but possible beginning of an incident
*/
INFO(0.5f),
/**
* Direct interaction which can lead to damage
*/
LIGHT(1),
/**
* Direkt interaction which can spread to severe incidents
*/
MODERATE(3),
/**
* Direct and most likely harmful interaction
*/
SEVERE(5);
public final float weight;
Severity(float weight) {
this.weight = weight;
}
}
}
private static final class AreaState {
final UUID worldId;
final int chunkX, chunkZ;
/** Rolling bucket scores for Spike Detection. */
final NavigableMap<Long, Double> scores = new ConcurrentSkipListMap<>();
/** Incidents per Bucket */
final Map<Long, List<GriefIncident>> incidentsByBucket = new ConcurrentHashMap<>();
volatile double ema = 0.0;
volatile long lastAlertAt = 0L;
AreaState(UUID worldId, int chunkX, int chunkZ) {
this.worldId = worldId;
this.chunkX = chunkX;
this.chunkZ = chunkZ;
}
void addIncident(GriefIncident incident) {
long b = incident.timestamp / BUCKET_DURATION_MS;
this.scores.merge(b, (double) incident.severity.weight, Double::sum);
this.incidentsByBucket
.computeIfAbsent(b, k -> Collections.synchronizedList(new ArrayList<>()))
.add(incident);
}
double currentScore(long bucketIdx) {
return this.scores.getOrDefault(bucketIdx, 0.0);
}
void prune(long bucket) {
long oldest = bucket - BUCKETS_PER_CHUNK;
this.scores.headMap(oldest, true).clear();
this.incidentsByBucket.keySet().removeIf(b -> b < oldest);
}
boolean isEmpty() {
return this.scores.isEmpty() && this.incidentsByBucket.isEmpty();
}
}
/** Duration of a time bucket in milliseconds. */
private static final long BUCKET_DURATION_MS = 60 * 1000;
/** Number of buckets kept in memory per area. Defines analysis window length. */
private static final int BUCKETS_PER_CHUNK = 30;
/** Maximum retention time for individual incidents in milliseconds. */
private static final long INCIDENT_RETAIN_MS = 60 * 60 * 1000;
/** Spike factor against EMA baseline. Triggers if current score >= baseline * FACTOR_SPIKE. */
private static final double FACTOR_SPIKE = 3.0;
/** Absolute threshold for spike detection. Triggers if current score exceeds this value. */
private static final double HARD_THRESHOLD = 20.0;
/** Cooldown time in ms to suppress repeated alerts for the same area. */
private static final long ALERT_COOLDOWN_MS = 2 * 60 * 1000;
/** Smoothing factor for EMA baseline. Lower = smoother, higher = more reactive. 0.0 < EMA_ALPHA <= 1.0 */
private static final double EMA_ALPHA = 0.3;
/** Stores direct incidents mapped by player UUID. */
private final Map<UUID, Set<GriefIncident>> directGriefRegistry = new ConcurrentHashMap<>();
/** Stores passive incidents mapped by chunk key. */
private final Map<Long, Set<GriefIncident>> passiveGriefRegistry = new ConcurrentHashMap<>();
/** Stores scores by area */
private final Map<Long, AreaState> areas = new ConcurrentHashMap<>();
public void trackDirect(Player player, GriefIncident incident) {
this.directGriefRegistry
.computeIfAbsent(player.getUniqueId(), uuid -> ConcurrentHashMap.newKeySet())
.add(incident);
this.trackPassive(player.getLocation().getChunk(), incident);
}
public void trackPassive(Chunk chunk, GriefIncident incident) {
this.passiveGriefRegistry
.computeIfAbsent(chunk.getChunkKey(), aLong -> ConcurrentHashMap.newKeySet())
.add(incident);
final long areaKey = this.packArea(incident.worldId, chunk.getX(), chunk.getZ());
this.areas
.computeIfAbsent(areaKey, key -> new AreaState(incident.worldId, chunk.getX(), chunk.getZ()))
.addIncident(incident);
Bukkit.broadcast(text(String.format("%d, %d: %s - %s: {%s}", chunk.getX(), chunk.getZ(), incident.severity, incident.event, incident.data)));
}
@Override
public void onEnable() {
Bukkit.getScheduler().runTaskTimerAsynchronously(Main.instance(), () -> {
final long now = System.currentTimeMillis();
final long bucketIdx = this.bucketIdx(now);
this.areas.forEach((areaKey, st) -> {
final double currentScore = st.currentScore(bucketIdx);
if (currentScore <= 0.0) return;
final double base = (st.ema == 0.0) ? currentScore : st.ema;
final double newBase = EMA_ALPHA * currentScore + (1 - EMA_ALPHA) * base;
st.ema = newBase;
boolean spike = currentScore >= HARD_THRESHOLD || currentScore >= base * FACTOR_SPIKE;
if (spike && (now - st.lastAlertAt) >= ALERT_COOLDOWN_MS) {
st.lastAlertAt = now;
Bukkit.getScheduler().runTask(Main.instance(), () ->
this.alertAdmins(areaKey, bucketIdx, currentScore, newBase)
);
}
});
}, Ticks.TICKS_PER_SECOND, Ticks.TICKS_PER_SECOND);
Bukkit.getScheduler().runTaskTimerAsynchronously(Main.instance(), () -> {
final long cutoff = System.currentTimeMillis() - INCIDENT_RETAIN_MS;
final long nowBucket = this.bucketIdx(System.currentTimeMillis());
this.directGriefRegistry.entrySet().removeIf(e -> {
e.getValue().removeIf(inc -> inc.timestamp < cutoff);
return e.getValue().isEmpty();
});
this.passiveGriefRegistry.entrySet().removeIf(e -> {
e.getValue().removeIf(inc -> inc.timestamp < cutoff);
return e.getValue().isEmpty();
});
this.areas.entrySet().removeIf(en -> {
AreaState state = en.getValue();
state.prune(nowBucket);
return state.isEmpty();
});
}, Ticks.TICKS_PER_SECOND * 30, Ticks.TICKS_PER_SECOND * 30);
}
private void alertAdmins(long areaKey, long bucketIdx, double curr, double baseline) {
AreaState meta = this.areas.get(areaKey);
if (meta == null) return;
int cx = meta.chunkX, cz = meta.chunkZ;
UUID worldId = meta.worldId;
World world = Bukkit.getWorld(worldId);
if (world == null) return;
int bx = (cx << 4) + 8;
int bz = (cz << 4) + 8;
int by = world.getHighestBlockYAt(bx, bz);
Location center = new Location(world, bx + 0.5, by + 1.0, bz + 0.5);
List<Player> nearest = Bukkit.getOnlinePlayers().stream()
.filter(p -> p.getWorld().equals(world))
.sorted(Comparator.comparingDouble(p -> p.getLocation().distanceSquared(center)))
.limit(3)
.collect(Collectors.toList());
String playersHover = nearest.isEmpty()
? "Keine Spieler in der Nähe"
: String.join("\n", nearest.stream()
.map(p -> String.format("- %s (%.1fm)", p.getName(), Math.sqrt(p.getLocation().distanceSquared(center))))
.toList());
List<GriefIncident> incidents = meta.incidentsByBucket.getOrDefault(bucketIdx, List.of());
String incidentsHover = incidents.isEmpty()
? "Keine Details"
: String.join("\n", incidents.stream().limit(20)
.map(i -> String.format("• %s · %s · %s", i.event, i.severity, i.data))
.toList());
Component coords = text("[" + cx + ", " + cz + "]")
.hoverEvent(HoverEvent.showText(text(
"Chunk: [" + cx + ", " + cz + "]\n" +
"Block: [" + bx + ", " + by + ", " + bz + "]\n\n" +
"Nächste Spieler:\n" + playersHover
)))
.clickEvent(ClickEvent.suggestCommand(String.format("/tp %d %d %d", bx, by, bz)));
Component incCount = text(incidents.size() + " Incidents")
.hoverEvent(HoverEvent.showText(text(incidentsHover)));
Component msg = text("Möglicher Grief in ").append(coords)
.append(text(" - score=" + String.format("%.1f", curr)))
.append(text(" base=" + String.format("%.1f", baseline)))
.append(text(" - ")).append(incCount);
Bukkit.getOnlinePlayers().stream()
.filter(p -> p.hasPermission("antigrief.alert"))
.forEach(p -> p.sendMessage(msg));
Bukkit.getConsoleSender().sendMessage(
String.format("[AntiGrief] Alert %s score=%.1f base=%.1f incidents=%d @ [%d,%d]",
world.getName(), curr, baseline, incidents.size(), cx, cz));
}
private long bucketIdx(long timestamp) {
return timestamp / AntiGrief.BUCKET_DURATION_MS;
}
private long packArea(UUID worldId, int chunkX, int chunkZ) {
long chunkKey = (((long)chunkX) << 32) ^ (chunkZ & 0xffffffffL);
return worldId.getMostSignificantBits() ^ worldId.getLeastSignificantBits() ^ chunkKey;
}
public Stream<Block> getSurroundingBlocks(Location location) {
Block center = location.getBlock();
World world = center.getWorld();
int x = center.getX();
int y = center.getY();
int z = center.getZ();
return Stream.of(
world.getBlockAt(x + 1, y, z),
world.getBlockAt(x - 1, y, z),
world.getBlockAt(x, y + 1, z),
world.getBlockAt(x, y - 1, z),
world.getBlockAt(x, y, z + 1),
world.getBlockAt(x, y, z - 1)
);
}
public Map<UUID, Set<GriefIncident>> getDirectGriefRegistry() {
return this.directGriefRegistry;
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(new PlayerGriefListener());
return List.of(
new BlockRelatedGriefListener(),
new ExplosionRelatedGriefListener(),
new FireRelatedGriefListener(),
new LiquidRelatedGriefListener(),
new EntityRelatedGriefListener()
);
}
}

View File

@@ -20,10 +20,10 @@ public class GriefOverviewCommand extends ApplianceCommand<AntiGrief> {
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
sender.sendMessage(
this.getAppliance().getGriefRegistry().entrySet().stream()
this.getAppliance().getDirectGriefRegistry().entrySet().stream()
.map(griefEntry -> {
List<TextComponent> entries = griefEntry.getValue().stream()
.map(incident -> Component.text(incident.location().toString()))
.map(incident -> Component.text(incident.x()))
.toList();
ComponentBuilder<TextComponent, TextComponent.Builder> builder = Component.text()
.append(Component.text(griefEntry.getKey().toString()))

View File

@@ -0,0 +1,38 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.listener;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.AntiGrief;
import org.bukkit.block.Container;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.inventory.Inventory;
import java.util.Arrays;
import java.util.Objects;
public class BlockRelatedGriefListener extends ApplianceListener<AntiGrief> {
@EventHandler
public void containerBlockBreak(BlockBreakEvent event) {
if(!(event.getBlock().getState() instanceof Container container)) return;
Inventory containerInv = container.getInventory();
if(containerInv.isEmpty()) return;
long itemCount = Arrays.stream(containerInv.getStorageContents())
.filter(Objects::nonNull)
.filter(itemStack -> !itemStack.isEmpty())
.count();
this.getAppliance().trackDirect(
event.getPlayer(),
new AntiGrief.GriefIncident(
event.getBlock().getLocation(),
event,
event.getBlock().getType(),
itemCount > containerInv.getSize() / 2
? AntiGrief.GriefIncident.Severity.SEVERE
: AntiGrief.GriefIncident.Severity.MODERATE
)
);
}
}

View File

@@ -0,0 +1,142 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.listener;
import com.destroystokyo.paper.event.entity.CreeperIgniteEvent;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.AntiGrief;
import org.bukkit.Bukkit;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Item;
import org.bukkit.entity.Tameable;
import org.bukkit.entity.Villager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDeathEvent;
import java.util.List;
import java.util.Set;
public class EntityRelatedGriefListener extends ApplianceListener<AntiGrief> {
@EventHandler
public void buildWither(CreatureSpawnEvent event) {
if(!event.getSpawnReason().equals(CreatureSpawnEvent.SpawnReason.BUILD_WITHER)) return;
this.getAppliance().trackPassive(
event.getEntity().getLocation().getChunk(),
new AntiGrief.GriefIncident(
event.getEntity().getLocation(),
event,
event.getEntity().getType(),
AntiGrief.GriefIncident.Severity.MODERATE
)
);
}
@EventHandler
public void creeperPurposelyIgnite(CreeperIgniteEvent event) {
this.getAppliance().trackPassive(
event.getEntity().getChunk(),
new AntiGrief.GriefIncident(
event.getEntity().getLocation(),
event,
event.getEntity().getType(),
AntiGrief.GriefIncident.Severity.SEVERE
)
);
}
@EventHandler
public void villagerDeath(EntityDeathEvent event) {
if(!(event.getEntity() instanceof Villager villager)) return;
if(event.getEntity().getLastDamageCause() != null) {
EntityDamageEvent lastDamage = event.getEntity().getLastDamageCause();
List<EntityDamageEvent.DamageCause> suspiciousCauses = List.of(
EntityDamageEvent.DamageCause.FIRE,
EntityDamageEvent.DamageCause.LAVA,
EntityDamageEvent.DamageCause.PROJECTILE,
EntityDamageEvent.DamageCause.ENTITY_ATTACK,
EntityDamageEvent.DamageCause.FIRE_TICK
);
if(!suspiciousCauses.contains(lastDamage.getCause())) return;
}
this.getAppliance().trackPassive(
villager.getChunk(),
new AntiGrief.GriefIncident(
villager.getLocation(),
event,
List.of(villager.getVillagerType(), String.valueOf(villager.getLastDamageCause())),
AntiGrief.GriefIncident.Severity.LIGHT
)
);
}
@EventHandler
public void petKilled(EntityDeathEvent event) {
Set<EntityType> petEntities = Set.of(
EntityType.SNIFFER,
EntityType.WOLF,
EntityType.AXOLOTL,
EntityType.ALLAY,
EntityType.CAMEL,
EntityType.PARROT,
EntityType.CAT,
EntityType.OCELOT,
EntityType.HORSE,
EntityType.DONKEY,
EntityType.MULE,
EntityType.LLAMA,
EntityType.FOX,
EntityType.TURTLE,
EntityType.PANDA,
EntityType.GOAT,
EntityType.BEE
);
if(!petEntities.contains(event.getEntity().getType())) return;
this.getAppliance().trackPassive(
event.getEntity().getChunk(),
new AntiGrief.GriefIncident(
event.getEntity().getLocation(),
event,
event.getEntity().getType(),
event.getEntity() instanceof Tameable tameable
? tameable.isTamed() ? AntiGrief.GriefIncident.Severity.SEVERE : AntiGrief.GriefIncident.Severity.MODERATE
: AntiGrief.GriefIncident.Severity.LIGHT
)
);
}
@EventHandler
public void itemBurned(EntityDamageEvent event) {
if(!(event.getEntity() instanceof Item item)) return;
int amount = item.getItemStack().getAmount();
int half = item.getItemStack().getMaxStackSize() / 2;
if (amount < half / 2) return;
List<EntityDamageEvent.DamageCause> forbiddenCauses = List.of(
EntityDamageEvent.DamageCause.FIRE,
EntityDamageEvent.DamageCause.FIRE_TICK,
EntityDamageEvent.DamageCause.LAVA,
EntityDamageEvent.DamageCause.HOT_FLOOR,
EntityDamageEvent.DamageCause.CAMPFIRE
);
if(forbiddenCauses.contains(event.getCause())) return;
Bukkit.getScheduler().runTaskLater(Main.instance(), () -> {
if (item.isValid() || !item.isDead()) return;
this.getAppliance().trackPassive(
event.getEntity().getChunk(),
new AntiGrief.GriefIncident(
event.getEntity().getLocation(),
event,
event.getEntity().getType(),
amount > half
? AntiGrief.GriefIncident.Severity.MODERATE
: AntiGrief.GriefIncident.Severity.LIGHT
)
);
}, 1L);
}
}

View File

@@ -0,0 +1,105 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.listener;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.AntiGrief;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.minecart.ExplosiveMinecart;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.BlockExplodeEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.block.TNTPrimeEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.entity.EntityPlaceEvent;
import org.bukkit.event.vehicle.VehicleCreateEvent;
import java.util.List;
public class ExplosionRelatedGriefListener extends ApplianceListener<AntiGrief> {
@EventHandler
public void tntPlacement(BlockPlaceEvent event) {
if(!event.getBlockPlaced().getType().equals(Material.TNT)) return;
this.getAppliance().trackDirect(
event.getPlayer(),
new AntiGrief.GriefIncident(
event.getBlock().getLocation(),
event,
event.getBlock().getType(),
AntiGrief.GriefIncident.Severity.MODERATE
)
);
}
@EventHandler
public void crystalPlacement(EntityPlaceEvent event) {
if(!event.getEntityType().equals(EntityType.END_CRYSTAL)) return;
AntiGrief.GriefIncident incident = new AntiGrief.GriefIncident(
event.getEntity().getLocation(),
event,
event.getEntityType(),
AntiGrief.GriefIncident.Severity.LIGHT
);
if(event.getPlayer() != null)
this.getAppliance().trackDirect(event.getPlayer(), incident);
else
this.getAppliance().trackPassive(event.getBlock().getChunk(), incident);
}
@EventHandler
public void tntPrime(TNTPrimeEvent event) {
AntiGrief.GriefIncident incident = new AntiGrief.GriefIncident(
event.getBlock().getLocation(),
event,
List.of(event.getCause(), event.getBlock().getType()),
AntiGrief.GriefIncident.Severity.MODERATE
);
if(event.getCause().equals(TNTPrimeEvent.PrimeCause.PLAYER) && event.getPrimingEntity() instanceof Player player) {
this.getAppliance().trackDirect(player, incident);
}
this.getAppliance().trackPassive(event.getBlock().getChunk(), incident);
}
@EventHandler
public void tntMinecartPlace(VehicleCreateEvent event) {
if(!(event.getVehicle() instanceof ExplosiveMinecart minecart)) return;
this.getAppliance().trackPassive(
event.getVehicle().getChunk(),
new AntiGrief.GriefIncident(
minecart.getLocation(),
event,
minecart.getType(),
AntiGrief.GriefIncident.Severity.SEVERE
)
);
}
@EventHandler
public void entityExplosion(EntityExplodeEvent event) {
this.getAppliance().trackPassive(
event.getEntity().getChunk(),
new AntiGrief.GriefIncident(
event.getLocation(),
event,
List.of(event.getEntityType(), event.blockList().stream().map(Block::getType).distinct().toList()),
AntiGrief.GriefIncident.Severity.MODERATE
)
);
}
@EventHandler
public void blockExplosion(BlockExplodeEvent event) {
this.getAppliance().trackPassive(
event.getBlock().getChunk(),
new AntiGrief.GriefIncident(
event.getBlock().getLocation(),
event,
List.of(event.getBlock().getType(), event.getExplodedBlockState().getType()),
AntiGrief.GriefIncident.Severity.MODERATE
)
);
}
}

View File

@@ -0,0 +1,70 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.listener;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.AntiGrief;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.*;
import java.util.List;
public class FireRelatedGriefListener extends ApplianceListener<AntiGrief> {
@EventHandler
public void activeBlockIgnite(BlockPlaceEvent event) {
if(!event.getBlock().getType().equals(Material.FIRE)) return;
if(this.getAppliance().getSurroundingBlocks(event.getBlock().getLocation()).noneMatch(Block::isBurnable)) return;
this.getAppliance().trackDirect(
event.getPlayer(),
new AntiGrief.GriefIncident(
event.getBlock().getLocation(),
event,
event.getBlock().getType(),
AntiGrief.GriefIncident.Severity.MODERATE
)
);
}
@EventHandler
public void blockIgnite(BlockIgniteEvent event) {
if(!event.getBlock().isBurnable()) return;
this.getAppliance().trackPassive(
event.getBlock().getChunk(),
new AntiGrief.GriefIncident(
event.getBlock().getLocation(),
event,
List.of(event.getBlock().getType(), event.getCause()),
event.getCause().equals(BlockIgniteEvent.IgniteCause.FLINT_AND_STEEL)
? AntiGrief.GriefIncident.Severity.MODERATE
: AntiGrief.GriefIncident.Severity.LIGHT
)
);
}
@EventHandler
public void fireSpread(BlockSpreadEvent event) {
if(!event.getBlock().getType().equals(Material.FIRE)) return;
this.getAppliance().trackPassive(
event.getBlock().getChunk(),
new AntiGrief.GriefIncident(
event.getBlock().getLocation(),
event,
event.getBlock().getType(),
AntiGrief.GriefIncident.Severity.LIGHT
)
);
}
@EventHandler
public void blockBurned(BlockBurnEvent event) {
this.getAppliance().trackPassive(
event.getBlock().getChunk(),
new AntiGrief.GriefIncident(
event.getBlock().getLocation(),
event,
event.getBlock().getType(),
AntiGrief.GriefIncident.Severity.MODERATE
)
);
}
}

View File

@@ -0,0 +1,59 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.listener;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.AntiGrief;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.BlockFormEvent;
import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.event.player.PlayerBucketEmptyEvent;
import java.util.List;
public class LiquidRelatedGriefListener extends ApplianceListener<AntiGrief> {
@EventHandler
public void liquidFlow(BlockFromToEvent event) {
if(event.getToBlock().isEmpty()) return;
if(event.getToBlock().isSolid()) return;
this.getAppliance().trackPassive(
event.getToBlock().getChunk(),
new AntiGrief.GriefIncident(
event.getToBlock().getLocation(),
event,
event.getToBlock().getType(),
AntiGrief.GriefIncident.Severity.INFO
)
);
}
@EventHandler
public void lavaCast(BlockFormEvent event) {
if(!List.of(Material.COBBLESTONE, Material.STONE, Material.OBSIDIAN).contains(event.getNewState().getType())) return;
this.getAppliance().trackPassive(
event.getBlock().getChunk(),
new AntiGrief.GriefIncident(
event.getBlock().getLocation(),
event,
List.of(event.getBlock().getType(), event.getNewState().getType()),
AntiGrief.GriefIncident.Severity.LIGHT
)
);
}
@EventHandler
public void lavaPlace(PlayerBucketEmptyEvent event) {
if(!event.getBucket().equals(Material.LAVA_BUCKET)) return;
if(this.getAppliance().getSurroundingBlocks(event.getBlockClicked().getLocation()).noneMatch(Block::isBurnable)) return;
this.getAppliance().trackDirect(
event.getPlayer(),
new AntiGrief.GriefIncident(
event.getBlock().getLocation(),
event,
List.of(event.getBlock().getType(), event.getBucket()),
AntiGrief.GriefIncident.Severity.MODERATE
)
);
}
}

View File

@@ -1,20 +0,0 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.player;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.craftattack.appliances.security.antiGrief.AntiGrief;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.BlockIgniteEvent;
import org.bukkit.event.entity.ExplosionPrimeEvent;
public class PlayerGriefListener extends ApplianceListener<AntiGrief> {
@EventHandler
public void onIgnite(BlockIgniteEvent event) {
if(event.getPlayer() == null) return;
this.getAppliance().addTracking(event.getPlayer(), new AntiGrief.GriefIncident(event.getBlock().getLocation(), event.getBlock()));
}
@EventHandler
public void onTnt(ExplosionPrimeEvent event) {
}
}