diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..6d5c333 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,45 @@ +name: build +on: + push: + branches: + - main +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: checkout repository + uses: actions/checkout@v2 + - name: validate gradle wrapper + uses: gradle/wrapper-validation-action@v1 + - name: setup jdk 8.0 + uses: actions/setup-java@v2 + with: + distribution: adopt + java-version: 8.0 + - name: make gradle wrapper executable + run: chmod +x ./gradlew + # 第一次构建 + - name: build + id: build_1 + run: ./gradlew build + # 第二次构建 + - name: build (retry 1) + id: build_2 + if: steps.build_1.outcome == 'failure' + run: ./gradlew build + # 第三次构建 + - name: build (retry 2) + id: build_3 + if: steps.build_2.outcome == 'failure' + run: ./gradlew build + # 第四次构建 + - name: build (retry 3) + id: build_4 + if: steps.build_3.outcome == 'failure' + run: ./gradlew build + # 上传构建文件 + - name: capture build artifacts + uses: actions/upload-artifact@v2 + with: + name: Artifacts + path: build/libs/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e0d53d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.gradle +.idea +build diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1625c17 --- /dev/null +++ b/LICENSE @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0e6aec5 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# TabooLib SDK + +## 构建发行版本 + +发行版本用于正常使用, 不含 TabooLib 本体。 + +``` +./gradlew build +``` + +## 构建开发版本 + +开发版本包含 TabooLib 本体, 用于开发者使用, 但不可运行。 + +``` +./gradlew taboolibBuildApi -PDeleteCode +``` + +> 参数 -PDeleteCode 表示移除所有逻辑代码以减少体积。 diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..8dd4e6e --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,48 @@ +import io.izzel.taboolib.gradle.* +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + java + id("io.izzel.taboolib") version "2.0.11" + id("org.jetbrains.kotlin.jvm") version "1.8.22" + kotlin("plugin.serialization") version "1.8.22" +} + +taboolib { + env { + // 安装模块 + install(UNIVERSAL, BUKKIT, BUKKIT_UTIL, BUKKIT_XSERIES, UI, NMS) + } + version { taboolib = "6.1.2-beta10" } +} + +repositories { + mavenCentral() + maven("https://repo.papermc.io/repository/maven-public/") +} + +dependencies { + compileOnly("com.mojang:authlib:1.5.25") + compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT") + compileOnly(kotlin("stdlib")) + compileOnly(fileTree("libs")) + + compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.3") + compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3") +} + +tasks.withType { + options.encoding = "UTF-8" +} + +tasks.withType { + kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs = listOf("-Xjvm-default=all") + } +} + +configure { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..f6283d0 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,6 @@ +group=com.mcstarrysky.land +version=1.0.0 +kotlin.incremental=true +kotlin.incremental.java=true +kotlin.caching.enabled=true +kotlin.parallel.tasks.in.project=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..94336fc Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..573fe49 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..cb68d4d --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "Land" \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/Land.kt b/src/main/kotlin/com/mcstarrysky/land/Land.kt new file mode 100644 index 0000000..f4b3df3 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/Land.kt @@ -0,0 +1,73 @@ +package com.mcstarrysky.land + +import com.mcstarrysky.land.manager.LandManager +import org.bukkit.inventory.ItemStack +import taboolib.common.env.RuntimeDependencies +import taboolib.common.env.RuntimeDependency +import taboolib.common.platform.Plugin +import taboolib.library.xseries.XMaterial +import taboolib.module.ui.virtual.InventoryHandler +import taboolib.platform.util.buildItem + +/** + * Land + * com.mcstarrysky.land.Land + * + * @author mical + * @since 2024/8/2 22:20 + */ +@RuntimeDependencies( + RuntimeDependency( + "org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.3", + test = "!kotlinx.serialization.Serializer", + relocate = ["!kotlin.", "!kotlin1822."] + ), + RuntimeDependency( + "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3", + test = "!kotlinx.serialization.json.Json", + relocate = ["!kotlin.", "!kotlin1822."] + ) +) +object Land : Plugin() { + + val tool: ItemStack = buildItem(XMaterial.STICK) { + name = "&a选择棒" + lore += listOf( + "&7左键任意方块选择区块1", + "&7右键任意方块选择区块2", + "&7输入&{#8abcd1}/land&7进入领地主菜单" + ) + colored() + } + + val crystal: ItemStack = buildItem(XMaterial.AMETHYST_SHARD) { + name = "&{#D8D8FA}开拓水晶" + lore += listOf( + "&7可以在创建", + "&7创建最小为&a3*3*3&7区块大小的领地", + "&7圈地时准星需指向该核心" + ) + colored() + } + + val freeLandTool: ItemStack = buildItem(XMaterial.ARROW) { + name = "&b免费领地棒" + lore += listOf( + "&7点击方块即可免费创建并获取一个领地", + "&7领地大小为5*5区块", + "&7领地中心为你点击的方块的位置", + "&7只能在&{#8abcd1}主世界&7使用,且只能使用一次", + "&7领地大小获取后可自行扩展" + ) + colored() + } + + override fun onEnable() { + LandManager.import() + InventoryHandler.instance + } + + override fun onDisable() { + LandManager.export() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/LandCommands.kt b/src/main/kotlin/com/mcstarrysky/land/LandCommands.kt new file mode 100644 index 0000000..cc29edb --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/LandCommands.kt @@ -0,0 +1,80 @@ +package com.mcstarrysky.land + +import com.mcstarrysky.land.manager.LandManager +import com.mcstarrysky.land.menu.LandMainMenu +import com.mcstarrysky.land.util.prettyInfo +import com.mcstarrysky.land.util.remove +import org.bukkit.Bukkit +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player +import taboolib.common.LifeCycle +import taboolib.common.platform.Awake +import taboolib.common.platform.command.PermissionDefault +import taboolib.common.platform.command.command +import taboolib.common.platform.command.player + +/** + * Land + * com.mcstarrysky.land.LandCommands + * + * @author mical + * @since 2024/8/3 14:18 + */ +object LandCommands { + + @Awake(LifeCycle.ENABLE) + fun commandLand() { + command("land", permission = "starrysky.land", permissionDefault = PermissionDefault.TRUE) { + execute { sender, _, _ -> + LandMainMenu.openMenu(sender) + } + } + } + + @Awake(LifeCycle.ENABLE) + fun commandGo() { + command("go", permission = "starrysky.go", permissionDefault = PermissionDefault.TRUE) { + dynamic("id") { + suggestionUncheck { sender, _ -> + val lands = LandManager.getLands(sender) + lands.map { it.id.toString() }.toMutableList().also { lands.map { it.name } } + } + exec { + val id = ctx["id"] + try { + val land = LandManager.lands.firstOrNull { it.id == id.toInt() } + if (land == null) { + sender.prettyInfo("指定 ID 的领地不存在") + return@exec + } + land.teleport(sender) + } catch (_: NumberFormatException) { + val land = LandManager.lands.firstOrNull { it.name == id } + if (land == null) { + sender.prettyInfo("指定名字的领地不存在(请检查大小写)") + return@exec + } + land.teleport(sender) + } + } + } + } + } + + @Awake(LifeCycle.ENABLE) + fun commandTest() { + command("landpdc", permission = "admin") { + exec { + sender.inventory.addItem(Land.tool.clone()) + sender.inventory.addItem(Land.crystal.clone()) + sender.inventory.addItem(Land.freeLandTool.clone()) + } + dynamic("player") { + exec { + val player = Bukkit.getPlayerExact(ctx["player"]) ?: return@exec + player.remove("land_free_created") + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/LandSettings.kt b/src/main/kotlin/com/mcstarrysky/land/LandSettings.kt new file mode 100644 index 0000000..36ddbb0 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/LandSettings.kt @@ -0,0 +1,26 @@ +package com.mcstarrysky.land + +import taboolib.library.configuration.ConfigurationSection +import taboolib.module.configuration.Config +import taboolib.module.configuration.ConfigNode +import taboolib.module.configuration.Configuration +import taboolib.module.configuration.conversion +import taboolib.module.configuration.util.getStringColored + +/** + * Land + * com.mcstarrysky.land.LandSettings + * + * @author mical + * @since 2024/8/3 15:51 + */ +object LandSettings { + + @Config(autoReload = true) + private lateinit var config: Configuration + + @delegate:ConfigNode("Settings.world-aliases") + val worldAliases: Map by conversion> { + getKeys(false).associateWith { getStringColored(it) ?: it } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/data/Land.kt b/src/main/kotlin/com/mcstarrysky/land/data/Land.kt new file mode 100644 index 0000000..f9887fc --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/data/Land.kt @@ -0,0 +1,103 @@ +package com.mcstarrysky.land.data + +import com.mcstarrysky.land.flag.PermTeleport +import com.mcstarrysky.land.manager.LandManager +import com.mcstarrysky.land.serializers.ChunkSerializer +import com.mcstarrysky.land.serializers.LocationSerializer +import com.mcstarrysky.land.serializers.UUIDSerializer +import com.mcstarrysky.land.util.DATE_FORMAT +import com.mcstarrysky.land.util.ZERO_UUID +import com.mcstarrysky.land.util.json +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import kotlinx.serialization.encodeToString +import org.bukkit.Bukkit +import org.bukkit.Chunk +import org.bukkit.Location +import org.bukkit.OfflinePlayer +import org.bukkit.entity.Player +import taboolib.common5.cbool +import java.util.Date +import java.util.UUID + +/** + * Land + * com.mcstarrysky.land.data.Land + * + * @author mical + * @since 2024/8/2 22:43 + */ +@Serializable +data class Land( + val id: Int, + var name: String, + @Serializable(with = UUIDSerializer::class) + var owner: UUID, + val timestamp: Long, + val world: String, + var description: String = "没有介绍", + val area: MutableList<@Serializable(with = ChunkSerializer::class) Chunk>, + var enterMessage: String? = "你进入了 &{#8abcd1}$name", + var leaveMessage: String? = "你离开了 &{#8abcd1}$name", + @Serializable(with = LocationSerializer::class) + var tpLocation: Location, + val cooperators: MutableList<@Serializable(with = UUIDSerializer::class) UUID>, + val flags: MutableMap = mutableMapOf() +) { + + @Transient + val date = DATE_FORMAT.format(Date(timestamp)) + + fun isInArea(location: Location): Boolean { + return area.any { it == location.chunk } + } + + fun teleport(player: Player) { + PermTeleport.teleport(player, this) + } + + fun hasPermission(player: Player): Boolean { + return player.isOp || player.uniqueId == owner || player.uniqueId in cooperators + } + + fun saveToString(): String { + return json.encodeToString(this) + } + + fun getOwner(): OfflinePlayer? { + if (owner != ZERO_UUID) { + return Bukkit.getOfflinePlayer(owner) + } + return null + } + + fun getOwnerName(): String { + return getOwner()?.name ?: "StarrySky 管理组" + } + + /** + * 适用于权限判断时 + */ + fun getFlag(flag: String): Boolean { + return flags[flag]?.cbool ?: LandManager.permissions.firstOrNull { it.id == flag }?.default ?: false + } + + /** + * 适用于 UI 展示 + */ + fun getFlagOrNull(flag: String): Boolean? { + return flags[flag]?.cbool + } + + fun setFlag(flag: String, value: Boolean?) { + if (value == null) { + flags -= flag + return + } + flags += flag to value + } + + fun export() { + LandManager.save(this) + } +} diff --git a/src/main/kotlin/com/mcstarrysky/land/flag/PermBucket.kt b/src/main/kotlin/com/mcstarrysky/land/flag/PermBucket.kt new file mode 100644 index 0000000..195fd34 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/flag/PermBucket.kt @@ -0,0 +1,65 @@ +package com.mcstarrysky.land.flag + +import com.mcstarrysky.land.data.Land +import com.mcstarrysky.land.manager.LandManager +import com.mcstarrysky.land.util.display +import com.mcstarrysky.land.util.prettyInfo +import com.mcstarrysky.land.util.registerPermission +import org.bukkit.event.player.PlayerBucketEvent +import org.bukkit.inventory.ItemStack +import taboolib.common.LifeCycle +import taboolib.common.platform.Awake +import taboolib.common.platform.event.SubscribeEvent +import taboolib.library.xseries.XMaterial +import taboolib.platform.util.buildItem + +/** + * Land + * com.mcstarrysky.land.flag.PermBucket + * + * @author mical + * @since 2024/8/3 17:40 + */ +object PermBucket : Permission { + +// @Awake(LifeCycle.ENABLE) +// private fun init() { +// registerPermission() +// } + + override val id: String + get() = "bucket" + + override val default: Boolean + get() = false + + override val worldSide: Boolean + get() = true + + override val playerSide: Boolean + get() = true + + override fun generateMenuItem(land: Land): ItemStack { + return buildItem(XMaterial.BUCKET) { + name = "&f使用桶 ${land.getFlagOrNull(id).display}" + lore += listOf( + "&7允许行为:", + "&8使用桶", + "", + "&e左键修改值, 右键取消设置" + ) + if (land.getFlagOrNull(id) == true) shiny() + colored() + } + } + +// @SubscribeEvent(ignoreCancelled = true) + fun e(e: PlayerBucketEvent) { + LandManager.getLand(e.player.location)?.run { + if (!hasPermission(e.player) && !getFlag(this@PermBucket.id)) { + e.isCancelled = true + e.player.prettyInfo("没有权限, 禁止水/岩浆桶倒/装7\\(标记: ${this@PermBucket.id}\\)") + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/flag/PermBuild.kt b/src/main/kotlin/com/mcstarrysky/land/flag/PermBuild.kt new file mode 100644 index 0000000..d97736e --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/flag/PermBuild.kt @@ -0,0 +1,159 @@ +package com.mcstarrysky.land.flag + +import com.mcstarrysky.land.data.Land +import com.mcstarrysky.land.manager.LandManager +import com.mcstarrysky.land.util.display +import com.mcstarrysky.land.util.prettyInfo +import com.mcstarrysky.land.util.registerPermission +import org.bukkit.entity.ArmorStand +import org.bukkit.entity.Player +import org.bukkit.event.block.Action +import org.bukkit.event.block.BlockBreakEvent +import org.bukkit.event.block.BlockPlaceEvent +import org.bukkit.event.entity.EntityDamageByEntityEvent +import org.bukkit.event.hanging.HangingBreakByEntityEvent +import org.bukkit.event.hanging.HangingPlaceEvent +import org.bukkit.event.player.PlayerBucketEmptyEvent +import org.bukkit.event.player.PlayerBucketFillEvent +import org.bukkit.event.player.PlayerInteractEvent +import org.bukkit.inventory.ItemFlag +import org.bukkit.inventory.ItemStack +import taboolib.common.LifeCycle +import taboolib.common.platform.Awake +import taboolib.common.platform.event.SubscribeEvent +import taboolib.library.xseries.XMaterial +import taboolib.platform.util.buildItem + +/** + * Land + * com.mcstarrysky.land.flag.PermBuild + * + * @author mical + * @since 2024/8/3 17:26 + */ +object PermBuild : Permission { + + @Awake(LifeCycle.ENABLE) + private fun init() { + registerPermission() + } + + override val id: String + get() = "build" + + override val default: Boolean + get() = false + + override val worldSide: Boolean + get() = true + + override val playerSide: Boolean + get() = true + + override fun generateMenuItem(land: Land): ItemStack { + return buildItem(XMaterial.GRASS_BLOCK) { + name = "&f建筑 ${land.getFlagOrNull(id).display}" + lore += listOf( + "&7允许行为:", + "&8放置方块, 破坏方块, 放置挂饰, 破坏挂饰", + "&8放置盔甲架, 破坏盔甲架, 装满桶, 倒空桶", + "", + "&e左键修改值, 右键取消设置" + ) + + flags += ItemFlag.values().toList() + if (land.getFlagOrNull(id) == true) shiny() + colored() + } + } + + @SubscribeEvent(ignoreCancelled = true) + fun e(e: BlockBreakEvent) { + LandManager.getLand(e.block.location)?.run { + if (!hasPermission(e.player) && !getFlag(this@PermBuild.id)) { + e.isCancelled = true + e.player.prettyInfo("没有权限, 禁止打破方块/挂画&7\\(标记: ${this@PermBuild.id}\\)") + } + } + } + + @SubscribeEvent(ignoreCancelled = true) + fun e(e: BlockPlaceEvent) { + LandManager.getLand(e.block.location)?.run { + if (!hasPermission(e.player) && !getFlag(this@PermBuild.id)) { + e.isCancelled = true + e.player.prettyInfo("没有权限, 禁止放置方块/挂画或接触展示框&7\\(标记: ${this@PermBuild.id}\\)") + } + } + } + + @SubscribeEvent(ignoreCancelled = true) + fun e(e: HangingPlaceEvent) { + val player = e.player ?: return + LandManager.getLand(e.block.location)?.run { + if (!hasPermission(player) && !getFlag(this@PermBuild.id)) { + e.isCancelled = true + player.prettyInfo("没有权限, 禁止放置方块/挂画或接触展示框&7\\(标记: ${this@PermBuild.id}\\)") + } + } + } + + @SubscribeEvent(ignoreCancelled = true) + fun e(e: HangingBreakByEntityEvent) { + if (e.remover is Player) { + val player = e.remover as Player + LandManager.getLand(e.entity.location.block.location)?.run { + if (!hasPermission(player) && !getFlag(this@PermBuild.id)) { + e.isCancelled = true + player.prettyInfo("没有权限, 禁止打破方块/挂画&7\\(标记: ${this@PermBuild.id}\\)") + } + } + } + } + + @SubscribeEvent(ignoreCancelled = true) + fun e(e: PlayerInteractEvent) { + if (e.action == Action.RIGHT_CLICK_BLOCK && e.item?.type == org.bukkit.Material.ARMOR_STAND) { + LandManager.getLand(e.clickedBlock?.location ?: return)?.run { + if (!hasPermission(e.player) && !getFlag(this@PermBuild.id)) { + e.isCancelled = true + e.player.prettyInfo("没有权限, 禁止触碰盔甲架&7\\(标记: ${this@PermBuild.id}\\)") + } + } + } + } + + @SubscribeEvent(ignoreCancelled = true) + fun e(e: EntityDamageByEntityEvent) { + if (e.entity is ArmorStand) { + val player = e.damager as? Player ?: return +// val player = Servers.getAttackerInDamageEvent(e) ?: return + LandManager.getLand(e.entity.location.block.location)?.run { + if (!hasPermission(player) && !getFlag(this@PermBuild.id)) { + e.isCancelled = true + player.prettyInfo("没有权限, 禁止触碰盔甲架&7\\(标记: ${this@PermBuild.id}\\)") + } + } + } + } + + @SubscribeEvent(ignoreCancelled = true) + fun e(e: PlayerBucketFillEvent) { + LandManager.getLand(e.block.location)?.run { + if (!hasPermission(e.player) && !getFlag(this@PermBuild.id)) { + e.isCancelled = true + e.player.prettyInfo("没有权限, 禁止水/岩浆桶倒/装&7\\(标记: ${this@PermBuild.id}\\)") + } + } + } + + @SubscribeEvent(ignoreCancelled = true) + fun e(e: PlayerBucketEmptyEvent) { + LandManager.getLand(e.block.location)?.run { + if (!hasPermission(e.player) && !getFlag(this@PermBuild.id)) { + e.isCancelled = true + e.player.prettyInfo("没有权限, 禁止水/岩浆桶倒/装7\\(标记: ${this@PermBuild.id}\\)") + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/flag/PermContainer.kt b/src/main/kotlin/com/mcstarrysky/land/flag/PermContainer.kt new file mode 100644 index 0000000..7f967d2 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/flag/PermContainer.kt @@ -0,0 +1,73 @@ +package com.mcstarrysky.land.flag + +import com.mcstarrysky.land.data.Land +import com.mcstarrysky.land.manager.LandManager +import com.mcstarrysky.land.util.display +import com.mcstarrysky.land.util.prettyInfo +import com.mcstarrysky.land.util.registerPermission +import org.bukkit.entity.Player +import org.bukkit.event.block.Action +import org.bukkit.event.inventory.InventoryOpenEvent +import org.bukkit.event.player.PlayerInteractEvent +import org.bukkit.inventory.ItemFlag +import org.bukkit.inventory.ItemStack +import taboolib.common.LifeCycle +import taboolib.common.platform.Awake +import taboolib.common.platform.event.SubscribeEvent +import taboolib.library.xseries.XMaterial +import taboolib.platform.util.buildItem + +/** + * Land + * com.mcstarrysky.land.flag.PermContainer + * + * @author mical + * @since 2024/8/3 17:47 + */ +object PermContainer : Permission { + + @Awake(LifeCycle.ENABLE) + private fun init() { + registerPermission() + } + + override val id: String + get() = "container" + + override val default: Boolean + get() = false + + override val worldSide: Boolean + get() = true + + override val playerSide: Boolean + get() = true + + override fun generateMenuItem(land: Land): ItemStack { + return buildItem(XMaterial.CHEST) { + name = "&f容器 ${land.getFlagOrNull(id).display}" + lore += listOf( + "&7允许行为:", + "&8打开容器", + "", + "&e左键修改值, 右键取消设置" + ) + flags += ItemFlag.values().toList() + if (land.getFlagOrNull(id) == true) shiny() + colored() + } + } + + @SubscribeEvent(ignoreCancelled = true) + fun e(e: InventoryOpenEvent) { + if (e.inventory.location != null) { + val player = e.player as? Player ?: return + LandManager.getLand(e.inventory.location ?: return)?.run { + if (!hasPermission(player) && !getFlag(this@PermContainer.id)) { + e.isCancelled = true + player.prettyInfo("没有权限, 禁止使用容器&7\\(标记: ${this@PermContainer.id}\\)") + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/flag/PermEntityExplosion.kt b/src/main/kotlin/com/mcstarrysky/land/flag/PermEntityExplosion.kt new file mode 100644 index 0000000..72d7d44 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/flag/PermEntityExplosion.kt @@ -0,0 +1,60 @@ +package com.mcstarrysky.land.flag + +import com.mcstarrysky.land.data.Land +import com.mcstarrysky.land.manager.LandManager +import com.mcstarrysky.land.util.display +import com.mcstarrysky.land.util.registerPermission +import org.bukkit.event.entity.EntityExplodeEvent +import org.bukkit.inventory.ItemStack +import taboolib.common.LifeCycle +import taboolib.common.platform.Awake +import taboolib.common.platform.event.EventPriority +import taboolib.common.platform.event.SubscribeEvent +import taboolib.library.xseries.XMaterial +import taboolib.platform.util.buildItem + +/** + * Land + * com.mcstarrysky.land.flag.PermExplosion + * + * @author mical + * @since 2024/8/3 14:26 + */ +object PermEntityExplosion : Permission { + + @Awake(LifeCycle.ENABLE) + private fun init() { + registerPermission() + } + + override val id: String + get() = "entity_explosion" + + override val worldSide: Boolean + get() = true + + override val playerSide: Boolean + get() = true + + override fun generateMenuItem(land: Land): ItemStack { + return buildItem(XMaterial.CREEPER_SPAWN_EGG) { + name = "&f爆炸 ${land.getFlagOrNull(id).display}" + lore += listOf( + "&7允许行为:", + "&8生物爆炸", + "", + "&e左键修改值, 右键取消设置" + ) + if (land.getFlagOrNull(id) == true) shiny() + colored() + } + } + + @SubscribeEvent(EventPriority.MONITOR) + fun e(e: EntityExplodeEvent) { + e.blockList().removeIf { + val land = LandManager.getLand(it.location) + land != null && !land.getFlag(id) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/flag/PermInteract.kt b/src/main/kotlin/com/mcstarrysky/land/flag/PermInteract.kt new file mode 100644 index 0000000..e3b5bd2 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/flag/PermInteract.kt @@ -0,0 +1,68 @@ +package com.mcstarrysky.land.flag + +import com.mcstarrysky.land.data.Land +import com.mcstarrysky.land.manager.LandManager +import com.mcstarrysky.land.util.display +import com.mcstarrysky.land.util.prettyInfo +import com.mcstarrysky.land.util.registerPermission +import org.bukkit.event.block.Action +import org.bukkit.event.player.PlayerInteractEvent +import org.bukkit.inventory.ItemStack +import taboolib.common.LifeCycle +import taboolib.common.platform.Awake +import taboolib.common.platform.event.SubscribeEvent +import taboolib.library.xseries.XMaterial +import taboolib.platform.util.buildItem + +/** + * Land + * com.mcstarrysky.land.flag.PermInteract + * + * @author mical + * @since 2024/8/3 17:43 + */ +object PermInteract : Permission { + + @Awake(LifeCycle.ENABLE) + private fun init() { + registerPermission() + } + + override val id: String + get() = "interact" + + override val default: Boolean + get() = false + + override val worldSide: Boolean + get() = true + + override val playerSide: Boolean + get() = true + + override fun generateMenuItem(land: Land): ItemStack { + return buildItem(XMaterial.OAK_DOOR) { + name = "&f交互 ${land.getFlagOrNull(id).display}" + lore += listOf( + "&7允许行为:", + "&8方块交互", + "", + "&e左键修改值, 右键取消设置" + ) + if (land.getFlagOrNull(id) == true) shiny() + colored() + } + } + + @SubscribeEvent(ignoreCancelled = true) + fun e(e: PlayerInteractEvent) { + if (e.action == Action.RIGHT_CLICK_BLOCK) { + LandManager.getLand(e.clickedBlock?.location ?: return)?.run { + if (!hasPermission(e.player) && !getFlag(this@PermInteract.id)) { + e.isCancelled = true + e.player.prettyInfo("没有权限, 禁止接触任意物品方块&7\\(标记: ${this@PermInteract.id}\\)") + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/flag/PermMobSpawn.kt b/src/main/kotlin/com/mcstarrysky/land/flag/PermMobSpawn.kt new file mode 100644 index 0000000..58ded88 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/flag/PermMobSpawn.kt @@ -0,0 +1,69 @@ +package com.mcstarrysky.land.flag + +import com.mcstarrysky.land.data.Land +import com.mcstarrysky.land.manager.LandManager +import com.mcstarrysky.land.util.display +import com.mcstarrysky.land.util.registerPermission +import org.bukkit.entity.Mob +import org.bukkit.event.entity.EntitySpawnEvent +import org.bukkit.inventory.ItemFlag +import org.bukkit.inventory.ItemStack +import taboolib.common.LifeCycle +import taboolib.common.platform.Awake +import taboolib.common.platform.event.SubscribeEvent +import taboolib.library.xseries.XMaterial +import taboolib.platform.util.buildItem + +/** + * Land + * com.mcstarrysky.land.flag.PermMobSpawn + * + * @author mical + * @since 2024/8/3 17:23 + */ +object PermMobSpawn : Permission { + + @Awake(LifeCycle.ENABLE) + private fun init() { + registerPermission() + } + + override val id: String + get() = "mob_spawn" + + override val default: Boolean + get() = true + + override val worldSide: Boolean + get() = true + + override val playerSide: Boolean + get() = true + + override fun generateMenuItem(land: Land): ItemStack { + return buildItem(XMaterial.ZOMBIE_SPAWN_EGG) { + name = "&f怪物产生 ${land.getFlagOrNull(id).display}" + lore += listOf( + "&7允许行为:", + "&8生成怪物", + "", + "&e左键修改值, 右键取消设置" + ) + flags += ItemFlag.values().toList() + if (land.getFlagOrNull(id) == true) shiny() + colored() + } + } + + @SubscribeEvent(ignoreCancelled = true) + fun e(e: EntitySpawnEvent) { + if (e.entity !is Mob){ + return + } + LandManager.getLand(e.entity.location)?.run { + if (!getFlag(this@PermMobSpawn.id)) { + e.isCancelled = true + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/flag/PermMove.kt b/src/main/kotlin/com/mcstarrysky/land/flag/PermMove.kt new file mode 100644 index 0000000..2d2b9a9 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/flag/PermMove.kt @@ -0,0 +1,101 @@ +package com.mcstarrysky.land.flag + +import com.mcstarrysky.land.data.Land +import com.mcstarrysky.land.manager.LandManager +import com.mcstarrysky.land.util.* +import org.bukkit.Location +import org.bukkit.inventory.ItemFlag +import org.bukkit.inventory.ItemStack +import taboolib.common.LifeCycle +import taboolib.common.platform.Awake +import taboolib.common.platform.Schedule +import taboolib.library.xseries.XMaterial +import taboolib.platform.util.buildItem +import taboolib.platform.util.onlinePlayers +import taboolib.platform.util.setMeta + +/** + * Land + * com.mcstarrysky.land.flag.PermMove + * + * @author mical + * @since 2024/8/3 21:07 + */ +object PermMove : Permission { + + @Awake(LifeCycle.ENABLE) + private fun init() { + registerPermission() + } + + override val id: String + get() = "move" + + override val worldSide: Boolean + get() = true + + override val playerSide: Boolean + get() = true + + override fun generateMenuItem(land: Land): ItemStack { + return buildItem(XMaterial.IRON_BOOTS) { + name = "&f移动 ${land.getFlagOrNull(id).display}" + lore += listOf( + "&7允许行为:", + "&8领地内移动", + "", + "&e左键修改值, 右键取消设置" + ) + flags += ItemFlag.values().toList() + if (land.getFlagOrNull(id) == true) shiny() + colored() + } + } + +// @SubscribeEvent(ignoreCancelled = true) +// fun e(e: PlayerMoveEvent) { +// LandManager.getLand(e.player.location)?.run { +// if (!hasPermission(e.player) && !getFlag(this@PermMove.id)) { +// e.player.velocity = VelocityUtils.calculateVelocity(e.player, e.to, e.from, 5.0) +// e.isCancelled = true +// e.player.prettyInfo("没有权限, 禁止移动&7\\(标记: ${this@PermMove.id}\\)") +// } +// } +// } + + @Schedule(period = 14L, async = true) + fun tick() { + onlinePlayers.forEach { p -> + val loc = p.location + LandManager.getLand(loc)?.run { + if (!hasPermission(p) && !getFlag(this@PermMove.id)) { + val centre = LocationUtils.calculateLandCenter(area, p.world) + val l: Location = p.location // 获取玩家当前位置 + + // 计算移动方向和距离 + var x: Double = if (l.blockX > centre.blockX) { + 1 // 如果玩家在地块中心的右侧,向右移动 + } else { + -1 // 如果玩家在地块中心的左侧,向左移动 + }.toDouble() + + var z: Double = if (l.blockZ > centre.blockZ) { + 1 // 如果玩家在地块中心的下方,向下移动 + } else { + -1 // 如果玩家在地块中心的上方,向上移动 + }.toDouble() + + val distance = 1.0 // 假设移动距离为10个方块,根据实际情况调整 + x *= distance // 计算x方向上的移动距离 + z *= distance // 计算z方向上的移动距离 + + // 执行玩家的移动 + + p.setMeta("land_ban_move_teleport", true) + p.teleportAsync(l.add(x, 0.0, z)) // 将玩家传送到新的位置 + p.prettyInfo("没有权限, 禁止移动&7\\(标记: ${this@PermMove.id}\\)") + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/flag/PermTeleport.kt b/src/main/kotlin/com/mcstarrysky/land/flag/PermTeleport.kt new file mode 100644 index 0000000..30dcb53 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/flag/PermTeleport.kt @@ -0,0 +1,59 @@ +package com.mcstarrysky.land.flag + +import com.mcstarrysky.land.data.Land +import com.mcstarrysky.land.util.display +import com.mcstarrysky.land.util.prettyInfo +import com.mcstarrysky.land.util.registerPermission +import org.bukkit.entity.Player +import org.bukkit.inventory.ItemStack +import taboolib.common.LifeCycle +import taboolib.common.platform.Awake +import taboolib.library.xseries.XMaterial +import taboolib.platform.util.buildItem + +/** + * Land + * com.mcstarrysky.land.flag.PermTeleport + * + * @author mical + * @since 2024/8/3 00:59 + */ +object PermTeleport : Permission { + + @Awake(LifeCycle.ENABLE) + private fun init() { + registerPermission() + } + + override val id: String + get() = "teleport" + + override val worldSide: Boolean + get() = true + + override val playerSide: Boolean + get() = true + + override fun generateMenuItem(land: Land): ItemStack { + return buildItem(XMaterial.ENDER_PEARL) { + name = "&f传送 ${land.getFlagOrNull(id).display}" + lore += listOf( + "&7允许行为:", + "&8传送到该领地", + "", + "&e左键修改值, 右键取消设置" + ) + if (land.getFlagOrNull(id) == true) shiny() + colored() + } + } + + fun teleport(player: Player, land: Land) { + if (land.getFlag(id) || land.hasPermission(player)) { + player.teleportAsync(land.tpLocation) + player.prettyInfo("传送完成!") + } else { + player.prettyInfo("没有权限, 禁止使用传送点&7\\(标记: ${id}\\)") + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/flag/PermTeleportIn.kt b/src/main/kotlin/com/mcstarrysky/land/flag/PermTeleportIn.kt new file mode 100644 index 0000000..8635662 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/flag/PermTeleportIn.kt @@ -0,0 +1,72 @@ +package com.mcstarrysky.land.flag + +import com.mcstarrysky.land.data.Land +import com.mcstarrysky.land.manager.LandManager +import com.mcstarrysky.land.util.display +import com.mcstarrysky.land.util.prettyInfo +import com.mcstarrysky.land.util.registerPermission +import org.bukkit.event.player.PlayerTeleportEvent +import org.bukkit.inventory.ItemFlag +import org.bukkit.inventory.ItemStack +import taboolib.common.LifeCycle +import taboolib.common.platform.Awake +import taboolib.common.platform.event.SubscribeEvent +import taboolib.library.xseries.XMaterial +import taboolib.platform.util.buildItem +import taboolib.platform.util.hasMeta +import taboolib.platform.util.removeMeta + +/** + * Land + * com.mcstarrysky.land.flag.PermTeleportIn + * + * @author mical + * @since 2024/8/3 21:09 + */ +object PermTeleportIn : Permission { + + @Awake(LifeCycle.ENABLE) + private fun init() { + registerPermission() + } + + override val id: String + get() = "teleport_in" + + override val worldSide: Boolean + get() = true + + override val playerSide: Boolean + get() = true + + override fun generateMenuItem(land: Land): ItemStack { + return buildItem(XMaterial.DIAMOND_BOOTS) { + name = "&f传送进来 ${land.getFlagOrNull(id).display}" + lore += listOf( + "&7允许行为:", + "&8传送进来", + "", + "&e左键修改值, 右键取消设置" + ) + flags += ItemFlag.values().toList() + if (land.getFlagOrNull(id) == true) shiny() + colored() + } + } + + @SubscribeEvent(ignoreCancelled = true) + fun e(e: PlayerTeleportEvent) { + LandManager.getLand(e.to)?.run { + if (LandManager.getLand(e.from)?.id != id) { + if (!hasPermission(e.player) && !getFlag(this@PermTeleportIn.id)) { + if (e.player.hasMeta("land_ban_move_teleport")) { + e.player.removeMeta("land_ban_move_teleport") + } else { + e.isCancelled = true + e.player.prettyInfo("没有权限, 禁止传送进来&7\\(标记: ${this@PermTeleportIn.id}\\)") + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/flag/PermTeleportOut.kt b/src/main/kotlin/com/mcstarrysky/land/flag/PermTeleportOut.kt new file mode 100644 index 0000000..cea3eea --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/flag/PermTeleportOut.kt @@ -0,0 +1,72 @@ +package com.mcstarrysky.land.flag + +import com.mcstarrysky.land.data.Land +import com.mcstarrysky.land.manager.LandManager +import com.mcstarrysky.land.util.display +import com.mcstarrysky.land.util.prettyInfo +import com.mcstarrysky.land.util.registerPermission +import org.bukkit.event.player.PlayerTeleportEvent +import org.bukkit.inventory.ItemFlag +import org.bukkit.inventory.ItemStack +import taboolib.common.LifeCycle +import taboolib.common.platform.Awake +import taboolib.common.platform.event.SubscribeEvent +import taboolib.library.xseries.XMaterial +import taboolib.platform.util.buildItem +import taboolib.platform.util.hasMeta +import taboolib.platform.util.removeMeta + +/** + * Land + * com.mcstarrysky.land.flag.PermTeleportIn + * + * @author mical + * @since 2024/8/3 21:09 + */ +object PermTeleportOut : Permission { + + @Awake(LifeCycle.ENABLE) + private fun init() { + registerPermission() + } + + override val id: String + get() = "teleport_out" + + override val worldSide: Boolean + get() = true + + override val playerSide: Boolean + get() = true + + override fun generateMenuItem(land: Land): ItemStack { + return buildItem(XMaterial.LEATHER_BOOTS) { + name = "&f传送出去 ${land.getFlagOrNull(id).display}" + lore += listOf( + "&7允许行为:", + "&8传送出去", + "", + "&e左键修改值, 右键取消设置" + ) + flags += ItemFlag.values().toList() + if (land.getFlagOrNull(id) == true) shiny() + colored() + } + } + + @SubscribeEvent(ignoreCancelled = true) + fun e(e: PlayerTeleportEvent) { + LandManager.getLand(e.from)?.run { + if (LandManager.getLand(e.to)?.id != id) { + if (!hasPermission(e.player) && !getFlag(this@PermTeleportOut.id)) { + if (e.player.hasMeta("land_ban_move_teleport")) { + e.player.removeMeta("land_ban_move_teleport") + } else { + e.isCancelled = true + e.player.prettyInfo("没有权限, 禁止传送出去&7\\(标记: ${this@PermTeleportOut.id}\\)") + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/flag/Permission.kt b/src/main/kotlin/com/mcstarrysky/land/flag/Permission.kt new file mode 100644 index 0000000..b21243a --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/flag/Permission.kt @@ -0,0 +1,52 @@ +package com.mcstarrysky.land.flag + +import com.mcstarrysky.land.data.Land +import org.bukkit.inventory.ItemStack + +/** + * Realms + * ink.ptms.realms.permission.Permission + * + * @author sky + * @since 2021/3/18 9:20 上午 + */ +interface Permission { + + /** + * 界面优先级 + */ + val priority: Int + get() = 0 + + /** + * 默认选项 + */ + val default: Boolean + get() = true + + /** + * 序号 + */ + val id: String + + /** + * 世界权限 + */ + val worldSide: Boolean + + /** + * 玩家权限 + */ + val playerSide: Boolean + + /** + * 管理员可视 + */ + val adminSide: Boolean + get() = false + + /** + * 构建界面物品 + */ + fun generateMenuItem(land: Land): ItemStack +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/listener/FreeLandHandler.kt b/src/main/kotlin/com/mcstarrysky/land/listener/FreeLandHandler.kt new file mode 100644 index 0000000..33f1119 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/listener/FreeLandHandler.kt @@ -0,0 +1,29 @@ +package com.mcstarrysky.land.listener + +import com.mcstarrysky.land.Land +import com.mcstarrysky.land.manager.LandManager +import org.bukkit.event.player.PlayerInteractEvent +import taboolib.common.platform.event.SubscribeEvent +import taboolib.platform.util.isLeftClickBlock +import taboolib.platform.util.isMainhand +import taboolib.platform.util.isRightClickBlock + +/** + * Land + * com.mcstarrysky.land.listener.FreeLandHandler + * + * @author mical + * @since 2024/8/3 14:35 + */ +object FreeLandHandler { + + @SubscribeEvent + fun e(e: PlayerInteractEvent) { + if (e.isMainhand() && (e.isRightClickBlock() || e.isLeftClickBlock())) { + if (e.player.equipment.itemInMainHand.isSimilar(Land.freeLandTool)) { + LandManager.createFree(e.player, e.clickedBlock!!.location) + e.isCancelled = true + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/listener/LandEnterLeaveListener.kt b/src/main/kotlin/com/mcstarrysky/land/listener/LandEnterLeaveListener.kt new file mode 100644 index 0000000..4a75471 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/listener/LandEnterLeaveListener.kt @@ -0,0 +1,28 @@ +package com.mcstarrysky.land.listener + +import com.mcstarrysky.land.manager.LandManager +import com.mcstarrysky.land.util.prettyInfo +import org.bukkit.event.player.PlayerMoveEvent +import taboolib.common.platform.event.SubscribeEvent + +/** + * Land + * com.mcstarrysky.land.listener.KandEnterLeaveListener + * + * @author mical + * @since 2024/8/3 14:48 + */ +object LandEnterLeaveListener { + + // @SubscribeEvent + fun e(e: PlayerMoveEvent) { + if (e.from.x != e.to.x || e.from.y != e.to.y || e.from.z != e.to.z) { + val from = LandManager.getLand(e.from) + val to = LandManager.getLand(e.to) + if (from != to) { + from?.leaveMessage?.let { e.player.prettyInfo(it) } + to?.enterMessage?.let { e.player.prettyInfo(it) } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/listener/PosSelection.kt b/src/main/kotlin/com/mcstarrysky/land/listener/PosSelection.kt new file mode 100644 index 0000000..fb729a2 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/listener/PosSelection.kt @@ -0,0 +1,40 @@ +package com.mcstarrysky.land.listener + +import com.mcstarrysky.land.Land +import org.bukkit.Chunk +import org.bukkit.event.player.PlayerInteractEvent +import taboolib.common.platform.event.SubscribeEvent +import taboolib.platform.util.isLeftClickBlock +import taboolib.platform.util.isMainhand +import taboolib.platform.util.isRightClickBlock +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap + +/** + * Land + * com.mcstarrysky.land.listener.PosSelection + * + * @author mical + * @since 2024/8/2 23:08 + */ +object PosSelection { + + val pos1 = ConcurrentHashMap() + val pos2 = ConcurrentHashMap() + + // @SubscribeEvent + fun e(e: PlayerInteractEvent) { + val player = e.player + // 主手 + if (e.isMainhand()) { + // 是选择棒 + if (player.equipment.itemInMainHand.isSimilar(Land.tool)) { + // 判断左右键 + when { + e.isLeftClickBlock() -> pos1.computeIfAbsent(player.uniqueId) { e.clickedBlock!!.location.chunk } + e.isRightClickBlock() -> pos2.computeIfAbsent(player.uniqueId) { e.clickedBlock!!.location.chunk } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/manager/EnterLeaveManager.kt b/src/main/kotlin/com/mcstarrysky/land/manager/EnterLeaveManager.kt new file mode 100644 index 0000000..8ed4540 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/manager/EnterLeaveManager.kt @@ -0,0 +1,32 @@ +package com.mcstarrysky.land.manager + +import com.mcstarrysky.land.util.prettyInfo +import taboolib.common.platform.Schedule +import taboolib.platform.util.onlinePlayers +import java.util.UUID + +/** + * Land + * com.mcstarrysky.land.manager.EnterLeaveManager + * + * @author mical + * @since 2024/8/3 19:14 + */ +object EnterLeaveManager { + + // 玩家 UUID - 领地编号 + private val previousLand = LinkedHashMap() + + @Schedule(period = 14L, async = true) // 700ms + fun tick() { + onlinePlayers.forEach { p -> + val land = LandManager.getLand(p.location)?.id ?: -1 + val pre = previousLand.remove(p.uniqueId) ?: -1 + previousLand += p.uniqueId to land + if (pre != land) { + LandManager.getLand(pre)?.leaveMessage?.let { p.prettyInfo(it) } + LandManager.getLand(land)?.enterMessage?.let { p.prettyInfo(it) } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/manager/LandManager.kt b/src/main/kotlin/com/mcstarrysky/land/manager/LandManager.kt new file mode 100644 index 0000000..afd64e4 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/manager/LandManager.kt @@ -0,0 +1,145 @@ +package com.mcstarrysky.land.manager + +import com.mcstarrysky.land.data.Land +import com.mcstarrysky.land.flag.Permission +import com.mcstarrysky.land.util.* +import org.bukkit.Chunk +import org.bukkit.Location +import org.bukkit.entity.Player +import org.bukkit.persistence.PersistentDataType +import taboolib.common.io.newFile +import taboolib.common.io.newFolder +import taboolib.common.platform.Schedule +import taboolib.common.platform.function.getDataFolder +import java.nio.charset.StandardCharsets +import java.util.LinkedList + +/** + * Land + * com.mcstarrysky.land.manager.LandManager + * + * @author mical + * @since 2024/8/2 23:06 + */ +object LandManager { + +// val defaultFlags = mapOf( +// "banBreak" to true, +// "banPlace" to true, +// "banIgnite" to true, +// "banDoor" to true, +// "banContact" to true, +// "banButton" to true, +// "banContainer" to true, +// "banUseOther" to true, +// "banBucket" to true, +// "banPvp" to true, +// "protect" to true, +// "banPotionHurt" to true +// ) + + val defaultFlags = mapOf( + "bucket" to false, + "build" to false, + "container" to false, + "entity_explosion" to true, + "interact" to false, + "mob_spawn" to true, + "teleport" to true, + "move" to true, + "teleport_in" to true, + "teleport_out" to true + ) + + val lands = LinkedList() + + val permissions = LinkedList() + + fun import() { + newFolder(getDataFolder(), "lands").listFiles()?.forEach { file -> + if (file.extension == "json") { + lands += json.decodeFromString(Land.serializer(), file.readText(StandardCharsets.UTF_8)) + } + } + } + + @Schedule(period = 20 * 60L) + fun export() { + lands.forEach(::save) + } + + fun getLands(player: Player): List { + return lands.filter { it.owner == player.uniqueId } + } + + fun save(land: Land) { + newFile(getDataFolder(), "lands/${land.id}.json") + .writeText(land.saveToString(), StandardCharsets.UTF_8) + } + + fun getLand(location: Location): Land? { + return lands.firstOrNull { it.isInArea(location) } + } + + fun getLand(id: Int): Land? { + return lands.firstOrNull { it.id == id } + } + + fun hasLand(name: String): Boolean { + return lands.any { it.name == name } + } + + fun create(player: Player, name: String, chunk1: Chunk, chunk2: Chunk) { + + } + + fun createFree(player: Player, clickedLocation: Location) { + if (hasFree(player)) { + player.prettyInfo("你已经获取过免费领地了.") + return + } + if (player.world.name != "world") { + player.prettyInfo("免费领地棒只能在[主世界](color=#8abcd1)使用") + return + } + var name = "免费领地_${player.name}" + // 避免极小概率的领地重复情况 + if (hasLand(name)) { + name += System.currentTimeMillis() + } + val area = ChunkUtils.getCenteredChunks(clickedLocation, 5) + if (area.any { chunk -> lands.any { it.area.contains(chunk) } }) { + player.prettyInfo("你的领地区域与现有领地区域重叠, 请重新选择位置!") + return + } +// val centre = LocationUtils.calculateLandCenter(area, player.world) +// player.prettyInfo("自动设置领地传送点为领地中央位置.") + val land = Land( + newId(), + name, + player.uniqueId, + System.currentTimeMillis(), + clickedLocation.world.name, + "${player.name} 的免费领地", + area = area.toMutableList(), + tpLocation = clickedLocation.add(0.0, 1.0, 0.0), + cooperators = mutableListOf(), + ) + for ((flag, value) in defaultFlags) { + player.prettyInfo("自动添加标记 [{0}](color=#8abcd1) &7\\(值: {1}\\)", flag, value) + land.flags += flag to value + } + lands += land + player.prettyInfo("创建免费领地成功.") + + player["land_free_created", PersistentDataType.BOOLEAN] = true + } + + fun hasFree(player: Player): Boolean { + return player.has("land_free_created", PersistentDataType.BOOLEAN) + } + + fun newId(): Int { + return (lands.maxByOrNull { it.id }?.id ?: 0) + 1 + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/menu/LandCooperatorsMenu.kt b/src/main/kotlin/com/mcstarrysky/land/menu/LandCooperatorsMenu.kt new file mode 100644 index 0000000..933c26d --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/menu/LandCooperatorsMenu.kt @@ -0,0 +1,92 @@ +package com.mcstarrysky.land.menu + +import com.mcstarrysky.land.data.Land +import com.mcstarrysky.land.util.MenuRegistry +import com.mcstarrysky.land.util.MenuRegistry.markHeader +import com.mcstarrysky.land.util.MenuRegistry.markPageButton +import com.mcstarrysky.land.util.prettyInfo +import com.mcstarrysky.land.util.skull +import org.bukkit.Bukkit +import org.bukkit.OfflinePlayer +import org.bukkit.entity.Player +import taboolib.common.util.sync +import taboolib.library.xseries.XMaterial +import taboolib.module.ui.openMenu +import taboolib.module.ui.type.PageableChest +import taboolib.platform.util.buildItem +import taboolib.platform.util.nextChat +import java.util.function.Consumer + +/** + * Land + * com.mcstarrysky.land.menu.LandCooperatorsMenu + * + * @author mical + * @since 2024/8/3 16:45 + */ +object LandCooperatorsMenu { + + fun openMenu(player: Player, land: Land, back: Consumer?, elements: List) { + player.openMenu>("领地(ID:${land.id}) ${land.name} 合作者") { + virtualize() + + map( + "b===+==pn", + "#########", + "#########" + ) + + slotsBy('#') + + elements { land.cooperators.map { Bukkit.getOfflinePlayer(it) } } + + markHeader() + markPageButton() + + set('b', MenuRegistry.BACK) { LandInfoMenu.openMenu(player, land, back, elements) } + + onGenerate(async = true) { _, p, _, _ -> + buildItem(XMaterial.PLAYER_HEAD) { + name = "&f" + p.name + lore += listOf( + "&e单击删除" + ) + colored() + }.skull(p.name) + } + + onClick { _, p -> + land.cooperators -= p.uniqueId + openMenu(player, land, back, elements) + } + + set('+', buildItem(XMaterial.NAME_TAG) { + name = "&a添加合作者" + lore += listOf( + "&7协作者具有领地的大部分权限", + "&7但没有领地的设置与修改权" + ) + colored() + }) { + clicker.closeInventory() + clicker.prettyInfo("请在聊天框输入合作者名字, 或输入'取消'来取消操作!") + clicker.nextChat { ctx -> + if (ctx == "取消") + return@nextChat + val offlinePlayer = Bukkit.getOfflinePlayerIfCached(ctx) + if (offlinePlayer == null) { + clicker.prettyInfo("并没有找到这位玩家!") + return@nextChat + } + land.cooperators += offlinePlayer.uniqueId + clicker.prettyInfo("添加成功!") + sync { openMenu(clicker, land, back, elements) } + } + } + + onClose { + land.export() + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/menu/LandFlagsMenu.kt b/src/main/kotlin/com/mcstarrysky/land/menu/LandFlagsMenu.kt new file mode 100644 index 0000000..4e6c52e --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/menu/LandFlagsMenu.kt @@ -0,0 +1,74 @@ +package com.mcstarrysky.land.menu + +import com.mcstarrysky.land.data.Land +import com.mcstarrysky.land.flag.Permission +import com.mcstarrysky.land.manager.LandManager +import com.mcstarrysky.land.util.MenuRegistry +import com.mcstarrysky.land.util.MenuRegistry.markHeader +import com.mcstarrysky.land.util.MenuRegistry.markPageButton +import org.bukkit.entity.Player +import org.bukkit.event.inventory.ClickType +import taboolib.common.util.sync +import taboolib.module.ui.openMenu +import taboolib.module.ui.type.PageableChest +import java.util.function.Consumer + +/** + * Land + * com.mcstarrysky.land.menu.LandFlagsMenu + * + * @author mical + * @since 2024/8/3 16:27 + */ +object LandFlagsMenu { + + fun openMenu(player: Player, land: Land, back: Consumer?, elements: List) { + player.openMenu>("领地(ID:${land.id}) ${land.name} 标记管理") { + virtualize() + + map( + "b======pn", + "#########", + "#########" + ) + + slotsBy('#') + + elements { LandManager.permissions } + + onGenerate(async = true) { _, flag, _, _ -> flag.generateMenuItem(land) } + + markHeader() + markPageButton() + + set('b', MenuRegistry.BACK) { LandInfoMenu.openMenu(player, land, back, elements) } + + onClick { event, flag -> + when (event.virtualEvent().clickType) { + ClickType.LEFT, ClickType.SHIFT_LEFT -> { + // 如果没设置, 就设置成默认值 + if (land.getFlagOrNull(flag.id) == null) { + land.setFlag(flag.id, flag.default) + } else { + val value = land.getFlag(flag.id) + land.setFlag(flag.id, !value) + } + openMenu(player, land, back, elements) + } + ClickType.RIGHT, ClickType.SHIFT_RIGHT -> { + if (land.getFlagOrNull(flag.id) != null) { + land.setFlag(flag.id, null) + openMenu(player, land, back, elements) + } + } + else -> { + } + } + } + + onClose { + land.export() + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/menu/LandInfoMenu.kt b/src/main/kotlin/com/mcstarrysky/land/menu/LandInfoMenu.kt new file mode 100644 index 0000000..0e26ccc --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/menu/LandInfoMenu.kt @@ -0,0 +1,261 @@ +package com.mcstarrysky.land.menu + +import com.mcstarrysky.land.LandSettings +import com.mcstarrysky.land.data.Land +import com.mcstarrysky.land.manager.LandManager +import com.mcstarrysky.land.util.MenuRegistry +import com.mcstarrysky.land.util.MenuRegistry.markBoard +import com.mcstarrysky.land.util.MenuRegistry.markHeader +import com.mcstarrysky.land.util.cacheMessageWithPrefixColor +import com.mcstarrysky.land.util.isValidLandName +import com.mcstarrysky.land.util.prettyInfo +import org.bukkit.Bukkit +import org.bukkit.entity.Player +import org.bukkit.event.inventory.ClickType +import taboolib.library.xseries.XMaterial +import taboolib.module.ui.openMenu +import taboolib.module.ui.type.Chest +import taboolib.platform.util.buildItem +import taboolib.platform.util.nextChat +import java.util.function.Consumer + +/** + * Land + * com.mcstarrysky.land.menu.LandInfoMenu + * + * @author mical + * @since 2024/8/3 15:40 + */ +object LandInfoMenu { + + fun openMenu(player: Player, land: Land, back: Consumer?, elements: List) { + player.openMenu("领地(ID:${land.id}) ${land.name}") { + virtualize() + + map( + "b========", + " a z f d ", // 描述, 加入, 传送点, 标记 + " g c m e ", // 转让所有者, 退出, 合作者, 删除 + "=========" + ) + + set('b', MenuRegistry.BACK) { + LandListMenu.openMenu(player, elements, back) + } + + markHeader() + markBoard() + + set('a', buildItem(XMaterial.CHERRY_HANGING_SIGN) { + name = "&f" + land.name + lore += listOf( + "&7左键更改描述, 右键改名", + "&7", + "&{#dcc44c}世界: &7" + (LandSettings.worldAliases[land.world] ?: land.world), + "&{#dcc44c}描述: &7" + land.description, + "&{#dcc44c}所有者: &7" + land.getOwnerName(), + "&{#dcc44c}进入信息: &7" + cacheMessageWithPrefixColor(land.enterMessage ?: "无").toLegacyText(), + "&{#dcc44c}离开信息: &7" + cacheMessageWithPrefixColor(land.leaveMessage ?: "无").toLegacyText(), + "&{#dcc44c}创建时间: &7" + land.date + ) + colored() + }) { + if (check(clicker, land)) { + when (virtualEvent().clickType) { + ClickType.LEFT, ClickType.SHIFT_LEFT -> { + clicker.closeInventory() + clicker.prettyInfo("请在聊天框输入新的描述, 或输入'取消'来取消操作!") + clicker.nextChat { ctx -> + if (ctx == "取消") + return@nextChat + land.description = ctx + land.export() + clicker.prettyInfo("修改成功!") + } + } + ClickType.RIGHT, ClickType.SHIFT_RIGHT -> { + clicker.closeInventory() + clicker.prettyInfo("请在聊天框输入新的名字, 或输入'取消'来取消操作!") + clicker.nextChat { ctx -> + if (ctx == "取消") + return@nextChat + if (!ctx.isValidLandName()) { + clicker.prettyInfo("为避免与领地编号混淆, 名字不能是纯数字!") + return@nextChat + } + if (LandManager.hasLand(ctx)) { + clicker.prettyInfo("你输入的新名字已存在! 请重新操作") + return@nextChat + } + land.name = ctx + land.export() + clicker.prettyInfo("修改成功!") + } + } + else -> { + } + } + } + } + + set('z', buildItem(XMaterial.CHERRY_DOOR) { + name = "&e设置进入信息" + lore += listOf( + "&7注意,不管将进入信息修改成什么", + "&7都会带一个领地前缀", + "&7", + "&e单击修改,右键关闭进入信息" + ) + colored() + }) { + if (!check(player, land)) return@set + when (virtualEvent().clickType) { + ClickType.LEFT, ClickType.SHIFT_LEFT -> { + clicker.closeInventory() + clicker.prettyInfo("请在聊天框输入新的进入信息, 支持行内复合文本. 输入'取消'来取消操作!") + clicker.nextChat { ctx -> + if (ctx == "取消") + return@nextChat + land.enterMessage = ctx + land.export() + clicker.prettyInfo("修改成功!") + } + } + ClickType.RIGHT, ClickType.SHIFT_RIGHT -> { + clicker.closeInventory() + land.enterMessage = null + land.export() + clicker.prettyInfo("修改成功!") + } + else -> { + } + } + } + + set('c', buildItem(XMaterial.IRON_DOOR) { + name = "&e设置离开信息" + lore += listOf( + "&7注意,不管将离开信息修改成什么", + "&7都会带一个领地前缀", + "&7", + "&e单击修改,右键关闭离开信息" + ) + colored() + }) { + if (!check(player, land)) return@set + when (virtualEvent().clickType) { + ClickType.LEFT, ClickType.SHIFT_LEFT -> { + clicker.closeInventory() + clicker.prettyInfo("请在聊天框输入新的离开信息, 支持行内复合文本. 输入'取消'来取消操作!") + clicker.nextChat { ctx -> + if (ctx == "取消") + return@nextChat + land.leaveMessage = ctx + land.export() + clicker.prettyInfo("修改成功!") + } + } + ClickType.RIGHT, ClickType.SHIFT_RIGHT -> { + clicker.closeInventory() + land.leaveMessage = null + land.export() + clicker.prettyInfo("修改成功!") + } + else -> { + } + } + } + + set('f', buildItem(XMaterial.COMPASS) { + name = "&b设置传送点" + lore += listOf( + "&7点击设置你脚下为领地传送点" + ) + colored() + }) { + if (!check(player, land)) return@set + if (!land.isInArea(clicker.location)) { + clicker.prettyInfo("你必须设置一个领地内的点!") + return@set + } + land.tpLocation = clicker.location + land.export() + clicker.prettyInfo("修改成功!") + } + + set('g', buildItem(XMaterial.ENDER_EYE) { + name = "&c转让所有者" + lore += listOf( + "&7注意: 此操作不可撤销!" + ) + colored() + }) { + if (!check(player, land)) return@set + clicker.closeInventory() + clicker.prettyInfo("请在聊天框输入新的玩家名, 或输入'取消'来取消操作!") + clicker.nextChat { ctx -> + if (ctx == "取消") + return@nextChat + val offlinePlayer = Bukkit.getOfflinePlayerIfCached(ctx) + if (offlinePlayer == null) { + clicker.prettyInfo("并没有找到这位玩家!") + return@nextChat + } + land.owner = offlinePlayer.uniqueId + land.export() + clicker.prettyInfo("转让成功!") + } + } + + set('e', buildItem(XMaterial.BARRIER) { + name = "&c删除领地" + lore += listOf( + "&7注意: 此操作不可撤销!" + ) + colored() + }) { + if (!check(player, land)) return@set + clicker.closeInventory() + clicker.prettyInfo("你确定要删除吗? 输入'确认'来确认, 或输入其他内容来取消操作!") + clicker.nextChat { ctx -> + if (ctx != "确认") + return@nextChat + LandManager.lands -= land + clicker.prettyInfo("已删除领地!") + } + } + + set('d', buildItem(XMaterial.NAME_TAG) { + name = "&e查看领地标记" + lore += listOf( + "&7设置各个权限" + ) + colored() + }) { + if (!check(player, land)) return@set + LandFlagsMenu.openMenu(player, land, back, elements) + } + + set('m', buildItem(XMaterial.PLAYER_HEAD) { + name = "&a查看协作者" + lore += listOf( + "&7协作者具有领地的大部分权限", + "&7但没有领地的设置与修改权" + ) + colored() + }) { + if (!check(player, land)) return@set + LandCooperatorsMenu.openMenu(player, land, back, elements) + } + } + } + + private fun check(player: Player, land: Land): Boolean { + if (!land.hasPermission(player)) { + player.closeInventory() + player.prettyInfo("你没有权限修改这个领地的内容!") + return false + } + return true + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/menu/LandListMenu.kt b/src/main/kotlin/com/mcstarrysky/land/menu/LandListMenu.kt new file mode 100644 index 0000000..46123e6 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/menu/LandListMenu.kt @@ -0,0 +1,74 @@ +package com.mcstarrysky.land.menu + +import com.mcstarrysky.land.data.Land +import com.mcstarrysky.land.util.MenuRegistry +import com.mcstarrysky.land.util.MenuRegistry.markHeader +import com.mcstarrysky.land.util.MenuRegistry.markPageButton +import org.bukkit.entity.Player +import org.bukkit.event.inventory.ClickType +import taboolib.library.xseries.XMaterial +import taboolib.module.ui.openMenu +import taboolib.module.ui.type.PageableChest +import taboolib.platform.util.buildItem +import java.util.function.Consumer + +/** + * Land + * com.mcstarrysky.land.menu.LandListMenu + * + * @author mical + * @since 2024/8/2 23:34 + */ +object LandListMenu { + + fun openMenu(player: Player, elements: List, back: Consumer?) { + player.openMenu>("领地列表 #%p") { + virtualize() + + map( + "b======pn", + "#########", + "#########", + "#########" + ) + + slotsBy('#') + + elements { elements } + + markHeader() + markPageButton() + + set('b', MenuRegistry.BACK) { + LandMainMenu.openMenu(player, back) + } + + onGenerate(async = true) { _, land, _, _ -> + buildItem(XMaterial.MAP) { + name = "&f" + land.name + lore += listOf( + "&7左键传送, 右键查看", + "&7", + "&{#dcc44c}编号: &7" + land.id, + "&{#dcc44c}描述: &7" + land.description + ) + colored() + } + } + + onClick { e, land -> + when (e.virtualEvent().clickType) { + ClickType.LEFT, ClickType.SHIFT_LEFT -> { + e.clicker.closeInventory() + land.teleport(e.clicker) + } + ClickType.RIGHT, ClickType.SHIFT_RIGHT -> { + LandInfoMenu.openMenu(player, land, back, elements) + } + else -> { + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/menu/LandMainMenu.kt b/src/main/kotlin/com/mcstarrysky/land/menu/LandMainMenu.kt new file mode 100644 index 0000000..a39003b --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/menu/LandMainMenu.kt @@ -0,0 +1,109 @@ +package com.mcstarrysky.land.menu + +import com.mcstarrysky.land.manager.LandManager +import com.mcstarrysky.land.util.MenuRegistry +import com.mcstarrysky.land.util.MenuRegistry.markHeader +import com.mcstarrysky.land.util.prettyInfo +import org.bukkit.Bukkit +import org.bukkit.entity.Player +import org.bukkit.inventory.ItemFlag +import taboolib.common.util.sync +import taboolib.library.xseries.XMaterial +import taboolib.module.ui.openMenu +import taboolib.module.ui.type.Chest +import taboolib.platform.util.buildItem +import taboolib.platform.util.nextChat +import java.util.function.Consumer + +/** + * Land + * com.mcstarrysky.land.menu.LandMainMenu + * + * @author mical + * @since 2024/8/2 23:16 + */ +object LandMainMenu { + + fun openMenu(player: Player, back: Consumer? = null) { + player.openMenu("领地主菜单") { + virtualize() + + map( + "b========", + "m n c d e", // 我的领地 全服领地 脚下领地 领地选择 + "=========" + ) + + markHeader() + + if (back != null) { + set('b', MenuRegistry.BACK) { + back.accept(player) + } + } else { + set('b', MenuRegistry.HEAD) + } + + set('m', buildItem(XMaterial.NETHER_STAR) { + name = "&{#5cb3cc}查看我的领地列表" + colored() + }) { + LandListMenu.openMenu( + player, + LandManager.getLands(player), + back + ) + } + + set('n', buildItem(XMaterial.ENDER_PEARL) { + name = "&{#66c18c}查看全部领地列表" + colored() + }) { + LandListMenu.openMenu( + player, + LandManager.lands, + back + ) + } + + set('c', buildItem(XMaterial.NETHERITE_BOOTS) { + name = "&{#fcd337}查看当前位置的领地" + colored() + flags += ItemFlag.values().toList() + }) { + LandListMenu.openMenu( + player, + listOfNotNull(LandManager.getLand(player.location)), + back + ) + } + + set('d', buildItem(XMaterial.NAME_TAG) { + name = "&e搜索领地" + colored() + }) { + clicker.closeInventory() + clicker.closeInventory() + clicker.prettyInfo("请在聊天框输入领地编号或名字包含的字词, 或输入'取消'来取消操作!") + clicker.nextChat { ctx -> + if (ctx == "取消") + return@nextChat + sync { + LandListMenu.openMenu( + player, + LandManager.lands.filter { it.id.toString() == ctx || it.name.contains(ctx) }, + back + ) + } + } + } + + set('e', buildItem(XMaterial.MAP) { + name = "&{#f97d1c}选择范围菜单" + colored() + }) { + LandSelectionMenu.openMenu(clicker, back) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/menu/LandSelectionMenu.kt b/src/main/kotlin/com/mcstarrysky/land/menu/LandSelectionMenu.kt new file mode 100644 index 0000000..601b848 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/menu/LandSelectionMenu.kt @@ -0,0 +1,60 @@ +package com.mcstarrysky.land.menu + +import com.mcstarrysky.land.Land +import com.mcstarrysky.land.util.MenuRegistry +import com.mcstarrysky.land.util.MenuRegistry.markBoard +import com.mcstarrysky.land.util.MenuRegistry.markHeader +import com.mcstarrysky.land.util.prettyInfo +import org.bukkit.entity.Player +import taboolib.library.xseries.XMaterial +import taboolib.module.ui.openMenu +import taboolib.module.ui.type.Chest +import taboolib.platform.util.buildItem +import taboolib.platform.util.giveItem +import java.util.function.Consumer + +/** + * Land + * com.mcstarrysky.land.menu.LandSelectionMenu + * + * @author mical + * @since 2024/8/3 17:00 + */ +object LandSelectionMenu { + + fun openMenu(player: Player, back: Consumer?) { + player.openMenu("选择范围") { + virtualize() + + map( + "b========", + " f ", // 免费领地棒 + "=========" + ) + + markHeader() + + set('b', MenuRegistry.BACK) { LandMainMenu.openMenu(player, back) } + + markBoard() + + set('f', buildItem(XMaterial.ARROW) { + name = "&b免费领地棒" + lore += listOf( + "&e单击领取", + "&7", + "&7点击方块即可免费创建并获取一个领地", + "&7领地大小为5*5区块", + "&7领地中心为你点击的方块的位置", + "&7只能在&{#8abcd1}主世界&7使用,且只能使用一次", + "&7领地大小获取后可自行扩展" + ) + colored() + }) { + clicker.closeInventory() + clicker.giveItem(Land.freeLandTool.clone()) + clicker.prettyInfo("获取免费领地棒成功.") + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/serializers/ChunkSerializer.kt b/src/main/kotlin/com/mcstarrysky/land/serializers/ChunkSerializer.kt new file mode 100644 index 0000000..7722361 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/serializers/ChunkSerializer.kt @@ -0,0 +1,31 @@ +package com.mcstarrysky.land.serializers + +import com.mcstarrysky.land.util.deserializeToChunk +import com.mcstarrysky.land.util.serializeToString +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import org.bukkit.Chunk + +/** + * Land + * com.mcstarrysky.land.serializers.ChunkSerializer + * + * @author mical + * @since 2024/8/2 22:45 + */ +object ChunkSerializer : KSerializer { + + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("org.bukkit.Chunk", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: Chunk) { + encoder.encodeString(value.serializeToString()) + } + + override fun deserialize(decoder: Decoder): Chunk { + return decoder.decodeString().deserializeToChunk() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/serializers/LocationSerializer.kt b/src/main/kotlin/com/mcstarrysky/land/serializers/LocationSerializer.kt new file mode 100644 index 0000000..930e809 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/serializers/LocationSerializer.kt @@ -0,0 +1,31 @@ +package com.mcstarrysky.land.serializers + +import com.mcstarrysky.land.util.deserializeToLocation +import com.mcstarrysky.land.util.serializeToString +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import org.bukkit.Location + +/** + * Land + * com.mcstarrysky.land.serializers.LocationSerializer + * + * @author mical + * @since 2024/8/2 22:41 + */ +object LocationSerializer : KSerializer { + + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("org.bukkit.Location", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: Location) { + encoder.encodeString(value.serializeToString()) + } + + override fun deserialize(decoder: Decoder): Location { + return decoder.decodeString().deserializeToLocation() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/serializers/UUIDSerializer.kt b/src/main/kotlin/com/mcstarrysky/land/serializers/UUIDSerializer.kt new file mode 100644 index 0000000..02a5cf5 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/serializers/UUIDSerializer.kt @@ -0,0 +1,29 @@ +package com.mcstarrysky.land.serializers + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import java.util.* + +/** + * Land + * com.mcstarrysky.land.serializers.UUIDSerializer + * + * @author mical + * @since 2024/8/2 22:40 + */ +object UUIDSerializer : KSerializer { + + override val descriptor: SerialDescriptor + get() = buildClassSerialDescriptor("java.util.UUID") + + override fun deserialize(decoder: Decoder): UUID { + return UUID.fromString(decoder.decodeString()) + } + + override fun serialize(encoder: Encoder, value: UUID) { + encoder.encodeString(value.toString()) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/util/ChunkUtils.kt b/src/main/kotlin/com/mcstarrysky/land/util/ChunkUtils.kt new file mode 100644 index 0000000..6b58765 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/util/ChunkUtils.kt @@ -0,0 +1,32 @@ +package com.mcstarrysky.land.util + +import org.bukkit.Chunk +import org.bukkit.Location + +/** + * Land + * com.mcstarrysky.land.util.ChunkUtils + * + * @author mical + * @since 2024/8/3 14:03 + */ +object ChunkUtils { + + fun getCenteredChunks(location: Location, m: Int): List { + val chunks = mutableListOf() + val playerChunk = location.chunk + val playerChunkX = playerChunk.x + val playerChunkZ = playerChunk.z + val radius = m / 2 + + // Iterate through an m*m grid of chunks around the player's chunk + for (dz in -radius..radius) { // Iterate vertically + for (dx in -radius..radius) { // Iterate horizontally + val chunk = location.world.getChunkAt(playerChunkX + dx, playerChunkZ + dz) + chunks.add(chunk) + } + } + + return chunks + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/util/ColorUtils.kt b/src/main/kotlin/com/mcstarrysky/land/util/ColorUtils.kt new file mode 100644 index 0000000..32f97ef --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/util/ColorUtils.kt @@ -0,0 +1,76 @@ +package com.mcstarrysky.land.util + +import java.awt.Color +import kotlin.math.min + + +fun modifiedColorCode(colorCode: String, ratio: Float): String { + val colorStr = colorCode.removePrefix("#") + + val red = Integer.parseInt(colorStr.substring(0, 2), 16) + val green = Integer.parseInt(colorStr.substring(2, 4), 16) + val blue = Integer.parseInt(colorStr.substring(4, 6), 16) + + val newRed = (red * ratio).toInt() + val newGreen = (green * ratio).toInt() + val newBlue = (blue * ratio).toInt() + + val finalRed = min(newRed + 30, 255) + val finalGreen = min(newGreen + 30, 255) + val finalBlue = min(newBlue + 30, 255) + + return String.format("#%02x%02x%02x", finalRed, finalGreen, finalBlue) +} + +fun whiteColorCode(colorCode: String): String { + return gradientColors(colorCode, "#ffffff", 10)[8] +} + +fun gradientColors(cA: String, cB: String, number: Int): List { + val a = hex2RGB(cA) + val b = hex2RGB(cB) + val colors: MutableList = ArrayList() + for (i in 0 until number) { + val aR = a[0] + val aG = a[1] + val aB = a[2] + val bR = b[0] + val bG = b[1] + val bB = b[2] + colors.add( + rgb2Hex( + calculateColor(aR, bR, number - 1, i), + calculateColor(aG, bG, number - 1, i), + calculateColor(aB, bB, number - 1, i) + ) + ) + } + return colors +} + +fun calculateColor(a: Int, b: Int, step: Int, number: Int): Int { + return a + (b - a) * number / step +} + +fun rgb2Hex(r: Int, g: Int, b: Int): String { + return String.format("#%02X%02X%02X", r, g, b) +} + +fun hex2RGB(hexStr: String): IntArray { + if (hexStr.length == 7) { + val rgb = IntArray(3) + rgb[0] = hexStr.substring(1, 3).toInt(16) + rgb[1] = hexStr.substring(3, 5).toInt(16) + rgb[2] = hexStr.substring(5, 7).toInt(16) + return rgb + } + throw Exception() +} + +fun hexToColor(hex: String): Color { + val red = hex.substring(1, 3).toInt(16) + val green = hex.substring(3, 5).toInt(16) + val blue = hex.substring(5, 7).toInt(16) + + return Color(red, green, blue) +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/util/LocationUtils.kt b/src/main/kotlin/com/mcstarrysky/land/util/LocationUtils.kt new file mode 100644 index 0000000..eeba2cf --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/util/LocationUtils.kt @@ -0,0 +1,76 @@ +package com.mcstarrysky.land.util + +import org.bukkit.Chunk +import org.bukkit.Location +import org.bukkit.World + +/** + * Land + * com.mcstarrysky.land.util.LocationUtils + * + * @author mical + * @since 2024/8/3 14:07 + */ +object LocationUtils { + + /** + * 计算领地中心的位置,确保在任何情况下都能返回一个有效的地面位置。 + * @param chunks 区块列表 + * @param world 世界对象 + * @return 地面上的一个位置 + * @throws IllegalArgumentException 如果区块列表为空 + */ + fun calculateLandCenter(chunks: List, world: World): Location { + require(chunks.isNotEmpty()) { "区块列表不能为空" } + + var totalX = 0.0 + var totalZ = 0.0 + + // 计算总的 X 和 Z 坐标 + for (chunk in chunks) { + val chunkCenter = getChunkCenter(chunk) + totalX += chunkCenter.x + totalZ += chunkCenter.z + } + + // 计算平均 X 和 Z 坐标 + val avgX = totalX / chunks.size + val avgZ = totalZ / chunks.size + + // 从最高方块到海平面开始搜索地面位置 + val highestY = world.getHighestBlockYAt(avgX.toInt(), avgZ.toInt()) + + var groundY = 0 + for (y in highestY downTo 0) { + val testLocation = Location(world, avgX, y.toDouble(), avgZ) + if (isLocationOnGround(testLocation)) { + groundY = y + break + } + } + + // 如果未找到地面位置,则返回海平面位置 + return Location(world, avgX, groundY.toDouble(), avgZ) + } + + /** + * 获取区块中心的位置 + * @param chunk 区块对象 + * @return 区块中心的位置 + */ + fun getChunkCenter(chunk: Chunk): Location { + val x = (chunk.x shl 4) + 8 + val z = (chunk.z shl 4) + 8 + return Location(chunk.world, x.toDouble(), 0.0, z.toDouble()) + } + + /** + * 检查位置是否在地面上 + * @param location 待检查的位置 + * @return true 如果位置在地面上,否则 false + */ + fun isLocationOnGround(location: Location): Boolean { + val block = location.block + return block.type.isSolid // 可根据具体需求修改条件,判断是否为地面方块 + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/util/MenuRegistry.kt b/src/main/kotlin/com/mcstarrysky/land/util/MenuRegistry.kt new file mode 100644 index 0000000..93eba69 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/util/MenuRegistry.kt @@ -0,0 +1,95 @@ +package com.mcstarrysky.land.util + +import org.bukkit.Material +import org.bukkit.inventory.ItemStack +import taboolib.module.ui.type.Chest +import taboolib.module.ui.type.PageableChest +import taboolib.platform.util.buildItem + +/** + * Land + * com.mcstarrysky.land.util.MenuRegistry + * + * @author mical + * @since 2024/8/2 23:20 + */ +object MenuRegistry { + + val NEXT: ItemStack = buildItem(Material.ARROW) { + name = "&f下一页" + colored() + } + + val PRE: ItemStack = buildItem(Material.ARROW) { + name = "&f上一页" + colored() + } + + val NO_NEXT: ItemStack = buildItem(Material.FEATHER) { + name = "&f没有下一页" + colored() + } + + val NO_PRE: ItemStack = buildItem(Material.FEATHER) { + name = "&f没有上一页" + colored() + } + + val HEAD = buildItem(Material.BLACK_STAINED_GLASS_PANE) { + name = " " + } + + val BOARD = buildItem(Material.GRAY_STAINED_GLASS_PANE) { + name = " " + } + + val CLOSE = buildItem(Material.BARRIER) { + name = "&c关闭菜单" + colored() + } + + val BACK = buildItem(Material.CLOCK) { + name = "&{#ddca57}返回上一菜单" + colored() + } + + fun Chest.markHeader( + char: Char = '=', + itemStack: ItemStack = HEAD + ) { + set(char, itemStack) + onClick(char) { event -> + event.isCancelled = true + } + } + + fun Chest.markBoard( + char: Char = ' ', + itemStack: ItemStack = BOARD + ) { + set(char, itemStack) + onClick(char) { event -> + event.isCancelled = true + } + } + + fun PageableChest.markPageButton( + next: Char = 'n', + previous: Char = 'p' + ) { + setNextPage(getFirstSlot(next)) { _, hasNextPage -> + if (hasNextPage) { + NEXT + } else { + NO_NEXT + } + } + setPreviousPage(getFirstSlot(previous)) { _, hasNextPage -> + if (hasNextPage) { + PRE + } else { + NO_PRE + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/util/PDCUtils.kt b/src/main/kotlin/com/mcstarrysky/land/util/PDCUtils.kt new file mode 100644 index 0000000..0d7b82b --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/util/PDCUtils.kt @@ -0,0 +1,33 @@ +package com.mcstarrysky.land.util + +import org.bukkit.NamespacedKey +import org.bukkit.persistence.PersistentDataHolder +import org.bukkit.persistence.PersistentDataType + +/** + * 从 PDC 获取内容 + */ +operator fun PersistentDataHolder.get(key: String, type: PersistentDataType): Z? { + return persistentDataContainer.get(NamespacedKey.minecraft(key), type) +} + +/** + * 向 PDC 设置内容 + */ +operator fun PersistentDataHolder.set(key: String, type: PersistentDataType, value: Z) { + persistentDataContainer.set(NamespacedKey.minecraft(key), type, value) +} + +/** + * 判断 PDC 是否包含某个键 + */ +fun PersistentDataHolder.has(key: String, type: PersistentDataType): Boolean { + return persistentDataContainer.has(NamespacedKey.minecraft(key), type) +} + +/** + * 从 PDC 移除内容 + */ +fun PersistentDataHolder.remove(key: String) { + return persistentDataContainer.remove(NamespacedKey.minecraft(key)) +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/util/SkullUtils.kt b/src/main/kotlin/com/mcstarrysky/land/util/SkullUtils.kt new file mode 100644 index 0000000..8e4f359 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/util/SkullUtils.kt @@ -0,0 +1,71 @@ +package com.mcstarrysky.land.util + +import com.google.gson.JsonParser +import com.mojang.authlib.GameProfile +import com.mojang.authlib.properties.Property +import org.bukkit.Bukkit +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.meta.SkullMeta +import taboolib.common5.util.decodeBase64 +import taboolib.library.reflex.Reflex.Companion.setProperty +import taboolib.module.nms.MinecraftVersion +import taboolib.platform.util.isAir +import taboolib.platform.util.modifyMeta +import java.net.URL +import java.util.* + +/** + * Land + * com.mcstarrysky.land.util.SkullUtils + * + * @author mical + * @since 2024/8/3 16:52 + */ +fun ItemStack.skull(skull: String?): ItemStack { + skull ?: return this + if (this.isAir) return this + if (itemMeta !is SkullMeta) return this + return if (skull.length <= 20) modifyMeta { owner = skull } + else textured(skull) +} + +/** + * 旧版 JsonParser + * 旧版没有 parseString 静态方法 + */ +val JSON_PARSER = JsonParser() + +infix fun ItemStack.textured(headBase64: String): ItemStack { + return modifyMeta { + if (MinecraftVersion.majorLegacy >= 12000) { + val profile = Bukkit.createProfile(UUID.randomUUID(), "TabooLib") + val textures = profile.textures + textures.skin = URL(getTextureURLFromBase64(headBase64)) + profile.setTextures(textures) + playerProfile = profile + } else { + val profile = GameProfile(UUID.randomUUID(), "TabooLib") + val texture = if (headBase64.length in 60..100) encodeTexture(headBase64) else headBase64 + profile.properties.put("textures", Property("textures", texture, "Aiyatsbus_TexturedSkull")) + + setProperty("profile", profile) + } + } +} + +@Suppress("HttpUrlsUsage") +fun encodeTexture(input: String): String { + return with(Base64.getEncoder()) { + encodeToString("{\"textures\":{\"SKIN\":{\"url\":\"http://textures.minecraft.net/texture/$input\"}}}".toByteArray()) + } +} + +private fun getTextureURLFromBase64(headBase64: String): String { + return JSON_PARSER + .parse(String(headBase64.decodeBase64())) + .asJsonObject + .getAsJsonObject("textures") + .getAsJsonObject("SKIN") + .get("url") + .asString +} \ No newline at end of file diff --git a/src/main/kotlin/com/mcstarrysky/land/util/Utils.kt b/src/main/kotlin/com/mcstarrysky/land/util/Utils.kt new file mode 100644 index 0000000..427e6c4 --- /dev/null +++ b/src/main/kotlin/com/mcstarrysky/land/util/Utils.kt @@ -0,0 +1,91 @@ +package com.mcstarrysky.land.util + +import com.mcstarrysky.land.flag.Permission +import com.mcstarrysky.land.manager.LandManager +import kotlinx.serialization.json.Json +import org.bukkit.Bukkit +import org.bukkit.Chunk +import org.bukkit.Location +import org.bukkit.command.CommandSender +import taboolib.common.platform.function.adaptCommandSender +import taboolib.common.util.replaceWithOrder +import taboolib.common.util.unsafeLazy +import taboolib.module.chat.Source +import taboolib.module.chat.component +import java.text.SimpleDateFormat +import java.util.UUID + +/** + * Land + * com.mcstarrysky.land.LandUtils + * + * @author mical + * @since 2024/8/2 22:33 + */ +val ZERO_UUID = UUID(0, 0) + +fun Permission.registerPermission() { + if (LandManager.permissions.none { it.id == id }) { + LandManager.permissions += this + } +} + +fun String.isValidLandName(): Boolean { + // 判断名字是否为纯数字 + if (this.all { it.isDigit() }) { + return false + } + // 如果名字不是纯数字,则认为是合法的 + return true +} + +val Boolean?.display: String + get() = if (this == null) "§e未设置" else if (this) "§a允许" else "§c阻止" + +val json by unsafeLazy { + Json { + ignoreUnknownKeys = true + coerceInputValues = true + allowStructuredMapKeys = true + } +} + +fun Location.serializeToString(): String { + return "${world.name}~$x,$y,$z~$yaw,$pitch" +} + +fun String.deserializeToLocation(): Location { + val (world, pos, rotation) = split("~") + val (x, y, z) = pos.split(",").map { it.toDouble() } + val (yaw, pitch) = rotation.split(",").map { it.toFloat() } + return Location(Bukkit.getWorld(world), x, y, z, yaw, pitch) +} + +fun Chunk.serializeToString(): String { + return "${world.name}~$x,$z" +} + +fun String.deserializeToChunk(): Chunk { + val (world, pos) = split("~") + val (x, z) = pos.split(",").map { it.toInt() } + return Bukkit.getWorld(world)!!.getChunkAt(x, z) +} + +private const val color = "#1af1aa" + +private val prefix = whiteColorCode(color) + +fun CommandSender.prettyInfo(message: String, vararg args: Any) { + cacheMessageWithPrefix(message, *args).sendTo(adaptCommandSender(this)) +} + +fun cacheMessageWithPrefix(message: String, vararg args: Any): Source { + return "&8\\[&{${color}}领地&8\\] &{${prefix}}$message".replaceWithOrder(*args) + .component().buildColored() +} + +fun cacheMessageWithPrefixColor(message: String, vararg args: Any): Source { + return "&{${prefix}}$message".replaceWithOrder(*args).component().buildColored() +} + +val DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..921ab79 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,10 @@ + + +# Powered by TabooLib 6.1 # + + +Settings: + world-aliases: + world: "主世界" + world_nether: "地狱" + world_the_end: "末地" \ No newline at end of file