diff --git a/README.md b/README.md index de00083d..4de60e24 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![ClaimChunk Title](imgs/logo_carrier.png) [![Version Info](https://img.shields.io/static/v1?label=Version&message=0.0.23-RC8&color=blueviolet&style=for-the-badge)](https://github.com/cjburkey01/ClaimChunk/releases) -[![Download Info](https://img.shields.io/static/v1?label=Spigot&message=1.19.2&color=blueviolet&style=for-the-badge)](https://www.spigotmc.org/resources/claimchunk.44458/) +[![Download Info](https://img.shields.io/static/v1?label=Spigot&message=1.20.6&color=blueviolet&style=for-the-badge)](https://www.spigotmc.org/resources/claimchunk.44458/) [![Servers Using Claimchunk](https://img.shields.io/bstats/servers/5179?label=Servers&color=cornflowerblue&style=for-the-badge)](https://bstats.org/plugin/bukkit/ClaimChunk) [![Players Using Claimchunk](https://img.shields.io/bstats/players/5179?label=Players&color=cornflowerblue&style=for-the-badge)](https://bstats.org/plugin/bukkit/ClaimChunk) @@ -10,14 +10,16 @@ Info ---- -Spigot plugin for 1.17+ allowing the claiming of chunks. +Spigot plugin for 1.20+ allowing the claiming of chunks. *The destiny of chunks is to unite not to divide*
*Let's make the world ours.* Usage and more information can be found [on the wiki](https://github.com/cjburkey01/ClaimChunk/wiki). -* **1.17 - 1.19.2** | The latest version works seamlessly. +* **1.20-1.20.6+** | The latest version works seamlessly (excluding bugs, of course). +* **1.17 - 1.20** | The latest *known* working version is [0.0.23-RC8](https://github.com/cjburkey01/ClaimChunk/releases/tag/0.0.23-RC8). + * Newer versions of the plugin will require Java 17, but may still work. * **1.13 - 1.16.5** | The latest working version is [0.0.22](https://github.com/cjburkey01/ClaimChunk/releases/tag/0.0.22). * Note: If you disable titles, 0.0.23 might work on version older than 1.17. If you experience issues, however, they may be more difficult to address. * **Pre-1.13** | The latest working version is [0.0.20](https://github.com/cjburkey01/ClaimChunk/releases/tag/0.0.20) diff --git a/build.gradle.kts b/build.gradle.kts index 03634dd7..9f69444f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,10 +7,10 @@ import de.undercouch.gradle.tasks.download.Download; plugins { java; - id("de.undercouch.download") version "5.3.0"; - id("io.freefair.lombok") version "6.5.1"; + id("de.undercouch.download") version "5.6.0"; + id("io.freefair.lombok") version "8.6"; // Including dependencies in final jar - id("com.github.johnrengelman.shadow") version "7.1.2"; + id("com.github.johnrengelman.shadow") version "8.1.1"; } object DepData { @@ -23,21 +23,23 @@ object DepData { // Only used if you run `gradlew installSpigot` const val SPIGOT_BUILD_TOOLS_URL = "https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar"; + const val SPIGOT_REV = "1.20.4"; // Dependency versions - const val BUKKIT_VERSION = "1.19.2-R0.1-SNAPSHOT"; - const val SPIGOT_VERSION = "1.19.2-R0.1-SNAPSHOT"; - const val LATEST_MC_VERSION = "1.19.2"; + const val BUKKIT_VERSION = "1.20.4-R0.1-SNAPSHOT"; + const val SPIGOT_VERSION = "1.20.4-R0.1-SNAPSHOT"; + const val LATEST_MC_VERSION = "1.20.6"; const val VAULT_API_VERSION = "1.7"; const val WORLD_EDIT_CORE_VERSION = "7.2.9"; const val WORLD_GUARD_BUKKIT_VERSION = "7.0.7"; const val PLACEHOLDER_API_VERSION = "2.11.1"; const val JETBRAINS_ANNOTATIONS_VERSION = "23.0.0"; - const val JUNIT_VERSION = "5.9.0"; + const val JUNIT_VERSION = "5.10.2"; + const val JUNIT_LAUNCHER_VERSION = "1.10.2"; // Goldmensch's SmartCommandDispatcher. Thank you!! - const val SMART_COMMAND_DISPATCHER_VERSION = "2.0.1"; + // const val SMART_COMMAND_DISPATCHER_VERSION = "2.0.1"; // And internationalization library! - const val JALL_I18N_VERSION = "1.0.2" + // const val JALL_I18N_VERSION = "1.0.2" // Directories const val TEST_SERVER_DIR = "run"; @@ -66,9 +68,10 @@ version = DepData.THIS_VERSION; val mainDir = layout.projectDirectory; -// Use Java 16 (17 was too restrictive) -extensions.configure { - toolchain.languageVersion.set(JavaLanguageVersion.of(16)); +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17); + } } tasks { @@ -97,11 +100,11 @@ tasks { // Move SmartCommandDispatcher to a unique package to avoid clashes with // any other plugins that might include it in their jar files. - relocate("de.goldmensch.commanddispatcher", - "claimchunk.dependency.de.goldmensch.commanddispatcher"); + // relocate("de.goldmensch.commanddispatcher", + // "claimchunk.dependency.de.goldmensch.commanddispatcher"); // Do the same with JALL - relocate("io.github.goldmensch.jall", - "claimchunk.dependency.io.github.goldmensch.jall"); + // relocate("io.github.goldmensch.jall", + // "claimchunk.dependency.io.github.goldmensch.jall"); } test { @@ -142,7 +145,7 @@ tasks { // Fill in readme placeholders register("updateReadme") { - mustRunAfter("shadowJar"); + mustRunAfter("shadowJar", "build"); description = "Expands tokens in the unbuilt readme file into the main readme file"; val inf = mainDir.file(DepData.README_IN); @@ -190,16 +193,16 @@ tasks { val testServerDir = mainDir.dir(DepData.TEST_SERVER_DIR); val tmpDir = testServerDir.dir("TEMP"); - val tmpServerJar = tmpDir.file("spigot-${DepData.LATEST_MC_VERSION}.jar"); + val tmpServerJar = tmpDir.file("spigot-${DepData.SPIGOT_REV}.jar"); // Run the build tools jar (the manifest main class) mainClass.set("-jar"); workingDir(tmpDir); - args("BuildTools.jar"); + args("BuildTools.jar", "--nogui", "--rev", DepData.SPIGOT_REV); doLast { println("Cleaning up Spigot build"); - tmpServerJar.asFile.copyTo(testServerDir.file("spigot-${DepData.LATEST_MC_VERSION}.jar").asFile, true); + tmpServerJar.asFile.copyTo(testServerDir.file("spigot-${DepData.SPIGOT_REV}.jar").asFile, true); tmpDir.asFile.deleteRecursively(); } } @@ -239,14 +242,12 @@ tasks { description = "Attempts to format source files for ClaimChunk to unify programming style."; // For now, this file is just included with the project for the sake of - // ease of use. Perhaps I should release an updated version of the - // plugin someone else developed, it's outdated and wouldn't work. - // (Hence my reinventing the broken wheel here) - val execJarFile = mainDir.file("req/google-java-format-1.11.0-all-deps.jar"); + // ease of use. + val execJarFile = mainDir.file("req/google-java-format-1.22.0-all-deps.jar"); // Include all source Java files // (I don't think there's a case where I would want to avoid formatting - // a file, but be it necessary, this is where it would be implemented. + // a file, but be it necessary, this is where it would be implemented) val includedFiles = fileTree("src") { include("**/*.java") }.files; @@ -301,10 +302,10 @@ dependencies { compileOnly("me.clip:placeholderapi:${DepData.PLACEHOLDER_API_VERSION}"); // Dependencies that needs to be shaded into the final jar - implementation("de.goldmensch:SmartCommandDispatcher:${DepData.SMART_COMMAND_DISPATCHER_VERSION}"); - implementation("io.github.goldmensch:JALL:${DepData.JALL_I18N_VERSION}"); + // implementation("de.goldmensch:SmartCommandDispatcher:${DepData.SMART_COMMAND_DISPATCHER_VERSION}"); + // implementation("io.github.goldmensch:JALL:${DepData.JALL_I18N_VERSION}"); - testCompileOnly("org.junit.jupiter:junit-jupiter-api:${DepData.JUNIT_VERSION}"); - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${DepData.JUNIT_VERSION}"); + testImplementation("org.junit.jupiter:junit-jupiter:${DepData.JUNIT_VERSION}"); + testRuntimeOnly("org.junit.platform:junit-platform-launcher:${DepData.JUNIT_LAUNCHER_VERSION}"); } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f..249e5832 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae04661e..d951fac2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 744e882e..a69d9cb6 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -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 +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MSYS* | MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 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" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + 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 @@ -106,80 +140,101 @@ 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 +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# 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 +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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 +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - 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\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - 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; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# 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" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index ac1b06f9..53a6b238 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/req/google-java-format-1.22.0-all-deps.jar b/req/google-java-format-1.22.0-all-deps.jar new file mode 100644 index 00000000..2e8b83d0 Binary files /dev/null and b/req/google-java-format-1.22.0-all-deps.jar differ diff --git a/src/main/java/com/cjburkey/claimchunk/Utils.java b/src/main/java/com/cjburkey/claimchunk/Utils.java index 70537737..936390a5 100644 --- a/src/main/java/com/cjburkey/claimchunk/Utils.java +++ b/src/main/java/com/cjburkey/claimchunk/Utils.java @@ -74,7 +74,7 @@ public static BaseComponent toComponent(@Nullable CommandSender sender, String i placeholders == null ? input : ClaimChunkPlaceholders.fillPlaceholders(sender, input); - return new TextComponent(TextComponent.fromLegacyText(color(str))); + return new TextComponent(TextComponent.fromLegacy(color(str))); } public static void msg(CommandSender to, BaseComponent msg) { @@ -100,10 +100,11 @@ public static void toPlayer(@NotNull Player ply, @NotNull BaseComponent msg) { // Show the message in the action bar ply.spigot().sendMessage(ChatMessageType.ACTION_BAR, msg); } else { - // Show the message in the sub title (bigger but less room) + // Show the message in the subtitle (bigger but less room) ply.sendTitle(" ", msg.toLegacyText(), in, stay, out); } } catch (Exception e) { + //noinspection CallToPrintStackTrace e.printStackTrace(); // An error occurred, use chat @@ -126,7 +127,7 @@ public static void toPlayer(Player ply, String text) { * claimchunk.admin}, they will have all permissions. * * @param sender The given command sender (player, console, etc). - * @param basic Whether or not {@code claimchunk.player} should also grant this permission. + * @param basic Whether {@code claimchunk.player} should also grant this permission. * @param perm The string for the permission node. * @return A boolean representing whether the sender has this permission. */ @@ -189,11 +190,7 @@ public static Map getDefaultPermissionsMap() { public static Map getAllFalsePermissionsMap() { // Map of permissions with all set to false Map permissionsMap = getDefaultPermissionsMap(); - - for (String p : permissionsMap.keySet()) { - permissionsMap.put(p, false); - } - + permissionsMap.replaceAll((p, v) -> false); return permissionsMap; } diff --git a/src/main/java/com/cjburkey/claimchunk/chunk/ChunkPlayerPermissions.java b/src/main/java/com/cjburkey/claimchunk/chunk/ChunkPlayerPermissions.java index 8531c86a..218e16a7 100644 --- a/src/main/java/com/cjburkey/claimchunk/chunk/ChunkPlayerPermissions.java +++ b/src/main/java/com/cjburkey/claimchunk/chunk/ChunkPlayerPermissions.java @@ -145,10 +145,10 @@ public static ChunkPlayerPermissions fromPermissionsMap(Map per case "doors" -> chunkPlayerPermissions.allowUseDoors(perm.getValue()); case "redstone" -> chunkPlayerPermissions.allowUseRedstone(perm.getValue()); case "interactVehicles" -> chunkPlayerPermissions.allowUseVehicles(perm.getValue()); - case "interactEntities" -> chunkPlayerPermissions.allowInteractEntities( - perm.getValue()); - case "interactBlocks" -> chunkPlayerPermissions.allowInteractBlocks( - perm.getValue()); + case "interactEntities" -> + chunkPlayerPermissions.allowInteractEntities(perm.getValue()); + case "interactBlocks" -> + chunkPlayerPermissions.allowInteractBlocks(perm.getValue()); case "useContainers" -> chunkPlayerPermissions.allowUseContainers(perm.getValue()); } } diff --git a/src/main/java/com/cjburkey/claimchunk/config/ClaimChunkWorldProfile.java b/src/main/java/com/cjburkey/claimchunk/config/ClaimChunkWorldProfile.java index c07c5841..3194199f 100644 --- a/src/main/java/com/cjburkey/claimchunk/config/ClaimChunkWorldProfile.java +++ b/src/main/java/com/cjburkey/claimchunk/config/ClaimChunkWorldProfile.java @@ -48,10 +48,13 @@ public class ClaimChunkWorldProfile { * one will be able to make any claims, including admins. */ public boolean enabled; + /** Whether players' claims should be protected when they are offline. */ public boolean protectOffline = true; + /** Whether players' claims should be protected when they are online. */ public boolean protectOnline = true; + /** * If this is true, non-owner or access players won't be able to use ender pearls within claimed * chunks. @@ -60,6 +63,7 @@ public class ClaimChunkWorldProfile { /** Mapping from entity config class names to a set of entities for that class. */ public final HashMap> entityClasses = new HashMap<>(); + /** Mapping from block config class names to a set of blocks (materials) for that class. */ public final HashMap> blockClasses = new HashMap<>(); @@ -70,10 +74,13 @@ public class ClaimChunkWorldProfile { /** The fire spread config protection profile. */ public FullSpreadProfile fireSpread = new FullSpreadProfile(); + /** The water spread config protection profile. */ public FullSpreadProfile waterSpread = new FullSpreadProfile(); + /** The lava spread config protection profile. */ public FullSpreadProfile lavaSpread = new FullSpreadProfile(); + /** The piston extension config protection profile. */ public SpreadProfile pistonExtend = new SpreadProfile(); @@ -83,13 +90,16 @@ public class ClaimChunkWorldProfile { /** A set of commands that should be denied for un-owning players in claimed chunks. */ public HashSet blockedCmdsInDiffClaimed = new HashSet<>(); + /** A set of commands that should be denied for players in their own claimed chunks. */ public HashSet blockedCmdsInOwnClaimed = new HashSet<>(); + /** A set of commands that should be denied for players in unclaimed chunks. */ public HashSet blockedCmdsInUnclaimed = new HashSet<>(); /** The access storage for claimed chunks. */ public final Accesses claimedChunks; + /** The access storage for unclaimed chunks. */ public final Accesses unclaimedChunks; diff --git a/src/main/java/com/cjburkey/claimchunk/event/WorldProfileEventHandler.java b/src/main/java/com/cjburkey/claimchunk/event/WorldProfileEventHandler.java index 787756f5..db0ef5ec 100644 --- a/src/main/java/com/cjburkey/claimchunk/event/WorldProfileEventHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/event/WorldProfileEventHandler.java @@ -731,13 +731,14 @@ private boolean onBlockEvent( switch (accessType) { case BREAK -> permissionNeeded = "break"; case PLACE -> permissionNeeded = "place"; - case INTERACT -> permissionNeeded = - switch (blockClass) { - case "REDSTONE" -> "redstone"; - case "DOOR" -> "doors"; - case "CONTAINER" -> "useContainers"; - default -> "interactBlocks"; - }; + case INTERACT -> + permissionNeeded = + switch (blockClass) { + case "REDSTONE" -> "redstone"; + case "DOOR" -> "doors"; + case "CONTAINER" -> "useContainers"; + default -> "interactBlocks"; + }; } final boolean isOwner = (chunkOwner != null && chunkOwner.equals(ply)); final boolean isOwnerOrAccess = diff --git a/src/main/java/com/cjburkey/claimchunk/smartcommand/CCBukkitCommand.java b/src/main/java/com/cjburkey/claimchunk/smartcommand/CCBukkitCommand.java index e1f6e9d1..bbd06d44 100644 --- a/src/main/java/com/cjburkey/claimchunk/smartcommand/CCBukkitCommand.java +++ b/src/main/java/com/cjburkey/claimchunk/smartcommand/CCBukkitCommand.java @@ -58,7 +58,9 @@ public boolean execute( } private static Object getPrivateField(Object object, String field) - throws SecurityException, NoSuchFieldException, IllegalArgumentException, + throws SecurityException, + NoSuchFieldException, + IllegalArgumentException, IllegalAccessException { Class clazz = object.getClass(); Field objectField = diff --git a/src/main/java/com/cjburkey/claimchunk/smartcommand/CCSubCommand.java b/src/main/java/com/cjburkey/claimchunk/smartcommand/CCSubCommand.java index 19e23d2c..cb4e9106 100644 --- a/src/main/java/com/cjburkey/claimchunk/smartcommand/CCSubCommand.java +++ b/src/main/java/com/cjburkey/claimchunk/smartcommand/CCSubCommand.java @@ -232,22 +232,22 @@ public final List onTabComplete( String partialArg = args[argIndex]; return switch (getPermittedArguments()[argIndex].tab) { case ONLINE_PLAYER -> - // Return all online players - getOnlinePlayers(partialArg); + // Return all online players + getOnlinePlayers(partialArg); case OFFLINE_PLAYER -> - // Return all players - getOfflinePlayers(partialArg); + // Return all players + getOfflinePlayers(partialArg); case BOOLEAN -> - // Return a boolean value - Arrays.asList( - claimChunk.getMessages().argTypeBoolTrue, - claimChunk.getMessages().argTypeBoolFalse); + // Return a boolean value + Arrays.asList( + claimChunk.getMessages().argTypeBoolTrue, + claimChunk.getMessages().argTypeBoolFalse); case PERMISSION -> - // Return possible permission arguments - getPermissionArgs(partialArg); + // Return possible permission arguments + getPermissionArgs(partialArg); default -> - // Return an empty list because it's an invalid/none tab completion - Collections.emptyList(); + // Return an empty list because it's an invalid/none tab completion + Collections.emptyList(); }; } diff --git a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/admin/AdminOverrideCmd.java b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/admin/AdminOverrideCmd.java index 2def509e..243345a0 100644 --- a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/admin/AdminOverrideCmd.java +++ b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/admin/AdminOverrideCmd.java @@ -11,7 +11,9 @@ import java.util.Optional; -/** @since 0.0.23 */ +/** + * @since 0.0.23 + */ public class AdminOverrideCmd extends CCSubCommand { public AdminOverrideCmd(ClaimChunk claimChunk) { diff --git a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/AccessCmd.java b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/AccessCmd.java index 5116c50c..3667d72f 100644 --- a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/AccessCmd.java +++ b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/AccessCmd.java @@ -14,7 +14,9 @@ import java.util.Map; import java.util.Optional; -/** @since 0.0.24 */ +/** + * @since 0.0.24 + */ public class AccessCmd extends CCSubCommand { private static final String[] nonPlayerArguments = diff --git a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/AlertCmd.java b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/AlertCmd.java index ee0f5d2a..84ed01db 100644 --- a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/AlertCmd.java +++ b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/AlertCmd.java @@ -11,7 +11,9 @@ import java.util.Optional; -/** @since 0.0.23 */ +/** + * @since 0.0.23 + */ public class AlertCmd extends CCSubCommand { public AlertCmd(ClaimChunk claimChunk) { diff --git a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/AutoCmd.java b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/AutoCmd.java index aa9275d3..32068363 100644 --- a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/AutoCmd.java +++ b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/AutoCmd.java @@ -12,7 +12,9 @@ import java.util.Optional; -/** @since 0.0.23 */ +/** + * @since 0.0.23 + */ public class AutoCmd extends CCSubCommand { public AutoCmd(ClaimChunk claimChunk) { diff --git a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/CheckAccessCmd.java b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/CheckAccessCmd.java index 9e151b42..6ab0490e 100644 --- a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/CheckAccessCmd.java +++ b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/CheckAccessCmd.java @@ -11,7 +11,9 @@ import java.util.Optional; -/** @since 0.0.24 */ +/** + * @since 0.0.24 + */ public class CheckAccessCmd extends CCSubCommand { public CheckAccessCmd(ClaimChunk claimChunk) { diff --git a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ClaimCmd.java b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ClaimCmd.java index bc82d1ec..4d887748 100644 --- a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ClaimCmd.java +++ b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ClaimCmd.java @@ -11,7 +11,9 @@ import java.util.Optional; -/** @since 0.0.23 */ +/** + * @since 0.0.23 + */ public class ClaimCmd extends CCSubCommand { public ClaimCmd(ClaimChunk claimChunk) { diff --git a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/GiveCmd.java b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/GiveCmd.java index 374ae7a6..2d148a5f 100644 --- a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/GiveCmd.java +++ b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/GiveCmd.java @@ -11,7 +11,9 @@ import java.util.Optional; -/** @since 0.0.23 */ +/** + * @since 0.0.23 + */ public class GiveCmd extends CCSubCommand { public GiveCmd(ClaimChunk claimChunk) { diff --git a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/HelpCmd.java b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/HelpCmd.java index 26cdc932..762d3cc2 100644 --- a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/HelpCmd.java +++ b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/HelpCmd.java @@ -12,7 +12,9 @@ import java.util.Optional; -/** @since 0.0.23 */ +/** + * @since 0.0.23 + */ public class HelpCmd extends CCSubCommand { private final ClaimChunkBaseCommand baseCommand; diff --git a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/InfoCmd.java b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/InfoCmd.java index 42897c35..2c6e2ba6 100644 --- a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/InfoCmd.java +++ b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/InfoCmd.java @@ -11,7 +11,9 @@ import java.util.Optional; -/** @since 0.0.23 */ +/** + * @since 0.0.23 + */ public class InfoCmd extends CCSubCommand { public InfoCmd(ClaimChunk claimChunk) { diff --git a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ListCmd.java b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ListCmd.java index 937d30ac..eaad92d3 100644 --- a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ListCmd.java +++ b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ListCmd.java @@ -12,7 +12,9 @@ import java.util.Optional; -/** @since 0.0.23 */ +/** + * @since 0.0.23 + */ public class ListCmd extends CCSubCommand { public ListCmd(ClaimChunk claimChunk) { diff --git a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/NameCmd.java b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/NameCmd.java index e5dabebc..518d08a7 100644 --- a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/NameCmd.java +++ b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/NameCmd.java @@ -11,7 +11,9 @@ import java.util.Optional; -/** @since 0.0.23 */ +/** + * @since 0.0.23 + */ public class NameCmd extends CCSubCommand { public NameCmd(ClaimChunk claimChunk) { diff --git a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/RevokeAccessCmd.java b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/RevokeAccessCmd.java index 656cb2ee..8f12f363 100644 --- a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/RevokeAccessCmd.java +++ b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/RevokeAccessCmd.java @@ -11,7 +11,9 @@ import java.util.Optional; -/** @since 0.0.24 */ +/** + * @since 0.0.24 + */ public class RevokeAccessCmd extends CCSubCommand { public RevokeAccessCmd(ClaimChunk claimChunk) { diff --git a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ShowClaimedCmd.java b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ShowClaimedCmd.java index def0adeb..af456425 100644 --- a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ShowClaimedCmd.java +++ b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ShowClaimedCmd.java @@ -13,7 +13,9 @@ import java.util.HashSet; import java.util.Optional; -/** @since 0.0.23 */ +/** + * @since 0.0.23 + */ public class ShowClaimedCmd extends CCSubCommand { public int maxSeconds = 60; diff --git a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ShowCmd.java b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ShowCmd.java index a8b13243..5cad200d 100644 --- a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ShowCmd.java +++ b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ShowCmd.java @@ -13,7 +13,9 @@ import java.util.Optional; -/** @since 0.0.23 */ +/** + * @since 0.0.23 + */ public class ShowCmd extends CCSubCommand { public int maxSeconds = 60; diff --git a/src/main/java/de/goldmensch/commanddispatcher/ArraySets.java b/src/main/java/de/goldmensch/commanddispatcher/ArraySets.java new file mode 100644 index 00000000..8ed7f22b --- /dev/null +++ b/src/main/java/de/goldmensch/commanddispatcher/ArraySets.java @@ -0,0 +1,20 @@ +package de.goldmensch.commanddispatcher; + +import org.jetbrains.annotations.NotNull; + +import java.util.Set; + +public class ArraySets { + public static @NotNull T[] getBiggest(@NotNull Set set) { + @SuppressWarnings("unchecked") + T[] biggest = (T[]) new Object[0]; + + for (T[] c : set) { + if (c.length > biggest.length) { + biggest = c; + } + } + + return biggest; + } +} diff --git a/src/main/java/de/goldmensch/commanddispatcher/ArrayUtil.java b/src/main/java/de/goldmensch/commanddispatcher/ArrayUtil.java new file mode 100644 index 00000000..0f2666b0 --- /dev/null +++ b/src/main/java/de/goldmensch/commanddispatcher/ArrayUtil.java @@ -0,0 +1,35 @@ +package de.goldmensch.commanddispatcher; + +import org.jetbrains.annotations.NotNull; + +public class ArrayUtil { + + private ArrayUtil() {} + + public static boolean startWith(@NotNull T[] r, @NotNull T[] s) { + if (s.length > r.length) return false; + for (int i = 0; i < s.length; i++) { + if (!r[i].equals(s[i])) { + return false; + } + } + return true; + } + + public static @NotNull String[] toLowerCase(@NotNull String[] a) { + var lowerArray = new String[a.length]; + for (int i = 0; i < a.length; i++) { + lowerArray[i] = a[i].toLowerCase(); + } + return lowerArray; + } + + public static @NotNull String buildString(@NotNull String[] a) { + var builder = new StringBuilder(); + for (String c : a) { + builder.append(c); + builder.append(" "); + } + return builder.toString().trim(); + } +} diff --git a/src/main/java/de/goldmensch/commanddispatcher/Commands.java b/src/main/java/de/goldmensch/commanddispatcher/Commands.java new file mode 100644 index 00000000..d90b4acb --- /dev/null +++ b/src/main/java/de/goldmensch/commanddispatcher/Commands.java @@ -0,0 +1,28 @@ +package de.goldmensch.commanddispatcher; + +import de.goldmensch.commanddispatcher.subcommand.SmartSubCommand; + +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +public final class Commands { + + private Commands() {} + + public static boolean checkExecutor(@NotNull CommandSender sender, @NotNull Executor executor) { + return executor == Executor.CONSOLE_PLAYER || executor == Executor.fromSender(sender); + } + + public static boolean checkPermission( + @NotNull CommandSender sender, @NotNull Optional posPermission) { + return posPermission.isEmpty() || sender.hasPermission(posPermission.get()); + } + + public static boolean checkPermissionAndExecutor( + @NotNull CommandSender sender, @NotNull SmartSubCommand command) { + return Commands.checkExecutor(sender, command.getExecutor()) + && Commands.checkPermission(sender, command.getPermission()); + } +} diff --git a/src/main/java/de/goldmensch/commanddispatcher/Executor.java b/src/main/java/de/goldmensch/commanddispatcher/Executor.java new file mode 100644 index 00000000..42d6227e --- /dev/null +++ b/src/main/java/de/goldmensch/commanddispatcher/Executor.java @@ -0,0 +1,32 @@ +package de.goldmensch.commanddispatcher; + +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +public enum Executor { + /*** + * The command can be executed only from the console. + */ + CONSOLE, + /*** + * The command can be executed only from a player. + */ + PLAYER, + /*** + * The command can be executed from a player or the console. + */ + CONSOLE_PLAYER; + + /*** + * Gives you the ExecutorLevel belonging to the CommandSender. + * @param sender The CommandSender + * @return The ExecutorLevel that corresponds to the CommandSender. + */ + public static Executor fromSender(@NotNull CommandSender sender) { + if (sender instanceof Player) { + return Executor.PLAYER; + } + return Executor.CONSOLE; + } +} diff --git a/src/main/java/de/goldmensch/commanddispatcher/annotations/Description.java b/src/main/java/de/goldmensch/commanddispatcher/annotations/Description.java new file mode 100644 index 00000000..203bcf3a --- /dev/null +++ b/src/main/java/de/goldmensch/commanddispatcher/annotations/Description.java @@ -0,0 +1,12 @@ +package de.goldmensch.commanddispatcher.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Description { + String value(); +} diff --git a/src/main/java/de/goldmensch/commanddispatcher/command/SmartCommand.java b/src/main/java/de/goldmensch/commanddispatcher/command/SmartCommand.java new file mode 100644 index 00000000..2223a443 --- /dev/null +++ b/src/main/java/de/goldmensch/commanddispatcher/command/SmartCommand.java @@ -0,0 +1,204 @@ +package de.goldmensch.commanddispatcher.command; + +import de.goldmensch.commanddispatcher.ArraySets; +import de.goldmensch.commanddispatcher.ArrayUtil; +import de.goldmensch.commanddispatcher.Commands; +import de.goldmensch.commanddispatcher.Executor; +import de.goldmensch.commanddispatcher.annotations.Description; +import de.goldmensch.commanddispatcher.exceptions.CommandNotValidException; +import de.goldmensch.commanddispatcher.subcommand.SmartSubCommand; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.bukkit.command.TabExecutor; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public abstract class SmartCommand implements TabExecutor { + private final HashMap subCommandMap = new HashMap<>(); + + /*** + *

Registers a subcommand, the subcommand may occur only once with the same path!

+ *

Example: 'registerSubCommand(new AboutSub(ExecutorLevel.CONSOLE_PLAYER, ""), "about");'

+ * + * @param command The subcommand to be executed + * @param args the complete path of the command including name + */ + public void registerSubCommand(@NotNull SmartSubCommand command, @NotNull String... args) { + args = ArrayUtil.toLowerCase(args); + if (isValid(args)) { + command.setName(ArrayUtil.buildString(args)); + addAnnotations(command); + subCommandMap.put(args, command); + } else { + throw new CommandNotValidException(command.getClass()); + } + } + + private void addAnnotations(@NotNull SmartSubCommand command) { + for (var ann : command.getClass().getAnnotations()) { + if (ann instanceof Description description) { + command.setDescription(description.value()); + } + } + } + + protected boolean isValid(@NotNull String[] args) { + return (args.length != 0) && (!subCommandMap.containsKey(args)); + } + + protected @NotNull Optional searchSub(@NotNull String[] args) { + var possibleSubCommand = new HashSet(); + for (var posArgs : subCommandMap.keySet()) { + if (ArrayUtil.startWith(args, posArgs)) { + possibleSubCommand.add(posArgs); + } + } + + return !possibleSubCommand.isEmpty() + ? Optional.of(getBiggest(args, possibleSubCommand)) + : Optional.empty(); + } + + private @NotNull SubCommandEntity getBiggest( + @NotNull String[] args, @NotNull Set possibleCommands) { + var matchArgs = ArraySets.getBiggest(possibleCommands); + return new SubCommandEntity( + subCommandMap.get(matchArgs), + Arrays.copyOfRange(args, matchArgs.length, args.length)); + } + + /*** + * @hidden + */ + public abstract boolean noSubFound( + @NotNull String[] args, + @NotNull CommandSender sender, + @NotNull Command command, + @NotNull String label); + + /*** + * @hidden + */ + public abstract void wrongExecutor( + @NotNull SubCommandEntity command, + @NotNull CommandSender sender, + @NotNull Executor requiredExecutor); + + /*** + * @hidden + */ + public abstract void noPermission( + @NotNull SubCommandEntity command, @NotNull CommandSender sender); + + /*** + * @hidden + */ + @Override + @ApiStatus.Internal + public boolean onCommand( + @NotNull CommandSender sender, + @NotNull Command bukkitCommand, + @NotNull String label, + @NotNull String[] args) { + var posSubCommand = searchSub(args); + if (posSubCommand.isEmpty()) { + return noSubFound(args, sender, bukkitCommand, label); + } + + var foundCommand = posSubCommand.get(); + var command = foundCommand.getCommand(); + if (!Commands.checkExecutor(sender, command.getExecutor())) { + wrongExecutor(foundCommand, sender, command.getExecutor()); + return true; + } + + if (!Commands.checkPermission(sender, command.getPermission())) { + noPermission(foundCommand, sender); + return true; + } + + return foundCommand + .getCommand() + .onCommand(sender, bukkitCommand, label, foundCommand.getArgs()); + } + + /*** + * @hidden + */ + @Override + @ApiStatus.Internal + public @Nullable List onTabComplete( + @NotNull CommandSender sender, + @NotNull Command command, + @NotNull String alias, + @NotNull String[] a) { + var completion = new ArrayList(); + + var args = ArrayUtil.toLowerCase(a); + var argPath = java.util.Arrays.copyOf(args, args.length - 1); + + for (var sub : subCommandMap.entrySet()) { + var argLength = args.length - 1; + var comArgs = sub.getKey(); + + if (!Commands.checkPermissionAndExecutor(sender, sub.getValue()) + || (comArgs.length < args.length)) continue; + var comPath = java.util.Arrays.copyOf(comArgs, argLength); + if (java.util.Arrays.equals(comPath, argPath)) { + var comArg = comArgs[argLength]; + var arg = args[argLength]; + if (comArg.startsWith(arg)) completion.add(comArg); + } + } + + var foundCommand = searchSub(args); + if (foundCommand.isPresent()) { + var subCommand = foundCommand.get().getCommand(); + if (Commands.checkPermissionAndExecutor(sender, subCommand) + && subCommand instanceof TabCompleter tabCompleter) { + var commandCompletion = + tabCompleter.onTabComplete( + sender, command, alias, foundCommand.get().getArgs()); + if (commandCompletion != null) completion.addAll(commandCompletion); + } + } + + return completion; + } + + protected @NotNull Map getSubCommandMap() { + return Collections.unmodifiableMap(subCommandMap); + } + + public static final class SubCommandEntity { + private final SmartSubCommand command; + private final String[] args; + + private SubCommandEntity(@NotNull SmartSubCommand command, @NotNull String[] args) { + this.command = command; + this.args = args; + } + + /*** + *

The new arguments are the arguments without the path and the name of the command.

+ *

Example: subcommand path : "arg0 name"

+ *

path: "arg0 name value" becomes Arguments: "value"

+ * @return The new Arguments of the command. + */ + public @NotNull String[] getArgs() { + return args; + } + + /*** + * @return The SubCommand + */ + public @NotNull SmartSubCommand getCommand() { + return command; + } + } +} diff --git a/src/main/java/de/goldmensch/commanddispatcher/exceptions/CommandNotValidException.java b/src/main/java/de/goldmensch/commanddispatcher/exceptions/CommandNotValidException.java new file mode 100644 index 00000000..c651ebe8 --- /dev/null +++ b/src/main/java/de/goldmensch/commanddispatcher/exceptions/CommandNotValidException.java @@ -0,0 +1,14 @@ +package de.goldmensch.commanddispatcher.exceptions; + +import de.goldmensch.commanddispatcher.subcommand.SmartSubCommand; + +import org.jetbrains.annotations.NotNull; + +public class CommandNotValidException extends RuntimeException { + + @java.io.Serial private static final long serialVersionUID = 17441953375440988L; + + public CommandNotValidException(@NotNull Class sub) { + super("Command not valid, class: " + sub.getName()); + } +} diff --git a/src/main/java/de/goldmensch/commanddispatcher/subcommand/SmartSubCommand.java b/src/main/java/de/goldmensch/commanddispatcher/subcommand/SmartSubCommand.java new file mode 100644 index 00000000..601990d7 --- /dev/null +++ b/src/main/java/de/goldmensch/commanddispatcher/subcommand/SmartSubCommand.java @@ -0,0 +1,60 @@ +package de.goldmensch.commanddispatcher.subcommand; + +import de.goldmensch.commanddispatcher.Executor; + +import org.bukkit.command.CommandExecutor; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; + +public abstract class SmartSubCommand implements CommandExecutor { + + private final Executor executor; + private final String permission; + private String name; + private String description; + + public SmartSubCommand(@NotNull Executor executor, @Nullable String permission) { + this.executor = executor; + this.permission = permission; + } + + @ApiStatus.Internal + public void setName(@NotNull String name) { + this.name = name; + } + + @ApiStatus.Internal + public void setDescription(@NotNull String description) { + this.description = description; + } + + /*** + * @return The {@link Executor} of the SubCommand + */ + public @NotNull Executor getExecutor() { + return executor; + } + + /*** + * @return The permission of the SubCommand + */ + public @NotNull Optional getPermission() { + return permission.isEmpty() ? Optional.empty() : Optional.of(permission); + } + + /*** + *

The name is a string consisting of the path and the name.

+ *

Example: The StringArray {"arg0", "arg1", "name"} becomes "arg0 arg1 name"

+ * @return The name of the SubCommand + */ + public @NotNull String getName() { + return name; + } + + public @NotNull Optional getDescription() { + return Optional.ofNullable(description); + } +} diff --git a/unbuilt_readme.md b/unbuilt_readme.md index 725d54b1..4c9b09dd 100644 --- a/unbuilt_readme.md +++ b/unbuilt_readme.md @@ -10,14 +10,16 @@ Info ---- -Spigot plugin for 1.17+ allowing the claiming of chunks. +Spigot plugin for 1.20+ allowing the claiming of chunks. *The destiny of chunks is to unite not to divide*
*Let's make the world ours.* Usage and more information can be found [on the wiki](https://github.com/cjburkey01/ClaimChunk/wiki). -* **1.17 - @LATEST_MC_VERSION@** | The latest version works seamlessly. +* **1.20-@LATEST_MC_VERSION@+** | The latest version works seamlessly (excluding bugs, of course). +* **1.17 - 1.20** | The latest *known* working version is [0.0.23-RC8](https://github.com/cjburkey01/ClaimChunk/releases/tag/0.0.23-RC8). + * Newer versions of the plugin will require Java 17, but may still work. * **1.13 - 1.16.5** | The latest working version is [0.0.22](https://github.com/cjburkey01/ClaimChunk/releases/tag/0.0.22). * Note: If you disable titles, 0.0.23 might work on version older than 1.17. If you experience issues, however, they may be more difficult to address. * **Pre-1.13** | The latest working version is [0.0.20](https://github.com/cjburkey01/ClaimChunk/releases/tag/0.0.20)