package xyz.etztech.minealert.listeners; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.ComponentBuilder; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockPlaceEvent; import xyz.etztech.minealert.Color; import xyz.etztech.minealert.Lang; import xyz.etztech.minealert.MineAlert; import xyz.etztech.minealert.MuteType; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; public class OreAlertListener implements Listener { private final MineAlert plugin; private static final Set cache = Collections.synchronizedSet(new HashSet<>()); private static final Map> map = new ConcurrentHashMap<>(); private static final Queue queue = new ConcurrentLinkedQueue<>(); public OreAlertListener(MineAlert plugin) { this.plugin = plugin; this.plugin.getServer().getPluginManager().registerEvents(this, plugin); this.plugin.getServer().getScheduler().runTaskAsynchronously(this.plugin, this::task); this.plugin.getServer().getScheduler().runTaskTimerAsynchronously(this.plugin, this::cleanup, 0, 20 * 60 * this.plugin.getConfig().getInt("ore.cleanup", 5)); } @EventHandler public void onBlockBreak(BlockBreakEvent event) { if (MineAlert.hasIgnoreAlertPerm(event.getPlayer(), MuteType.OREALERT_MUTE)) return; if (cache.contains(event.getBlock().getLocation())) return; if (!isMaterialTracked(event.getBlock().getType())) return; queue.add(new BlockEvent(event.getPlayer(), event.getBlock().getType(), event.getBlock().getLocation(), true)); int radius = this.plugin.getConfig().getInt("ore.radius", 3); for (int x = -radius; x < radius; x++) { for (int y = -radius; y < radius; y++) { for (int z = -radius; z < radius; z++) { if (x == 0 && y == 0 && z == 0) continue; Block block = event.getBlock().getRelative(x, y, z); if (cache.contains(block.getLocation())) continue; if (!isMaterialTracked(block.getType())) continue; queue.add(new BlockEvent(event.getPlayer(), block.getType(), block.getLocation(), false)); } } } } @EventHandler public void onBlockPlace(BlockPlaceEvent event) { this.plugin.getServer().getScheduler().runTaskAsynchronously(this.plugin, () -> { Block eventBlock = event.getBlock(); if (isMaterialTracked(eventBlock.getType())) cache.add(eventBlock.getLocation()); if (eventBlock.getType() == Material.TNT || (eventBlock.getType().toString().contains("_BED") && eventBlock.getWorld().getEnvironment() == World.Environment.NETHER)) { for (int x = -4; x < 4; x++) { for (int y = -4; y < 4; y++) { for (int z = -4; z < 4; z++) { Block block = eventBlock.getRelative(x, y, z); cache.add(block.getLocation()); } } } } }); } private boolean isMaterialTracked(Material material) { for (String s: this.plugin.getConfig().getConfigurationSection("ore.blocks").getKeys(false)) { if (Lang.getMaterialKey(material).equals(s)) { return true; } } return false; } private void task() { while (this.plugin.isEnabled()) { BlockEvent event = this.queue.poll(); if (event != null) { if (cache.contains(event.getLocation())) continue; cache.add(event.getLocation()); if (event.isParent()) { addStrike(event); check(event); } } } } private void addStrike(BlockEvent event) { List events = map.getOrDefault(event.getPlayer().getUniqueId(), new ArrayList<>()); events.add(event); map.put(event.getPlayer().getUniqueId(), events); } private void check(BlockEvent event) { String blockKey = Lang.getMaterialKey(event.getMaterial()); int start = this.plugin.getConfigIntFallback( 5, String.format("ore.blocks.%s.start", blockKey), "ore.start" ); int each = this.plugin.getConfigIntFallback( 1, String.format("ore.blocks.%s.each", blockKey), "ore.each" ); int ping = this.plugin.getConfigIntFallback( 5, String.format("ore.blocks.%s.ping", blockKey), "ore.ping" ); int belowY = this.plugin.getConfigIntFallback( 255, String.format("ore.blocks.%s.below_y", blockKey), "ore.below_y" ); int aboveY = this.plugin.getConfigIntFallback( 0, String.format("ore.blocks.%s.above_y", blockKey), "ore.above_y" ); purge(map.getOrDefault(event.getPlayer().getUniqueId(), new ArrayList<>()).iterator()); int yLevel = event.location.getBlockY(); if (yLevel > belowY || yLevel < aboveY) { return; } if (MuteType.GRIEFALERT_MUTE.hasMuteStatus(event.getPlayer(), plugin)) { return; } int strikes = 0; for (BlockEvent e : map.getOrDefault(event.getPlayer().getUniqueId(), new ArrayList<>())) { if (e.isParent() && e.getMaterial().name().equals(event.getMaterial().name())) { strikes++; } } double alert = (double) strikes / start; if (alert == 1) { sendAlert(event, strikes, true); } else if (alert > 1) { if (strikes % ping == 0) { sendAlert(event, strikes, true); } else if (strikes % each == 0) { sendAlert(event, strikes, false); } } } private void cleanup() { for (Iterator it = map.keySet().iterator(); it.hasNext(); ) { UUID playerUUID = it.next(); purge(map.get(playerUUID).iterator()); if (map.get(playerUUID).isEmpty()) { it.remove(); } } } private void purge(Iterator events) { Date now = Calendar.getInstance().getTime(); while (events.hasNext()) { BlockEvent e = events.next(); int purge = 1000 * 60 * this.plugin.getConfigIntFallback( 30, String.format("ore.blocks.%s.purge", Lang.getMaterialKey(e.getMaterial())), "ore.purge" ); if (new Date(e.getTime().getTime() + purge).before(now)) { cache.remove(e.getLocation()); events.remove(); } } } private void sendAlert(BlockEvent event, int strikes, boolean ping) { String message = Lang.ORE_ALERT.getMessage(event.getPlayer().getName(), strikes, Lang.getMaterialName(event.getMaterial())); Color color = new Color(plugin.getConfigStringFallback( "#AAAAAA", String.format("ore.blocks.%s.color", Lang.getMaterialKey(event.getMaterial())), "ore.color" )); String usernameURL = this.plugin.getConfigStringFallback( "", String.format("ore.blocks.%s.url", Lang.getMaterialKey(event.getMaterial())), "ore.url", "url" ); usernameURL = MineAlert.formatAlertURL(usernameURL, event.getPlayer(), event.getLocation()); ComponentBuilder builder = new ComponentBuilder() .append(message).color(color.getChatColor()); if (!"".equals(usernameURL)) { builder.event(new ClickEvent(ClickEvent.Action.OPEN_URL, usernameURL)); } BaseComponent[] component = builder.create(); for (Player player : Bukkit.getOnlinePlayers()) { if (player.hasPermission("minealert.alert")) { player.spigot().sendMessage(component); } } // Webhook String webhook = this.plugin.getConfigStringFallback( "", String.format("ore.blocks.%s.webhook", Lang.getMaterialKey(event.getMaterial())), "ore.webhook", "webhook" ); if (!"".equals(webhook)) { this.plugin.sendWebhook(webhook, color, event.getPlayer(), event.getLocation(), message, usernameURL); } } public static Set getCache() { return cache; } public static Map> getMap() { return map; } public static Queue getQueue() { return queue; } private static class BlockEvent { private final Player player; private final Material material; private final Location location; private final Boolean parent; private final Date time; BlockEvent(Player player, Material material, Location location, Boolean parent) { this.player = player; this.material = material; this.location = location; this.parent = parent; this.time = Calendar.getInstance().getTime(); } public Player getPlayer() { return player; } public Material getMaterial() { return material; } public Location getLocation() { return location; } public Boolean isParent() { return parent; } public Date getTime() { return time; } } }