From 27b53217d7d63b344fb5fbe54580d7c2561e2ef1 Mon Sep 17 00:00:00 2001 From: Pyr Date: Mon, 17 Apr 2023 17:15:50 +0100 Subject: [PATCH] add code --- .gitignore | 42 + .idea/.gitignore | 3 + .idea/discord.xml | 7 + .idea/gradle.xml | 18 + .idea/inspectionProfiles/Project_Default.xml | 50 ++ .idea/misc.xml | 13 + .idea/uiDesigner.xml | 124 +++ .idea/vcs.xml | 6 + README.md | 0 build.gradle | 45 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59821 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 234 +++++ gradlew.bat | 89 ++ settings.gradle | 2 + .../znpcservers/UnexpectedCallException.java | 7 + .../znpcservers/cache/CacheCategory.java | 42 + .../znpcservers/cache/CachePackage.java | 35 + .../znpcservers/cache/CacheRegistry.java | 814 ++++++++++++++++++ .../znpcservers/cache/TypeCache.java | 311 +++++++ .../znpcservers/commands/Command.java | 89 ++ .../commands/CommandExecuteException.java | 7 + .../commands/CommandInformation.java | 18 + .../znpcservers/commands/CommandInvoker.java | 27 + .../commands/CommandNotFoundException.java | 7 + .../commands/CommandPermissionException.java | 7 + .../znpcservers/commands/CommandSender.java | 64 ++ .../commands/list/DefaultCommand.java | 574 ++++++++++++ .../list/inventory/ConversationGUI.java | 127 +++ .../configuration/Configuration.java | 127 +++ .../configuration/ConfigurationConstants.java | 26 + .../configuration/ConfigurationValue.java | 82 ++ .../listeners/InventoryListener.java | 31 + .../znpcservers/listeners/PlayerListener.java | 48 ++ .../znpcservers/npc/CustomizationLoader.java | 51 ++ .../znpcservers/npc/FunctionContext.java | 34 + .../znpcservers/npc/FunctionFactory.java | 41 + .../znetworkw/znpcservers/npc/ItemSlot.java | 27 + .../github/znetworkw/znpcservers/npc/NPC.java | 357 ++++++++ .../znetworkw/znpcservers/npc/NPCAction.java | 97 +++ .../znpcservers/npc/NPCFunction.java | 66 ++ .../znetworkw/znpcservers/npc/NPCModel.java | 238 +++++ .../znetworkw/znpcservers/npc/NPCPath.java | 306 +++++++ .../znetworkw/znpcservers/npc/NPCSkin.java | 71 ++ .../znetworkw/znpcservers/npc/NPCType.java | 135 +++ .../znetworkw/znpcservers/npc/NamingType.java | 15 + .../znpcservers/npc/TypeProperty.java | 41 + .../npc/conversation/Conversation.java | 61 ++ .../npc/conversation/ConversationKey.java | 63 ++ .../npc/conversation/ConversationModel.java | 76 ++ .../conversation/ConversationProcessor.java | 79 ++ .../znpcservers/npc/event/ClickType.java | 13 + .../npc/event/NPCInteractEvent.java | 47 + .../npc/function/GlowFunction.java | 46 + .../znpcservers/npc/hologram/Hologram.java | 142 +++ .../npc/hologram/replacer/LineReplacer.java | 26 + .../npc/hologram/replacer/RGBLine.java | 44 + .../znpcservers/npc/packet/Packet.java | 112 +++ .../znpcservers/npc/packet/PacketCache.java | 87 ++ .../znpcservers/npc/packet/PacketFactory.java | 19 + .../znpcservers/npc/packet/PacketV16.java | 28 + .../znpcservers/npc/packet/PacketV17.java | 27 + .../znpcservers/npc/packet/PacketV18.java | 15 + .../znpcservers/npc/packet/PacketV19.java | 23 + .../znpcservers/npc/packet/PacketV8.java | 75 ++ .../znpcservers/npc/packet/PacketV9.java | 46 + .../znpcservers/npc/packet/PacketValue.java | 14 + .../znpcservers/npc/packet/ValueType.java | 20 + .../znpcservers/npc/task/NPCLoadTask.java | 34 + .../znpcservers/npc/task/NPCManagerTask.java | 43 + .../znpcservers/npc/task/NPCSaveTask.java | 15 + .../znpcservers/skin/SkinFetcher.java | 89 ++ .../znpcservers/skin/SkinFetcherBuilder.java | 76 ++ .../znpcservers/skin/SkinFetcherResult.java | 5 + .../znpcservers/user/EventService.java | 60 ++ .../znetworkw/znpcservers/user/ZUser.java | 139 +++ .../znpcservers/utility/BungeeUtils.java | 28 + .../znpcservers/utility/GuavaCollectors.java | 23 + .../znpcservers/utility/MetricsLite.java | 263 ++++++ .../znpcservers/utility/ReflectionUtils.java | 23 + .../znpcservers/utility/SchedulerUtils.java | 34 + .../znetworkw/znpcservers/utility/Utils.java | 86 ++ .../utility/inventory/ZInventory.java | 51 ++ .../utility/inventory/ZInventoryCallback.java | 7 + .../utility/inventory/ZInventoryHolder.java | 20 + .../utility/inventory/ZInventoryItem.java | 36 + .../utility/inventory/ZInventoryPage.java | 82 ++ .../utility/itemstack/ItemStackBuilder.java | 53 ++ .../itemstack/ItemStackSerializer.java | 80 ++ .../utility/location/ZLocation.java | 102 +++ .../java/lol/pyr/znpcsplus/ZNPCsPlus.java | 95 ++ src/main/resources/plugin.yml | 5 + 92 files changed, 7072 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/discord.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/uiDesigner.xml create mode 100644 .idea/vcs.xml create mode 100644 README.md create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/java/io/github/znetworkw/znpcservers/UnexpectedCallException.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/cache/CacheCategory.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/cache/CachePackage.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/cache/CacheRegistry.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/cache/TypeCache.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/commands/Command.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/commands/CommandExecuteException.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/commands/CommandInformation.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/commands/CommandInvoker.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/commands/CommandNotFoundException.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/commands/CommandPermissionException.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/commands/CommandSender.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/commands/list/DefaultCommand.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/commands/list/inventory/ConversationGUI.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/configuration/Configuration.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/configuration/ConfigurationConstants.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/configuration/ConfigurationValue.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/listeners/InventoryListener.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/listeners/PlayerListener.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/CustomizationLoader.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/FunctionContext.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/FunctionFactory.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/ItemSlot.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/NPC.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/NPCAction.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/NPCFunction.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/NPCModel.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/NPCPath.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/NPCSkin.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/NPCType.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/NamingType.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/TypeProperty.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/conversation/Conversation.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/conversation/ConversationKey.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/conversation/ConversationModel.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/conversation/ConversationProcessor.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/event/ClickType.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/event/NPCInteractEvent.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/function/GlowFunction.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/hologram/Hologram.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/hologram/replacer/LineReplacer.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/hologram/replacer/RGBLine.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/packet/Packet.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketCache.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketFactory.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV16.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV17.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV18.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV19.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV8.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV9.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketValue.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/packet/ValueType.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/task/NPCLoadTask.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/task/NPCManagerTask.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/npc/task/NPCSaveTask.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/skin/SkinFetcher.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/skin/SkinFetcherBuilder.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/skin/SkinFetcherResult.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/user/EventService.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/user/ZUser.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/utility/BungeeUtils.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/utility/GuavaCollectors.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/utility/MetricsLite.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/utility/ReflectionUtils.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/utility/SchedulerUtils.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/utility/Utils.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/utility/inventory/ZInventory.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/utility/inventory/ZInventoryCallback.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/utility/inventory/ZInventoryHolder.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/utility/inventory/ZInventoryItem.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/utility/inventory/ZInventoryPage.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/utility/itemstack/ItemStackBuilder.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/utility/itemstack/ItemStackSerializer.java create mode 100644 src/main/java/io/github/znetworkw/znpcservers/utility/location/ZLocation.java create mode 100644 src/main/java/lol/pyr/znpcsplus/ZNPCsPlus.java create mode 100644 src/main/resources/plugin.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b63da45 --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..d8e9561 --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..26dd40c --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..a414ae3 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,50 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..59d7055 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..f135a7a --- /dev/null +++ b/build.gradle @@ -0,0 +1,45 @@ +plugins { + id "java" + id "com.github.johnrengelman.shadow" version "8.1.0" +} + +group "lol.pyr" +version "1.0" + +repositories { + mavenCentral() + maven { + url "https://hub.spigotmc.org/nexus/content/repositories/snapshots/" + } + maven { + url "https://repo.extendedclip.com/content/repositories/placeholderapi/" + } + maven { + url "https://repo.pyr.lol/releases/" + } +} + +dependencies { + compileOnly "org.spigotmc:spigot-api:1.19.2-R0.1-SNAPSHOT" + compileOnly "me.clip:placeholderapi:2.11.1" +} + +group "net.boxpvpv" +version "1.0.0" + +compileJava { + options.release.set(17) +} + +shadowJar { + archiveClassifier.set "" + relocate "lol.pyr.extendedcommands", "lol.pyr.znpcsplus.lib.command" + minimize() +} + +processResources { + expand("version": version) +} + +tasks.compileJava.dependsOn clean +tasks.jar.finalizedBy shadowJar \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..41d9927a4d4fb3f96a785543079b8df6723c946b GIT binary patch literal 59821 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..f72df95 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..1b6c787 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/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/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# 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"' + +# 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 + which java >/dev/null 2>&1 || 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 + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + 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 + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# 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..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@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=. +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%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +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%"=="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! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..82ab976 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'ZNPCsPlus' + diff --git a/src/main/java/io/github/znetworkw/znpcservers/UnexpectedCallException.java b/src/main/java/io/github/znetworkw/znpcservers/UnexpectedCallException.java new file mode 100644 index 0000000..b218a52 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/UnexpectedCallException.java @@ -0,0 +1,7 @@ +package io.github.znetworkw.znpcservers; + +public class UnexpectedCallException extends RuntimeException { + public UnexpectedCallException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/cache/CacheCategory.java b/src/main/java/io/github/znetworkw/znpcservers/cache/CacheCategory.java new file mode 100644 index 0000000..b218ecb --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/cache/CacheCategory.java @@ -0,0 +1,42 @@ +package io.github.znetworkw.znpcservers.cache; + +public enum CacheCategory { + DEFAULT(""), + NETWORK("network"), + PROTOCOL("network.protocol"), + CHAT("network.chat"), + PACKET("network.protocol.game"), + SYNCHER("network.syncher"), + ENTITY("world.entity"), + WORLD_ENTITY_PLAYER("world.entity.player"), + ITEM("world.item"), + WORLD_LEVEL("world.level"), + WORLD_SCORES("world.scores"), + SERVER_LEVEL("server.level"), + SERVER_NETWORK("server.network"), + SERVER("server"); + + private static final String EMPTY_STRING = ""; + + private final String subPackageName; + + private final String packageName; + + CacheCategory(String subPackageName) { + this.subPackageName = subPackageName; + StringBuilder stringBuilder = new StringBuilder(CachePackage.MINECRAFT_SERVER.getFixedPackageName()); + if (subPackageName.length() > 0) { + stringBuilder.append("."); + stringBuilder.append(subPackageName); + } + this.packageName = stringBuilder.toString(); + } + + public String getSubPackageName() { + return this.subPackageName; + } + + public String getPackageName() { + return this.packageName; + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/cache/CachePackage.java b/src/main/java/io/github/znetworkw/znpcservers/cache/CachePackage.java new file mode 100644 index 0000000..3d776fd --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/cache/CachePackage.java @@ -0,0 +1,35 @@ +package io.github.znetworkw.znpcservers.cache; + +import io.github.znetworkw.znpcservers.utility.Utils; + +public enum CachePackage { + DEFAULT, + CRAFT_BUKKIT("org.bukkit.craftbukkit." + Utils.getBukkitPackage()), + MINECRAFT_SERVER("net.minecraft"); + + private static final String EMPTY_STRING = ""; + + private static final String DOT = "."; + + private final String fixedPackageName; + + CachePackage(String packageName) { + this + + .fixedPackageName = Utils.versionNewer(17) ? packageName : (packageName + (packageName.contains("minecraft") ? (".server." + Utils.getBukkitPackage()) : "")); + } + + CachePackage() { + this.fixedPackageName = ""; + } + + public String getForCategory(CacheCategory packetCategory, String extra) { + return Utils.versionNewer(17) ? (packetCategory + .getPackageName() + ((extra.length() > 0) ? ("." + extra) : "")) : + this.fixedPackageName; + } + + public String getFixedPackageName() { + return this.fixedPackageName; + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/cache/CacheRegistry.java b/src/main/java/io/github/znetworkw/znpcservers/cache/CacheRegistry.java new file mode 100644 index 0000000..0f9458d --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/cache/CacheRegistry.java @@ -0,0 +1,814 @@ +package io.github.znetworkw.znpcservers.cache; + +import com.mojang.authlib.GameProfile; +import io.github.znetworkw.znpcservers.utility.Utils; +import io.netty.channel.Channel; +import org.bukkit.inventory.ItemStack; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +public final class CacheRegistry { + public static final Class PACKET_PLAY_IN_USE_ENTITY_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName("PacketPlayInUseEntity"))).load(); + + public static final Class ENUM_PLAYER_INFO_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName("PacketPlayOutPlayerInfo$EnumPlayerInfoAction") + .withClassName("ClientboundPlayerInfoUpdatePacket$a"))).load(); + + public static final Class PACKET_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PROTOCOL) + .withClassName("Packet"))).load(); + + public static final Class ENTITY_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withClassName("Entity"))).load(); + + public static final Class ENTITY_LIVING = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withClassName("EntityLiving"))).load(); + + public static final Class ENTITY_PLAYER_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.SERVER_LEVEL) + .withClassName("EntityPlayer"))).load(); + + public static final Class ENTITY_ARMOR_STAND_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("decoration") + .withClassName("EntityArmorStand"))).load(); + + public static final Class ENTITY_BAT_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("ambient") + .withClassName("EntityBat"))).load(); + + public static final Class ENTITY_BLAZE_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("monster") + .withClassName("EntityBlaze"))).load(); + + public static final Class ENTITY_CAVE_SPIDER_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("monster") + .withClassName("EntityCaveSpider"))).load(); + + public static final Class ENTITY_CHICKEN_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("animal") + .withClassName("EntityChicken"))).load(); + + public static final Class ENTITY_COW_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("animal") + .withClassName("EntityCow"))).load(); + + public static final Class ENTITY_CREEPER_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("monster") + .withClassName("EntityCreeper"))).load(); + + public static final Class ENTITY_ENDER_DRAGON_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("boss.enderdragon") + .withClassName("EntityEnderDragon"))).load(); + + public static final Class ENTITY_ENDERMAN_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("monster") + .withClassName("EntityEnderman"))).load(); + + public static final Class ENTITY_HUMAN_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("player") + .withClassName("EntityHuman"))).load(); + + public static final Class ENTITY_ENDERMITE_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("monster") + .withClassName("EntityEndermite"))).load(); + + public static final Class ENTITY_GHAST_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("monster") + .withClassName("EntityGhast"))).load(); + + public static final Class ENTITY_IRON_GOLEM_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("animal") + .withClassName("EntityIronGolem"))).load(); + + public static final Class ENTITY_GIANT_ZOMBIE_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("monster") + .withClassName("EntityGiantZombie"))).load(); + + public static final Class ENTITY_GUARDIAN_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("monster") + .withClassName("EntityGuardian"))).load(); + + public static final Class ENTITY_HORSE_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("animal.horse") + .withClassName("EntityHorse"))).load(); + + public static final Class ENTITY_LLAMA_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("animal.horse") + .withClassName("EntityLlama"))).load(); + + public static final Class ENTITY_MAGMA_CUBE_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("monster") + .withClassName("EntityMagmaCube"))).load(); + + public static final Class ENTITY_MUSHROOM_COW_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("animal") + .withClassName("EntityMushroomCow"))).load(); + + public static final Class ENTITY_OCELOT_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("animal") + .withClassName("EntityOcelot"))).load(); + + public static final Class ENTITY_TURTLE = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("animal") + .withClassName("EntityTurtle"))).load(); + + public static final Class ENTITY_WARDEN = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("monster.warden") + .withClassName("EntityWarden"))).load(); + + public static final Class ENTITY_BEE_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("animal") + .withClassName("EntityBee"))).load(); + + public static final Class ENTITY_PARROT_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("animal") + .withClassName("EntityParrot"))).load(); + + public static final Class ENTITY_PIG_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("animal") + .withClassName("EntityPig"))).load(); + + public static final Class ENTITY_RABBIT_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("animal") + .withClassName("EntityRabbit"))).load(); + + public static final Class ENTITY_POLAR_BEAR_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("animal") + .withClassName("EntityPolarBear"))).load(); + + public static final Class ENTITY_PANDA_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("animal") + .withClassName("EntityPanda"))).load(); + + public static final Class ENTITY_SHEEP_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("animal") + .withClassName("EntitySheep"))).load(); + + public static final Class ENTITY_SNOWMAN_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("animal") + .withClassName("EntitySnowman"))).load(); + + public static final Class ENTITY_SHULKER_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("monster") + .withClassName("EntityShulker"))).load(); + + public static final Class ENTITY_SILVERFISH_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("monster") + .withClassName("EntitySilverfish"))).load(); + + public static final Class ENTITY_SKELETON_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("monster") + .withClassName("EntitySkeleton"))).load(); + + public static final Class ENTITY_SLIME_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("monster") + .withClassName("EntitySlime"))).load(); + + public static final Class ENTITY_SPIDER_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("monster") + .withClassName("EntitySpider"))).load(); + + public static final Class ENTITY_SQUID_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("animal") + .withClassName("EntitySquid"))).load(); + + public static final Class ENTITY_VILLAGER_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("npc") + .withClassName("EntityVillager"))).load(); + + public static final Class ENTITY_WITCH_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("monster") + .withClassName("EntityWitch"))).load(); + + public static final Class ENTITY_WITHER_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("boss.wither") + .withClassName("EntityWither"))).load(); + + public static final Class ENTITY_ZOMBIE_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("monster") + .withClassName("EntityZombie"))).load(); + + public static final Class ENTITY_WOLF_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("animal") + .withClassName("EntityWolf"))).load(); + + public static final Class ENTITY_AXOLOTL_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("animal.axolotl") + .withClassName("Axolotl"))).load(); + + public static final Class ENTITY_GOAT_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("animal.goat") + .withClassName("Goat"))).load(); + + public static final Class ENTITY_FOX_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withAdditionalData("animal") + .withClassName("EntityFox"))).load(); + + public static final Class ENTITY_TYPES_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withClassName("EntityTypes"))).load(); + + public static final Class ENUM_CHAT_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withClassName("EnumChatFormat"))).load(); + + public static final Class ENUM_ITEM_SLOT = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withClassName("EnumItemSlot"))).load(); + + public static final Class I_CHAT_BASE_COMPONENT = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.CHAT) + .withClassName("IChatBaseComponent"))).load(); + + public static final Class ITEM_STACK_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ITEM) + .withClassName("ItemStack"))).load(); + + public static final Class DATA_WATCHER_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.SYNCHER) + .withClassName("DataWatcher"))).load(); + + public static final Class DATA_WATCHER_OBJECT = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.SYNCHER) + .withClassName("DataWatcherObject"))).load(); + + public static final Class DATA_WATCHER_REGISTRY = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.SYNCHER) + .withClassName("DataWatcherRegistry"))).load(); + + public static final Class DATA_WATCHER_SERIALIZER = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.SYNCHER) + .withClassName("DataWatcherSerializer"))).load(); + + public static final Class WORLD_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.WORLD_LEVEL) + .withClassName("World"))).load(); + + public static final Class CRAFT_ITEM_STACK_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.CRAFT_BUKKIT)) + + .withClassName("inventory.CraftItemStack"))).load(); + + public static final Class WORLD_SERVER_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.SERVER_LEVEL) + .withClassName("WorldServer"))).load(); + + public static final Class MINECRAFT_SERVER_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.SERVER) + .withClassName("MinecraftServer"))).load(); + + public static final Class PLAYER_INTERACT_MANAGER_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.SERVER_LEVEL) + .withClassName("PlayerInteractManager"))).load(); + + public static final Class PLAYER_CONNECTION_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.SERVER_NETWORK) + .withClassName("PlayerConnection"))).load(); + + public static final Class NETWORK_MANAGER_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.NETWORK) + .withClassName("NetworkManager"))).load(); + + public static final Class PACKET_PLAY_OUT_PLAYER_INFO_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName("PacketPlayOutPlayerInfo") + .withClassName("ClientboundPlayerInfoUpdatePacket"))) + .load(); + + public static final Class PACKET_PLAY_OUT_PLAYER_INFO_REMOVE_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName("ClientboundPlayerInfoRemovePacket"))) + .load(); + + public static final Class PACKET_PLAY_OUT_SCOREBOARD_TEAM_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName("PacketPlayOutScoreboardTeam"))).load(); + + public static final Class PACKET_PLAY_OUT_ENTITY_DESTROY_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName("PacketPlayOutEntityDestroy"))).load(); + + public static final Class SCOREBOARD_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.WORLD_SCORES) + .withClassName("Scoreboard"))).load(); + + public static final Class SCOREBOARD_TEAM_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.WORLD_SCORES) + .withClassName("ScoreboardTeam"))).load(); + + public static final Class ENUM_TAG_VISIBILITY = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.WORLD_SCORES) + .withClassName("ScoreboardTeamBase$EnumNameTagVisibility"))).load(); + + public static final Class CRAFT_CHAT_MESSAGE_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.CRAFT_BUKKIT)) + + .withClassName("util.CraftChatMessage"))).load(); + + public static final Class PROFILE_PUBLIC_KEY_CLASS = (new TypeCache.BaseCache.ClazzLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.WORLD_ENTITY_PLAYER) + .withClassName("ProfilePublicKey"))).load(); + + public static final TypeCache.BaseCache> SCOREBOARD_TEAM_CONSTRUCTOR = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(SCOREBOARD_TEAM_CLASS) + .withParameterTypes(SCOREBOARD_CLASS, String.class)); + + public static final TypeCache.BaseCache> PLAYER_CONSTRUCTOR_OLD = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(ENTITY_PLAYER_CLASS) + .withParameterTypes(MINECRAFT_SERVER_CLASS, WORLD_SERVER_CLASS, GameProfile.class, PLAYER_INTERACT_MANAGER_CLASS)); + + public static final TypeCache.BaseCache> PLAYER_CONSTRUCTOR_NEW = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(ENTITY_PLAYER_CLASS) + .withParameterTypes(MINECRAFT_SERVER_CLASS, WORLD_SERVER_CLASS, GameProfile.class)); + + public static final TypeCache.BaseCache> PLAYER_CONSTRUCTOR_NEW_1 = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(ENTITY_PLAYER_CLASS) + .withParameterTypes(MINECRAFT_SERVER_CLASS, WORLD_SERVER_CLASS, GameProfile.class, PROFILE_PUBLIC_KEY_CLASS)); + + public static final TypeCache.BaseCache> PLAYER_CONSTRUCTOR_NEW_2 = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(ENTITY_PLAYER_CLASS) + .withParameterTypes(MINECRAFT_SERVER_CLASS, WORLD_SERVER_CLASS, GameProfile.class)); + + public static final TypeCache.BaseCache> PACKET_PLAY_OUT_PLAYER_INFO_CONSTRUCTOR = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(PACKET_PLAY_OUT_PLAYER_INFO_CLASS) + .withParameterTypes(ENUM_PLAYER_INFO_CLASS, (Utils.BUKKIT_VERSION > 16) ? Collection.class : Iterable.class).withParameterTypes(ENUM_PLAYER_INFO_CLASS, ENTITY_PLAYER_CLASS)); + + public static final TypeCache.BaseCache> PACKET_PLAY_OUT_PLAYER_INFO_REMOVE_CONSTRUCTOR = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(PACKET_PLAY_OUT_PLAYER_INFO_REMOVE_CLASS) + .withParameterTypes(List.class)); + + public static final TypeCache.BaseCache> PACKET_PLAY_OUT_ENTITY_LOOK_CONSTRUCTOR = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName("PacketPlayOutEntity$PacketPlayOutEntityLook") + .withParameterTypes(int.class, byte.class, byte.class, boolean.class)); + + public static final TypeCache.BaseCache> PACKET_PLAY_OUT_ENTITY_HEAD_ROTATION_CONSTRUCTOR = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName("PacketPlayOutEntityHeadRotation") + .withParameterTypes(ENTITY_CLASS, byte.class)); + + public static final TypeCache.BaseCache> PACKET_PLAY_OUT_ENTITY_TELEPORT_CONSTRUCTOR = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName("PacketPlayOutEntityTeleport") + .withParameterTypes(ENTITY_CLASS)); + + public static final TypeCache.BaseCache> PACKET_PLAY_OUT_ENTITY_META_DATA_CONSTRUCTOR = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName("PacketPlayOutEntityMetadata") + .withParameterTypes(int.class, DATA_WATCHER_CLASS, boolean.class)); + + public static final TypeCache.BaseCache> PACKET_PLAY_OUT_ENTITY_META_DATA_CONSTRUCTOR_V1 = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName("PacketPlayOutEntityMetadata") + .withParameterTypes(int.class, List.class)); + + public static final TypeCache.BaseCache> PACKET_PLAY_OUT_NAMED_ENTITY_CONSTRUCTOR = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName("PacketPlayOutNamedEntitySpawn") + .withParameterTypes(ENTITY_HUMAN_CLASS)); + + public static final TypeCache.BaseCache> PACKET_PLAY_OUT_ENTITY_DESTROY_CONSTRUCTOR = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(PACKET_PLAY_OUT_ENTITY_DESTROY_CLASS) + .withParameterTypes(int.class).withParameterTypes(int[].class)); + + public static final TypeCache.BaseCache> PACKET_PLAY_OUT_SPAWN_ENTITY_CONSTRUCTOR = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName("PacketPlayOutSpawnEntity") + .withClassName("PacketPlayOutSpawnEntityLiving") + .withParameterTypes(ENTITY_LIVING).withParameterTypes(ENTITY_CLASS)); + + public static final TypeCache.BaseCache> PLAYER_INTERACT_MANAGER_OLD_CONSTRUCTOR = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName("PlayerInteractManager") + .withParameterTypes(WORLD_CLASS)); + + public static final TypeCache.BaseCache> PLAYER_INTERACT_MANAGER_NEW_CONSTRUCTOR = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName("PlayerInteractManager") + .withParameterTypes(WORLD_SERVER_CLASS)); + + public static final TypeCache.BaseCache> PACKET_PLAY_OUT_SCOREBOARD_TEAM_CONSTRUCTOR_OLD = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(PACKET_PLAY_OUT_SCOREBOARD_TEAM_CLASS)); + + public static final TypeCache.BaseCache> PACKET_PLAY_OUT_ENTITY_EQUIPMENT_CONSTRUCTOR_OLD = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName("PacketPlayOutEntityEquipment") + .withParameterTypes(int.class, int.class, ITEM_STACK_CLASS)); + + public static final TypeCache.BaseCache> PACKET_PLAY_OUT_ENTITY_EQUIPMENT_CONSTRUCTOR_NEWEST_OLD = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName("PacketPlayOutEntityEquipment") + .withParameterTypes(int.class, ENUM_ITEM_SLOT, ITEM_STACK_CLASS)); + + public static final TypeCache.BaseCache> PACKET_PLAY_OUT_ENTITY_EQUIPMENT_CONSTRUCTOR_V1 = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName("PacketPlayOutEntityEquipment") + .withParameterTypes(int.class, List.class)); + + public static final TypeCache.BaseCache> I_CHAT_BASE_COMPONENT_A_CONSTRUCTOR = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.CHAT) + .withClassName("ChatComponentText") + .withParameterTypes(String.class)); + + public static final TypeCache.BaseCache> ENTITY_CONSTRUCTOR = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(ENTITY_ARMOR_STAND_CLASS) + .withParameterTypes(WORLD_CLASS, double.class, double.class, double.class)); + + public static final TypeCache.BaseCache> DATA_WATCHER_OBJECT_CONSTRUCTOR = new TypeCache.BaseCache.ConstructorLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(DATA_WATCHER_OBJECT) + .withParameterTypes(int.class, DATA_WATCHER_SERIALIZER)); + + public static final TypeCache.BaseCache AS_NMS_COPY_METHOD = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.CRAFT_BUKKIT)) + + .withClassName("inventory.CraftItemStack") + .withMethodName("asNMSCopy") + .withParameterTypes(ItemStack.class)); + + public static final TypeCache.BaseCache GET_PROFILE_METHOD = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withClassName(ENTITY_HUMAN_CLASS) + .withExpectResult(GameProfile.class)); + + public static final TypeCache.BaseCache GET_ENTITY_ID = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(ENTITY_CLASS) + .withMethodName("getId") + .withMethodName("ae") + .withMethodName("ah") + .withExpectResult(int.class)); + + public static final TypeCache.BaseCache GET_HANDLE_PLAYER_METHOD = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.CRAFT_BUKKIT)) + + .withClassName("entity.CraftPlayer").withClassName("entity.CraftHumanEntity") + .withMethodName("getHandle")); + + public static final TypeCache.BaseCache GET_HANDLE_WORLD_METHOD = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.CRAFT_BUKKIT)) + + .withClassName("CraftWorld") + .withMethodName("getHandle")); + + public static final TypeCache.BaseCache GET_SERVER_METHOD = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.CRAFT_BUKKIT)) + + .withClassName("CraftServer") + .withMethodName("getServer")); + + public static final TypeCache.BaseCache SEND_PACKET_METHOD = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(PLAYER_CONNECTION_CLASS) + .withMethodName("sendPacket").withMethodName("a") + .withParameterTypes(PACKET_CLASS)); + + public static final TypeCache.BaseCache SET_CUSTOM_NAME_OLD_METHOD = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(ENTITY_CLASS) + .withMethodName("setCustomName") + .withParameterTypes(String.class)); + + public static final TypeCache.BaseCache SET_CUSTOM_NAME_NEW_METHOD = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(ENTITY_CLASS) + .withMethodName("setCustomName") + .withMethodName("a") + .withMethodName("b") + .withParameterTypes(I_CHAT_BASE_COMPONENT).withExpectResult(void.class)); + + public static final TypeCache.BaseCache SET_CUSTOM_NAME_VISIBLE_METHOD = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(ENTITY_CLASS) + .withMethodName("setCustomNameVisible") + .withMethodName("n") + .withParameterTypes(boolean.class)); + + public static final TypeCache.BaseCache SET_INVISIBLE_METHOD = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(ENTITY_ARMOR_STAND_CLASS) + .withMethodName("setInvisible").withMethodName("j") + .withParameterTypes(boolean.class)); + + public static final TypeCache.BaseCache SET_LOCATION_METHOD = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(ENTITY_CLASS) + .withMethodName("setPositionRotation") + .withMethodName("a") + .withParameterTypes(double.class, double.class, double.class, float.class, float.class)); + + public static final TypeCache.BaseCache SET_DATA_WATCHER_METHOD = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(DATA_WATCHER_CLASS) + .withMethodName("set").withMethodName("b") + .withParameterTypes(DATA_WATCHER_OBJECT, Object.class)); + + public static final TypeCache.BaseCache WATCH_DATA_WATCHER_METHOD = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(DATA_WATCHER_CLASS) + .withMethodName("watch") + .withParameterTypes(int.class, Object.class)); + + public static final TypeCache.BaseCache GET_DATA_WATCHER_METHOD = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(ENTITY_CLASS) + .withMethodName("getDataWatcher") + .withMethodName("ai") + .withMethodName("al") + .withExpectResult(DATA_WATCHER_CLASS)); + + public static final TypeCache.BaseCache GET_BUKKIT_ENTITY_METHOD = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(ENTITY_CLASS) + .withMethodName("getBukkitEntity")); + + public static final TypeCache.BaseCache GET_ENUM_CHAT_ID_METHOD = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(ENUM_CHAT_CLASS) + .withMethodName("b")); + + public static final TypeCache.BaseCache ENUM_CHAT_TO_STRING_METHOD = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(ENUM_CHAT_CLASS) + .withExpectResult(String.class) + .withMethodName("toString")); + + public static final TypeCache.BaseCache ENTITY_TYPES_A_METHOD = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.ENTITY) + .withClassName(ENTITY_TYPES_CLASS) + .withMethodName("a") + .withParameterTypes(String.class)); + + public static final TypeCache.BaseCache PACKET_PLAY_OUT_SCOREBOARD_TEAM_CREATE_V1 = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(PACKET_PLAY_OUT_SCOREBOARD_TEAM_CLASS) + .withMethodName("a") + .withParameterTypes(SCOREBOARD_TEAM_CLASS)); + + public static final TypeCache.BaseCache PACKET_PLAY_OUT_SCOREBOARD_TEAM_CREATE = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(PACKET_PLAY_OUT_SCOREBOARD_TEAM_CLASS) + .withMethodName("a") + .withParameterTypes(SCOREBOARD_TEAM_CLASS, boolean.class)); + + public static final TypeCache.BaseCache SCOREBOARD_PLAYER_LIST = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(SCOREBOARD_TEAM_CLASS) + .withMethodName("getPlayerNameSet").withMethodName("g")); + + public static final TypeCache.BaseCache ENUM_CHAT_FORMAT_FIND = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withClassName(ENUM_CHAT_CLASS) + .withParameterTypes(String.class).withExpectResult(ENUM_CHAT_CLASS)); + + public static final TypeCache.BaseCache CRAFT_CHAT_MESSAGE_METHOD = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.CRAFT_BUKKIT)) + + .withClassName(CRAFT_CHAT_MESSAGE_CLASS) + .withMethodName("fromStringOrNull") + .withParameterTypes(String.class)); + + public static final TypeCache.BaseCache GET_UNIQUE_ID_METHOD = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withClassName(ENTITY_CLASS) + .withExpectResult(UUID.class)); + + public static final TypeCache.BaseCache GET_DATAWATCHER_B_LIST = new TypeCache.BaseCache.MethodLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withMethodName("c") + .withClassName(DATA_WATCHER_CLASS)); + + public static final TypeCache.BaseCache PLAYER_CONNECTION_FIELD = new TypeCache.BaseCache.FieldLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.SERVER_LEVEL) + .withClassName(ENTITY_PLAYER_CLASS) + .withFieldName((Utils.BUKKIT_VERSION > 16) ? "b" : "playerConnection")); + + public static final TypeCache.BaseCache NETWORK_MANAGER_FIELD = new TypeCache.BaseCache.FieldLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(PLAYER_CONNECTION_CLASS) + .withFieldName((Utils.BUKKIT_VERSION > 16) ? "a" : "networkManager") + .withExpectResult(NETWORK_MANAGER_CLASS)); + + public static final TypeCache.BaseCache CHANNEL_FIELD = new TypeCache.BaseCache.FieldLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.SERVER_NETWORK) + .withClassName(NETWORK_MANAGER_CLASS) + .withExpectResult(Channel.class)); + + public static final TypeCache.BaseCache PACKET_IN_USE_ENTITY_ID_FIELD = new TypeCache.BaseCache.FieldLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName("PacketPlayInUseEntity") + .withFieldName("a")); + + public static final TypeCache.BaseCache BUKKIT_COMMAND_MAP = new TypeCache.BaseCache.FieldLoader((new TypeCache.CacheBuilder(CachePackage.CRAFT_BUKKIT)) + + .withClassName("CraftServer") + .withFieldName("commandMap")); + + public static final TypeCache.BaseCache ADD_PLAYER_FIELD = (new TypeCache.BaseCache.FieldLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName("PacketPlayOutPlayerInfo$EnumPlayerInfoAction") + .withClassName("ClientboundPlayerInfoUpdatePacket$a") + .withFieldName((Utils.BUKKIT_VERSION > 16) ? "a" : "ADD_PLAYER"))).asValueField(); + + public static final TypeCache.BaseCache UPDATE_LISTED_FIELD = (new TypeCache.BaseCache.FieldLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName("ClientboundPlayerInfoUpdatePacket$a") + .withFieldName("d"))).asValueField(); + + public static final TypeCache.BaseCache REMOVE_PLAYER_FIELD = (new TypeCache.BaseCache.FieldLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName("PacketPlayOutPlayerInfo$EnumPlayerInfoAction") + .withClassName("ClientboundPlayerInfoUpdatePacket$a") + .withFieldName((Utils.BUKKIT_VERSION > 16) ? "e" : "REMOVE_PLAYER"))).asValueField(); + + public static final TypeCache.BaseCache DATA_WATCHER_REGISTER_FIELD = (new TypeCache.BaseCache.FieldLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(DATA_WATCHER_REGISTRY) + .withFieldName("a"))).asValueField(); + + public static final TypeCache.BaseCache ENUM_TAG_VISIBILITY_NEVER_FIELD = (new TypeCache.BaseCache.FieldLoader((new TypeCache.CacheBuilder(CachePackage.MINECRAFT_SERVER)) + + .withCategory(CacheCategory.PACKET) + .withClassName(ENUM_TAG_VISIBILITY) + .withFieldName("b"))).asValueField(); +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/cache/TypeCache.java b/src/main/java/io/github/znetworkw/znpcservers/cache/TypeCache.java new file mode 100644 index 0000000..543291e --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/cache/TypeCache.java @@ -0,0 +1,311 @@ +package io.github.znetworkw.znpcservers.cache; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.UnmodifiableIterator; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +public interface TypeCache { + class ClassCache { + protected static final ConcurrentMap CACHE = new ConcurrentHashMap<>(); + + public static Object find(String name, Class objectClass) { + return CACHE.get(new CacheKey(name, objectClass)); + } + + public static void register(String name, Object object, Class objectClass) { + CACHE.putIfAbsent(new CacheKey(name, objectClass), object); + } + + private static class CacheKey { + private final Class type; + + private final String value; + + public CacheKey(String value, Class type) { + this.type = type; + this.value = value; + } + + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + CacheKey classKey = (CacheKey) o; + return (Objects.equals(this.type, classKey.type) && Objects.equals(this.value, classKey.value)); + } + + public int hashCode() { + return Objects.hash(this.type, this.value); + } + } + } + + class CacheBuilder { + private static final String EMPTY_STRING = ""; + + private final CachePackage cachePackage; + + private final CacheCategory cacheCategory; + + private final String fieldName; + + private final List className; + + private final List methods; + + private final String additionalData; + + private final Class clazz; + + private final ImmutableList[]> parameterTypes; + + private final Class expectType; + + public CacheBuilder(CachePackage cachePackage) { + this(cachePackage, CacheCategory.DEFAULT, new ArrayList<>(), "", new ArrayList<>(), "", + + ImmutableList.of(), null); + } + + protected CacheBuilder(CachePackage cachePackage, CacheCategory cacheCategory, List className, String fieldName, List methods, String additionalData, ImmutableList[]> parameterTypes, Class expectType) { + this.cachePackage = cachePackage; + this.cacheCategory = cacheCategory; + this.className = className; + this.methods = methods; + this.fieldName = fieldName; + this.additionalData = additionalData; + this.parameterTypes = parameterTypes; + this.clazz = null; + this.expectType = expectType; + } + + public CacheBuilder withCategory(CacheCategory cacheCategory) { + return new CacheBuilder(this.cachePackage, cacheCategory, this.className, this.fieldName, this.methods, this.additionalData, this.parameterTypes, this.expectType); + } + + public CacheBuilder withClassName(String className) { + return new CacheBuilder(this.cachePackage, this.cacheCategory, + (List) ImmutableList.builder().addAll(this.className) + .add(formatClass(className)).build(), this.fieldName, this.methods, this.additionalData, this.parameterTypes, this.expectType); + } + + public CacheBuilder withClassName(Class clazz) { + return new CacheBuilder(this.cachePackage, this.cacheCategory, + + (List) ImmutableList.builder().addAll(this.className).add((clazz == null) ? "" : clazz.getName()).build(), this.fieldName, this.methods, this.additionalData, this.parameterTypes, this.expectType); + } + + public CacheBuilder withMethodName(String methodName) { + return new CacheBuilder(this.cachePackage, this.cacheCategory, this.className, this.fieldName, + + (List) ImmutableList.builder().addAll(this.methods).add(methodName).build(), this.additionalData, this.parameterTypes, this.expectType); + } + + public CacheBuilder withFieldName(String fieldName) { + return new CacheBuilder(this.cachePackage, this.cacheCategory, this.className, fieldName, this.methods, this.additionalData, this.parameterTypes, this.expectType); + } + + public CacheBuilder withAdditionalData(String additionalData) { + return new CacheBuilder(this.cachePackage, this.cacheCategory, this.className, this.fieldName, this.methods, additionalData, this.parameterTypes, this.expectType); + } + + public CacheBuilder withParameterTypes(Class... types) { + return new CacheBuilder(this.cachePackage, this.cacheCategory, this.className, this.fieldName, this.methods, this.additionalData, + + ImmutableList.copyOf(Iterables.concat((Iterable) this.parameterTypes, (Iterable) ImmutableList.of(types))), this.expectType); + } + + public CacheBuilder withExpectResult(Class expectType) { + return new CacheBuilder(this.cachePackage, this.cacheCategory, this.className, this.fieldName, this.methods, this.additionalData, this.parameterTypes, expectType); + } + + protected String formatClass(String className) { + switch (this.cachePackage) { + case MINECRAFT_SERVER: + case CRAFT_BUKKIT: + return String.format(((this.cachePackage == CachePackage.CRAFT_BUKKIT) ? + this.cachePackage.getFixedPackageName() : this.cachePackage.getForCategory(this.cacheCategory, this.additionalData)) + ".%s", className); + case DEFAULT: + return className; + } + throw new IllegalArgumentException("Unexpected package " + this.cachePackage.name()); + } + } + + abstract class BaseCache { + private static final Logger LOGGER = Logger.getLogger(BaseCache.class.getName()); + + protected final TypeCache.CacheBuilder cacheBuilder; + + protected Class BUILDER_CLASS; + + private T cached; + + private boolean loaded = false; + + protected BaseCache(TypeCache.CacheBuilder cacheBuilder) { + this.cacheBuilder = cacheBuilder; + for (String classes : cacheBuilder.className) { + try { + this.BUILDER_CLASS = Class.forName(classes); + } catch (ClassNotFoundException classNotFoundException) { + } + } + } + + public T load() { + if (this.loaded) + return this.cached; + try { + if (this.BUILDER_CLASS == null) + throw new IllegalStateException("can't find class for: " + this.cacheBuilder + .className); + T eval = (this.cached != null) ? this.cached : (this.cached = onLoad()); + if (eval == null) + throw new NullPointerException(); + } catch (Throwable throwable) { + if (throwable instanceof IllegalStateException) + log("No cache found for: " + this.cacheBuilder.className); + log("No cache found for: " + this.cacheBuilder.className + " : " + this.cacheBuilder.methods.toString()); + log("Skipping cache for " + this.cacheBuilder.className); + } + this.loaded = true; + return this.cached; + } + + private void log(String message) { + LOGGER.log(Level.WARNING, message); + } + + protected abstract T onLoad() throws Exception; + + public static class ClazzLoader extends BaseCache> { + public ClazzLoader(TypeCache.CacheBuilder cacheBuilder) { + super(cacheBuilder); + } + + protected Class onLoad() { + return this.BUILDER_CLASS; + } + } + + public static class MethodLoader extends BaseCache { + public MethodLoader(TypeCache.CacheBuilder builder) { + super(builder); + } + + protected Method onLoad() { + Method methodThis = null; + List methods = this.cacheBuilder.methods; + boolean hasExpectedType = (this.cacheBuilder.expectType != null); + if (methods.isEmpty() && hasExpectedType) + for (Method method : this.BUILDER_CLASS.getDeclaredMethods()) { + if (method.getReturnType() == this.cacheBuilder.expectType) + return method; + } + for (String methodName : this.cacheBuilder.methods) { + try { + Method maybeGet; + if (!Iterables.isEmpty(this.cacheBuilder.parameterTypes)) { + maybeGet = this.BUILDER_CLASS.getDeclaredMethod(methodName, (Class[]) Iterables.get((Iterable) this.cacheBuilder.parameterTypes, 0)); + } else { + maybeGet = this.BUILDER_CLASS.getDeclaredMethod(methodName); + } + if (this.cacheBuilder.expectType != null && this.cacheBuilder.expectType != maybeGet.getReturnType()) + continue; + maybeGet.setAccessible(true); + methodThis = maybeGet; + } catch (NoSuchMethodException noSuchMethodException) { + } + } + return methodThis; + } + } + + public static class FieldLoader extends BaseCache { + public FieldLoader(TypeCache.CacheBuilder cacheBuilder) { + super(cacheBuilder); + } + + protected Field onLoad() throws NoSuchFieldException { + if (this.cacheBuilder.expectType != null) + for (Field field1 : this.BUILDER_CLASS.getDeclaredFields()) { + if (field1.getType() == this.cacheBuilder.expectType) { + field1.setAccessible(true); + return field1; + } + } + Field field = this.BUILDER_CLASS.getDeclaredField(this.cacheBuilder.fieldName); + field.setAccessible(true); + return field; + } + + public AsValueField asValueField() { + return new AsValueField(this); + } + + private static class AsValueField extends TypeCache.BaseCache { + private final TypeCache.BaseCache.FieldLoader fieldLoader; + + public AsValueField(TypeCache.BaseCache.FieldLoader fieldLoader) { + super(fieldLoader.cacheBuilder); + this.fieldLoader = fieldLoader; + } + + protected Object onLoad() throws IllegalAccessException, NoSuchFieldException { + Field field = this.fieldLoader.onLoad(); + return field.get(null); + } + } + } + + public static class ConstructorLoader extends BaseCache> { + public ConstructorLoader(TypeCache.CacheBuilder cacheBuilder) { + super(cacheBuilder); + } + + protected Constructor onLoad() throws NoSuchMethodException { + Constructor constructor = null; + if (Iterables.size(this.cacheBuilder.parameterTypes) > 1) { + for (UnmodifiableIterator[]> unmodifiableIterator = this.cacheBuilder.parameterTypes.iterator(); unmodifiableIterator.hasNext(); ) { + Class[] keyParameters = unmodifiableIterator.next(); + try { + constructor = this.BUILDER_CLASS.getDeclaredConstructor(keyParameters); + } catch (NoSuchMethodException noSuchMethodException) { + } + } + } else { + constructor = (Iterables.size(this.cacheBuilder.parameterTypes) > 0) ? this.BUILDER_CLASS.getDeclaredConstructor((Class[]) Iterables.get((Iterable) this.cacheBuilder.parameterTypes, 0)) : this.BUILDER_CLASS.getDeclaredConstructor(); + } + if (constructor != null) + constructor.setAccessible(true); + return constructor; + } + } + + public static class EnumLoader extends BaseCache[]> { + public EnumLoader(TypeCache.CacheBuilder cacheBuilder) { + super(cacheBuilder); + } + + protected Enum[] onLoad() { + Enum[] arrayOfEnum = (Enum[]) this.BUILDER_CLASS.getEnumConstants(); + for (Enum enumConstant : arrayOfEnum) + TypeCache.ClassCache.register(enumConstant.name(), enumConstant, this.BUILDER_CLASS); + return (Enum[]) arrayOfEnum; + } + } + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/commands/Command.java b/src/main/java/io/github/znetworkw/znpcservers/commands/Command.java new file mode 100644 index 0000000..7769713 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/commands/Command.java @@ -0,0 +1,89 @@ +package io.github.znetworkw.znpcservers.commands; + +import com.google.common.collect.Iterables; +import io.github.znetworkw.znpcservers.cache.CacheRegistry; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandMap; +import org.bukkit.command.CommandSender; +import org.bukkit.command.defaults.BukkitCommand; + +import java.lang.reflect.Method; +import java.util.*; + +public class Command extends BukkitCommand { + private static final String WHITESPACE = " "; + + private static final CommandMap COMMAND_MAP; + + static { + try { + COMMAND_MAP = (CommandMap) CacheRegistry.BUKKIT_COMMAND_MAP.load().get(Bukkit.getServer()); + } catch (IllegalAccessException exception) { + throw new IllegalStateException("can't access bukkit command map."); + } + } + + private final Map subCommands; + + public Command(String name) { + super(name); + this.subCommands = new HashMap<>(); + load(); + } + + private void load() { + COMMAND_MAP.register(getName(), this); + for (Method method : getClass().getMethods()) { + if (method.isAnnotationPresent(CommandInformation.class)) { + CommandInformation cmdInfo = method.getAnnotation(CommandInformation.class); + this.subCommands.put(cmdInfo, new CommandInvoker(this, method, cmdInfo.permission())); + } + } + } + + private Map loadArgs(CommandInformation subCommand, Iterable args) { + int size = Iterables.size(args); + int subCommandsSize = (subCommand.arguments()).length; + Map argsMap = new HashMap<>(); + if (size > 1) + if (subCommand.isMultiple()) { + argsMap.put(Iterables.get(args, 1), String.join(" ", Iterables.skip(args, 2))); + } else { + for (int i = 0; i < Math.min(subCommandsSize, size); i++) { + int fixedLength = i + 1; + if (size > fixedLength) { + String input = Iterables.get(args, fixedLength); + if (fixedLength == subCommandsSize) + input = String.join(" ", Iterables.skip(args, subCommandsSize)); + argsMap.put(subCommand.arguments()[i], input); + } + } + } + return argsMap; + } + + public Set getCommands() { + return this.subCommands.keySet(); + } + + public boolean execute(CommandSender sender, String commandLabel, String[] args) { + Optional> subCommandOptional = this.subCommands.entrySet().stream().filter(command -> command.getKey().name().contentEquals((args.length > 0) ? args[0] : "")).findFirst(); + if (!subCommandOptional.isPresent()) { + sender.sendMessage(ChatColor.RED + "can't find command: " + commandLabel + "."); + return false; + } + try { + Map.Entry subCommand = subCommandOptional.get(); + ((CommandInvoker) subCommand.getValue()).execute(new CommandSender(sender), + + loadArgs(subCommand.getKey(), Arrays.asList(args))); + } catch (CommandExecuteException e) { + sender.sendMessage(ChatColor.RED + "can't execute command."); + e.printStackTrace(); + } catch (CommandPermissionException e) { + sender.sendMessage(ChatColor.RED + "no permission for run this command."); + } + return true; + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/commands/CommandExecuteException.java b/src/main/java/io/github/znetworkw/znpcservers/commands/CommandExecuteException.java new file mode 100644 index 0000000..2603ddf --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/commands/CommandExecuteException.java @@ -0,0 +1,7 @@ +package io.github.znetworkw.znpcservers.commands; + +public class CommandExecuteException extends Exception { + public CommandExecuteException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/commands/CommandInformation.java b/src/main/java/io/github/znetworkw/znpcservers/commands/CommandInformation.java new file mode 100644 index 0000000..c185f09 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/commands/CommandInformation.java @@ -0,0 +1,18 @@ +package io.github.znetworkw.znpcservers.commands; + +import java.lang.annotation.*; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface CommandInformation { + String[] arguments(); + + String[] help() default {}; + + String name(); + + String permission(); + + boolean isMultiple() default false; +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/commands/CommandInvoker.java b/src/main/java/io/github/znetworkw/znpcservers/commands/CommandInvoker.java new file mode 100644 index 0000000..53cdab7 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/commands/CommandInvoker.java @@ -0,0 +1,27 @@ +package io.github.znetworkw.znpcservers.commands; + +import java.lang.reflect.Method; + +public class CommandInvoker { + private final Command command; + + private final Method commandMethod; + + private final String permission; + + public CommandInvoker(Command command, Method commandMethod, String permission) { + this.command = command; + this.commandMethod = commandMethod; + this.permission = permission; + } + + public void execute(CommandSender sender, Object command) throws CommandPermissionException, CommandExecuteException { + if (this.permission.length() > 0 && !sender.getCommandSender().hasPermission(this.permission)) + throw new CommandPermissionException("Insufficient permission."); + try { + this.commandMethod.invoke(this.command, sender, command); + } catch (IllegalAccessException | java.lang.reflect.InvocationTargetException e) { + throw new CommandExecuteException(e.getMessage(), e.getCause()); + } + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/commands/CommandNotFoundException.java b/src/main/java/io/github/znetworkw/znpcservers/commands/CommandNotFoundException.java new file mode 100644 index 0000000..7811c45 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/commands/CommandNotFoundException.java @@ -0,0 +1,7 @@ +package io.github.znetworkw.znpcservers.commands; + +public class CommandNotFoundException extends Exception { + public CommandNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/commands/CommandPermissionException.java b/src/main/java/io/github/znetworkw/znpcservers/commands/CommandPermissionException.java new file mode 100644 index 0000000..a1aaabe --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/commands/CommandPermissionException.java @@ -0,0 +1,7 @@ +package io.github.znetworkw.znpcservers.commands; + +public class CommandPermissionException extends Exception { + public CommandPermissionException(String message) { + super(message); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/commands/CommandSender.java b/src/main/java/io/github/znetworkw/znpcservers/commands/CommandSender.java new file mode 100644 index 0000000..5e0e023 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/commands/CommandSender.java @@ -0,0 +1,64 @@ +package io.github.znetworkw.znpcservers.commands; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import io.github.znetworkw.znpcservers.utility.Utils; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; +import org.bukkit.entity.Player; + +import java.util.Arrays; +import java.util.stream.Collectors; + +public class CommandSender { + static final Joiner LINE_SEPARATOR_JOINER = Joiner.on("\n"); + + private static final ImmutableList HELP_PREFIX = ImmutableList.of("&6&lEXAMPLES&r:"); + + private final org.bukkit.command.CommandSender commandSender; + + private final SenderType type; + + public CommandSender(org.bukkit.command.CommandSender commandSender) { + this.commandSender = commandSender; + this.type = (commandSender instanceof Player) ? SenderType.PLAYER : SenderType.CONSOLE; + } + + public void sendMessage(String message) { + sendMessage(message, null); + } + + public void sendMessage(CommandInformation subCommand) { + sendMessage(" &7» &6/&eznpcs " + subCommand.name() + " " + + Arrays.stream(subCommand.arguments()) + .map(s -> "<" + s + ">") + .collect(Collectors.joining(" ")), + Arrays.asList(subCommand.help())); + } + + public void sendMessage(String message, Iterable hover) { + TextComponent textComponent = new TextComponent(TextComponent.fromLegacyText(Utils.toColor(message))); + if (hover != null) + textComponent.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, (new ComponentBuilder( + Utils.toColor(LINE_SEPARATOR_JOINER + .join(Iterables.concat(HELP_PREFIX, hover))))) + .create())); + getPlayer().spigot().sendMessage(textComponent); + } + + public Player getPlayer() { + if (this.type != SenderType.PLAYER) + throw new IllegalStateException("sender is not a player."); + return (Player) getCommandSender(); + } + + public org.bukkit.command.CommandSender getCommandSender() { + return this.commandSender; + } + + enum SenderType { + PLAYER, CONSOLE + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/commands/list/DefaultCommand.java b/src/main/java/io/github/znetworkw/znpcservers/commands/list/DefaultCommand.java new file mode 100644 index 0000000..bd90d3d --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/commands/list/DefaultCommand.java @@ -0,0 +1,574 @@ +package io.github.znetworkw.znpcservers.commands.list; + +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.primitives.Doubles; +import com.google.common.primitives.Ints; +import lol.pyr.znpcsplus.ZNPCsPlus; +import io.github.znetworkw.znpcservers.commands.Command; +import io.github.znetworkw.znpcservers.commands.CommandInformation; +import io.github.znetworkw.znpcservers.commands.CommandSender; +import io.github.znetworkw.znpcservers.commands.list.inventory.ConversationGUI; +import io.github.znetworkw.znpcservers.configuration.Configuration; +import io.github.znetworkw.znpcservers.configuration.ConfigurationConstants; +import io.github.znetworkw.znpcservers.configuration.ConfigurationValue; +import io.github.znetworkw.znpcservers.npc.*; +import io.github.znetworkw.znpcservers.npc.conversation.Conversation; +import io.github.znetworkw.znpcservers.npc.conversation.ConversationModel; +import io.github.znetworkw.znpcservers.user.ZUser; +import io.github.znetworkw.znpcservers.utility.location.ZLocation; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.*; +import org.bukkit.entity.Player; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class DefaultCommand extends Command { + private static final String WHITESPACE = " "; + + private static final Splitter SPACE_SPLITTER = Splitter.on(" "); + + private static final Joiner SPACE_JOINER = Joiner.on(" "); + + private static final SkinFunction DO_APPLY_SKIN; + + static { + DO_APPLY_SKIN = ((sender, npc, skin) -> NPCSkin.forName(skin, ())); + } + + public DefaultCommand() { + super("znpcs"); + } + + @CommandInformation(arguments = {}, name = "", permission = "") + public void defaultCommand(CommandSender sender, Map args) { + sender.sendMessage("&6&m------------------------------------------"); + sender.sendMessage("&b&lZNPCS &8» &7ZNetwork"); + sender.sendMessage("&6https://www.spigotmc.org/resources/znpcs.80940"); + Objects.requireNonNull(sender); + Objects.requireNonNull(sender); + getCommands().forEach(sender::sendMessage); + sender.sendMessage(ChatColor.DARK_GRAY + "Hover over the commands to see help for the command."); + sender.sendMessage("&6&m------------------------------------------"); + } + + @CommandInformation(arguments = {"id", "type", "name"}, name = "create", permission = "znpcs.cmd.create", help = {" &f&l* &e/znpcs create PLAYER Qentin"}) + public void createNPC(CommandSender sender, Map args) { + if (args.size() < 3) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INCORRECT_USAGE); + return; + } + Integer id = Ints.tryParse(args.get("id")); + if (id == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NUMBER); + return; + } + boolean foundNPC = ConfigurationConstants.NPC_LIST.stream().anyMatch(npc -> (npc.getId() == id.intValue())); + if (foundNPC) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NPC_FOUND); + return; + } + String name = args.get("name").trim(); + if (name.length() < 3 || name.length() > 16) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NAME_LENGTH); + return; + } + NPCType npcType = NPCType.valueOf(args.get("type").toUpperCase()); + NPC npc = ZNPCsPlus.createNPC(id.intValue(), npcType, sender.getPlayer().getLocation(), name); + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.SUCCESS); + if (npcType == NPCType.PLAYER) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.FETCHING_SKIN, name); + DO_APPLY_SKIN.apply(sender.getPlayer(), npc, name); + } + } + + @CommandInformation(arguments = {"id"}, name = "delete", permission = "znpcs.cmd.delete", help = {" &f&l* &e/znpcs delete "}) + public void deleteNPC(CommandSender sender, Map args) { + if (args.size() < 1) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INCORRECT_USAGE); + return; + } + Integer id = Ints.tryParse(args.get("id")); + if (id == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NUMBER); + return; + } + NPC foundNPC = NPC.find(id.intValue()); + if (foundNPC == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NPC_NOT_FOUND); + return; + } + ZNPCsPlus.deleteNPC(id.intValue()); + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.SUCCESS); + } + + @CommandInformation(arguments = {}, name = "list", permission = "znpcs.cmd.list") + public void list(CommandSender sender, Map args) { + if (ConfigurationConstants.NPC_LIST.isEmpty()) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NO_NPC_FOUND); + } else { + sender.sendMessage(ChatColor.DARK_GREEN + "NPC list:"); + for (NPCModel npcModel : ConfigurationConstants.NPC_LIST) { + List parts = new ArrayList<>(); + String message = "- " + npcModel.getId() + " " + npcModel.getHologramLines().toString() + " (" + npcModel.getLocation().getWorldName() + " " + (int) npcModel.getLocation().getX() + " " + (int) npcModel.getLocation().getY() + " " + (int) npcModel.getLocation().getZ() + ") "; + TextComponent textComponent = new TextComponent(message); + textComponent.setColor(ChatColor.GREEN); + parts.add(textComponent); + TextComponent textComponent2 = new TextComponent("[TELEPORT]"); + textComponent2.setBold(Boolean.valueOf(true)); + textComponent2.setColor(ChatColor.DARK_GREEN); + textComponent2.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, (new ComponentBuilder("Click to teleport this npc!")) + + .color(ChatColor.GREEN).create())); + textComponent2.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/znpcs teleport " + npcModel + .getId())); + parts.add(textComponent2); + parts.add(new TextComponent(" ")); + TextComponent textComponent3 = new TextComponent("[DELETE]"); + textComponent3.setBold(Boolean.valueOf(true)); + textComponent3.setColor(ChatColor.DARK_RED); + textComponent3.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, (new ComponentBuilder("Click to delete this npc!")) + + .color(ChatColor.RED).create())); + textComponent3.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/znpcs delete " + npcModel + .getId())); + parts.add(textComponent3); + sender.getPlayer().spigot().sendMessage(parts.toArray(new BaseComponent[0])); + } + } + } + + @CommandInformation(arguments = {"id", "skin"}, name = "skin", permission = "znpcs.cmd.skin", help = {" &f&l* &e/znpcs skin Notch"}) + public void setSkin(CommandSender sender, Map args) { + if (args.size() < 1) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INCORRECT_USAGE); + return; + } + Integer id = Ints.tryParse(args.get("id")); + if (id == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NUMBER); + return; + } + NPC foundNPC = NPC.find(id.intValue()); + if (foundNPC == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NPC_NOT_FOUND); + return; + } + String skin = args.get("skin"); + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.FETCHING_SKIN, skin); + DO_APPLY_SKIN.apply(sender.getPlayer(), foundNPC, args.get("skin")); + } + + @CommandInformation(arguments = {"id", "slot"}, name = "equip", permission = "znpcs.cmd.equip", help = {" &f&l* &e/znpcs equip [HAND,OFFHAND,HELMET,CHESTPLATE,LEGGINGS,BOOTS]", "&8(You need to have the item in your hand.)"}) + public void equip(CommandSender sender, Map args) { + if (args.size() < 2) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INCORRECT_USAGE); + return; + } + Integer id = Ints.tryParse(args.get("id")); + if (id == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NUMBER); + return; + } + NPC foundNPC = NPC.find(id.intValue()); + if (foundNPC == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NPC_NOT_FOUND); + return; + } + foundNPC.getNpcPojo().getNpcEquip().put( + ItemSlot.valueOf(args.get("slot").toUpperCase()), sender + .getPlayer().getInventory().getItemInHand()); + foundNPC.getPackets().flushCache("equipPackets"); + Objects.requireNonNull(foundNPC); + Objects.requireNonNull(foundNPC); + foundNPC.getViewers().forEach(foundNPC::sendEquipPackets); + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.SUCCESS); + } + + @CommandInformation(arguments = {"id", "lines"}, name = "lines", permission = "znpcs.cmd.lines", help = {" &f&l* &e/znpcs lines First Second Third-Space"}) + public void changeLines(CommandSender sender, Map args) { + if (args.size() < 2) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INCORRECT_USAGE); + return; + } + Integer id = Ints.tryParse(args.get("id")); + if (id == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NUMBER); + return; + } + NPC foundNPC = NPC.find(id.intValue()); + if (foundNPC == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NPC_NOT_FOUND); + return; + } + foundNPC.getNpcPojo().setHologramLines(Lists.reverse(SPACE_SPLITTER.splitToList(args.get("lines")))); + foundNPC.getHologram().createHologram(); + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.SUCCESS); + } + + @CommandInformation(arguments = {"id"}, name = "move", permission = "znpcs.cmd.move", help = {" &f&l* &e/znpcs move "}) + public void move(CommandSender sender, Map args) { + if (args.size() < 1) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INCORRECT_USAGE); + return; + } + Integer id = Ints.tryParse(args.get("id")); + if (id == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NUMBER); + return; + } + NPC foundNPC = NPC.find(id.intValue()); + if (foundNPC == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NPC_NOT_FOUND); + return; + } + foundNPC.getNpcPojo().setLocation(new ZLocation(sender.getPlayer().getLocation())); + foundNPC.changeType(foundNPC.getNpcPojo().getNpcType()); + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.SUCCESS); + } + + @CommandInformation(arguments = {"id", "type"}, name = "type", permission = "znpcs.cmd.type", help = {" &f&l* &e/znpcs type ZOMBIE"}) + public void changeType(CommandSender sender, Map args) { + if (args.size() < 2) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INCORRECT_USAGE); + return; + } + Integer id = Ints.tryParse(args.get("id")); + if (id == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NUMBER); + return; + } + NPC foundNPC = NPC.find(id.intValue()); + if (foundNPC == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NPC_NOT_FOUND); + return; + } + NPCType npcType = NPCType.valueOf(args.get("type").toUpperCase()); + if (npcType != NPCType.PLAYER && npcType.getConstructor() == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.UNSUPPORTED_ENTITY); + return; + } + foundNPC.changeType(npcType); + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.SUCCESS); + } + + @CommandInformation(arguments = {"add", "remove", "cooldown", "list"}, name = "action", isMultiple = true, permission = "znpcs.cmd.action", help = {" &f&l* &e/znpcs action add SERVER skywars", " &f&l* &e/znpcs action add CMD spawn", " &f&l* &e/znpcs action remove ", " &f&l* &e/znpcs action cooldown ", " &f&l* &e/znpcs action list "}) + public void action(CommandSender sender, Map args) { + if (args.size() < 1) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INCORRECT_USAGE); + return; + } + if (args.containsKey("add")) { + List split = SPACE_SPLITTER.splitToList(args.get("add")); + if (split.size() < 3) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.ACTION_ADD_INCORRECT_USAGE); + return; + } + Integer id = Ints.tryParse(split.get(0)); + if (id == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NUMBER); + return; + } + NPC foundNPC = NPC.find(id.intValue()); + if (foundNPC == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NPC_NOT_FOUND); + return; + } + foundNPC.getNpcPojo().getClickActions().add(new NPCAction(split.get(1).toUpperCase(), SPACE_JOINER.join(Iterables.skip(split, 2)))); + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.SUCCESS); + } else if (args.containsKey("remove")) { + List split = SPACE_SPLITTER.splitToList(args.get("remove")); + Integer id = Ints.tryParse(split.get(0)); + if (id == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NUMBER); + return; + } + NPC foundNPC = NPC.find(id.intValue()); + if (foundNPC == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NPC_NOT_FOUND); + return; + } + Integer actionId = Ints.tryParse(split.get(1)); + if (actionId == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NUMBER); + } else { + if (actionId.intValue() >= foundNPC.getNpcPojo().getClickActions().size()) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NO_ACTION_FOUND); + return; + } + foundNPC.getNpcPojo().getClickActions().remove(actionId.intValue()); + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.SUCCESS); + } + } else if (args.containsKey("cooldown")) { + List split = SPACE_SPLITTER.splitToList(args.get("cooldown")); + if (split.size() < 2) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.ACTION_DELAY_INCORRECT_USAGE); + return; + } + Integer id = Ints.tryParse(split.get(0)); + if (id == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NUMBER); + return; + } + NPC foundNPC = NPC.find(id.intValue()); + if (foundNPC == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NPC_NOT_FOUND); + return; + } + Integer actionId = Ints.tryParse(split.get(1)); + Integer actionDelay = Ints.tryParse(split.get(2)); + if (actionId == null || id == null || actionDelay == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NUMBER); + } else { + if (actionId.intValue() >= foundNPC.getNpcPojo().getClickActions().size()) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NO_ACTION_FOUND); + return; + } + foundNPC.getNpcPojo().getClickActions().get(actionId.intValue()).setDelay(actionDelay.intValue()); + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.SUCCESS); + } + } else if (args.containsKey("list")) { + Integer id = Ints.tryParse(args.get("list")); + if (id == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NUMBER); + return; + } + NPC foundNPC = NPC.find(id.intValue()); + if (foundNPC == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NPC_NOT_FOUND); + return; + } + if (foundNPC.getNpcPojo().getClickActions().isEmpty()) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NO_ACTION_FOUND); + } else { + foundNPC.getNpcPojo().getClickActions().forEach(s -> sender.sendMessage("&8(&a" + foundNPC.getNpcPojo().getClickActions().indexOf(s) + "&8) &6" + s.toString())); + } + } + } + + @CommandInformation(arguments = {"id", "type", "value"}, name = "toggle", permission = "znpcs.cmd.toggle", help = {" &f&l* &e/znpcs toggle look"}) + public void toggle(CommandSender sender, Map args) { + if (args.size() < 2) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INCORRECT_USAGE); + return; + } + Integer id = Ints.tryParse(args.get("id")); + if (id == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NUMBER); + return; + } + NPC foundNPC = NPC.find(id.intValue()); + if (foundNPC == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NPC_NOT_FOUND); + return; + } + NPCFunction npcFunction = FunctionFactory.findFunctionForName(args.get("type")); + if (npcFunction.getName().equalsIgnoreCase("glow")) { + npcFunction.doRunFunction(foundNPC, new FunctionContext.ContextWithValue(foundNPC, args.get("value"))); + } else { + npcFunction.doRunFunction(foundNPC, new FunctionContext.DefaultContext(foundNPC)); + } + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.SUCCESS); + } + + @CommandInformation(arguments = {"id", "customizeValues"}, name = "customize", permission = "znpcs.cmd.customize", help = {" &f&l* &e/znpcs customize "}) + public void customize(CommandSender sender, Map args) { + if (args.size() < 2) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INCORRECT_USAGE); + return; + } + Integer id = Ints.tryParse(args.get("id")); + if (id == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NUMBER); + return; + } + NPC foundNPC = NPC.find(id.intValue()); + if (foundNPC == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NPC_NOT_FOUND); + return; + } + NPCType npcType = foundNPC.getNpcPojo().getNpcType(); + List customizeOptions = SPACE_SPLITTER.splitToList(args.get("customizeValues")); + String methodName = customizeOptions.get(0); + if (npcType.getCustomizationLoader().contains(methodName)) { + Method method = npcType.getCustomizationLoader().getMethods().get(methodName); + Iterable split = Iterables.skip(customizeOptions, 1); + if (Iterables.size(split) < (method.getParameterTypes()).length) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.TOO_FEW_ARGUMENTS); + return; + } + String[] values = Iterables.toArray(split, String.class); + npcType.updateCustomization(foundNPC, methodName, values); + foundNPC.getNpcPojo().getCustomizationMap().put(methodName, values); + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.SUCCESS); + } else { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.METHOD_NOT_FOUND); + for (Map.Entry method : npcType.getCustomizationLoader().getMethods().entrySet()) + sender.sendMessage(ChatColor.YELLOW + method.getKey() + " " + SPACE_JOINER.join(method.getValue().getParameterTypes())); + } + } + + @CommandInformation(arguments = {"set", "create", "exit", "path", "list"}, name = "path", isMultiple = true, permission = "znpcs.cmd.path", help = {" &f&l* &e/znpcs path create name", " &f&l* &e/znpcs path set name"}) + public void path(CommandSender sender, Map args) { + if (args.size() < 1) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INCORRECT_USAGE); + return; + } + ZUser znpcUser = ZUser.find(sender.getPlayer()); + if (znpcUser == null) + return; + if (args.containsKey("set")) { + List split = SPACE_SPLITTER.splitToList(args.get("set")); + if (split.size() < 2) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.PATH_SET_INCORRECT_USAGE); + return; + } + Integer id = Ints.tryParse(split.get(0)); + if (id == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NUMBER); + return; + } + NPC foundNPC = NPC.find(id.intValue()); + if (foundNPC == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NPC_NOT_FOUND); + return; + } + foundNPC.setPath(NPCPath.AbstractTypeWriter.find(split.get(1))); + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.SUCCESS); + } else if (args.containsKey("create")) { + String pathName = args.get("create"); + if (pathName.length() < 3 || pathName.length() > 16) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NAME_LENGTH); + return; + } + if (NPCPath.AbstractTypeWriter.find(pathName) != null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.PATH_FOUND); + return; + } + if (znpcUser.isHasPath()) { + sender.getPlayer().sendMessage(ChatColor.RED + "You already have a path creator active, to remove it use /znpcs path exit."); + return; + } + NPCPath.AbstractTypeWriter.forCreation(pathName, znpcUser, NPCPath.AbstractTypeWriter.TypeWriter.MOVEMENT); + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.PATH_START); + } else if (args.containsKey("exit")) { + znpcUser.setHasPath(false); + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.EXIT_PATH); + } else if (args.containsKey("list")) { + if (NPCPath.AbstractTypeWriter.getPaths().isEmpty()) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NO_PATH_FOUND); + } else { + NPCPath.AbstractTypeWriter.getPaths().forEach(path -> sender.getPlayer().sendMessage(ChatColor.GREEN + path.getName())); + } + } + } + + @CommandInformation(arguments = {"id"}, name = "teleport", permission = "znpcs.cmd.teleport", help = {" &f&l* &e/znpcs teleport "}) + public void teleport(CommandSender sender, Map args) { + if (args.size() < 1) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INCORRECT_USAGE); + return; + } + Integer id = Ints.tryParse(args.get("id")); + if (id == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NUMBER); + return; + } + NPC foundNPC = NPC.find(id.intValue()); + if (foundNPC == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NPC_NOT_FOUND); + return; + } + sender.getPlayer().teleport(foundNPC.getLocation()); + } + + @CommandInformation(arguments = {"id", "height"}, name = "height", permission = "znpcs.cmd.height", help = {" &f&l* &e/znpcs height 2", "&8Add more height to the hologram of the npc"}) + public void changeHologramHeight(CommandSender sender, Map args) { + if (args.size() < 2) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INCORRECT_USAGE); + return; + } + Integer id = Ints.tryParse(args.get("id")); + if (id == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NUMBER); + return; + } + NPC foundNPC = NPC.find(id.intValue()); + if (foundNPC == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NPC_NOT_FOUND); + return; + } + Double givenHeight = Doubles.tryParse(args.get("height")); + if (givenHeight == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NUMBER); + return; + } + foundNPC.getNpcPojo().setHologramHeight(givenHeight.doubleValue()); + foundNPC.getHologram().createHologram(); + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.SUCCESS); + } + + @CommandInformation(arguments = {"create", "remove", "gui", "set"}, name = "conversation", isMultiple = true, permission = "znpcs.cmd.conversation", help = {" &f&l* &e/znpcs conversation create first", " &f&l* &e/znpcs conversation remove first", " &f&l* &e/znpcs conversation set first [CLICK:RADIUS]", " &f&l* &e/znpcs conversation gui &8(&7Open a gui to manage the conversations&8)", "&8RADIUS: &7it is activated when the player is near the npc", "&8CLICK: &7it is activated when the player interacts with the npc"}) + public void conversations(CommandSender sender, Map args) { + if (args.size() < 1) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INCORRECT_USAGE); + return; + } + if (args.containsKey("create")) { + String conversationName = args.get("create"); + if (Conversation.exists(conversationName)) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.CONVERSATION_FOUND); + return; + } + if (conversationName.length() < 3 || conversationName.length() > 16) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NAME_LENGTH); + return; + } + ConfigurationConstants.NPC_CONVERSATIONS.add(new Conversation(conversationName)); + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.SUCCESS); + } else if (args.containsKey("remove")) { + String conversationName = args.get("remove"); + if (!Conversation.exists(conversationName)) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NO_CONVERSATION_FOUND); + return; + } + ConfigurationConstants.NPC_CONVERSATIONS.remove(Conversation.forName(conversationName)); + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.SUCCESS); + } else if (args.containsKey("gui")) { + sender.getPlayer().openInventory((new ConversationGUI(sender.getPlayer())).build()); + } else if (args.containsKey("set")) { + List split = SPACE_SPLITTER.splitToList(args.get("set")); + if (split.size() < 2) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.CONVERSATION_SET_INCORRECT_USAGE); + return; + } + Integer id = Ints.tryParse(split.get(0)); + if (id == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.INVALID_NUMBER); + return; + } + NPC foundNPC = NPC.find(id.intValue()); + if (foundNPC == null) { + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.NPC_NOT_FOUND); + return; + } + String conversationName = split.get(1); + if (Conversation.exists(conversationName)) { + foundNPC.getNpcPojo().setConversation(new ConversationModel(conversationName, (split.size() > 1) ? split.get(2) : "CLICK")); + } else { + foundNPC.getNpcPojo().setConversation(null); + } + Configuration.MESSAGES.sendMessage(sender.getCommandSender(), ConfigurationValue.SUCCESS); + } + } + + interface SkinFunction { + void apply(Player param1Player, NPC param1NPC, String param1String); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/commands/list/inventory/ConversationGUI.java b/src/main/java/io/github/znetworkw/znpcservers/commands/list/inventory/ConversationGUI.java new file mode 100644 index 0000000..9461148 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/commands/list/inventory/ConversationGUI.java @@ -0,0 +1,127 @@ +package io.github.znetworkw.znpcservers.commands.list.inventory; + +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import io.github.znetworkw.znpcservers.configuration.Configuration; +import io.github.znetworkw.znpcservers.configuration.ConfigurationConstants; +import io.github.znetworkw.znpcservers.configuration.ConfigurationValue; +import io.github.znetworkw.znpcservers.npc.NPCAction; +import io.github.znetworkw.znpcservers.npc.conversation.Conversation; +import io.github.znetworkw.znpcservers.npc.conversation.ConversationKey; +import io.github.znetworkw.znpcservers.user.EventService; +import io.github.znetworkw.znpcservers.user.ZUser; +import io.github.znetworkw.znpcservers.utility.Utils; +import io.github.znetworkw.znpcservers.utility.inventory.ZInventory; +import io.github.znetworkw.znpcservers.utility.inventory.ZInventoryPage; +import io.github.znetworkw.znpcservers.utility.itemstack.ItemStackBuilder; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.player.AsyncPlayerChatEvent; + +public class ConversationGUI extends ZInventory { + private static final String WHITESPACE = " "; + + private static final Splitter SPACE_SPLITTER = Splitter.on(" "); + + private static final Joiner SPACE_JOINER = Joiner.on(" "); + + public ConversationGUI(Player player) { + super(player); + setCurrentPage(new MainPage(this)); + } + + static class MainPage extends ZInventoryPage { + public MainPage(ZInventory inventory) { + super(inventory, "Conversations", 5); + } + + public void update() { + for (int i = 0; i < ConfigurationConstants.NPC_CONVERSATIONS.size(); i++) { + Conversation conversation = ConfigurationConstants.NPC_CONVERSATIONS.get(i); + addItem(ItemStackBuilder.forMaterial(Material.PAPER).setName(ChatColor.GREEN + conversation.getName()).setLore(new String[]{"&7this conversation has &b" + conversation.getTexts().size() + " &7texts,", "&7it will activate when a player is on a &b" + conversation.getRadius() + "x" + conversation.getRadius() + " &7radius,", "&7or when a player interacts with an npc.", "&7when the conversation is finish, there is a &b" + conversation.getDelay() + "s &7delay to start again.", "&f&lUSES", " &bLeft-click &7to manage texts.", " &bRight-click &7to add a new text.", " &bQ &7to change the radius.", " &bMiddle-click &7to change the cooldown."}, ).build(), i, clickEvent -> { + if (clickEvent.getClick() == ClickType.DROP) { + Utils.sendTitle(getPlayer(), "&b&lCHANGE RADIUS", "&7Type the new radius..."); + EventService.addService(ZUser.find(getPlayer()), AsyncPlayerChatEvent.class).addConsumer(()).addConsumer(()); + } else if (clickEvent.isRightClick()) { + Utils.sendTitle(getPlayer(), "&e&lADD LINE", "&7Type the new line..."); + EventService.addService(ZUser.find(getPlayer()), AsyncPlayerChatEvent.class).addConsumer(()).addConsumer(()); + } else if (clickEvent.isLeftClick()) { + (new EditConversationPage(getInventory(), conversation)).openInventory(); + } else if (clickEvent.getClick() == ClickType.MIDDLE) { + Utils.sendTitle(getPlayer(), "&6&lCHANGE COOLDOWN", "&7Type the new cooldown..."); + EventService.addService(ZUser.find(getPlayer()), AsyncPlayerChatEvent.class).addConsumer(()).addConsumer(()); + } + }); + } + } + + static class EditConversationPage extends ZInventoryPage { + private final Conversation conversation; + + public EditConversationPage(ZInventory inventory, Conversation conversation) { + super(inventory, "Editing conversation " + conversation.getName(), 5); + this.conversation = conversation; + } + + public void update() { + for (int i = 0; i < this.conversation.getTexts().size(); i++) { + ConversationKey conversationKey = this.conversation.getTexts().get(i); + addItem(ItemStackBuilder.forMaterial(Material.NAME_TAG).setName(ChatColor.AQUA + conversationKey.getTextFormatted() + "....").setLore(new String[]{ + "&7this conversation text has a delay of &b" + conversationKey.getDelay() + "s &7to be executed,", "&7the sound for the text is &b" + ((conversationKey.getSoundName() == null) ? "NONE" : conversationKey.getSoundName()) + "&7,", "&7before sending the text there is a delay of &b" + conversationKey.getDelay() + "s", "&7the index for the text is &b" + i + "&7,", "&7and the conversation has currently &b" + conversationKey.getActions().size() + " actions&7.", "&f&lUSES", " &bLeft-click &7to change the position.", " &bRight-click &7to remove text.", " &bLeft-Shift-click &7to change the sound.", " &bMiddle-click &7to change the delay.", + " &bRight-Shift-click &7to edit the text.", " &bQ &7to manage actions."}, ).build(), i, clickEvent -> { + if (clickEvent.getClick() == ClickType.SHIFT_LEFT) { + Utils.sendTitle(getPlayer(), "&c&lCHANGE SOUND", "&7Type the new sound..."); + EventService.addService(ZUser.find(getPlayer()), AsyncPlayerChatEvent.class).addConsumer(()).addConsumer(()); + } else if (clickEvent.getClick() == ClickType.SHIFT_RIGHT) { + Utils.sendTitle(getPlayer(), "&a&lEDIT TEXT", "&7Type the new text..."); + EventService.addService(ZUser.find(getPlayer()), AsyncPlayerChatEvent.class).addConsumer(()).addConsumer(()); + } else if (clickEvent.isLeftClick()) { + Utils.sendTitle(getPlayer(), "&e&lCHANGE POSITION &a>=0&c<=" + this.conversation.getTexts().size(), "&7Type the new position..."); + EventService.addService(ZUser.find(getPlayer()), AsyncPlayerChatEvent.class).addConsumer(()).addConsumer(()); + } else if (clickEvent.isRightClick()) { + this.conversation.getTexts().remove(conversationKey); + Configuration.MESSAGES.sendMessage(getPlayer(), ConfigurationValue.SUCCESS); + openInventory(); + } else if (clickEvent.getClick() == ClickType.MIDDLE) { + Utils.sendTitle(getPlayer(), "&d&lCHANGE DELAY", "&7Type the new delay..."); + EventService.addService(ZUser.find(getPlayer()), AsyncPlayerChatEvent.class).addConsumer(()).addConsumer(()); + } else if (clickEvent.getClick() == ClickType.DROP) { + (new ConversationGUI.MainPage.ActionManagementPage(getInventory(), this.conversation, conversationKey)).openInventory(); + } + }); + } + } + } + + static class ActionManagementPage extends ZInventoryPage { + private final Conversation conversation; + + private final ConversationKey conversationKey; + + public ActionManagementPage(ZInventory inventory, Conversation conversation, ConversationKey conversationKey) { + super(inventory, "Editing " + conversationKey.getTextFormatted(), 5); + this.conversation = conversation; + this.conversationKey = conversationKey; + } + + public void update() { + for (int i = 0; i < this.conversationKey.getActions().size(); i++) { + NPCAction znpcAction = this.conversationKey.getActions().get(i); + addItem(ItemStackBuilder.forMaterial(Material.ANVIL).setName(ChatColor.AQUA + znpcAction.getAction().substring(0, Math.min(znpcAction.getAction().length(), 24)) + "....").setLore("&7this action type is &b" + znpcAction.getActionType(), "&f&lUSES", " &bRight-click &7to remove text.").build(), i, clickEvent -> { + if (clickEvent.isRightClick()) { + this.conversationKey.getActions().remove(znpcAction); + Configuration.MESSAGES.sendMessage(getPlayer(), ConfigurationValue.SUCCESS); + openInventory(); + } + }); + } + addItem(ItemStackBuilder.forMaterial(Material.EMERALD).setName(ChatColor.AQUA + "ADD A NEW ACTION").setLore(new String[]{"&7click here..."}, ).build(), getRows() - 5, clickEvent -> { + Utils.sendTitle(getPlayer(), "&d&lADD ACTION", "&7Type the action..."); + EventService.addService(ZUser.find(getPlayer()), AsyncPlayerChatEvent.class).addConsumer(()).addConsumer(()); + }); + } + } + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/configuration/Configuration.java b/src/main/java/io/github/znetworkw/znpcservers/configuration/Configuration.java new file mode 100644 index 0000000..88d0b72 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/configuration/Configuration.java @@ -0,0 +1,127 @@ +package io.github.znetworkw.znpcservers.configuration; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import lol.pyr.znpcsplus.ZNPCsPlus; +import io.github.znetworkw.znpcservers.utility.Utils; +import org.bukkit.command.CommandSender; + +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.lang.reflect.Type; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.util.Map; +import java.util.stream.Collectors; + +public class Configuration { + public static final Configuration CONFIGURATION = new Configuration("config"); + public static final Configuration MESSAGES = new Configuration("messages"); + public static final Configuration CONVERSATIONS = new Configuration("conversations"); + public static final Configuration DATA = new Configuration("data"); + public static final ImmutableList SAVE_CONFIGURATIONS = ImmutableList.of(CONVERSATIONS, DATA); + static final String CONFIG_FORMAT = ".json"; + private static final JsonParser JSON_PARSER = new JsonParser(); + private static final Charset CHARSET = StandardCharsets.UTF_8; + private final String name; + private final Path path; + private final Map configurationValues; + + protected Configuration(String name) { + this(name, ZNPCsPlus.PLUGIN_FOLDER.toPath().resolve(name + ".json")); + } + + private Configuration(String name, Path path) { + if (!path.getFileName().toString().endsWith(".json")) + throw new IllegalStateException("invalid configuration format for: " + path.getFileName()); + this.name = name; + this.path = path; + this + + .configurationValues = (Map) ((ImmutableSet) ConfigurationValue.VALUES_BY_NAME.get(name)).stream().collect(Collectors.toMap(c -> c, ConfigurationValue::getValue)); + onLoad(); + } + + protected void onLoad() { + synchronized (this.path) { + try { + Reader reader = Files.newBufferedReader(this.path, CHARSET); + try { + JsonElement data = JSON_PARSER.parse(reader); + if (data == null) { + if (reader != null) + reader.close(); + return; + } + for (ConfigurationValue configValue : this.configurationValues.keySet()) { + boolean single = (this.configurationValues.size() == 1); + JsonElement jsonElement = single ? data : (data.isJsonObject() ? data.getAsJsonObject().get(configValue.name()) : null); + if (jsonElement != null && !jsonElement.isJsonNull()) { + if (!single && configValue.getPrimitiveType().isEnum()) { + this.configurationValues.put(configValue, ZNPCsPlus.GSON.fromJson(jsonElement, configValue.getPrimitiveType())); + continue; + } + this.configurationValues.put(configValue, ZNPCsPlus.GSON.fromJson(jsonElement,.Gson.Types.newParameterizedTypeWithOwner(null, configValue.getValue().getClass(), new Type[]{configValue.getPrimitiveType()}))) + } + } + if (reader != null) + reader.close(); + } catch (Throwable throwable) { + if (reader != null) + try { + reader.close(); + } catch (Throwable throwable1) { + throwable.addSuppressed(throwable1); + } + throw throwable; + } + } catch (NoSuchFileException noSuchFileException) { + + } catch (IOException e) { + throw new IllegalStateException("Failed to read config: " + this.name); + } finally { + save(); + } + } + } + + public void save() { + synchronized (this.path) { + try { + Writer writer = Files.newBufferedWriter(this.path, CHARSET); + try { + ZNPCsPlus.GSON.toJson((this.configurationValues.size() == 1) ? + this.configurationValues.values().iterator().next() : this.configurationValues, writer); + if (writer != null) + writer.close(); + } catch (Throwable throwable) { + if (writer != null) + try { + writer.close(); + } catch (Throwable throwable1) { + throwable.addSuppressed(throwable1); + } + throw throwable; + } + } catch (IOException e) { + throw new IllegalStateException("Failed to save config: " + this.name); + } + } + } + + public T getValue(ConfigurationValue configValue) { + synchronized (this.path) { + return (T) this.configurationValues.get(configValue); + } + } + + public void sendMessage(CommandSender sender, ConfigurationValue configValue, Object... replaces) { + sender.sendMessage(Utils.toColor(String.format(getValue(configValue), replaces))); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/configuration/ConfigurationConstants.java b/src/main/java/io/github/znetworkw/znpcservers/configuration/ConfigurationConstants.java new file mode 100644 index 0000000..3424654 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/configuration/ConfigurationConstants.java @@ -0,0 +1,26 @@ +package io.github.znetworkw.znpcservers.configuration; + +import io.github.znetworkw.znpcservers.npc.NPCModel; +import io.github.znetworkw.znpcservers.npc.conversation.Conversation; + +import java.util.List; + +public final class ConfigurationConstants { + public static final String SPACE_SYMBOL = Configuration.CONFIGURATION.getValue(ConfigurationValue.REPLACE_SYMBOL); + + public static final int VIEW_DISTANCE = Configuration.CONFIGURATION.getValue(ConfigurationValue.VIEW_DISTANCE).intValue(); + + public static final int SAVE_DELAY = Configuration.CONFIGURATION.getValue(ConfigurationValue.SAVE_NPCS_DELAY_SECONDS).intValue(); + + public static final boolean RGB_ANIMATION = Configuration.CONFIGURATION.getValue(ConfigurationValue.ANIMATION_RGB).booleanValue(); + + public static final List NPC_LIST = Configuration.DATA.getValue(ConfigurationValue.NPC_LIST); + + public static final List NPC_CONVERSATIONS = Configuration.CONVERSATIONS.getValue(ConfigurationValue.CONVERSATION_LIST); + + static { + NPC_LIST.stream() + .map(io.github.znetworkw.znpcservers.npc.NPC::new) + .forEach(io.github.znetworkw.znpcservers.npc.task.NPCLoadTask::new); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/configuration/ConfigurationValue.java b/src/main/java/io/github/znetworkw/znpcservers/configuration/ConfigurationValue.java new file mode 100644 index 0000000..a4c3674 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/configuration/ConfigurationValue.java @@ -0,0 +1,82 @@ +package io.github.znetworkw.znpcservers.configuration; + +import com.google.common.collect.ImmutableSet; +import io.github.znetworkw.znpcservers.npc.NPCModel; +import io.github.znetworkw.znpcservers.npc.NamingType; +import io.github.znetworkw.znpcservers.npc.conversation.Conversation; +import io.github.znetworkw.znpcservers.utility.GuavaCollectors; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +public enum ConfigurationValue { + NPC_LIST("data", new ArrayList(), NPCModel.class), + VIEW_DISTANCE("config", Integer.valueOf(32), Integer.class), + REPLACE_SYMBOL("config", "-", String.class), + SAVE_NPCS_DELAY_SECONDS("config", Integer.valueOf(600), Integer.class), + MAX_PATH_LOCATIONS("config", Integer.valueOf(500), Integer.class), + NAMING_METHOD("config", NamingType.DEFAULT, NamingType.class), + DEBUG_ENABLED("config", Boolean.TRUE, Boolean.class), + LINE_SPACING("config", Double.valueOf(0.3D), Double.class), + ANIMATION_RGB("config", Boolean.FALSE, Boolean.class), + NO_PERMISSION("messages", "&cYou do not have permission to execute this command.", String.class), + SUCCESS("messages", "&aDone...", String.class), + INCORRECT_USAGE("messages", "&cIncorrect use of command.", String.class), + COMMAND_NOT_FOUND("messages", "&cThis command was not found.", String.class), + COMMAND_ERROR("messages", "&cThere was an error executing the command, see the console for more information.", String.class), + INVALID_NUMBER("messages", "&cHey!, The inserted number/id does not look like a number..", String.class), + NPC_NOT_FOUND("messages", "&cHey!, I couldnt find a npc with this id.", String.class), + TOO_FEW_ARGUMENTS("messages", "&cToo few arguments.", String.class), + PATH_START("messages", "&aDone, now walk where you want the npc to, when u finish type /znpcs path exit.", String.class), + EXIT_PATH("messages", "&cYou have exited the waypoint creation.", String.class), + PATH_FOUND("messages", "&cThere is already a path with this name.", String.class), + NPC_FOUND("messages", "&cThere is already a npc with this id.", String.class), + NO_PATH_FOUND("messages", "&cNo path found.", String.class), + NO_SKIN_FOUND("messages", "&cSkin not found.", String.class), + NO_NPC_FOUND("messages", "&cNo npc found.", String.class), + NO_ACTION_FOUND("messages", "&cNo action found.", String.class), + METHOD_NOT_FOUND("messages", "&cNo method found.", String.class), + INVALID_NAME_LENGTH("messages", "&cThe name is too short or long, it must be in the range of (3 to 16) characters.", String.class), + UNSUPPORTED_ENTITY("messages", "&cEntity type not available for your current version.", String.class), + PATH_SET_INCORRECT_USAGE("messages", "&eUsage: &aset ", String.class), + ACTION_ADD_INCORRECT_USAGE("messages", "&eUsage: &a ", String.class), + ACTION_DELAY_INCORRECT_USAGE("messages", "&eUsage: &a ", String.class), + CONVERSATION_SET_INCORRECT_USAGE("messages", "&cUsage: ", String.class), + NO_CONVERSATION_FOUND("messages", "&cNo conversation found.", String.class), + CONVERSATION_FOUND("messages", "&cThere is already a conversation with this name.", String.class), + INVALID_SIZE("messages", "&cThe position cannot exceed the limit.", String.class), + FETCHING_SKIN("messages", "&aFetching skin for name: &f%s&a, wait...", String.class), + CANT_GET_SKIN("messages", "&ccan't fetch skin with name: %s.", String.class), + GET_SKIN("messages", "&aSkin fetched.", String.class), + CONVERSATION_LIST("conversations", new ArrayList(), Conversation.class); + + public static final Map> VALUES_BY_NAME; + + static { + VALUES_BY_NAME = Arrays.stream(values()).collect(Collectors.groupingBy(ConfigurationValue::getConfigName, GuavaCollectors.toImmutableSet())); + } + + private final String configName; + private final Object value; + private final Class primitiveType; + + ConfigurationValue(String configName, Object value, Class primitiveType) { + this.configName = configName; + this.value = value; + this.primitiveType = primitiveType; + } + + public String getConfigName() { + return this.configName; + } + + public Object getValue() { + return this.value; + } + + public Class getPrimitiveType() { + return this.primitiveType; + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/listeners/InventoryListener.java b/src/main/java/io/github/znetworkw/znpcservers/listeners/InventoryListener.java new file mode 100644 index 0000000..df573f8 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/listeners/InventoryListener.java @@ -0,0 +1,31 @@ +package io.github.znetworkw.znpcservers.listeners; + +import io.github.znetworkw.znpcservers.utility.inventory.ZInventory; +import io.github.znetworkw.znpcservers.utility.inventory.ZInventoryHolder; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.plugin.Plugin; + +public class InventoryListener implements Listener { + public InventoryListener(Plugin serversNPC) { + serversNPC.getServer().getPluginManager().registerEvents(this, serversNPC); + } + + @EventHandler + public void onClick(InventoryClickEvent event) { + if (!(event.getWhoClicked() instanceof Player)) + return; + if (event.getCurrentItem() == null) + return; + if (!(event.getInventory().getHolder() instanceof ZInventoryHolder)) + return; + event.setCancelled(true); + ZInventory zInventory = ((ZInventoryHolder) event.getInventory().getHolder()).getzInventory(); + if (!zInventory.getPage().containsItem(event.getRawSlot())) + return; + zInventory.getPage().findItem(event.getRawSlot()).getInventoryCallback().onClick(event); + ((Player) event.getWhoClicked()).updateInventory(); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/listeners/PlayerListener.java b/src/main/java/io/github/znetworkw/znpcservers/listeners/PlayerListener.java new file mode 100644 index 0000000..2d154c8 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/listeners/PlayerListener.java @@ -0,0 +1,48 @@ +package io.github.znetworkw.znpcservers.listeners; + +import io.github.znetworkw.znpcservers.npc.conversation.ConversationModel; +import io.github.znetworkw.znpcservers.npc.event.NPCInteractEvent; +import io.github.znetworkw.znpcservers.user.EventService; +import io.github.znetworkw.znpcservers.user.ZUser; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.plugin.Plugin; + +public class PlayerListener implements Listener { + public PlayerListener(Plugin serversNPC) { + serversNPC.getServer().getPluginManager().registerEvents(this, serversNPC); + } + + @EventHandler + public void onJoin(PlayerJoinEvent event) { + ZUser.find(event.getPlayer()); + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + ZUser.unregister(event.getPlayer()); + } + + @EventHandler(ignoreCancelled = true) + public void onTalk(AsyncPlayerChatEvent event) { + ZUser zUser = ZUser.find(event.getPlayer()); + if (EventService.hasService(zUser, AsyncPlayerChatEvent.class)) { + event.setCancelled(true); + EventService eventService = EventService.findService(zUser, AsyncPlayerChatEvent.class); + eventService.runAll(event); + zUser.getEventServices().remove(eventService); + } + } + + @EventHandler + public void onConversation(NPCInteractEvent event) { + ConversationModel conversationStorage = event.getNpc().getNpcPojo().getConversation(); + if (conversationStorage == null || conversationStorage + .getConversationType() != ConversationModel.ConversationType.CLICK) + return; + event.getNpc().tryStartConversation(event.getPlayer()); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/CustomizationLoader.java b/src/main/java/io/github/znetworkw/znpcservers/npc/CustomizationLoader.java new file mode 100644 index 0000000..b54fb5a --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/CustomizationLoader.java @@ -0,0 +1,51 @@ +package io.github.znetworkw.znpcservers.npc; + +import com.google.common.collect.Iterables; +import io.github.znetworkw.znpcservers.cache.CachePackage; +import io.github.znetworkw.znpcservers.cache.TypeCache; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +public class CustomizationLoader { + private final Class entityClass; + + private final Map methods; + + public CustomizationLoader(EntityType entityType, Iterable methodsName) { + this(entityType.getEntityClass(), methodsName); + } + + protected CustomizationLoader(Class entityClass, Iterable methodsName) { + this.entityClass = entityClass; + this.methods = loadMethods(methodsName); + } + + protected Map loadMethods(Iterable iterable) { + Map builder = new HashMap<>(); + for (Method method : this.entityClass.getMethods()) { + if (!builder.containsKey(method.getName()) && + Iterables.contains(iterable, method.getName())) { + for (Class parameter : method.getParameterTypes()) { + TypeProperty typeProperty = TypeProperty.forType(parameter); + if (typeProperty == null && parameter.isEnum()) + (new TypeCache.BaseCache.EnumLoader((new TypeCache.CacheBuilder(CachePackage.DEFAULT)) + .withClassName(parameter.getTypeName()))).load(); + } + builder.put(method.getName(), method); + } + } + return builder; + } + + public boolean contains(String name) { + return this.methods.containsKey(name); + } + + public Map getMethods() { + return this.methods; + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/FunctionContext.java b/src/main/java/io/github/znetworkw/znpcservers/npc/FunctionContext.java new file mode 100644 index 0000000..dec0bdd --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/FunctionContext.java @@ -0,0 +1,34 @@ +package io.github.znetworkw.znpcservers.npc; + +public interface FunctionContext { + NPC getNPC(); + + interface WithValue extends FunctionContext { + String getValue(); + } + + class DefaultContext implements FunctionContext { + private final NPC npc; + + public DefaultContext(NPC npc) { + this.npc = npc; + } + + public NPC getNPC() { + return this.npc; + } + } + + class ContextWithValue extends DefaultContext implements WithValue { + private final String value; + + public ContextWithValue(NPC npc, String value) { + super(npc); + this.value = value; + } + + public String getValue() { + return this.value; + } + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/FunctionFactory.java b/src/main/java/io/github/znetworkw/znpcservers/npc/FunctionFactory.java new file mode 100644 index 0000000..595ffb4 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/FunctionFactory.java @@ -0,0 +1,41 @@ +package io.github.znetworkw.znpcservers.npc; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.github.znetworkw.znpcservers.npc.function.GlowFunction; +import io.github.znetworkw.znpcservers.utility.GuavaCollectors; + +public final class FunctionFactory { + public static ImmutableList WITHOUT_FUNCTION = ImmutableList.of(new NPCFunction.WithoutFunction("look"), new NPCFunction.WithoutFunctionSelfUpdate("holo"), new NPCFunction.WithoutFunctionSelfUpdate("mirror")); + + public static ImmutableList WITH_FUNCTION = ImmutableList.of(new GlowFunction()); + + public static ImmutableList ALL = ImmutableList.builder() + .addAll(WITHOUT_FUNCTION) + .addAll(WITH_FUNCTION) + .build(); + + public static ImmutableMap BY_NAME; + + static { + BY_NAME = ALL.stream().collect(GuavaCollectors.toImmutableMap(NPCFunction::getName, function -> function)); + } + + public static NPCFunction findFunctionForName(String name) { + return BY_NAME.get(name); + } + + public static ImmutableList findFunctionsForNpc(NPC npc) { + return ALL.stream() + .filter(function -> isTrue(npc, function)) + .collect(GuavaCollectors.toImmutableList()); + } + + public static boolean isTrue(NPC npc, NPCFunction function) { + return npc.getNpcPojo().getFunctions().getOrDefault(function.getName(), Boolean.FALSE).booleanValue(); + } + + public static boolean isTrue(NPC npc, String function) { + return isTrue(npc, findFunctionForName(function)); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/ItemSlot.java b/src/main/java/io/github/znetworkw/znpcservers/npc/ItemSlot.java new file mode 100644 index 0000000..dc36c6e --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/ItemSlot.java @@ -0,0 +1,27 @@ +package io.github.znetworkw.znpcservers.npc; + +public enum ItemSlot { + HELMET(5), + CHESTPLATE(4), + LEGGINGS(3), + BOOTS(2), + OFFHAND(1), + HAND(0); + + private final int slot; + + private final int slotOld; + + ItemSlot(int slot) { + this.slot = slot; + this.slotOld = (slot == 0) ? 0 : (slot - 1); + } + + public int getSlot() { + return this.slot; + } + + public int getSlotOld() { + return this.slotOld; + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/NPC.java b/src/main/java/io/github/znetworkw/znpcservers/npc/NPC.java new file mode 100644 index 0000000..787f8dc --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/NPC.java @@ -0,0 +1,357 @@ +package io.github.znetworkw.znpcservers.npc; + +import com.google.common.collect.ImmutableList; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; +import lol.pyr.znpcsplus.ZNPCsPlus; +import io.github.znetworkw.znpcservers.UnexpectedCallException; +import io.github.znetworkw.znpcservers.cache.CacheRegistry; +import io.github.znetworkw.znpcservers.npc.conversation.ConversationModel; +import io.github.znetworkw.znpcservers.npc.hologram.Hologram; +import io.github.znetworkw.znpcservers.npc.packet.PacketCache; +import io.github.znetworkw.znpcservers.user.ZUser; +import io.github.znetworkw.znpcservers.utility.Utils; +import io.github.znetworkw.znpcservers.utility.location.ZLocation; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import java.lang.reflect.Constructor; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class NPC { + private static final ConcurrentMap NPC_MAP = new ConcurrentHashMap<>(); + + private static final String PROFILE_TEXTURES = "textures"; + + private static final String START_PREFIX = "[ZNPC] "; + + private final Set viewers = new HashSet<>(); + + private final PacketCache packets = new PacketCache(); + + private final NPCModel npcPojo; + + private final Hologram hologram; + + private final String npcName; + + private final NPCSkin npcSkin; + + private long lastMove = -1L; + + private int entityID; + + private Object glowColor; + + private Object tabConstructor; + + private Object updateTabConstructor; + + private Object nmsEntity; + + private Object bukkitEntity; + + private UUID uuid; + + private GameProfile gameProfile; + + private NPCPath.PathInitializer npcPath; + + public NPC(NPCModel npcModel, boolean load) { + this.npcPojo = npcModel; + this.hologram = new Hologram(this); + this.npcName = NamingType.DEFAULT.resolve(this); + this.npcSkin = NPCSkin.forValues(npcModel.getSkin(), npcModel.getSignature()); + if (load) + onLoad(); + } + + public NPC(NPCModel npcModel) { + this(npcModel, false); + } + + public static NPC find(int id) { + return NPC_MAP.get(Integer.valueOf(id)); + } + + public static void unregister(int id) { + NPC npc = find(id); + if (npc == null) + throw new IllegalStateException("can't find npc with id " + id); + NPC_MAP.remove(Integer.valueOf(id)); + npc.deleteViewers(); + } + + public static Collection all() { + return NPC_MAP.values(); + } + + public void onLoad() { + if (NPC_MAP.containsKey(Integer.valueOf(getNpcPojo().getId()))) + throw new IllegalStateException("npc with id " + getNpcPojo().getId() + " already exists."); + this.gameProfile = new GameProfile(UUID.randomUUID(), "[ZNPC] " + this.npcName); + this.gameProfile.getProperties().put("textures", new Property("textures", this.npcPojo.getSkin(), this.npcPojo.getSignature())); + changeType(this.npcPojo.getNpcType()); + updateProfile(this.gameProfile.getProperties()); + setLocation(getNpcPojo().getLocation().bukkitLocation(), false); + this.hologram.createHologram(); + if (this.npcPojo.getPathName() != null) + setPath(NPCPath.AbstractTypeWriter.find(this.npcPojo.getPathName())); + this.npcPojo.getCustomizationMap().forEach((key, value) -> this.npcPojo.getNpcType().updateCustomization(this, key, value)); + NPC_MAP.put(Integer.valueOf(getNpcPojo().getId()), this); + } + + public NPCModel getNpcPojo() { + return this.npcPojo; + } + + public UUID getUUID() { + return this.uuid; + } + + public int getEntityID() { + return this.entityID; + } + + public Object getBukkitEntity() { + return this.bukkitEntity; + } + + public Object getNmsEntity() { + return this.nmsEntity; + } + + public Object getGlowColor() { + return this.glowColor; + } + + public void setGlowColor(Object glowColor) { + this.glowColor = glowColor; + } + + public GameProfile getGameProfile() { + return this.gameProfile; + } + + public NPCPath.PathInitializer getNpcPath() { + return this.npcPath; + } + + public Hologram getHologram() { + return this.hologram; + } + + public Set getViewers() { + return this.viewers; + } + + public PacketCache getPackets() { + return this.packets; + } + + public void setLocation(Location location, boolean updateTime) { + try { + if (this.npcPath == null) { + lookAt(null, location, true); + if (updateTime) + this.lastMove = System.nanoTime(); + this.npcPojo.setLocation(new ZLocation(location = new Location(location.getWorld(), location.getBlockX() + 0.5D, location.getY(), location.getBlockZ() + 0.5D, location.getYaw(), location.getPitch()))); + } + CacheRegistry.SET_LOCATION_METHOD.load().invoke(this.nmsEntity, Double.valueOf(location.getX()), Double.valueOf(location.getY()), Double.valueOf(location.getZ()), Float.valueOf(location.getYaw()), Float.valueOf(location.getPitch())); + Object npcTeleportPacket = ((Constructor) CacheRegistry.PACKET_PLAY_OUT_ENTITY_TELEPORT_CONSTRUCTOR.load()).newInstance(this.nmsEntity); + this.viewers.forEach(player -> Utils.sendPackets(player, npcTeleportPacket)); + this.hologram.setLocation(location, this.npcPojo.getNpcType().getHoloHeight()); + } catch (ReflectiveOperationException operationException) { + throw new UnexpectedCallException(operationException); + } + } + + public void changeSkin(NPCSkin skinFetch) { + this.npcPojo.setSkin(skinFetch.getTexture()); + this.npcPojo.setSignature(skinFetch.getSignature()); + this.gameProfile.getProperties().clear(); + this.gameProfile.getProperties().put("textures", new Property("textures", this.npcPojo + .getSkin(), this.npcPojo.getSignature())); + updateProfile(this.gameProfile.getProperties()); + deleteViewers(); + } + + public void setSecondLayerSkin() { + try { + Object dataWatcherObject = CacheRegistry.GET_DATA_WATCHER_METHOD.load().invoke(this.nmsEntity); + if (Utils.versionNewer(9)) { + CacheRegistry.SET_DATA_WATCHER_METHOD.load().invoke(dataWatcherObject, ((Constructor) CacheRegistry.DATA_WATCHER_OBJECT_CONSTRUCTOR + .load()).newInstance(Integer.valueOf(this.npcSkin.getLayerIndex()), CacheRegistry.DATA_WATCHER_REGISTER_FIELD + .load()), Byte.valueOf(127)); + } else { + CacheRegistry.WATCH_DATA_WATCHER_METHOD.load().invoke(dataWatcherObject, Integer.valueOf(10), Byte.valueOf(127)); + } + } catch (ReflectiveOperationException operationException) { + throw new UnexpectedCallException(operationException); + } + } + + public synchronized void changeType(NPCType npcType) { + deleteViewers(); + try { + Object nmsWorld = CacheRegistry.GET_HANDLE_WORLD_METHOD.load().invoke(getLocation().getWorld()); + boolean isPlayer = (npcType == NPCType.PLAYER); + this.nmsEntity = isPlayer ? this.packets.getProxyInstance().getPlayerPacket(nmsWorld, this.gameProfile) : (Utils.versionNewer(14) ? npcType.getConstructor().newInstance(npcType.getNmsEntityType(), nmsWorld) : npcType.getConstructor().newInstance(nmsWorld)); + this.bukkitEntity = CacheRegistry.GET_BUKKIT_ENTITY_METHOD.load().invoke(this.nmsEntity); + this.uuid = (UUID) CacheRegistry.GET_UNIQUE_ID_METHOD.load().invoke(this.nmsEntity, new Object[0]); + if (isPlayer) { + try { + this.tabConstructor = ((Constructor) CacheRegistry.PACKET_PLAY_OUT_PLAYER_INFO_CONSTRUCTOR.load()).newInstance(CacheRegistry.ADD_PLAYER_FIELD.load(), Collections.singletonList(this.nmsEntity)); + } catch (Throwable e) { + this.tabConstructor = ((Constructor) CacheRegistry.PACKET_PLAY_OUT_PLAYER_INFO_CONSTRUCTOR.load()).newInstance(CacheRegistry.ADD_PLAYER_FIELD.load(), this.nmsEntity); + this.updateTabConstructor = ((Constructor) CacheRegistry.PACKET_PLAY_OUT_PLAYER_INFO_CONSTRUCTOR.load()).newInstance(CacheRegistry.UPDATE_LISTED_FIELD.load(), this.nmsEntity); + } + setSecondLayerSkin(); + } + this.npcPojo.setNpcType(npcType); + setLocation(getLocation(), false); + this.packets.flushCache("spawnPacket", "removeTab"); + this.entityID = ((Integer) CacheRegistry.GET_ENTITY_ID.load().invoke(this.nmsEntity, new Object[0])).intValue(); + FunctionFactory.findFunctionsForNpc(this).forEach(function -> function.resolve(this)); + getPackets().getProxyInstance().update(this.packets); + this.hologram.createHologram(); + } catch (ReflectiveOperationException operationException) { + throw new UnexpectedCallException(operationException); + } + } + + public synchronized void spawn(ZUser user) { + if (this.viewers.contains(user)) + throw new IllegalStateException(user.getUUID().toString() + " is already a viewer."); + try { + this.viewers.add(user); + boolean npcIsPlayer = (this.npcPojo.getNpcType() == NPCType.PLAYER); + if (FunctionFactory.isTrue(this, "glow") || npcIsPlayer) { + ImmutableList scoreboardPackets = this.packets.getProxyInstance().updateScoreboard(this); + scoreboardPackets.forEach(p -> Utils.sendPackets(user, p)); + } + if (npcIsPlayer) { + if (FunctionFactory.isTrue(this, "mirror")) + updateProfile(user.getGameProfile().getProperties()); + Utils.sendPackets(user, this.tabConstructor, this.updateTabConstructor); + } + Utils.sendPackets(user, this.packets.getProxyInstance().getSpawnPacket(this.nmsEntity, npcIsPlayer)); + if (FunctionFactory.isTrue(this, "holo")) + this.hologram.spawn(user); + updateMetadata(Collections.singleton(user)); + sendEquipPackets(user); + lookAt(user, getLocation(), true); + if (npcIsPlayer) { + Object removeTabPacket = this.packets.getProxyInstance().getTabRemovePacket(this.nmsEntity); + ZNPCsPlus.SCHEDULER.scheduleSyncDelayedTask(() -> Utils.sendPackets(user, removeTabPacket, this.updateTabConstructor), 60); + } + } catch (ReflectiveOperationException operationException) { + delete(user); + throw new UnexpectedCallException(operationException); + } + } + + public synchronized void delete(ZUser user) { + if (!this.viewers.contains(user)) + throw new IllegalStateException(user.getUUID().toString() + " is not a viewer."); + this.viewers.remove(user); + handleDelete(user); + } + + private void handleDelete(ZUser user) { + try { + if (this.npcPojo.getNpcType() == NPCType.PLAYER) + this.packets.getProxyInstance().getTabRemovePacket(this.nmsEntity); + this.hologram.delete(user); + Utils.sendPackets(user, this.packets.getProxyInstance().getDestroyPacket(this.entityID)); + } catch (ReflectiveOperationException operationException) { + throw new UnexpectedCallException(operationException); + } + } + + public void lookAt(ZUser player, Location location, boolean rotation) { + long lastMoveNanos = System.nanoTime() - this.lastMove; + if (this.lastMove > 1L && lastMoveNanos < 1000000000L) + return; + Location direction = rotation ? location : this.npcPojo.getLocation().bukkitLocation().clone().setDirection(location.clone().subtract(this.npcPojo.getLocation().bukkitLocation().clone()).toVector()); + try { + Object lookPacket = ((Constructor) CacheRegistry.PACKET_PLAY_OUT_ENTITY_LOOK_CONSTRUCTOR.load()).newInstance(Integer.valueOf(this.entityID), Byte.valueOf((byte) (int) (direction.getYaw() * 256.0F / 360.0F)), Byte.valueOf((byte) (int) (direction.getPitch() * 256.0F / 360.0F)), Boolean.TRUE); + Object headRotationPacket = ((Constructor) CacheRegistry.PACKET_PLAY_OUT_ENTITY_HEAD_ROTATION_CONSTRUCTOR.load()).newInstance(this.nmsEntity, Byte.valueOf((byte) (int) (direction.getYaw() * 256.0F / 360.0F))); + if (player != null) { + Utils.sendPackets(player, lookPacket, headRotationPacket); + } else { + this.viewers.forEach(players -> Utils.sendPackets(players, headRotationPacket)); + } + } catch (ReflectiveOperationException operationException) { + throw new UnexpectedCallException(operationException); + } + } + + public void deleteViewers() { + for (ZUser user : this.viewers) + handleDelete(user); + this.viewers.clear(); + } + + protected void updateMetadata(Iterable users) { + try { + Object metaData = this.packets.getProxyInstance().getMetadataPacket(this.entityID, this.nmsEntity); + for (ZUser user : users) { + Utils.sendPackets(user, metaData); + } + } catch (ReflectiveOperationException operationException) { + operationException.getCause().printStackTrace(); + operationException.printStackTrace(); + } + } + + public void updateProfile(PropertyMap propertyMap) { + if (this.npcPojo.getNpcType() != NPCType.PLAYER) + return; + try { + Object gameProfileObj = CacheRegistry.GET_PROFILE_METHOD.load().invoke(this.nmsEntity); + Utils.setValue(gameProfileObj, "name", this.gameProfile.getName()); + Utils.setValue(gameProfileObj, "id", this.gameProfile.getId()); + Utils.setValue(gameProfileObj, "properties", propertyMap); + } catch (ReflectiveOperationException operationException) { + throw new UnexpectedCallException(operationException); + } + } + + public void sendEquipPackets(ZUser zUser) { + if (this.npcPojo.getNpcEquip().isEmpty()) + return; + try { + ImmutableList equipPackets = this.packets.getProxyInstance().getEquipPackets(this); + equipPackets.forEach(o -> Utils.sendPackets(zUser, o)); + } catch (ReflectiveOperationException operationException) { + throw new UnexpectedCallException(operationException.getCause()); + } + } + + public void setPath(NPCPath.AbstractTypeWriter typeWriter) { + if (typeWriter == null) { + this.npcPath = null; + this.npcPojo.setPathName("none"); + } else { + this.npcPath = typeWriter.getPath(this); + this.npcPojo.setPathName(typeWriter.getName()); + } + } + + public void tryStartConversation(Player player) { + ConversationModel conversation = this.npcPojo.getConversation(); + if (conversation == null) + throw new IllegalStateException("can't find conversation"); + conversation.startConversation(this, player); + } + + public Location getLocation() { + return (this.npcPath != null) ? + this.npcPath.getLocation().bukkitLocation() : + this.npcPojo.getLocation().bukkitLocation(); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/NPCAction.java b/src/main/java/io/github/znetworkw/znpcservers/npc/NPCAction.java new file mode 100644 index 0000000..553fc6d --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/NPCAction.java @@ -0,0 +1,97 @@ +package io.github.znetworkw.znpcservers.npc; + +import com.google.common.base.MoreObjects; +import lol.pyr.znpcsplus.ZNPCsPlus; +import io.github.znetworkw.znpcservers.npc.event.ClickType; +import io.github.znetworkw.znpcservers.user.ZUser; +import io.github.znetworkw.znpcservers.utility.Utils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; + +public class NPCAction { + private final ActionType actionType; + + private final ClickType clickType; + + private final String action; + + private int delay; + + public NPCAction(ActionType actionType, ClickType clickType, String action, int delay) { + this.actionType = actionType; + this.clickType = clickType; + this.action = action; + this.delay = delay; + } + + public NPCAction(String actionType, String action) { + this(ActionType.valueOf(actionType), ClickType.DEFAULT, action, 0); + } + + public ActionType getActionType() { + return this.actionType; + } + + public ClickType getClickType() { + return this.clickType; + } + + public String getAction() { + return this.action; + } + + public int getDelay() { + return this.delay; + } + + public void setDelay(int delay) { + this.delay = delay; + } + + public long getFixedDelay() { + return 1000000000L * this.delay; + } + + public void run(ZUser user, String action) { + this.actionType.run(user, Utils.PLACEHOLDER_SUPPORT ? Utils.getWithPlaceholders(action, user.toPlayer()) : action); + } + + public String toString() { + return MoreObjects.toStringHelper(this) + .add("actionType", this.actionType) + .add("clickType", this.clickType) + .add("action", this.action) + .add("delay", this.delay) + .toString(); + } + + enum ActionType { + CMD { + public void run(ZUser user, String actionValue) { + user.toPlayer().performCommand(actionValue); + } + }, + CONSOLE { + public void run(ZUser user, String actionValue) { + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), actionValue); + } + }, + CHAT { + public void run(ZUser user, String actionValue) { + user.toPlayer().chat(actionValue); + } + }, + MESSAGE { + public void run(ZUser user, String actionValue) { + user.toPlayer().sendMessage(ChatColor.translateAlternateColorCodes('&', actionValue)); + } + }, + SERVER { + public void run(ZUser user, String actionValue) { + ZNPCsPlus.BUNGEE_UTILS.sendPlayerToServer(user.toPlayer(), actionValue); + } + }; + + public abstract void run(ZUser param1ZUser, String param1String); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/NPCFunction.java b/src/main/java/io/github/znetworkw/znpcservers/npc/NPCFunction.java new file mode 100644 index 0000000..33e74f7 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/NPCFunction.java @@ -0,0 +1,66 @@ +package io.github.znetworkw.znpcservers.npc; + +public abstract class NPCFunction { + private final String name; + + public NPCFunction(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + protected abstract boolean allow(NPC paramNPC); + + protected abstract ResultType runFunction(NPC paramNPC, FunctionContext paramFunctionContext); + + public void doRunFunction(NPC npc, FunctionContext functionContext) { + if (!allow(npc)) + return; + ResultType resultType = runFunction(npc, functionContext); + if (resultType == ResultType.SUCCESS) + npc.getNpcPojo().getFunctions().put(getName(), Boolean.valueOf(!isTrue(npc))); + } + + protected ResultType resolve(NPC npc) { + throw new IllegalStateException("resolve is not implemented."); + } + + public boolean isTrue(NPC npc) { + return FunctionFactory.isTrue(npc, this); + } + + public enum ResultType { + SUCCESS, FAIL + } + + public static class WithoutFunction extends NPCFunction { + public WithoutFunction(String name) { + super(name); + } + + protected NPCFunction.ResultType runFunction(NPC npc, FunctionContext functionContext) { + return NPCFunction.ResultType.SUCCESS; + } + + protected boolean allow(NPC npc) { + return true; + } + + protected NPCFunction.ResultType resolve(NPC npc) { + return NPCFunction.ResultType.SUCCESS; + } + } + + public static class WithoutFunctionSelfUpdate extends WithoutFunction { + public WithoutFunctionSelfUpdate(String name) { + super(name); + } + + protected NPCFunction.ResultType runFunction(NPC npc, FunctionContext functionContext) { + npc.deleteViewers(); + return NPCFunction.ResultType.SUCCESS; + } + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/NPCModel.java b/src/main/java/io/github/znetworkw/znpcservers/npc/NPCModel.java new file mode 100644 index 0000000..0ff287f --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/NPCModel.java @@ -0,0 +1,238 @@ +package io.github.znetworkw.znpcservers.npc; + +import io.github.znetworkw.znpcservers.npc.conversation.ConversationModel; +import io.github.znetworkw.znpcservers.utility.location.ZLocation; +import org.bukkit.inventory.ItemStack; + +import java.util.*; + +public class NPCModel { + private static final String EMPTY_STRING = ""; + + private int id; + + private double hologramHeight; + + private String skin; + + private String signature = ""; + + private String pathName; + + private String glowName; + + private ConversationModel conversation; + + private ZLocation location; + + private NPCType npcType; + + private List hologramLines; + + private List clickActions; + + private Map npcEquip; + + private Map npcFunctions; + + private Map customizationMap; + + public NPCModel(int id) { + this(); + this.id = id; + this.skin = ""; + this.signature = ""; + this.npcType = NPCType.PLAYER; + } + + private NPCModel() { + this.hologramLines = Collections.singletonList("/znpcs lines"); + this.clickActions = new ArrayList<>(); + this.npcEquip = new HashMap<>(); + this.customizationMap = new HashMap<>(); + this.npcFunctions = new HashMap<>(); + this.npcFunctions.put("holo", Boolean.TRUE); + } + + public int getId() { + return this.id; + } + + public void setId(int id) { + this.id = id; + } + + public NPCModel withId(int id) { + setId(id); + return this; + } + + public double getHologramHeight() { + return this.hologramHeight; + } + + public void setHologramHeight(double hologramHeight) { + this.hologramHeight = hologramHeight; + } + + public NPCModel withHologramHeight(double hologramHeight) { + setHologramHeight(hologramHeight); + return this; + } + + public String getSkin() { + return this.skin; + } + + public void setSkin(String skin) { + this.skin = skin; + } + + public NPCModel withSkin(String skin) { + setSkin(skin); + return this; + } + + public String getSignature() { + return this.signature; + } + + public void setSignature(String signature) { + this.signature = signature; + } + + public NPCModel withSignature(String signature) { + setSignature(signature); + return this; + } + + public String getPathName() { + return this.pathName; + } + + public void setPathName(String pathName) { + this.pathName = pathName; + } + + public NPCModel withPathName(String pathName) { + setPathName(pathName); + return this; + } + + public String getGlowName() { + return this.glowName; + } + + public void setGlowName(String glowName) { + this.glowName = glowName; + } + + public NPCModel withGlowName(String glowName) { + setGlowName(this.pathName); + return this; + } + + public ConversationModel getConversation() { + return this.conversation; + } + + public void setConversation(ConversationModel conversation) { + this.conversation = conversation; + } + + public NPCModel withConversation(ConversationModel conversation) { + setConversation(conversation); + return this; + } + + public List getHologramLines() { + return this.hologramLines; + } + + public void setHologramLines(List hologramLines) { + this.hologramLines = hologramLines; + } + + public NPCModel withHologramLines(List hologramLines) { + setHologramLines(hologramLines); + return this; + } + + public ZLocation getLocation() { + return this.location; + } + + public void setLocation(ZLocation location) { + this.location = location; + } + + public NPCModel withLocation(ZLocation location) { + setLocation(location); + return this; + } + + public NPCType getNpcType() { + return this.npcType; + } + + public void setNpcType(NPCType npcType) { + this.npcType = npcType; + } + + public NPCModel withNpcType(NPCType npcType) { + setNpcType(npcType); + return this; + } + + public List getClickActions() { + return this.clickActions; + } + + public void setClickActions(List clickActions) { + this.clickActions = clickActions; + } + + public NPCModel withClickActions(List clickActions) { + setClickActions(clickActions); + return this; + } + + public Map getNpcEquip() { + return this.npcEquip; + } + + public void setNpcEquip(Map npcEquip) { + this.npcEquip = npcEquip; + } + + public NPCModel withNpcEquip(Map npcEquip) { + setNpcEquip(npcEquip); + return this; + } + + public Map getCustomizationMap() { + return this.customizationMap; + } + + public void setCustomizationMap(Map customizationMap) { + this.customizationMap = customizationMap; + } + + public NPCModel withCustomizationMap(Map customizationMap) { + setCustomizationMap(customizationMap); + return this; + } + + public Map getFunctions() { + return this.npcFunctions; + } + + public void setFunctions(Map npcFunctions) { + this.npcFunctions = npcFunctions; + } + + public NPCModel withFunctionValues(Map npcFunctions) { + setFunctions(npcFunctions); + return this; + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/NPCPath.java b/src/main/java/io/github/znetworkw/znpcservers/npc/NPCPath.java new file mode 100644 index 0000000..be99680 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/NPCPath.java @@ -0,0 +1,306 @@ +package io.github.znetworkw.znpcservers.npc; + +import lol.pyr.znpcsplus.ZNPCsPlus; +import io.github.znetworkw.znpcservers.configuration.Configuration; +import io.github.znetworkw.znpcservers.configuration.ConfigurationValue; +import io.github.znetworkw.znpcservers.user.ZUser; +import io.github.znetworkw.znpcservers.utility.location.ZLocation; +import org.bukkit.Location; +import org.bukkit.scheduler.BukkitTask; +import org.bukkit.util.Vector; + +import java.io.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +public interface NPCPath { + void initialize(DataInputStream paramDataInputStream) throws IOException; + + void write(DataOutputStream paramDataOutputStream) throws IOException; + + void start(); + + PathInitializer getPath(NPC paramNPC); + + interface PathInitializer { + void handle(); + + ZLocation getLocation(); + + abstract class AbstractPath implements PathInitializer { + private final NPC npc; + + private final NPCPath.AbstractTypeWriter typeWriter; + + private ZLocation location; + + public AbstractPath(NPC npc, NPCPath.AbstractTypeWriter typeWriter) { + this.npc = npc; + this.typeWriter = typeWriter; + } + + public NPC getNpc() { + return this.npc; + } + + public NPCPath.AbstractTypeWriter getPath() { + return this.typeWriter; + } + + public ZLocation getLocation() { + return this.location; + } + + public void setLocation(ZLocation location) { + this.location = location; + } + } + } + + class ZNPCPathDelegator { + private final File file; + + protected ZNPCPathDelegator(File file) { + this.file = file; + } + + public static ZNPCPathDelegator forFile(File file) { + return new ZNPCPathDelegator(file); + } + + public static ZNPCPathDelegator forPath(NPCPath.AbstractTypeWriter pathAbstract) { + return new ZNPCPathDelegator(pathAbstract.getFile()); + } + + public DataOutputStream getOutputStream() throws IOException { + return new DataOutputStream(new FileOutputStream(this.file)); + } + + public DataInputStream getInputStream() throws IOException { + return new DataInputStream(new FileInputStream(this.file)); + } + } + + abstract class AbstractTypeWriter implements NPCPath { + private static final Logger LOGGER = Logger.getLogger(AbstractTypeWriter.class.getName()); + + private static final ConcurrentMap PATH_TYPES = new ConcurrentHashMap<>(); + + private static final int PATH_DELAY = 1; + + private final TypeWriter typeWriter; + + private final File file; + + private final List locationList; + + public AbstractTypeWriter(TypeWriter typeWriter, File file) { + this.typeWriter = typeWriter; + this.file = file; + this.locationList = new ArrayList<>(); + } + + public AbstractTypeWriter(TypeWriter typeWriter, String pathName) { + this(typeWriter, new File(ZNPCsPlus.PATH_FOLDER, pathName + ".path")); + } + + public static AbstractTypeWriter forCreation(String pathName, ZUser user, TypeWriter typeWriter) { + if (typeWriter == TypeWriter.MOVEMENT) + return new TypeMovement(pathName, user); + throw new IllegalStateException("can't find type writer for: " + typeWriter.name()); + } + + public static AbstractTypeWriter forFile(File file, TypeWriter typeWriter) { + if (typeWriter == TypeWriter.MOVEMENT) + return new TypeMovement(file); + throw new IllegalStateException("can't find type writer for: " + typeWriter.name()); + } + + public static void register(AbstractTypeWriter abstractZNPCPath) { + PATH_TYPES.put(abstractZNPCPath.getName(), abstractZNPCPath); + } + + public static AbstractTypeWriter find(String name) { + return PATH_TYPES.get(name); + } + + public static Collection getPaths() { + return PATH_TYPES.values(); + } + + public void load() { + try { + DataInputStream reader = NPCPath.ZNPCPathDelegator.forFile(this.file).getInputStream(); + try { + initialize(reader); + register(this); + if (reader != null) + reader.close(); + } catch (Throwable throwable) { + if (reader != null) + try { + reader.close(); + } catch (Throwable throwable1) { + throwable.addSuppressed(throwable1); + } + throw throwable; + } + } catch (IOException e) { + LOGGER.log(Level.WARNING, String.format("The path %s could not be loaded", this.file.getName())); + } + } + + public void write() { + try { + DataOutputStream writer = NPCPath.ZNPCPathDelegator.forFile(getFile()).getOutputStream(); + try { + write(writer); + if (writer != null) + writer.close(); + } catch (Throwable throwable) { + if (writer != null) + try { + writer.close(); + } catch (Throwable throwable1) { + throwable.addSuppressed(throwable1); + } + throw throwable; + } + } catch (IOException e) { + LOGGER.log(Level.WARNING, String.format("Path %s could not be created", getName()), e); + } + } + + public File getFile() { + return this.file; + } + + public List getLocationList() { + return this.locationList; + } + + public String getName() { + return this.file.getName().substring(0, this.file.getName().lastIndexOf('.')); + } + + public enum TypeWriter { + MOVEMENT + } + + private static class TypeMovement extends AbstractTypeWriter { + private static final int MAX_LOCATIONS = ((Integer) Configuration.CONFIGURATION.getValue(ConfigurationValue.MAX_PATH_LOCATIONS)).intValue(); + + private ZUser npcUser; + + private BukkitTask bukkitTask; + + public TypeMovement(File file) { + super(NPCPath.AbstractTypeWriter.TypeWriter.MOVEMENT, file); + } + + public TypeMovement(String fileName, ZUser npcUser) { + super(NPCPath.AbstractTypeWriter.TypeWriter.MOVEMENT, fileName); + this.npcUser = npcUser; + start(); + } + + public void initialize(DataInputStream dataInputStream) throws IOException { + while (dataInputStream.available() > 0) { + String worldName = dataInputStream.readUTF(); + double x = dataInputStream.readDouble(); + double y = dataInputStream.readDouble(); + double z = dataInputStream.readDouble(); + float yaw = dataInputStream.readFloat(); + float pitch = dataInputStream.readFloat(); + getLocationList().add(new ZLocation(worldName, x, y, z, yaw, pitch)); + } + } + + public void write(DataOutputStream dataOutputStream) throws IOException { + if (getLocationList().isEmpty()) + return; + Iterator locationIterator = getLocationList().iterator(); + while (locationIterator.hasNext()) { + ZLocation location = locationIterator.next(); + dataOutputStream.writeUTF(location.getWorldName()); + dataOutputStream.writeDouble(location.getX()); + dataOutputStream.writeDouble(location.getY()); + dataOutputStream.writeDouble(location.getZ()); + dataOutputStream.writeFloat(location.getYaw()); + dataOutputStream.writeFloat(location.getPitch()); + if (!locationIterator.hasNext()) + register(this); + } + } + + public void start() { + this.npcUser.setHasPath(true); + this.bukkitTask = ZNPCsPlus.SCHEDULER.runTaskTimerAsynchronously(() -> { + if (this.npcUser.toPlayer() != null && this.npcUser.isHasPath() && MAX_LOCATIONS > getLocationList().size()) { + Location location = this.npcUser.toPlayer().getLocation(); + if (isValid(location)) + getLocationList().add(new ZLocation(location)); + } else { + this.bukkitTask.cancel(); + this.npcUser.setHasPath(false); + write(); + } + }1, 1); + } + + public MovementPath getPath(NPC npc) { + return new MovementPath(npc, this); + } + + protected boolean isValid(Location location) { + if (getLocationList().isEmpty()) + return true; + ZLocation last = getLocationList().get(getLocationList().size() - 1); + double xDiff = Math.abs(last.getX() - location.getX()); + double yDiff = Math.abs(last.getY() - location.getY()); + double zDiff = Math.abs(last.getZ() - location.getZ()); + return (xDiff + yDiff + zDiff > 0.01D); + } + + protected static class MovementPath extends NPCPath.PathInitializer.AbstractPath { + private int currentEntryPath = 0; + + private boolean pathReverse = false; + + public MovementPath(NPC npc, NPCPath.AbstractTypeWriter.TypeMovement path) { + super(npc, path); + } + + public void handle() { + updatePathLocation(getPath().getLocationList().get(this.currentEntryPath = getNextLocation())); + int nextIndex = getNextLocation(); + if (nextIndex < 1) { + this.pathReverse = false; + } else if (nextIndex >= getPath().getLocationList().size() - 1) { + this.pathReverse = true; + } + } + + private int getNextLocation() { + return this.pathReverse ? (this.currentEntryPath - 1) : (this.currentEntryPath + 1); + } + + protected void updatePathLocation(ZLocation location) { + setLocation(location); + ZLocation next = getPath().getLocationList().get(getNextLocation()); + Vector vector = next.toVector().add(new Vector(0.0D, location.getY() - next.getY(), 0.0D)); + Location direction = next.bukkitLocation().clone().setDirection(location.toVector().subtract(vector) + .multiply(new Vector(-1, 0, -1))); + getNpc().setLocation(direction, false); + getNpc().lookAt(null, direction, true); + } + } + } + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/NPCSkin.java b/src/main/java/io/github/znetworkw/znpcservers/npc/NPCSkin.java new file mode 100644 index 0000000..0328d9b --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/NPCSkin.java @@ -0,0 +1,71 @@ +package io.github.znetworkw.znpcservers.npc; + +import io.github.znetworkw.znpcservers.skin.SkinFetcherBuilder; +import io.github.znetworkw.znpcservers.skin.SkinFetcherResult; +import io.github.znetworkw.znpcservers.utility.Utils; + +public class NPCSkin { + private static final String EMPTY_STRING = ""; + + private static final String[] EMPTY_ARRAY = new String[]{"", ""}; + + private static final int LAYER_INDEX = SkinLayerValues.findLayerByVersion(); + + private final String texture; + + private final String signature; + + protected NPCSkin(String... values) { + if (values.length < 1) + throw new IllegalArgumentException("Length cannot be zero or negative."); + this.texture = values[0]; + this.signature = values[1]; + } + + public static NPCSkin forValues(String... values) { + return new NPCSkin((values.length > 0) ? values : EMPTY_ARRAY); + } + + public static void forName(String skin, SkinFetcherResult skinFetcherResult) { + SkinFetcherBuilder.withName(skin).toSkinFetcher().doReadSkin(skinFetcherResult); + } + + public String getTexture() { + return this.texture; + } + + public String getSignature() { + return this.signature; + } + + public int getLayerIndex() { + return LAYER_INDEX; + } + + enum SkinLayerValues { + V8(8, 12), + V9(10, 13), + V14(14, 15), + V16(15, 16), + V17(17, 17), + V18(18, 17); + + final int minVersion; + + final int layerValue; + + SkinLayerValues(int minVersion, int layerValue) { + this.minVersion = minVersion; + this.layerValue = layerValue; + } + + static int findLayerByVersion() { + int value = V8.layerValue; + for (SkinLayerValues skinLayerValue : values()) { + if (Utils.BUKKIT_VERSION >= skinLayerValue.minVersion) + value = skinLayerValue.layerValue; + } + return value; + } + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/NPCType.java b/src/main/java/io/github/znetworkw/znpcservers/npc/NPCType.java new file mode 100644 index 0000000..e35761d --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/NPCType.java @@ -0,0 +1,135 @@ +package io.github.znetworkw.znpcservers.npc; + +import io.github.znetworkw.znpcservers.UnexpectedCallException; +import io.github.znetworkw.znpcservers.cache.CacheRegistry; +import io.github.znetworkw.znpcservers.cache.TypeCache; +import io.github.znetworkw.znpcservers.utility.Utils; +import org.bukkit.entity.EntityType; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Optional; + +public enum NPCType { + PLAYER(CacheRegistry.ENTITY_PLAYER_CLASS, 0.0D, new String[0]), + ARMOR_STAND(CacheRegistry.ENTITY_ARMOR_STAND_CLASS, 0.0D, new String[]{"setSmall", "setArms"}), + CREEPER(CacheRegistry.ENTITY_CREEPER_CLASS, -0.15D, new String[]{"setPowered"}), + BAT(CacheRegistry.ENTITY_BAT_CLASS, -0.5D, new String[]{"setAwake"}), + BLAZE(CacheRegistry.ENTITY_BLAZE_CLASS, 0.0D, new String[0]), + CAVE_SPIDER(CacheRegistry.ENTITY_CAVE_SPIDER_CLASS, -1.0D, new String[0]), + COW(CacheRegistry.ENTITY_COW_CLASS, -0.25D, new String[]{"setAge"}), + CHICKEN(CacheRegistry.ENTITY_CHICKEN_CLASS, -1.0D, new String[]{"setAge"}), + ENDER_DRAGON(CacheRegistry.ENTITY_ENDER_DRAGON_CLASS, 1.5D, new String[0]), + ENDERMAN(CacheRegistry.ENTITY_ENDERMAN_CLASS, 0.7D, new String[0]), + ENDERMITE(CacheRegistry.ENTITY_ENDERMITE_CLASS, -1.5D, new String[0]), + GHAST(CacheRegistry.ENTITY_GHAST_CLASS, 3.0D, new String[0]), + IRON_GOLEM(CacheRegistry.ENTITY_IRON_GOLEM_CLASS, 0.75D, new String[0]), + GIANT(CacheRegistry.ENTITY_GIANT_ZOMBIE_CLASS, 11.0D, new String[0]), + GUARDIAN(CacheRegistry.ENTITY_GUARDIAN_CLASS, -0.7D, new String[0]), + HORSE(CacheRegistry.ENTITY_HORSE_CLASS, 0.0D, new String[]{"setStyle", "setAge", "setColor", "setVariant"}), + LLAMA(CacheRegistry.ENTITY_LLAMA_CLASS, 0.0D, new String[]{"setAge"}), + MAGMA_CUBE(CacheRegistry.ENTITY_MAGMA_CUBE_CLASS, -1.25D, new String[]{"setSize"}), + MUSHROOM_COW(CacheRegistry.ENTITY_MUSHROOM_COW_CLASS, -0.25D, new String[]{"setAge"}), + OCELOT(CacheRegistry.ENTITY_OCELOT_CLASS, -1.0D, new String[]{"setCatType", "setAge"}), + PARROT(CacheRegistry.ENTITY_PARROT_CLASS, -1.5D, new String[]{"setVariant"}), + PIG(CacheRegistry.ENTITY_PIG_CLASS, -1.0D, new String[]{"setAge"}), + PANDA(CacheRegistry.ENTITY_PANDA_CLASS, -0.6D, new String[]{"setAge", "setMainGene", "setHiddenGene"}), + RABBIT(CacheRegistry.ENTITY_RABBIT_CLASS, -1.0D, new String[]{"setRabbitType"}), + POLAR_BEAR(CacheRegistry.ENTITY_POLAR_BEAR_CLASS, -0.5D, new String[0]), + SHEEP(CacheRegistry.ENTITY_SHEEP_CLASS, -0.5D, new String[]{"setAge", "setSheared", "setColor"}), + SILVERFISH(CacheRegistry.ENTITY_SILVERFISH_CLASS, -1.5D, new String[0]), + SNOWMAN(CacheRegistry.ENTITY_SNOWMAN_CLASS, 0.0D, new String[]{"setHasPumpkin", "setDerp"}), + SKELETON(CacheRegistry.ENTITY_SKELETON_CLASS, 0.0D, new String[0]), + SHULKER(CacheRegistry.ENTITY_SHULKER_CLASS, 0.0D, new String[0]), + SLIME(CacheRegistry.ENTITY_SLIME_CLASS, -1.25D, new String[]{"setSize"}), + SPIDER(CacheRegistry.ENTITY_SPIDER_CLASS, -1.0D, new String[0]), + SQUID(CacheRegistry.ENTITY_SQUID_CLASS, -1.0D, new String[0]), + VILLAGER(CacheRegistry.ENTITY_VILLAGER_CLASS, 0.0D, new String[]{"setProfession", "setVillagerType", "setAge"}), + WITCH(CacheRegistry.ENTITY_WITCH_CLASS, 0.5D, new String[0]), + WITHER(CacheRegistry.ENTITY_WITHER_CLASS, 1.75D, new String[0]), + ZOMBIE(CacheRegistry.ENTITY_ZOMBIE_CLASS, 0.0D, new String[]{"setBaby"}), + WOLF(CacheRegistry.ENTITY_WOLF_CLASS, -1.0D, new String[]{"setSitting", "setTamed", "setAngry", "setAge", "setCollarColor"}), + FOX(CacheRegistry.ENTITY_FOX_CLASS, -1.0D, new String[]{"setFoxType", "setSitting", "setSleeping", "setAge", "setCrouching"}), + BEE(CacheRegistry.ENTITY_BEE_CLASS, -1.0D, new String[]{"setAnger", "setHasNectar", "setHasStung"}), + TURTLE(CacheRegistry.ENTITY_TURTLE, -1.0D, new String[0]), + WARDEN(CacheRegistry.ENTITY_WARDEN, 1.0D, new String[0]), + AXOLOTL(CacheRegistry.ENTITY_AXOLOTL_CLASS, -1.0D, new String[]{"setVariant", "setAge"}), + GOAT(CacheRegistry.ENTITY_GOAT_CLASS, -0.5D, new String[]{"setScreamingGoat", "setAge"}); + + private static final String EMPTY_STRING = ""; + + private final double holoHeight; + + private final CustomizationLoader customizationLoader; + + private final Constructor constructor; + + private EntityType bukkitEntityType; + + private Object nmsEntityType; + + NPCType(Class entityClass, String newName, double holoHeight, String... methods) { + this.holoHeight = holoHeight; + this + + .customizationLoader = (entityClass == null) ? null : new CustomizationLoader(this.bukkitEntityType = EntityType.valueOf((newName.length() > 0) ? newName : name()), Arrays.asList(methods)); + if (entityClass == null || entityClass + .isAssignableFrom(CacheRegistry.ENTITY_PLAYER_CLASS)) { + this.constructor = null; + return; + } + try { + if (Utils.versionNewer(14)) { + this.nmsEntityType = ((Optional) CacheRegistry.ENTITY_TYPES_A_METHOD.load().invoke(null, new Object[]{this.bukkitEntityType.getKey().getKey().toLowerCase()})).get(); + this.constructor = entityClass.getConstructor(CacheRegistry.ENTITY_TYPES_CLASS, CacheRegistry.WORLD_CLASS); + } else { + this.constructor = entityClass.getConstructor(CacheRegistry.WORLD_CLASS); + } + } catch (ReflectiveOperationException operationException) { + throw new UnexpectedCallException(operationException); + } + } + + public static Object[] arrayToPrimitive(String[] strings, Method method) { + Class[] methodParameterTypes = method.getParameterTypes(); + Object[] newArray = new Object[methodParameterTypes.length]; + for (int i = 0; i < methodParameterTypes.length; i++) { + TypeProperty typeProperty = TypeProperty.forType(methodParameterTypes[i]); + if (typeProperty != null) { + newArray[i] = typeProperty.getFunction().apply(strings[i]); + } else { + newArray[i] = TypeCache.ClassCache.find(strings[i], methodParameterTypes[i]); + } + } + return newArray; + } + + public double getHoloHeight() { + return this.holoHeight; + } + + public Constructor getConstructor() { + return this.constructor; + } + + public Object getNmsEntityType() { + return this.nmsEntityType; + } + + public CustomizationLoader getCustomizationLoader() { + return this.customizationLoader; + } + + public void updateCustomization(NPC npc, String name, String[] values) { + if (!this.customizationLoader.contains(name)) + return; + try { + Method method = this.customizationLoader.getMethods().get(name); + method.invoke(npc.getBukkitEntity(), arrayToPrimitive(values, method)); + npc.updateMetadata(npc.getViewers()); + } catch (IllegalAccessException | java.lang.reflect.InvocationTargetException e) { + throw new IllegalStateException("can't invoke method: " + name, e); + } + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/NamingType.java b/src/main/java/io/github/znetworkw/znpcservers/npc/NamingType.java new file mode 100644 index 0000000..6b664a8 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/NamingType.java @@ -0,0 +1,15 @@ +package io.github.znetworkw.znpcservers.npc; + +import io.github.znetworkw.znpcservers.utility.Utils; + +public enum NamingType { + DEFAULT { + public String resolve(NPC npc) { + return Utils.randomString(6); + } + }; + + private static final int FIXED_LENGTH = 6; + + public abstract String resolve(NPC paramNPC); +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/TypeProperty.java b/src/main/java/io/github/znetworkw/znpcservers/npc/TypeProperty.java new file mode 100644 index 0000000..79f8e45 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/TypeProperty.java @@ -0,0 +1,41 @@ +package io.github.znetworkw.znpcservers.npc; + +import java.util.function.Function; + +public enum TypeProperty { + STRING(String::toString), + BOOLEAN(Boolean::parseBoolean), + INT(Integer::parseInt), + DOUBLE(Double::parseDouble), + FLOAT(Float::parseFloat), + SHORT(Short::parseShort), + LONG(Long::parseLong); + + private final Function function; + + TypeProperty(Function function) { + this.function = function; + } + + public static TypeProperty forType(Class primitiveType) { + if (primitiveType == String.class) + return STRING; + if (primitiveType == boolean.class) + return BOOLEAN; + if (primitiveType == int.class) + return INT; + if (primitiveType == double.class) + return DOUBLE; + if (primitiveType == float.class) + return FLOAT; + if (primitiveType == short.class) + return SHORT; + if (primitiveType == long.class) + return LONG; + return null; + } + + public Function getFunction() { + return this.function; + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/conversation/Conversation.java b/src/main/java/io/github/znetworkw/znpcservers/npc/conversation/Conversation.java new file mode 100644 index 0000000..6cdead9 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/conversation/Conversation.java @@ -0,0 +1,61 @@ +package io.github.znetworkw.znpcservers.npc.conversation; + +import io.github.znetworkw.znpcservers.configuration.ConfigurationConstants; + +import java.util.ArrayList; +import java.util.List; + +public class Conversation { + private final String name; + + private final List texts; + + private int radius = 5; + + private int delay = 10; + + public Conversation(String name) { + this(name, new ArrayList<>()); + } + + protected Conversation(String name, List text) { + this.name = name; + this.texts = text; + } + + public static Conversation forName(String name) { + return ConfigurationConstants.NPC_CONVERSATIONS.stream() + .filter(conversation -> conversation.getName().equalsIgnoreCase(name)) + .findFirst() + .orElse(null); + } + + public static boolean exists(String name) { + return ConfigurationConstants.NPC_CONVERSATIONS.stream() + .anyMatch(conversation -> conversation.getName().equalsIgnoreCase(name)); + } + + public String getName() { + return this.name; + } + + public List getTexts() { + return this.texts; + } + + public int getDelay() { + return this.delay; + } + + public void setDelay(int delay) { + this.delay = delay; + } + + public int getRadius() { + return this.radius; + } + + public void setRadius(int radius) { + this.radius = radius; + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/conversation/ConversationKey.java b/src/main/java/io/github/znetworkw/znpcservers/npc/conversation/ConversationKey.java new file mode 100644 index 0000000..616fdba --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/conversation/ConversationKey.java @@ -0,0 +1,63 @@ +package io.github.znetworkw.znpcservers.npc.conversation; + +import com.google.common.base.Splitter; +import io.github.znetworkw.znpcservers.npc.NPCAction; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +public class ConversationKey { + private static final Splitter SPACE_SPLITTER = Splitter.on(" "); + + private final List lines; + + private final List actions; + + private int delay = 1; + + private String soundName; + + public ConversationKey(String line) { + this(SPACE_SPLITTER.split(line)); + } + + public ConversationKey(Iterable line) { + this + .lines = StreamSupport.stream(line.spliterator(), false).map(String::toString).collect(Collectors.toList()); + this.actions = new ArrayList<>(); + } + + public List getLines() { + return this.lines; + } + + public int getDelay() { + return this.delay; + } + + public void setDelay(int delay) { + this.delay = delay; + } + + public String getSoundName() { + return this.soundName; + } + + public void setSoundName(String soundName) { + this.soundName = soundName; + } + + public List getActions() { + return this.actions; + } + + public String getTextFormatted() { + if (this.lines.isEmpty()) + return ""; + String text = this.lines.iterator().next(); + int fixedLength = Math.min(text.length(), 28); + return text.substring(0, fixedLength); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/conversation/ConversationModel.java b/src/main/java/io/github/znetworkw/znpcservers/npc/conversation/ConversationModel.java new file mode 100644 index 0000000..bde6095 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/conversation/ConversationModel.java @@ -0,0 +1,76 @@ +package io.github.znetworkw.znpcservers.npc.conversation; + +import io.github.znetworkw.znpcservers.npc.NPC; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Stream; + +public class ConversationModel { + private final transient Map lastStarted = new HashMap<>(); + private String conversationName; + private ConversationType conversationType; + + public ConversationModel(String conversationName, String conversationType) { + this.conversationName = conversationName; + try { + this.conversationType = ConversationType.valueOf(conversationType.toUpperCase()); + } catch (IllegalArgumentException exception) { + throw new IllegalStateException("can't find conversation type " + conversationType); + } + } + + private ConversationModel() { + } + + public String getConversationName() { + return this.conversationName; + } + + public ConversationType getConversationType() { + return this.conversationType; + } + + public Conversation getConversation() { + return Conversation.forName(this.conversationName); + } + + public void startConversation(NPC npc, Player player) { + if (!Conversation.exists(this.conversationName)) + throw new IllegalStateException("can't find conversation " + this.conversationName); + if (ConversationProcessor.isPlayerConversing(player.getUniqueId())) + return; + if (this.lastStarted.containsKey(player.getUniqueId())) { + long lastConversationNanos = System.nanoTime() - this.lastStarted.get(player.getUniqueId()).longValue(); + if (lastConversationNanos < 1000000000L * getConversation().getDelay()) + return; + } + this.lastStarted.remove(player.getUniqueId()); + if (this.conversationType.canStart(npc, getConversation(), player)) { + new ConversationProcessor(npc, this, player); + this.lastStarted.put(player.getUniqueId(), Long.valueOf(System.nanoTime())); + } + } + + public boolean canRun(NPC npc, Player player) { + return Stream.of(ConversationType.values()).anyMatch(conversationType1 -> !conversationType1.canStart(npc, getConversation(), player)); + } + + public enum ConversationType { + RADIUS { + public boolean canStart(NPC npc, Conversation conversation, Player player) { + return (player.getWorld() == npc.getLocation().getWorld() && player + .getLocation().distance(npc.getLocation()) <= conversation.getRadius()); + } + }, + CLICK { + public boolean canStart(NPC npc, Conversation conversation, Player player) { + return true; + } + }; + + abstract boolean canStart(NPC param1NPC, Conversation param1Conversation, Player param1Player); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/conversation/ConversationProcessor.java b/src/main/java/io/github/znetworkw/znpcservers/npc/conversation/ConversationProcessor.java new file mode 100644 index 0000000..a4e6d0e --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/conversation/ConversationProcessor.java @@ -0,0 +1,79 @@ +package io.github.znetworkw.znpcservers.npc.conversation; + +import lol.pyr.znpcsplus.ZNPCsPlus; +import io.github.znetworkw.znpcservers.configuration.ConfigurationConstants; +import io.github.znetworkw.znpcservers.npc.NPC; +import io.github.znetworkw.znpcservers.npc.hologram.replacer.LineReplacer; +import io.github.znetworkw.znpcservers.user.ZUser; +import org.bukkit.Bukkit; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class ConversationProcessor { + private static final Map RUNNING_CONVERSATIONS = new HashMap<>(); + + private static final String WHITE_SPACE = " "; + + private static final int CONVERSATION_DELAY = 20; + + private final NPC npc; + + private final ConversationModel conversationModel; + + private final Player player; + + private int conversationIndex = 0; + + private long conversationIndexDelay = System.nanoTime(); + + public ConversationProcessor(NPC npc, ConversationModel conversationModel, Player player) { + if (conversationModel.getConversation().getTexts().isEmpty()) + throw new IllegalStateException("conversation should have a text."); + this.npc = npc; + this.conversationModel = conversationModel; + this.player = player; + RUNNING_CONVERSATIONS.put(player.getUniqueId(), conversationModel.getConversationName()); + start(); + } + + public static boolean isPlayerConversing(UUID uuid) { + return RUNNING_CONVERSATIONS.containsKey(uuid); + } + + private void start() { + ZNPCsPlus.SCHEDULER.runTaskTimer(new BukkitRunnable() { + public void run() { + if (Bukkit.getPlayer(ConversationProcessor.this.player.getUniqueId()) == null || ConversationProcessor.this + .conversationIndex > ConversationProcessor.this.conversationModel.getConversation().getTexts().size() - 1 || ConversationProcessor.this + .conversationModel.canRun(ConversationProcessor.this.npc, ConversationProcessor.this.player)) { + ConversationProcessor.RUNNING_CONVERSATIONS.remove(ConversationProcessor.this.player.getUniqueId()); + cancel(); + return; + } + ConversationKey conversationKey = ConversationProcessor.this.conversationModel.getConversation().getTexts().get(ConversationProcessor.this.conversationIndex); + long conversationDelayNanos = System.nanoTime() - ConversationProcessor.this.conversationIndexDelay; + if (ConversationProcessor.this.conversationIndex != 0 && conversationDelayNanos < 1000000000L * conversationKey + .getDelay()) + return; + ZUser user = ZUser.find(ConversationProcessor.this.player); + conversationKey.getLines().forEach(s -> ConversationProcessor.this.player.sendMessage(LineReplacer.makeAll(user, s).replace(ConfigurationConstants.SPACE_SYMBOL, " "))); + if (conversationKey.getActions().size() > 0) + conversationKey.getActions().forEach(action -> action.run(user, action.getAction())); + if (conversationKey.getSoundName() != null && conversationKey + .getSoundName().length() > 0) + try { + Sound sound = Sound.valueOf(conversationKey.getSoundName().toUpperCase()); + ConversationProcessor.this.player.playSound(ConversationProcessor.this.player.getLocation(), sound, 0.2F, 1.0F); + } catch (IllegalArgumentException illegalArgumentException) { + } + ConversationProcessor.this.conversationIndexDelay = System.nanoTime(); + ConversationProcessor.this.conversationIndex++; + } + }5, 20); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/event/ClickType.java b/src/main/java/io/github/znetworkw/znpcservers/npc/event/ClickType.java new file mode 100644 index 0000000..99e8871 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/event/ClickType.java @@ -0,0 +1,13 @@ +package io.github.znetworkw.znpcservers.npc.event; + +public enum ClickType { + RIGHT, LEFT, DEFAULT; + + public static ClickType forName(String clickName) { + if (clickName.startsWith("INTERACT")) + return RIGHT; + if (clickName.startsWith("ATTACK")) + return LEFT; + return DEFAULT; + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/event/NPCInteractEvent.java b/src/main/java/io/github/znetworkw/znpcservers/npc/event/NPCInteractEvent.java new file mode 100644 index 0000000..9dce459 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/event/NPCInteractEvent.java @@ -0,0 +1,47 @@ +package io.github.znetworkw.znpcservers.npc.event; + +import io.github.znetworkw.znpcservers.npc.NPC; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +public class NPCInteractEvent extends Event { + private static final HandlerList handlerList = new HandlerList(); + private final Player player; + private final ClickType clickType; + private final NPC npc; + + public NPCInteractEvent(Player player, ClickType clickType, NPC npc) { + this.player = player; + this.clickType = clickType; + this.npc = npc; + } + + public NPCInteractEvent(Player player, String clickType, NPC npc) { + this(player, ClickType.forName(clickType), npc); + } + + public static HandlerList getHandlerList() { + return handlerList; + } + + public Player getPlayer() { + return this.player; + } + + public NPC getNpc() { + return this.npc; + } + + public boolean isRightClick() { + return (this.clickType == ClickType.RIGHT); + } + + public boolean isLeftClick() { + return (this.clickType == ClickType.LEFT); + } + + public HandlerList getHandlers() { + return handlerList; + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/function/GlowFunction.java b/src/main/java/io/github/znetworkw/znpcservers/npc/function/GlowFunction.java new file mode 100644 index 0000000..351f513 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/function/GlowFunction.java @@ -0,0 +1,46 @@ +package io.github.znetworkw.znpcservers.npc.function; + +import io.github.znetworkw.znpcservers.cache.CacheRegistry; +import io.github.znetworkw.znpcservers.npc.FunctionContext; +import io.github.znetworkw.znpcservers.npc.FunctionFactory; +import io.github.znetworkw.znpcservers.npc.NPC; +import io.github.znetworkw.znpcservers.npc.NPCFunction; + +import java.lang.reflect.Constructor; + +public class GlowFunction extends NPCFunction { + public GlowFunction() { + super("glow"); + } + + protected NPCFunction.ResultType runFunction(NPC npc, FunctionContext functionContext) { + if (!(functionContext instanceof FunctionContext.ContextWithValue)) + throw new IllegalStateException("invalid context type, " + functionContext.getClass().getSimpleName() + ", expected ContextWithValue."); + String glowColorName = ((FunctionContext.ContextWithValue) functionContext).getValue(); + try { + Object glowColor = CacheRegistry.ENUM_CHAT_FORMAT_FIND.load().invoke(null, ( + glowColorName == null || glowColorName.length() == 0) ? "WHITE" : glowColorName); + if (glowColor == null) + return NPCFunction.ResultType.FAIL; + npc.getNpcPojo().setGlowName(glowColorName); + npc.setGlowColor(glowColor); + CacheRegistry.SET_DATA_WATCHER_METHOD.load().invoke(CacheRegistry.GET_DATA_WATCHER_METHOD + .load().invoke(npc.getNmsEntity()), ((Constructor) CacheRegistry.DATA_WATCHER_OBJECT_CONSTRUCTOR + .load()).newInstance(Integer.valueOf(0), CacheRegistry.DATA_WATCHER_REGISTER_FIELD + .load()), Byte.valueOf(!FunctionFactory.isTrue(npc, this) ? 64 : 0)); + npc.getPackets().getProxyInstance().update(npc.getPackets()); + npc.deleteViewers(); + return NPCFunction.ResultType.SUCCESS; + } catch (ReflectiveOperationException operationException) { + return NPCFunction.ResultType.FAIL; + } + } + + protected boolean allow(NPC npc) { + return npc.getPackets().getProxyInstance().allowGlowColor(); + } + + public NPCFunction.ResultType resolve(NPC npc) { + return runFunction(npc, new FunctionContext.ContextWithValue(npc, npc.getNpcPojo().getGlowName())); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/hologram/Hologram.java b/src/main/java/io/github/znetworkw/znpcservers/npc/hologram/Hologram.java new file mode 100644 index 0000000..f9b0770 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/hologram/Hologram.java @@ -0,0 +1,142 @@ +package io.github.znetworkw.znpcservers.npc.hologram; + +import io.github.znetworkw.znpcservers.UnexpectedCallException; +import io.github.znetworkw.znpcservers.cache.CacheRegistry; +import io.github.znetworkw.znpcservers.configuration.Configuration; +import io.github.znetworkw.znpcservers.configuration.ConfigurationConstants; +import io.github.znetworkw.znpcservers.configuration.ConfigurationValue; +import io.github.znetworkw.znpcservers.npc.NPC; +import io.github.znetworkw.znpcservers.npc.hologram.replacer.LineReplacer; +import io.github.znetworkw.znpcservers.user.ZUser; +import io.github.znetworkw.znpcservers.utility.Utils; +import org.bukkit.Location; + +import javax.annotation.Nullable; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; + +public class Hologram { + private static final String WHITESPACE = " "; + + private static final boolean NEW_METHOD = (Utils.BUKKIT_VERSION > 12); + + private static final double LINE_SPACING = ((Double) Configuration.CONFIGURATION.getValue(ConfigurationValue.LINE_SPACING)).doubleValue(); + + private final List hologramLines = new ArrayList<>(); + + private final NPC npc; + + public Hologram(NPC npc) { + this.npc = npc; + } + + public void createHologram() { + this.npc.getViewers().forEach(this::delete); + try { + this.hologramLines.clear(); + double y = 0.0D; + Location location = this.npc.getLocation(); + for (String line : this.npc.getNpcPojo().getHologramLines()) { + boolean visible = !line.equalsIgnoreCase("%space%"); + Object armorStand = ((Constructor) CacheRegistry.ENTITY_CONSTRUCTOR.load()).newInstance(CacheRegistry.GET_HANDLE_WORLD_METHOD.load().invoke(location.getWorld()), + Double.valueOf(location.getX()), Double.valueOf(location.getY() - 0.15D + y), Double.valueOf(location.getZ())); + if (visible) { + CacheRegistry.SET_CUSTOM_NAME_VISIBLE_METHOD.load().invoke(armorStand, Boolean.valueOf(true)); + updateLine(line, armorStand, null); + } + CacheRegistry.SET_INVISIBLE_METHOD.load().invoke(armorStand, Boolean.valueOf(true)); + this.hologramLines.add(new HologramLine(line.replace(ConfigurationConstants.SPACE_SYMBOL, " "), armorStand, ((Integer) CacheRegistry.GET_ENTITY_ID + .load().invoke(armorStand, new Object[0])).intValue())); + y += LINE_SPACING; + } + setLocation(location, 0.0D); + this.npc.getPackets().flushCache("getHologramSpawnPacket"); + this.npc.getViewers().forEach(this::spawn); + } catch (ReflectiveOperationException operationException) { + throw new UnexpectedCallException(operationException); + } + } + + public void spawn(ZUser user) { + this.hologramLines.forEach(hologramLine -> { + try { + Object entityPlayerPacketSpawn = this.npc.getPackets().getProxyInstance().getHologramSpawnPacket(hologramLine.armorStand); + Utils.sendPackets(user, entityPlayerPacketSpawn); + } catch (ReflectiveOperationException operationException) { + delete(user); + } + }); + } + + public void delete(ZUser user) { + this.hologramLines.forEach(hologramLine -> { + try { + Utils.sendPackets(user, this.npc.getPackets().getProxyInstance().getDestroyPacket(HologramLine.access$200(hologramLine))); + } catch (ReflectiveOperationException operationException) { + throw new UnexpectedCallException(operationException); + } + }); + } + + public void updateNames(ZUser user) { + for (HologramLine hologramLine : this.hologramLines) { + try { + updateLine(hologramLine.line, hologramLine.armorStand, user); + Object metaData = this.npc.getPackets().getProxyInstance().getMetadataPacket(hologramLine.id, hologramLine.armorStand); + Utils.sendPackets(user, metaData); + } catch (ReflectiveOperationException operationException) { + throw new UnexpectedCallException(operationException); + } + } + } + + public void updateLocation() { + this.hologramLines.forEach(hologramLine -> { + try { + Object packet = ((Constructor) CacheRegistry.PACKET_PLAY_OUT_ENTITY_TELEPORT_CONSTRUCTOR.load()).newInstance(new Object[]{HologramLine.access$100(hologramLine)}); + this.npc.getViewers().forEach(()); + } catch (ReflectiveOperationException operationException) { + throw new UnexpectedCallException(operationException); + } + }); + } + + public void setLocation(Location location, double height) { + location = location.clone().add(0.0D, height, 0.0D); + try { + double y = this.npc.getNpcPojo().getHologramHeight(); + for (HologramLine hologramLine : this.hologramLines) { + CacheRegistry.SET_LOCATION_METHOD.load().invoke(hologramLine.armorStand, Double.valueOf(location.getX()), Double.valueOf(location.getY() - 0.15D + y), + Double.valueOf(location.getZ()), Float.valueOf(location.getYaw()), Float.valueOf(location.getPitch())); + y += LINE_SPACING; + } + updateLocation(); + } catch (ReflectiveOperationException operationException) { + throw new UnexpectedCallException(operationException); + } + } + + private void updateLine(String line, Object armorStand, @Nullable ZUser user) throws InvocationTargetException, IllegalAccessException { + if (NEW_METHOD) { + CacheRegistry.SET_CUSTOM_NAME_NEW_METHOD.load().invoke(armorStand, CacheRegistry.CRAFT_CHAT_MESSAGE_METHOD.load().invoke(null, LineReplacer.makeAll(user, line))); + } else { + CacheRegistry.SET_CUSTOM_NAME_OLD_METHOD.load().invoke(armorStand, LineReplacer.makeAll(user, line)); + } + } + + private static class HologramLine { + private final String line; + + private final Object armorStand; + + private final int id; + + protected HologramLine(String line, Object armorStand, int id) { + this.line = line; + this.armorStand = armorStand; + this.id = id; + } + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/hologram/replacer/LineReplacer.java b/src/main/java/io/github/znetworkw/znpcservers/npc/hologram/replacer/LineReplacer.java new file mode 100644 index 0000000..7e731fc --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/hologram/replacer/LineReplacer.java @@ -0,0 +1,26 @@ +package io.github.znetworkw.znpcservers.npc.hologram.replacer; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.UnmodifiableIterator; +import io.github.znetworkw.znpcservers.user.ZUser; +import io.github.znetworkw.znpcservers.utility.Utils; + +public interface LineReplacer { + ImmutableList LINE_REPLACERS = ImmutableList.of(new RGBLine()); + + static String makeAll(ZUser user, String string) { + for (UnmodifiableIterator unmodifiableIterator = LINE_REPLACERS.iterator(); unmodifiableIterator.hasNext(); ) { + LineReplacer lineReplacer = unmodifiableIterator.next(); + if (!lineReplacer.isSupported()) + continue; + string = lineReplacer.make(string); + } + return Utils.toColor((Utils.PLACEHOLDER_SUPPORT && user != null) ? + Utils.getWithPlaceholders(string, user.toPlayer()) : + string); + } + + String make(String paramString); + + boolean isSupported(); +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/hologram/replacer/RGBLine.java b/src/main/java/io/github/znetworkw/znpcservers/npc/hologram/replacer/RGBLine.java new file mode 100644 index 0000000..9b44402 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/hologram/replacer/RGBLine.java @@ -0,0 +1,44 @@ +package io.github.znetworkw.znpcservers.npc.hologram.replacer; + +import io.github.znetworkw.znpcservers.configuration.ConfigurationConstants; +import io.github.znetworkw.znpcservers.utility.Utils; +import net.md_5.bungee.api.ChatColor; + +import java.util.concurrent.ThreadLocalRandom; + +public class RGBLine implements LineReplacer { + private static final char HEX_COLOR_CHAR = '#'; + + private static final int HEX_COLOR_LENGTH = 6; + + public String make(String string) { + String rgbString = string; + for (int i = 0; i < rgbString.length(); i++) { + char charAt = rgbString.charAt(i); + if (charAt == '#') { + int endIndex = i + 6 + 1; + boolean success = true; + StringBuilder hexCodeStringBuilder = new StringBuilder(); + for (int i2 = i; i2 < endIndex; i2++) { + if (rgbString.length() - 1 < i2) { + success = false; + break; + } + char hexCode = rgbString.charAt(i2); + hexCodeStringBuilder.append((ConfigurationConstants.RGB_ANIMATION && hexCode != '#') ? + Integer.toHexString(ThreadLocalRandom.current().nextInt(16)) : Character.valueOf(hexCode)); + } + if (success) + try { + rgbString = rgbString.substring(0, i) + ChatColor.of(hexCodeStringBuilder.toString()) + rgbString.substring(endIndex); + } catch (Exception exception) { + } + } + } + return rgbString; + } + + public boolean isSupported() { + return (Utils.BUKKIT_VERSION > 15); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/packet/Packet.java b/src/main/java/io/github/znetworkw/znpcservers/npc/packet/Packet.java new file mode 100644 index 0000000..ee5c5ec --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/packet/Packet.java @@ -0,0 +1,112 @@ +package io.github.znetworkw.znpcservers.npc.packet; + +import com.google.common.collect.ImmutableList; +import com.mojang.authlib.GameProfile; +import io.github.znetworkw.znpcservers.cache.CacheRegistry; +import io.github.znetworkw.znpcservers.npc.FunctionFactory; +import io.github.znetworkw.znpcservers.npc.ItemSlot; +import io.github.znetworkw.znpcservers.npc.NPC; +import io.github.znetworkw.znpcservers.npc.NPCType; +import io.github.znetworkw.znpcservers.utility.ReflectionUtils; +import io.github.znetworkw.znpcservers.utility.Utils; +import org.bukkit.inventory.ItemStack; + +import java.lang.reflect.Constructor; +import java.util.Collection; +import java.util.Collections; + +public interface Packet { + int version(); + + @PacketValue(keyName = "playerPacket") + Object getPlayerPacket(Object paramObject, GameProfile paramGameProfile) throws ReflectiveOperationException; + + @PacketValue(keyName = "spawnPacket") + Object getSpawnPacket(Object paramObject, boolean paramBoolean) throws ReflectiveOperationException; + + Object convertItemStack(int paramInt, ItemSlot paramItemSlot, ItemStack paramItemStack) throws ReflectiveOperationException; + + Object getClickType(Object paramObject) throws ReflectiveOperationException; + + Object getMetadataPacket(int paramInt, Object paramObject) throws ReflectiveOperationException; + + @PacketValue(keyName = "hologramSpawnPacket", valueType = ValueType.ARGUMENTS) + Object getHologramSpawnPacket(Object paramObject) throws ReflectiveOperationException; + + @PacketValue(keyName = "destroyPacket", valueType = ValueType.ARGUMENTS) + default Object getDestroyPacket(int entityId) throws ReflectiveOperationException { + (new int[1])[0] = entityId; + return ((Constructor) CacheRegistry.PACKET_PLAY_OUT_ENTITY_DESTROY_CONSTRUCTOR.load()).newInstance(((Constructor) CacheRegistry.PACKET_PLAY_OUT_ENTITY_DESTROY_CONSTRUCTOR.load()).getParameterTypes()[0].isArray() ? new int[1] : Integer.valueOf(entityId)); + } + + @PacketValue(keyName = "enumSlot", valueType = ValueType.ARGUMENTS) + default Object getItemSlot(int slot) { + return CacheRegistry.ENUM_ITEM_SLOT.getEnumConstants()[slot]; + } + + @PacketValue(keyName = "removeTab") + default Object getTabRemovePacket(Object nmsEntity) throws ReflectiveOperationException { + try { + return ((Constructor) CacheRegistry.PACKET_PLAY_OUT_PLAYER_INFO_CONSTRUCTOR.load()).newInstance(CacheRegistry.REMOVE_PLAYER_FIELD + .load(), + Collections.singletonList(nmsEntity)); + } catch (Throwable throwable) { + boolean useOldMethod = (CacheRegistry.PACKET_PLAY_OUT_PLAYER_INFO_REMOVE_CLASS != null); + if (useOldMethod) + return ((Constructor) CacheRegistry.PACKET_PLAY_OUT_PLAYER_INFO_REMOVE_CONSTRUCTOR.load()) + .newInstance(Collections.singletonList(CacheRegistry.GET_UNIQUE_ID_METHOD.load().invoke(nmsEntity))); + return ((Constructor) CacheRegistry.PACKET_PLAY_OUT_PLAYER_INFO_CONSTRUCTOR.load()).newInstance(CacheRegistry.REMOVE_PLAYER_FIELD + .load(), nmsEntity); + } + } + + @PacketValue(keyName = "equipPackets") + ImmutableList getEquipPackets(NPC paramNPC) throws ReflectiveOperationException; + + @PacketValue(keyName = "scoreboardPackets") + default ImmutableList updateScoreboard(NPC npc) throws ReflectiveOperationException { + ImmutableList.Builder builder = ImmutableList.builder(); + boolean isVersion17 = (Utils.BUKKIT_VERSION > 16); + boolean isVersion9 = (Utils.BUKKIT_VERSION > 8); + Object scoreboardTeamPacket = isVersion17 ? ((Constructor) CacheRegistry.SCOREBOARD_TEAM_CONSTRUCTOR.load()).newInstance(new Object[]{null, npc.getGameProfile().getName()}) : ((Constructor) CacheRegistry.PACKET_PLAY_OUT_SCOREBOARD_TEAM_CONSTRUCTOR_OLD.load()).newInstance(); + if (!isVersion17) { + Utils.setValue(scoreboardTeamPacket, "a", npc.getGameProfile().getName()); + Utils.setValue(scoreboardTeamPacket, isVersion9 ? "i" : "h", Integer.valueOf(1)); + } + builder.add(isVersion17 ? CacheRegistry.PACKET_PLAY_OUT_SCOREBOARD_TEAM_CREATE_V1.load().invoke(null, scoreboardTeamPacket) : scoreboardTeamPacket); + if (isVersion17) { + scoreboardTeamPacket = ((Constructor) CacheRegistry.SCOREBOARD_TEAM_CONSTRUCTOR.load()).newInstance(new Object[]{null, npc.getGameProfile().getName()}); + if (Utils.BUKKIT_VERSION > 17) { + Utils.setValue(scoreboardTeamPacket, "d", npc.getGameProfile().getName()); + ReflectionUtils.findFieldForClassAndSet(scoreboardTeamPacket, CacheRegistry.ENUM_TAG_VISIBILITY, CacheRegistry.ENUM_TAG_VISIBILITY_NEVER_FIELD.load()); + Utils.setValue(scoreboardTeamPacket, "m", CacheRegistry.ENUM_CHAT_FORMAT_FIND.load().invoke(null, "DARK_GRAY")); + } else { + Utils.setValue(scoreboardTeamPacket, "e", npc.getGameProfile().getName()); + Utils.setValue(scoreboardTeamPacket, "l", CacheRegistry.ENUM_TAG_VISIBILITY_NEVER_FIELD.load()); + } + } else { + scoreboardTeamPacket = ((Constructor) CacheRegistry.PACKET_PLAY_OUT_SCOREBOARD_TEAM_CONSTRUCTOR_OLD.load()).newInstance(); + Utils.setValue(scoreboardTeamPacket, "a", npc.getGameProfile().getName()); + Utils.setValue(scoreboardTeamPacket, "e", "never"); + Utils.setValue(scoreboardTeamPacket, isVersion9 ? "i" : "h", Integer.valueOf(0)); + } + Collection collection = isVersion17 ? (Collection) CacheRegistry.SCOREBOARD_PLAYER_LIST.load().invoke(scoreboardTeamPacket, new Object[0]) : (Collection) Utils.getValue(scoreboardTeamPacket, isVersion9 ? "h" : "g"); + if (npc.getNpcPojo().getNpcType() == NPCType.PLAYER) { + collection.add(npc.getGameProfile().getName()); + } else { + collection.add(npc.getUUID().toString()); + } + if (allowGlowColor() && FunctionFactory.isTrue(npc, "glow")) + updateGlowPacket(npc, scoreboardTeamPacket); + builder.add(isVersion17 ? CacheRegistry.PACKET_PLAY_OUT_SCOREBOARD_TEAM_CREATE.load().invoke(null, scoreboardTeamPacket, Boolean.TRUE) : scoreboardTeamPacket); + return builder.build(); + } + + void updateGlowPacket(NPC paramNPC, Object paramObject) throws ReflectiveOperationException; + + boolean allowGlowColor(); + + default void update(PacketCache packetCache) throws ReflectiveOperationException { + packetCache.flushCache("scoreboardPackets"); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketCache.java b/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketCache.java new file mode 100644 index 0000000..d9ac9c6 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketCache.java @@ -0,0 +1,87 @@ +package io.github.znetworkw.znpcservers.npc.packet; + +import com.google.common.collect.ImmutableMap; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class PacketCache { + protected static final ImmutableMap VALUE_LOOKUP_BY_NAME; + + static { + ImmutableMap.Builder methodPacketValueBuilder = ImmutableMap.builder(); + for (Method method : Packet.class.getMethods()) { + if (method.isAnnotationPresent(PacketValue.class)) + methodPacketValueBuilder.put(method, method.getAnnotation(PacketValue.class)); + } + VALUE_LOOKUP_BY_NAME = methodPacketValueBuilder.build(); + } + + private final Map packetResultCache = new ConcurrentHashMap<>(); + + private final Packet proxyInstance; + + public PacketCache(Packet packet) { + this.proxyInstance = newProxyInstance(packet); + } + + public PacketCache() { + this(PacketFactory.PACKET_FOR_CURRENT_VERSION); + } + + public Packet getProxyInstance() { + return this.proxyInstance; + } + + protected Packet newProxyInstance(Packet packet) { + return (Packet) Proxy.newProxyInstance(packet + .getClass().getClassLoader(), new Class[]{Packet.class}, new PacketHandler(this, packet)); + } + + private Object getOrCache(Packet instance, Method method, Object[] args) { + if (!VALUE_LOOKUP_BY_NAME.containsKey(method)) + throw new IllegalStateException("value not found for method: " + method.getName()); + PacketValue packetValue = VALUE_LOOKUP_BY_NAME.get(method); + String keyString = packetValue.valueType().resolve(packetValue.keyName(), args); + return this.packetResultCache.computeIfAbsent(keyString, o -> { + try { + return method.invoke(instance, args); + } catch (InvocationTargetException | IllegalAccessException operationException) { + throw new AssertionError("can't invoke method: " + method.getName(), operationException); + } + }); + } + + public void flushCache(String... strings) { + Set> set = this.packetResultCache.entrySet(); + for (String string : strings) + set.removeIf(entry -> entry.getKey().startsWith(string)); + } + + public void flushCache() { + flushCache(VALUE_LOOKUP_BY_NAME.values().stream() + .map(PacketValue::keyName).toArray(x$0 -> new String[x$0])); + } + + private static class PacketHandler implements InvocationHandler { + private final PacketCache packetCache; + + private final Packet packets; + + public PacketHandler(PacketCache packetCache, Packet packets) { + this.packetCache = packetCache; + this.packets = packets; + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (PacketCache.VALUE_LOOKUP_BY_NAME.containsKey(method)) + return this.packetCache.getOrCache(this.packets, method, args); + return method.invoke(this.packets, args); + } + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketFactory.java b/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketFactory.java new file mode 100644 index 0000000..b872bac --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketFactory.java @@ -0,0 +1,19 @@ +package io.github.znetworkw.znpcservers.npc.packet; + +import com.google.common.collect.ImmutableSet; +import io.github.znetworkw.znpcservers.utility.Utils; + +import java.util.Comparator; + +public final class PacketFactory { + public static final ImmutableSet ALL = ImmutableSet.of(new PacketV8(), new PacketV9(), new PacketV16(), new PacketV17(), new PacketV18(), new PacketV19(), (Object[]) new Packet[0]); + + public static final Packet PACKET_FOR_CURRENT_VERSION = findPacketForVersion(Utils.BUKKIT_VERSION); + + public static Packet findPacketForVersion(int version) { + return ALL.stream() + .filter(packet -> (version >= packet.version())) + .max(Comparator.comparing(Packet::version)) + .orElseThrow(() -> new IllegalArgumentException("No packet instance found for version: " + version)); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV16.java b/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV16.java new file mode 100644 index 0000000..b5a3e11 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV16.java @@ -0,0 +1,28 @@ +package io.github.znetworkw.znpcservers.npc.packet; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.mojang.datafixers.util.Pair; +import io.github.znetworkw.znpcservers.cache.CacheRegistry; +import io.github.znetworkw.znpcservers.npc.ItemSlot; +import io.github.znetworkw.znpcservers.npc.NPC; +import org.bukkit.inventory.ItemStack; + +import java.lang.reflect.Constructor; +import java.util.List; +import java.util.Map; + +public class PacketV16 extends PacketV9 { + public int version() { + return 16; + } + + public ImmutableList getEquipPackets(NPC npc) throws ReflectiveOperationException { + List> pairs = Lists.newArrayListWithCapacity((ItemSlot.values()).length); + for (Map.Entry entry : npc.getNpcPojo().getNpcEquip().entrySet()) + pairs.add(new Pair(getItemSlot(entry + .getKey().getSlot()), + convertItemStack(npc.getEntityID(), entry.getKey(), entry.getValue()))); + return ImmutableList.of(((Constructor) CacheRegistry.PACKET_PLAY_OUT_ENTITY_EQUIPMENT_CONSTRUCTOR_V1.load()).newInstance(Integer.valueOf(npc.getEntityID()), pairs)); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV17.java b/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV17.java new file mode 100644 index 0000000..85eac78 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV17.java @@ -0,0 +1,27 @@ +package io.github.znetworkw.znpcservers.npc.packet; + +import com.mojang.authlib.GameProfile; +import io.github.znetworkw.znpcservers.cache.CacheRegistry; +import io.github.znetworkw.znpcservers.npc.NPC; +import io.github.znetworkw.znpcservers.utility.Utils; +import org.bukkit.Bukkit; + +import java.lang.reflect.Constructor; + +public class PacketV17 extends PacketV16 { + public int version() { + return 17; + } + + public Object getPlayerPacket(Object nmsWorld, GameProfile gameProfile) throws ReflectiveOperationException { + return ((Constructor) CacheRegistry.PLAYER_CONSTRUCTOR_NEW.load()).newInstance(new Object[]{CacheRegistry.GET_SERVER_METHOD.load().invoke(Bukkit.getServer()), nmsWorld, gameProfile}); + } + + public void updateGlowPacket(NPC npc, Object packet) throws ReflectiveOperationException { + Utils.setValue(packet, "n", CacheRegistry.ENUM_CHAT_FORMAT_FIND.load().invoke(null, npc.getNpcPojo().getGlowName())); + } + + public Object getClickType(Object interactPacket) { + return "INTERACT"; + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV18.java b/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV18.java new file mode 100644 index 0000000..b88a3a6 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV18.java @@ -0,0 +1,15 @@ +package io.github.znetworkw.znpcservers.npc.packet; + +import io.github.znetworkw.znpcservers.cache.CacheRegistry; +import io.github.znetworkw.znpcservers.npc.NPC; +import io.github.znetworkw.znpcservers.utility.Utils; + +public class PacketV18 extends PacketV17 { + public int version() { + return 18; + } + + public void updateGlowPacket(NPC npc, Object packet) throws ReflectiveOperationException { + Utils.setValue(packet, "m", CacheRegistry.ENUM_CHAT_FORMAT_FIND.load().invoke(null, npc.getNpcPojo().getGlowName())); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV19.java b/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV19.java new file mode 100644 index 0000000..6595810 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV19.java @@ -0,0 +1,23 @@ +package io.github.znetworkw.znpcservers.npc.packet; + +import com.mojang.authlib.GameProfile; +import io.github.znetworkw.znpcservers.cache.CacheRegistry; +import org.bukkit.Bukkit; + +import java.lang.reflect.Constructor; + +public class PacketV19 extends PacketV18 { + public int version() { + return 19; + } + + public Object getPlayerPacket(Object nmsWorld, GameProfile gameProfile) throws ReflectiveOperationException { + try { + return ((Constructor) CacheRegistry.PLAYER_CONSTRUCTOR_NEW_1.load()).newInstance(new Object[]{CacheRegistry.GET_SERVER_METHOD + .load().invoke(Bukkit.getServer()), nmsWorld, gameProfile, null}); + } catch (Throwable e) { + return ((Constructor) CacheRegistry.PLAYER_CONSTRUCTOR_NEW_2.load()).newInstance(new Object[]{CacheRegistry.GET_SERVER_METHOD + .load().invoke(Bukkit.getServer()), nmsWorld, gameProfile}); + } + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV8.java b/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV8.java new file mode 100644 index 0000000..aa5a076 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV8.java @@ -0,0 +1,75 @@ +package io.github.znetworkw.znpcservers.npc.packet; + +import com.google.common.collect.ImmutableList; +import com.mojang.authlib.GameProfile; +import io.github.znetworkw.znpcservers.cache.CacheRegistry; +import io.github.znetworkw.znpcservers.npc.ItemSlot; +import io.github.znetworkw.znpcservers.npc.NPC; +import io.github.znetworkw.znpcservers.utility.Utils; +import org.bukkit.Bukkit; +import org.bukkit.inventory.ItemStack; + +import java.lang.reflect.Constructor; +import java.util.Map; + +public class PacketV8 implements Packet { + public int version() { + return 8; + } + + public Object getPlayerPacket(Object nmsWorld, GameProfile gameProfile) throws ReflectiveOperationException { + Constructor constructor = (Utils.BUKKIT_VERSION > 13) ? CacheRegistry.PLAYER_INTERACT_MANAGER_NEW_CONSTRUCTOR.load() : CacheRegistry.PLAYER_INTERACT_MANAGER_OLD_CONSTRUCTOR.load(); + return ((Constructor) CacheRegistry.PLAYER_CONSTRUCTOR_OLD.load()).newInstance(new Object[]{CacheRegistry.GET_SERVER_METHOD + .load().invoke(Bukkit.getServer()), nmsWorld, gameProfile, constructor + + .newInstance(nmsWorld)}); + } + + public Object getSpawnPacket(Object nmsEntity, boolean isPlayer) throws ReflectiveOperationException { + return isPlayer ? ((Constructor) CacheRegistry.PACKET_PLAY_OUT_NAMED_ENTITY_CONSTRUCTOR.load()).newInstance(nmsEntity) : ((Constructor) CacheRegistry.PACKET_PLAY_OUT_SPAWN_ENTITY_CONSTRUCTOR.load()).newInstance(nmsEntity); + } + + public Object convertItemStack(int entityId, ItemSlot itemSlot, ItemStack itemStack) throws ReflectiveOperationException { + return ((Constructor) CacheRegistry.PACKET_PLAY_OUT_ENTITY_EQUIPMENT_CONSTRUCTOR_OLD.load()).newInstance(Integer.valueOf(entityId), + Integer.valueOf(itemSlot.getSlotOld()), CacheRegistry.AS_NMS_COPY_METHOD + .load().invoke(CacheRegistry.CRAFT_ITEM_STACK_CLASS, itemStack)); + } + + public Object getClickType(Object interactPacket) throws ReflectiveOperationException { + return Utils.getValue(interactPacket, "action"); + } + + public Object getMetadataPacket(int entityId, Object nmsEntity) throws ReflectiveOperationException { + Object dataWatcher = CacheRegistry.GET_DATA_WATCHER_METHOD.load().invoke(nmsEntity); + try { + return ((Constructor) CacheRegistry.PACKET_PLAY_OUT_ENTITY_META_DATA_CONSTRUCTOR.load()).newInstance(Integer.valueOf(entityId), dataWatcher, Boolean.valueOf(true)); + } catch (Exception e2) { + return ((Constructor) CacheRegistry.PACKET_PLAY_OUT_ENTITY_META_DATA_CONSTRUCTOR_V1 + .load()) + .newInstance(Integer.valueOf(entityId), CacheRegistry.GET_DATAWATCHER_B_LIST + .load().invoke(dataWatcher)); + } + } + + public Object getHologramSpawnPacket(Object armorStand) throws ReflectiveOperationException { + return ((Constructor) CacheRegistry.PACKET_PLAY_OUT_SPAWN_ENTITY_CONSTRUCTOR.load()).newInstance(armorStand); + } + + public ImmutableList getEquipPackets(NPC npc) throws ReflectiveOperationException { + ImmutableList.Builder builder = ImmutableList.builder(); + for (Map.Entry stackEntry : npc.getNpcPojo().getNpcEquip().entrySet()) { + builder.add(((Constructor) CacheRegistry.PACKET_PLAY_OUT_ENTITY_EQUIPMENT_CONSTRUCTOR_OLD.load()).newInstance(Integer.valueOf(npc.getEntityID()), + Integer.valueOf(stackEntry.getKey().getSlotOld()), + convertItemStack(npc.getEntityID(), stackEntry.getKey(), stackEntry.getValue()))); + } + return builder.build(); + } + + public void updateGlowPacket(NPC npc, Object packet) throws ReflectiveOperationException { + throw new IllegalStateException("Glow color is not supported for 1.8 version."); + } + + public boolean allowGlowColor() { + return false; + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV9.java b/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV9.java new file mode 100644 index 0000000..65ec2ff --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketV9.java @@ -0,0 +1,46 @@ +package io.github.znetworkw.znpcservers.npc.packet; + +import com.google.common.collect.ImmutableList; +import io.github.znetworkw.znpcservers.cache.CacheRegistry; +import io.github.znetworkw.znpcservers.npc.ItemSlot; +import io.github.znetworkw.znpcservers.npc.NPC; +import io.github.znetworkw.znpcservers.utility.Utils; +import org.bukkit.inventory.ItemStack; + +import java.lang.reflect.Constructor; +import java.util.Map; + +public class PacketV9 extends PacketV8 { + public int version() { + return 9; + } + + public Object convertItemStack(int entityId, ItemSlot itemSlot, ItemStack itemStack) throws ReflectiveOperationException { + return CacheRegistry.AS_NMS_COPY_METHOD.load().invoke(CacheRegistry.CRAFT_ITEM_STACK_CLASS, itemStack); + } + + public ImmutableList getEquipPackets(NPC npc) throws ReflectiveOperationException { + ImmutableList.Builder builder = ImmutableList.builder(); + for (Map.Entry stackEntry : npc.getNpcPojo().getNpcEquip().entrySet()) { + builder.add(((Constructor) CacheRegistry.PACKET_PLAY_OUT_ENTITY_EQUIPMENT_CONSTRUCTOR_NEWEST_OLD.load()).newInstance(Integer.valueOf(npc.getEntityID()), + getItemSlot(stackEntry.getKey().getSlot()), + convertItemStack(npc.getEntityID(), stackEntry.getKey(), stackEntry.getValue()))); + } + return builder.build(); + } + + public void updateGlowPacket(NPC npc, Object packet) throws ReflectiveOperationException { + Object enumChatString = CacheRegistry.ENUM_CHAT_TO_STRING_METHOD.load().invoke(npc.getGlowColor()); + if (Utils.BUKKIT_VERSION > 12) { + Utils.setValue(packet, npc.getGlowColor(), CacheRegistry.ENUM_CHAT_CLASS); + Utils.setValue(packet, "c", ((Constructor) CacheRegistry.I_CHAT_BASE_COMPONENT_A_CONSTRUCTOR.load()).newInstance(enumChatString)); + } else { + Utils.setValue(packet, "g", CacheRegistry.GET_ENUM_CHAT_ID_METHOD.load().invoke(npc.getGlowColor())); + Utils.setValue(packet, "c", enumChatString); + } + } + + public boolean allowGlowColor() { + return true; + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketValue.java b/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketValue.java new file mode 100644 index 0000000..9121afc --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/packet/PacketValue.java @@ -0,0 +1,14 @@ +package io.github.znetworkw.znpcservers.npc.packet; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface PacketValue { + String keyName(); + + ValueType valueType() default ValueType.DEFAULT; +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/packet/ValueType.java b/src/main/java/io/github/znetworkw/znpcservers/npc/packet/ValueType.java new file mode 100644 index 0000000..0f7c5f9 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/packet/ValueType.java @@ -0,0 +1,20 @@ +package io.github.znetworkw.znpcservers.npc.packet; + +import java.util.Arrays; + +public enum ValueType { + ARGUMENTS { + String resolve(String keyName, Object[] args) { + if (args.length == 0) + throw new IllegalArgumentException("invalid size, must be > 0"); + return keyName + Arrays.hashCode(args); + } + }, + DEFAULT { + String resolve(String keyName, Object[] args) { + return keyName; + } + }; + + abstract String resolve(String paramString, Object[] paramArrayOfObject); +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/task/NPCLoadTask.java b/src/main/java/io/github/znetworkw/znpcservers/npc/task/NPCLoadTask.java new file mode 100644 index 0000000..c04040b --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/task/NPCLoadTask.java @@ -0,0 +1,34 @@ +package io.github.znetworkw.znpcservers.npc.task; + +import lol.pyr.znpcsplus.ZNPCsPlus; +import io.github.znetworkw.znpcservers.npc.NPC; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.scheduler.BukkitRunnable; + +public class NPCLoadTask extends BukkitRunnable { + private static final int DELAY = 40; + + private static final int MAX_TRIES = 10; + + private final NPC npc; + + private int tries = 0; + + public NPCLoadTask(NPC npc) { + this.npc = npc; + ZNPCsPlus.SCHEDULER.runTaskTimer(this, 40); + } + + public void run() { + if (this.tries++ > 10) { + cancel(); + return; + } + World world = Bukkit.getWorld(this.npc.getNpcPojo().getLocation().getWorldName()); + if (world == null) + return; + cancel(); + this.npc.onLoad(); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/task/NPCManagerTask.java b/src/main/java/io/github/znetworkw/znpcservers/npc/task/NPCManagerTask.java new file mode 100644 index 0000000..4466628 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/task/NPCManagerTask.java @@ -0,0 +1,43 @@ +package io.github.znetworkw.znpcservers.npc.task; + +import io.github.znetworkw.znpcservers.configuration.ConfigurationConstants; +import io.github.znetworkw.znpcservers.npc.FunctionFactory; +import io.github.znetworkw.znpcservers.npc.NPC; +import io.github.znetworkw.znpcservers.npc.conversation.ConversationModel; +import io.github.znetworkw.znpcservers.user.ZUser; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitRunnable; + +public class NPCManagerTask extends BukkitRunnable { + public NPCManagerTask(Plugin serversNPC) { + runTaskTimerAsynchronously(serversNPC, 60L, 1L); + } + + public void run() { + for (NPC npc : NPC.all()) { + boolean hasPath = (npc.getNpcPath() != null); + if (hasPath) + npc.getNpcPath().handle(); + for (Player player : Bukkit.getOnlinePlayers()) { + ZUser zUser = ZUser.find(player); + boolean canSeeNPC = (player.getWorld() == npc.getLocation().getWorld() && player.getLocation().distance(npc.getLocation()) <= ConfigurationConstants.VIEW_DISTANCE); + if (npc.getViewers().contains(zUser) && !canSeeNPC) { + npc.delete(zUser); + continue; + } + if (canSeeNPC) { + if (!npc.getViewers().contains(zUser)) + npc.spawn(zUser); + if (FunctionFactory.isTrue(npc, "look") && !hasPath) + npc.lookAt(zUser, player.getLocation(), false); + npc.getHologram().updateNames(zUser); + ConversationModel conversationStorage = npc.getNpcPojo().getConversation(); + if (conversationStorage != null && conversationStorage.getConversationType() == ConversationModel.ConversationType.RADIUS) + npc.tryStartConversation(player); + } + } + } + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/npc/task/NPCSaveTask.java b/src/main/java/io/github/znetworkw/znpcservers/npc/task/NPCSaveTask.java new file mode 100644 index 0000000..859c5c8 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/npc/task/NPCSaveTask.java @@ -0,0 +1,15 @@ +package io.github.znetworkw.znpcservers.npc.task; + +import io.github.znetworkw.znpcservers.configuration.Configuration; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitRunnable; + +public class NPCSaveTask extends BukkitRunnable { + public NPCSaveTask(Plugin serversNPC, int seconds) { + runTaskTimer(serversNPC, 200L, seconds); + } + + public void run() { + Configuration.SAVE_CONFIGURATIONS.forEach(Configuration::save); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/skin/SkinFetcher.java b/src/main/java/io/github/znetworkw/znpcservers/skin/SkinFetcher.java new file mode 100644 index 0000000..feecd7a --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/skin/SkinFetcher.java @@ -0,0 +1,89 @@ +package io.github.znetworkw.znpcservers.skin; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import java.io.DataOutputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class SkinFetcher { + private static final String EMPTY_STRING = ""; + + private static final String DEFAULT_CHARSET = "UTF-8"; + + private static final ExecutorService SKIN_EXECUTOR_SERVICE = Executors.newCachedThreadPool(); + + private static final JsonParser JSON_PARSER = new JsonParser(); + + private final SkinFetcherBuilder builder; + + public SkinFetcher(SkinFetcherBuilder builder) { + this.builder = builder; + } + + public CompletableFuture doReadSkin(SkinFetcherResult skinFetcherResult) { + CompletableFuture completableFuture = new CompletableFuture<>(); + SKIN_EXECUTOR_SERVICE.submit(() -> { + try { + HttpURLConnection connection = (HttpURLConnection) (new URL(this.builder.getAPIServer().getURL() + getData())).openConnection(); + connection.setRequestMethod(this.builder.getAPIServer().getMethod()); + if (this.builder.isUrlType()) { + connection.setDoOutput(true); + DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); + try { + outputStream.writeBytes("url=" + URLEncoder.encode(this.builder.getData(), StandardCharsets.UTF_8)); + outputStream.close(); + } catch (Throwable throwable) { + try { + outputStream.close(); + } catch (Throwable throwable1) { + throwable.addSuppressed(throwable1); + } + throw throwable; + } + } + try { + Reader reader = new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8); + try { + completableFuture.complete(JSON_PARSER.parse(reader).getAsJsonObject()); + reader.close(); + } catch (Throwable throwable) { + try { + reader.close(); + } catch (Throwable throwable1) { + throwable.addSuppressed(throwable1); + } + throw throwable; + } + } finally { + connection.disconnect(); + } + } catch (Throwable throwable) { + throwable.printStackTrace(); + completableFuture.completeExceptionally(throwable); + } + }); + completableFuture.whenComplete((response, throwable) -> { + if (completableFuture.isCompletedExceptionally()) { + skinFetcherResult.onDone(null, throwable); + } else { + JsonObject jsonObject = response.getAsJsonObject(this.builder.getAPIServer().getValueKey()); + JsonObject properties = jsonObject.getAsJsonObject(this.builder.getAPIServer().getSignatureKey()); + skinFetcherResult.onDone(new String[]{properties.get("value").getAsString(), properties.get("signature").getAsString()}, null); + } + }); + return completableFuture; + } + + private String getData() { + return this.builder.isProfileType() ? ("/" + this.builder.getData()) : ""; + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/skin/SkinFetcherBuilder.java b/src/main/java/io/github/znetworkw/znpcservers/skin/SkinFetcherBuilder.java new file mode 100644 index 0000000..60efaa8 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/skin/SkinFetcherBuilder.java @@ -0,0 +1,76 @@ +package io.github.znetworkw.znpcservers.skin; + +public class SkinFetcherBuilder { + private final SkinServer apiServer; + + private final String name; + + protected SkinFetcherBuilder(SkinServer apiServer, String name) { + this.apiServer = apiServer; + this.name = name; + } + + public static SkinFetcherBuilder create(SkinServer skinAPIURL, String name) { + return new SkinFetcherBuilder(skinAPIURL, name); + } + + public static SkinFetcherBuilder withName(String name) { + return create(name.startsWith("http") ? SkinServer.GENERATE_API : SkinServer.PROFILE_API, name); + } + + public SkinServer getAPIServer() { + return this.apiServer; + } + + public String getData() { + return this.name; + } + + public boolean isUrlType() { + return (this.apiServer == SkinServer.GENERATE_API); + } + + public boolean isProfileType() { + return (this.apiServer == SkinServer.PROFILE_API); + } + + public SkinFetcher toSkinFetcher() { + return new SkinFetcher(this); + } + + public enum SkinServer { + PROFILE_API("GET", "https://api.ashcon.app/mojang/v2/user", "textures", "raw"), + GENERATE_API("POST", "https://api.mineskin.org/generate/url", "data", "texture"); + + private final String method; + + private final String url; + + private final String valueKey; + + private final String signatureKey; + + SkinServer(String method, String url, String valueKey, String signatureKey) { + this.method = method; + this.url = url; + this.valueKey = valueKey; + this.signatureKey = signatureKey; + } + + public String getMethod() { + return this.method; + } + + public String getURL() { + return this.url; + } + + public String getValueKey() { + return this.valueKey; + } + + public String getSignatureKey() { + return this.signatureKey; + } + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/skin/SkinFetcherResult.java b/src/main/java/io/github/znetworkw/znpcservers/skin/SkinFetcherResult.java new file mode 100644 index 0000000..59db73e --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/skin/SkinFetcherResult.java @@ -0,0 +1,5 @@ +package io.github.znetworkw.znpcservers.skin; + +public interface SkinFetcherResult { + void onDone(String[] paramArrayOfString, Throwable paramThrowable); +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/user/EventService.java b/src/main/java/io/github/znetworkw/znpcservers/user/EventService.java new file mode 100644 index 0000000..382eb56 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/user/EventService.java @@ -0,0 +1,60 @@ +package io.github.znetworkw.znpcservers.user; + +import lol.pyr.znpcsplus.ZNPCsPlus; +import org.bukkit.event.Event; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; + +public class EventService { + private final Class eventClass; + + private final List> eventConsumers; + + protected EventService(Class eventClass, List> eventConsumers) { + this.eventClass = eventClass; + this.eventConsumers = eventConsumers; + } + + public static EventService addService(ZUser user, Class eventClass) { + if (hasService(user, eventClass)) + throw new IllegalStateException(eventClass.getSimpleName() + " is already register for " + user.getUUID().toString()); + EventService service = new EventService<>(eventClass, new ArrayList<>()); + user.getEventServices().add(service); + user.toPlayer().closeInventory(); + return service; + } + + public static EventService findService(ZUser user, Class eventClass) { + Objects.requireNonNull(EventService.class); + Objects.requireNonNull(EventService.class); + return user.getEventServices().stream().filter(eventService -> eventService.getEventClass().isAssignableFrom(eventClass)).map(EventService.class::cast) + .findFirst() + .orElse(null); + } + + public static boolean hasService(ZUser user, Class eventClass) { + return user.getEventServices() + .stream() + .anyMatch(eventService -> (eventService.getEventClass() == eventClass)); + } + + public Class getEventClass() { + return this.eventClass; + } + + public List> getEventConsumers() { + return this.eventConsumers; + } + + public EventService addConsumer(Consumer consumer) { + getEventConsumers().add(consumer); + return this; + } + + public void runAll(T event) { + ZNPCsPlus.SCHEDULER.runTask(() -> this.eventConsumers.forEach(())); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/user/ZUser.java b/src/main/java/io/github/znetworkw/znpcservers/user/ZUser.java new file mode 100644 index 0000000..ce155f4 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/user/ZUser.java @@ -0,0 +1,139 @@ +package io.github.znetworkw.znpcservers.user; + +import com.mojang.authlib.GameProfile; +import lol.pyr.znpcsplus.ZNPCsPlus; +import io.github.znetworkw.znpcservers.cache.CacheRegistry; +import io.github.znetworkw.znpcservers.npc.NPC; +import io.github.znetworkw.znpcservers.npc.NPCAction; +import io.github.znetworkw.znpcservers.npc.event.ClickType; +import io.github.znetworkw.znpcservers.npc.event.NPCInteractEvent; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageDecoder; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.*; + +public class ZUser { + private static final String CHANNEL_NAME = "npc_interact"; + + private static final int DEFAULT_DELAY = 1; + + private static final Map USER_MAP = new HashMap<>(); + + private final Map lastClicked; + + private final List> eventServices; + + private final UUID uuid; + + private final GameProfile gameProfile; + + private final Object playerConnection; + + private boolean hasPath = false; + + private long lastInteract = 0L; + + public ZUser(UUID uuid) { + this.uuid = uuid; + this.lastClicked = new HashMap<>(); + this.eventServices = new ArrayList<>(); + try { + Object playerHandle = CacheRegistry.GET_HANDLE_PLAYER_METHOD.load().invoke(toPlayer()); + this.gameProfile = (GameProfile) CacheRegistry.GET_PROFILE_METHOD.load().invoke(playerHandle, new Object[0]); + Channel channel = (Channel) CacheRegistry.CHANNEL_FIELD.load().get(CacheRegistry.NETWORK_MANAGER_FIELD.load() + .get(this.playerConnection = CacheRegistry.PLAYER_CONNECTION_FIELD.load().get(playerHandle))); + if (channel.pipeline().names().contains("npc_interact")) + channel.pipeline().remove("npc_interact"); + channel.pipeline().addAfter("decoder", "npc_interact", (ChannelHandler) new ZNPCSocketDecoder()); + } catch (IllegalAccessException | java.lang.reflect.InvocationTargetException e) { + throw new IllegalStateException("can't create player " + uuid.toString(), e.getCause()); + } + } + + public static ZUser find(UUID uuid) { + return USER_MAP.computeIfAbsent(uuid, ZUser::new); + } + + public static ZUser find(Player player) { + return find(player.getUniqueId()); + } + + public static void unregister(Player player) { + ZUser zUser = USER_MAP.get(player.getUniqueId()); + if (zUser == null) + throw new IllegalStateException("can't find user " + player.getUniqueId()); + USER_MAP.remove(player.getUniqueId()); + NPC.all().stream() + .filter(npc -> npc.getViewers().contains(zUser)) + .forEach(npc -> npc.delete(zUser)); + } + + public UUID getUUID() { + return this.uuid; + } + + public GameProfile getGameProfile() { + return this.gameProfile; + } + + public Object getPlayerConnection() { + return this.playerConnection; + } + + public boolean isHasPath() { + return this.hasPath; + } + + public void setHasPath(boolean hasPath) { + this.hasPath = hasPath; + } + + public List> getEventServices() { + return this.eventServices; + } + + public Player toPlayer() { + return Bukkit.getPlayer(this.uuid); + } + + class ZNPCSocketDecoder extends MessageToMessageDecoder { + protected void decode(ChannelHandlerContext channelHandlerContext, Object packet, List out) throws Exception { + out.add(packet); + if (packet.getClass() == CacheRegistry.PACKET_PLAY_IN_USE_ENTITY_CLASS) { + long lastInteractNanos = System.nanoTime() - ZUser.this.lastInteract; + if (ZUser.this.lastInteract != 0L && lastInteractNanos < 1000000000L) + return; + int entityId = CacheRegistry.PACKET_IN_USE_ENTITY_ID_FIELD.load().getInt(packet); + NPC npc = NPC.all().stream().filter(npc1 -> (npc1.getEntityID() == entityId)).findFirst().orElse(null); + if (npc == null) + return; + ClickType clickName = ClickType.forName(npc.getPackets().getProxyInstance().getClickType(packet).toString()); + ZUser.this.lastInteract = System.nanoTime(); + ZNPCsPlus.SCHEDULER.scheduleSyncDelayedTask(() -> { + Bukkit.getServer().getPluginManager().callEvent(new NPCInteractEvent(ZUser.this.toPlayer(), clickName, npc)); + List actions = npc.getNpcPojo().getClickActions(); + if (actions == null || actions.isEmpty()) + return; + for (NPCAction npcAction : actions) { + if (npcAction.getClickType() != ClickType.DEFAULT && clickName != npcAction.getClickType()) + continue; + if (npcAction.getDelay() > 0) { + int actionId = npc.getNpcPojo().getClickActions().indexOf(npcAction); + if (ZUser.this.lastClicked.containsKey(Integer.valueOf(actionId))) { + long lastClickNanos = System.nanoTime() - ZUser.this.lastClicked.get(Integer.valueOf(actionId)).longValue(); + if (lastClickNanos < npcAction.getFixedDelay()) + continue; + } + ZUser.this.lastClicked.put(Integer.valueOf(actionId), Long.valueOf(System.nanoTime())); + } + npcAction.run(ZUser.this, npcAction.getAction()); + } + }1); + } + } + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/utility/BungeeUtils.java b/src/main/java/io/github/znetworkw/znpcservers/utility/BungeeUtils.java new file mode 100644 index 0000000..b62bd83 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/utility/BungeeUtils.java @@ -0,0 +1,28 @@ +package io.github.znetworkw.znpcservers.utility; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class BungeeUtils { + private final Plugin plugin; + + public BungeeUtils(Plugin plugin) { + this.plugin = plugin; + } + + public void sendPlayerToServer(Player player, String server) { + ByteArrayOutputStream b = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(b); + try { + out.writeUTF("Connect"); + out.writeUTF(server); + } catch (IOException e) { + e.printStackTrace(); + } + player.sendPluginMessage(this.plugin, "BungeeCord", b.toByteArray()); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/utility/GuavaCollectors.java b/src/main/java/io/github/znetworkw/znpcservers/utility/GuavaCollectors.java new file mode 100644 index 0000000..4058f07 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/utility/GuavaCollectors.java @@ -0,0 +1,23 @@ +package io.github.znetworkw.znpcservers.utility; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; + +public final class GuavaCollectors { + public static Collector> toImmutableList() { + return Collectors.collectingAndThen(Collectors.toList(), ImmutableList::copyOf); + } + + public static Collector> toImmutableSet() { + return Collectors.collectingAndThen(Collectors.toSet(), ImmutableSet::copyOf); + } + + public static Collector> toImmutableMap(Function keyFunction, Function valueFunction) { + return Collectors.collectingAndThen(Collectors.toMap(keyFunction, valueFunction), ImmutableMap::copyOf); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/utility/MetricsLite.java b/src/main/java/io/github/znetworkw/znpcservers/utility/MetricsLite.java new file mode 100644 index 0000000..a9c7950 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/utility/MetricsLite.java @@ -0,0 +1,263 @@ +package io.github.znetworkw.znpcservers.utility; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.bukkit.plugin.ServicePriority; + +import javax.net.ssl.HttpsURLConnection; +import java.io.*; +import java.lang.reflect.Method; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; +import java.util.logging.Level; +import java.util.zip.GZIPOutputStream; + +public class MetricsLite { + public static final int B_STATS_VERSION = 1; + + private static final String URL = "https://bStats.org/submitData/bukkit"; + private static boolean logFailedRequests; + private static boolean logSentData; + private static boolean logResponseStatusText; + private static String serverUUID; + + static { + if (System.getProperty("bstats.relocatecheck") == null || !System.getProperty("bstats.relocatecheck").equals("false")) { + String defaultPackage = new String(new byte[]{ + 111, 114, 103, 46, 98, 115, 116, 97, 116, 115, + 46, 98, 117, 107, 107, 105, 116}); + String examplePackage = new String(new byte[]{ + 121, 111, 117, 114, 46, 112, 97, 99, 107, 97, + 103, 101}); + if (MetricsLite.class.getPackage().getName().equals(defaultPackage) || MetricsLite.class.getPackage().getName().equals(examplePackage)) + throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); + } + } + + private final boolean enabled; + private final Plugin plugin; + private final int pluginId; + + public MetricsLite(Plugin plugin, int pluginId) { + if (plugin == null) + throw new IllegalArgumentException("Plugin cannot be null!"); + this.plugin = plugin; + this.pluginId = pluginId; + File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); + File configFile = new File(bStatsFolder, "config.yml"); + YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); + if (!config.isSet("serverUuid")) { + config.addDefault("enabled", Boolean.TRUE); + config.addDefault("serverUuid", UUID.randomUUID().toString()); + config.addDefault("logFailedRequests", Boolean.FALSE); + config.addDefault("logSentData", Boolean.FALSE); + config.addDefault("logResponseStatusText", Boolean.FALSE); + config.options().header("bStats collects some data for plugin authors like how many servers are using their plugins.\nTo honor their work, you should not disable it.\nThis has nearly no effect on the server performance!\nCheck out https://bStats.org/ to learn more :)") + + .copyDefaults(true); + try { + config.save(configFile); + } catch (IOException iOException) { + } + } + serverUUID = config.getString("serverUuid"); + logFailedRequests = config.getBoolean("logFailedRequests", false); + this.enabled = config.getBoolean("enabled", true); + logSentData = config.getBoolean("logSentData", false); + logResponseStatusText = config.getBoolean("logResponseStatusText", false); + if (this.enabled) { + boolean found = false; + for (Class service : Bukkit.getServicesManager().getKnownServices()) { + try { + service.getField("B_STATS_VERSION"); + found = true; + break; + } catch (NoSuchFieldException noSuchFieldException) { + } + } + Bukkit.getServicesManager().register(MetricsLite.class, this, plugin, ServicePriority.Normal); + if (!found) + startSubmitting(); + } + } + + private static void sendData(Plugin plugin, JsonObject data) throws Exception { + if (data == null) + throw new IllegalArgumentException("Data cannot be null!"); + if (Bukkit.isPrimaryThread()) + throw new IllegalAccessException("This method must not be called from the main thread!"); + if (logSentData) + plugin.getLogger().info("Sending data to bStats: " + data); + HttpsURLConnection connection = (HttpsURLConnection) (new URL("https://bStats.org/submitData/bukkit")).openConnection(); + byte[] compressedData = compress(data.toString()); + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("User-Agent", "MC-Server/1"); + connection.setDoOutput(true); + DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); + try { + outputStream.write(compressedData); + outputStream.close(); + } catch (Throwable throwable) { + try { + outputStream.close(); + } catch (Throwable throwable1) { + throwable.addSuppressed(throwable1); + } + throw throwable; + } + StringBuilder builder = new StringBuilder(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + try { + String line; + while ((line = bufferedReader.readLine()) != null) + builder.append(line); + bufferedReader.close(); + } catch (Throwable throwable) { + try { + bufferedReader.close(); + } catch (Throwable throwable1) { + throwable.addSuppressed(throwable1); + } + throw throwable; + } + if (logResponseStatusText) + plugin.getLogger().info("Sent data to bStats and received response: " + builder); + } + + private static byte[] compress(String str) throws IOException { + if (str == null) + return null; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + GZIPOutputStream gzip = new GZIPOutputStream(outputStream); + try { + gzip.write(str.getBytes(StandardCharsets.UTF_8)); + gzip.close(); + } catch (Throwable throwable) { + try { + gzip.close(); + } catch (Throwable throwable1) { + throwable.addSuppressed(throwable1); + } + throw throwable; + } + return outputStream.toByteArray(); + } + + public boolean isEnabled() { + return this.enabled; + } + + private void startSubmitting() { + final Timer timer = new Timer(true); + timer.scheduleAtFixedRate(new TimerTask() { + public void run() { + if (!MetricsLite.this.plugin.isEnabled()) { + timer.cancel(); + return; + } + Bukkit.getScheduler().runTask(MetricsLite.this.plugin, () -> MetricsLite.this.submitData()); + } + } 300000L, 1800000L) + } + + public JsonObject getPluginData() { + JsonObject data = new JsonObject(); + String pluginName = this.plugin.getDescription().getName(); + String pluginVersion = this.plugin.getDescription().getVersion(); + data.addProperty("pluginName", pluginName); + data.addProperty("id", Integer.valueOf(this.pluginId)); + data.addProperty("pluginVersion", pluginVersion); + data.add("customCharts", new JsonArray()); + return data; + } + + private JsonObject getServerData() { + int playerAmount; + try { + Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); + playerAmount = onlinePlayersMethod.getReturnType().equals(Collection.class) ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer(), new Object[0])).size() : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer(), new Object[0])).length; + } catch (Exception e) { + playerAmount = Bukkit.getOnlinePlayers().size(); + } + int onlineMode = Bukkit.getOnlineMode() ? 1 : 0; + String bukkitVersion = Bukkit.getVersion(); + String bukkitName = Bukkit.getName(); + String javaVersion = System.getProperty("java.version"); + String osName = System.getProperty("os.name"); + String osArch = System.getProperty("os.arch"); + String osVersion = System.getProperty("os.version"); + int coreCount = Runtime.getRuntime().availableProcessors(); + JsonObject data = new JsonObject(); + data.addProperty("serverUUID", serverUUID); + data.addProperty("playerAmount", Integer.valueOf(playerAmount)); + data.addProperty("onlineMode", Integer.valueOf(onlineMode)); + data.addProperty("bukkitVersion", bukkitVersion); + data.addProperty("bukkitName", bukkitName); + data.addProperty("javaVersion", javaVersion); + data.addProperty("osName", osName); + data.addProperty("osArch", osArch); + data.addProperty("osVersion", osVersion); + data.addProperty("coreCount", Integer.valueOf(coreCount)); + return data; + } + + private void submitData() { + JsonObject data = getServerData(); + JsonArray pluginData = new JsonArray(); + for (Class service : Bukkit.getServicesManager().getKnownServices()) { + try { + service.getField("B_STATS_VERSION"); + for (RegisteredServiceProvider provider : Bukkit.getServicesManager().getRegistrations(service)) { + try { + Object plugin = provider.getService().getMethod("getPluginData").invoke(provider.getProvider()); + if (plugin instanceof JsonObject) { + pluginData.add((JsonElement) plugin); + continue; + } + try { + Class jsonObjectJsonSimple = Class.forName("org.json.simple.JSONObject"); + if (plugin.getClass().isAssignableFrom(jsonObjectJsonSimple)) { + Method jsonStringGetter = jsonObjectJsonSimple.getDeclaredMethod("toJSONString"); + jsonStringGetter.setAccessible(true); + String jsonString = (String) jsonStringGetter.invoke(plugin, new Object[0]); + JsonObject object = (new JsonParser()).parse(jsonString).getAsJsonObject(); + pluginData.add(object); + } + } catch (ClassNotFoundException e) { + if (logFailedRequests) + this.plugin.getLogger().log(Level.SEVERE, "Encountered unexpected exception ", e); + } + } catch (NullPointerException | NoSuchMethodException | IllegalAccessException | + java.lang.reflect.InvocationTargetException nullPointerException) { + } + } + } catch (NoSuchFieldException noSuchFieldException) { + } + } + data.add("plugins", pluginData); + (new Thread(() -> { + try { + sendData(this.plugin, data); + } catch (Exception e) { + if (logFailedRequests) + this.plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + this.plugin.getName(), e); + } + })).start(); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/utility/ReflectionUtils.java b/src/main/java/io/github/znetworkw/znpcservers/utility/ReflectionUtils.java new file mode 100644 index 0000000..fc0e433 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/utility/ReflectionUtils.java @@ -0,0 +1,23 @@ +package io.github.znetworkw.znpcservers.utility; + +import java.lang.reflect.Field; + +public final class ReflectionUtils { + public static Field findFieldForClass(Object instance, Class type) { + for (Field field : instance.getClass().getDeclaredFields()) { + if (field.getType() == type) { + field.setAccessible(true); + return field; + } + } + return null; + } + + public static Field findFieldForClassAndSet(Object instance, Class type, Object value) throws ReflectiveOperationException { + Field field = findFieldForClass(instance, type); + if (field == null) + return null; + field.set(instance, value); + return field; + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/utility/SchedulerUtils.java b/src/main/java/io/github/znetworkw/znpcservers/utility/SchedulerUtils.java new file mode 100644 index 0000000..b43c832 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/utility/SchedulerUtils.java @@ -0,0 +1,34 @@ +package io.github.znetworkw.znpcservers.utility; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; + +public class SchedulerUtils { + private final Plugin plugin; + + public SchedulerUtils(Plugin plugin) { + this.plugin = plugin; + } + + public BukkitTask runTaskTimer(BukkitRunnable bukkitRunnable, int delay) { + return runTaskTimer(bukkitRunnable, delay, delay); + } + + public BukkitTask runTaskTimer(BukkitRunnable bukkitRunnable, int delay, int continuousDelay) { + return bukkitRunnable.runTaskTimer(this.plugin, delay, continuousDelay); + } + + public BukkitTask runTaskTimerAsynchronously(Runnable runnable, int delay, int continuousDelay) { + return Bukkit.getScheduler().runTaskTimerAsynchronously(this.plugin, runnable, delay, continuousDelay); + } + + public void scheduleSyncDelayedTask(Runnable runnable, int delay) { + Bukkit.getScheduler().scheduleSyncDelayedTask(this.plugin, runnable, delay); + } + + public BukkitTask runTask(Runnable runnable) { + return Bukkit.getScheduler().runTask(this.plugin, runnable); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/utility/Utils.java b/src/main/java/io/github/znetworkw/znpcservers/utility/Utils.java new file mode 100644 index 0000000..fe7b0af --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/utility/Utils.java @@ -0,0 +1,86 @@ +package io.github.znetworkw.znpcservers.utility; + +import io.github.znetworkw.znpcservers.cache.CacheRegistry; +import io.github.znetworkw.znpcservers.configuration.ConfigurationConstants; +import io.github.znetworkw.znpcservers.user.ZUser; +import me.clip.placeholderapi.PlaceholderAPI; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.lang.reflect.Field; +import java.util.concurrent.ThreadLocalRandom; + +public final class Utils { + public static final int BUKKIT_VERSION; + + public static final long SECOND_INTERVAL_NANOS = 1000000000L; + + public static boolean PLACEHOLDER_SUPPORT = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI"); + + static { + BUKKIT_VERSION = NumberUtils.toInt(getFormattedBukkitPackage()); + } + + public static boolean versionNewer(int version) { + return (BUKKIT_VERSION >= version); + } + + public static String getBukkitPackage() { + return Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; + } + + public static String getFormattedBukkitPackage() { + String version = getBukkitPackage().replace("v", "").replace("R", ""); + return version.substring(2, version.length() - 2); + } + + public static String toColor(String string) { + return ChatColor.translateAlternateColorCodes('&', string); + } + + public static String getWithPlaceholders(String string, Player player) { + return PlaceholderAPI.setPlaceholders(player, string).replace(ConfigurationConstants.SPACE_SYMBOL, " "); + } + + public static String randomString(int length) { + StringBuilder stringBuilder = new StringBuilder(); + for (int index = 0; index < length; index++) + stringBuilder.append(ThreadLocalRandom.current().nextInt(0, 9)); + return stringBuilder.toString(); + } + + public static void sendTitle(Player player, String title, String subTitle) { + player.sendTitle(toColor(title), toColor(subTitle)); + } + + public static void setValue(Object fieldInstance, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException { + Field f = fieldInstance.getClass().getDeclaredField(fieldName); + f.setAccessible(true); + f.set(fieldInstance, value); + } + + public static void setValue(Object fieldInstance, Object value, Class expectedType) throws NoSuchFieldException, IllegalAccessException { + for (Field field : fieldInstance.getClass().getDeclaredFields()) { + if (field.getType() == expectedType) + setValue(fieldInstance, field.getName(), value); + } + } + + public static Object getValue(Object instance, String fieldName) throws NoSuchFieldException, IllegalAccessException { + Field f = instance.getClass().getDeclaredField(fieldName); + f.setAccessible(true); + return f.get(instance); + } + + public static void sendPackets(ZUser user, Object... packets) { + try { + for (Object packet : packets) { + if (packet != null) + CacheRegistry.SEND_PACKET_METHOD.load().invoke(user.getPlayerConnection(), packet); + } + } catch (IllegalAccessException | java.lang.reflect.InvocationTargetException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/utility/inventory/ZInventory.java b/src/main/java/io/github/znetworkw/znpcservers/utility/inventory/ZInventory.java new file mode 100644 index 0000000..eddcac1 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/utility/inventory/ZInventory.java @@ -0,0 +1,51 @@ +package io.github.znetworkw.znpcservers.utility.inventory; + +import io.github.znetworkw.znpcservers.utility.Utils; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; + +public class ZInventory { + private final Player player; + + private ZInventoryPage page; + + private Inventory inventory; + + public ZInventory(Player player) { + this.player = player; + } + + public Player getPlayer() { + return this.player; + } + + public ZInventoryPage getPage() { + return this.page; + } + + public Inventory getInventory() { + return this.inventory; + } + + public void setCurrentPage(ZInventoryPage page) { + this.page = page; + } + + public Inventory build(ZInventoryPage page) { + if (page == null) + throw new IllegalStateException("page is null"); + if (page.getRows() / 9 > 6) + throw new IllegalArgumentException(String.format("Unexpected rows size. Has %d, max %d", Integer.valueOf(page.getRows()), Integer.valueOf(6))); + setCurrentPage(page); + page.getInventoryItems().removeIf(zInventoryItem -> !zInventoryItem.isDefault()); + page.update(); + this.inventory = Bukkit.createInventory(new ZInventoryHolder(this), page.getRows(), Utils.toColor(page.getPageName())); + page.getInventoryItems().forEach(zInventoryItem -> this.inventory.setItem(zInventoryItem.getSlot(), zInventoryItem.getItemStack())); + return this.inventory; + } + + public Inventory build() { + return build(this.page); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/utility/inventory/ZInventoryCallback.java b/src/main/java/io/github/znetworkw/znpcservers/utility/inventory/ZInventoryCallback.java new file mode 100644 index 0000000..e7cec79 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/utility/inventory/ZInventoryCallback.java @@ -0,0 +1,7 @@ +package io.github.znetworkw.znpcservers.utility.inventory; + +import org.bukkit.event.inventory.InventoryClickEvent; + +public interface ZInventoryCallback { + void onClick(InventoryClickEvent paramInventoryClickEvent); +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/utility/inventory/ZInventoryHolder.java b/src/main/java/io/github/znetworkw/znpcservers/utility/inventory/ZInventoryHolder.java new file mode 100644 index 0000000..022075c --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/utility/inventory/ZInventoryHolder.java @@ -0,0 +1,20 @@ +package io.github.znetworkw.znpcservers.utility.inventory; + +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; + +public class ZInventoryHolder implements InventoryHolder { + private final ZInventory zInventory; + + public ZInventoryHolder(ZInventory zInventory) { + this.zInventory = zInventory; + } + + public ZInventory getzInventory() { + return this.zInventory; + } + + public Inventory getInventory() { + return this.zInventory.getInventory(); + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/utility/inventory/ZInventoryItem.java b/src/main/java/io/github/znetworkw/znpcservers/utility/inventory/ZInventoryItem.java new file mode 100644 index 0000000..819b75a --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/utility/inventory/ZInventoryItem.java @@ -0,0 +1,36 @@ +package io.github.znetworkw.znpcservers.utility.inventory; + +import org.bukkit.inventory.ItemStack; + +public class ZInventoryItem { + private final ItemStack itemStack; + + private final int slot; + + private final boolean isDefault; + + private final ZInventoryCallback clickCallback; + + public ZInventoryItem(ItemStack itemStack, int slot, boolean isDefault, ZInventoryCallback zInventoryCallback) { + this.itemStack = itemStack; + this.slot = slot; + this.isDefault = isDefault; + this.clickCallback = zInventoryCallback; + } + + public ItemStack getItemStack() { + return this.itemStack; + } + + public int getSlot() { + return this.slot; + } + + public boolean isDefault() { + return this.isDefault; + } + + public ZInventoryCallback getInventoryCallback() { + return this.clickCallback; + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/utility/inventory/ZInventoryPage.java b/src/main/java/io/github/znetworkw/znpcservers/utility/inventory/ZInventoryPage.java new file mode 100644 index 0000000..52a38bc --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/utility/inventory/ZInventoryPage.java @@ -0,0 +1,82 @@ +package io.github.znetworkw.znpcservers.utility.inventory; + +import io.github.znetworkw.znpcservers.utility.itemstack.ItemStackBuilder; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; + +public abstract class ZInventoryPage { + private final ZInventory zInventory; + + private final String pageName; + + private final int rows; + + private final List inventoryItems; + + public ZInventoryPage(ZInventory zInventory, String inventoryName, int rows) { + this.zInventory = zInventory; + this.pageName = inventoryName; + this.rows = rows * 9; + this.inventoryItems = new ArrayList<>(); + if (zInventory.getInventory() != null) { + ZInventoryPage zInventoryPage = zInventory.getPage(); + addItem(ItemStackBuilder.forMaterial(Material.ARROW) + .setName(ChatColor.GREEN + "Go back") + .setLore(new String[]{ChatColor.GRAY + "click here..."}, ).build(), this.rows - 9, true, event -> { + zInventory.setCurrentPage(zInventoryPage); + openInventory(); + }); + } + zInventory.setCurrentPage(this); + } + + public ZInventory getInventory() { + return this.zInventory; + } + + public String getPageName() { + return this.pageName; + } + + public int getRows() { + return this.rows; + } + + public List getInventoryItems() { + return this.inventoryItems; + } + + public boolean containsItem(int slot) { + return this.inventoryItems.stream().anyMatch(zInventoryItem -> (zInventoryItem.getSlot() == slot)); + } + + public ZInventoryItem findItem(int slot) { + return this.inventoryItems.stream() + .filter(zInventoryItem -> (zInventoryItem.getSlot() == slot)) + .findFirst() + .orElseThrow(() -> new IllegalStateException("can't find item for slot " + slot)); + } + + public void addItem(ItemStack itemStack, int slot, boolean isDefault, ZInventoryCallback callback) { + this.inventoryItems.add(new ZInventoryItem(itemStack, slot, isDefault, callback)); + } + + public void addItem(ItemStack itemStack, int slot, ZInventoryCallback callback) { + addItem(itemStack, slot, false, callback); + } + + public Player getPlayer() { + return this.zInventory.getPlayer(); + } + + public void openInventory() { + this.zInventory.getPlayer().openInventory(this.zInventory.build()); + } + + public abstract void update(); +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/utility/itemstack/ItemStackBuilder.java b/src/main/java/io/github/znetworkw/znpcservers/utility/itemstack/ItemStackBuilder.java new file mode 100644 index 0000000..476982a --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/utility/itemstack/ItemStackBuilder.java @@ -0,0 +1,53 @@ +package io.github.znetworkw.znpcservers.utility.itemstack; + +import io.github.znetworkw.znpcservers.utility.Utils; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.Arrays; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +public class ItemStackBuilder { + private final ItemStack itemStack; + + private final ItemMeta itemMeta; + + protected ItemStackBuilder(ItemStack stack) { + this.itemStack = stack; + this.itemMeta = stack.getItemMeta(); + } + + public static ItemStackBuilder forMaterial(Material material) { + if (material == null || material == Material.AIR) + throw new IllegalStateException("can't create builder for a NULL material."); + return new ItemStackBuilder(new ItemStack(material, 1)); + } + + public ItemStackBuilder setName(String name) { + this.itemMeta.setDisplayName(Utils.toColor(name)); + return this; + } + + public ItemStackBuilder setLore(Iterable lore) { + this.itemMeta.setLore(StreamSupport.stream(lore.spliterator(), false) + .map(Utils::toColor).collect(Collectors.toList())); + this.itemStack.setItemMeta(this.itemMeta); + return this; + } + + public ItemStackBuilder setLore(String... lore) { + return setLore(Arrays.asList(lore)); + } + + public ItemStackBuilder setAmount(int amount) { + this.itemStack.setAmount(amount); + return this; + } + + public ItemStack build() { + this.itemStack.setItemMeta(this.itemMeta); + return this.itemStack; + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/utility/itemstack/ItemStackSerializer.java b/src/main/java/io/github/znetworkw/znpcservers/utility/itemstack/ItemStackSerializer.java new file mode 100644 index 0000000..c39af72 --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/utility/itemstack/ItemStackSerializer.java @@ -0,0 +1,80 @@ +package io.github.znetworkw.znpcservers.utility.itemstack; + +import com.google.gson.*; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.io.BukkitObjectInputStream; +import org.bukkit.util.io.BukkitObjectOutputStream; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.Base64; + +public class ItemStackSerializer implements JsonSerializer, JsonDeserializer { + private static final ItemStack DEFAULT = new ItemStack(Material.AIR); + + public ItemStack deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + try { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(json.getAsString())); + try { + BukkitObjectInputStream bukkitObjectOutputStream = new BukkitObjectInputStream(byteArrayInputStream); + try { + ItemStack itemStack = (ItemStack) bukkitObjectOutputStream.readObject(); + bukkitObjectOutputStream.close(); + byteArrayInputStream.close(); + return itemStack; + } catch (Throwable throwable) { + try { + bukkitObjectOutputStream.close(); + } catch (Throwable throwable1) { + throwable.addSuppressed(throwable1); + } + throw throwable; + } + } catch (Throwable throwable) { + try { + byteArrayInputStream.close(); + } catch (Throwable throwable1) { + throwable.addSuppressed(throwable1); + } + throw throwable; + } + } catch (IOException | ClassNotFoundException e) { + return DEFAULT; + } + } + + public JsonElement serialize(ItemStack src, Type typeOfSrc, JsonSerializationContext context) { + try { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + try { + BukkitObjectOutputStream bukkitObjectOutputStream = new BukkitObjectOutputStream(byteArrayOutputStream); + try { + bukkitObjectOutputStream.writeObject(src); + JsonPrimitive jsonPrimitive = new JsonPrimitive(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray())); + bukkitObjectOutputStream.close(); + byteArrayOutputStream.close(); + return jsonPrimitive; + } catch (Throwable throwable) { + try { + bukkitObjectOutputStream.close(); + } catch (Throwable throwable1) { + throwable.addSuppressed(throwable1); + } + throw throwable; + } + } catch (Throwable throwable) { + try { + byteArrayOutputStream.close(); + } catch (Throwable throwable1) { + throwable.addSuppressed(throwable1); + } + throw throwable; + } + } catch (IOException e) { + throw new JsonParseException("Cannot serialize itemstack", e); + } + } +} diff --git a/src/main/java/io/github/znetworkw/znpcservers/utility/location/ZLocation.java b/src/main/java/io/github/znetworkw/znpcservers/utility/location/ZLocation.java new file mode 100644 index 0000000..5a8cdfb --- /dev/null +++ b/src/main/java/io/github/znetworkw/znpcservers/utility/location/ZLocation.java @@ -0,0 +1,102 @@ +package io.github.znetworkw.znpcservers.utility.location; + +import com.google.gson.*; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.util.Vector; + +import java.lang.reflect.Type; + +public class ZLocation { + public static final ZLocationSerializer SERIALIZER = new ZLocationSerializer(); + + private final String worldName; + + private final double x; + + private final double y; + + private final double z; + + private final float yaw; + + private final float pitch; + + private Location bukkitLocation; + + public ZLocation(String worldName, double x, double y, double z, float yaw, float pitch) { + this.worldName = worldName; + this.x = x; + this.y = y; + this.z = z; + this.yaw = yaw; + this.pitch = pitch; + } + + public ZLocation(Location location) { + this(location.getWorld().getName(), location + .getX(), location + .getY(), location + .getZ(), location + .getYaw(), location + .getPitch()); + } + + public String getWorldName() { + return this.worldName; + } + + public double getX() { + return this.x; + } + + public double getY() { + return this.y; + } + + public double getZ() { + return this.z; + } + + public float getYaw() { + return this.yaw; + } + + public float getPitch() { + return this.pitch; + } + + public Location bukkitLocation() { + if (this.bukkitLocation != null) + return this.bukkitLocation; + return this + .bukkitLocation = new Location(Bukkit.getWorld(this.worldName), this.x, this.y, this.z, this.yaw, this.pitch); + } + + public Vector toVector() { + return bukkitLocation().toVector(); + } + + static class ZLocationSerializer implements JsonSerializer, JsonDeserializer { + public JsonElement serialize(ZLocation src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("world", src.getWorldName()); + jsonObject.addProperty("x", Double.valueOf(src.getX())); + jsonObject.addProperty("y", Double.valueOf(src.getY())); + jsonObject.addProperty("z", Double.valueOf(src.getZ())); + jsonObject.addProperty("yaw", Float.valueOf(src.getYaw())); + jsonObject.addProperty("pitch", Float.valueOf(src.getPitch())); + return jsonObject; + } + + public ZLocation deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + return new ZLocation(jsonObject.get("world").getAsString(), jsonObject + .get("x").getAsDouble(), jsonObject + .get("y").getAsDouble(), jsonObject + .get("z").getAsDouble(), jsonObject + .get("yaw").getAsFloat(), jsonObject + .get("pitch").getAsFloat()); + } + } +} diff --git a/src/main/java/lol/pyr/znpcsplus/ZNPCsPlus.java b/src/main/java/lol/pyr/znpcsplus/ZNPCsPlus.java new file mode 100644 index 0000000..086f042 --- /dev/null +++ b/src/main/java/lol/pyr/znpcsplus/ZNPCsPlus.java @@ -0,0 +1,95 @@ +package lol.pyr.znpcsplus; + +import com.google.common.collect.ImmutableList; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import io.github.znetworkw.znpcservers.commands.list.DefaultCommand; +import io.github.znetworkw.znpcservers.configuration.Configuration; +import io.github.znetworkw.znpcservers.configuration.ConfigurationConstants; +import io.github.znetworkw.znpcservers.listeners.InventoryListener; +import io.github.znetworkw.znpcservers.listeners.PlayerListener; +import io.github.znetworkw.znpcservers.npc.NPC; +import io.github.znetworkw.znpcservers.npc.NPCModel; +import io.github.znetworkw.znpcservers.npc.NPCPath; +import io.github.znetworkw.znpcservers.npc.NPCType; +import io.github.znetworkw.znpcservers.npc.task.NPCManagerTask; +import io.github.znetworkw.znpcservers.npc.task.NPCSaveTask; +import io.github.znetworkw.znpcservers.user.ZUser; +import io.github.znetworkw.znpcservers.utility.BungeeUtils; +import io.github.znetworkw.znpcservers.utility.MetricsLite; +import io.github.znetworkw.znpcservers.utility.SchedulerUtils; +import io.github.znetworkw.znpcservers.utility.itemstack.ItemStackSerializer; +import io.github.znetworkw.znpcservers.utility.location.ZLocation; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.util.Collections; + +public class ZNPCsPlus extends JavaPlugin { + public static final File PLUGIN_FOLDER = new File("plugins/ServersNPC"); + + public static final File PATH_FOLDER = new File("plugins/ServersNPC/paths"); + public static final Gson GSON = (new GsonBuilder()) + .registerTypeAdapter(ZLocation.class, ZLocation.SERIALIZER) + .registerTypeHierarchyAdapter(ItemStack.class, new ItemStackSerializer()) + .setPrettyPrinting() + .disableHtmlEscaping() + .create(); + private static final int PLUGIN_ID = 8054; + public static SchedulerUtils SCHEDULER; + public static BungeeUtils BUNGEE_UTILS; + + public static NPC createNPC(int id, NPCType npcType, Location location, String name) { + NPC find = NPC.find(id); + if (find != null) + return find; + NPCModel pojo = (new NPCModel(id)).withHologramLines(Collections.singletonList(name)).withLocation(new ZLocation(location)).withNpcType(npcType); + ConfigurationConstants.NPC_LIST.add(pojo); + return new NPC(pojo, true); + } + + public static void deleteNPC(int npcID) { + NPC npc = NPC.find(npcID); + if (npc == null) + throw new IllegalStateException("can't find npc: " + npcID); + NPC.unregister(npcID); + ConfigurationConstants.NPC_LIST.remove(npc.getNpcPojo()); + } + + public void onEnable() { + ImmutableList files = ImmutableList.of(PLUGIN_FOLDER, PATH_FOLDER); + for (File file : files) file.mkdirs(); + + loadAllPaths(); + getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); + new MetricsLite(this, PLUGIN_ID); + new DefaultCommand(); + SCHEDULER = new SchedulerUtils(this); + BUNGEE_UTILS = new BungeeUtils(this); + Bukkit.getOnlinePlayers().forEach(ZUser::find); + new NPCManagerTask(this); + new NPCSaveTask(this, ConfigurationConstants.SAVE_DELAY); + new PlayerListener(this); + new InventoryListener(this); + } + + public void onDisable() { + Configuration.SAVE_CONFIGURATIONS.forEach(Configuration::save); + Bukkit.getOnlinePlayers().forEach(ZUser::unregister); + } + + public void loadAllPaths() { + File[] listFiles = PATH_FOLDER.listFiles(); + if (listFiles == null) + return; + for (File file : listFiles) { + if (file.getName().endsWith(".path")) { + NPCPath.AbstractTypeWriter abstractTypeWriter = NPCPath.AbstractTypeWriter.forFile(file, NPCPath.AbstractTypeWriter.TypeWriter.MOVEMENT); + abstractTypeWriter.load(); + } + } + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..911b935 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,5 @@ +name: ZNPCsPlus +main: lol.pyr.znpcsplus.ZNPCsPlus +version: ${version} +api-version: 1.13 +softdepend: [ PlaceholderAPI ] \ No newline at end of file