├── .github └── workflows │ └── buildJar.yml ├── .gitignore ├── LICENSE ├── README.md ├── Version.txt ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── libs ├── SkinsRestorer.jar └── TrMenu-3.0-RC-2.jar ├── settings.gradle.kts └── src └── main ├── kotlin └── me │ └── arasple │ └── mc │ └── trhologram │ ├── TrHologram.kt │ ├── api │ ├── InstantVector.kt │ ├── Position.kt │ ├── Settings.kt │ ├── TrHologramAPI.kt │ ├── base │ │ ├── BaseCondition.kt │ │ ├── ClickHandler.kt │ │ ├── ItemTexture.kt │ │ └── TickEvent.kt │ ├── event │ │ └── HologramInteractEvent.kt │ ├── hologram │ │ ├── HologramBuilder.kt │ │ ├── HologramComponent.kt │ │ ├── ItemHologram.kt │ │ └── TextHologram.kt │ └── nms │ │ ├── NMS.kt │ │ ├── NMSImpl.kt │ │ ├── NMSListener.kt │ │ └── packet │ │ ├── PacketArmorStandModify.kt │ │ ├── PacketArmorStandName.kt │ │ ├── PacketEntity.kt │ │ ├── PacketEntityDestroy.kt │ │ ├── PacketEntityMount.kt │ │ ├── PacketEntitySpawn.kt │ │ ├── PacketEntityVelocity.kt │ │ ├── PacketEquipment.kt │ │ ├── PacketHeadRotation.kt │ │ └── PacketItemModify.kt │ ├── module │ ├── action │ │ ├── ClickReaction.kt │ │ └── Reaction.kt │ ├── command │ │ ├── CommandExecutor.kt │ │ ├── CommandHandler.kt │ │ └── impl │ │ │ ├── CommandCreate.kt │ │ │ ├── CommandDelete.kt │ │ │ ├── CommandList.kt │ │ │ ├── CommandMirror.kt │ │ │ ├── CommandMovehere.kt │ │ │ ├── CommandReload.kt │ │ │ └── CommandTeleport.kt │ ├── condition │ │ └── Condition.kt │ ├── conf │ │ ├── HologramLoader.kt │ │ └── Property.kt │ ├── display │ │ ├── Hologram.kt │ │ └── texture │ │ │ ├── Texture.kt │ │ │ ├── TextureMeta.kt │ │ │ ├── TextureType.kt │ │ │ └── TrMenuTexture.kt │ ├── editor │ │ └── Editor.kt │ ├── hook │ │ ├── HookAbstract.kt │ │ ├── HookPlugin.kt │ │ └── impl │ │ │ ├── HookSkinsRestorer.kt │ │ │ └── HookTrMenu.kt │ ├── listener │ │ ├── ListenerHologramInteract.kt │ │ ├── ListenerJoin.kt │ │ ├── ListenerMovement.kt │ │ ├── ListenerQuit.kt │ │ ├── ListenerRespawn.kt │ │ └── ListenerWorldChange.kt │ └── service │ │ ├── Metrics.kt │ │ └── Updater.kt │ └── util │ ├── Heads.kt │ ├── ItemHelper.kt │ ├── Parser.kt │ └── Variables.kt └── resources ├── holograms └── Demo.yml ├── lang ├── RU_ru.yml ├── en_US.yml └── zh_CN.yml └── settings.yml /.github/workflows/buildJar.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | releaseJar: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-java@v1 15 | with: 16 | java-version: 8 17 | - name: Cache .gradle/caches 18 | uses: actions/cache@v1 19 | with: 20 | path: ~/.gradle/caches 21 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} 22 | restore-keys: ${{ runner.os }}-gradle- 23 | - name: Cache .gradle/wrapper 24 | uses: actions/cache@v1 25 | with: 26 | path: ~/.gradle/wrapper 27 | key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('**/*.gradle') }} 28 | restore-keys: ${{ runner.os }}-gradle-wrapper- 29 | - name: Grant execute permission for gradlew 30 | run: chmod +x gradlew 31 | - name: Build with Gradle 32 | run: ./gradlew clean build -s 33 | - name: Upload Artifacts 34 | uses: actions/upload-artifact@v2 35 | with: 36 | name: TrHologram Artifact 37 | path: build/libs/*.jar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | build -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Arasple 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![animate_60fps_.gif](https://i.loli.net/2021/08/19/wnVevcdAjBr2NXb.gif) 2 | # TrHologram 3 | 4 | #### Modern & Lightweight Holographic-Plugin 5 | ![](https://img.shields.io/github/last-commit/Micalhl/TrHologram?logo=artstation&style=for-the-badge&color=9266CC)![](https://img.shields.io/github/issues/Micalhl/TrHologram?style=for-the-badge&logo=slashdot)![](https://img.shields.io/github/release/Arasple/TrHologram?style=for-the-badge&color=00C58E&logo=ionic) 6 | 7 | --- 8 | 9 | ![bStats](https://bstats.org/signatures/bukkit/TrHologram.svg) 10 | 11 | --- 12 | 13 | ### Oops! 14 | Because of my studies, the updates is slowing down. Sorry :( 15 | 16 | ### Features 17 | 18 | - **Highly Optimized** 19 | - 100% Packet-based hologram (armorstand, item), no-lag 20 | - Async update tasks 21 | 22 | - **Light & Powerful** 23 | - Individual update task for each line 24 | - Custom view distance & view condition 25 | - Custom line spacing and offset for individual line 26 | - Support to display floating item with custom texture 27 | - Interactive holograms (4 clicktypes integrated) 28 | - PlaceholderAPI and custom functions support 29 | 30 | - **API** 31 | - Friendly developer API, create dynamic holograms easily 32 | 33 | --- 34 | 35 | ### API 36 | 37 | #### Dependency 38 | 39 | In Maven: 40 | ```xml 41 | 42 | 43 | roselle-public 44 | https://repo.mcage.cn/repository/maven-public/ 45 | 46 | 47 | 48 | 49 | 50 | me.arasple 51 | trmenu 52 | 3.0-PRE-20 53 | pure 54 | provided 55 | 56 | 57 | ``` 58 | 59 | In Gradle Kotlin DSL: 60 | ```kotlin 61 | repositories { 62 | maven("https://repo.mcage.cn/repository/maven-public/") 63 | } 64 | dependencies { 65 | compileOnly("me.arasple:TrMenu:3.0-PRE-20:pure") 66 | } 67 | 68 | ``` 69 | 70 | #### Usage 71 | 72 | ```java 73 | class Demo { 74 | 75 | public void display(Player viewer) { 76 | Hologram hologram = TrHologramAPI 77 | .builder(viewer.getLocation()) 78 | .append("Hello World") 79 | .append(player -> player.getInventory().getItemInMainHand(), 40) 80 | .interspace(0.5) 81 | .append("Time: %server_time_ss%", 20) 82 | .build(); 83 | 84 | hologram.refreshVisibility(viewer); 85 | 86 | TextHologram line = hologram.getTextLine(0); 87 | line.setText("Hello TrHologram"); 88 | } 89 | 90 | } 91 | ``` -------------------------------------------------------------------------------- /Version.txt: -------------------------------------------------------------------------------- 1 | 2.4-pre25 -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `maven-publish` 3 | id("java") 4 | id("io.izzel.taboolib") version "1.34" 5 | id("org.jetbrains.kotlin.jvm") version "1.5.10" 6 | id("com.github.johnrengelman.shadow") version "7.0.0" 7 | } 8 | 9 | group = "me.arasple.mc.trhologram" 10 | version = "2.4-pre29" 11 | description = "Modern & Advanced Hologram-Plugin for Minecraft Servers" 12 | 13 | taboolib { 14 | install( 15 | "common", 16 | "common-5", 17 | "platform-bukkit", 18 | "module-configuration", 19 | "module-chat", 20 | "module-lang", 21 | "module-nms", 22 | "module-nms-util", 23 | "module-metrics", 24 | "module-kether" 25 | ) 26 | 27 | description { 28 | contributors { 29 | name("Arasple") 30 | } 31 | dependencies { 32 | name("PlaceholderAPI").optional(true) 33 | name("TrMenu").optional(true) 34 | name("SkinsRestorer").optional(true) 35 | name("Multiverse-Core").loadafter(true) 36 | name("PlotSquared").loadafter(true) 37 | } 38 | } 39 | 40 | classifier = null 41 | version = "6.0.9-65" 42 | } 43 | 44 | repositories { 45 | mavenCentral() 46 | maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") 47 | maven("https://repo.codemc.org/repository/maven-public/") 48 | } 49 | 50 | dependencies { 51 | compileOnly("org.jetbrains.kotlin:kotlin-stdlib") 52 | compileOnly("ink.ptms:nms-all:1.0.0") 53 | compileOnly("ink.ptms.core:v11900:11900-minimize:mapped") 54 | compileOnly("ink.ptms.core:v11900:11900-minimize:universal") 55 | compileOnly("ink.ptms.core:v11800:11800-minimize:mapped") 56 | compileOnly("ink.ptms.core:v11800:11800-minimize:universal") 57 | compileOnly("ink.ptms.core:v11701:11701:mapped") 58 | compileOnly("ink.ptms.core:v11701:11701:universal") 59 | compileOnly("me.clip:placeholderapi:2.10.9") 60 | compileOnly(fileTree("libs")) 61 | } 62 | 63 | 64 | tasks.shadowJar { 65 | dependencies { 66 | taboolib.modules.forEach { exclude(dependency("io.izzel:taboolib:${taboolib.version}:$it")) } 67 | exclude(dependency("com.google.code.gson:gson:2.8.6")) 68 | exclude(dependency("org.bstats:bstats-bukkit:1.5")) 69 | 70 | exclude("data") 71 | exclude("META-INF/*.kotlin_module") 72 | exclude("META-INF/maven") 73 | exclude("lang") 74 | exclude("holograms") 75 | exclude("*.yml") 76 | } 77 | relocate("taboolib", "${project.group}.taboolib") 78 | archiveClassifier.set("pure") 79 | } 80 | 81 | configure { 82 | publications { 83 | create("shadow") { 84 | shadow.component(this) 85 | groupId = "me.arasple" 86 | } 87 | } 88 | repositories { 89 | maven { 90 | url = uri("https://repo.iroselle.com/repository/maven-releases/") 91 | credentials { 92 | file("access.txt").also { 93 | if (!it.exists()) return@credentials 94 | }.readLines().apply { 95 | username = this[0] 96 | password = this[1] 97 | } 98 | } 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Micalhl/TrHologram/6cbaeab5511fc7030db15ed92a5edc77ac30d2fe/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /libs/SkinsRestorer.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Micalhl/TrHologram/6cbaeab5511fc7030db15ed92a5edc77ac30d2fe/libs/SkinsRestorer.jar -------------------------------------------------------------------------------- /libs/TrMenu-3.0-RC-2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Micalhl/TrHologram/6cbaeab5511fc7030db15ed92a5edc77ac30d2fe/libs/TrMenu-3.0-RC-2.jar -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "TrHologram" 2 | 3 | -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/TrHologram.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram 2 | 3 | import me.arasple.mc.trhologram.api.Settings 4 | import me.arasple.mc.trhologram.module.conf.HologramLoader 5 | import me.arasple.mc.trhologram.module.display.Hologram 6 | import me.arasple.mc.trhologram.module.service.Updater 7 | import org.bukkit.Bukkit 8 | import taboolib.common.platform.Plugin 9 | import taboolib.common.platform.function.console 10 | import taboolib.common.platform.function.disablePlugin 11 | import taboolib.common.platform.function.pluginVersion 12 | import taboolib.module.lang.sendLang 13 | import taboolib.module.nms.MinecraftVersion 14 | import taboolib.platform.BukkitPlugin 15 | 16 | /** 17 | * @author Arasple 18 | * @date 2021/1/25 12:11 19 | */ 20 | object TrHologram : Plugin() { 21 | 22 | val plugin by lazy { BukkitPlugin.getInstance() } 23 | 24 | override fun onLoad() { 25 | console().sendLang("Plugin-Loading", Bukkit.getBukkitVersion()) 26 | } 27 | 28 | override fun onEnable() { 29 | if (MinecraftVersion.majorLegacy <= 10900) { 30 | console().sendLang("Plugin-UnsupportedVersion", pluginVersion) 31 | disablePlugin() 32 | return 33 | } 34 | 35 | Settings.init() 36 | HologramLoader.load(Bukkit.getConsoleSender()) 37 | console().sendLang("Plugin-Enabled", pluginVersion) 38 | 39 | Updater.init() 40 | } 41 | 42 | override fun onDisable() { 43 | Hologram.holograms.forEach(Hologram::destroy) 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/InstantVector.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api 2 | 3 | import me.arasple.mc.trhologram.api.nms.NMS 4 | import me.arasple.mc.trhologram.api.nms.packet.PacketEntityVelocity 5 | import org.bukkit.World 6 | import org.bukkit.entity.Player 7 | import org.bukkit.util.Vector 8 | import taboolib.common.platform.function.submit 9 | 10 | // 模 / 时间 = 速度 11 | class InstantVector(val x: Double, val y: Double, val z: Double, val arrival: Long = 0): Cloneable { 12 | 13 | val useing = mutableMapOf() 14 | 15 | val hasArrival get() = arrival > 0 16 | 17 | val planVector: InstantVector get() { 18 | if (!hasArrival) { 19 | return this 20 | } 21 | return InstantVector(x / arrival, y / arrival, z / arrival) 22 | } 23 | 24 | fun useToVanilla(player: Player, entityId: Int) { 25 | if (!hasArrival) { 26 | NMS.INSTANCE.sendEntityPacket(player, PacketEntityVelocity(entityId, x, y, z)) 27 | return 28 | } 29 | val vec = planVector 30 | useing[entityId] = vec 31 | NMS.INSTANCE.sendEntityPacket(player, PacketEntityVelocity(entityId, vec.x, vec.y, vec.z)) 32 | submit(delay = arrival) { 33 | NMS.INSTANCE.sendEntityPacket(player, PacketEntityVelocity(entityId, 0.0, 0.0, 0.0)) 34 | useing.remove(entityId) 35 | } 36 | } 37 | 38 | fun toPosition(w: World) = 39 | Position(w, x, y, z) 40 | 41 | override fun clone() = 42 | super.clone() as InstantVector 43 | 44 | companion object { 45 | 46 | @JvmStatic 47 | fun fromVector(v: Vector, arrival: Long= 0) = 48 | InstantVector(v.x, v.y, v.z, arrival) 49 | 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/Position.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api 2 | 3 | import org.bukkit.Bukkit 4 | import org.bukkit.Location 5 | import org.bukkit.World 6 | import kotlin.math.pow 7 | import kotlin.math.sqrt 8 | 9 | /** 10 | * @author Arasple 11 | * @date 2021/2/10 10:53 12 | */ 13 | class Position: Cloneable { 14 | 15 | val world: World 16 | val x: Double 17 | val y: Double 18 | val z: Double 19 | 20 | constructor(x: Double = 0.0, y: Double = 0.0, z: Double = 0.0) : this(Bukkit.getWorlds()[0], x, y, z) 21 | 22 | constructor(world: World = Bukkit.getWorlds()[0], x: Double = 0.0, y: Double = 0.0, z: Double = 0.0) { 23 | this.world = world 24 | this.x = x 25 | this.y = y 26 | this.z = z 27 | } 28 | 29 | fun distance(l: Position) = 30 | distance(l.x, l.y, l.z) 31 | 32 | fun distance(l: Location) = 33 | distance(l.x, l.y, l.z) 34 | 35 | fun clone(x: Double = 0.0, y: Double = 0.0, z: Double = 0.0) = 36 | Position(world, this.x + x, this.y + y, this.z + z) 37 | 38 | private fun distance(x: Double, y: Double, z: Double) = 39 | sqrt((this.x - x).pow(2) + (this.y - y).pow(2) + (this.z - z).pow(2)) 40 | 41 | fun toVelocity() = 42 | InstantVector(x, y, z) 43 | 44 | fun toLocation() = 45 | Location(world, x, y, z) 46 | 47 | override fun toString() = 48 | "${world.name} ~ $x, $y, $z" 49 | 50 | override fun clone(): Location { 51 | return super.clone() as Location 52 | } 53 | 54 | companion object { 55 | 56 | @JvmStatic 57 | fun fromLocation(l: Location) = 58 | Position(l.world!!, l.x, l.y, l.z) 59 | 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/Settings.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api 2 | 3 | import taboolib.common.platform.function.console 4 | import taboolib.common5.Baffle 5 | import taboolib.module.configuration.Config 6 | import taboolib.module.configuration.Configuration 7 | import taboolib.module.lang.sendLang 8 | import java.util.concurrent.TimeUnit 9 | 10 | /** 11 | * @author Arasple 12 | * @date 2021/2/11 10:07 13 | */ 14 | class Settings { 15 | 16 | companion object { 17 | 18 | @Config("settings.yml", migrate = true, autoReload = true) 19 | lateinit var CONF: Configuration 20 | private set 21 | 22 | internal var INSTANCE = Settings() 23 | 24 | fun init() { 25 | CONF.onReload { 26 | onSettingsReload() 27 | console().sendLang("Configuration-Auto-Reload") 28 | } 29 | } 30 | 31 | fun onSettingsReload() { 32 | INSTANCE = Settings() 33 | } 34 | 35 | } 36 | 37 | val loadPaths: List by lazy { 38 | CONF.getStringList("Loader.Hologram-Files") 39 | } 40 | 41 | val interactDelay by lazy { 42 | Baffle.of(CONF.getLong("Hologram.Interact-Min-Delay").coerceAtLeast(100), TimeUnit.MILLISECONDS) 43 | } 44 | 45 | val lineSpacing by lazy { 46 | CONF.getDouble("Hologram.Options.Default-Line-Spacing", 0.25) 47 | } 48 | 49 | val viewDistance by lazy { 50 | CONF.getDouble("Hologram.Options.Default-View-Distance", 20.0) 51 | } 52 | 53 | val viewCondition by lazy { 54 | CONF.getString("Hologram.Options.Default-View-Condition", "") 55 | } 56 | 57 | val refershCondition by lazy { 58 | CONF.getLong("Hologram.Options.Default-Refresh-Condition", -1) 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/TrHologramAPI.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api 2 | 3 | import me.arasple.mc.trhologram.api.hologram.HologramBuilder 4 | import me.arasple.mc.trhologram.api.hologram.HologramComponent 5 | import me.arasple.mc.trhologram.api.hologram.ItemHologram 6 | import me.arasple.mc.trhologram.api.hologram.TextHologram 7 | import me.arasple.mc.trhologram.module.display.Hologram 8 | import org.bukkit.Location 9 | import org.bukkit.entity.Player 10 | import org.bukkit.inventory.ItemStack 11 | import taboolib.common.platform.function.adaptPlayer 12 | import taboolib.common5.mirrorNow 13 | import taboolib.library.kether.LocalizedException 14 | import taboolib.module.kether.KetherShell 15 | import java.util.concurrent.CompletableFuture 16 | 17 | /** 18 | * @author Arasple 19 | * @date 2021/2/10 9:38 20 | */ 21 | object TrHologramAPI { 22 | 23 | /** 24 | * 实体 ID 取得 25 | */ 26 | private var INDEX = resetIndex() 27 | 28 | fun getIndex(): Int { 29 | return INDEX++ 30 | } 31 | 32 | internal fun resetIndex(): Int { 33 | return 1197897763 + (0..7763).random() 34 | } 35 | 36 | @JvmStatic 37 | fun eval(player: Player, script: String): CompletableFuture { 38 | return mirrorNow("Hologram:Handler:ScriptEval") { 39 | return@mirrorNow try { 40 | KetherShell.eval(script, namespace = listOf("trhologram", "trmenu")) { 41 | sender = adaptPlayer(player) 42 | } 43 | } catch (e: LocalizedException) { 44 | println("[TrHologram] Unexpected exception while parsing kether shell:") 45 | e.localizedMessage.split("\n").forEach { 46 | println("[TrHologram] $it") 47 | } 48 | CompletableFuture.completedFuture(false) 49 | } 50 | } 51 | } 52 | 53 | @JvmStatic 54 | fun getHologramById(id: String): Hologram? { 55 | return Hologram.holograms.find { it.id == id } 56 | } 57 | 58 | @JvmStatic 59 | fun createTextCompoent( 60 | initText: String, 61 | location: Location, 62 | tick: Long = -1, 63 | onTick: (HologramComponent) -> Unit = {} 64 | ): TextHologram { 65 | return TextHologram(initText, Position.fromLocation(location), tick, onTick) 66 | } 67 | 68 | @JvmStatic 69 | fun createItemCompoent( 70 | initItem: ItemStack, 71 | location: Location, 72 | tick: Long = -1, 73 | onTick: (HologramComponent) -> Unit = {} 74 | ): ItemHologram { 75 | return ItemHologram(initItem, Position.fromLocation(location), tick, onTick) 76 | } 77 | 78 | @JvmStatic 79 | fun builder(location: Location): HologramBuilder { 80 | return HologramBuilder(location = location) 81 | } 82 | 83 | 84 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/base/BaseCondition.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api.base 2 | 3 | import org.bukkit.entity.Player 4 | import java.util.concurrent.CompletableFuture 5 | 6 | /** 7 | * @author Arasple 8 | * @date 2021/2/12 14:08 9 | */ 10 | fun interface BaseCondition { 11 | 12 | fun eval(player: Player): CompletableFuture 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/base/ClickHandler.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api.base 2 | 3 | import me.arasple.mc.trhologram.api.event.HologramInteractEvent 4 | import org.bukkit.entity.Player 5 | 6 | /** 7 | * @author Arasple 8 | * @date 2021/2/12 14:10 9 | */ 10 | fun interface ClickHandler { 11 | 12 | fun eval(player: Player, type: HologramInteractEvent.Type) 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/base/ItemTexture.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api.base 2 | 3 | import org.bukkit.entity.Player 4 | import org.bukkit.inventory.ItemStack 5 | 6 | /** 7 | * @author Arasple 8 | * @date 2021/2/11 22:50 9 | */ 10 | fun interface ItemTexture { 11 | 12 | fun generate(player: Player): ItemStack 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/base/TickEvent.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api.base 2 | 3 | import me.arasple.mc.trhologram.api.hologram.HologramComponent 4 | 5 | /** 6 | * @author Arasple 7 | * @date 2021/2/12 20:38 8 | */ 9 | fun interface TickEvent { 10 | 11 | fun run(hologramComponent: HologramComponent) 12 | 13 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/event/HologramInteractEvent.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api.event 2 | 3 | import me.arasple.mc.trhologram.module.display.Hologram 4 | import org.bukkit.entity.Player 5 | import taboolib.platform.type.BukkitProxyEvent 6 | 7 | /** 8 | * @author Arasple 9 | * @date 2021/2/11 16:34 10 | */ 11 | class HologramInteractEvent(val player: Player, val type: Type, val hologram: Hologram) : BukkitProxyEvent() { 12 | 13 | enum class Type { 14 | 15 | ALL, 16 | 17 | LEFT, 18 | 19 | RIGHT, 20 | 21 | SHIFT_LEFT, 22 | 23 | SHIFT_RIGHT 24 | 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/hologram/HologramBuilder.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api.hologram 2 | 3 | import me.arasple.mc.trhologram.api.Position 4 | import me.arasple.mc.trhologram.api.base.BaseCondition 5 | import me.arasple.mc.trhologram.api.base.ClickHandler 6 | import me.arasple.mc.trhologram.api.base.ItemTexture 7 | import me.arasple.mc.trhologram.api.base.TickEvent 8 | import me.arasple.mc.trhologram.module.display.Hologram 9 | import org.bukkit.Location 10 | import java.util.* 11 | 12 | /** 13 | * @author Arasple 14 | * @date 2021/2/12 14:04 15 | */ 16 | class HologramBuilder( 17 | private val id: String = UUID.randomUUID().toString(), 18 | private val location: Location, 19 | var offset: Double = 0.25, 20 | var viewDistance: Double = 15.0, 21 | var viewCondition: BaseCondition? = null, 22 | var refreshCondition: Long = -1, 23 | var clickHandler: ClickHandler = ClickHandler { _, _ -> } 24 | ) { 25 | 26 | private val components = mutableListOf() 27 | private var position = Position.fromLocation(location).clone(y = -1.25) 28 | 29 | fun offset(value: Double): HologramBuilder { 30 | offset = value 31 | return this 32 | } 33 | 34 | fun interspace(value: Double): HologramBuilder { 35 | position = position.clone(y = -value) 36 | return this 37 | } 38 | 39 | /** 40 | * Append a text line 41 | */ 42 | @JvmOverloads 43 | fun append( 44 | text: String, 45 | update: Long = -1, 46 | offset: Double = this.offset, 47 | onTick: TickEvent? = null 48 | ): HologramBuilder { 49 | interspace(offset) 50 | components.add(TextHologram(text, position, update, onTick)) 51 | return this 52 | } 53 | 54 | /** 55 | * Append a floating item line 56 | */ 57 | @JvmOverloads 58 | fun append( 59 | texture: ItemTexture, 60 | update: Long = -1, 61 | offset: Double = this.offset, 62 | onTick: TickEvent? = null 63 | ): HologramBuilder { 64 | interspace(0.5 + offset) 65 | components.add(ItemHologram(texture, position, update, onTick)) 66 | 67 | return this 68 | } 69 | 70 | fun viewDistance(value: Double): HologramBuilder { 71 | viewDistance = value 72 | return this 73 | } 74 | 75 | fun viewCondition(value: BaseCondition): HologramBuilder { 76 | viewCondition = value 77 | return this 78 | } 79 | 80 | fun refreshCondition(period: Long): HologramBuilder { 81 | refreshCondition = period 82 | return this 83 | } 84 | 85 | fun click(handler: ClickHandler): HologramBuilder { 86 | clickHandler = handler 87 | return this 88 | } 89 | 90 | fun removeLast() { 91 | components.removeLast() 92 | position = components.last().position 93 | } 94 | 95 | /** 96 | * @param external 97 | */ 98 | @JvmOverloads 99 | fun build(external: Boolean = true): Hologram { 100 | return Hologram( 101 | id, 102 | Position.fromLocation(location), 103 | viewDistance, 104 | viewCondition, 105 | refreshCondition, 106 | components, 107 | clickHandler 108 | ).also { 109 | (if (external) Hologram.externalHolograms 110 | else Hologram.holograms).add(it) 111 | } 112 | } 113 | 114 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/hologram/HologramComponent.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api.hologram 2 | 3 | import me.arasple.mc.trhologram.api.Position 4 | import me.arasple.mc.trhologram.api.TrHologramAPI 5 | import me.arasple.mc.trhologram.api.base.TickEvent 6 | import me.arasple.mc.trhologram.api.nms.packet.PacketEntityDestroy 7 | import org.bukkit.Bukkit 8 | import org.bukkit.entity.Player 9 | import taboolib.common.platform.function.submit 10 | import taboolib.common.platform.service.PlatformExecutor 11 | import kotlin.properties.Delegates 12 | 13 | /** 14 | * @author Arasple 15 | * @date 2021/2/10 10:36 16 | */ 17 | abstract class HologramComponent( 18 | val position: Position, 19 | tick: Long = -1, 20 | val onTick: TickEvent? 21 | ) { 22 | 23 | abstract fun spawn(player: Player) 24 | 25 | abstract fun onTick() 26 | 27 | val entityId by lazy { TrHologramAPI.getIndex() } 28 | 29 | internal val viewers = mutableSetOf() 30 | 31 | private var task: PlatformExecutor.PlatformTask? = null 32 | 33 | var period0 by Delegates.observable(tick) { _, _, _ -> 34 | deployment() 35 | } 36 | 37 | init { 38 | deployment() 39 | } 40 | 41 | private fun deployment() { 42 | task?.cancel() 43 | if (period0 > 0) task = submit(delay = period0, period = period0, async = true) { 44 | viewers.removeIf { 45 | val player = Bukkit.getPlayerExact(it) 46 | player == null || !player.isOnline 47 | } 48 | onTick() 49 | onTick?.run(this@HologramComponent) 50 | } 51 | } 52 | 53 | fun destroy(player: Player) { 54 | PacketEntityDestroy(entityId).send(player) 55 | viewers.remove(player.name) 56 | } 57 | 58 | fun destroy() { 59 | task?.cancel() 60 | forViewers { destroy(it) } 61 | } 62 | 63 | fun forViewers(viewer: (Player) -> Unit) { 64 | viewers.mapNotNull { Bukkit.getPlayerExact(it) }.forEach(viewer) 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/hologram/ItemHologram.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api.hologram 2 | 3 | import me.arasple.mc.trhologram.api.Position 4 | import me.arasple.mc.trhologram.api.TrHologramAPI 5 | import me.arasple.mc.trhologram.api.base.ItemTexture 6 | import me.arasple.mc.trhologram.api.base.TickEvent 7 | import me.arasple.mc.trhologram.api.nms.packet.* 8 | import me.arasple.mc.trhologram.module.display.texture.Texture 9 | import org.bukkit.Bukkit 10 | import org.bukkit.entity.Player 11 | import org.bukkit.inventory.ItemStack 12 | import taboolib.common.platform.function.submit 13 | import taboolib.common5.mirrorNow 14 | 15 | /** 16 | * @author Arasple 17 | * @date 2021/2/10 21:29 18 | * 19 | * Y: position.Y + 0.75 20 | */ 21 | class ItemHologram( 22 | texture: ItemTexture, 23 | position: Position, 24 | tick: Long = -1, 25 | onTick: TickEvent? = null 26 | ) : HologramComponent(position.clone(y = 1.3), tick, onTick) { 27 | 28 | constructor( 29 | itemStack: ItemStack, 30 | position: Position, 31 | tick: Long = -1, 32 | onTick: (HologramComponent) -> Unit = {} 33 | ) : this( 34 | Texture.createTexture(itemStack), position.clone(y = 1.3), tick, onTick 35 | ) 36 | 37 | var display: ItemTexture = texture 38 | set(value) { 39 | forViewers { updateItem(it) } 40 | field = value 41 | } 42 | 43 | override fun spawn(player: Player) { 44 | destroy(player) 45 | val teid = TrHologramAPI.getIndex() 46 | 47 | PacketEntitySpawn(teid, position).send(player) 48 | PacketArmorStandModify( 49 | teid, 50 | isInvisible = true, 51 | isGlowing = false, 52 | isSmall = true, 53 | hasArms = false, 54 | noBasePlate = true, 55 | isMarker = true 56 | ).send(player) 57 | 58 | PacketEntitySpawn(entityId, position, false).send(player) 59 | updateItem(player) 60 | 61 | submit(delay = 1L, async = !Bukkit.isPrimaryThread()) { 62 | PacketEntityMount(teid, IntArray(1) { entityId }).send(player) 63 | submit(delay = 20 * 5, async = !Bukkit.isPrimaryThread()) { 64 | PacketEntityDestroy(teid).send(player) 65 | } 66 | } 67 | 68 | viewers.add(player.name) 69 | } 70 | 71 | override fun onTick() { 72 | mirrorNow("Hologram:Event:Tick:ItemComponent") { 73 | forViewers { updateItem(it) } 74 | } 75 | } 76 | 77 | private fun updateItem(player: Player) { 78 | PacketItemModify(entityId, isInvisible = false, isGlowing = false, itemStack = display.generate(player)).send( 79 | player 80 | ) 81 | } 82 | 83 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/hologram/TextHologram.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api.hologram 2 | 3 | import me.arasple.mc.trhologram.api.Position 4 | import me.arasple.mc.trhologram.api.base.TickEvent 5 | import me.arasple.mc.trhologram.api.nms.packet.PacketArmorStandModify 6 | import me.arasple.mc.trhologram.api.nms.packet.PacketArmorStandName 7 | import me.arasple.mc.trhologram.api.nms.packet.PacketEntitySpawn 8 | import me.arasple.mc.trhologram.util.parseString 9 | import org.bukkit.entity.Player 10 | import taboolib.common5.mirrorNow 11 | 12 | /** 13 | * @author Arasple 14 | * @date 2021/2/10 10:28 15 | * 16 | * Y: rawY + 1 ~ rawY + 1.25 17 | */ 18 | class TextHologram( 19 | name: String, 20 | position: Position, 21 | tick: Long, 22 | onTick: TickEvent? = null 23 | ) : HologramComponent(position, tick, onTick) { 24 | 25 | var text: String = name 26 | set(value) { 27 | onTick() 28 | field = value 29 | } 30 | 31 | private fun updateName(player: Player) { 32 | PacketArmorStandName(entityId, true, player.parseString(text)).send(player) 33 | } 34 | 35 | override fun spawn(player: Player) { 36 | PacketEntitySpawn(entityId, position).send(player) 37 | PacketArmorStandModify( 38 | entityId, 39 | isInvisible = true, 40 | isGlowing = false, 41 | isSmall = true, 42 | hasArms = false, 43 | noBasePlate = true, 44 | isMarker = false 45 | ).send(player) 46 | updateName(player) 47 | 48 | viewers.add(player.name) 49 | } 50 | 51 | override fun onTick() { 52 | mirrorNow("Hologram:Event:Tick:TextComponent") { 53 | forViewers { updateName(it) } 54 | } 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/nms/NMS.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api.nms 2 | 3 | import com.mojang.authlib.GameProfile 4 | import me.arasple.mc.trhologram.api.nms.packet.PacketEntity 5 | import org.bukkit.entity.Player 6 | import org.bukkit.util.Vector 7 | import taboolib.common.reflect.Reflex.Companion.setProperty 8 | import taboolib.module.nms.nmsProxy 9 | import taboolib.module.nms.sendPacket 10 | 11 | /** 12 | * @author Arasple 13 | * @date 2020/12/4 21:20 14 | */ 15 | abstract class NMS { 16 | 17 | companion object { 18 | 19 | val INSTANCE by lazy { 20 | nmsProxy() 21 | } 22 | } 23 | 24 | abstract fun sendEntityPacket(player: Player, vararg packets: PacketEntity) 25 | 26 | abstract fun sendEntityMetadata(player: Player, entityId: Int, vararg objects: Any) 27 | 28 | abstract fun parseVec3d(obj: Any): Vector 29 | 30 | fun sendPacket(player: Player, packet: Any, vararg fields: Pair) { 31 | fields.forEach { packet.setProperty(it.first.toString(), it.second) } 32 | player.sendPacket(packet) 33 | } 34 | 35 | abstract fun getGameProfile(player: Player): GameProfile 36 | 37 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/nms/NMSImpl.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api.nms 2 | 3 | import com.mojang.authlib.GameProfile 4 | import com.mojang.datafixers.util.Pair 5 | import me.arasple.mc.trhologram.api.nms.packet.* 6 | import me.arasple.mc.trhologram.util.ItemHelper 7 | import net.minecraft.server.v1_16_R1.* 8 | import net.minecraft.server.v1_16_R3.ChatComponentText 9 | import net.minecraft.server.v1_16_R3.EntityTypes 10 | import net.minecraft.server.v1_16_R3.PacketPlayOutMount 11 | import org.bukkit.Material 12 | import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer 13 | import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftItemStack 14 | import org.bukkit.craftbukkit.v1_16_R3.util.CraftChatMessage 15 | import org.bukkit.entity.Player 16 | import org.bukkit.inventory.EquipmentSlot 17 | import org.bukkit.inventory.ItemStack 18 | import org.bukkit.util.Vector 19 | import taboolib.common.reflect.Reflex.Companion.unsafeInstance 20 | import taboolib.module.nms.MinecraftVersion 21 | import taboolib.type.BukkitEquipment 22 | import java.util.* 23 | 24 | /** 25 | * @author Arasple 26 | * @date 2020/12/4 21:25 27 | */ 28 | class NMSImpl : NMS() { 29 | 30 | private val version = MinecraftVersion.majorLegacy 31 | private val emptyItemStack = CraftItemStack.asNMSCopy((ItemStack(Material.AIR))) 32 | private val indexs = arrayOf( 33 | // armorstand 34 | arrayOf(11500 to 14, 11400 to 13, 11000 to 11, 10900 to 10), 35 | // item 36 | arrayOf(11300 to 7, 11000 to 6, 10900 to 5), 37 | ).map { it -> it.firstOrNull { version >= it.first }?.second ?: -1 } 38 | 39 | /** 40 | * 处理实体封装后的数据包 41 | */ 42 | override fun sendEntityPacket(player: Player, vararg packets: PacketEntity) { 43 | packets.forEach { 44 | val id = it.entityId 45 | 46 | when (it) { 47 | // Set Entity velocity 48 | is PacketEntityVelocity -> { 49 | sendPacket(player, PacketPlayOutEntityVelocity(id, Vec3D(it.x, it.y, it.z))) 50 | } 51 | // Destroy entity 52 | is PacketEntityDestroy -> sendPacket(player, PacketPlayOutEntityDestroy(id)) 53 | // Spawn Armor Stand 54 | is PacketEntitySpawn -> { 55 | if (MinecraftVersion.isUniversal) { 56 | sendPacket( 57 | player, 58 | PacketPlayOutSpawnEntity::class.java.unsafeInstance(), 59 | "id" to id, 60 | "uuid" to (it.uuid ?: UUID.randomUUID()), 61 | "x" to it.position.x, 62 | "y" to it.position.y, 63 | "z" to it.position.z, 64 | "xa" to 0, 65 | "ya" to 0, 66 | "za" to 0, 67 | "type" to if (it.type) EntityTypes.ARMOR_STAND else EntityTypes.ITEM 68 | ) 69 | } else { 70 | sendPacket( 71 | player, 72 | PacketPlayOutSpawnEntity(), 73 | "a" to id, 74 | "b" to (it.uuid ?: UUID.randomUUID()), 75 | "c" to it.position.x, 76 | "d" to it.position.y, 77 | "e" to it.position.z, 78 | "f" to 0.toByte(), 79 | "g" to 0.toByte(), 80 | "k" to if (it.type) if (version >= 11400) EntityTypes.ARMOR_STAND else 78 else if (version >= 11400) EntityTypes.ITEM else 2 81 | ) 82 | } 83 | // Cancel gravity 84 | sendEntityMetadata( 85 | player, 86 | id, 87 | getMetaEntityBoolean(5, true) 88 | ) 89 | } 90 | is PacketEntityMount -> { 91 | if (MinecraftVersion.isUniversal) { 92 | sendPacket(player, PacketPlayOutMount::class.java.unsafeInstance(), "vehicle" to it.entityId, "passengers" to it.mount) 93 | } else { 94 | sendPacket(player, PacketPlayOutMount(), "a" to it.entityId, "b" to it.mount) 95 | } 96 | } 97 | // Packet Modify 98 | is PacketArmorStandModify -> { 99 | var entity = 0 100 | var armorstand = 0 101 | if (it.isInvisible) entity += 0x20.toByte() 102 | if (it.isGlowing) entity += 0x40.toByte() 103 | if (it.isSmall) armorstand += 0x01.toByte() 104 | if (it.hasArms) armorstand += 0x04.toByte() 105 | if (it.noBasePlate) armorstand += 0x08.toByte() 106 | if (it.isMarker) armorstand += 0x10.toByte() 107 | sendEntityMetadata( 108 | player, id, 109 | getMetaEntityByte(0, entity.toByte()), 110 | getMetaEntityByte(indexs[0], armorstand.toByte()), 111 | ) 112 | } 113 | // Modify Item 114 | is PacketItemModify -> { 115 | var entity = 0 116 | val itemNull = ItemHelper.isNull(it.itemStack) 117 | if (it.isInvisible || itemNull) entity += 0x80.toByte() 118 | if (it.isGlowing) entity += 0x40.toByte() 119 | 120 | sendEntityMetadata(player, id, getMetaEntityByte(0, entity.toByte())) 121 | if (!itemNull) sendEntityMetadata(player, id, getMetaItem(indexs[1], it.itemStack)) 122 | } 123 | // Entity Name 124 | is PacketArmorStandName -> { 125 | sendEntityMetadata( 126 | player, id, 127 | getMetaEntityChatBaseComponent(2, it.name), 128 | getMetaEntityBoolean(3, it.isCustomNameVisible) 129 | ) 130 | } 131 | is PacketHeadRotation -> { 132 | if (MinecraftVersion.isUniversal) { 133 | sendPacket( 134 | player, 135 | PacketPlayOutEntityHeadRotation::class.java.unsafeInstance(), 136 | "id" to id, 137 | "headYaw" to it.headYaw 138 | ) 139 | } else { 140 | sendPacket( 141 | player, 142 | PacketPlayOutEntityHeadRotation(), 143 | "a" to id, 144 | "b" to it.headYaw 145 | ) 146 | } 147 | } 148 | is PacketEquipment -> updateEquipment(player, id, it.slot, it.itemStack) 149 | } 150 | } 151 | 152 | } 153 | 154 | /** 155 | * 更新实体属性 Metadata 156 | */ 157 | override fun sendEntityMetadata(player: Player, entityId: Int, vararg objects: Any) { 158 | if (MinecraftVersion.isUniversal) { 159 | sendPacket( 160 | player, 161 | PacketPlayOutEntityMetadata::class.java.unsafeInstance(), 162 | "id" to entityId, 163 | "packedItems" to objects.map { it as DataWatcher.Item<*> }.toList() 164 | ) 165 | } else { 166 | sendPacket( 167 | player, 168 | PacketPlayOutEntityMetadata(), 169 | "a" to entityId, 170 | "b" to objects.map { it as DataWatcher.Item<*> }.toList() 171 | ) 172 | } 173 | } 174 | 175 | override fun parseVec3d(obj: Any): Vector { 176 | return Vector((obj as Vec3D).x, obj.y, obj.z) 177 | } 178 | 179 | override fun getGameProfile(player: Player): GameProfile { 180 | return (player as CraftPlayer).profile 181 | } 182 | 183 | /** 184 | * 私有方法 & NMS 相关处理 185 | */ 186 | 187 | // TODO 188 | private fun updateEquipment(player: Player, entityId: Int, slot: EquipmentSlot, itemStack: ItemStack) { 189 | when { 190 | version >= 11600 -> { 191 | sendPacket( 192 | player, 193 | PacketPlayOutEntityEquipment( 194 | entityId, 195 | listOf( 196 | Pair( 197 | EnumItemSlot.fromName(BukkitEquipment.fromBukkit(slot)?.nms), 198 | org.bukkit.craftbukkit.v1_16_R1.inventory.CraftItemStack.asNMSCopy(itemStack) 199 | ) 200 | ) 201 | ) 202 | ) 203 | } 204 | version >= 11300 -> { 205 | sendPacket( 206 | player, 207 | net.minecraft.server.v1_13_R2.PacketPlayOutEntityEquipment( 208 | entityId, 209 | net.minecraft.server.v1_13_R2.EnumItemSlot.fromName(BukkitEquipment.fromBukkit(slot)?.nms), 210 | org.bukkit.craftbukkit.v1_13_R2.inventory.CraftItemStack.asNMSCopy(itemStack) 211 | ) 212 | ) 213 | } 214 | else -> { 215 | sendPacket( 216 | player, 217 | net.minecraft.server.v1_12_R1.PacketPlayOutEntityEquipment( 218 | entityId, 219 | net.minecraft.server.v1_12_R1.EnumItemSlot.a(BukkitEquipment.fromBukkit(slot)?.nms), 220 | org.bukkit.craftbukkit.v1_12_R1.inventory.CraftItemStack.asNMSCopy(itemStack) 221 | ) 222 | ) 223 | } 224 | } 225 | } 226 | 227 | 228 | private fun getMetaEntityByte(index: Int, value: Byte): Any { 229 | return DataWatcher.Item(DataWatcherObject(index, DataWatcherRegistry.a), value) 230 | } 231 | 232 | private fun getMetaEntityBoolean(index: Int, value: Boolean): Any { 233 | return if (version >= 11300) { 234 | DataWatcher.Item(DataWatcherObject(index, DataWatcherRegistry.i), value) 235 | } else { 236 | net.minecraft.server.v1_12_R1.DataWatcher.Item( 237 | net.minecraft.server.v1_12_R1.DataWatcherObject( 238 | index, 239 | net.minecraft.server.v1_12_R1.DataWatcherRegistry.h 240 | ), value 241 | ) 242 | } 243 | } 244 | 245 | private fun getMetaEntityChatBaseComponent(index: Int, name: String?): Any { 246 | return if (version >= 11300) { 247 | DataWatcher.Item>( 248 | DataWatcherObject(index, DataWatcherRegistry.f), 249 | Optional.ofNullable( 250 | (if (name == null) null else toChatBaseComponent(name, true)) as IChatBaseComponent? 251 | ) 252 | ) 253 | } else { 254 | net.minecraft.server.v1_12_R1.DataWatcher.Item( 255 | net.minecraft.server.v1_12_R1.DataWatcherObject( 256 | index, 257 | net.minecraft.server.v1_12_R1.DataWatcherRegistry.d 258 | ), name ?: "" 259 | ) 260 | } 261 | } 262 | 263 | private fun getMetaItem(index: Int, itemStack: ItemStack): Any { 264 | return when { 265 | version >= 11300 -> { 266 | DataWatcher.Item( 267 | DataWatcherObject(index, DataWatcherRegistry.g), 268 | org.bukkit.craftbukkit.v1_16_R1.inventory.CraftItemStack.asNMSCopy(itemStack) 269 | ) 270 | } 271 | version >= 11200 -> { 272 | net.minecraft.server.v1_12_R1.DataWatcher.Item( 273 | net.minecraft.server.v1_12_R1.DataWatcherObject( 274 | 6, 275 | net.minecraft.server.v1_12_R1.DataWatcherRegistry.f 276 | ), org.bukkit.craftbukkit.v1_12_R1.inventory.CraftItemStack.asNMSCopy(itemStack) 277 | ) 278 | } 279 | else -> { 280 | return net.minecraft.server.v1_9_R2.DataWatcher.Item( 281 | net.minecraft.server.v1_9_R2.DataWatcherObject( 282 | 6, 283 | net.minecraft.server.v1_9_R2.DataWatcherRegistry.f 284 | ), 285 | com.google.common.base.Optional.fromNullable( 286 | org.bukkit.craftbukkit.v1_9_R2.inventory.CraftItemStack.asNMSCopy( 287 | itemStack 288 | ) 289 | ) 290 | ) 291 | } 292 | } 293 | } 294 | 295 | private fun toChatBaseComponent(string: String, craftChatMessage: Boolean): Any { 296 | return if (craftChatMessage) CraftChatMessage.fromString(string).first() 297 | else ChatComponentText(string) 298 | } 299 | 300 | private fun toNMSItemStack(vararg itemStack: ItemStack?): Any { 301 | if (itemStack.size > 1) { 302 | return itemStack.map { asNMSCopy(it) } 303 | } 304 | return asNMSCopy(itemStack[0]) 305 | } 306 | 307 | private fun asNMSCopy(itemStack: ItemStack?): Any { 308 | return if (itemStack == null || itemStack.type == Material.AIR) emptyItemStack 309 | else CraftItemStack.asNMSCopy(itemStack) 310 | } 311 | 312 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/nms/NMSListener.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api.nms 2 | 3 | import me.arasple.mc.trhologram.api.event.HologramInteractEvent 4 | import me.arasple.mc.trhologram.api.event.HologramInteractEvent.Type.* 5 | import me.arasple.mc.trhologram.module.display.Hologram 6 | import taboolib.common.platform.event.SubscribeEvent 7 | import taboolib.common5.mirrorNow 8 | import taboolib.module.nms.MinecraftVersion 9 | import taboolib.module.nms.PacketReceiveEvent 10 | 11 | /** 12 | * @author Arasple 13 | * @date 2021/2/10 11:27 14 | */ 15 | object NMSListener { 16 | 17 | @SubscribeEvent 18 | fun e(e: PacketReceiveEvent) { 19 | if (e.packet.name == "PacketPlayInUseEntity") { 20 | mirrorNow("Hologram:Event:Interact") { 21 | val entityId = e.packet.read("a").also { 22 | if (it == null || it < 1197897763) { 23 | return@mirrorNow 24 | } 25 | } 26 | val hologram = 27 | Hologram.findHologram { it -> it.components.any { it.entityId == entityId } } ?: return@mirrorNow 28 | 29 | val sneaking = e.player.isSneaking 30 | if (MinecraftVersion.isUniversal) { 31 | val action = e.packet.read("action")!! 32 | when (action.javaClass.simpleName) { 33 | // ATTACK 34 | "d" -> if (sneaking) SHIFT_LEFT else LEFT 35 | else -> if (sneaking) SHIFT_RIGHT else RIGHT 36 | } 37 | } else { 38 | val type = when (e.packet.read("action").toString()) { 39 | "ATTACK" -> if (sneaking) SHIFT_LEFT else LEFT 40 | else -> if (sneaking) SHIFT_RIGHT else RIGHT 41 | } 42 | 43 | HologramInteractEvent(e.player, type, hologram).call() 44 | } 45 | } 46 | } 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/nms/packet/PacketArmorStandModify.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api.nms.packet 2 | 3 | /** 4 | * @author Arasple 5 | * @date 2021/1/25 12:20 6 | */ 7 | class PacketArmorStandModify( 8 | entityId: Int, 9 | val isInvisible: Boolean, 10 | val isGlowing: Boolean, 11 | val isSmall: Boolean, 12 | val hasArms: Boolean, 13 | val noBasePlate: Boolean, 14 | val isMarker: Boolean 15 | ) : PacketEntity(entityId) -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/nms/packet/PacketArmorStandName.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api.nms.packet 2 | 3 | /** 4 | * @author Arasple 5 | * @date 2021/1/25 12:20 6 | */ 7 | class PacketArmorStandName(entityId: Int, val isCustomNameVisible: Boolean, val name: String) : PacketEntity(entityId) -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/nms/packet/PacketEntity.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api.nms.packet 2 | 3 | import me.arasple.mc.trhologram.api.nms.NMS 4 | import org.bukkit.entity.Player 5 | import java.util.* 6 | 7 | /** 8 | * @author Arasple 9 | * @date 2020/12/4 21:22 10 | */ 11 | abstract class PacketEntity(val entityId: Int = -1, val uuid: UUID? = null) { 12 | 13 | fun send(player: Player) = NMS.INSTANCE.sendEntityPacket(player, this) 14 | 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/nms/packet/PacketEntityDestroy.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api.nms.packet 2 | 3 | /** 4 | * @author Arasple 5 | * @date 2021/1/25 12:20 6 | */ 7 | class PacketEntityDestroy(entityId: Int) : PacketEntity(entityId) -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/nms/packet/PacketEntityMount.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api.nms.packet 2 | 3 | /** 4 | * @author Arasple 5 | * @date 2020/12/4 21:22 6 | */ 7 | class PacketEntityMount(entityId: Int, val mount: IntArray) : PacketEntity(entityId) -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/nms/packet/PacketEntitySpawn.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api.nms.packet 2 | 3 | import me.arasple.mc.trhologram.api.Position 4 | 5 | /** 6 | * @author Arasple 7 | * @date 2021/1/25 12:20 8 | */ 9 | class PacketEntitySpawn(entityId: Int, val position: Position, val type: Boolean = true) : PacketEntity(entityId) -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/nms/packet/PacketEntityVelocity.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api.nms.packet 2 | 3 | import me.arasple.mc.trhologram.api.InstantVector 4 | 5 | class PacketEntityVelocity(entityId: Int, val x: Double, val y: Double, val z: Double): PacketEntity(entityId) -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/nms/packet/PacketEquipment.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api.nms.packet 2 | 3 | import org.bukkit.inventory.EquipmentSlot 4 | import org.bukkit.inventory.ItemStack 5 | 6 | /** 7 | * @author Arasple 8 | * @date 2020/12/4 21:22 9 | */ 10 | class PacketEquipment(entityId: Int, val slot: EquipmentSlot, val itemStack: ItemStack) : PacketEntity(entityId) -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/nms/packet/PacketHeadRotation.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api.nms.packet 2 | 3 | /** 4 | * @author Mical 5 | * @date 2021/8/28 23:16 6 | */ 7 | class PacketHeadRotation(entityId: Int, val headYaw: Int) : PacketEntity(entityId) -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/api/nms/packet/PacketItemModify.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.api.nms.packet 2 | 3 | import org.bukkit.inventory.ItemStack 4 | 5 | /** 6 | * @author Arasple 7 | * @date 2021/2/10 10:19 8 | */ 9 | class PacketItemModify( 10 | entityId: Int, 11 | val isInvisible: Boolean, 12 | val isGlowing: Boolean, 13 | val itemStack: ItemStack 14 | ) : PacketEntity(entityId) -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/action/ClickReaction.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.action 2 | 3 | import me.arasple.mc.trhologram.api.base.ClickHandler 4 | import me.arasple.mc.trhologram.api.event.HologramInteractEvent.Type 5 | import org.bukkit.entity.Player 6 | 7 | /** 8 | * @author Arasple 9 | * @date 2021/2/12 14:11 10 | */ 11 | @JvmInline 12 | value class ClickReaction(private val reactions: Map) : ClickHandler { 13 | 14 | override fun eval(player: Player, type: Type) { 15 | reactions[Type.ALL]?.eval(player) 16 | reactions[type]?.eval(player) 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/action/Reaction.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.action 2 | 3 | import me.arasple.mc.trhologram.api.TrHologramAPI 4 | import org.bukkit.entity.Player 5 | import java.util.concurrent.CompletableFuture 6 | 7 | /** 8 | * @author Arasple 9 | * @date 2021/2/11 16:31 10 | */ 11 | @JvmInline 12 | value class Reaction(private val kether: String) { 13 | 14 | constructor(kethers: List) : this(kethers.joinToString("\n")) 15 | 16 | fun eval(player: Player): CompletableFuture { 17 | return TrHologramAPI.eval(player, kether) 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/command/CommandExecutor.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.command 2 | 3 | import taboolib.common.platform.ProxyCommandSender 4 | import taboolib.common.platform.command.SimpleCommandBody 5 | import taboolib.module.chat.colored 6 | import taboolib.module.lang.asLangText 7 | 8 | /** 9 | * @author Mical 10 | * @date 2021/8/17 11:56 11 | */ 12 | interface CommandExecutor { 13 | 14 | val command: SimpleCommandBody 15 | 16 | fun description(sender: ProxyCommandSender): String = 17 | sender.asLangText("COMMAND-${name.uppercase()}-DESCRIPTION").colored() 18 | 19 | val name: String 20 | 21 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/command/CommandHandler.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.command 2 | 3 | import me.arasple.mc.trhologram.module.command.impl.* 4 | import org.bukkit.command.CommandSender 5 | import taboolib.common.platform.command.CommandBody 6 | import taboolib.common.platform.command.CommandHeader 7 | import taboolib.common.platform.command.mainCommand 8 | import taboolib.common.platform.command.subCommand 9 | import taboolib.common.platform.function.adaptCommandSender 10 | import taboolib.common.platform.function.pluginVersion 11 | import taboolib.module.chat.TellrawJson 12 | import taboolib.module.nms.MinecraftVersion 13 | import taboolib.platform.util.asLangText 14 | 15 | /** 16 | * @author Arasple 17 | * @author Score2 18 | * @date 2021/2/11 16:46 19 | */ 20 | @CommandHeader(name = "trhologram", aliases = ["hologram", "trhd", "hd"], permission = "trhologram.access") 21 | object CommandHandler { 22 | 23 | val sub = hashMapOf() 24 | 25 | @CommandBody(permission = "trhologram.command.list", optional = true) 26 | val list = CommandList.command 27 | 28 | @CommandBody(permission = "trhologram.command.teleport", optional = true) 29 | val teleport = CommandTeleport.command 30 | 31 | @CommandBody(permission = "trhologram.command.movehere", optional = true) 32 | val movehere = CommandMovehere.command 33 | 34 | @CommandBody(permission = "trhologram.command.create", optional = true) 35 | val create = CommandCreate.command 36 | 37 | @CommandBody(permission = "trhologram.command.delete", optional = true) 38 | val delete = CommandDelete.command 39 | 40 | @CommandBody(permission = "trhologram.command.reload", optional = true) 41 | val reload = CommandReload.command 42 | 43 | @CommandBody(permission = "trhologram.command.mirror", optional = true) 44 | val mirror = CommandMirror.command 45 | 46 | @CommandBody 47 | val help = subCommand { 48 | execute { sender, _, _ -> 49 | generateMainHelper(sender) 50 | } 51 | } 52 | 53 | @CommandBody 54 | val main = mainCommand { 55 | execute { sender, _, argument -> 56 | if (argument.isEmpty()) { 57 | generateMainHelper(sender) 58 | return@execute 59 | } 60 | sender.sendMessage("§8[§2Tr§aHologram§8] §cERROR §3| Args §6$argument §3not found.") 61 | TellrawJson() 62 | .append("§8[§2Tr§aHologram§8] §bINFO §3| Type ").append("§f/trhologram help") 63 | .hoverText("§f/trhologram help §8- §7more help...") 64 | .suggestCommand("/trhologram help") 65 | .append("§3 for help.") 66 | .sendTo(adaptCommandSender(sender)) 67 | } 68 | } 69 | 70 | private fun generateMainHelper(sender: CommandSender) { 71 | val proxySender = adaptCommandSender(sender) 72 | proxySender.sendMessage("") 73 | TellrawJson() 74 | .append(" ").append("§2TrHologram") 75 | .hoverText("§7TrHologram is modern and advanced Hologram-Plugin") 76 | .append(" ").append("§f${pluginVersion}") 77 | .hoverText( 78 | """ 79 | §7Plugin version: §2${pluginVersion} 80 | §7NMS version: §b${MinecraftVersion.minecraftVersion} 81 | """.trimIndent() 82 | ).sendTo(proxySender) 83 | proxySender.sendMessage("") 84 | TellrawJson() 85 | .append(" §7${sender.asLangText("Command-Help-Type")}: ").append("§f/trhologram §8[...]") 86 | .hoverText("§f/trhologram §8[...]") 87 | .suggestCommand("/trhologram ") 88 | .sendTo(proxySender) 89 | proxySender.sendMessage(" §7${sender.asLangText("Command-Help-Args")}:") 90 | 91 | fun displayArg(name: String, desc: String) { 92 | TellrawJson() 93 | .append(" §8- ").append("§f$name") 94 | .hoverText("§f/trhologram $name §8- §7$desc") 95 | .suggestCommand("/trhologram $name ") 96 | .sendTo(proxySender) 97 | proxySender.sendMessage(" §7$desc") 98 | } 99 | sub.forEach { (name, executor) -> displayArg(name, executor.description(proxySender)) } 100 | proxySender.sendMessage("") 101 | } 102 | 103 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/command/impl/CommandCreate.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.command.impl 2 | 3 | import me.arasple.mc.trhologram.module.command.CommandExecutor 4 | import me.arasple.mc.trhologram.module.command.CommandHandler 5 | import me.arasple.mc.trhologram.module.conf.HologramLoader 6 | import me.arasple.mc.trhologram.module.display.Hologram 7 | import org.bukkit.entity.Player 8 | import taboolib.common.platform.command.subCommand 9 | import taboolib.platform.util.sendLang 10 | 11 | /** 12 | * @author Arasple 13 | * @date 2021/2/12 14:52 14 | */ 15 | object CommandCreate : CommandExecutor { 16 | 17 | override val command = subCommand { 18 | dynamic { 19 | suggestion(uncheck = true) { _, _ -> 20 | Hologram.holograms.map { it.id } 21 | } 22 | execute { sender, _, argument -> 23 | val args = argument.split(" ") 24 | commandCreate(sender, args[0]) 25 | } 26 | } 27 | } 28 | 29 | override val name: String = "create" 30 | 31 | init { 32 | CommandHandler.sub[name] = this 33 | } 34 | 35 | private fun commandCreate(sender: Player, name: String) { 36 | val hologram = Hologram.findHologram { it.id.equals(name, true) } 37 | 38 | if (hologram != null) { 39 | sender.sendLang("Command-Existed") 40 | return 41 | } 42 | 43 | HologramLoader.create(name, sender.location.clone().add(0.0, 2.0, 0.0)).refreshVisibility(sender) 44 | sender.sendLang("Command-Created") 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/command/impl/CommandDelete.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.command.impl 2 | 3 | import me.arasple.mc.trhologram.module.command.CommandExecutor 4 | import me.arasple.mc.trhologram.module.command.CommandHandler 5 | import me.arasple.mc.trhologram.module.display.Hologram 6 | import org.bukkit.entity.Player 7 | import taboolib.common.platform.command.subCommand 8 | import taboolib.platform.util.sendLang 9 | import java.io.File 10 | 11 | /** 12 | * @author Arasple 13 | * @date 2021/2/12 17:41 14 | */ 15 | object CommandDelete : CommandExecutor { 16 | 17 | override val command = subCommand { 18 | dynamic { 19 | suggestion(uncheck = true) { _, _ -> 20 | Hologram.holograms.map { it.id } 21 | } 22 | execute { sender, _, argument -> 23 | val args = argument.split(" ") 24 | commandDelete(sender, args[0]) 25 | } 26 | } 27 | } 28 | 29 | override val name: String = "delete" 30 | 31 | init { 32 | CommandHandler.sub[name] = this 33 | } 34 | 35 | private fun commandDelete(sender: Player, name: String) { 36 | val hologram = Hologram.findHologram { it.id.equals(name, true) } 37 | 38 | if (hologram == null) { 39 | sender.sendLang("Command-Not-Exists", name) 40 | return 41 | } 42 | hologram.loadedPath?.let { File(it).delete() } 43 | hologram.destroy() 44 | Hologram.holograms.remove(hologram) 45 | sender.sendLang("Command-Deleted", name) 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/command/impl/CommandList.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.command.impl 2 | 3 | import me.arasple.mc.trhologram.module.command.CommandExecutor 4 | import me.arasple.mc.trhologram.module.command.CommandHandler 5 | import me.arasple.mc.trhologram.module.display.Hologram 6 | import taboolib.common.platform.ProxyCommandSender 7 | import taboolib.common.platform.command.subCommand 8 | import taboolib.module.lang.sendLang 9 | 10 | /** 11 | * @author Arasple 12 | * @date 2021/2/12 17:59 13 | */ 14 | object CommandList : CommandExecutor { 15 | 16 | override val command = subCommand { 17 | dynamic(optional = true) { 18 | execute { sender, _, argument -> 19 | commandList(sender, argument) 20 | } 21 | } 22 | execute { sender, _, _ -> 23 | commandList(sender, null) 24 | } 25 | } 26 | 27 | override val name: String = "list" 28 | 29 | init { 30 | CommandHandler.sub[name] = this 31 | } 32 | 33 | private fun commandList(sender: ProxyCommandSender, filter: String?) { 34 | val holograms = Hologram.holograms.filter { filter == null || it.id.contains(filter, true) }.sortedBy { it.id } 35 | 36 | if (holograms.isEmpty()) { 37 | sender.sendLang("Command-List-Error", filter ?: "*") 38 | } else { 39 | sender.sendLang("Command-List-Header", holograms.size, filter ?: "*") 40 | 41 | holograms.forEach { 42 | sender.sendLang( 43 | "Command-List-Format", 44 | it.id, 45 | it.components.size 46 | ) 47 | } 48 | } 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/command/impl/CommandMirror.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.command.impl 2 | 3 | import me.arasple.mc.trhologram.module.command.CommandExecutor 4 | import me.arasple.mc.trhologram.module.command.CommandHandler 5 | import org.bukkit.Bukkit 6 | import taboolib.common.platform.ProxyCommandSender 7 | import taboolib.common.platform.command.subCommand 8 | import taboolib.common.platform.function.submit 9 | import taboolib.common5.Mirror 10 | 11 | /** 12 | * @author Arasple 13 | * @date 2021/2/12 18:45 14 | */ 15 | object CommandMirror : CommandExecutor { 16 | 17 | override val command = subCommand { 18 | execute { sender, _, _ -> 19 | commandMirror(sender) 20 | } 21 | } 22 | 23 | override val name: String = "mirror" 24 | 25 | init { 26 | CommandHandler.sub[name] = this 27 | } 28 | 29 | private fun commandMirror(sender: ProxyCommandSender) { 30 | submit(async = !Bukkit.isPrimaryThread()) { 31 | Mirror.report(sender) { 32 | childFormat = "§8 {0}§7{1} §2[{3} ms] §7{4}%" 33 | parentFormat = "§8 §8{0}§7{1} §8[{3} ms] §7{4}%" 34 | }.run { 35 | sender.sendMessage("\n§2§lHologram §a§l§nPerformance Mirror\n§r") 36 | } 37 | } 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/command/impl/CommandMovehere.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.command.impl 2 | 3 | import me.arasple.mc.trhologram.module.command.CommandExecutor 4 | import me.arasple.mc.trhologram.module.command.CommandHandler 5 | import me.arasple.mc.trhologram.module.display.Hologram 6 | import me.arasple.mc.trhologram.module.editor.Editor 7 | import me.arasple.mc.trhologram.util.parseString 8 | import org.bukkit.entity.Player 9 | import taboolib.common.platform.command.subCommand 10 | import taboolib.platform.util.sendLang 11 | 12 | /** 13 | * @author Arasple 14 | * @date 2021/2/12 20:45 15 | */ 16 | object CommandMovehere : CommandExecutor { 17 | 18 | override val command = subCommand { 19 | dynamic { 20 | suggestion { _, _ -> 21 | Hologram.holograms.map { it.id } 22 | } 23 | execute { sender, _, argument -> 24 | val args = argument.split(" ") 25 | commandMoveHere(sender, args[0]) 26 | } 27 | } 28 | } 29 | 30 | override val name: String = "movehere" 31 | 32 | init { 33 | CommandHandler.sub[name] = this 34 | } 35 | 36 | private fun commandMoveHere(sender: Player, name: String) { 37 | val hologram = Hologram.findHologram { it.id.equals(name, true) } 38 | 39 | if (hologram == null) { 40 | sender.sendLang("Command-Not-Exists", name) 41 | return 42 | } 43 | 44 | Editor.modify(hologram) { 45 | it["Location"] = sender.location.parseString() 46 | } 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/command/impl/CommandReload.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.command.impl 2 | 3 | import me.arasple.mc.trhologram.api.Settings 4 | import me.arasple.mc.trhologram.module.command.CommandExecutor 5 | import me.arasple.mc.trhologram.module.command.CommandHandler 6 | import me.arasple.mc.trhologram.module.conf.HologramLoader 7 | import me.arasple.mc.trhologram.module.display.Hologram 8 | import org.bukkit.Bukkit 9 | import org.bukkit.command.CommandSender 10 | import taboolib.common.platform.command.subCommand 11 | import taboolib.platform.util.sendLang 12 | import java.io.File 13 | 14 | /** 15 | * @author Arasple 16 | * @date 2021/2/12 17:43 17 | */ 18 | object CommandReload : CommandExecutor { 19 | 20 | override val command = subCommand { 21 | dynamic(optional = true) { 22 | suggestion { _, _ -> 23 | Hologram.holograms.map { it.id } 24 | } 25 | execute { sender, _, argument -> 26 | val args = argument.split(" ") 27 | commandReload(sender, args[0]) 28 | } 29 | } 30 | execute { sender, _, _ -> 31 | commandReload(sender, null) 32 | } 33 | } 34 | 35 | override val name: String = "reload" 36 | 37 | init { 38 | CommandHandler.sub[name] = this 39 | } 40 | 41 | private fun commandReload(sender: CommandSender, name: String?) { 42 | Settings.onSettingsReload() 43 | val hologram = Hologram.findHologram { it.id.equals(name, true) } 44 | 45 | if (hologram != null) { 46 | hologram.destroy() 47 | Hologram.holograms.remove(hologram) 48 | 49 | hologram.loadedPath?.let { 50 | HologramLoader.load(File(it)) 51 | sender.sendLang("Command-Reload", hologram.id) 52 | } 53 | } else { 54 | HologramLoader.load(sender) 55 | Bukkit.getOnlinePlayers().forEach(Hologram::refreshAll) 56 | } 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/command/impl/CommandTeleport.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.command.impl 2 | 3 | import me.arasple.mc.trhologram.module.command.CommandExecutor 4 | import me.arasple.mc.trhologram.module.command.CommandHandler 5 | import me.arasple.mc.trhologram.module.display.Hologram 6 | import org.bukkit.entity.Player 7 | import taboolib.common.platform.command.subCommand 8 | import taboolib.platform.util.sendLang 9 | 10 | /** 11 | * @author Arasple 12 | * @date 2021/2/13 10:43 13 | */ 14 | object CommandTeleport : CommandExecutor { 15 | 16 | override val command = subCommand { 17 | dynamic { 18 | suggestion { _, _ -> 19 | Hologram.holograms.map { it.id } 20 | } 21 | execute { sender, _, argument -> 22 | val args = argument.split(" ") 23 | commandTeleport(sender, args[0]) 24 | } 25 | } 26 | } 27 | 28 | override val name: String = "teleport" 29 | 30 | init { 31 | CommandHandler.sub[name] = this 32 | } 33 | 34 | private fun commandTeleport(sender: Player, name: String) { 35 | val hologram = Hologram.findHologram { it.id.equals(name, true) } 36 | 37 | if (hologram == null) { 38 | sender.sendLang("Command-Not-Exists", name) 39 | return 40 | } 41 | 42 | sender.teleport(hologram.position.toLocation()) 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/condition/Condition.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.condition 2 | 3 | import me.arasple.mc.trhologram.api.TrHologramAPI 4 | import me.arasple.mc.trhologram.api.base.BaseCondition 5 | import org.bukkit.entity.Player 6 | import taboolib.common5.Coerce 7 | import java.util.concurrent.CompletableFuture 8 | 9 | /** 10 | * @author Arasple 11 | * @date 2021/2/10 11:02 12 | */ 13 | @JvmInline 14 | value class Condition(private val expression: String) : BaseCondition { 15 | 16 | override fun eval(player: Player): CompletableFuture { 17 | return if (expression.isEmpty()) CompletableFuture.completedFuture(true) 18 | else eval(player, expression) 19 | } 20 | 21 | override fun toString(): String { 22 | return expression 23 | } 24 | 25 | companion object { 26 | 27 | fun eval(player: Player, script: String): CompletableFuture { 28 | return TrHologramAPI.eval(player, script).thenApply { 29 | Coerce.toBoolean(it) 30 | } 31 | } 32 | 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/conf/HologramLoader.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.conf 2 | 3 | import me.arasple.mc.trhologram.TrHologram 4 | import me.arasple.mc.trhologram.api.Position 5 | import me.arasple.mc.trhologram.api.Settings 6 | import me.arasple.mc.trhologram.api.TrHologramAPI 7 | import me.arasple.mc.trhologram.api.event.HologramInteractEvent.Type 8 | import me.arasple.mc.trhologram.api.hologram.HologramComponent 9 | import me.arasple.mc.trhologram.api.hologram.ItemHologram 10 | import me.arasple.mc.trhologram.api.hologram.TextHologram 11 | import me.arasple.mc.trhologram.module.action.ClickReaction 12 | import me.arasple.mc.trhologram.module.action.Reaction 13 | import me.arasple.mc.trhologram.module.condition.Condition 14 | import me.arasple.mc.trhologram.module.display.Hologram 15 | import me.arasple.mc.trhologram.module.display.texture.Texture 16 | import me.arasple.mc.trhologram.module.display.texture.TrMenuTexture 17 | import me.arasple.mc.trhologram.module.hook.HookPlugin 18 | import me.arasple.mc.trhologram.util.parseLocation 19 | import me.arasple.mc.trhologram.util.parseString 20 | import org.bukkit.Location 21 | import org.bukkit.command.CommandSender 22 | import org.bukkit.configuration.file.YamlConfiguration 23 | import taboolib.common.io.newFile 24 | import taboolib.common.platform.function.releaseResourceFile 25 | import taboolib.platform.util.sendLang 26 | import java.io.File 27 | import java.nio.charset.StandardCharsets 28 | import kotlin.system.measureNanoTime 29 | 30 | /** 31 | * @author Arasple 32 | * @date 2021/2/11 10:03 33 | */ 34 | object HologramLoader { 35 | 36 | private val folder by lazy { 37 | Hologram.clear() 38 | val folder = File(TrHologram.plugin.dataFolder, "holograms") 39 | 40 | if (!folder.exists()) { 41 | releaseResourceFile("holograms/Demo.yml", true) 42 | } 43 | 44 | folder 45 | } 46 | 47 | fun create(id: String, location: Location): Hologram { 48 | val hologram = 49 | """ 50 | Location: ${location.parseString()} 51 | 52 | Contents: 53 | - '&7Hello, &2Tr&aHologram&7!' 54 | - '{item: emerald}' 55 | - '&3Nice to meet you, &a{{player name}}' 56 | - '&3Author: &aArasple{offset=0.35}' 57 | 58 | Actions: 59 | All: 'tell color *"&bHi, you just clicked this hologram"' 60 | """.trimIndent() 61 | 62 | newFile(folder, "$id.yml").also { 63 | it.writeText(hologram, StandardCharsets.UTF_8) 64 | return load(it) 65 | } 66 | } 67 | 68 | fun load(sender: CommandSender) { 69 | measureNanoTime { load() }.div(1000000.0).let { 70 | sender.sendLang("Hologram-Loaded", Hologram.holograms.size, it) 71 | } 72 | } 73 | 74 | fun load(): Int { 75 | Hologram.clear() 76 | TrHologramAPI.resetIndex() 77 | 78 | filterHologramFiles(folder).forEach { load(it) } 79 | Settings.INSTANCE.loadPaths.flatMap { filterHologramFiles(File(it)) }.forEach { load(it) } 80 | 81 | return Hologram.holograms.size 82 | } 83 | 84 | fun load(file: File): Hologram { 85 | val id = file.nameWithoutExtension 86 | val conf = YamlConfiguration.loadConfiguration(file) 87 | val location = conf.getString("Location")?.parseLocation() ?: throw Exception("No valid location") 88 | val lineSpacing = conf.getDouble("Options.Line-Spacing", Settings.INSTANCE.lineSpacing) 89 | val viewDistance = 90 | conf.getDouble("Options.View-Distance", Settings.INSTANCE.viewDistance).coerceAtMost(50.0) 91 | val viewCondition = conf.getString("Options.View-Condition", Settings.INSTANCE.viewCondition).let { 92 | if (it == null || it.isBlank()) null 93 | else Condition(it) 94 | } 95 | val refreshCondition = conf.getLong("Options.Refresh-Condition", Settings.INSTANCE.refershCondition) 96 | val contents = conf.getStringList("Contents").ifEmpty { listOf("TrHologram") } 97 | val actions = mutableMapOf() 98 | 99 | val all = conf.get("Actions.All").toStringList() 100 | val left = conf.get("Actions.Left").toStringList() 101 | val shiftLeft = conf.get("Actions.Shift_Left").toStringList() 102 | val right = conf.get("Actions.Right").toStringList() 103 | val shiftRight = conf.get("Actions.Shift_Right").toStringList() 104 | 105 | if (all.isNotEmpty()) actions[Type.ALL] = Reaction(all) 106 | if (left.isNotEmpty()) actions[Type.LEFT] = Reaction(left) 107 | if (shiftLeft.isNotEmpty()) actions[Type.SHIFT_LEFT] = Reaction(shiftLeft) 108 | if (right.isNotEmpty()) actions[Type.RIGHT] = Reaction(right) 109 | if (shiftRight.isNotEmpty()) actions[Type.SHIFT_RIGHT] = Reaction(shiftRight) 110 | 111 | val holograms = mutableListOf() 112 | var position = Position.fromLocation(location).clone(y = -1.25) 113 | 114 | contents.forEach { 115 | val (line, options) = Property.from(it) 116 | val itemDisplay = options[Property.ITEM] 117 | val update = options[Property.UPDATE]?.toLongOrNull() ?: -1 118 | val offset = options[Property.OFFSET]?.toDoubleOrNull() ?: lineSpacing 119 | val isItem = itemDisplay != null 120 | val fix = if (isItem) 0.5 else 0.0 121 | 122 | position = position.clone(y = -(fix + offset)) 123 | 124 | val hologram = when { 125 | isItem ->{ 126 | val texture = if (!HookPlugin.getTrMenu().isHooked) Texture.createTexture(itemDisplay!!) else TrMenuTexture(itemDisplay!!) 127 | ItemHologram(texture, position, update) 128 | } 129 | line.isBlank() -> null 130 | else -> TextHologram(line, position, update) 131 | } 132 | 133 | if (hologram != null) holograms.add(hologram) 134 | } 135 | 136 | // Loaded & Add 137 | val hologram = Hologram( 138 | id, 139 | Position.fromLocation(location), 140 | viewDistance, 141 | viewCondition, 142 | refreshCondition, 143 | holograms, 144 | ClickReaction(actions), 145 | file.absolutePath 146 | ) 147 | Hologram.holograms.add(hologram) 148 | return hologram 149 | } 150 | 151 | private fun filterHologramFiles(file: File): List { 152 | return mutableListOf().run { 153 | if (file.isDirectory) { 154 | file.listFiles()?.forEach { 155 | addAll(filterHologramFiles(it)) 156 | } 157 | } else if (!file.name.startsWith("#") && file.extension.equals("yml", true)) { 158 | add(file) 159 | } 160 | this 161 | } 162 | } 163 | 164 | @Suppress("UNCHECKED_CAST") 165 | private fun Any?.toStringList(): List { 166 | return when (this) { 167 | is List<*> -> this as List 168 | else -> listOf(toString()) 169 | } 170 | } 171 | 172 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/conf/Property.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.conf 2 | 3 | /** 4 | * @author Arasple 5 | * @date 2021/2/11 17:26 6 | */ 7 | enum class Property(val regex: Regex, val group: Int) { 8 | 9 | OFFSET("\\{offset?[:=] ?([0-9.]+)}"), 10 | 11 | UPDATE("\\{(update|refresh)?[:=] ?([0-9]+)}", 2), 12 | 13 | ITEM("\\{items?[:=] ?(.+)}"); 14 | 15 | constructor(regex: String, group: Int = 1) : this("(?i)$regex".toRegex(), group) 16 | 17 | companion object { 18 | 19 | fun from(string: String): Pair> { 20 | var content = string 21 | val map = values().mapNotNull { 22 | val value = it.regex.find(content)?.groupValues?.get(it.group) 23 | value?.let { v -> 24 | content = content.replace(it.regex, "") 25 | it to v 26 | } 27 | }.toMap() 28 | 29 | return content to map 30 | } 31 | 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/display/Hologram.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.display 2 | 3 | import me.arasple.mc.trhologram.api.Position 4 | import me.arasple.mc.trhologram.api.TrHologramAPI 5 | import me.arasple.mc.trhologram.api.base.BaseCondition 6 | import me.arasple.mc.trhologram.api.base.ClickHandler 7 | import me.arasple.mc.trhologram.api.hologram.HologramBuilder 8 | import me.arasple.mc.trhologram.api.hologram.HologramComponent 9 | import me.arasple.mc.trhologram.api.hologram.ItemHologram 10 | import me.arasple.mc.trhologram.api.hologram.TextHologram 11 | import org.bukkit.Bukkit 12 | import org.bukkit.entity.Player 13 | import taboolib.common.platform.function.submit 14 | import taboolib.common.platform.service.PlatformExecutor 15 | import taboolib.common5.mirrorNow 16 | 17 | /** 18 | * @author Arasple 19 | * @date 2021/2/11 10:06 20 | */ 21 | class Hologram( 22 | val id: String, 23 | val position: Position, 24 | val viewDistance: Double = 0.0, 25 | val viewCondition: BaseCondition? = null, 26 | val refreshCondition: Long = -1, 27 | val components: List, 28 | val reactions: ClickHandler, 29 | val loadedPath: String? = null 30 | ) { 31 | 32 | companion object { 33 | 34 | internal val externalHolograms = mutableSetOf() 35 | internal val holograms = mutableSetOf() 36 | 37 | fun findHologram(predicate: (Hologram) -> Boolean): Hologram? { 38 | return holograms.find(predicate) ?: externalHolograms.find(predicate) 39 | } 40 | 41 | fun refreshAll(player: Player) { 42 | mirrorNow("Hologram:Event:Refresh") { 43 | externalHolograms.filter { it.sameWorld(player) }.forEach { 44 | it.refreshVisibility(player) 45 | } 46 | holograms.filter { it.sameWorld(player) }.forEach { 47 | it.refreshVisibility(player) 48 | } 49 | } 50 | } 51 | 52 | fun clear() { 53 | holograms.removeIf { 54 | it.destroy() 55 | true 56 | } 57 | } 58 | 59 | fun destroyAll(player: Player) { 60 | holograms.forEach { 61 | it.destroy(player) 62 | } 63 | } 64 | 65 | } 66 | 67 | private val visibleByCondition = mutableMapOf() 68 | private val viewers = mutableSetOf() 69 | private var refreshTask: PlatformExecutor.PlatformTask? = null 70 | 71 | init { 72 | deployment() 73 | } 74 | 75 | private fun deployment() { 76 | refreshTask?.cancel() 77 | if (refreshCondition > 0) refreshTask = 78 | submit(delay = refreshCondition, period = refreshCondition, async = true) { 79 | viewers.removeIf { 80 | val player = Bukkit.getPlayerExact(it) 81 | player == null || !player.isOnline 82 | } 83 | Bukkit.getOnlinePlayers().filter { visibleByDistance(it) }.forEach { 84 | refreshCondition(it) 85 | refreshVisibility(it) 86 | } 87 | } 88 | } 89 | 90 | fun refreshVisibility(player: Player) { 91 | visible(player, visibleByDistance(player) && visibleByCondition(player)) 92 | } 93 | 94 | fun sameWorld(player: Player): Boolean { 95 | return position.world == player.world 96 | } 97 | 98 | fun getTextLine(index: Int): TextHologram { 99 | return components[index] as TextHologram 100 | } 101 | 102 | fun getItemLine(index: Int): ItemHologram { 103 | return components[index] as ItemHologram 104 | } 105 | 106 | private fun visible(player: Player, visible: Boolean) { 107 | if (visible) { 108 | if (viewers.add(player.name)) components.forEach { it.spawn(player) } 109 | } else { 110 | if (viewers.remove(player.name)) components.forEach { it.destroy(player) } 111 | } 112 | } 113 | 114 | private fun visibleByDistance(player: Player): Boolean { 115 | return player.world == position.world && position.distance(player.location) <= viewDistance 116 | } 117 | 118 | private fun visibleByCondition(player: Player): Boolean { 119 | return if (visibleByCondition.containsKey(player.name)) { 120 | visibleByCondition[player.name] ?: true 121 | } else { 122 | refreshCondition(player) 123 | true 124 | } 125 | } 126 | 127 | private fun refreshCondition(player: Player) { 128 | viewCondition?.eval(player)?.thenApply { 129 | visibleByCondition[player.name] = it 130 | } 131 | } 132 | 133 | fun forViewers(viewer: (Player) -> Unit) { 134 | viewers.mapNotNull { Bukkit.getPlayerExact(it) }.forEach(viewer) 135 | } 136 | 137 | fun destroy(player: Player) { 138 | viewers.remove(player.name) 139 | components.forEach { 140 | it.viewers.remove(player.name) 141 | } 142 | } 143 | 144 | fun destroy() { 145 | refreshTask?.cancel() 146 | components.forEach(HologramComponent::destroy) 147 | externalHolograms.remove(this) 148 | } 149 | 150 | /** 151 | * WARNING: This will make all custom interspace invalid 152 | */ 153 | fun rebuild(): HologramBuilder { 154 | destroy() 155 | 156 | return TrHologramAPI.builder(position.toLocation()).run { 157 | viewDistance(viewDistance) 158 | refreshCondition(refreshCondition) 159 | click(reactions) 160 | 161 | viewCondition?.let { viewCondition(it) } 162 | components.forEach { 163 | when (it) { 164 | is TextHologram -> append(it.text, it.period0, onTick = it.onTick) 165 | is ItemHologram -> append(it.display, it.period0, onTick = it.onTick) 166 | } 167 | } 168 | 169 | this 170 | } 171 | } 172 | 173 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/display/texture/Texture.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.display.texture 2 | 3 | import me.arasple.mc.trhologram.api.base.ItemTexture 4 | import me.arasple.mc.trhologram.util.Heads 5 | import me.arasple.mc.trhologram.util.ItemHelper 6 | import me.arasple.mc.trhologram.util.containsPlaceholder 7 | import me.arasple.mc.trhologram.util.parseString 8 | import org.bukkit.Color 9 | import org.bukkit.Material 10 | import org.bukkit.entity.Player 11 | import org.bukkit.inventory.ItemStack 12 | import org.bukkit.inventory.meta.LeatherArmorMeta 13 | import org.bukkit.inventory.meta.SkullMeta 14 | import taboolib.common.util.Strings.similarDegree 15 | import taboolib.library.xseries.XMaterial 16 | import taboolib.module.nms.MinecraftVersion 17 | import taboolib.platform.util.ItemBuilder 18 | import taboolib.platform.util.buildItem 19 | import kotlin.math.min 20 | 21 | /** 22 | * @author Arasple 23 | * @date 2021/1/24 11:50 24 | */ 25 | class Texture( 26 | val raw: String, 27 | val type: TextureType, 28 | val texture: String, 29 | val dynamic: Boolean, 30 | val static: ItemStack?, 31 | val meta: Map 32 | ) : ItemTexture { 33 | 34 | override fun generate(player: Player): ItemStack { 35 | if (static != null) return static 36 | val temp = if (dynamic) player.parseString(texture) else texture 37 | 38 | val itemStack = when (type) { 39 | TextureType.NORMAL -> parseMaterial(temp) 40 | TextureType.HEAD -> Heads.getHead(temp) 41 | TextureType.RAW -> ItemHelper.fromJson(temp) 42 | } 43 | 44 | if (itemStack != null) { 45 | val itemMeta = itemStack.itemMeta 46 | meta.forEach { (meta, metaValue) -> 47 | val value = player.parseString(metaValue) 48 | when (meta) { 49 | TextureMeta.DATA_VALUE -> itemStack.durability = value.toShortOrNull() ?: 0 50 | TextureMeta.MODEL_DATA -> { 51 | itemMeta?.setCustomModelData(value.toInt()).also { itemStack.itemMeta = itemMeta } 52 | } 53 | TextureMeta.LEATHER_DYE -> if (itemMeta is LeatherArmorMeta) { 54 | itemMeta.setColor(serializeColor(value)).also { itemStack.itemMeta = itemMeta } 55 | } 56 | } 57 | } 58 | } 59 | 60 | return itemStack ?: FALL_BACK 61 | } 62 | 63 | companion object { 64 | 65 | val FALL_BACK = ItemStack(Material.BEDROCK) 66 | 67 | fun createTexture(itemStack: ItemStack): Texture { 68 | val material = itemStack.type.name.lowercase().replace("_", " ") 69 | val itemMeta = itemStack.itemMeta 70 | 71 | // Head Meta 72 | val texture = let { 73 | if (itemMeta is SkullMeta) { 74 | return@let if (itemMeta.hasOwner()) "head:${itemMeta.owningPlayer?.name}" 75 | else "head:${Heads.seekTexture(itemStack)}" 76 | } 77 | // Model Data 78 | if (MinecraftVersion.majorLegacy >= 11400 && itemMeta != null && itemMeta.hasCustomModelData()) { 79 | return@let "$material{model-data:${itemMeta.customModelData}}" 80 | } 81 | // Leather 82 | if (itemMeta is LeatherArmorMeta) { 83 | return@let "$material{dye:${deserializeColor(itemMeta.color)}}" 84 | } 85 | return@let material 86 | } 87 | 88 | return createTexture(texture) 89 | } 90 | 91 | 92 | fun createTexture(raw: String): Texture { 93 | var type = TextureType.NORMAL 94 | var static: ItemStack? = null 95 | var texture = raw 96 | val meta = mutableMapOf() 97 | 98 | TextureMeta.values().forEach { 99 | it.regex.find(raw)?.groupValues?.get(1)?.also { value -> 100 | meta[it] = value 101 | texture = texture.replace(it.regex, "") 102 | } 103 | } 104 | 105 | TextureType.values().filter { it.group != -1 }.forEach { 106 | it.regex.find(texture)?.groupValues?.get(it.group)?.also { value -> 107 | type = it 108 | texture = value 109 | } 110 | } 111 | 112 | val dynamic = texture.containsPlaceholder() 113 | if (type == TextureType.NORMAL) { 114 | if (texture.startsWith("{")) { 115 | type = TextureType.RAW 116 | if (!dynamic) static = ItemHelper.fromJson(texture)!! 117 | } 118 | } 119 | return Texture(raw, type, texture, dynamic, static, meta) 120 | } 121 | 122 | private fun parseMaterial(material: String): ItemStack { 123 | val split = material.split(":", limit = 2) 124 | val data = split.getOrNull(1)?.toIntOrNull() ?: 0 125 | val id = split[0].toIntOrNull() ?: split[0].uppercase().replace("[ _]".toRegex(), "_") 126 | 127 | return buildItem(XMaterial.matchXMaterial(FALL_BACK)) { 128 | var rawMaterial = id 129 | 130 | if (id is Int) { 131 | XMaterial.matchXMaterial(id, 0).let { 132 | if (it.isPresent) { 133 | this.material = it.get().parseMaterial()!! 134 | this.damage = data 135 | } else { 136 | XMaterial.STONE 137 | } 138 | } 139 | 140 | } else { 141 | val name = id.toString() 142 | try { 143 | this.material = XMaterial.valueOf(name).parseMaterial()!! 144 | } catch (e: Throwable) { 145 | val xMaterial = 146 | XMaterial.values().find { it.name.equals(name, true) } 147 | ?: XMaterial.values() 148 | .find { it -> it.legacy.any { it == name } } 149 | ?: XMaterial.values() 150 | .maxByOrNull { similarDegree(name, it.name) } 151 | xMaterial?.parseItem() ?: FALL_BACK 152 | } 153 | } 154 | } 155 | } 156 | 157 | fun serializeColor(color: String): Color { 158 | val rgb = color.split(",").toTypedArray() 159 | if (rgb.size == 3) { 160 | val r = min(rgb[0].toIntOrNull() ?: 0, 255) 161 | val g = min(rgb[1].toIntOrNull() ?: 0, 255) 162 | val b = min(rgb[2].toIntOrNull() ?: 0, 255) 163 | return Color.fromRGB(r, g, b) 164 | } 165 | return Color.BLACK 166 | } 167 | 168 | fun deserializeColor(color: Color): String { 169 | return "${color.red},${color.green},${color.blue}" 170 | } 171 | 172 | } 173 | 174 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/display/texture/TextureMeta.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.display.texture 2 | 3 | /** 4 | * @author Arasple 5 | * @date 2021/1/24 12:15 6 | * 7 | */ 8 | enum class TextureMeta(val regex: Regex) { 9 | 10 | DATA_VALUE("(?i)[<{]data-?value[:=](\\d+?)[>}]"), 11 | 12 | MODEL_DATA("(?i)[<{]model-?data[:=](\\d+?)[>}]"), 13 | 14 | LEATHER_DYE("(?i)[<{]dye[:=](\\d{3},\\d{3},\\d{3})[>}]"); 15 | 16 | constructor(regex: String) : this(regex.toRegex()) 17 | 18 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/display/texture/TextureType.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.display.texture 2 | 3 | /** 4 | * @author Arasple 5 | * @date 2021/1/24 11:55 6 | */ 7 | enum class TextureType(val regex: Regex, val group: Int) { 8 | 9 | /** 10 | * Red Stained Glass Pane 11 | * Wool:3 (Data-Value does not support variables here) 12 | */ 13 | NORMAL, 14 | 15 | /** 16 | * {identifier}:{parsedArgument} 17 | * 18 | * e.g. 19 | * head:%player_name% 20 | * head:BlackSky 21 | * 22 | * if the server runs SkinsRestorer and in offline mode, the player head will automatically support 23 | * if argument's length is larger than 64, then identified as custom textued skull 24 | * 25 | * head:eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNDRmNDUyZDk5OGVhYmFjNDY0MmM2YjBmZTVhOGY0ZTJlNjczZWRjYWUyYTZkZmQ5ZTZhMmU4NmU3ODZlZGFjMCJ9fX0= 26 | * head:44f452d998eabac4642c6b0fe5a8f4e2e673edcae2a6dfd9e6a2e86e786edac0 27 | */ 28 | HEAD("[<{]?(player|custom|textured?)?-?(head|skull)[:=]([\\w.%{}]+)[>}]?", 3), 29 | 30 | /** 31 | * {json Content} 32 | * 33 | * Will be parsed if there contains placeholders 34 | */ 35 | RAW; 36 | 37 | constructor(regex: String = "", group: Int = -1) : this(regex.toRegex(), group) 38 | 39 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/display/texture/TrMenuTexture.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.display.texture 2 | 3 | import me.arasple.mc.trhologram.api.base.ItemTexture 4 | import me.arasple.mc.trmenu.module.display.MenuSession 5 | import org.bukkit.entity.Player 6 | import org.bukkit.inventory.ItemStack 7 | 8 | /** 9 | * @author Arasple 10 | * @date 2021/2/12 21:11 11 | */ 12 | class TrMenuTexture(texture: String) : ItemTexture { 13 | 14 | private val texture = me.arasple.mc.trmenu.module.display.texture.Texture.createTexture(texture) 15 | 16 | override fun generate(player: Player): ItemStack { 17 | return texture.generate(MenuSession.getSession(player)) 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/editor/Editor.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.editor 2 | 3 | import me.arasple.mc.trhologram.module.conf.HologramLoader 4 | import me.arasple.mc.trhologram.module.display.Hologram 5 | import org.bukkit.Bukkit 6 | import org.bukkit.configuration.file.YamlConfiguration 7 | import java.io.File 8 | 9 | /** 10 | * @author Arasple 11 | * @date 2021/2/12 17:09 12 | */ 13 | object Editor { 14 | 15 | fun modify(hologram: Hologram, modify: (YamlConfiguration) -> Unit) { 16 | val file = File(hologram.loadedPath!!) 17 | val conf = YamlConfiguration.loadConfiguration(file) 18 | modify(conf) 19 | conf.save(file) 20 | hologram.destroy() 21 | Hologram.holograms.remove(hologram) 22 | HologramLoader.load(file).run { 23 | Bukkit.getOnlinePlayers().forEach(this::refreshVisibility) 24 | } 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/hook/HookAbstract.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.hook 2 | 3 | import org.bukkit.Bukkit 4 | import org.bukkit.plugin.Plugin 5 | import taboolib.common.platform.function.console 6 | import taboolib.module.lang.sendLang 7 | 8 | /** 9 | * @author Mical 10 | * @date 2021/8/19 21:00 11 | */ 12 | abstract class HookAbstract { 13 | 14 | open val name by lazy { getPluginName() } 15 | 16 | val plugin: Plugin? by lazy { 17 | Bukkit.getPluginManager().getPlugin(name) 18 | } 19 | 20 | open val isHooked by lazy { 21 | plugin != null && plugin!!.isEnabled 22 | } 23 | 24 | open fun getPluginName(): String { 25 | return javaClass.simpleName.substring(4) 26 | } 27 | 28 | open fun checkHooked(): Boolean { 29 | return if (isHooked) true else false.also { reportAbuse() } 30 | } 31 | 32 | fun reportAbuse() { 33 | console().sendLang("Plugin-Dependency-Abuse", name) 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/hook/HookPlugin.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.hook 2 | 3 | import me.arasple.mc.trhologram.module.hook.impl.HookSkinsRestorer 4 | import me.arasple.mc.trhologram.module.hook.impl.HookTrMenu 5 | import taboolib.common.LifeCycle 6 | import taboolib.common.platform.Awake 7 | import taboolib.common.platform.function.console 8 | import taboolib.module.lang.sendLang 9 | 10 | /** 11 | * @author Mical 12 | * @date 2021/8/19 21:00 13 | */ 14 | object HookPlugin { 15 | 16 | @Awake(LifeCycle.ENABLE) 17 | fun printInfo() { 18 | registry.filter { it.isHooked }.forEach { 19 | console().sendLang("Plugin-Dependency-Hooked", it.name) 20 | } 21 | } 22 | 23 | private val registry: Array = arrayOf( 24 | HookSkinsRestorer(), 25 | HookTrMenu() 26 | ) 27 | 28 | fun getSkinsRestorer(): HookSkinsRestorer { 29 | return registry[0] as HookSkinsRestorer 30 | } 31 | 32 | fun getTrMenu(): HookTrMenu { 33 | return registry[1] as HookTrMenu 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/hook/impl/HookSkinsRestorer.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.hook.impl 2 | 3 | import com.mojang.authlib.properties.Property 4 | import me.arasple.mc.trhologram.module.hook.HookAbstract 5 | import net.skinsrestorer.api.SkinsRestorerAPI 6 | import org.bukkit.Bukkit 7 | 8 | /** 9 | * @author Mical 10 | * @date 2021/8/19 20:52 11 | */ 12 | class HookSkinsRestorer : HookAbstract() { 13 | 14 | private val skinsRestorerAPI: SkinsRestorerAPI? = 15 | if (isHooked) { 16 | SkinsRestorerAPI.getApi() 17 | } else { 18 | null 19 | } 20 | get() { 21 | if (field == null) reportAbuse() 22 | return field 23 | } 24 | 25 | fun getPlayerSkinTexture(name: String): String? { 26 | skinsRestorerAPI?.let { 27 | val uuid = Bukkit.getPlayerExact(name)?.uniqueId?.toString() ?: return null 28 | if (it.getProfile(uuid) == null) { 29 | return null 30 | } 31 | 32 | val skinData = it.getSkinData(name) 33 | return (skinData as Property).value 34 | } 35 | return null 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/hook/impl/HookTrMenu.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.hook.impl 2 | 3 | import me.arasple.mc.trhologram.module.hook.HookAbstract 4 | 5 | /** 6 | * @author Mical 7 | * @date 2021/8/19 21:05 8 | */ 9 | class HookTrMenu : HookAbstract() { 10 | 11 | override val isHooked: Boolean 12 | get() = super.isHooked && plugin!!.description.version.startsWith("3") 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/listener/ListenerHologramInteract.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.listener 2 | 3 | import me.arasple.mc.trhologram.api.Settings 4 | import me.arasple.mc.trhologram.api.event.HologramInteractEvent 5 | import taboolib.common.platform.event.EventPriority 6 | import taboolib.common.platform.event.SubscribeEvent 7 | import taboolib.common.platform.function.submit 8 | 9 | /** 10 | * @author Arasple 11 | * @date 2021/2/11 16:41 12 | */ 13 | object ListenerHologramInteract { 14 | 15 | @SubscribeEvent(priority = EventPriority.HIGHEST, ignoreCancelled = true) 16 | fun onInteract(e: HologramInteractEvent) { 17 | val player = e.player 18 | 19 | if (Settings.INSTANCE.interactDelay.hasNext(player.name)) { 20 | submit(delay = 1L, async = false) { 21 | e.hologram.reactions.eval(player, e.type) 22 | } 23 | } 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/listener/ListenerJoin.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.listener 2 | 3 | import me.arasple.mc.trhologram.module.display.Hologram 4 | import org.bukkit.event.player.PlayerJoinEvent 5 | import taboolib.common.platform.event.SubscribeEvent 6 | 7 | /** 8 | * @author Arasple 9 | * @date 2021/2/12 13:55 10 | */ 11 | object ListenerJoin { 12 | 13 | @SubscribeEvent 14 | fun onJoin(e: PlayerJoinEvent) { 15 | Hologram.refreshAll(e.player) 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/listener/ListenerMovement.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.listener 2 | 3 | import me.arasple.mc.trhologram.module.display.Hologram 4 | import org.bukkit.event.player.PlayerMoveEvent 5 | import taboolib.common.platform.event.SubscribeEvent 6 | import taboolib.common5.Baffle 7 | import java.util.concurrent.TimeUnit 8 | 9 | /** 10 | * @author Arasple 11 | * @date 2021/2/11 9:58 12 | */ 13 | object ListenerMovement { 14 | 15 | val cd = Baffle.of(1, TimeUnit.SECONDS) 16 | 17 | @SubscribeEvent 18 | fun onMove(e: PlayerMoveEvent) { 19 | val player = e.player 20 | 21 | if (!cd.hasNext(player.name)) { 22 | Hologram.refreshAll(player) 23 | } 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/listener/ListenerQuit.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.listener 2 | 3 | import me.arasple.mc.trhologram.module.display.Hologram 4 | import org.bukkit.event.player.PlayerQuitEvent 5 | import taboolib.common.platform.event.SubscribeEvent 6 | 7 | /** 8 | * @author Arasple 9 | * @date 2021/2/12 13:55 10 | */ 11 | object ListenerQuit { 12 | 13 | @SubscribeEvent 14 | fun onQuit(e: PlayerQuitEvent) { 15 | val player = e.player 16 | 17 | ListenerMovement.cd.reset(player.name) 18 | Hologram.destroyAll(player) 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/listener/ListenerRespawn.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.listener 2 | 3 | import me.arasple.mc.trhologram.module.display.Hologram 4 | import org.bukkit.event.player.PlayerRespawnEvent 5 | import taboolib.common.platform.event.SubscribeEvent 6 | import taboolib.common.platform.function.submit 7 | 8 | /** 9 | * @author Arasple 10 | * @date 2021/2/12 13:58 11 | */ 12 | object ListenerRespawn { 13 | 14 | @SubscribeEvent 15 | fun onRespawn(e: PlayerRespawnEvent) { 16 | submit(delay = 2, async = true) { 17 | Hologram.refreshAll(e.player) 18 | } 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/listener/ListenerWorldChange.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.listener 2 | 3 | import me.arasple.mc.trhologram.module.display.Hologram 4 | import org.bukkit.event.player.PlayerChangedWorldEvent 5 | import taboolib.common.platform.event.SubscribeEvent 6 | 7 | /** 8 | * @author Arasple 9 | * @date 2021/2/12 13:58 10 | */ 11 | object ListenerWorldChange { 12 | 13 | @SubscribeEvent 14 | fun onChange(e: PlayerChangedWorldEvent) { 15 | Hologram.destroyAll(e.player) 16 | Hologram.refreshAll(e.player) 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/service/Metrics.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.service 2 | 3 | import me.arasple.mc.trhologram.module.display.Hologram 4 | import taboolib.common.LifeCycle 5 | import taboolib.common.platform.Awake 6 | import taboolib.common.platform.Platform 7 | import taboolib.common.platform.function.pluginVersion 8 | import taboolib.module.metrics.Metrics 9 | import taboolib.module.metrics.charts.SingleLineChart 10 | 11 | /** 12 | * @author Arasple 13 | * @date 2020/3/7 22:15 14 | */ 15 | object Metrics { 16 | 17 | private val B_STATS by lazy { Metrics(6387, pluginVersion, Platform.BUKKIT) } 18 | 19 | @Awake(LifeCycle.INIT) 20 | fun initialization() { 21 | B_STATS.let { 22 | it.addCustomChart(SingleLineChart("holograms") { 23 | Hologram.holograms.size + Hologram.externalHolograms.size 24 | }) 25 | } 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/module/service/Updater.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.module.service 2 | 3 | import org.bukkit.event.player.PlayerJoinEvent 4 | import taboolib.common.platform.Schedule 5 | import taboolib.common.platform.event.EventPriority 6 | import taboolib.common.platform.event.SubscribeEvent 7 | import taboolib.common.platform.function.console 8 | import taboolib.common.platform.function.pluginVersion 9 | import taboolib.common.platform.function.submit 10 | import taboolib.module.lang.sendLang 11 | import taboolib.platform.util.sendLang 12 | import java.io.BufferedReader 13 | import java.io.InputStreamReader 14 | import java.net.HttpURLConnection 15 | import java.net.URL 16 | import java.nio.charset.StandardCharsets 17 | import java.util.* 18 | 19 | /** 20 | * @author Arasple 21 | * @date 2020/7/28 18:30 22 | */ 23 | object Updater { 24 | 25 | // FIXME: 从某种意义上来说, 有没有可能网络延迟太大然后无法获取到版本号? 也许应该换一个地方存. 26 | private val url = URL("https://github.com/Micalhl/TrHologram/raw/master/Version.txt") 27 | private var LATEST_VERSION: String? = "" 28 | private var NOTIFIED = false 29 | private val NOTIFIED_PLAYER = mutableSetOf() 30 | 31 | fun init() { 32 | submit(delay = 20, period = (10 * 60 * 20), async = true) { 33 | grabInfo() 34 | } 35 | } 36 | 37 | @Schedule(true, 20, 20 * 60 * 10) 38 | private fun grabInfo() { 39 | LATEST_VERSION = getLatestVersion().also { 40 | if (it != null && !NOTIFIED && it != pluginVersion) { 41 | console().sendLang("Plugin-Update", it) 42 | NOTIFIED = true 43 | } 44 | } 45 | } 46 | 47 | private fun getLatestVersion(): String? { 48 | var connection: HttpURLConnection? = null 49 | try { 50 | connection = url.openConnection() as HttpURLConnection 51 | connection.connectTimeout = 5000 52 | val buffer = StringBuilder(255) 53 | BufferedReader(InputStreamReader(connection.inputStream, StandardCharsets.UTF_8)).use { reader -> 54 | val buffer0 = CharArray(255) 55 | while (true) { 56 | val length = reader.read(buffer0) 57 | if (length == -1) break 58 | buffer.append(buffer0, 0, length) 59 | } 60 | } 61 | return buffer.toString().trim() 62 | } catch (ignored: Throwable) { 63 | } finally { 64 | connection?.disconnect() 65 | } 66 | return null 67 | } 68 | 69 | @SubscribeEvent(EventPriority.HIGHEST) 70 | fun e(e: PlayerJoinEvent) { 71 | LATEST_VERSION?.let { 72 | if (e.player.isOp && LATEST_VERSION != pluginVersion && !NOTIFIED_PLAYER.contains(e.player.uniqueId)) { 73 | e.player.sendLang("Plugin-Update", it) 74 | NOTIFIED_PLAYER.add(e.player.uniqueId) 75 | } 76 | } 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/util/Heads.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.util 2 | 3 | import com.google.gson.JsonObject 4 | import com.google.gson.JsonParser 5 | import com.mojang.authlib.GameProfile 6 | import com.mojang.authlib.properties.Property 7 | import me.arasple.mc.trhologram.api.nms.NMS 8 | import me.arasple.mc.trhologram.module.hook.HookPlugin 9 | import org.bukkit.Bukkit 10 | import org.bukkit.inventory.ItemStack 11 | import org.bukkit.inventory.meta.SkullMeta 12 | import taboolib.common.platform.function.console 13 | import taboolib.common.platform.function.submit 14 | import taboolib.library.xseries.XMaterial 15 | import java.net.URL 16 | import java.util.* 17 | 18 | /** 19 | * @author Arasple 20 | * @date 2021/1/27 14:05 21 | */ 22 | object Heads { 23 | 24 | private val MOJANG_API = arrayOf( 25 | "https://api.mojang.com/users/profiles/minecraft/", 26 | "https://sessionserver.mojang.com/session/minecraft/profile/" 27 | ) 28 | 29 | private val DEFAULT_HEAD = XMaterial.PLAYER_HEAD.parseItem()!! 30 | private val CACHED_PLAYER_TEXTURE = mutableMapOf() 31 | private val CACHED_SKULLS = mutableMapOf() 32 | 33 | fun getHead(id: String): ItemStack { 34 | return if (id.length > 20) getCustomTextureHead(id) 35 | else getPlayerHead(id) 36 | } 37 | 38 | fun getPlayerHead(name: String): ItemStack { 39 | if (CACHED_SKULLS.containsKey(name)) { 40 | return CACHED_SKULLS[name] ?: DEFAULT_HEAD 41 | } else { 42 | CACHED_SKULLS[name] = DEFAULT_HEAD.clone() 43 | .also { item -> playerTexture(name) { modifyTexture(it, item) } ?: return DEFAULT_HEAD } 44 | return CACHED_SKULLS[name] ?: DEFAULT_HEAD 45 | } 46 | } 47 | 48 | fun getCustomTextureHead(texture: String): ItemStack { 49 | return CACHED_SKULLS.computeIfAbsent(texture) { 50 | modifyTexture(texture, DEFAULT_HEAD.clone()) 51 | } 52 | } 53 | 54 | fun seekTexture(itemStack: ItemStack): String? { 55 | val meta = itemStack.itemMeta ?: return null 56 | 57 | if (meta is SkullMeta) { 58 | meta.owningPlayer?.name?.let { return it } 59 | } 60 | 61 | val field = meta.javaClass.getDeclaredField("profile").also { it.isAccessible = true } 62 | (field.get(meta) as GameProfile?)?.properties?.values()?.forEach { 63 | if (it.name == "textures") return it.value 64 | } 65 | return null 66 | } 67 | 68 | /** 69 | * PRIVATE UTILS 70 | */ 71 | @Suppress("DEPRECATION") 72 | private fun playerTexture(name: String, block: (String) -> Unit): Unit? { 73 | when { 74 | HookPlugin.getSkinsRestorer().isHooked -> { 75 | HookPlugin.getSkinsRestorer().getPlayerSkinTexture(name)?.also(block) ?: return null 76 | } 77 | Bukkit.getPlayer(name)?.isOnline == true -> { 78 | NMS.INSTANCE.getGameProfile(Bukkit.getPlayer(name)!!).properties["textures"] 79 | .find { it.value != null }?.value 80 | ?.also(block) 81 | ?: return null 82 | } 83 | else -> { 84 | submit(async = true) { 85 | val profile = JsonParser().parse(fromURL("${MOJANG_API[0]}$name")) as? JsonObject 86 | if (profile == null) { 87 | console().sendMessage("§7[§3Texture§7] Texture player $name not found.") 88 | return@submit 89 | } 90 | val uuid = profile["id"].asString 91 | (JsonParser.parseString(fromURL("${MOJANG_API[1]}$uuid")) as JsonObject).getAsJsonArray("properties") 92 | (JsonParser.parseString(fromURL("${MOJANG_API[1]}$uuid")) as JsonObject).getAsJsonArray("properties") 93 | .forEach { 94 | if ("textures" == it.asJsonObject["name"].asString) { 95 | CACHED_PLAYER_TEXTURE[name] = it.asJsonObject["value"].asString.also(block) 96 | } 97 | } 98 | } 99 | } 100 | } 101 | return Unit 102 | } 103 | 104 | private fun modifyTexture(input: String, itemStack: ItemStack): ItemStack { 105 | val meta = itemStack.itemMeta as SkullMeta 106 | val profile = GameProfile(UUID.randomUUID(), null) 107 | val field = meta.javaClass.getDeclaredField("profile") 108 | val texture = if (input.length in 60..100) encodeTexture(input) else input 109 | 110 | profile.properties.put("textures", Property("textures", texture, "TrMenu_TexturedSkull")) 111 | field.isAccessible = true 112 | field[meta] = profile 113 | itemStack.itemMeta = meta 114 | return itemStack 115 | } 116 | 117 | private fun encodeTexture(input: String): String { 118 | val encoder = Base64.getEncoder() 119 | return encoder.encodeToString("{\"textures\":{\"SKIN\":{\"url\":\"http://textures.minecraft.net/texture/$input\"}}}".toByteArray()) 120 | } 121 | 122 | private fun fromURL(url: String): String { 123 | return try { 124 | String(URL(url).openStream().readBytes()) 125 | } catch (t: Throwable) { 126 | "" 127 | } 128 | } 129 | 130 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/util/ItemHelper.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.util 2 | 3 | import com.google.gson.JsonObject 4 | import com.google.gson.JsonParser 5 | import org.bukkit.Material 6 | import org.bukkit.inventory.ItemStack 7 | import taboolib.library.xseries.XMaterial 8 | import taboolib.module.nms.ItemTag 9 | import taboolib.platform.util.buildItem 10 | 11 | /** 12 | * @author Arasple 13 | * @date 2021/2/4 9:56 14 | */ 15 | object ItemHelper { 16 | 17 | fun isNull(item: ItemStack?): Boolean { 18 | return item == null || item.type == Material.AIR 19 | } 20 | 21 | fun fromJson(json: String): ItemStack? { 22 | try { 23 | val parse = JsonParser.parseString(json) 24 | if (parse is JsonObject) { 25 | val itemBuild = buildItem(parse["type"].let { it ?: XMaterial.STONE; XMaterial.valueOf(it.asString) }) { 26 | parse["data"].let { 27 | it ?: return@let 28 | damage = it.asInt 29 | } 30 | parse["amount"].let { 31 | it ?: return@let 32 | amount = it.asInt 33 | } 34 | } 35 | val meta = parse["meta"] 36 | return if (meta != null) itemBuild.also { ItemTag.fromJson(meta.toString()).saveTo(it) } 37 | else itemBuild 38 | } 39 | return null 40 | } catch (e: Throwable) { 41 | return null 42 | } 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/util/Parser.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.util 2 | 3 | import org.bukkit.Bukkit 4 | import org.bukkit.Location 5 | import org.bukkit.entity.Player 6 | import taboolib.common.platform.function.adaptPlayer 7 | import taboolib.common5.Coerce 8 | import taboolib.module.chat.colored 9 | import taboolib.module.kether.KetherFunction 10 | import taboolib.platform.compat.replacePlaceholder 11 | 12 | /** 13 | * @author Arasple 14 | * @date 2021/2/10 21:15 15 | */ 16 | private val PLACEHOLDER_API = "[%{](.+?)[%}]".toRegex() 17 | 18 | fun String.containsPlaceholder(): Boolean { 19 | return PLACEHOLDER_API.find(this) != null 20 | } 21 | 22 | fun String.parseLocation(): Location { 23 | val (world, loc) = split("~", limit = 2) 24 | val (x, y, z) = loc.split(",", limit = 3).map { it.toDouble() } 25 | 26 | return Location(Bukkit.getWorld(world), x, y, z) 27 | } 28 | 29 | fun Location.parseString(): String { 30 | val world = world?.name 31 | val x = Coerce.format(x) 32 | val y = Coerce.format(y) 33 | val z = Coerce.format(z) 34 | 35 | return "$world~$x,$y,$z" 36 | } 37 | 38 | fun Player.parseString(string: String): String { 39 | return KetherFunction.parse(string) { sender = adaptPlayer(this@parseString) }.colored().replacePlaceholder(this) 40 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/arasple/mc/trhologram/util/Variables.kt: -------------------------------------------------------------------------------- 1 | package me.arasple.mc.trhologram.util 2 | 3 | /** 4 | * @author Bkm016 5 | * @date 2021/1/31 17:21 6 | */ 7 | class Variables(source: String, regex: Regex, group: (List) -> String = { it[1] }) { 8 | 9 | val element: List = source.toElements(regex, group) 10 | 11 | companion object { 12 | 13 | val cacheVariables = hashMapOf() 14 | 15 | private fun String.toElements(regex: Regex, group: (List) -> String): List { 16 | val list = mutableListOf() 17 | var index = 0 18 | regex.findAll(this).forEach { 19 | list.add(Element(substring(index, it.range.first))) 20 | list.add(Element(group.invoke(it.groupValues), true)) 21 | index = it.range.last + 1 22 | } 23 | val last = Element(substring(index, length)) 24 | if (last.value.isNotEmpty()) { 25 | list.add(last) 26 | } 27 | return list 28 | } 29 | 30 | } 31 | 32 | class Element(var value: String, var isVariable: Boolean = false) 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/resources/holograms/Demo.yml: -------------------------------------------------------------------------------- 1 | Location: world~0,83,24 2 | 3 | Options: 4 | Line-Spacing: 0.25 5 | View-Distance: 10 6 | View-Condition: 'perm **' 7 | Refresh-Condition: 20 8 | 9 | # 10 | # Options: 11 | # {update/refresh: } -> Hologram Tick Period 12 | # {item: } -> ItemHologram 13 | # {offset: } 14 | # 15 | 16 | Contents: 17 | - 'Hello &3Tr&bHologram' 18 | - '{item=head:%player_name%}' 19 | - '' 20 | - '&7Date: &f%server_time_HH:mm:ss%{update:20}' 21 | - '&7Name: &a%player_name%' 22 | 23 | Actions: 24 | Left: 'tell color *"&3Hello, you left-clicked this hologram!"' 25 | Shift_Left: 26 | - 'tell color *"&7What''s up?"' 27 | Right: [ '' ] 28 | Shift_Right: [ '' ] 29 | -------------------------------------------------------------------------------- /src/main/resources/lang/RU_ru.yml: -------------------------------------------------------------------------------- 1 | Plugin-UnsupportedVersion: '&8[&2Tr&aHologram&8] &cERROR &8| &cПлагин не поддерживает версию сервера Minecraft!' 2 | Plugin-Loading: 3 | - '&r' 4 | - '&7Загрузка &2Tr&aHologram... &8{0}' 5 | - '&aПеревод подготовлен UnknownZuVEnO' 6 | - '&r' 7 | Plugin-Enabled: '&8[&2Tr&aHologram&8] &bINFO &8| &3Плагин запущен! Версия плагина &2{0}&7.' 8 | Plugin-Update: 9 | - '&8[&2Tr&aHologram&8] &bUPDATE &8| &3Обновление &a{0}&3 найдено! Скачайте его для исправления багов и новых фишек!' 10 | - '&bhttps://www.spigotmc.org/resources/75503/' 11 | Plugin-Dependency-Hooked: '&8[&2Tr&aHologram&8] &6HOOK &8| &7Дополнение &f{0} &7привязан.' 12 | Plugin-Dependency-Abuse: '&8[&2Tr&aHologram&8] &6HOOK &8| &4Попытка загрузить неустановленный требуемый плагин &c{0}' 13 | 14 | Hologram-Loaded: '&8[&2Tr&aHologram&8] &aFINE &8| &a{0} &3Голограммы загрузились за &8({1} ms)' 15 | 16 | Paster-Processing: '&8[&2Tr&aHologram&8] &7Вставляем содержимое...' 17 | Paster-Success: '&8[&2Tr&aHologram&8] &3Содержимое вставлено за &a{0}' 18 | Paster-Failed: '&8[&2Tr&aHologram&8] &cПроизошла ошибка при вставке содержимого. Проверьте консоль.' 19 | 20 | Command-Help-Type: 'Тип' 21 | Command-Help-Args: 'Параметры' 22 | 23 | COMMAND-CREATE-DESCRIPTION: 'Создание новой голограммы' 24 | COMMAND-DELETE-DESCRIPTION: 'Удалить существующую голограмму' 25 | COMMAND-LIST-DESCRIPTION: 'Список загруженных голограмм' 26 | COMMAND-MIRROR-DESCRIPTION: 'Мониторинг производительности' 27 | COMMAND-MOVEHERE-DESCRIPTION: 'Телепортируйте голограмму в свое местоположение' 28 | COMMAND-RELOAD-DESCRIPTION: 'Перезагрузить голограммы' 29 | COMMAND-TELEPORT-DESCRIPTION: 'Телепортироваться в местоположение голограммы' 30 | 31 | Command-Reload: '&8[&2Tr&aHologram&8] &aFINE &8| &3Голограммы перезагрузились. &a{0}' 32 | Command-List-Error: '&8[&2Tr&aHologram&8] &7NORM &8| &7Голограм не нашлось 0_0 &8(Filter: {0})' 33 | Command-List-Header: 34 | - '' 35 | - '&8[&2Tr&aHologram&8] &aFINE &8| &7Загружено &f{0} &7голограмм &8(Filter: {1}): ' 36 | - '' 37 | Command-List-Format: 38 | - type: JSON 39 | text: '&8- [&3{0}] &7| &8(Lines: {1})&r' 40 | args: 41 | - hover: '&7Кликните, чтобы переместить' 42 | command: '/trhologram teleport {0}' 43 | Command-Not-Exists: '&8[&2Tr&aHologram&8] &7NORM &8| &7Голограмма &f{0} не существует.' 44 | Command-Deleted: '&8[&2Tr&aHologram&8] &aNORM &8| &3Удалена голограмма с именем &3{0}&3.' 45 | Command-Existed: 46 | - type: TITLE 47 | title: '&c&lОПЕРАЦИЯ ОТМЕНЕНА' 48 | subtitle: '&7&lДублирование ID голограммы' 49 | - type: SOUND 50 | sound: ITEM_SHIELD_BREAK 51 | volume: 1 52 | pitch: 2 53 | Command-Created: 54 | - type: TITLE 55 | title: '&a&lГОТОВО' 56 | subtitle: '&a&lТеперь настройте голограмму' 57 | - type: SOUND 58 | sound: ITEM_BOTTLE_FILL 59 | volume: 1 60 | pitch: 2 -------------------------------------------------------------------------------- /src/main/resources/lang/en_US.yml: -------------------------------------------------------------------------------- 1 | Plugin-UnsupportedVersion: '&8[&2Tr&aHologram&8] &cERROR &8| &cPlugin does not support this outdated Minecraft version' 2 | Plugin-Loading: 3 | - '&r' 4 | - '&7Loading &2Tr&aHologram... &8{0}' 5 | - '&r' 6 | Plugin-Enabled: '&8[&2Tr&aHologram&8] &bINFO &8| &3Plugin has been enabled. Currently running version &2{0}&7.' 7 | Plugin-Update: 8 | - '&8[&2Tr&aHologram&8] &bUPDATE &8| &3Update &a{0}&3 found ! Download it from the link below for new features and bug fix!' 9 | - '&bhttps://www.spigotmc.org/resources/75503/' 10 | Plugin-Dependency-Hooked: '&8[&2Tr&aHologram&8] &6HOOK &8| &7Soft-Dependency &f{0} &7is hooked.' 11 | Plugin-Dependency-Abuse: '&8[&2Tr&aHologram&8] &6HOOK &8| &4Attempted to use the non-installed soft-depend plugin &c{0}' 12 | 13 | Hologram-Loaded: '&8[&2Tr&aHologram&8] &aFINE &8| &a{0} &3holograms were loaded &8({1} ms)' 14 | 15 | Paster-Processing: '&8[&2Tr&aHologram&8] &7Pasting content ...' 16 | Paster-Success: '&8[&2Tr&aHologram&8] &3The content has been pasted to &a{0}' 17 | Paster-Failed: '&8[&2Tr&aHologram&8] &cAn error occurred while pasting the content. Please check out console.' 18 | 19 | Command-Help-Type: 'Type' 20 | Command-Help-Args: 'Parameters' 21 | 22 | COMMAND-CREATE-DESCRIPTION: 'Create a new hologram' 23 | COMMAND-DELETE-DESCRIPTION: 'Delete an existed hologram' 24 | COMMAND-LIST-DESCRIPTION: 'List loaded holograms' 25 | COMMAND-MIRROR-DESCRIPTION: 'Monitor performance' 26 | COMMAND-MOVEHERE-DESCRIPTION: 'Teleport hologram to your location' 27 | COMMAND-RELOAD-DESCRIPTION: 'Reload holograms' 28 | COMMAND-TELEPORT-DESCRIPTION: 'Teleport to the hologram''s location' 29 | 30 | Command-Reload: '&8[&2Tr&aHologram&8] &aFINE &8| &3Successfully reloaded the hologram &a{0}' 31 | Command-List-Error: '&8[&2Tr&aHologram&8] &7NORM &8| &7No holograms are found... &8(Filter: {0})' 32 | Command-List-Header: 33 | - '' 34 | - '&8[&2Tr&aHologram&8] &aFINE &8| &7Loaded &f{0} &7holograms &8(Filter: {1}): ' 35 | - '' 36 | Command-List-Format: 37 | - type: JSON 38 | text: '&8- [&3{0}] &7| &8(Lines: {1})&r' 39 | args: 40 | - hover: '&7Click to teleport' 41 | command: '/trhologram teleport {0}' 42 | Command-Not-Exists: '&8[&2Tr&aHologram&8] &7NORM &8| &7Hologram &f{0} does not exist.' 43 | Command-Deleted: '&8[&2Tr&aHologram&8] &aNORM &8| &3Deleted hologram named &3{0}&3.' 44 | Command-Existed: 45 | - type: TITLE 46 | title: '&c&lOPERATION FAILED' 47 | subtitle: '&7&lDuplicate hologram ID' 48 | - type: SOUND 49 | sound: ITEM_SHIELD_BREAK 50 | volume: 1 51 | pitch: 2 52 | Command-Created: 53 | - type: TITLE 54 | title: '&a&lCREATED' 55 | subtitle: '&a&lEdit the hologram now' 56 | - type: SOUND 57 | sound: ITEM_BOTTLE_FILL 58 | volume: 1 59 | pitch: 2 -------------------------------------------------------------------------------- /src/main/resources/lang/zh_CN.yml: -------------------------------------------------------------------------------- 1 | Plugin-UnsupportedVersion: '&8[&2Tr&aHologram&8] &cERROR &8| &c插件不支持当前服务器版本' 2 | Plugin-Loading: 3 | - '&r' 4 | - '&7正在加载 &2Tr&aHologram... &8{0}' 5 | - '&r' 6 | Plugin-Enabled: '&8[&2Tr&aHologram&8] &bINFO &8| &3插件启用. 当前运行版本 &2{0}&7.' 7 | Plugin-Update: 8 | - '&8[&2Tr&aHologram&8] &bUPDATE &8| &3更新 &a{0}&3 找到, 通过下方链接下载!' 9 | - '&bhttps://github.com/Micalhl/TrHologram/actions' 10 | Plugin-Dependency-Hooked: '&8[&2Tr&aHologram&8] &6HOOK &8| &7软依赖 &f{0} &7已兼容.' 11 | Plugin-Dependency-Abuse: '&8[&2Tr&aHologram&8] &6HOOK &8| &4试图使用未挂钩插件 &c{0}' 12 | 13 | Configuration-Auto-Reload: '&8[&2Tr&aHologram&8] &aFINE &8| &3检测到配置文件更改, 已自动重新载入...' 14 | 15 | Hologram-Loaded: '&8[&2Tr&aHologram&8] &aFINE &8| &a{0} &3个全息图已加载 &8({1} ms)' 16 | 17 | Paster-Processing: '&8[&2Tr&aHologram&8] &7正在粘贴内容 ...' 18 | Paster-Success: '&8[&2Tr&aHologram&8] &3内容已被粘贴到 &a{0}' 19 | Paster-Failed: '&8[&2Tr&aHologram&8] &c粘贴内容时发现错误. 请检查控制台.' 20 | 21 | Command-Help-Type: '命令' 22 | Command-Help-Args: '参数' 23 | 24 | COMMAND-CREATE-DESCRIPTION: '创建一个新的全息图' 25 | COMMAND-DELETE-DESCRIPTION: '删除一个已经存在的全息图' 26 | COMMAND-LIST-DESCRIPTION: '列出已加载的全息图' 27 | COMMAND-MIRROR-DESCRIPTION: '监控性能' 28 | COMMAND-MOVEHERE-DESCRIPTION: '将全息图传送到你的位置' 29 | COMMAND-RELOAD-DESCRIPTION: '重新加载全息图' 30 | COMMAND-TELEPORT-DESCRIPTION: '传送到全息图的位置' 31 | 32 | Command-Reload: '&8[&2Tr&aHologram&8] &aFINE &8| &3成功重载全息图 &a{0}' 33 | Command-List-Error: '&8[&2Tr&aHologram&8] &7NORM &8| &7没有发现全息图... &8(Filter: {0})' 34 | Command-List-Header: 35 | - '' 36 | - '&8[&2Tr&aHologram&8] &aFINE &8| &7已加载 &f{0} &7个全息图 &8(Filter: {1}): ' 37 | - '' 38 | Command-List-Format: 39 | - type: JSON 40 | text: '&8- [&3{0}] &7| &8(Lines: {1})&r' 41 | args: 42 | - hover: '&7点击传送' 43 | command: '/trhologram teleport {0}' 44 | Command-Not-Exists: '&8[&2Tr&aHologram&8] &7NORM &8| &7指定的全息图 &f{0} 不存在.' 45 | Command-Deleted: '&8[&2Tr&aHologram&8] &aNORM &8| &3已删除名为 &3{0} &3的全息图.' 46 | Command-Existed: 47 | - type: TITLE 48 | title: '&c&l操作失败' 49 | subtitle: '&7&l重复的全息图 ID' 50 | - type: SOUND 51 | sound: ITEM_SHIELD_BREAK 52 | volume: 1 53 | pitch: 2 54 | Command-Created: 55 | - type: TITLE 56 | title: '&a&l已创建' 57 | subtitle: '&a&l快来修改吧 :)' 58 | - type: SOUND 59 | sound: ITEM_BOTTLE_FILL 60 | volume: 1 61 | pitch: 2 -------------------------------------------------------------------------------- /src/main/resources/settings.yml: -------------------------------------------------------------------------------- 1 | Loader: 2 | Hologram-Files: 3 | - 'plugins/CustomHologramsFolder' 4 | 5 | Hologram: 6 | Interact-Min-Delay: 500 7 | Options: 8 | Default-Line-Spacing: 0.25 9 | Default-View-Distance: 20 10 | Default-View-Condition: '' 11 | Default-Refresh-Condition: -1 12 | --------------------------------------------------------------------------------