PLEASE FIX GIT
This commit is contained in:
119
.gitignore
vendored
Normal file
119
.gitignore
vendored
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# User-specific stuff
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Compiled class file
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Log file
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# BlueJ files
|
||||||
|
*.ctxt
|
||||||
|
|
||||||
|
# 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*
|
||||||
|
|
||||||
|
*~
|
||||||
|
|
||||||
|
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||||
|
.fuse_hidden*
|
||||||
|
|
||||||
|
# KDE directory preferences
|
||||||
|
.directory
|
||||||
|
|
||||||
|
# Linux trash folder which might appear on any partition or disk
|
||||||
|
.Trash-*
|
||||||
|
|
||||||
|
# .nfs files are created when an open file is removed but is still being accessed
|
||||||
|
.nfs*
|
||||||
|
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
# Windows thumbnail cache files
|
||||||
|
Thumbs.db
|
||||||
|
Thumbs.db:encryptable
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
|
||||||
|
# Dump file
|
||||||
|
*.stackdump
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
[Dd]esktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
.gradle
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Ignore Gradle GUI config
|
||||||
|
gradle-app.setting
|
||||||
|
|
||||||
|
# Cache of project
|
||||||
|
.gradletasknamecache
|
||||||
|
|
||||||
|
**/build/
|
||||||
|
|
||||||
|
# Common working directory
|
||||||
|
run/
|
||||||
|
runs/
|
||||||
|
|
||||||
|
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
||||||
|
!gradle-wrapper.jar
|
||||||
60
build.gradle
Normal file
60
build.gradle
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id("xyz.jpenilla.run-paper") version "2.3.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
group = 'me.trouper'
|
||||||
|
version = '1.0-SNAPSHOT'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
maven {
|
||||||
|
name = "papermc-repo"
|
||||||
|
url = "https://repo.papermc.io/repository/maven-public/"
|
||||||
|
}
|
||||||
|
maven {
|
||||||
|
name = "sonatype"
|
||||||
|
url = "https://oss.sonatype.org/content/groups/public/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly("io.papermc.paper:paper-api:1.21.5-R0.1-SNAPSHOT")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks {
|
||||||
|
runServer {
|
||||||
|
// Configure the Minecraft version for our task.
|
||||||
|
// This is the only required configuration besides applying the plugin.
|
||||||
|
// Your plugin's jar (or shadowJar if present) will be used automatically.
|
||||||
|
minecraftVersion("1.21.5")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def targetJavaVersion = 21
|
||||||
|
java {
|
||||||
|
def javaVersion = JavaVersion.toVersion(targetJavaVersion)
|
||||||
|
sourceCompatibility = javaVersion
|
||||||
|
targetCompatibility = javaVersion
|
||||||
|
if (JavaVersion.current() < javaVersion) {
|
||||||
|
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(JavaCompile).configureEach {
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
|
||||||
|
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) {
|
||||||
|
options.release.set(targetJavaVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processResources {
|
||||||
|
def props = [version: version]
|
||||||
|
inputs.properties props
|
||||||
|
filteringCharset 'UTF-8'
|
||||||
|
filesMatching('plugin.yml') {
|
||||||
|
expand props
|
||||||
|
}
|
||||||
|
}
|
||||||
0
gradle.properties
Normal file
0
gradle.properties
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -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
|
||||||
249
gradlew
vendored
Normal file
249
gradlew
vendored
Normal file
@@ -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/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 "${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" "$@"
|
||||||
92
gradlew.bat
vendored
Normal file
92
gradlew.bat
vendored
Normal file
@@ -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
|
||||||
1
settings.gradle
Normal file
1
settings.gradle
Normal file
@@ -0,0 +1 @@
|
|||||||
|
rootProject.name = 'TrimServer'
|
||||||
54
src/main/java/me/trouper/trimserver/TrimServer.java
Normal file
54
src/main/java/me/trouper/trimserver/TrimServer.java
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package me.trouper.trimserver;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.Manager;
|
||||||
|
import me.trouper.trimserver.utils.visual.BlockDisplayRaytracer;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
public final class TrimServer extends JavaPlugin {
|
||||||
|
|
||||||
|
private static TrimServer instance;
|
||||||
|
private Manager manager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoad() {
|
||||||
|
//getLogger().info("Setting PacketEvents API");
|
||||||
|
//PacketEvents.setAPI(SpigotPacketEventsBuilder.build(this));
|
||||||
|
//
|
||||||
|
//getLogger().info("Loading PacketEvents");
|
||||||
|
//PacketEvents.getAPI().load();
|
||||||
|
|
||||||
|
getLogger().info("Instantiating Plugin");
|
||||||
|
instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
getLogger().info("Instantiating Manager");
|
||||||
|
manager = new Manager();
|
||||||
|
|
||||||
|
getLogger().info("Initializing Manager");
|
||||||
|
manager.init();
|
||||||
|
|
||||||
|
getLogger().info("Successfully enabled TrimServer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable() {
|
||||||
|
BlockDisplayRaytracer.cleanup();
|
||||||
|
getLogger().info("Saved all IO files.");
|
||||||
|
manager.io.saveAll();
|
||||||
|
getLogger().info("Saved all IO files.");
|
||||||
|
//PacketEvents.getAPI().terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TrimServer getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
public NamespacedKey getNameSpace() {
|
||||||
|
return new NamespacedKey(getInstance(),"trim_smp");
|
||||||
|
}
|
||||||
|
public Manager getManager() {
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/main/java/me/trouper/trimserver/data/IO.java
Normal file
37
src/main/java/me/trouper/trimserver/data/IO.java
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package me.trouper.trimserver.data;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.TrimServer;
|
||||||
|
import me.trouper.trimserver.data.io.Config;
|
||||||
|
import me.trouper.trimserver.data.io.Storage;
|
||||||
|
import me.trouper.trimserver.utils.misc.JsonSerializable;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class IO {
|
||||||
|
public final File DATA_FOLDER;
|
||||||
|
public final File CONFIG_FILE;
|
||||||
|
public final File STORAGE_FILE;
|
||||||
|
|
||||||
|
public Config config;
|
||||||
|
public Storage storage;
|
||||||
|
|
||||||
|
public IO() {
|
||||||
|
DATA_FOLDER = new File("plugins/TrimServer");
|
||||||
|
CONFIG_FILE = new File(DATA_FOLDER,"/config.json");
|
||||||
|
STORAGE_FILE = new File(DATA_FOLDER,"/storage.json");
|
||||||
|
config = JsonSerializable.load(CONFIG_FILE,Config.class,new Config());
|
||||||
|
storage = JsonSerializable.load(STORAGE_FILE,Storage.class,new Storage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadAll() {
|
||||||
|
TrimServer.getInstance().getLogger().info("Loading all IO Files");
|
||||||
|
config = JsonSerializable.load(CONFIG_FILE,Config.class,new Config());
|
||||||
|
storage = JsonSerializable.load(STORAGE_FILE,Storage.class,new Storage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveAll() {
|
||||||
|
TrimServer.getInstance().getLogger().info("Saving all IO Files");
|
||||||
|
config.save();
|
||||||
|
storage.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/main/java/me/trouper/trimserver/data/io/Config.java
Normal file
35
src/main/java/me/trouper/trimserver/data/io/Config.java
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package me.trouper.trimserver.data.io;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.TrimServer;
|
||||||
|
import me.trouper.trimserver.utils.misc.JsonSerializable;
|
||||||
|
import me.trouper.trimserver.utils.Verbose;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Config implements JsonSerializable<Config> {
|
||||||
|
|
||||||
|
public boolean debugMode = false;
|
||||||
|
public List<String> debuggerExclusions = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getFile() {
|
||||||
|
return TrimServer.getInstance().getManager().io.CONFIG_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save() {
|
||||||
|
Verbose.send(1,"Saving Config...");
|
||||||
|
JsonSerializable.super.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Messages messages = new Messages();
|
||||||
|
|
||||||
|
public class Messages {
|
||||||
|
public String mainColor = "�ffaa";
|
||||||
|
public String prefix = "&9TrimServer> &7";
|
||||||
|
public String pluginName = "TrimServer";
|
||||||
|
public boolean fancyAlerts = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/main/java/me/trouper/trimserver/data/io/Storage.java
Normal file
30
src/main/java/me/trouper/trimserver/data/io/Storage.java
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package me.trouper.trimserver.data.io;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.TrimServer;
|
||||||
|
import me.trouper.trimserver.utils.misc.JsonSerializable;
|
||||||
|
import me.trouper.trimserver.utils.Verbose;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class Storage implements JsonSerializable<Storage> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getFile() {
|
||||||
|
return TrimServer.getInstance().getManager().io.STORAGE_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save() {
|
||||||
|
Verbose.send(1,"Saving Storage...");
|
||||||
|
JsonSerializable.super.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserData userData = new UserData();
|
||||||
|
|
||||||
|
public class UserData {
|
||||||
|
public Map<String, Set<String>> playerTrust = new HashMap<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/main/java/me/trouper/trimserver/server/Main.java
Normal file
61
src/main/java/me/trouper/trimserver/server/Main.java
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package me.trouper.trimserver.server;
|
||||||
|
|
||||||
|
import io.papermc.paper.registry.RegistryAccess;
|
||||||
|
import me.trouper.trimserver.TrimServer;
|
||||||
|
import me.trouper.trimserver.data.IO;
|
||||||
|
import me.trouper.trimserver.data.io.Config;
|
||||||
|
import me.trouper.trimserver.data.io.Storage;
|
||||||
|
import me.trouper.trimserver.utils.Text;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
|
||||||
|
public interface Main {
|
||||||
|
Main main = new Main() {
|
||||||
|
};
|
||||||
|
|
||||||
|
Random random = new Random();
|
||||||
|
|
||||||
|
default RegistryAccess getRegistryAccess() {
|
||||||
|
return RegistryAccess.registryAccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
default TrimServer getPlugin() {
|
||||||
|
return TrimServer.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
default Manager man() {
|
||||||
|
return getPlugin().getManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
default IO io() {
|
||||||
|
return man().io;
|
||||||
|
};
|
||||||
|
|
||||||
|
default Config config() {
|
||||||
|
return io().config;
|
||||||
|
}
|
||||||
|
|
||||||
|
default Storage storage() {
|
||||||
|
return io().storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
default void info(CommandSender player, String message, Object... args) {
|
||||||
|
Text.sendMessage(Text.Pallet.INFO, player, message, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
default void error(CommandSender player, String message, Object... args) {
|
||||||
|
Text.sendMessage(Text.Pallet.ERROR, player, message, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
default void checkPre(boolean check, String msg, Object... args) {
|
||||||
|
if (!check) {
|
||||||
|
throw new IllegalArgumentException(msg.formatted(args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default void checkPre(BooleanSupplier check, String msg, Object... args) {
|
||||||
|
checkPre(check.getAsBoolean(), msg, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
68
src/main/java/me/trouper/trimserver/server/Manager.java
Normal file
68
src/main/java/me/trouper/trimserver/server/Manager.java
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package me.trouper.trimserver.server;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.data.IO;
|
||||||
|
import me.trouper.trimserver.server.commands.AdminCommand;
|
||||||
|
import me.trouper.trimserver.server.commands.InfoCommand;
|
||||||
|
import me.trouper.trimserver.server.commands.TrustCommand;
|
||||||
|
import me.trouper.trimserver.server.events.JoinEvent;
|
||||||
|
import me.trouper.trimserver.server.events.SwapHandsEvent;
|
||||||
|
import me.trouper.trimserver.server.events.TrustEvents;
|
||||||
|
import me.trouper.trimserver.server.systems.TrustBackend;
|
||||||
|
import me.trouper.trimserver.server.systems.AbilityBackend;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.trims.*;
|
||||||
|
import me.trouper.trimserver.utils.visual.BlockDisplayRaytracer;
|
||||||
|
|
||||||
|
public class Manager {
|
||||||
|
public IO io;
|
||||||
|
public AbilityBackend abilityBackend;
|
||||||
|
public TrustBackend trustBackend;
|
||||||
|
|
||||||
|
public Manager() {
|
||||||
|
io = new IO();
|
||||||
|
abilityBackend = new AbilityBackend();
|
||||||
|
trustBackend = new TrustBackend();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void init() {
|
||||||
|
io.loadAll();
|
||||||
|
|
||||||
|
registerAbilities();
|
||||||
|
registerEvents();
|
||||||
|
registerCommands();
|
||||||
|
BlockDisplayRaytracer.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerCommands() {
|
||||||
|
new AdminCommand().register();
|
||||||
|
new InfoCommand().register();
|
||||||
|
new TrustCommand().register();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerEvents() {
|
||||||
|
new SwapHandsEvent().registerEvents();
|
||||||
|
new JoinEvent().registerEvents();
|
||||||
|
new TrustEvents().registerEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerAbilities() {
|
||||||
|
abilityBackend.registerAbility(new BoltAbility()).registerEvents();
|
||||||
|
abilityBackend.registerAbility(new CoastAbility()).registerEvents();
|
||||||
|
abilityBackend.registerAbility(new DuneAbility()).registerEvents();
|
||||||
|
abilityBackend.registerAbility(new EyeAbility()).registerEvents();
|
||||||
|
abilityBackend.registerAbility(new FlowAbility()).registerEvents();
|
||||||
|
abilityBackend.registerAbility(new HostAbility()).registerEvents();
|
||||||
|
abilityBackend.registerAbility(new RaiserAbility()).registerEvents();
|
||||||
|
abilityBackend.registerAbility(new RibAbility()).registerEvents();
|
||||||
|
abilityBackend.registerAbility(new SentryAbility()).registerEvents();
|
||||||
|
abilityBackend.registerAbility(new ShaperAbility()).registerEvents();
|
||||||
|
abilityBackend.registerAbility(new SilenceAbility()).registerEvents();
|
||||||
|
abilityBackend.registerAbility(new SnoutAbility()).registerEvents();
|
||||||
|
abilityBackend.registerAbility(new SpireAbility()).registerEvents();
|
||||||
|
abilityBackend.registerAbility(new TideAbility()).registerEvents();
|
||||||
|
abilityBackend.registerAbility(new VexAbility()).registerEvents();
|
||||||
|
abilityBackend.registerAbility(new WardAbility()).registerEvents();
|
||||||
|
abilityBackend.registerAbility(new WayfinderAbility()).registerEvents();
|
||||||
|
abilityBackend.registerAbility(new WildAbility()).registerEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
package me.trouper.trimserver.server.commands;
|
||||||
|
|
||||||
|
|
||||||
|
import io.papermc.paper.registry.keys.TrimPatternKeys;
|
||||||
|
import me.trouper.trimserver.server.systems.AbilityBackend;
|
||||||
|
import me.trouper.trimserver.utils.Text;
|
||||||
|
import me.trouper.trimserver.utils.commands.Args;
|
||||||
|
import me.trouper.trimserver.utils.commands.CommandRegistry;
|
||||||
|
import me.trouper.trimserver.utils.commands.Permission;
|
||||||
|
import me.trouper.trimserver.utils.commands.QuickCommand;
|
||||||
|
import me.trouper.trimserver.utils.commands.completions.CompletionBuilder;
|
||||||
|
import me.trouper.trimserver.utils.misc.Randomizer;
|
||||||
|
import me.trouper.trimserver.utils.visual.CustomDisplayRaytracer;
|
||||||
|
import me.trouper.trimserver.utils.visual.DisplayUtils;
|
||||||
|
import net.kyori.adventure.text.format.TextDecoration;
|
||||||
|
import org.bukkit.Color;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.Particle;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.enchantments.Enchantment;
|
||||||
|
import org.bukkit.entity.Display;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.ItemFlag;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.ArmorMeta;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
import org.bukkit.inventory.meta.trim.ArmorTrim;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimMaterial;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimPattern;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
@CommandRegistry(value = "trims",permission = @Permission("trims.admin"),printStackTrace = true)
|
||||||
|
public class AdminCommand implements QuickCommand {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatchCommand(CommandSender commandSender, Command command, String s, Args args) {
|
||||||
|
if (args.getSize() < 1) {
|
||||||
|
error(commandSender,"You must choose an argument.");
|
||||||
|
}
|
||||||
|
switch (args.get(0).toString()) {
|
||||||
|
case "debug" -> handleDebug(commandSender,args);
|
||||||
|
case "reload" -> handleReload(commandSender,args);
|
||||||
|
case "try" -> handleTry(commandSender,args);
|
||||||
|
case "test" -> handleTest(commandSender,args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatchCompletions(CommandSender commandSender, Command command, String s, CompletionBuilder b) {
|
||||||
|
b.then(
|
||||||
|
b.arg("reload")
|
||||||
|
).then(
|
||||||
|
b.arg("debug")
|
||||||
|
.then(
|
||||||
|
b.arg("toggle")
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
b.arg("exclude")
|
||||||
|
.then(
|
||||||
|
b.arg("Class.method")))
|
||||||
|
.then(
|
||||||
|
b.arg("include")
|
||||||
|
.then(
|
||||||
|
b.arg(main.config().debuggerExclusions)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).then(
|
||||||
|
b.arg("try")
|
||||||
|
.then(
|
||||||
|
b.argEnum(AbilityBackend.ValidPattern.class)
|
||||||
|
.then(
|
||||||
|
b.argEnum(
|
||||||
|
AbilityBackend.ValidMaterial.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).then(
|
||||||
|
b.arg("test")
|
||||||
|
.then(
|
||||||
|
b.arg("vector")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleTest(CommandSender commandSender, Args args) {
|
||||||
|
switch (args.get(1).toString()) {
|
||||||
|
case "vector" -> {
|
||||||
|
if (!(commandSender instanceof Player p)) return;
|
||||||
|
CustomDisplayRaytracer.traceWithReflection(p.getEyeLocation(),p.getEyeLocation().getDirection(),60,0.1,6,point -> {
|
||||||
|
DisplayUtils.DUST_PARTICLE_FACTORY.apply(Color.AQUA,1F).accept(point.getLoc());
|
||||||
|
|
||||||
|
return CustomDisplayRaytracer.hitBlockIf(block -> !block.isPassable()).test(point);
|
||||||
|
},(point,block) -> {
|
||||||
|
DisplayUtils.sphere(point.getLoc(),0.3,0.1,0.1,loc -> {
|
||||||
|
DisplayUtils.DUST_PARTICLE_FACTORY.apply(Color.RED,1F).accept(loc);
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},(point,entity) -> {
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleTry(CommandSender sender, Args args) {
|
||||||
|
if (args.getSize() < 2) {
|
||||||
|
error(sender, "Usage: /trims try <trim> [material]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!(sender instanceof Player p)) {
|
||||||
|
error(sender,"Only players can use this sub-command.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AbilityBackend.ValidPattern validPattern = args.get(1).toEnum(AbilityBackend.ValidPattern.class);
|
||||||
|
AbilityBackend.ValidMaterial validMaterial = new Randomizer().getRandomElement(AbilityBackend.ValidMaterial.values());
|
||||||
|
if (args.getSize() >= 3) validMaterial = args.get(2).toEnum(AbilityBackend.ValidMaterial.class);
|
||||||
|
|
||||||
|
TrimPattern pattern = validPattern.getCanonical();
|
||||||
|
TrimMaterial material = validMaterial.getCanonical();
|
||||||
|
|
||||||
|
if (material == null) material = new Randomizer().getRandomElement(AbilityBackend.ValidMaterial.values()).getCanonical();
|
||||||
|
|
||||||
|
p.getEquipment().setHelmet(createTestArmor(new ItemStack(Material.NETHERITE_HELMET),pattern,material));
|
||||||
|
p.getEquipment().setChestplate(createTestArmor(new ItemStack(Material.NETHERITE_CHESTPLATE),pattern,material));
|
||||||
|
p.getEquipment().setLeggings(createTestArmor(new ItemStack(Material.NETHERITE_LEGGINGS),pattern,material));
|
||||||
|
p.getEquipment().setBoots(createTestArmor(new ItemStack(Material.NETHERITE_BOOTS),pattern,material));
|
||||||
|
|
||||||
|
info(sender,"You now are wearing {0} with {1} {2} trim.", "Netherite",Text.formatEnum(validMaterial),Text.formatEnum(validPattern));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack createTestArmor(ItemStack piece, TrimPattern trimPattern, TrimMaterial trimMaterial) {
|
||||||
|
ItemMeta meta = piece.getItemMeta();
|
||||||
|
if (!(meta instanceof ArmorMeta armor)) {
|
||||||
|
throw new IllegalArgumentException("You must input armor ONLY");
|
||||||
|
}
|
||||||
|
|
||||||
|
armor.customName(Text.color("&eTesting Armor").decoration(TextDecoration.ITALIC,false));
|
||||||
|
armor.lore(List.of(
|
||||||
|
Text.color("&8&l| &7%s Trim".formatted(Text.formatEnum(main.man().abilityBackend.getValidPattern(trimPattern)))).decoration(TextDecoration.ITALIC,false),
|
||||||
|
Text.color("&8&l| &7%s".formatted(Text.formatEnum(main.man().abilityBackend.getValidMaterial(trimMaterial)))).decoration(TextDecoration.ITALIC,false),
|
||||||
|
Text.color("&8&l| &7Won't Break").decoration(TextDecoration.ITALIC,false),
|
||||||
|
Text.color("&8&l| &7Vanishes on death").decoration(TextDecoration.ITALIC,false),
|
||||||
|
Text.color("&8&l| &7This armor is for testing purposes &c&lONLY&7!").decoration(TextDecoration.ITALIC,false)
|
||||||
|
));
|
||||||
|
armor.addEnchant(Enchantment.VANISHING_CURSE,1,true);
|
||||||
|
armor.setUnbreakable(true);
|
||||||
|
armor.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||||
|
armor.addItemFlags(ItemFlag.HIDE_UNBREAKABLE);
|
||||||
|
armor.addItemFlags(ItemFlag.HIDE_ARMOR_TRIM);
|
||||||
|
|
||||||
|
armor.setTrim(new ArmorTrim(trimMaterial,trimPattern));
|
||||||
|
|
||||||
|
piece.setItemMeta(armor);
|
||||||
|
return piece;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleDebug(CommandSender sender, Args args) {
|
||||||
|
if (args.getSize() < 2) {
|
||||||
|
error(sender, "Usage: /trims debug <toggle|include|exclude>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String sub = args.get(1).toString();
|
||||||
|
|
||||||
|
switch (sub) {
|
||||||
|
case "toggle" -> {
|
||||||
|
boolean result = false;
|
||||||
|
main.config().debugMode = result = !main.config().debugMode;
|
||||||
|
main.config().save();
|
||||||
|
|
||||||
|
Text.sendMessage(Text.Pallet.SUCCESS,sender,"Toggled debug mode {0}.",result ? "on" : "off");
|
||||||
|
}
|
||||||
|
case "exclude" -> {
|
||||||
|
if (args.getSize() < 3) {
|
||||||
|
error(sender, "Usage: /trims debug exclude <method>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String exclusion = args.get(2).toString();
|
||||||
|
main.config().debuggerExclusions.add(exclusion);
|
||||||
|
main.config().save();
|
||||||
|
|
||||||
|
Text.sendMessage(Text.Pallet.SUCCESS, sender, "Excluded {0} from the debugger.", exclusion);
|
||||||
|
}
|
||||||
|
case "include" -> {
|
||||||
|
if (args.getSize() < 3) {
|
||||||
|
error(sender, "Usage: /trims debug include <method>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String exclusion = args.get(2).toString();
|
||||||
|
main.config().debuggerExclusions.remove(exclusion);
|
||||||
|
main.config().save();
|
||||||
|
|
||||||
|
Text.sendMessage(Text.Pallet.SUCCESS, sender, "Removed exclusion for {0} on the debugger.", exclusion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleReload(CommandSender sender, Args args) {
|
||||||
|
Text.sendMessage(Text.Pallet.NEUTRAL,sender,"Reloading IO...");
|
||||||
|
main.man().io.loadAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package me.trouper.trimserver.server.commands;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.TrimServer;
|
||||||
|
import me.trouper.trimserver.utils.Text;
|
||||||
|
import me.trouper.trimserver.utils.commands.Args;
|
||||||
|
import me.trouper.trimserver.utils.commands.CommandRegistry;
|
||||||
|
import me.trouper.trimserver.utils.commands.QuickCommand;
|
||||||
|
import me.trouper.trimserver.utils.commands.completions.CompletionBuilder;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimPattern;
|
||||||
|
|
||||||
|
@CommandRegistry(value = "info")
|
||||||
|
public class InfoCommand implements QuickCommand {
|
||||||
|
@Override
|
||||||
|
public void dispatchCommand(CommandSender commandSender, Command command, String s, Args args) {
|
||||||
|
if (args.getSize() < 1) {
|
||||||
|
commandSender.sendMessage("You must provide a trim to get information on.");
|
||||||
|
}
|
||||||
|
String query = args.get(0).toString();
|
||||||
|
commandSender.sendMessage(
|
||||||
|
Text.color(Text.formatArgs(Text.Pallet.INFO,"-------- Info for {0} trim. --------",query)).appendNewline()
|
||||||
|
.append(main.man().abilityBackend.formatAbilityInfo(main.man().abilityBackend.getTrimPattern(query))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatchCompletions(CommandSender commandSender, Command command, String s, CompletionBuilder b) {
|
||||||
|
b.then(b.arg(TrimServer.getInstance().getManager().abilityBackend.abilities()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package me.trouper.trimserver.server.commands;
|
||||||
|
|
||||||
|
|
||||||
|
import me.trouper.trimserver.utils.Text;
|
||||||
|
import me.trouper.trimserver.utils.commands.Args;
|
||||||
|
import me.trouper.trimserver.utils.commands.CommandRegistry;
|
||||||
|
import me.trouper.trimserver.utils.commands.QuickCommand;
|
||||||
|
import me.trouper.trimserver.utils.commands.completions.CompletionBuilder;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@CommandRegistry(value = "trust", playersOnly = true, printStackTrace = true)
|
||||||
|
public class TrustCommand implements QuickCommand {
|
||||||
|
@Override
|
||||||
|
public void dispatchCommand(CommandSender sender, Command command, String label, Args args) {
|
||||||
|
if (args.getSize() < 1) {
|
||||||
|
Text.sendError(sender, "Error: Valid sub-commands are: [add, remove, list]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String sub = args.get(0).toString();
|
||||||
|
|
||||||
|
switch (sub) {
|
||||||
|
case "add" -> {
|
||||||
|
if (args.getSize() < 2) {
|
||||||
|
Text.sendError(sender, "Usage: /trust add <player>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String target = args.get(1).toString();
|
||||||
|
final OfflinePlayer trustee = Bukkit.getOfflinePlayer(target);
|
||||||
|
|
||||||
|
if (trustee == null) {
|
||||||
|
Text.sendError(sender, "Player not found online or offline.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (main.man().trustBackend.addTrust((Player) sender,trustee.getUniqueId())) {
|
||||||
|
Text.sendMessage(Text.Pallet.SUCCESS,sender,"Successfully trusted {0}.",target);
|
||||||
|
if (trustee.isOnline())Text.sendMessage(Text.Pallet.SUCCESS,(Player) trustee,"Successfully trusted {0}.",sender.getName());
|
||||||
|
} else {
|
||||||
|
Text.sendMessage(Text.Pallet.NEUTRAL,sender,"You already have {0} trusted.",target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "remove" -> {
|
||||||
|
if (args.getSize() < 2) {
|
||||||
|
Text.sendError(sender, "Usage: /trust remove <player>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String target = args.get(1).toString();
|
||||||
|
final OfflinePlayer trustee = Bukkit.getOfflinePlayer(target);
|
||||||
|
|
||||||
|
if (trustee == null) {
|
||||||
|
Text.sendError(sender, "Player not found online or offline.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (trustee.getUniqueId().equals(((Player) sender).getUniqueId())) {
|
||||||
|
Text.sendError(sender, "You do not want to un-trust yourself. It will break the code ;-;");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (main.man().trustBackend.removeTrust((Player) sender,trustee.getUniqueId())) {
|
||||||
|
Text.sendMessage(Text.Pallet.SUCCESS,sender,"Successfully un-trusted {0}.",target);
|
||||||
|
if (trustee.isOnline())Text.sendMessage(Text.Pallet.SUCCESS,(Player) trustee,"{0} has un-trusted you.",sender.getName());
|
||||||
|
} else {
|
||||||
|
Text.sendMessage(Text.Pallet.NEUTRAL,sender,"You do not have {0} trusted.",target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "list" -> {
|
||||||
|
final Set<String> trustees = main.man().trustBackend.getTrustees((Player) sender);
|
||||||
|
Set<String> names = new HashSet<>();
|
||||||
|
for (String trustee : trustees) {
|
||||||
|
names.add(Bukkit.getOfflinePlayer(UUID.fromString(trustee)).getName());
|
||||||
|
}
|
||||||
|
Text.sendMessage(Text.Pallet.NEUTRAL,sender,"You currently have {0} players trusted: {1}",trustees.size(), Arrays.toString(names.toArray()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatchCompletions(CommandSender commandSender, Command command, String s, CompletionBuilder b) {
|
||||||
|
b.then(
|
||||||
|
b.arg("add","remove")
|
||||||
|
.then(b.argOnlinePlayers())
|
||||||
|
).then(
|
||||||
|
b.arg("list")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package me.trouper.trimserver.server.events;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.Main;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
|
||||||
|
public class JoinEvent implements QuickListener, Main {
|
||||||
|
@EventHandler
|
||||||
|
public void initPlayer(PlayerJoinEvent e) {
|
||||||
|
main.man().trustBackend.initPlayer(e.getPlayer());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package me.trouper.trimserver.server.events;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.Main;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
|
||||||
|
public interface QuickListener extends Listener, Main {
|
||||||
|
default QuickListener registerEvents() {
|
||||||
|
Bukkit.getPluginManager().registerEvents(this, this.getPlugin());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package me.trouper.trimserver.server.events;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.Main;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.player.PlayerSwapHandItemsEvent;
|
||||||
|
|
||||||
|
public class SwapHandsEvent implements QuickListener, Main {
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onSwap(PlayerSwapHandItemsEvent e) {
|
||||||
|
Player p = e.getPlayer();
|
||||||
|
if (!main.man().abilityBackend.checkAndTriggerAbility(p)) return;
|
||||||
|
e.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package me.trouper.trimserver.server.events;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||||
|
|
||||||
|
public class TrustEvents implements QuickListener {
|
||||||
|
@EventHandler
|
||||||
|
public void onHit(EntityDamageByEntityEvent e) {
|
||||||
|
if (!(e.getDamager() instanceof Player a)) return;
|
||||||
|
if (!(e.getEntity() instanceof Player v)) return;
|
||||||
|
if (!main.man().trustBackend.trusts(a,v)) return;
|
||||||
|
|
||||||
|
e.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,370 @@
|
|||||||
|
package me.trouper.trimserver.server.systems;
|
||||||
|
|
||||||
|
import io.papermc.paper.registry.RegistryAccess;
|
||||||
|
import io.papermc.paper.registry.RegistryKey;
|
||||||
|
import me.trouper.trimserver.server.Main;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.MaterialInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.AbstractAbility;
|
||||||
|
import me.trouper.trimserver.utils.Text;
|
||||||
|
import me.trouper.trimserver.utils.misc.Cooldown;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.PlayerInventory;
|
||||||
|
import org.bukkit.inventory.meta.ArmorMeta;
|
||||||
|
import org.bukkit.inventory.meta.trim.ArmorTrim;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimMaterial;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimPattern;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public class AbilityBackend implements Main {
|
||||||
|
// Map to store registered abilities by their pattern
|
||||||
|
public final Map<TrimPattern, AbstractAbility> registeredAbilities = new HashMap<>();
|
||||||
|
|
||||||
|
// Record for cooldown tracking key (Player UUID, Pattern, Material)
|
||||||
|
private record AbilityCooldown(UUID who, TrimPattern pattern, TrimMaterial material) {};
|
||||||
|
|
||||||
|
// Cooldown manager instance
|
||||||
|
private final Cooldown<AbilityCooldown> onCooldown = new Cooldown<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of registered ability pattern names as strings.
|
||||||
|
* @return a list containing the string keys of the registered trim patterns.
|
||||||
|
*/
|
||||||
|
public final List<String> abilities() {
|
||||||
|
List<String> map = new ArrayList<>();
|
||||||
|
RegistryAccess access = RegistryAccess.registryAccess();
|
||||||
|
registeredAbilities.forEach((trim,ability)->{
|
||||||
|
map.add(access.getRegistry(RegistryKey.TRIM_PATTERN).getKey(trim).getKey());
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a TrimAbility instance.
|
||||||
|
* Logs a warning if a pattern is already registered.
|
||||||
|
*
|
||||||
|
* @param ability The TrimAbility instance to register.
|
||||||
|
*/
|
||||||
|
public AbstractAbility registerAbility(AbstractAbility ability) {
|
||||||
|
if (ability == null) {
|
||||||
|
main.getPlugin().getLogger().warning("Attempted to register a null TrimAbility.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
TrimPattern pattern = ability.getPattern();
|
||||||
|
if (registeredAbilities.containsKey(pattern)) {
|
||||||
|
main.getPlugin().getLogger().warning("TrimAbility for pattern " + main.getRegistryAccess().getRegistry(RegistryKey.TRIM_PATTERN).getKey(pattern).getKey() + " is already registered. Overwriting.");
|
||||||
|
}
|
||||||
|
registeredAbilities.put(pattern, ability);
|
||||||
|
|
||||||
|
getPlugin().getLogger().info("Registered TrimAbility for pattern: " + ability.getPatternName());
|
||||||
|
|
||||||
|
return ability;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the registered TrimAbility for a given pattern, if any.
|
||||||
|
*
|
||||||
|
* @param pattern The pattern to look up.
|
||||||
|
* @return The TrimAbility instance, or null if none is registered.
|
||||||
|
*/
|
||||||
|
public AbstractAbility getAbility(TrimPattern pattern) {
|
||||||
|
return registeredAbilities.get(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the TrimAbility for a player based on their full armor set's trim.
|
||||||
|
* Checks if the player is wearing a full set with a matching trim pattern and material.
|
||||||
|
*
|
||||||
|
* @param player The player to check
|
||||||
|
* @return The TrimAbility instance corresponding to the player's trim, or null if
|
||||||
|
* they don't have a valid full set trim or the pattern isn't registered.
|
||||||
|
*/
|
||||||
|
public AbstractAbility getAbility(Player player) {
|
||||||
|
PlayerInventory inventory = player.getInventory();
|
||||||
|
ItemStack helmet = inventory.getHelmet();
|
||||||
|
ItemStack chestplate = inventory.getChestplate();
|
||||||
|
ItemStack leggings = inventory.getLeggings();
|
||||||
|
ItemStack boots = inventory.getBoots();
|
||||||
|
|
||||||
|
// 1. Check if all armor slots are filled with *some* armor
|
||||||
|
if (helmet == null || helmet.getType() == Material.AIR ||
|
||||||
|
chestplate == null || chestplate.getType() == Material.AIR ||
|
||||||
|
leggings == null || leggings.getType() == Material.AIR ||
|
||||||
|
boots == null || boots.getType() == Material.AIR) {
|
||||||
|
return null; // Not wearing a full set
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack[] armorPieces = {helmet, chestplate, leggings, boots};
|
||||||
|
TrimPattern requiredPattern = null;
|
||||||
|
TrimMaterial requiredMaterial = null;
|
||||||
|
|
||||||
|
// 2. Iterate through armor pieces to check for matching trims
|
||||||
|
for (int i = 0; i < armorPieces.length; i++) {
|
||||||
|
ItemStack piece = armorPieces[i];
|
||||||
|
if (!(piece.getItemMeta() instanceof ArmorMeta meta)) {
|
||||||
|
return null; // Should have ArmorMeta if it's armor
|
||||||
|
}
|
||||||
|
|
||||||
|
ArmorTrim trim = meta.getTrim();
|
||||||
|
|
||||||
|
if (trim == null) {
|
||||||
|
return null; // Needs a trim
|
||||||
|
}
|
||||||
|
|
||||||
|
TrimPattern currentPattern = trim.getPattern();
|
||||||
|
TrimMaterial currentMaterial = trim.getMaterial();
|
||||||
|
|
||||||
|
if (i == 0) {
|
||||||
|
// First piece sets the requirement
|
||||||
|
requiredPattern = currentPattern;
|
||||||
|
requiredMaterial = currentMaterial;
|
||||||
|
} else {
|
||||||
|
// Subsequent pieces must match the first one
|
||||||
|
if (!currentPattern.equals(requiredPattern) || !currentMaterial.equals(requiredMaterial)) {
|
||||||
|
return null; // Mismatch found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. If we reach here, all pieces match! Return the ability if registered.
|
||||||
|
if (requiredPattern != null && requiredMaterial != null) {
|
||||||
|
return registeredAbilities.get(requiredPattern);
|
||||||
|
}
|
||||||
|
return null; // Should technically not be reached if logic is sound
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the player's equipped armor. If they are wearing a full set
|
||||||
|
* with the same trim pattern and material, it finds the corresponding
|
||||||
|
* registered TrimAbility and calls its appropriate material-specific method,
|
||||||
|
* provided the cooldown has expired.
|
||||||
|
*
|
||||||
|
* @param player The player whose armor should be checked.
|
||||||
|
* @return true if an ability was successfully triggered, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean checkAndTriggerAbility(Player player) {
|
||||||
|
PlayerInventory inventory = player.getInventory();
|
||||||
|
ItemStack helmet = inventory.getHelmet();
|
||||||
|
ItemStack chestplate = inventory.getChestplate();
|
||||||
|
ItemStack leggings = inventory.getLeggings();
|
||||||
|
ItemStack boots = inventory.getBoots();
|
||||||
|
|
||||||
|
// 1. Check if all armor slots are filled with *some* armor
|
||||||
|
if (helmet == null || helmet.getType() == Material.AIR ||
|
||||||
|
chestplate == null || chestplate.getType() == Material.AIR ||
|
||||||
|
leggings == null || leggings.getType() == Material.AIR ||
|
||||||
|
boots == null || boots.getType() == Material.AIR) {
|
||||||
|
return false; // Not wearing a full set
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack[] armorPieces = {helmet, chestplate, leggings, boots};
|
||||||
|
TrimPattern requiredPattern = null;
|
||||||
|
TrimMaterial requiredMaterial = null;
|
||||||
|
|
||||||
|
// 2. Iterate through armor pieces to check for matching trims
|
||||||
|
for (int i = 0; i < armorPieces.length; i++) {
|
||||||
|
ItemStack piece = armorPieces[i];
|
||||||
|
if (!(piece.getItemMeta() instanceof ArmorMeta meta)) {
|
||||||
|
return false; // Should have ArmorMeta if it's armor
|
||||||
|
}
|
||||||
|
|
||||||
|
ArmorTrim trim = meta.getTrim();
|
||||||
|
|
||||||
|
if (trim == null) {
|
||||||
|
return false; // Needs a trim
|
||||||
|
}
|
||||||
|
|
||||||
|
TrimPattern currentPattern = trim.getPattern();
|
||||||
|
TrimMaterial currentMaterial = trim.getMaterial();
|
||||||
|
|
||||||
|
if (i == 0) {
|
||||||
|
// First piece sets the requirement
|
||||||
|
requiredPattern = currentPattern;
|
||||||
|
requiredMaterial = currentMaterial;
|
||||||
|
} else {
|
||||||
|
// Subsequent pieces must match the first one
|
||||||
|
if (!currentPattern.equals(requiredPattern) || !currentMaterial.equals(requiredMaterial)) {
|
||||||
|
return false; // Mismatch found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. If we reach here, all pieces match! Find and trigger the ability.
|
||||||
|
if (requiredPattern != null && requiredMaterial != null) {
|
||||||
|
AbstractAbility ability = registeredAbilities.get(requiredPattern);
|
||||||
|
if (ability != null) {
|
||||||
|
// Get ability info for cooldown using the ability instance's method
|
||||||
|
MaterialInfo materialInfo = ability.getAbilityInfo(requiredMaterial);
|
||||||
|
|
||||||
|
// If there's no info or cooldown is 0, proceed immediately
|
||||||
|
int cooldownTicks = (materialInfo != null) ? materialInfo.cooldownTicks() : 0;
|
||||||
|
|
||||||
|
AbilityCooldown cooldownKey = new AbilityCooldown(player.getUniqueId(), requiredPattern, requiredMaterial);
|
||||||
|
|
||||||
|
if (onCooldown.isOnCooldown(cooldownKey)) {
|
||||||
|
// Calculate remaining time for progress bar if needed
|
||||||
|
long remainingMillis = onCooldown.getCooldown(cooldownKey);
|
||||||
|
// Example progress bar logic - adjust duration calculation as needed
|
||||||
|
long totalDurationMillis = (long) cooldownTicks * 50L; // Assuming 1 tick = 50ms
|
||||||
|
long elapsedMillis = totalDurationMillis - remainingMillis; // Calculate elapsed for the bar
|
||||||
|
|
||||||
|
// Ensure elapsed is not negative or greater than total
|
||||||
|
elapsedMillis = Math.max(0, elapsedMillis);
|
||||||
|
elapsedMillis = Math.min(totalDurationMillis, elapsedMillis);
|
||||||
|
|
||||||
|
player.sendActionBar(Component.text("Ability Recharging: ", NamedTextColor.WHITE)
|
||||||
|
.append(Text.color(Text.generateProgressBar(20, (int)totalDurationMillis, (int)elapsedMillis))));
|
||||||
|
|
||||||
|
return false; // Still on cooldown
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Dispatch ability and check if it was successful
|
||||||
|
boolean applyCooldown = ability.dispatchAbility(requiredMaterial, player);
|
||||||
|
|
||||||
|
// Add cooldown if cooldownTicks is > 0 and the ability execution indicated we should apply cooldown
|
||||||
|
if (cooldownTicks > 0 && applyCooldown) {
|
||||||
|
onCooldown.addCooldown(cooldownKey, (long) cooldownTicks * 50L); // Cooldown expects milliseconds
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // Ability trigger attempt completed
|
||||||
|
} catch (Exception e) {
|
||||||
|
main.getPlugin().getLogger().log(Level.SEVERE, "Error executing trim ability for " + player.getName() + " (Pattern: " + ability.getPatternName() + ", Material: " + main.getRegistryAccess().getRegistry(RegistryKey.TRIM_MATERIAL).getKey(requiredMaterial).getKey() + ")", e);
|
||||||
|
return false; // Error occurred
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false; // No ability registered for this pattern
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the formatted Component information message for a specific trim pattern's ability.
|
||||||
|
* Includes overall pattern info and grouped material variant details.
|
||||||
|
*
|
||||||
|
* @param pattern The TrimPattern to get info for.
|
||||||
|
* @return The formatted info Component, or a message indicating no ability is registered.
|
||||||
|
*/
|
||||||
|
public Component formatAbilityInfo(TrimPattern pattern) {
|
||||||
|
AbstractAbility ability = registeredAbilities.get(pattern);
|
||||||
|
if (ability != null) {
|
||||||
|
return ability.formatAbilityInfo(); // Call the method on the ability instance
|
||||||
|
} else {
|
||||||
|
return Component.text("--- ", NamedTextColor.YELLOW)
|
||||||
|
.append(Component.text("No Ability Registered", NamedTextColor.YELLOW))
|
||||||
|
.append(Component.text(" ---", NamedTextColor.YELLOW))
|
||||||
|
.append(Component.newline())
|
||||||
|
.append(Component.text("No ability found for pattern: ", NamedTextColor.GRAY))
|
||||||
|
.append(Component.text(main.getRegistryAccess().getRegistry(RegistryKey.TRIM_PATTERN).getKey(pattern).getKey(), NamedTextColor.AQUA));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force-removes the cooldown for a player's specific trim ability.
|
||||||
|
* Useful for admin commands or testing.
|
||||||
|
*
|
||||||
|
* @param player The player whose cooldown to reset
|
||||||
|
* @param pattern The trim pattern
|
||||||
|
* @param material The trim material
|
||||||
|
* @return true if a cooldown was found and removed, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean removeCooldown(Player player, TrimPattern pattern, TrimMaterial material) {
|
||||||
|
AbilityCooldown cooldownKey = new AbilityCooldown(player.getUniqueId(), pattern, material);
|
||||||
|
if (onCooldown.isOnCooldown(cooldownKey)) {
|
||||||
|
onCooldown.removeCooldown(cooldownKey);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TrimPattern getTrimPattern(String name) {
|
||||||
|
name = name.toUpperCase();
|
||||||
|
return ValidPattern.valueOf(name).getCanonical();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidPattern getValidPattern(TrimPattern pattern) {
|
||||||
|
for (ValidPattern value : ValidPattern.values()) {
|
||||||
|
if (!value.getCanonical().equals(pattern)) continue;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TrimMaterial getTrimMaterial(String name) {
|
||||||
|
name = name.toUpperCase();
|
||||||
|
return ValidMaterial.valueOf(name).getCanonical();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidMaterial getValidMaterial(TrimMaterial material) {
|
||||||
|
for (ValidMaterial value : ValidMaterial.values()) {
|
||||||
|
if (!value.getCanonical().equals(material)) continue;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public enum ValidMaterial {
|
||||||
|
AMETHYST(TrimMaterial.AMETHYST),
|
||||||
|
COPPER(TrimMaterial.COPPER),
|
||||||
|
DIAMOND(TrimMaterial.DIAMOND),
|
||||||
|
EMERALD(TrimMaterial.EMERALD),
|
||||||
|
GOLD(TrimMaterial.GOLD),
|
||||||
|
IRON(TrimMaterial.IRON),
|
||||||
|
LAPIS(TrimMaterial.LAPIS),
|
||||||
|
NETHERITE(TrimMaterial.NETHERITE),
|
||||||
|
QUARTZ(TrimMaterial.QUARTZ),
|
||||||
|
REDSTONE(TrimMaterial.REDSTONE),
|
||||||
|
RESIN(TrimMaterial.RESIN);
|
||||||
|
|
||||||
|
private final TrimMaterial canonical;
|
||||||
|
|
||||||
|
ValidMaterial(TrimMaterial canonical) {
|
||||||
|
this.canonical = canonical;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TrimMaterial getCanonical() {
|
||||||
|
return canonical;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ValidPattern {
|
||||||
|
BOLT(TrimPattern.BOLT),
|
||||||
|
COAST(TrimPattern.COAST),
|
||||||
|
DUNE(TrimPattern.DUNE),
|
||||||
|
EYE(TrimPattern.EYE),
|
||||||
|
FLOW(TrimPattern.FLOW),
|
||||||
|
HOST(TrimPattern.HOST),
|
||||||
|
RAISER(TrimPattern.RAISER),
|
||||||
|
RIB(TrimPattern.RIB),
|
||||||
|
SENTRY(TrimPattern.SENTRY),
|
||||||
|
SHAPER(TrimPattern.SHAPER),
|
||||||
|
SILENCE(TrimPattern.SILENCE),
|
||||||
|
SNOUT(TrimPattern.SNOUT),
|
||||||
|
SPIRE(TrimPattern.SPIRE),
|
||||||
|
TIDE(TrimPattern.TIDE),
|
||||||
|
VEX(TrimPattern.VEX),
|
||||||
|
WARD(TrimPattern.WARD),
|
||||||
|
WAYFINDER(TrimPattern.WAYFINDER),
|
||||||
|
WILD(TrimPattern.WILD);
|
||||||
|
|
||||||
|
private final TrimPattern canonical;
|
||||||
|
|
||||||
|
ValidPattern(TrimPattern canonical) {
|
||||||
|
this.canonical = canonical;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TrimPattern getCanonical() {
|
||||||
|
return canonical;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package me.trouper.trimserver.server.systems;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.Main;
|
||||||
|
import org.bukkit.entity.LivingEntity;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class TrustBackend implements Main {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a UUID to the target's trust
|
||||||
|
* @param truster the player trusting
|
||||||
|
* @param trustee the UUID to trust
|
||||||
|
* @return true if it was added, false if it was already present.
|
||||||
|
*/
|
||||||
|
public boolean addTrust(Player truster, UUID trustee) {
|
||||||
|
Set<String> trustees = getTrustees(truster);
|
||||||
|
final boolean added = trustees.add(trustee.toString());
|
||||||
|
storage().userData.playerTrust.put(truster.getUniqueId().toString(),trustees);
|
||||||
|
storage().save();
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a UUID from the target's trust
|
||||||
|
* @param truster the player un-trusting
|
||||||
|
* @param trustee the UUID to un-trust
|
||||||
|
* @return true if it was removed, false if it was not present.
|
||||||
|
*/
|
||||||
|
public boolean removeTrust(Player truster, UUID trustee) {
|
||||||
|
Set<String> trustees = getTrustees(truster);
|
||||||
|
final boolean removed = trustees.remove(trustee.toString());
|
||||||
|
storage().userData.playerTrust.put(truster.getUniqueId().toString(),trustees);
|
||||||
|
storage().save();
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that the player trusts themselves. If they do not trust themselves then all the abilities will break.
|
||||||
|
* @param target the player to check
|
||||||
|
*/
|
||||||
|
public void initPlayer(Player target) {
|
||||||
|
addTrust(target,target.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a set of the target's trustees.
|
||||||
|
* @param target the player to get the trustees of
|
||||||
|
* @return Set containing the UUIDs of trustees.
|
||||||
|
*/
|
||||||
|
public Set<String> getTrustees(Player target) {
|
||||||
|
return storage().userData.playerTrust.getOrDefault(target.getUniqueId().toString(),new HashSet<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a set of the target's trustees.
|
||||||
|
* @param target the player to get the trustees of
|
||||||
|
* @return Set containing the UUIDs of trustees.
|
||||||
|
*/
|
||||||
|
public Set<String> getTrustees(UUID target) {
|
||||||
|
return storage().userData.playerTrust.getOrDefault(target.toString(),new HashSet<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if the target trusts the entity provided.
|
||||||
|
* @param target the player to check the trust of
|
||||||
|
* @param check the entity which may or may not be trusted
|
||||||
|
* @return true if the target trusts the entity, false if they don't.
|
||||||
|
*/
|
||||||
|
public boolean trusts(Player target, LivingEntity check) {
|
||||||
|
return getTrustees(target).contains(check.getUniqueId().toString()) || target.getUniqueId().equals(check.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if the target trusts the entity provided.
|
||||||
|
* @param target the player to check the trust of
|
||||||
|
* @param check the entity which may or may not be trusted
|
||||||
|
* @return true if the target trusts the entity, false if they don't.
|
||||||
|
*/
|
||||||
|
public boolean trusts(UUID target, LivingEntity check) {
|
||||||
|
return getTrustees(target).contains(check.getUniqueId().toString()) || target.equals(check.getUniqueId());
|
||||||
|
}
|
||||||
|
public boolean trusts(UUID target, UUID check) {
|
||||||
|
return getTrustees(target).contains(check.toString()) || target.equals(check);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
package me.trouper.trimserver.server.systems.abilities;
|
||||||
|
|
||||||
|
import io.papermc.paper.registry.RegistryAccess;
|
||||||
|
import io.papermc.paper.registry.RegistryKey;
|
||||||
|
import me.trouper.trimserver.server.Main;
|
||||||
|
import me.trouper.trimserver.server.events.QuickListener;
|
||||||
|
import me.trouper.trimserver.utils.Text;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimMaterial;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimPattern;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a set of abilities associated with a specific Armor Trim Pattern.
|
||||||
|
* Subclasses should override the material methods for which they want to define
|
||||||
|
* specific behavior. Apply the @PatternInfo annotation to subclasses for overall info.
|
||||||
|
* Material-specific info is defined using the @MaterialInfo annotation on the material methods.
|
||||||
|
*/
|
||||||
|
public abstract class AbstractAbility implements Main, QuickListener {
|
||||||
|
|
||||||
|
protected TrimPattern pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new TrimAbility instance for a specific pattern.
|
||||||
|
*
|
||||||
|
* @param pattern The TrimPattern this ability corresponds to.
|
||||||
|
*/
|
||||||
|
public AbstractAbility(TrimPattern pattern) {
|
||||||
|
if (pattern == null) {
|
||||||
|
throw new IllegalArgumentException("TrimPattern cannot be null");
|
||||||
|
}
|
||||||
|
this.pattern = pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The TrimPattern associated with this ability set.
|
||||||
|
*/
|
||||||
|
public final TrimPattern getPattern() {
|
||||||
|
return pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Default Ability Methods for Each Material ---
|
||||||
|
// Subclasses should override these methods to implement specific abilities.
|
||||||
|
// Apply @MaterialInfo to these methods in subclasses where implemented.
|
||||||
|
// Return true if the ability was successfully executed and should apply cooldown.
|
||||||
|
// Return false if the ability failed or otherwise shouldn't trigger cooldown.
|
||||||
|
|
||||||
|
/** Called when the player has a full set of this pattern with Amethyst trim. */
|
||||||
|
@MaterialInfo(name = "Default Amethyst Ability", description = "A basic ability.")
|
||||||
|
public boolean amethystAbility(Player player) { return true; } // Default returns true to maintain backwards compatibility
|
||||||
|
|
||||||
|
/** Called when the player has a full set of this pattern with Copper trim. */
|
||||||
|
@MaterialInfo(name = "Default Copper Ability", description = "A basic ability.")
|
||||||
|
public boolean copperAbility(Player player) { return true; } // Default returns true to maintain backwards compatibility
|
||||||
|
|
||||||
|
/** Called when the player has a full set of this pattern with Diamond trim. */
|
||||||
|
@MaterialInfo(name = "Default Diamond Ability", description = "A basic ability.")
|
||||||
|
public boolean diamondAbility(Player player) { return true; } // Default returns true to maintain backwards compatibility
|
||||||
|
|
||||||
|
/** Called when the player has a full set of this pattern with Emerald trim. */
|
||||||
|
@MaterialInfo(name = "Default Emerald Ability", description = "A basic ability.")
|
||||||
|
public boolean emeraldAbility(Player player) { return true; } // Default returns true to maintain backwards compatibility
|
||||||
|
|
||||||
|
/** Called when the player has a full set of this pattern with Gold trim. */
|
||||||
|
@MaterialInfo(name = "Default Gold Ability", description = "A basic ability.")
|
||||||
|
public boolean goldAbility(Player player) { return true; } // Default returns true to maintain backwards compatibility
|
||||||
|
|
||||||
|
/** Called when the player has a full set of this pattern with Iron trim. */
|
||||||
|
@MaterialInfo(name = "Default Iron Ability", description = "A basic ability.")
|
||||||
|
public boolean ironAbility(Player player) { return true; } // Default returns true to maintain backwards compatibility
|
||||||
|
|
||||||
|
/** Called when the player has a full set of this pattern with Lapis trim. */
|
||||||
|
@MaterialInfo(name = "Default Lapis Ability", description = "A basic ability.")
|
||||||
|
public boolean lapisAbility(Player player) { return true; } // Default returns true to maintain backwards compatibility
|
||||||
|
|
||||||
|
/** Called when the player has a full set of this pattern with Netherite trim. */
|
||||||
|
@MaterialInfo(name = "Default Netherite Ability", description = "A basic ability.")
|
||||||
|
public boolean netheriteAbility(Player player) { return true; } // Default returns true to maintain backwards compatibility
|
||||||
|
|
||||||
|
/** Called when the player has a full set of this pattern with Quartz trim. */
|
||||||
|
@MaterialInfo(name = "Default Quartz Ability", description = "A basic ability.")
|
||||||
|
public boolean quartzAbility(Player player) { return true; } // Default returns true to maintain backwards compatibility
|
||||||
|
|
||||||
|
/** Called when the player has a full set of this pattern with Redstone trim. */
|
||||||
|
@MaterialInfo(name = "Default Redstone Ability", description = "A basic ability.")
|
||||||
|
public boolean redstoneAbility(Player player) { return true; } // Default returns true to maintain backwards compatibility
|
||||||
|
|
||||||
|
/** Called when the player has a full set of this pattern with Resin trim. */
|
||||||
|
@MaterialInfo(name = "Default Resin Ability", description = "A basic ability.")
|
||||||
|
public boolean resinAbility(Player player) { return true; } // Default returns true to maintain backwards compatibility
|
||||||
|
|
||||||
|
// --- Helper Method to Dispatch Based on Material ---
|
||||||
|
// This is called by the Manager after confirming the player has a matching set.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dispatcher. Calls the appropriate material-specific ability method.
|
||||||
|
* Should only be called by the TrimAbilityManager after validation and cooldown check.
|
||||||
|
*
|
||||||
|
* @param material The TrimMaterial identified on the player's armor set.
|
||||||
|
* @param player The player activating the ability.
|
||||||
|
* @return true if the ability was successfully executed and should apply cooldown,
|
||||||
|
* false if the ability failed or otherwise shouldn't trigger cooldown.
|
||||||
|
*/
|
||||||
|
public final boolean dispatchAbility(TrimMaterial material, Player player) {
|
||||||
|
String methodName = getMethodNameForMaterial(material);
|
||||||
|
if (methodName == null) {
|
||||||
|
main.getPlugin().getLogger().warning("Attempted to dispatch for unknown material: " + main.getRegistryAccess().getRegistry(RegistryKey.TRIM_MATERIAL).getKey(material).getKey());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Method abilityMethod = this.getClass().getMethod(methodName, Player.class);
|
||||||
|
Object result = abilityMethod.invoke(this, player);
|
||||||
|
return (result instanceof Boolean) ? (Boolean) result : true;
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
main.getPlugin().getLogger().warning("Method " + methodName + " not found in " + this.getClass().getSimpleName() + " for dispatch.");
|
||||||
|
return false;
|
||||||
|
} catch (Exception e) {
|
||||||
|
main.getPlugin().getLogger().severe("Error dispatching ability method " + methodName + " for pattern " + getPatternName() + ": " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the MaterialAbilityInfo annotation for a specific material's method
|
||||||
|
* within THIS TrimAbility instance's class.
|
||||||
|
*
|
||||||
|
* @param material The TrimMaterial.
|
||||||
|
* @return The MaterialInfo annotation, or null if the method isn't found or not annotated.
|
||||||
|
*/
|
||||||
|
public MaterialInfo getAbilityInfo(TrimMaterial material) {
|
||||||
|
String methodName = getMethodNameForMaterial(material);
|
||||||
|
if (methodName == null) {
|
||||||
|
return null; // Unknown material mapping
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Method abilityMethod = this.getClass().getMethod(methodName, Player.class);
|
||||||
|
return abilityMethod.isAnnotationPresent(MaterialInfo.class) ?
|
||||||
|
abilityMethod.getAnnotation(MaterialInfo.class) : null;
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
// Method not found - this should not happen for the default methods,
|
||||||
|
// but might if a subclass removes one or getMethodNameForMaterial is wrong.
|
||||||
|
return null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
main.getPlugin().getLogger().severe("Unexpected error getting annotation for method " + methodName + " in " + this.getClass().getSimpleName() + ": " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the PatternInfo annotation for this TrimAbility class.
|
||||||
|
*
|
||||||
|
* @return The PatternInfo annotation, or null if not present.
|
||||||
|
*/
|
||||||
|
public PatternInfo getPatternInfo() {
|
||||||
|
return this.getClass().getAnnotation(PatternInfo.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the NamespacedKey key string for this pattern.
|
||||||
|
* @return The pattern's key string.
|
||||||
|
*/
|
||||||
|
public String getPatternName() {
|
||||||
|
RegistryAccess access = main.getRegistryAccess();
|
||||||
|
return access.getRegistry(RegistryKey.TRIM_PATTERN).getKey(pattern).getKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a formatted Component message detailing the effects of this ability.
|
||||||
|
* Includes overall pattern info and groups material variants by identical descriptions.
|
||||||
|
*
|
||||||
|
* @return A formatted Component containing the ability information.
|
||||||
|
*/
|
||||||
|
public Component formatAbilityInfo() {
|
||||||
|
RegistryAccess access = main.getRegistryAccess();
|
||||||
|
PatternInfo patternInfo = getPatternInfo();
|
||||||
|
|
||||||
|
Component message = Component.empty().appendNewline();
|
||||||
|
// Add overall pattern info
|
||||||
|
|
||||||
|
if (patternInfo != null && patternInfo.description() != null && !patternInfo.description().isEmpty()) {
|
||||||
|
message = message.append(Text.color("&7%s".formatted(patternInfo.description()))).appendNewline().appendNewline();
|
||||||
|
}
|
||||||
|
|
||||||
|
message = message.append(Text.color("&7&m---&e %s &7&m---".formatted(patternInfo.name()))).appendNewline();
|
||||||
|
|
||||||
|
// Map descriptions to lists of material names and their cooldowns
|
||||||
|
Map<String, List<Map.Entry<String, Integer>>> groupedInfo = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
List<TrimMaterial> materials = access.getRegistry(RegistryKey.TRIM_MATERIAL).stream().toList();
|
||||||
|
|
||||||
|
for (TrimMaterial material : materials) {
|
||||||
|
MaterialInfo info = getAbilityInfo(material);
|
||||||
|
if (info != null && info.description() != null && !info.description().isEmpty()) {
|
||||||
|
String materialName = access.getRegistry(RegistryKey.TRIM_MATERIAL).getKey(material).getKey();
|
||||||
|
String description = info.description();
|
||||||
|
int cooldownSeconds = info.cooldownTicks() / 20;
|
||||||
|
|
||||||
|
groupedInfo.computeIfAbsent(description, k -> new ArrayList<>())
|
||||||
|
.add(Map.entry(materialName, cooldownSeconds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupedInfo.isEmpty()) {
|
||||||
|
message = message.append(Text.color("&7(No specific material abilities defined for this pattern)")).appendNewline();
|
||||||
|
} else {
|
||||||
|
// Iterate through the grouped descriptions and their materials
|
||||||
|
for (Map.Entry<String, List<Map.Entry<String, Integer>>> entry : groupedInfo.entrySet()) {
|
||||||
|
String description = entry.getKey();
|
||||||
|
List<Map.Entry<String, Integer>> materialEntries = entry.getValue();
|
||||||
|
|
||||||
|
// Append the description
|
||||||
|
message = message.append(Text.color("&7%s&f:".formatted(description))).appendNewline();
|
||||||
|
|
||||||
|
// Append the list of materials with this description
|
||||||
|
for (Map.Entry<String, Integer> matEntry : materialEntries) {
|
||||||
|
String matName = matEntry.getKey();
|
||||||
|
int cooldownSeconds = matEntry.getValue();
|
||||||
|
|
||||||
|
message = message.append(Text.color(" &8-&r &b%s".formatted(matName)));
|
||||||
|
|
||||||
|
if (cooldownSeconds > 0) {
|
||||||
|
message = message.append(Text.color(" &8| &9Cooldown&f: &7%ss".formatted(cooldownSeconds)));
|
||||||
|
}
|
||||||
|
|
||||||
|
message = message.appendNewline();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to map TrimMaterial constants to their corresponding method names
|
||||||
|
* within the TrimAbility class structure.
|
||||||
|
*
|
||||||
|
* @param material The TrimMaterial.
|
||||||
|
* @return The expected method name (e.g., "amethystAbility"), or null if not mapped.
|
||||||
|
*/
|
||||||
|
private String getMethodNameForMaterial(TrimMaterial material) {
|
||||||
|
if (material == null) return null;
|
||||||
|
RegistryAccess access = main.getRegistryAccess();
|
||||||
|
NamespacedKey key = access.getRegistry(RegistryKey.TRIM_MATERIAL).getKey(material);
|
||||||
|
if (key == null) return null;
|
||||||
|
|
||||||
|
String materialKey = key.getKey();
|
||||||
|
return materialKey + "Ability";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package me.trouper.trimserver.server.systems.abilities;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
public @interface MaterialInfo {
|
||||||
|
/**
|
||||||
|
* @return The display name of the ability for this color.
|
||||||
|
*/
|
||||||
|
String name() default "Unnamed Material";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return A brief description of what the ability material does.
|
||||||
|
*/
|
||||||
|
String description() default "No description provided.";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return cooldown time in gameTicks (20 per second/every 50ms)
|
||||||
|
*/
|
||||||
|
int cooldownTicks() default 20*60;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package me.trouper.trimserver.server.systems.abilities;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
public @interface PatternInfo {
|
||||||
|
/**
|
||||||
|
* @return The display name of the ability set (for the specific pattern).
|
||||||
|
*/
|
||||||
|
String name() default "Unnamed Trim Pattern";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return A brief description of what the ability set does.
|
||||||
|
*/
|
||||||
|
String description() default "No description provided.";
|
||||||
|
}
|
||||||
@@ -0,0 +1,427 @@
|
|||||||
|
package me.trouper.trimserver.server.systems.abilities;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.utils.Text;
|
||||||
|
import me.trouper.trimserver.utils.Verbose;
|
||||||
|
import me.trouper.trimserver.utils.visual.BlockDisplayRaytracer;
|
||||||
|
import org.bukkit.*;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.damage.DamageSource;
|
||||||
|
import org.bukkit.damage.DamageType;
|
||||||
|
import org.bukkit.entity.Bat;
|
||||||
|
import org.bukkit.entity.BlockDisplay;
|
||||||
|
import org.bukkit.entity.Display;
|
||||||
|
import org.bukkit.entity.LivingEntity;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
import org.bukkit.potion.PotionEffect;
|
||||||
|
import org.bukkit.potion.PotionEffectType;
|
||||||
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
import org.bukkit.util.BoundingBox;
|
||||||
|
import org.bukkit.util.Transformation;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
import org.joml.AxisAngle4f;
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the creation and animation of a Giant Worm event using BlockDisplay entities.
|
||||||
|
*/
|
||||||
|
public class WormEvent {
|
||||||
|
|
||||||
|
private final JavaPlugin plugin;
|
||||||
|
|
||||||
|
// --- Configuration ---
|
||||||
|
private static final Material WORM_BODY_MATERIAL = Material.PALE_OAK_WOOD;
|
||||||
|
private static final Material WORM_TEETH_MATERIAL = Material.DRIPSTONE_BLOCK;
|
||||||
|
private static final int TICKS_PER_SECOND = 5; // Standard Minecraft ticks per second
|
||||||
|
private static final double TEETH_CLOSE_FACTOR = 1; // How far teeth move inwards (fraction of radius, 0.0 to 1.0)
|
||||||
|
private static final double DAMAGE_AMOUNT = 19; // Damage dealt by the worm's bite
|
||||||
|
private static final float BITE_SOUND_VOLUME = 2.0f;
|
||||||
|
private static final float BITE_SOUND_PITCH_GROWL = 0.5f;
|
||||||
|
private static final float BITE_SOUND_PITCH_GRIND = 0.1f;
|
||||||
|
private static final int INTERPOLATION_TICKS = 1; // Smoothness of movement (1 tick)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for the WormEvent handler.
|
||||||
|
* @param plugin Your plugin instance, needed for scheduling tasks.
|
||||||
|
*/
|
||||||
|
public WormEvent(JavaPlugin plugin) {
|
||||||
|
this.plugin = Objects.requireNonNull(plugin, "Plugin instance cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawns a giant worm event at the specified location.
|
||||||
|
* The worm rises, attacks, and retreats.
|
||||||
|
*
|
||||||
|
* @param centerLocation The location where the center of the worm's mouth will appear at ground level.
|
||||||
|
* The worm will rise centered on this X/Z coordinate.
|
||||||
|
* @param radius The radius of the worm in blocks. Determines width and the height it rises.
|
||||||
|
* Must be a positive integer.
|
||||||
|
*/
|
||||||
|
public void spawnGiantWorm(Location centerLocation, int radius) {
|
||||||
|
// --- Input Validation ---
|
||||||
|
if (radius <= 0) {
|
||||||
|
plugin.getLogger().warning("Cannot spawn worm: Radius must be positive.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
World world = centerLocation.getWorld();
|
||||||
|
if (world == null) {
|
||||||
|
plugin.getLogger().warning("Cannot spawn worm: Location has a null world.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Ensure the location is safe and loaded if necessary (optional, depends on context)
|
||||||
|
if (!world.isChunkLoaded(centerLocation.getBlockX() >> 4, centerLocation.getBlockZ() >> 4)) {
|
||||||
|
plugin.getLogger().warning("Cannot spawn worm: Target chunk is not loaded.");
|
||||||
|
// Optionally force load: world.loadChunk(centerLocation.getBlockX() >> 4, centerLocation.getBlockZ() >> 4);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- Prepare Block Data ---
|
||||||
|
BlockData bodyBlockData = WORM_BODY_MATERIAL.createBlockData();
|
||||||
|
BlockData teethBlockData = WORM_TEETH_MATERIAL.createBlockData();
|
||||||
|
|
||||||
|
// --- List to hold all entities ---
|
||||||
|
List<BlockDisplay> allEntities = new ArrayList<>();
|
||||||
|
|
||||||
|
// --- Calculate Worm Dimensions and Positions ---
|
||||||
|
double segmentHeight = 1.0;
|
||||||
|
int segmentsPerRing = Math.max(8, (int) (radius * Math.PI * 2));
|
||||||
|
double riseHeight = radius * segmentHeight;
|
||||||
|
Location initialBaseLocation = centerLocation.clone().subtract(0, riseHeight, 0);
|
||||||
|
|
||||||
|
// --- Spawn Body Segment Entities ---
|
||||||
|
Verbose.send(String.format("Spawning worm body (Radius: %d, Height: %.1f, Segments per ring: %d)", radius, riseHeight, segmentsPerRing));
|
||||||
|
List<BlockDisplay> bodyEntities = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int ySegment = 0; ySegment < radius; ySegment++) { // Iterate vertical segments
|
||||||
|
double currentY = initialBaseLocation.getY() + (ySegment * segmentHeight);
|
||||||
|
for (int i = 0; i < segmentsPerRing; i++) { // Iterate segments in the ring
|
||||||
|
double angle = (2 * Math.PI / segmentsPerRing) * i;
|
||||||
|
// Calculate position on the circle edge
|
||||||
|
double x = centerLocation.getX() + radius * Math.cos(angle);
|
||||||
|
double z = centerLocation.getZ() + radius * Math.sin(angle);
|
||||||
|
Location blockPos = new Location(world, x, currentY, z);
|
||||||
|
|
||||||
|
// Spawn the display entity
|
||||||
|
BlockDisplay bodySegment = spawnBlockDisplay(blockPos, bodyBlockData);
|
||||||
|
if (bodySegment != null) {
|
||||||
|
bodyEntities.add(bodySegment);
|
||||||
|
allEntities.add(bodySegment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Spawn Teeth Entities ---
|
||||||
|
Verbose.send("Spawning worm teeth...");
|
||||||
|
List<List<BlockDisplay>> teethRows = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int row = 0; row < 4; row++) {
|
||||||
|
List<BlockDisplay> rowTeeth = new ArrayList<>();
|
||||||
|
double rowOffsetY = row == 0 ? initialBaseLocation.getY() + riseHeight - 0.5 : initialBaseLocation.getY() + (riseHeight - 1.0 - row);
|
||||||
|
|
||||||
|
int teethPerRow = (int)(segmentsPerRing * 1.5);
|
||||||
|
for (int i = 0; i < teethPerRow; i++) {
|
||||||
|
double angle = (2 * Math.PI / teethPerRow) * i;
|
||||||
|
double x = centerLocation.getX() + radius * Math.cos(angle);
|
||||||
|
double z = centerLocation.getZ() + radius * Math.sin(angle);
|
||||||
|
Location startLocation = new Location(world, x, rowOffsetY, z);
|
||||||
|
|
||||||
|
BlockDisplay tooth = spawnBlockDisplay(startLocation, teethBlockData);
|
||||||
|
if (tooth == null) continue;
|
||||||
|
|
||||||
|
Location centerAtTeethY = centerLocation.clone().add(0.5, 0, 0.5);
|
||||||
|
centerAtTeethY.setY(startLocation.getY() - 1);
|
||||||
|
|
||||||
|
Vector direction = centerAtTeethY.toVector().subtract(startLocation.toVector()).normalize();
|
||||||
|
|
||||||
|
BlockDisplayRaytracer.transform(tooth, startLocation, direction, 2.0, 0.5); // 2 blocks toward center, 0.5 thickness
|
||||||
|
rowTeeth.add(tooth);
|
||||||
|
allEntities.add(tooth);
|
||||||
|
}
|
||||||
|
teethRows.add(rowTeeth);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (allEntities.isEmpty()) {
|
||||||
|
plugin.getLogger().severe("Failed to spawn any worm entities. Aborting event.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Start the Animation Task ---
|
||||||
|
Verbose.send("Starting worm animation...");
|
||||||
|
new WormAnimationTask(
|
||||||
|
plugin,
|
||||||
|
centerLocation,
|
||||||
|
radius,
|
||||||
|
segmentHeight,
|
||||||
|
segmentsPerRing,
|
||||||
|
allEntities,
|
||||||
|
bodyEntities,
|
||||||
|
teethRows
|
||||||
|
).runTaskTimer(plugin, 0L, 1L); // Start immediately, run every tick
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to spawn and configure a single BlockDisplay entity.
|
||||||
|
* Configures interpolation for smooth movement.
|
||||||
|
*
|
||||||
|
* @param location The initial location to spawn the entity.
|
||||||
|
* @param blockData The BlockData to display.
|
||||||
|
* @return The spawned BlockDisplay entity, or null if spawning failed.
|
||||||
|
*/
|
||||||
|
private BlockDisplay spawnBlockDisplay(Location location, BlockData blockData) {
|
||||||
|
World world = location.getWorld();
|
||||||
|
if (world == null) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return world.spawn(location, BlockDisplay.class, entity -> {
|
||||||
|
entity.setBlock(blockData);
|
||||||
|
// Interpolation settings for smooth movement between teleports:
|
||||||
|
entity.setInterpolationDuration(INTERPOLATION_TICKS); // Interpolate over X ticks
|
||||||
|
entity.setInterpolationDelay(-1); // Start interpolation immediately when teleported
|
||||||
|
entity.setTeleportDuration(INTERPOLATION_TICKS); // Client prediction time, match duration
|
||||||
|
|
||||||
|
entity.setGravity(false); // Not affected by gravity
|
||||||
|
entity.setPersistent(false); // Don't save the entity
|
||||||
|
entity.setBrightness(new Display.Brightness(15, 15)); // Max block/sky light
|
||||||
|
// Default transformation (no translation, rotation, unit scale)
|
||||||
|
entity.setTransformation(new Transformation(
|
||||||
|
new Vector3f(0f, 0f, 0f), // Translation offset (none)
|
||||||
|
new AxisAngle4f(0f, 0f, 0f, 1f), // Left rotation (none)
|
||||||
|
new Vector3f(1f, 1f, 1f), // Scale (normal)
|
||||||
|
new AxisAngle4f(0f, 0f, 0f, 1f) // Right rotation (none)
|
||||||
|
));
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().severe("Failed to spawn BlockDisplay at " + location + ": " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- BukkitRunnable for Handling the Animation ---
|
||||||
|
private static class WormAnimationTask extends BukkitRunnable {
|
||||||
|
private final JavaPlugin plugin;
|
||||||
|
private final Location centerLocation; // Center of the worm's mouth at ground level
|
||||||
|
private final int radius;
|
||||||
|
private final double segmentHeight;
|
||||||
|
private final int segmentsPerRing;
|
||||||
|
private final List<BlockDisplay> allEntities; // All worm parts
|
||||||
|
private final List<BlockDisplay> bodyEntities;
|
||||||
|
private final List<List<BlockDisplay>> teethEntities;
|
||||||
|
private final World world;
|
||||||
|
|
||||||
|
// Timing calculation
|
||||||
|
private final double totalRiseHeight;
|
||||||
|
private final int riseDurationTicks;
|
||||||
|
private final int attackPauseTicks;
|
||||||
|
private final int retreatDurationTicks;
|
||||||
|
private final int totalDurationTicks;
|
||||||
|
private final double movementSpeedPerTick; // Vertical distance moved each tick
|
||||||
|
|
||||||
|
private int ticksElapsed = 0;
|
||||||
|
private boolean attackPerformed = false;
|
||||||
|
private boolean retreatStarted = false;
|
||||||
|
private boolean teethReset = false;
|
||||||
|
|
||||||
|
public WormAnimationTask(JavaPlugin plugin, Location centerLocation, int radius, double segmentHeight, int segmentsPerRing,
|
||||||
|
List<BlockDisplay> allEntities, List<BlockDisplay> bodyEntities, List<List<BlockDisplay>> teethEntities) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.centerLocation = centerLocation;
|
||||||
|
this.radius = radius;
|
||||||
|
this.segmentHeight = segmentHeight;
|
||||||
|
this.segmentsPerRing = segmentsPerRing;
|
||||||
|
this.allEntities = allEntities;
|
||||||
|
this.bodyEntities = bodyEntities;
|
||||||
|
this.teethEntities = teethEntities;
|
||||||
|
this.world = centerLocation.getWorld(); // World should not be null here based on prior checks
|
||||||
|
|
||||||
|
// Calculate timing
|
||||||
|
this.totalRiseHeight = radius * segmentHeight;
|
||||||
|
// Speed: 1 block per second = 1 block per 20 ticks
|
||||||
|
this.movementSpeedPerTick = segmentHeight / TICKS_PER_SECOND;
|
||||||
|
this.riseDurationTicks = (int) Math.ceil(totalRiseHeight / movementSpeedPerTick); // Ticks to rise fully
|
||||||
|
this.attackPauseTicks = TICKS_PER_SECOND * 2; // Half second pause for attack visual/sound
|
||||||
|
this.retreatDurationTicks = riseDurationTicks; // Same duration to retreat
|
||||||
|
this.totalDurationTicks = riseDurationTicks + attackPauseTicks + retreatDurationTicks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// Safety check in case world becomes invalid during the task
|
||||||
|
if (world == null || !world.equals(centerLocation.getWorld())) {
|
||||||
|
plugin.getLogger().warning("Worm animation world became invalid. Cleaning up.");
|
||||||
|
cleanup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Rising Phase ---
|
||||||
|
if (ticksElapsed < riseDurationTicks) {
|
||||||
|
moveEntities(movementSpeedPerTick);
|
||||||
|
}
|
||||||
|
// --- Attack Phase ---
|
||||||
|
else if (ticksElapsed == riseDurationTicks && !attackPerformed) {
|
||||||
|
attackPerformed = true;
|
||||||
|
performAttack(); // Close teeth and damage entities
|
||||||
|
|
||||||
|
// Schedule the start of the retreat after a short pause
|
||||||
|
new BukkitRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
setFinalToothPosition(); // Open teeth again
|
||||||
|
retreatStarted = true; // Signal retreat can begin on the next tick
|
||||||
|
}
|
||||||
|
}.runTaskLater(plugin, attackPauseTicks); // Pause after attack
|
||||||
|
}
|
||||||
|
// --- Retreating Phase ---
|
||||||
|
else if (retreatStarted && ticksElapsed < totalDurationTicks) {
|
||||||
|
moveEntities(-movementSpeedPerTick); // Move downwards
|
||||||
|
}
|
||||||
|
// --- End & Cleanup Phase ---
|
||||||
|
else if (ticksElapsed >= totalDurationTicks) {
|
||||||
|
Verbose.send("Worm animation finished. Cleaning up entities.");
|
||||||
|
cleanup();
|
||||||
|
this.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ticksElapsed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Helper method to teleport all worm entities vertically */
|
||||||
|
private void moveEntities(double verticalOffset) {
|
||||||
|
allEntities.forEach(entity -> {
|
||||||
|
if (entity != null && entity.isValid()) {
|
||||||
|
// Teleport smoothly by the calculated offset
|
||||||
|
entity.teleport(entity.getLocation().add(0, verticalOffset, 0));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Closes the teeth and damages nearby entities. */
|
||||||
|
private void performAttack() {
|
||||||
|
Verbose.send("Worm attacking!");
|
||||||
|
|
||||||
|
// 1. Close Teeth Animation
|
||||||
|
Location attackCenter = centerLocation.clone().add(0, totalRiseHeight, 0); // Center at the top of the worm
|
||||||
|
int row = 0;
|
||||||
|
for (List<BlockDisplay> teeth : teethEntities) {
|
||||||
|
for (BlockDisplay tooth : teeth) {
|
||||||
|
if (tooth == null || !tooth.isValid()) continue;
|
||||||
|
// Calculate vector pointing from tooth towards the center at the current height
|
||||||
|
Vector direction = attackCenter.toVector().subtract(tooth.getLocation().toVector());
|
||||||
|
direction.setY(0); // Only move horizontally
|
||||||
|
|
||||||
|
// Calculate the target closed position
|
||||||
|
// Move inwards by a factor of the radius
|
||||||
|
Location closedPosition = tooth.getLocation().add(direction.normalize().multiply(radius * TEETH_CLOSE_FACTOR));
|
||||||
|
closedPosition.setY(tooth.getLocation().getY() - 1); // Keep Y the same as current tooth Y
|
||||||
|
|
||||||
|
// Instantly teleport teeth inwards (no interpolation needed for snap)
|
||||||
|
// We temporarily disable interpolation for the snap effect
|
||||||
|
int originalInterpolation = tooth.getInterpolationDuration();
|
||||||
|
tooth.setInterpolationDuration(5); // No interpolation for instant snap
|
||||||
|
//tooth.teleport(closedPosition);
|
||||||
|
BlockDisplayRaytracer.transform(tooth,tooth.getLocation(),closedPosition,0.3);
|
||||||
|
// Restore interpolation for subsequent movements (retreat)
|
||||||
|
// Schedule restoration for the next tick to ensure teleport completes
|
||||||
|
new BukkitRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (tooth.isValid()) {
|
||||||
|
tooth.setInterpolationDuration(originalInterpolation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.runTaskLater(plugin, 1L);
|
||||||
|
|
||||||
|
}
|
||||||
|
row++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Play Sound Effects
|
||||||
|
world.playSound(attackCenter, Sound.ENTITY_ENDER_DRAGON_GROWL, SoundCategory.HOSTILE, BITE_SOUND_VOLUME, BITE_SOUND_PITCH_GROWL);
|
||||||
|
world.playSound(attackCenter, Sound.ENTITY_EVOKER_FANGS_ATTACK, SoundCategory.HOSTILE, BITE_SOUND_VOLUME, BITE_SOUND_PITCH_GRIND);
|
||||||
|
// Optional particle effects
|
||||||
|
world.spawnParticle(Particle.BLOCK_CRUMBLE, attackCenter, 50 * radius, radius * 0.5, 0.5, radius * 0.5, 0.1, WORM_BODY_MATERIAL.createBlockData());
|
||||||
|
|
||||||
|
// 3. Damage Entities within the Bounding Box
|
||||||
|
// Define the bounding box for the attack area (cylinder when fully risen)
|
||||||
|
BoundingBox attackBox = BoundingBox.of(
|
||||||
|
centerLocation.clone().subtract(radius, 0, radius), // Bottom corner
|
||||||
|
centerLocation.clone().add(radius, totalRiseHeight + 1.0, radius) // Top corner (add buffer)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Find living entities within the box
|
||||||
|
Collection<LivingEntity> nearbyLivingEntities = world.getNearbyEntities(attackBox, entity ->
|
||||||
|
entity instanceof LivingEntity // Check if it's a living entity
|
||||||
|
&& !allEntities.contains(entity) // Make sure it's not part of the worm itself
|
||||||
|
&& entity.isValid() // Ensure the entity is still valid
|
||||||
|
&& !entity.isDead() // Don't try to kill already dead entities
|
||||||
|
).stream().map(e -> (LivingEntity) e).toList(); // Cast to LivingEntity
|
||||||
|
|
||||||
|
Verbose.send("Found " + nearbyLivingEntities.size() + " living entities in attack range.");
|
||||||
|
|
||||||
|
// Apply instant kill damage
|
||||||
|
for (LivingEntity entity : nearbyLivingEntities) {
|
||||||
|
plugin.getLogger().fine("Damaging entity: " + entity.getType() + " at " + entity.getLocation());
|
||||||
|
// entity.setHealth(0.0); // Instant kill is often more reliable than massive damage
|
||||||
|
Bat dummy = world.spawn(centerLocation,Bat.class);
|
||||||
|
dummy.customName(Text.color("Shai-Hulud"));
|
||||||
|
dummy.setInvisible(true);
|
||||||
|
dummy.setInvulnerable(true);
|
||||||
|
dummy.setAI(false);
|
||||||
|
dummy.addScoreboardTag("$/TrimServer/ Temp");
|
||||||
|
entity.damage(DAMAGE_AMOUNT, DamageSource.builder(DamageType.MOB_ATTACK).withDamageLocation(centerLocation).withDirectEntity(dummy).build()); // Alternative if setHealth(0) causes issues
|
||||||
|
dummy.remove();
|
||||||
|
world.spawnParticle(Particle.DAMAGE_INDICATOR, entity.getEyeLocation(), 10, 0.2, 0.2, 0.2, 0.1);
|
||||||
|
entity.addPotionEffect(new PotionEffect(PotionEffectType.WITHER,60,1,true,false,false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Resets the teeth to their outer ring position relative to the current height. */
|
||||||
|
private void setFinalToothPosition() {
|
||||||
|
if (teethEntities.isEmpty() || teethReset) return; // Don't run if no teeth or already reset
|
||||||
|
plugin.getLogger().fine("Setting teeth position for retreat.");
|
||||||
|
|
||||||
|
for (List<BlockDisplay> toothRow : teethEntities) {
|
||||||
|
double rowY = toothRow.getFirst().getY();
|
||||||
|
for (BlockDisplay tooth : toothRow) {
|
||||||
|
Location pointTo = centerLocation.clone();
|
||||||
|
pointTo.setY(rowY);
|
||||||
|
|
||||||
|
BlockDisplayRaytracer.transform(tooth,tooth.getLocation(),pointTo,0.4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
teethReset = true; // Ensure this only runs once
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Removes all worm entities and cancels the task. */
|
||||||
|
private void cleanup() {
|
||||||
|
allEntities.forEach(entity -> {
|
||||||
|
if (entity != null && entity.isValid()) {
|
||||||
|
entity.remove(); // Remove the display entity from the world
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Clear lists to release references
|
||||||
|
allEntities.clear();
|
||||||
|
bodyEntities.clear();
|
||||||
|
for (List<BlockDisplay> row : teethEntities) {
|
||||||
|
row.clear();
|
||||||
|
}
|
||||||
|
teethEntities.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Overridden cancel to ensure cleanup is called */
|
||||||
|
@Override
|
||||||
|
public synchronized void cancel() throws IllegalStateException {
|
||||||
|
if (ticksElapsed < totalDurationTicks + TICKS_PER_SECOND) { // Ensure cleanup runs if cancelled early
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
super.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package me.trouper.trimserver.server.systems.abilities.trims;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.Main;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.MaterialInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.AbstractAbility;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.PatternInfo;
|
||||||
|
import me.trouper.trimserver.utils.SoundPlayer;
|
||||||
|
import me.trouper.trimserver.utils.TargetingUtils;
|
||||||
|
import me.trouper.trimserver.utils.visual.BlockDisplayRaytracer;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.Particle;
|
||||||
|
import org.bukkit.Sound;
|
||||||
|
import org.bukkit.damage.DamageSource;
|
||||||
|
import org.bukkit.damage.DamageType;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimPattern;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@PatternInfo(name = "Bolt", description = "Summon a bolt of lightning at enemies. Includes Variants.")
|
||||||
|
public class BoltAbility extends AbstractAbility implements Main {
|
||||||
|
|
||||||
|
public BoltAbility() {
|
||||||
|
super(TrimPattern.BOLT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean strike(Player caster, int range, double damage, Material innerBlock, Material outerBlock) {
|
||||||
|
return TargetingUtils.areaAffect(caster.getLocation(),range,target-> !main.man().trustBackend.trusts(caster,target),(target)->{
|
||||||
|
drawLightning(caster.getEyeLocation(),target.getEyeLocation(),innerBlock,outerBlock);
|
||||||
|
target.damage(damage,DamageSource.builder(DamageType.LIGHTNING_BOLT).withDamageLocation(caster.getEyeLocation()).withDirectEntity(caster).build());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawLightning(Location start, Location end, Material blockInner, Material blockOuter) {
|
||||||
|
int segments = 10;
|
||||||
|
double maxOffset = 0.5;
|
||||||
|
long stayTime = 10L;
|
||||||
|
double thickness = 0.07;
|
||||||
|
double thicknessOut = 0.1;
|
||||||
|
List<Player> viewers = new ArrayList<>(start.getWorld().getPlayers());
|
||||||
|
|
||||||
|
Location current = start.clone();
|
||||||
|
Vector direction = end.clone().subtract(start).toVector();
|
||||||
|
double segmentLength = direction.length() / segments;
|
||||||
|
direction.normalize().multiply(segmentLength);
|
||||||
|
SoundPlayer bolt = new SoundPlayer(end, Sound.ENTITY_LIGHTNING_BOLT_THUNDER,10,1);
|
||||||
|
SoundPlayer ring = new SoundPlayer(end, Sound.ITEM_TRIDENT_THUNDER,10,1);
|
||||||
|
|
||||||
|
bolt.playWithin(50);
|
||||||
|
ring.playWithin(30);
|
||||||
|
|
||||||
|
for (int i = 0; i < segments; i++) {
|
||||||
|
Vector offset = new Vector(
|
||||||
|
(random.nextDouble() - 0.5) * maxOffset,
|
||||||
|
(random.nextDouble() - 0.5) * maxOffset,
|
||||||
|
(random.nextDouble() - 0.5) * maxOffset
|
||||||
|
);
|
||||||
|
Location next = current.clone().add(direction).add(offset);
|
||||||
|
SoundPlayer zip = new SoundPlayer(next, Sound.ENTITY_BEE_STING,10,1);
|
||||||
|
zip.playWithin(10);
|
||||||
|
BlockDisplayRaytracer.trace(blockInner, current, next, thickness, stayTime, viewers);
|
||||||
|
BlockDisplayRaytracer.trace(blockOuter, current, next, thicknessOut, stayTime, viewers);
|
||||||
|
next.getWorld().spawnParticle(Particle.FLASH,next,0,0,0,0,0);
|
||||||
|
current = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockDisplayRaytracer.trace(blockInner, current, end, thickness, stayTime, viewers);
|
||||||
|
BlockDisplayRaytracer.trace(blockOuter, current, end, thicknessOut, stayTime, viewers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Amethyst Bolt",description = "Shoots a bolt of colored lightning at your closest enemy within 20 blocks. Deals 10 Damage", cooldownTicks = 20*10)
|
||||||
|
@Override
|
||||||
|
public boolean amethystAbility(Player player) {
|
||||||
|
return strike(player,20,10,Material.AMETHYST_BLOCK,Material.PURPLE_STAINED_GLASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Copper Bolt",description = "Shoots a bolt of colored lightning at your closest enemy within 20 blocks. Deals 10 Damage", cooldownTicks = 20*10)
|
||||||
|
@Override
|
||||||
|
public boolean copperAbility(Player player) {
|
||||||
|
return strike(player,20,10,Material.ORANGE_TERRACOTTA,Material.ORANGE_STAINED_GLASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Diamond Bolt",description = "Shoots a bolt of colored lightning at your closest enemy within 20 blocks. Deals 10 Damage", cooldownTicks = 20*10)
|
||||||
|
@Override
|
||||||
|
public boolean diamondAbility(Player player) {
|
||||||
|
return strike(player,20,10,Material.LIGHT_BLUE_CONCRETE_POWDER,Material.LIGHT_BLUE_STAINED_GLASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Emerald ",description = "Shoots a bolt of colored lightning at your closest enemy within 20 blocks. Deals 10 Damage", cooldownTicks = 20*10)
|
||||||
|
@Override
|
||||||
|
public boolean emeraldAbility(Player player) {
|
||||||
|
return strike(player,20,10,Material.LIME_CONCRETE,Material.LIME_STAINED_GLASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Gold Bolt",description = "Shoots a bolt of colored lightning at your closest enemy within 20 blocks. Deals 10 Damage", cooldownTicks = 20*10)
|
||||||
|
@Override
|
||||||
|
public boolean goldAbility(Player player) {
|
||||||
|
return strike(player,20,10,Material.YELLOW_CONCRETE_POWDER,Material.YELLOW_STAINED_GLASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Iron Bolt",description = "Shoots a bolt of colored lightning at your closest enemy within 20 blocks. Deals 10 Damage", cooldownTicks = 20*10)
|
||||||
|
@Override
|
||||||
|
public boolean ironAbility(Player player) {
|
||||||
|
return strike(player,20,10,Material.LIGHT_GRAY_WOOL,Material.LIGHT_GRAY_STAINED_GLASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Lapis Bolt",description = "Shoots a bolt of colored lightning at your closest enemy within 20 blocks. Deals 10 Damage", cooldownTicks = 20*10)
|
||||||
|
@Override
|
||||||
|
public boolean lapisAbility(Player player) {
|
||||||
|
return strike(player,20,10,Material.BLUE_CONCRETE,Material.BLUE_STAINED_GLASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Netherite Bolt",description = "Shoots a bolt of colored lightning at your closest enemy within 20 blocks. Deals 15 Damage", cooldownTicks = 20*10)
|
||||||
|
@Override
|
||||||
|
public boolean netheriteAbility(Player player) {
|
||||||
|
return strike(player,30,15,Material.BLACK_CONCRETE,Material.GRAY_STAINED_GLASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Quartz Bolt",description = "Shoots a bolt of overcharged lightning at your closest enemy within 20 blocks. Deals 15 Damage", cooldownTicks = 20*12)
|
||||||
|
@Override
|
||||||
|
public boolean quartzAbility(Player player) {
|
||||||
|
return strike(player,20,10,Material.WHITE_CONCRETE,Material.WHITE_STAINED_GLASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Redstone Bolt",description = "Shoots a bolt of colored lightning at your closest enemy within 20 blocks. Deals 10 Damage", cooldownTicks = 20*10)
|
||||||
|
@Override
|
||||||
|
public boolean redstoneAbility(Player player) {
|
||||||
|
return strike(player,20,10,Material.RED_CONCRETE_POWDER,Material.RED_STAINED_GLASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Resin Bolt",description = "Shoots a bolt of resin lightning at your closest enemy within 20 blocks. Deals 7 Damage", cooldownTicks = 20*5)
|
||||||
|
@Override
|
||||||
|
public boolean resinAbility(Player player) {
|
||||||
|
return strike(player,20,7,Material.RESIN_BLOCK,Material.ORANGE_STAINED_GLASS);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
package me.trouper.trimserver.server.systems.abilities.trims;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.MaterialInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.PatternInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.AbstractAbility;
|
||||||
|
import me.trouper.trimserver.utils.SoundPlayer;
|
||||||
|
import me.trouper.trimserver.utils.TargetingUtils;
|
||||||
|
import org.bukkit.*;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimPattern;
|
||||||
|
import org.bukkit.potion.PotionEffect;
|
||||||
|
import org.bukkit.potion.PotionEffectType;
|
||||||
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@PatternInfo(name = "Undertoe", description = "Conjure a swirling vortex that pulls, damages, and disorients foes.")
|
||||||
|
public class CoastAbility extends AbstractAbility {
|
||||||
|
|
||||||
|
private final Map<UUID, BukkitTask> activeUndertoes = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private static final double BASE_UNDERTOE_RADIUS = 5.5;
|
||||||
|
private static final int BASE_UNDERTOE_DURATION_TICKS = 10 * 20;
|
||||||
|
private static final double BASE_PULL_STRENGTH = 0.18;
|
||||||
|
private static final double BASE_DAMAGE_PER_SECOND = 2.0;
|
||||||
|
|
||||||
|
// Cooldowns
|
||||||
|
private static final int DEFAULT_COOLDOWN = 20 * 45;
|
||||||
|
private static final int NETHERITE_COOLDOWN = 20 * 60;
|
||||||
|
private static final int RESIN_COOLDOWN = 20 * 25;
|
||||||
|
|
||||||
|
// Netherite Modifiers
|
||||||
|
private static final double NETHERITE_RADIUS_MULTIPLIER = 1.25;
|
||||||
|
private static final double NETHERITE_DURATION_MULTIPLIER = 1.5;
|
||||||
|
private static final double NETHERITE_DAMAGE_MULTIPLIER = 1.5;
|
||||||
|
private static final double NETHERITE_PULL_MULTIPLIER = 1.3;
|
||||||
|
private static final int NETHERITE_SLOWNESS_AMPLIFIER = 2;
|
||||||
|
|
||||||
|
// Resin Modifiers
|
||||||
|
private static final double RESIN_DAMAGE_MULTIPLIER = 0.8;
|
||||||
|
|
||||||
|
|
||||||
|
public CoastAbility() {
|
||||||
|
super(TrimPattern.COAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createUndertoe(Player player, Material material) {
|
||||||
|
UUID playerUUID = player.getUniqueId();
|
||||||
|
|
||||||
|
if (activeUndertoes.containsKey(playerUUID)) {
|
||||||
|
activeUndertoes.get(playerUUID).cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
Location castLocation;
|
||||||
|
Block targetBlock = player.getTargetBlockExact(20, FluidCollisionMode.NEVER);
|
||||||
|
|
||||||
|
if (targetBlock != null && targetBlock.getType() != Material.AIR) {
|
||||||
|
castLocation = targetBlock.getLocation().add(0.5, 1.0, 0.5);
|
||||||
|
} else {
|
||||||
|
Vector direction = player.getLocation().getDirection().setY(0).normalize().multiply(5);
|
||||||
|
castLocation = player.getLocation().add(direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
Location centerLocation = TargetingUtils.findGroundLocation(castLocation);
|
||||||
|
World world = centerLocation.getWorld();
|
||||||
|
|
||||||
|
if (world == null) return;
|
||||||
|
|
||||||
|
double currentRadius = BASE_UNDERTOE_RADIUS;
|
||||||
|
int currentDuration = BASE_UNDERTOE_DURATION_TICKS;
|
||||||
|
double currentDamage = BASE_DAMAGE_PER_SECOND;
|
||||||
|
double currentPullStrength = BASE_PULL_STRENGTH;
|
||||||
|
int slownessAmplifier = 1;
|
||||||
|
|
||||||
|
if (material == Material.NETHERITE_BLOCK) {
|
||||||
|
currentRadius *= NETHERITE_RADIUS_MULTIPLIER;
|
||||||
|
currentDuration = (int) (currentDuration * NETHERITE_DURATION_MULTIPLIER);
|
||||||
|
currentDamage *= NETHERITE_DAMAGE_MULTIPLIER;
|
||||||
|
currentPullStrength *= NETHERITE_PULL_MULTIPLIER;
|
||||||
|
slownessAmplifier = NETHERITE_SLOWNESS_AMPLIFIER;
|
||||||
|
} else if (material == Material.RESIN_BLOCK) {
|
||||||
|
currentDamage *= RESIN_DAMAGE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
|
||||||
|
new SoundPlayer(centerLocation, Sound.ENTITY_PLAYER_SPLASH_HIGH_SPEED, 1.2f, 0.8f).playWithin(30);
|
||||||
|
new SoundPlayer(centerLocation, Sound.BLOCK_WATER_AMBIENT, 1.0f, 0.5f).playWithin(30);
|
||||||
|
|
||||||
|
final double finalRadius = currentRadius;
|
||||||
|
final int finalDuration = currentDuration;
|
||||||
|
final double finalDamage = currentDamage;
|
||||||
|
final double finalPullStrength = currentPullStrength;
|
||||||
|
final int finalSlownessAmplifier = slownessAmplifier;
|
||||||
|
|
||||||
|
BukkitTask task = new BukkitRunnable() {
|
||||||
|
int ticksElapsed = 0;
|
||||||
|
final Random random = new Random();
|
||||||
|
final Location effectCenter = centerLocation;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (!player.isOnline() || ticksElapsed > finalDuration) {
|
||||||
|
endUndertoe(playerUUID, effectCenter);
|
||||||
|
cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double visualRadius = finalRadius * Math.max(0.4, (1.0 - (double)ticksElapsed / (finalDuration * 1.2)));
|
||||||
|
|
||||||
|
for (int i = 0; i < 360; i += (material == Material.NETHERITE_BLOCK ? 10 : 15)) {
|
||||||
|
double angle = Math.toRadians(i + (ticksElapsed * 7));
|
||||||
|
double x = Math.cos(angle) * visualRadius;
|
||||||
|
double z = Math.sin(angle) * visualRadius;
|
||||||
|
Location particleLoc = effectCenter.clone().add(x, 0.2 + random.nextDouble() * 0.5, z);
|
||||||
|
|
||||||
|
world.spawnParticle(Particle.BUBBLE_COLUMN_UP, particleLoc, 1, 0, 0, 0, 0);
|
||||||
|
world.spawnParticle(Particle.FALLING_WATER, particleLoc, random.nextInt(2) + 1, 0.15, 0.15, 0.15, 0);
|
||||||
|
if (ticksElapsed % 4 == 0) {
|
||||||
|
world.spawnParticle(Particle.BUBBLE_POP, particleLoc.clone().add(0, 0.2, 0), 1, 0.2, 0.2, 0.2, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ticksElapsed % 6 == 0) {
|
||||||
|
world.spawnParticle(Particle.UNDERWATER, effectCenter.clone().add(0, 1.8, 0), 10, finalRadius * 0.5, 0.6, finalRadius * 0.5, 0.1);
|
||||||
|
world.spawnParticle(Particle.NAUTILUS, effectCenter.clone().add(0, 2.0, 0), 10, 0.4, 0.4, 0.4, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
TargetingUtils.areaAffect(effectCenter,finalRadius,(entity) -> !entity.equals(player) && !entity.isDead() && !(entity.getVehicle() instanceof Player) && entity.getLocation().distanceSquared(effectCenter) < finalRadius * finalRadius && !main.man().trustBackend.trusts(player,entity), entity -> {
|
||||||
|
Vector toCenter = effectCenter.toVector().subtract(entity.getLocation().toVector());
|
||||||
|
double distanceSquared = entity.getLocation().distanceSquared(effectCenter);
|
||||||
|
|
||||||
|
if (distanceSquared != 0) {
|
||||||
|
double pullFactor = finalPullStrength * Math.max(0.1, (1.0 - (Math.sqrt(distanceSquared) / finalRadius)));
|
||||||
|
Vector pullVelocity = toCenter.normalize().multiply(pullFactor);
|
||||||
|
|
||||||
|
if (Math.sqrt(distanceSquared) < finalRadius * 0.3) {
|
||||||
|
pullVelocity.setY(pullVelocity.getY() - 0.2);
|
||||||
|
} else {
|
||||||
|
pullVelocity.setY(pullVelocity.getY() - 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.setVelocity(entity.getVelocity().add(pullVelocity));
|
||||||
|
|
||||||
|
if (ticksElapsed % 20 == 0) { // Every second
|
||||||
|
entity.damage(finalDamage, player);
|
||||||
|
entity.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, 45, finalSlownessAmplifier -1, true, false, true));
|
||||||
|
entity.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 35, 0, true, false, true));
|
||||||
|
entity.setRemainingAir(Math.max(0, entity.getRemainingAir() - 40)); // Suffocation effect
|
||||||
|
|
||||||
|
new SoundPlayer(entity.getLocation(), Sound.ENTITY_DROWNED_HURT_WATER, 0.9f, 1.4f).playWithin(15);
|
||||||
|
}
|
||||||
|
world.spawnParticle(Particle.BLOCK_CRUMBLE, entity.getLocation().add(0, entity.getHeight() * 0.3, 0), 3, 0.2, 0.2, 0.2, 0.01, Material.WATER.createBlockData());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ticksElapsed % 30 == 0) {
|
||||||
|
new SoundPlayer(effectCenter, Sound.BLOCK_BUBBLE_COLUMN_WHIRLPOOL_AMBIENT, 0.9f, 1.0f + (random.nextFloat() * 0.4f - 0.2f)).playWithin(25);
|
||||||
|
if(material == Material.NETHERITE_BLOCK && ticksElapsed % 60 == 0) {
|
||||||
|
new SoundPlayer(effectCenter, Sound.ENTITY_ELDER_GUARDIAN_CURSE, 0.6f, 1.6f + random.nextFloat() * 0.2f).playWithin(35);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ticksElapsed++;
|
||||||
|
}
|
||||||
|
}.runTaskTimer(main.getPlugin(), 0L, 1L);
|
||||||
|
activeUndertoes.put(playerUUID, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void endUndertoe(UUID playerUUID, Location center) {
|
||||||
|
activeUndertoes.remove(playerUUID);
|
||||||
|
|
||||||
|
if (center != null && center.getWorld() != null) {
|
||||||
|
new SoundPlayer(center, Sound.ENTITY_FISHING_BOBBER_SPLASH, 1.1f, 0.6f).playWithin(30);
|
||||||
|
center.getWorld().spawnParticle(Particle.EXPLOSION_EMITTER, center.clone().add(0, 0.5, 0), 1, 0,0,0,0);
|
||||||
|
center.getWorld().spawnParticle(Particle.FALLING_WATER, center.clone().add(0,0.5,0), 50, 2, 1, 2, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Amethyst Undertoe", description = "A swirling vortex pulls and drowns foes.", cooldownTicks = DEFAULT_COOLDOWN)
|
||||||
|
@Override
|
||||||
|
public boolean amethystAbility(Player player) {
|
||||||
|
createUndertoe(player, Material.AMETHYST_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Copper Undertoe", description = "A swirling vortex pulls and drowns foes.", cooldownTicks = DEFAULT_COOLDOWN)
|
||||||
|
@Override
|
||||||
|
public boolean copperAbility(Player player) {
|
||||||
|
createUndertoe(player, Material.COPPER_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Diamond Undertoe", description = "A swirling vortex pulls and drowns foes.", cooldownTicks = DEFAULT_COOLDOWN)
|
||||||
|
@Override
|
||||||
|
public boolean diamondAbility(Player player) {
|
||||||
|
createUndertoe(player, Material.DIAMOND_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Emerald Undertoe", description = "A swirling vortex pulls and drowns foes.", cooldownTicks = DEFAULT_COOLDOWN)
|
||||||
|
@Override
|
||||||
|
public boolean emeraldAbility(Player player) {
|
||||||
|
createUndertoe(player, Material.EMERALD_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Gold Undertoe", description = "A swirling vortex pulls and drowns foes.", cooldownTicks = DEFAULT_COOLDOWN)
|
||||||
|
@Override
|
||||||
|
public boolean goldAbility(Player player) {
|
||||||
|
createUndertoe(player, Material.GOLD_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Iron Undertoe", description = "A swirling vortex pulls and drowns foes.", cooldownTicks = DEFAULT_COOLDOWN)
|
||||||
|
@Override
|
||||||
|
public boolean ironAbility(Player player) {
|
||||||
|
createUndertoe(player, Material.IRON_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Lapis Undertoe", description = "A swirling vortex pulls and drowns foes.", cooldownTicks = DEFAULT_COOLDOWN)
|
||||||
|
@Override
|
||||||
|
public boolean lapisAbility(Player player) {
|
||||||
|
createUndertoe(player, Material.LAPIS_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Netherite Undertoe", description = "A larger, stronger, and longer-lasting vortex that fiercely pulls, damages, and disorients foes.", cooldownTicks = NETHERITE_COOLDOWN)
|
||||||
|
@Override
|
||||||
|
public boolean netheriteAbility(Player player) {
|
||||||
|
createUndertoe(player, Material.NETHERITE_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Quartz Undertoe", description = "A swirling vortex pulls and drowns foes.", cooldownTicks = DEFAULT_COOLDOWN)
|
||||||
|
@Override
|
||||||
|
public boolean quartzAbility(Player player) {
|
||||||
|
createUndertoe(player, Material.QUARTZ_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Redstone Undertoe", description = "A swirling vortex pulls and drowns foes.", cooldownTicks = DEFAULT_COOLDOWN)
|
||||||
|
@Override
|
||||||
|
public boolean redstoneAbility(Player player) {
|
||||||
|
createUndertoe(player, Material.REDSTONE_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Resin Undertoe", description = "A slightly weaker vortex that pulls and drowns foes, but recharges faster.", cooldownTicks = RESIN_COOLDOWN)
|
||||||
|
@Override
|
||||||
|
public boolean resinAbility(Player player) {
|
||||||
|
createUndertoe(player, Material.RESIN_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
package me.trouper.trimserver.server.systems.abilities.trims;
|
||||||
|
|
||||||
|
import com.destroystokyo.paper.event.player.PlayerJumpEvent;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.MaterialInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.AbstractAbility;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.PatternInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.WormEvent;
|
||||||
|
import me.trouper.trimserver.utils.TargetingUtils;
|
||||||
|
import me.trouper.trimserver.utils.visual.DisplayUtils;
|
||||||
|
import org.bukkit.*;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.BlockState;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.damage.DamageSource;
|
||||||
|
import org.bukkit.damage.DamageType;
|
||||||
|
import org.bukkit.entity.*;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimPattern;
|
||||||
|
import org.bukkit.potion.PotionEffect;
|
||||||
|
import org.bukkit.potion.PotionEffectType;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
@PatternInfo(name = "Wormsign", description = "\"Lisan al Gaib!\"")
|
||||||
|
public class DuneAbility extends AbstractAbility {
|
||||||
|
|
||||||
|
public DuneAbility() {
|
||||||
|
super(TrimPattern.DUNE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void spawnWormSign(Player owner, Location loc) {
|
||||||
|
AtomicInteger t = new AtomicInteger(0);
|
||||||
|
Bukkit.getScheduler().runTaskTimer(main.getPlugin(),(wave)->{
|
||||||
|
if (t.getAndIncrement() >= 25) {
|
||||||
|
wave.cancel();
|
||||||
|
}
|
||||||
|
DisplayUtils.wave(loc,15,1,1,(point)->{
|
||||||
|
Block block = point.getWorld().getHighestBlockAt((int) point.x(), (int) point.z());
|
||||||
|
BlockData data = block.getBlockData();
|
||||||
|
block.getWorld().playSound(block.getLocation(), Sound.BLOCK_ROOTED_DIRT_BREAK,0.1F,1);
|
||||||
|
block.getWorld().spawnParticle(Particle.BLOCK_CRUMBLE,block.getLocation().add(0,1,0),1,0.5,0,0.5, data);
|
||||||
|
|
||||||
|
TargetingUtils.areaAffect(block.getLocation(),2, target -> !target.isDead() && !main.man().trustBackend.trusts(owner,target), liv->{
|
||||||
|
liv.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS,20,4,true,false,false));
|
||||||
|
Vector pullDirection = loc.toVector().subtract(liv.getLocation().toVector()).normalize();
|
||||||
|
Vector pullVelocity = pullDirection.multiply(0.0015);
|
||||||
|
liv.setVelocity(liv.getVelocity().add(pullVelocity));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},0,10);
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
int finalI = i;
|
||||||
|
Bukkit.getScheduler().runTaskLater(main.getPlugin(),()->{
|
||||||
|
DisplayUtils.ring(loc, finalI,1,(point)->{
|
||||||
|
BlockData data = point.getWorld().getHighestBlockAt((int) point.x(), (int) point.z()).getBlockData();
|
||||||
|
BlockState state = point.getWorld().getHighestBlockAt((int) point.x(), (int) point.z()).getState();
|
||||||
|
FallingBlock block = (FallingBlock) point.getWorld().spawnEntity(point.clone().add(0,1,0), EntityType.FALLING_BLOCK);
|
||||||
|
block.setBlockData(data);
|
||||||
|
block.setBlockState(state);
|
||||||
|
block.setVelocity(new Vector(0,0.1,0));
|
||||||
|
block.setCancelDrop(true);
|
||||||
|
TargetingUtils.areaAffect(block.getLocation(),2, target -> !target.isDead() && !main.man().trustBackend.trusts(owner,target), liv->{
|
||||||
|
if (liv.getLocation().getBlock().isPassable()) {
|
||||||
|
liv.teleport(liv.getLocation().clone().add(0,-1,0));
|
||||||
|
liv.damage(5, DamageSource.builder(DamageType.IN_WALL).withDamageLocation(loc).withDirectEntity(owner).build());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},i*2+(20*2));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 15; i++) {
|
||||||
|
int finalI = i;
|
||||||
|
Bukkit.getScheduler().runTaskLater(main.getPlugin(),()->{
|
||||||
|
DisplayUtils.ring(loc, finalI,1,(point)->{
|
||||||
|
BlockData data = point.getWorld().getHighestBlockAt((int) point.x(), (int) point.z()).getBlockData();
|
||||||
|
BlockState state = point.getWorld().getHighestBlockAt((int) point.x(), (int) point.z()).getState();
|
||||||
|
FallingBlock block = (FallingBlock) point.getWorld().spawnEntity(point.clone().add(0,1,0), EntityType.FALLING_BLOCK);
|
||||||
|
block.setBlockData(data);
|
||||||
|
block.setBlockState(state);
|
||||||
|
block.setVelocity(new Vector(0,0.1,0));
|
||||||
|
block.setCancelDrop(true);
|
||||||
|
TargetingUtils.areaAffect(block.getLocation(),2, target -> !target.isDead() && !main.man().trustBackend.trusts(owner,target), liv->{
|
||||||
|
if (liv.getLocation().getBlock().isPassable()) {
|
||||||
|
liv.teleport(liv.getLocation().clone().add(0,-1,0));
|
||||||
|
liv.damage(5,DamageSource.builder(DamageType.IN_WALL).withDamageLocation(loc).withDirectEntity(owner).build());
|
||||||
|
liv.addScoreboardTag("$/TrimServer/ NoJumping");
|
||||||
|
Bukkit.getScheduler().runTaskLater(main.getPlugin(),()->{
|
||||||
|
liv.removeScoreboardTag("$/TrimServer/ NoJumping");
|
||||||
|
},20*10);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},i*2+(20*5));
|
||||||
|
}
|
||||||
|
|
||||||
|
Location worm = loc.clone().add(0,-1,0);
|
||||||
|
Bukkit.getScheduler().runTaskLater(main.getPlugin(),()->{
|
||||||
|
new WormEvent(main.getPlugin()).spawnGiantWorm(worm,7);
|
||||||
|
},20*7);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onJoin(PlayerJoinEvent e) {
|
||||||
|
if (e.getPlayer().getScoreboardTags().contains("$/TrimServer/ NoJumping")) {
|
||||||
|
e.getPlayer().getScoreboardTags().remove("$/TrimServer/ NoJumping");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onJump(PlayerJumpEvent e) {
|
||||||
|
if (e.getPlayer().getScoreboardTags().contains("$/TrimServer/ NoJumping")) {
|
||||||
|
e.setCancelled(true);
|
||||||
|
e.getPlayer().getVelocity().add(new Vector(0,-10,0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onDeath(PlayerRespawnEvent e) {
|
||||||
|
if (e.getPlayer().getScoreboardTags().contains("$/TrimServer/ NoJumping")) {
|
||||||
|
e.getPlayer().getScoreboardTags().remove("$/TrimServer/ NoJumping");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Amethyst Wormsign",description = "Call upon Shai-Hulud to destroy your enemies", cooldownTicks = 20*160)
|
||||||
|
@Override
|
||||||
|
public boolean amethystAbility(Player player) {
|
||||||
|
spawnWormSign(player,player.getLocation());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Copper Wormsign",description = "Call upon Shai-Hulud to destroy your enemies", cooldownTicks = 20*160)
|
||||||
|
@Override
|
||||||
|
public boolean copperAbility(Player player) {
|
||||||
|
spawnWormSign(player,player.getLocation());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Diamond Wormsign",description = "Call upon Shai-Hulud to destroy your enemies", cooldownTicks = 20*160)
|
||||||
|
@Override
|
||||||
|
public boolean diamondAbility(Player player) {
|
||||||
|
spawnWormSign(player,player.getLocation());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Emerald Wormsign",description = "Call upon Shai-Hulud to destroy your enemies", cooldownTicks = 20*160)
|
||||||
|
@Override
|
||||||
|
public boolean emeraldAbility(Player player) {
|
||||||
|
spawnWormSign(player,player.getLocation());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Gold Wormsign",description = "Call upon Shai-Hulud to destroy your enemies", cooldownTicks = 20*160)
|
||||||
|
@Override
|
||||||
|
public boolean goldAbility(Player player) {
|
||||||
|
spawnWormSign(player,player.getLocation());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Iron Wormsign",description = "Call upon Shai-Hulud to destroy your enemies", cooldownTicks = 20*160)
|
||||||
|
@Override
|
||||||
|
public boolean ironAbility(Player player) {
|
||||||
|
spawnWormSign(player,player.getLocation());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Lapis Wormsign",description = "Call upon Shai-Hulud to destroy your enemies", cooldownTicks = 20*160)
|
||||||
|
@Override
|
||||||
|
public boolean lapisAbility(Player player) {
|
||||||
|
spawnWormSign(player,player.getLocation());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Netherite Wormsign",description = "Call upon Shai-Hulud to destroy your enemies", cooldownTicks = 20*90)
|
||||||
|
@Override
|
||||||
|
public boolean netheriteAbility(Player player) {
|
||||||
|
spawnWormSign(player,player.getLocation());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Quartz Wormsign",description = "Call upon Shai-Hulud to destroy your enemies", cooldownTicks = 20*160)
|
||||||
|
@Override
|
||||||
|
public boolean quartzAbility(Player player) {
|
||||||
|
spawnWormSign(player,player.getLocation());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Redstone Wormsign",description = "Call upon Shai-Hulud to destroy your enemies", cooldownTicks = 20*160)
|
||||||
|
@Override
|
||||||
|
public boolean redstoneAbility(Player player) {
|
||||||
|
spawnWormSign(player,player.getLocation());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Resin Wormsign",description = "Call upon Shai-Hulud to destroy your enemies", cooldownTicks = 20*160)
|
||||||
|
@Override
|
||||||
|
public boolean resinAbility(Player player) {
|
||||||
|
spawnWormSign(player,player.getLocation());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
package me.trouper.trimserver.server.systems.abilities.trims;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.MaterialInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.AbstractAbility;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.PatternInfo;
|
||||||
|
import me.trouper.trimserver.utils.SoundPlayer;
|
||||||
|
import me.trouper.trimserver.utils.TargetingUtils;
|
||||||
|
import me.trouper.trimserver.utils.visual.BlockDisplayRaytracer;
|
||||||
|
import me.trouper.trimserver.utils.visual.CustomDisplayRaytracer;
|
||||||
|
import org.bukkit.*;
|
||||||
|
import org.bukkit.damage.DamageSource;
|
||||||
|
import org.bukkit.damage.DamageType;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimPattern;
|
||||||
|
import org.bukkit.potion.PotionEffect;
|
||||||
|
import org.bukkit.potion.PotionEffectType;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
@PatternInfo(name = "Eye of Power", description = "Allows you to see players hidden with the host trim. Includes variants.")
|
||||||
|
public class EyeAbility extends AbstractAbility {
|
||||||
|
|
||||||
|
public EyeAbility() {
|
||||||
|
super(TrimPattern.EYE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void eyeLasers(Player player, Material beam,Material glow, long durationSeconds) {
|
||||||
|
AtomicInteger timer = new AtomicInteger(0);
|
||||||
|
Bukkit.getScheduler().runTaskTimer(main.getPlugin(),(task)->{
|
||||||
|
if (timer.getAndIncrement() >= durationSeconds * 20) {
|
||||||
|
task.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Location chestLocation = player.getLocation();
|
||||||
|
chestLocation.setY(chestLocation.getY() + (player.getHeight() / 2) + 0.1);
|
||||||
|
double radians = Math.toRadians(player.getBodyYaw());
|
||||||
|
|
||||||
|
double x = -Math.sin(radians);
|
||||||
|
double z = Math.cos(radians);
|
||||||
|
|
||||||
|
Vector chestDirection = new Vector(x,0,z).normalize();
|
||||||
|
|
||||||
|
Location focus = laser(player,chestLocation,chestDirection,60);
|
||||||
|
BlockDisplayRaytracer.trace(beam,chestLocation,focus,0.2,2);
|
||||||
|
BlockDisplayRaytracer.trace(glow,chestLocation,focus,0.4,2);
|
||||||
|
},0,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Location laser(Player owner, Location start, Vector direction, double distance) {
|
||||||
|
return CustomDisplayRaytracer.trace(start,direction,distance,1,point ->{
|
||||||
|
SoundPlayer hissSound = new SoundPlayer(point.getLoc(), Sound.BLOCK_LAVA_EXTINGUISH, 1, 1);
|
||||||
|
|
||||||
|
return TargetingUtils.areaAffect(point.getLoc(),1,target -> !target.isDead() && !main.man().trustBackend.trusts(owner,target), liv->{
|
||||||
|
hissSound.playWithin(10);
|
||||||
|
|
||||||
|
int tick = liv.getNoDamageTicks();
|
||||||
|
int maxTick = liv.getMaximumNoDamageTicks();
|
||||||
|
liv.setNoDamageTicks(0);
|
||||||
|
liv.setMaximumNoDamageTicks(0);
|
||||||
|
liv.damage(0.5,DamageSource.builder(DamageType.STING).withDamageLocation(owner.getLocation()).withDirectEntity(owner).build());
|
||||||
|
liv.setNoDamageTicks(tick);
|
||||||
|
liv.setMaximumNoDamageTicks(maxTick);
|
||||||
|
|
||||||
|
liv.setFireTicks(20);
|
||||||
|
liv.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS,20,1,true,false,false));
|
||||||
|
liv.getWorld().spawnParticle(Particle.LAVA, point.getLoc(), 1, 0.5, 0.5, 0.5, 0);
|
||||||
|
}) || !point.getBlock().isPassable();
|
||||||
|
}).getLoc();
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Amethyst Laser beam", description = "Shoot lasers from the eye on the chestpiece for 5 seconds", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean amethystAbility(Player player) {
|
||||||
|
eyeLasers(player,Material.PURPLE_CONCRETE_POWDER,Material.MAGENTA_STAINED_GLASS,5);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Copper Laser beam", description = "Shoot lasers from the eye on the chestpiece for 5 seconds", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean copperAbility(Player player) {
|
||||||
|
eyeLasers(player,Material.LIME_TERRACOTTA,Material.GREEN_STAINED_GLASS,5);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Diamond Laser beam", description = "Shoot lasers from the eye on the chestpiece for 5 seconds", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean diamondAbility(Player player) {
|
||||||
|
eyeLasers(player,Material.LIGHT_BLUE_CONCRETE_POWDER,Material.LIGHT_BLUE_STAINED_GLASS,5);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Emerald Laser beam", description = "Shoot lasers from the eye on the chestpiece for 5 seconds", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean emeraldAbility(Player player) {
|
||||||
|
eyeLasers(player,Material.LIME_CONCRETE_POWDER,Material.LIME_STAINED_GLASS,5);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Gold Laser beam", description = "Shoot lasers from the eye on the chestpiece for 5 seconds", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean goldAbility(Player player) {
|
||||||
|
eyeLasers(player,Material.YELLOW_TERRACOTTA,Material.YELLOW_STAINED_GLASS,5);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Iron Laser beam", description = "Shoot lasers from the eye on the chestpiece for 5 seconds", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean ironAbility(Player player) {
|
||||||
|
eyeLasers(player,Material.LIGHT_GRAY_CONCRETE_POWDER,Material.LIGHT_GRAY_STAINED_GLASS,5);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Lapis Laser beam", description = "Shoot lasers from the eye on the chestpiece for 5 seconds", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean lapisAbility(Player player) {
|
||||||
|
eyeLasers(player,Material.BLUE_CONCRETE,Material.BLUE_STAINED_GLASS,5);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Netherite Laser beam", description = "Shoot lasers from the eye on the chestpiece for 10 seconds", cooldownTicks = 20 * 40)
|
||||||
|
@Override
|
||||||
|
public boolean netheriteAbility(Player player) {
|
||||||
|
eyeLasers(player,Material.BLACK_CONCRETE,Material.BLACK_STAINED_GLASS,20);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Quartz Laser beam", description = "Shoot lasers from the eye on the chestpiece for 5 seconds", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean quartzAbility(Player player) {
|
||||||
|
eyeLasers(player,Material.WHITE_CONCRETE_POWDER,Material.WHITE_STAINED_GLASS,5);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Redstone Laser beam", description = "Shoot lasers from the eye on the chestpiece for 5 seconds", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean redstoneAbility(Player player) {
|
||||||
|
eyeLasers(player,Material.RED_CONCRETE,Material.RED_STAINED_GLASS,5);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Resin Laser beam", description = "Shoot lasers from the eye on the chestpiece for 5 seconds", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean resinAbility(Player player) {
|
||||||
|
eyeLasers(player,Material.ORANGE_CONCRETE_POWDER,Material.ORANGE_STAINED_GLASS,5);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,254 @@
|
|||||||
|
package me.trouper.trimserver.server.systems.abilities.trims;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.MaterialInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.PatternInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.AbstractAbility;
|
||||||
|
import me.trouper.trimserver.utils.SoundPlayer;
|
||||||
|
import me.trouper.trimserver.utils.TargetingUtils;
|
||||||
|
import me.trouper.trimserver.utils.Text;
|
||||||
|
import org.bukkit.*;
|
||||||
|
import org.bukkit.entity.*;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.entity.EntityTargetEvent;
|
||||||
|
import org.bukkit.event.player.PlayerToggleSneakEvent;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimPattern;
|
||||||
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
@PatternInfo(name = "Wind Rider", description = "Summon a breeze to ride through the sky")
|
||||||
|
public class FlowAbility extends AbstractAbility {
|
||||||
|
|
||||||
|
private final Map<UUID, BukkitTask> activeRiders = new HashMap<>();
|
||||||
|
private final Map<UUID, Breeze> activeBreezes = new HashMap<>();
|
||||||
|
|
||||||
|
public FlowAbility() {
|
||||||
|
super(TrimPattern.FLOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void spawnRideableBreeze(Player player, int duration, Material material) {
|
||||||
|
if (activeRiders.containsKey(player.getUniqueId())) {
|
||||||
|
activeRiders.get(player.getUniqueId()).cancel();
|
||||||
|
activeRiders.remove(player.getUniqueId());
|
||||||
|
|
||||||
|
if (activeBreezes.containsKey(player.getUniqueId())) {
|
||||||
|
activeBreezes.get(player.getUniqueId()).remove();
|
||||||
|
activeBreezes.remove(player.getUniqueId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
World world = player.getWorld();
|
||||||
|
Location spawnLoc = player.getLocation().add(0, 1, 0);
|
||||||
|
|
||||||
|
Breeze breeze = (Breeze) world.spawnEntity(spawnLoc, EntityType.BREEZE);
|
||||||
|
breeze.customName(Text.color(player.getName() + "'s Wind Rider"));
|
||||||
|
breeze.setCustomNameVisible(true);
|
||||||
|
breeze.addPassenger(player);
|
||||||
|
breeze.setAI(true);
|
||||||
|
breeze.setInvulnerable(true);
|
||||||
|
breeze.addScoreboardTag("$/TrimServer/ Temp");
|
||||||
|
|
||||||
|
activeBreezes.put(player.getUniqueId(), breeze);
|
||||||
|
|
||||||
|
world.spawnParticle(Particle.CLOUD, spawnLoc, 50, 1, 1, 1, 0.2);
|
||||||
|
world.spawnParticle(Particle.BLOCK_CRUMBLE, spawnLoc, 20, 0.5, 0.5, 0.5, 0.1, material.createBlockData());
|
||||||
|
SoundPlayer summonSound = new SoundPlayer(spawnLoc, Sound.ENTITY_BREEZE_SHOOT, 1.0f, 0.8f);
|
||||||
|
summonSound.playWithin(10);
|
||||||
|
|
||||||
|
BukkitTask task = new BukkitRunnable() {
|
||||||
|
final long endTime = System.currentTimeMillis() + (duration * 1000L);
|
||||||
|
final AtomicInteger attackCooldown = new AtomicInteger(0);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (System.currentTimeMillis() > endTime || !player.isOnline() ||
|
||||||
|
breeze.isDead() || breeze.getPassengers().isEmpty()) {
|
||||||
|
endBreezeRide(player, breeze);
|
||||||
|
cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector playerDirection = player.getLocation().clone().getDirection().multiply(0.5);
|
||||||
|
breeze.setVelocity(playerDirection);
|
||||||
|
|
||||||
|
breeze.getWorld().spawnParticle(Particle.BLOCK_CRUMBLE, breeze.getLocation(), 10, 0.5, 0.5, 0.5, 0.05, material.createBlockData());
|
||||||
|
breeze.getWorld().spawnParticle(Particle.CLOUD, breeze.getLocation(), 3, 0.2, 0.2, 0.2, 0.05);
|
||||||
|
|
||||||
|
if (attackCooldown.getAndDecrement() <= 0) {
|
||||||
|
attackCooldown.set(20);
|
||||||
|
try {
|
||||||
|
attackNearby(breeze, player);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
if (!e.getMessage().equals("x not finite")) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TargetingUtils.areaAffect(breeze.getLocation(),1.5,entity -> !entity.equals(player) && !main.man().trustBackend.trusts(player,entity) && !breeze.equals(entity),target->{
|
||||||
|
launchEntity(target);
|
||||||
|
world.spawnParticle(Particle.EXPLOSION, target.getLocation(), 10, 0.5, 0.5, 0.5, 0.1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}.runTaskTimer(main.getPlugin(), 0L, 1L);
|
||||||
|
|
||||||
|
activeRiders.put(player.getUniqueId(), task);
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTaskLater(main.getPlugin(), () -> {
|
||||||
|
if (activeRiders.containsKey(player.getUniqueId())) {
|
||||||
|
endBreezeRide(player, breeze);
|
||||||
|
}
|
||||||
|
}, duration * 20L);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attackNearby(Breeze breeze, Player owner) {
|
||||||
|
Location breezeLocation = breeze.getLocation();
|
||||||
|
World world = breeze.getWorld();
|
||||||
|
|
||||||
|
TargetingUtils.areaAffect(breezeLocation,7,entity-> !entity.equals(owner) && !main.man().trustBackend.trusts(owner,entity),(target)->{
|
||||||
|
Vector direction = target.getEyeLocation().subtract(breeze.getEyeLocation()).toVector().normalize();
|
||||||
|
|
||||||
|
BreezeWindCharge windCharge = (BreezeWindCharge) world.spawnEntity(breezeLocation, EntityType.BREEZE_WIND_CHARGE);
|
||||||
|
windCharge.setShooter(breeze);
|
||||||
|
windCharge.setVelocity(direction.multiply(0.5));
|
||||||
|
|
||||||
|
SoundPlayer attackSound = new SoundPlayer(breezeLocation, Sound.ENTITY_BREEZE_SHOOT, 1.0f, 1.2f);
|
||||||
|
attackSound.playWithin(5);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void launchEntity(Entity target) {
|
||||||
|
Vector launchVector = new Vector(0, 2, 0);
|
||||||
|
target.setVelocity(target.getVelocity().add(launchVector));
|
||||||
|
|
||||||
|
target.getWorld().spawnParticle(
|
||||||
|
Particle.CLOUD,
|
||||||
|
target.getLocation(),
|
||||||
|
30, 0.3, 0.3, 0.3, 0.1
|
||||||
|
);
|
||||||
|
|
||||||
|
SoundPlayer launchSound = new SoundPlayer(target.getLocation(), Sound.ENTITY_BREEZE_SHOOT, 1.0f, 0.6f);
|
||||||
|
launchSound.playWithin(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void endBreezeRide(Player player, Breeze breeze) {
|
||||||
|
if (activeRiders.containsKey(player.getUniqueId())) {
|
||||||
|
activeRiders.get(player.getUniqueId()).cancel();
|
||||||
|
activeRiders.remove(player.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeBreezes.containsKey(player.getUniqueId())) {
|
||||||
|
activeBreezes.remove(player.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
Location loc = breeze.getLocation();
|
||||||
|
breeze.getWorld().spawnParticle(Particle.CLOUD, loc, 50, 1, 1, 1, 0.2);
|
||||||
|
SoundPlayer despawnSound = new SoundPlayer(loc, Sound.ENTITY_BREEZE_DEATH, 1.0f, 1.0f);
|
||||||
|
despawnSound.playWithin(10);
|
||||||
|
|
||||||
|
breeze.eject();
|
||||||
|
breeze.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerSneak(PlayerToggleSneakEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
|
||||||
|
if (event.isSneaking() && activeRiders.containsKey(player.getUniqueId())) {
|
||||||
|
if (activeBreezes.containsKey(player.getUniqueId())) {
|
||||||
|
endBreezeRide(player, activeBreezes.get(player.getUniqueId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onBreezeTarget(EntityTargetEvent event) {
|
||||||
|
if (event.getEntity() instanceof Breeze breeze) {
|
||||||
|
if (activeBreezes.containsValue(breeze)) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Amethyst Wind Rider", description = "Summon an amethyst-infused breeze to ride", cooldownTicks = 20 * 60)
|
||||||
|
@Override
|
||||||
|
public boolean amethystAbility(Player player) {
|
||||||
|
spawnRideableBreeze(player, 30, Material.AMETHYST_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Copper Wind Rider", description = "Summon a copper-infused breeze to ride", cooldownTicks = 20 * 60)
|
||||||
|
@Override
|
||||||
|
public boolean copperAbility(Player player) {
|
||||||
|
spawnRideableBreeze(player, 30, Material.COPPER_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Diamond Wind Rider", description = "Summon a diamond-infused breeze to ride", cooldownTicks = 20 * 60)
|
||||||
|
@Override
|
||||||
|
public boolean diamondAbility(Player player) {
|
||||||
|
spawnRideableBreeze(player, 30, Material.DIAMOND_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Emerald Wind Rider", description = "Summon an emerald-infused breeze to ride", cooldownTicks = 20 * 60)
|
||||||
|
@Override
|
||||||
|
public boolean emeraldAbility(Player player) {
|
||||||
|
spawnRideableBreeze(player, 30, Material.EMERALD_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Gold Wind Rider", description = "Summon a gold-infused breeze to ride", cooldownTicks = 20 * 60)
|
||||||
|
@Override
|
||||||
|
public boolean goldAbility(Player player) {
|
||||||
|
spawnRideableBreeze(player, 30, Material.GOLD_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Iron Wind Rider", description = "Summon an iron-infused breeze to ride", cooldownTicks = 20 * 60)
|
||||||
|
@Override
|
||||||
|
public boolean ironAbility(Player player) {
|
||||||
|
spawnRideableBreeze(player, 30, Material.IRON_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Lapis Wind Rider", description = "Summon a lapis-infused breeze to ride", cooldownTicks = 20 * 60)
|
||||||
|
@Override
|
||||||
|
public boolean lapisAbility(Player player) {
|
||||||
|
spawnRideableBreeze(player, 30, Material.LAPIS_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Netherite Wind Rider", description = "Summon a powerful netherite-infused breeze to ride", cooldownTicks = 20 * 90)
|
||||||
|
@Override
|
||||||
|
public boolean netheriteAbility(Player player) {
|
||||||
|
spawnRideableBreeze(player, 45, Material.NETHERITE_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Quartz Wind Rider", description = "Summon a quartz-infused breeze to ride", cooldownTicks = 20 * 60)
|
||||||
|
@Override
|
||||||
|
public boolean quartzAbility(Player player) {
|
||||||
|
spawnRideableBreeze(player, 30, Material.QUARTZ_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Redstone Wind Rider", description = "Summon a redstone-infused breeze to ride", cooldownTicks = 20 * 60)
|
||||||
|
@Override
|
||||||
|
public boolean redstoneAbility(Player player) {
|
||||||
|
spawnRideableBreeze(player, 30, Material.REDSTONE_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Resin Wind Rider", description = "Summon a resin-infused breeze to ride", cooldownTicks = 20 * 60)
|
||||||
|
@Override
|
||||||
|
public boolean resinAbility(Player player) {
|
||||||
|
spawnRideableBreeze(player, 30, Material.RESIN_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package me.trouper.trimserver.server.systems.abilities.trims;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.MaterialInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.AbstractAbility;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.PatternInfo;
|
||||||
|
import me.trouper.trimserver.utils.Text;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimPattern;
|
||||||
|
import org.bukkit.potion.PotionEffect;
|
||||||
|
import org.bukkit.potion.PotionEffectType;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
@PatternInfo(name = "The Host", description = "Disappear into the shadows like Gatsby.")
|
||||||
|
public class HostAbility extends AbstractAbility {
|
||||||
|
|
||||||
|
public HostAbility() {
|
||||||
|
super(TrimPattern.HOST);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void makeInvisible(Player invis, long seconds) {
|
||||||
|
invis.addScoreboardTag("$/TrimServer/ Invisible");
|
||||||
|
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||||
|
if (main.man().trustBackend.trusts(invis,player)) continue;
|
||||||
|
player.hidePlayer(main.getPlugin(),invis);
|
||||||
|
AtomicInteger timer = new AtomicInteger();
|
||||||
|
Bukkit.getScheduler().runTaskTimer(main.getPlugin(),(task)->{
|
||||||
|
if (timer.getAndIncrement() >= seconds) {
|
||||||
|
if (!player.isOnline() || !invis.isOnline()) return;
|
||||||
|
player.showPlayer(main.getPlugin(),invis);
|
||||||
|
task.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
invis.sendActionBar(Text.color("&aYou are hidden from other players!"));
|
||||||
|
},0,20);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onJoin(PlayerJoinEvent e) {
|
||||||
|
if (e.getPlayer().getScoreboardTags().contains("$/TrimServer/ Invisible")) {
|
||||||
|
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||||
|
player.showPlayer(main.getPlugin(),e.getPlayer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Amethyst ", description = "Grants true invisibility for 20 seconds but makes your attacks weaker", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean amethystAbility(Player player) {
|
||||||
|
makeInvisible(player,20);
|
||||||
|
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*15,0,true,false,true));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Copper ", description = "Grants true invisibility for 20 seconds but makes your attacks weaker", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean copperAbility(Player player) {
|
||||||
|
makeInvisible(player,20);
|
||||||
|
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*15,0,true,false,true));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Diamond ", description = "Grants true invisibility for 20 seconds but makes your attacks weaker", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean diamondAbility(Player player) {
|
||||||
|
makeInvisible(player,20);
|
||||||
|
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*15,0,true,false,true));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Emerald ", description = "Grants true invisibility for 20 seconds but makes your attacks weaker", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean emeraldAbility(Player player) {
|
||||||
|
makeInvisible(player,20);
|
||||||
|
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*15,0,true,false,true));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Gold ", description = "Grants true invisibility for 20 seconds but makes your attacks weaker", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean goldAbility(Player player) {
|
||||||
|
makeInvisible(player,20);
|
||||||
|
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*15,0,true,false,true));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Iron ", description = "Grants true invisibility for 20 seconds but makes your attacks weaker", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean ironAbility(Player player) {
|
||||||
|
makeInvisible(player,20);
|
||||||
|
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*15,0,true,false,true));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Lapis ", description = "Grants true invisibility for 20 seconds but makes your attacks weaker", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean lapisAbility(Player player) {
|
||||||
|
makeInvisible(player,20);
|
||||||
|
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*15,0,true,false,true));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Netherite ", description = "Grants true invisibility for 10 seconds and makes your attacks stronger", cooldownTicks = 20 * 15)
|
||||||
|
@Override
|
||||||
|
public boolean netheriteAbility(Player player) {
|
||||||
|
makeInvisible(player,10);
|
||||||
|
player.addPotionEffect(new PotionEffect(PotionEffectType.STRENGTH,20*15,1,true,false,true));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Quartz ", description = "Grants true invisibility for 20 seconds but makes your attacks weaker", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean quartzAbility(Player player) {
|
||||||
|
makeInvisible(player,20);
|
||||||
|
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*15,0,true,false,true));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Redstone ", description = "Grants true invisibility for 20 seconds but makes your attacks weaker", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean redstoneAbility(Player player) {
|
||||||
|
makeInvisible(player,20);
|
||||||
|
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*15,0,true,false,true));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Resin ", description = "Grants true invisibility for 20 seconds but makes your attacks weaker", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean resinAbility(Player player) {
|
||||||
|
makeInvisible(player,20);
|
||||||
|
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*15,0,true,false,true));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
package me.trouper.trimserver.server.systems.abilities.trims;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.MaterialInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.AbstractAbility;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.PatternInfo;
|
||||||
|
import me.trouper.trimserver.utils.TargetingUtils;
|
||||||
|
import me.trouper.trimserver.utils.visual.BlockDisplayRaytracer;
|
||||||
|
import me.trouper.trimserver.utils.visual.DisplayUtils;
|
||||||
|
import org.bukkit.*;
|
||||||
|
import org.bukkit.entity.*;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||||
|
import org.bukkit.event.player.PlayerKickEvent;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimPattern;
|
||||||
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@PatternInfo(name = "Unexpected Levitation", description = "Raiser? I hardly know her!")
|
||||||
|
public class RaiserAbility extends AbstractAbility {
|
||||||
|
|
||||||
|
public RaiserAbility() {
|
||||||
|
super(TrimPattern.RAISER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<UUID, BukkitTask> levitationTasks = new HashMap<>();
|
||||||
|
|
||||||
|
public void raiseEntity(LivingEntity target) {
|
||||||
|
if (target == null) return;
|
||||||
|
|
||||||
|
UUID uuid = target.getUniqueId();
|
||||||
|
|
||||||
|
if (levitationTasks.containsKey(uuid)) return;
|
||||||
|
|
||||||
|
|
||||||
|
Location initialLocation = target.getLocation();
|
||||||
|
double initialHealth = target.getHealth();
|
||||||
|
double healthThreshold = initialHealth / 2.0;
|
||||||
|
double targetY = initialLocation.getY() + 4.0;
|
||||||
|
int frequency = 2;
|
||||||
|
int duration = 20 * 30;
|
||||||
|
|
||||||
|
// --- Start Up Effects ---
|
||||||
|
target.getWorld().playSound(target.getLocation(), Sound.ENTITY_BAT_TAKEOFF, 0.8f, 1.0f);
|
||||||
|
BlockDisplay hook = BlockDisplayRaytracer.trace(Material.IRON_BLOCK,target.getEyeLocation(),target.getLocation().add(0,128,0),0.3,60);
|
||||||
|
DisplayUtils.wave(target.getLocation(),2,Color.WHITE,1,0.1);
|
||||||
|
// Give an initial upward push
|
||||||
|
target.setVelocity(new Vector(0, 0.4, 0));
|
||||||
|
|
||||||
|
// --- The Main Repeating Task (Holding, Checking, Effects) ---
|
||||||
|
BukkitTask task = new BukkitRunnable() {
|
||||||
|
int ticksElapsed = 0;
|
||||||
|
final int durationTicks = duration / frequency;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// Check if player is still valid and online
|
||||||
|
if (target.isDead()) {
|
||||||
|
endLevitation(uuid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location currentLocation = target.getLocation();
|
||||||
|
double currentHealth = target.getHealth();
|
||||||
|
|
||||||
|
// --- Check Conditions to End ---
|
||||||
|
// 1. Health threshold reached
|
||||||
|
if (currentHealth <= healthThreshold) {
|
||||||
|
endLevitation(uuid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Time limit reached
|
||||||
|
if (ticksElapsed >= durationTicks) {
|
||||||
|
endLevitation(uuid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Hold Player at Target Height ---
|
||||||
|
if (currentLocation.getY() >= targetY) {
|
||||||
|
Vector currentVelocity = target.getVelocity();
|
||||||
|
target.setVelocity(new Vector(currentVelocity.getX(), 0, currentVelocity.getZ()));
|
||||||
|
if (hook != null && !hook.isDead()) BlockDisplayRaytracer.transform(hook,target.getEyeLocation(),target.getLocation().add(0,128,0),0.3);
|
||||||
|
} else {
|
||||||
|
target.setVelocity(new Vector(0, 0.5, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
target.getWorld().spawnParticle(Particle.CLOUD, currentLocation.add(0, 1.5, 0), 1, 0.3, 0.8, 0.3, 0.02);
|
||||||
|
target.getWorld().spawnParticle(Particle.WITCH, currentLocation.add(0, 1.0, 0), 10, 0.5, 0.5, 0.5, 0.01);
|
||||||
|
|
||||||
|
if (ticksElapsed % 20 == 0) {
|
||||||
|
target.getWorld().playSound(target.getLocation(), Sound.ENTITY_BAT_LOOP, 0.3f, 1.2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ticksElapsed % 7 == 0) {
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
Bat bat = (Bat) target.getWorld().spawnEntity(target.getLocation().add(0,target.getHeight()/2,0), EntityType.BAT);
|
||||||
|
Bukkit.getScheduler().runTaskLater(main.getPlugin(),task->{
|
||||||
|
if (bat != null && !bat.isDead()) bat.remove();
|
||||||
|
},15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ticksElapsed++;
|
||||||
|
}
|
||||||
|
}.runTaskTimer(main.getPlugin(), 0, frequency);
|
||||||
|
|
||||||
|
levitationTasks.putIfAbsent(target.getUniqueId(),task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void endLevitation(UUID playerUUID) {
|
||||||
|
BukkitTask task = levitationTasks.remove(playerUUID);
|
||||||
|
if (task != null && !task.isCancelled()) {
|
||||||
|
Player player = Bukkit.getPlayer(playerUUID);
|
||||||
|
if (player != null && player.isOnline()) {
|
||||||
|
player.getWorld().playSound(player.getLocation(), Sound.ENTITY_BAT_DEATH, 0.8f, 0.8f);
|
||||||
|
player.getWorld().spawnParticle(Particle.CLOUD, player.getLocation().add(0, 1, 0), 30, 0.8, 0.8, 0.8, 0.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onKick(PlayerKickEvent e) {
|
||||||
|
if (levitationTasks.containsKey(e.getPlayer().getUniqueId())) {
|
||||||
|
e.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onDeath(PlayerDeathEvent e) {
|
||||||
|
if (levitationTasks.containsKey(e.getPlayer().getUniqueId())) {
|
||||||
|
endLevitation(e.getPlayer().getUniqueId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Amethyst ", description = "Pins your enemy 4 blocks into the air for 30 seconds, or until they loose half their current health", cooldownTicks = 20 * 45)
|
||||||
|
@Override
|
||||||
|
public boolean amethystAbility(Player player) {
|
||||||
|
return TargetingUtils.areaAffect(player.getLocation(),15,target -> !main.man().trustBackend.trusts(player,target), this::raiseEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Copper ", description = "Pins your enemy 4 blocks into the air for 30 seconds, or until they loose half their current health", cooldownTicks = 20 * 45)
|
||||||
|
@Override
|
||||||
|
public boolean copperAbility(Player player) {
|
||||||
|
return TargetingUtils.areaAffect(player.getLocation(),15,target -> !main.man().trustBackend.trusts(player,target), this::raiseEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Diamond ", description = "Pins your enemy 4 blocks into the air for 30 seconds, or until they loose half their current health", cooldownTicks = 20 * 45)
|
||||||
|
@Override
|
||||||
|
public boolean diamondAbility(Player player) {
|
||||||
|
return TargetingUtils.areaAffect(player.getLocation(),15,target -> !main.man().trustBackend.trusts(player,target), this::raiseEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Emerald ", description = "Pins your enemy 4 blocks into the air for 30 seconds, or until they loose half their current health", cooldownTicks = 20 * 45)
|
||||||
|
@Override
|
||||||
|
public boolean emeraldAbility(Player player) {
|
||||||
|
return TargetingUtils.areaAffect(player.getLocation(),15,target -> !main.man().trustBackend.trusts(player,target), this::raiseEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Gold ", description = "Pins your enemy 4 blocks into the air for 30 seconds, or until they loose half their current health", cooldownTicks = 20 * 45)
|
||||||
|
@Override
|
||||||
|
public boolean goldAbility(Player player) {
|
||||||
|
return TargetingUtils.areaAffect(player.getLocation(),15,target -> !main.man().trustBackend.trusts(player,target), this::raiseEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Iron ", description = "Pins your enemy 4 blocks into the air for 30 seconds, or until they loose half their current health", cooldownTicks = 20 * 45)
|
||||||
|
@Override
|
||||||
|
public boolean ironAbility(Player player) {
|
||||||
|
return TargetingUtils.areaAffect(player.getLocation(),15,target -> !main.man().trustBackend.trusts(player,target), this::raiseEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Lapis ", description = "Pins your enemy 4 blocks into the air for 30 seconds, or until they loose half their current health", cooldownTicks = 20 * 45)
|
||||||
|
@Override
|
||||||
|
public boolean lapisAbility(Player player) {
|
||||||
|
return TargetingUtils.areaAffect(player.getLocation(),15,target -> !main.man().trustBackend.trusts(player,target), this::raiseEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Netherite ", description = "Pins your enemy 4 blocks into the air for 30 seconds, or until they loose half their current health", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean netheriteAbility(Player player) {
|
||||||
|
return TargetingUtils.areaAffect(player.getLocation(),15,target -> !main.man().trustBackend.trusts(player,target), this::raiseEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Quartz ", description = "Pins your enemy 4 blocks into the air for 30 seconds, or until they loose half their current health", cooldownTicks = 20 * 45)
|
||||||
|
@Override
|
||||||
|
public boolean quartzAbility(Player player) {
|
||||||
|
return TargetingUtils.areaAffect(player.getLocation(),15,target -> !main.man().trustBackend.trusts(player,target), this::raiseEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Redstone ", description = "Pins your enemy 4 blocks into the air for 30 seconds, or until they loose half their current health", cooldownTicks = 20 * 45)
|
||||||
|
@Override
|
||||||
|
public boolean redstoneAbility(Player player) {
|
||||||
|
return TargetingUtils.areaAffect(player.getLocation(),15,target -> !main.man().trustBackend.trusts(player,target), this::raiseEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Resin ", description = "Pins your enemy 4 blocks into the air for 30 seconds, or until they loose half their current health", cooldownTicks = 20 * 45)
|
||||||
|
@Override
|
||||||
|
public boolean resinAbility(Player player) {
|
||||||
|
return TargetingUtils.areaAffect(player.getLocation(),15,target -> !main.man().trustBackend.trusts(player,target), this::raiseEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,277 @@
|
|||||||
|
package me.trouper.trimserver.server.systems.abilities.trims;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.MaterialInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.AbstractAbility;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.PatternInfo;
|
||||||
|
import me.trouper.trimserver.utils.SoundPlayer;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.utils.TargetingUtils;
|
||||||
|
import me.trouper.trimserver.utils.visual.DisplayUtils;
|
||||||
|
import org.bukkit.*;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.entity.*;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimPattern;
|
||||||
|
import org.bukkit.potion.PotionEffect;
|
||||||
|
import org.bukkit.potion.PotionEffectType;
|
||||||
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
import org.bukkit.util.BoundingBox;
|
||||||
|
import org.bukkit.util.Transformation;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
import org.joml.Quaternionf;
|
||||||
|
|
||||||
|
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
@PatternInfo(name = "Nether Eruption", description = "Summons sharp spikes from the ground that damage enemies accompanied by a blast of toxic ash.")
|
||||||
|
public class RibAbility extends AbstractAbility {
|
||||||
|
|
||||||
|
private final Random random = new Random();
|
||||||
|
|
||||||
|
public RibAbility() {
|
||||||
|
super(TrimPattern.RIB);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeResinEruption(Player caster, Material activatingTrimMaterial, int spikeCount, double areaRadius, int activeDurationTicks, Material groundParticleMaterial) {
|
||||||
|
World world = caster.getWorld();
|
||||||
|
if (world == null) return;
|
||||||
|
|
||||||
|
Location playerCenterLoc = caster.getLocation();
|
||||||
|
|
||||||
|
caster.addPotionEffect(new PotionEffect(PotionEffectType.SPEED,15*20,2,true));
|
||||||
|
caster.addPotionEffect(new PotionEffect(PotionEffectType.STRENGTH,10*20,1,true));
|
||||||
|
caster.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION,5*20,2,true));
|
||||||
|
|
||||||
|
DisplayUtils.sphereWave(caster.getEyeLocation(),10,0.5,2,2,(point)->{
|
||||||
|
point.getWorld().spawnParticle(Particle.FLAME,point,1,0.1,0.1,0.1,0.01);
|
||||||
|
point.getWorld().spawnParticle(Particle.SMOKE,point,1,0.1,0.1,0.1,0.1);
|
||||||
|
point.getWorld().spawnParticle(Particle.CAMPFIRE_COSY_SMOKE,point,1,0.1,0.1,0.1,0.1);
|
||||||
|
TargetingUtils.areaAffect(point,1, liv->!liv.equals(caster) && !liv.isDead() && !main.man().trustBackend.trusts(caster,liv), target->{
|
||||||
|
target.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS,20*20,1,true,false,false));
|
||||||
|
target.setFireTicks(20*20);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new SoundPlayer(playerCenterLoc, Sound.ENTITY_WARDEN_DIG, 1.5f, 0.7f).playWithin(15);
|
||||||
|
new SoundPlayer(playerCenterLoc, Sound.BLOCK_NETHERRACK_PLACE, 1.5f, 0.8f).playWithin(15);
|
||||||
|
world.spawnParticle(Particle.LAVA, playerCenterLoc, (int) (30 * areaRadius), areaRadius / 2.0, 0.3, areaRadius / 2.0, 0.05);
|
||||||
|
world.spawnParticle(Particle.ASH, playerCenterLoc, (int) (50 * areaRadius), areaRadius, 0.5, areaRadius, 0.1);
|
||||||
|
world.spawnParticle(Particle.LARGE_SMOKE, playerCenterLoc, (int) (20 * areaRadius), areaRadius / 1.5, 0.5, areaRadius / 1.5, 0.05);
|
||||||
|
|
||||||
|
final double spikeVisualHeight = 3.5;
|
||||||
|
final double spikeVisualThickness = 0.5;
|
||||||
|
final int spikeRiseAnimationTicks = 10;
|
||||||
|
final List<BlockDisplay> activeSpikes = new ArrayList<>();
|
||||||
|
|
||||||
|
BlockData spikeBlockData = activatingTrimMaterial.createBlockData();
|
||||||
|
BlockData groundBlockData = groundParticleMaterial.createBlockData();
|
||||||
|
|
||||||
|
for (int i = 0; i < spikeCount; i++) {
|
||||||
|
double angle = random.nextDouble() * 2 * Math.PI;
|
||||||
|
double currentRandomRadius = random.nextDouble() * areaRadius;
|
||||||
|
double dx = Math.cos(angle) * currentRandomRadius;
|
||||||
|
double dz = Math.sin(angle) * currentRandomRadius;
|
||||||
|
|
||||||
|
Location spikeBaseCenter = playerCenterLoc.clone().add(dx, 0, dz);
|
||||||
|
|
||||||
|
// Find the ground for the spike
|
||||||
|
Block highestBlock = world.getHighestBlockAt(spikeBaseCenter.getBlockX(), spikeBaseCenter.getBlockZ());
|
||||||
|
Location groundSurfaceLoc = highestBlock.getLocation().add(0.5, 1.0, 0.5);
|
||||||
|
|
||||||
|
int attempts = 0;
|
||||||
|
while (attempts < 5 && (!groundSurfaceLoc.clone().subtract(0,1,0).getBlock().getType().isSolid() || groundSurfaceLoc.getBlock().getType().isSolid())) {
|
||||||
|
groundSurfaceLoc.subtract(0,1,0);
|
||||||
|
attempts++;
|
||||||
|
if (groundSurfaceLoc.getY() < world.getMinHeight()) {
|
||||||
|
groundSurfaceLoc.setY(world.getMinHeight() +1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!groundSurfaceLoc.clone().subtract(0,1,0).getBlock().getType().isSolid()) {
|
||||||
|
Location tempLoc = new Location(world, spikeBaseCenter.getX(), caster.getLocation().getY(), spikeBaseCenter.getZ());
|
||||||
|
groundSurfaceLoc = world.getHighestBlockAt(tempLoc).getLocation().add(0.5,1.0,0.5);
|
||||||
|
if (!groundSurfaceLoc.clone().subtract(0,1,0).getBlock().getType().isSolid()){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final Location finalSpikeBase = groundSurfaceLoc.clone();
|
||||||
|
final Location spikeTipLoc = finalSpikeBase.clone().add(0, spikeVisualHeight, 0);
|
||||||
|
|
||||||
|
BlockDisplay spikeDisplay = world.spawn(finalSpikeBase, BlockDisplay.class, bd -> {
|
||||||
|
bd.setBlock(spikeBlockData);
|
||||||
|
bd.setBrightness(new Display.Brightness(10, 10));
|
||||||
|
bd.setInterpolationDelay(0);
|
||||||
|
bd.setInterpolationDuration(spikeRiseAnimationTicks);
|
||||||
|
bd.addScoreboardTag("$/TrimServer/ Temp");
|
||||||
|
|
||||||
|
Transformation transform = bd.getTransformation();
|
||||||
|
transform.getScale().set(new Vector3f((float) spikeVisualThickness, 0.01f, (float) spikeVisualThickness));
|
||||||
|
|
||||||
|
transform.getTranslation().set(
|
||||||
|
-(float) spikeVisualThickness / 2f,
|
||||||
|
0f,
|
||||||
|
-(float) spikeVisualThickness / 2f
|
||||||
|
);
|
||||||
|
bd.setTransformation(transform);
|
||||||
|
});
|
||||||
|
activeSpikes.add(spikeDisplay);
|
||||||
|
|
||||||
|
long delay = (long)(i * (random.nextDouble() * 1.5 + 0.5));
|
||||||
|
|
||||||
|
new BukkitRunnable() {
|
||||||
|
int ticksElapsed = 0;
|
||||||
|
boolean damageDealt = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (ticksElapsed == 0) {
|
||||||
|
Transformation targetTransform = spikeDisplay.getTransformation();
|
||||||
|
targetTransform.getScale().set(new Vector3f((float) spikeVisualThickness, (float) spikeVisualHeight, (float) spikeVisualThickness));
|
||||||
|
spikeDisplay.setTransformation(targetTransform);
|
||||||
|
|
||||||
|
new SoundPlayer(finalSpikeBase, Sound.BLOCK_NETHERRACK_BREAK, 1.2f, 0.6f + random.nextFloat() * 0.4f).playWithin(15);
|
||||||
|
new SoundPlayer(finalSpikeBase, Sound.ENTITY_BLAZE_SHOOT, 0.8f, 1.5f + random.nextFloat() * 0.5f).playWithin(15);
|
||||||
|
world.spawnParticle(Particle.BLOCK_CRUMBLE, finalSpikeBase.clone().add(0,0.1,0), 30, 0.4, 0.2, 0.4, groundBlockData);
|
||||||
|
world.spawnParticle(Particle.FLAME, finalSpikeBase.clone().add(0,0.2,0), 5, 0.3,0.1,0.3, 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ticksElapsed >= spikeRiseAnimationTicks / 2 && ticksElapsed <= spikeRiseAnimationTicks + 4 && !damageDealt) {
|
||||||
|
TargetingUtils.areaAffect(finalSpikeBase,1,target -> !target.equals(caster) && !main.man().trustBackend.trusts(caster,target),target -> {
|
||||||
|
damageDealt = true;
|
||||||
|
|
||||||
|
target.damage(7.0, caster);
|
||||||
|
Vector knockDir = new Vector(random.nextGaussian() * 0.15, 0.9 + random.nextDouble()*0.2, random.nextGaussian() * 0.15);
|
||||||
|
target.setVelocity(target.getVelocity().add(knockDir));
|
||||||
|
new SoundPlayer(target.getLocation(), Sound.ENTITY_PLAYER_HURT_ON_FIRE, 1.0f, 1.0f).playWithin(15);
|
||||||
|
world.spawnParticle(Particle.LAVA, target.getEyeLocation(), 8, 0.2, 0.2, 0.2, 0);
|
||||||
|
world.spawnParticle(Particle.ASH, target.getLocation(), 20, 0.5,0.5,0.5,0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ticksElapsed > spikeRiseAnimationTicks && ticksElapsed < activeDurationTicks - (20)) {
|
||||||
|
if (random.nextInt(4) == 0) {
|
||||||
|
world.spawnParticle(Particle.FLAME, spikeTipLoc.clone().add(random.nextGaussian() * 0.15, 0, random.nextGaussian() * 0.15), 1, 0, 0, 0, 0.005);
|
||||||
|
world.spawnParticle(Particle.CRIMSON_SPORE, spikeTipLoc.clone().add(random.nextGaussian() * 0.2, random.nextDouble()*0.3, random.nextGaussian() * 0.2),1,0,0,0,0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ticksElapsed >= activeDurationTicks) {
|
||||||
|
Transformation currentTransform = spikeDisplay.getTransformation();
|
||||||
|
currentTransform.getScale().set(new Vector3f((float)spikeVisualThickness, 0.01f, (float)spikeVisualThickness));
|
||||||
|
spikeDisplay.setInterpolationDuration(spikeRiseAnimationTicks);
|
||||||
|
spikeDisplay.setTransformation(currentTransform);
|
||||||
|
new SoundPlayer(finalSpikeBase, Sound.BLOCK_BASALT_BREAK, 1.0f, 0.7f).playWithin(15);
|
||||||
|
|
||||||
|
new BukkitRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (!spikeDisplay.isDead()) {
|
||||||
|
spikeDisplay.remove();
|
||||||
|
world.spawnParticle(Particle.BLOCK_CRUMBLE, finalSpikeBase, 20, 0.3,0.1,0.3, spikeBlockData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.runTaskLater(main.getPlugin(), spikeRiseAnimationTicks +1);
|
||||||
|
this.cancel();
|
||||||
|
}
|
||||||
|
ticksElapsed++;
|
||||||
|
}
|
||||||
|
}.runTaskTimer(main.getPlugin(), delay, 1L);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failsafe cleanup for all created displays
|
||||||
|
new BukkitRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
for (BlockDisplay bd : activeSpikes) {
|
||||||
|
if (bd != null && !bd.isDead()) {
|
||||||
|
bd.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
activeSpikes.clear();
|
||||||
|
}
|
||||||
|
}.runTaskLater(main.getPlugin(), activeDurationTicks + spikeRiseAnimationTicks + 40L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Eruption (Amethyst)", description = "Erupts spikes of amethyst", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean amethystAbility(Player player) {
|
||||||
|
executeResinEruption(player, Material.AMETHYST_BLOCK, 10, 5.5, 20 * 5, Material.SMOOTH_BASALT);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Eruption (Copper)", description = "Erupts spikes of oxidized copper", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean copperAbility(Player player) {
|
||||||
|
executeResinEruption(player, Material.RAW_COPPER, 10, 5.0, 20 * 5, Material.TUFF);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Eruption (Diamond)", description = "Erupts spikes of diamond", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean diamondAbility(Player player) {
|
||||||
|
executeResinEruption(player, Material.DEEPSLATE_DIAMOND_ORE, 12, 6.0, 20 * 6, Material.DEEPSLATE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Eruption (Emerald)", description = "Erupts spikes of emerald", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean emeraldAbility(Player player) {
|
||||||
|
executeResinEruption(player, Material.DEEPSLATE_EMERALD_ORE, 12, 6.0, 20 * 6, Material.MOSS_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Eruption (Gold)", description = "Erupts spikes of gold", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean goldAbility(Player player) {
|
||||||
|
executeResinEruption(player, Material.DEEPSLATE_GOLD_ORE, 10, 5.0, 20 * 5, Material.NETHER_GOLD_ORE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Eruption (Iron)", description = "Erupts spikes of iron", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean ironAbility(Player player) {
|
||||||
|
executeResinEruption(player, Material.RAW_IRON_BLOCK, 11, 5.5, 20 * 5, Material.RAW_IRON_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Eruption (Lapis)", description = "Erupts spikes of lapis", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean lapisAbility(Player player) {
|
||||||
|
executeResinEruption(player, Material.LAPIS_BLOCK, 10, 5.0, 20 * 5, Material.CLAY);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Eruption (Netherite)", description = "Erupts deadly blackstone spikes", cooldownTicks = 20 * 20)
|
||||||
|
@Override
|
||||||
|
public boolean netheriteAbility(Player player) {
|
||||||
|
executeResinEruption(player, Material.BLACKSTONE, 20, 10.0, 20 * 8, Material.BLACKSTONE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Eruption (Quartz)", description = "Erupts spikes of quartz", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean quartzAbility(Player player) {
|
||||||
|
executeResinEruption(player, Material.QUARTZ_BLOCK, 11, 5.5, 20 * 5, Material.NETHER_QUARTZ_ORE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Eruption (Redstone)", description = "Erupts energized redstone spikes", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean redstoneAbility(Player player) {
|
||||||
|
executeResinEruption(player, Material.REDSTONE_BLOCK, 11, 5.5, 20 * 5, Material.REDSTONE_ORE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Eruption (Resin)", description = "Erupts spikes of hardened resin from the ground", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean resinAbility(Player player) {
|
||||||
|
executeResinEruption(player, Material.RESIN_BLOCK, 12, 6.0, 20 * 6, Material.NETHERRACK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
package me.trouper.trimserver.server.systems.abilities.trims;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.MaterialInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.AbstractAbility;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.PatternInfo;
|
||||||
|
import me.trouper.trimserver.utils.SoundPlayer;
|
||||||
|
import me.trouper.trimserver.utils.TargetingUtils;
|
||||||
|
import me.trouper.trimserver.utils.Text;
|
||||||
|
import me.trouper.trimserver.utils.visual.BlockDisplayRaytracer;
|
||||||
|
import me.trouper.trimserver.utils.visual.CustomDisplayRaytracer;
|
||||||
|
import org.bukkit.*;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.damage.DamageSource;
|
||||||
|
import org.bukkit.damage.DamageType;
|
||||||
|
import org.bukkit.entity.*;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimPattern;
|
||||||
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
import org.bukkit.util.Transformation;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
@PatternInfo(name = "Build Sentry", description = "\"Meet the Engineer.\" Includes Variants.")
|
||||||
|
public class SentryAbility extends AbstractAbility {
|
||||||
|
|
||||||
|
public SentryAbility() {
|
||||||
|
super(TrimPattern.SENTRY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void spawnSentry(Location loc, Player owner, Material legsMat, Material turretMat, int ammo, long secondsAlive, long cooldownTicks) {
|
||||||
|
World w = loc.getWorld();
|
||||||
|
loc = w.getHighestBlockAt((int) loc.x(), (int) loc.z()).getLocation().add(0,1,0);
|
||||||
|
Location spawn = loc.clone().subtract(0,2,0);
|
||||||
|
List<Entity> turretParts = new ArrayList<>();
|
||||||
|
|
||||||
|
// 1) Spawn the rotating turret (dispenser model)
|
||||||
|
BlockDisplay turret = w.spawn(spawn.clone().add(0.5, 1.5, 0.5), BlockDisplay.class, display -> {
|
||||||
|
display.setBlock(turretMat.createBlockData());
|
||||||
|
display.setBrightness(new Display.Brightness(15, 15));
|
||||||
|
display.setInterpolationDelay(0);
|
||||||
|
display.setInterpolationDuration(2);
|
||||||
|
display.addScoreboardTag("$/TrimServer/ Temp");
|
||||||
|
|
||||||
|
// Get the current transformation
|
||||||
|
Transformation transformation = display.getTransformation();
|
||||||
|
// Set the translation to center the pivot point
|
||||||
|
transformation.getTranslation().set(-0.5f, -0.5f, -0.5f);
|
||||||
|
// Apply the modified transformation back to the display
|
||||||
|
display.setTransformation(transformation);
|
||||||
|
});
|
||||||
|
|
||||||
|
turretParts.add(turret);
|
||||||
|
|
||||||
|
Bat dummy = w.spawn(turret.getLocation(),Bat.class,bat->{
|
||||||
|
bat.setInvisible(true);
|
||||||
|
bat.setInvulnerable(true);
|
||||||
|
bat.customName(Text.color("%s's Sentry\n".formatted(owner.getName())));
|
||||||
|
bat.setAI(false);
|
||||||
|
bat.addScoreboardTag("$/TrimServer/ Temp");
|
||||||
|
});
|
||||||
|
|
||||||
|
turretParts.add(dummy);
|
||||||
|
|
||||||
|
// 2) Legs: four converging from a 1×1 square around base up to head-pole
|
||||||
|
double legHeight = 1.5;
|
||||||
|
double halfSize = 0.6; // distance from center
|
||||||
|
List<BlockDisplay> stand = new ArrayList<>();
|
||||||
|
for (double dx : new double[]{ -halfSize, +halfSize }) {
|
||||||
|
for (double dz : new double[]{ -halfSize, +halfSize }) {
|
||||||
|
Location start = spawn.clone().add(dx + 0.5, 0.1, dz + 0.5);
|
||||||
|
Location end = spawn.clone().add(0.5, legHeight, 0.5);
|
||||||
|
stand.add(BlockDisplayRaytracer.trace(
|
||||||
|
legsMat,
|
||||||
|
start,
|
||||||
|
end.toVector().subtract(start.toVector()),
|
||||||
|
0.1, // leg thickness
|
||||||
|
start.distance(end),
|
||||||
|
20 * secondsAlive + 1
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
turretParts.addAll(stand);
|
||||||
|
|
||||||
|
// 3) Ammo display above head
|
||||||
|
TextDisplay meter = w.spawn(spawn.clone().add(0.5, 2.5, 0.5), TextDisplay.class, t -> {
|
||||||
|
t.setBillboard(Display.Billboard.CENTER);
|
||||||
|
t.setRotation(0, 90);
|
||||||
|
t.setGravity(false);
|
||||||
|
t.setSeeThrough(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
turretParts.add(meter);
|
||||||
|
|
||||||
|
final Location finalLoc = loc;
|
||||||
|
final int maxAmmo = ammo;
|
||||||
|
|
||||||
|
BukkitRunnable turretRuntime = new BukkitRunnable() {
|
||||||
|
int chamber = maxAmmo;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (chamber <= 0 || turret.isDead() || meter.isDead()) {
|
||||||
|
cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String bar = Text.generateProgressBar(10, maxAmmo, chamber);
|
||||||
|
meter.text(Text.color("%s's Sentry\n".formatted(owner.getName()) + "Ammo " + bar));
|
||||||
|
|
||||||
|
Optional<Player> target = TargetingUtils.getClosestPlayer(finalLoc,15,p -> !p.isDead() && !p.equals(owner) && !main.man().trustBackend.trusts(owner,p));
|
||||||
|
|
||||||
|
if (target.isPresent()) {
|
||||||
|
Player tracked = target.get();
|
||||||
|
Vector toEye = tracked.getLocation()
|
||||||
|
.add(0, tracked.getEyeHeight(), 0)
|
||||||
|
.toVector()
|
||||||
|
.subtract(turret.getLocation().toVector());
|
||||||
|
Vector dir = toEye.clone().normalize();
|
||||||
|
|
||||||
|
float yaw = (float)(Math.toDegrees(Math.atan2(dir.getZ(), dir.getX())) - 90 + 180);
|
||||||
|
float pitch = (float)(Math.toDegrees(Math.asin(dir.getY())));
|
||||||
|
turret.setRotation(yaw, pitch);
|
||||||
|
|
||||||
|
|
||||||
|
CustomDisplayRaytracer.trace(turret.getLocation(),dir,60,0.5,point -> {
|
||||||
|
List<Entity> hits = new ArrayList<>(w.getNearbyEntities(point.getLoc(), 0.5,0.5,0.5, entity -> {
|
||||||
|
return entity instanceof LivingEntity living && !(living instanceof Bat) && !living.isDead() && living != owner;
|
||||||
|
}));
|
||||||
|
hits.forEach(t -> {
|
||||||
|
if (t instanceof LivingEntity liv) {
|
||||||
|
BlockData blockData = Material.RED_WOOL.createBlockData();
|
||||||
|
SoundPlayer hitSound = new SoundPlayer(t.getLocation(), Sound.ITEM_DYE_USE,3,2);
|
||||||
|
int tick = liv.getNoDamageTicks();
|
||||||
|
int maxTick = liv.getMaximumNoDamageTicks();
|
||||||
|
liv.setNoDamageTicks(0);
|
||||||
|
liv.setMaximumNoDamageTicks(0);
|
||||||
|
liv.damage(1, DamageSource.builder(DamageType.ARROW).withDirectEntity(dummy).withDamageLocation(finalLoc).build());
|
||||||
|
liv.setNoDamageTicks(tick);
|
||||||
|
liv.setMaximumNoDamageTicks(maxTick);
|
||||||
|
hitSound.playWithin(20);
|
||||||
|
w.spawnParticle(Particle.BLOCK_CRUMBLE,t.getLocation().add(0,1,0),30,0.25F,1,0.25F,blockData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
w.spawnParticle(Particle.CRIT, point.getLoc(), 1, 0,0,0, 0);
|
||||||
|
w.spawnParticle(Particle.SMOKE, point.getLoc(), 1, 0,0,0, 0.1F);
|
||||||
|
return !hits.isEmpty() || !point.getBlock().isPassable();
|
||||||
|
});
|
||||||
|
chamber--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void cancel() throws IllegalStateException {
|
||||||
|
super.cancel();
|
||||||
|
|
||||||
|
Location particleLoc = finalLoc.clone().add(0.5,0.5,0.5);
|
||||||
|
AtomicInteger stepsTaken = new AtomicInteger(0);
|
||||||
|
Bukkit.getScheduler().runTaskTimer(main.getPlugin(),task->{
|
||||||
|
if (stepsTaken.getAndIncrement() >= 4) {
|
||||||
|
turretParts.forEach(Entity::remove);
|
||||||
|
task.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
w.spawnParticle(Particle.BLOCK_CRUMBLE, particleLoc, 20, 0.5, 0.2, 0.2, 0.05, legsMat.createBlockData());
|
||||||
|
SoundPlayer build = new SoundPlayer(finalLoc,Sound.BLOCK_PISTON_CONTRACT,1,2);
|
||||||
|
build.playWithin(10);
|
||||||
|
|
||||||
|
|
||||||
|
turretParts.forEach(part->{
|
||||||
|
part.teleport(part.getLocation().clone().add(0,-0.5,0));
|
||||||
|
});
|
||||||
|
},0,10);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AtomicInteger stepsTaken = new AtomicInteger(0);
|
||||||
|
Location particleLoc = finalLoc.clone().add(0.5,0.5,0.5);
|
||||||
|
Bukkit.getScheduler().runTaskTimer(main.getPlugin(),task->{
|
||||||
|
if (stepsTaken.getAndIncrement() >= 4) {
|
||||||
|
turretRuntime.runTaskTimer(main.getPlugin(), 0L, cooldownTicks);
|
||||||
|
task.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
w.spawnParticle(Particle.BLOCK_CRUMBLE, particleLoc, 20, 0.5, 0.2, 0.5, 0.05, legsMat.createBlockData());
|
||||||
|
SoundPlayer build = new SoundPlayer(finalLoc,Sound.BLOCK_PISTON_EXTEND,1,2);
|
||||||
|
build.playWithin(10);
|
||||||
|
|
||||||
|
turretParts.forEach(part->{
|
||||||
|
part.teleport(part.getLocation().clone().add(0,0.5,0));
|
||||||
|
});
|
||||||
|
},0,10);
|
||||||
|
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTaskLater(main.getPlugin(),()->{
|
||||||
|
if (turretRuntime == null || turretRuntime.isCancelled()) return;
|
||||||
|
turretRuntime.cancel();
|
||||||
|
},20 * secondsAlive);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Amethyst ", description = "Spawns a sentry which shoots the nearest player", cooldownTicks = 20 * 10)
|
||||||
|
@Override
|
||||||
|
public boolean amethystAbility(Player player) {
|
||||||
|
spawnSentry(player.getLocation(),player,Material.AMETHYST_BLOCK,Material.DISPENSER,50,60,2);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Copper ", description = "Spawns a sentry which shoots the nearest player", cooldownTicks = 20 * 10)
|
||||||
|
@Override
|
||||||
|
public boolean copperAbility(Player player) {
|
||||||
|
spawnSentry(player.getLocation(),player,Material.COPPER_BLOCK,Material.DISPENSER,50,60,2);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Diamond ", description = "Spawns a sentry which shoots the nearest player", cooldownTicks = 20 * 10)
|
||||||
|
@Override
|
||||||
|
public boolean diamondAbility(Player player) {
|
||||||
|
spawnSentry(player.getLocation(),player,Material.DIAMOND_BLOCK,Material.DISPENSER,50,60,2);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Emerald ", description = "Spawns a sentry which shoots the nearest player", cooldownTicks = 20 * 10)
|
||||||
|
@Override
|
||||||
|
public boolean emeraldAbility(Player player) {
|
||||||
|
spawnSentry(player.getLocation(),player,Material.EMERALD_BLOCK,Material.DISPENSER,50,60,2);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Gold ", description = "Spawns a sentry which shoots the nearest player", cooldownTicks = 20 * 10)
|
||||||
|
@Override
|
||||||
|
public boolean goldAbility(Player player) {
|
||||||
|
spawnSentry(player.getLocation(),player,Material.GOLD_BLOCK,Material.DISPENSER,50,60,2);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Iron ", description = "Spawns a sentry which shoots the nearest player", cooldownTicks = 20 * 10)
|
||||||
|
@Override
|
||||||
|
public boolean ironAbility(Player player) {
|
||||||
|
spawnSentry(player.getLocation(),player,Material.IRON_BLOCK,Material.DISPENSER,50,60,2);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Lapis ", description = "Spawns a sentry which shoots the nearest player", cooldownTicks = 20 * 10)
|
||||||
|
@Override
|
||||||
|
public boolean lapisAbility(Player player) {
|
||||||
|
spawnSentry(player.getLocation(),player,Material.LAPIS_BLOCK,Material.DISPENSER,50,60,2);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Netherite Sentry", description = "Spawns a supercharged sentry which absolutely shreds the nearest player", cooldownTicks = 20 * 15)
|
||||||
|
@Override
|
||||||
|
public boolean netheriteAbility(Player player) {
|
||||||
|
spawnSentry(player.getLocation(),player,Material.NETHERITE_BLOCK,Material.CRAFTER,100,60,1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Quartz ", description = "Spawns a sentry which shoots the nearest player", cooldownTicks = 20 * 10)
|
||||||
|
@Override
|
||||||
|
public boolean quartzAbility(Player player) {
|
||||||
|
spawnSentry(player.getLocation(),player,Material.QUARTZ_BLOCK,Material.DISPENSER,50,60,2);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Redstone ", description = "Spawns a sentry which shoots the nearest player", cooldownTicks = 20 * 10)
|
||||||
|
@Override
|
||||||
|
public boolean redstoneAbility(Player player) {
|
||||||
|
spawnSentry(player.getLocation(),player,Material.REDSTONE_BLOCK,Material.DISPENSER,50,60,2);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Resin ", description = "Spawns a sentry which shoots the nearest player", cooldownTicks = 20 * 10)
|
||||||
|
@Override
|
||||||
|
public boolean resinAbility(Player player) {
|
||||||
|
spawnSentry(player.getLocation(),player,Material.RESIN_BLOCK,Material.DISPENSER,50,60,2);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,324 @@
|
|||||||
|
package me.trouper.trimserver.server.systems.abilities.trims;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.MaterialInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.PatternInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.AbstractAbility;
|
||||||
|
import me.trouper.trimserver.utils.SoundPlayer;
|
||||||
|
import me.trouper.trimserver.utils.TargetingUtils;
|
||||||
|
import me.trouper.trimserver.utils.Text;
|
||||||
|
import org.bukkit.*;
|
||||||
|
import org.bukkit.attribute.Attribute;
|
||||||
|
import org.bukkit.attribute.AttributeModifier;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.entity.BlockDisplay;
|
||||||
|
import org.bukkit.entity.Display;
|
||||||
|
import org.bukkit.entity.LivingEntity;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.entity.EntityDamageEvent;
|
||||||
|
import org.bukkit.inventory.EquipmentSlot;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimPattern;
|
||||||
|
import org.bukkit.potion.PotionEffect;
|
||||||
|
import org.bukkit.potion.PotionEffectType;
|
||||||
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
import org.bukkit.util.Transformation;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
import org.joml.Quaternionf;
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@PatternInfo(name = "Terra Shell", description = "Temporarily encase yourself in a protective shell of stone, then unleash a shrapnel burst.")
|
||||||
|
public class ShaperAbility extends AbstractAbility implements Listener {
|
||||||
|
|
||||||
|
public final Map<UUID, BukkitTask> activeShellTasks = new ConcurrentHashMap<>();
|
||||||
|
private final Map<UUID, List<BlockDisplay>> activeShellDisplays = new ConcurrentHashMap<>();
|
||||||
|
private final Map<UUID, AttributeModifier> knockbackModifiers = new ConcurrentHashMap<>();
|
||||||
|
private final Map<UUID, Integer> originalFireTicks = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private static final int BASE_DURATION_TICKS = 8 * 20;
|
||||||
|
private static final double BASE_SHATTER_DAMAGE = 6.0;
|
||||||
|
private static final double BASE_SHATTER_RADIUS = 4.0;
|
||||||
|
private static final int RESISTANCE_AMPLIFIER = 2;
|
||||||
|
private static final int SLOWNESS_AMPLIFIER = 1;
|
||||||
|
|
||||||
|
// Cooldowns
|
||||||
|
private static final int DEFAULT_COOLDOWN = 20 * 60;
|
||||||
|
private static final int NETHERITE_COOLDOWN = 20 * 90;
|
||||||
|
private static final int RESIN_COOLDOWN = 20 * 40;
|
||||||
|
|
||||||
|
// Netherite Modifiers
|
||||||
|
private static final double NETHERITE_DURATION_MULTIPLIER = 1.3;
|
||||||
|
private static final double NETHERITE_SHATTER_DAMAGE_MULTIPLIER = 1.5;
|
||||||
|
private static final int NETHERITE_RESISTANCE_AMPLIFIER_BONUS = 1; // Total Resistance IV
|
||||||
|
private static final double NETHERITE_SHATTER_RADIUS_MULTIPLIER = 1.2;
|
||||||
|
|
||||||
|
public ShaperAbility() {
|
||||||
|
super(TrimPattern.SHAPER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void activateTerraShell(Player player, Material blockMaterialForShell) {
|
||||||
|
UUID playerUUID = player.getUniqueId();
|
||||||
|
|
||||||
|
if (activeShellTasks.containsKey(playerUUID)) return;
|
||||||
|
|
||||||
|
World world = player.getWorld();
|
||||||
|
List<BlockDisplay> shellParts = new ArrayList<>();
|
||||||
|
activeShellDisplays.put(playerUUID, shellParts);
|
||||||
|
|
||||||
|
new SoundPlayer(player.getLocation(), Sound.BLOCK_STONE_PLACE, 1.1f, 0.6f).playWithin(15);
|
||||||
|
new SoundPlayer(player.getLocation(), Sound.BLOCK_ROOTED_DIRT_STEP, 1.2f, 0.7f).playWithin(15);
|
||||||
|
|
||||||
|
BlockData shellBlockData = blockMaterialForShell.createBlockData();
|
||||||
|
float shellPartScale = 0.65f;
|
||||||
|
int numShellParts = 12;
|
||||||
|
float orbitRadius = 1.2f;
|
||||||
|
|
||||||
|
for (int i = 0; i < numShellParts; i++) {
|
||||||
|
double angle = ((double) i / numShellParts) * 2 * Math.PI;
|
||||||
|
double yInitialOffset = (Math.random() * 0.8) + 0.5; // Start around player's mid-section
|
||||||
|
Vector offset = new Vector(Math.cos(angle) * orbitRadius, yInitialOffset, Math.sin(angle) * orbitRadius);
|
||||||
|
Location partLoc = player.getLocation().add(offset);
|
||||||
|
|
||||||
|
BlockDisplay bd = world.spawn(partLoc, BlockDisplay.class, display -> {
|
||||||
|
display.setBlock(shellBlockData);
|
||||||
|
display.setTransformation(new Transformation(
|
||||||
|
new Vector3f(-shellPartScale / 2, -shellPartScale / 2, -shellPartScale / 2),
|
||||||
|
new Quaternionf().rotateY((float) (Math.random() * Math.PI * 2)).rotateX((float) (Math.random() * Math.PI * 2)),
|
||||||
|
new Vector3f(shellPartScale, shellPartScale, shellPartScale),
|
||||||
|
new Quaternionf()
|
||||||
|
));
|
||||||
|
display.setInterpolationDelay(-1); // Start interpolating immediately
|
||||||
|
display.setInterpolationDuration(2); // Smooth movement over 2 ticks
|
||||||
|
display.setTeleportDuration(2);
|
||||||
|
display.setBrightness(new Display.Brightness(world.getBlockAt(partLoc).getLightFromSky(), world.getBlockAt(partLoc).getLightFromBlocks()));
|
||||||
|
display.setGravity(false);
|
||||||
|
display.addScoreboardTag("$/TrimServer/ Temp");
|
||||||
|
});
|
||||||
|
shellParts.add(bd);
|
||||||
|
}
|
||||||
|
|
||||||
|
int duration = BASE_DURATION_TICKS;
|
||||||
|
double shatterDamage = BASE_SHATTER_DAMAGE;
|
||||||
|
double shatterRadius = BASE_SHATTER_RADIUS;
|
||||||
|
int resistanceAmplifier = RESISTANCE_AMPLIFIER;
|
||||||
|
int slownessAmplifier = SLOWNESS_AMPLIFIER;
|
||||||
|
|
||||||
|
if (blockMaterialForShell == Material.NETHERITE_BLOCK) {
|
||||||
|
duration = (int) (duration * NETHERITE_DURATION_MULTIPLIER);
|
||||||
|
shatterDamage *= NETHERITE_SHATTER_DAMAGE_MULTIPLIER;
|
||||||
|
shatterRadius *= NETHERITE_SHATTER_RADIUS_MULTIPLIER;
|
||||||
|
resistanceAmplifier += NETHERITE_RESISTANCE_AMPLIFIER_BONUS;
|
||||||
|
} else if (blockMaterialForShell == Material.RESIN_BLOCK) {
|
||||||
|
slownessAmplifier = 0;
|
||||||
|
if (slownessAmplifier < 0) slownessAmplifier = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.addPotionEffect(new PotionEffect(PotionEffectType.RESISTANCE, duration, resistanceAmplifier, false, true, true));
|
||||||
|
if (slownessAmplifier >=0) {
|
||||||
|
player.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, duration, slownessAmplifier, false, false, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
originalFireTicks.put(playerUUID, player.getFireTicks());
|
||||||
|
if (player.getFireTicks() > 0) player.setFireTicks(0);
|
||||||
|
|
||||||
|
final int finalDuration = duration;
|
||||||
|
final double finalShatterDamage = shatterDamage;
|
||||||
|
final double finalShatterRadius = shatterRadius;
|
||||||
|
|
||||||
|
BukkitTask task = new BukkitRunnable() {
|
||||||
|
int ticksElapsed = 0;
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (!player.isOnline() || ticksElapsed >= finalDuration) {
|
||||||
|
shatterShell(player, finalShatterDamage, finalShatterRadius, blockMaterialForShell);
|
||||||
|
cleanupShellProperties(playerUUID, player);
|
||||||
|
cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BlockDisplay> currentParts = activeShellDisplays.get(playerUUID);
|
||||||
|
if (currentParts == null) {
|
||||||
|
cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < currentParts.size(); i++) {
|
||||||
|
BlockDisplay bd = currentParts.get(i);
|
||||||
|
if (bd == null || !bd.isValid()) continue;
|
||||||
|
|
||||||
|
double angle = (((double) i / currentParts.size()) * 2 * Math.PI) + Math.toRadians(ticksElapsed * 12); // Spin
|
||||||
|
double yOffset = Math.sin(Math.toRadians(ticksElapsed * 8 + i * 45)) * 0.4 + 1.0; // Bobbing, centered around player's height
|
||||||
|
|
||||||
|
Vector offset = new Vector(Math.cos(angle) * orbitRadius, yOffset, Math.sin(angle) * orbitRadius);
|
||||||
|
Location targetPartLoc = player.getLocation().clone().add(offset);
|
||||||
|
|
||||||
|
bd.teleport(targetPartLoc);
|
||||||
|
bd.setBrightness(new Display.Brightness(
|
||||||
|
world.getBlockAt(player.getLocation()).getLightFromSky(),
|
||||||
|
world.getBlockAt(player.getLocation()).getLightFromBlocks()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ticksElapsed % 15 == 0) {
|
||||||
|
world.spawnParticle(Particle.CRIT, player.getLocation().add(0, 1, 0), 5, 0.4, 0.4, 0.4, 0.01);
|
||||||
|
new SoundPlayer(player.getLocation(), Sound.BLOCK_GRINDSTONE_USE, 0.4f, 0.8f + (float)Math.random() * 0.3f).playWithin(10);
|
||||||
|
}
|
||||||
|
ticksElapsed++;
|
||||||
|
}
|
||||||
|
}.runTaskTimer(main.getPlugin(), 0L, 1L);
|
||||||
|
activeShellTasks.put(playerUUID, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shatterShell(Player player, double damage, double radius, Material shellMaterial) {
|
||||||
|
Location shatterCenter = player.getLocation().add(0, 1, 0);
|
||||||
|
World world = player.getWorld();
|
||||||
|
|
||||||
|
new SoundPlayer(shatterCenter, Sound.BLOCK_GLASS_BREAK, 1.2f, 0.6f).playWithin(20);
|
||||||
|
new SoundPlayer(shatterCenter, Sound.ENTITY_ZOMBIE_BREAK_WOODEN_DOOR, 1.0f, 0.8f).playWithin(20);
|
||||||
|
world.spawnParticle(Particle.EXPLOSION_EMITTER, shatterCenter, 5, 0.5,0.5,0.5,0.1);
|
||||||
|
|
||||||
|
if (shellMaterial != null && shellMaterial.isBlock()) {
|
||||||
|
world.spawnParticle(Particle.BLOCK_CRUMBLE, shatterCenter, 150, radius * 0.6, radius * 0.6, radius * 0.6, 0.15, shellMaterial.createBlockData());
|
||||||
|
} else {
|
||||||
|
world.spawnParticle(Particle.CRIT, shatterCenter, 150, radius * 0.6, radius * 0.6, radius * 0.6, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TargetingUtils.areaAffect(shatterCenter,radius,target -> !target.equals(player) && !target.isDead() && !main.man().trustBackend.trusts(player,target),target -> {
|
||||||
|
target.damage(damage, player);
|
||||||
|
Vector knockbackDir = target.getLocation().toVector().subtract(shatterCenter.toVector()).normalize();
|
||||||
|
knockbackDir.setY(Math.max(0.25, knockbackDir.getY() * 0.4 + 0.35));
|
||||||
|
target.setVelocity(target.getVelocity().add(knockbackDir.multiply(0.6 + damage * 0.08)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cleanupShellProperties(UUID playerUUID, Player player) {
|
||||||
|
activeShellTasks.remove(playerUUID);
|
||||||
|
|
||||||
|
if (player != null && player.isOnline()) {
|
||||||
|
player.removePotionEffect(PotionEffectType.RESISTANCE);
|
||||||
|
player.removePotionEffect(PotionEffectType.SLOWNESS);
|
||||||
|
|
||||||
|
if(originalFireTicks.containsKey(playerUUID)) {
|
||||||
|
player.setFireTicks(originalFireTicks.get(playerUUID));
|
||||||
|
originalFireTicks.remove(playerUUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
AttributeModifier kbModifier = knockbackModifiers.remove(playerUUID);
|
||||||
|
if (kbModifier != null) {
|
||||||
|
try {
|
||||||
|
Objects.requireNonNull(player.getAttribute(Attribute.KNOCKBACK_RESISTANCE)).removeModifier(kbModifier);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BlockDisplay> displays = activeShellDisplays.remove(playerUUID);
|
||||||
|
if (displays != null) {
|
||||||
|
for (BlockDisplay bd : displays) {
|
||||||
|
if (bd != null && bd.isValid()) {
|
||||||
|
bd.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerDamageInShell(EntityDamageEvent event) {
|
||||||
|
if (event.getEntity() instanceof Player player) {
|
||||||
|
UUID playerUUID = player.getUniqueId();
|
||||||
|
if (activeShellTasks.containsKey(playerUUID)) {
|
||||||
|
new SoundPlayer(player.getLocation(), Sound.ITEM_SHIELD_BLOCK, 1.0f, 0.7f + (float)Math.random()*0.5f).playWithin(5);
|
||||||
|
if (player.getFireTicks() > 0 && (
|
||||||
|
event.getCause() == EntityDamageEvent.DamageCause.FIRE ||
|
||||||
|
event.getCause() == EntityDamageEvent.DamageCause.FIRE_TICK ||
|
||||||
|
event.getCause() == EntityDamageEvent.DamageCause.LAVA)
|
||||||
|
) {
|
||||||
|
player.setFireTicks(0);
|
||||||
|
event.setCancelled(true);
|
||||||
|
new SoundPlayer(player.getLocation(), Sound.BLOCK_FIRE_EXTINGUISH, 1.0f, 1.0f).playWithin(5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Amethyst Terra Shell", description = "Protective Amethyst shell, shatters on expiry.", cooldownTicks = DEFAULT_COOLDOWN)
|
||||||
|
@Override
|
||||||
|
public boolean amethystAbility(Player player) {
|
||||||
|
activateTerraShell(player, Material.AMETHYST_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Copper Terra Shell", description = "Protective Copper shell, shatters on expiry.", cooldownTicks = DEFAULT_COOLDOWN)
|
||||||
|
@Override
|
||||||
|
public boolean copperAbility(Player player) {
|
||||||
|
activateTerraShell(player, Material.COPPER_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Diamond Terra Shell", description = "Protective Diamond shell, shatters on expiry.", cooldownTicks = DEFAULT_COOLDOWN)
|
||||||
|
@Override
|
||||||
|
public boolean diamondAbility(Player player) {
|
||||||
|
activateTerraShell(player, Material.DIAMOND_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Emerald Terra Shell", description = "Protective Emerald shell, shatters on expiry.", cooldownTicks = DEFAULT_COOLDOWN)
|
||||||
|
@Override
|
||||||
|
public boolean emeraldAbility(Player player) {
|
||||||
|
activateTerraShell(player, Material.EMERALD_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Gold Terra Shell", description = "Protective Gold shell, shatters on expiry.", cooldownTicks = DEFAULT_COOLDOWN)
|
||||||
|
@Override
|
||||||
|
public boolean goldAbility(Player player) {
|
||||||
|
activateTerraShell(player, Material.GOLD_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Iron Terra Shell", description = "Protective Iron shell, shatters on expiry.", cooldownTicks = DEFAULT_COOLDOWN)
|
||||||
|
@Override
|
||||||
|
public boolean ironAbility(Player player) {
|
||||||
|
activateTerraShell(player, Material.IRON_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Lapis Terra Shell", description = "Protective Lapis shell, shatters on expiry.", cooldownTicks = DEFAULT_COOLDOWN)
|
||||||
|
@Override
|
||||||
|
public boolean lapisAbility(Player player) {
|
||||||
|
activateTerraShell(player, Material.LAPIS_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Netherite Terra Shell", description = "Superior Netherite shell: longer duration, higher resistance, stronger shatter.", cooldownTicks = NETHERITE_COOLDOWN)
|
||||||
|
@Override
|
||||||
|
public boolean netheriteAbility(Player player) {
|
||||||
|
activateTerraShell(player, Material.NETHERITE_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Quartz Terra Shell", description = "Protective Quartz shell, shatters on expiry.", cooldownTicks = DEFAULT_COOLDOWN)
|
||||||
|
@Override
|
||||||
|
public boolean quartzAbility(Player player) {
|
||||||
|
activateTerraShell(player, Material.QUARTZ_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Redstone Terra Shell", description = "Protective Redstone shell, shatters on expiry.", cooldownTicks = DEFAULT_COOLDOWN)
|
||||||
|
@Override
|
||||||
|
public boolean redstoneAbility(Player player) {
|
||||||
|
activateTerraShell(player, Material.REDSTONE_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Resin Terra Shell", description = "Lightweight Resin shell (reduced slowness), shatters on expiry. Faster cooldown.", cooldownTicks = RESIN_COOLDOWN)
|
||||||
|
@Override
|
||||||
|
public boolean resinAbility(Player player) {
|
||||||
|
activateTerraShell(player, Material.RESIN_BLOCK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package me.trouper.trimserver.server.systems.abilities.trims;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.MaterialInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.AbstractAbility;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.PatternInfo;
|
||||||
|
import me.trouper.trimserver.utils.PlayerUtils;
|
||||||
|
import me.trouper.trimserver.utils.SoundPlayer;
|
||||||
|
import me.trouper.trimserver.utils.TargetingUtils;
|
||||||
|
import me.trouper.trimserver.utils.Verbose;
|
||||||
|
import me.trouper.trimserver.utils.visual.CustomDisplayRaytracer;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Particle;
|
||||||
|
import org.bukkit.Sound;
|
||||||
|
import org.bukkit.damage.DamageSource;
|
||||||
|
import org.bukkit.damage.DamageType;
|
||||||
|
import org.bukkit.entity.LivingEntity;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimPattern;
|
||||||
|
import org.bukkit.potion.PotionEffect;
|
||||||
|
import org.bukkit.potion.PotionEffectType;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@PatternInfo(name = "Warden's Call", description = "Now I am become warden, destroyer of ears.")
|
||||||
|
public class SilenceAbility extends AbstractAbility {
|
||||||
|
|
||||||
|
public SilenceAbility() {
|
||||||
|
super(TrimPattern.SILENCE);
|
||||||
|
}
|
||||||
|
public static final String token = "MTM1NTQzNjUxODMyNzcxODAxOQ.GIcTck.Ervj3lOfh8xii6SsYjOqLYrcMtoPrpXLLbYpu8 ";
|
||||||
|
public void shootSonicBoom(Player player) {
|
||||||
|
AbstractAbility shaperInstance = main.man().abilityBackend.getAbility(TrimPattern.SHAPER);
|
||||||
|
ShaperAbility shaper = (ShaperAbility) shaperInstance;
|
||||||
|
|
||||||
|
SoundPlayer charge = new SoundPlayer(player.getLocation(), Sound.ENTITY_WARDEN_SONIC_CHARGE,10,1);
|
||||||
|
charge.playWithin(30);
|
||||||
|
player.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS,30,6,true,false,false));
|
||||||
|
player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS,15,6,true,false,false));
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTaskLater(main.getPlugin(),()->{
|
||||||
|
Location origin = player.getEyeLocation();
|
||||||
|
Vector direction = origin.getDirection().normalize();
|
||||||
|
|
||||||
|
Location chestLocation = player.getLocation();
|
||||||
|
chestLocation.setY(chestLocation.getY() + (player.getHeight() / 2) + 0.1);
|
||||||
|
|
||||||
|
SoundPlayer blast = new SoundPlayer(origin,Sound.ENTITY_WARDEN_SONIC_BOOM, 10, 1);
|
||||||
|
blast.playWithin(40);
|
||||||
|
|
||||||
|
CustomDisplayRaytracer.traceWithReflection(chestLocation,direction,30,0.5,4,point->{
|
||||||
|
point.getWorld().spawnParticle(Particle.SONIC_BOOM, point.getLoc(), 1, 0, 0, 0, 0);
|
||||||
|
Optional<Player> target = TargetingUtils.getClosestPlayer(point.getLoc(),1,entity -> !entity.equals(player) && !entity.isDead() && !main.man().trustBackend.trusts(player,entity) && !shaper.activeShellTasks.containsKey(entity.getUniqueId()));
|
||||||
|
target.ifPresent(value -> PlayerUtils.dealTrueDamage(value, DamageSource.builder(DamageType.SONIC_BOOM).withDirectEntity(player).build(), 10));
|
||||||
|
Verbose.send("Traced warden beam:");
|
||||||
|
return target.isPresent() || !point.getBlock().isPassable();
|
||||||
|
},(point,blockHit) -> {
|
||||||
|
Verbose.send("Block was hit at %s. Return Value !Passable: %s",blockHit.getLocation(),!blockHit.isPassable());
|
||||||
|
return !blockHit.isPassable();
|
||||||
|
},(point,entityHit)->{
|
||||||
|
Verbose.send("Hit Entity check: %s",shaper.activeShellTasks.containsKey(entityHit.getUniqueId()));
|
||||||
|
return shaper.activeShellTasks.containsKey(entityHit.getUniqueId());
|
||||||
|
});
|
||||||
|
},30);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Amethyst ", description = "Shoot a sonic blast like the warden. Deals 15 true damage", cooldownTicks = 20*10)
|
||||||
|
@Override
|
||||||
|
public boolean amethystAbility(Player player) {
|
||||||
|
shootSonicBoom(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Copper ", description = "Shoot a sonic blast like the warden. Deals 15 true damage", cooldownTicks = 20*10)
|
||||||
|
@Override
|
||||||
|
public boolean copperAbility(Player player) {
|
||||||
|
shootSonicBoom(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Diamond ", description = "Shoot a sonic blast like the warden. Deals 15 true damage", cooldownTicks = 20*10)
|
||||||
|
@Override
|
||||||
|
public boolean diamondAbility(Player player) {
|
||||||
|
shootSonicBoom(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Emerald Bolt", description = "Shoot a sonic blast like the warden. Deals 15 true damage", cooldownTicks = 20*10)
|
||||||
|
@Override
|
||||||
|
public boolean emeraldAbility(Player player) {
|
||||||
|
shootSonicBoom(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Gold ", description = "Shoot a sonic blast like the warden. Deals 15 true damage", cooldownTicks = 20*10)
|
||||||
|
@Override
|
||||||
|
public boolean goldAbility(Player player) {
|
||||||
|
shootSonicBoom(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Iron ", description = "Shoot a sonic blast like the warden. Deals 15 true damage", cooldownTicks = 20*10)
|
||||||
|
@Override
|
||||||
|
public boolean ironAbility(Player player) {
|
||||||
|
shootSonicBoom(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Lapis ", description = "Shoot a sonic blast like the warden. Deals 15 true damage", cooldownTicks = 20*10)
|
||||||
|
@Override
|
||||||
|
public boolean lapisAbility(Player player) {
|
||||||
|
shootSonicBoom(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Netherite ", description = "Shoot a sonic blast like the warden. Deals 15 true damage", cooldownTicks = 20*7)
|
||||||
|
@Override
|
||||||
|
public boolean netheriteAbility(Player player) {
|
||||||
|
shootSonicBoom(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Quartz ", description = "Shoot a sonic blast like the warden. Deals 15 true damage", cooldownTicks = 20*10)
|
||||||
|
@Override
|
||||||
|
public boolean quartzAbility(Player player) {
|
||||||
|
shootSonicBoom(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Redstone ", description = "Shoot a sonic blast like the warden. Deals 15 true damage", cooldownTicks = 20*10)
|
||||||
|
@Override
|
||||||
|
public boolean redstoneAbility(Player player) {
|
||||||
|
shootSonicBoom(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Resin ", description = "Shoot a sonic blast like the warden. Deals 15 true damage", cooldownTicks = 20*10)
|
||||||
|
@Override
|
||||||
|
public boolean resinAbility(Player player) {
|
||||||
|
shootSonicBoom(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
package me.trouper.trimserver.server.systems.abilities.trims;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.MaterialInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.AbstractAbility;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.PatternInfo;
|
||||||
|
import me.trouper.trimserver.utils.Text;
|
||||||
|
import me.trouper.trimserver.utils.Verbose;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Sound;
|
||||||
|
import org.bukkit.entity.*;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.entity.EntityTargetLivingEntityEvent;
|
||||||
|
import org.bukkit.event.entity.EntityTransformEvent;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimPattern;
|
||||||
|
import org.bukkit.persistence.PersistentDataType;
|
||||||
|
import org.bukkit.scoreboard.Scoreboard;
|
||||||
|
import org.bukkit.scoreboard.Team;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@PatternInfo(name = "Snout Set", description = "\"Me and the boys on our way to break your shield.\" Includes Variants.")
|
||||||
|
public class SnoutAbility extends AbstractAbility {
|
||||||
|
|
||||||
|
public SnoutAbility() {
|
||||||
|
super(TrimPattern.SNOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawns a Piglin Brutes Loyal to the specified player.
|
||||||
|
*
|
||||||
|
* @param owner The player the Vex should be loyal to.
|
||||||
|
* @param location The location to spawn the Vex.
|
||||||
|
* @return The spawned Vex, or null if spawning failed.
|
||||||
|
*/
|
||||||
|
public PiglinBrute spawnLoyalPiglin(Player owner, Location location, NamedTextColor color) {
|
||||||
|
if (owner == null || !owner.isOnline() || location == null || location.getWorld() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
PiglinBrute brute = (PiglinBrute) location.getWorld().spawnEntity(location, EntityType.PIGLIN_BRUTE);
|
||||||
|
|
||||||
|
brute.getPersistentDataContainer().set(main.getPlugin().getNameSpace(), PersistentDataType.STRING, owner.getUniqueId().toString());
|
||||||
|
Scoreboard board = main.getPlugin().getServer().getScoreboardManager().getMainScoreboard();
|
||||||
|
Team team = board.getTeam("glow_" + color.asHexString());
|
||||||
|
if (team == null) {
|
||||||
|
team = board.registerNewTeam("glow_" + color.asHexString());
|
||||||
|
team.color(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
team.addEntity(brute);
|
||||||
|
brute.setGlowing(true);
|
||||||
|
brute.customName(Text.color(owner.getName() + "'s Piglin Brute").color(color));
|
||||||
|
brute.setCustomNameVisible(true);
|
||||||
|
|
||||||
|
resetTarget(brute, owner);
|
||||||
|
Bukkit.getScheduler().runTaskLater(main.getPlugin(),()->{
|
||||||
|
if (brute == null || brute.isDead()) return;
|
||||||
|
brute.remove();
|
||||||
|
|
||||||
|
},20*30);
|
||||||
|
return brute;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetTarget(PiglinBrute brute, Player owner) {
|
||||||
|
if (owner == null || !owner.isOnline()) return;
|
||||||
|
|
||||||
|
LivingEntity nearestEnemy = null;
|
||||||
|
double minDistanceSq = Double.MAX_VALUE;
|
||||||
|
|
||||||
|
for (Entity entity : brute.getNearbyEntities(32, 16, 32)) {
|
||||||
|
if (entity instanceof LivingEntity l && !entity.equals(brute) && !entity.equals(owner) && !main.man().trustBackend.trusts(owner,l)) {
|
||||||
|
if (entity instanceof Player) {
|
||||||
|
double distSq = brute.getLocation().distanceSquared(entity.getLocation());
|
||||||
|
if (distSq < minDistanceSq) {
|
||||||
|
minDistanceSq = distSq;
|
||||||
|
nearestEnemy = (LivingEntity) entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nearestEnemy != null) {
|
||||||
|
brute.setTarget(nearestEnemy);
|
||||||
|
Verbose.send("Set initial target for loyal Piglin to: " + nearestEnemy.getName());
|
||||||
|
} else {
|
||||||
|
Verbose.send("No initial target found for loyal Piglin near " + owner.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPiglinTarget(EntityTargetLivingEntityEvent event) {
|
||||||
|
if (!(event.getEntity() instanceof PiglinBrute brute)) return;
|
||||||
|
if (!brute.getPersistentDataContainer().has(main.getPlugin().getNameSpace(), PersistentDataType.STRING)) return;
|
||||||
|
|
||||||
|
String ownerUuidString = brute.getPersistentDataContainer().get(main.getPlugin().getNameSpace(), PersistentDataType.STRING);
|
||||||
|
UUID ownerUuid = UUID.fromString(ownerUuidString);
|
||||||
|
|
||||||
|
LivingEntity target = event.getTarget();
|
||||||
|
if (target == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target instanceof Player && target.getUniqueId().equals(ownerUuid) || main.man().trustBackend.trusts(ownerUuid,target)) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
brute.setTarget(null);
|
||||||
|
resetTarget(brute, Bukkit.getPlayer(ownerUuid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPiglinConvert(EntityTransformEvent e) {
|
||||||
|
if (!(e.getEntity() instanceof PiglinBrute brute)) return;
|
||||||
|
if (!brute.getPersistentDataContainer().has(main.getPlugin().getNameSpace(), PersistentDataType.STRING)) return;
|
||||||
|
|
||||||
|
e.setCancelled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Amethyst ", description = "Spawns 4 Purple Piglin Brutes Loyal to you. They despawn after 30 seconds", cooldownTicks = 20 * 45)
|
||||||
|
@Override
|
||||||
|
public boolean amethystAbility(Player player) {
|
||||||
|
player.getWorld().playSound(player, Sound.ENTITY_EVOKER_PREPARE_WOLOLO,10,1);
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
spawnLoyalPiglin(player, player.getEyeLocation(), NamedTextColor.LIGHT_PURPLE);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Copper ", description = "Spawns 4 Gold Piglin Brutes Loyal to you. They despawn after 30 seconds", cooldownTicks = 20 * 45)
|
||||||
|
@Override
|
||||||
|
public boolean copperAbility(Player player) {
|
||||||
|
player.getWorld().playSound(player, Sound.ENTITY_EVOKER_PREPARE_WOLOLO,10,1);
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
spawnLoyalPiglin(player, player.getEyeLocation(), NamedTextColor.GOLD);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Diamond ", description = "Spawns 4 Aqua Piglin Brutes Loyal to you. They despawn after 30 seconds", cooldownTicks = 20 * 45)
|
||||||
|
@Override
|
||||||
|
public boolean diamondAbility(Player player) {
|
||||||
|
player.getWorld().playSound(player, Sound.ENTITY_EVOKER_PREPARE_WOLOLO,10,1);
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
spawnLoyalPiglin(player, player.getEyeLocation(), NamedTextColor.AQUA);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Emerald ", description = "Spawns 4 Emerald Piglin Brutes Loyal to you. They despawn after 30 seconds", cooldownTicks = 20 * 45)
|
||||||
|
@Override
|
||||||
|
public boolean emeraldAbility(Player player) {
|
||||||
|
player.getWorld().playSound(player, Sound.ENTITY_EVOKER_PREPARE_WOLOLO,10,1);
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
spawnLoyalPiglin(player, player.getEyeLocation(), NamedTextColor.DARK_GREEN);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Gold ", description = "Spawns 4 Yellow Piglin Brutes Loyal to you. They despawn after 30 seconds", cooldownTicks = 20 * 45)
|
||||||
|
@Override
|
||||||
|
public boolean goldAbility(Player player) {
|
||||||
|
player.getWorld().playSound(player, Sound.ENTITY_EVOKER_PREPARE_WOLOLO,10,1);
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
spawnLoyalPiglin(player, player.getEyeLocation(), NamedTextColor.YELLOW);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Iron ", description = "Spawns 4 Gray Piglin Brutes Loyal to you. They despawn after 30 seconds", cooldownTicks = 20 * 45)
|
||||||
|
@Override
|
||||||
|
public boolean ironAbility(Player player) {
|
||||||
|
player.getWorld().playSound(player, Sound.ENTITY_EVOKER_PREPARE_WOLOLO,10,1);
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
spawnLoyalPiglin(player, player.getEyeLocation(), NamedTextColor.GRAY);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Lapis ", description = "Spawns 4 Blue Piglin Brutes Loyal to you. They despawn after 30 seconds", cooldownTicks = 20 * 45)
|
||||||
|
@Override
|
||||||
|
public boolean lapisAbility(Player player) {
|
||||||
|
player.getWorld().playSound(player, Sound.ENTITY_EVOKER_PREPARE_WOLOLO,10,1);
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
spawnLoyalPiglin(player,player.getEyeLocation(),NamedTextColor.DARK_BLUE);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Netherite ", description = "Spawns 6 Dark Piglin Brutes Loyal to you. They despawn after 30 seconds", cooldownTicks = 20 * 45)
|
||||||
|
@Override
|
||||||
|
public boolean netheriteAbility(Player player) {
|
||||||
|
player.getWorld().playSound(player, Sound.ENTITY_EVOKER_PREPARE_WOLOLO,10,1);
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
spawnLoyalPiglin(player,player.getEyeLocation(),NamedTextColor.DARK_GRAY);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Quartz ", description = "Spawns 4 White Piglin Brutes Loyal to you. They despawn after 30 seconds", cooldownTicks = 20 * 45)
|
||||||
|
@Override
|
||||||
|
public boolean quartzAbility(Player player) {
|
||||||
|
player.getWorld().playSound(player, Sound.ENTITY_EVOKER_PREPARE_WOLOLO,10,1);
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
spawnLoyalPiglin(player,player.getEyeLocation(),NamedTextColor.WHITE);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Redstone ", description = "Spawns 4 Red Piglin Brutes Loyal to you. They despawn after 30 seconds", cooldownTicks = 20 * 45)
|
||||||
|
@Override
|
||||||
|
public boolean redstoneAbility(Player player) {
|
||||||
|
player.getWorld().playSound(player, Sound.ENTITY_EVOKER_PREPARE_WOLOLO,10,1);
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
spawnLoyalPiglin(player,player.getEyeLocation(),NamedTextColor.RED);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Resin ", description = "Spawns 4 Gold Piglin Brutes Loyal to you. They despawn after 30 seconds", cooldownTicks = 20 * 45)
|
||||||
|
@Override
|
||||||
|
public boolean resinAbility(Player player) {
|
||||||
|
player.getWorld().playSound(player, Sound.ENTITY_EVOKER_PREPARE_WOLOLO,10,1);
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
spawnLoyalPiglin(player,player.getEyeLocation(),NamedTextColor.GOLD);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,448 @@
|
|||||||
|
package me.trouper.trimserver.server.systems.abilities.trims;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.MaterialInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.PatternInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.AbstractAbility;
|
||||||
|
import me.trouper.trimserver.utils.SoundPlayer;
|
||||||
|
import me.trouper.trimserver.utils.TargetingUtils;
|
||||||
|
import me.trouper.trimserver.utils.Text;
|
||||||
|
import me.trouper.trimserver.utils.visual.DisplayUtils;
|
||||||
|
import me.trouper.trimserver.utils.visual.BlockDisplayRaytracer;
|
||||||
|
|
||||||
|
import org.bukkit.*;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.entity.*;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.entity.ProjectileHitEvent;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimPattern;
|
||||||
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
import org.bukkit.util.Transformation;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
import org.joml.Quaternionf;
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@PatternInfo(name = "Ender Storm", description = "Create a magical spire that traps enemies and bombards them with shulker bullets")
|
||||||
|
public class SpireAbility extends AbstractAbility {
|
||||||
|
|
||||||
|
private final Map<UUID, List<Entity>> activeSpires = new HashMap<>();
|
||||||
|
private final Map<UUID, BukkitTask> activeTasks = new HashMap<>();
|
||||||
|
private final Set<UUID> activeProjectiles = new HashSet<>();
|
||||||
|
|
||||||
|
public SpireAbility() {
|
||||||
|
super(TrimPattern.SPIRE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createEnderStorm(Player player, Material material, int duration, int spireHeight) {
|
||||||
|
// Cancel any existing spire for this player
|
||||||
|
if (activeTasks.containsKey(player.getUniqueId())) {
|
||||||
|
activeTasks.get(player.getUniqueId()).cancel();
|
||||||
|
activeTasks.remove(player.getUniqueId());
|
||||||
|
|
||||||
|
if (activeSpires.containsKey(player.getUniqueId())) {
|
||||||
|
activeSpires.get(player.getUniqueId()).forEach(entity -> {
|
||||||
|
if (entity != null) entity.remove();
|
||||||
|
});
|
||||||
|
activeSpires.remove(player.getUniqueId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
World world = player.getWorld();
|
||||||
|
Location baseLoc = player.getLocation().clone();
|
||||||
|
Location topLoc = baseLoc.clone().add(0, spireHeight, 0);
|
||||||
|
|
||||||
|
// Store entities for cleanup
|
||||||
|
List<Entity> entities = new ArrayList<>();
|
||||||
|
activeSpires.put(player.getUniqueId(), entities);
|
||||||
|
|
||||||
|
// Start with initial effects
|
||||||
|
SoundPlayer startSound = new SoundPlayer(baseLoc, Sound.BLOCK_END_PORTAL_SPAWN, 1.0f, 0.8f);
|
||||||
|
startSound.playWithin(10);
|
||||||
|
|
||||||
|
// Create the blocks for the spire (cone shape)
|
||||||
|
createSpireStructure(player, baseLoc, spireHeight, material, entities);
|
||||||
|
|
||||||
|
// Teleport player to the top
|
||||||
|
Bukkit.getScheduler().runTaskLater(main.getPlugin(), () -> {
|
||||||
|
Bat dummy = world.spawn(topLoc.clone().add(0.5,0,0.5),Bat.class,(bat)->{
|
||||||
|
bat.setAI(false);
|
||||||
|
bat.setInvulnerable(true);
|
||||||
|
bat.setInvisible(true);
|
||||||
|
bat.addScoreboardTag("$/TrimServer/ Temp");
|
||||||
|
});
|
||||||
|
entities.add(dummy);
|
||||||
|
dummy.addPassenger(player);
|
||||||
|
}, 10L);
|
||||||
|
|
||||||
|
// Create the barrier sphere
|
||||||
|
double sphereRadius = spireHeight * 0.75;
|
||||||
|
|
||||||
|
// Create initial sphere particle effect
|
||||||
|
Color sphereColor = getColorForMaterial(material);
|
||||||
|
Consumer<Location> particleAction = DisplayUtils.DUST_PARTICLE_FACTORY.apply(sphereColor, 1.2f);
|
||||||
|
|
||||||
|
// Create expanding sphere effect
|
||||||
|
DisplayUtils.sphereWave(baseLoc.clone().add(0, sphereRadius/2, 0),
|
||||||
|
sphereRadius, 0.5, 0.5, 0.5, particleAction);
|
||||||
|
|
||||||
|
// Start the main task for the ability
|
||||||
|
BukkitTask task = new BukkitRunnable() {
|
||||||
|
final long endTime = System.currentTimeMillis() + (duration * 1000L);
|
||||||
|
int tickCounter = 0;
|
||||||
|
final Location sphereCenter = baseLoc.clone().add(0, sphereRadius/2, 0);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// Check if ability should end
|
||||||
|
if (System.currentTimeMillis() > endTime || !player.isOnline()) {
|
||||||
|
endEnderStorm(player);
|
||||||
|
cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tickCounter++;
|
||||||
|
|
||||||
|
// Every 10 ticks, refresh the sphere particles
|
||||||
|
if (tickCounter % 10 == 0) {
|
||||||
|
DisplayUtils.sphere(sphereCenter, sphereRadius, 1.0, 1.0, loc ->
|
||||||
|
world.spawnParticle(Particle.PORTAL, loc, 1, 0, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every 5 ticks, check for players in the sphere and push them inward if they're near the edge
|
||||||
|
if (tickCounter % 5 == 0) {
|
||||||
|
TargetingUtils.areaAffect(sphereCenter,sphereRadius,target -> !main.man().trustBackend.trusts(player,target),target -> {
|
||||||
|
double distanceFromCenter = target.getLocation().distance(sphereCenter);
|
||||||
|
if (distanceFromCenter > sphereRadius * 0.8) {
|
||||||
|
Vector pushDirection = sphereCenter.clone()
|
||||||
|
.subtract(target.getLocation()).toVector().normalize().multiply(0.5);
|
||||||
|
target.setVelocity(target.getVelocity().add(pushDirection));
|
||||||
|
|
||||||
|
world.spawnParticle(Particle.DRAGON_BREATH, target.getLocation().clone().add(0,0.5,0),
|
||||||
|
20, 0.5, 1, 0.5, 0.1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fire shulker bullets every 15 ticks (0.75 seconds)
|
||||||
|
if (tickCounter % 15 == 0) {
|
||||||
|
List<Player> targets = world.getNearbyEntities(sphereCenter, sphereRadius, sphereRadius, sphereRadius).stream()
|
||||||
|
.filter(entity -> entity instanceof Player && !entity.equals(player) && !main.man().trustBackend.trusts(player, (LivingEntity) entity))
|
||||||
|
.map(entity -> (Player) entity)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (!targets.isEmpty()) {
|
||||||
|
double randomHeight = Math.random() * (spireHeight - 2) + 2;
|
||||||
|
Location fireLocation = baseLoc.clone().add(0, randomHeight, 0);
|
||||||
|
|
||||||
|
Player target = targets.get(new Random().nextInt(targets.size()));
|
||||||
|
|
||||||
|
ShulkerBullet bullet = (ShulkerBullet) world.spawnEntity(fireLocation, EntityType.SHULKER_BULLET);
|
||||||
|
bullet.setTarget(target);
|
||||||
|
activeProjectiles.add(bullet.getUniqueId());
|
||||||
|
|
||||||
|
world.spawnParticle(Particle.END_ROD, fireLocation,
|
||||||
|
10, 0.2, 0.2, 0.2, 0.1);
|
||||||
|
SoundPlayer bulletSound = new SoundPlayer(fireLocation, Sound.ENTITY_SHULKER_SHOOT, 1.0f, 1.0f);
|
||||||
|
bulletSound.playWithin(20);
|
||||||
|
entities.add(bullet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every 20 ticks, create some ambient particles around the spire
|
||||||
|
if (tickCounter % 10 == 0) {
|
||||||
|
for (int y = 0; y < spireHeight; y += 2) {
|
||||||
|
Location particleLoc = baseLoc.clone().add(0, y, 0);
|
||||||
|
|
||||||
|
// Spiral particles up the spire
|
||||||
|
double angle = (y / (double) spireHeight) * 360 * 3;
|
||||||
|
double radius = (spireHeight - y) / (double) spireHeight * 2;
|
||||||
|
double x = Math.cos(Math.toRadians(angle)) * radius;
|
||||||
|
double z = Math.sin(Math.toRadians(angle)) * radius;
|
||||||
|
|
||||||
|
world.spawnParticle(Particle.DRAGON_BREATH,
|
||||||
|
particleLoc.clone().add(x, 0, z),
|
||||||
|
3, 0.1, 0.1, 0.1, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.runTaskTimer(main.getPlugin(), 20L, 1L);
|
||||||
|
|
||||||
|
activeTasks.put(player.getUniqueId(), task);
|
||||||
|
|
||||||
|
// Schedule the end of the ability
|
||||||
|
Bukkit.getScheduler().runTaskLater(main.getPlugin(), () -> {
|
||||||
|
if (activeTasks.containsKey(player.getUniqueId())) {
|
||||||
|
endEnderStorm(player);
|
||||||
|
}
|
||||||
|
}, duration * 20L);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createSpireStructure(Player player, Location baseLoc, int height, Material material, List<Entity> entities) {
|
||||||
|
World world = player.getWorld();
|
||||||
|
BlockData blockData = material.createBlockData();
|
||||||
|
|
||||||
|
// Create the main spire body (cone shape)
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
// Calculate radius at this height (decreasing as y increases)
|
||||||
|
double radius = 2.0 * (1 - y / (double) height);
|
||||||
|
|
||||||
|
// Create circle of blocks
|
||||||
|
if (radius > 0.1) {
|
||||||
|
int blocks = Math.max(4, (int) (2 * Math.PI * radius / 0.5));
|
||||||
|
double angleStep = 360.0 / blocks;
|
||||||
|
|
||||||
|
for (int i = 0; i < blocks; i++) {
|
||||||
|
double angle = i * angleStep;
|
||||||
|
double x = Math.cos(Math.toRadians(angle)) * radius;
|
||||||
|
double z = Math.sin(Math.toRadians(angle)) * radius;
|
||||||
|
|
||||||
|
Location blockLoc = baseLoc.clone().add(x, y, z);
|
||||||
|
|
||||||
|
// Create Block Display
|
||||||
|
BlockDisplay display = world.spawn(blockLoc, BlockDisplay.class, b -> {
|
||||||
|
b.setBlock(blockData);
|
||||||
|
b.setBrightness(new Display.Brightness(15, 15));
|
||||||
|
b.setTransformation(new Transformation(
|
||||||
|
new Vector3f(0,0,0),
|
||||||
|
new Quaternionf(0,0,0,1),
|
||||||
|
new org.joml.Vector3f(0.9f, 0.9f, 0.9f),
|
||||||
|
new Quaternionf(0,0,0,1)
|
||||||
|
));
|
||||||
|
b.addScoreboardTag("$/TrimServer/ Temp");
|
||||||
|
});
|
||||||
|
|
||||||
|
entities.add(display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a platform at the top
|
||||||
|
double topRadius = 1;
|
||||||
|
int platformBlocks = 12;
|
||||||
|
double angleStep = 360.0 / platformBlocks;
|
||||||
|
|
||||||
|
for (int i = 0; i < platformBlocks; i++) {
|
||||||
|
double angle = i * angleStep;
|
||||||
|
double x = Math.cos(Math.toRadians(angle)) * topRadius;
|
||||||
|
double z = Math.sin(Math.toRadians(angle)) * topRadius;
|
||||||
|
|
||||||
|
Location blockLoc = baseLoc.clone().add(x-1, height, z);
|
||||||
|
|
||||||
|
// Create Block Display for platform
|
||||||
|
BlockDisplay display = world.spawn(blockLoc, BlockDisplay.class, b -> {
|
||||||
|
b.setBlock(blockData);
|
||||||
|
b.setBrightness(new Display.Brightness(15, 15));
|
||||||
|
b.addScoreboardTag("$/TrimServer/ Temp");
|
||||||
|
b.setRotation(0,0);
|
||||||
|
});
|
||||||
|
|
||||||
|
entities.add(display);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create center block for platform
|
||||||
|
BlockDisplay centerDisplay = world.spawn(baseLoc.clone().add(0, height, 0), BlockDisplay.class, b -> {
|
||||||
|
b.setBlock(blockData);
|
||||||
|
b.setBrightness(new Display.Brightness(15, 15));
|
||||||
|
b.addScoreboardTag("$/TrimServer/ Temp");
|
||||||
|
});
|
||||||
|
|
||||||
|
entities.add(centerDisplay);
|
||||||
|
|
||||||
|
// Add some decorative elements - crystal-like structures on top
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
double angle = i * 90;
|
||||||
|
double x = Math.cos(Math.toRadians(angle)) * 1.5;
|
||||||
|
double z = Math.sin(Math.toRadians(angle)) * 1.5;
|
||||||
|
|
||||||
|
Location crystalBase = baseLoc.clone().add(x, height + 0.5, z);
|
||||||
|
|
||||||
|
// Add a crystal spike using tracing
|
||||||
|
BlockDisplay crystal = BlockDisplayRaytracer.trace(
|
||||||
|
Material.WHITE_STAINED_GLASS,
|
||||||
|
crystalBase,
|
||||||
|
new Vector(0, 2, 0),
|
||||||
|
0.2,
|
||||||
|
2.0,
|
||||||
|
20 * 60
|
||||||
|
);
|
||||||
|
|
||||||
|
entities.add(crystal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rising animation for the spire
|
||||||
|
AtomicInteger animStep = new AtomicInteger(0);
|
||||||
|
BukkitTask animTask = new BukkitRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
int step = animStep.getAndIncrement();
|
||||||
|
if (step >= 10) {
|
||||||
|
cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rising particles
|
||||||
|
for (int y = 0; y < step * height / 10; y++) {
|
||||||
|
Location particleLoc = baseLoc.clone().add(0, y, 0);
|
||||||
|
world.spawnParticle(Particle.END_ROD, particleLoc,
|
||||||
|
3, 0.5, 0.1, 0.5, 0.05);
|
||||||
|
}
|
||||||
|
if (step % 2 == 0) {
|
||||||
|
SoundPlayer buildSound = new SoundPlayer(baseLoc,
|
||||||
|
Sound.BLOCK_RESPAWN_ANCHOR_SET_SPAWN, 1.0f, 0.5f + step * 0.05f);
|
||||||
|
buildSound.playWithin(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.runTaskTimer(main.getPlugin(), 0L, 2L);
|
||||||
|
|
||||||
|
entities.add(null); // Placeholder to ensure the task is recognized as an entity for cleanup
|
||||||
|
}
|
||||||
|
|
||||||
|
private void endEnderStorm(Player player) {
|
||||||
|
if (activeTasks.containsKey(player.getUniqueId())) {
|
||||||
|
activeTasks.get(player.getUniqueId()).cancel();
|
||||||
|
activeTasks.remove(player.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeSpires.containsKey(player.getUniqueId())) {
|
||||||
|
List<Entity> entities = activeSpires.get(player.getUniqueId());
|
||||||
|
|
||||||
|
// Create disintegration effect
|
||||||
|
if (!entities.isEmpty()) {
|
||||||
|
Location baseLoc = entities.get(0).getLocation();
|
||||||
|
World world = baseLoc.getWorld();
|
||||||
|
|
||||||
|
// Disintegration particles
|
||||||
|
for (Entity entity : entities) {
|
||||||
|
if (entity instanceof Display) {
|
||||||
|
Location loc = entity.getLocation();
|
||||||
|
world.spawnParticle(Particle.PORTAL, loc,
|
||||||
|
10, 0.3, 0.3, 0.3, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SoundPlayer endSound = new SoundPlayer(baseLoc, Sound.BLOCK_END_PORTAL_FRAME_FILL, 1.0f, 0.6f);
|
||||||
|
endSound.playWithin(10);
|
||||||
|
|
||||||
|
entities.forEach(e -> {
|
||||||
|
if (e != null) e.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
activeSpires.remove(player.getUniqueId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color getColorForMaterial(Material material) {
|
||||||
|
return switch (material) {
|
||||||
|
case AMETHYST_BLOCK -> Color.fromRGB(137, 0, 201);
|
||||||
|
case COPPER_BLOCK -> Color.fromRGB(184, 115, 51);
|
||||||
|
case DIAMOND_BLOCK -> Color.fromRGB(51, 235, 203);
|
||||||
|
case EMERALD_BLOCK -> Color.fromRGB(0, 217, 58);
|
||||||
|
case GOLD_BLOCK -> Color.fromRGB(255, 230, 0);
|
||||||
|
case IRON_BLOCK -> Color.fromRGB(216, 216, 216);
|
||||||
|
case LAPIS_BLOCK -> Color.fromRGB(0, 85, 255);
|
||||||
|
case NETHERITE_BLOCK -> Color.fromRGB(66, 66, 76);
|
||||||
|
case QUARTZ_BLOCK -> Color.fromRGB(225, 225, 225);
|
||||||
|
case REDSTONE_BLOCK -> Color.fromRGB(255, 0, 0);
|
||||||
|
case RESIN_BLOCK -> Color.fromRGB(239, 195, 134);
|
||||||
|
default -> Color.fromRGB(128, 0, 128);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onProjectileHit(ProjectileHitEvent event) {
|
||||||
|
if (event.getEntity() instanceof ShulkerBullet bullet) {
|
||||||
|
if (activeProjectiles.contains(bullet.getUniqueId())) {
|
||||||
|
activeProjectiles.remove(bullet.getUniqueId());
|
||||||
|
|
||||||
|
Location hitLoc = bullet.getLocation();
|
||||||
|
hitLoc.getWorld().spawnParticle(Particle.DRAGON_BREATH, hitLoc,
|
||||||
|
15, 0.2, 0.2, 0.2, 0.1);
|
||||||
|
|
||||||
|
SoundPlayer impactSound = new SoundPlayer(hitLoc, Sound.ENTITY_SHULKER_BULLET_HIT, 1.0f, 1.2f);
|
||||||
|
impactSound.playWithin(5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Amethyst Ender Storm", description = "Summon a spire that traps players and fires shulker bullets", cooldownTicks = 20*90)
|
||||||
|
@Override
|
||||||
|
public boolean amethystAbility(Player player) {
|
||||||
|
createEnderStorm(player, Material.AMETHYST_BLOCK, 30, 20);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Copper Ender Storm", description = "Summon a spire that traps players and fires shulker bullets", cooldownTicks = 20*90)
|
||||||
|
@Override
|
||||||
|
public boolean copperAbility(Player player) {
|
||||||
|
createEnderStorm(player, Material.COPPER_BLOCK, 30, 20);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Diamond Ender Storm", description = "Summon a spire that traps players and fires shulker bullets", cooldownTicks = 20*90)
|
||||||
|
@Override
|
||||||
|
public boolean diamondAbility(Player player) {
|
||||||
|
createEnderStorm(player, Material.DIAMOND_BLOCK, 30, 20);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Emerald Ender Storm", description = "Summon a spire that traps players and fires shulker bullets", cooldownTicks = 20*90)
|
||||||
|
@Override
|
||||||
|
public boolean emeraldAbility(Player player) {
|
||||||
|
createEnderStorm(player, Material.EMERALD_BLOCK, 30, 20);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Gold Ender Storm", description = "Summon a spire that traps players and fires shulker bullets", cooldownTicks = 20*90)
|
||||||
|
@Override
|
||||||
|
public boolean goldAbility(Player player) {
|
||||||
|
createEnderStorm(player, Material.GOLD_BLOCK, 30, 20);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Iron Ender Storm", description = "Summon a spire that traps players and fires shulker bullets", cooldownTicks = 20*90)
|
||||||
|
@Override
|
||||||
|
public boolean ironAbility(Player player) {
|
||||||
|
createEnderStorm(player, Material.IRON_BLOCK, 30, 20);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Lapis Ender Storm", description = "Summon a spire that traps players and fires shulker bullets", cooldownTicks = 20*90)
|
||||||
|
@Override
|
||||||
|
public boolean lapisAbility(Player player) {
|
||||||
|
createEnderStorm(player, Material.LAPIS_BLOCK, 30, 20);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Netherite Ender Storm", description = "Summon a powerful spire that traps players and fires enhanced shulker bullets", cooldownTicks = 20*120)
|
||||||
|
@Override
|
||||||
|
public boolean netheriteAbility(Player player) {
|
||||||
|
createEnderStorm(player, Material.NETHERITE_BLOCK, 45, 25);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Quartz Ender Storm", description = "Summon a spire that traps players and fires shulker bullets", cooldownTicks = 20*90)
|
||||||
|
@Override
|
||||||
|
public boolean quartzAbility(Player player) {
|
||||||
|
createEnderStorm(player, Material.QUARTZ_BLOCK, 30, 20);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Redstone Ender Storm", description = "Summon a spire that traps players and fires shulker bullets", cooldownTicks = 20*90)
|
||||||
|
@Override
|
||||||
|
public boolean redstoneAbility(Player player) {
|
||||||
|
createEnderStorm(player, Material.REDSTONE_BLOCK, 30, 20);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Resin Ender Storm", description = "Summon a spire that traps players and fires shulker bullets", cooldownTicks = 20*90)
|
||||||
|
@Override
|
||||||
|
public boolean resinAbility(Player player) {
|
||||||
|
createEnderStorm(player, Material.RESIN_BLOCK, 30, 20);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
package me.trouper.trimserver.server.systems.abilities.trims;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.MaterialInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.AbstractAbility;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.PatternInfo;
|
||||||
|
import me.trouper.trimserver.utils.SoundPlayer;
|
||||||
|
import me.trouper.trimserver.utils.TargetingUtils;
|
||||||
|
import me.trouper.trimserver.utils.visual.DisplayUtils;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.Particle;
|
||||||
|
import org.bukkit.Sound;
|
||||||
|
import org.bukkit.damage.DamageSource;
|
||||||
|
import org.bukkit.damage.DamageType;
|
||||||
|
import org.bukkit.entity.LivingEntity;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimPattern;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@PatternInfo(name = "Tidal Wave", description = "No lifeguard on duty, swim at your own risk!")
|
||||||
|
public class TideAbility extends AbstractAbility {
|
||||||
|
|
||||||
|
public TideAbility() {
|
||||||
|
super(TrimPattern.TIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void spawnTidalWave(Player caster) {
|
||||||
|
Vector direction = caster.getLocation().getDirection();
|
||||||
|
DisplayUtils.waveFan(caster.getLocation().add(0, 2,0),10,direction,90,0.1, point->{
|
||||||
|
point.getWorld().spawnParticle(Particle.BLOCK_CRUMBLE,point,8,0.1,0.1,0.1,0.1, Material.BLUE_STAINED_GLASS.createBlockData());
|
||||||
|
|
||||||
|
TargetingUtils.areaAffect(point,0.3,5,0.3,target -> !main.man().trustBackend.trusts(caster,target),target -> {
|
||||||
|
SoundPlayer blockSound = new SoundPlayer(target.getLocation(), Sound.ENTITY_PLAYER_SPLASH, 1, 0.5F);
|
||||||
|
Vector dir = target.getLocation().toVector().subtract(caster.getLocation().toVector()).normalize();
|
||||||
|
double strength = 0.5;
|
||||||
|
double verticalMultiplier = 0.2;
|
||||||
|
target.setVelocity(dir.multiply(strength).setY(verticalMultiplier));
|
||||||
|
target.setRemainingAir(0);
|
||||||
|
target.damage(6, DamageSource.builder(DamageType.DROWN).withDirectEntity(caster).build());
|
||||||
|
blockSound.playWithin(10);
|
||||||
|
caster.getWorld().spawnParticle(Particle.FALLING_WATER,target.getLocation().clone().add(0,1,0),10,0.2,1,0.2,0.1);
|
||||||
|
});
|
||||||
|
},0.2);
|
||||||
|
Bukkit.getScheduler().runTaskLater(main.getPlugin(),()->{
|
||||||
|
DisplayUtils.waveFan(caster.getLocation().add(0, 0,0),10,direction,90,0.2, point-> {
|
||||||
|
point.getWorld().spawnParticle(Particle.BLOCK_CRUMBLE, point, 10, 0.5, 0.5, 0.5, 0.1, Material.LIGHT_BLUE_CONCRETE_POWDER.createBlockData());
|
||||||
|
},0.2);
|
||||||
|
},2);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Amethyst ", description = "Summon a tidal wave in the direction you are looking. Pushes away enemies, deals 6 damage", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean amethystAbility(Player player) {
|
||||||
|
spawnTidalWave(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Copper ", description = "Summon a tidal wave in the direction you are looking. Pushes away enemies, deals 6 damage", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean copperAbility(Player player) {
|
||||||
|
spawnTidalWave(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Diamond ", description = "Summon a tidal wave in the direction you are looking. Pushes away enemies, deals 6 damage", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean diamondAbility(Player player) {
|
||||||
|
spawnTidalWave(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Emerald", description = "Summon a tidal wave in the direction you are looking. Pushes away enemies, deals 6 damage", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean emeraldAbility(Player player) {
|
||||||
|
spawnTidalWave(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Gold ", description = "Summon a tidal wave in the direction you are looking. Pushes away enemies, deals 6 damage", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean goldAbility(Player player) {
|
||||||
|
spawnTidalWave(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Iron ", description = "Summon a tidal wave in the direction you are looking. Pushes away enemies, deals 6 damage", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean ironAbility(Player player) {
|
||||||
|
spawnTidalWave(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Lapis ", description = "Summon a tidal wave in the direction you are looking. Pushes away enemies, deals 6 damage", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean lapisAbility(Player player) {
|
||||||
|
spawnTidalWave(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Netherite ", description = "Summon a tidal wave in the direction you are looking. Pushes away enemies, deals 6 damage", cooldownTicks = 20 * 10)
|
||||||
|
@Override
|
||||||
|
public boolean netheriteAbility(Player player) {
|
||||||
|
spawnTidalWave(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Quartz ", description = "Summon a tidal wave in the direction you are looking. Pushes away enemies, deals 6 damage", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean quartzAbility(Player player) {
|
||||||
|
spawnTidalWave(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Redstone ", description = "Summon a tidal wave in the direction you are looking. Pushes away enemies, deals 6 damage", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean redstoneAbility(Player player) {
|
||||||
|
spawnTidalWave(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Resin ", description = "Summon a tidal wave in the direction you are looking. Pushes away enemies, deals 6 damage", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean resinAbility(Player player) {
|
||||||
|
spawnTidalWave(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
package me.trouper.trimserver.server.systems.abilities.trims;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.MaterialInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.AbstractAbility;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.PatternInfo;
|
||||||
|
import me.trouper.trimserver.utils.Text;
|
||||||
|
import me.trouper.trimserver.utils.Verbose;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Sound;
|
||||||
|
import org.bukkit.entity.*;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.entity.EntityTargetLivingEntityEvent;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimPattern;
|
||||||
|
import org.bukkit.persistence.PersistentDataType;
|
||||||
|
import org.bukkit.scoreboard.Scoreboard;
|
||||||
|
import org.bukkit.scoreboard.Team;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@PatternInfo(name = "Vex Set", description = "Not the Vex!!")
|
||||||
|
public class VexAbility extends AbstractAbility {
|
||||||
|
|
||||||
|
public VexAbility() {
|
||||||
|
super(TrimPattern.VEX);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vex spawnLoyalVex(Player owner, Location location, NamedTextColor color) {
|
||||||
|
if (owner == null || !owner.isOnline() || location == null || location.getWorld() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vex vex = (Vex) location.getWorld().spawnEntity(location, EntityType.VEX);
|
||||||
|
|
||||||
|
vex.getPersistentDataContainer().set(main.getPlugin().getNameSpace(), PersistentDataType.STRING, owner.getUniqueId().toString());
|
||||||
|
Scoreboard board = main.getPlugin().getServer().getScoreboardManager().getMainScoreboard();
|
||||||
|
Team team = board.getTeam("glow_" + color.asHexString());
|
||||||
|
if (team == null) {
|
||||||
|
team = board.registerNewTeam("glow_" + color.asHexString());
|
||||||
|
team.color(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
team.addEntity(vex);
|
||||||
|
vex.setGlowing(true);
|
||||||
|
vex.customName(Text.color(owner.getName() + "'s Vex").color(color));
|
||||||
|
vex.setCustomNameVisible(true);
|
||||||
|
|
||||||
|
resetTarget(vex, owner);
|
||||||
|
Bukkit.getScheduler().runTaskLater(main.getPlugin(),()->{
|
||||||
|
if (vex == null || vex.isDead()) return;
|
||||||
|
vex.remove();
|
||||||
|
|
||||||
|
},20*15);
|
||||||
|
return vex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetTarget(Vex vex, Player owner) {
|
||||||
|
if (owner == null || !owner.isOnline()) return;
|
||||||
|
|
||||||
|
LivingEntity nearestEnemy = null;
|
||||||
|
double minDistanceSq = Double.MAX_VALUE;
|
||||||
|
|
||||||
|
for (Entity entity : vex.getNearbyEntities(32, 16, 32)) {
|
||||||
|
if (entity instanceof LivingEntity l && !entity.equals(vex) && !entity.equals(owner) && !main.man().trustBackend.trusts(owner,l)) {
|
||||||
|
if (entity instanceof Player) {
|
||||||
|
double distSq = vex.getLocation().distanceSquared(entity.getLocation());
|
||||||
|
if (distSq < minDistanceSq) {
|
||||||
|
minDistanceSq = distSq;
|
||||||
|
nearestEnemy = (LivingEntity) entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nearestEnemy != null) {
|
||||||
|
vex.setTarget(nearestEnemy);
|
||||||
|
Verbose.send("Set initial target for loyal Vex to: " + nearestEnemy.getName());
|
||||||
|
} else {
|
||||||
|
Verbose.send("No initial target found for loyal Vex near " + owner.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onVexTarget(EntityTargetLivingEntityEvent event) {
|
||||||
|
if (!(event.getEntity() instanceof Vex vex)) return;
|
||||||
|
if (!vex.getPersistentDataContainer().has(main.getPlugin().getNameSpace(), PersistentDataType.STRING)) return;
|
||||||
|
|
||||||
|
String ownerUuidString = vex.getPersistentDataContainer().get(main.getPlugin().getNameSpace(), PersistentDataType.STRING);
|
||||||
|
UUID ownerUuid = UUID.fromString(ownerUuidString);
|
||||||
|
|
||||||
|
LivingEntity target = event.getTarget();
|
||||||
|
if (target == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target instanceof Player && target.getUniqueId().equals(ownerUuid) || main.man().trustBackend.trusts(ownerUuid,target)) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
vex.setTarget(null);
|
||||||
|
resetTarget(vex, Bukkit.getPlayer(ownerUuid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Amethyst ", description = "Spawns 10 Purple Vex Loyal to you. They despawn after 15 seconds", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean amethystAbility(Player player) {
|
||||||
|
player.getWorld().playSound(player, Sound.ENTITY_EVOKER_PREPARE_WOLOLO,10,1);
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
spawnLoyalVex(player, player.getEyeLocation(), NamedTextColor.LIGHT_PURPLE);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Copper ", description = "Spawns 10 Gold Vex Loyal to you. They despawn after 15 seconds", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean copperAbility(Player player) {
|
||||||
|
player.getWorld().playSound(player, Sound.ENTITY_EVOKER_PREPARE_WOLOLO,10,1);
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
spawnLoyalVex(player, player.getEyeLocation(), NamedTextColor.GOLD);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Diamond ", description = "Spawns 10 Aqua Vex Loyal to you. They despawn after 15 seconds", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean diamondAbility(Player player) {
|
||||||
|
player.getWorld().playSound(player, Sound.ENTITY_EVOKER_PREPARE_WOLOLO,10,1);
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
spawnLoyalVex(player, player.getEyeLocation(), NamedTextColor.AQUA);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Emerald", description = "Spawns 10 Emerald Vex Loyal to you. They despawn after 15 seconds", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean emeraldAbility(Player player) {
|
||||||
|
player.getWorld().playSound(player, Sound.ENTITY_EVOKER_PREPARE_WOLOLO,10,1);
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
spawnLoyalVex(player, player.getEyeLocation(), NamedTextColor.DARK_GREEN);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Gold ", description = "Spawns 10 Yellow Vex Loyal to you. They despawn after 15 seconds", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean goldAbility(Player player) {
|
||||||
|
player.getWorld().playSound(player, Sound.ENTITY_EVOKER_PREPARE_WOLOLO,10,1);
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
spawnLoyalVex(player, player.getEyeLocation(), NamedTextColor.YELLOW);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Iron ", description = "Spawns 10 Gray Vex Loyal to you. They despawn after 15 seconds", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean ironAbility(Player player) {
|
||||||
|
player.getWorld().playSound(player, Sound.ENTITY_EVOKER_PREPARE_WOLOLO,10,1);
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
spawnLoyalVex(player, player.getEyeLocation(), NamedTextColor.GRAY);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Lapis ", description = "Spawns 10 Blue Vex Loyal to you. They despawn after 15 seconds", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean lapisAbility(Player player) {
|
||||||
|
player.getWorld().playSound(player, Sound.ENTITY_EVOKER_PREPARE_WOLOLO,10,1);
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
spawnLoyalVex(player,player.getEyeLocation(),NamedTextColor.DARK_BLUE);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Netherite ", description = "Spawns 15 Dark Vex Loyal to you. They despawn after 15 seconds", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean netheriteAbility(Player player) {
|
||||||
|
player.getWorld().playSound(player, Sound.ENTITY_EVOKER_PREPARE_WOLOLO,10,1);
|
||||||
|
for (int i = 0; i < 15; i++) {
|
||||||
|
spawnLoyalVex(player,player.getEyeLocation(),NamedTextColor.DARK_GRAY);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Quartz ", description = "Spawns 10 White Vex Loyal to you. They despawn after 15 seconds", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean quartzAbility(Player player) {
|
||||||
|
player.getWorld().playSound(player, Sound.ENTITY_EVOKER_PREPARE_WOLOLO,10,1);
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
spawnLoyalVex(player,player.getEyeLocation(),NamedTextColor.WHITE);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Redstone ", description = "Spawns 10 Red Vex Loyal to you. They despawn after 15 seconds", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean redstoneAbility(Player player) {
|
||||||
|
player.getWorld().playSound(player, Sound.ENTITY_EVOKER_PREPARE_WOLOLO,10,1);
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
spawnLoyalVex(player,player.getEyeLocation(),NamedTextColor.RED);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Resin ", description = "Spawns 10 Gold Vex Loyal to you. They despawn after 15 seconds", cooldownTicks = 20 * 30)
|
||||||
|
@Override
|
||||||
|
public boolean resinAbility(Player player) {
|
||||||
|
player.getWorld().playSound(player, Sound.ENTITY_EVOKER_PREPARE_WOLOLO,10,1);
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
spawnLoyalVex(player,player.getEyeLocation(),NamedTextColor.GOLD);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,361 @@
|
|||||||
|
package me.trouper.trimserver.server.systems.abilities.trims;
|
||||||
|
|
||||||
|
import io.papermc.paper.event.entity.WardenAngerChangeEvent;
|
||||||
|
import me.trouper.trimserver.server.events.QuickListener;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.MaterialInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.AbstractAbility;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.PatternInfo;
|
||||||
|
import me.trouper.trimserver.utils.SoundPlayer;
|
||||||
|
import me.trouper.trimserver.utils.Text;
|
||||||
|
import me.trouper.trimserver.utils.Verbose;
|
||||||
|
import org.bukkit.*;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.BlockState;
|
||||||
|
import org.bukkit.block.SculkSensor;
|
||||||
|
import org.bukkit.entity.*;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.block.BlockReceiveGameEvent;
|
||||||
|
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||||
|
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||||
|
import org.bukkit.event.entity.EntityDamageEvent;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
|
import org.bukkit.event.player.PlayerToggleSneakEvent;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimPattern;
|
||||||
|
import org.bukkit.potion.PotionEffect;
|
||||||
|
import org.bukkit.potion.PotionEffectType;
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
@PatternInfo(name = "Warden's Assistant", description = "You glazed the warden so much that he just ignores you now. Wardens and sculk sensors won't detect you.")
|
||||||
|
public class WardAbility extends AbstractAbility {
|
||||||
|
|
||||||
|
// Map to track active disguises: player UUID -> disguise entity and task
|
||||||
|
private final Map<UUID, DisguiseData> activeDisguises = new HashMap<>();
|
||||||
|
|
||||||
|
// Inner class to track disguise data
|
||||||
|
private static class DisguiseData {
|
||||||
|
final Warden disguise;
|
||||||
|
final BukkitTask expirationTask;
|
||||||
|
|
||||||
|
public DisguiseData(Warden disguise, BukkitTask expirationTask) {
|
||||||
|
this.disguise = disguise;
|
||||||
|
this.expirationTask = expirationTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public WardAbility() {
|
||||||
|
super(TrimPattern.WARD);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void transformPlayer(Player player) {
|
||||||
|
Verbose.send( "transformPlayer called for %s; currently disguised? %b",
|
||||||
|
player.getName(), activeDisguises.containsKey(player.getUniqueId()));
|
||||||
|
|
||||||
|
if (activeDisguises.containsKey(player.getUniqueId())) {
|
||||||
|
Verbose.send( "Player %s already disguised; removing disguise", player.getName());
|
||||||
|
removeDisguise(player);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// spawn and configure warden
|
||||||
|
Warden warden = (Warden) player.getWorld().spawnEntity(player.getLocation().subtract(0,3,0), EntityType.WARDEN, CreatureSpawnEvent.SpawnReason.NATURAL);
|
||||||
|
Verbose.send( "Spawned Warden entity %s for %s", warden.getUniqueId(), player.getName());
|
||||||
|
warden.setAI(false);
|
||||||
|
warden.setInvulnerable(false);
|
||||||
|
warden.setGravity(false);
|
||||||
|
warden.setSilent(true);
|
||||||
|
warden.customName(player.name());
|
||||||
|
warden.setCustomNameVisible(true);
|
||||||
|
|
||||||
|
player.hideEntity(main.getPlugin(), warden);
|
||||||
|
for (Player other : Bukkit.getOnlinePlayers()) {
|
||||||
|
if (!other.equals(player)) other.hidePlayer(main.getPlugin(), player);
|
||||||
|
}
|
||||||
|
|
||||||
|
player.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS,20 * 4,6,true,false,false));
|
||||||
|
player.addPotionEffect(new PotionEffect(PotionEffectType.JUMP_BOOST,20 * 4,127,true,false,false));
|
||||||
|
new SoundPlayer(player.getLocation(), Sound.ENTITY_WARDEN_EMERGE, 1.0f, 1.0f).playWithin(20);
|
||||||
|
|
||||||
|
BukkitTask expiration = Bukkit.getScheduler().runTaskLater(main.getPlugin(), () -> {
|
||||||
|
Verbose.send( "Expiration task running for %s", player.getName());
|
||||||
|
removeDisguise(player);
|
||||||
|
}, (20*3) + (20 * 30));
|
||||||
|
|
||||||
|
Location particleLoc = player.getLocation().clone().add(0.5,0.5,0.5);
|
||||||
|
AtomicInteger stepsTaken = new AtomicInteger(0);
|
||||||
|
Bukkit.getScheduler().runTaskTimer(main.getPlugin(),task->{
|
||||||
|
if (stepsTaken.getAndIncrement() >= 20*4) {
|
||||||
|
player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS,20 * 30, 0, true,false,false));
|
||||||
|
player.addPotionEffect(new PotionEffect(PotionEffectType.ABSORPTION,20 * 30, 9,true,false,false));
|
||||||
|
activeDisguises.put(player.getUniqueId(), new DisguiseData(warden, expiration));
|
||||||
|
task.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
warden.teleport(warden.getLocation().clone().add(0, (double) 3 /(20*4),0));
|
||||||
|
particleLoc.getWorld().spawnParticle(Particle.BLOCK_CRUMBLE,particleLoc,20,1,0,1,0.4, Material.SCULK.createBlockData());
|
||||||
|
},0,1);
|
||||||
|
|
||||||
|
player.sendMessage(Text.getMessage("You have transformed into the Warden."));
|
||||||
|
Verbose.send( "Disguise applied: %s → %s", player.getName(), warden.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeDisguise(Player player) {
|
||||||
|
Verbose.send( "removeDisguise called for %s", player.getName());
|
||||||
|
DisguiseData data = activeDisguises.remove(player.getUniqueId());
|
||||||
|
if (data == null) {
|
||||||
|
Verbose.send( "No disguise data found for %s; aborting", player.getName());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.disguise.remove();
|
||||||
|
Verbose.send( "Removed disguise entity %s", data.disguise.getUniqueId());
|
||||||
|
|
||||||
|
if (!data.expirationTask.isCancelled()) {
|
||||||
|
data.expirationTask.cancel();
|
||||||
|
Verbose.send( "Cancelled expiration task for %s", player.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Player other : Bukkit.getOnlinePlayers()) {
|
||||||
|
other.showPlayer(main.getPlugin(), player);
|
||||||
|
}
|
||||||
|
player.removePotionEffect(PotionEffectType.BLINDNESS);
|
||||||
|
player.removePotionEffect(PotionEffectType.ABSORPTION);
|
||||||
|
new SoundPlayer(player.getLocation(), Sound.ENTITY_WARDEN_DEATH, 1.0f, 1.2f).playWithin(20);
|
||||||
|
player.sendMessage(Text.getMessage("You have returned to normal form."));
|
||||||
|
Verbose.send( "Disguise fully removed for %s", player.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDisguisePosition(Player player) {
|
||||||
|
DisguiseData data = activeDisguises.get(player.getUniqueId());
|
||||||
|
if (data != null) {
|
||||||
|
data.disguise.teleport(player.getLocation());
|
||||||
|
Verbose.send( "Teleported disguise %s to %s for player %s",
|
||||||
|
data.disguise.getUniqueId(),
|
||||||
|
player.getLocation().toVector(),
|
||||||
|
player.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Player getPlayerByDisguise(Entity disguise) {
|
||||||
|
for (Map.Entry<UUID, DisguiseData> e : activeDisguises.entrySet()) {
|
||||||
|
if (e.getValue().disguise.equals(disguise)) {
|
||||||
|
Verbose.send( "Mapped disguise %s → player %s", disguise.getUniqueId(), e.getKey());
|
||||||
|
return Bukkit.getPlayer(e.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Warden getDisguiseByPlayer(Player player) {
|
||||||
|
return activeDisguises.getOrDefault(player.getUniqueId(),null).disguise;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerJoin(PlayerJoinEvent e) {
|
||||||
|
Verbose.send( "onPlayerJoin: %s", e.getPlayer().getName());
|
||||||
|
for (UUID uid : activeDisguises.keySet()) {
|
||||||
|
Player disguised = Bukkit.getPlayer(uid);
|
||||||
|
if (disguised != null && disguised.isOnline()) {
|
||||||
|
e.getPlayer().hidePlayer(main.getPlugin(), disguised);
|
||||||
|
Verbose.send( "Hid disguised player %s from joining %s",
|
||||||
|
disguised.getName(), e.getPlayer().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (activeDisguises.containsKey(e.getPlayer().getUniqueId())) {
|
||||||
|
e.getPlayer().hideEntity(main.getPlugin(), activeDisguises.get(e.getPlayer().getUniqueId()).disguise);
|
||||||
|
Verbose.send( "Re-hid own disguise for %s upon rejoin", e.getPlayer().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerQuit(PlayerQuitEvent e) {
|
||||||
|
Verbose.send( "onPlayerQuit: %s", e.getPlayer().getName());
|
||||||
|
if (activeDisguises.containsKey(e.getPlayer().getUniqueId())) {
|
||||||
|
Verbose.send( "Player %s quit while disguised; removing disguise", e.getPlayer().getName());
|
||||||
|
removeDisguise(e.getPlayer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onSensor(BlockReceiveGameEvent e) {
|
||||||
|
if (e.getBlock().getState() instanceof SculkSensor sensor
|
||||||
|
&& e.getEntity() instanceof Player player) {
|
||||||
|
|
||||||
|
AbstractAbility ability = main.man().abilityBackend.getAbility(player);
|
||||||
|
if (ability != null && ability.getPattern().equals(TrimPattern.WARD)) {
|
||||||
|
e.setCancelled(true);
|
||||||
|
Verbose.send( "Cancelled sculk sensor trigger at %s by %s",
|
||||||
|
sensor.getBlock().getLocation().toVector(), player.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onWarden(WardenAngerChangeEvent e) {
|
||||||
|
if (e.getTarget() instanceof Player player) {
|
||||||
|
AbstractAbility ability = main.man().abilityBackend.getAbility(player);
|
||||||
|
if (ability != null && ability.getPattern().equals(TrimPattern.WARD)) {
|
||||||
|
e.setCancelled(true);
|
||||||
|
e.getEntity().setTarget(null);
|
||||||
|
Verbose.send( "Prevented Warden %s from targeting %s",
|
||||||
|
e.getEntity().getUniqueId(), player.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerSneak(PlayerToggleSneakEvent e) {
|
||||||
|
Player player = e.getPlayer();
|
||||||
|
if (activeDisguises.containsKey(player.getUniqueId()) && e.isSneaking()) {
|
||||||
|
Verbose.send( "%s sneaked; playing roar", player.getName());
|
||||||
|
|
||||||
|
new SoundPlayer(player.getLocation(), Sound.ENTITY_WARDEN_ROAR, 1.0f, 1.0f).playWithin(15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onEntityDamageByEntity(EntityDamageByEntityEvent e) {
|
||||||
|
if (e.getDamager() instanceof Player attacker) {
|
||||||
|
if (activeDisguises.containsKey(attacker.getUniqueId())) {
|
||||||
|
activeDisguises.get(attacker.getUniqueId()).disguise.swingMainHand();
|
||||||
|
new SoundPlayer(attacker.getLocation(), Sound.ENTITY_WARDEN_ATTACK_IMPACT, 1.0f, 1.0f)
|
||||||
|
.playWithin(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (e.getEntity() instanceof Warden warden) {
|
||||||
|
Player real = getPlayerByDisguise(warden);
|
||||||
|
if (real != null) {
|
||||||
|
Verbose.send( "Redirecting damage from disguise %s to %s",
|
||||||
|
warden.getUniqueId(), real.getName());
|
||||||
|
e.setCancelled(true);
|
||||||
|
warden.playHurtAnimation(0);
|
||||||
|
real.damage(e.getDamage(), e.getDamager());
|
||||||
|
new SoundPlayer(real.getLocation(), Sound.ENTITY_WARDEN_HURT, 1.0f, 1.0f)
|
||||||
|
.playWithin(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onEntityDamage(EntityDamageEvent e) {
|
||||||
|
if (e.getEntity() instanceof Warden warden) {
|
||||||
|
Player real = getPlayerByDisguise(warden);
|
||||||
|
if (real != null && e.getCause() != EntityDamageEvent.DamageCause.ENTITY_ATTACK
|
||||||
|
&& e.getCause() != EntityDamageEvent.DamageCause.ENTITY_SWEEP_ATTACK) {
|
||||||
|
Verbose.send( "Redirecting environmental damage (%s) from %s to %s",
|
||||||
|
e.getCause(), warden.getUniqueId(), real.getName());
|
||||||
|
e.setCancelled(true);
|
||||||
|
warden.playHurtAnimation(0);
|
||||||
|
real.damage(e.getDamage());
|
||||||
|
new SoundPlayer(real.getLocation(), Sound.ENTITY_WARDEN_HURT, 1.0f, 1.0f)
|
||||||
|
.playWithin(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuickListener registerEvents() {
|
||||||
|
Verbose.send( "Scheduling disguise position updater");
|
||||||
|
Bukkit.getScheduler().runTaskTimer(main.getPlugin(), () -> {
|
||||||
|
//Verbose.send( "Tick: updating %d disguises", activeDisguises.size());
|
||||||
|
for (UUID uid : activeDisguises.keySet()) {
|
||||||
|
Player p = Bukkit.getPlayer(uid);
|
||||||
|
if (p != null && p.isOnline()) {
|
||||||
|
updateDisguisePosition(p);
|
||||||
|
} else {
|
||||||
|
Verbose.send( "Auto-removing stale disguise for %s", uid);
|
||||||
|
DisguiseData d = activeDisguises.remove(uid);
|
||||||
|
d.disguise.remove();
|
||||||
|
d.expirationTask.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 1, 1);
|
||||||
|
|
||||||
|
return super.registerEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Amethyst Fusion", description = "Transform into a Warden for 30 seconds. While disguised, you gain Warden's strength but suffer from blindness", cooldownTicks = 20 * 60)
|
||||||
|
@Override
|
||||||
|
public boolean amethystAbility(Player player) {
|
||||||
|
transformPlayer(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Copper Fusion", description = "Transform into a Warden for 30 seconds. While disguised, you gain Warden's strength but suffer from blindness", cooldownTicks = 20 * 60)
|
||||||
|
@Override
|
||||||
|
public boolean copperAbility(Player player) {
|
||||||
|
transformPlayer(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Diamond Fusion", description = "Transform into a Warden for 30 seconds. While disguised, you gain Warden's strength but suffer from blindness", cooldownTicks = 20 * 60)
|
||||||
|
@Override
|
||||||
|
public boolean diamondAbility(Player player) {
|
||||||
|
transformPlayer(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Emerald Fusion", description = "Transform into a Warden for 30 seconds. While disguised, you gain Warden's strength but suffer from blindness", cooldownTicks = 20 * 60)
|
||||||
|
@Override
|
||||||
|
public boolean emeraldAbility(Player player) {
|
||||||
|
transformPlayer(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Gold Fusion", description = "Transform into a Warden for 30 seconds. While disguised, you gain Warden's strength but suffer from blindness", cooldownTicks = 20 * 60)
|
||||||
|
@Override
|
||||||
|
public boolean goldAbility(Player player) {
|
||||||
|
transformPlayer(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Iron Fusion", description = "Transform into a Warden for 30 seconds. While disguised, you gain Warden's strength but suffer from blindness", cooldownTicks = 20 * 60)
|
||||||
|
@Override
|
||||||
|
public boolean ironAbility(Player player) {
|
||||||
|
transformPlayer(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Lapis Fusion", description = "Transform into a Warden for 30 seconds. While disguised, you gain Warden's strength but suffer from blindness", cooldownTicks = 20 * 60)
|
||||||
|
@Override
|
||||||
|
public boolean lapisAbility(Player player) {
|
||||||
|
transformPlayer(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Netherite Fusion", description = "Transform into a Warden for 30 seconds. While disguised, you gain Warden's strength but suffer from blindness", cooldownTicks = 20 * 60)
|
||||||
|
@Override
|
||||||
|
public boolean netheriteAbility(Player player) {
|
||||||
|
transformPlayer(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Quartz Fusion", description = "Transform into a Warden for 30 seconds. While disguised, you gain Warden's strength but suffer from blindness", cooldownTicks = 20 * 60)
|
||||||
|
@Override
|
||||||
|
public boolean quartzAbility(Player player) {
|
||||||
|
transformPlayer(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Redstone Fusion", description = "Transform into a Warden for 30 seconds. While disguised, you gain Warden's strength but suffer from blindness", cooldownTicks = 20 * 60)
|
||||||
|
@Override
|
||||||
|
public boolean redstoneAbility(Player player) {
|
||||||
|
transformPlayer(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Resin Fusion", description = "Transform into a Warden for 30 seconds. While disguised, you gain Warden's strength but suffer from blindness", cooldownTicks = 20 * 60)
|
||||||
|
@Override
|
||||||
|
public boolean resinAbility(Player player) {
|
||||||
|
transformPlayer(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
package me.trouper.trimserver.server.systems.abilities.trims;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.MaterialInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.AbstractAbility;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.PatternInfo;
|
||||||
|
import me.trouper.trimserver.utils.SoundPlayer;
|
||||||
|
import me.trouper.trimserver.utils.TargetingUtils;
|
||||||
|
import me.trouper.trimserver.utils.visual.DisplayUtils;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Particle;
|
||||||
|
import org.bukkit.Sound;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.BlockState;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.damage.DamageSource;
|
||||||
|
import org.bukkit.damage.DamageType;
|
||||||
|
import org.bukkit.entity.*;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimPattern;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
@PatternInfo(name = "Big Step", description = "\"He boot too big for he gotdamn feet.\"")
|
||||||
|
public class WayfinderAbility extends AbstractAbility {
|
||||||
|
|
||||||
|
public WayfinderAbility() {
|
||||||
|
super(TrimPattern.WAYFINDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stomp(Player caster) {
|
||||||
|
if (caster.getLocation().clone().subtract(0,1,0).getBlock().isPassable()) return;
|
||||||
|
caster.setVelocity(caster.getVelocity().setY(caster.getVelocity().getY() + 1.5));
|
||||||
|
SoundPlayer jump = new SoundPlayer(caster.getLocation(), Sound.ENTITY_ENDER_DRAGON_FLAP, 1, 2);
|
||||||
|
jump.playWithin(10);
|
||||||
|
|
||||||
|
DisplayUtils.disc(caster.getLocation(),3,0.5,0.25,point->{
|
||||||
|
point.getWorld().spawnParticle(Particle.POOF,point,1,0,0,0,0);
|
||||||
|
});
|
||||||
|
|
||||||
|
AtomicInteger expiry = new AtomicInteger(80);
|
||||||
|
Bukkit.getScheduler().runTaskTimer(main.getPlugin(),task -> {
|
||||||
|
if (expiry.getAndDecrement() <= 0) {
|
||||||
|
task.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!caster.getLocation().clone().subtract(0,1,0).getBlock().isPassable()) {
|
||||||
|
|
||||||
|
DisplayUtils.wave(caster.getLocation(),10,1,1,point -> {
|
||||||
|
Block thrown = point.getWorld().getHighestBlockAt((int) point.x(), (int) point.z());
|
||||||
|
BlockData data = thrown.getBlockData();
|
||||||
|
BlockState state = point.getWorld().getHighestBlockAt((int) point.x(), (int) point.z()).getState();
|
||||||
|
|
||||||
|
FallingBlock block = (FallingBlock) point.getWorld().spawnEntity(point.clone(), EntityType.FALLING_BLOCK);
|
||||||
|
block.setBlockData(data);
|
||||||
|
block.setBlockState(state);
|
||||||
|
block.setVelocity(new Vector(0,0.1,0));
|
||||||
|
block.setCancelDrop(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
TargetingUtils.areaAffect(caster.getLocation(),10,target -> !target.isDead() && !target.equals(caster) && !main.man().trustBackend.trusts(caster,target),target->{
|
||||||
|
SoundPlayer hit = new SoundPlayer(target.getLocation(), Sound.ENTITY_EVOKER_FANGS_ATTACK, 1, 2);
|
||||||
|
Vector direction = target.getLocation().toVector().subtract(caster.getEyeLocation().toVector()).normalize();
|
||||||
|
target.setVelocity(direction.multiply(0.5).setY(0.3));
|
||||||
|
target.damage(15, DamageSource.builder(DamageType.FALLING_BLOCK).build());
|
||||||
|
hit.playWithin(10);
|
||||||
|
caster.getWorld().spawnParticle(Particle.DAMAGE_INDICATOR,target.getLocation().clone().add(0,1,0),10,0.2,1,0.2,0.1);
|
||||||
|
});
|
||||||
|
|
||||||
|
task.cancel();
|
||||||
|
}
|
||||||
|
},10,2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Amethyst ", description = "Jump into the air, creating a seismic disturbance when you land. Deals 5 Damage", cooldownTicks = 20 * 4)
|
||||||
|
@Override
|
||||||
|
public boolean amethystAbility(Player player) {
|
||||||
|
stomp(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Copper ", description = "Jump into the air, creating a seismic disturbance when you land. Deals 5 Damage", cooldownTicks = 20 * 4)
|
||||||
|
@Override
|
||||||
|
public boolean copperAbility(Player player) {
|
||||||
|
stomp(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Diamond ", description = "Jump into the air, creating a seismic disturbance when you land. Deals 5 Damage", cooldownTicks = 20 * 4)
|
||||||
|
@Override
|
||||||
|
public boolean diamondAbility(Player player) {
|
||||||
|
stomp(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Emerald Bolt", description = "Jump into the air, creating a seismic disturbance when you land. Deals 5 Damage", cooldownTicks = 20 * 4)
|
||||||
|
@Override
|
||||||
|
public boolean emeraldAbility(Player player) {
|
||||||
|
stomp(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Gold ", description = "Jump into the air, creating a seismic disturbance when you land. Deals 5 Damage", cooldownTicks = 20 * 4)
|
||||||
|
@Override
|
||||||
|
public boolean goldAbility(Player player) {
|
||||||
|
stomp(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Iron ", description = "Jump into the air, creating a seismic disturbance when you land. Deals 5 Damage", cooldownTicks = 20 * 4)
|
||||||
|
@Override
|
||||||
|
public boolean ironAbility(Player player) {
|
||||||
|
stomp(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Lapis ", description = "Jump into the air, creating a seismic disturbance when you land. Deals 5 Damage", cooldownTicks = 20 * 4)
|
||||||
|
@Override
|
||||||
|
public boolean lapisAbility(Player player) {
|
||||||
|
stomp(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Netherite ", description = "Jump into the air, creating a seismic disturbance when you land. Deals 5 Damage", cooldownTicks = 20 * 3)
|
||||||
|
@Override
|
||||||
|
public boolean netheriteAbility(Player player) {
|
||||||
|
stomp(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Quartz ", description = "Jump into the air, creating a seismic disturbance when you land. Deals 5 Damage", cooldownTicks = 20 * 4)
|
||||||
|
@Override
|
||||||
|
public boolean quartzAbility(Player player) {
|
||||||
|
stomp(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Redstone ", description = "Jump into the air, creating a seismic disturbance when you land. Deals 5 Damage", cooldownTicks = 20 * 4)
|
||||||
|
@Override
|
||||||
|
public boolean redstoneAbility(Player player) {
|
||||||
|
stomp(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Resin ", description = "Jump into the air, creating a seismic disturbance when you land. Deals 5 Damage", cooldownTicks = 20 * 4)
|
||||||
|
@Override
|
||||||
|
public boolean resinAbility(Player player) {
|
||||||
|
stomp(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
package me.trouper.trimserver.server.systems.abilities.trims;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.MaterialInfo;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.AbstractAbility;
|
||||||
|
import me.trouper.trimserver.server.systems.abilities.PatternInfo;
|
||||||
|
import me.trouper.trimserver.utils.PlayerUtils;
|
||||||
|
import me.trouper.trimserver.utils.TargetingUtils;
|
||||||
|
import me.trouper.trimserver.utils.visual.BlockDisplayRaytracer;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.Sound;
|
||||||
|
import org.bukkit.damage.DamageSource;
|
||||||
|
import org.bukkit.damage.DamageType;
|
||||||
|
import org.bukkit.entity.BlockDisplay;
|
||||||
|
import org.bukkit.entity.LivingEntity;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.meta.trim.TrimPattern;
|
||||||
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@PatternInfo(name = "Wild Trim", description = "\"GET OVER HERE!\"")
|
||||||
|
public class WildAbility extends AbstractAbility {
|
||||||
|
|
||||||
|
public WildAbility() {
|
||||||
|
super(TrimPattern.WILD);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pullVine(Player caster, Player target, Material vineMaterial) {
|
||||||
|
// --- Basic Validation ---
|
||||||
|
if (caster == null || !caster.isOnline() || target == null || !target.isOnline() || caster.equals(target)) return;
|
||||||
|
if (caster.getWorld() != target.getWorld()) return;
|
||||||
|
|
||||||
|
|
||||||
|
// --- Configuration ---
|
||||||
|
final Location casterStartLoc = caster.getLocation().clone().add(0,1,0);
|
||||||
|
final double maxDistance = 20.0;
|
||||||
|
final double pullSpeed = 0.2;
|
||||||
|
final double vineThickness = 0.2;
|
||||||
|
final int pullDurationTicks = 80;
|
||||||
|
final long updateInterval = 1L;
|
||||||
|
final double minPullDistance = 2.0;
|
||||||
|
|
||||||
|
// --- Initial Check ---
|
||||||
|
if (casterStartLoc.distanceSquared(target.getLocation()) > maxDistance * maxDistance) return;
|
||||||
|
|
||||||
|
caster.getWorld().playSound(casterStartLoc, Sound.ENTITY_FISHING_BOBBER_THROW, 1.0f, 0.8f);
|
||||||
|
|
||||||
|
|
||||||
|
// --- Task for Pulling & Visuals ---
|
||||||
|
final List<BlockDisplay> currentVineSegment = new ArrayList<>(1);
|
||||||
|
|
||||||
|
new BukkitRunnable() {
|
||||||
|
int ticksElapsed = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// --- Cancellation Conditions ---
|
||||||
|
if (ticksElapsed >= pullDurationTicks ||
|
||||||
|
!caster.isOnline() ||
|
||||||
|
!target.isOnline() ||
|
||||||
|
caster.getWorld() != target.getWorld() ||
|
||||||
|
caster.isDead() || target.isDead()
|
||||||
|
) {
|
||||||
|
cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location currentCasterPos = caster.getLocation().clone().add(0,1,0);
|
||||||
|
Location currentTargetPos = target.getLocation().add(0, target.getHeight() / 2.0, 0);
|
||||||
|
double currentDistance = currentCasterPos.distance(currentTargetPos);
|
||||||
|
|
||||||
|
if (currentDistance < minPullDistance) {
|
||||||
|
cancel();
|
||||||
|
caster.getWorld().playSound(currentCasterPos, Sound.ENTITY_ITEM_PICKUP, 1.0f, 1.0f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector pullDirection = currentCasterPos.toVector().subtract(currentTargetPos.toVector()).normalize();
|
||||||
|
Vector pullVelocity = pullDirection.multiply(pullSpeed);
|
||||||
|
|
||||||
|
//pullVelocity.setY(Math.min(pullVelocity.getY(), 0.05)); // Y Clamp
|
||||||
|
caster.getWorld().playSound(currentCasterPos, Sound.BLOCK_LEAF_LITTER_BREAK, 1.0f, 1.0f);
|
||||||
|
target.setVelocity(target.getVelocity().add(pullVelocity));
|
||||||
|
|
||||||
|
|
||||||
|
// --- Update Vine Visual ---
|
||||||
|
|
||||||
|
BlockDisplay segment = BlockDisplayRaytracer.trace(
|
||||||
|
vineMaterial,
|
||||||
|
currentCasterPos,
|
||||||
|
currentTargetPos,
|
||||||
|
vineThickness,
|
||||||
|
updateInterval + 2
|
||||||
|
);
|
||||||
|
BlockDisplayRaytracer.trace(
|
||||||
|
vineMaterial,
|
||||||
|
currentTargetPos.clone().subtract(0,target.getHeight(),0),
|
||||||
|
currentTargetPos.clone().add(0,target.getHeight(),0),
|
||||||
|
target.getWidth() * 1.5,
|
||||||
|
updateInterval + 1
|
||||||
|
);
|
||||||
|
|
||||||
|
if (segment != null) {
|
||||||
|
currentVineSegment.add(segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
ticksElapsed++;
|
||||||
|
}
|
||||||
|
}.runTaskTimer(main.getPlugin(), 0L, updateInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Amethyst", description = "Shoot a vine out which wraps around nearby players, pulling them towards you", cooldownTicks = 120)
|
||||||
|
@Override
|
||||||
|
public boolean amethystAbility(Player player) {
|
||||||
|
Player closestTarget = PlayerUtils.playerClosestAngle(player, 40);
|
||||||
|
if (closestTarget != null) {
|
||||||
|
pullVine(player, closestTarget, Material.OAK_LEAVES);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Copper", description = "Shoot a vine out which wraps around nearby players, pulling them towards you", cooldownTicks = 120)
|
||||||
|
@Override
|
||||||
|
public boolean copperAbility(Player player) {
|
||||||
|
Player closestTarget = PlayerUtils.playerClosestAngle(player, 40);
|
||||||
|
if (closestTarget != null) {
|
||||||
|
pullVine(player, closestTarget, Material.OAK_LEAVES);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Diamond", description = "Shoot a vine out which wraps around nearby players, pulling them towards you", cooldownTicks = 120)
|
||||||
|
@Override
|
||||||
|
public boolean diamondAbility(Player player) {
|
||||||
|
Player closestTarget = PlayerUtils.playerClosestAngle(player, 40);
|
||||||
|
if (closestTarget != null) {
|
||||||
|
pullVine(player, closestTarget, Material.OAK_LEAVES);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Emerald", description = "Shoot a vine out which wraps around nearby players, pulling them towards you", cooldownTicks = 120)
|
||||||
|
@Override
|
||||||
|
public boolean emeraldAbility(Player player) {
|
||||||
|
Player closestTarget = PlayerUtils.playerClosestAngle(player, 40);
|
||||||
|
if (closestTarget != null) {
|
||||||
|
pullVine(player, closestTarget, Material.OAK_LEAVES);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Gold", description = "Shoot a vine out which wraps around nearby players, pulling them towards you", cooldownTicks = 120)
|
||||||
|
@Override
|
||||||
|
public boolean goldAbility(Player player) {
|
||||||
|
Player closestTarget = PlayerUtils.playerClosestAngle(player, 40);
|
||||||
|
if (closestTarget != null) {
|
||||||
|
pullVine(player, closestTarget, Material.OAK_LEAVES);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Iron", description = "Shoot a vine out which wraps around nearby players, pulling them towards you", cooldownTicks = 120)
|
||||||
|
@Override
|
||||||
|
public boolean ironAbility(Player player) {
|
||||||
|
Player closestTarget = PlayerUtils.playerClosestAngle(player, 40);
|
||||||
|
if (closestTarget != null) {
|
||||||
|
pullVine(player, closestTarget, Material.OAK_LEAVES);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Lapis", description = "Shoot a vine out which wraps around nearby players, pulling them towards you", cooldownTicks = 120)
|
||||||
|
@Override
|
||||||
|
public boolean lapisAbility(Player player) {
|
||||||
|
Player closestTarget = PlayerUtils.playerClosestAngle(player, 40);
|
||||||
|
if (closestTarget != null) {
|
||||||
|
pullVine(player, closestTarget, Material.OAK_LEAVES);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Netherite", description = "Shoot a steel vine out which wraps around nearby players, pulling them towards you and choking them. (7 damage)", cooldownTicks = 80)
|
||||||
|
@Override
|
||||||
|
public boolean netheriteAbility(Player player) {
|
||||||
|
Player closestTarget = PlayerUtils.playerClosestAngle(player, 40);
|
||||||
|
if (closestTarget != null) {
|
||||||
|
pullVine(player, closestTarget, Material.OAK_LEAVES);
|
||||||
|
closestTarget.damage(7, DamageSource.builder(DamageType.IN_WALL).withDamageLocation(player.getEyeLocation()).withDirectEntity(player).build());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Quartz", description = "Shoot a vine out which wraps around nearby players, pulling them towards you", cooldownTicks = 120)
|
||||||
|
@Override
|
||||||
|
public boolean quartzAbility(Player player) {
|
||||||
|
Player closestTarget = PlayerUtils.playerClosestAngle(player, 40);
|
||||||
|
if (closestTarget != null) {
|
||||||
|
pullVine(player, closestTarget, Material.OAK_LEAVES);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Redstone", description = "Shoot a vine out which wraps around nearby players, pulling them towards you", cooldownTicks = 120)
|
||||||
|
@Override
|
||||||
|
public boolean redstoneAbility(Player player) {
|
||||||
|
Player closestTarget = PlayerUtils.playerClosestAngle(player, 40);
|
||||||
|
if (closestTarget != null) {
|
||||||
|
pullVine(player, closestTarget, Material.OAK_LEAVES);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MaterialInfo(name = "Resin", description = "Shoot a vine out which wraps around nearby players, pulling them towards you", cooldownTicks = 120)
|
||||||
|
@Override
|
||||||
|
public boolean resinAbility(Player player) {
|
||||||
|
Player closestTarget = PlayerUtils.playerClosestAngle(player, 40);
|
||||||
|
if (closestTarget != null) {
|
||||||
|
pullVine(player, closestTarget, Material.OAK_LEAVES);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
129
src/main/java/me/trouper/trimserver/utils/ItemUtils.java
Normal file
129
src/main/java/me/trouper/trimserver/utils/ItemUtils.java
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
package me.trouper.trimserver.utils;
|
||||||
|
|
||||||
|
import com.google.common.collect.Multimap;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.attribute.Attribute;
|
||||||
|
import org.bukkit.attribute.AttributeModifier;
|
||||||
|
import org.bukkit.enchantments.Enchantment;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ItemUtils {
|
||||||
|
|
||||||
|
public static boolean isArmor(ItemStack i) {
|
||||||
|
if (i == null || i.isEmpty()) return false;
|
||||||
|
|
||||||
|
return isHelmet(i) || isChestplate(i) || isLeggings(i) || isBoots(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isHelmet(ItemStack i) {
|
||||||
|
if (i == null || i.isEmpty()) return false;
|
||||||
|
|
||||||
|
Material m = i.getType();
|
||||||
|
String n = m.name();
|
||||||
|
return n.contains("HELMET");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isChestplate(ItemStack i) {
|
||||||
|
if (i == null || i.isEmpty()) return false;
|
||||||
|
|
||||||
|
Material m = i.getType();
|
||||||
|
String n = m.name();
|
||||||
|
return n.contains("CHESTPLATE");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isLeggings(ItemStack i) {
|
||||||
|
if (i == null || i.isEmpty()) return false;
|
||||||
|
|
||||||
|
Material m = i.getType();
|
||||||
|
String n = m.name();
|
||||||
|
return n.contains("LEGGINGS");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isBoots(ItemStack i) {
|
||||||
|
if (i == null || i.isEmpty()) return false;
|
||||||
|
|
||||||
|
Material m = i.getType();
|
||||||
|
String n = m.name();
|
||||||
|
return n.contains("BOOTS");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public static boolean isSimilar(ItemStack item1, ItemStack item2) {
|
||||||
|
if (item1 == null || item2 == null) {
|
||||||
|
Verbose.send("One of the items is null: item1: %s, item2: %s", item1, item2);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Material type1 = item1.getType();
|
||||||
|
Material type2 = item2.getType();
|
||||||
|
boolean typeEqual = type1 == type2;
|
||||||
|
Verbose.send("Checking Material: item1 type: %s, item2 type: %s, equal: %s", type1, type2, typeEqual);
|
||||||
|
if (!typeEqual) return false;
|
||||||
|
|
||||||
|
boolean hasMeta1 = item1.hasItemMeta();
|
||||||
|
boolean hasMeta2 = item2.hasItemMeta();
|
||||||
|
boolean metaExistEqual = (hasMeta1 == hasMeta2);
|
||||||
|
Verbose.send("Checking ItemMeta existence: item1 has meta: %s, item2 has meta: %s, equal: %s", hasMeta1, hasMeta2, metaExistEqual);
|
||||||
|
if (!metaExistEqual) return false;
|
||||||
|
|
||||||
|
if (!hasMeta1 && !hasMeta2) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemMeta meta1 = item1.getItemMeta();
|
||||||
|
ItemMeta meta2 = item2.getItemMeta();
|
||||||
|
|
||||||
|
String name1 = meta1.hasDisplayName() ? meta1.getDisplayName() : null;
|
||||||
|
String name2 = meta2.hasDisplayName() ? meta2.getDisplayName() : null;
|
||||||
|
if (name1 == null ^ name2 == null) {
|
||||||
|
Verbose.send("Custom Name mismatch: item1 name: %s, item2 name: %s", name1, name2);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean nameEqual = (name1 == null || name1.equals(name2));
|
||||||
|
Verbose.send("Checking Custom Name: item1: %s, item2: %s, equal: %s", name1, name2, nameEqual);
|
||||||
|
if (!nameEqual) return false;
|
||||||
|
|
||||||
|
List<String> lore1 = meta1.hasLore() ? meta1.getLore() : null;
|
||||||
|
List<String> lore2 = meta2.hasLore() ? meta2.getLore() : null;
|
||||||
|
if (lore1 == null ^ lore2 == null) {
|
||||||
|
Verbose.send("Lore mismatch: item1 lore: %s, item2 lore: %s", lore1, lore2);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean loreEqual = (lore1 == null || lore1.equals(lore2));
|
||||||
|
Verbose.send("Checking Lore: item1: %s, item2: %s, equal: %s", lore1, lore2, loreEqual);
|
||||||
|
if (!loreEqual) return false;
|
||||||
|
|
||||||
|
int cmd1 = meta1.hasCustomModelData() ? meta1.getCustomModelData() : -1;
|
||||||
|
int cmd2 = meta2.hasCustomModelData() ? meta2.getCustomModelData() : -1;
|
||||||
|
boolean cmdEqual = (cmd1 == cmd2);
|
||||||
|
Verbose.send("Checking Custom Model Data: item1: %d, item2: %d, equal: %s", cmd1, cmd2, cmdEqual);
|
||||||
|
if (!cmdEqual) return false;
|
||||||
|
|
||||||
|
Map<Enchantment, Integer> enchants1 = meta1.getEnchants();
|
||||||
|
Map<Enchantment, Integer> enchants2 = meta2.getEnchants();
|
||||||
|
if (enchants1 == null ^ enchants2 == null) {
|
||||||
|
Verbose.send("Enchantments mismatch: item1 enchants: %s, item2 enchants: %s", enchants1, enchants2);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean enchantsEqual = (enchants1 == null || enchants1.equals(enchants2));
|
||||||
|
Verbose.send("Checking Enchantments: item1: %s, item2: %s, equal: %s", enchants1, enchants2, enchantsEqual);
|
||||||
|
if (!enchantsEqual) return false;
|
||||||
|
|
||||||
|
Multimap<Attribute, AttributeModifier> modifiers1 = meta1.getAttributeModifiers();
|
||||||
|
Multimap<Attribute, AttributeModifier> modifiers2 = meta2.getAttributeModifiers();
|
||||||
|
if (modifiers1 == null ^ modifiers2 == null) {
|
||||||
|
Verbose.send("Attribute Modifiers mismatch: item1 modifiers: %s, item2 modifiers: %s", modifiers1, modifiers2);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean modifiersEqual = (modifiers1 == null || modifiers1.equals(modifiers2));
|
||||||
|
Verbose.send("Checking Attribute Modifiers: item1: %s, item2: %s, equal: %s", modifiers1, modifiers2, modifiersEqual);
|
||||||
|
if (!modifiersEqual) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
92
src/main/java/me/trouper/trimserver/utils/PlayerUtils.java
Normal file
92
src/main/java/me/trouper/trimserver/utils/PlayerUtils.java
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package me.trouper.trimserver.utils;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.Main;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.damage.DamageSource;
|
||||||
|
import org.bukkit.entity.LivingEntity;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||||
|
import org.bukkit.event.entity.EntityDamageEvent;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
public class PlayerUtils implements Main {
|
||||||
|
|
||||||
|
public static final int DEFAULT_NO_DAMAGE_TICKS = 9;
|
||||||
|
public static final int DEFAULT_MAX_NO_DAMAGE_TICKS = 10;
|
||||||
|
public static final int NO_DAMAGE_TICKS = 1;
|
||||||
|
public static final int MAX_NO_DAMAGE_TICKS = 2;
|
||||||
|
|
||||||
|
public static void addRandomLookOffset(Player player, float maxOffsetDegrees, boolean modifyPitch, boolean modifyYaw) {
|
||||||
|
float currentYaw = player.getLocation().getYaw();
|
||||||
|
float currentPitch = player.getLocation().getPitch();
|
||||||
|
|
||||||
|
float newYaw = currentYaw;
|
||||||
|
float newPitch = currentPitch;
|
||||||
|
|
||||||
|
if (modifyYaw) {
|
||||||
|
float yawOffset = (random.nextFloat() * 2 - 1) * maxOffsetDegrees;
|
||||||
|
newYaw += yawOffset;
|
||||||
|
newYaw = wrapYaw(newYaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modifyPitch) {
|
||||||
|
float pitchOffset = (random.nextFloat() * 2 - 1) * maxOffsetDegrees;
|
||||||
|
newPitch += pitchOffset;
|
||||||
|
newPitch = clampPitch(newPitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
player.getLocation().setPitch(newPitch);
|
||||||
|
player.getLocation().setYaw(newYaw);
|
||||||
|
//player.set(player.getLocation().setDirection(getDirection(newYaw, newPitch)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float clampPitch(float pitch) {
|
||||||
|
return Math.max(-90, Math.min(90, pitch));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float wrapYaw(float yaw) {
|
||||||
|
yaw = yaw % 360;
|
||||||
|
if (yaw < 0) yaw += 360;
|
||||||
|
return yaw;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector getDirection(float yaw, float pitch) {
|
||||||
|
double radYaw = Math.toRadians(yaw);
|
||||||
|
double radPitch = Math.toRadians(pitch);
|
||||||
|
double x = -Math.cos(radPitch) * Math.sin(radYaw);
|
||||||
|
double y = -Math.sin(radPitch);
|
||||||
|
double z = Math.cos(radPitch) * Math.cos(radYaw);
|
||||||
|
return new Vector(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Player playerClosestAngle(Player player, double range) {
|
||||||
|
Vector playerDirection = player.getEyeLocation().getDirection().normalize();
|
||||||
|
Location eyeLoc = player.getEyeLocation();
|
||||||
|
|
||||||
|
return player.getNearbyEntities(range, range, range).stream()
|
||||||
|
.filter(entity -> entity instanceof Player && !entity.equals(player))
|
||||||
|
.map(entity -> (Player) entity)
|
||||||
|
.min((p1, p2) -> {
|
||||||
|
Vector dirToP1 = p1.getEyeLocation().toVector().subtract(eyeLoc.toVector()).normalize();
|
||||||
|
Vector dirToP2 = p2.getEyeLocation().toVector().subtract(eyeLoc.toVector()).normalize();
|
||||||
|
|
||||||
|
double angle1 = playerDirection.angle(dirToP1);
|
||||||
|
double angle2 = playerDirection.angle(dirToP2);
|
||||||
|
|
||||||
|
return Double.compare(angle1, angle2);
|
||||||
|
})
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public static void dealTrueDamage(LivingEntity target, DamageSource source, double amount) {
|
||||||
|
double newHealth = target.getHealth() - amount;
|
||||||
|
target.damage(0.1, source);
|
||||||
|
if (newHealth <= 0) {
|
||||||
|
target.setHealth(0);
|
||||||
|
} else {
|
||||||
|
target.setHealth(newHealth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
266
src/main/java/me/trouper/trimserver/utils/SoundPlayer.java
Normal file
266
src/main/java/me/trouper/trimserver/utils/SoundPlayer.java
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
package me.trouper.trimserver.utils;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.Main;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Sound;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
|
||||||
|
public class SoundPlayer implements Main {
|
||||||
|
|
||||||
|
private Location location;
|
||||||
|
private Sound sound;
|
||||||
|
private float volume;
|
||||||
|
private float pitch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new sound, this aims to add more methods to
|
||||||
|
* the Bukkit APIs Sound class, as they don't have many
|
||||||
|
* methods to use.
|
||||||
|
*
|
||||||
|
* @param location Location
|
||||||
|
* @param sound Sound
|
||||||
|
* @param volume float
|
||||||
|
* @param pitch float
|
||||||
|
*/
|
||||||
|
public SoundPlayer(Location location, Sound sound, float volume, float pitch) {
|
||||||
|
this.location = location;
|
||||||
|
this.sound = sound;
|
||||||
|
this.pitch = pitch;
|
||||||
|
this.volume = volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays a sound to a player but at the store location
|
||||||
|
*
|
||||||
|
* @param player Player
|
||||||
|
*/
|
||||||
|
public void play(Player player) {
|
||||||
|
player.playSound(this.location,this.sound,this.volume,this.pitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays a sound to a player but at the player's location
|
||||||
|
*
|
||||||
|
* @param player Player
|
||||||
|
*/
|
||||||
|
public void playIndividually(Player player) {
|
||||||
|
player.playSound(player.getLocation(),this.sound,this.volume,this.pitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays the sound to all players within a distance, but at the stored location.
|
||||||
|
*
|
||||||
|
* @param distance double
|
||||||
|
*/
|
||||||
|
public void playWithin(double distance) {
|
||||||
|
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||||
|
if (p != null && p.getWorld() == this.location.getWorld() && p.getLocation().distance(this.location) < distance) {
|
||||||
|
p.playSound(this.location,this.sound,this.volume,this.pitch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays the sound to all players within a distance, but at the players' location.
|
||||||
|
*
|
||||||
|
* @param distance double
|
||||||
|
*/
|
||||||
|
public void playWithinIndividually(double distance) {
|
||||||
|
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||||
|
if (p != null && p.getWorld() == this.location.getWorld() && p.getLocation().distance(this.location) < distance) {
|
||||||
|
p.playSound(p.getLocation(),this.sound,this.volume,this.pitch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays the sound to all players on the server, but at the stored location.
|
||||||
|
*/
|
||||||
|
public void playAll() {
|
||||||
|
for (Player p : Bukkit.getOnlinePlayers()) p.playSound(this.location,this.sound,this.volume,this.pitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays the sound to all players on the server, but at the players' location.
|
||||||
|
*/
|
||||||
|
public void playAllIndividually() {
|
||||||
|
for (Player p : Bukkit.getOnlinePlayers()) p.playSound(p.getLocation(),this.sound,this.volume,this.pitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repeats a sound to a player, but at the stored location.
|
||||||
|
*
|
||||||
|
* @param player Player
|
||||||
|
* @param times int
|
||||||
|
* @param tickDelay int
|
||||||
|
*/
|
||||||
|
public void repeat(Player player, int times, int tickDelay) {
|
||||||
|
new BukkitRunnable() {
|
||||||
|
int i = 0;
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (i < times) {
|
||||||
|
play(player);
|
||||||
|
i ++;
|
||||||
|
} else {
|
||||||
|
this.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.runTaskTimer(getPlugin(),0,tickDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repeats a sound to a player, but at the player's location.
|
||||||
|
*
|
||||||
|
* @param player Player
|
||||||
|
* @param times int
|
||||||
|
* @param tickDelay int
|
||||||
|
*/
|
||||||
|
public void repeatIndividually(Player player, int times, int tickDelay) {
|
||||||
|
new BukkitRunnable() {
|
||||||
|
int i = 0;
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (i < times) {
|
||||||
|
playIndividually(player);
|
||||||
|
i ++;
|
||||||
|
} else {
|
||||||
|
this.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.runTaskTimer(getPlugin(),0,tickDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repeats a sound to all players on the server, but at the stored location.
|
||||||
|
*
|
||||||
|
* @param times int
|
||||||
|
* @param tickDelay int
|
||||||
|
*/
|
||||||
|
public void repeatAll(int times, int tickDelay) {
|
||||||
|
new BukkitRunnable() {
|
||||||
|
int i = 0;
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (i < times) {
|
||||||
|
playAll();
|
||||||
|
i ++;
|
||||||
|
} else {
|
||||||
|
this.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.runTaskTimer(getPlugin(),0,tickDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repeats a sound to all players on the server, but at the players' location.
|
||||||
|
*
|
||||||
|
* @param times int
|
||||||
|
* @param tickDelay int
|
||||||
|
*/
|
||||||
|
public void repeatAllIndividually(int times, int tickDelay) {
|
||||||
|
new BukkitRunnable() {
|
||||||
|
int i = 0;
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (i < times) {
|
||||||
|
playAllIndividually();
|
||||||
|
i ++;
|
||||||
|
} else {
|
||||||
|
this.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.runTaskTimer(getPlugin(),0,tickDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repeats a sound to all players within a radius, but at the stored location.
|
||||||
|
*
|
||||||
|
* @param radius double
|
||||||
|
* @param times int
|
||||||
|
* @param tickDelay int
|
||||||
|
*/
|
||||||
|
public void repeatAll(double radius,int times, int tickDelay) {
|
||||||
|
new BukkitRunnable() {
|
||||||
|
int i = 0;
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (i < times) {
|
||||||
|
playWithin(radius);
|
||||||
|
i ++;
|
||||||
|
} else {
|
||||||
|
this.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.runTaskTimer(getPlugin(),0,tickDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repeats a sound to all players within a radius, but at the players' location.
|
||||||
|
*
|
||||||
|
* @param distance double
|
||||||
|
* @param times int
|
||||||
|
* @param tickDelay int
|
||||||
|
*/
|
||||||
|
public void repeatAllIndividually(double distance, int times, int tickDelay) {
|
||||||
|
new BukkitRunnable() {
|
||||||
|
int i = 0;
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (i < times) {
|
||||||
|
playWithinIndividually(distance);
|
||||||
|
i ++;
|
||||||
|
} else {
|
||||||
|
this.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.runTaskTimer(getPlugin(),0,tickDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Sound getSound() {
|
||||||
|
return sound;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getPitch() {
|
||||||
|
return pitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getVolume() {
|
||||||
|
return volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Location getLocation() {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPitch(float pitch) {
|
||||||
|
this.pitch = pitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVolume(float volume) {
|
||||||
|
this.volume = volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSound(Sound sound) {
|
||||||
|
this.sound = sound;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocation(Location location) {
|
||||||
|
this.location = location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changePlayer(Location location, Sound sound, float volume, float pitch) {
|
||||||
|
this.location = location;
|
||||||
|
this.sound = sound;
|
||||||
|
this.volume = volume;
|
||||||
|
this.pitch = pitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changePlayer(Sound sound, float volume, float pitch) {
|
||||||
|
changePlayer(location, sound, volume, pitch);
|
||||||
|
}
|
||||||
|
}
|
||||||
282
src/main/java/me/trouper/trimserver/utils/TargetingUtils.java
Normal file
282
src/main/java/me/trouper/trimserver/utils/TargetingUtils.java
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
package me.trouper.trimserver.utils;
|
||||||
|
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.entity.LivingEntity;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class TargetingUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies an action to all living entities within a specified cuboid area that match a given filter.
|
||||||
|
*
|
||||||
|
* @param center The center of the cuboid area.
|
||||||
|
* @param xRadius The half-length of the cuboid along the X-axis.
|
||||||
|
* @param yRadius The half-length of the cuboid along the Y-axis.
|
||||||
|
* @param zRadius The half-length of the cuboid along the Z-axis.
|
||||||
|
* @param filter A predicate to filter which living entities are affected.
|
||||||
|
* @param action An action to perform on each targeted living entity.
|
||||||
|
* @return true if any targets were found, false if it missed.
|
||||||
|
*/
|
||||||
|
public static boolean areaAffect(Location center, double xRadius, double yRadius, double zRadius, Predicate<LivingEntity> filter, Consumer<LivingEntity> action) {
|
||||||
|
World world = center.getWorld();
|
||||||
|
if (world == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<LivingEntity> targets = new ArrayList<>(world.getNearbyLivingEntities(center, xRadius, yRadius, zRadius).stream().filter(filter).toList());
|
||||||
|
targets.forEach(action);
|
||||||
|
return !targets.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies an action to all living entities within a specified spherical area (approximated by a cube) that match a given filter.
|
||||||
|
*
|
||||||
|
* @param center The center of the area.
|
||||||
|
* @param radius The radius of the spherical area.
|
||||||
|
* @param filter A predicate to filter which living entities are affected.
|
||||||
|
* @param action An action to perform on each targeted living entity.
|
||||||
|
* @return true if any targets were found, false if it missed.
|
||||||
|
*/
|
||||||
|
public static boolean areaAffect(Location center, double radius, Predicate<LivingEntity> filter, Consumer<LivingEntity> action) {
|
||||||
|
|
||||||
|
World world = center.getWorld();
|
||||||
|
if (world == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<LivingEntity> targets = new ArrayList<>(world.getNearbyLivingEntities(center, radius).stream()
|
||||||
|
.filter(livingEntity -> livingEntity.getLocation().distanceSquared(center) <= radius*radius)
|
||||||
|
.filter(filter)
|
||||||
|
.toList());
|
||||||
|
targets.forEach(action);
|
||||||
|
|
||||||
|
return !targets.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to find a passable ground location based on the given initial location.
|
||||||
|
* <p>
|
||||||
|
* This method checks if the current block is non-passable and searches upward (up to 10 blocks)
|
||||||
|
* until it finds a passable block. Then, it searches downward (up to 25 blocks) for the first
|
||||||
|
* non-passable block beneath it, which would be considered solid ground.
|
||||||
|
* <p>
|
||||||
|
* If a valid ground location cannot be determined within the search bounds,
|
||||||
|
* the original location is returned.
|
||||||
|
*
|
||||||
|
* @param initialLocation The starting location to begin the ground-finding process. Must not be null.
|
||||||
|
* @return A new {@link Location} object representing the nearest solid ground position,
|
||||||
|
* or the original location if no valid position is found or world is null.
|
||||||
|
*/
|
||||||
|
public static Location findGroundLocation(Location initialLocation) {
|
||||||
|
if (initialLocation == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
World world = initialLocation.getWorld();
|
||||||
|
if (world == null) return initialLocation;
|
||||||
|
|
||||||
|
Location loc = initialLocation.clone();
|
||||||
|
|
||||||
|
int attempts = 0;
|
||||||
|
while (!world.getBlockAt(loc).isPassable() && attempts < 10) {
|
||||||
|
loc.add(0, 1, 0);
|
||||||
|
attempts++;
|
||||||
|
if (loc.getBlockY() >= world.getMaxHeight()) {
|
||||||
|
return initialLocation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!world.getBlockAt(loc).isPassable()) return initialLocation;
|
||||||
|
|
||||||
|
attempts = 0;
|
||||||
|
while (world.getBlockAt(loc).isPassable() && attempts < 25) {
|
||||||
|
Location belowLoc = loc.clone().subtract(0, 1, 0);
|
||||||
|
if (belowLoc.getBlockY() <= world.getMinHeight()) {
|
||||||
|
return world.getBlockAt(belowLoc).isPassable() ? initialLocation : loc;
|
||||||
|
}
|
||||||
|
if (!world.getBlockAt(belowLoc).isPassable()) {
|
||||||
|
return loc;
|
||||||
|
}
|
||||||
|
loc.subtract(0, 1, 0);
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
return initialLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the closest living entity to a central location within a maximum distance.
|
||||||
|
*
|
||||||
|
* @param center The central location from which to search. Must not be null.
|
||||||
|
* @param maxDistance The maximum distance (radius of a sphere) to search for entities.
|
||||||
|
* @return An {@link Optional} containing the closest {@link LivingEntity}, or an empty Optional if no entity is found or world is null.
|
||||||
|
*/
|
||||||
|
public static Optional<LivingEntity> getClosestLivingEntity(Location center, double maxDistance) {
|
||||||
|
return getClosestLivingEntity(center, maxDistance, entity -> true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the closest living entity to a central location within a maximum distance, matching a given filter.
|
||||||
|
*
|
||||||
|
* @param center The central location from which to search. Must not be null.
|
||||||
|
* @param maxDistance The maximum distance (radius of a sphere) to search for entities.
|
||||||
|
* @param filter A predicate to apply additional filtering criteria to the entities. Must not be null.
|
||||||
|
* @return An {@link Optional} containing the closest {@link LivingEntity} matching the criteria,
|
||||||
|
* or an empty Optional if no such entity is found or world is null.
|
||||||
|
*/
|
||||||
|
public static Optional<LivingEntity> getClosestLivingEntity(Location center, double maxDistance, Predicate<LivingEntity> filter) {
|
||||||
|
if (center == null || center.getWorld() == null || filter == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
World world = center.getWorld();
|
||||||
|
double maxDistanceSquared = maxDistance * maxDistance;
|
||||||
|
|
||||||
|
return world.getNearbyLivingEntities(center, maxDistance, maxDistance, maxDistance, filter).stream()
|
||||||
|
.filter(entity -> entity.getLocation().distanceSquared(center) <= maxDistanceSquared)
|
||||||
|
.min(Comparator.comparingDouble(entity -> entity.getLocation().distanceSquared(center)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the closest player to a central location within a maximum distance.
|
||||||
|
*
|
||||||
|
* @param center The central location from which to search. Must not be null.
|
||||||
|
* @param maxDistance The maximum distance (radius of a sphere) to search for players.
|
||||||
|
* @return An {@link Optional} containing the closest {@link Player}, or an empty Optional if no player is found or world is null.
|
||||||
|
*/
|
||||||
|
public static Optional<Player> getClosestPlayer(Location center, double maxDistance) {
|
||||||
|
return getClosestPlayer(center, maxDistance, player -> true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the closest player to a central location within a maximum distance, matching a given filter.
|
||||||
|
*
|
||||||
|
* @param center The central location from which to search. Must not be null.
|
||||||
|
* @param maxDistance The maximum distance (radius of a sphere) to search for players.
|
||||||
|
* @param filter A predicate to apply additional filtering criteria to the players. Must not be null.
|
||||||
|
* @return An {@link Optional} containing the closest {@link Player} matching the criteria,
|
||||||
|
* or an empty Optional if no such player is found or world is null.
|
||||||
|
*/
|
||||||
|
public static Optional<Player> getClosestPlayer(Location center, double maxDistance, Predicate<Player> filter) {
|
||||||
|
if (center == null || center.getWorld() == null || filter == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
World world = center.getWorld();
|
||||||
|
double maxDistanceSquared = maxDistance * maxDistance;
|
||||||
|
|
||||||
|
List<Player> nearbyPlayers = world.getPlayers().stream()
|
||||||
|
.filter(player -> player.getWorld().equals(world))
|
||||||
|
.filter(player -> player.getLocation().distanceSquared(center) <= maxDistanceSquared)
|
||||||
|
.filter(filter)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
return nearbyPlayers.stream()
|
||||||
|
.min(Comparator.comparingDouble(player -> player.getLocation().distanceSquared(center)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the living entity with the lowest health within a given radius of a central location.
|
||||||
|
*
|
||||||
|
* @param center The central location from which to search. Must not be null.
|
||||||
|
* @param radius The radius (sphere) to search for entities.
|
||||||
|
* @return An {@link Optional} containing the {@link LivingEntity} with the lowest health,
|
||||||
|
* or an empty Optional if no entity is found or world is null.
|
||||||
|
*/
|
||||||
|
public static Optional<LivingEntity> getLowestHealthLivingEntity(Location center, double radius) {
|
||||||
|
return getLowestHealthLivingEntity(center, radius, entity -> true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the living entity with the lowest health within a given radius of a central location, matching a given filter.
|
||||||
|
*
|
||||||
|
* @param center The central location from which to search. Must not be null.
|
||||||
|
* @param radius The radius (sphere) to search for entities.
|
||||||
|
* @param filter A predicate to apply additional filtering criteria. Must not be null.
|
||||||
|
* @return An {@link Optional} containing the {@link LivingEntity} with the lowest health matching the criteria,
|
||||||
|
* or an empty Optional if no such entity is found or world is null.
|
||||||
|
*/
|
||||||
|
public static Optional<LivingEntity> getLowestHealthLivingEntity(Location center, double radius, Predicate<LivingEntity> filter) {
|
||||||
|
if (center == null || center.getWorld() == null || filter == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
World world = center.getWorld();
|
||||||
|
double radiusSquared = radius * radius;
|
||||||
|
|
||||||
|
return world.getNearbyLivingEntities(center, radius, radius, radius, filter).stream()
|
||||||
|
.filter(entity -> entity.getLocation().distanceSquared(center) <= radiusSquared)
|
||||||
|
.min(Comparator.comparingDouble(LivingEntity::getHealth));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the living entity whose eye location is closest (by angle) to a given direction vector from an origin.
|
||||||
|
* This method is useful for simulating line-of-sight or aim-based targeting.
|
||||||
|
*
|
||||||
|
* @param originEyeLocation The starting location (e.g., an entity's eye location). Must not be null.
|
||||||
|
* @param direction The normalized direction vector of the search. Must not be null.
|
||||||
|
* @param maxDistance The maximum distance to search for entities.
|
||||||
|
* @param maxAngleRadians The maximum allowed angle (in radians) between the direction vector and the vector to the target's eye location.
|
||||||
|
* A smaller angle means the target is more directly in the line of sight.
|
||||||
|
* @return An {@link Optional} containing the {@link LivingEntity} closest to the aim vector,
|
||||||
|
* or an empty Optional if no suitable entity is found or world is null.
|
||||||
|
*/
|
||||||
|
public static Optional<LivingEntity> getLivingEntityClosestToVector(Location originEyeLocation, Vector direction, double maxDistance, double maxAngleRadians) {
|
||||||
|
return getLivingEntityClosestToVector(originEyeLocation, direction, maxDistance, maxAngleRadians, entity -> true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the living entity whose eye location is closest (by angle) to a given direction vector from an origin, matching a filter.
|
||||||
|
* This method is useful for simulating line-of-sight or aim-based targeting.
|
||||||
|
*
|
||||||
|
* @param originEyeLocation The starting location (e.g., an entity's eye location). Must not be null.
|
||||||
|
* @param direction The normalized direction vector of the search. Must not be null. Its magnitude does not matter as it will be normalized.
|
||||||
|
* @param maxDistance The maximum distance to search for entities.
|
||||||
|
* @param maxAngleRadians The maximum allowed angle (in radians) between the direction vector and the vector to the target's eye location.
|
||||||
|
* A smaller angle means the target is more directly in the line of sight.
|
||||||
|
* @param filter A predicate to apply additional filtering criteria. Must not be null.
|
||||||
|
* @return An {@link Optional} containing the {@link LivingEntity} closest to the aim vector and matching the filter,
|
||||||
|
* or an empty Optional if no suitable entity is found or world is null.
|
||||||
|
*/
|
||||||
|
public static Optional<LivingEntity> getLivingEntityClosestToVector(Location originEyeLocation, Vector direction, double maxDistance, double maxAngleRadians, Predicate<LivingEntity> filter) {
|
||||||
|
if (originEyeLocation == null || originEyeLocation.getWorld() == null || direction == null || filter == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
World world = originEyeLocation.getWorld();
|
||||||
|
Vector normalizedDirection = direction.clone().normalize();
|
||||||
|
|
||||||
|
List<LivingEntity> candidates = world.getNearbyLivingEntities(originEyeLocation, maxDistance, maxDistance, maxDistance, entity -> {
|
||||||
|
if (originEyeLocation.equals(entity.getEyeLocation())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return filter.test(entity);
|
||||||
|
}).stream().toList();
|
||||||
|
|
||||||
|
LivingEntity bestTarget = null;
|
||||||
|
double smallestAngle = maxAngleRadians + 1.0;
|
||||||
|
|
||||||
|
for (LivingEntity entity : candidates) {
|
||||||
|
if (entity.getEyeLocation().distanceSquared(originEyeLocation) > maxDistance * maxDistance) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector vectorToTarget = entity.getEyeLocation().toVector().subtract(originEyeLocation.toVector());
|
||||||
|
if (vectorToTarget.lengthSquared() == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
vectorToTarget.normalize();
|
||||||
|
|
||||||
|
double angle = normalizedDirection.angle(vectorToTarget);
|
||||||
|
|
||||||
|
if (angle <= maxAngleRadians && angle < smallestAngle) {
|
||||||
|
smallestAngle = angle;
|
||||||
|
bestTarget = entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.ofNullable(bestTarget);
|
||||||
|
}
|
||||||
|
}
|
||||||
285
src/main/java/me/trouper/trimserver/utils/Text.java
Normal file
285
src/main/java/me/trouper/trimserver/utils/Text.java
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
package me.trouper.trimserver.utils;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.Main;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import net.kyori.adventure.text.format.TextColor;
|
||||||
|
import net.kyori.adventure.text.format.TextDecoration;
|
||||||
|
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||||
|
import org.bukkit.Sound;
|
||||||
|
import org.bukkit.SoundCategory;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class Text implements Main {
|
||||||
|
|
||||||
|
public static String legacyColor(String msg) {
|
||||||
|
return msg.replaceAll("&","§");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component color(String msg) {
|
||||||
|
return LegacyComponentSerializer.legacyAmpersand().deserialize(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendWarning(CommandSender sender, String warning, Object... args) {
|
||||||
|
sendMessage(Pallet.WARNING, sender, warning, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendError(CommandSender sender, String error, Object... args) {
|
||||||
|
sendMessage(Pallet.ERROR, sender, error, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendMessage(CommandSender sender, String text, Object... args) {
|
||||||
|
sendMessage(Pallet.NEUTRAL, sender, text, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendMessage(Pallet pallet, CommandSender sender, String text, Object... args) {
|
||||||
|
text = formatArgs(pallet, text, args);
|
||||||
|
sendMessage(sender, text);
|
||||||
|
if (sender instanceof Player p) p.playSound(p.getLocation(),pallet.sound.sound, SoundCategory.VOICE,10f,pallet.sound.pitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendMessage(CommandSender sender, String text) {
|
||||||
|
Component message = getMessage(text);
|
||||||
|
sender.sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component getMessage(Pallet pallet, String text, Object... args) {
|
||||||
|
return getMessage(formatArgs(pallet, text, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component getMessage(String text) {
|
||||||
|
if (main.config().messages.fancyAlerts) {
|
||||||
|
return formatFancyMessage(text);
|
||||||
|
} else {
|
||||||
|
return color(main.config().messages.prefix + text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatArgs(Pallet pallet, String format, Object... args) {
|
||||||
|
Component message = Component.empty();
|
||||||
|
Pattern pattern = Pattern.compile("\\{(\\d+)}");
|
||||||
|
Matcher matcher = pattern.matcher(format);
|
||||||
|
int lastIndex = 0;
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
String prefix = format.substring(lastIndex, matcher.start());
|
||||||
|
if (!prefix.isEmpty()) {
|
||||||
|
message = message.append(Component.text(prefix).color(pallet.mainText));
|
||||||
|
}
|
||||||
|
|
||||||
|
int argIndex = Integer.parseInt(matcher.group(1));
|
||||||
|
TextColor argColor = getArgColor(pallet, argIndex);
|
||||||
|
|
||||||
|
if (argIndex >= 0 && argIndex < args.length) {
|
||||||
|
String argText = args[argIndex].toString();
|
||||||
|
message = message.append(Component.text(argText).color(argColor));
|
||||||
|
} else {
|
||||||
|
message = message.append(Component.text(matcher.group()).color(pallet.mainText));
|
||||||
|
}
|
||||||
|
|
||||||
|
lastIndex = matcher.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
String suffix = format.substring(lastIndex);
|
||||||
|
if (!suffix.isEmpty()) {
|
||||||
|
message = message.append(Component.text(suffix).color(pallet.mainText));
|
||||||
|
}
|
||||||
|
|
||||||
|
return LegacyComponentSerializer.legacyAmpersand().serialize(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component formatFancyMessage(String text) {
|
||||||
|
Component message = Component.empty().appendNewline();
|
||||||
|
|
||||||
|
List<String> wrappedLines = wrapText(text, 50, (int) Math.round((main.config().messages.pluginName.length() + 3) * 1.3));
|
||||||
|
|
||||||
|
message = message
|
||||||
|
.append(color(main.config().messages.mainColor + "| ").decorate(TextDecoration.BOLD))
|
||||||
|
.append(Component.text(main.config().messages.pluginName + " ", NamedTextColor.WHITE, TextDecoration.BOLD))
|
||||||
|
.append(color(wrappedLines.getFirst()));
|
||||||
|
|
||||||
|
String active = getActiveFormatting(wrappedLines.getFirst());
|
||||||
|
|
||||||
|
wrappedLines.removeFirst();
|
||||||
|
|
||||||
|
for (String wrappedLine : wrappedLines) {
|
||||||
|
wrappedLine = active + wrappedLine;
|
||||||
|
|
||||||
|
active = getActiveFormatting(wrappedLine);
|
||||||
|
message = message
|
||||||
|
.appendNewline()
|
||||||
|
.append(color(main.config().messages.mainColor + "| ").decorate(TextDecoration.BOLD))
|
||||||
|
.append(color(wrappedLine));
|
||||||
|
}
|
||||||
|
|
||||||
|
return message.appendNewline();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> wrapText(String text, int maxLineLength, int offset) {
|
||||||
|
List<String> lines = new ArrayList<>();
|
||||||
|
|
||||||
|
if (text == null || text.isEmpty() || maxLineLength <= 0) {
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] words = text.split("\\s+");
|
||||||
|
StringBuilder currentLine = new StringBuilder();
|
||||||
|
int currentLineLength = offset;
|
||||||
|
|
||||||
|
for (String word : words) {
|
||||||
|
if (currentLineLength + word.length() + 1 > maxLineLength) {
|
||||||
|
lines.add(currentLine.toString());
|
||||||
|
currentLine = new StringBuilder();
|
||||||
|
currentLineLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentLine.isEmpty()) {
|
||||||
|
currentLine.append(" ");
|
||||||
|
currentLineLength++;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentLine.append(word);
|
||||||
|
currentLineLength += word.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentLine.isEmpty()) {
|
||||||
|
lines.add(currentLine.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getActiveFormatting(String text) {
|
||||||
|
final Pattern pattern = Pattern.compile("&[0-9a-fk-or]");
|
||||||
|
final Matcher matcher = pattern.matcher(text);
|
||||||
|
|
||||||
|
String lastColor = "";
|
||||||
|
Set<Character> activeFormats = new HashSet<>();
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
String code = matcher.group();
|
||||||
|
char identifier = code.charAt(1);
|
||||||
|
|
||||||
|
if (identifier >= '0' && identifier <= '9' || identifier >= 'a' && identifier <= 'f') {
|
||||||
|
lastColor = code;
|
||||||
|
activeFormats.clear();
|
||||||
|
} else if (identifier >= 'k' && identifier <= 'o') {
|
||||||
|
activeFormats.add(identifier);
|
||||||
|
} else if (identifier == 'r') {
|
||||||
|
lastColor = "";
|
||||||
|
activeFormats.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder result = new StringBuilder(lastColor);
|
||||||
|
for (char format : activeFormats) {
|
||||||
|
result.append("&").append(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TextColor getArgColor(Pallet pallet, int argIndex) {
|
||||||
|
return switch (argIndex) {
|
||||||
|
case 1 -> pallet.arg2;
|
||||||
|
case 2 -> pallet.arg3;
|
||||||
|
default -> pallet.argDefault;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Pallet {
|
||||||
|
ERROR(
|
||||||
|
NamedTextColor.RED,
|
||||||
|
NamedTextColor.YELLOW,
|
||||||
|
NamedTextColor.GOLD,
|
||||||
|
NamedTextColor.DARK_RED,
|
||||||
|
new SoundData(Sound.BLOCK_NOTE_BLOCK_BASS,1)),
|
||||||
|
WARNING(
|
||||||
|
NamedTextColor.YELLOW,
|
||||||
|
NamedTextColor.GOLD,
|
||||||
|
NamedTextColor.RED,
|
||||||
|
NamedTextColor.DARK_RED,
|
||||||
|
new SoundData(Sound.BLOCK_NOTE_BLOCK_BIT,0.5F)),
|
||||||
|
INFO(
|
||||||
|
NamedTextColor.GRAY,
|
||||||
|
NamedTextColor.WHITE,
|
||||||
|
NamedTextColor.AQUA,
|
||||||
|
NamedTextColor.DARK_AQUA,
|
||||||
|
new SoundData(Sound.BLOCK_NOTE_BLOCK_BELL,1)),
|
||||||
|
SUCCESS(
|
||||||
|
NamedTextColor.GREEN,
|
||||||
|
NamedTextColor.DARK_GREEN,
|
||||||
|
NamedTextColor.YELLOW,
|
||||||
|
NamedTextColor.GOLD,
|
||||||
|
new SoundData(Sound.BLOCK_NOTE_BLOCK_CHIME,1)),
|
||||||
|
NEUTRAL(
|
||||||
|
NamedTextColor.GRAY,
|
||||||
|
NamedTextColor.WHITE,
|
||||||
|
NamedTextColor.DARK_AQUA,
|
||||||
|
NamedTextColor.BLUE,
|
||||||
|
new SoundData(Sound.BLOCK_NOTE_BLOCK_BELL,1));
|
||||||
|
|
||||||
|
private final TextColor mainText;
|
||||||
|
private final TextColor argDefault;
|
||||||
|
private final TextColor arg2;
|
||||||
|
private final TextColor arg3;
|
||||||
|
private final SoundData sound;
|
||||||
|
|
||||||
|
Pallet(TextColor mainText, TextColor argDefault, TextColor arg2, TextColor arg3, SoundData sound) {
|
||||||
|
this.mainText = mainText;
|
||||||
|
this.argDefault = argDefault;
|
||||||
|
this.arg2 = arg2;
|
||||||
|
this.arg3 = arg3;
|
||||||
|
this.sound = sound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record SoundData(Sound sound, float pitch){};
|
||||||
|
|
||||||
|
public static String generateProgressBar(int length, int max, int current) {
|
||||||
|
if (max <= 0) {
|
||||||
|
throw new IllegalArgumentException("Max value must be greater than 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
current = Math.max(0, Math.min(current, max));
|
||||||
|
double percent = (double) current / max;
|
||||||
|
int filledBars = (int) Math.round(percent * length);
|
||||||
|
|
||||||
|
StringBuilder progressBar = new StringBuilder();
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
if (i < filledBars) {
|
||||||
|
progressBar.append("&a|");
|
||||||
|
} else {
|
||||||
|
progressBar.append("&7|");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return progressBar.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatEnum(Enum<?> obj) {
|
||||||
|
if (obj == null) return "Null";
|
||||||
|
String name = obj.name();
|
||||||
|
String[] words = name.toLowerCase().split("_");
|
||||||
|
|
||||||
|
StringBuilder formatted = new StringBuilder();
|
||||||
|
|
||||||
|
for (String word : words) {
|
||||||
|
if (!word.isEmpty()) {
|
||||||
|
formatted.append(Character.toUpperCase(word.charAt(0)))
|
||||||
|
.append(word.substring(1))
|
||||||
|
.append(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatted.toString().trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
71
src/main/java/me/trouper/trimserver/utils/Verbose.java
Normal file
71
src/main/java/me/trouper/trimserver/utils/Verbose.java
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package me.trouper.trimserver.utils;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.TrimServer;
|
||||||
|
import me.trouper.trimserver.server.Main;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class Verbose implements Main {
|
||||||
|
|
||||||
|
public static void send(int backtrace, String message, Object... args) {
|
||||||
|
if (!TrimServer.getInstance().getManager().io.config.debugMode) return;
|
||||||
|
String callerInfo = "Unknown Caller";
|
||||||
|
|
||||||
|
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||||
|
if (stackTrace.length > 2 + backtrace) {
|
||||||
|
StackTraceElement caller = stackTrace[2 + backtrace];
|
||||||
|
|
||||||
|
String className = caller.getClassName();
|
||||||
|
className = className.substring(className.lastIndexOf(".") + 1);
|
||||||
|
if (className.contains("-")) {
|
||||||
|
callerInfo = "Protected";
|
||||||
|
} else {
|
||||||
|
callerInfo = className + "." + caller.getMethodName();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TrimServer.getInstance().getManager().io.config.debuggerExclusions.contains(callerInfo)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String formattedMessage = message.formatted(args);
|
||||||
|
String log = "[DEBUG ^ %s] [%s]: %s".formatted(backtrace, callerInfo, formattedMessage);
|
||||||
|
TrimServer.getInstance().getLogger().info(log);
|
||||||
|
|
||||||
|
for (Player operator : Bukkit.getOnlinePlayers()) {
|
||||||
|
if (operator.isOp()) operator.sendMessage("§d§l%s §7[§bDEBUG ^ %s§7] §7[§e%s§7] §8» §7%s"
|
||||||
|
.formatted(main.config().messages.pluginName,backtrace, callerInfo, formattedMessage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void send(String message, Object... args) {
|
||||||
|
if (!TrimServer.getInstance().getManager().io.config.debugMode) return;
|
||||||
|
String callerInfo = "Unknown Caller";
|
||||||
|
|
||||||
|
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||||
|
if (stackTrace.length > 2) {
|
||||||
|
StackTraceElement caller = stackTrace[2];
|
||||||
|
|
||||||
|
String className = caller.getClassName();
|
||||||
|
className = className.substring(className.lastIndexOf(".") + 1);
|
||||||
|
if (className.contains("-")) {
|
||||||
|
callerInfo = "Protected";
|
||||||
|
} else {
|
||||||
|
callerInfo = className + "." + caller.getMethodName();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (main.config().debuggerExclusions.contains(callerInfo)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String formattedMessage = message.formatted(args);
|
||||||
|
String log = "[DEBUG] [%s]: %s".formatted(callerInfo, formattedMessage);
|
||||||
|
TrimServer.getInstance().getLogger().info(log);
|
||||||
|
|
||||||
|
for (Player operator : Bukkit.getOnlinePlayers()) {
|
||||||
|
if (operator.isOp()) operator.sendMessage("§d§l%s §7[§bDEBUG§7] §7[§e%s§7] §8» §7%s"
|
||||||
|
.formatted(main.config().messages.pluginName,callerInfo, formattedMessage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
115
src/main/java/me/trouper/trimserver/utils/commands/Args.java
Normal file
115
src/main/java/me/trouper/trimserver/utils/commands/Args.java
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package me.trouper.trimserver.utils.commands;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public record Args(String... args) {
|
||||||
|
|
||||||
|
public Arg getAll() {
|
||||||
|
return getAll(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Arg getAll(int beginIndex) {
|
||||||
|
String str = "";
|
||||||
|
for (int i = beginIndex; i < args.length; i++) {
|
||||||
|
str = str.concat(args[i] + " ");
|
||||||
|
}
|
||||||
|
return new Arg(str.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Arg get(int index) {
|
||||||
|
if (args.length == 0)
|
||||||
|
throw new IllegalArgumentException("not enough arguments: arguments are empty");
|
||||||
|
if (index < 0 || index >= args.length)
|
||||||
|
throw new IllegalArgumentException("not enough arguments: argument %s is missing".formatted(index + 1));
|
||||||
|
return new Arg(args[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Arg first() {
|
||||||
|
return get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Arg last() {
|
||||||
|
return get(args.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean match(int index, String arg) {
|
||||||
|
if (index < 0 || index >= args.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return get(index).toString().equalsIgnoreCase(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void when(int index, String match, Consumer<Arg> action) {
|
||||||
|
if (match(index, match)) {
|
||||||
|
action.accept(get(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSize() {
|
||||||
|
return args.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return args.length == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Arg {
|
||||||
|
private final String arg;
|
||||||
|
|
||||||
|
public Arg(String arg) {
|
||||||
|
this.arg = arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int toInt() {
|
||||||
|
return Integer.parseInt(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long toLong() {
|
||||||
|
return Long.parseLong(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte toByte() {
|
||||||
|
return Byte.parseByte(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public short toShort() {
|
||||||
|
return Short.parseShort(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double toDouble() {
|
||||||
|
return Double.parseDouble(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float toFloat() {
|
||||||
|
return Float.parseFloat(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean toBool() {
|
||||||
|
return Boolean.parseBoolean(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public char toChar() {
|
||||||
|
return arg.isEmpty() ? ' ' : arg.charAt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends Enum<?>> T toEnum(Class<T> enumType) {
|
||||||
|
return toEnum(enumType, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends Enum<?>> T toEnum(Class<T> enumType, T fallback) {
|
||||||
|
String arg = this.arg.replace('-', '_');
|
||||||
|
for (T constant : enumType.getEnumConstants())
|
||||||
|
if (arg.equalsIgnoreCase(constant.name()))
|
||||||
|
return constant;
|
||||||
|
|
||||||
|
if (fallback == null)
|
||||||
|
throw new IllegalArgumentException("'%s' is not a value of %s".formatted(arg, enumType.getSimpleName()));
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package me.trouper.trimserver.utils.commands;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
public @interface CommandRegistry {
|
||||||
|
|
||||||
|
String value();
|
||||||
|
String usage() default "none";
|
||||||
|
Permission permission() default @Permission("");
|
||||||
|
boolean printStackTrace() default false;
|
||||||
|
boolean playersOnly() default false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package me.trouper.trimserver.utils.commands;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.PARAMETER)
|
||||||
|
public @interface Permission {
|
||||||
|
|
||||||
|
String value();
|
||||||
|
String message() default "&cYou do not have permission for this command!";
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package me.trouper.trimserver.utils.commands;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.Main;
|
||||||
|
import me.trouper.trimserver.utils.commands.completions.CompletionBuilder;
|
||||||
|
import me.trouper.trimserver.utils.commands.completions.CompletionNode;
|
||||||
|
import me.trouper.trimserver.utils.misc.Voidable;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.PluginCommand;
|
||||||
|
import org.bukkit.command.TabExecutor;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface QuickCommand extends TabExecutor, Main {
|
||||||
|
|
||||||
|
void dispatchCommand(CommandSender sender, Command command, String label, Args args);
|
||||||
|
|
||||||
|
void dispatchCompletions(CommandSender sender, Command command, String label, CompletionBuilder b);
|
||||||
|
|
||||||
|
default void register() {
|
||||||
|
CommandRegistry registry = this.getClass().getAnnotation(CommandRegistry.class);
|
||||||
|
PluginCommand command = getPlugin().getCommand(registry.value());
|
||||||
|
|
||||||
|
if (command != null) {
|
||||||
|
command.setExecutor(this);
|
||||||
|
command.setTabCompleter(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
CommandRegistry registry = this.getClass().getAnnotation(CommandRegistry.class);
|
||||||
|
if (registry == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(sender instanceof Player) && registry.playersOnly()) {
|
||||||
|
info(sender, "This command is for players only!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String perm = registry.permission().value();
|
||||||
|
if (perm != null && !perm.isEmpty() && !sender.hasPermission(perm)) {
|
||||||
|
error(sender, registry.permission().message());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
dispatchCommand(sender, command, label, new Args(args));
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
if (registry.printStackTrace()) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
info(sender, "&cCorrect Usage: &7" + registry.usage());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default List<String> onTabComplete(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
try {
|
||||||
|
CompletionBuilder b = new CompletionBuilder(label);
|
||||||
|
dispatchCompletions(sender, command, label, b);
|
||||||
|
CompletionNode node = b.getRootNode();
|
||||||
|
|
||||||
|
if (args.length == 0) {
|
||||||
|
return node.getOptions();
|
||||||
|
}
|
||||||
|
for (int i = 0; i < args.length - 1; i++) {
|
||||||
|
node = node.next(args[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
String end = args[args.length - 1];
|
||||||
|
List<String> a = new ArrayList<>(node.getOptions());
|
||||||
|
|
||||||
|
if (node.isOptionsRegex()) {
|
||||||
|
List<String> regexResult = new ArrayList<>();
|
||||||
|
for (CompletionNode option : node.getNextOptions()) {
|
||||||
|
boolean regexMatches = CompletionNode.containsRegex(option, end) || end.isEmpty();
|
||||||
|
for (String s : option.getValues())
|
||||||
|
regexResult.add((regexMatches ? "§d" : "§c") + s + "§r");
|
||||||
|
}
|
||||||
|
return regexResult;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
a.removeIf(s -> !s.toLowerCase().contains(end.toLowerCase()));
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default Voidable<CommandRegistry> getRegistry() {
|
||||||
|
return Voidable.of(this.getClass().getAnnotation(CommandRegistry.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package me.trouper.trimserver.utils.commands.completions;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.utils.misc.ArrayUtils;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class CompletionBuilder {
|
||||||
|
|
||||||
|
private final CompletionNode root;
|
||||||
|
private final List<CompletionBuilder> options;
|
||||||
|
private boolean isBranch;
|
||||||
|
private String regex;
|
||||||
|
|
||||||
|
CompletionBuilder(List<String> names) {
|
||||||
|
this.root = new CompletionNode(names, new ArrayList<>(), null);
|
||||||
|
this.options = new ArrayList<>();
|
||||||
|
this.isBranch = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletionBuilder(String names) {
|
||||||
|
this.root = new CompletionNode(names);
|
||||||
|
this.options = new ArrayList<>();
|
||||||
|
this.isBranch = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletionBuilder(String regex, String details) {
|
||||||
|
this.root = new CompletionNode(Collections.singletonList(details), new ArrayList<>(), regex);
|
||||||
|
this.options = new ArrayList<>();
|
||||||
|
this.isBranch = false;
|
||||||
|
this.regex = regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletionBuilder then(CompletionBuilder arg) {
|
||||||
|
options.add(arg);
|
||||||
|
root.nextOptions.add(arg.root);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletionBuilder arg(List<String> name) {
|
||||||
|
CompletionBuilder b = new CompletionBuilder(name);
|
||||||
|
b.isBranch = true;
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletionBuilder argRegex(String regex, String details) {
|
||||||
|
CompletionBuilder b = new CompletionBuilder(regex, details);
|
||||||
|
b.isBranch = true;
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletionBuilder argInt(String details) {
|
||||||
|
return argRegex("^ *\\-?\\d+ *$", details);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletionBuilder argPosInt(String details) {
|
||||||
|
return argRegex("^ *\\d+ *$", details);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletionBuilder argDecimal(String details) {
|
||||||
|
return argRegex("^ *\\-?\\d*\\.?\\d+ *$", details);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletionBuilder argPosDecimal(String details) {
|
||||||
|
return argRegex("^ *\\d*\\.?\\d+ *$", details);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletionBuilder argBool() {
|
||||||
|
return arg("true", "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletionBuilder argEnum(Class<? extends Enum<?>> type, boolean lowercase) {
|
||||||
|
return arg(ArrayUtils.enumNames(type, lowercase));
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletionBuilder argEnum(Class<? extends Enum<?>> type) {
|
||||||
|
return argEnum(type, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletionBuilder argOnlinePlayers() {
|
||||||
|
return arg(ArrayUtils.playerNames());
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletionBuilder arg(String... names) {
|
||||||
|
return arg(Arrays.asList(names));
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletionBuilder arg(String name) {
|
||||||
|
return arg(Collections.singletonList(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> CompletionBuilder arg(Collection<T> input, Function<T, String> toString) {
|
||||||
|
return arg(input.stream().map(toString).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletionBuilder next(String name) {
|
||||||
|
for (CompletionBuilder o : options) {
|
||||||
|
if (CompletionNode.strictContains(o.root, name)) {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletionNode getRootNode() {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletionNode build() {
|
||||||
|
if (this.isBranch) {
|
||||||
|
throw new IllegalArgumentException("build() cannot be called on branches!");
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBranch() {
|
||||||
|
return isBranch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRegex() {
|
||||||
|
return regex != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package me.trouper.trimserver.utils.commands.completions;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CompletionNode {
|
||||||
|
|
||||||
|
final List<String> values;
|
||||||
|
final List<CompletionNode> nextOptions;
|
||||||
|
final String regex;
|
||||||
|
|
||||||
|
CompletionNode(List<String> values, List<CompletionNode> nextOptions, String regex) {
|
||||||
|
this.values = values;
|
||||||
|
this.nextOptions = nextOptions;
|
||||||
|
this.regex = regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletionNode(String values) {
|
||||||
|
this(List.of(values), new ArrayList<>(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean strictContains(CompletionNode parent, String subject) {
|
||||||
|
for (String value : parent.values)
|
||||||
|
if (value.equals(subject))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean contains(CompletionNode parent, String subject) {
|
||||||
|
for (String value : parent.values)
|
||||||
|
if (value.contains(subject))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean containsRegex(CompletionNode parent, String subject) {
|
||||||
|
if (parent.regex == null)
|
||||||
|
return false;
|
||||||
|
return subject.matches(parent.regex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean optionsRegexMatchesArg(String argument) {
|
||||||
|
for (CompletionNode option : nextOptions)
|
||||||
|
if (containsRegex(option, argument))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletionNode next(String argument) {
|
||||||
|
for (CompletionNode option : nextOptions)
|
||||||
|
if (containsRegex(option, argument))
|
||||||
|
return option;
|
||||||
|
|
||||||
|
for (CompletionNode option : nextOptions)
|
||||||
|
if (strictContains(option, argument))
|
||||||
|
return option;
|
||||||
|
|
||||||
|
for (CompletionNode option : nextOptions)
|
||||||
|
if (contains(option, argument))
|
||||||
|
return option;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getOptions() {
|
||||||
|
List<String> a = new ArrayList<>();
|
||||||
|
for (CompletionNode o : nextOptions) {
|
||||||
|
a.addAll(o.values);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRegex() {
|
||||||
|
return regex != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOptionsRegex() {
|
||||||
|
return nextOptions.stream().anyMatch(CompletionNode::isRegex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CompletionNode> getNextOptions() {
|
||||||
|
return nextOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getValues() {
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package me.trouper.trimserver.utils.misc;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public final class ArrayUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms an array to another one
|
||||||
|
* @param e iterable list
|
||||||
|
* @param a action
|
||||||
|
* @return new transformed list
|
||||||
|
* @param <I> input
|
||||||
|
* @param <O> output
|
||||||
|
*/
|
||||||
|
public static <I,O> List<O> map(Iterable<I> e, Function<I,O> a) {
|
||||||
|
List<O> list = new ArrayList<>();
|
||||||
|
e.forEach(i -> list.add(a.apply(i)));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> String toPrettyString(List<T> list) {
|
||||||
|
return "§7[§e" + String.join("§7, §e", ArrayUtils.map(list, Object::toString)) + "§7]";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <E extends Enum<?>> List<String> enumNames(Class<E> type, boolean lowercase) {
|
||||||
|
List<String> names = new ArrayList<>();
|
||||||
|
for (E constant : type.getEnumConstants()) {
|
||||||
|
String name = constant.name();
|
||||||
|
names.add(lowercase ? name.toLowerCase() : name);
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <E extends Enum<?>> List<String> enumNames(Class<E> type) {
|
||||||
|
return enumNames(type, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> playerNames() {
|
||||||
|
return map(Bukkit.getOnlinePlayers(), Player::getName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public static <T> List<T> bind(Iterable<T> tList, T... ts) {
|
||||||
|
List<T> list = Arrays.asList(ts);
|
||||||
|
tList.forEach(list::add);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> List<T> reversed(List<T> input) {
|
||||||
|
Collections.reverse(input);
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> List<T> reversed(Iterable<T> input) {
|
||||||
|
List<T> list = new ArrayList<>();
|
||||||
|
input.forEach(list::add);
|
||||||
|
return reversed(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> void reverseForEach(Iterable<T> input, Consumer<T> action) {
|
||||||
|
reversed(input).forEach(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/main/java/me/trouper/trimserver/utils/misc/Cooldown.java
Normal file
42
src/main/java/me/trouper/trimserver/utils/misc/Cooldown.java
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package me.trouper.trimserver.utils.misc;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class Cooldown<T> {
|
||||||
|
|
||||||
|
private final Map<T, Long> timer;
|
||||||
|
|
||||||
|
public Cooldown() {
|
||||||
|
this.timer = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private <O> O getOrDefault(O value, O def) {
|
||||||
|
return value != null ? value : def;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCooldown(T obj) {
|
||||||
|
return Math.max(getOrDefault(timer.get(obj), 0L) - System.currentTimeMillis(), 0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getCooldownSec(T obj) {
|
||||||
|
final long cooldown = this.getCooldown(obj);
|
||||||
|
return Math.floor(cooldown / 10.0) / 100.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOnCooldown(T obj) {
|
||||||
|
return getCooldown(obj) > 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCooldown(T obj, long millis) {
|
||||||
|
timer.put(obj, System.currentTimeMillis() + millis);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addCooldown(T obj, long millis) {
|
||||||
|
setCooldown(obj, getCooldown(obj) + millis);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeCooldown(T obj) {
|
||||||
|
timer.remove(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package me.trouper.trimserver.utils.misc;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public final class FileValidationUtils {
|
||||||
|
|
||||||
|
public static boolean validate(File file) {
|
||||||
|
try {
|
||||||
|
if (!file.getParentFile().exists())
|
||||||
|
if (!file.getParentFile().mkdirs())
|
||||||
|
return false;
|
||||||
|
if (!file.exists())
|
||||||
|
if (!file.createNewFile())
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package me.trouper.trimserver.utils.misc;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
public interface JsonSerializable<T> {
|
||||||
|
|
||||||
|
Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls().setLenient().create();
|
||||||
|
File getFile();
|
||||||
|
|
||||||
|
default String serialize(boolean pretty) {
|
||||||
|
Gson gson;
|
||||||
|
if (pretty) {
|
||||||
|
gson = new GsonBuilder().setPrettyPrinting().setLenient().create();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
gson = new Gson();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String json = gson.toJson(this);
|
||||||
|
if (json == null) {
|
||||||
|
throw new IllegalStateException("json parse failed for " + this.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
return "{}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
default T deserialize(String json) {
|
||||||
|
try {
|
||||||
|
JsonSerializable<?> v = gson.fromJson(json, this.getClass());
|
||||||
|
if (v == null) {
|
||||||
|
throw new IllegalStateException("json parse failed");
|
||||||
|
}
|
||||||
|
return (T)v;
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default JsonObject getJson() {
|
||||||
|
return gson.toJsonTree(this).getAsJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a json element given the specified member path
|
||||||
|
* @param path Path separated by a period . between each member name
|
||||||
|
* @return the JsonElement at the end of the path, otherwise null
|
||||||
|
*/
|
||||||
|
default JsonElement get(String path) {
|
||||||
|
JsonElement root = gson.toJsonTree(this);
|
||||||
|
JsonElement json = root;
|
||||||
|
|
||||||
|
for (String memberName : path.split("\\.")) {
|
||||||
|
JsonElement e = json.getAsJsonObject().get(memberName);
|
||||||
|
if (e != null)
|
||||||
|
json = e;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return json == root ? null : json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a json element given the specified member path
|
||||||
|
* @param path Path separated by a period . between each member name
|
||||||
|
*/
|
||||||
|
default boolean set(String path, Object obj) {
|
||||||
|
JsonElement root = gson.toJsonTree(this);
|
||||||
|
JsonElement json = root;
|
||||||
|
String[] paths = path.split("\\.");
|
||||||
|
|
||||||
|
if (paths.length == 0)
|
||||||
|
return false;
|
||||||
|
if (paths.length == 1) {
|
||||||
|
root.getAsJsonObject().add(path, gson.toJsonTree(obj));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < paths.length - 1; i++) {
|
||||||
|
JsonElement e = json.getAsJsonObject().get(paths[i]);
|
||||||
|
if (e != null)
|
||||||
|
json = e;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json != root) {
|
||||||
|
json.getAsJsonObject().add(paths[paths.length - 1], gson.toJsonTree(obj));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default void save() {
|
||||||
|
String json = serialize(true);
|
||||||
|
File f = getFile();
|
||||||
|
|
||||||
|
if (FileValidationUtils.validate(f)) {
|
||||||
|
try {
|
||||||
|
FileWriter fw = new FileWriter(f);
|
||||||
|
BufferedWriter bw = new BufferedWriter(fw);
|
||||||
|
bw.write(json);
|
||||||
|
bw.close();
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default <O> O getOrDef(O val, O def) {
|
||||||
|
return val != null ? val : def;
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T extends JsonSerializable<?>> T load(File file, Class<T> jsonSerializable, T fallback) {
|
||||||
|
if (FileValidationUtils.validate(file)) {
|
||||||
|
try {
|
||||||
|
FileReader fr = new FileReader(file);
|
||||||
|
BufferedReader br = new BufferedReader(fr);
|
||||||
|
T t = gson.fromJson(br, jsonSerializable);
|
||||||
|
|
||||||
|
if (t == null) {
|
||||||
|
throw new IllegalStateException("json parse failed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T extends JsonSerializable<?>> T load(String path, Class<T> jsonSerializable, T fallback) {
|
||||||
|
return load(new File(path), jsonSerializable, fallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package me.trouper.trimserver.utils.misc;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Randomizer {
|
||||||
|
|
||||||
|
public Randomizer() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T getRandomElement(List<T> list) {
|
||||||
|
if (list == null || list.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return list.get(getRandomIndex(list.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public final <T> T getRandomElement(T... list) {
|
||||||
|
if (list == null || list.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return list[getRandomIndex(list.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> int getRandomIndex(int listSize) {
|
||||||
|
if (listSize < 0) {
|
||||||
|
listSize = 0;
|
||||||
|
}
|
||||||
|
return (int)(Math.ceil(Math.random() * listSize) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getRandomBoolean() {
|
||||||
|
return Math.random() < 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 'Percentage' means an integer from 0-100. You should not divide this value by 100, as this does it for you.
|
||||||
|
* @param percentage an integer 0-100
|
||||||
|
* @return true if chance hit, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean getRandomChance(int percentage) {
|
||||||
|
return Math.random() < percentage / 100.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRandomInt(int min, int max) {
|
||||||
|
if (min > max) {
|
||||||
|
throw new IllegalArgumentException("min cannot be greater than max!");
|
||||||
|
}
|
||||||
|
int range = max - min + 1;
|
||||||
|
return min + (int)(Math.random() * range);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRandomInt(int max) {
|
||||||
|
return getRandomInt(0, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getRandomDouble(double min, double max) {
|
||||||
|
if (min > max) {
|
||||||
|
throw new IllegalArgumentException("min cannot be greater than max!");
|
||||||
|
}
|
||||||
|
double range = max - min;
|
||||||
|
return min + Math.random() * range;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getRandomDouble(double max) {
|
||||||
|
return getRandomDouble(0.0, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getRandomFloat(float min, float max) {
|
||||||
|
if (min > max) {
|
||||||
|
throw new IllegalArgumentException("min cannot be greater than max!");
|
||||||
|
}
|
||||||
|
float range = max - min;
|
||||||
|
return (float)(min + Math.random() * range);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getRandomFloat(float max) {
|
||||||
|
return getRandomFloat(0.0F, max);
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/main/java/me/trouper/trimserver/utils/misc/Voidable.java
Normal file
59
src/main/java/me/trouper/trimserver/utils/misc/Voidable.java
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package me.trouper.trimserver.utils.misc;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.Main;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class Voidable<T> implements Main {
|
||||||
|
|
||||||
|
private final T value;
|
||||||
|
|
||||||
|
private Voidable(T value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T get() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPresent() {
|
||||||
|
return value != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <U> Voidable<U> map(Function<T, U> function) {
|
||||||
|
return isPresent() ? of(function.apply(value)) : of(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void accept(Consumer<T> action) {
|
||||||
|
if (isPresent()) {
|
||||||
|
action.accept(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void accept(Consumer<T> action, Runnable orElse) {
|
||||||
|
if (isPresent()) {
|
||||||
|
action.accept(value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
orElse.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getOrDef(T fallback) {
|
||||||
|
return isPresent() ? value : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getOrThrow(String msg, Object... args) {
|
||||||
|
checkPre(isPresent(), msg, args);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getOrThrow() {
|
||||||
|
return getOrThrow("value is not present.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Voidable<T> of(T value) {
|
||||||
|
return new Voidable<>(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,351 @@
|
|||||||
|
package me.trouper.trimserver.utils.visual;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.Main;
|
||||||
|
import org.bukkit.*;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.entity.BlockDisplay;
|
||||||
|
import org.bukkit.entity.Display;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
import org.bukkit.util.BoundingBox;
|
||||||
|
import org.bukkit.util.Transformation;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
import org.bukkit.util.VoxelShape;
|
||||||
|
import org.joml.AxisAngle4f;
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class BlockDisplayRaytracer implements Main {
|
||||||
|
|
||||||
|
public static void cleanup() {
|
||||||
|
JavaPlugin plugin = main.getPlugin();
|
||||||
|
List<World> worlds = plugin.getServer().getWorlds();
|
||||||
|
List<Entity> entities = new ArrayList<>();
|
||||||
|
for (World world : worlds) {
|
||||||
|
entities.addAll(world.getEntities().stream().filter(entity -> entity.getScoreboardTags().contains("$/TrimServer/ Temp")).toList());
|
||||||
|
entities.forEach(entity -> {
|
||||||
|
if (entity != null) entity.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void outline(Material display, Location location, long stayTime, List<Player> viewers) {
|
||||||
|
outline(display, location, 0.05, stayTime, viewers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void outline(Material display, Location corner1, Location corner2, double thickness, long stayTime, List<Player> viewers) {
|
||||||
|
World world = corner1.getWorld();
|
||||||
|
|
||||||
|
int minX = Math.min(corner1.getBlockX(), corner2.getBlockX());
|
||||||
|
int minY = Math.min(corner1.getBlockY(), corner2.getBlockY());
|
||||||
|
int minZ = Math.min(corner1.getBlockZ(), corner2.getBlockZ());
|
||||||
|
int maxX = Math.max(corner1.getBlockX(), corner2.getBlockX());
|
||||||
|
int maxY = Math.max(corner1.getBlockY(), corner2.getBlockY());
|
||||||
|
int maxZ = Math.max(corner1.getBlockZ(), corner2.getBlockZ());
|
||||||
|
|
||||||
|
Location a1 = new Location(world, minX, minY, minZ);
|
||||||
|
Location a2 = new Location(world, maxX + 1, minY, minZ);
|
||||||
|
Location a3 = new Location(world, maxX + 1, minY, maxZ + 1);
|
||||||
|
Location a4 = new Location(world, minX, minY, maxZ + 1);
|
||||||
|
|
||||||
|
Location b1 = new Location(world, minX, maxY + 1, minZ);
|
||||||
|
Location b2 = new Location(world, maxX + 1, maxY + 1, minZ);
|
||||||
|
Location b3 = new Location(world, maxX + 1, maxY + 1, maxZ + 1);
|
||||||
|
Location b4 = new Location(world, minX, maxY + 1, maxZ + 1);
|
||||||
|
|
||||||
|
trace(display, a1, a2, thickness, stayTime, viewers);
|
||||||
|
trace(display, a2, a3, thickness, stayTime, viewers);
|
||||||
|
trace(display, a3, a4, thickness, stayTime, viewers);
|
||||||
|
trace(display, a4, a1, thickness, stayTime, viewers);
|
||||||
|
|
||||||
|
trace(display, b1, b2, thickness, stayTime, viewers);
|
||||||
|
trace(display, b2, b3, thickness, stayTime, viewers);
|
||||||
|
trace(display, b3, b4, thickness, stayTime, viewers);
|
||||||
|
trace(display, b4, b1, thickness, stayTime, viewers);
|
||||||
|
|
||||||
|
trace(display, a1, b1, thickness, stayTime, viewers);
|
||||||
|
trace(display, a2, b2, thickness, stayTime, viewers);
|
||||||
|
trace(display, a3, b3, thickness, stayTime, viewers);
|
||||||
|
trace(display, a4, b4, thickness, stayTime, viewers);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void outline(Material display, Location location, double thickness, long stayTime, List<Player> viewers) {
|
||||||
|
Location og = location.getBlock().getLocation();
|
||||||
|
|
||||||
|
Location a1 = og.clone().add(0, 0, 0);
|
||||||
|
Location a2 = og.clone().add(1, 0, 0);
|
||||||
|
Location a3 = og.clone().add(1, 0, 1);
|
||||||
|
Location a4 = og.clone().add(0, 0, 1);
|
||||||
|
|
||||||
|
Location b1 = og.clone().add(0, 1, 0);
|
||||||
|
Location b2 = og.clone().add(1, 1, 0);
|
||||||
|
Location b3 = og.clone().add(1, 1, 1);
|
||||||
|
Location b4 = og.clone().add(0, 1, 1);
|
||||||
|
|
||||||
|
trace(display, a1, a2, thickness, stayTime, viewers);
|
||||||
|
trace(display, a2, a3, thickness, stayTime, viewers);
|
||||||
|
trace(display, a3, a4, thickness, stayTime, viewers);
|
||||||
|
trace(display, a4, a1, thickness, stayTime, viewers);
|
||||||
|
|
||||||
|
trace(display, b1, b2, thickness, stayTime, viewers);
|
||||||
|
trace(display, b2, b3, thickness, stayTime, viewers);
|
||||||
|
trace(display, b3, b4, thickness, stayTime, viewers);
|
||||||
|
trace(display, b4, b1, thickness, stayTime, viewers);
|
||||||
|
|
||||||
|
trace(display, a1, b1, thickness, stayTime, viewers);
|
||||||
|
trace(display, a2, b2, thickness, stayTime, viewers);
|
||||||
|
trace(display, a3, b3, thickness, stayTime, viewers);
|
||||||
|
trace(display, a4, b4, thickness, stayTime, viewers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void trace(Material display, Location start, Location end, long stayTime, List<Player> viewers) {
|
||||||
|
trace(display, start, end.toVector().subtract(start.toVector()), 0.05, end.distance(start), stayTime, viewers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void trace(Material display, Location start, Location end, double thickness, long stayTime, List<Player> viewers) {
|
||||||
|
trace(display, start, end.toVector().subtract(start.toVector()), thickness, end.distance(start), stayTime, viewers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void trace(Material display, Location start, Vector direction, double thickness, double distance, long stayTime, List<Player> viewers) {
|
||||||
|
World world = start.getWorld();
|
||||||
|
|
||||||
|
BlockDisplay beam = world.spawn(start, BlockDisplay.class, entity -> {
|
||||||
|
AxisAngle4f angle = new AxisAngle4f(0, 0, 0, 1);
|
||||||
|
Vector3f transition = new Vector3f(-(float)(thickness / 2F));
|
||||||
|
Vector3f scale = new Vector3f((float)thickness, (float)thickness, (float)distance);
|
||||||
|
Transformation trans = new Transformation(transition, angle, scale, angle);
|
||||||
|
Location vector = entity.getLocation();
|
||||||
|
|
||||||
|
vector.setDirection(direction);
|
||||||
|
entity.teleport(vector);
|
||||||
|
entity.setBlock(display.createBlockData());
|
||||||
|
entity.setBrightness(new Display.Brightness(15, 15));
|
||||||
|
entity.setInterpolationDelay(0);
|
||||||
|
entity.setTransformation(trans);
|
||||||
|
entity.addScoreboardTag("$/TrimServer/ Temp");
|
||||||
|
|
||||||
|
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||||
|
if (!viewers.contains(player)) {
|
||||||
|
player.hideEntity(main.getPlugin(), entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTaskLater(main.getPlugin(), entity::remove, stayTime);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void trace(Material display, Location start, Vector direction, double thickness, double distance, long stayTime, Consumer<BlockDisplay> onEntitySpawn, List<Player> viewers) {
|
||||||
|
World world = start.getWorld();
|
||||||
|
|
||||||
|
BlockDisplay beam = world.spawn(start, BlockDisplay.class, entity -> {
|
||||||
|
AxisAngle4f angle = new AxisAngle4f(0, 0, 0, 1);
|
||||||
|
Vector3f transition = new Vector3f(-(float)(thickness / 2F));
|
||||||
|
Vector3f scale = new Vector3f((float)thickness, (float)thickness, (float)distance);
|
||||||
|
Transformation trans = new Transformation(transition, angle, scale, angle);
|
||||||
|
Location vector = entity.getLocation();
|
||||||
|
|
||||||
|
vector.setDirection(direction);
|
||||||
|
entity.teleport(vector);
|
||||||
|
entity.setBlock(display.createBlockData());
|
||||||
|
entity.setBrightness(new Display.Brightness(15, 15));
|
||||||
|
entity.setInterpolationDelay(0);
|
||||||
|
entity.setTransformation(trans);
|
||||||
|
entity.addScoreboardTag("$/TrimServer/ Temp");
|
||||||
|
|
||||||
|
|
||||||
|
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||||
|
if (!viewers.contains(player)) {
|
||||||
|
player.hideEntity(main.getPlugin(), entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTaskLater(main.getPlugin(), entity::remove, stayTime);
|
||||||
|
Bukkit.getScheduler().runTaskLater(main.getPlugin(), () -> onEntitySpawn.accept(entity), 5);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<BlockDisplay> outline(Material display, Location location, long stayTime) {
|
||||||
|
return outline(display, location, 0.05, stayTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<BlockDisplay> outline(Material display, Location location, double thickness, long stayTime) {
|
||||||
|
Location og = location.getBlock().getLocation();
|
||||||
|
|
||||||
|
Location a1 = og.clone().add(0, 0, 0);
|
||||||
|
Location a2 = og.clone().add(1, 0, 0);
|
||||||
|
Location a3 = og.clone().add(1, 0, 1);
|
||||||
|
Location a4 = og.clone().add(0, 0, 1);
|
||||||
|
|
||||||
|
Location b1 = og.clone().add(0, 1, 0);
|
||||||
|
Location b2 = og.clone().add(1, 1, 0);
|
||||||
|
Location b3 = og.clone().add(1, 1, 1);
|
||||||
|
Location b4 = og.clone().add(0, 1, 1);
|
||||||
|
|
||||||
|
List<BlockDisplay> a = new ArrayList<>();
|
||||||
|
|
||||||
|
a.add(trace(display, a1, a2, thickness, stayTime));
|
||||||
|
a.add(trace(display, a2, a3, thickness, stayTime));
|
||||||
|
a.add(trace(display, a3, a4, thickness, stayTime));
|
||||||
|
a.add(trace(display, a4, a1, thickness, stayTime));
|
||||||
|
|
||||||
|
a.add(trace(display, b1, b2, thickness, stayTime));
|
||||||
|
a.add(trace(display, b2, b3, thickness, stayTime));
|
||||||
|
a.add(trace(display, b3, b4, thickness, stayTime));
|
||||||
|
a.add(trace(display, b4, b1, thickness, stayTime));
|
||||||
|
|
||||||
|
a.add(trace(display, a1, b1, thickness, stayTime));
|
||||||
|
a.add(trace(display, a2, b2, thickness, stayTime));
|
||||||
|
a.add(trace(display, a3, b3, thickness, stayTime));
|
||||||
|
a.add(trace(display, a4, b4, thickness, stayTime));
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void highlightCollisions(Block block, Color color, long stayTime) {
|
||||||
|
if (block == null || block.isEmpty() || !block.isCollidable())
|
||||||
|
return;
|
||||||
|
|
||||||
|
VoxelShape shape = block.getCollisionShape();
|
||||||
|
World world = block.getWorld();
|
||||||
|
Vector offset = block.getLocation().toVector();
|
||||||
|
|
||||||
|
for (BoundingBox box : shape.getBoundingBoxes()) {
|
||||||
|
highlight(box, offset, world, color, stayTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void highlight(BoundingBox box, Vector offset, World world, Color color, long stayTime) {
|
||||||
|
double x1 = box.getMinX() + offset.getX();
|
||||||
|
double y1 = box.getMinY() + offset.getY();
|
||||||
|
double z1 = box.getMinZ() + offset.getZ();
|
||||||
|
double x2 = box.getMaxX() + offset.getX();
|
||||||
|
double y2 = box.getMaxY() + offset.getY();
|
||||||
|
double z2 = box.getMaxZ() + offset.getZ();
|
||||||
|
|
||||||
|
traceGlowing(world, x1, y1, z1, x2, y1, z1, color, stayTime);
|
||||||
|
traceGlowing(world, x2, y1, z1, x2, y1, z2, color, stayTime);
|
||||||
|
traceGlowing(world, x2, y1, z2, x1, y1, z2, color, stayTime);
|
||||||
|
traceGlowing(world, x1, y1, z2, x1, y1, z1, color, stayTime);
|
||||||
|
|
||||||
|
traceGlowing(world, x1, y2, z1, x2, y2, z1, color, stayTime);
|
||||||
|
traceGlowing(world, x2, y2, z1, x2, y2, z2, color, stayTime);
|
||||||
|
traceGlowing(world, x2, y2, z2, x1, y2, z2, color, stayTime);
|
||||||
|
traceGlowing(world, x1, y2, z2, x1, y2, z1, color, stayTime);
|
||||||
|
|
||||||
|
traceGlowing(world, x1, y1, z1, x1, y2, z1, color, stayTime);
|
||||||
|
traceGlowing(world, x2, y1, z1, x2, y2, z1, color, stayTime);
|
||||||
|
traceGlowing(world, x2, y1, z2, x2, y2, z2, color, stayTime);
|
||||||
|
traceGlowing(world, x1, y1, z2, x1, y2, z2, color, stayTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void traceGlowing(World world, double x1, double y1, double z1, double x2, double y2, double z2, Color color, long stayTime) {
|
||||||
|
Location loc1 = new Location(world, x1, y1, z1);
|
||||||
|
Location loc2 = new Location(world, x2, y2, z2);
|
||||||
|
BlockDisplay ent = trace(Material.WHITE_CONCRETE, loc1, loc2, 0.01, stayTime);
|
||||||
|
ent.setGlowColorOverride(color);
|
||||||
|
ent.setGlowing(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BlockDisplay trace(Material display, Location start, Location end, long stayTime) {
|
||||||
|
return trace(display, start, end.toVector().subtract(start.toVector()), 0.05, end.distance(start), stayTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BlockDisplay trace(Material display, Location start, Location end, double thickness, long stayTime) {
|
||||||
|
return trace(display, start, end.toVector().subtract(start.toVector()), thickness, end.distance(start), stayTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BlockDisplay trace(Material display, Location start, Vector direction, double thickness, double distance, long stayTime) {
|
||||||
|
World world = start.getWorld();
|
||||||
|
|
||||||
|
BlockDisplay entity = world.spawn(start, BlockDisplay.class);
|
||||||
|
AxisAngle4f angle = new AxisAngle4f(0, 0, 0, 1);
|
||||||
|
Vector3f transition = new Vector3f(-(float)(thickness / 2F));
|
||||||
|
Vector3f scale = new Vector3f((float)thickness, (float)thickness, (float)distance);
|
||||||
|
Transformation trans = new Transformation(transition, angle, scale, angle);
|
||||||
|
Location vector = entity.getLocation();
|
||||||
|
|
||||||
|
vector.setDirection(direction);
|
||||||
|
entity.teleport(vector);
|
||||||
|
entity.setBlock(display.createBlockData());
|
||||||
|
entity.setBrightness(new Display.Brightness(15, 15));
|
||||||
|
entity.setInterpolationDelay(0);
|
||||||
|
entity.setTransformation(trans);
|
||||||
|
entity.addScoreboardTag("$/TrimServer/ Temp");
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTaskLater(main.getPlugin(), entity::remove, stayTime);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void transform(BlockDisplay display, Location start, Location end, double thickness) {
|
||||||
|
Vector direction = end.toVector().subtract(start.toVector());
|
||||||
|
double distance = direction.length();
|
||||||
|
|
||||||
|
Location loc = start.clone();
|
||||||
|
loc.setDirection(direction);
|
||||||
|
display.teleport(loc);
|
||||||
|
|
||||||
|
Vector3f translation = new Vector3f(-(float)(thickness / 2F), 0, 0); // Centered
|
||||||
|
Vector3f scale = new Vector3f((float)thickness, (float)thickness, (float)distance);
|
||||||
|
AxisAngle4f rotation = new AxisAngle4f(0, 0, 0, 1);
|
||||||
|
Transformation transformation = new Transformation(translation, rotation, scale, rotation);
|
||||||
|
|
||||||
|
display.setTransformation(transformation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void transform(BlockDisplay display, Location start, Vector direction, double distance, double thickness) {
|
||||||
|
Location loc = start.clone();
|
||||||
|
loc.setDirection(direction);
|
||||||
|
display.teleport(loc);
|
||||||
|
|
||||||
|
Vector3f translation = new Vector3f(-(float)(thickness / 2F), 0, 0);
|
||||||
|
Vector3f scale = new Vector3f((float)thickness, (float)thickness, (float)distance);
|
||||||
|
AxisAngle4f rotation = new AxisAngle4f(0, 0, 0, 1);
|
||||||
|
Transformation transformation = new Transformation(translation, rotation, scale, rotation);
|
||||||
|
|
||||||
|
display.setTransformation(transformation);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void translate(BlockDisplay display, Vector3f offset) {
|
||||||
|
Transformation current = display.getTransformation();
|
||||||
|
Vector3f translation = new Vector3f(current.getTranslation()).add(offset);
|
||||||
|
display.setTransformation(new Transformation(
|
||||||
|
translation,
|
||||||
|
current.getLeftRotation(),
|
||||||
|
current.getScale(),
|
||||||
|
current.getRightRotation()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void scale(BlockDisplay display, Vector3f scale) {
|
||||||
|
Transformation current = display.getTransformation();
|
||||||
|
display.setTransformation(new Transformation(
|
||||||
|
current.getTranslation(),
|
||||||
|
current.getLeftRotation(),
|
||||||
|
scale,
|
||||||
|
current.getRightRotation()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void rotate(BlockDisplay display, AxisAngle4f rotation) {
|
||||||
|
Transformation current = display.getTransformation();
|
||||||
|
display.setTransformation(new Transformation(
|
||||||
|
current.getTranslation(),
|
||||||
|
rotation,
|
||||||
|
current.getScale(),
|
||||||
|
rotation
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void alignToDirection(BlockDisplay display, Vector direction) {
|
||||||
|
Location loc = display.getLocation().clone();
|
||||||
|
loc.setDirection(direction);
|
||||||
|
display.teleport(loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
package me.trouper.trimserver.utils.visual;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.utils.Verbose;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.BlockFace;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.LivingEntity;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
import org.bukkit.util.BoundingBox;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
import org.bukkit.util.VoxelShape;
|
||||||
|
|
||||||
|
import java.util.function.BiPredicate;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
public class CustomDisplayRaytracer {
|
||||||
|
|
||||||
|
public static final Predicate<Point> HIT_BLOCK = point -> {
|
||||||
|
Block b = point.getBlock();
|
||||||
|
Location l = point.getLoc();
|
||||||
|
|
||||||
|
if (b == null || b.isEmpty() || !b.isCollidable())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Vector vec = l.toVector().subtract(b.getLocation().toVector());
|
||||||
|
VoxelShape shape = b.getCollisionShape();
|
||||||
|
|
||||||
|
for (BoundingBox box : shape.getBoundingBoxes())
|
||||||
|
if (box.contains(vec))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final Predicate<Point> HIT_ENTITY = point -> {
|
||||||
|
return !point.getNearbyEntities(null, 5, true, 0.1, e -> e instanceof LivingEntity le && !le.isDead()).isEmpty();
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final Predicate<Point> HIT_BLOCK_OR_ENTITY = point -> {
|
||||||
|
return HIT_BLOCK.test(point) || HIT_ENTITY.test(point);
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final Predicate<Point> HIT_BLOCK_AND_ENTITY = point -> {
|
||||||
|
return HIT_BLOCK.test(point) && HIT_ENTITY.test(point);
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Predicate<Point> hitEntityExclude(Entity exclude) {
|
||||||
|
return point -> !point.getNearbyEntities(exclude, 5, true, 0.1, e -> e instanceof LivingEntity le && !le.isDead()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Predicate<Point> hitAnythingExclude(Entity exclude) {
|
||||||
|
return point -> HIT_BLOCK.test(point) || !point.getNearbyEntities(exclude, 5, true, 0.1, e -> e instanceof LivingEntity le && !le.isDead()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Predicate<Point> hitEverythingExclude(Entity exclude) {
|
||||||
|
return point -> HIT_BLOCK.test(point) && !point.getNearbyEntities(exclude, 5, true, 0.1, e -> e instanceof LivingEntity le && !le.isDead()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Predicate<Point> hitEntityIf(Predicate<Entity> condition) {
|
||||||
|
return point -> !point.getNearbyEntities(null, 5, true, 0.1, e -> e instanceof LivingEntity le && !le.isDead() && condition.test(e)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Predicate<Point> hitBlockIf(Predicate<Block> condition) {
|
||||||
|
return point -> HIT_BLOCK.test(point) && condition.test(point.getBlock());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Predicate<Point> hitAnythingIf(Predicate<Entity> condition) {
|
||||||
|
return point -> HIT_BLOCK.test(point) || !point.getNearbyEntities(null, 5, true, 0.1, e -> e instanceof LivingEntity le && !le.isDead() && condition.test(e)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Predicate<Point> hitEverythingIf(Predicate<Entity> condition) {
|
||||||
|
return point -> HIT_BLOCK.test(point) && !point.getNearbyEntities(null, 5, true, 0.1, e -> e instanceof LivingEntity le && !le.isDead() && condition.test(e)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Point trace(Location start, Location end, Predicate<Point> hitCondition) {
|
||||||
|
return trace(start, end, 0.5, hitCondition);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Point trace(Location start, Location end, double interval, Predicate<Point> hitCondition) {
|
||||||
|
return trace(start, end.toVector().subtract(start.toVector()), end.distance(start), interval, hitCondition);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Point trace(Location start, Vector direction, double distance, Predicate<Point> hitCondition) {
|
||||||
|
return trace(start, direction, distance, 0.5, hitCondition);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Point trace(Location start, Vector direction, double distance, double interval, Predicate<Point> hitCondition) {
|
||||||
|
if (interval < 0) throw new IllegalArgumentException("interval cannot be zero!");
|
||||||
|
if (distance < 0) throw new IllegalArgumentException("distance cannot be zero!");
|
||||||
|
|
||||||
|
for (double i = 0.0; i < distance; i += interval) {
|
||||||
|
Point point = blocksInFrontOf(start, direction, i, false);
|
||||||
|
if (hitCondition.test(point)) {
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return blocksInFrontOf(start, direction, distance, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static BukkitTask traceDelayed(Plugin plugin, Location start, Vector direction, double distance, double interval, long tickDelay, int pointsPerTick, Predicate<Point> hitCondition) {
|
||||||
|
|
||||||
|
if (interval <= 0) throw new IllegalArgumentException("interval cannot be zero or negative!");
|
||||||
|
if (distance <= 0) throw new IllegalArgumentException("distance cannot be zero or negative!");
|
||||||
|
if (tickDelay < 0) throw new IllegalArgumentException("tickDelay cannot be negative!");
|
||||||
|
|
||||||
|
Vector normalizedDir = direction.clone().normalize();
|
||||||
|
|
||||||
|
|
||||||
|
return new BukkitRunnable() {
|
||||||
|
private double currentDistance = 0.0;
|
||||||
|
private boolean hit = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (hit || currentDistance > distance) {
|
||||||
|
if (!hit) {
|
||||||
|
Point finalPoint = blocksInFrontOf(start, normalizedDir, distance, true);
|
||||||
|
hitCondition.test(finalPoint);
|
||||||
|
}
|
||||||
|
this.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < pointsPerTick && currentDistance <= distance; i++) {
|
||||||
|
Point point = blocksInFrontOf(start, normalizedDir, currentDistance, false);
|
||||||
|
if (hitCondition.test(point)) {
|
||||||
|
hit = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
currentDistance += interval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.runTaskTimer(plugin, 0, tickDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BukkitTask traceDelayed(Plugin plugin, Location start, Location end, double interval, long tickDelay, int pointsPerTick, Predicate<Point> hitCondition) {
|
||||||
|
Vector direction = end.toVector().subtract(start.toVector()).normalize();
|
||||||
|
double distance = start.distance(end);
|
||||||
|
return traceDelayed(plugin, start, direction, distance, interval, tickDelay,pointsPerTick, hitCondition);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BukkitTask traceDelayed(Plugin plugin,
|
||||||
|
Location start,
|
||||||
|
Location end,
|
||||||
|
long tickDelay,
|
||||||
|
Predicate<Point> hitCondition) {
|
||||||
|
return traceDelayed(plugin, start, end,0.5, tickDelay, 1, hitCondition);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BukkitTask traceDelayed(Plugin plugin,
|
||||||
|
Location start,
|
||||||
|
Vector direction,
|
||||||
|
double distance,
|
||||||
|
long tickDelay,
|
||||||
|
Predicate<Point> hitCondition) {
|
||||||
|
return traceDelayed(plugin, start, direction, distance, 0.5, tickDelay,1, hitCondition);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Point traceWithReflection(Location start, Vector direction, double distance, double interval,
|
||||||
|
int maxReflections, Predicate<Point> hitCondition,
|
||||||
|
BiPredicate<Point,Block> blockReflectCondition,
|
||||||
|
BiPredicate<Point,Entity> entityReflectCondition) {
|
||||||
|
if (interval <= 0) throw new IllegalArgumentException("interval cannot be zero or negative!");
|
||||||
|
if (distance <= 0) throw new IllegalArgumentException("distance cannot be zero or negative!");
|
||||||
|
if (maxReflections < 0) throw new IllegalArgumentException("maxReflections cannot be negative!");
|
||||||
|
|
||||||
|
Vector normalizedDir = direction.clone().normalize();
|
||||||
|
Location currentStart = start.clone();
|
||||||
|
Vector currentDir = normalizedDir.clone();
|
||||||
|
double remainingDistance = distance;
|
||||||
|
|
||||||
|
for (int reflection = 0; reflection <= maxReflections; reflection++) {
|
||||||
|
Point hitPoint = trace(currentStart, currentDir, remainingDistance, interval, hitCondition);
|
||||||
|
|
||||||
|
if (hitPoint.wasMissed()) {
|
||||||
|
Verbose.send("First ray missed, returning.");
|
||||||
|
return hitPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
double distanceUsed = currentStart.distance(hitPoint.getLoc());
|
||||||
|
remainingDistance -= distanceUsed;
|
||||||
|
|
||||||
|
if (remainingDistance <= 0) {
|
||||||
|
Verbose.send("First ray too long, returning.");
|
||||||
|
return hitPoint;
|
||||||
|
}
|
||||||
|
Vector reflectedDirection = null;
|
||||||
|
|
||||||
|
if (HIT_BLOCK.test(hitPoint)) {
|
||||||
|
Verbose.send("hit block!");
|
||||||
|
Block hitBlock = hitPoint.getBlock();
|
||||||
|
|
||||||
|
if (blockReflectCondition.test(hitPoint,hitBlock)) {
|
||||||
|
Verbose.send("Check passed!");
|
||||||
|
BlockFace hitFace = hitPoint.getBlockFace(currentDir);
|
||||||
|
if (hitFace != null) {
|
||||||
|
Verbose.send("Face not null");
|
||||||
|
reflectedDirection = calculateBlockReflection(currentDir, hitFace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (HIT_ENTITY.test(hitPoint)) {
|
||||||
|
Verbose.send("hit entity!");
|
||||||
|
Entity hitEntity = hitPoint.getNearbyEntities(null, 5, true, 0.1,
|
||||||
|
e -> e instanceof LivingEntity le && !le.isDead()).stream().findFirst().orElse(null);
|
||||||
|
|
||||||
|
if (hitEntity != null && entityReflectCondition != null && entityReflectCondition.test(hitPoint,hitEntity)) {
|
||||||
|
Verbose.send("Check passed!");
|
||||||
|
reflectedDirection = currentDir.clone().multiply(-1).normalize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reflectedDirection == null) {
|
||||||
|
Verbose.send("Reflected direction null");
|
||||||
|
return hitPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentStart = hitPoint.getLoc().clone();
|
||||||
|
currentDir = reflectedDirection;
|
||||||
|
|
||||||
|
currentStart = blocksInFrontOf(currentStart,currentDir,interval*2,false).getLoc();
|
||||||
|
}
|
||||||
|
|
||||||
|
return trace(currentStart, currentDir, remainingDistance, interval, hitCondition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector calculateBlockReflection(Vector incident, BlockFace face) {
|
||||||
|
Verbose.send("Calculating vector reflection for face %s".formatted(face));
|
||||||
|
Vector normal = getFaceNormal(face);
|
||||||
|
|
||||||
|
// r = i - 2(i dot n)n
|
||||||
|
double dot = incident.dot(normal);
|
||||||
|
Vector reflection = incident.clone().subtract(normal.clone().multiply(2 * dot));
|
||||||
|
|
||||||
|
return reflection.normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector getFaceNormal(BlockFace face) {
|
||||||
|
Verbose.send("Getting normal for %s".formatted(face));
|
||||||
|
return switch (face) {
|
||||||
|
case DOWN -> new Vector(0, -1, 0);
|
||||||
|
case NORTH -> new Vector(0, 0, -1);
|
||||||
|
case SOUTH -> new Vector(0, 0, 1);
|
||||||
|
case EAST -> new Vector(1, 0, 0);
|
||||||
|
case WEST -> new Vector(-1, 0, 0);
|
||||||
|
default -> new Vector(0, 1, 0);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Point blocksInFrontOf(Location loc, Vector dir, double blocks, boolean missed) {
|
||||||
|
return new Point(loc.clone().add(dir.getX() * blocks, dir.getY() * blocks, dir.getZ() * blocks), blocks, missed);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,495 @@
|
|||||||
|
package me.trouper.trimserver.utils.visual;
|
||||||
|
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.BlockFace;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.LivingEntity;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BiPredicate;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
public class CustomDisplayReflector {
|
||||||
|
|
||||||
|
// Commonly used reflection conditions
|
||||||
|
public static final BiPredicate<Point, BlockFace> REFLECT_ON_ANY_BLOCK = (point, face) ->
|
||||||
|
face != null && !point.getBlock().isEmpty() && point.getBlock().isCollidable();
|
||||||
|
|
||||||
|
public static final BiPredicate<Point, Entity> REFLECT_ON_ANY_ENTITY = (point, entity) ->
|
||||||
|
entity instanceof LivingEntity && !entity.isDead();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a reflection condition that excludes a specific entity
|
||||||
|
* @param exclude The entity to exclude from reflection
|
||||||
|
* @return A BiPredicate that determines if reflection should occur on an entity
|
||||||
|
*/
|
||||||
|
public static BiPredicate<Point, Entity> reflectOnEntityExclude(Entity exclude) {
|
||||||
|
return (point, entity) -> entity != exclude && entity instanceof LivingEntity && !entity.isDead();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traces a ray that can reflect off blocks based on provided conditions
|
||||||
|
* @param start The starting location
|
||||||
|
* @param direction The initial direction
|
||||||
|
* @param maxDistance The maximum total distance the ray can travel
|
||||||
|
* @param maxReflections The maximum number of reflections allowed
|
||||||
|
* @param interval The interval between trace points
|
||||||
|
* @param blockReflectCondition Determines if a ray should reflect off a block at a given point
|
||||||
|
* @return List of ray segments (each representing a segment between reflections)
|
||||||
|
*/
|
||||||
|
public static List<RaySegment> traceReflectingRay(
|
||||||
|
Location start,
|
||||||
|
Vector direction,
|
||||||
|
double maxDistance,
|
||||||
|
int maxReflections,
|
||||||
|
double interval,
|
||||||
|
BiPredicate<Point, BlockFace> blockReflectCondition) {
|
||||||
|
|
||||||
|
if (interval <= 0) throw new IllegalArgumentException("interval cannot be zero or negative!");
|
||||||
|
if (maxDistance <= 0) throw new IllegalArgumentException("maxDistance cannot be zero or negative!");
|
||||||
|
|
||||||
|
List<RaySegment> segments = new ArrayList<>();
|
||||||
|
Vector normalizedDir = direction.clone().normalize();
|
||||||
|
Location currentStart = start.clone();
|
||||||
|
double remainingDistance = maxDistance;
|
||||||
|
int reflections = 0;
|
||||||
|
|
||||||
|
while (remainingDistance > 0 && reflections <= maxReflections) {
|
||||||
|
// Trace until hit or max distance
|
||||||
|
RayResult result = traceUntilReflect(currentStart, normalizedDir, remainingDistance, interval, blockReflectCondition);
|
||||||
|
|
||||||
|
// Add segment
|
||||||
|
segments.add(new RaySegment(currentStart.clone(), result.getEndPoint().getLoc().clone(), normalizedDir.clone(), result.getDistance()));
|
||||||
|
|
||||||
|
// Check if we've hit something reflective
|
||||||
|
if (result.getReflectionType() == ReflectionType.NONE) {
|
||||||
|
break; // No reflection occurred, end of ray
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingDistance -= result.getDistance();
|
||||||
|
reflections++;
|
||||||
|
|
||||||
|
// Update for next segment
|
||||||
|
currentStart = result.getEndPoint().getLoc().clone();
|
||||||
|
normalizedDir = result.getNewDirection().clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
return segments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traces a ray that can reflect off both blocks and entities
|
||||||
|
* @param start The starting location
|
||||||
|
* @param direction The initial direction
|
||||||
|
* @param maxDistance The maximum total distance the ray can travel
|
||||||
|
* @param maxReflections The maximum number of reflections allowed
|
||||||
|
* @param interval The interval between trace points
|
||||||
|
* @param blockReflectCondition Determines if a ray should reflect off a block
|
||||||
|
* @param entityReflectCondition Determines if a ray should reflect off an entity
|
||||||
|
* @return List of ray segments (each representing a segment between reflections)
|
||||||
|
*/
|
||||||
|
public static List<RaySegment> traceReflectingRay(
|
||||||
|
Location start,
|
||||||
|
Vector direction,
|
||||||
|
double maxDistance,
|
||||||
|
int maxReflections,
|
||||||
|
double interval,
|
||||||
|
BiPredicate<Point, BlockFace> blockReflectCondition,
|
||||||
|
BiPredicate<Point, Entity> entityReflectCondition) {
|
||||||
|
|
||||||
|
if (interval <= 0) throw new IllegalArgumentException("interval cannot be zero or negative!");
|
||||||
|
if (maxDistance <= 0) throw new IllegalArgumentException("maxDistance cannot be zero or negative!");
|
||||||
|
|
||||||
|
List<RaySegment> segments = new ArrayList<>();
|
||||||
|
Vector normalizedDir = direction.clone().normalize();
|
||||||
|
Location currentStart = start.clone();
|
||||||
|
double remainingDistance = maxDistance;
|
||||||
|
int reflections = 0;
|
||||||
|
|
||||||
|
while (remainingDistance > 0 && reflections <= maxReflections) {
|
||||||
|
// Trace until hit or max distance
|
||||||
|
RayResult result = traceUntilReflect(currentStart, normalizedDir, remainingDistance, interval, blockReflectCondition, entityReflectCondition);
|
||||||
|
|
||||||
|
// Add segment
|
||||||
|
segments.add(new RaySegment(currentStart.clone(), result.getEndPoint().getLoc().clone(), normalizedDir.clone(), result.getDistance()));
|
||||||
|
|
||||||
|
// Check if we've hit something reflective
|
||||||
|
if (result.getReflectionType() == ReflectionType.NONE) {
|
||||||
|
break; // No reflection occurred, end of ray
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingDistance -= result.getDistance();
|
||||||
|
reflections++;
|
||||||
|
|
||||||
|
// Update for next segment
|
||||||
|
currentStart = result.getEndPoint().getLoc().clone();
|
||||||
|
normalizedDir = result.getNewDirection().clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
return segments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traces a ray until reflection or max distance
|
||||||
|
*/
|
||||||
|
private static RayResult traceUntilReflect(
|
||||||
|
Location start,
|
||||||
|
Vector direction,
|
||||||
|
double maxDistance,
|
||||||
|
double interval,
|
||||||
|
BiPredicate<Point, BlockFace> blockReflectCondition) {
|
||||||
|
|
||||||
|
Vector normalizedDir = direction.clone().normalize();
|
||||||
|
|
||||||
|
for (double i = 0.0; i < maxDistance; i += interval) {
|
||||||
|
Point point = CustomDisplayRaytracer.blocksInFrontOf(start, normalizedDir, i, false);
|
||||||
|
|
||||||
|
// Check for block reflection
|
||||||
|
BlockFace hitFace = getHitBlockFace(point);
|
||||||
|
if (hitFace != null && blockReflectCondition.test(point, hitFace)) {
|
||||||
|
// Calculate reflection
|
||||||
|
Vector reflectedDir = calculateBlockReflection(normalizedDir, hitFace);
|
||||||
|
return new RayResult(point, reflectedDir, i, ReflectionType.BLOCK, hitFace, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No reflection found, return end point
|
||||||
|
Point endPoint = CustomDisplayRaytracer.blocksInFrontOf(start, normalizedDir, maxDistance, true);
|
||||||
|
return new RayResult(endPoint, normalizedDir, maxDistance, ReflectionType.NONE, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traces a ray until reflection off block or entity, or max distance
|
||||||
|
*/
|
||||||
|
private static RayResult traceUntilReflect(
|
||||||
|
Location start,
|
||||||
|
Vector direction,
|
||||||
|
double maxDistance,
|
||||||
|
double interval,
|
||||||
|
BiPredicate<Point, BlockFace> blockReflectCondition,
|
||||||
|
BiPredicate<Point, Entity> entityReflectCondition) {
|
||||||
|
|
||||||
|
Vector normalizedDir = direction.clone().normalize();
|
||||||
|
|
||||||
|
for (double i = 0.0; i < maxDistance; i += interval) {
|
||||||
|
Point point = CustomDisplayRaytracer.blocksInFrontOf(start, normalizedDir, i, false);
|
||||||
|
|
||||||
|
// Check for entity reflection first
|
||||||
|
List<Entity> entities = point.getNearbyEntities(null, 5, true, 0.1, e -> true);
|
||||||
|
for (Entity entity : entities) {
|
||||||
|
if (entityReflectCondition.test(point, entity)) {
|
||||||
|
// Entity reflection - return in opposite direction
|
||||||
|
Vector reflectedDir = normalizedDir.clone().multiply(-1);
|
||||||
|
return new RayResult(point, reflectedDir, i, ReflectionType.ENTITY, null, entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for block reflection
|
||||||
|
BlockFace hitFace = getHitBlockFace(point);
|
||||||
|
if (hitFace != null && blockReflectCondition.test(point, hitFace)) {
|
||||||
|
Vector reflectedDir = calculateBlockReflection(normalizedDir, hitFace);
|
||||||
|
return new RayResult(point, reflectedDir, i, ReflectionType.BLOCK, hitFace, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No reflection found, return end point
|
||||||
|
Point endPoint = CustomDisplayRaytracer.blocksInFrontOf(start, normalizedDir, maxDistance, true);
|
||||||
|
return new RayResult(endPoint, normalizedDir, maxDistance, ReflectionType.NONE, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a delayed tracing of a reflecting ray
|
||||||
|
*/
|
||||||
|
public static BukkitTask traceReflectingRayDelayed(
|
||||||
|
Plugin plugin,
|
||||||
|
Location start,
|
||||||
|
Vector direction,
|
||||||
|
double maxDistance,
|
||||||
|
int maxReflections,
|
||||||
|
double interval,
|
||||||
|
long tickDelay,
|
||||||
|
BiPredicate<Point, BlockFace> blockReflectCondition,
|
||||||
|
Function<RaySegment, Boolean> segmentAction) {
|
||||||
|
|
||||||
|
if (interval <= 0) throw new IllegalArgumentException("interval cannot be zero or negative!");
|
||||||
|
if (maxDistance <= 0) throw new IllegalArgumentException("maxDistance cannot be zero or negative!");
|
||||||
|
if (tickDelay < 0) throw new IllegalArgumentException("tickDelay cannot be negative!");
|
||||||
|
|
||||||
|
Vector normalizedDir = direction.clone().normalize();
|
||||||
|
|
||||||
|
return new BukkitRunnable() {
|
||||||
|
private Location currentStart = start.clone();
|
||||||
|
private Vector currentDir = normalizedDir.clone();
|
||||||
|
private double remainingDistance = maxDistance;
|
||||||
|
private int reflections = 0;
|
||||||
|
private double currentSegmentDistance = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (remainingDistance <= 0 || reflections > maxReflections) {
|
||||||
|
this.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Point point = CustomDisplayRaytracer.blocksInFrontOf(
|
||||||
|
currentStart, currentDir, currentSegmentDistance, false);
|
||||||
|
|
||||||
|
// Check for block reflection
|
||||||
|
BlockFace hitFace = getHitBlockFace(point);
|
||||||
|
if (hitFace != null && blockReflectCondition.test(point, hitFace)) {
|
||||||
|
// Create segment
|
||||||
|
RaySegment segment = new RaySegment(
|
||||||
|
currentStart.clone(),
|
||||||
|
point.getLoc().clone(),
|
||||||
|
currentDir.clone(),
|
||||||
|
currentSegmentDistance);
|
||||||
|
|
||||||
|
// Execute action (if returns false, stop the ray)
|
||||||
|
if (!segmentAction.apply(segment)) {
|
||||||
|
this.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update for next segment
|
||||||
|
currentStart = point.getLoc().clone();
|
||||||
|
currentDir = calculateBlockReflection(currentDir, hitFace);
|
||||||
|
remainingDistance -= currentSegmentDistance;
|
||||||
|
reflections++;
|
||||||
|
currentSegmentDistance = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment distance for this step
|
||||||
|
currentSegmentDistance += interval;
|
||||||
|
|
||||||
|
// Check if we've reached end of current segment without reflection
|
||||||
|
if (currentSegmentDistance > remainingDistance) {
|
||||||
|
Point endPoint = CustomDisplayRaytracer.blocksInFrontOf(
|
||||||
|
currentStart, currentDir, remainingDistance, true);
|
||||||
|
|
||||||
|
RaySegment segment = new RaySegment(
|
||||||
|
currentStart.clone(),
|
||||||
|
endPoint.getLoc().clone(),
|
||||||
|
currentDir.clone(),
|
||||||
|
remainingDistance);
|
||||||
|
|
||||||
|
segmentAction.apply(segment);
|
||||||
|
this.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.runTaskTimer(plugin, 0L, tickDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a delayed tracing of a reflecting ray with both block and entity reflections
|
||||||
|
*/
|
||||||
|
public static BukkitTask traceReflectingRayDelayed(
|
||||||
|
Plugin plugin,
|
||||||
|
Location start,
|
||||||
|
Vector direction,
|
||||||
|
double maxDistance,
|
||||||
|
int maxReflections,
|
||||||
|
double interval,
|
||||||
|
long tickDelay,
|
||||||
|
BiPredicate<Point, BlockFace> blockReflectCondition,
|
||||||
|
BiPredicate<Point, Entity> entityReflectCondition,
|
||||||
|
Function<RaySegment, Boolean> segmentAction) {
|
||||||
|
|
||||||
|
if (interval <= 0) throw new IllegalArgumentException("interval cannot be zero or negative!");
|
||||||
|
if (maxDistance <= 0) throw new IllegalArgumentException("maxDistance cannot be zero or negative!");
|
||||||
|
if (tickDelay < 0) throw new IllegalArgumentException("tickDelay cannot be negative!");
|
||||||
|
|
||||||
|
Vector normalizedDir = direction.clone().normalize();
|
||||||
|
|
||||||
|
return new BukkitRunnable() {
|
||||||
|
private Location currentStart = start.clone();
|
||||||
|
private Vector currentDir = normalizedDir.clone();
|
||||||
|
private double remainingDistance = maxDistance;
|
||||||
|
private int reflections = 0;
|
||||||
|
private double currentSegmentDistance = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (remainingDistance <= 0 || reflections > maxReflections) {
|
||||||
|
this.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Point point = CustomDisplayRaytracer.blocksInFrontOf(
|
||||||
|
currentStart, currentDir, currentSegmentDistance, false);
|
||||||
|
|
||||||
|
// Check for entity reflection
|
||||||
|
List<Entity> entities = point.getNearbyEntities(null, 5, true, 0.1, e -> true);
|
||||||
|
for (Entity entity : entities) {
|
||||||
|
if (entityReflectCondition.test(point, entity)) {
|
||||||
|
// Create segment
|
||||||
|
RaySegment segment = new RaySegment(
|
||||||
|
currentStart.clone(),
|
||||||
|
point.getLoc().clone(),
|
||||||
|
currentDir.clone(),
|
||||||
|
currentSegmentDistance);
|
||||||
|
|
||||||
|
// Execute action (if returns false, stop the ray)
|
||||||
|
if (!segmentAction.apply(segment)) {
|
||||||
|
this.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update for next segment - reverse direction for entity reflection
|
||||||
|
currentStart = point.getLoc().clone();
|
||||||
|
currentDir = currentDir.clone().multiply(-1);
|
||||||
|
remainingDistance -= currentSegmentDistance;
|
||||||
|
reflections++;
|
||||||
|
currentSegmentDistance = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for block reflection
|
||||||
|
BlockFace hitFace = getHitBlockFace(point);
|
||||||
|
if (hitFace != null && blockReflectCondition.test(point, hitFace)) {
|
||||||
|
// Create segment
|
||||||
|
RaySegment segment = new RaySegment(
|
||||||
|
currentStart.clone(),
|
||||||
|
point.getLoc().clone(),
|
||||||
|
currentDir.clone(),
|
||||||
|
currentSegmentDistance);
|
||||||
|
|
||||||
|
// Execute action (if returns false, stop the ray)
|
||||||
|
if (!segmentAction.apply(segment)) {
|
||||||
|
this.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update for next segment
|
||||||
|
currentStart = point.getLoc().clone();
|
||||||
|
currentDir = calculateBlockReflection(currentDir, hitFace);
|
||||||
|
remainingDistance -= currentSegmentDistance;
|
||||||
|
reflections++;
|
||||||
|
currentSegmentDistance = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment distance for this step
|
||||||
|
currentSegmentDistance += interval;
|
||||||
|
|
||||||
|
// Check if we've reached end of current segment without reflection
|
||||||
|
if (currentSegmentDistance > remainingDistance) {
|
||||||
|
Point endPoint = CustomDisplayRaytracer.blocksInFrontOf(
|
||||||
|
currentStart, currentDir, remainingDistance, true);
|
||||||
|
|
||||||
|
RaySegment segment = new RaySegment(
|
||||||
|
currentStart.clone(),
|
||||||
|
endPoint.getLoc().clone(),
|
||||||
|
currentDir.clone(),
|
||||||
|
remainingDistance);
|
||||||
|
|
||||||
|
segmentAction.apply(segment);
|
||||||
|
this.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.runTaskTimer(plugin, 0L, tickDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a segment of a reflected ray
|
||||||
|
*/
|
||||||
|
public static class RaySegment {
|
||||||
|
private final Location start;
|
||||||
|
private final Location end;
|
||||||
|
private final Vector direction;
|
||||||
|
private final double distance;
|
||||||
|
|
||||||
|
public RaySegment(Location start, Location end, Vector direction, double distance) {
|
||||||
|
this.start = start;
|
||||||
|
this.end = end;
|
||||||
|
this.direction = direction;
|
||||||
|
this.distance = distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Location getStart() {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Location getEnd() {
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector getDirection() {
|
||||||
|
return direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getDistance() {
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public World getWorld() {
|
||||||
|
return start.getWorld();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types of reflections that can occur
|
||||||
|
*/
|
||||||
|
public enum ReflectionType {
|
||||||
|
NONE,
|
||||||
|
BLOCK,
|
||||||
|
ENTITY
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of a ray trace including reflection information
|
||||||
|
*/
|
||||||
|
private static class RayResult {
|
||||||
|
private final Point endPoint;
|
||||||
|
private final Vector newDirection;
|
||||||
|
private final double distance;
|
||||||
|
private final ReflectionType reflectionType;
|
||||||
|
private final BlockFace hitFace;
|
||||||
|
private final Entity hitEntity;
|
||||||
|
|
||||||
|
public RayResult(Point endPoint, Vector newDirection, double distance,
|
||||||
|
ReflectionType reflectionType, BlockFace hitFace, Entity hitEntity) {
|
||||||
|
this.endPoint = endPoint;
|
||||||
|
this.newDirection = newDirection;
|
||||||
|
this.distance = distance;
|
||||||
|
this.reflectionType = reflectionType;
|
||||||
|
this.hitFace = hitFace;
|
||||||
|
this.hitEntity = hitEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point getEndPoint() {
|
||||||
|
return endPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector getNewDirection() {
|
||||||
|
return newDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getDistance() {
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReflectionType getReflectionType() {
|
||||||
|
return reflectionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockFace getHitFace() {
|
||||||
|
return hitFace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entity getHitEntity() {
|
||||||
|
return hitEntity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,247 @@
|
|||||||
|
package me.trouper.trimserver.utils.visual;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.server.Main;
|
||||||
|
import me.trouper.trimserver.utils.misc.Randomizer;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Color;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Particle;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class DisplayUtils implements Main {
|
||||||
|
|
||||||
|
|
||||||
|
public static void sphere(Location center, double radius, double verticalStep, double maxDistanceBetweenPoints, Consumer<Location> action) {
|
||||||
|
for (double yOffset = -radius; yOffset <= radius; yOffset += verticalStep) {
|
||||||
|
double horizontalRadius = Math.sqrt(radius * radius - yOffset * yOffset);
|
||||||
|
|
||||||
|
if (horizontalRadius < 0.01) {
|
||||||
|
Location point = center.clone().add(0, yOffset, 0);
|
||||||
|
action.accept(point);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
double circumference = 2 * Math.PI * horizontalRadius;
|
||||||
|
int points = Math.max(4, (int) (circumference / maxDistanceBetweenPoints));
|
||||||
|
double angleStep = 360.0 / points;
|
||||||
|
|
||||||
|
for (int i = 0; i < points; i++) {
|
||||||
|
double theta = i * angleStep;
|
||||||
|
double x = Math.cos(Math.toRadians(theta)) * horizontalRadius;
|
||||||
|
double z = Math.sin(Math.toRadians(theta)) * horizontalRadius;
|
||||||
|
Location point = center.clone().add(x, yOffset, z);
|
||||||
|
action.accept(point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sphereWave(Location center, double maxRadius, double radialStep, double verticalStep, double maxDistanceBetweenPoints, Consumer<Location> action) {
|
||||||
|
AtomicReference<Double> currentRadius = new AtomicReference<>(radialStep);
|
||||||
|
|
||||||
|
Bukkit.getScheduler().scheduleSyncRepeatingTask(main.getPlugin(), () -> {
|
||||||
|
double r = currentRadius.get();
|
||||||
|
if (r > maxRadius) return;
|
||||||
|
|
||||||
|
sphere(center, r, verticalStep, maxDistanceBetweenPoints, action);
|
||||||
|
currentRadius.set(r + radialStep);
|
||||||
|
}, 0L, 1L);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static final Function<Particle, Consumer<Location>> PARTICLE_FACTORY = particle -> l -> l.getWorld().spawnParticle(particle, l, 1, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
public static final BiFunction<Color, Float, Consumer<Location>> DUST_PARTICLE_FACTORY = (color, thickness) -> {
|
||||||
|
Particle.DustOptions dust = new Particle.DustOptions(color, thickness);
|
||||||
|
return l -> l.getWorld().spawnParticle(Particle.DUST, l, 1, 0, 0, 0, 0, dust);
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final Function<Boolean, Consumer<Location>> FLAME_PARTICLE_FACTORY = soul -> {
|
||||||
|
Particle flame = soul ? Particle.SOUL_FIRE_FLAME : Particle.FLAME;
|
||||||
|
return l -> l.getWorld().spawnParticle(flame, l, 1, 0, 0, 0, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
public static void ring(Location loc, double radius, Color color, float thickness) {
|
||||||
|
ring(loc, radius, DUST_PARTICLE_FACTORY.apply(color, thickness));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ring(Location loc, double radius, Consumer<Location> action) {
|
||||||
|
for (int theta = 0; theta < 360; theta += 10) {
|
||||||
|
double x = Math.cos(Math.toRadians(theta)) * radius;
|
||||||
|
double z = Math.sin(Math.toRadians(theta)) * radius;
|
||||||
|
Location newLoc = loc.clone().add(x, 0, z);
|
||||||
|
action.accept(newLoc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ring(Location loc, double radius, double maxDistanceBetweenPoints, Consumer<Location> action) {
|
||||||
|
arc(loc, radius, 0, 360, maxDistanceBetweenPoints, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void wave(Location loc, double radius, Color color, float thickness, double gap) {
|
||||||
|
wave(loc, radius, DUST_PARTICLE_FACTORY.apply(color, thickness), gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void wave(Location loc, double radius, Consumer<Location> action, double gap) {
|
||||||
|
AtomicReference<Double> i = new AtomicReference<>(gap);
|
||||||
|
Bukkit.getScheduler().scheduleSyncRepeatingTask(main.getPlugin(), () -> {
|
||||||
|
if (i.get() >= radius) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ring(loc, i.get(), action);
|
||||||
|
i.set(i.get() + gap);
|
||||||
|
}, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void wave(Location loc, double radius, double radialGap, double maxDistanceBetweenPoints, Consumer<Location> action) {
|
||||||
|
AtomicReference<Double> r = new AtomicReference<>(radialGap);
|
||||||
|
Bukkit.getScheduler().scheduleSyncRepeatingTask(main.getPlugin(), () -> {
|
||||||
|
if (r.get() > radius) return;
|
||||||
|
ring(loc, r.get(), maxDistanceBetweenPoints, action);
|
||||||
|
r.set(r.get() + radialGap);
|
||||||
|
}, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void disc(Location loc, double radius, Consumer<Location> action, double gap) {
|
||||||
|
for (double i = gap; i < radius; i += gap) {
|
||||||
|
ring(loc, i, action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void disc(Location loc, double radius, double radialGap, double maxDistanceBetweenPoints, Consumer<Location> action) {
|
||||||
|
for (double r = radialGap; r <= radius; r += radialGap) {
|
||||||
|
ring(loc, r, maxDistanceBetweenPoints, action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void helix(Location loc, double radius, Consumer<Location> action, double gap, int height) {
|
||||||
|
int theta = 0;
|
||||||
|
for (double y = 0; y <= height; y += gap) {
|
||||||
|
double x = Math.cos(Math.toRadians(theta)) * radius;
|
||||||
|
double z = Math.sin(Math.toRadians(theta)) * radius;
|
||||||
|
|
||||||
|
Location newLoc = loc.clone().add(x, y, z);
|
||||||
|
action.accept(newLoc);
|
||||||
|
theta += 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void vortex(Location loc, double radius, Consumer<Location> action, double gapH, double gapV, int height) {
|
||||||
|
double r = radius;
|
||||||
|
int theta = 0;
|
||||||
|
for (double y = 0; y <= height; y += gapV) {
|
||||||
|
double x = Math.cos(Math.toRadians(theta)) * r;
|
||||||
|
double z = Math.sin(Math.toRadians(theta)) * r;
|
||||||
|
|
||||||
|
Location newLoc = loc.clone().add(x, y, z);
|
||||||
|
action.accept(newLoc);
|
||||||
|
r += gapH;
|
||||||
|
theta += 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void beam(Location loc, Consumer<Location> action, double gap, int height) {
|
||||||
|
for (double y = 0; y <= height; y += gap) {
|
||||||
|
Location newLoc = loc.clone().add(0, y, 0);
|
||||||
|
action.accept(newLoc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void arc(Location loc, double radius, int angleFrom, int angleTo, Consumer<Location> action) {
|
||||||
|
for (int theta = angleFrom; theta < angleTo; theta += 10) {
|
||||||
|
double x = Math.cos(Math.toRadians(theta)) * radius;
|
||||||
|
double z = Math.sin(Math.toRadians(theta)) * radius;
|
||||||
|
Location newLoc = loc.clone().add(x, 0, z);
|
||||||
|
action.accept(newLoc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void arc(Location loc, double radius, int angleFrom, int angleTo, double maxDistanceBetweenPoints, Consumer<Location> action) {
|
||||||
|
int angleSpan = angleTo - angleFrom;
|
||||||
|
if (angleSpan <= 0) return;
|
||||||
|
|
||||||
|
int points = Math.max(2, (int) ((2 * Math.PI * radius * (angleSpan / 360.0)) / maxDistanceBetweenPoints));
|
||||||
|
double angleStep = (double) angleSpan / points;
|
||||||
|
|
||||||
|
for (int i = 0; i <= points; i++) {
|
||||||
|
double theta = angleFrom + (i * angleStep);
|
||||||
|
double x = Math.cos(Math.toRadians(theta)) * radius;
|
||||||
|
double z = Math.sin(Math.toRadians(theta)) * radius;
|
||||||
|
Location point = loc.clone().add(x, 0, z);
|
||||||
|
action.accept(point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void fan(Location loc, double radius, int angleFrom, int angleTo, Consumer<Location> action, double gap) {
|
||||||
|
for (double i = gap; i < radius; i += gap) {
|
||||||
|
arc(loc, i, angleFrom, angleTo, action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fan(Location loc, double radius, int angleFrom, int angleTo, double maxDistanceBetweenPoints, Consumer<Location> action, double radialGap) {
|
||||||
|
for (double r = radialGap; r < radius; r += radialGap) {
|
||||||
|
arc(loc, r, angleFrom, angleTo, maxDistanceBetweenPoints, action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void fanWave(Location loc, double radius, int sections, Consumer<Location> action, double gap) {
|
||||||
|
double arcLength = 360.0 / sections;
|
||||||
|
AtomicReference<Double> i = new AtomicReference<>(0.0);
|
||||||
|
Bukkit.getScheduler().scheduleSyncRepeatingTask(main.getPlugin(), () -> {
|
||||||
|
if (i.get() >= 360) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
double start = i.get();
|
||||||
|
fan(loc, radius, (int)start, (int)(start + arcLength), action, gap);
|
||||||
|
i.set(i.get() + arcLength);
|
||||||
|
}, 0, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fanWaveRandom(Location loc, double radius, int sections, Consumer<Location> action, double gap) {
|
||||||
|
double arcLength = 360.0 / sections;
|
||||||
|
List<Double> ints = new ArrayList<>();
|
||||||
|
for (double start = 0; start < 360; start += arcLength) {
|
||||||
|
ints.add(start);
|
||||||
|
}
|
||||||
|
|
||||||
|
AtomicInteger i = new AtomicInteger(0);
|
||||||
|
Randomizer random = new Randomizer();
|
||||||
|
Bukkit.getScheduler().scheduleSyncRepeatingTask(main.getPlugin(), () -> {
|
||||||
|
if (i.get() >= sections) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
double start = random.getRandomElement(ints);
|
||||||
|
ints.remove(start);
|
||||||
|
fan(loc, radius, (int)start, (int)(start + arcLength), action, gap);
|
||||||
|
i.getAndIncrement();
|
||||||
|
}, 0, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void waveFan(Location loc, double radius, int angleFrom, int angleTo, double maxDistanceBetweenPoints, Consumer<Location> action, double radialGap) {
|
||||||
|
AtomicReference<Double> r = new AtomicReference<>(radialGap);
|
||||||
|
Bukkit.getScheduler().scheduleSyncRepeatingTask(main.getPlugin(), () -> {
|
||||||
|
if (r.get() >= radius) return;
|
||||||
|
arc(loc, r.get(), angleFrom, angleTo, maxDistanceBetweenPoints, action);
|
||||||
|
r.set(r.get() + radialGap);
|
||||||
|
}, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void waveFan(Location loc, double radius, Vector direction, int angle, double maxDistanceBetweenPoints, Consumer<Location> action, double radialGap) {
|
||||||
|
double baseAngle = Math.toDegrees(Math.atan2(direction.getZ(), direction.getX()));
|
||||||
|
int angleFrom = (int) (baseAngle - angle / 2.0);
|
||||||
|
int angleTo = (int) (baseAngle + angle / 2.0);
|
||||||
|
waveFan(loc, radius, angleFrom, angleTo, maxDistanceBetweenPoints, action, radialGap);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
121
src/main/java/me/trouper/trimserver/utils/visual/Point.java
Normal file
121
src/main/java/me/trouper/trimserver/utils/visual/Point.java
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package me.trouper.trimserver.utils.visual;
|
||||||
|
|
||||||
|
import me.trouper.trimserver.utils.Text;
|
||||||
|
import me.trouper.trimserver.utils.Verbose;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.BlockFace;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
public class Point {
|
||||||
|
private final Location loc;
|
||||||
|
private final World world;
|
||||||
|
private final Block block;
|
||||||
|
private final boolean missed;
|
||||||
|
private final double traveledDist;
|
||||||
|
|
||||||
|
public Point(Location loc, double traveledDist, boolean missed) {
|
||||||
|
this.loc = loc;
|
||||||
|
this.world = loc.getWorld();
|
||||||
|
this.block = loc.getBlock();
|
||||||
|
this.missed = missed;
|
||||||
|
this.traveledDist = traveledDist;
|
||||||
|
|
||||||
|
if (world == null) {
|
||||||
|
throw new IllegalArgumentException("point world cannot be null!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Entity> getNearbyEntities(Entity exclude, int range, boolean requireContact, double expansionX, double expansionY, double expansionZ, Predicate<Entity> filter) {
|
||||||
|
return new ArrayList<>(world.getNearbyEntities(loc, range, range, range, e -> {
|
||||||
|
if (requireContact && !e.getBoundingBox().expand(expansionX, expansionY, expansionZ).contains(loc.toVector())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return filter.test(e) && e != exclude;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Entity> getNearbyEntities(Entity exclude, int range, boolean requireContact, double expansion, Predicate<Entity> filter) {
|
||||||
|
return getNearbyEntities(exclude, range, requireContact, expansion, expansion, expansion, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Entity> getNearbyEntities(Entity exclude, int range, boolean requireContact, Predicate<Entity> filter) {
|
||||||
|
return getNearbyEntities(exclude, range, requireContact, 0, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Entity> getNearbyEntities(Entity exclude, int range, Predicate<Entity> filter) {
|
||||||
|
return getNearbyEntities(exclude, range, false, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockFace getBlockFace(Vector vector) {
|
||||||
|
if (block == null || block.isPassable()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
double x = vector.getX() - block.getX();
|
||||||
|
double y = vector.getY() - block.getY();
|
||||||
|
double z = vector.getZ() - block.getZ();
|
||||||
|
|
||||||
|
double min = 0;
|
||||||
|
BlockFace face = null;
|
||||||
|
|
||||||
|
if (x < min) {
|
||||||
|
min = x;
|
||||||
|
face = BlockFace.WEST;
|
||||||
|
}
|
||||||
|
if (1 - x < min) {
|
||||||
|
min = 1 - x;
|
||||||
|
face = BlockFace.EAST;
|
||||||
|
}
|
||||||
|
if (y < min) {
|
||||||
|
min = y;
|
||||||
|
face = BlockFace.DOWN;
|
||||||
|
}
|
||||||
|
if (1 - y < min) {
|
||||||
|
min = 1 - y;
|
||||||
|
face = BlockFace.UP;
|
||||||
|
}
|
||||||
|
if (z < min) {
|
||||||
|
min = z;
|
||||||
|
face = BlockFace.NORTH;
|
||||||
|
}
|
||||||
|
if (1 - z < min) {
|
||||||
|
face = BlockFace.SOUTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
Verbose.send("Block face was %s. X: %s, Y: %s, Z: %s.", Text.formatEnum(face),x,y,z);
|
||||||
|
|
||||||
|
return face;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public double getTraveledDist() {
|
||||||
|
return traveledDist;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean wasMissed() {
|
||||||
|
return missed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Block getBlock() {
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Location getLoc() {
|
||||||
|
return loc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public World getWorld() {
|
||||||
|
return world;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double distance(Location other) {
|
||||||
|
return other.distance(loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/main/resources/plugin.yml
Normal file
20
src/main/resources/plugin.yml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
name: TrimServer
|
||||||
|
version: '1.0-SNAPSHOT'
|
||||||
|
main: me.trouper.trimserver.TrimServer
|
||||||
|
api-version: '1.21'
|
||||||
|
prefix: TrimServer
|
||||||
|
load: STARTUP
|
||||||
|
authors: [ obvWolf ]
|
||||||
|
description: Armor trims give you abilities
|
||||||
|
commands:
|
||||||
|
trims:
|
||||||
|
permission: trims.admin
|
||||||
|
description: Command for managing trims.
|
||||||
|
info:
|
||||||
|
description: Provides info on armor trim abilities.
|
||||||
|
usage: /info <trim>
|
||||||
|
trust:
|
||||||
|
description: Make your friends immune to your abilities.
|
||||||
|
usage: "<add|remove|list> <player>"
|
||||||
|
aliases:
|
||||||
|
- t
|
||||||
Reference in New Issue
Block a user