├── .github └── workflows │ └── gradle.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java └── top │ └── zoyn │ └── grus │ ├── Grus.java │ ├── I18N.java │ ├── api │ ├── GrusAPI.java │ └── event │ │ └── PlayerBoundaryExpChangeEvent.java │ ├── command │ ├── GrusCommand.java │ ├── SubCommand.java │ └── subcommand │ │ ├── BoundaryCommand.java │ │ ├── HelpCommand.java │ │ ├── LingemCommand.java │ │ ├── MeCommand.java │ │ └── ReloadCommand.java │ ├── listener │ ├── EntityDeathListener.java │ └── PlayerJoinListener.java │ ├── manager │ ├── BoundaryExpDropManager.java │ ├── BoundaryManager.java │ ├── GodTrialManager.java │ ├── ItemManager.java │ ├── LanguageManager.java │ └── LingemManager.java │ ├── model │ ├── ChiOrb.java │ └── MagicTreasure.java │ ├── papi │ └── GrusExpansion.java │ └── utils │ ├── ConfigurationUtils.java │ ├── ItemBuilder.java │ ├── Logger.java │ ├── LoreMap.java │ ├── MessageUtils.java │ ├── PageableInventory.java │ └── PermissionUtils.java └── resources ├── config.yml ├── language ├── en-US.yml └── zh-CN.yml └── plugin.yml /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time 6 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle 7 | 8 | name: Java CI with Gradle 9 | 10 | on: 11 | push: 12 | branches: [ "main" ] 13 | pull_request: 14 | branches: [ "main" ] 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | build: 21 | 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - uses: actions/checkout@v3 26 | - name: Set up JDK 11 27 | uses: actions/setup-java@v3 28 | with: 29 | java-version: '11' 30 | distribution: 'temurin' 31 | 32 | - name: Grant execute permission for gradlew 33 | run: chmod +X gradlew 34 | 35 | - name: Build with Gradle 36 | run: ./gradlew build 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Zoyn 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grus 2 | 3 | 一个基于 BukkitAPI 的修仙插件 4 | 5 | [![Java CI with Gradle](https://github.com/602723113/Grus/actions/workflows/gradle.yml/badge.svg)](https://github.com/602723113/Grus/actions/workflows/gradle.yml) 6 | 7 | > 修仙觅长生,热血任逍遥,踏莲曳波涤剑骨,凭虚御风塑圣魂! —— 《凡人修仙传》 8 | 9 | ### 境界篇 10 | 11 | 在 Grus 的 [默认配置](https://github.com/602723113/Grus/blob/main/src/main/resources/config.yml) 12 | 当中修仙者境界划分为`练气、筑基初阶、筑基中阶、筑基巅峰、结丹、元婴、化神、炼虚、合体、大乘`十重境界。 13 | 14 | |境界|所需修为| 15 | |:---:|:---:| 16 | |炼气|500| 17 | |筑基初阶|1500| 18 | |筑基中阶|7500| 19 | |筑基巅峰|15000| 20 | |结丹|50000| 21 | |元婴|500000| 22 | |化神|5000000| 23 | |炼虚|50000000| 24 | |合体|500000000| 25 | |大乘|5000000000| 26 | 27 | 每一种境界所需的 **修为值**[^1] 都可以自定义, 并且在 config.yml 当中可以自行删改修仙者的境界配置, 可以做到不同服务器有不同的修仙模式 28 | 29 | [^1]: 修为值:指玩家的修仙时所积攒的经验值 30 | 31 | 详情请见 config.yml 有更详细的介绍 32 | 33 | --- 34 | 35 | ### 灵根篇 36 | 37 | 灵根指修仙者的属性, 表示修仙者的修行素质, 特殊的功法可以设定只有特殊的灵根才能学习 38 | 39 | 在 Grus 的 [默认配置](https://github.com/602723113/Grus/blob/main/src/main/resources/config.yml) 40 | 当中共设定有默认五大灵根分别是: `金、木、水、火、土`, 且在 config.yml 内可自行设定更多的灵根, 灵根属性支持完全的自定义 41 | 42 | 玩家获得灵根后则会有以下几种效果 43 | 44 | - **天灵根**:只有一种属性的单一灵根,灵根充裕。修炼速度是普通灵根的数倍。 45 | - **真灵根**:具有两、三种属性的灵根,每种属性灵根充裕。修炼速度较快。 46 | - **伪灵根**:具有四、五种属性的灵根,很杂且不充裕,每种属性的灵根都不完全,修炼速度很慢。 47 | 48 | 修炼速度设定可见 config.yml 内进行设定 49 | 50 | --- 51 | 52 | ### 法器篇 53 | 54 | 另见 [Grus-MagicTreasures](https://github.com/GrusWorld/Grus-MagicTreasures) 55 | 56 | --- 57 | 58 | ### 符箓篇 59 | 60 | 等人写 61 | 62 | --- 63 | 64 | ### 功法篇 65 | 66 | 等人写 -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | group = 'top.zoyn' 6 | version = '1.0-SNAPSHOT' 7 | 8 | repositories { 9 | mavenCentral() 10 | maven { 11 | name = 'lss-repo' 12 | url = 'https://maven.fastmirror.net/repositories/minecraft' 13 | } 14 | maven { 15 | url = 'https://repo.extendedclip.com/content/repositories/placeholderapi/' 16 | } 17 | maven { url "https://repo.dmulloy2.net/repository/public/" } 18 | } 19 | 20 | dependencies { 21 | compileOnly 'org.spigotmc:spigot-api:1.15.2-R0.1-SNAPSHOT' 22 | compileOnly 'me.clip:placeholderapi:2.11.2' 23 | compileOnly 'com.comphenix.protocol:ProtocolLib:4.7.0' 24 | } 25 | 26 | tasks.withType(JavaCompile) { 27 | options.encoding = "UTF-8" 28 | } 29 | 30 | def targetJavaVersion = 8 31 | java { 32 | def javaVersion = JavaVersion.toVersion(targetJavaVersion) 33 | sourceCompatibility = javaVersion 34 | targetCompatibility = javaVersion 35 | if (JavaVersion.current() < javaVersion) { 36 | toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) 37 | } 38 | } 39 | 40 | tasks.withType(JavaCompile).configureEach { 41 | if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { 42 | options.release = targetJavaVersion 43 | } 44 | } 45 | 46 | processResources { 47 | def props = [version: version] 48 | inputs.properties props 49 | filteringCharset 'UTF-8' 50 | filesMatching('plugin.yml') { 51 | expand props 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/602723113/Grus/d5756a27324c3b5c8a4b37d6f43076ac25bc58ca/gradle.properties -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/602723113/Grus/d5756a27324c3b5c8a4b37d6f43076ac25bc58ca/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.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright ? 2015-2021 the original 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 POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions ?$var?, ?${var}?, ?${var:-default}?, ?${var+SET}?, 36 | # ?${var#prefix}?, ?${var%suffix}?, and ?$( cmd )?; 37 | # * compound commands having a testable exit status, especially ?case?; 38 | # * various built-in commands including ?command?, ?set?, and ?ulimit?. 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'Grus' 2 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/Grus.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus; 2 | 3 | import com.comphenix.protocol.ProtocolLibrary; 4 | import com.comphenix.protocol.ProtocolManager; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.plugin.java.JavaPlugin; 7 | import top.zoyn.grus.command.GrusCommand; 8 | import top.zoyn.grus.listener.EntityDeathListener; 9 | import top.zoyn.grus.manager.BoundaryExpDropManager; 10 | import top.zoyn.grus.manager.BoundaryManager; 11 | import top.zoyn.grus.manager.LanguageManager; 12 | import top.zoyn.grus.manager.LingemManager; 13 | import top.zoyn.grus.papi.GrusExpansion; 14 | 15 | import java.io.File; 16 | 17 | public final class Grus extends JavaPlugin { 18 | 19 | private static Grus instance; 20 | 21 | private LanguageManager languageManager; 22 | private BoundaryManager boundaryManager; 23 | private BoundaryExpDropManager boundaryExpDropManager; 24 | private LingemManager lingemManager; 25 | private ProtocolManager protocolManager; 26 | 27 | public static Grus getInstance() { 28 | return instance; 29 | } 30 | 31 | public void onLoad() { 32 | protocolManager = ProtocolLibrary.getProtocolManager(); 33 | } 34 | 35 | @Override 36 | public void onEnable() { 37 | instance = this; 38 | 39 | // 本地数据 40 | saveDefaultConfig(); 41 | saveResource("language" + File.separator + "zh-CN.yml", false); 42 | saveResource("language" + File.separator + "en-US.yml", false); 43 | 44 | // 管理器 45 | languageManager = new LanguageManager(); 46 | boundaryManager = new BoundaryManager(); 47 | boundaryExpDropManager = new BoundaryExpDropManager(); 48 | lingemManager = new LingemManager(); 49 | 50 | // 监听器 51 | new EntityDeathListener(instance); 52 | 53 | // 指令 54 | getCommand("grus").setExecutor(new GrusCommand()); 55 | 56 | // PlaceholderAPI 注册 57 | if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { 58 | new GrusExpansion().register(); 59 | } 60 | } 61 | 62 | @Override 63 | public void onDisable() { 64 | boundaryManager.save(); 65 | lingemManager.save(); 66 | } 67 | 68 | public LanguageManager getLanguageManager() { 69 | return languageManager; 70 | } 71 | 72 | public BoundaryManager getBoundaryManager() { 73 | return boundaryManager; 74 | } 75 | 76 | public LingemManager getLingemManager() { 77 | return lingemManager; 78 | } 79 | 80 | public BoundaryExpDropManager getBoundaryExpDropManager() { 81 | return boundaryExpDropManager; 82 | } 83 | 84 | public ProtocolManager getProtocolManager() { 85 | return protocolManager; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/I18N.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus; 2 | 3 | import org.bukkit.ChatColor; 4 | import org.bukkit.configuration.file.FileConfiguration; 5 | 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | 9 | public enum I18N { 10 | 11 | HELP("help"), 12 | HELP_OP("help-op"), 13 | HELP_BOUNDARY("help-boundary"), 14 | HELP_LINGEM("help-lingem"), 15 | RELOAD_DONE("reload-done"), 16 | WRONG_COMMAND_USAGE("wrong-command-usage"), 17 | NOT_NUMBER("not-number"), 18 | MESSAGE_PREFIX("message-prefix"), 19 | UNKNOWN_COMMAND("unknown-command"), 20 | NO_PERMISSION("no-permission"), 21 | NO_LINGEM("no-lingem"), 22 | 23 | ME("me"), 24 | YOU_HAVE_GAINED_EXP("you-have-gained-exp"), 25 | PLAYER_DO_NOT_EXIST("player-do-not-exist"), 26 | BOUNDARY_LOOK("boundary-look"), 27 | BOUNDARY_ADD("boundary-add"), 28 | BOUNDARY_REMOVE("boundary-remove"), 29 | LINGEM_LOOK("lingem-look"), 30 | LINGEM_ADD("lingem-add"), 31 | LINGEM_REMOVE("lingem-remove"), 32 | LINGEM_RESET("lingem-reset"), 33 | LINGEM_IS_NOT_EXIST("lingem-is-not-exist"), 34 | LINGEM_HAVE_NOT_DISPLAY("lingem-have-not-display"), 35 | 36 | CONSOLE_WRONG_CHI_ORB_SETTING("console-wrong-chi-orb-setting"), 37 | CONSOLE_SET_LANGUAGE("console-set-language"), 38 | CONSOLE_LOAD_LINGEM("console-load-lingem"), 39 | CONSOLE_LOAD_BOUNDARY("console-load-boundary"), 40 | CONSOLE_LOAD_BOUNDARY_EXP_DROP("console-load-boundary-exp-drop"), 41 | CONSOLE_MUST_BE_PLAYER("console-must-be-player"); 42 | 43 | private final String key; 44 | private Object message; 45 | 46 | I18N(String key) { 47 | this.key = key; 48 | } 49 | 50 | public void init(FileConfiguration config) { 51 | if (config.isString(key)) { 52 | this.message = translateColorCode(config.getString(key)); 53 | } else if (config.isList(key)) { 54 | this.message = translateColorCode(config.getStringList(key)); 55 | } 56 | } 57 | 58 | public String getMessage() { 59 | return (String) this.message; 60 | } 61 | 62 | public List getAsStringList() { 63 | return (List) this.message; 64 | } 65 | 66 | private static String translateColorCode(String message) { 67 | return ChatColor.translateAlternateColorCodes('&', message); 68 | } 69 | 70 | private static List translateColorCode(List messages) { 71 | return messages.stream().map(I18N::translateColorCode).collect(Collectors.toList()); 72 | } 73 | 74 | @Override 75 | public String toString() { 76 | return super.toString(); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/api/GrusAPI.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.api; 2 | 3 | import org.bukkit.Location; 4 | import top.zoyn.grus.Grus; 5 | import top.zoyn.grus.manager.BoundaryExpDropManager; 6 | import top.zoyn.grus.manager.BoundaryManager; 7 | import top.zoyn.grus.manager.LanguageManager; 8 | import top.zoyn.grus.manager.LingemManager; 9 | import top.zoyn.grus.model.ChiOrb; 10 | 11 | public class GrusAPI { 12 | 13 | /** 14 | * 获取 语言管理器 15 | * 16 | * @return {@link LanguageManager} 17 | */ 18 | public static LanguageManager getLanguageManager() { 19 | return Grus.getInstance().getLanguageManager(); 20 | } 21 | 22 | /** 23 | * 获取 境界管理器 24 | * 25 | * @return {@link BoundaryManager} 26 | */ 27 | public static BoundaryManager getBoundaryManager() { 28 | return Grus.getInstance().getBoundaryManager(); 29 | } 30 | 31 | /** 32 | * 获取 境界经验值掉落管理器 33 | * 34 | * @return {@link BoundaryExpDropManager} 35 | */ 36 | public static BoundaryExpDropManager getBoundaryExpDropManager() { 37 | return Grus.getInstance().getBoundaryExpDropManager(); 38 | } 39 | 40 | 41 | /** 42 | * 获取 灵根管理器 43 | * 44 | * @return {@link LingemManager} 45 | */ 46 | public static LingemManager getLingemManager() { 47 | return Grus.getInstance().getLingemManager(); 48 | } 49 | 50 | /** 51 | * 在给定的坐标生成一个真气实体 52 | * 53 | * @param spawnLocation 给定的坐标 54 | * @param exp 真气所携带的修为 55 | * @return {@link ChiOrb} 56 | */ 57 | public static ChiOrb spawnChiOrb(Location spawnLocation, int exp) { 58 | return new ChiOrb(spawnLocation, exp); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/api/event/PlayerBoundaryExpChangeEvent.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.api.event; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.bukkit.event.Cancellable; 5 | import org.bukkit.event.Event; 6 | import org.bukkit.event.HandlerList; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public class PlayerBoundaryExpChangeEvent extends Event implements Cancellable { 10 | 11 | private double from; 12 | private double to; 13 | private Player player; 14 | private boolean cancel = false; 15 | 16 | private static final HandlerList handlers = new HandlerList(); 17 | 18 | public PlayerBoundaryExpChangeEvent(double from, double to, Player player) { 19 | this.from = from; 20 | this.to = to; 21 | this.player = player; 22 | } 23 | 24 | @Override 25 | public @NotNull HandlerList getHandlers() { 26 | return handlers; 27 | } 28 | 29 | public static HandlerList getHandlerList() { 30 | return handlers; 31 | } 32 | 33 | @Override 34 | public boolean isCancelled() { 35 | return cancel; 36 | } 37 | 38 | @Override 39 | public void setCancelled(boolean cancel) { 40 | this.cancel = cancel; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/command/GrusCommand.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.command; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.google.common.collect.Maps; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.command.Command; 7 | import org.bukkit.command.CommandSender; 8 | import org.bukkit.command.TabExecutor; 9 | import top.zoyn.grus.I18N; 10 | import top.zoyn.grus.command.subcommand.*; 11 | import top.zoyn.grus.utils.MessageUtils; 12 | 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.Objects; 16 | 17 | public class GrusCommand implements TabExecutor { 18 | 19 | private static final Map commandMap = Maps.newHashMap(); 20 | 21 | /** 22 | * 初始化指令 23 | */ 24 | public GrusCommand() { 25 | registerCommand("help", new HelpCommand()); 26 | registerCommand("me", new MeCommand()); 27 | registerCommand("lingem", new LingemCommand()); 28 | registerCommand("boundary", new BoundaryCommand()); 29 | registerCommand("reload", new ReloadCommand()); 30 | } 31 | 32 | private void registerCommand(String commandName, SubCommand subCommand) { 33 | if (commandMap.containsKey(commandName)) { 34 | Bukkit.getLogger().warning("!"); 35 | return; 36 | } 37 | commandMap.put(commandName, subCommand); 38 | } 39 | 40 | @Override 41 | public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { 42 | if (args.length == 0) { 43 | commandMap.get("help").execute(sender, args); 44 | return true; 45 | } 46 | if (!commandMap.containsKey(args[0])) { 47 | MessageUtils.sendPrefixMessage(sender, I18N.UNKNOWN_COMMAND.getMessage()); 48 | return true; 49 | } 50 | 51 | // 第一个参数是 args[0] 52 | SubCommand subCommand = commandMap.get(args[0]); 53 | subCommand.execute(sender, args); 54 | return true; 55 | } 56 | 57 | @Override 58 | public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { 59 | if (args.length == 1) { 60 | List res = Lists.newArrayList(commandMap.keySet()); 61 | res.removeIf(s -> !s.startsWith(args[0])); 62 | return res; 63 | } 64 | if (args.length == 2) { 65 | SubCommand subCommand = commandMap.get(args[0]); 66 | if (Objects.nonNull(subCommand)) { 67 | return subCommand.tabComplete(args); 68 | } 69 | } 70 | return null; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/command/SubCommand.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.command; 2 | 3 | import org.bukkit.command.CommandSender; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 表示一个子命令 9 | * 10 | * @author Zoyn 11 | * @since 2023-1-14 12 | */ 13 | public interface SubCommand { 14 | 15 | void execute(CommandSender sender, String[] args); 16 | 17 | default List tabComplete(String[] args) { 18 | return null; 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/command/subcommand/BoundaryCommand.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.command.subcommand; 2 | 3 | import com.google.common.collect.Lists; 4 | import me.clip.placeholderapi.PlaceholderAPI; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.OfflinePlayer; 7 | import org.bukkit.command.CommandSender; 8 | import top.zoyn.grus.I18N; 9 | import top.zoyn.grus.api.GrusAPI; 10 | import top.zoyn.grus.command.SubCommand; 11 | import top.zoyn.grus.manager.BoundaryManager; 12 | import top.zoyn.grus.utils.MessageUtils; 13 | import top.zoyn.grus.utils.PermissionUtils; 14 | 15 | import java.util.List; 16 | import java.util.UUID; 17 | 18 | public class BoundaryCommand implements SubCommand { 19 | 20 | @Override 21 | public void execute(CommandSender sender, String[] args) { 22 | if (PermissionUtils.nonAdminAuth(sender)) { 23 | return; 24 | } 25 | if (args.length == 1) { 26 | I18N.HELP_BOUNDARY.getAsStringList().forEach(sender::sendMessage); 27 | return; 28 | } 29 | if ("look".equalsIgnoreCase(args[1])) { 30 | if (args.length <= 2) { 31 | MessageUtils.sendWrongUsageMessage(sender); 32 | return; 33 | } 34 | 35 | String playerName = args[2]; 36 | OfflinePlayer player; 37 | UUID uid; 38 | try { 39 | uid = UUID.fromString(playerName); 40 | player = Bukkit.getOfflinePlayer(uid); 41 | } catch (IllegalArgumentException exception) { 42 | player = Bukkit.getOfflinePlayer(playerName); 43 | } 44 | if (player == null) { 45 | MessageUtils.sendPlayerNotFoundMessage(sender, playerName); 46 | return; 47 | } 48 | for (String s : I18N.BOUNDARY_LOOK.getAsStringList()) { 49 | // 这里直接套用 PlaceholderAPI 里面的变量 50 | sender.sendMessage(PlaceholderAPI.setPlaceholders(player, s)); 51 | } 52 | } 53 | if ("add".equalsIgnoreCase(args[1])) { 54 | if (args.length <= 3) { 55 | MessageUtils.sendWrongUsageMessage(sender); 56 | return; 57 | } 58 | String playerName = args[2]; 59 | double exp; 60 | try{ 61 | exp = Double.parseDouble(args[3]); 62 | } catch (NumberFormatException exception) { 63 | MessageUtils.sendNotNumberMessage(sender); 64 | return; 65 | } 66 | 67 | OfflinePlayer player; 68 | UUID uid; 69 | try { 70 | uid = UUID.fromString(playerName); 71 | player = Bukkit.getOfflinePlayer(uid); 72 | } catch (IllegalArgumentException exception) { 73 | player = Bukkit.getOfflinePlayer(playerName); 74 | } 75 | if (player == null) { 76 | MessageUtils.sendPlayerNotFoundMessage(sender, playerName); 77 | return; 78 | } 79 | BoundaryManager manager = GrusAPI.getBoundaryManager(); 80 | manager.addBoundaryExp(player, exp); 81 | sender.sendMessage(I18N.BOUNDARY_ADD.getMessage() 82 | .replace("%player_name%", playerName) 83 | .replace("%exp%", "" + exp)); 84 | } 85 | if ("remove".equalsIgnoreCase(args[1])) { 86 | if (args.length <= 3) { 87 | MessageUtils.sendWrongUsageMessage(sender); 88 | return; 89 | } 90 | String playerName = args[2]; 91 | double exp; 92 | try{ 93 | exp = Double.parseDouble(args[3]); 94 | } catch (NumberFormatException exception) { 95 | MessageUtils.sendNotNumberMessage(sender); 96 | return; 97 | } 98 | OfflinePlayer player; 99 | UUID uid; 100 | try { 101 | uid = UUID.fromString(playerName); 102 | player = Bukkit.getOfflinePlayer(uid); 103 | } catch (IllegalArgumentException exception) { 104 | player = Bukkit.getOfflinePlayer(playerName); 105 | } 106 | if (player == null) { 107 | MessageUtils.sendPlayerNotFoundMessage(sender, playerName); 108 | return; 109 | } 110 | BoundaryManager manager = GrusAPI.getBoundaryManager(); 111 | manager.removeBoundaryExp(player, exp); 112 | sender.sendMessage(I18N.BOUNDARY_REMOVE.getMessage() 113 | .replace("%player_name%", playerName) 114 | .replace("%exp%", "" + exp)); 115 | } 116 | } 117 | 118 | @Override 119 | public List tabComplete(String[] args) { 120 | List res = Lists.newArrayList("look", "add", "remove"); 121 | res.removeIf(s -> !s.startsWith(args[1])); 122 | return Lists.newArrayList(res); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/command/subcommand/HelpCommand.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.command.subcommand; 2 | 3 | import org.bukkit.command.CommandSender; 4 | import top.zoyn.grus.I18N; 5 | import top.zoyn.grus.command.SubCommand; 6 | 7 | public class HelpCommand implements SubCommand { 8 | 9 | @Override 10 | public void execute(CommandSender sender, String[] args) { 11 | I18N.HELP.getAsStringList().forEach(sender::sendMessage); 12 | if (sender.isOp()) { 13 | I18N.HELP_OP.getAsStringList().forEach(sender::sendMessage); 14 | } 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/command/subcommand/LingemCommand.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.command.subcommand; 2 | 3 | import com.google.common.collect.Lists; 4 | import me.clip.placeholderapi.PlaceholderAPI; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.OfflinePlayer; 7 | import org.bukkit.command.CommandSender; 8 | import top.zoyn.grus.I18N; 9 | import top.zoyn.grus.api.GrusAPI; 10 | import top.zoyn.grus.command.SubCommand; 11 | import top.zoyn.grus.manager.BoundaryManager; 12 | import top.zoyn.grus.manager.LingemManager; 13 | import top.zoyn.grus.utils.MessageUtils; 14 | import top.zoyn.grus.utils.PermissionUtils; 15 | 16 | import java.util.List; 17 | import java.util.UUID; 18 | 19 | public class LingemCommand implements SubCommand { 20 | 21 | @Override 22 | public void execute(CommandSender sender, String[] args) { 23 | if (PermissionUtils.nonAdminAuth(sender)) { 24 | return; 25 | } 26 | if (args.length == 1) { 27 | I18N.HELP_LINGEM.getAsStringList().forEach(sender::sendMessage); 28 | return; 29 | } 30 | if ("look".equalsIgnoreCase(args[1])) { 31 | if (args.length <= 2) { 32 | MessageUtils.sendWrongUsageMessage(sender); 33 | return; 34 | } 35 | 36 | String playerName = args[2]; 37 | OfflinePlayer player; 38 | UUID uid; 39 | try { 40 | uid = UUID.fromString(playerName); 41 | player = Bukkit.getOfflinePlayer(uid); 42 | } catch (IllegalArgumentException exception) { 43 | player = Bukkit.getOfflinePlayer(playerName); 44 | } 45 | if (player == null) { 46 | MessageUtils.sendPlayerNotFoundMessage(sender, playerName); 47 | return; 48 | } 49 | for (String s : I18N.LINGEM_LOOK.getAsStringList()) { 50 | // 这里直接套用 PlaceholderAPI 里面的变量 51 | sender.sendMessage(PlaceholderAPI.setPlaceholders(player, s)); 52 | } 53 | } 54 | if ("add".equalsIgnoreCase(args[1])) { 55 | if (args.length <= 3) { 56 | MessageUtils.sendWrongUsageMessage(sender); 57 | return; 58 | } 59 | String playerName = args[2]; 60 | String lingem = args[3]; 61 | if (!GrusAPI.getLingemManager().hasLingemInDefault(lingem)) { 62 | MessageUtils.sendPrefixMessage(sender,I18N.LINGEM_IS_NOT_EXIST.getMessage().replace("%lingem%", lingem)); 63 | return; 64 | } 65 | OfflinePlayer player; 66 | UUID uid; 67 | try { 68 | uid = UUID.fromString(playerName); 69 | player = Bukkit.getOfflinePlayer(uid); 70 | } catch (IllegalArgumentException exception) { 71 | player = Bukkit.getOfflinePlayer(playerName); 72 | } 73 | if (player == null) { 74 | MessageUtils.sendPlayerNotFoundMessage(sender, playerName); 75 | return; 76 | } 77 | LingemManager manager = GrusAPI.getLingemManager(); 78 | manager.addLingem(player, lingem); 79 | MessageUtils.sendPrefixMessage(sender,I18N.LINGEM_ADD.getMessage() 80 | .replace("%player_name%", playerName) 81 | .replace("%lingem%", "" + lingem)); 82 | } 83 | if ("remove".equalsIgnoreCase(args[1])) { 84 | if (args.length <= 3) { 85 | MessageUtils.sendWrongUsageMessage(sender); 86 | return; 87 | } 88 | String playerName = args[2]; 89 | String lingem = args[3]; 90 | OfflinePlayer player; 91 | UUID uid; 92 | try { 93 | uid = UUID.fromString(playerName); 94 | player = Bukkit.getOfflinePlayer(uid); 95 | } catch (IllegalArgumentException exception) { 96 | player = Bukkit.getOfflinePlayer(playerName); 97 | } 98 | if (player == null) { 99 | MessageUtils.sendPlayerNotFoundMessage(sender, playerName); 100 | return; 101 | } 102 | LingemManager manager = GrusAPI.getLingemManager(); 103 | if (!manager.hasLingem(player)) { 104 | MessageUtils.sendPrefixMessage(sender,I18N.LINGEM_IS_NOT_EXIST.getMessage().replace("%lingem%", lingem)); 105 | return; 106 | } 107 | manager.removeLingem(player, lingem); 108 | MessageUtils.sendPrefixMessage(sender,I18N.LINGEM_REMOVE.getMessage() 109 | .replace("%player_name%", playerName) 110 | .replace("%lingem%", "" + lingem)); 111 | } 112 | 113 | if ("reset".equalsIgnoreCase(args[1])) { 114 | if (args.length <= 2) { 115 | MessageUtils.sendWrongUsageMessage(sender); 116 | return; 117 | } 118 | String playerName = args[2]; 119 | OfflinePlayer player; 120 | UUID uid; 121 | try { 122 | uid = UUID.fromString(playerName); 123 | player = Bukkit.getOfflinePlayer(uid); 124 | } catch (IllegalArgumentException exception) { 125 | player = Bukkit.getOfflinePlayer(playerName); 126 | } 127 | if (player == null) { 128 | MessageUtils.sendPlayerNotFoundMessage(sender, playerName); 129 | return; 130 | } 131 | LingemManager manager = GrusAPI.getLingemManager(); 132 | manager.resetLingem(player); 133 | MessageUtils.sendPrefixMessage(sender,I18N.LINGEM_RESET.getMessage().replace("%player_name%", playerName)); 134 | } 135 | } 136 | 137 | @Override 138 | public List tabComplete(String[] args) { 139 | List res = Lists.newArrayList("look", "add", "remove", "reset"); 140 | res.removeIf(s -> !s.startsWith(args[1])); 141 | return Lists.newArrayList(res); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/command/subcommand/MeCommand.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.command.subcommand; 2 | 3 | import me.clip.placeholderapi.PlaceholderAPI; 4 | import org.bukkit.command.CommandSender; 5 | import org.bukkit.entity.Player; 6 | import top.zoyn.grus.I18N; 7 | import top.zoyn.grus.command.SubCommand; 8 | import top.zoyn.grus.utils.MessageUtils; 9 | 10 | public class MeCommand implements SubCommand { 11 | 12 | @Override 13 | public void execute(CommandSender sender, String[] args) { 14 | if (!(sender instanceof Player)) { 15 | MessageUtils.sendPrefixMessage(sender,I18N.CONSOLE_MUST_BE_PLAYER.getMessage()); 16 | return; 17 | } 18 | Player player = (Player) sender; 19 | I18N.ME.getAsStringList().forEach(s -> { 20 | // 这里直接套用 PlaceholderAPI 里面的变量 21 | player.sendMessage(PlaceholderAPI.setPlaceholders(player, s)); 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/command/subcommand/ReloadCommand.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.command.subcommand; 2 | 3 | import org.bukkit.command.CommandSender; 4 | import top.zoyn.grus.Grus; 5 | import top.zoyn.grus.I18N; 6 | import top.zoyn.grus.api.GrusAPI; 7 | import top.zoyn.grus.command.SubCommand; 8 | import top.zoyn.grus.utils.MessageUtils; 9 | import top.zoyn.grus.utils.PermissionUtils; 10 | 11 | public class ReloadCommand implements SubCommand { 12 | 13 | @Override 14 | public void execute(CommandSender sender, String[] args) { 15 | if (PermissionUtils.nonAdminAuth(sender)) { 16 | return; 17 | } 18 | Grus.getInstance().reloadConfig(); 19 | GrusAPI.getLanguageManager().reload(); 20 | GrusAPI.getLingemManager().reload(); 21 | GrusAPI.getBoundaryExpDropManager().reload(); 22 | GrusAPI.getBoundaryManager().reload(); 23 | 24 | MessageUtils.sendPrefixMessage(sender,I18N.RELOAD_DONE.getMessage()); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/listener/EntityDeathListener.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.listener; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.entity.LivingEntity; 5 | import org.bukkit.entity.Player; 6 | import org.bukkit.event.EventHandler; 7 | import org.bukkit.event.Listener; 8 | import org.bukkit.event.entity.EntityDeathEvent; 9 | import top.zoyn.grus.Grus; 10 | import top.zoyn.grus.I18N; 11 | import top.zoyn.grus.api.GrusAPI; 12 | import top.zoyn.grus.manager.BoundaryExpDropManager; 13 | 14 | public class EntityDeathListener implements Listener { 15 | 16 | public EntityDeathListener(Grus instance) { 17 | Bukkit.getPluginManager().registerEvents(this, instance); 18 | } 19 | 20 | @EventHandler 21 | public void onDeath(EntityDeathEvent event) { 22 | LivingEntity entity = event.getEntity(); 23 | // 原版掉落处理 24 | if (GrusAPI.getBoundaryExpDropManager().getExpDropMode().equals(BoundaryExpDropManager.ExpDropMode.VANILLA)) { 25 | Player player = entity.getKiller(); 26 | double exp = GrusAPI.getBoundaryExpDropManager().getExpDropByEntityType(entity.getType()); 27 | if (player == null || 28 | // 说明不属于可获得修为的怪物 29 | exp <= 0) { 30 | return; 31 | } 32 | GrusAPI.getBoundaryManager().addBoundaryExp(player, exp); 33 | player.sendMessage(I18N.YOU_HAVE_GAINED_EXP.getMessage().replace("%exp%", "" + exp)); 34 | return; 35 | } 36 | // 真气掉落处理 37 | if (GrusAPI.getBoundaryExpDropManager().getExpDropMode().equals(BoundaryExpDropManager.ExpDropMode.ORB)) { 38 | double exp = GrusAPI.getBoundaryExpDropManager().getExpDropByEntityType(entity.getType()); 39 | GrusAPI.spawnChiOrb(entity.getLocation(), (int) exp); 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/listener/PlayerJoinListener.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.listener; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.bukkit.event.EventHandler; 5 | import org.bukkit.event.Listener; 6 | import org.bukkit.event.player.PlayerJoinEvent; 7 | 8 | public class PlayerJoinListener implements Listener { 9 | 10 | @EventHandler 11 | public void onJoin(PlayerJoinEvent event) { 12 | Player player = event.getPlayer(); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/manager/BoundaryExpDropManager.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.manager; 2 | 3 | import com.comphenix.protocol.PacketType; 4 | import com.comphenix.protocol.events.PacketAdapter; 5 | import com.comphenix.protocol.events.PacketContainer; 6 | import com.comphenix.protocol.events.PacketEvent; 7 | import com.comphenix.protocol.events.PacketListener; 8 | import com.google.common.collect.Maps; 9 | import org.bukkit.configuration.ConfigurationSection; 10 | import org.bukkit.entity.Entity; 11 | import org.bukkit.entity.EntityType; 12 | import org.bukkit.entity.ExperienceOrb; 13 | import org.bukkit.entity.Player; 14 | import top.zoyn.grus.Grus; 15 | import top.zoyn.grus.I18N; 16 | import top.zoyn.grus.api.GrusAPI; 17 | import top.zoyn.grus.model.ChiOrb; 18 | import top.zoyn.grus.utils.Logger; 19 | 20 | import java.util.Map; 21 | 22 | public class BoundaryExpDropManager { 23 | 24 | private Map defaultExpDrop = Maps.newHashMap(); 25 | 26 | private ExpDropMode MODE; 27 | private PacketListener chiOrbListener; 28 | 29 | public BoundaryExpDropManager() { 30 | // 真气球监听 31 | chiOrbListener = new PacketAdapter(Grus.getInstance(), PacketType.Play.Server.COLLECT) { 32 | @Override 33 | public void onPacketSending(PacketEvent event) { 34 | PacketContainer packet = event.getPacket(); 35 | Player player = event.getPlayer(); 36 | int collectedEntity = packet.getIntegers().read(0); 37 | 38 | // 经验球识别 39 | Entity entity = Grus.getInstance().getProtocolManager().getEntityFromID(event.getPlayer().getWorld(), collectedEntity); 40 | if (entity instanceof ExperienceOrb) { 41 | ExperienceOrb expOrb = (ExperienceOrb) entity; 42 | // 真气球识别 43 | if (ChiOrb.getChiOrbName().equalsIgnoreCase(expOrb.getCustomName())) { 44 | GrusAPI.getBoundaryManager().addBoundaryExp(player, expOrb.getExperience()); 45 | player.sendMessage(I18N.YOU_HAVE_GAINED_EXP.getMessage().replace("%exp%", "" + expOrb.getExperience())); 46 | } 47 | } 48 | } 49 | }; 50 | reload(); 51 | } 52 | 53 | public void reload() { 54 | defaultExpDrop.clear(); 55 | Grus.getInstance().getProtocolManager().removePacketListener(chiOrbListener); 56 | 57 | 58 | ConfigurationSection config = Grus.getInstance().getConfig().getConfigurationSection("boundary-exp-drop-settings"); 59 | MODE = ExpDropMode.valueOf(config.getString("mode").toUpperCase()); 60 | // config 预设数据 61 | config.getConfigurationSection("monster") 62 | .getKeys(false) 63 | .forEach(section -> defaultExpDrop.put(section.toUpperCase().replace("-", "_"), config.getDouble("monster." + section))); 64 | 65 | if (MODE.equals(ExpDropMode.ORB)) { 66 | // spigot.yml 经验球掉落合并 设置判断 67 | if (Grus.getInstance().getServer().spigot().getConfig().getInt("world-settings.default.merge-radius.exp") != -1) { 68 | Logger.error(I18N.CONSOLE_WRONG_CHI_ORB_SETTING.getMessage()); 69 | MODE = ExpDropMode.VANILLA; 70 | } else { 71 | // 真气球获取监听 72 | Grus.getInstance().getProtocolManager().addPacketListener(chiOrbListener); 73 | } 74 | } 75 | 76 | Logger.info(I18N.CONSOLE_LOAD_BOUNDARY_EXP_DROP.getMessage().replace("%mode%", MODE.toString())); 77 | } 78 | 79 | public double getExpDropByEntityType(EntityType type) { 80 | if (type == null) { 81 | return 0; 82 | } 83 | return defaultExpDrop.getOrDefault(type.toString(), 0D); 84 | } 85 | 86 | public ExpDropMode getExpDropMode() { 87 | return MODE; 88 | } 89 | 90 | /** 91 | * 经验(修为)掉落模式 92 | */ 93 | public enum ExpDropMode { 94 | VANILLA, 95 | ORB, 96 | NONE; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/manager/BoundaryManager.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.manager; 2 | 3 | import com.google.common.collect.Maps; 4 | import org.bukkit.Bukkit; 5 | import org.bukkit.ChatColor; 6 | import org.bukkit.OfflinePlayer; 7 | import org.bukkit.configuration.ConfigurationSection; 8 | import org.bukkit.configuration.file.FileConfiguration; 9 | import top.zoyn.grus.Grus; 10 | import top.zoyn.grus.I18N; 11 | import top.zoyn.grus.api.event.PlayerBoundaryExpChangeEvent; 12 | import top.zoyn.grus.utils.ConfigurationUtils; 13 | import top.zoyn.grus.utils.Logger; 14 | 15 | import java.io.File; 16 | import java.io.IOException; 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.UUID; 20 | import java.util.stream.Collectors; 21 | 22 | /** 23 | * 玩家境界管理器 24 | * 25 | * @author Zoyn 26 | * @since 2023/1/05 27 | */ 28 | public class BoundaryManager { 29 | 30 | /** 31 | * 玩家uid 对应 境界数值(经验值) 32 | */ 33 | private Map playerBoundary = Maps.newHashMap(); 34 | /** 35 | * 预设境界 来源于 config 当中 36 | */ 37 | private Map defaultBoundary = Maps.newLinkedHashMap(); 38 | /** 39 | * 预设境界的显示设定 40 | */ 41 | private Map boundaryDisplay = Maps.newHashMap(); 42 | 43 | /** 44 | * 无境界时的显示名 45 | */ 46 | private static String NO_BOUNDARY_DISPLAY; 47 | 48 | private File boundaryFile; 49 | private File boundaryFolder; 50 | private FileConfiguration boundaryDataConfig; 51 | 52 | public BoundaryManager() { 53 | reload(); 54 | } 55 | 56 | public void reload() { 57 | defaultBoundary.clear(); 58 | playerBoundary.clear(); 59 | 60 | boundaryFolder = new File(Grus.getInstance().getDataFolder(), "data"); 61 | boundaryFile = new File(boundaryFolder, "boundary-data.yml"); 62 | // 玩家境界灵气数据文件创建 63 | if (!boundaryFile.exists()) { 64 | boundaryFolder.mkdirs(); 65 | try { 66 | boundaryFile.createNewFile(); 67 | } catch (IOException e) { 68 | e.printStackTrace(); 69 | } 70 | } 71 | boundaryDataConfig = ConfigurationUtils.loadYML(boundaryFile); 72 | // 玩家数据 73 | ConfigurationSection data = boundaryDataConfig.getConfigurationSection("data"); 74 | if (data != null) { 75 | data.getKeys(false).forEach(uid -> playerBoundary.put(UUID.fromString(uid), boundaryDataConfig.getDouble("data." + uid))); 76 | } 77 | 78 | ConfigurationSection boundaryConfig = Grus.getInstance().getConfig().getConfigurationSection("boundary-settings"); 79 | // config 预设数据 80 | boundaryConfig.getConfigurationSection("level") 81 | .getKeys(false) 82 | .forEach(section -> defaultBoundary.put(section, boundaryConfig.getDouble("level." + section))); 83 | boundaryConfig.getConfigurationSection("display") 84 | .getKeys(false) 85 | .forEach(section -> boundaryDisplay.put(section, ChatColor.translateAlternateColorCodes('&', boundaryConfig.getString("display." + section)))); 86 | 87 | // 无境界时的显示名 88 | NO_BOUNDARY_DISPLAY = ChatColor.translateAlternateColorCodes('&', boundaryConfig.getString("no-boundary-display")); 89 | 90 | Logger.info(I18N.CONSOLE_LOAD_BOUNDARY.getMessage() 91 | .replace("%num%", "" + defaultBoundary.keySet().size()) 92 | .replace("%content%", defaultBoundary.keySet().toString())); 93 | } 94 | 95 | public void save() { 96 | for (Map.Entry entry : playerBoundary.entrySet()) { 97 | boundaryDataConfig.set("data." + entry.getKey(), entry.getValue()); 98 | } 99 | // 保存数据 100 | ConfigurationUtils.saveYML(boundaryDataConfig, boundaryFile); 101 | } 102 | 103 | /** 104 | * 取得境界的展示名 105 | * 106 | * @param boundary 指定的境界 107 | * @return 如果无法找到对应的境界展示名则会返回 无境界时的展示名 108 | */ 109 | public String getDisplayBoundary(String boundary) { 110 | return boundaryDisplay.getOrDefault(boundary, NO_BOUNDARY_DISPLAY); 111 | } 112 | 113 | /** 114 | * 获取玩家对应的境界 115 | * 116 | * @param player 指定的玩家 117 | * @return 玩家当前的境界 118 | */ 119 | public String getPlayerBoundary(OfflinePlayer player) { 120 | String boundary = NO_BOUNDARY_DISPLAY; 121 | if (hasBoundary(player)) { 122 | double playerBoundaryExp = playerBoundary.get(player.getUniqueId()); 123 | // 查找玩家对应的境界 124 | for (Map.Entry entry : defaultBoundary.entrySet()) { 125 | if (entry.getValue() <= playerBoundaryExp) { 126 | boundary = entry.getKey(); 127 | } 128 | } 129 | } 130 | return boundary; 131 | } 132 | 133 | /** 134 | * 获取玩家的修为值, 即修炼得到的经验值 135 | * 136 | * @param player 指定的玩家 137 | * @return 玩家灵力值 138 | */ 139 | public double getPlayerBoundaryExp(OfflinePlayer player) { 140 | if (hasBoundary(player)) { 141 | return playerBoundary.get(player.getUniqueId()); 142 | } 143 | return 0; 144 | } 145 | 146 | /** 147 | * 给指定的玩家增加指定的修为 148 | * 149 | * @param player 指定的玩家 150 | * @param exp 指定的修为 151 | */ 152 | public void addBoundaryExp(OfflinePlayer player, double exp) { 153 | if (hasBoundary(player)) { 154 | double result = playerBoundary.get(player.getUniqueId()) + exp; 155 | changePlayerBoundary(player, result); 156 | } else { 157 | PlayerBoundaryExpChangeEvent event = new PlayerBoundaryExpChangeEvent(0, exp, player.getPlayer()); 158 | Bukkit.getPluginManager().callEvent(event); 159 | if (event.isCancelled()) { 160 | return; 161 | } 162 | playerBoundary.put(player.getUniqueId(), exp); 163 | } 164 | } 165 | 166 | /** 167 | * 移除指定的玩家指定的修为值 168 | * 169 | * @param player 指定的玩家 170 | * @param exp 指定的修为 171 | */ 172 | public void removeBoundaryExp(OfflinePlayer player, double exp) { 173 | if (hasBoundary(player)) { 174 | double result = playerBoundary.get(player.getUniqueId()) - exp; 175 | changePlayerBoundary(player, result); 176 | } 177 | } 178 | 179 | /** 180 | * 设置指定的玩家的修为值 181 | * 182 | * @param player 指定的玩家 183 | * @param result 结果修为 184 | */ 185 | private void changePlayerBoundary(OfflinePlayer player, double result) { 186 | if (result < 0) { 187 | result = 0; 188 | } 189 | PlayerBoundaryExpChangeEvent event = new PlayerBoundaryExpChangeEvent(playerBoundary.get(player.getUniqueId()), result, player.getPlayer()); 190 | Bukkit.getPluginManager().callEvent(event); 191 | if (event.isCancelled()) { 192 | return; 193 | } 194 | playerBoundary.put(player.getUniqueId(), result); 195 | } 196 | 197 | /** 198 | * 判断一名玩家是否有修为值 199 | * 200 | * @param player 指定的玩家 201 | * @return 若玩家已有修为则会返回 true 202 | */ 203 | public boolean hasBoundary(OfflinePlayer player) { 204 | return playerBoundary.containsKey(player.getUniqueId()); 205 | } 206 | 207 | /* 208 | * 计算一名玩家的经验值 209 | * 210 | * @param player 指定的玩家 211 | * @return 当前玩家境界的经验值 212 | * 213 | * */ 214 | public double getExcessExp(OfflinePlayer player){ 215 | double playerTotalExp = getPlayerBoundaryExp(player); 216 | List expList = defaultBoundary.values().stream() 217 | .filter(amount -> playerTotalExp > amount) 218 | .collect(Collectors.toList()); 219 | return playerTotalExp - expList.get(expList.size() + 1); 220 | } 221 | 222 | } 223 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/manager/GodTrialManager.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.manager; 2 | 3 | /** 4 | * 渡劫管理器 5 | * 6 | * @author Zoyn 7 | */ 8 | public class GodTrialManager { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/manager/ItemManager.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.manager; 2 | 3 | import com.google.common.collect.Maps; 4 | import top.zoyn.grus.model.MagicTreasure; 5 | 6 | import java.util.Map; 7 | 8 | public class ItemManager { 9 | 10 | private final Map treasures = Maps.newHashMap(); 11 | 12 | public ItemManager() { 13 | reload(); 14 | } 15 | 16 | public void reload() { 17 | 18 | } 19 | 20 | public void addItem(String key, MagicTreasure treasure) { 21 | 22 | } 23 | 24 | public void RemoveItem(String key) { 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/manager/LanguageManager.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.manager; 2 | 3 | import org.bukkit.configuration.file.FileConfiguration; 4 | import top.zoyn.grus.Grus; 5 | import top.zoyn.grus.I18N; 6 | import top.zoyn.grus.utils.ConfigurationUtils; 7 | import top.zoyn.grus.utils.Logger; 8 | 9 | import java.io.File; 10 | 11 | /** 12 | * 语言管理器 13 | * 14 | * @author Zoyn 15 | */ 16 | public class LanguageManager { 17 | 18 | private String language; 19 | private File languageFile; 20 | private File languageFolder; 21 | private FileConfiguration languageConfig; 22 | 23 | public LanguageManager() { 24 | reload(); 25 | } 26 | 27 | public LanguageManager reload() { 28 | // 默认语言为 zh-CN 29 | language = Grus.getInstance().getConfig().getString("language", "zh-CN"); 30 | languageFolder = new File(Grus.getInstance().getDataFolder(), "language"); 31 | languageFile = new File(languageFolder, language + ".yml"); 32 | languageConfig = ConfigurationUtils.loadYML(languageFile); 33 | 34 | // 重新加载多语言系统 35 | for (I18N value : I18N.values()) { 36 | value.init(languageConfig); 37 | } 38 | 39 | Logger.info(I18N.CONSOLE_SET_LANGUAGE.getMessage() + language); 40 | return this; 41 | } 42 | 43 | public FileConfiguration getLanguageConfig() { 44 | return languageConfig; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/manager/LingemManager.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.manager; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.google.common.collect.Maps; 5 | import org.bukkit.ChatColor; 6 | import org.bukkit.OfflinePlayer; 7 | import org.bukkit.configuration.ConfigurationSection; 8 | import org.bukkit.configuration.file.FileConfiguration; 9 | import top.zoyn.grus.Grus; 10 | import top.zoyn.grus.I18N; 11 | import top.zoyn.grus.utils.ConfigurationUtils; 12 | import top.zoyn.grus.utils.Logger; 13 | 14 | import java.io.File; 15 | import java.io.IOException; 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.UUID; 19 | 20 | /** 21 | * 玩家灵根管理器 22 | * 23 | * @author Zoyn 24 | * @since 2023/1/05 25 | */ 26 | public class LingemManager { 27 | 28 | /** 29 | * 玩家uid 对应 灵根数组 30 | */ 31 | private Map> playerLingem = Maps.newHashMap(); 32 | private Map lingemChances = Maps.newHashMap(); 33 | /** 34 | * 预设灵根的显示设定 35 | */ 36 | private Map lingemDisplay = Maps.newHashMap(); 37 | private File lingemFile; 38 | private File lingemFolder; 39 | private FileConfiguration lingemDataConfig; 40 | 41 | public LingemManager() { 42 | reload(); 43 | } 44 | 45 | public void reload() { 46 | lingemChances.clear(); 47 | playerLingem.clear(); 48 | 49 | lingemFolder = new File(Grus.getInstance().getDataFolder(), "data"); 50 | lingemFile = new File(lingemFolder, "lingem-data.yml"); 51 | // 灵根数据文件创建 52 | if (!lingemFile.exists()) { 53 | lingemFolder.mkdirs(); 54 | try { 55 | lingemFile.createNewFile(); 56 | } catch (IOException e) { 57 | e.printStackTrace(); 58 | } 59 | } 60 | lingemDataConfig = ConfigurationUtils.loadYML(lingemFile); 61 | // 玩家数据 62 | ConfigurationSection data = lingemDataConfig.getConfigurationSection("data"); 63 | if (data != null) { 64 | data.getKeys(false).forEach(uid -> playerLingem.put(UUID.fromString(uid), lingemDataConfig.getStringList("data." + uid))); 65 | } 66 | 67 | ConfigurationSection lingemConfig = Grus.getInstance().getConfig().getConfigurationSection("lingem-settings"); 68 | // config 预设数据 69 | lingemConfig.getConfigurationSection("chances").getKeys(false) 70 | .forEach(section -> lingemChances.put(section, lingemConfig.getDouble("chances." + section))); 71 | lingemConfig.getConfigurationSection("display").getKeys(false) 72 | .forEach(section -> lingemDisplay.put(section, ChatColor.translateAlternateColorCodes('&', lingemConfig.getString("display." + section)))); 73 | 74 | Logger.info(I18N.CONSOLE_LOAD_LINGEM.getMessage() 75 | .replace("%num%", "" + lingemChances.keySet().size()) 76 | .replace("%content%", lingemChances.keySet().toString())); 77 | } 78 | 79 | public void save() { 80 | for (Map.Entry> entry : playerLingem.entrySet()) { 81 | lingemDataConfig.set("data." + entry.getKey(), entry.getValue()); 82 | } 83 | // 保存数据 84 | ConfigurationUtils.saveYML(lingemDataConfig, lingemFile); 85 | } 86 | 87 | public Map> getPlayerLingem() { 88 | return playerLingem; 89 | } 90 | 91 | public Map getLingemChances() { 92 | return lingemChances; 93 | } 94 | 95 | /** 96 | * 获取玩家的灵根 97 | * 98 | * @return 灵根列表 99 | */ 100 | public List getPlayerLingem(OfflinePlayer player) { 101 | if (hasLingem(player)) { 102 | return playerLingem.get(player.getUniqueId()); 103 | } 104 | return Lists.newArrayList(); 105 | } 106 | 107 | public List getPlayerDisplayLingem(OfflinePlayer player) { 108 | List playerLingem = getPlayerLingem(player); 109 | // 无灵根时 110 | if (getPlayerLingem(player).isEmpty()) { 111 | return playerLingem; 112 | } 113 | List display = Lists.newArrayList(); 114 | playerLingem.forEach(name -> display.add(getLingemDisplayNameByLingem(name))); 115 | return display; 116 | } 117 | 118 | public String getLingemDisplayNameByLingem(String lingem) { 119 | if (hasLingemInDefault(lingem)) { 120 | return lingemDisplay.get(lingem); 121 | } 122 | return I18N.LINGEM_HAVE_NOT_DISPLAY.getMessage(); 123 | } 124 | 125 | /** 126 | * 判断一名玩家是否有灵根 127 | * 128 | * @param player 要判断的玩家 129 | * @return 如果有灵根则返回true 130 | */ 131 | public boolean hasLingem(OfflinePlayer player) { 132 | if (playerLingem.containsKey(player.getUniqueId())) { 133 | return !playerLingem.get(player.getUniqueId()).isEmpty(); 134 | } 135 | return false; 136 | } 137 | 138 | /** 139 | * 移除一名玩家的某个灵根 140 | * 141 | * @param player 指定的玩家 142 | * @param lingem 指定的灵根 143 | */ 144 | public void removeLingem(OfflinePlayer player, String lingem) { 145 | if (hasLingem(player)) { 146 | playerLingem.get(player.getUniqueId()).remove(lingem); 147 | } 148 | } 149 | 150 | /** 151 | * 重置某一个玩家的所有灵根数据 152 | * 153 | * @param player 指定的玩家 154 | */ 155 | public void resetLingem(OfflinePlayer player) { 156 | // 移除数据 157 | playerLingem.remove(player.getUniqueId()); 158 | 159 | List lingems = Lists.newArrayList(); 160 | lingemChances.keySet().forEach(lingem -> { 161 | // 多重灵根判断 162 | if (Math.random() < lingemChances.get(lingem)) { 163 | lingems.add(lingem); 164 | } 165 | }); 166 | if (lingems.size() == 0) { 167 | lingems.add(lingemChances.keySet() 168 | .stream() 169 | .findAny() 170 | .get() 171 | ); 172 | } 173 | playerLingem.put(player.getUniqueId(), lingems); 174 | } 175 | 176 | /** 177 | * 给指定的玩家增加一个灵根 178 | * 179 | * @param player 指定的玩家 180 | * @param lingem 指定的灵根 181 | */ 182 | public void addLingem(OfflinePlayer player, String lingem) { 183 | if (hasLingem(player)) { 184 | playerLingem.get(player.getUniqueId()).add(lingem); 185 | } else { 186 | playerLingem.put(player.getUniqueId(), Lists.newArrayList(lingem)); 187 | } 188 | } 189 | 190 | /** 191 | * 查询给定的灵根是否属于 config 中的灵根 192 | * 193 | * @param lingem 给定的灵根 194 | * @return 如果存在于 config 中则返回 true 195 | */ 196 | public boolean hasLingemInDefault(String lingem) { 197 | return lingemChances.containsKey(lingem); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/model/ChiOrb.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.model; 2 | 3 | import org.bukkit.ChatColor; 4 | import org.bukkit.Location; 5 | import org.bukkit.entity.EntityType; 6 | import org.bukkit.entity.ExperienceOrb; 7 | import top.zoyn.grus.Grus; 8 | 9 | /** 10 | * 表示一个真气的实体 11 | * 12 | * @author Zoyn 13 | */ 14 | public class ChiOrb { 15 | 16 | private ExperienceOrb expOrb; 17 | 18 | public ChiOrb(Location spawnLocation, int exp) { 19 | expOrb = (ExperienceOrb) spawnLocation.getWorld().spawnEntity(spawnLocation, EntityType.EXPERIENCE_ORB); 20 | expOrb.setCustomName(ChatColor.translateAlternateColorCodes('&', Grus.getInstance().getConfig().getString("boundary-exp-drop-settings.chi-orb-name"))); 21 | expOrb.setCustomNameVisible(true); 22 | expOrb.setExperience(exp); 23 | } 24 | 25 | public ExperienceOrb getEntity() { 26 | return expOrb; 27 | } 28 | 29 | public Location getLocation() { 30 | return expOrb.getLocation(); 31 | } 32 | 33 | public int getBoundaryExp() { 34 | return expOrb.getExperience(); 35 | } 36 | 37 | public void setBoundaryExp(int exp) { 38 | expOrb.setExperience(exp); 39 | } 40 | 41 | public static String getChiOrbName() { 42 | return ChatColor.translateAlternateColorCodes('&', Grus.getInstance().getConfig().getString("chi-orb-name")); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/model/MagicTreasure.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.model; 2 | 3 | import org.bukkit.entity.LivingEntity; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.inventory.ItemStack; 6 | 7 | /** 8 | * 代表一个法器 9 | * 10 | * @author Zoyn 11 | */ 12 | public abstract class MagicTreasure { 13 | 14 | private ItemStack itemStack; 15 | 16 | public MagicTreasure() { 17 | } 18 | 19 | public ItemStack getItemStack() { 20 | return itemStack; 21 | } 22 | 23 | public void setItemStack(ItemStack itemStack) { 24 | this.itemStack = itemStack; 25 | } 26 | 27 | public abstract void onInteract(Player player); 28 | 29 | public abstract void onAttack(LivingEntity attack, LivingEntity entity); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/papi/GrusExpansion.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.papi; 2 | 3 | import me.clip.placeholderapi.expansion.PlaceholderExpansion; 4 | import org.bukkit.OfflinePlayer; 5 | import top.zoyn.grus.Grus; 6 | import top.zoyn.grus.I18N; 7 | import top.zoyn.grus.api.GrusAPI; 8 | import top.zoyn.grus.manager.BoundaryManager; 9 | import top.zoyn.grus.manager.LingemManager; 10 | 11 | public class GrusExpansion extends PlaceholderExpansion { 12 | 13 | @Override 14 | public String getAuthor() { 15 | return "Zoyn"; 16 | } 17 | 18 | @Override 19 | public String getIdentifier() { 20 | return "grus"; 21 | } 22 | 23 | @Override 24 | public String getVersion() { 25 | return Grus.getInstance().getDescription().getVersion(); 26 | } 27 | 28 | @Override 29 | public String onRequest(OfflinePlayer player, String params) { 30 | BoundaryManager boundaryManager = GrusAPI.getBoundaryManager(); 31 | LingemManager lingemManager = GrusAPI.getLingemManager(); 32 | switch (params.toLowerCase()){ 33 | case "boundary_level": 34 | return boundaryManager.getDisplayBoundary(boundaryManager.getPlayerBoundary(player)); 35 | case "boundary_exp": 36 | return "" + GrusAPI.getBoundaryManager().getPlayerBoundaryExp(player); 37 | case "boundary_excess_exp": 38 | return "" + GrusAPI.getBoundaryManager().getExcessExp(player); 39 | case "lingem": 40 | return lingemManager.hasLingem(player) ? lingemManager.getPlayerDisplayLingem(player).toString() : I18N.NO_LINGEM.getMessage(); 41 | default: 42 | } 43 | return null; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/utils/ConfigurationUtils.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.utils; 2 | 3 | import org.bukkit.configuration.file.FileConfiguration; 4 | import org.bukkit.configuration.file.YamlConfiguration; 5 | import org.yaml.snakeyaml.DumperOptions; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.lang.reflect.Field; 10 | 11 | /** 12 | * Easy to use yml 13 | * 14 | * @author Zoyn 15 | * @since 2023-1-14 16 | */ 17 | public final class ConfigurationUtils { 18 | 19 | private ConfigurationUtils() { 20 | } 21 | 22 | public static FileConfiguration loadYML(String path) { 23 | File file = new File(path); 24 | return loadYML(file); 25 | } 26 | 27 | 28 | public static void saveYML(FileConfiguration fileConfiguration, File file) { 29 | try { 30 | fileConfiguration.save(file); 31 | } catch (IOException e) { 32 | e.printStackTrace(); 33 | } 34 | } 35 | 36 | @SuppressWarnings("all") 37 | public static FileConfiguration loadYML(File file) { 38 | if (!file.exists()) { 39 | try { 40 | file.createNewFile(); 41 | } catch (IOException e) { 42 | e.printStackTrace(); 43 | } 44 | } 45 | FileConfiguration YML = YamlConfiguration.loadConfiguration(file); 46 | DumperOptions yamlOptions; 47 | try { 48 | Field f = YamlConfiguration.class.getDeclaredField("yamlOptions"); 49 | f.setAccessible(true); 50 | yamlOptions = new DumperOptions() { 51 | @Override 52 | public void setAllowUnicode(boolean allowUnicode) { 53 | super.setAllowUnicode(false); 54 | } 55 | 56 | @Override 57 | public void setLineBreak(LineBreak lineBreak) { 58 | super.setLineBreak(LineBreak.getPlatformLineBreak()); 59 | } 60 | }; 61 | yamlOptions.setLineBreak(DumperOptions.LineBreak.getPlatformLineBreak()); 62 | f.set(YML, yamlOptions); 63 | } catch (ReflectiveOperationException ex) { 64 | ex.printStackTrace(); 65 | } 66 | return YML; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/utils/ItemBuilder.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.utils; 2 | 3 | import org.bukkit.Material; 4 | import org.bukkit.enchantments.Enchantment; 5 | import org.bukkit.inventory.ItemFlag; 6 | import org.bukkit.inventory.ItemStack; 7 | import org.bukkit.inventory.meta.ItemMeta; 8 | 9 | import java.util.Arrays; 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | 13 | /** 14 | * @author Zoyn 15 | */ 16 | public class ItemBuilder { 17 | 18 | private final ItemStack itemStack; 19 | private final ItemMeta itemMeta; 20 | 21 | public ItemBuilder() { 22 | this(Material.STONE, 1, 0); 23 | } 24 | 25 | public ItemBuilder(Material material) { 26 | this(material, 1, 0); 27 | } 28 | 29 | public ItemBuilder(Material material, int amount, int damage) { 30 | this(material, amount, (short) damage); 31 | } 32 | 33 | public ItemBuilder(ItemStack itemStack) { 34 | this(itemStack.getType(), itemStack.getAmount(), itemStack.getDurability()); 35 | } 36 | 37 | public ItemBuilder(Material material, int amount, short damage) { 38 | this.itemStack = new ItemStack(material, amount, damage); 39 | this.itemMeta = itemStack.getItemMeta(); 40 | } 41 | 42 | public ItemBuilder material(Material material) { 43 | itemStack.setType(material); 44 | return this; 45 | } 46 | 47 | public ItemBuilder amount(int amount) { 48 | itemStack.setAmount(amount); 49 | return this; 50 | } 51 | 52 | public ItemBuilder itemFlag(ItemFlag... itemFlags) { 53 | itemMeta.addItemFlags(itemFlags); 54 | return this; 55 | } 56 | 57 | public ItemBuilder glow(boolean isGlow) { 58 | if (isGlow) { 59 | itemMeta.addEnchant(Enchantment.DURABILITY, 1, true); 60 | } else { 61 | itemMeta.getEnchants().keySet().forEach(itemMeta::removeEnchant); 62 | } 63 | return this; 64 | } 65 | 66 | public ItemBuilder lore(List lore) { 67 | itemMeta.setLore(lore.stream().map(s -> s.replace("&", "§")).collect(Collectors.toList())); 68 | return this; 69 | } 70 | 71 | public ItemBuilder lore(String... lore) { 72 | itemMeta.setLore(Arrays.stream(lore).map(s -> s.replace("&", "§")).collect(Collectors.toList())); 73 | return this; 74 | } 75 | 76 | public ItemBuilder displayName(String displayName) { 77 | itemMeta.setDisplayName(displayName.replace("&", "§")); 78 | return this; 79 | } 80 | 81 | public ItemBuilder enchant(Enchantment enchantment, int level, boolean ignoreLevelRestriction) { 82 | itemMeta.addEnchant(enchantment, level, ignoreLevelRestriction); 83 | return this; 84 | } 85 | 86 | public ItemStack build() { 87 | itemStack.setItemMeta(itemMeta); 88 | return itemStack; 89 | } 90 | 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/utils/Logger.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.utils; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.command.ConsoleCommandSender; 5 | import top.zoyn.grus.I18N; 6 | 7 | import java.util.Arrays; 8 | 9 | /** 10 | * @author Zoyn 11 | * @since 2023-1-14 12 | */ 13 | public final class Logger { 14 | 15 | private static final ConsoleCommandSender logger = Bukkit.getConsoleSender(); 16 | 17 | public static void info(String msg) { 18 | logger.sendMessage(I18N.MESSAGE_PREFIX.getMessage() + msg); 19 | } 20 | 21 | public static void info(String... msg) { 22 | Arrays.stream(msg).forEach(Logger::info); 23 | } 24 | 25 | public static void warn(String msg) { 26 | logger.sendMessage(I18N.MESSAGE_PREFIX.getMessage() + "§f| §c§lWARN§7] §r" + msg); 27 | } 28 | 29 | public static void warn(String... msg) { 30 | Arrays.stream(msg).forEach(Logger::warn); 31 | } 32 | 33 | public static void error(String msg) { 34 | logger.sendMessage(I18N.MESSAGE_PREFIX.getMessage() + "§f| §4§lERROR§7] §r" + msg); 35 | } 36 | 37 | public static void error(String... msg) { 38 | Arrays.stream(msg).forEach(Logger::error); 39 | } 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/utils/LoreMap.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.utils; 2 | 3 | import java.util.concurrent.ConcurrentHashMap; 4 | 5 | /** 6 | * 只读Lore对象存储Map 7 | *

8 | * 来源于: https://github.com/TabooLib/taboolib/blob/master/common-5/src/main/java/taboolib/common5/LoreMap.java 9 | * 10 | * @author YiMiner 11 | **/ 12 | public class LoreMap { 13 | 14 | private final TrieNode root = new TrieNode(); 15 | 16 | private final boolean ignorePrefix; 17 | private final boolean ignoreSpace; 18 | private final boolean ignoreColor; 19 | 20 | /** 21 | * 初始化一个LoreMap 22 | * 23 | * @param ignoreSpace 是否无视空格 24 | * @param ignoreColor 是否无视颜色 25 | * @param ignorePrefix 是否无视前缀 26 | **/ 27 | public LoreMap(boolean ignoreSpace, boolean ignoreColor, boolean ignorePrefix) { 28 | this.ignoreSpace = ignoreSpace; 29 | this.ignoreColor = ignoreColor; 30 | this.ignorePrefix = ignorePrefix; 31 | } 32 | 33 | /** 34 | * 初始化一个LoreMap, 匹配lore前缀, 无视颜色和空格 35 | **/ 36 | public LoreMap() { 37 | this(true, true, false); 38 | } 39 | 40 | /** 41 | * 向LoreMap中放入lore和对应的对象 42 | * 43 | * @param lore 物品lore 44 | * @param value 要放入的对象 45 | **/ 46 | public void put(String lore, T value) { 47 | lore = lore.replaceAll("&", "§"); 48 | if (ignoreSpace) { 49 | lore = lore.replaceAll("\\s", ""); 50 | } 51 | if (ignoreColor) { 52 | lore = lore.replaceAll("§.", ""); 53 | } 54 | int depth = 0; 55 | TrieNode current = root; 56 | while (depth < lore.length()) { 57 | LoreChar c = new LoreChar(lore.charAt(depth)); 58 | if (current.child.containsKey(c)) { 59 | current = current.child.get(c); 60 | } else { 61 | TrieNode node = new TrieNode(); 62 | node.depth++; 63 | node.pre = current; 64 | current.child.put(c, node); 65 | current = node; 66 | } 67 | if (depth == lore.length() - 1) { 68 | current.obj = value; 69 | } 70 | depth++; 71 | } 72 | } 73 | 74 | /** 75 | * 查询lore对应的对象 76 | * 77 | * @return 查到的对象. 无则返回null 78 | **/ 79 | public T get(String lore) { 80 | int depth = 0; 81 | if (ignoreSpace) { 82 | lore = lore.replaceAll("\\s", ""); 83 | } 84 | if (ignoreColor) { 85 | lore = lore.replaceAll("§.", ""); 86 | } 87 | TrieNode current = root; 88 | if (ignorePrefix) { 89 | while (depth < lore.length()) { 90 | if (root.child.containsKey(new LoreChar(lore.charAt(depth)))) { 91 | break; 92 | } 93 | depth++; 94 | } 95 | } 96 | while (depth < lore.length()) { 97 | LoreChar c = new LoreChar(lore.charAt(depth)); 98 | TrieNode node = current.child.get(c); 99 | if (node == null) { 100 | return null; 101 | } 102 | if (node.obj != null) { 103 | return node.obj; 104 | } 105 | current = node; 106 | depth++; 107 | } 108 | return null; 109 | } 110 | 111 | public MatchResult getMatchResult(String lore) { 112 | int depth = 0; 113 | if (ignoreSpace) { 114 | lore = lore.replaceAll("\\s", ""); 115 | } 116 | if (ignoreColor) { 117 | lore = lore.replaceAll("§.", ""); 118 | } 119 | if (ignorePrefix) { 120 | while (depth < lore.length()) { 121 | if (root.child.containsKey(new LoreChar(lore.charAt(depth)))) { 122 | break; 123 | } 124 | depth++; 125 | } 126 | } 127 | TrieNode current = root; 128 | while (depth < lore.length()) { 129 | LoreChar c = new LoreChar(lore.charAt(depth)); 130 | TrieNode node = current.child.get(c); 131 | if (node == null) { 132 | return null; 133 | } 134 | if (node.obj != null) { 135 | return new MatchResult<>(depth < lore.length() - 1 ? lore.substring(depth + 1) : null, node.obj); 136 | } 137 | current = node; 138 | depth++; 139 | } 140 | return null; 141 | } 142 | 143 | public void clear() { 144 | root.child.clear(); 145 | } 146 | 147 | public class TrieNode { 148 | final ConcurrentHashMap child = new ConcurrentHashMap<>(); 149 | TrieNode pre = null; 150 | T obj = null; 151 | int depth = 0; 152 | } 153 | 154 | public static class MatchResult { 155 | public final String remain; 156 | public final T obj; 157 | 158 | public MatchResult(String remain, T obj) { 159 | this.remain = remain; 160 | this.obj = obj; 161 | } 162 | } 163 | 164 | public static class LoreChar { 165 | 166 | private final char c; 167 | 168 | public LoreChar(char c) { 169 | this.c = c; 170 | } 171 | 172 | public char get() { 173 | return c; 174 | } 175 | 176 | @Override 177 | public int hashCode() { 178 | return c; 179 | } 180 | 181 | @Override 182 | public boolean equals(Object o) { 183 | return (o instanceof LoreChar) && ((LoreChar) o).c == c; 184 | } 185 | } 186 | 187 | } 188 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/utils/MessageUtils.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.utils; 2 | 3 | import org.bukkit.command.CommandSender; 4 | import org.jetbrains.annotations.NotNull; 5 | import top.zoyn.grus.I18N; 6 | 7 | /** 8 | * @author Crsuh2er0 9 | * @apiNote 10 | * @since 2023/1/18 11 | */ 12 | public class MessageUtils { 13 | private static final String PREFIX = I18N.MESSAGE_PREFIX.getMessage(); 14 | private MessageUtils() { 15 | } 16 | public static void sendPrefixMessage(@NotNull CommandSender sender, String message){ 17 | sender.sendMessage(PREFIX + message); 18 | } 19 | public static void sendWrongUsageMessage(CommandSender sender){ 20 | sendPrefixMessage(sender,I18N.WRONG_COMMAND_USAGE.getMessage()); 21 | } 22 | public static void sendPlayerNotFoundMessage(CommandSender sender,String playerName){ 23 | sendPrefixMessage(sender,I18N.PLAYER_DO_NOT_EXIST.getMessage().replace("%player_name%",playerName)); 24 | } 25 | public static void sendNotNumberMessage(CommandSender sender){ 26 | sendPrefixMessage(sender,I18N.NOT_NUMBER.getMessage()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/utils/PageableInventory.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.utils; 2 | 3 | import com.google.common.collect.Lists; 4 | import org.bukkit.Bukkit; 5 | import org.bukkit.Material; 6 | import org.bukkit.inventory.Inventory; 7 | import org.bukkit.inventory.InventoryHolder; 8 | import org.bukkit.inventory.ItemStack; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * 可分页的Inventory 14 | *

15 | * 我强烈建议你可以通过继承该类的方式进行编写 16 | * 如果直接通过 {@link InventoryHolder} 进行判断, 也许会出现监听至其他的Gui 17 | *

18 | * 19 | *

20 | * 使用方法: 21 | * 22 | * // PageableInventory pageInv = new PageableInventory(容器大小, 容器名字, 物品列表, 初始页数, 每页所显示的数量); 23 | * PageableInventory pageInv = new PageableInventory(54, "&aTest", 物品列表, 1, 45); 24 | * player.openInventory(pageInv.getInventory()); 25 | * 26 | *

27 | * 28 | * @author Zoyn 29 | */ 30 | public class PageableInventory implements InventoryHolder { 31 | 32 | private final Inventory inventory; 33 | private final List items; 34 | private final int itemsPerPage; 35 | private int page; 36 | private int maxPage; 37 | 38 | public PageableInventory(int size, String title, List items) { 39 | this(size, title, items, 1, 45); 40 | } 41 | 42 | public PageableInventory(int size, String title, ItemStack... itemStacks) { 43 | this(size, title, Lists.newArrayList(itemStacks), 1, 45); 44 | } 45 | 46 | /** 47 | * 可分页的Inventory 48 | * 49 | * @param size 容器大小 50 | * @param title 容器标题 51 | * @param items 所有容器内的物品 52 | * @param page 初始页数, 默认为 1 53 | * @param itemsPerPage 每页显示数量, 默认为 45 54 | */ 55 | public PageableInventory(int size, String title, List items, int page, int itemsPerPage) { 56 | this.inventory = Bukkit.createInventory(this, size, title.replace("&", "§")); 57 | this.items = items; 58 | this.page = page; 59 | this.itemsPerPage = itemsPerPage; 60 | 61 | build(); 62 | } 63 | 64 | public PageableInventory(Inventory inventory, List items, int page, int itemsPerPage) { 65 | this.inventory = inventory; 66 | this.items = items; 67 | this.page = page; 68 | this.itemsPerPage = itemsPerPage; 69 | 70 | build(); 71 | } 72 | 73 | private void build() { 74 | // 这里偷个懒, 直接 +1 了 75 | maxPage = (items.size() / itemsPerPage) + 1; 76 | 77 | if (!items.isEmpty()) { 78 | for (ItemStack itemStack : getItemsByPage(page)) { 79 | inventory.addItem(itemStack); 80 | } 81 | } 82 | } 83 | 84 | public List getItemsByPage(int page) { 85 | if (page <= 0) { 86 | return getItemsByPage(1); 87 | } 88 | 89 | List subList; 90 | int pageSize = itemsPerPage; 91 | int totalCount = items.size(); 92 | if (pageSize >= totalCount) { 93 | subList = items; 94 | } else { 95 | int fromIndex = Math.min(pageSize * (page - 1), totalCount); 96 | int endIndex = Math.min(pageSize * page, totalCount); 97 | 98 | subList = items.subList(fromIndex, endIndex); 99 | } 100 | return subList; 101 | } 102 | 103 | public void refresh() { 104 | // 检测是否需要清除 105 | if (inventory.getItem(0) != null || !inventory.getItem(0).getType().equals(Material.AIR)) { 106 | for (int i = 0; i < itemsPerPage; i++) { 107 | // 检测是否是最后一页的最后一个底的翻页 108 | if (inventory.getItem(i) == null || inventory.getItem(i).getType().equals(Material.AIR)) { 109 | break; 110 | } 111 | inventory.setItem(i, new ItemStack(Material.AIR)); 112 | } 113 | } 114 | 115 | if (!items.isEmpty()) { 116 | for (ItemStack itemStack : getItemsByPage(page)) { 117 | inventory.addItem(itemStack); 118 | } 119 | } 120 | } 121 | 122 | public void nextPage() { 123 | if (page >= maxPage) { 124 | page = maxPage; 125 | return; 126 | } 127 | this.page += 1; 128 | refresh(); 129 | } 130 | 131 | public void prevPage() { 132 | if (page <= 1) { 133 | page = 1; 134 | return; 135 | } 136 | this.page -= 1; 137 | refresh(); 138 | } 139 | 140 | public int getPage() { 141 | return page; 142 | } 143 | 144 | public void setPage(int page) { 145 | if (page <= 0) { 146 | this.page = 1; 147 | return; 148 | } 149 | if (page > maxPage) { 150 | this.page = maxPage; 151 | return; 152 | } 153 | this.page = page; 154 | refresh(); 155 | } 156 | 157 | public int getItemsPerPage() { 158 | return itemsPerPage; 159 | } 160 | 161 | @Override 162 | public Inventory getInventory() { 163 | return inventory; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/top/zoyn/grus/utils/PermissionUtils.java: -------------------------------------------------------------------------------- 1 | package top.zoyn.grus.utils; 2 | 3 | import org.bukkit.command.CommandSender; 4 | import org.jetbrains.annotations.NotNull; 5 | import top.zoyn.grus.I18N; 6 | 7 | /** 8 | * @author Crsuh2er0 9 | * @apiNote 10 | * @since 2023/1/18 11 | */ 12 | public class PermissionUtils { 13 | private PermissionUtils() { 14 | } 15 | 16 | /** 17 | * @return true if the sender is not op 18 | * false if the sender is op 19 | */ 20 | public static boolean nonAdminAuth(@NotNull CommandSender sender) { 21 | if (sender.isOp()) { 22 | return false; 23 | } else { 24 | sender.sendMessage(I18N.MESSAGE_PREFIX.getMessage() + I18N.NO_PERMISSION.getMessage()); 25 | return true; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | ################################### 2 | # Grus 配置文件 3 | ################################### 4 | 5 | # 插件语言, 默认为zh-CN 6 | # 可用选项请查看 ./Grus/language/ 文件夹 7 | language: zh-CN 8 | 9 | # 修仙者境界设定 10 | # 以下默认设定来源于 https://baike.baidu.com/item/凡人修仙传 11 | boundary-settings: 12 | no-boundary-display: "&f凡人" 13 | # 等级:应当要有的灵气值 14 | # 填写时请务必按照 从小到大的顺序填写 15 | level: 16 | 炼气: 500 17 | 筑基初阶: 1500 18 | 筑基中阶: 7000 19 | 筑基巅峰: 15000 20 | 结丹: 50000 21 | 元婴: 500000 22 | 化神: 5000000 23 | 炼虚: 50000000 24 | 合体: 500000000 25 | 大乘: 5000000000 26 | # 设置每个等级的显示名 27 | display: 28 | 炼气: "&f炼气" 29 | 筑基初阶: "&2筑基初阶" 30 | 筑基中阶: "&3筑基中阶" 31 | 筑基巅峰: "&5筑基中阶" 32 | 结丹: "&6炼气" 33 | 元婴: "&d炼气" 34 | 化神: "&s炼气" 35 | 炼虚: "&1炼气" 36 | 合体: "&c合体" 37 | 大乘: "&4大乘" 38 | 39 | # 灵根设定 40 | # 灵根指修仙者的属性, 表示修仙者的修行素质 41 | # 伪灵根:具有四、五种属性的灵根,很杂且不充裕,每种属性的灵根都不完全,修炼速度很慢。 42 | # 真灵根:具有两、三种属性的灵根,每种属性灵根充裕。修炼速度较快。 43 | # 天灵根:只有一种属性的单一灵根,灵根充裕。修炼速度是普通灵根的数倍,结丹时没有瓶颈。 44 | lingem-settings: 45 | # 获得每一种属性的概率 46 | # 若插件随机到修仙者无法获得任何灵根, 则会获得天灵根且随机一种灵根 (从某种意义上说也算是特别幸运) 47 | chances: 48 | 金: 0.2 49 | 木: 0.2 50 | 水: 0.2 51 | 火: 0.2 52 | 土: 0.2 53 | display: 54 | 金: "&6金灵根" 55 | 木: "&2木灵根" 56 | 水: "&9水灵根" 57 | 火: "&c火灵根" 58 | 土: "&e土灵根" 59 | 60 | # 境界修为值掉落设定 61 | boundary-exp-drop-settings: 62 | # 境界经验值怪物掉落模式 63 | # 可用选项: vanilla/orb/none 64 | # vanilla: 原版掉落模式, 适用于无其他RPG元素的生存服务器(当然 生存+RPG 也可以 只需要 MythicMobs 调用指令即可 但数值需要进行更改) 65 | # orb: 真气实体掉落方式, 适用于增加修仙的真实与趣味性, 在 vanilla的基础上 改为 击杀怪物掉落真气实体 66 | # 但是要注意的是 orb 会改变游戏世界设定将经验堆叠关闭以防止真气球和原版经验球合并 67 | # 需要您手动将 spigot.yml 当中的 world-settings.default.merge-radius.exp 修改为 -1 才可使用 68 | # none: 无掉落模式, 适用于安装有自定义实体的插件的自定义RPG实体服务器 69 | mode: "vanilla" 70 | # 在真气实体掉落方式下, 真气球的显示名 71 | chi-orb-name: "&e真气" 72 | # 以下数值只能为整数, 默认数值则是利用原版怪物的血量进行编写 73 | monster: 74 | blaze: 10 75 | cave-spider: 6 76 | creeper: 10 77 | elder-guardian: 40 78 | enderman: 20 79 | endermite: 4 80 | evoker: 12 81 | giant: 50 82 | guardian: 15 83 | husk: 10 84 | illager: 12 85 | illusioner: 16 86 | pig-zombie: 10 87 | silverfish: 4 88 | skeleton: 10 89 | spell-caster: 10 90 | spider: 8 91 | stray: 10 92 | vex: 7 93 | vindicator: 12 94 | witch: 13 95 | wither: 150 96 | wither-skeleton: 10 97 | zombie: 10 98 | zombie-villager: 10 99 | 100 | # 属性设定 101 | attribute-settings: 102 | # 属性系统模式 103 | # vanilla: 原版模式, 将会使用 Grus 自带的属性系统, 内容较少, 不会对原版战斗产生直接影响 104 | # 可以额外增加属性插件 AttributeSystem, SX-Attribute, AttributePlus, OriginAttribute 等进行自行操作即可 105 | # none: 无处理模式, 将会停用 Grus 自带的属性系统 106 | mode: vanilla 107 | 108 | # 渡劫设定 109 | god-trial-settings: 110 | # 渡劫设定 111 | mode: vanilla 112 | # 境界设定 113 | boundary: 114 | 练气: 115 | # 落雷数量 116 | amount: 60 117 | # 持续时间 118 | period: 60 119 | # 雷击伤害 120 | damage: 2 121 | 筑基巅峰: 122 | amount: 300 123 | period: 300 124 | damage: 10 125 | 结丹: 126 | amount: 500 127 | period: 300 128 | damage: 25 -------------------------------------------------------------------------------- /src/main/resources/language/en-US.yml: -------------------------------------------------------------------------------- 1 | ################################################# 2 | # en-US American English 3 | # Edit By: Zoyn 4 | ################################################# 5 | 6 | message-prefix: "&e[&6Grus&e] " 7 | unknown-command: "&cUnknown command!" 8 | no-permission: "&cYou don't have permission!" 9 | no-lingem: "&fThere is no lingem." 10 | you-have-gained-exp: "&eYou have gained &6%exp% &eboundary exp." 11 | not-number: "&cPlease enter a number." 12 | help: 13 | - "&7===== &e[&6Grus&e] &7=====" 14 | - " &e/grus me &6check your stats " 15 | - " &e/grus help &6looking for help " 16 | me: 17 | - "&eDear &f%player_name%" 18 | - "&6Your current Lingem attribute: &e%grus_lingem%" 19 | - "&6Your current boundary level: &e%grus_boundary_level%" 20 | - "&6Your current boundary experience: &e%grus_boundary_exp%" 21 | 22 | console-set-language: "Set language: " 23 | console-load-lingem: "Load lingem(of %num%): %content%" 24 | console-load-boundary: "Load boundary(of %num%): %content%" 25 | console-load-boundary-exp-drop: "Load boundary exp drop setting" 26 | console-must-be-player: "&cYou must be a player!" -------------------------------------------------------------------------------- /src/main/resources/language/zh-CN.yml: -------------------------------------------------------------------------------- 1 | ################################################# 2 | # zh-CN 中文 3 | # Edit By: Zoyn 4 | ################################################# 5 | 6 | message-prefix: "&e[&6Grus&e] " 7 | unknown-command: "&c未知命令!" 8 | no-permission: "&c权限不足!" 9 | # 原本这里是 无灵根 但是根据实际情况, 还是改成未知尚好 10 | no-lingem: "&f未知" 11 | reload-done: "&a重载成功" 12 | 13 | help: 14 | - "&7===== &e[&6Grus &f&l| &6修仙&e] &7=====" 15 | - " &e/grus help &6查阅帮助 " 16 | - " &e/grus me &6查看当前境界 " 17 | # 以下内容会承接上文并会对OP进行显示 18 | help-op: 19 | - " &e/grus boundary &6查阅境界修为管理帮助 " 20 | - " &e/grus lingem &6查阅灵根管理帮助 " 21 | - " &e/grus reload &6重新加载插件数据 " 22 | help-boundary: 23 | - "&e[&6修为更改&e] &f>> (该内容只有OP方可查询)" 24 | - " &e/grus boundary look <玩家名/UUID> &6查看该玩家的境界与修为 " 25 | - " &e/grus boundary add <玩家名/UUID> <修为值> &6增加该玩家的修为 " 26 | - " &e/grus boundary remove <玩家名/UUID> <修为值> &6减少该玩家的修为 " 27 | help-lingem: 28 | - "&e[&6灵根更改&e] &f>> (该内容只有OP方可查询)" 29 | - " &e/grus lingem look <玩家名/UUID> &6查看该玩家的灵根 " 30 | - " &e/grus lingem add <玩家名/UUID> <灵根名> &6增加该玩家一指定的灵根 " 31 | - " &e/grus lingem remove <玩家名/UUID> <灵根名> &6移除该玩家的某一灵根 " 32 | - " &e/grus lingem reset <玩家名/UUID> &6重置该玩家的灵根(常用于玩家陨落后重置灵根)" 33 | 34 | me: 35 | - " " 36 | - " " 37 | - " &e尊敬的 &f%player_name% &e道友" 38 | - " &6道友, 您当前的灵根为: &e%grus_lingem%" 39 | - " &6道友, 您当前的境界为: &e%grus_boundary_level%" 40 | - " &6道友, 您当前的修为共: &e%grus_boundary_exp%" 41 | - " " 42 | - " " 43 | you-have-gained-exp: "&e你得到了 &6%exp% &e点修为" 44 | player-do-not-exist: "&c玩家 %player_name% 不存在!" 45 | wrong-command-usage: "&c指令用法错误!" 46 | not-number: "&c你输入的不是一个数字!" 47 | boundary-look: 48 | - "&e道友 &f%player_name%&e(&f%player_uuid%&e)" 49 | - "&e该道友当前的灵根为: %grus_lingem%" 50 | - "&e该道友当前的境界为: %grus_boundary_level%" 51 | - "&e该道友当前的修为共: %grus_boundary_exp%" 52 | boundary-add: "&e成功向 %player_name% 增加了 %exp% 点修为" 53 | boundary-remove: "&e成功减少了 %player_name% 共 %exp% 点修为" 54 | lingem-look: 55 | - "&e道友 &f%player_name%&e(&f%player_uuid%&e)" 56 | - "&e该道友当前的灵根为: %grus_lingem%" 57 | - "&e该道友当前的境界为: %grus_boundary_level%" 58 | - "&e该道友当前的修为共: %grus_boundary_exp%" 59 | lingem-add: "&e成功向 %player_name% 增补了 %lingem%" 60 | lingem-is-not-exist: "&c灵根 %lingem% 不存在!" 61 | lingem-remove: "&e成功将 %player_name% 的 %lingem% 逐出体外" 62 | lingem-reset: "&e已将 %player_name% 的灵根进行重置" 63 | lingem-have-not-display: "&c该灵根未设置显示名!" 64 | 65 | console-wrong-chi-orb-setting: "&c你必须将服务端下 spigot.yml 下的 world-settings.default.merge-radius.exp 设置为 -1 才可以使用真气实体掉落方式, 已自动设置为 vanilla 掉落方式" 66 | console-set-language: "已设置语言: " 67 | console-load-lingem: "已读取灵根(共 %num% 个): %content%" 68 | console-load-boundary: "已读取境界设定(共 %num% 个): %content%" 69 | console-load-boundary-exp-drop: "已读取修为掉落设置, 当前掉落方式为: %mode%" 70 | console-must-be-player: "&c你必须是一名玩家!" -------------------------------------------------------------------------------- /src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: Grus 2 | version: '${version}' 3 | main: top.zoyn.grus.Grus 4 | api-version: 1.15 5 | authors: [ Zoyn ] 6 | depend: [ PlaceholderAPI, ProtocolLib ] 7 | commands: 8 | grus: 9 | description: "Main command" 10 | aliases: 11 | - "xiuxian" 12 | - "xian" 13 | --------------------------------------------------------------------------------