From b4c722092b8b80e2062d75d6b794a7a85d04c3db Mon Sep 17 00:00:00 2001 From: Ardakaz Date: Fri, 23 May 2025 22:34:24 +0300 Subject: [PATCH] Initial code --- config.yml | 35 +++ plugin.yml | 7 + src/net/ardakaz/griefalert/GriefAlert.java | 256 ++++++++++++++++++ .../ardakaz/griefalert/GriefAlertEvent.java | 28 ++ 4 files changed, 326 insertions(+) create mode 100755 config.yml create mode 100755 plugin.yml create mode 100755 src/net/ardakaz/griefalert/GriefAlert.java create mode 100755 src/net/ardakaz/griefalert/GriefAlertEvent.java diff --git a/config.yml b/config.yml new file mode 100755 index 0000000..aea2ee6 --- /dev/null +++ b/config.yml @@ -0,0 +1,35 @@ +# GriefAlert v0.2 by Ardakaz + +# Ignore all stealing? +allow-stealing: false + +# Breaking these blocks don't cause alerts. (Material) +excluded-blocks: + - WHEAT + - CARROTS + - POTATOES + - BEETROOTS + - NETHER_WART + - PUMPKIN + - MELON + - SUGAR_CANE + - KELP_PLANT + - KELP + - SWEET_BERRY_BUSH + - COCOA + +# Taking items from these containers cause alerts. (InventoryType) +valid-containers: + - CHEST + - BARREL + - BLAST_FURNACE + - BREWING + - FURNACE + - DISPENSER + - DROPPER + - HOPPER + - SHULKER_BOX + - SMOKER + +# [Optional] Dynmap link for external hooks. +map-link: "" \ No newline at end of file diff --git a/plugin.yml b/plugin.yml new file mode 100755 index 0000000..c819c8b --- /dev/null +++ b/plugin.yml @@ -0,0 +1,7 @@ +name: GriefAlert +main: net.ardakaz.griefalert.GriefAlert +version: 0.2.1 +api-version: 1.21 +depends: [CoreProtect] +description: A simple grief alert plugin using CoreProtect API. +author: Ardakaz diff --git a/src/net/ardakaz/griefalert/GriefAlert.java b/src/net/ardakaz/griefalert/GriefAlert.java new file mode 100755 index 0000000..961c312 --- /dev/null +++ b/src/net/ardakaz/griefalert/GriefAlert.java @@ -0,0 +1,256 @@ +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.Material; +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.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class GriefAlert extends JavaPlugin implements Listener { + + 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; + + // 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"); + + getLogger().info("GriefAlert has been enabled."); + } + + @Override + public void onDisable() { + getLogger().info("GriefAlert has been disabled."); + } + + @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) { + // Alert + 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); + } + } + + // 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) { + // 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); + } else { + // Putting back + 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); + } + } + } + + // Sends the alert (or cancels it) + private void alert(String message, String playerName, String mapLink, String target) { + // 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; + } + + // Send an event for external hooks + GriefAlertEvent griefalert_event; + if (mapLink != null && !mapLink.isEmpty()) { + 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: if the block was placed by another player, returns their name. + private static String inspectBlock(Block block, Player player) { + List lookup = coreProtectAPI.blockLookup(block, 50000000); + if (lookup == null || lookup.size() <= 0) { + // Natural block + return null; + } + + String[] result = lookup.get(0); + ParseResult parseResult = coreProtectAPI.parseResult(result); + if (parseResult.isRolledBack() && lookup.size() != 1) { + result = lookup.get(1); + parseResult = coreProtectAPI.parseResult(result); + } + + if (result == null || parseResult == null || parseResult.getPlayer().startsWith("#") || parseResult.getPlayer().equals(player.getName())) { + // Placed by breaker or natural event + return null; + } + return parseResult.getPlayer(); + + } + + 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 100755 index 0000000..511595c --- /dev/null +++ b/src/net/ardakaz/griefalert/GriefAlertEvent.java @@ -0,0 +1,28 @@ +// "API" for other plugins + +package net.ardakaz.griefalert; + +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