WIP: AntiGrief
This commit is contained in:
@@ -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 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 void addTracking(Player player, GriefIncident incident) {
|
||||
this.griefRegistry.computeIfAbsent(player.getUniqueId(), uuid -> new ArrayList<>());
|
||||
this.griefRegistry.get(player.getUniqueId()).add(incident);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Map<UUID, List<GriefIncident>> getGriefRegistry() {
|
||||
return this.griefRegistry;
|
||||
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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -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()))
|
||||
|
@@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user