diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f91f646 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,12 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + +# Binary files should be left untouched +*.jar binary + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7fd666a --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.idea +.vscode +.gradle +build +*.iml \ No newline at end of file diff --git a/README.md b/README.md index 5e634f0..adea393 100644 --- a/README.md +++ b/README.md @@ -31,4 +31,13 @@ If you have DiscordSRV installed, you can add this to it's alerts.yml: Channel: grief-alerts Content: "${getAlert()}" ``` -You'll also have to specify the grief-alerts channel in config.yml. \ No newline at end of file +You'll also have to specify the grief-alerts channel in config.yml. + +# Development + + +* Use `./gradlew clean` to clean the project +* Use `./gradlew assemble` to compile the project +* Use `./gradlew test` to test the project +* Use `./gradlew jar` to create the jar +* Use `./gradlew build` to compile, test and create the jar \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..8d32959 --- /dev/null +++ b/build.gradle @@ -0,0 +1,43 @@ +plugins{ + id "java" +} + +group = "net.ardakaz.griefalert" +version = "0.6" + +repositories{ + mavenCentral() + + maven{ + name = "papermc" + url = "https://repo.papermc.io/repository/maven-public/" + } + maven { + name = "core-protect" + url = "https://maven.playpro.com/" + } +} + +dependencies { + compileOnly "io.papermc.paper:paper-api:26.1.2.build.72-stable" + compileOnly "net.coreprotect:coreprotect:23.1" + + testImplementation(platform('org.junit:junit-bom:5.10.5')) + testImplementation('org.junit.jupiter:junit-jupiter') + testRuntimeOnly('org.junit.platform:junit-platform-launcher') +} + + +java{ + toolchain{ + languageVersion = JavaLanguageVersion.of(25) + } +} + +tasks.processResources { + filteringCharset = "UTF-8" +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..5154008 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,7 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties + +org.gradle.configuration-cache=true +org.gradle.parallel=true +org.gradle.caching=true + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..4ac3234 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,2 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f8e1ee3 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..bad7c24 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..adff685 --- /dev/null +++ b/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100755 index 0000000..c4bdd3a --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,93 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..d795db3 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'GriefAlert' diff --git a/src/net/ardakaz/griefalert/GriefAlert.java b/src/main/java/net/ardakaz/griefalert/GriefAlert.java similarity index 97% rename from src/net/ardakaz/griefalert/GriefAlert.java rename to src/main/java/net/ardakaz/griefalert/GriefAlert.java index 9b71803..b3c8a10 100644 --- a/src/net/ardakaz/griefalert/GriefAlert.java +++ b/src/main/java/net/ardakaz/griefalert/GriefAlert.java @@ -1,827 +1,827 @@ -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.entity.*; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.block.Action; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; -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.event.player.PlayerInteractEvent; -import org.bukkit.inventory.EntityEquipment; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.java.JavaPlugin; - -import java.sql.*; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - - -public class GriefAlert extends JavaPlugin implements Listener { - - private static CoreProtectAPI coreProtectAPI; - private final String DB_FILE = "ignored_locations.db"; - 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; - - // 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.isEmpty()) { - // 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; - } - - 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; - } - - // 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 player)) return; - 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) { - 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 armorStand)) return; - if (!(event.getDamager() instanceof Player player)) return; - 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 armorStand)) return; - Player player = event.getPlayer(); - 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; - event.getClickedPosition();// 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(); - boolean handEmpty = 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(); - } else if (!handEmpty && armorEmpty) { - action = "added " + handItem.getType(); - } else if (!handEmpty) { - action = "swapped " + armorItem.getType() + " with " + handItem.getType(); - } else { - action = "interacted with"; - } - String message = ChatColor.GRAY + playerName + " " + action + " on 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); - } - } - - //event handler for item frames - @EventHandler(ignoreCancelled = true) - public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { - if (!(event.getRightClicked() instanceof ItemFrame frame)) return; - - Player player = event.getPlayer(); - 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 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); - - } - } - - // 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 frame)) return; - if (!(event.getDamager() instanceof Player player)) return; - - 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.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 an item frame 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); - } - - @EventHandler(ignoreCancelled = true) - public void onItemFrameBreak(HangingBreakByEntityEvent event) { - if (!(event.getEntity() instanceof ItemFrame frame)) return; - if (!(event.getRemover() instanceof Player player)) return; - - 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 + " 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); - - } - - @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 a 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; - } - - //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 + " in " + Owner.getName() + "'s build 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 + "in " + animalOwner + "'s build 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); - - - } - - /*code i hope for copper waxing/unwaxing theres no proper onwax() or onUnWax() - so im kinda gonna try to just check a situation like, if ur holding an axe, left clicking a copper block - and its waxed, ill assume ur unwaxing, and therefor sending an alert - but some of this code was written at 1am so idk how and why im why and howing */ - - @EventHandler - public void onBlockClick(PlayerInteractEvent event) { - if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return; - - Player player = event.getPlayer(); - Block block = event.getClickedBlock(); - ItemStack item = event.getItem(); - - if (block == null || item == null) return; - - Material before = block.getType(); - - - int x = block.getX(); - int y = block.getY(); - int z = block.getZ(); - String world = block.getWorld().getName(); - - - if (isLocationIgnored(x, y, z, world)) { - return; - } - - boolean isCopper = before.name().contains("COPPER"); - //if aint copper it can no copper and means no copper affected - if (!isCopper) return; - //for waxing logic might not even be needed - //boolean isHoneycomb = item.getType() == Material.HONEYCOMB; - - //for unwax logic and also might not be needed - //boolean isAxe = item.getType().toString().endsWith("_AXE"); - - String target = inspectBlock(block, player); - if (Objects.equals(target, player.getName())) return; - if (target == null) return; - - Bukkit.getScheduler().runTaskLater(this, () -> { - String action; - Material after = block.getType(); - - if (before == after) return; - - boolean wasWaxed = before.name().startsWith("WAXED_"); - boolean isWaxed = after.name().startsWith("WAXED_"); - - if (wasWaxed && !isWaxed) { - action = "Unwaxed"; - } else if (!wasWaxed && isWaxed) { - action = "Waxed"; - } else if (item != null && item.getType().name().endsWith("_AXE")) { - action = "undusted"; - - } else { - action = "error 404 check with kleedje30"; - } - - String message = ChatColor.GRAY + player.getName() + action + " Copper " + " at " + x + " " + y + " " + z + getHumanWorldName(world); - alert(message, player.getName(), "[Map Link](" + MAP_LINK + "/?worldname=" + world + "&zoom=7&x=" + x + "&y=" + y + "&z=" + z + ")", target, x, y, z, world); - - }, 1L); - } - - //stripping logs - @EventHandler - public void onLogStrip(PlayerInteractEvent event) { - if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return; - - Player player = event.getPlayer(); - Block block = event.getClickedBlock(); - ItemStack item = event.getItem(); - - if (block == null || item == null) return; - - Material before = block.getType(); - - int x = block.getX(); - int y = block.getY(); - int z = block.getZ(); - String world = block.getWorld().getName(); - - boolean isWood = before.name().contains("LOG"); - - if (!isWood) return; - - - if (isLocationIgnored(x, y, z, world)) { - return; - } - - String target = inspectBlock(block, player); - if (target == null) return; - - if (target.equals(player.getName())) return; - - Bukkit.getScheduler().runTaskLater(this, () -> { - String action; - Material after = block.getType(); - if (before == after) return; - - if (!before.name().contains("STRIPPED") && after.name().contains("STRIPPED")) { - action = "Stripped"; - } else { - return; - } - - - String message = ChatColor.GRAY + player.getName() + action + " a(n) " + before + " at " + x + " " + y + " " + z + getHumanWorldName(world); - alert(message, player.getName(), "[Map Link](" + MAP_LINK + "/?worldname=" + world + "&zoom=7&x=" + x + "&y=" + y + "&z=" + z + ")", target, x, y, z, world); - - - }, 1L); - } - - private CoreProtectAPI getCoreProtect() { - Plugin plugin = getServer().getPluginManager().getPlugin("CoreProtect"); - - if (!(plugin instanceof CoreProtect)) { - return null; - } - - return ((CoreProtect) plugin).getAPI(); - } +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.entity.*; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; +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.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; + +import java.sql.*; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + + +public class GriefAlert extends JavaPlugin implements Listener { + + private static CoreProtectAPI coreProtectAPI; + private final String DB_FILE = "ignored_locations.db"; + 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; + + // 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.isEmpty()) { + // 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; + } + + 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; + } + + // 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 player)) return; + 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) { + 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 armorStand)) return; + if (!(event.getDamager() instanceof Player player)) return; + 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 armorStand)) return; + Player player = event.getPlayer(); + 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; + event.getClickedPosition();// 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(); + boolean handEmpty = 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(); + } else if (!handEmpty && armorEmpty) { + action = "added " + handItem.getType(); + } else if (!handEmpty) { + action = "swapped " + armorItem.getType() + " with " + handItem.getType(); + } else { + action = "interacted with"; + } + String message = ChatColor.GRAY + playerName + " " + action + " on 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); + } + } + + //event handler for item frames + @EventHandler(ignoreCancelled = true) + public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { + if (!(event.getRightClicked() instanceof ItemFrame frame)) return; + + Player player = event.getPlayer(); + 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 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); + + } + } + + // 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 frame)) return; + if (!(event.getDamager() instanceof Player player)) return; + + 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.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 an item frame 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); + } + + @EventHandler(ignoreCancelled = true) + public void onItemFrameBreak(HangingBreakByEntityEvent event) { + if (!(event.getEntity() instanceof ItemFrame frame)) return; + if (!(event.getRemover() instanceof Player player)) return; + + 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 + " 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); + + } + + @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 a 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; + } + + //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 + " in " + Owner.getName() + "'s build 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 + "in " + animalOwner + "'s build 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); + + + } + + /*code i hope for copper waxing/unwaxing theres no proper onwax() or onUnWax() + so im kinda gonna try to just check a situation like, if ur holding an axe, left clicking a copper block + and its waxed, ill assume ur unwaxing, and therefor sending an alert + but some of this code was written at 1am so idk how and why im why and howing */ + + @EventHandler + public void onBlockClick(PlayerInteractEvent event) { + if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return; + + Player player = event.getPlayer(); + Block block = event.getClickedBlock(); + ItemStack item = event.getItem(); + + if (block == null || item == null) return; + + Material before = block.getType(); + + + int x = block.getX(); + int y = block.getY(); + int z = block.getZ(); + String world = block.getWorld().getName(); + + + if (isLocationIgnored(x, y, z, world)) { + return; + } + + boolean isCopper = before.name().contains("COPPER"); + //if aint copper it can no copper and means no copper affected + if (!isCopper) return; + //for waxing logic might not even be needed + //boolean isHoneycomb = item.getType() == Material.HONEYCOMB; + + //for unwax logic and also might not be needed + //boolean isAxe = item.getType().toString().endsWith("_AXE"); + + String target = inspectBlock(block, player); + if (Objects.equals(target, player.getName())) return; + if (target == null) return; + + Bukkit.getScheduler().runTaskLater(this, () -> { + String action; + Material after = block.getType(); + + if (before == after) return; + + boolean wasWaxed = before.name().startsWith("WAXED_"); + boolean isWaxed = after.name().startsWith("WAXED_"); + + if (wasWaxed && !isWaxed) { + action = "Unwaxed"; + } else if (!wasWaxed && isWaxed) { + action = "Waxed"; + } else if (item != null && item.getType().name().endsWith("_AXE")) { + action = "undusted"; + + } else { + action = "error 404 check with kleedje30"; + } + + String message = ChatColor.GRAY + player.getName() + action + " Copper " + " at " + x + " " + y + " " + z + getHumanWorldName(world); + alert(message, player.getName(), "[Map Link](" + MAP_LINK + "/?worldname=" + world + "&zoom=7&x=" + x + "&y=" + y + "&z=" + z + ")", target, x, y, z, world); + + }, 1L); + } + + //stripping logs + @EventHandler + public void onLogStrip(PlayerInteractEvent event) { + if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return; + + Player player = event.getPlayer(); + Block block = event.getClickedBlock(); + ItemStack item = event.getItem(); + + if (block == null || item == null) return; + + Material before = block.getType(); + + int x = block.getX(); + int y = block.getY(); + int z = block.getZ(); + String world = block.getWorld().getName(); + + boolean isWood = before.name().contains("LOG"); + + if (!isWood) return; + + + if (isLocationIgnored(x, y, z, world)) { + return; + } + + String target = inspectBlock(block, player); + if (target == null) return; + + if (target.equals(player.getName())) return; + + Bukkit.getScheduler().runTaskLater(this, () -> { + String action; + Material after = block.getType(); + if (before == after) return; + + if (!before.name().contains("STRIPPED") && after.name().contains("STRIPPED")) { + action = "Stripped"; + } else { + return; + } + + + String message = ChatColor.GRAY + player.getName() + action + " a(n) " + before + " at " + x + " " + y + " " + z + getHumanWorldName(world); + alert(message, player.getName(), "[Map Link](" + MAP_LINK + "/?worldname=" + world + "&zoom=7&x=" + x + "&y=" + y + "&z=" + z + ")", target, x, y, z, world); + + + }, 1L); + } + + private CoreProtectAPI getCoreProtect() { + Plugin plugin = getServer().getPluginManager().getPlugin("CoreProtect"); + + if (!(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/main/java/net/ardakaz/griefalert/GriefAlertEvent.java similarity index 95% rename from src/net/ardakaz/griefalert/GriefAlertEvent.java rename to src/main/java/net/ardakaz/griefalert/GriefAlertEvent.java index a347edf..e2103ba 100644 --- a/src/net/ardakaz/griefalert/GriefAlertEvent.java +++ b/src/main/java/net/ardakaz/griefalert/GriefAlertEvent.java @@ -1,28 +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; - } +// "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 diff --git a/config.yml b/src/main/resources/config.yml similarity index 100% rename from config.yml rename to src/main/resources/config.yml diff --git a/plugin.yml b/src/main/resources/plugin.yml similarity index 100% rename from plugin.yml rename to src/main/resources/plugin.yml diff --git a/src/test/java/net/ardakaz/griefalert/GriefAlertTest.java b/src/test/java/net/ardakaz/griefalert/GriefAlertTest.java new file mode 100644 index 0000000..29c4f31 --- /dev/null +++ b/src/test/java/net/ardakaz/griefalert/GriefAlertTest.java @@ -0,0 +1,12 @@ +package net.ardakaz.griefalert; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class GriefAlertTest { + @Test + public void placeholderTest() { + assertTrue(false); + } +} \ No newline at end of file