From d90ef86e3fa6e65c641afd262fe9cfee8b43e7f4 Mon Sep 17 00:00:00 2001 From: Ardakaz Date: Sat, 23 Aug 2025 06:20:33 +0000 Subject: [PATCH] Initial code --- src/net/ardakaz/griefalert/GriefAlert.java | 740 ++++++++++++++++++ .../ardakaz/griefalert/GriefAlertEvent.java | 29 + 2 files changed, 769 insertions(+) create mode 100644 src/net/ardakaz/griefalert/GriefAlert.java create mode 100644 src/net/ardakaz/griefalert/GriefAlertEvent.java diff --git a/src/net/ardakaz/griefalert/GriefAlert.java b/src/net/ardakaz/griefalert/GriefAlert.java new file mode 100644 index 0000000..af99328 --- /dev/null +++ b/src/net/ardakaz/griefalert/GriefAlert.java @@ -0,0 +1,740 @@ +package net.ardakaz.griefalert; + +import net.coreprotect.CoreProtect; +import net.coreprotect.CoreProtectAPI; +import net.coreprotect.CoreProtectAPI.ParseResult; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.bukkit.entity.AnimalTamer; +import org.bukkit.entity.Cat; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Tameable; +import org.bukkit.entity.Wolf; +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.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.hanging.HangingBreakByEntityEvent; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.player.PlayerInteractAtEntityEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.entity.ArmorStand; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.entity.ItemFrame; + + + +public class GriefAlert extends JavaPlugin implements Listener, TabCompleter { + + private static CoreProtectAPI coreProtectAPI; + private Integer identicalAlerts = 1; + private String lastAlert; + + private Set EXCLUDED_BLOCKS; + private Set VALID_CONTAINERS; + private String MAP_LINK; + private Boolean ALLOW_STEALING; + + private Connection connection; + private final String DB_FILE = "ignored_locations.db"; + + // Init GriefAlert + @Override + public void onEnable() { + + coreProtectAPI = getCoreProtect(); + if (coreProtectAPI == null) { + getLogger().severe("CoreProtect not found! Disabling plugin."); + getServer().getPluginManager().disablePlugin(this); + return; + } + + getServer().getPluginManager().registerEvents(this, this); + + // Config + saveDefaultConfig(); + List excludedBlocks = getConfig().getStringList("excluded-blocks"); + EXCLUDED_BLOCKS = excludedBlocks.stream().map(Material::valueOf).collect(Collectors.toSet()); + List validContainers = getConfig().getStringList("valid-containers"); + VALID_CONTAINERS = validContainers.stream().map(InventoryType::valueOf).collect(Collectors.toSet()); + MAP_LINK = getConfig().getString("map-link"); + ALLOW_STEALING = getConfig().getBoolean("allow-stealing"); + + setupDatabase(); + + getCommand("griefalert").setTabCompleter(this); + + getLogger().info("GriefAlert has been enabled."); + } + + @Override + public void onDisable() { + getLogger().info("GriefAlert has been disabled."); + if (connection != null) { + try { connection.close(); } catch (SQLException ignored) {} + } + } + + private void setupDatabase() { + try { + connection = DriverManager.getConnection("jdbc:sqlite:" + getDataFolder().getAbsolutePath() + "/" + DB_FILE); + Statement stmt = connection.createStatement(); + stmt.executeUpdate("CREATE TABLE IF NOT EXISTS ignored_locations (x INTEGER, y INTEGER, z INTEGER, world TEXT, PRIMARY KEY (x, y, z, world))"); + stmt.close(); + } catch (SQLException e) { + getLogger().severe("Could not set up SQLite database: " + e.getMessage()); + } + } + + private boolean isLocationIgnored(int x, int y, int z, String world) { + try { + PreparedStatement ps = connection.prepareStatement("SELECT 1 FROM ignored_locations WHERE x=? AND y=? AND z=? AND world=?"); + ps.setInt(1, x); + ps.setInt(2, y); + ps.setInt(3, z); + ps.setString(4, world); + ResultSet rs = ps.executeQuery(); + boolean exists = rs.next(); + rs.close(); + ps.close(); + return exists; + } catch (SQLException e) { + getLogger().warning("DB error: " + e.getMessage()); + return false; + } + } + + private boolean addIgnoredLocation(int x, int y, int z, String world) { + try { + PreparedStatement ps = connection.prepareStatement("INSERT OR IGNORE INTO ignored_locations (x, y, z, world) VALUES (?, ?, ?, ?)"); + ps.setInt(1, x); + ps.setInt(2, y); + ps.setInt(3, z); + ps.setString(4, world); + int updated = ps.executeUpdate(); + ps.close(); + return updated > 0; + } catch (SQLException e) { + getLogger().warning("DB error: " + e.getMessage()); + return false; + } + } + + private boolean removeIgnoredLocation(int x, int y, int z, String world) { + try { + PreparedStatement ps = connection.prepareStatement("DELETE FROM ignored_locations WHERE x=? AND y=? AND z=? AND world=?"); + ps.setInt(1, x); + ps.setInt(2, y); + ps.setInt(3, z); + ps.setString(4, world); + int updated = ps.executeUpdate(); + ps.close(); + return updated > 0; + } catch (SQLException e) { + getLogger().warning("DB error: " + e.getMessage()); + return false; + } + } + + @EventHandler (ignoreCancelled = true) + // Block break alerts + public void onBlockBreak(BlockBreakEvent event) { + // Exclusion list + if (EXCLUDED_BLOCKS.contains(event.getBlock().getType())) { + return; + } + + // Event parser + String playerName = event.getPlayer().getName(); + String blockType = event.getBlock().getType().toString(); + int x = event.getBlock().getX(); + int y = event.getBlock().getY(); + int z = event.getBlock().getZ(); + String worldName = event.getBlock().getWorld().getName(); + + // Check if grief + String target = inspectBlock(event.getBlock(), event.getPlayer()); + if (target != null) { + String message = ChatColor.GRAY + playerName + " broke " + blockType + " placed by " + target + " at " + x + " " + y + " " + z + getHumanWorldName(worldName); + alert(message, playerName, "[Map Link](" + MAP_LINK + "/?worldname=" + worldName + "&zoom=7&x=" + x + "&y=" + y + "&z=" + z + ")", target, x, y, z, worldName); + } + } + + // Stealing alerts + @EventHandler (ignoreCancelled = true) + public void onInventoryClick(InventoryClickEvent event) { + if (ALLOW_STEALING) { + return; + } + boolean stealing; + + // Event parser for inv + if (!(event.getWhoClicked() instanceof Player)) return; + Player player = (Player) event.getWhoClicked(); + Inventory inventory = event.getInventory(); + Inventory clickedInventory = event.getClickedInventory(); + ItemStack item = event.getCurrentItem(); + + if (item == null || inventory.getLocation() == null || item.getType() == Material.AIR) { + return; + } + + // Exclusion list + if (!VALID_CONTAINERS.contains(inventory.getType())) { + return; + } + + // Inv actions (needs fixing) + InventoryAction action = event.getAction(); + if ((action == InventoryAction.PICKUP_ALL || action == InventoryAction.PICKUP_HALF || + action == InventoryAction.PICKUP_ONE || action == InventoryAction.PICKUP_SOME || + action == InventoryAction.MOVE_TO_OTHER_INVENTORY) && clickedInventory == inventory) { + stealing = true; + } else if (action == InventoryAction.PLACE_ALL || action == InventoryAction.PLACE_SOME || + action == InventoryAction.PLACE_ONE || (action == InventoryAction.MOVE_TO_OTHER_INVENTORY && clickedInventory != inventory)) { + stealing = false; + } else { + return; + } + + // Event parser for container + check if grief + String target = inspectBlock(inventory.getLocation().getBlock(), player); + if (target != null) { + String playerName = player.getName(); + String itemName = item.getType().toString(); + int amount = item.getAmount(); + int x = inventory.getLocation().getBlockX(); + int y = inventory.getLocation().getBlockY(); + int z = inventory.getLocation().getBlockZ(); + String worldName = inventory.getLocation().getWorld().getName(); + if (stealing) { + String message = ChatColor.GRAY + playerName + " took " + amount + " " + itemName + " from " + target + "'s container at " + x + " " + y + " " + z + getHumanWorldName(worldName); + alert(message, playerName, "[Map Link](" + MAP_LINK + "/?worldname=" + worldName + "&zoom=7&x=" + x + "&y=" + y + "&z=" + z + ")", target, x, y, z, worldName); + } else { + String message = ChatColor.GRAY + playerName + " put " + amount + " " + itemName + " into " + target + "'s container at " + x + " " + y + " " + z + getHumanWorldName(worldName); + alert(message, playerName, "[Map Link](" + MAP_LINK + "/?worldname=" + worldName + "&zoom=7&x=" + x + "&y=" + y + "&z=" + z + ")", target, x, y, z, worldName); + } + } + } + + // Armor Stand break alerts + @EventHandler(ignoreCancelled = true) + public void onArmorStandBreak(EntityDamageByEntityEvent event) { + if (!(event.getEntity() instanceof ArmorStand)) return; + if (!(event.getDamager() instanceof Player)) return; + Player player = (Player) event.getDamager(); + ArmorStand armorStand = (ArmorStand) event.getEntity(); + int x = armorStand.getLocation().getBlockX(); + int y = armorStand.getLocation().getBlockY(); + int z = armorStand.getLocation().getBlockZ(); + String worldName = armorStand.getWorld().getName(); + if (isLocationIgnored(x, y, z, worldName)) { + event.setCancelled(true); + return; + } + String playerName = player.getName(); + String target = inspectBlock(armorStand.getLocation().getBlock(), player); + if (target != null) { + String message = ChatColor.GRAY + playerName + " broke an armor stand placed by " + target + " at " + x + " " + y + " " + z + getHumanWorldName(worldName); + alert(message, playerName, "[Map Link](" + MAP_LINK + "/?worldname=" + worldName + "&zoom=7&x=" + x + "&y=" + y + "&z=" + z + ")", target, x, y, z, worldName); + } + } + + // Armor Stand item interaction alerts + @EventHandler(ignoreCancelled = true) + public void onArmorStandInteract(PlayerInteractAtEntityEvent event) { + if (!(event.getRightClicked() instanceof ArmorStand)) return; + Player player = event.getPlayer(); + ArmorStand armorStand = (ArmorStand) event.getRightClicked(); + int x = armorStand.getLocation().getBlockX(); + int y = armorStand.getLocation().getBlockY(); + int z = armorStand.getLocation().getBlockZ(); + String worldName = armorStand.getWorld().getName(); + if (isLocationIgnored(x, y, z, worldName)) { + event.setCancelled(true); + return; + } + String target = inspectBlock(armorStand.getLocation().getBlock(), player); + if (target != null) { + String playerName = player.getName(); + ItemStack handItem = player.getInventory().getItemInMainHand(); + // Determine which slot is being interacted with + //org.bukkit.inventory.EquipmentSlot slot = event.getHand(); + EntityEquipment equipment = armorStand.getEquipment(); + ItemStack armorItem = null; + if (event.getClickedPosition() != null) { + // Approximate slot by Y position (not perfect, but Bukkit API is limited so it is what it is :3) + double yPos = event.getClickedPosition().getY(); + if (yPos > 1.6) armorItem = equipment.getHelmet(); + else if (yPos > 1.2) armorItem = equipment.getChestplate(); + else if (yPos > 0.8) armorItem = equipment.getLeggings(); + else if (yPos > 0.1) armorItem = equipment.getBoots(); + else armorItem = equipment.getItemInMainHand(); + } else { + armorItem = equipment.getItemInMainHand(); + } + boolean handEmpty = handItem == null || handItem.getType() == Material.AIR; + boolean armorEmpty = armorItem == null || armorItem.getType() == Material.AIR; + String action; + //this works at the instance of interaction before items get moved around + if (handEmpty && !armorEmpty) { + action = "took " + armorItem.getType().toString(); + } else if (!handEmpty && armorEmpty) { + action = "added " + handItem.getType().toString(); + } else if (!handEmpty && !armorEmpty) { + action = "swapped " + armorItem.getType().toString() + " with " + handItem.getType().toString(); + } else { + action = "interacted with"; + } + String message = ChatColor.GRAY + playerName + " " + action + " on an armor stand owned by " + target + " at " + x + " " + y + " " + z + getHumanWorldName(worldName); + alert(message, playerName, "[Map Link](" + MAP_LINK + "/?worldname=" + worldName + "&zoom=7&x=" + x + "&y=" + y + "&z=" + z + ")", target, x, y, z, worldName); + } + } + //event handler for item frames + @EventHandler(ignoreCancelled = true) + public void onPlayerInteractEntity(PlayerInteractEntityEvent event){ + if(!(event.getRightClicked() instanceof ItemFrame)) return; + + Player player = event.getPlayer(); + ItemFrame frame = (ItemFrame) event.getRightClicked(); + int x = frame.getLocation().getBlockX(); + int y = frame.getLocation().getBlockY(); + int z = frame.getLocation().getBlockZ(); + String worldName = frame.getWorld().getName(); + if (isLocationIgnored(x, y, z, worldName)) { + event.setCancelled(true); + return; + } + String target = inspectBlock(frame.getLocation().getBlock(), player); + if(target !=null){ + ItemStack itemInFrame = frame.getItem(); + ItemStack handItem = player.getInventory().getItemInMainHand(); + boolean handEmpty = handItem ==null || handItem.getType() == Material.AIR; + boolean frameEmpty = itemInFrame ==null || itemInFrame.getType() == Material.AIR; + String playerName = player.getName(); + + String action; + if (!handEmpty && frameEmpty) { + action = "added " + handItem.getType(); + } + else { + return; + } + + String message = ChatColor.GRAY + playerName + " " + action + " an item to an item frame owned by " + target + " at " + x + " " + y + " " + z + getHumanWorldName(worldName); + alert(message, playerName, "[Map Link](" + MAP_LINK + "/?worldname=" + worldName + "&zoom=7&x=" + x + "&y=" + y + "&z=" + z + ")", target, x, y, z, worldName); + + } + } + + + + //breaking item frame item (uses a different event then placing an item) + @EventHandler (ignoreCancelled = true) + public void onItemFrameDamage(EntityDamageByEntityEvent event){ + if (!(event.getEntity() instanceof ItemFrame)) return; + if (!(event.getDamager() instanceof Player)) return; + + ItemFrame frame = (ItemFrame) event.getEntity(); + Player player = (Player) event.getDamager(); + ItemStack item = frame.getItem(); + String playerName = player.getName(); + + int x = frame.getLocation().getBlockX(); + int y = frame.getLocation().getBlockY(); + int z = frame.getLocation().getBlockZ(); + String worldName = frame.getWorld().getName(); + + if (isLocationIgnored(x, y, z, worldName)) { + event.setCancelled(true); + return; + } + String target = inspectBlock(frame.getLocation().getBlock(), player); + if (target == null || target.equals(player.getName())) return; + + + if (item == null || item.getType() == Material.AIR) return; + // At this point, the player is removing the item + String action = "took " + item.getType(); + String message = ChatColor.GRAY + playerName + " " + action + " from item frame owned by " + target + " at " + x + " " + y + " " + z + getHumanWorldName(worldName); + alert(message, playerName, "[Map Link](" + MAP_LINK + "/?worldname=" + worldName + "&zoom=7&x=" + x + "&y=" + y + "&z=" + z + ")", target, x, y, z, worldName); + } + + + @EventHandler(ignoreCancelled = true) + public void onItemFrameBreak(HangingBreakByEntityEvent event){ + if (!(event.getEntity() instanceof ItemFrame)) return; + if (!(event.getRemover() instanceof Player)) return; + + ItemFrame frame = (ItemFrame) event.getEntity(); + Player player = (Player) event.getRemover(); + String playerName = player.getName(); + + + Location loc = frame.getLocation(); + int x = loc.getBlockX(); + int y = loc.getBlockY(); + int z = loc.getBlockZ(); + String worldName = loc.getWorld().getName(); + + if (isLocationIgnored(x, y, z, worldName)) { + event.setCancelled(true); + return; + } + String target = inspectBlock(loc.getBlock(), player); + if (target ==null || target.equals(player.getName())) return; + + String action = "broke an item frame"; + String message = ChatColor.GRAY + playerName + " " + action + " owned by " + target + " at " + x + " " + y + " " + z + getHumanWorldName(worldName); + alert(message, playerName, "[Map Link](" + MAP_LINK + "/?worldname=" + worldName + "&zoom=7&x=" + x + "&y=" + y + "&z=" + z + ")", target, x, y, z, worldName); + + } + + + + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!command.getName().equalsIgnoreCase("griefalert")) return false; + if (args.length == 0) return false; + String sub = args[0].toLowerCase(); + if (sub.equals("ignore")) { + if (!sender.hasPermission("griefalert.staff.ignore")) { + sender.sendMessage(ChatColor.RED + "You do not have permission."); + return true; + } + String world; + int x, y, z; + if (args.length == 4) { + // /griefalert ignore x y z (use sender's world) + if (!(sender instanceof Player)) { + sender.sendMessage(ChatColor.RED + "Only players can use this command without specifying a world."); + return true; + } + world = ((Player)sender).getWorld().getName(); + try { + x = Integer.parseInt(args[1]); + y = Integer.parseInt(args[2]); + z = Integer.parseInt(args[3]); + } catch (NumberFormatException e) { + sender.sendMessage(ChatColor.RED + "Coordinates must be numbers."); + return true; + } + } else if (args.length == 5) { + // /griefalert ignore world x y z + world = args[1]; + try { + x = Integer.parseInt(args[2]); + y = Integer.parseInt(args[3]); + z = Integer.parseInt(args[4]); + } catch (NumberFormatException e) { + sender.sendMessage(ChatColor.RED + "Coordinates must be numbers."); + return true; + } + } else { + sender.sendMessage(ChatColor.RED + "Usage: /griefalert ignore or /griefalert ignore "); + return true; + } + boolean result = addIgnoredLocation(x, y, z, world); + if (result) { + sender.sendMessage(ChatColor.GREEN + "Location ignored."); + } else { + sender.sendMessage(ChatColor.YELLOW + "Location was already ignored."); + } + return true; + } else if (sub.equals("unignore")) { + if (!sender.hasPermission("griefalert.staff.UnIgnore")) { + sender.sendMessage(ChatColor.RED + "You do not have permission."); + return true; + } + String world; + int x, y, z; + if (args.length == 4) { + // /griefalert unignore x y z (use sender's world) + if (!(sender instanceof Player)) { + sender.sendMessage(ChatColor.RED + "Only players can use this command without specifying a world."); + return true; + } + world = ((Player)sender).getWorld().getName(); + try { + x = Integer.parseInt(args[1]); + y = Integer.parseInt(args[2]); + z = Integer.parseInt(args[3]); + } catch (NumberFormatException e) { + sender.sendMessage(ChatColor.RED + "Coordinates must be valid."); + return true; + } + } else if (args.length == 5) { + // /griefalert unignore world x y z + world = args[1]; + try { + x = Integer.parseInt(args[2]); + y = Integer.parseInt(args[3]); + z = Integer.parseInt(args[4]); + } catch (NumberFormatException e) { + sender.sendMessage(ChatColor.RED + "Coordinates must be numbers."); + return true; + } + } else { + sender.sendMessage(ChatColor.RED + "Usage: /griefalert unignore or /griefalert unignore "); + return true; + } + boolean result = removeIgnoredLocation(x, y, z, world); + if (result) { + sender.sendMessage(ChatColor.GREEN + "Location unignored."); + } else { + sender.sendMessage(ChatColor.YELLOW + "Location was not ignored."); + } + return true; + } else if (sub.equals("check")) { + // /griefalert check or /griefalert check + String world; + int x, y, z; + if (args.length == 4) { + // /griefalert check x y z (use sender's world) + if (!(sender instanceof Player)) { + sender.sendMessage(ChatColor.RED + "Only players can use this command without specifying a world."); + return true; + } + world = ((Player)sender).getWorld().getName(); + try { + x = Integer.parseInt(args[1]); + y = Integer.parseInt(args[2]); + z = Integer.parseInt(args[3]); + } catch (NumberFormatException e) { + sender.sendMessage(ChatColor.RED + "Coordinates must be numbers."); + return true; + } + } else if (args.length == 5) { + // /griefalert check world x y z + world = args[1]; + try { + x = Integer.parseInt(args[2]); + y = Integer.parseInt(args[3]); + z = Integer.parseInt(args[4]); + } catch (NumberFormatException e) { + sender.sendMessage(ChatColor.RED + "Coordinates must be numbers."); + return true; + } + } else { + sender.sendMessage(ChatColor.RED + "Usage: /griefalert check (optional) "); + return true; + } + boolean ignored = isLocationIgnored(x, y, z, world); + if (ignored) { + sender.sendMessage(ChatColor.YELLOW + "GriefAlert is DISABLED at " + x + ", " + y + ", " + z + " in " + world + "."); + } else { + sender.sendMessage(ChatColor.GREEN + "GriefAlert is ENABLED at " + x + ", " + y + ", " + z + " in " + world + "."); + } + return true; + } + sender.sendMessage(ChatColor.RED + "Unknown subcommand. Use: ignore, unignore, check"); + return false; + } + + @Override + public java.util.List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + if (!command.getName().equalsIgnoreCase("griefalert")) return null; + java.util.List completions = new java.util.ArrayList<>(); + if (args.length == 1) { + java.util.List subs = java.util.Arrays.asList("ignore", "unignore", "check"); + for (String s : subs) { + if (s.startsWith(args[0].toLowerCase())) completions.add(s); + } + return completions; + } + if (args.length == 2 && (args[0].equalsIgnoreCase("ignore") || args[0].equalsIgnoreCase("unignore") || args[0].equalsIgnoreCase("check"))) { + // Suggest world names for the 2nd argument + for (org.bukkit.World world : org.bukkit.Bukkit.getWorlds()) { + completions.add(world.getName()); + } + return completions; + } + // Optionally, you could add number tab completion for coordinates, but it's not necessary + return null; + } + + // Sends the alert (or cancels it) + private void alert(String message, String playerName, String mapLink, String target, int x, int y, int z, String world) { + // Exclude trusted people + Player griefer = Bukkit.getPlayer(playerName); + if (griefer.hasPermission("griefalert.exclude") || griefer.hasPermission("griefalert.exclude." + target)) { + return; + } + // Spam limiter + String realAlertMessage = message; + String[] alert1 = null; + if (lastAlert != null) { + alert1 = lastAlert.split(" "); + } + String[] alert2 = message.split(" "); + if (alert1 != null) { + if (alert1[2].equals(alert2[2]) && alert1[5].equals(alert2[5]) && alert1[1].equals("broke") && alert2[1].equals("broke")) { + identicalAlerts += 1; + } else if (Arrays.equals(alert1, alert2)) { + identicalAlerts += 1; + } else { + identicalAlerts = 1; + } + } + if (identicalAlerts == 4) { + message = ChatColor.GRAY + "Same behavior continues."; + mapLink = null; + } + if (identicalAlerts > 4) { + return; + } + // Use direct location check + if (world != null && isLocationIgnored(x, y, z, world)) { + return; // Do not alert if location is ignored + } + // Send an event for external hooks + GriefAlertEvent griefalert_event; + if (MAP_LINK != null && !MAP_LINK.isEmpty() && mapLink != null) { + griefalert_event = new GriefAlertEvent(message + " (" + mapLink + ")"); + } else { + griefalert_event = new GriefAlertEvent(message); + } + getServer().getPluginManager().callEvent(griefalert_event); + // Notify staff ingame + for (Player player : Bukkit.getOnlinePlayers()) { + if (player.hasPermission("griefalert.notify")) { + player.sendMessage(message); + } + } + lastAlert = realAlertMessage; + } + + // Block inspector: only the most recent placement counts for ownership. + private static String inspectBlock(Block block, Player player) { + List lookup = coreProtectAPI.blockLookup(block, 50000000); + if (lookup == null || lookup.size() == 0) { + // Natural block + return null; + } + // Find the most recent placement event only + for (String[] result : lookup) { + ParseResult parseResult = coreProtectAPI.parseResult(result); + if (parseResult == null) continue; + if (parseResult.getActionId() == 1 && !parseResult.isRolledBack() && !parseResult.getPlayer().startsWith("#")) { + // If the current player placed it, it's theirs (no alert) + if (parseResult.getPlayer().equals(player.getName())) { + return null; + } else { + return parseResult.getPlayer(); + } + } + // If we see a break before a placement, stop (block is gone) + if (parseResult.getActionId() == 0) { + break; + } + } + // No valid placement found + return null; + } + + //pet alert(dog and cat only) + @EventHandler + public void onPetKill(EntityDeathEvent event){ + if (!(event.getEntity() instanceof Tameable tameable)) return; + if (!tameable.isTamed()) return; + + //checks if wolf or cat or not + if (!(event.getEntity() instanceof Wolf) && !(event.getEntity()instanceof Cat)) return; + + + AnimalTamer Owner = tameable.getOwner(); + if (Owner == null) return; + if (!(event.getEntity().getKiller() instanceof Player killer)) return; + //if owner kills nothing happens + if (killer.getUniqueId().equals(Owner.getUniqueId())) return; + + int x = event.getEntity().getLocation().getBlockX(); + int y = event.getEntity().getLocation().getBlockY(); + int z = event.getEntity().getLocation().getBlockZ(); + String worldName = event.getEntity().getWorld().getName(); + String pet = event.getEntity().getType().name(); + + String message = ChatColor.GRAY + killer.getName() + " killed" +" " + pet + " "+ "owned by " + Owner.getName() + " at " + x + " " + y + " " + z + getHumanWorldName(worldName); + alert(message, killer.getName(), "[Map Link](" + MAP_LINK + "/?worldname=" + worldName + "&zoom=7&x=" + x + "&y=" + y + "&z=" + z + ")", Owner.getName(), x, y, z, worldName); + + } + //farm animal handler + @EventHandler + public void onFarmAnimalDeath(EntityDeathEvent event){ + if(!(event.getEntity().getKiller() instanceof Player killer)) return; + List alertanimals = getConfig().getStringList("triggered-animals"); + String typeName = event.getEntityType().name(); + + + if(! alertanimals.contains(typeName)) return; + + Block blockBelow = event.getEntity().getLocation().subtract(0, 1, 0).getBlock(); + String animalOwner = inspectBlock(blockBelow, killer); + + if (animalOwner == null) return; + + if (animalOwner.equals(killer.getName())) return; + + + int x = event.getEntity().getLocation().getBlockX(); + int y = event.getEntity().getLocation().getBlockY(); + int z = event.getEntity().getLocation().getBlockZ(); + String worldName = event.getEntity().getWorld().getName(); + + + String message = ChatColor.GRAY + killer.getName() + " killed" +" " + typeName + " "+ "owned by " + animalOwner + " at " + x + " " + y + " " + z + getHumanWorldName(worldName); + + alert(message, killer.getName(), "[Map Link](" + MAP_LINK + "/?worldname=" + worldName + "&zoom=7&x=" + x + "&y=" + y + "&z=" + z + ")", animalOwner, x, y, z, worldName); + + + } + + private static String getHumanWorldName(String worldName) { + String world = ""; + + if (worldName.endsWith("_nether")) { + world = " in the Nether"; + } + else if (worldName.endsWith("_the_end")) { + world = " in the End"; + } + + return world; + } + + private CoreProtectAPI getCoreProtect() { + Plugin plugin = getServer().getPluginManager().getPlugin("CoreProtect"); + + if (plugin == null || !(plugin instanceof CoreProtect)) { + return null; + } + + return ((CoreProtect) plugin).getAPI(); + } +} \ No newline at end of file diff --git a/src/net/ardakaz/griefalert/GriefAlertEvent.java b/src/net/ardakaz/griefalert/GriefAlertEvent.java new file mode 100644 index 0000000..7975e88 --- /dev/null +++ b/src/net/ardakaz/griefalert/GriefAlertEvent.java @@ -0,0 +1,29 @@ +// "API" for other plugins + +package net.ardakaz.griefalert; + +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +public class GriefAlertEvent extends Event { + private static final HandlerList HANDLERS = new HandlerList(); + private String alert = ""; + + public GriefAlertEvent(String alert) { + this.alert = alert; + } + + public static HandlerList getHandlerList() { + return HANDLERS; + } + + @Override + public HandlerList getHandlers() { + return HANDLERS; + } + + public String getAlert() { + return this.alert; + } +} \ No newline at end of file