diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index a217b34..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,7 +0,0 @@ -version: 2 -updates: -- package-ecosystem: maven - directory: "/" - schedule: - interval: daily - open-pull-requests-limit: 10 diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml deleted file mode 100644 index 1c9a468..0000000 --- a/.github/workflows/maven.yml +++ /dev/null @@ -1,26 +0,0 @@ -# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven - -name: Java CI with Maven - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Set up JDK 17 - uses: actions/setup-java@v2 - with: - java-version: '17' - distribution: 'temurin' - cache: maven - - name: Build with Maven - run: mvn -B package --file pom.xml diff --git a/.github/workflows/modrinth-release.yml b/.github/workflows/modrinth-release.yml deleted file mode 100644 index 3f1ce36..0000000 --- a/.github/workflows/modrinth-release.yml +++ /dev/null @@ -1,73 +0,0 @@ -# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -name: Modrinth Release - -on: - workflow_dispatch: - release: - types: - - published -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - java-version: '17' - distribution: 'temurin' - cache: maven - - name: Build with Maven - run: mvn -B package --file pom.xml - - # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive - - name: Update dependency graph - uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6 - - uses: Kir-Antipov/mc-publish@v3.2 - with: - modrinth-id: DnMaciap - modrinth-featured: true - modrinth-token: ${{ secrets.MODRINTH_TOKEN }} - modrinth-dependencies: | - Lu3KuzdV | required | * - - github-discussion: Announcements - github-token: ${{ secrets.GITHUB_TOKEN }} - github-draft: false - - #curseforge-id: 709850 - #curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }} - - files-primary: target/!(*-@(dev|sources|javadoc)).jar - files-secondary: target/*-@(dev|sources|javadoc).jar - loaders: | - spigot - paper - purpur - game-versions: | - 1.15 - 1.15.1 - 1.15.2 - 1.16 - 1.16.1 - 1.16.2 - 1.16.3 - 1.16.4 - 1.16.5 - 1.17 - 1.17.1 - 1.18 - 1.18.1 - 1.18.2 - 1.19 - 1.19.1 - 1.19.2 - java: | - 17 diff --git a/.gitignore b/.gitignore index fd95e32..1fbe46b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,37 @@ +# Compiled class file *.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +# macOS system files +.DS_Store + +# gradle build output files +build/ +.gradle/ + +# eclipse project files .settings/ -target/ -.idea/ -*.iml -.classpath .project -/bin/ -/.idea/ +.classpath +bin/ diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 802d8bc..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/dictionaries/Ghost_chu.xml b/.idea/dictionaries/Ghost_chu.xml deleted file mode 100644 index dcd45e6..0000000 --- a/.idea/dictionaries/Ghost_chu.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 2f6f49e..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/CoreProtectTNT.iml b/CoreProtectTNT.iml deleted file mode 100644 index fa63d4b..0000000 --- a/CoreProtectTNT.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - SPIGOT - - - - - \ No newline at end of file diff --git a/README.md b/README.md index 710050c..7520a22 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,10 @@ -# CoreProtectTNT -A CoreProtect addon allow you log the TNT (or TNTMinecart), Creeper, Ghast, Bed and even ItemFrame breaks! +# CoreProtectTNT-OG -[Modrinth](https://modrinth.com/plugin/coreprotecttnt) +A fork of [CoreProtectTNT](https://modrinth.com/plugin/coreprotecttnt) maintained by [TrueOG Network](https://true-og.net) for 1.19.4. CoreProtect-TNT allows you to log TNT, TNTMinecart, Creeper, Ghast, Bed and even ItemFrame breaks! + +## Changes over CoreProtectTNT: + +- Maven -> Gradle. +- ChatColor -> MiniMessage TextComponent API. +- 1.19.2 -> 1.19.4 API. +- Updated CoreProtect API to v22.4. diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..3483976 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,90 @@ +plugins { + java // Tell gradle this is a java project. + id("io.github.goooler.shadow") version "8.1.8" + eclipse // Import eclipse plugin for IDE integration. + kotlin("jvm") version "1.9.23" // Import kotlin jvm plugin for kotlin/java integration. +} + +java { + // Declare java version. + sourceCompatibility = JavaVersion.VERSION_17 +} + +group = "net.trueog" // Declare bundle identifier. +version = "2.3.1" // Declare plugin version (will be in .jar). +val apiVersion = "1.19" // Declare minecraft server target version. + +tasks.named("processResources") { + val props = mapOf( + "version" to version, + "apiVersion" to apiVersion + ) + + inputs.properties(props) // Indicates to rerun if version changes. + + filesMatching("plugin.yml") { + expand(props) + } +} + +repositories { + mavenCentral() + + maven { + url = uri("https://repo.purpurmc.org/snapshots") + } + + maven { + url = uri("https://maven.playpro.com/") + } // CoreProtect repository. +} + +dependencies { + compileOnly("org.purpurmc.purpur:purpur-api:1.19.4-R0.1-SNAPSHOT") // Declare purpur API version to be packaged. + compileOnly("io.github.miniplaceholders:miniplaceholders-api:2.2.3") // Import MiniPlaceholders API. + + compileOnly("net.coreprotect:coreprotect:22.4") +} + +tasks.withType().configureEach { + isPreserveFileTimestamps = false + isReproducibleFileOrder = true +} + +tasks.shadowJar { + exclude("io.github.miniplaceholders.*") // Exclude the MiniPlaceholders package from being shadowed. + minimize() +} + +tasks.jar { + dependsOn(tasks.shadowJar) + archiveClassifier.set("part") +} + +tasks.shadowJar { + archiveClassifier.set("") // Use empty string instead of null + from("LICENSE") { + into("/") + } +} + +tasks.jar { + dependsOn("shadowJar") +} + +tasks.withType().configureEach { + options.compilerArgs.add("-parameters") + options.encoding = "UTF-8" + options.isFork = true +} + +kotlin { + jvmToolchain(17) +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + vendor = JvmVendorSpec.GRAAL_VM + } +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a441313 --- /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-8.8-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..1aa94a4 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 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. +# + +############################################################################## +# +# 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/subprojects/plugins/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 "${APP_HOME:-./}" > /dev/null && pwd -P ) || 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 + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# 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" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + 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, 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" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# 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 100644 index 0000000..7101f8e --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@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 + +@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 + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +: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/pom.xml b/pom.xml deleted file mode 100644 index 84dbeb7..0000000 --- a/pom.xml +++ /dev/null @@ -1,97 +0,0 @@ - - - 4.0.0 - - com.ghostchu - CoreProtectTNT - 2.3.1 - - - - spigot-repo - https://hub.spigotmc.org/nexus/content/repositories/snapshots/ - - - coreprotect-repo - https://maven.playpro.com/ - - - - - UTF-8 - - - - - org.spigotmc - spigot-api - 1.19.2-R0.1-SNAPSHOT - provided - - - net.coreprotect - coreprotect - 21.2 - provided - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.10.1 - - 17 - 17 - UTF-8 - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.4.0 - - UTF-8 - - - - - attach-javadocs - - jar - - - false - - - - - - - - org.apache.maven.plugins - maven-source-plugin - 3.2.1 - - - attach-sources - - jar - - - - - - - - true - src/main/resources - - - - diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..78bd22e --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "CoreProtectTNT-OG" diff --git a/src/main/java/com/ghostchu/coreprotecttnt/Main.java b/src/main/java/com/ghostchu/coreprotecttnt/Main.java deleted file mode 100644 index e142e56..0000000 --- a/src/main/java/com/ghostchu/coreprotecttnt/Main.java +++ /dev/null @@ -1,543 +0,0 @@ -package com.ghostchu.coreprotecttnt; - -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import net.coreprotect.CoreProtect; -import net.coreprotect.CoreProtectAPI; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.block.data.type.Bed; -import org.bukkit.block.data.type.RespawnAnchor; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.*; -import org.bukkit.entity.minecart.ExplosiveMinecart; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.block.*; -import org.bukkit.event.entity.*; -import org.bukkit.event.hanging.HangingBreakEvent; -import org.bukkit.event.player.PlayerInteractEntityEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.projectiles.ProjectileSource; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -public class Main extends JavaPlugin implements Listener { - private final Cache probablyCache = CacheBuilder - .newBuilder() - .expireAfterAccess(1, TimeUnit.HOURS) - .concurrencyLevel(4) // Sync and Async threads - .maximumSize(50000) // Drop objects if too much, because it will cost expensive lookup. - .recordStats() - .build(); - private CoreProtectAPI api; - - @Override - public void onEnable() { - Bukkit.getPluginManager().registerEvents(this, this); - saveDefaultConfig(); - Plugin depend = Bukkit.getPluginManager().getPlugin("CoreProtect"); - if (depend == null) { - getPluginLoader().disablePlugin(this); - return; - } - api = ((CoreProtect) depend).getAPI(); - } - - // Bed/RespawnAnchor explosion (tracing) - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - public void onPlayerInteractBedOrRespawnAnchorExplosion(PlayerInteractEvent e) { - if (e.getAction() != Action.RIGHT_CLICK_BLOCK) { - return; - } - Block clickedBlock = e.getClickedBlock(); - Location locationHead = clickedBlock.getLocation(); - if (clickedBlock.getBlockData() instanceof Bed bed) { - Location locationFoot = locationHead.clone().subtract(bed.getFacing().getDirection()); - if (bed.getPart() == Bed.Part.FOOT) { - locationHead.add(bed.getFacing().getDirection()); - } - String reason = "#bed-" + e.getPlayer().getName(); - probablyCache.put(locationHead, reason); - probablyCache.put(locationFoot, reason); - } - if (clickedBlock.getBlockData() instanceof RespawnAnchor) { - probablyCache.put(clickedBlock.getLocation(), "#respawnanchor-" + e.getPlayer().getName()); - } - } - - // Creeper ignite (tracing) - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - public void onPlayerInteractCreeper(PlayerInteractEntityEvent e) { - if (!(e.getRightClicked() instanceof Creeper)) { - return; - } - probablyCache.put(e.getRightClicked(), "#ignitecreeper-" + e.getPlayer().getName()); - } - - // Block explode (logger) - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - public void onBlockExplode(BlockExplodeEvent e) { - ConfigurationSection section = Util.bakeConfigSection(getConfig(), "block-explosion"); - if (!section.getBoolean("enable", true)) { - return; - } - Location location = e.getBlock().getLocation(); - String probablyCauses = probablyCache.getIfPresent(e.getBlock()); - if (probablyCauses == null) { - probablyCauses = probablyCache.getIfPresent(location); - } - if (probablyCauses == null) { - if (section.getBoolean("disable-unknown", true)) { - e.blockList().clear(); - Util.broadcastNearPlayers(location, section.getString("alert")); - } - } - // Found causes, let's begin for logging - for (Block block : e.blockList()) { - api.logRemoval(probablyCauses, block.getLocation(), block.getType(), block.getBlockData()); - } - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - public void onBlockPlaceOnHanging(BlockPlaceEvent event) { - // We can't check the hanging in this event, may cause server lagging, just store it - probablyCache.put(event.getBlock().getLocation(), event.getPlayer().getName()); - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - public void onBlockBreak(BlockPlaceEvent event) { - // We can't check the hanging in this event, may cause server lagging, just store it - // Maybe a player break the tnt and a plugin igniting it? - probablyCache.put(event.getBlock().getLocation(), event.getPlayer().getName()); - } - - // Player item put into ItemFrame / Rotate ItemFrame (logger) - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - public void onClickItemFrame(PlayerInteractEntityEvent e) { // Add item to item-frame or rotating - if (!(e.getRightClicked() instanceof ItemFrame itemFrame)) { - return; - } - ConfigurationSection section = Util.bakeConfigSection(getConfig(), "itemframe"); - if (!section.getBoolean("enable", true)) { - return; - } - // Player interacted itemframe - api.logInteraction(e.getPlayer().getName(), e.getRightClicked().getLocation()); - // Check item I/O - if (itemFrame.getItem().getType().isAir()) { // Probably put item now - ItemStack mainItem = e.getPlayer().getInventory().getItemInMainHand(); - ItemStack offItem = e.getPlayer().getInventory().getItemInOffHand(); - ItemStack putIn = mainItem.getType().isAir() ? offItem : mainItem; - if (!putIn.getType().isAir()) { - // Put in item - api.logPlacement("#additem-" + e.getPlayer().getName(), e.getRightClicked().getLocation(), putIn.getType(), null); - return; - } - } - // Probably rotating ItemFrame - api.logRemoval("#rotate-" + e.getPlayer().getName(), e.getRightClicked().getLocation(), itemFrame.getItem().getType(), null); - api.logPlacement("#rotate-" + e.getPlayer().getName(), e.getRightClicked().getLocation(), itemFrame.getItem().getType(), null); - } - - // Any projectile shoot (listener) - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - public void onProjectileLaunch(ProjectileLaunchEvent e) { - if (e.getEntity().getShooter() == null) { - return; - } - ProjectileSource projectileSource = e.getEntity().getShooter(); - String source = ""; - if (!(projectileSource instanceof Player)) { - source += "#"; // We only hope non-player object use hashtag - } - source += e.getEntity().getName() + "-"; - if (projectileSource instanceof Entity entity) { - if (projectileSource instanceof Mob mob && ((Mob) projectileSource).getTarget() != null) { - source += mob.getTarget().getName(); - } else { - source += entity.getName(); - } - } else { - if (projectileSource instanceof Block block) { - source += block.getType().name(); - } else { - source += projectileSource.getClass().getName(); - } - } - probablyCache.put(e.getEntity(), source); - probablyCache.put(projectileSource, source); - } - - // TNT ignites by Player (listener) - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - public void onIgniteTNT(EntitySpawnEvent e) { - Entity tnt = e.getEntity(); - if (!(e.getEntity() instanceof TNTPrimed tntPrimed)) { - return; - } - Entity source = tntPrimed.getSource(); - if (source != null) { - //Bukkit has given the ignition source, track it directly. - String sourceFromCache = probablyCache.getIfPresent(source); - if (sourceFromCache != null) { - probablyCache.put(tnt, sourceFromCache); - } - if (source.getType() == EntityType.PLAYER) { - probablyCache.put(tntPrimed, source.getName()); - return; - } - } - Location blockCorner = tnt.getLocation().clone().subtract(0.5, 0, 0.5); - for (Map.Entry entry : probablyCache.asMap().entrySet()) { - if (entry.getKey() instanceof Location loc) { - if (loc.getWorld().equals(blockCorner.getWorld()) && loc.distance(blockCorner) < 0.5) { - probablyCache.put(tnt, entry.getValue()); - break; - } - } - } - } - - // HangingBreak (logger) - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - public void onHangingBreak(HangingBreakEvent e) { - ConfigurationSection section = Util.bakeConfigSection(getConfig(), "hanging"); - if (!section.getBoolean("enable", true)) { - return; - } - if (e.getCause() == HangingBreakEvent.RemoveCause.PHYSICS || e.getCause() == HangingBreakEvent.RemoveCause.DEFAULT) { - return; // We can't track them tho. - } - - Block hangingPosBlock = e.getEntity().getLocation().getBlock(); - String reason = probablyCache.getIfPresent(hangingPosBlock.getLocation()); - if (reason != null) { - Material mat = Material.matchMaterial(e.getEntity().getType().name()); - if (mat != null) { - api.logRemoval("#" + e.getCause().name() + "-" + reason, hangingPosBlock.getLocation(), Material.matchMaterial(e.getEntity().getType().name()), null); - } else { - api.logInteraction("#" + e.getCause().name() + "-" + reason, hangingPosBlock.getLocation()); - } - } - } - - // EndCrystal rigged by entity (listener) - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onEndCrystalHit(EntityDamageByEntityEvent e) { - if (!(e.getEntity() instanceof EnderCrystal)) { - return; - } - if (e.getDamager() instanceof Player) { - probablyCache.put(e.getEntity(), e.getDamager().getName()); - } else { - String sourceFromCache = probablyCache.getIfPresent(e.getDamager()); - if (sourceFromCache != null) { - probablyCache.put(e.getEntity(), sourceFromCache); - } else if (e.getDamager() instanceof Projectile projectile) { - if (projectile.getShooter() != null && projectile.getShooter() instanceof Player player) { - probablyCache.put(e.getEntity(), player.getName()); - } - } - } - } - - // Haning hit by entity (logger) - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - public void onHangingHit(EntityDamageByEntityEvent e) { - if (!(e.getEntity() instanceof Hanging)) { - return; - } - ConfigurationSection section = Util.bakeConfigSection(getConfig(), "itemframe"); - if (!section.getBoolean("enable", true)) { - return; - } - ItemFrame itemFrame = (ItemFrame) e.getEntity(); - if (itemFrame.getItem().getType().isAir() || itemFrame.isInvulnerable()) { - return; - } - if (e.getDamager() instanceof Player) { - probablyCache.put(e.getEntity(), e.getDamager().getName()); - api.logInteraction(e.getDamager().getName(), itemFrame.getLocation()); - api.logRemoval(e.getDamager().getName(), itemFrame.getLocation(), itemFrame.getItem().getType(), null); - } else { - String cause = probablyCache.getIfPresent(e.getDamager()); - if (cause != null) { - String reason = "#" + e.getDamager().getName() + "-" + cause; - probablyCache.put(e.getEntity(), reason); - api.logRemoval(reason, itemFrame.getLocation(), itemFrame.getItem().getType(), null); - } - } - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - public void onPaintingHit(EntityDamageByEntityEvent e) { - if (!(e.getEntity() instanceof Painting)) { - return; - } - ConfigurationSection section = Util.bakeConfigSection(getConfig(), "painting"); - if (!section.getBoolean("enable", true)) { - return; - } - ItemFrame itemFrame = (ItemFrame) e.getEntity(); - if (itemFrame.getItem().getType().isAir() || itemFrame.isInvulnerable()) { - return; - } - - if (e.getDamager() instanceof Player) { - api.logInteraction(e.getDamager().getName(), itemFrame.getLocation()); - api.logRemoval(e.getDamager().getName(), itemFrame.getLocation(), itemFrame.getItem().getType(), null); - } else { - String reason = probablyCache.getIfPresent(e.getDamager()); - if (reason != null) { - api.logInteraction("#" + e.getDamager().getName() + "-" + reason, itemFrame.getLocation()); - api.logRemoval("#" + e.getDamager().getName() + "-" + reason, itemFrame.getLocation(), itemFrame.getItem().getType(), null); - } else { - if (section.getBoolean("disable-unknown")) { - e.setCancelled(true); - e.setDamage(0.0d); - Util.broadcastNearPlayers(e.getEntity().getLocation(), section.getString("alert")); - } - } - } - } - - - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onEntityHitByProjectile(EntityDamageByEntityEvent e) { - if (e.getDamager() instanceof Projectile projectile) { - if (projectile.getShooter() instanceof Player player) { - probablyCache.put(e.getEntity(), player.getName()); - return; - } - String reason = probablyCache.getIfPresent(e.getDamager()); - if (reason != null) { - probablyCache.put(e.getEntity(), reason); - return; - } - probablyCache.put(e.getEntity(), e.getDamager().getName()); - } - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - public void onBlockIgnite(BlockIgniteEvent e) { - if (e.getIgnitingEntity() != null) { - if (e.getIgnitingEntity().getType() == EntityType.PLAYER) { - probablyCache.put(e.getBlock().getLocation(), e.getPlayer().getName()); - // Don't add it to probablyIgnitedThisTick because it's the simplest case and is logged by Core Protect - return; - } - String sourceFromCache = probablyCache.getIfPresent(e.getIgnitingEntity()); - if (sourceFromCache != null) { - probablyCache.put(e.getBlock().getLocation(), sourceFromCache); - return; - } else if (e.getIgnitingEntity() instanceof Projectile projectile) { - if (((Projectile) e.getIgnitingEntity()).getShooter() != null) { - ProjectileSource shooter = projectile.getShooter(); - if (shooter instanceof Player player) { - probablyCache.put(e.getBlock().getLocation(), player.getName()); - return; - } - } - } - } - if (e.getIgnitingBlock() != null) { - String sourceFromCache = probablyCache.getIfPresent(e.getIgnitingBlock().getLocation()); - if (sourceFromCache != null) { - probablyCache.put(e.getBlock().getLocation(), sourceFromCache); - return; - } - } - ConfigurationSection section = Util.bakeConfigSection(getConfig(), "fire"); - if (!section.getBoolean("enable", true)) { - return; - } - if (section.getBoolean("disable-unknown", true)) { - e.setCancelled(true); - } - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - public void onBlockBurn(BlockBurnEvent e) { - ConfigurationSection section = Util.bakeConfigSection(getConfig(), "fire"); - if (!section.getBoolean("enable", true)) { - return; - } - if (e.getIgnitingBlock() != null) { - String sourceFromCache = probablyCache.getIfPresent(e.getIgnitingBlock().getLocation()); - if (sourceFromCache != null) { - probablyCache.put(e.getBlock().getLocation(), sourceFromCache); - api.logRemoval("#fire-" + probablyCache.getIfPresent(e.getIgnitingBlock().getLocation()), e.getBlock().getLocation(), e.getBlock().getType(), e.getBlock().getBlockData()); - } else if (section.getBoolean("disable-unknown", true)) { - e.setCancelled(true); - Util.broadcastNearPlayers(e.getIgnitingBlock().getLocation(), section.getString("alert")); - } - } - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onBombHit(ProjectileHitEvent e) { - if (e.getHitEntity() instanceof ExplosiveMinecart || e.getEntityType() == EntityType.ENDER_CRYSTAL) { - if (e.getEntity().getShooter() != null && e.getEntity().getShooter() instanceof Player) { - if (e.getHitEntity() != null) { - String sourceFromCache = probablyCache.getIfPresent(e.getEntity()); - if (sourceFromCache != null) { - probablyCache.put(e.getHitEntity(), sourceFromCache); - } else { - if (e.getEntity().getShooter() != null && e.getEntity().getShooter() instanceof Player shooter) { - probablyCache.put(e.getHitEntity(), shooter.getName()); - } - } - } - } - } - } - - - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - public void onExplode(EntityExplodeEvent e) { - Entity entity = e.getEntity(); - List blockList = e.blockList(); - if (blockList.isEmpty()) { - return; - } - List pendingRemoval = new ArrayList<>(); - String entityName = e.getEntityType().name().toLowerCase(Locale.ROOT); - ConfigurationSection section = Util.bakeConfigSection(getConfig(), "entity-explosion"); - if (!section.getBoolean("enable", true)) { - return; - } - String track = probablyCache.getIfPresent(entity); - // TNT or EnderCrystal - if (entity instanceof TNTPrimed || entity instanceof EnderCrystal) { - if (track != null) { - String reason = "#" + entityName + "-" + track; - for (Block block : blockList) { - api.logRemoval(reason, block.getLocation(), block.getType(), block.getBlockData()); - probablyCache.put(block.getLocation(), reason); - } - pendingRemoval.add(entity); - } else { - //Notify players this tnt or end crystal won't break any blocks - if (!section.getBoolean("disable-unknown", true)) { - return; - } - e.blockList().clear(); - e.getEntity().remove(); - Util.broadcastNearPlayers(entity.getLocation(), section.getString("alert")); - } - pendingRemoval.forEach(probablyCache::invalidate); - return; - } - // Creeper... aww man - if (entity instanceof Creeper creeper) { - // New added: Player ignite creeper - if (track != null) { - for (Block block : blockList) { - api.logRemoval(track, block.getLocation(), block.getType(), block.getBlockData()); - } - } else { - LivingEntity creeperTarget = creeper.getTarget(); - if (creeperTarget != null) { - for (Block block : blockList) { - api.logRemoval("#creeper-" + creeperTarget.getName(), block.getLocation(), block.getType(), block.getBlockData()); - probablyCache.put(block.getLocation(), "#creeper-" + creeperTarget.getName()); - } - } else { - //Notify players this creeper won't break any blocks - if (!section.getBoolean("disable-unknown")) { - return; - } - e.blockList().clear(); - e.getEntity().remove(); - Util.broadcastNearPlayers(e.getLocation(), section.getString("alert")); - return; - } - } - return; - } - if (entity instanceof Fireball) { - if (track != null) { - String reason = "#fireball-" + track; - for (Block block : blockList) { - api.logRemoval(reason, block.getLocation(), block.getType(), block.getBlockData()); - probablyCache.put(block.getLocation(), reason); - } - pendingRemoval.add(entity); - } else { - if (section.getBoolean("disable-unknown")) { - e.blockList().clear(); - e.getEntity().remove(); - Util.broadcastNearPlayers(entity.getLocation(), section.getString("alert")); - } - } - pendingRemoval.forEach(probablyCache::invalidate); - return; - } - if (entity instanceof ExplosiveMinecart) { - boolean isLogged = false; - Location blockCorner = entity.getLocation().clone().subtract(0.5, 0, 0.5); - for (Map.Entry entry : probablyCache.asMap().entrySet()) { - if (entry.getKey() instanceof Location loc) { - if (loc.getWorld().equals(blockCorner.getWorld()) && loc.distance(blockCorner) < 1) { - for (Block block : blockList) { - api.logRemoval("#tntminecart-" + entry.getValue(), block.getLocation(), block.getType(), block.getBlockData()); - probablyCache.put(block.getLocation(), "#tntminecart-" + entry.getValue()); - } - isLogged = true; - break; - } - } - } - if (!isLogged) { - if (probablyCache.getIfPresent(entity) != null) { - String reason = "#tntminecart-" + probablyCache.getIfPresent(entity); - for (Block block : blockList) { - api.logRemoval(reason, block.getLocation(), block.getType(), block.getBlockData()); - probablyCache.put(block.getLocation(), reason); - } - pendingRemoval.add(entity); - } else if (section.getBoolean("disable-unknown")) { - e.blockList().clear(); - Util.broadcastNearPlayers(entity.getLocation(), section.getString("alert")); - } - } - pendingRemoval.forEach(probablyCache::invalidate); - return; - } - if (track == null || track.isEmpty()) { - if (e.getEntity() instanceof Mob mob && ((Mob) e.getEntity()).getTarget() != null) { - track = mob.getTarget().getName(); - } - } - // No matches, plugin explode or cannot to track? - if (track == null || track.isEmpty()) { - EntityDamageEvent cause = e.getEntity().getLastDamageCause(); - if (cause != null) { - if (cause instanceof EntityDamageByEntityEvent entityDamageByEntityEvent) { - track = "#" + e.getEntity().getName() + "-" + entityDamageByEntityEvent.getDamager().getName(); - } - } - } - - if (track != null && !track.isEmpty()) { - for (Block block : e.blockList()) { - api.logRemoval(track, block.getLocation(), block.getType(), block.getBlockData()); - } - } else if (section.getBoolean("disable-unknown")) { - e.blockList().clear(); - e.getEntity().remove(); - Util.broadcastNearPlayers(entity.getLocation(), section.getString("alert")); - } - } -} diff --git a/src/main/java/com/ghostchu/coreprotecttnt/Util.java b/src/main/java/com/ghostchu/coreprotecttnt/Util.java deleted file mode 100644 index b8a4070..0000000 --- a/src/main/java/com/ghostchu/coreprotecttnt/Util.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.ghostchu.coreprotecttnt; - -import org.bukkit.ChatColor; -import org.bukkit.Location; -import org.bukkit.configuration.Configuration; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; - -public class Util { - public static void broadcastNearPlayers(Location location, String message) { - if (message == null || message.isEmpty()) { - return; // Do not send empty message - } - String msg = ChatColor.translateAlternateColorCodes('&', message); - for (Entity around : location.getWorld().getNearbyEntities(location, 15, 15, 15, (entity) -> entity instanceof Player)) { - around.sendMessage(msg); - } - - } - - public static ConfigurationSection bakeConfigSection(Configuration configuration, String path) { - ConfigurationSection section = configuration.getConfigurationSection(path); - if (section == null) { - // Create default section with default values - section = configuration.createSection(path); - section.set("enable", true); - section.set("disable-unknown", true); - section.set("alert", ChatColor.RED + "Failed to read translation, configuration section missing!"); - } - return section; - } -} diff --git a/src/main/java/net/trueog/coreprotecttntog/Companion.java b/src/main/java/net/trueog/coreprotecttntog/Companion.java new file mode 100644 index 0000000..29f58bf --- /dev/null +++ b/src/main/java/net/trueog/coreprotecttntog/Companion.java @@ -0,0 +1,32 @@ +package net.trueog.coreprotecttntog; + +import org.bukkit.Location; +import org.bukkit.configuration.Configuration; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; + +public class Companion { + public static void broadcastNearPlayers(Location location, String message) { + if (message == null || message.isEmpty()) { + return; // Do not send empty message + } + for (Entity around : location.getWorld().getNearbyEntities(location, 15, 15, 15, (entity) -> entity instanceof Player)) { + if(around instanceof Player) { + Utils.coreProtectTNTOGMessage((Player) around, message); + } + } + } + + public static ConfigurationSection bakeConfigSection(Configuration configuration, String path) { + ConfigurationSection section = configuration.getConfigurationSection(path); + if (section == null) { + // Create default section with default values + section = configuration.createSection(path); + section.set("enable", true); + section.set("disable-unknown", true); + section.set("alert", "ERROR: Failed to read translation, configuration section missing!"); + } + return section; + } +} \ No newline at end of file diff --git a/src/main/java/net/trueog/coreprotecttntog/CoreProtectTNTOG.java b/src/main/java/net/trueog/coreprotecttntog/CoreProtectTNTOG.java new file mode 100644 index 0000000..48bf774 --- /dev/null +++ b/src/main/java/net/trueog/coreprotecttntog/CoreProtectTNTOG.java @@ -0,0 +1,566 @@ +package net.trueog.coreprotecttntog; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.data.type.Bed; +import org.bukkit.block.data.type.RespawnAnchor; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Creeper; +import org.bukkit.entity.EnderCrystal; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Fireball; +import org.bukkit.entity.Hanging; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Mob; +import org.bukkit.entity.Painting; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.TNTPrimed; +import org.bukkit.entity.minecart.ExplosiveMinecart; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBurnEvent; +import org.bukkit.event.block.BlockExplodeEvent; +import org.bukkit.event.block.BlockIgniteEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.entity.EntitySpawnEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.event.entity.ProjectileLaunchEvent; +import org.bukkit.event.hanging.HangingBreakEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.projectiles.ProjectileSource; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +import net.coreprotect.CoreProtect; +import net.coreprotect.CoreProtectAPI; + +public class CoreProtectTNTOG extends JavaPlugin implements Listener { + private final Cache probablyCache = CacheBuilder + .newBuilder() + .expireAfterAccess(1, TimeUnit.HOURS) + .concurrencyLevel(4) // Sync and Async threads + .maximumSize(50000) // Drop objects if too much, because it will cost expensive lookup. + .recordStats() + .build(); + private CoreProtectAPI api; + + @Override + public void onEnable() { + Bukkit.getPluginManager().registerEvents(this, this); + saveDefaultConfig(); + Plugin depend = Bukkit.getPluginManager().getPlugin("CoreProtect"); + if (depend == null) { + getServer().getPluginManager().disablePlugin(this); + return; + } + api = ((CoreProtect) depend).getAPI(); + } + + // Bed/RespawnAnchor explosion (tracing) + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onPlayerInteractBedOrRespawnAnchorExplosion(PlayerInteractEvent e) { + if (e.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + Block clickedBlock = e.getClickedBlock(); + Location locationHead = clickedBlock.getLocation(); + if (clickedBlock.getBlockData() instanceof Bed bed) { + Location locationFoot = locationHead.clone().subtract(bed.getFacing().getDirection()); + if (bed.getPart() == Bed.Part.FOOT) { + locationHead.add(bed.getFacing().getDirection()); + } + String reason = "#bed-" + e.getPlayer().getName(); + probablyCache.put(locationHead, reason); + probablyCache.put(locationFoot, reason); + } + if (clickedBlock.getBlockData() instanceof RespawnAnchor) { + probablyCache.put(clickedBlock.getLocation(), "#respawnanchor-" + e.getPlayer().getName()); + } + } + + // Creeper ignite (tracing) + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onPlayerInteractCreeper(PlayerInteractEntityEvent e) { + if (!(e.getRightClicked() instanceof Creeper)) { + return; + } + probablyCache.put(e.getRightClicked(), "#ignitecreeper-" + e.getPlayer().getName()); + } + + // Block explode (logger) + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onBlockExplode(BlockExplodeEvent e) { + ConfigurationSection section = Companion.bakeConfigSection(getConfig(), "block-explosion"); + if (!section.getBoolean("enable", true)) { + return; + } + Location location = e.getBlock().getLocation(); + String probablyCauses = probablyCache.getIfPresent(e.getBlock()); + if (probablyCauses == null) { + probablyCauses = probablyCache.getIfPresent(location); + } + if (probablyCauses == null) { + if (section.getBoolean("disable-unknown", true)) { + e.blockList().clear(); + Companion.broadcastNearPlayers(location, section.getString("alert")); + } + } + // Found causes, let's begin for logging + for (Block block : e.blockList()) { + api.logRemoval(probablyCauses, block.getLocation(), block.getType(), block.getBlockData()); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onBlockPlaceOnHanging(BlockPlaceEvent event) { + // We can't check the hanging in this event, may cause server lagging, just store it + probablyCache.put(event.getBlock().getLocation(), event.getPlayer().getName()); + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onBlockBreak(BlockPlaceEvent event) { + // We can't check the hanging in this event, may cause server lagging, just store it + // Maybe a player break the tnt and a plugin igniting it? + probablyCache.put(event.getBlock().getLocation(), event.getPlayer().getName()); + } + + // Player item put into ItemFrame / Rotate ItemFrame (logger) + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onClickItemFrame(PlayerInteractEntityEvent e) { // Add item to item-frame or rotating + if (!(e.getRightClicked() instanceof ItemFrame itemFrame)) { + return; + } + ConfigurationSection section = Companion.bakeConfigSection(getConfig(), "itemframe"); + if (!section.getBoolean("enable", true)) { + return; + } + // Player interacted itemframe + api.logInteraction(e.getPlayer().getName(), e.getRightClicked().getLocation()); + // Check item I/O + if (itemFrame.getItem().getType().isAir()) { // Probably put item now + ItemStack mainItem = e.getPlayer().getInventory().getItemInMainHand(); + ItemStack offItem = e.getPlayer().getInventory().getItemInOffHand(); + ItemStack putIn = mainItem.getType().isAir() ? offItem : mainItem; + if (!putIn.getType().isAir()) { + // Put in item + api.logPlacement("#additem-" + e.getPlayer().getName(), e.getRightClicked().getLocation(), putIn.getType(), null); + return; + } + } + // Probably rotating ItemFrame + api.logRemoval("#rotate-" + e.getPlayer().getName(), e.getRightClicked().getLocation(), itemFrame.getItem().getType(), null); + api.logPlacement("#rotate-" + e.getPlayer().getName(), e.getRightClicked().getLocation(), itemFrame.getItem().getType(), null); + } + + // Any projectile shoot (listener) + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onProjectileLaunch(ProjectileLaunchEvent e) { + if (e.getEntity().getShooter() == null) { + return; + } + ProjectileSource projectileSource = e.getEntity().getShooter(); + String source = ""; + if (!(projectileSource instanceof Player)) { + source += "#"; // We only hope non-player object use hashtag + } + source += e.getEntity().getName() + "-"; + if (projectileSource instanceof Entity entity) { + if (projectileSource instanceof Mob mob && ((Mob) projectileSource).getTarget() != null) { + source += mob.getTarget().getName(); + } else { + source += entity.getName(); + } + } else { + if (projectileSource instanceof Block block) { + source += block.getType().name(); + } else { + source += projectileSource.getClass().getName(); + } + } + probablyCache.put(e.getEntity(), source); + probablyCache.put(projectileSource, source); + } + + // TNT ignites by Player (listener) + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onIgniteTNT(EntitySpawnEvent e) { + Entity tnt = e.getEntity(); + if (!(e.getEntity() instanceof TNTPrimed tntPrimed)) { + return; + } + Entity source = tntPrimed.getSource(); + if (source != null) { + //Bukkit has given the ignition source, track it directly. + String sourceFromCache = probablyCache.getIfPresent(source); + if (sourceFromCache != null) { + probablyCache.put(tnt, sourceFromCache); + } + if (source.getType() == EntityType.PLAYER) { + probablyCache.put(tntPrimed, source.getName()); + return; + } + } + Location blockCorner = tnt.getLocation().clone().subtract(0.5, 0, 0.5); + for (Map.Entry entry : probablyCache.asMap().entrySet()) { + if (entry.getKey() instanceof Location loc) { + if (loc.getWorld().equals(blockCorner.getWorld()) && loc.distance(blockCorner) < 0.5) { + probablyCache.put(tnt, entry.getValue()); + break; + } + } + } + } + + // HangingBreak (logger) + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onHangingBreak(HangingBreakEvent e) { + ConfigurationSection section = Companion.bakeConfigSection(getConfig(), "hanging"); + if (!section.getBoolean("enable", true)) { + return; + } + if (e.getCause() == HangingBreakEvent.RemoveCause.PHYSICS || e.getCause() == HangingBreakEvent.RemoveCause.DEFAULT) { + return; // We can't track them tho. + } + + Block hangingPosBlock = e.getEntity().getLocation().getBlock(); + String reason = probablyCache.getIfPresent(hangingPosBlock.getLocation()); + if (reason != null) { + Material mat = Material.matchMaterial(e.getEntity().getType().name()); + if (mat != null) { + api.logRemoval("#" + e.getCause().name() + "-" + reason, hangingPosBlock.getLocation(), Material.matchMaterial(e.getEntity().getType().name()), null); + } else { + api.logInteraction("#" + e.getCause().name() + "-" + reason, hangingPosBlock.getLocation()); + } + } + } + + // EndCrystal rigged by entity (listener) + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onEndCrystalHit(EntityDamageByEntityEvent e) { + if (!(e.getEntity() instanceof EnderCrystal)) { + return; + } + if (e.getDamager() instanceof Player) { + probablyCache.put(e.getEntity(), e.getDamager().getName()); + } else { + String sourceFromCache = probablyCache.getIfPresent(e.getDamager()); + if (sourceFromCache != null) { + probablyCache.put(e.getEntity(), sourceFromCache); + } else if (e.getDamager() instanceof Projectile projectile) { + if (projectile.getShooter() != null && projectile.getShooter() instanceof Player player) { + probablyCache.put(e.getEntity(), player.getName()); + } + } + } + } + + // Haning hit by entity (logger) + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onHangingHit(EntityDamageByEntityEvent e) { + if (!(e.getEntity() instanceof Hanging)) { + return; + } + ConfigurationSection section = Companion.bakeConfigSection(getConfig(), "itemframe"); + if (!section.getBoolean("enable", true)) { + return; + } + ItemFrame itemFrame = (ItemFrame) e.getEntity(); + if (itemFrame.getItem().getType().isAir() || itemFrame.isInvulnerable()) { + return; + } + if (e.getDamager() instanceof Player) { + probablyCache.put(e.getEntity(), e.getDamager().getName()); + api.logInteraction(e.getDamager().getName(), itemFrame.getLocation()); + api.logRemoval(e.getDamager().getName(), itemFrame.getLocation(), itemFrame.getItem().getType(), null); + } else { + String cause = probablyCache.getIfPresent(e.getDamager()); + if (cause != null) { + String reason = "#" + e.getDamager().getName() + "-" + cause; + probablyCache.put(e.getEntity(), reason); + api.logRemoval(reason, itemFrame.getLocation(), itemFrame.getItem().getType(), null); + } + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onPaintingHit(EntityDamageByEntityEvent e) { + if (!(e.getEntity() instanceof Painting)) { + return; + } + ConfigurationSection section = Companion.bakeConfigSection(getConfig(), "painting"); + if (!section.getBoolean("enable", true)) { + return; + } + ItemFrame itemFrame = (ItemFrame) e.getEntity(); + if (itemFrame.getItem().getType().isAir() || itemFrame.isInvulnerable()) { + return; + } + + if (e.getDamager() instanceof Player) { + api.logInteraction(e.getDamager().getName(), itemFrame.getLocation()); + api.logRemoval(e.getDamager().getName(), itemFrame.getLocation(), itemFrame.getItem().getType(), null); + } else { + String reason = probablyCache.getIfPresent(e.getDamager()); + if (reason != null) { + api.logInteraction("#" + e.getDamager().getName() + "-" + reason, itemFrame.getLocation()); + api.logRemoval("#" + e.getDamager().getName() + "-" + reason, itemFrame.getLocation(), itemFrame.getItem().getType(), null); + } else { + if (section.getBoolean("disable-unknown")) { + e.setCancelled(true); + e.setDamage(0.0d); + Companion.broadcastNearPlayers(e.getEntity().getLocation(), section.getString("alert")); + } + } + } + } + + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onEntityHitByProjectile(EntityDamageByEntityEvent e) { + if (e.getDamager() instanceof Projectile projectile) { + if (projectile.getShooter() instanceof Player player) { + probablyCache.put(e.getEntity(), player.getName()); + return; + } + String reason = probablyCache.getIfPresent(e.getDamager()); + if (reason != null) { + probablyCache.put(e.getEntity(), reason); + return; + } + probablyCache.put(e.getEntity(), e.getDamager().getName()); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onBlockIgnite(BlockIgniteEvent e) { + if (e.getIgnitingEntity() != null) { + if (e.getIgnitingEntity().getType() == EntityType.PLAYER) { + probablyCache.put(e.getBlock().getLocation(), e.getPlayer().getName()); + // Don't add it to probablyIgnitedThisTick because it's the simplest case and is logged by Core Protect + return; + } + String sourceFromCache = probablyCache.getIfPresent(e.getIgnitingEntity()); + if (sourceFromCache != null) { + probablyCache.put(e.getBlock().getLocation(), sourceFromCache); + return; + } else if (e.getIgnitingEntity() instanceof Projectile projectile) { + if (((Projectile) e.getIgnitingEntity()).getShooter() != null) { + ProjectileSource shooter = projectile.getShooter(); + if (shooter instanceof Player player) { + probablyCache.put(e.getBlock().getLocation(), player.getName()); + return; + } + } + } + } + if (e.getIgnitingBlock() != null) { + String sourceFromCache = probablyCache.getIfPresent(e.getIgnitingBlock().getLocation()); + if (sourceFromCache != null) { + probablyCache.put(e.getBlock().getLocation(), sourceFromCache); + return; + } + } + ConfigurationSection section = Companion.bakeConfigSection(getConfig(), "fire"); + if (!section.getBoolean("enable", true)) { + return; + } + if (section.getBoolean("disable-unknown", true)) { + e.setCancelled(true); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onBlockBurn(BlockBurnEvent e) { + ConfigurationSection section = Companion.bakeConfigSection(getConfig(), "fire"); + if (!section.getBoolean("enable", true)) { + return; + } + if (e.getIgnitingBlock() != null) { + String sourceFromCache = probablyCache.getIfPresent(e.getIgnitingBlock().getLocation()); + if (sourceFromCache != null) { + probablyCache.put(e.getBlock().getLocation(), sourceFromCache); + api.logRemoval("#fire-" + probablyCache.getIfPresent(e.getIgnitingBlock().getLocation()), e.getBlock().getLocation(), e.getBlock().getType(), e.getBlock().getBlockData()); + } else if (section.getBoolean("disable-unknown", true)) { + e.setCancelled(true); + Companion.broadcastNearPlayers(e.getIgnitingBlock().getLocation(), section.getString("alert")); + } + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onBombHit(ProjectileHitEvent e) { + if (e.getHitEntity() instanceof ExplosiveMinecart || e.getEntityType() == EntityType.ENDER_CRYSTAL) { + if (e.getEntity().getShooter() != null && e.getEntity().getShooter() instanceof Player) { + if (e.getHitEntity() != null) { + String sourceFromCache = probablyCache.getIfPresent(e.getEntity()); + if (sourceFromCache != null) { + probablyCache.put(e.getHitEntity(), sourceFromCache); + } else { + if (e.getEntity().getShooter() != null && e.getEntity().getShooter() instanceof Player shooter) { + probablyCache.put(e.getHitEntity(), shooter.getName()); + } + } + } + } + } + } + + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onExplode(EntityExplodeEvent e) { + Entity entity = e.getEntity(); + List blockList = e.blockList(); + if (blockList.isEmpty()) { + return; + } + List pendingRemoval = new ArrayList<>(); + String entityName = e.getEntityType().name().toLowerCase(Locale.ROOT); + ConfigurationSection section = Companion.bakeConfigSection(getConfig(), "entity-explosion"); + if (!section.getBoolean("enable", true)) { + return; + } + String track = probablyCache.getIfPresent(entity); + // TNT or EnderCrystal + if (entity instanceof TNTPrimed || entity instanceof EnderCrystal) { + if (track != null) { + String reason = "#" + entityName + "-" + track; + for (Block block : blockList) { + api.logRemoval(reason, block.getLocation(), block.getType(), block.getBlockData()); + probablyCache.put(block.getLocation(), reason); + } + pendingRemoval.add(entity); + } else { + //Notify players this tnt or end crystal won't break any blocks + if (!section.getBoolean("disable-unknown", true)) { + return; + } + e.blockList().clear(); + e.getEntity().remove(); + Companion.broadcastNearPlayers(entity.getLocation(), section.getString("alert")); + } + pendingRemoval.forEach(probablyCache::invalidate); + return; + } + // Creeper... aww man + if (entity instanceof Creeper creeper) { + // New added: Player ignite creeper + if (track != null) { + for (Block block : blockList) { + api.logRemoval(track, block.getLocation(), block.getType(), block.getBlockData()); + } + } else { + LivingEntity creeperTarget = creeper.getTarget(); + if (creeperTarget != null) { + for (Block block : blockList) { + api.logRemoval("#creeper-" + creeperTarget.getName(), block.getLocation(), block.getType(), block.getBlockData()); + probablyCache.put(block.getLocation(), "#creeper-" + creeperTarget.getName()); + } + } else { + //Notify players this creeper won't break any blocks + if (!section.getBoolean("disable-unknown")) { + return; + } + e.blockList().clear(); + e.getEntity().remove(); + Companion.broadcastNearPlayers(e.getLocation(), section.getString("alert")); + return; + } + } + return; + } + if (entity instanceof Fireball) { + if (track != null) { + String reason = "#fireball-" + track; + for (Block block : blockList) { + api.logRemoval(reason, block.getLocation(), block.getType(), block.getBlockData()); + probablyCache.put(block.getLocation(), reason); + } + pendingRemoval.add(entity); + } else { + if (section.getBoolean("disable-unknown")) { + e.blockList().clear(); + e.getEntity().remove(); + Companion.broadcastNearPlayers(entity.getLocation(), section.getString("alert")); + } + } + pendingRemoval.forEach(probablyCache::invalidate); + return; + } + if (entity instanceof ExplosiveMinecart) { + boolean isLogged = false; + Location blockCorner = entity.getLocation().clone().subtract(0.5, 0, 0.5); + for (Map.Entry entry : probablyCache.asMap().entrySet()) { + if (entry.getKey() instanceof Location loc) { + if (loc.getWorld().equals(blockCorner.getWorld()) && loc.distance(blockCorner) < 1) { + for (Block block : blockList) { + api.logRemoval("#tntminecart-" + entry.getValue(), block.getLocation(), block.getType(), block.getBlockData()); + probablyCache.put(block.getLocation(), "#tntminecart-" + entry.getValue()); + } + isLogged = true; + break; + } + } + } + if (!isLogged) { + if (probablyCache.getIfPresent(entity) != null) { + String reason = "#tntminecart-" + probablyCache.getIfPresent(entity); + for (Block block : blockList) { + api.logRemoval(reason, block.getLocation(), block.getType(), block.getBlockData()); + probablyCache.put(block.getLocation(), reason); + } + pendingRemoval.add(entity); + } else if (section.getBoolean("disable-unknown")) { + e.blockList().clear(); + Companion.broadcastNearPlayers(entity.getLocation(), section.getString("alert")); + } + } + pendingRemoval.forEach(probablyCache::invalidate); + return; + } + if (track == null || track.isEmpty()) { + if (e.getEntity() instanceof Mob mob && ((Mob) e.getEntity()).getTarget() != null) { + track = mob.getTarget().getName(); + } + } + // No matches, plugin explode or cannot to track? + if (track == null || track.isEmpty()) { + EntityDamageEvent cause = e.getEntity().getLastDamageCause(); + if (cause != null) { + if (cause instanceof EntityDamageByEntityEvent entityDamageByEntityEvent) { + track = "#" + e.getEntity().getName() + "-" + entityDamageByEntityEvent.getDamager().getName(); + } + } + } + + if (track != null && !track.isEmpty()) { + for (Block block : e.blockList()) { + api.logRemoval(track, block.getLocation(), block.getType(), block.getBlockData()); + } + } else if (section.getBoolean("disable-unknown")) { + e.blockList().clear(); + e.getEntity().remove(); + Companion.broadcastNearPlayers(entity.getLocation(), section.getString("alert")); + } + } +} \ No newline at end of file diff --git a/src/main/java/net/trueog/coreprotecttntog/Utils.java b/src/main/java/net/trueog/coreprotecttntog/Utils.java new file mode 100644 index 0000000..3213e84 --- /dev/null +++ b/src/main/java/net/trueog/coreprotecttntog/Utils.java @@ -0,0 +1,90 @@ +package net.trueog.coreprotecttntog; + +import org.bukkit.entity.Player; + +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +public class Utils { + + // Sends a formatted message to the player (including name replacement). + public static void coreProtectTNTOGMessage(Player p, String message) { + + p.sendMessage(legacySerializerAnyCase((message))); + + } + + public static TextComponent legacySerializerAnyCase(String subject) { + + int count = 0; + // Count the number of '&' characters to determine the size of the array + for (char c : subject.toCharArray()) { + + if (c == '&') { + + count++; + + } + + } + + // Create an array to store the positions of '&' characters + int[] positions = new int[count]; + int index = 0; + // Find the positions of '&' characters and store in the array + for (int i = 0; i < subject.length(); i++) { + + if (subject.charAt(i) == '&') { + + if (isUpperBukkitCode(subject.charAt(i + 1))) { + + subject = replaceCharAtIndex(subject, (i + 1), Character.toLowerCase(subject.charAt(i + 1))); + + } + + positions[index++] = i; + + } + + } + + return LegacyComponentSerializer.legacyAmpersand().deserialize(subject); + + } + + private static boolean isUpperBukkitCode(char input) { + + char[] bukkitColorCodes = {'A', 'B', 'C', 'D', 'E', 'F', 'K', 'L', 'M', 'N', 'O', 'R'}; + boolean match = false; + + // Loop through each character in the array. + for (char c : bukkitColorCodes) { + // Check if the current character in the array is equal to the input character. + if (c == input) { + + match = true; + + } + + } + + return match; + + } + + private static String replaceCharAtIndex(String original, int index, char newChar) { + + // Check if the index is valid + if (index >= 0 && index < original.length()) { + + // Create a new string with the replaced character + return original.substring(0, index) + newChar + original.substring(index + 1); + + } + + // If the index is invalid, return the original string + return original; + + } + +} \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 31dcceb..7b0db6c 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ -name: CoreProtectTNT -main: com.ghostchu.coreprotecttnt.Main +name: CoreProtectTNT-OG +main: net.trueog.coreprotecttntog.Main version: ${version} -api-version: 1.16 +api-version: 1.19 depend: - - CoreProtect \ No newline at end of file + - CoreProtect