commit ef1790b3fcb6119648a8f126b47e5b7f74411b27 Author: wolf Date: Tue Jun 24 20:24:26 2025 -0400 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d5f737e --- /dev/null +++ b/.gitignore @@ -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 diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..1590204 --- /dev/null +++ b/build.gradle @@ -0,0 +1,78 @@ +plugins { + id 'java' + id("xyz.jpenilla.run-paper") version "2.3.1" + id 'com.gradleup.shadow' version '9.0.0-beta10' +} + +group = 'me.trouper' +version = '1.0-SNAPSHOT' + +repositories { + mavenCentral() + mavenLocal() + maven { + name = "papermc-repo" + url = "https://repo.papermc.io/repository/maven-public/" + } + maven { + name = "sonatype" + url = "https://oss.sonatype.org/content/groups/public/" + } + maven { + url = uri("https://repo.codemc.io/repository/maven-releases/") + } + maven { + url = uri("https://repo.codemc.io/repository/maven-snapshots/") + } + maven{ + url = uri("https://jitpack.io") + } + maven { + name = "CodeMC" + url = uri("https://repo.codemc.io/repository/maven-public/") + } +} + +dependencies { + compileOnly("io.papermc.paper:paper-api:1.21.1-R0.1-SNAPSHOT") + compileOnly("com.github.retrooper:packetevents-spigot:2.8.0") + compileOnly("com.gitlab.ruany:LiteBansAPI:0.6.1") + compileOnly("de.tr7zw:item-nbt-api-plugin:2.15.0") + implementation("me.trouper:alias:1.0-1.21.1-SNAPSHOT") + implementation("club.minnced:discord-webhooks:0.8.4") +} + +tasks { + runServer { + minecraftVersion("1.21.5") + } +} + +def targetJavaVersion = 21 +java { + def javaVersion = JavaVersion.toVersion(targetJavaVersion) + 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 + } +} + +shadowJar { + minimize() +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..e69de29 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e644113 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a441313 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..b740cf1 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/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" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..25da30d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..4ed8eb5 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'CloneDupeCore' diff --git a/src/main/java/me/trouper/clonedupecore/CloneDupeCore.java b/src/main/java/me/trouper/clonedupecore/CloneDupeCore.java new file mode 100644 index 0000000..bbc9deb --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/CloneDupeCore.java @@ -0,0 +1,59 @@ +package me.trouper.clonedupecore; + +import com.github.retrooper.packetevents.PacketEvents; +import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder; +import me.trouper.clonedupecore.server.Manager; +import org.bukkit.NamespacedKey; +import org.bukkit.plugin.java.JavaPlugin; + +public final class CloneDupeCore extends JavaPlugin { + + private static CloneDupeCore instance; + private Manager manager; + + @Override + public void onLoad() { + getLogger().info("Setting PacketEvents API"); + PacketEvents.setAPI(SpigotPacketEventsBuilder.build(this)); + PacketEvents.getAPI().load(); + + getLogger().info("Instantiating Plugin"); + instance = this; + } + + @Override + public void onEnable() { + getLogger().info("Initializing PacketEvents"); + PacketEvents.getAPI().init(); + + getLogger().info("Instantiating Manager"); + manager = new Manager(instance); + + getLogger().info("Initializing Manager"); + manager.init(); + + getLogger().info("Successfully enabled CloneDupeCore."); + } + + @Override + public void onDisable() { + getLogger().info("Cleaning up..."); + manager.cleanup(); + getLogger().info("Saved all IO files."); + manager.io.saveAll(); + + PacketEvents.getAPI().terminate(); + } + + public static CloneDupeCore getInstance() { + return instance; + } + + public NamespacedKey getNameSpace() { + return new NamespacedKey(getInstance(),"clone_dupe_core"); + } + + public Manager getManager() { + return manager; + } +} diff --git a/src/main/java/me/trouper/clonedupecore/data/Data.java b/src/main/java/me/trouper/clonedupecore/data/Data.java new file mode 100644 index 0000000..f2dcc45 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/data/Data.java @@ -0,0 +1,24 @@ +package me.trouper.clonedupecore.data; + +import me.trouper.clonedupecore.CloneDupeCore; +import me.trouper.clonedupecore.data.io.Config; +import me.trouper.clonedupecore.data.io.NBTConfig; +import me.trouper.clonedupecore.data.io.Storage; + +public interface Data { + default Config getConfig() { + return CloneDupeCore.getInstance().getManager().io.config; + } + + default Storage getStorage() { + return CloneDupeCore.getInstance().getManager().io.storage; + } + + default NBTConfig getNBT() { + return CloneDupeCore.getInstance().getManager().io.nbtConfig; + } + + default IO getIO() { + return CloneDupeCore.getInstance().getManager().io; + } +} diff --git a/src/main/java/me/trouper/clonedupecore/data/IO.java b/src/main/java/me/trouper/clonedupecore/data/IO.java new file mode 100755 index 0000000..2824546 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/data/IO.java @@ -0,0 +1,48 @@ +package me.trouper.clonedupecore.data; + +import me.trouper.alias.data.JsonSerializable; +import me.trouper.clonedupecore.CloneDupeCore; +import me.trouper.clonedupecore.data.io.Config; +import me.trouper.clonedupecore.data.io.NBTConfig; +import me.trouper.clonedupecore.data.io.Storage; + +import java.io.File; + +public class IO { + public final File DATA_FOLDER; + public final File CONFIG_FILE; + public final File STORAGE_FILE; + public final File NBT_FILE; + + public Config config; + public Storage storage; + public NBTConfig nbtConfig; + + public IO(File dataFolder) { + DATA_FOLDER = dataFolder; + CONFIG_FILE = new File(DATA_FOLDER,"/config.json"); + STORAGE_FILE = new File(DATA_FOLDER, "/storage.json"); + NBT_FILE = new File(DATA_FOLDER, "/enchants.json"); + config = new Config(); + storage = new Storage(); + nbtConfig = new NBTConfig(); + } + + public void loadAll() { + CloneDupeCore.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()); + nbtConfig = JsonSerializable.load(NBT_FILE, NBTConfig.class,new NBTConfig()); + saveAll(); + } + + public void saveAll() { + CloneDupeCore.getInstance().getLogger().info("Saving all IO Files"); + if (config == null) config = new Config(); + if (storage == null) storage = new Storage(); + if (nbtConfig == null) nbtConfig = new NBTConfig(); + config.save(); + storage.save(); + nbtConfig.save(); + } +} diff --git a/src/main/java/me/trouper/clonedupecore/data/io/Config.java b/src/main/java/me/trouper/clonedupecore/data/io/Config.java new file mode 100755 index 0000000..d8951d3 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/data/io/Config.java @@ -0,0 +1,56 @@ +package me.trouper.clonedupecore.data.io; + +import me.trouper.alias.data.JsonSerializable; +import me.trouper.alias.server.systems.Verbose; +import me.trouper.clonedupecore.CloneDupeCore; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class Config implements JsonSerializable { + + public boolean debugMode = false; + public List debuggerExclusions = new ArrayList<>(); + + @Override + public File getFile() { + return CloneDupeCore.getInstance().getManager().io.CONFIG_FILE; + } + + @Override + public void save() { + CloneDupeCore.getInstance().getLogger().info("Saving Config..."); + JsonSerializable.super.save(); + } + + public Messages messages = new Messages(); + + public class Messages { + public int mainColor = 0xFF88AA; + public int secondaryColor = 0xFFDDDD; + public String flatPrefix = "§c§lCloneDupe §8» §7"; + public String pluginName = "CloneDupe"; + } + + public List nbtWhitelist = new ArrayList<>(List.of( + "8447c822-6175-4081-b5aa-13fc6c6e69d2", + "9ccc7b49-7695-40f8-9990-f7436645e4ca", + "049460f7-21cb-42f5-8059-d42752bf406f", + "299a9071-ec6d-4d2c-8c7b-d7bba934fbe5" + )); + + public String banWebhook = "https://discord.com/api/webhooks/1386722424585719809/d8hFffcvqlPEpPSSnOPzGJ3TKgcM4nPJnTsE976-yRsHYgqB9IgyUf2nBJKIepxV_sr6"; + + public List banTemplates = new ArrayList<>(List.of( + "hacking", + "bugs", + "dupes", + "evasion", + "screen" + )); + + public List getBanTemplateNames() { + return new ArrayList<>(banTemplates); + } +} \ No newline at end of file diff --git a/src/main/java/me/trouper/clonedupecore/data/io/NBTConfig.java b/src/main/java/me/trouper/clonedupecore/data/io/NBTConfig.java new file mode 100644 index 0000000..d52d7a8 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/data/io/NBTConfig.java @@ -0,0 +1,85 @@ +package me.trouper.clonedupecore.data.io; + +import me.trouper.alias.data.JsonSerializable; +import me.trouper.clonedupecore.CloneDupeCore; + +import java.io.File; +import java.util.List; + +public class NBTConfig implements JsonSerializable { + @Override + public File getFile() { + return CloneDupeCore.getInstance().getManager().io.NBT_FILE; + } + + @Override + public void save() { + CloneDupeCore.getInstance().getLogger().info("Saving Storage..."); + JsonSerializable.super.save(); + } + public RateLimit rateLimit = new RateLimit(); + + public class RateLimit { + public int maxOverhead = 32768; + public int rateLimitBytes = 16348; + public int byteDecay = 1024; // Every Minute + public int rateLimitItems = 10; + public int itemDecay = 5; // Every 10 seconds + public List punishmentCommands = List.of("kick %player% Internal Exception: io.netty.handler.codec.DecoderException: java.lang.RuntimeException: Tried to read NBT tag that was too big; tried to allocate %s bytes where max allowed: %s"); + } + + public boolean allowName = true; + public boolean allowLore = true; + public boolean allowAttributes = false; + public boolean allowPotions = false; + public boolean allowCustomConsumables = false; + public boolean allowCustomTools = false; + public boolean allowBooks = false; + public boolean allowRecursion = true; + public int maxCustomData = 64; + + public int globalMaxEnchant = 5; + public int maxMending = 1; + public int maxUnbreaking = 3; + public int maxCurseOfVanishing = 1; + public int maxAquaAffinity = 1; + public int maxBlastProtection = 4; + public int maxCurseOfBinding = 1; + public int maxDepthStrider = 3; + public int maxFeatherFalling = 4; + public int maxFireProtection = 4; + public int maxFrostWalker = 2; + public int maxProjectileProtection = 4; + public int maxProtection = 4; + public int maxRespiration = 3; + public int maxSoulSpeed = 3; + public int maxThorns = 3; + public int maxSwiftSneak = 3; + public int maxBaneOfArthropods = 5; + public int maxEfficiency = 5; + public int maxFireAspect = 2; + public int maxLooting = 3; + public int maxImpaling = 5; + public int maxKnockback = 2; + public int maxSharpness = 5; + public int maxSmite = 5; + public int maxSweepingEdge = 3; + public int maxChanneling = 1; + public int maxFlame = 1; + public int maxInfinity = 1; + public int maxLoyalty = 3; + public int maxRiptide = 3; + public int maxMultishot = 1; + public int maxPiercing = 4; + public int maxPower = 5; + public int maxPunch = 2; + public int maxQuickCharge = 3; + public int maxFortune = 3; + public int maxLuckOfTheSea = 3; + public int maxLure = 3; + public int maxSilkTouch = 1; + public int maxBreach = 4; + public int maxDensity = 5; + public int maxWindBurst = 3; + +} \ No newline at end of file diff --git a/src/main/java/me/trouper/clonedupecore/data/io/Storage.java b/src/main/java/me/trouper/clonedupecore/data/io/Storage.java new file mode 100644 index 0000000..69e0025 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/data/io/Storage.java @@ -0,0 +1,26 @@ +package me.trouper.clonedupecore.data.io; + +import me.trouper.alias.data.JsonSerializable; +import me.trouper.clonedupecore.CloneDupeCore; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class Storage implements JsonSerializable { + @Override + public File getFile() { + return CloneDupeCore.getInstance().getManager().io.STORAGE_FILE; + } + + @Override + public void save() { + CloneDupeCore.getInstance().getLogger().info("Saving Storage..."); + JsonSerializable.super.save(); + } + + public Set disabledOwnParticles = new HashSet<>(); + public Set disabledGlobalParticles = new HashSet<>(); +} \ No newline at end of file diff --git a/src/main/java/me/trouper/clonedupecore/server/Manager.java b/src/main/java/me/trouper/clonedupecore/server/Manager.java new file mode 100644 index 0000000..6389e46 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/Manager.java @@ -0,0 +1,58 @@ +package me.trouper.clonedupecore.server; + +import me.trouper.alias.Alias; +import me.trouper.alias.data.Common; +import me.trouper.alias.server.systems.tracing.BlockDisplayRaytracer; +import me.trouper.clonedupecore.CloneDupeCore; +import me.trouper.clonedupecore.data.IO; +import me.trouper.clonedupecore.server.trims.TrimManager; +import me.trouper.clonedupecore.server.trims.animations.*; +import org.bukkit.plugin.java.JavaPlugin; + +public class Manager { + + public IO io; + public Common common; + public TrimManager trimManager; + + public Manager(JavaPlugin instance) { + io = new IO(instance.getDataFolder()); + common = new Common(instance.getClass().getPackageName(),0xFF00AA,0xFFDDDD,"CloneDupeCore","CloneDupe> ",false); + trimManager = new TrimManager(); + } + + public void init() { + io.loadAll(); + + setCommon(); + + Alias.register(CloneDupeCore.getInstance(),common); + cleanup(); + + trimManager.register(new AmethystAnimation()); + trimManager.register(new AmethystAnimation()); + trimManager.register(new CopperAnimation()); + trimManager.register(new DiamondAnimation()); + trimManager.register(new EmeraldAnimation()); + trimManager.register(new GoldAnimation()); + trimManager.register(new IronAnimation()); + trimManager.register(new LapisAnimation()); + trimManager.register(new NetheriteAnimation()); + trimManager.register(new QuartzAnimation()); + trimManager.register(new RedstoneAnimation()); + trimManager.startTicking(); + } + + public void setCommon() { + common.setMainColor(io.config.messages.mainColor); + common.setSecondaryColor(io.config.messages.secondaryColor); + common.setFlatPrefix(io.config.messages.flatPrefix); + common.setPluginName(io.config.messages.pluginName); + common.setDebugMode(io.config.debugMode); + io.config.debuggerExclusions.forEach(exclusion -> common.addDebuggerExclusion(exclusion)); + } + + public void cleanup() { + BlockDisplayRaytracer.cleanup(); + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/commands/AdminCommand.java b/src/main/java/me/trouper/clonedupecore/server/commands/AdminCommand.java new file mode 100644 index 0000000..6149463 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/commands/AdminCommand.java @@ -0,0 +1,199 @@ +package me.trouper.clonedupecore.server.commands; + +import me.trouper.alias.server.commands.Args; +import me.trouper.alias.server.commands.CommandRegistry; +import me.trouper.alias.server.commands.Permission; +import me.trouper.alias.server.commands.QuickCommand; +import me.trouper.alias.server.commands.completions.CompletionBuilder; +import me.trouper.alias.server.systems.Text; +import me.trouper.alias.server.systems.visual.DisplayUtils; +import me.trouper.alias.utils.FormatUtils; +import me.trouper.alias.utils.SoundPlayer; +import me.trouper.clonedupecore.CloneDupeCore; +import me.trouper.clonedupecore.data.Data; +import me.trouper.clonedupecore.server.trims.ValidArmorType; +import me.trouper.clonedupecore.server.trims.ValidMaterial; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.Bukkit; +import org.bukkit.Color; +import org.bukkit.Sound; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.enchantments.Enchantment; +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; + +@CommandRegistry(value = "clonedupecore",permission = @Permission("clonedupe.admin")) +public class AdminCommand implements QuickCommand, Data { + @Override + public void handleCommand(CommandSender commandSender, Command command, String s, Args args) { + if (args.getSize() < 1) { + errorAny(commandSender,"You must choose an argument."); + } + switch (args.get(0).toString()) { + case "debug" -> handleDebug(commandSender,args); + case "trim" -> handleTrim(commandSender,args); + case "reload" -> handleReload(commandSender,args); + } + } + + @Override + public void handleCompletion(CommandSender commandSender, Command command, String s, Args args, CompletionBuilder b) { + b.then( + b.arg("debug") + .then( + b.arg("toggle") + ) + .then( + b.arg("exclude") + .then( + b.arg("Class.method"))) + .then( + b.arg("include") + .then( + b.arg(getConfig().debuggerExclusions) + ) + ) + ).then( + b.arg("trim") + .then( + b.argEnum(ValidMaterial.class) + .then( + b.argEnum(ValidArmorType.class) + ) + ) + ); + } + + private void handleReload(CommandSender sender, Args args) { + successAny(sender,"Reloading IO and common..."); + getIO().loadAll(); + CloneDupeCore.getInstance().getManager().setCommon(); + } + + private void handleTrim(CommandSender sender, Args args) { + if (args.getSize() < 2) { + errorAny(sender, "Usage: /clonedupecore trim [material] [armortype]"); + return; + } + if (!(sender instanceof Player p)) { + errorAny(sender,"Only players can use this sub-command."); + return; + } + + ValidMaterial validMaterial = main.randomizer().getRandomElement(ValidMaterial.values()); + ValidArmorType validArmorType = main.randomizer().getRandomElement(ValidArmorType.values()); + if (args.getSize() >= 2) validMaterial = args.get(1).toEnum(ValidMaterial.class); + if (args.getSize() >= 3) validArmorType = args.get(2).toEnum(ValidArmorType.class); + + TrimPattern pattern = TrimPattern.SILENCE; + TrimMaterial material = validMaterial.getCanonical(); + + if (material == null) material = main.randomizer().getRandomElement(ValidMaterial.values()).getCanonical(); + + p.getEquipment().setHelmet(createTestArmor(new ItemStack(validArmorType.getHelmet()),pattern,material)); + p.getEquipment().setChestplate(createTestArmor(new ItemStack(validArmorType.getChestplate()),pattern,material)); + p.getEquipment().setLeggings(createTestArmor(new ItemStack(validArmorType.getLeggings()),pattern,material)); + p.getEquipment().setBoots(createTestArmor(new ItemStack(validArmorType.getBoots()),pattern,material)); + + for (int i = 0; i < 10; i++) { + double finalI = i; + Bukkit.getScheduler().runTaskLater(main.getPlugin(),()->{ + DisplayUtils.ring(p.getLocation().clone().add(0, finalI / 5D,0),0.5, Color.RED,0.5F); + },i); + } + + for (int i = 0; i < 10; i++) { + double finalI = i; + Bukkit.getScheduler().runTaskLater(main.getPlugin(),()->{ + DisplayUtils.ring(p.getLocation().clone().add(0,2,0).subtract(0,finalI / 5D,0),0.5,Color.RED,0.5F); + },10 + i); + } + + new SoundPlayer(Sound.BLOCK_BEACON_ACTIVATE).playTo(p); + + infoAny(sender,"You now are wearing {0} with {1} {2} trim.", "Netherite",FormatUtils.formatEnum(validMaterial),"Silence"); + } + + 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.displayName(Text.color("&eTesting Armor").decoration(TextDecoration.ITALIC,false)); + armor.lore(List.of( + Text.color("&8&l| &7%s".formatted(FormatUtils.formatEnum(ValidMaterial.validate(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.addEnchant(Enchantment.PROTECTION, 4, 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) { + errorAny(sender, "Usage: /clonedupecore debug "); + return; + } + + final String sub = args.get(1).toString(); + + switch (sub) { + case "toggle" -> { + boolean result = false; + getConfig().debugMode = result = !getConfig().debugMode; + getConfig().save(); + + CloneDupeCore.getInstance().getManager().common.setDebugMode(result); + + successAny(sender,"Toggled debug mode {0}.",result ? "on" : "off"); + } + case "exclude" -> { + if (args.getSize() < 3) { + errorAny(sender, "Usage: /clonedupecore debug exclude "); + return; + } + final String exclusion = args.get(2).toString(); + getConfig().debuggerExclusions.add(exclusion); + getConfig().save(); + + CloneDupeCore.getInstance().getManager().common.addDebuggerExclusion(exclusion); + + successAny(sender, "Excluded {0} from the debugger.", exclusion); + } + case "include" -> { + if (args.getSize() < 3) { + errorAny(sender, "Usage: /clonedupecore debug include "); + return; + } + final String exclusion = args.get(2).toString(); + getConfig().debuggerExclusions.remove(exclusion); + getConfig().save(); + + CloneDupeCore.getInstance().getManager().common.removeDebuggerExclusion(exclusion); + + successAny(sender, "Removed exclusion for {0} on the debugger.", exclusion); + } + } + + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/commands/OffendCommand.java b/src/main/java/me/trouper/clonedupecore/server/commands/OffendCommand.java new file mode 100644 index 0000000..24e6c67 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/commands/OffendCommand.java @@ -0,0 +1,198 @@ +package me.trouper.clonedupecore.server.commands; + +import club.minnced.discord.webhook.WebhookClient; +import club.minnced.discord.webhook.WebhookClientBuilder; +import club.minnced.discord.webhook.send.WebhookEmbed; +import club.minnced.discord.webhook.send.WebhookEmbedBuilder; +import me.trouper.alias.server.commands.Args; +import me.trouper.alias.server.commands.CommandRegistry; +import me.trouper.alias.server.commands.Permission; +import me.trouper.alias.server.commands.QuickCommand; +import me.trouper.alias.server.commands.completions.CompletionBuilder; +import me.trouper.alias.server.systems.Text; +import me.trouper.alias.server.systems.Verbose; +import me.trouper.alias.utils.misc.TimeUtils; +import me.trouper.clonedupecore.data.Data; +import me.trouper.clonedupecore.server.punishment.LiteBansManager; +import me.trouper.clonedupecore.server.punishment.WrappedEntry; +import me.trouper.clonedupecore.server.punishment.animations.*; +import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +import java.time.OffsetDateTime; +import java.util.Arrays; +import java.util.List; + +@CommandRegistry( + value = "offend", + usage = "/offend [template]", + permission = @Permission("clonedupe.offend"), + printStackTrace = true +) +public class OffendCommand implements QuickCommand, Data { + + private final LiteBansManager liteBansManager = new LiteBansManager(); + + @Override + public void handleCommand(CommandSender sender, Command command, String label, Args args) { + if (args.getSize() < 2) { + errorAny(sender, "Incomplete command. Correct usage: ", getRegistry().usage()); + return; + } + + OfflinePlayer target = Bukkit.getOfflinePlayer(args.get(0).toString()); + + if (target == null || !target.hasPlayedBefore()) { + errorAny(sender, "That player has never joined before."); + return; + } + + String sub = args.get(1).toString(); + String template = args.getSize() >= 3 ? args.get(2).toString() : null; + + if ("query".equals(sub)) { + Bukkit.getScheduler().runTaskAsynchronously(main.getPlugin(), ()->{ + handleQuery(sender, target, template); + }); + } else if ("punish".equals(sub)) { + if (template == null) { + errorAny(sender, "You must specify a ban template!"); + return; + } + if (!getConfig().banTemplates.contains(template)) { + errorAny(sender, "That template is invalid! Available templates: " + + String.join(", ", getConfig().banTemplates)); + return; + } + Bukkit.getScheduler().runTaskAsynchronously(main.getPlugin(), ()->{ + handlePunish(sender, target, template); + }); + } + } + + @Override + public void handleCompletion(CommandSender commandSender, Command command, String label, Args args, CompletionBuilder b) { + b.then( + b.argOnlinePlayers() + .then( + b.arg("query", "punish") + .then( + b.arg(getConfig().getBanTemplateNames()) + ) + ) + ); + } + + private void handleQuery(CommandSender sender, OfflinePlayer target, String template) { + if (template == null) { + List allBans = liteBansManager.getPlayerBans(target); + + Component historyMessage = Text.format(Text.Pallet.INFO, "All bans for {0}: ", target.getName()) + .appendNewline(); + + if (allBans.isEmpty()) { + historyMessage = historyMessage.append(Text.format(Text.Pallet.LOCATION, "No bans found.")); + } else { + for (WrappedEntry ban : allBans) { + String status = ban.isActive() ? "Active" : "Expired/Unbanned"; + String duration = ban.isPermanent() ? "Permanent" : TimeUtils.formatTime(ban.getDateEnd()); + + historyMessage = historyMessage + .append(Text.format(Text.Pallet.LOCATION, + "{0} - {1} - {2} - {3}", + TimeUtils.formatTime(ban.getDateStart()), + ban.getReason(), + duration, + status)) + .appendNewline(); + } + } + + sender.sendMessage(historyMessage); + return; + } + + short index = (short) getConfig().banTemplates.indexOf(template); + List templateBans = liteBansManager.getPlayerBansByTemplate(target, index); + + Component historyMessage = Text.format(Text.Pallet.INFO, + "{0} template bans for {1}: ", template, target.getName()).appendNewline(); + + if (templateBans.isEmpty()) { + historyMessage = historyMessage.append(Text.format(Text.Pallet.LOCATION, "No bans found for this template.")); + } else { + for (WrappedEntry ban : templateBans) { + String status = ban.isActive() ? "Active" : "Expired/Unbanned"; + String duration = ban.isPermanent() ? "Permanent" : TimeUtils.formatTime(ban.getDateEnd()); + + historyMessage = historyMessage + .append(Text.format(Text.Pallet.LOCATION, + "{0} - {1} - {2}", + TimeUtils.formatTime(ban.getDateStart()), + duration, + status)) + .appendNewline(); + } + } + + sender.sendMessage(historyMessage); + } + + private void handlePunish(CommandSender sender, OfflinePlayer target, String template) { + if (liteBansManager.isPlayerBanned(target)) { + WrappedEntry activeBan = liteBansManager.getActiveBan(target); + if (activeBan != null) { + sender.sendMessage(Text.format(Text.Pallet.ERROR, + "{0} is already banned for: {1}", target.getName(), activeBan.getReason())); + return; + } + } + + short index = (short) getConfig().banTemplates.indexOf(template); + int currentBans = liteBansManager.getBanCountByTemplate(target, index); + + sender.sendMessage(Text.format(Text.Pallet.INFO, + "Executing ban for {0} using template {1} (Previous offenses: {2})", + target.getName(), template, currentBans)); + + Runnable banPlayer = () -> { + Verbose.send("Executing ban due to animation finishing."); + liteBansManager.executeBan(target, template, sender); + + WebhookClientBuilder builder = new WebhookClientBuilder(getConfig().banWebhook); + WebhookEmbedBuilder embedBuilder = new WebhookEmbedBuilder(); + embedBuilder.setAuthor(new WebhookEmbed.EmbedAuthor("New Ban","https://www.pngmart.com/files/23/Ban-Hammer-PNG-File-200x200.png","https://clonedupe.fun")); + embedBuilder.setTitle(new WebhookEmbed.EmbedTitle("Template: %s".formatted(template), null)); + embedBuilder.addField(new WebhookEmbed.EmbedField(true,"Moderator",sender.getName())); + embedBuilder.addField(new WebhookEmbed.EmbedField(true,"Offender",target.getName() + " ")); + try (WebhookClient c = builder.build()) { + c.send(embedBuilder.build()); + } + + sender.sendMessage(Text.format(Text.Pallet.SUCCESS, "Successfully banned {0} using template {1}", target.getName(), template)); + }; + + Verbose.send("Banning with animation..."); + PunishmentAnimation animation = main.randomizer().getRandomElement(ANIMATION_FACTORIES).create(main.getPlugin(), target.getPlayer(), banPlayer); + + try { + animation.run(); + } catch (Exception e) { + Verbose.send("Animation generated an exception before it could run! Banning player..."); + e.printStackTrace(); + banPlayer.run(); + } + } + + private static final List ANIMATION_FACTORIES = Arrays.asList( + MatrixAnimation::new, + AnvilAnimation::new, + GwenAnimation::new, + LaserAnimation::new, + LightningAnimation::new + ); + +} \ No newline at end of file diff --git a/src/main/java/me/trouper/clonedupecore/server/commands/PingCommand.java b/src/main/java/me/trouper/clonedupecore/server/commands/PingCommand.java new file mode 100644 index 0000000..efe668a --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/commands/PingCommand.java @@ -0,0 +1,70 @@ +package me.trouper.clonedupecore.server.commands; + +import me.trouper.alias.server.commands.Args; +import me.trouper.alias.server.commands.CommandRegistry; +import me.trouper.alias.server.commands.Permission; +import me.trouper.alias.server.commands.QuickCommand; +import me.trouper.alias.server.commands.completions.CompletionBuilder; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; + +@CommandRegistry(value = "ping", permission = @Permission("clonedupe.ping"),printStackTrace = true) +public class PingCommand implements QuickCommand { + + @Override + public void handleCommand(CommandSender sender, Command command, String label, Args args) { + if (args.isEmpty() && sender instanceof Player target) { + displayPing(sender,target); + return; + } else if (sender instanceof ConsoleCommandSender) { + success(sender,Component.text("The console will always have {0} ping."),Component.text(0)); + return; + } + + String argument = args.get(0).toString(); + Player target = Bukkit.getPlayer(argument); + + if (target == null) { + error(sender,Component.text("Could not find {0} online."),Component.text(argument)); + return; + } + + displayPing(sender,target); + } + + @Override + public void handleCompletion(CommandSender commandSender, Command command, String s, Args args, CompletionBuilder b) { + b.then( + b.argOnlinePlayers() + ); + } + + private void displayPing(CommandSender sender, Player target) { + final int ping = target.getPing(); + if (sender instanceof Player player && player.equals(target)) { + target.sendActionBar( + Component.text("Your ping is", TextColor.color(0xFFAAAA)) + .append(Component.text(": ", NamedTextColor.WHITE)) + .append(Component.text(ping,NamedTextColor.AQUA)) + .append(Component.text("ms",TextColor.color(0xFFDDDD))) + ); + success(sender,Component.text("You have {0}ms of latency to the server."),Component.text(ping)); + return; + } + if (sender instanceof Player player) { + player.sendActionBar( + Component.text("Server <-> %s ping".formatted(target.getName()), TextColor.color(0xFFAAAA)) + .append(Component.text(": ", NamedTextColor.WHITE)) + .append(Component.text(ping,NamedTextColor.AQUA)) + .append(Component.text("ms",TextColor.color(0xFFDDDD))) + ); + } + success(sender,Component.text("{0} has {1}ms of latency to the server."),target.name(),Component.text(ping)); + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/commands/TrimEffectCommand.java b/src/main/java/me/trouper/clonedupecore/server/commands/TrimEffectCommand.java new file mode 100644 index 0000000..a379eab --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/commands/TrimEffectCommand.java @@ -0,0 +1,47 @@ +package me.trouper.clonedupecore.server.commands; + +import me.trouper.alias.server.commands.Args; +import me.trouper.alias.server.commands.CommandRegistry; +import me.trouper.alias.server.commands.QuickCommand; +import me.trouper.alias.server.commands.completions.CompletionBuilder; +import me.trouper.clonedupecore.data.Data; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +@CommandRegistry( + value = "trimeffect", usage = "/trimeffect ", + consoleAllowed = false, + blocksAllowed = false +) +public class TrimEffectCommand implements QuickCommand, Data { + + @Override + public void handleCommand(CommandSender commandSender, Command command, String s, Args args) { + Player p = (Player) commandSender; + if (args.getSize() != 1) { + errorAny(commandSender,"Correct Usage: ", getRegistry().usage()); + return; + } + if ("global".equals(args.get(0).toString())) { + if (getStorage().disabledGlobalParticles.add(p.getUniqueId().toString())) { + successAny(commandSender,"Disabled global Trim effects."); + } else { + getStorage().disabledGlobalParticles.remove(p.getUniqueId().toString()); + successAny(commandSender,"Enabled global Trim effects."); + } + } else { + if (getStorage().disabledOwnParticles.add(p.getUniqueId().toString())) { + successAny(commandSender,"Disabled your own Trim effects."); + } else { + getStorage().disabledOwnParticles.remove(p.getUniqueId().toString()); + successAny(commandSender,"Enabled your own Trim effects."); + } + } + } + + @Override + public void handleCompletion(CommandSender commandSender, Command command, String s, Args args, CompletionBuilder b) { + b.then(b.arg("global","self")); + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/commands/TrollCommand.java b/src/main/java/me/trouper/clonedupecore/server/commands/TrollCommand.java new file mode 100644 index 0000000..a2d40c2 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/commands/TrollCommand.java @@ -0,0 +1,137 @@ +package me.trouper.clonedupecore.server.commands; + +import me.trouper.alias.server.commands.Args; +import me.trouper.alias.server.commands.CommandRegistry; +import me.trouper.alias.server.commands.Permission; +import me.trouper.alias.server.commands.QuickCommand; +import me.trouper.alias.server.commands.completions.CompletionBuilder; +import me.trouper.alias.server.systems.AbstractWand; +import me.trouper.alias.server.systems.Text; +import me.trouper.clonedupecore.server.trolls.*; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@CommandRegistry(value = "clonetroll",permission = @Permission("clonedupe.troll"),printStackTrace = true) +public class TrollCommand implements QuickCommand { + + public final List trollRegistry = new ArrayList<>(); + + public TrollCommand() { + trollRegistry.addAll(List.of( + new DragTrollWand(), + new FlickerTroll(), + new LiarTroll(), + new LSDTroll(), + new MatrixTroll(), + new VoidTrollWand(), + new OrbitalTrollWand(), + new CameraTroll(), + new TestTrollWand() + )); + } + + @Override + public void handleCommand(CommandSender sender, Command command, String label, Args args) { + Component helpMessage = Component.empty() + .append(Component.text("CloneDupe Troll Guide", NamedTextColor.RED).decorate(TextDecoration.BOLD)).appendNewline() + .append(Component.text(""" + - These features can be used to mess with players - + SAFE trolls are purely packet based, and relogging will clear all their effects. + HARMLESS trolls are safe but not packet based. Relogging or rebooting may not clear all effects. + DAMAGING trolls may deal damage to the player's health and armor. + DESTRUCTIVE trolls may break blocks in the world, delete player items, or crash games. Some may be irreversible, so handle them carefully. + Use the *stop* argument to end or cancel a troll. + """,NamedTextColor.GRAY)).appendNewline() + .append(Component.text("Syntax",NamedTextColor.GOLD) + .append(Component.text(": ",NamedTextColor.WHITE) + .append(Component.text("/clonetroll [player] [stop]",NamedTextColor.GRAY)))).appendNewline() + .append(Component.text("Features",NamedTextColor.GOLD) + .append(Component.text(":",NamedTextColor.WHITE))).appendNewline(); + + for (TrollFeature troll : trollRegistry) { + helpMessage = helpMessage.append(Text.format(Text.Pallet.NEUTRAL,"[{0}] - {1}: {2}",troll.getRating(),troll.getName(),troll.getDescription())).appendNewline(); + } + + if (args.getSize() < 1 || (args.getSize() == 1 && Objects.equals("info",args.get(0).toString()))) { + sender.sendMessage(helpMessage); + return; + } + + String choice = args.get(0).toString(); + TrollFeature troll = null; + + for (TrollFeature abstractTroll : trollRegistry) { + if (!abstractTroll.getName().equals(choice)) continue; + troll = abstractTroll; + break; + } + + if (troll == null) { + errorAny(sender,"You must pick a valid troll. {0} does not exist!",choice); + return; + } + + if (troll instanceof AbstractWand wand) { + troll.execute(sender,null); + + if (!(sender instanceof Player p)) { + warningAny(sender,"Wands can only be given to a player."); + return; + } + if (!p.hasPermission(wand.getUsePermission())) { + warningAny(sender,"You lack the permission to use this wand."); + return; + } + + if (!p.getInventory().addItem(wand.getWandItem().clone()).isEmpty()) { + errorAny(p, "Your inventory is full!"); + } else { + success(p, Component.text("You have received a {0}"), wand.getWandItem().displayName()); + } + + return; + } + + String target = args.get(1).toString(); + Player victim = Bukkit.getPlayer(target); + + if (victim == null || !victim.isOnline()) { + errorAny(sender,"You must pick an online player. {0} is not online!",target); + return; + } + + if (args.getSize() >= 3 && Objects.equals("stop",args.get(2).toString())) { + troll.stop(sender,victim); + return; + } + + troll.execute(sender,victim); + } + + @Override + public void handleCompletion(CommandSender sender, Command command, String label, Args args, CompletionBuilder b) { + List extras = new ArrayList<>(); + trollRegistry.forEach(extra -> extras.add(extra.getName())); + b.then( + b.arg(extras) + .then( + b.argOnlinePlayers() + .then( + b.arg("stop") + ) + ) + ).then( + b.arg("info") + ); + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/events/CreativeHotbarEvent.java b/src/main/java/me/trouper/clonedupecore/server/events/CreativeHotbarEvent.java new file mode 100644 index 0000000..d657b09 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/events/CreativeHotbarEvent.java @@ -0,0 +1,35 @@ +package me.trouper.clonedupecore.server.events; + +import me.trouper.alias.server.events.QuickListener; +import me.trouper.clonedupecore.CloneDupeCore; +import me.trouper.clonedupecore.server.events.hotbar.items.ItemCheck; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.inventory.InventoryCreativeEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +public class CreativeHotbarEvent implements QuickListener { + + @EventHandler + private void onNBT(InventoryCreativeEvent e) { + if (e.getCursor().getItemMeta() == null) return; + if (!(e.getWhoClicked() instanceof Player p)) return; + if (e.getCursor() == null) return; + + ItemStack i = e.getCursor(); + + if (i.getItemMeta() == null) return; + if (!i.hasItemMeta()) return; + + if (CloneDupeCore.getInstance().getManager().io.config.nbtWhitelist.contains(p.getUniqueId().toString())) return; + if (new ItemCheck().passes(i)) return; + + e.setCancelled(true); + + ItemStack replacement = new ItemStack(i.getType()); + ItemMeta meta = i.getItemMeta(); + e.setCurrentItem(replacement); + e.getCursor().setItemMeta(meta); + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/events/GamemodeEvent.java b/src/main/java/me/trouper/clonedupecore/server/events/GamemodeEvent.java new file mode 100644 index 0000000..305c733 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/events/GamemodeEvent.java @@ -0,0 +1,18 @@ +package me.trouper.clonedupecore.server.events; + +import me.trouper.alias.server.events.QuickListener; +import org.bukkit.GameMode; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerJoinEvent; + +public class GamemodeEvent implements QuickListener { + + @EventHandler + private void onJoin(PlayerJoinEvent e) { + Player p = e.getPlayer(); + if (p.isOp()) return; + + p.setGameMode(GameMode.SURVIVAL); + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/events/hotbar/AbstractCheck.java b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/AbstractCheck.java new file mode 100644 index 0000000..e30c2f0 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/AbstractCheck.java @@ -0,0 +1,9 @@ +package me.trouper.clonedupecore.server.events.hotbar; + +import me.trouper.clonedupecore.data.Data; +import me.trouper.clonedupecore.data.io.NBTConfig; + +public abstract class AbstractCheck implements Data { + public NBTConfig config = getNBT(); + public abstract boolean passes(T input); +} diff --git a/src/main/java/me/trouper/clonedupecore/server/events/hotbar/entities/EntityCheck.java b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/entities/EntityCheck.java new file mode 100644 index 0000000..f356668 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/entities/EntityCheck.java @@ -0,0 +1,76 @@ +package me.trouper.clonedupecore.server.events.hotbar.entities; + +import de.tr7zw.nbtapi.NBT; +import me.trouper.alias.server.systems.Verbose; +import me.trouper.alias.utils.InventoryUtils; +import me.trouper.clonedupecore.server.events.hotbar.AbstractCheck; +import me.trouper.clonedupecore.server.events.hotbar.items.ItemCheck; +import me.trouper.clonedupecore.server.events.hotbar.misc.InventoryCheck; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Item; +import org.bukkit.entity.Mob; +import org.bukkit.entity.Villager; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.MerchantRecipe; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class EntityCheck extends AbstractCheck { + + @Override + public boolean passes(Entity entity) { + if (entity instanceof Item itemEntity) { + if (!new ItemCheck().passes(itemEntity.getItemStack())) { + Verbose.send("Entity failed check: Item not allowed."); + return false; + } + } + Inventory inv = InventoryUtils.getInventory(entity); + if (inv != null && !new InventoryCheck().passes(inv)) { + Verbose.send("Entity inventory failed check."); + return false; + } + if (entity instanceof Villager villager) { + for (MerchantRecipe recipe : villager.getRecipes()) { + if (!new ItemCheck().passes(recipe.getResult())) { + Verbose.send("Villager recipe failed check."); + return false; + } + } + } + if (entity instanceof Mob mob) { + if (!new EquipmentCheck().passes(mob)) { + Verbose.send("Mob equipment failed check."); + return false; + } + } + if (!entity.getPassengers().isEmpty()) { + if (!config.allowRecursion) { + Verbose.send("Entity recursion not allowed."); + return false; + } + for (Entity passenger : entity.getPassengers()) { + if (!passes(passenger)) { + Verbose.send("Entity passenger failed check."); + return false; + } + } + } + AtomicBoolean failsTiming = new AtomicBoolean(false); + NBT.get(entity, nbt -> { + if (nbt.hasTag("DeathTime") && nbt.getInteger("DeathTime") < 1) { + Verbose.send("Entity death time check failed."); + failsTiming.set(true); + } + if (nbt.hasTag("HurtTime") && nbt.getInteger("HurtTime") < 1) { + Verbose.send("Entity hurt time check failed."); + failsTiming.set(true); + } + }); + if (failsTiming.get()) { + Verbose.send("Entity timing check failed."); + return false; + } + return true; + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/events/hotbar/entities/EntitySnapshotCheck.java b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/entities/EntitySnapshotCheck.java new file mode 100644 index 0000000..f91edc9 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/entities/EntitySnapshotCheck.java @@ -0,0 +1,21 @@ +package me.trouper.clonedupecore.server.events.hotbar.entities; + +import me.trouper.alias.server.systems.Verbose; +import me.trouper.clonedupecore.server.events.hotbar.AbstractCheck; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntitySnapshot; + +public class EntitySnapshotCheck extends AbstractCheck { + + @Override + public boolean passes(EntitySnapshot input) { + Location loc = new Location(Bukkit.getWorlds().getFirst(), 0, 1000000, 0); + Entity temp = input.createEntity(loc); + boolean result = new EntityCheck().passes(temp); + Verbose.send("Temp Entity %s Entity Check", result ? "failed" : "passed"); + temp.remove(); + return result; + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/events/hotbar/entities/EquipmentCheck.java b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/entities/EquipmentCheck.java new file mode 100644 index 0000000..daabec3 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/entities/EquipmentCheck.java @@ -0,0 +1,25 @@ +package me.trouper.clonedupecore.server.events.hotbar.entities; + +import me.trouper.alias.server.systems.Verbose; +import me.trouper.clonedupecore.server.events.hotbar.AbstractCheck; +import me.trouper.clonedupecore.server.events.hotbar.items.ItemCheck; +import org.bukkit.entity.Mob; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +public class EquipmentCheck extends AbstractCheck { + + @Override + public boolean passes(Mob mob) { + Verbose.send("Running mob check."); + for (EquipmentSlot slot : EquipmentSlot.values()) { + if (mob.getEquipment().getItem(slot).isEmpty()) continue; + ItemStack item = mob.getEquipment().getItem(slot); + if (!new ItemCheck().passes(item)) { + Verbose.send("Equipment slot did not pass."); + return false; + } + } + return true; + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/events/hotbar/items/EnchantmentCheck.java b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/items/EnchantmentCheck.java new file mode 100644 index 0000000..df2cd90 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/items/EnchantmentCheck.java @@ -0,0 +1,129 @@ +package me.trouper.clonedupecore.server.events.hotbar.items; + +import me.trouper.alias.server.systems.Verbose; +import me.trouper.clonedupecore.server.events.hotbar.AbstractCheck; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.Map; + +import static org.bukkit.enchantments.Enchantment.MENDING; + +public class EnchantmentCheck extends AbstractCheck { + + @Override + public boolean passes(ItemStack input) { + return !hasIllegalEnchants(input); + } + + public boolean hasIllegalEnchants(ItemStack item) { + Verbose.send("Checking item for illegal enchants: ", item.getType().name()); + if (item.hasItemMeta() && item.getItemMeta().hasEnchants()) { + ItemMeta meta = item.getItemMeta(); + Map enchantments = meta.getEnchants(); + for (Map.Entry entry : enchantments.entrySet()) { + Enchantment enchantment = entry.getKey(); + int level = entry.getValue(); + if (level > getNBT().globalMaxEnchant || isOverLimit(enchantment, level)) { + return true; + } + } + } + return false; + } + + public boolean isOverLimit(Enchantment enchantment, int level) { + int maxLevel = getNBT().globalMaxEnchant; + + if (enchantment.equals(MENDING)) { + maxLevel = getNBT().maxMending; + } else if (enchantment.equals(Enchantment.UNBREAKING)) { + maxLevel = getNBT().maxUnbreaking; + } else if (enchantment.equals(Enchantment.VANISHING_CURSE)) { + maxLevel = getNBT().maxCurseOfVanishing; + } else if (enchantment.equals(Enchantment.BINDING_CURSE)) { + maxLevel = getNBT().maxCurseOfBinding; + } else if (enchantment.equals(Enchantment.AQUA_AFFINITY)) { + maxLevel = getNBT().maxAquaAffinity; + } else if (enchantment.equals(Enchantment.PROTECTION)) { + maxLevel = getNBT().maxProtection; + } else if (enchantment.equals(Enchantment.BLAST_PROTECTION)) { + maxLevel = getNBT().maxBlastProtection; + } else if (enchantment.equals(Enchantment.DEPTH_STRIDER)) { + maxLevel = getNBT().maxDepthStrider; + } else if (enchantment.equals(Enchantment.FEATHER_FALLING)) { + maxLevel = getNBT().maxFeatherFalling; + } else if (enchantment.equals(Enchantment.FIRE_PROTECTION)) { + maxLevel = getNBT().maxFireProtection; + } else if (enchantment.equals(Enchantment.FROST_WALKER)) { + maxLevel = getNBT().maxFrostWalker; + } else if (enchantment.equals(Enchantment.PROJECTILE_PROTECTION)) { + maxLevel = getNBT().maxProjectileProtection; + } else if (enchantment.equals(Enchantment.RESPIRATION)) { + maxLevel = getNBT().maxRespiration; + } else if (enchantment.equals(Enchantment.SOUL_SPEED)) { + maxLevel = getNBT().maxSoulSpeed; + } else if (enchantment.equals(Enchantment.THORNS)) { + maxLevel = getNBT().maxThorns; + } else if (enchantment.equals(Enchantment.SWEEPING_EDGE)) { + maxLevel = getNBT().maxSweepingEdge; + } else if (enchantment.equals(Enchantment.SWIFT_SNEAK)) { + maxLevel = getNBT().maxSwiftSneak; + } else if (enchantment.equals(Enchantment.BANE_OF_ARTHROPODS)) { + maxLevel = getNBT().maxBaneOfArthropods; + } else if (enchantment.equals(Enchantment.FIRE_ASPECT)) { + maxLevel = getNBT().maxFireAspect; + } else if (enchantment.equals(Enchantment.LOOTING)) { + maxLevel = getNBT().maxLooting; + } else if (enchantment.equals(Enchantment.IMPALING)) { + maxLevel = getNBT().maxImpaling; + } else if (enchantment.equals(Enchantment.KNOCKBACK)) { + maxLevel = getNBT().maxKnockback; + } else if (enchantment.equals(Enchantment.SHARPNESS)) { + maxLevel = getNBT().maxSharpness; + } else if (enchantment.equals(Enchantment.SMITE)) { + maxLevel = getNBT().maxSmite; + } else if (enchantment.equals(Enchantment.CHANNELING)) { + maxLevel = getNBT().maxChanneling; + } else if (enchantment.equals(Enchantment.FLAME)) { + maxLevel = getNBT().maxFlame; + } else if (enchantment.equals(Enchantment.INFINITY)) { + maxLevel = getNBT().maxInfinity; + } else if (enchantment.equals(Enchantment.LOYALTY)) { + maxLevel = getNBT().maxLoyalty; + } else if (enchantment.equals(Enchantment.RIPTIDE)) { + maxLevel = getNBT().maxRiptide; + } else if (enchantment.equals(Enchantment.MULTISHOT)) { + maxLevel = getNBT().maxMultishot; + } else if (enchantment.equals(Enchantment.PIERCING)) { + maxLevel = getNBT().maxPiercing; + } else if (enchantment.equals(Enchantment.POWER)) { + maxLevel = getNBT().maxPower; + } else if (enchantment.equals(Enchantment.PUNCH)) { + maxLevel = getNBT().maxPunch; + } else if (enchantment.equals(Enchantment.QUICK_CHARGE)) { + maxLevel = getNBT().maxQuickCharge; + } else if (enchantment.equals(Enchantment.EFFICIENCY)) { + maxLevel = getNBT().maxEfficiency; + } else if (enchantment.equals(Enchantment.FORTUNE)) { + maxLevel = getNBT().maxFortune; + } else if (enchantment.equals(Enchantment.LUCK_OF_THE_SEA)) { + maxLevel = getNBT().maxLuckOfTheSea; + } else if (enchantment.equals(Enchantment.LURE)) { + maxLevel = getNBT().maxLure; + } else if (enchantment.equals(Enchantment.SILK_TOUCH)) { + maxLevel = getNBT().maxSilkTouch; + } else if (enchantment.equals(Enchantment.BREACH)) { + maxLevel = getNBT().maxBreach; + } else if (enchantment.equals(Enchantment.DENSITY)) { + maxLevel = getNBT().maxDensity; + } else if (enchantment.equals(Enchantment.WIND_BURST)) { + maxLevel = getNBT().maxWindBurst; + } + + return level > maxLevel; + } + + +} diff --git a/src/main/java/me/trouper/clonedupecore/server/events/hotbar/items/ItemCheck.java b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/items/ItemCheck.java new file mode 100644 index 0000000..4c3e40b --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/items/ItemCheck.java @@ -0,0 +1,80 @@ +package me.trouper.clonedupecore.server.events.hotbar.items; + +import de.tr7zw.nbtapi.NBT; +import de.tr7zw.nbtapi.iface.ReadWriteNBT; +import me.trouper.alias.server.systems.Verbose; +import me.trouper.alias.utils.InventoryUtils; +import me.trouper.clonedupecore.CloneDupeCore; +import me.trouper.clonedupecore.server.events.hotbar.AbstractCheck; +import me.trouper.clonedupecore.server.events.hotbar.misc.BlockStateCheck; +import me.trouper.clonedupecore.server.events.hotbar.misc.InventoryCheck; +import me.trouper.clonedupecore.server.events.hotbar.nbt.ComponentCheck; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import java.util.Arrays; + +public class ItemCheck extends AbstractCheck { + + + @Override + public boolean passes(ItemStack item) { + try { + return scan(item); + } catch (Exception ex) { + CloneDupeCore.getInstance().getLogger().warning("Caught an exception while handling an item check: " + Arrays.toString(ex.getStackTrace())); + return false; + } + } + + + private boolean scan(ItemStack item) { + Verbose.send("Checking item: " + item.getType().name()); + + // No metadata? Nothing to check. + if (item.getItemMeta() == null) { + Verbose.send("Item passes because it has no metadata."); + return true; + } + + if (!new MetaCheck().passes(item)) { + Verbose.send("Item failed metadata check."); + return false; + } + + + // NBT-based checks + ReadWriteNBT nbt = NBT.itemStackToNBT(item); + ReadWriteNBT components = nbt.getCompound("components"); + if (components != null) { + if (!new ComponentCheck().passes(components)) { + Verbose.send("Components check failed."); + return false; + } + } + + // Spawn egg checks. + if (!new SpawnEggCheck().passes(item)) { + Verbose.send("Spawn egg check failed."); + return false; + } + + if (!new BlockStateCheck().passes(item)) { + Verbose.send("Block State check failed."); + return false; + } + + // Check for an inventory inside the item. + Inventory inv = InventoryUtils.getInventory(item); + if (inv != null) { + Verbose.send("Item contains an inventory: " + inv); + if (!new InventoryCheck().passes(inv)) { + Verbose.send("Item failed inventory check."); + return false; + } + } + + Verbose.send("Item passed all checks."); + return true; + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/events/hotbar/items/MetaCheck.java b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/items/MetaCheck.java new file mode 100644 index 0000000..b667117 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/items/MetaCheck.java @@ -0,0 +1,55 @@ +package me.trouper.clonedupecore.server.events.hotbar.items; + +import me.trouper.alias.server.systems.Verbose; +import me.trouper.clonedupecore.server.events.hotbar.AbstractCheck; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BookMeta; +import org.bukkit.inventory.meta.BundleMeta; +import org.bukkit.inventory.meta.ItemMeta; + +public class MetaCheck extends AbstractCheck { + + @Override + public boolean passes(ItemStack item) { + ItemMeta meta = item.getItemMeta(); + // Name, lore, potion, attribute and enchantment checks. + if (!config.allowName && meta.hasDisplayName()) { + Verbose.send("Custom names not allowed."); + return false; + } + if (!config.allowLore && meta.hasLore()) { + Verbose.send("Custom lore not allowed."); + return false; + } + if (!config.allowBooks && meta instanceof BookMeta) { + Verbose.send("Item failed book check."); + return false; + } + if (!config.allowPotions && + (item.getType().equals(Material.POTION) || + item.getType().equals(Material.SPLASH_POTION) || + item.getType().equals(Material.LINGERING_POTION))) { + Verbose.send("Potions not allowed."); + return false; + } + if (!config.allowAttributes && meta.hasAttributeModifiers()) { + Verbose.send("Attribute modifiers not allowed."); + return false; + } + if (config.globalMaxEnchant != 0 && new EnchantmentCheck().hasIllegalEnchants(item)) { + Verbose.send("Illegal enchantments found."); + return false; + } + + + // Bundle check – recursively check the contained items. + if (item.getType().name().contains("_BUNDLE") && meta instanceof BundleMeta bm) { + for (ItemStack bundleItem : bm.getItems()) { + if (!passes(bundleItem)) return false; + } + } + + return true; + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/events/hotbar/items/RateLimitCheck.java b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/items/RateLimitCheck.java new file mode 100644 index 0000000..9d382de --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/items/RateLimitCheck.java @@ -0,0 +1,79 @@ +package me.trouper.clonedupecore.server.events.hotbar.items; + +import de.tr7zw.nbtapi.NBT; +import kotlin.Pair; +import me.trouper.alias.server.systems.Verbose; +import me.trouper.clonedupecore.CloneDupeCore; +import me.trouper.clonedupecore.server.events.hotbar.AbstractCheck; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class RateLimitCheck extends AbstractCheck> { + + public static Map dataUsed = new HashMap<>(); + public static Map itemsUsed = new HashMap<>(); + + @Override + public boolean passes(Pair input) { + Player player = input.getFirst(); + UUID uuid = player.getUniqueId(); + ItemStack item = input.getSecond(); + + return itemLimit(uuid,item) && dataLimit(uuid,item); + } + + private boolean itemLimit(UUID uuid, ItemStack item) { + int currentUsed = itemsUsed.getOrDefault(uuid,0); + Verbose.send("Current Player used items: " + currentUsed); + currentUsed++; + itemsUsed.put(uuid,currentUsed); + return currentUsed <= config.rateLimit.rateLimitItems; + } + + + private boolean dataLimit(UUID uuid, ItemStack item) { + int currentData = dataUsed.getOrDefault(uuid,0); + + Verbose.send("Current Player used data: " + currentData); + try { + int itemData = NBT.readNbt(item).toString().length(); + Verbose.send("Item data: " + itemData); + if (currentData < config.rateLimit.maxOverhead) currentData += itemData; + } catch (Exception e) { + CloneDupeCore.getInstance().getLogger().warning("Could not determine size of item. Blocking."); + CloneDupeCore.getInstance().getLogger().warning(Arrays.toString(e.getStackTrace())); + return false; + } + + dataUsed.put(uuid,currentData); + + Verbose.send("New Player used data: " + currentData); + + return currentData <= config.rateLimit.rateLimitBytes; + } + + public void decayData() { + for (UUID uuid : dataUsed.keySet()) { + int currentData = dataUsed.get(uuid); + if (currentData > 0) { + currentData -= config.rateLimit.byteDecay; + dataUsed.put(uuid, Math.max(0, currentData)); + } + } + } + + public void decayItems() { + for (UUID uuid : itemsUsed.keySet()) { + int currentItems = itemsUsed.get(uuid); + if (currentItems > 0) { + currentItems -= config.rateLimit.itemDecay; + itemsUsed.put(uuid, Math.max(0, currentItems)); + } + } + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/events/hotbar/items/SpawnEggCheck.java b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/items/SpawnEggCheck.java new file mode 100644 index 0000000..96ca037 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/items/SpawnEggCheck.java @@ -0,0 +1,50 @@ +package me.trouper.clonedupecore.server.events.hotbar.items; + +import de.tr7zw.nbtapi.NBT; +import de.tr7zw.nbtapi.iface.ReadWriteNBT; +import me.trouper.alias.server.systems.Verbose; +import me.trouper.clonedupecore.server.events.hotbar.AbstractCheck; +import me.trouper.clonedupecore.server.events.hotbar.entities.EntitySnapshotCheck; +import me.trouper.clonedupecore.server.events.hotbar.nbt.EntityDataCheck; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.SpawnEggMeta; + +public class SpawnEggCheck extends AbstractCheck { + + @Override + public boolean passes(ItemStack item) { + Verbose.send("Running spawn egg checks on item: ",item.getType().name()); + if (!item.getType().name().toLowerCase().contains("spawn_egg")) return true; + if (!SpawnEggCheck.entityMatches(item)) { + Verbose.send("Spawn egg entity doesn't match item type."); + return false; + } + ReadWriteNBT nbt = NBT.itemStackToNBT(item); + ReadWriteNBT components = nbt.getCompound("components"); + if (components != null) { + var entityData = components.getCompound("minecraft:entity_data"); + if (!new EntityDataCheck().passes(entityData)) { + Verbose.send("Spawn egg entity data check failed."); + return false; + } + } + + if (item.hasItemMeta() && item.getItemMeta() instanceof SpawnEggMeta sem) { + if (sem.getSpawnedEntity() != null && !new EntitySnapshotCheck().passes(sem.getSpawnedEntity())) { + Verbose.send("Spawn egg entity snapshot check failed."); + return false; + } + } + + return true; + } + + public static boolean entityMatches(ItemStack item) { + if (item.hasItemMeta() && item.getItemMeta() instanceof SpawnEggMeta sem) { + String eggEntityName = item.getType().name().replace("_SPAWN_EGG", ""); + return sem.getSpawnedEntity() != null && + sem.getSpawnedEntity().getEntityType().name().equals(eggEntityName); + } + return false; + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/events/hotbar/misc/BlockStateCheck.java b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/misc/BlockStateCheck.java new file mode 100644 index 0000000..431cc4d --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/misc/BlockStateCheck.java @@ -0,0 +1,100 @@ +package me.trouper.clonedupecore.server.events.hotbar.misc; + +import me.trouper.alias.server.systems.Verbose; +import me.trouper.clonedupecore.server.events.hotbar.AbstractCheck; +import me.trouper.clonedupecore.server.events.hotbar.entities.EntitySnapshotCheck; +import me.trouper.clonedupecore.server.events.hotbar.items.ItemCheck; +import org.bukkit.Material; +import org.bukkit.block.*; +import org.bukkit.entity.EntityType; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BlockStateMeta; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.spawner.TrialSpawnerConfiguration; + +public class BlockStateCheck extends AbstractCheck { + @Override + public boolean passes(ItemStack item) { + ItemMeta meta = item.getItemMeta(); + + if (!(meta instanceof BlockStateMeta blockStateMeta)) { + Verbose.send("Item passes due to not being a block state meta"); + return true; + } + + if (item.getType().name().contains("CAMPFIRE") ) { + BlockState bs = blockStateMeta.getBlockState(); + if (bs instanceof Campfire campfire) { + for (int slot = 0; slot < 4; slot++) { + ItemStack campfireItem = campfire.getItem(slot); + if (campfireItem != null && !new ItemCheck().passes(campfireItem)) { + Verbose.send("Campfire item failed check."); + return false; + } + } + } + } + + // Lectern and Chiseled Bookshelf check (by validating their inventories). + if (item.getType().equals(Material.LECTERN)) { + BlockState bs = blockStateMeta.getBlockState(); + if (bs instanceof Lectern lectern) { + if (!new InventoryCheck().passes(lectern.getInventory())) { + Verbose.send("Lectern inventory failed check."); + return false; + } + } + } + if (item.getType().equals(Material.CHISELED_BOOKSHELF)) { + BlockState bs = blockStateMeta.getBlockState(); + if (bs instanceof ChiseledBookshelf bookshelf) { + if (!new InventoryCheck().passes(bookshelf.getInventory())) { + Verbose.send("Chiseled bookshelf inventory failed check."); + return false; + } + } + } + + // Spawner check. + if (item.getType().equals(Material.SPAWNER)) { + BlockState bs = blockStateMeta.getBlockState(); + if (bs instanceof CreatureSpawner spawner) { + if (spawner.getSpawnedEntity() != null) { + if (spawner.getSpawnedEntity().getEntityType().equals(EntityType.FALLING_BLOCK) || + spawner.getSpawnedEntity().getEntityType().equals(EntityType.COMMAND_BLOCK_MINECART)) { + Verbose.send("Spawner contains disallowed entity type."); + return false; + } + if (!new EntitySnapshotCheck().passes(spawner.getSpawnedEntity())) { + Verbose.send("Spawner entity snapshot check failed."); + return false; + } + } + } + } + + // Trial Spawner check. + if (item.getType() == Material.TRIAL_SPAWNER) { + BlockState bs = blockStateMeta.getBlockState(); + if (bs instanceof TrialSpawner spawner) { + Verbose.send("Running trial spawner check."); + if (spawner.getNormalConfiguration() != null) { + TrialSpawnerConfiguration config = spawner.getNormalConfiguration(); + if (config.getSpawnedEntity() != null && !new EntitySnapshotCheck().passes(config.getSpawnedEntity())) { + Verbose.send("Trial Spawner failed check: Normal entity snapshot not allowed."); + return false; + } + } + if (spawner.getOminousConfiguration() != null) { + TrialSpawnerConfiguration config = spawner.getOminousConfiguration(); + if (config.getSpawnedEntity() != null && !new EntitySnapshotCheck().passes(config.getSpawnedEntity())) { + Verbose.send("Trial Spawner failed check: Ominous entity snapshot not allowed."); + return false; + } + } + } + } + + return true; + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/events/hotbar/misc/InventoryCheck.java b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/misc/InventoryCheck.java new file mode 100644 index 0000000..f5c491a --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/misc/InventoryCheck.java @@ -0,0 +1,35 @@ +package me.trouper.clonedupecore.server.events.hotbar.misc; + +import me.trouper.alias.server.systems.Verbose; +import me.trouper.alias.utils.InventoryUtils; +import me.trouper.clonedupecore.server.events.hotbar.AbstractCheck; +import me.trouper.clonedupecore.server.events.hotbar.items.ItemCheck; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +public class InventoryCheck extends AbstractCheck { + + @Override + public boolean passes(Inventory inv) { + Verbose.send("Running Inventory Check"); + + for (ItemStack i : inv.getContents()) { + if (i == null || i.getType().isAir()) continue; + if (!new ItemCheck().passes(i)) { + Verbose.send("Inventory item failed check."); + return false; + } + Inventory subInventory = InventoryUtils.getInventory(i); + if (subInventory != null && !config.allowRecursion) { + Verbose.send("Recursion is disabled. Failing check."); + return false; + } + if (subInventory != null && !passes(subInventory)) { + Verbose.send("Sub-inventory failed check."); + return false; + } + } + Verbose.send("Inventory passed all checks."); + return true; + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/events/hotbar/nbt/ComponentCheck.java b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/nbt/ComponentCheck.java new file mode 100644 index 0000000..8efc64e --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/nbt/ComponentCheck.java @@ -0,0 +1,32 @@ +package me.trouper.clonedupecore.server.events.hotbar.nbt; + +import de.tr7zw.nbtapi.iface.ReadWriteNBT; +import me.trouper.alias.server.systems.Verbose; +import me.trouper.clonedupecore.server.events.hotbar.AbstractCheck; + +public class ComponentCheck extends AbstractCheck { + + @Override + public boolean passes(ReadWriteNBT components) { + Verbose.send("Checking Consumable & tool"); + if (!config.allowCustomConsumables && components.getCompound("minecraft:consumable") != null) { + Verbose.send("Item is consumable and not allowed."); + return false; + } + if (!config.allowCustomTools && components.getCompound("minecraft:tool") != null) { + Verbose.send("Item is custom tool and not allowed."); + return false; + } + + Verbose.send("Checking Entity data"); + + ReadWriteNBT entityData = components.getCompound("minecraft:entity_data"); + if (!new EntityDataCheck().passes(entityData)) { + Verbose.send("Entity Data Check Failed."); + return false; + } + + return true; + } + +} diff --git a/src/main/java/me/trouper/clonedupecore/server/events/hotbar/nbt/EntityDataCheck.java b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/nbt/EntityDataCheck.java new file mode 100644 index 0000000..84769ee --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/events/hotbar/nbt/EntityDataCheck.java @@ -0,0 +1,40 @@ +package me.trouper.clonedupecore.server.events.hotbar.nbt; + +import de.tr7zw.nbtapi.NBT; +import de.tr7zw.nbtapi.iface.ReadWriteNBT; +import me.trouper.alias.server.systems.Verbose; +import me.trouper.clonedupecore.server.events.hotbar.AbstractCheck; +import me.trouper.clonedupecore.server.events.hotbar.items.ItemCheck; +import org.bukkit.inventory.ItemStack; + +public class EntityDataCheck extends AbstractCheck { + @Override + public boolean passes(ReadWriteNBT entityData) { + if (entityData == null) { + Verbose.send("Entity Data check passed. There was no data."); + return true; + } + + ReadWriteNBT itemData = entityData.getCompound("Item"); + if (itemData != null) { + Verbose.send("Entity data holds an item"); + ItemStack heldItem = NBT.itemStackFromNBT(itemData); + if (heldItem != null && !new ItemCheck().passes(heldItem)) { + Verbose.send("Item contents failed check."); + return false; + } + } + + if (entityData.hasTag("DeathTime") && entityData.getInteger("DeathTime") < 1) { + Verbose.send("Death time check failed."); + return false; + } + if (entityData.hasTag("HurtTime") && entityData.getInteger("HurtTime") < 1) { + Verbose.send("Hurt time check failed."); + return false; + } + + Verbose.send("Entity Data check passed. There was no flagging."); + return true; + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/punishment/Freeze.java b/src/main/java/me/trouper/clonedupecore/server/punishment/Freeze.java new file mode 100644 index 0000000..9b6f1c6 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/punishment/Freeze.java @@ -0,0 +1,113 @@ +package me.trouper.clonedupecore.server.punishment; + +import me.trouper.alias.server.Main; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByBlockEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class Freeze implements Main { + private static final Map FROZEN_PLAYERS = new HashMap<>(); + + public static void freezePlayer(OfflinePlayer player) { + UUID uuid = player.getUniqueId(); + if (!FROZEN_PLAYERS.containsKey(uuid)) { + FrozenPlayer frozen = new FrozenPlayer(main.getPlugin(), uuid); + FROZEN_PLAYERS.put(uuid, frozen); + } + } + + public static void thawPlayer(OfflinePlayer player) { + UUID uuid = player.getUniqueId(); + FrozenPlayer frozen = FROZEN_PLAYERS.remove(uuid); + if (frozen != null) { + frozen.thaw(); + } + } + + public static class FrozenPlayer { + + private final UUID uuid; + private final Listener listener; + private boolean frozen; + + public FrozenPlayer(JavaPlugin plugin, UUID uuid) { + this.uuid = uuid; + this.frozen = true; + + this.listener = new Listener() { + + @EventHandler + public void onPlayerMove(PlayerMoveEvent event) { + if (!event.getPlayer().getUniqueId().equals(uuid)) return; + if (!frozen) return; + + if (!event.getFrom().toVector().equals(event.getTo().toVector())) { + event.setTo(event.getFrom()); + } + } + + @EventHandler + public void onPlayerInteract(PlayerInteractEvent event) { + if (!event.getPlayer().getUniqueId().equals(uuid)) return; + if (!frozen) return; + + event.setCancelled(true); + } + + @EventHandler + public void onPlayerInteract(PlayerCommandPreprocessEvent event) { + if (!event.getPlayer().getUniqueId().equals(uuid)) return; + if (!frozen) return; + + event.setCancelled(true); + } + + @EventHandler + public void onPlayerDamage(EntityDamageByEntityEvent event) { + if (!event.getEntity().getUniqueId().equals(uuid)) return; + if (!frozen) return; + + event.setCancelled(true); + } + + @EventHandler + public void onPlayerDamage(EntityDamageByBlockEvent event) { + if (!event.getEntity().getUniqueId().equals(uuid)) return; + if (!frozen) return; + + event.setCancelled(true); + } + }; + + Bukkit.getPluginManager().registerEvents(listener, plugin); + } + + public void thaw() { + this.frozen = false; + HandlerList.unregisterAll(listener); + } + + public boolean isFrozen() { + return frozen; + } + + public UUID getUuid() { + return uuid; + } + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/punishment/LiteBansManager.java b/src/main/java/me/trouper/clonedupecore/server/punishment/LiteBansManager.java new file mode 100644 index 0000000..4a853d6 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/punishment/LiteBansManager.java @@ -0,0 +1,139 @@ +package me.trouper.clonedupecore.server.punishment; + +import club.minnced.discord.webhook.WebhookClient; +import club.minnced.discord.webhook.WebhookClientBuilder; +import club.minnced.discord.webhook.send.WebhookEmbed; +import club.minnced.discord.webhook.send.WebhookEmbedBuilder; +import litebans.api.Database; +import litebans.api.Entry; +import me.trouper.alias.server.Main; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class LiteBansManager { + + /** + * Get all ban entries for a specific player + */ + public List getPlayerBans(OfflinePlayer player) { + List bans = new ArrayList<>(); + try { + PreparedStatement statement = Database.get().prepareStatement( + "SELECT * FROM {bans} WHERE uuid = ? ORDER BY time DESC" + ); + statement.setString(1, player.getUniqueId().toString()); + ResultSet rs = statement.executeQuery(); + + while (rs.next()) { + bans.add(new WrappedEntry(rs)); + } + + rs.close(); + statement.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + return bans; + } + + /** + * Get ban entries for a specific player and template + * @param template a short representing where in the template.yml list the item is. + */ + public List getPlayerBansByTemplate(OfflinePlayer player, short template) { + List bans = new ArrayList<>(); + try { + PreparedStatement statement = Database.get().prepareStatement( + "SELECT * FROM {bans} WHERE uuid = ? AND template = ? ORDER BY time DESC" + ); + statement.setString(1, player.getUniqueId().toString()); + statement.setShort(2, template); + ResultSet result = statement.executeQuery(); + + while (result.next()) { + bans.add(new WrappedEntry(result)); + } + + result.close(); + statement.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + return bans; + } + + /** + * Get the count of bans for a specific template + * @param template a short representing where in the template.yml list the item is. + */ + public int getBanCountByTemplate(OfflinePlayer player, short template) { + return getPlayerBansByTemplate(player, template).size(); + } + + /** + * Check if player is currently banned + */ + public boolean isPlayerBanned(OfflinePlayer player) { + try { + PreparedStatement statement = Database.get().prepareStatement( + "SELECT COUNT(*) FROM {bans} WHERE uuid = ? AND active = 1" + ); + statement.setString(1, player.getUniqueId().toString()); + ResultSet result = statement.executeQuery(); + + if (result.next()) { + return result.getInt(1) > 0; + } + + result.close(); + statement.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + return false; + } + + /** + * Get active ban for player + */ + public WrappedEntry getActiveBan(OfflinePlayer player) { + try { + PreparedStatement statement = Database.get().prepareStatement( + "SELECT * FROM {bans} WHERE uuid = ? AND active = 1 ORDER BY time DESC LIMIT 1" + ); + statement.setString(1, player.getUniqueId().toString()); + ResultSet result = statement.executeQuery(); + + if (result.next()) { + WrappedEntry entry = new WrappedEntry(result); + result.close(); + statement.close(); + return entry; + } + + result.close(); + statement.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Execute a ban using LiteBans template system + */ + public void executeBan(OfflinePlayer player, String template, CommandSender executor) { + Bukkit.getScheduler().runTask(Main.main.getPlugin(),()-> { + Bukkit.dispatchCommand(executor, + String.format("litebans:ban %s %s -s", player.getName(), template)); + }); + } +} \ No newline at end of file diff --git a/src/main/java/me/trouper/clonedupecore/server/punishment/WrappedEntry.java b/src/main/java/me/trouper/clonedupecore/server/punishment/WrappedEntry.java new file mode 100644 index 0000000..e2ca229 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/punishment/WrappedEntry.java @@ -0,0 +1,61 @@ +package me.trouper.clonedupecore.server.punishment; + +import me.trouper.clonedupecore.data.Data; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Instant; + +public class WrappedEntry implements Data { + + private final long dateStart; + private final long dateEnd; + private final String reason; + private final String template; + private final boolean active; + private final boolean permanent; + + public WrappedEntry(long dateStart, long dateEnd, String reason, boolean active, boolean permanent) { + this.dateStart = dateStart; + this.dateEnd = dateEnd; + this.reason = reason; + this.active = active; + this.permanent = permanent; + this.template = "none!"; + } + + public WrappedEntry(ResultSet rs) throws SQLException { + String reason = rs.getString("reason"); + long time = rs.getLong("time"); + long until = rs.getLong("until"); + boolean active = rs.getBoolean("active"); + short template = rs.getShort("template"); + + this.reason = reason; + this.dateStart = time; + this.dateEnd = until; + this.active = active; + this.permanent = until <= 0; + this.template = getConfig().banTemplates.get(template); + } + + public long getDateStart() { + return dateStart; + } + + public long getDateEnd() { + return dateEnd; + } + + public String getReason() { + return reason; + } + + public boolean isActive() { + return active; + } + + public boolean isPermanent() { + return permanent; + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/punishment/animations/AnvilAnimation.java b/src/main/java/me/trouper/clonedupecore/server/punishment/animations/AnvilAnimation.java new file mode 100644 index 0000000..7eb2980 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/punishment/animations/AnvilAnimation.java @@ -0,0 +1,102 @@ +package me.trouper.clonedupecore.server.punishment.animations; + +import me.trouper.alias.server.systems.visual.DisplayUtils; +import me.trouper.alias.server.systems.visual.ParticleUtils; +import me.trouper.clonedupecore.server.punishment.Freeze; +import org.bukkit.*; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.Entity; +import org.bukkit.entity.FallingBlock; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class AnvilAnimation extends PunishmentAnimation { + + private final Set spawnedAnvils = new HashSet<>(); + private boolean spawned = false; + private boolean white = false; + + public AnvilAnimation(JavaPlugin plugin, Player player, Runnable finishTask) { + super(plugin, player, finishTask); + } + + @Override + protected void tick(int ticksElapsed) { + Freeze.freezePlayer(player); + if (!spawned) { + spawnAnvilRing(); + DisplayUtils.ring(player.getLocation().add(0,10,0),5,1,(point)->{ + ParticleUtils.builder() + .type(Particle.CLOUD) + .speed(0.07F) + .count(10) + .offset(0.5,0.2,0.5) + .spawn(point); + }); + spawned = true; + return; + } + for (int i = 0; i < 5; i++) { + int finalI = i; + DisplayUtils.ring(player.getLocation().add(0,0.1,0), (double)i/2D,0.5,(point)->{ + ParticleUtils.builder() + .type(Particle.DUST) + .data(new Particle.DustOptions(finalI % 2 == 0 ? Color.RED : Color.WHITE,2F)) + .spawn(point); + }); + } + + + spawnedAnvils.removeIf(block -> block.isDead() || block.isOnGround()); + + if (ticksElapsed > 20 && (spawnedAnvils.isEmpty() || ticksElapsed > 300)) { + close(); + } + } + + private void spawnAnvilRing() { + Location center = player.getLocation().add(0, 10, 0); + World world = player.getWorld(); + + int amount = 12; + for (int i = 0; i < amount; i++) { + int finalI = i; + Bukkit.getScheduler().runTaskLater(main.getPlugin(),()->{ + double angle = 2 * Math.PI * finalI / amount; + int RADIUS = 5; + double x = center.getX() + RADIUS * Math.cos(angle); + double z = center.getZ() + RADIUS * Math.sin(angle); + Location spawnLoc = new Location(world, x, center.getY(), z); + + FallingBlock anvil = world.spawn(spawnLoc, FallingBlock.class); + anvil.setBlockData(Material.ANVIL.createBlockData()); + anvil.setCancelDrop(true); + anvil.setHurtEntities(false); + + Vector velocity = player.getLocation().toVector().subtract(spawnLoc.toVector()).normalize().multiply(0.7); + velocity.setY(-0.3); + anvil.setVelocity(velocity); + + spawnedAnvils.add(anvil); + },i*2); + + } + } + + @Override + protected void cleanup() { + Freeze.thawPlayer(player); + for (FallingBlock block : spawnedAnvils) { + if (!block.isDead()) { + block.remove(); + } + } + spawnedAnvils.clear(); + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/punishment/animations/GwenAnimation.java b/src/main/java/me/trouper/clonedupecore/server/punishment/animations/GwenAnimation.java new file mode 100644 index 0000000..8286905 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/punishment/animations/GwenAnimation.java @@ -0,0 +1,98 @@ +package me.trouper.clonedupecore.server.punishment.animations; + +import me.trouper.alias.server.systems.Verbose; +import me.trouper.alias.utils.VectorUtils; +import me.trouper.clonedupecore.server.punishment.Freeze; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Guardian; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.List; + +public class GwenAnimation extends PunishmentAnimation { + + private final List guardians = new ArrayList<>(); + int guardianCount = 4; + double animationTicks = 20 * 8; + + public GwenAnimation(JavaPlugin plugin, Player player, Runnable finishTask) { + super(plugin, player, finishTask); + } + + @Override + protected void tick(int ticksElapsed) { + Freeze.freezePlayer(player); + + Location head = player.getEyeLocation(); + Location center = head.clone().add(0, 3, 0); + World w = head.getWorld(); + + if (ticksElapsed > animationTicks) { + head.getWorld().spawnParticle(Particle.EXPLOSION_EMITTER,head,10,1,1,1); + head.getWorld().strikeLightningEffect(head); + + Verbose.send("Closing G.W.E.N. animation"); + close(); + return; + } + + double animationPrecent = ticksElapsed / animationTicks; + double radius = 4 - 3 * animationPrecent; + double angleStep = 2 * Math.PI / guardianCount; + double rotationSpeed = animationPrecent / 2; + + if (ticksElapsed == 1) { + for (int i = 0; i < guardianCount; i++) { + double angle = i * angleStep; + double x = Math.cos(angle) * radius; + double z = Math.sin(angle) * radius; + Location loc = center.clone().add(x, 0, z); + Vector look = center.toVector().subtract(loc.toVector()); + float[] angles = VectorUtils.toAngles(look); + w.spawn(loc, Guardian.class, (guardian) -> { + guardians.add(guardian); + guardian.setRotation(angles[0], angles[1]); + guardian.setTarget(player); + guardian.setAI(false); + guardian.setInvulnerable(true); + guardian.setLaser(true); + }); + } + } + + for (int i = 0; i < guardians.size(); i++) { + Entity entity = guardians.get(i); + if (entity == null || entity.isDead()) continue; + + double angle = i * angleStep + rotationSpeed * ticksElapsed; + double x = Math.cos(angle) * radius; + double z = Math.sin(angle) * radius; + Location newLoc = center.clone().add(x, 0, z); + + Vector look = center.toVector().subtract(newLoc.toVector()); + float[] angles = VectorUtils.toAngles(look); + + entity.teleport(newLoc); + entity.setRotation(angles[0], angles[1]); + } + + } + + @Override + protected void cleanup() { + Freeze.thawPlayer(player); + + for (Entity entity : guardians) { + if (entity == null) continue; + entity.remove(); + } + + guardians.clear(); + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/punishment/animations/LaserAnimation.java b/src/main/java/me/trouper/clonedupecore/server/punishment/animations/LaserAnimation.java new file mode 100644 index 0000000..4c4e866 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/punishment/animations/LaserAnimation.java @@ -0,0 +1,64 @@ +package me.trouper.clonedupecore.server.punishment.animations; + +import me.trouper.alias.server.systems.tracing.BlockDisplayRaytracer; +import me.trouper.alias.server.systems.visual.DisplayUtils; +import me.trouper.clonedupecore.server.punishment.Freeze; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.block.Block; +import org.bukkit.entity.FallingBlock; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; + +public class LaserAnimation extends PunishmentAnimation { + public LaserAnimation(JavaPlugin plugin, Player player, Runnable finishTask) { + super(plugin, player, finishTask); + } + + @Override + protected void tick(int ticksElapsed) { + Freeze.freezePlayer(player); + Location target = player.getEyeLocation(); + Location center = player.getLocation(); + + if (ticksElapsed > 20 * 2) { + close(); + return; + } + + if (ticksElapsed == 1) { + DisplayUtils.sphereWave(target,8,1,1,(point)->{ + point.getWorld().spawnParticle(Particle.SMOKE,point,2,0.5,0.5,0.5,0.01); + point.getWorld().spawnParticle(Particle.FLAME,point,2,0.5,0.5,0.5,0.01); + }); + + DisplayUtils.wave(player.getLocation().clone().add(0,0.5,0),5,point -> { + Block block = point.getWorld().getBlockAt(point.clone().subtract(0,2,0)); + point.getWorld().spawn(point, FallingBlock.class,(launched)->{ + launched.setVelocity(point.toVector().subtract(center.toVector()).normalize().multiply(0.4).setY(1)); + launched.setBlockData(block.getBlockData()); + launched.setBlockState(block.getState()); + launched.setCancelDrop(true); + launched.setVisualFire(true); + }); + point.getWorld().spawnParticle(Particle.EXPLOSION,point,1); + point.getWorld().spawnParticle(Particle.BLOCK,point,1,block.getBlockData()); + },1); + } + + Location orbit = player.getLocation().clone().add(200-ticksElapsed*2,0,100); + orbit.setY(315); + + Bukkit.getScheduler().runTask(main.getPlugin(),()->{ + BlockDisplayRaytracer.trace(Material.SMOOTH_QUARTZ,target,orbit,0.5,2); + BlockDisplayRaytracer.trace(Material.RED_STAINED_GLASS,target,orbit,1 + main.random().nextDouble(),2); + }); + } + + @Override + protected void cleanup() { + Freeze.thawPlayer(player); + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/punishment/animations/LightningAnimation.java b/src/main/java/me/trouper/clonedupecore/server/punishment/animations/LightningAnimation.java new file mode 100644 index 0000000..e01ec9a --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/punishment/animations/LightningAnimation.java @@ -0,0 +1,61 @@ +package me.trouper.clonedupecore.server.punishment.animations; + +import me.trouper.alias.server.systems.visual.ParticleUtils; +import me.trouper.alias.utils.SoundPlayer; +import me.trouper.clonedupecore.server.punishment.Freeze; +import me.trouper.clonedupecore.server.trolls.DragTrollWand; +import me.trouper.clonedupecore.utils.PlayerUtils; +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.plugin.java.JavaPlugin; + +public class LightningAnimation extends PunishmentAnimation { + public LightningAnimation(JavaPlugin plugin, Player player, Runnable finishTask) { + super(plugin, player, finishTask); + } + + @Override + protected void tick(int ticksElapsed) { + Freeze.freezePlayer(player); + Location cloudCenter = player.getLocation().clone().add(0,10,0); + ParticleUtils.builder() + .type(Particle.CLOUD) + .offset(3, 0.8, 3) + .count(200) + .spawn(cloudCenter); + ParticleUtils.builder() + .type(Particle.ELECTRIC_SPARK) + .count(10) + .offset(0.3,1,0.3) + .spawn(player.getLocation().clone().add(0,1,0)); + + double x = main.randomizer().getRandomDouble(-4,4); + double z = main.randomizer().getRandomDouble(-4,4); + Location lightningLoc = cloudCenter.clone().add(x,0,z); + + if (ticksElapsed > 40) { + PlayerUtils.instantTrueDamage(player, DamageSource.builder(DamageType.LIGHTNING_BOLT).build(),0.001); + if (ticksElapsed % 15 == 0) { + new SoundPlayer(Sound.ENTITY_LIGHTNING_BOLT_IMPACT,1,1).playAt(player.getLocation(),30); + new SoundPlayer(Sound.ENTITY_LIGHTNING_BOLT_THUNDER,1,1).playAt(lightningLoc,30); + for (int i = 0; i < 10; i++) { + DragTrollWand.drawLightning(lightningLoc,player.getEyeLocation(), Material.BLUE_ICE, Material.LIGHT_BLUE_STAINED_GLASS); + } + } + } + + if (ticksElapsed > 120) { + close(); + } + } + + @Override + protected void cleanup() { + Freeze.thawPlayer(player); + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/punishment/animations/MatrixAnimation.java b/src/main/java/me/trouper/clonedupecore/server/punishment/animations/MatrixAnimation.java new file mode 100644 index 0000000..753c66b --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/punishment/animations/MatrixAnimation.java @@ -0,0 +1,98 @@ +package me.trouper.clonedupecore.server.punishment.animations; + +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; +import me.trouper.alias.server.systems.visual.DisplayUtils; +import me.trouper.clonedupecore.server.punishment.Freeze; +import me.trouper.clonedupecore.server.trolls.DragTrollWand; +import me.trouper.clonedupecore.server.trolls.MatrixTroll; +import org.bukkit.Bukkit; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Display; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.entity.TextDisplay; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.util.Transformation; +import org.joml.AxisAngle4f; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; + +public class MatrixAnimation extends PunishmentAnimation{ + public MatrixAnimation(JavaPlugin plugin, Player player, Runnable finishTask) { + super(plugin, player, finishTask); + } + + private final Map cleanupList = new HashMap<>(); + + @Override + protected void tick(int ticksElapsed) { + if (ticksElapsed > 60) { + player.getWorld().strikeLightningEffect(player.getLocation()); + close(); + } + Location center = player.getLocation().clone().add(0,1,0); + center.setYaw(0); + center.setPitch(0); + + ThreadLocalRandom random = ThreadLocalRandom.current(); + DragTrollWand.drawLightning(center,center.clone().add( + random.nextDouble(-8,8), + random.nextDouble(0,8), + random.nextDouble(-8,8) + ), Material.LIME_CONCRETE,Material.LIME_STAINED_GLASS); + + if (ticksElapsed != 1) return; + Freeze.freezePlayer(player); + + DisplayUtils.sphereWave(center,5,0.5,2,(point)->{ + TextDisplay display = point.getWorld().spawn(point,TextDisplay.class,(td)->{ + td.text(MatrixTroll.generateMatrix(2,2)); + td.setBillboard(Display.Billboard.FIXED); + float yawDegrees = random.nextFloat() * 360; + float yawRadians = (float) Math.toRadians(yawDegrees); + AxisAngle4f angle = new AxisAngle4f(yawRadians,0,1,0); + td.setTransformation(new Transformation( + new Vector3f(0,0,0), + angle, + new Vector3f(3,3,3), + new AxisAngle4f(0,0,0,0) + )); + td.setBackgroundColor(Color.fromARGB(0xFF000000)); + td.setBrightness(new Display.Brightness(15,15)); + }); + + ScheduledTask task = display.getScheduler().runAtFixedRate(main.getPlugin(),(t)->{ + float yawDegrees = random.nextFloat() * 360; + float yawRadians = (float) Math.toRadians(yawDegrees); + AxisAngle4f angle = new AxisAngle4f(yawRadians,0,1,0); + display.setTransformation(new Transformation( + new Vector3f(0,0,0), + angle, + new Vector3f(1,1,1), + new AxisAngle4f(0,0,0,0) + )); + display.setInterpolationDuration(4); + display.text(MatrixTroll.generateMatrix(9,5)); + },null,1,5); + cleanupList.put(display,task); + }); + } + + @Override + protected void cleanup() { + Freeze.thawPlayer(player); + for (Map.Entry entry : cleanupList.entrySet()) { + entry.getValue().cancel(); + entry.getKey().remove(); + } + cleanupList.clear(); + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/punishment/animations/PunishmentAnimation.java b/src/main/java/me/trouper/clonedupecore/server/punishment/animations/PunishmentAnimation.java new file mode 100644 index 0000000..2a1659f --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/punishment/animations/PunishmentAnimation.java @@ -0,0 +1,93 @@ +package me.trouper.clonedupecore.server.punishment.animations; + +import me.trouper.alias.server.Main; +import me.trouper.alias.server.systems.Verbose; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitRunnable; + +import java.io.Closeable; + +public abstract class PunishmentAnimation extends BukkitRunnable implements Closeable, Main { + + protected final JavaPlugin plugin; + protected final String playerName; + protected Player player; + protected int ticksElapsed = 0; + private boolean finished = false; + private final Runnable finishTask; + + public PunishmentAnimation(JavaPlugin plugin, Player player, Runnable finishTask) { + if (player == null || !player.isOnline()) { + throw new IllegalStateException("Player must be online and non-null to start animation."); + } + + this.plugin = plugin; + this.player = player; + this.playerName = player.getName(); + this.finishTask = finishTask; + + Verbose.send("Animation created, running task timer..."); + runTaskTimer(plugin, 0L, 1L); + } + + @FunctionalInterface + public interface AnimationFactory { + PunishmentAnimation create(JavaPlugin plugin, Player player, Runnable onFinish); + } + + @Override + public void run() { + if (isFinished()) return; + + this.player = Bukkit.getPlayerExact(playerName); + if (this.player == null || !this.player.isOnline()) { + Verbose.send("Finishing animation due to player logging off!"); + finishAnimation(); + return; + } + + try { + Bukkit.getScheduler().runTask(main.getPlugin(),()->{ + tick(ticksElapsed++); + }); + } catch (Exception e) { + Verbose.send("An animation experienced an unexpected runtime exception while ticking."); + e.printStackTrace(); + Bukkit.getScheduler().runTask(main.getPlugin(), this::finishAnimation); + } + } + + protected abstract void tick(int ticksElapsed); + + protected abstract void cleanup(); + + private void finishAnimation() { + if (isFinished()) return; + finished = true; + + try { + cancel(); + } catch (IllegalStateException ignored) {} + + cleanup(); + + if (finishTask != null) { + try { + finishTask.run(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + @Override + public void close() { + finishAnimation(); + } + + public boolean isFinished() { + return finished; + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/trims/MaterialAnimation.java b/src/main/java/me/trouper/clonedupecore/server/trims/MaterialAnimation.java new file mode 100644 index 0000000..2d10162 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trims/MaterialAnimation.java @@ -0,0 +1,28 @@ +package me.trouper.clonedupecore.server.trims; + +import me.trouper.alias.server.Main; +import org.bukkit.entity.Player; + +import java.util.Set; + +public abstract class MaterialAnimation implements Main { + private final ValidMaterial material; + private final long loopDuration; + + public MaterialAnimation(ValidMaterial material, long loopDuration) { + this.material = material; + this.loopDuration = loopDuration; + } + + public ValidMaterial getMaterial() { + return material; + } + + public long getLoopDuration() { + return loopDuration; + } + + public abstract void tickMoving(Player player, Set viewers, long loopTime); + public abstract void tickStationary(Player player, Set viewers, long loopTime); + public abstract void onRemove(Player player); +} diff --git a/src/main/java/me/trouper/clonedupecore/server/trims/TrimManager.java b/src/main/java/me/trouper/clonedupecore/server/trims/TrimManager.java new file mode 100644 index 0000000..3246c2b --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trims/TrimManager.java @@ -0,0 +1,125 @@ +package me.trouper.clonedupecore.server.trims; + +import me.trouper.alias.server.Main; +import me.trouper.clonedupecore.data.Data; +import me.trouper.clonedupecore.utils.ArmorUtils; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +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 java.util.*; +import java.util.stream.Collectors; + +public class TrimManager implements Main, Data { + private final Map animations = new EnumMap<>(ValidMaterial.class); + private final Map activePlayers = new HashMap<>(); + + + public void register(MaterialAnimation animation) { + animations.put(animation.getMaterial(), animation); + } + + public void tickPlayer(Player player) { + if (shouldHide(player)) return; + + UUID uuid = player.getUniqueId(); + ValidMaterial currentMaterial = getActiveMaterial(player); + ActiveTrim active = activePlayers.get(uuid); + Location currentLocation = player.getLocation(); + + if (currentMaterial == null) { + if (activePlayers.containsKey(uuid)) { + animations.get(activePlayers.get(uuid).material).onRemove(player); + activePlayers.remove(uuid); + } + return; + } + + if (active != null && active.material == currentMaterial) { + active.ticks++; + + if (active.lastCheckedLocation != null && active.lastCheckedLocation.getWorld().equals(currentLocation.getWorld()) && currentLocation.distanceSquared(active.lastCheckedLocation) > 0.001) { + active.lastMoveTimestamp = System.currentTimeMillis(); + } + + active.lastCheckedLocation = currentLocation; + } else { + active = new ActiveTrim(currentMaterial, currentLocation); + if (activePlayers.containsKey(uuid)) { + animations.get(activePlayers.get(uuid).material).onRemove(player); + } + activePlayers.put(uuid, active); + } + + MaterialAnimation animation = animations.get(currentMaterial); + if (animation != null) { + long loopTime = active.ticks % animation.getLoopDuration(); + long timeSinceLastMove = System.currentTimeMillis() - active.lastMoveTimestamp; + + if (timeSinceLastMove < 1000) { + animation.onRemove(player); + animation.tickMoving(player, getViewers(player), loopTime); + } else { + animation.tickStationary(player, getViewers(player), loopTime); + } + } + } + + public void startTicking() { + Bukkit.getScheduler().runTaskTimer(main.getPlugin(), () -> Bukkit.getOnlinePlayers().forEach(this::tickPlayer),0,1); + } + + private ValidMaterial getActiveMaterial(Player player) { + ItemStack[] armor = player.getInventory().getArmorContents(); + if (armor.length != 4) return null; + + ValidMaterial found = null; + for (ItemStack piece : armor) { + if (!ArmorUtils.isArmor(piece)) return null; + + ItemMeta meta = piece.getItemMeta(); + if (!(meta instanceof ArmorMeta armorMeta)) return null; + if (!armorMeta.hasTrim()) return null; + + ArmorTrim trim = armorMeta.getTrim(); + if (trim == null) return null; + + ValidMaterial vm = ValidMaterial.validate(trim.getMaterial()); + if (vm == null) return null; + + if (found == null) { + found = vm; + } else if (found != vm) { + return null; + } + } + return found; + } + + private boolean shouldHide(Player player) { + return getStorage().disabledOwnParticles.contains(player.getUniqueId().toString()); + } + + private Set getViewers(Player player) { + if (shouldHide(player)) return new HashSet<>(); + return Bukkit.getOnlinePlayers().stream().filter(viewer-> !getStorage().disabledGlobalParticles.contains(viewer.getUniqueId().toString())).collect(Collectors.toSet()); + } + + private static class ActiveTrim { + final ValidMaterial material; + long ticks; + long lastMoveTimestamp; + Location lastCheckedLocation; + + ActiveTrim(ValidMaterial material, Location initialLocation) { + this.material = material; + this.ticks = 0; + this.lastMoveTimestamp = System.currentTimeMillis(); + this.lastCheckedLocation = initialLocation; + } + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/trims/ValidArmorType.java b/src/main/java/me/trouper/clonedupecore/server/trims/ValidArmorType.java new file mode 100644 index 0000000..424c72b --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trims/ValidArmorType.java @@ -0,0 +1,47 @@ +package me.trouper.clonedupecore.server.trims; + +import org.bukkit.Material; + +public enum ValidArmorType { + LEATHER(Material.LEATHER_HELMET,Material.LEATHER_CHESTPLATE,Material.LEATHER_LEGGINGS,Material.LEATHER_BOOTS), + GOLDEN(Material.GOLDEN_HELMET,Material.GOLDEN_CHESTPLATE,Material.GOLDEN_LEGGINGS,Material.GOLDEN_BOOTS), + CHAINMAIL(Material.CHAINMAIL_HELMET,Material.CHAINMAIL_CHESTPLATE,Material.CHAINMAIL_LEGGINGS,Material.CHAINMAIL_BOOTS), + IRON(Material.IRON_HELMET,Material.IRON_CHESTPLATE,Material.IRON_LEGGINGS,Material.IRON_BOOTS), + DIAMOND(Material.DIAMOND_HELMET,Material.DIAMOND_CHESTPLATE,Material.DIAMOND_LEGGINGS,Material.DIAMOND_BOOTS), + NETHERITE(Material.NETHERITE_HELMET,Material.NETHERITE_CHESTPLATE,Material.NETHERITE_LEGGINGS,Material.NETHERITE_BOOTS); + + private final Material helmet; + private final Material chestplate; + private final Material leggings; + private final Material boots; + + ValidArmorType(Material helmet, Material chestplate, Material leggings, Material boots) { + this.helmet = helmet; + this.chestplate = chestplate; + this.leggings = leggings; + this.boots = boots; + } + + public Material getHelmet() { + return helmet; + } + + public Material getChestplate() { + return chestplate; + } + + public Material getLeggings() { + return leggings; + } + + public Material getBoots() { + return boots; + } + + public static ValidArmorType validate(Material armor) { + for (ValidArmorType value : values()) { + if (value.helmet.equals(armor) || value.chestplate.equals(armor) || value.leggings.equals(armor) || value.boots.equals(armor)) return value; + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/me/trouper/clonedupecore/server/trims/ValidMaterial.java b/src/main/java/me/trouper/clonedupecore/server/trims/ValidMaterial.java new file mode 100644 index 0000000..ea8b1df --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trims/ValidMaterial.java @@ -0,0 +1,39 @@ +package me.trouper.clonedupecore.server.trims; + +import org.bukkit.inventory.meta.trim.TrimMaterial; + +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); + + private final TrimMaterial canonical; + + ValidMaterial(TrimMaterial canonical) { + this.canonical = canonical; + } + + public TrimMaterial getCanonical() { + return canonical; + } + + public static ValidMaterial validate(TrimMaterial material) { + for (ValidMaterial value : ValidMaterial.values()) { + if (!value.getCanonical().equals(material)) continue; + return value; + } + return null; + } + + public static TrimMaterial validate(String name) { + name = name.toUpperCase(); + return ValidMaterial.valueOf(name).getCanonical(); + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/trims/animations/AmethystAnimation.java b/src/main/java/me/trouper/clonedupecore/server/trims/animations/AmethystAnimation.java new file mode 100644 index 0000000..236bc23 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trims/animations/AmethystAnimation.java @@ -0,0 +1,77 @@ +package me.trouper.clonedupecore.server.trims.animations; + +import me.trouper.alias.server.systems.visual.DisplayUtils; +import me.trouper.alias.server.systems.visual.ParticleUtils; +import me.trouper.clonedupecore.server.trims.MaterialAnimation; +import me.trouper.clonedupecore.server.trims.ValidMaterial; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.entity.Player; + +import java.util.Set; + +public class AmethystAnimation extends MaterialAnimation { + private static final Color AMETHYST_COLOR = Color.fromRGB(139, 69, 190); + private static final long LOOP_DURATION = 120L; + + public AmethystAnimation() { + super(ValidMaterial.AMETHYST, LOOP_DURATION); + } + + @Override + public void tickMoving(Player player, Set viewers, long loopTime) { + ParticleUtils.builder() + .type(Particle.BLOCK) + .data(Material.AMETHYST_BLOCK) + .viewers(viewers) + .offset(0.3,0.3,0.3) + .count(5) + .speed(0.05F) + .spawn(player.getLocation().add(0,0.1,0)); + } + + @Override + public void tickStationary(Player player, Set viewers, long loopTime) { + Location playerLoc = player.getLocation().add(0, 0, 0); + + double phase = (loopTime / (double) LOOP_DURATION) * 2 * Math.PI; + double radius = 1.5 + 0.5 * Math.sin(phase * 2); + + DisplayUtils.ring(playerLoc, radius, (point)->{ + ParticleUtils.builder() + .type(Particle.DUST) + .data(new Particle.DustOptions(AMETHYST_COLOR,1F)) + .viewers(viewers) + .spawn(point); + }); + + if (loopTime % 5 == 0) { + for (int i = 0; i < 3; i++) { + Location particleLoc = playerLoc.clone().add( + Math.random() * 2 - 1, + Math.random() * 2, + Math.random() * 2 - 1 + ); + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.ENCHANT) + .spawn(particleLoc); + } + } + + if (loopTime % 20 == 0) { + DisplayUtils.helix(playerLoc.clone().subtract(0, 0.5, 0), 0.8, (loc)->{ + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.DUST) + .data(new Particle.DustOptions(AMETHYST_COLOR,0.5F)) + .spawn(loc); + }, 0.2, 3); + } + } + + @Override + public void onRemove(Player player) {} +} \ No newline at end of file diff --git a/src/main/java/me/trouper/clonedupecore/server/trims/animations/CopperAnimation.java b/src/main/java/me/trouper/clonedupecore/server/trims/animations/CopperAnimation.java new file mode 100644 index 0000000..de239f8 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trims/animations/CopperAnimation.java @@ -0,0 +1,124 @@ +package me.trouper.clonedupecore.server.trims.animations; + +import me.trouper.alias.server.systems.visual.DisplayUtils; +import me.trouper.alias.server.systems.visual.ParticleUtils; +import me.trouper.clonedupecore.server.trims.MaterialAnimation; +import me.trouper.clonedupecore.server.trims.ValidMaterial; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.entity.BlockDisplay; +import org.bukkit.entity.Player; +import org.bukkit.util.Transformation; +import org.joml.AxisAngle4f; +import org.joml.Vector3f; + +import java.util.*; + +public class CopperAnimation extends MaterialAnimation { + + private static final long LOOP_DURATION = 100L; + private final Map> activeDisplays = new HashMap<>(); + + public CopperAnimation() { + super(ValidMaterial.COPPER, LOOP_DURATION); + } + + @Override + public void tickMoving(Player player, Set viewers, long loopTime) { + ParticleUtils.builder() + .type(Particle.BLOCK) + .data(Material.COPPER_BLOCK) + .viewers(viewers) + .offset(0.3,0.3,0.3) + .count(5) + .speed(0.05F) + .spawn(player.getLocation().add(0,0.1,0)); + } + + @Override + public void tickStationary(Player player, Set viewers, long loopTime) { + UUID playerId = player.getUniqueId(); + Location playerLoc = player.getLocation(); + + double angle = (loopTime / (double) LOOP_DURATION) * 2 * Math.PI; + + List displays = activeDisplays.computeIfAbsent(playerId, id -> { + List list = new ArrayList<>(); + for (int i = 0; i < 4; i++) { + Location loc = player.getLocation().add(0, 1, 0); + BlockDisplay display = player.getWorld().spawn(loc, BlockDisplay.class,disp->{ + disp.setVisibleByDefault(false); + viewers.forEach(viewer->{ + viewer.showEntity(main.getPlugin(),disp); + }); + }); + display.setBlock(Material.COPPER_BLOCK.createBlockData()); + display.addScoreboardTag(main.getCommon().getTempTag()); + display.setInterpolationDuration(1); + display.setInterpolationDelay(0); + list.add(display); + } + return list; + }); + + for (int i = 0; i < displays.size(); i++) { + BlockDisplay display = displays.get(i); + + double offsetAngle = angle + (i * Math.PI / 2); + double x = Math.cos(offsetAngle) * 2.0; + double z = Math.sin(offsetAngle) * 2.0; + double y = Math.sin(angle * 2) * 0.5; + + Location blockLoc = playerLoc.clone().add(x, y + 1, z); + display.teleport(blockLoc); + ParticleUtils.builder() + .type(Particle.ELECTRIC_SPARK) + .viewers(viewers) + .spawn(blockLoc); + + float scale = 0.3f + 0.1f * (float) Math.sin(angle * 3); + Transformation transform = new Transformation( + new Vector3f(0, 0, 0), + new AxisAngle4f((float) angle, 0, 1, 0), + new Vector3f(scale, scale, scale), + new AxisAngle4f(0, 0, 0, 1) + ); + display.setTransformation(transform); + } + + if (loopTime % 25 == 0) { + Location groundLoc = player.getLocation(); + DisplayUtils.wave(groundLoc, 4.0, 1.2f, 0.5, (point) -> { + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.BLOCK) + .data(Material.COPPER_BLOCK.createBlockData()) + .spawn(point); + }); + } + + if (loopTime % 15 == 0) { + Location groundLoc = player.getLocation(); + DisplayUtils.wave(groundLoc, 2.0, 0.4f, 0.2, (point) -> { + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.WAX_ON) + .spawn(point); + }); + } + } + + @Override + public void onRemove(Player player) { + List displays = activeDisplays.remove(player.getUniqueId()); + if (displays != null) { + for (BlockDisplay display : displays) { + if (!display.isDead()) { + display.remove(); + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/me/trouper/clonedupecore/server/trims/animations/DiamondAnimation.java b/src/main/java/me/trouper/clonedupecore/server/trims/animations/DiamondAnimation.java new file mode 100644 index 0000000..c0e91a8 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trims/animations/DiamondAnimation.java @@ -0,0 +1,126 @@ +package me.trouper.clonedupecore.server.trims.animations; + +import me.trouper.alias.server.systems.visual.DisplayUtils; +import me.trouper.alias.server.systems.visual.ParticleUtils; +import me.trouper.clonedupecore.server.trims.MaterialAnimation; +import me.trouper.clonedupecore.server.trims.ValidMaterial; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.entity.BlockDisplay; +import org.bukkit.entity.Player; +import org.bukkit.util.Transformation; +import org.joml.AxisAngle4f; +import org.joml.Vector3f; + +import java.util.*; + +public class DiamondAnimation extends MaterialAnimation { + private static final Color DIAMOND_COLOR = Color.fromRGB(185, 242, 255); + private static final long LOOP_DURATION = 160L; + + private final Map> activeDisplays = new HashMap<>(); + + public DiamondAnimation() { + super(ValidMaterial.DIAMOND, LOOP_DURATION); + } + + @Override + public void tickMoving(Player player, Set viewers, long loopTime) { + ParticleUtils.builder() + .type(Particle.BLOCK) + .data(Material.DIAMOND_BLOCK) + .viewers(viewers) + .offset(0.3,0.3,0.3) + .count(5) + .speed(0.05F) + .spawn(player.getLocation().add(0,0.1,0)); + } + + @Override + public void tickStationary(Player player, Set viewers, long loopTime) { + UUID playerId = player.getUniqueId(); + Location playerLoc = player.getLocation().add(0, 1.5, 0); + double mainAngle = (loopTime / (double) LOOP_DURATION) * 2 * Math.PI; + + List displays = activeDisplays.computeIfAbsent(playerId, id -> { + List list = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + Location initialLoc = player.getLocation().add(0, 1, 0); + BlockDisplay display = player.getWorld().spawn(initialLoc, BlockDisplay.class,disp->{ + disp.setVisibleByDefault(false); + viewers.forEach(viewer->{ + viewer.showEntity(main.getPlugin(),disp); + }); + }); + + display.setBlock(Material.DIAMOND_BLOCK.createBlockData()); + display.addScoreboardTag(main.getCommon().getTempTag()); + display.setInterpolationDuration(1); + display.setInterpolationDelay(0); + list.add(display); + } + return list; + }); + + for (int orbit = 0; orbit < displays.size(); orbit++) { + BlockDisplay display = displays.get(orbit); + + double orbitAngle = mainAngle * (1 + orbit * 0.3) + orbit * Math.PI * 2 / 3; + double radius = 1.8 - orbit * 0.2; + double height = Math.sin(orbitAngle * 2) * 0.8; + + double x = Math.cos(orbitAngle) * radius; + double z = Math.sin(orbitAngle) * radius; + + Location diamondLoc = playerLoc.clone().add(x, height, z); + display.teleport(diamondLoc); + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.DUST) + .data(new Particle.DustOptions(DIAMOND_COLOR,2F)) + .speed(0.1F) + .spawn(diamondLoc); + + float rotationAngle = (float)(orbitAngle * 2); + float scale = 0.25f + 0.05f * (float)Math.sin(mainAngle * 4); + + Transformation transform = new Transformation( + new Vector3f(0, 0, 0), + new AxisAngle4f(rotationAngle, 1, 1, 1), + new Vector3f(scale, scale, scale), + new AxisAngle4f(0, 0, 0, 1) + ); + display.setTransformation(transform); + + } + + if (loopTime % 3 == 0) { + for (int i = 0; i < 5; i++) { + Location sparkLoc = playerLoc.clone().add( + (Math.random() - 0.5) * 4, + (Math.random() - 0.5) * 3, + (Math.random() - 0.5) * 4 + ); + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.ELECTRIC_SPARK) + .speed(0.1F) + .spawn(sparkLoc); + } + } + } + + @Override + public void onRemove(Player player) { + List displays = activeDisplays.remove(player.getUniqueId()); + if (displays != null) { + for (BlockDisplay display : displays) { + if (!display.isDead()) { + display.remove(); + } + } + } + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/trims/animations/EmeraldAnimation.java b/src/main/java/me/trouper/clonedupecore/server/trims/animations/EmeraldAnimation.java new file mode 100644 index 0000000..440522f --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trims/animations/EmeraldAnimation.java @@ -0,0 +1,85 @@ +package me.trouper.clonedupecore.server.trims.animations; + +import me.trouper.alias.server.systems.visual.DisplayUtils; +import me.trouper.alias.server.systems.visual.ParticleUtils; +import me.trouper.clonedupecore.server.trims.MaterialAnimation; +import me.trouper.clonedupecore.server.trims.ValidMaterial; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +import java.util.Set; + +public class EmeraldAnimation extends MaterialAnimation { + private static final Color EMERALD_COLOR = Color.fromRGB(80, 220, 92); + private static final long LOOP_DURATION = 140L; + + public EmeraldAnimation() { + super(ValidMaterial.EMERALD, LOOP_DURATION); + } + + @Override + public void tickMoving(Player player, Set viewers, long loopTime) { + ParticleUtils.builder() + .type(Particle.BLOCK) + .data(Material.EMERALD_BLOCK) + .viewers(viewers) + .offset(0.3,0.3,0.3) + .count(5) + .speed(0.05F) + .spawn(player.getLocation().add(0,0.1,0)); + } + + @Override + public void tickStationary(Player player, Set viewers, long loopTime) { + Location playerLoc = player.getLocation(); + + double time = loopTime / (double) LOOP_DURATION; + double spiralAngle = time * 4 * Math.PI; + + for (int i = 0; i < 6; i++) { + double heightOffset = (i / 6.0) * 3 - 1.5; + double radius = 1.2 + 0.3 * Math.sin(spiralAngle + i); + double angle = spiralAngle + i * Math.PI / 3; + + double x = Math.cos(angle) * radius; + double z = Math.sin(angle) * radius; + + Location spiralLoc = playerLoc.clone().add(x, heightOffset + 1, z); + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.DUST) + .data(new Particle.DustOptions(EMERALD_COLOR,0.5F)) + .spawn(spiralLoc); + } + + double pulsePhase = (loopTime % 30) / 30.0 * 2 * Math.PI; + double pulseRadius = 2.0 + 0.5 * Math.sin(pulsePhase); + DisplayUtils.ring(playerLoc, pulseRadius,0.9,(loc)->{ + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.HAPPY_VILLAGER) + .spawn(loc); + }); + + if (loopTime % 8 == 0) { + for (int i = 0; i < 3; i++) { + Location natureLoc = playerLoc.clone().add( + (Math.random() - 0.5) * 3, + Math.random() * 2, + (Math.random() - 0.5) * 3 + ); + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.COMPOSTER) + .spawn(natureLoc); + } + } + } + + @Override + public void onRemove(Player player) {} +} \ No newline at end of file diff --git a/src/main/java/me/trouper/clonedupecore/server/trims/animations/GoldAnimation.java b/src/main/java/me/trouper/clonedupecore/server/trims/animations/GoldAnimation.java new file mode 100644 index 0000000..d3b3ce4 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trims/animations/GoldAnimation.java @@ -0,0 +1,105 @@ +package me.trouper.clonedupecore.server.trims.animations; + +import me.trouper.alias.server.systems.visual.DisplayUtils; +import me.trouper.alias.server.systems.visual.ParticleUtils; +import me.trouper.clonedupecore.server.trims.MaterialAnimation; +import me.trouper.clonedupecore.server.trims.ValidMaterial; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.entity.Player; + +import java.util.Set; + +public class GoldAnimation extends MaterialAnimation { + private static final Color GOLD_COLOR = Color.fromRGB(255, 215, 0); + private static final long LOOP_DURATION = 80L; + + public GoldAnimation() { + super(ValidMaterial.GOLD, LOOP_DURATION); + } + + @Override + public void tickMoving(Player player, Set viewers, long loopTime) { + DisplayUtils.ring(player.getLocation().clone().add(0, 2.1, 0), 0.3, 0.5f,(point)->{ + ParticleUtils.builder() + .type(Particle.DUST) + .data(new Particle.DustOptions(GOLD_COLOR,0.3F)) + .viewers(viewers) + .spawn(point); + }); + ParticleUtils.builder() + .type(Particle.BLOCK) + .data(Material.GOLD_BLOCK) + .viewers(viewers) + .offset(0.3,0.3,0.3) + .count(5) + .speed(0.05F) + .spawn(player.getLocation().add(0,0.1,0)); + } + + @Override + public void tickStationary(Player player, Set viewers, long loopTime) { + Location playerLoc = player.getLocation(); + + double crownAngle = (loopTime / (double) LOOP_DURATION) * 2 * Math.PI; + + DisplayUtils.ring(player.getLocation().clone().add(0, 2.1, 0), 0.3, 0.5f,(point)->{ + ParticleUtils.builder() + .type(Particle.DUST) + .data(new Particle.DustOptions(GOLD_COLOR,0.5F)) + .viewers(viewers) + .spawn(point); + }); + + if (loopTime % 4 == 0) { + for (int i = 0; i < 4; i++) { + Location fallLoc = playerLoc.clone().add( + (Math.random() - 0.5) * 4, + Math.random() * 3 + 2, + (Math.random() - 0.5) * 4 + ); + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.BLOCK) + .data(Material.GOLD_BLOCK.createBlockData()) + .offset(0.2,0.2,0.2) + .speed(0.01F) + .spawn(fallLoc); + } + } + + double wealthPhase = (loopTime % 60) / 60.0 * 2 * Math.PI; + for (int ring = 0; ring < 3; ring++) { + double ringRadius = 0.5 + ring * 0.5; + double ringPhase = wealthPhase + ring * Math.PI / 2; + double ringHeight = Math.sin(ringPhase) * 0.3; + + DisplayUtils.ring(playerLoc.clone().add(0, ringHeight + 0.6, 0), ringRadius, 0.8f,(loc)->{ + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.DUST) + .data(new Particle.DustOptions(GOLD_COLOR,0.5F)) + .spawn(loc); + }); + } + + if (loopTime % 6 == 0) { + Location sparkleLoc = playerLoc.clone().add( + Math.cos(crownAngle * 3) * 2, + Math.sin(crownAngle * 2) * 1.5, + Math.sin(crownAngle * 3) * 2 + ); + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.ELECTRIC_SPARK) + .offset(0.2,0.2,0.2) + .speed(0.3F) + .spawn(sparkleLoc); + } + } + + @Override + public void onRemove(Player player) {} +} \ No newline at end of file diff --git a/src/main/java/me/trouper/clonedupecore/server/trims/animations/IronAnimation.java b/src/main/java/me/trouper/clonedupecore/server/trims/animations/IronAnimation.java new file mode 100644 index 0000000..4078fd1 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trims/animations/IronAnimation.java @@ -0,0 +1,150 @@ +package me.trouper.clonedupecore.server.trims.animations; + +import me.trouper.alias.server.systems.visual.DisplayUtils; +import me.trouper.alias.server.systems.visual.ParticleUtils; +import me.trouper.clonedupecore.server.trims.MaterialAnimation; +import me.trouper.clonedupecore.server.trims.ValidMaterial; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.entity.ItemDisplay; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Transformation; +import org.joml.AxisAngle4f; +import org.joml.Vector3f; + +import java.util.*; + +public class IronAnimation extends MaterialAnimation { + private static final Color IRON_COLOR = Color.fromRGB(145, 145, 145); + private static final long LOOP_DURATION = 60L; + private static final List IRON_ITEMS = List.of( + Material.IRON_AXE, + Material.IRON_SWORD, + Material.IRON_PICKAXE, + Material.IRON_SHOVEL, + Material.IRON_HOE, + Material.IRON_INGOT + ); + + private final Map> activeDisplays = new HashMap<>(); + + public IronAnimation() { + super(ValidMaterial.IRON, LOOP_DURATION); + } + + @Override + public void tickMoving(Player player, Set viewers, long loopTime) { + ParticleUtils.builder() + .type(Particle.BLOCK) + .data(Material.IRON_BLOCK) + .viewers(viewers) + .offset(0.3,0.3,0.3) + .count(5) + .speed(0.05F) + .spawn(player.getLocation().add(0,0.1,0)); + } + + @Override + public void tickStationary(Player player, Set viewers, long loopTime) { + UUID playerId = player.getUniqueId(); + Location playerLoc = player.getLocation(); + + double shieldAngle = (loopTime / (double) LOOP_DURATION) * 2 * Math.PI; + + List items = activeDisplays.computeIfAbsent(playerId, id -> { + List list = new ArrayList<>(); + for (int i = 0; i < 4; i++) { + Location initialLoc = player.getLocation().add(0, 1, 0); + ItemDisplay shield = player.getWorld().spawn(initialLoc, ItemDisplay.class,disp->{ + disp.setVisibleByDefault(false); + viewers.forEach(viewer->{ + viewer.showEntity(main.getPlugin(),disp); + }); + }); + shield.setItemStack(new ItemStack(main.randomizer().getRandomElement(IRON_ITEMS))); + shield.addScoreboardTag(main.getCommon().getTempTag()); + shield.setInterpolationDuration(1); + shield.setInterpolationDelay(0); + list.add(shield); + } + return list; + }); + + for (int i = 0; i < items.size(); i++) { + ItemDisplay item = items.get(i); + + double angle = shieldAngle + (i * Math.PI / 2); + double distance = 2.2 + 0.3 * Math.sin(shieldAngle * 3); + + double x = Math.cos(angle) * distance; + double z = Math.sin(angle) * distance; + double y = 1.0 + Math.sin(shieldAngle * 2 + i) * 0.3; + + Location shieldLoc = playerLoc.clone().add(x, y, z); + item.teleport(shieldLoc); + + float rotY = (float) angle; + float scale = 0.8f; + + Transformation transform = new Transformation( + new Vector3f(0, 0, 0), + new AxisAngle4f(rotY, 0, 1, 0), + new Vector3f(scale, scale, scale), + new AxisAngle4f(0, 0, 0, 1) + ); + + item.setTransformation(transform); + } + + if (loopTime % 10 == 0) { + for (int i = 0; i < 6; i++) { + Location firworkLoc = playerLoc.clone().add( + (Math.random() - 0.5) * 3, + Math.random() * 2.5, + (Math.random() - 0.5) * 3 + ); + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.FIREWORK) + .speed(0.1F) + .spawn(firworkLoc); + } + } + + if (loopTime % 30 == 0) { + DisplayUtils.wave(playerLoc, 3.5, loc->{ + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.DUST) + .data(new Particle.DustOptions(IRON_COLOR,1F)) + .spawn(loc); + }, 0.6); + } + + if (loopTime % 5 == 0) { + Location dustLoc = playerLoc.clone().add(0, 0.1, 0); + DisplayUtils.ring(dustLoc, 1.8, 0.18,(loc)->{ + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.BLOCK) + .data(Material.IRON_BLOCK) + .spawn(loc); + }); + } + } + + @Override + public void onRemove(Player player) { + List displays = activeDisplays.remove(player.getUniqueId()); + if (displays != null) { + for (ItemDisplay display : displays) { + if (!display.isDead()) { + display.remove(); + } + } + } + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/trims/animations/LapisAnimation.java b/src/main/java/me/trouper/clonedupecore/server/trims/animations/LapisAnimation.java new file mode 100644 index 0000000..075c951 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trims/animations/LapisAnimation.java @@ -0,0 +1,139 @@ +package me.trouper.clonedupecore.server.trims.animations; + +import me.trouper.alias.server.systems.visual.DisplayUtils; +import me.trouper.alias.server.systems.visual.ParticleUtils; +import me.trouper.clonedupecore.server.trims.MaterialAnimation; +import me.trouper.clonedupecore.server.trims.ValidMaterial; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.entity.Player; + +import java.util.Set; + +public class LapisAnimation extends MaterialAnimation { + private static final Color LAPIS_COLOR = Color.fromRGB(31, 64, 181); + private static final long LOOP_DURATION = 120L; + + public LapisAnimation() { + super(ValidMaterial.LAPIS, LOOP_DURATION); + } + + @Override + public void tickMoving(Player player, Set viewers, long loopTime) { + DisplayUtils.ring(player.getLocation().clone().add(0, 2.1, 0), 0.3, 0.5f,(point)->{ + ParticleUtils.builder() + .type(Particle.ENCHANT) + .viewers(viewers) + .spawn(point); + }); + ParticleUtils.builder() + .type(Particle.BLOCK) + .data(Material.LAPIS_BLOCK) + .viewers(viewers) + .offset(0.3,0.3,0.3) + .count(5) + .speed(0.05F) + .spawn(player.getLocation().clone().add(0,0.1,0)); + } + + @Override + public void tickStationary(Player player, Set viewers, long loopTime) { + Location playerLoc = player.getLocation(); + + double magicAngle = (loopTime / (double) LOOP_DURATION) * 2 * Math.PI; + + DisplayUtils.ring(player.getLocation().clone().add(0, 2.1, 0), 0.3, 0.5f,(point)->{ + ParticleUtils.builder() + .type(Particle.ENCHANT) + .viewers(viewers) + .spawn(point); + }); + + for (int circle = 0; circle < 3; circle++) { + double circleHeight = circle * 0.8 - 0.8; + double circleAngle = magicAngle * (1 + circle * 0.5); + double circleRadius = 1.5 - circle * 0.2; + + Location circleCenter = playerLoc.clone().add(0, circleHeight, 0); + + for (int segment = 0; segment < 6; segment++) { + int startAngle = (int)(Math.toDegrees(circleAngle) + segment * 60); + int endAngle = startAngle + 30; + + DisplayUtils.arc(circleCenter, circleRadius, startAngle, endAngle, 0.2, (loc)->{ + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.DUST) + .data(new Particle.DustOptions(LAPIS_COLOR,0.5F)) + .spawn(loc); + }); + } + } + + if (loopTime % 8 == 0) { + for (int i = 0; i < 4; i++) { + double runeAngle = magicAngle + i * Math.PI / 2; + Location runeLoc = playerLoc.clone().add( + Math.cos(runeAngle) * 2.5, + Math.sin(magicAngle * 2) * 0.5 + 1, + Math.sin(runeAngle) * 2.5 + ); + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.ENCHANT) + .count(3) + .offset(0.1,0.1,0.1) + .spawn(runeLoc); + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.ENCHANTED_HIT) + .count(3) + .offset(0.1,0.1,0.1) + .spawn(runeLoc); + } + } + + if (loopTime % 15 == 0) { + DisplayUtils.vortex(playerLoc.clone().subtract(0, 0.5, 0), 0.8,(loc)->{ + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.DUST) + .data(new Particle.DustOptions(LAPIS_COLOR,0.5F)) + .spawn(loc); + }, + 0.05, 0.25, 4); + } + + if (loopTime % 12 == 0) { + for (int i = 0; i < 5; i++) { + Location sparkLoc = playerLoc.clone().add( + (Math.random() - 0.5) * 3, + Math.random() * 2, + (Math.random() - 0.5) * 3 + ); + sparkLoc.getWorld().spawnParticle(Particle.ELECTRIC_SPARK, sparkLoc, 1, 0, 0, 0, 0); + } + } + + double pulsePhase = (loopTime % 40) / 40.0 * 2 * Math.PI; + double pulseIntensity = 0.5 + 0.5 * Math.sin(pulsePhase); + Color pulseLapis = Color.fromRGB( + (int)(31 * pulseIntensity + 100 * (1 - pulseIntensity)), + (int)(64 * pulseIntensity + 150 * (1 - pulseIntensity)), + (int)(181 * pulseIntensity + 255 * (1 - pulseIntensity)) + ); + + DisplayUtils.ring(playerLoc, 2.2, (loc)->{ + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.DUST) + .data(new Particle.DustOptions(pulseLapis,0.5F)) + .spawn(loc); + }); + } + + @Override + public void onRemove(Player player) {} +} \ No newline at end of file diff --git a/src/main/java/me/trouper/clonedupecore/server/trims/animations/NetheriteAnimation.java b/src/main/java/me/trouper/clonedupecore/server/trims/animations/NetheriteAnimation.java new file mode 100644 index 0000000..b3bab20 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trims/animations/NetheriteAnimation.java @@ -0,0 +1,147 @@ +package me.trouper.clonedupecore.server.trims.animations; + +import me.trouper.alias.server.systems.visual.DisplayUtils; +import me.trouper.alias.server.systems.visual.ParticleUtils; +import me.trouper.clonedupecore.server.trims.MaterialAnimation; +import me.trouper.clonedupecore.server.trims.ValidMaterial; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.entity.BlockDisplay; +import org.bukkit.entity.Player; +import org.bukkit.util.Transformation; +import org.joml.AxisAngle4f; +import org.joml.Vector3f; + +import java.util.*; + +public class NetheriteAnimation extends MaterialAnimation { + private static final Color NETHERITE_COLOR = Color.fromRGB(68, 58, 59); + private static final long LOOP_DURATION = 200L; + + private final Map> activeDisplays = new HashMap<>(); + + public NetheriteAnimation() { + super(ValidMaterial.NETHERITE, LOOP_DURATION); + } + + @Override + public void tickMoving(Player player, Set viewers, long loopTime) { + ParticleUtils.builder() + .type(Particle.BLOCK) + .data(Material.LAVA) + .viewers(viewers) + .offset(0.3,0.3,0.3) + .count(5) + .speed(0.05F) + .spawn(player.getLocation().clone().add(0,0.1,0)); + } + + @Override + public void tickStationary(Player player, Set viewers, long loopTime) { + UUID playerId = player.getUniqueId(); + Location playerLoc = player.getLocation(); + + double angle = (loopTime / (double) LOOP_DURATION) * 2 * Math.PI; + + List displays = activeDisplays.computeIfAbsent(playerId, id -> { + List list = new ArrayList<>(); + for (int i = 0; i < 4; i++) { + Location initialLoc = player.getLocation().add(0, 1, 0); + BlockDisplay display = player.getWorld().spawn(initialLoc, BlockDisplay.class,disp->{ + disp.setVisibleByDefault(false); + viewers.forEach(viewer->{ + viewer.showEntity(main.getPlugin(),disp); + }); + }); + display.setBlock(Material.NETHERITE_BLOCK.createBlockData()); + display.addScoreboardTag(main.getCommon().getTempTag()); + display.setInterpolationDuration(1); + display.setInterpolationDelay(0); + list.add(display); + } + return list; + }); + + for (int i = 0; i < displays.size(); i++) { + BlockDisplay display = displays.get(i); + + double offsetAngle = angle + (i * Math.PI / 2); + double x = Math.cos(offsetAngle) * 2.0; + double z = Math.sin(offsetAngle) * 2.0; + double y = Math.sin(angle * 2) * 0.5; + + Location blockLoc = playerLoc.clone().add(x, y + 1, z); + display.teleport(blockLoc); + + float scale = 0.5f + 0.1f * (float) Math.sin(angle * 3); + Transformation transform = new Transformation( + new Vector3f(0, 0, 0), + new AxisAngle4f((float) angle, 0, 1, 0), + new Vector3f(scale, scale, scale), + new AxisAngle4f(0, 0, 0, 1) + ); + display.setTransformation(transform); + } + + if (loopTime % 6 == 0) { + for (int i = 0; i < 8; i++) { + Location fireLoc = playerLoc.clone().add( + (Math.random() - 0.5) * 6, + Math.random() * 0.5, + (Math.random() - 0.5) * 6 + ); + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.SOUL_FIRE_FLAME) + .spawn(fireLoc); + } + } + + if (loopTime % 50 == 0) { + DisplayUtils.fanWaveRandom(playerLoc, 4.5, 8, (loc)->{ + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.DUST) + .data(new Particle.DustOptions(NETHERITE_COLOR,0.8F)) + .spawn(loc); + }, 0.3); + } + + double auraPhase = (loopTime % 60) / 60.0 * 2 * Math.PI; + double auraRadius = 2.8 + 0.7 * Math.sin(auraPhase * 2); + DisplayUtils.ring(playerLoc.clone().add(0, 0.2, 0), auraRadius,0.8, (loc)->{ + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.FLAME) + .spawn(loc); + }); + + if (loopTime % 10 == 0) { + for (int i = 0; i < 6; i++) { + Location emberLoc = playerLoc.clone().add( + (Math.random() - 0.5) * 5, + Math.random() * 3 + 1, + (Math.random() - 0.5) * 5 + ); + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.LAVA) + .spawn(emberLoc); + } + } + } + + @Override + public void onRemove(Player player) { + List displays = activeDisplays.remove(player.getUniqueId()); + if (displays != null) { + for (BlockDisplay display : displays) { + if (!display.isDead()) { + display.remove(); + } + } + } + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/trims/animations/QuartzAnimation.java b/src/main/java/me/trouper/clonedupecore/server/trims/animations/QuartzAnimation.java new file mode 100644 index 0000000..25e8f36 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trims/animations/QuartzAnimation.java @@ -0,0 +1,87 @@ +package me.trouper.clonedupecore.server.trims.animations; + +import me.trouper.alias.server.systems.TaskManager; +import me.trouper.alias.server.systems.visual.DisplayUtils; +import me.trouper.alias.server.systems.visual.ParticleUtils; +import me.trouper.clonedupecore.server.trims.MaterialAnimation; +import me.trouper.clonedupecore.server.trims.ValidMaterial; +import org.bukkit.*; +import org.bukkit.entity.BlockDisplay; +import org.bukkit.entity.Player; +import org.bukkit.util.Transformation; +import org.joml.AxisAngle4f; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +import java.util.Set; + +public class QuartzAnimation extends MaterialAnimation { + private static final Color QUARTZ_COLOR = Color.fromRGB(255, 252, 245); + private static final long LOOP_DURATION = 100L; + + public QuartzAnimation() { + super(ValidMaterial.QUARTZ, LOOP_DURATION); + } + + @Override + public void tickMoving(Player player, Set viewers, long loopTime) { + ParticleUtils.builder() + .type(Particle.BLOCK) + .data(Material.QUARTZ_BLOCK) + .viewers(viewers) + .offset(0.3,0.3,0.3) + .count(5) + .speed(0.05F) + .spawn(player.getLocation().clone().add(0,0.1,0)); + } + + @Override + public void tickStationary(Player player, Set viewers, long loopTime) { + Location playerLoc = player.getLocation(); + + double angle = (loopTime / (double) LOOP_DURATION) * 2 * Math.PI; + for (int i = 0; i < 4; i++) { + double offsetAngle = angle + (i * Math.PI / 2); + double x = Math.cos(offsetAngle) * 2.0; + double z = Math.sin(offsetAngle) * 2.0; + double y = Math.sin(angle * 2) * 0.5; + + Location particleOrbit = playerLoc.clone().add(x, y + 1, z); + ParticleUtils.builder() + .type(Particle.BLOCK) + .data(Material.QUARTZ_BLOCK) + .count(1) + .speed(0) + .spawn(particleOrbit); + } + + if (loopTime % 4 == 0) { + for (int i = 0; i < 4; i++) { + Location sparkleLoc = playerLoc.clone().add( + (Math.random() - 0.5) * 4, + (Math.random() - 0.5) * 3, + (Math.random() - 0.5) * 4 + ); + sparkleLoc.getWorld().spawnParticle(Particle.END_ROD, sparkleLoc, 1, 0, 0, 0, 0.02); + } + } + + double purifyPhase = (loopTime % 50) / 50.0 * 2 * Math.PI; + for (int ring = 0; ring < 2; ring++) { + double ringRadius = 1.8 + ring * 0.8; + double ringPhase = purifyPhase + ring * Math.PI; + double ringHeight = Math.sin(ringPhase) * 0.4; + + DisplayUtils.ring(playerLoc.clone().add(0, ringHeight, 0), ringRadius,(point)->{ + ParticleUtils.builder() + .type(Particle.DUST) + .data(new Particle.DustOptions(QUARTZ_COLOR,1F)) + .viewers(viewers) + .spawn(point); + }); + } + } + + @Override + public void onRemove(Player player) {} +} \ No newline at end of file diff --git a/src/main/java/me/trouper/clonedupecore/server/trims/animations/RedstoneAnimation.java b/src/main/java/me/trouper/clonedupecore/server/trims/animations/RedstoneAnimation.java new file mode 100644 index 0000000..ba2c561 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trims/animations/RedstoneAnimation.java @@ -0,0 +1,128 @@ +package me.trouper.clonedupecore.server.trims.animations; + +import me.trouper.alias.server.systems.visual.DisplayUtils; +import me.trouper.alias.server.systems.visual.ParticleUtils; +import me.trouper.clonedupecore.server.trims.MaterialAnimation; +import me.trouper.clonedupecore.server.trims.ValidMaterial; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.entity.Player; + +import java.util.Set; + +public class RedstoneAnimation extends MaterialAnimation { + private static final Color REDSTONE_COLOR = Color.fromRGB(255, 0, 0); + private static final Color REDSTONE_DIM = Color.fromRGB(120, 0, 0); + private static final long LOOP_DURATION = 80L; // 4 seconds + + public RedstoneAnimation() { + super(ValidMaterial.REDSTONE, LOOP_DURATION); + } + + @Override + public void tickMoving(Player player, Set viewers, long loopTime) { + ParticleUtils.builder() + .type(Particle.BLOCK) + .data(Material.REDSTONE_BLOCK) + .viewers(viewers) + .offset(0.3,0.3,0.3) + .count(5) + .speed(0.05F) + .spawn(player.getLocation().clone().add(0,0.1,0)); + } + + @Override + public void tickStationary(Player player, Set viewers, long loopTime) { + Location playerLoc = player.getLocation(); + + double circuitPhase = (loopTime / (double) LOOP_DURATION) * 2 * Math.PI; + boolean isPowered = Math.sin(circuitPhase * 4) > 0; + Color currentColor = isPowered ? REDSTONE_COLOR : REDSTONE_DIM; + + double pathAngle = circuitPhase * 0.5; + for (int path = 0; path < 8; path++) { + double lineAngle = pathAngle + path * Math.PI / 4; + double startRadius = 0.8; + double endRadius = 2.5; + + for (double r = startRadius; r <= endRadius; r += 0.2) { + double x = Math.cos(lineAngle) * r; + double z = Math.sin(lineAngle) * r; + Location lineLoc = playerLoc.clone().add(x, 0, z); + + boolean sectionPowered = isPowered && ((int)(r * 5) % 3 != 0); + Color sectionColor = sectionPowered ? REDSTONE_COLOR : REDSTONE_DIM; + + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.DUST) + .data(new Particle.DustOptions(sectionColor,0.5F)) + .spawn(lineLoc); + } + } + + if (isPowered && loopTime % 8 == 0) { + for (int torch = 0; torch < 4; torch++) { + double torchAngle = circuitPhase + torch * Math.PI / 2; + Location torchLoc = playerLoc.clone().add( + Math.cos(torchAngle) * 2.2, + 0.5, + Math.sin(torchAngle) * 2.2 + ); + + DisplayUtils.beam(torchLoc, + (loc)->{ + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.PORTAL) + .spawn(loc); + }, + 0.1, 1); + } + } + + if (loopTime % 30 == 0) { + DisplayUtils.wave(playerLoc.clone(), 3.0, 0.8f, 0.4, (loc)->{ + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.ENCHANTED_HIT) + .spawn(loc); + }); + } + + if (loopTime % 6 == 0) { + for (int i = 0; i < 6; i++) { + Location dustLoc = playerLoc.clone().add( + (Math.random() - 0.5) * 3, + (Math.random() - 0.5) * 2, + (Math.random() - 0.5) * 3 + ); + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.DUST) + .data(new Particle.DustOptions(currentColor,0.5F)) + .spawn(dustLoc); + } + } + + double coreIntensity = isPowered ? 1.0 : 0.3; + Color coreColor = Color.fromRGB( + (int)(255 * coreIntensity), + (int)(50 * coreIntensity), + (int)(50 * coreIntensity) + ); + + DisplayUtils.ring(playerLoc, 1.2, 0.2f,(point)->{ + ParticleUtils.builder() + .viewers(viewers) + .type(Particle.DUST) + .data(new Particle.DustOptions(coreColor,0.5F)) + .spawn(point); + }); + } + + @Override + public void onRemove(Player player) {} +} \ No newline at end of file diff --git a/src/main/java/me/trouper/clonedupecore/server/trolls/CameraTroll.java b/src/main/java/me/trouper/clonedupecore/server/trolls/CameraTroll.java new file mode 100644 index 0000000..dc9e02f --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trolls/CameraTroll.java @@ -0,0 +1,72 @@ +package me.trouper.clonedupecore.server.trolls; + +import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes; +import com.github.retrooper.packetevents.protocol.player.GameMode; +import com.github.retrooper.packetevents.protocol.player.User; +import com.github.retrooper.packetevents.protocol.world.Difficulty; +import com.github.retrooper.packetevents.protocol.world.dimension.DimensionTypes; +import com.github.retrooper.packetevents.util.Vector3d; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerCamera; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerRespawn; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSpawnEntity; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerUpdateHealth; +import io.github.retrooper.packetevents.util.SpigotConversionUtil; +import me.trouper.clonedupecore.utils.PacketUtils; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.UUID; + +public class CameraTroll implements TrollFeature { + + @Override + public String getName() { + return "camera"; + } + + @Override + public Rating getRating() { + return Rating.SAFE; + } + + @Override + public String getDescription() { + return "Sets the player's camera to a random mob."; + } + + @Override + public void execute(CommandSender sender, Player target) { + User user = PacketUtils.getUser(target); + int entityID = target.getWorld().getEntityCount()+1; + WrapperPlayServerSpawnEntity spawnEntity = new WrapperPlayServerSpawnEntity( + entityID, UUID.randomUUID(), EntityTypes.ENDERMAN, SpigotConversionUtil.fromBukkitLocation(target.getLocation()),0,0,new Vector3d() + ); + WrapperPlayServerCamera camera = new WrapperPlayServerCamera(entityID); + Difficulty diff = Difficulty.valueOf(target.getWorld().getDifficulty().toString()); + GameMode gm = GameMode.valueOf(target.getGameMode().toString()); + WrapperPlayServerRespawn respawn = new WrapperPlayServerRespawn( + DimensionTypes.THE_END, + target.getWorld().getName(), + diff, + 1L, + gm, + null, + false, + false, + true, + null, + null, + null + ); + WrapperPlayServerUpdateHealth health = new WrapperPlayServerUpdateHealth(Float.parseFloat("" + target.getHealth()),target.getFoodLevel(),target.getSaturation()); + + user.sendPacket(spawnEntity); + user.sendPacket(camera); + user.sendPacket(health); + } + + @Override + public void stop(CommandSender sender, Player target) { + + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/trolls/DragTrollWand.java b/src/main/java/me/trouper/clonedupecore/server/trolls/DragTrollWand.java new file mode 100644 index 0000000..ee3837f --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trolls/DragTrollWand.java @@ -0,0 +1,267 @@ +package me.trouper.clonedupecore.server.trolls; + +import me.trouper.alias.server.systems.AbstractWand; +import me.trouper.alias.server.systems.Text; +import me.trouper.alias.server.systems.tracing.BlockDisplayRaytracer; +import me.trouper.alias.server.systems.tracing.CustomDisplayRaytracer; +import me.trouper.alias.server.systems.tracing.Point; +import me.trouper.alias.server.systems.visual.DisplayUtils; +import me.trouper.alias.utils.ItemBuilder; +import me.trouper.alias.utils.SoundPlayer; +import me.trouper.clonedupecore.utils.PlayerUtils; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.*; +import org.bukkit.command.CommandSender; +import org.bukkit.damage.DamageSource; +import org.bukkit.damage.DamageType; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitTask; +import org.bukkit.util.Vector; + +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicInteger; + +public class DragTrollWand extends AbstractWand implements TrollFeature { + + @Override + public String getName() { + return "drag"; + } + + @Override + public Rating getRating() { + return Rating.DAMAGING; + } + + @Override + public String getDescription() { + return "Gets a wand which you can use to controll players"; + } + + public static final ItemStack DRAG_WAND = ItemBuilder.of(Material.BREEZE_ROD) + .displayName(Component.text("Drag Wand", NamedTextColor.DARK_AQUA).decoration(TextDecoration.ITALIC,false).decoration(TextDecoration.BOLD,true)) + .loreComponent( + Text.color("&1▪ &bRight-Click:&7 Select Player"), + Text.color("&1▪ &bLeft-Click:&7 Throw"), + Text.color("&1▪ &bSneak Left-Click:&7 Electrocute") + ) + .build(); + + public DragTrollWand() { + super("clonedupe.troll", DRAG_WAND); + } + + @Override + public void execute(CommandSender sender, Player target) {} + + @Override + public void stop(CommandSender sender, Player target) {} + + private final Map playerDragMap = new HashMap<>(); + + @Override + protected void onRightClick(Player player) { + handleDragPlayer(player); + } + + @Override + protected void onLeftClick(Player player) { + handleThrowPlayer(player); + } + + @Override + protected void onLeftClickSneak(Player player) { + handleElectrocute(player); + } + + @Override + protected void onScrollUp(Player p) { + if (!playerDragMap.containsKey(p.getUniqueId())) return; + DragTask task = playerDragMap.get(p.getUniqueId()); + + task.setDistance(task.getDistance() + 0.5); + SoundPlayer.play(p,Sound.BLOCK_NOTE_BLOCK_HAT, 1, 0.8F); + } + + @Override + protected void onScrollDown(Player p) { + if (!playerDragMap.containsKey(p.getUniqueId())) return; + DragTask task = playerDragMap.get(p.getUniqueId()); + + if (task.getDistance() < 1.5) { + task.setDistance(2D); + SoundPlayer.play(p,Sound.BLOCK_NOTE_BLOCK_HAT, 1, 1.3F); + return; + } + task.setDistance(task.getDistance() - 0.5); + SoundPlayer.play(p,Sound.BLOCK_NOTE_BLOCK_HAT, 1, 1.7F); + } + + private void handleDragPlayer(Player user) { + if (playerDragMap.containsKey(user.getUniqueId())) { + DragTask task = playerDragMap.remove(user.getUniqueId()); + Entity target = task.drop(); + if (target == null) return; + + success(user,Component.text("You are no longer holding {0}."),target.name()); + return; + } + + CustomDisplayRaytracer.trace(user.getEyeLocation(),user.getEyeLocation().getDirection(),30,0.5, point -> { + List targets = point.getNearbyEntities(user,1, entity -> entity instanceof LivingEntity); + if (targets.isEmpty()) return false; + LivingEntity target = (LivingEntity) targets.getFirst(); + + playerDragMap.put(user.getUniqueId(),startDragging(user,target)); + target.setGlowing(true); + if (target instanceof Player t) t.setAllowFlight(true); + + DisplayUtils.helix(target.getLocation(),0.6,(helix)->{ + user.getWorld().spawnParticle(Particle.SOUL_FIRE_FLAME,helix,1,0,0,0,0); + },0.02,2); + + success(user,Component.text("You are now holding {0}."),target.name()); + return true; + }); + } + + private DragTask startDragging(Player user, LivingEntity victim) { + return new DragTask(victim.getUniqueId(), Bukkit.getScheduler().runTaskTimer(main.getPlugin(), () -> { + if (victim == null) { + playerDragMap.remove(user.getUniqueId()).drop(); + return; + } + if (victim instanceof Player v && !v.isOnline()) { + playerDragMap.remove(user.getUniqueId()).drop(); + return; + } + + DragTask task = playerDragMap.get(user.getUniqueId()); + + Point point = CustomDisplayRaytracer.blocksInFrontOf(user.getEyeLocation(), user.getEyeLocation().getDirection(),task.getDistance(), false); + victim.teleport(point.getLoc().clone().subtract(0, 1, 0)); + + user.sendActionBar(Component.text("You ", NamedTextColor.GREEN).append(Component.text("⇄ ",NamedTextColor.WHITE)).append(Component.text(victim.getName(),NamedTextColor.AQUA)).append(Component.text(" | ",NamedTextColor.GRAY)).append(Component.text(Math.round(task.getDistance()),NamedTextColor.DARK_GREEN))); + }, 0, 1),user.getLocation().distance(victim.getLocation())); + } + + private void handleThrowPlayer(Player user) { + if (!playerDragMap.containsKey(user.getUniqueId())) return; + + DragTask task = playerDragMap.remove(user.getUniqueId()); + Entity target = task.launch(user); + if (target == null) return; + + success(user,Component.text("You have thrown {0}."),target.name()); + } + + private void handleElectrocute(Player user) { + if (!playerDragMap.containsKey(user.getUniqueId())) return; + + DragTask task = playerDragMap.get(user.getUniqueId()); + Entity target = Bukkit.getEntity(task.dragged); + + if (target == null) { + task.drop(); + return; + } + + AtomicInteger ticksRemaining = new AtomicInteger(60); + Bukkit.getScheduler().runTaskTimer(main.getPlugin(),(shockTask)->{ + if (ticksRemaining.getAndDecrement() <= 0 || target == null) { + shockTask.cancel(); + return; + } + PlayerUtils.instantTrueDamage((LivingEntity) target, DamageSource.builder(DamageType.INDIRECT_MAGIC).build(),0.1); + drawLightning(user.getLocation().clone().add(0,1,0),target.getLocation(),Material.AMETHYST_BLOCK,Material.PURPLE_STAINED_GLASS); + },0,1); + + + } + + public static void drawLightning(Location start, Location end, Material blockInner, Material blockOuter) { + int segments = 10; + double maxOffset = 0.5; + long stayTime = 10L; + double thickness = 0.02; + double thicknessOut = 0.04; + List 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); + + ThreadLocalRandom localRandom = ThreadLocalRandom.current(); + + for (int i = 0; i < segments; i++) { + Vector offset = new Vector( + (localRandom.nextDouble() - 0.5) * maxOffset, + (localRandom.nextDouble() - 0.5) * maxOffset, + (localRandom.nextDouble() - 0.5) * maxOffset + ); + Location next = current.clone().add(direction).add(offset); + + BlockDisplayRaytracer.trace(blockInner, current, next, thickness, stayTime, viewers); + BlockDisplayRaytracer.trace(blockOuter, current, next, thicknessOut, stayTime, viewers); + current = next; + } + + BlockDisplayRaytracer.trace(blockInner, current, end, thickness, stayTime, viewers); + BlockDisplayRaytracer.trace(blockOuter, current, end, thicknessOut, stayTime, viewers); + end.getWorld().spawnParticle(Particle.FLASH,end,0,0,0,0,0); + } + + private static class DragTask { + private final UUID dragged; + private final BukkitTask task; + private double distance; + + private DragTask(UUID dragged, BukkitTask task, double distance) { + this.dragged = dragged; + this.task = task; + if (Double.isNaN(distance)) distance = 5; + this.distance = distance; + } + + public UUID getDragged() { + return dragged; + } + + public BukkitTask getTask() { + return task; + } + + public double getDistance() { + return distance; + } + + public void setDistance(double distance) { + this.distance = distance; + } + + public Entity drop() { + this.getTask().cancel(); + Entity target = Bukkit.getEntity(this.getDragged()); + if (target == null) return null; + + target.setGlowing(false); + if (target instanceof Player t) t.setAllowFlight(false); + return target; + } + + public Entity launch(Player thrower) { + Entity target = this.drop(); + if (target == null) return null; + + target.setVelocity(target.getVelocity().add(thrower.getEyeLocation().getDirection().normalize().multiply(2))); + return target; + } + }; +} diff --git a/src/main/java/me/trouper/clonedupecore/server/trolls/FlickerTroll.java b/src/main/java/me/trouper/clonedupecore/server/trolls/FlickerTroll.java new file mode 100644 index 0000000..97bb667 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trolls/FlickerTroll.java @@ -0,0 +1,48 @@ +package me.trouper.clonedupecore.server.trolls; + +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityAnimation; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class FlickerTroll implements TrollFeature { + + @Override + public String getName() { + return "flicker"; + } + + @Override + public String getDescription() { + return "Makes the target's screen flicker with the wake up animation."; + } + + @Override + public Rating getRating() { + return Rating.SAFE; + } + + private final List eepyPlayers = new ArrayList<>(); + + @Override + public void execute(CommandSender sender, Player target) { + var player = PacketEvents.getAPI().getPlayerManager().getUser(target); + eepyPlayers.add(target.getUniqueId()); + Bukkit.getScheduler().runTaskTimerAsynchronously(main.getPlugin(), (t) -> { + if (!eepyPlayers.contains(target.getUniqueId())) t.cancel(); + player.sendPacket(new WrapperPlayServerEntityAnimation(target.getEntityId(), WrapperPlayServerEntityAnimation.EntityAnimationType.WAKE_UP)); + }, 1, 1); + successAny(sender,"{0}'s screen is now flickering",target.getName()); + } + + @Override + public void stop(CommandSender sender, Player target) { + if (eepyPlayers.remove(target.getUniqueId())) successAny(sender,"Stopped flickering {0}'s screen.",target.getName()); + else errorAny(sender,"{0}'s screen is not flickering.",target.getName()); + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/trolls/LSDTroll.java b/src/main/java/me/trouper/clonedupecore/server/trolls/LSDTroll.java new file mode 100644 index 0000000..804d7c9 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trolls/LSDTroll.java @@ -0,0 +1,133 @@ +package me.trouper.clonedupecore.server.trolls; + +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.protocol.player.User; +import me.trouper.alias.utils.SoundPlayer; +import me.trouper.clonedupecore.utils.PacketUtils; +import net.kyori.adventure.text.Component; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitTask; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +public class LSDTroll implements TrollFeature { + + @Override + public String getName() { + return "lsd"; + } + + @Override + public String getDescription() { + return "Makes the player hallucinate."; + } + + @Override + public Rating getRating() { + return Rating.SAFE; + } + + private final List RAINBOW_BLOCKS = new ArrayList<>( + List.of( + Material.RED_WOOL, + Material.ORANGE_WOOL, + Material.YELLOW_WOOL, + Material.LIME_WOOL, + Material.LIGHT_BLUE_WOOL, + Material.MAGENTA_WOOL, + Material.PURPLE_WOOL + ) + ); + + private final List RAINBOW_GLASS = new ArrayList<>( + List.of( + Material.RED_STAINED_GLASS, + Material.ORANGE_STAINED_GLASS, + Material.YELLOW_STAINED_GLASS, + Material.LIME_STAINED_GLASS, + Material.LIGHT_BLUE_STAINED_GLASS, + Material.MAGENTA_STAINED_GLASS, + Material.PURPLE_STAINED_GLASS + ) + ); + + private final Map lsdTasks = new HashMap<>(); + + @Override + public void execute(CommandSender sender, Player target) { + if (lsdTasks.containsKey(target.getUniqueId())) { + error(sender,Component.text("{0} is already hallucinating!"),target.name()); + return; + } + + User v = PacketEvents.getAPI().getPlayerManager().getUser(target); + final int radius = 8; + AtomicInteger ticksPassed = new AtomicInteger(0); + + lsdTasks.put(target.getUniqueId(),Bukkit.getScheduler().runTaskTimer(main.getPlugin(),()->{ + if (!target.isOnline()) { + try { + lsdTasks.remove(target.getUniqueId()).cancel(); + } catch (Exception ignored) {} + return; + } + + int x = target.getLocation().getBlockX(); + int y = target.getLocation().getBlockY(); + int z = target.getLocation().getBlockZ(); + World w = target.getWorld(); + + Location center = target.getLocation(); + for (int xLoc = x - radius; xLoc < x + radius; xLoc++) { + for (int yLoc = y - radius; yLoc < y + radius; yLoc++) { + for (int zLoc = z - radius; zLoc < z + radius; zLoc++) { + Location loc = new Location(w,xLoc,yLoc,zLoc); + if (loc.distanceSquared(center) >= radius*radius) continue; + Block block = loc.getBlock(); + + if (block.isPassable()) { + if (loc.distanceSquared(center) <= (radius-3)*(radius-3)) { + PacketUtils.sendGhostBlock(v,loc,block.getType()); + continue; + } + double distance = loc.distance(center); + int waveIndex = (int) ((ticksPassed.get() / 2.0 - distance) % RAINBOW_GLASS.size()); + if (waveIndex < 0) waveIndex += RAINBOW_GLASS.size(); + Material waveMaterial = RAINBOW_GLASS.get(waveIndex); + + PacketUtils.sendGhostBlock(v, loc, waveMaterial); + continue; + } + + double distance = loc.distance(center); + int waveIndex = (int) ((ticksPassed.get() / 2.0 - distance) % RAINBOW_BLOCKS.size()); + if (waveIndex < 0) waveIndex += RAINBOW_BLOCKS.size(); + Material waveMaterial = RAINBOW_BLOCKS.get(waveIndex); + + PacketUtils.sendGhostBlock(v, loc, waveMaterial); + } + } + } + + float pitch = ((float) (ticksPassed.get() % 5)) / 10; + + SoundPlayer.play(target,Sound.BLOCK_NOTE_BLOCK_CHIME,10,pitch + 0.7F); + ticksPassed.incrementAndGet(); + },0,2)); + } + + @Override + public void stop(CommandSender sender, Player target) { + try { + lsdTasks.remove(target.getUniqueId()).cancel(); + success(sender, Component.text("Stopped {0}'s hallucinations."),target.name()); + } catch (NullPointerException ex) { + error(sender, Component.text("{0} is not hallucinating."),target.name()); + } + + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/trolls/LiarTroll.java b/src/main/java/me/trouper/clonedupecore/server/trolls/LiarTroll.java new file mode 100644 index 0000000..82c9208 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trolls/LiarTroll.java @@ -0,0 +1,114 @@ +package me.trouper.clonedupecore.server.trolls; + +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.protocol.player.User; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSetPlayerInventory; +import io.github.retrooper.packetevents.util.SpigotConversionUtil; +import me.trouper.alias.server.systems.Text; +import me.trouper.alias.utils.ItemBuilder; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.title.Title; +import org.bukkit.*; +import org.bukkit.command.CommandSender; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +public class LiarTroll implements TrollFeature { + + @Override + public String getName() { + return "liar"; + } + + @Override + public String getDescription() { + return "Liar, liar, pants on trinitrotoluene!"; + } + + @Override + public Rating getRating() { + return Rating.DAMAGING; + } + + private final Map activeLiars = new HashMap<>(); + + @Override + public void execute(CommandSender sender, Player target) { + User t = PacketEvents.getAPI().getPlayerManager().getUser(target); + activeLiars.put(target.getUniqueId(),target.getEquipment().getLeggings()); + + AtomicBoolean inverse = new AtomicBoolean(false); + AtomicInteger timeRemaining = new AtomicInteger(30 * 20); + + Bukkit.getScheduler().runTaskTimer(main.getPlugin(), (task)->{ + if (!activeLiars.containsKey(target.getUniqueId())) { + task.cancel(); + return; + } + + if (timeRemaining.get() <= 0) { + WrapperPlayServerSetPlayerInventory packet = new WrapperPlayServerSetPlayerInventory(37,SpigotConversionUtil.fromBukkitItemStack(activeLiars.get(target.getUniqueId()))); + t.sendPacket(packet); + target.getWorld().createExplosion(target.getLocation(),1,false,false); + task.cancel(); + return; + } + + ItemStack displayItem = ItemBuilder.of(Material.TNT) + .displayName(Component.text("Liar, Liar!", NamedTextColor.RED).decoration(TextDecoration.ITALIC,false).decorate(TextDecoration.BOLD)) + .loreComponent( + Component.text("Pants on trinitrotoluene!",NamedTextColor.GRAY), + Component.text("Explodes in ", NamedTextColor.GRAY).append(Component.text(timeRemaining.get() / 20,NamedTextColor.DARK_RED)).append(Component.text(" seconds.",NamedTextColor.GRAY)) + ) + .enchant(Enchantment.BINDING_CURSE) + .flags(ItemFlag.HIDE_ENCHANTS) + .build(); + + WrapperPlayServerSetPlayerInventory packet = new WrapperPlayServerSetPlayerInventory(37,SpigotConversionUtil.fromBukkitItemStack(displayItem)); + Title title = Title.title( + Text.color("&c⚠ &4&lWARNING &r&c⚠"), + Text.color("&7&nLEGGINGS COMBUSTION EMINENT!"), + Title.Times.times(Duration.of(0, ChronoUnit.SECONDS),Duration.of(1, ChronoUnit.SECONDS),Duration.of(0, ChronoUnit.SECONDS)) + ); + + if (inverse.get()) { + title = Title.title( + Text.color("&e⚠ &4&l&nWARNING&r &e⚠"), + Text.color("&7LEGGINGS COMBUSTION EMINENT!"), + Title.Times.times(Duration.of(0, ChronoUnit.SECONDS),Duration.of(1, ChronoUnit.SECONDS),Duration.of(0, ChronoUnit.SECONDS))) + ; + ItemStack pants = displayItem.withType(Material.WHITE_WOOL); + target.getWorld().playSound(target.getLocation(), Sound.BLOCK_NOTE_BLOCK_BIT, SoundCategory.MASTER,10,2F); + packet.setStack(SpigotConversionUtil.fromBukkitItemStack(pants)); + } else { + target.getWorld().playSound(target.getLocation(), Sound.BLOCK_NOTE_BLOCK_BIT, SoundCategory.MASTER,10,1.5F); + } + + t.sendPacket(packet); + target.showTitle(title); + target.getWorld().spawnParticle(Particle.SMOKE,target.getLocation().add(0,0.5,0),1,0.1,0.1,0.1,0.05); + + if (timeRemaining.get() <= 40) target.setFireTicks(2); + if (timeRemaining.getAndSet(timeRemaining.get() - 1) % 10 == 0) inverse.set(!inverse.get()); + },0,1); + + successAny(sender,"{0} will soon detonate!",target.getName()); + } + + @Override + public void stop(CommandSender sender, Player target) { + if (activeLiars.remove(target.getUniqueId()) != null) successAny(sender,"Removed the TNT from {0}'s pants.",target.getName()); + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/trolls/MatrixTroll.java b/src/main/java/me/trouper/clonedupecore/server/trolls/MatrixTroll.java new file mode 100644 index 0000000..d0617b0 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trolls/MatrixTroll.java @@ -0,0 +1,316 @@ +package me.trouper.clonedupecore.server.trolls; + +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerChangeGameState; +import me.trouper.clonedupecore.utils.PlayerUtils; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextColor; +import org.bukkit.Bukkit; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.damage.DamageSource; +import org.bukkit.damage.DamageType; +import org.bukkit.entity.Display; +import org.bukkit.entity.Player; +import org.bukkit.entity.TextDisplay; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.scheduler.BukkitTask; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.Transformation; +import org.bukkit.util.Vector; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; + +public class MatrixTroll implements TrollFeature { + + @Override + public String getName() { + return "matrix"; + } + + @Override + public String getDescription() { + return "Make the player escape The Matrix."; + } + + @Override + public Rating getRating() { + return Rating.HARMLESS; + } + + private final Map matrixUsers = new ConcurrentHashMap<>(); + + @Override + public void execute(CommandSender sender, Player target) { + if (matrixUsers.containsKey(target.getUniqueId())) { + error(sender,Component.text("{0} is already escaping The Matrix."),target.name()); + return; + } + + MatrixSession session = new MatrixSession(target); + matrixUsers.put(target.getUniqueId(), session); + session.start(); + success(sender,Component.text("Making {0} run from The Matrix."),target.name()); + } + + @Override + public void stop(CommandSender sender, Player target) { + MatrixSession session = matrixUsers.remove(target.getUniqueId()); + if (session != null) { + session.stop(); + success(sender,Component.text("Removed {0}'s Matirx."),target.name()); + } + } + + public static Component generateMatrix(int width, int height) { + ThreadLocalRandom localRandom = ThreadLocalRandom.current(); + Component matrix = Component.empty(); + for (int y = 0; y < height; y++) { + Component line = Component.empty(); + if (y > 0) { + line = line.appendNewline(); + } + + for (int x = 0; x < width; x++) { + char bit = (char) ('\u30A0' + localRandom.nextInt(96)); + + int greenValue = 100 + localRandom.nextInt(156); + TextColor green = TextColor.color(0, greenValue, 0); + + TextColor finalColor = localRandom.nextDouble() < 0.05 ? TextColor.color(200, 255, 200) : green; + + Component charComp = Component.text(bit).color(finalColor); + line = line.append(charComp); + } + matrix = matrix.append(line); + } + return matrix; + } + + private class MatrixSession { + private final Player player; + private final List columns = new ArrayList<>(); + private BukkitTask spawnTask; + private BukkitTask updateTask; + private BukkitTask collisionTask; + private boolean active = true; + private int spawnRadius = 5; + private int maxColumns = 10; + + public MatrixSession(Player player) { + this.player = player; + } + + public void start() { + spawnTask = Bukkit.getScheduler().runTaskTimer(getPlugin(), () -> { + if (!active || !player.isOnline()) { + stop(); + return; + } + + spawnSingleColumn(); + }, 0L, 2L); + + updateTask = Bukkit.getScheduler().runTaskTimer(getPlugin(), () -> { + if (!active) return; + + for (MatrixColumn column : columns) { + column.updateDisplays(); + } + }, 0L, 2L); + + collisionTask = Bukkit.getScheduler().runTaskTimer(getPlugin(), () -> { + if (!active || !player.isOnline()) { + stop(); + return; + } + + checkCollisionsAndLevitation(); + }, 0L, 1L); + } + + public void stop() { + active = false; + + if (spawnTask != null) spawnTask.cancel(); + if (updateTask != null) updateTask.cancel(); + if (collisionTask != null) collisionTask.cancel(); + + for (MatrixColumn column : columns) { + column.remove(); + } + columns.clear(); + + player.removePotionEffect(PotionEffectType.LEVITATION); + } + + private void spawnSingleColumn() { + if (columns.size() >= maxColumns) { + columns.getFirst().remove(); + columns.removeFirst(); + return; + } + + ThreadLocalRandom localRandom = ThreadLocalRandom.current(); + + Location playerLoc = player.getLocation(); + + double angle = localRandom.nextDouble() * 2 * Math.PI; + double distance = localRandom.nextDouble() * spawnRadius; + + double x = playerLoc.getX() + Math.cos(angle) * distance; + double z = playerLoc.getZ() + Math.sin(angle) * distance; + double y = playerLoc.getY() - 20; + + Location columnLoc = new Location(playerLoc.getWorld(), x, y, z); + MatrixColumn column = new MatrixColumn(columnLoc); + columns.add(column); + column.spawn(); + } + + private void checkCollisionsAndLevitation() { + Location playerLoc = player.getLocation(); + ThreadLocalRandom localRandom = ThreadLocalRandom.current(); + boolean inColumn = false; + MatrixColumn touchedColumn = null; + + for (MatrixColumn column : columns) { + if (column.getBoundingBox().contains(playerLoc.toVector())) { + inColumn = true; + touchedColumn = column; + } + } + + if (inColumn) { + player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 10, 0, false, false)); + + + if (localRandom.nextDouble() < 0.5) PlayerUtils.instantTrueDamage(player, DamageSource.builder(DamageType.HOT_FLOOR).build(),0.01); + if (localRandom.nextDouble() < 0.01) PacketEvents.getAPI().getPlayerManager().getUser(player).sendPacket(new WrapperPlayServerChangeGameState(WrapperPlayServerChangeGameState.Reason.PLAY_ELDER_GUARDIAN_MOB_APPEARANCE,0.0F)); + + List toRemove = new ArrayList<>(); + for (MatrixColumn column : columns) { + if (column != touchedColumn) { + column.remove(); + toRemove.add(column); + } + } + columns.removeAll(toRemove); + } + } + } + + private class MatrixColumn { + private final Location center; + private final List displays = new ArrayList<>(); + private final BoundingBox boundingBox; + private final int height = 40; + + public MatrixColumn(Location center) { + this.center = center.clone(); + + this.boundingBox = new BoundingBox( + center.getX() - 0.5, center.getY(), center.getZ() - 0.5, + center.getX() + 0.5, center.getY() + height, center.getZ() + 0.5 + ); + } + + public void spawn() { + float[] yaws = {0f, 90f, 180f, 270f}; + double offset = 0.5; + Vector[] directions = { + new Vector(0, 0, -offset), + new Vector(offset, 0, 0), + new Vector(0, 0, offset), + new Vector(-offset, 0, 0) + }; + + float textScale = 2.5f; + final Transformation scaleTransform = new Transformation( + new Vector3f(), + new Quaternionf(), + new Vector3f(textScale, textScale, textScale), + new Quaternionf() + ); + + for (int i = 0; i < 4; i++) { + Location displayLoc = center.clone().add(directions[i]); + displayLoc.add(0, height/4, 0); + displayLoc.setYaw(yaws[i]); + Component text = generateMatrix(2, height); + + int finalI = i; + + TextDisplay displayOut = center.getWorld().spawn(displayLoc, TextDisplay.class, CreatureSpawnEvent.SpawnReason.CUSTOM, entity -> { + entity.text(text); + entity.setBillboard(Display.Billboard.FIXED); + entity.setVisibleByDefault(false); + entity.setRotation(yaws[finalI], 0f); + entity.setBackgroundColor(Color.fromARGB(0xDD000000)); + entity.setTransformation(scaleTransform); + }); + + TextDisplay displayIn = center.getWorld().spawn(displayLoc, TextDisplay.class, CreatureSpawnEvent.SpawnReason.CUSTOM, entity -> { + entity.text(text); + entity.setBillboard(Display.Billboard.FIXED); + entity.setVisibleByDefault(false); + entity.setRotation(yaws[finalI] + 180f, 0f); + entity.setBackgroundColor(Color.fromARGB(0xDD000000)); + entity.setTransformation(scaleTransform); + }); + + Player target = matrixUsers.entrySet().stream() + .filter(entry -> entry.getValue().columns.stream().anyMatch(col -> col == this)) + .map(entry -> Bukkit.getPlayer(entry.getKey())) + .findFirst() + .orElse(null); + + if (target != null) { + target.showEntity(getPlugin(), displayIn); + target.showEntity(getPlugin(), displayOut); + } + + displays.add(displayOut); + displays.add(displayIn); + } + } + + public void updateDisplays() { + for (TextDisplay display : displays) { + if (!display.isDead()) { + display.text(generateMatrix(2, height)); + } + } + } + + public BoundingBox getBoundingBox() { + return boundingBox; + } + + public boolean checkCollision(Location playerLoc) { + for (TextDisplay display : displays) { + if (display.getLocation().distance(playerLoc) < 1.0) { + return true; + } + } + return false; + } + + public void remove() { + for (TextDisplay display : displays) { + display.remove(); + } + displays.clear(); + } + } +} \ No newline at end of file diff --git a/src/main/java/me/trouper/clonedupecore/server/trolls/OrbitalTrollWand.java b/src/main/java/me/trouper/clonedupecore/server/trolls/OrbitalTrollWand.java new file mode 100644 index 0000000..e4b7d61 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trolls/OrbitalTrollWand.java @@ -0,0 +1,514 @@ +package me.trouper.clonedupecore.server.trolls; + +import me.trouper.alias.server.systems.AbstractWand; +import me.trouper.alias.server.systems.Text; +import me.trouper.alias.server.systems.Verbose; +import me.trouper.alias.server.systems.tracing.BlockDisplayRaytracer; +import me.trouper.alias.server.systems.tracing.CustomDisplayRaytracer; +import me.trouper.alias.server.systems.tracing.Point; +import me.trouper.alias.server.systems.world.ExplosionOptions; +import me.trouper.alias.server.systems.world.ExplosionResult; +import me.trouper.alias.server.systems.world.ExplosionUtils; +import me.trouper.alias.utils.FormatUtils; +import me.trouper.alias.utils.ItemBuilder; +import me.trouper.alias.utils.SoundPlayer; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.*; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.BlockDisplay; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +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.Stack; +import java.util.UUID; + +public class OrbitalTrollWand extends AbstractWand implements TrollFeature { + private static final ItemStack ORBITAL_WAND = ItemBuilder.of(Material.TRIPWIRE_HOOK) + .displayName(Text.color("&4&lOrbital Sky Torch").decoration(TextDecoration.ITALIC,false)) + .loreComponent( + Text.color("&8▪ &cRight-Click:&f Aim Cannon"), + Text.color("&8▪ &cRight-Click Again:&f Charge Ion Beam"), + Text.color("&8▪ &cLeft-Click:&f Call off shot"), + Text.color("&8▪ &cSwap-Hands:&f Undo"), + Text.color("&7"), + Text.color("&7Now Includes &oreal&r&7 orbital configurations!"), + Text.color("&7"), + Text.color("&8Thank you TheCymaera and HeroBrayden!") + ) + .build(); + + public OrbitalTrollWand() { + super("clonedupe.orbital", ORBITAL_WAND); + } + + @Override + public String getName() { + return "orbital"; + } + + @Override + public String getDescription() { + return "Get a wand which lets you control the server's orbital ion cannon."; + } + + @Override + public Rating getRating() { + return Rating.DESTRUCTIVE; + } + + @Override + public void execute(CommandSender sender, Player target) {} + @Override + public void stop(CommandSender sender, Player target) {} + + private final Map cannonMap = new HashMap<>(); + private final Map> playerHistory = new HashMap<>(); + + private boolean undoHistory(Player player) { + Stack history = playerHistory.getOrDefault(player.getUniqueId(),new Stack<>()); + if (history == null || history.isEmpty()) return false; + try (ExplosionResult result = history.pop()) { + result.restore(); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + return true; + } + + @Override + protected void onSwapHand(Player player) { + try { + if (undoHistory(player)) { + Text.message(Text.Pallet.SUCCESS,false,player, Component.text("Restored previous explosion.")); + } else { + Text.message(Text.Pallet.ERROR,false,player,Component.text("Undo stack is empty!")); + } + } catch (Exception e) { + e.printStackTrace(); + Text.message(Text.Pallet.ERROR,false,player,Component.text("An exception was thrown when undoing the explosion! Check console for details.")); + } + } + + @Override + protected void onRightClick(Player player) { + if (cannonMap.containsKey(player.getUniqueId())) { + OrbitalIonCannon playerCannon = cannonMap.get(player.getUniqueId()); + if (playerCannon.getFireTask() != null || playerCannon.getChargeTask() != null) { + Text.message(Text.Pallet.ERROR,false,player,Component.text("You are already charging the Ion Cannon!")); + cannonMap.remove(player.getUniqueId()); + return; + } + playerCannon.charge(); + cannonMap.remove(player.getUniqueId()); + Text.message(Text.Pallet.SUCCESS,false,player, Component.text("Charging Ion Cannon...")); + return; + } + OrbitalIonCannon cannon = new OrbitalIonCannon(player, traceTargets(player)); + cannonMap.put(player.getUniqueId(),cannon); + Text.message(Text.Pallet.SUCCESS,false,player, Component.text("You can now aim the Ion Cannon.")); + } + + @Override + protected void onLeftClick(Player player) { + if (!cannonMap.containsKey(player.getUniqueId())) { + Text.message(Text.Pallet.ERROR,false,player,Component.text("You are not controlling the Ion Cannon.")); + return; + } + OrbitalIonCannon playerCannon = cannonMap.get(player.getUniqueId()); + playerCannon.destroy(); + cannonMap.remove(player.getUniqueId()); + Text.message(Text.Pallet.SUCCESS,false,player, Component.text("Deactivated Ion Cannon Safely.")); + + } + + @Override + protected void onScrollDown(Player player) { + if (!cannonMap.containsKey(player.getUniqueId())) { + Text.message(Text.Pallet.ERROR,false,player,Component.text("You are not controlling the Ion Cannon.")); + return; + } + + OrbitalIonCannon cannon = cannonMap.get(player.getUniqueId()); + + if (cannon.getPower() <= 1) { + SoundPlayer.play(player,Sound.BLOCK_NOTE_BLOCK_HAT, 1, 1.3F); + cannon.setPower(2); + return; + } + cannon.setPower(Math.max(1,cannon.getPower() - 5)); + SoundPlayer.play(player,Sound.BLOCK_NOTE_BLOCK_HAT, 1, 1.7F); + + player.sendActionBar(Text.format(Text.Pallet.INFO,"Set ion cannon power to {0}.",cannon.getPower())); + } + + @Override + protected void onScrollUp(Player player) { + if (!cannonMap.containsKey(player.getUniqueId())) { + Text.message(Text.Pallet.ERROR,false,player,Component.text("You are not controlling the Ion Cannon.")); + return; + } + + OrbitalIonCannon cannon = cannonMap.get(player.getUniqueId()); + + if (cannon.getPower() >= 50) { + SoundPlayer.play(player,Sound.BLOCK_NOTE_BLOCK_HAT, 1, 1.3F); + cannon.setPower(49); + return; + } + + cannon.setPower(Math.min(50,cannon.getPower() + 5)); + SoundPlayer.play(player,Sound.BLOCK_NOTE_BLOCK_HAT, 1, 0.8F); + + player.sendActionBar(Text.format(Text.Pallet.INFO,"Set ion cannon power to {0}.",cannon.getPower())); + } + + public class OrbitalIonCannon { + private final Player owner; + private final OrbitalConfiguration simulatedOrbit; + private Location skyRenderLocation; + private Location skyTraceLocation; + private Location targetLocation; + private Location groundTraced; + private BukkitTask aimTask; + private BukkitTask chargeTask; + private BukkitTask fireTask; + private int power; + private ExplosionResult explosionResult; + private final BukkitTask tickTask = new BukkitRunnable() { + @Override + public void run() { + updateOrbitLocation(); + setSkyRenderLocation(getSimulatedOrbit().getRenderLocation(getTargetLocation())); + setSkyTraceLocation(CustomDisplayRaytracer.blocksInFrontOf(getTargetLocation(),getSimulatedOrbit().getNormalToSatellite(getTargetLocation().toVector()),20,false).getLoc()); + if (!getAimTask().isCancelled()) { + setTargetLocation(traceTargets(getOwner())); + setGroundTraced(CustomDisplayRaytracer.trace(getSkyTraceLocation(),getTargetLocation(),5,(point)-> { + Material type = point.getLoc().getBlock().getType(); + boolean collidable = type.isCollidable(); + Verbose.send("Block at {0} collision: {1}, {2}",point.getLoc(),type,collidable); + return collidable; + }).getLoc()); + } + } + }.runTaskTimer(main.getPlugin(), 0, 1); + + private OrbitalIonCannon(Player owner, Location target) { + this.owner = owner; + this.targetLocation = target; + this.power = 20; + this.simulatedOrbit = new OrbitalConfiguration(target.toVector()); + + this.setAimTask(aimRunnable.runTaskTimer(main.getPlugin(), 0, 1)); + } + + private final BukkitRunnable fireRunnable = new BukkitRunnable() { + int ticksElapsed = 0; + + @Override + public void run() { + if (ticksElapsed >= 120) { + if (getExplosionResult() != null) { + getExplosionResult().close(); + } + destroy(); + return; + } + + if (ticksElapsed <= 40) { + BlockDisplay inner = BlockDisplayRaytracer.trace(Material.WHITE_CONCRETE_POWDER,getGroundTraced(),getSkyRenderLocation(),1,2); + inner.setGlowing(true); + inner.setGlowColorOverride(Color.fromRGB(0xFFDDDD)); + BlockDisplayRaytracer.trace(Material.WHITE_STAINED_GLASS,getGroundTraced(),getSkyRenderLocation(),1.5 + main.random().nextDouble(),2); + } + + if (ticksElapsed == 0) { + ExplosionOptions options = new ExplosionOptions() + .setMaxBurnRadius(getPower()) + .setFalloffRadius((double) getPower() / 3) + .setCoreRadius(((double) getPower() / 3) / 1.2) + .setBaseDamage(120) + .setBurnDelay(0) + .setDestructionDelay(1); + + Stack history = playerHistory.getOrDefault(getOwner().getUniqueId(),new Stack<>()); + + setExplosionResult(ExplosionUtils.createExplosion(getGroundTraced(),options)); + + history.push(getExplosionResult()); + + playerHistory.put(getOwner().getUniqueId(),history); + } + + ticksElapsed++; + } + }; + + private final BukkitRunnable chargeRunnable = new BukkitRunnable() { + int ticksElapsed = 0; + @Override + public void run() { + if (ticksElapsed >= 20) { + setFireTask(fireRunnable.runTaskTimer(main.getPlugin(),0,1)); + this.cancel(); + return; + } + + if (ticksElapsed == 1) { + new SoundPlayer(Sound.ENTITY_WARDEN_SONIC_CHARGE,30,0.5F).playAt(getTargetLocation(),40); + new SoundPlayer(Sound.BLOCK_CONDUIT_AMBIENT,30,0.5F).playAt(getGroundTraced(),40); + } + + double thickness = (double) ticksElapsed / 20; + int otherValues = ((ticksElapsed / 20 * 100) + 100); + + BlockDisplay inner = BlockDisplayRaytracer.trace(Material.RED_CONCRETE, getGroundTraced(),getSkyRenderLocation(),thickness,2); + inner.setGlowing(true); + inner.setGlowColorOverride(Color.fromRGB(255,otherValues,otherValues)); + BlockDisplayRaytracer.trace(Material.ORANGE_STAINED_GLASS, getGroundTraced(),getSkyRenderLocation(),thickness + 0.1 ,2); + + ticksElapsed++; + } + }; + + private final BukkitRunnable aimRunnable = new BukkitRunnable() { + int ticksElapsed = 0; + @Override + public void run() { + if (getChargeTask() != null) { + this.cancel(); + return; + } + + if (ticksElapsed == 0) { + new SoundPlayer(Sound.BLOCK_RESPAWN_ANCHOR_CHARGE,1,0.5F).playAt(groundTraced,40); + } + + BlockDisplay inner = BlockDisplayRaytracer.trace(Material.RED_CONCRETE_POWDER,getGroundTraced(),getSkyRenderLocation(),0.1,2); + inner.setGlowing(true); + inner.setGlowColorOverride(Color.fromRGB(0xFF0000)); + BlockDisplayRaytracer.trace(Material.RED_STAINED_GLASS,getGroundTraced(),getSkyRenderLocation(),0.2,2); + + ticksElapsed++; + } + }; + + private void updateOrbitLocation() { + if (getTargetLocation() == null || getTargetLocation().getWorld() == null) { + destroy(); + return; + } + + getSimulatedOrbit().tick(); + } + + private void destroy() { + if (getTickTask() != null) this.getTickTask().cancel(); + if (getFireTask() != null) this.getFireTask().cancel(); + if (getChargeTask() != null) this.getChargeTask().cancel(); + if (getAimTask() != null) this.getAimTask().cancel(); + } + + private void charge() { + this.getAimTask().cancel(); + this.setChargeTask(this.chargeRunnable.runTaskTimer(main.getPlugin(),0,1)); + } + + public Player getOwner() { + return owner; + } + + public Location getTargetLocation() { + return targetLocation; + } + + public void setTargetLocation(Location targetLocation) { + this.targetLocation = targetLocation; + } + + public BukkitTask getAimTask() { + return aimTask; + } + + public void setAimTask(BukkitTask aimTask) { + this.aimTask = aimTask; + } + + public BukkitTask getChargeTask() { + return chargeTask; + } + + public void setChargeTask(BukkitTask chargeTask) { + this.chargeTask = chargeTask; + } + + public BukkitTask getFireTask() { + return fireTask; + } + + public void setFireTask(BukkitTask fireTask) { + this.fireTask = fireTask; + } + + public BukkitTask getTickTask() { + return tickTask; + } + + public ExplosionResult getExplosionResult() { + return explosionResult; + } + + public void setExplosionResult(ExplosionResult explosionResult) { + this.explosionResult = explosionResult; + } + + public OrbitalConfiguration getSimulatedOrbit() { + return simulatedOrbit; + } + + public Location getSkyRenderLocation() { + return skyRenderLocation; + } + + public void setSkyRenderLocation(Location skyRenderLocation) { + this.skyRenderLocation = skyRenderLocation; + } + + public Location getGroundTraced() { + return groundTraced; + } + + public void setGroundTraced(Location groundTraced) { + this.groundTraced = groundTraced; + } + + public int getPower() { + return power; + } + + public void setPower(int power) { + this.power = power; + } + + public Location getSkyTraceLocation() { + return skyTraceLocation; + } + + public void setSkyTraceLocation(Location skyTraceLocation) { + this.skyTraceLocation = skyTraceLocation; + } + } + + public class OrbitalConfiguration { + private Vector baseSatellite; + + public OrbitalConfiguration(Vector center) { + this.baseSatellite = center.clone().setY(11_368_121); + } + + public void tick() { + Vector orbitalLocation = getBaseSatellite(); + if (orbitalLocation.getX() > 30_000_000 || orbitalLocation.getZ() > 30_000_000) { + setBaseSatellite(new Vector(-orbitalLocation.getX(),orbitalLocation.getY(),-orbitalLocation.getZ())); + return; + } + + setBaseSatellite(orbitalLocation.clone().add(new Vector(2_500,0,762))); + } + + public Location getRenderLocation(Location center) { + Vector dir = getNormalToSatellite(center.toVector()); + int castDistance = 128; + return center.clone().add(dir.getX() * castDistance, dir.getY() * castDistance, dir.getZ() * castDistance); + } + + public Vector getNormalToSatellite(Vector target) { + Vector closestSatellite = getClosestSatellite(target).clone(); + return closestSatellite.clone().subtract(target).normalize(); + } + + public Vector getNormalToTarget(Vector target) { + Vector closestSatellite = getClosestSatellite(target).clone(); + return target.clone().subtract(closestSatellite).normalize(); + } + + public Vector getClosestSatellite(Vector location) { + Vector[] satellites = new Vector[] { + getBaseSatellite(), + getPosXSat(), + getPosZSat(), + getPosXZSat(), + getNegXSat(), + getNegZSat(), + getNegXZSat(), + getPosXNegZSat(), + getNegXPosZSat() + }; + + Vector closest = satellites[0]; + double minDistance = location.distanceSquared(closest); + + for (int i = 1; i < satellites.length; i++) { + double distance = location.distanceSquared(satellites[i]); + if (distance < minDistance) { + minDistance = distance; + closest = satellites[i]; + } + } + + return closest; + } + + public Vector getPosXSat() { + return baseSatellite.clone().add(new Vector(60_000_000,0,0)); + } + + public Vector getPosZSat() { + return baseSatellite.clone().add(new Vector(0,0,60_000_000)); + } + + public Vector getPosXZSat() { + return baseSatellite.clone().add(new Vector(60_000_000,0,60_000_000)); + } + + public Vector getNegXSat() { + return baseSatellite.clone().add(new Vector(-60_000_000,0,0)); + } + + public Vector getNegZSat() { + return baseSatellite.clone().add(new Vector(0,0,-60_000_000)); + } + + public Vector getNegXZSat() { + return baseSatellite.clone().add(new Vector(-60_000_000,0,60_000_000)); + } + + public Vector getPosXNegZSat() { + return baseSatellite.clone().add(new Vector(60_000_000,0,-60_000_000)); + } + + public Vector getNegXPosZSat() { + return baseSatellite.clone().add(new Vector(-60_000_000,0,60_000_000)); + } + + public Vector getBaseSatellite() { + return baseSatellite; + } + + public void setBaseSatellite(Vector baseSatellite) { + this.baseSatellite = baseSatellite; + } + } + + private static Location traceTargets(Player player) { + Point hit = CustomDisplayRaytracer.trace(player.getEyeLocation(),player.getEyeLocation().getDirection(),128,CustomDisplayRaytracer.HIT_BLOCK); + + return hit.getLoc(); + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/trolls/TestTrollWand.java b/src/main/java/me/trouper/clonedupecore/server/trolls/TestTrollWand.java new file mode 100644 index 0000000..fccf546 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trolls/TestTrollWand.java @@ -0,0 +1,87 @@ +package me.trouper.clonedupecore.server.trolls; + +import me.trouper.alias.server.systems.AbstractWand; +import me.trouper.alias.server.systems.tracing.BlockDisplayRaytracer; +import me.trouper.alias.utils.ItemBuilder; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Material; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.BlockDisplay; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +public class TestTrollWand extends AbstractWand implements TrollFeature{ + + private static final ItemStack TEST_WAND = ItemBuilder.of(Material.TRIPWIRE_HOOK) + .displayName(Component.text("Testing Wand", NamedTextColor.YELLOW)) + .loreComponent( + Component.text("Prints out the action you preformed.") + ) + .build(); + + @Override + public String getName() { + return "testwand"; + } + + @Override + public String getDescription() { + return "A wand for testing interfaces."; + } + + @Override + public Rating getRating() { + return Rating.SAFE; + } + + public TestTrollWand() { + super("clonedupe.troll", TEST_WAND); + } + + @Override + public void execute(CommandSender sender, Player target) {} + + @Override + public void stop(CommandSender sender, Player target) {} + + @Override + protected void onScrollUp(Player player) { + infoAny(player, "You scrolled upwards"); + } + + @Override + protected void onScrollDown(Player player) { + infoAny(player, "You scrolled downwards"); + } + + @Override + protected void onSwapHand(Player player) { + infoAny(player, "You swapped hands"); + } + + @Override + protected void onSwapHandSneak(Player player) { + infoAny(player, "You swapped hands while sneaking"); + } + + @Override + protected void onLeftClick(Player player) { + infoAny(player, "You left clicked"); + } + + @Override + protected void onLeftClickSneak(Player player) { + infoAny(player, "You left clicked while sneaking"); + } + + @Override + protected void onRightClick(Player player) { + infoAny(player, "You right clicked"); + } + + @Override + protected void onRightClickSneak(Player player) { + infoAny(player, "You right clicked while sneaking"); + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/trolls/TrollFeature.java b/src/main/java/me/trouper/clonedupecore/server/trolls/TrollFeature.java new file mode 100644 index 0000000..33ec2a8 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trolls/TrollFeature.java @@ -0,0 +1,24 @@ +package me.trouper.clonedupecore.server.trolls; + +import me.trouper.alias.server.events.QuickListener; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public interface TrollFeature extends QuickListener { + String getName(); + + String getDescription(); + + Rating getRating(); + + void execute(CommandSender sender, Player target); + + void stop(CommandSender sender, Player target); + + enum Rating { + SAFE, + HARMLESS, + DAMAGING, + DESTRUCTIVE + } +} diff --git a/src/main/java/me/trouper/clonedupecore/server/trolls/VoidTrollWand.java b/src/main/java/me/trouper/clonedupecore/server/trolls/VoidTrollWand.java new file mode 100644 index 0000000..6d8871f --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/server/trolls/VoidTrollWand.java @@ -0,0 +1,184 @@ +package me.trouper.clonedupecore.server.trolls; + +import me.trouper.alias.server.systems.AbstractWand; +import me.trouper.alias.server.systems.Text; +import me.trouper.alias.server.systems.tracing.CustomDisplayRaytracer; +import me.trouper.alias.utils.ItemBuilder; +import me.trouper.alias.utils.SoundPlayer; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.*; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.BlockDisplay; +import org.bukkit.entity.Display; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Transformation; +import org.joml.AxisAngle4f; +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicInteger; + +public class VoidTrollWand extends AbstractWand implements TrollFeature { + private static final ItemStack VOID_WAND = ItemBuilder.of(Material.ECHO_SHARD) + .displayName(Text.color("&5&lVoid Wand").decoration(TextDecoration.ITALIC,false)) + .loreComponent( + Text.color("&8▪ &dRight-Click:&f Abyss"), + Text.color("&8▪ &dLeft-Click:&f Engulf") + ) + .build(); + + @Override + public String getName() { + return "void"; + } + + @Override + public String getDescription() { + return "Get a wand to harness the power of the void."; + } + + @Override + public Rating getRating() { + return Rating.DAMAGING; + } + + @Override + public void execute(CommandSender sender, Player target) {} + + @Override + public void stop(CommandSender sender, Player target) {} + + public VoidTrollWand() { + super("clonedupe.troll",VOID_WAND); + } + + @Override + protected void onRightClick(Player player) { + CustomDisplayRaytracer.traceDelayed(main.getPlugin(),player.getEyeLocation(),player.getEyeLocation().getDirection(),30,1, point -> { + List targets = point.getNearbyEntities(player,1,true, entity->entity instanceof LivingEntity).stream().map(entity -> (LivingEntity) entity).toList(); + if (targets.isEmpty()) { + SoundPlayer.play(point.getLoc(), Sound.BLOCK_SOUL_SAND_BREAK, 1,0.4F,10); + Particle.ASH.builder().location(point.getLoc()).offset(0.1,0.1,0.1).count(10).spawn(); + return false; + } + LivingEntity target = targets.getFirst(); + + Location targetLoc = target.getLocation().clone(); + Location originalLoc = target.getLocation().clone().add(0,0.1,0); + SoundPlayer.play(target.getLocation(), Sound.ITEM_TRIDENT_THUNDER, 1,0.4F,10); + + + World w = target.getWorld(); + AtomicInteger counter = new AtomicInteger(0); + Bukkit.getScheduler().runTaskTimer(main.getPlugin(),(task) -> { + if (counter.getAndIncrement() > 60) { + task.cancel(); + return; + } + + target.teleport(targetLoc.subtract(0, 0.05, 0)); + w.spawnParticle(Particle.BLOCK, originalLoc, 50, 0.5, 0, 0.5, 0, Material.BLACK_CONCRETE.createBlockData()); + w.spawnParticle(Particle.SQUID_INK, originalLoc, 50, 0.5, 0, 0.5, 0); + },0,1); + + return true; + }); + } + + @Override + protected void onLeftClick(Player player) { + CustomDisplayRaytracer.traceDelayed(main.getPlugin(),player.getEyeLocation(),player.getEyeLocation().getDirection(),30,1,point -> { + List targets = point.getNearbyEntities(player, 1, true, entity -> entity instanceof LivingEntity).stream().map(entity -> (LivingEntity) entity).toList(); + if (targets.isEmpty()) { + SoundPlayer.play(point.getLoc(), Sound.BLOCK_SOUL_SAND_BREAK, 1,0.4F,10); + Particle.ASH.builder().location(point.getLoc()).offset(0.1, 0.1, 0.1).count(10).spawn(); + return false; + } + LivingEntity target = targets.getFirst(); + + engulf(target); + return true; + }); + } + + public void engulf(LivingEntity target) { + List displays = new ArrayList<>(); + + final double effectLength = 80; + final double maxHeight = 2.5; + final int blocksPerRing = 6; + final double ringSpacing = 0.3; + final double baseRadius = 0.8; + final double ticksPerRing = effectLength / (maxHeight / ringSpacing); + final Location targetLoc = target.getLocation(); + + AtomicInteger ticksElapsed = new AtomicInteger(); + Bukkit.getScheduler().runTaskTimer(main.getPlugin(),(task)->{ + if (ticksElapsed.getAndIncrement() > effectLength) { + target.setHealth(0); + SoundPlayer.play(targetLoc, Sound.ENTITY_DROWNED_DEATH_WATER, 1,0.4F,10); + displays.forEach(BlockDisplay::remove); + task.cancel(); + return; + } + + double height = (ticksElapsed.get() / ticksPerRing) * ringSpacing; + displays.addAll(voidRing(targetLoc,height,baseRadius,blocksPerRing)); + target.teleport(targetLoc); + SoundPlayer.play(targetLoc, Sound.BLOCK_BUBBLE_COLUMN_BUBBLE_POP, 1,0.4F,10); + if (ticksElapsed.get() % 20 == 0) SoundPlayer.play(targetLoc, Sound.ENTITY_DROWNED_HURT, 1,0.4F,10); + + },0,1); + } + + public static List voidRing(Location center, double yOffset, double radius, int blockCount) { + List displays = new ArrayList<>(); + ThreadLocalRandom localRandom = ThreadLocalRandom.current(); + + double angleStep = (2 * Math.PI) / blockCount; + + for (int i = 0; i < blockCount; i++) { + double angle = i * angleStep; + + double randomRadius = radius + (localRandom.nextDouble() - 0.5) * 0.5; + double x = center.getX() + Math.cos(angle) * randomRadius; + double z = center.getZ() + Math.sin(angle) * randomRadius; + double y = center.getY() + yOffset; + + Location blockLoc = new Location(center.getWorld(), x, y, z); + + BlockDisplay display = center.getWorld().spawn(blockLoc, BlockDisplay.class); + display.setBlock(Material.BLACK_CONCRETE.createBlockData()); + display.addScoreboardTag("$/CloneDupeCore/ Temp"); + display.setBrightness(new Display.Brightness(0,0)); + + Vector3f translation = new Vector3f(0, 0, 0); + Vector3f scale = new Vector3f( + 0.6f + localRandom.nextFloat() * 0.8f, + 0.6f + localRandom.nextFloat() * 0.8f, + 0.6f + localRandom.nextFloat() * 0.8f + ); + + AxisAngle4f leftRotation = new AxisAngle4f( + localRandom.nextFloat() * (float)(2 * Math.PI), + localRandom.nextFloat() - 0.5f, + localRandom.nextFloat() - 0.5f, + localRandom.nextFloat() - 0.5f + ); + AxisAngle4f rightRotation = new AxisAngle4f(0, 0, 0, 0); + + Transformation transformation = new Transformation(translation, leftRotation, scale, rightRotation); + display.setTransformation(transformation); + + displays.add(display); + } + + return displays; + } + + +} diff --git a/src/main/java/me/trouper/clonedupecore/utils/ArmorUtils.java b/src/main/java/me/trouper/clonedupecore/utils/ArmorUtils.java new file mode 100644 index 0000000..5b1088b --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/utils/ArmorUtils.java @@ -0,0 +1,44 @@ +package me.trouper.clonedupecore.utils; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +public class ArmorUtils { + 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"); + } +} diff --git a/src/main/java/me/trouper/clonedupecore/utils/PacketUtils.java b/src/main/java/me/trouper/clonedupecore/utils/PacketUtils.java new file mode 100644 index 0000000..0d35ade --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/utils/PacketUtils.java @@ -0,0 +1,29 @@ +package me.trouper.clonedupecore.utils; + +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.protocol.player.User; +import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState; +import com.github.retrooper.packetevents.util.Vector3i; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerBlockChange; +import io.github.retrooper.packetevents.util.SpigotConversionUtil; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Player; + +public class PacketUtils { + public static void sendGhostBlock(User target, Location loc, Material material) { + if (!material.isBlock()) throw new IllegalArgumentException("Only block materials are allowed!"); + Vector3i locVec = new Vector3i(loc.getBlockX(),loc.getBlockY(),loc.getBlockZ()); + BlockData blockData = material.createBlockData(); + WrappedBlockState data = SpigotConversionUtil.fromBukkitBlockData(blockData); + + WrapperPlayServerBlockChange packet = new WrapperPlayServerBlockChange(locVec,data.getGlobalId()); + packet.setBlockState(data); + target.sendPacket(packet); + } + + public static User getUser(Player bukkitPlayer) { + return PacketEvents.getAPI().getPlayerManager().getUser(bukkitPlayer); + } +} diff --git a/src/main/java/me/trouper/clonedupecore/utils/PlayerUtils.java b/src/main/java/me/trouper/clonedupecore/utils/PlayerUtils.java new file mode 100755 index 0000000..efa83a1 --- /dev/null +++ b/src/main/java/me/trouper/clonedupecore/utils/PlayerUtils.java @@ -0,0 +1,113 @@ +package me.trouper.clonedupecore.utils; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.damage.DamageSource; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.util.Vector; + +import java.util.*; + +public class PlayerUtils { + + 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); + } + + public static void instantDamage(LivingEntity target, DamageSource source, double amount) { + int max = target.getMaximumNoDamageTicks(); + int ticks = target.getNoDamageTicks(); + target.setMaximumNoDamageTicks(1); + target.setNoDamageTicks(0); + target.damage(amount,source); + target.setMaximumNoDamageTicks(max); + target.setNoDamageTicks(ticks); + } + + public static void instantTrueDamage(LivingEntity target, DamageSource source, double amount) { + int max = target.getMaximumNoDamageTicks(); + int ticks = target.getNoDamageTicks(); + target.setMaximumNoDamageTicks(1); + target.setNoDamageTicks(0); + dealTrueDamage(target,source,amount); + target.setMaximumNoDamageTicks(max); + target.setNoDamageTicks(ticks); + } + + public static void dealTrueDamage(LivingEntity target, DamageSource source, double amount) { + if (source.getDirectEntity() instanceof Player a && target instanceof Player t) return; + + EntityEquipment equipment = target.getEquipment(); + if (equipment == null) { + target.damage(amount, source); + return; + } + + ItemStack helmet = equipment.getHelmet(); + ItemStack chestplate = equipment.getChestplate(); + ItemStack leggings = equipment.getLeggings(); + ItemStack boots = equipment.getBoots(); + + equipment.setHelmet(null); + equipment.setChestplate(null); + equipment.setLeggings(null); + equipment.setBoots(null); + + try { + target.damage(amount, source); + } finally { + equipment.setHelmet(helmet); + equipment.setChestplate(chestplate); + equipment.setLeggings(leggings); + equipment.setBoots(boots); + } + } +} + + diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..ae4a980 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,46 @@ +name: CloneDupeCore +version: '1.0-SNAPSHOT' +main: me.trouper.clonedupecore.CloneDupeCore +api-version: '1.21' +depend: + - packetevents + - LiteBans + - NBTAPI +load: POSTWORLD +authors: [ obvWolf ] +description: Server Utilities for CloneDupe +commands: + ping: + usage: /ping [player] + permission: clonedupe.ping + description: Display the ping of you or another player. + clonetroll: + usage: /troll [player] [stop] + permission: clonedupe.troll + description: Many features to harras players with. + aliases: + - troll + - ct + clonedupecore: + usage: /clonedupecore + permission: clonedupe.admin + offend: + usage: "/offend " + permission: clonedupe.offend + aliases: + - punish + trimeffect: + usage: /trimeffect +permissions: + clonedupe.troll: + default: op + description: Allows a player to use troll features and the troll wand. + clonedupe.ping: + default: true + description: Allows the use of the ping command. Enabled by default. + clonedupe.admin: + default: op + description: Allows the use of the admin command. + clonedupe.offend: + default: op + description: Allows punishment of players through litebans. \ No newline at end of file