From 240b83f43f790b586cc71c312ce8e069053d46d8 Mon Sep 17 00:00:00 2001 From: okx-code Date: Thu, 18 Mar 2021 04:42:59 +0000 Subject: [PATCH] Pebble text processing engine --- build.gradle | 48 ++- gradle/wrapper/gradle-wrapper.jar | Bin 55616 -> 58694 bytes gradle/wrapper/gradle-wrapper.properties | 11 +- gradlew | 371 +++++++++--------- gradlew.bat | 5 +- src/main/java/sh/okx/rankup/RankupHelper.java | 60 +-- src/main/java/sh/okx/rankup/RankupPlugin.java | 110 +----- .../okx/rankup/commands/PrestigeCommand.java | 7 +- .../okx/rankup/commands/PrestigesCommand.java | 11 +- .../sh/okx/rankup/commands/RankupCommand.java | 6 +- src/main/java/sh/okx/rankup/gui/Gui.java | 47 ++- .../okx/rankup/messages/MessageBuilder.java | 152 +------ .../rankup/messages/NullMessageBuilder.java | 27 +- .../rankup/messages/StringMessageBuilder.java | 119 ++++++ .../messages/pebble/PebbleMessageBuilder.java | 141 +++++++ .../messages/pebble/PrestigeContext.java | 24 ++ .../rankup/messages/pebble/RankContext.java | 78 ++++ .../messages/pebble/RequirementContext.java | 43 ++ .../rankup/placeholders/RankupExpansion.java | 6 +- .../java/sh/okx/rankup/prestige/Prestige.java | 9 +- src/main/java/sh/okx/rankup/ranks/Rank.java | 10 +- .../java/sh/okx/rankup/ranks/RankList.java | 16 +- .../requirement/mcmmo/McMMOSkillUtil.java | 3 +- .../okx/rankup/text/ChainedTextProcessor.java | 17 + .../okx/rankup/text/ColourTextProcessor.java | 11 + .../okx/rankup/text/LegacyTextProcessor.java | 107 +++++ .../text/PlaceholderApiTextProcessor.java | 23 ++ .../sh/okx/rankup/text/TextProcessor.java | 5 + .../okx/rankup/text/TextProcessorBuilder.java | 38 ++ .../text/pebble/DecimalFormatFilter.java | 38 ++ .../okx/rankup/text/pebble/PebbleOptions.java | 11 + .../text/pebble/PebbleTextProcessor.java | 54 +++ src/main/resources/config.yml | 2 +- src/main/resources/locale/en.yml | 63 ++- src/main/resources/locale/es.yml | 56 ++- src/main/resources/locale/fr.yml | 56 +-- src/main/resources/locale/it.yml | 62 ++- src/main/resources/locale/pt_br.yml | 56 +-- src/main/resources/locale/ru.yml | 56 +-- src/main/resources/locale/zh_cn.yml | 62 ++- src/main/resources/rankups.yml | 8 +- .../java/sh/okx/rankup/RankupBasicsTest.java | 67 ++++ src/test/java/sh/okx/rankup/RankupTest.java | 118 +++--- .../rankup/legacy/LegacyPlaceholderTest.java | 24 ++ .../rankup/messages/MessageBuilderTest.java | 2 +- .../messages/RankupPlaceholderTest.java | 24 ++ .../java/sh/okx/rankup/pebble/PebbleTest.java | 26 ++ src/test/resources/legacy/rankups.yml | 10 + 48 files changed, 1508 insertions(+), 792 deletions(-) create mode 100644 src/main/java/sh/okx/rankup/messages/StringMessageBuilder.java create mode 100644 src/main/java/sh/okx/rankup/messages/pebble/PebbleMessageBuilder.java create mode 100644 src/main/java/sh/okx/rankup/messages/pebble/PrestigeContext.java create mode 100644 src/main/java/sh/okx/rankup/messages/pebble/RankContext.java create mode 100644 src/main/java/sh/okx/rankup/messages/pebble/RequirementContext.java create mode 100644 src/main/java/sh/okx/rankup/text/ChainedTextProcessor.java create mode 100644 src/main/java/sh/okx/rankup/text/ColourTextProcessor.java create mode 100644 src/main/java/sh/okx/rankup/text/LegacyTextProcessor.java create mode 100644 src/main/java/sh/okx/rankup/text/PlaceholderApiTextProcessor.java create mode 100644 src/main/java/sh/okx/rankup/text/TextProcessor.java create mode 100644 src/main/java/sh/okx/rankup/text/TextProcessorBuilder.java create mode 100644 src/main/java/sh/okx/rankup/text/pebble/DecimalFormatFilter.java create mode 100644 src/main/java/sh/okx/rankup/text/pebble/PebbleOptions.java create mode 100644 src/main/java/sh/okx/rankup/text/pebble/PebbleTextProcessor.java create mode 100644 src/test/java/sh/okx/rankup/RankupBasicsTest.java create mode 100644 src/test/java/sh/okx/rankup/legacy/LegacyPlaceholderTest.java create mode 100644 src/test/java/sh/okx/rankup/messages/RankupPlaceholderTest.java create mode 100644 src/test/java/sh/okx/rankup/pebble/PebbleTest.java create mode 100644 src/test/resources/legacy/rankups.yml diff --git a/build.gradle b/build.gradle index 173c78a..fe34596 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,16 @@ plugins { id 'java' + id 'com.github.johnrengelman.shadow' version '6.1.0' id "io.freefair.lombok" version "5.1.0" } group 'sh.okx' -version '3.11.4-beta' +version '3.12' + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} repositories { mavenCentral() @@ -24,27 +30,42 @@ repositories { dependencies { testImplementation group: 'junit', name: 'junit', version: '4.12' - testImplementation 'com.github.seeseemelk:MockBukkit-v1.15:0.3.0' + testImplementation 'com.github.seeseemelk:MockBukkit-v1.16:1.0.0' + testImplementation group: 'org.slf4j', name: 'slf4j-nop', version: '1.7.30' + testImplementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.30' compileOnly 'org.jetbrains:annotations:16.0.2' - implementation 'org.spigotmc:spigot-api:1.16.4-R0.1-SNAPSHOT' - implementation('com.github.Realizedd:TokenManager:3.2.4') { + compileOnly 'org.spigotmc:spigot-api:1.17-R0.1-SNAPSHOT' + compileOnly('com.github.Realizedd:TokenManager:3.2.4') { transitive = false } - implementation('com.github.MilkBowl:VaultAPI:1.7') { + compileOnly('com.github.MilkBowl:VaultAPI:1.7') { exclude group: 'org.bukkit' } - implementation ('me.clip:placeholderapi:2.10.9') { + compileOnly ('me.clip:placeholderapi:2.10.9') { exclude group: 'org.bstats' } - implementation 'com.github.pyvesb:advanced-achievements:6.7.2' - implementation 'com.github.astei:Superbvote:700fca43659b438cb9bb36c218a7646d2f2ef315' - implementation('com.github.mcMMO-Dev:mcMMO:601297') { + compileOnly 'com.github.pyvesb:advanced-achievements:6.7.2' + compileOnly 'com.github.astei:Superbvote:700fca43659b438cb9bb36c218a7646d2f2ef315' + compileOnly('com.github.mcMMO-Dev:mcMMO:601297') { exclude group: 'com.sk89q.worldguard' } - implementation 'com.github.BenCodez:VotingPlugin:6.0' - implementation 'com.github.LlmDl:Towny:25fc18a' + compileOnly 'com.github.BenCodez:VotingPlugin:6.0' + compileOnly 'com.github.LlmDl:Towny:25fc18a' + + implementation ('io.pebbletemplates:pebble:3.1.5') { + exclude group: 'org.slf4j' + } +} + +artifacts { + archives shadowJar +} + +shadowJar { + archiveClassifier.set('') + minimize() } // automatically copy the version to plugin.yml @@ -64,8 +85,9 @@ processResources { } } -task spigot(type: Jar) { +task spigot(type: com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) { from sourceSets.main.runtimeClasspath destinationDirectory = file("./spigot/plugins/") archiveFileName = "Rankup.jar" -} \ No newline at end of file +} + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf016b3885f6930543d57b744ea8c220a1a..490fda8577df6c95960ba7077c43220e5bb2c0d9 100644 GIT binary patch delta 22806 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 Z!$zsDgoO%|(a?lZ=Qe6kxv{^r{{!uYR<-~D delta 19980 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 Z5H)%VJiSWIAOn$sbP+#;Q{V4m{s)AkOUM8K diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4c9e569..442d913 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Sun Apr 05 14:05:23 BST 2020 -distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStorePath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index cba68cc..2fe81a7 100755 --- a/gradlew +++ b/gradlew @@ -1,188 +1,183 @@ -#!/usr/bin/env sh - -# -# Copyright 2015 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -## -## Gradle start up script for 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='"-Xmx64m" "-Xms64m"' - -# 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" "$@" +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for 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='"-Xmx64m" "-Xms64m"' + +# 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 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 + # 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=`expr $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" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 15e1ee3..9109989 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -5,7 +5,7 @@ @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem -@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" diff --git a/src/main/java/sh/okx/rankup/RankupHelper.java b/src/main/java/sh/okx/rankup/RankupHelper.java index c6800e0..28b5eb9 100644 --- a/src/main/java/sh/okx/rankup/RankupHelper.java +++ b/src/main/java/sh/okx/rankup/RankupHelper.java @@ -51,11 +51,15 @@ public class RankupHelper { public void sendRankupMessages(Player player, RankElement rank) { plugin.getMessage(rank.getRank(), Message.SUCCESS_PUBLIC) .failIfEmpty() - .replaceRanks(player, rank.getRank(), rank.getNext().getRank()) + .replacePlayer(player) + .replaceOldRank(rank.getRank()) + .replaceRank(rank.getNext().getRank()) .broadcast(); plugin.getMessage(rank.getRank(), Message.SUCCESS_PRIVATE) .failIfEmpty() - .replaceRanks(player, rank.getRank(), rank.getNext().getRank()) + .replacePlayer(player) + .replaceOldRank(rank.getRank()) + .replaceRank(rank.getNext().getRank()) .send(player); } @@ -81,13 +85,15 @@ public class RankupHelper { plugin.getMessage(prestige.getRank(), Message.PRESTIGE_SUCCESS_PUBLIC) .failIfEmpty() - .replaceRanks(player, prestige.getRank(), prestige.getNext().getRank()) - .replaceFromTo(prestige.getRank()) + .replacePlayer(player) + .replaceOldRank(prestige.getRank()) + .replaceRank(prestige.getNext().getRank()) .broadcast(); plugin.getMessage(prestige.getRank(), Message.PRESTIGE_SUCCESS_PRIVATE) .failIfEmpty() - .replaceRanks(player, prestige.getRank(), prestige.getNext().getRank()) - .replaceFromTo(prestige.getRank()) + .replacePlayer(player) + .replaceOldRank(prestige.getRank()) + .replaceRank(prestige.getNext().getRank()) .send(player); } @@ -102,10 +108,10 @@ public class RankupHelper { plugin .getMessage(rank, secondsLeft > 1 ? Message.COOLDOWN_PLURAL : Message.COOLDOWN_SINGULAR) .failIfEmpty() - .replaceRanks(player, rank) - .replaceFromTo(rank) - .replace(Variable.SECONDS, cooldownSeconds) - .replace(Variable.SECONDS_LEFT, secondsLeft) + .replacePlayer(player) + .replaceRank(rank) + .replaceKey(Variable.SECONDS.toString(), cooldownSeconds) + .replaceKey(Variable.SECONDS_LEFT.toString(), secondsLeft) .send(player); return true; } @@ -151,22 +157,27 @@ public class RankupHelper { if (rankElement == null) { // check if in ladder plugin.getMessage(Message.NOT_IN_LADDER) .failIf(!message) - .replace(Variable.PLAYER, player.getName()) + .replacePlayer(player) .send(player); return false; } Rank rank = rankElement.getRank(); if (!rankElement.hasNext()) { Prestiges prestiges = plugin.getPrestiges(); - plugin.getMessage(prestiges == null || !prestiges.getByPlayer(player).hasNext() ? Message.NO_RANKUP : Message.MUST_PRESTIGE) + plugin.getMessage( + prestiges == null || !prestiges.getByPlayer(player).hasNext() ? Message.NO_RANKUP + : Message.MUST_PRESTIGE) .failIf(!message) - .replaceRanks(player, rankups.getTree().last().getRank()) + .replacePlayer(player) + .replaceRank(rankups.getTree().last().getRank()) .send(player); return false; } else if (!rank.hasRequirements(player)) { // check if they can afford it if (message) { - plugin.replaceMoneyRequirements(plugin.getMessage(rank, Message.REQUIREMENTS_NOT_MET) - .replaceRanks(player, rank, rankElement.getNext().getRank()), player, rank) + plugin.getMessage(rank, Message.REQUIREMENTS_NOT_MET) + .replacePlayer(player) + .replaceOldRank(rank) + .replaceRank(rankElement.getNext().getRank()) .send(player); } return false; @@ -198,25 +209,26 @@ public class RankupHelper { public boolean checkPrestige(Player player, boolean message) { Prestiges prestiges = plugin.getPrestiges(); RankElement prestigeElement = prestiges.getByPlayer(player); - if (prestigeElement == null || !prestigeElement.getRank().isEligible(player)) { // check if in ladder + if (prestigeElement == null || !prestigeElement.getRank() + .isEligible(player)) { // check if in ladder plugin.getMessage(Message.NOT_HIGH_ENOUGH) .failIf(!message) - .replace(Variable.PLAYER, player.getName()) + .replacePlayer(player) .send(player); return false; } else if (!prestigeElement.hasNext()) { // check if they are at the highest rank plugin.getMessage(prestigeElement.getRank(), Message.PRESTIGE_NO_PRESTIGE) .failIf(!message) - .replaceRanks(player, prestigeElement.getRank()) - .replaceFromTo(prestigeElement.getRank()) + .replacePlayer(player) + .replaceRank(prestigeElement.getRank()) .send(player); return false; } else if (!prestigeElement.getRank().hasRequirements(player)) { // check if they can afford it - plugin.replaceMoneyRequirements( - plugin.getMessage(prestigeElement.getRank(), Message.PRESTIGE_REQUIREMENTS_NOT_MET) - .failIf(!message) - .replaceRanks(player, prestigeElement.getRank(), prestigeElement.getNext().getRank()), player, prestigeElement.getRank()) - .replaceFromTo(prestigeElement.getRank()) + plugin.getMessage(prestigeElement.getRank(), Message.PRESTIGE_REQUIREMENTS_NOT_MET) + .failIf(!message) + .replacePlayer(player) + .replaceOldRank(prestigeElement.getRank()) + .replaceRank(prestigeElement.getNext().getRank()) .send(player); return false; } else if (checkCooldown(player, prestigeElement.getRank())) { diff --git a/src/main/java/sh/okx/rankup/RankupPlugin.java b/src/main/java/sh/okx/rankup/RankupPlugin.java index f7a042f..46f2f62 100644 --- a/src/main/java/sh/okx/rankup/RankupPlugin.java +++ b/src/main/java/sh/okx/rankup/RankupPlugin.java @@ -1,11 +1,9 @@ package sh.okx.rankup; import java.io.File; -import java.text.DecimalFormat; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.Supplier; import lombok.Getter; import org.bukkit.Bukkit; import org.bukkit.ChatColor; @@ -37,8 +35,7 @@ import sh.okx.rankup.hook.PermissionManager; import sh.okx.rankup.hook.VaultPermissionManager; import sh.okx.rankup.messages.Message; import sh.okx.rankup.messages.MessageBuilder; -import sh.okx.rankup.messages.NullMessageBuilder; -import sh.okx.rankup.messages.Variable; +import sh.okx.rankup.messages.pebble.PebbleMessageBuilder; import sh.okx.rankup.placeholders.Placeholders; import sh.okx.rankup.prestige.Prestige; import sh.okx.rankup.prestige.Prestiges; @@ -88,7 +85,7 @@ import sh.okx.rankup.util.VersionChecker; public class RankupPlugin extends JavaPlugin { - public static final int CONFIG_VERSION = 8; + public static final int CONFIG_VERSION = 10; @Getter private GroupProvider permissions; @@ -132,7 +129,7 @@ public class RankupPlugin extends JavaPlugin { reload(true); - if (System.getProperty("TEST") == null) { + if (System.getProperty("RANKUP_TEST") == null) { Metrics metrics = new Metrics(this); metrics.addCustomChart(new Metrics.SimplePie("confirmation", () -> config.getString("confirmation-type", "unknown"))); @@ -235,6 +232,10 @@ public class RankupPlugin extends JavaPlugin { helper = new RankupHelper(this); } + public MessageBuilder newMessageBuilder(String message) { + return new PebbleMessageBuilder(this, message); + } + public boolean error() { return error(null); } @@ -408,7 +409,7 @@ public class RankupPlugin extends JavaPlugin { new TokensRequirement(this, "tokenmanager-tokensh"), new TokensDeductibleRequirement(this, "tokenmanager-tokens")); } - if (Bukkit.getPluginManager().isPluginEnabled("SuperbVotes")) { + if (Bukkit.getPluginManager().isPluginEnabled("SuperbVote")) { requirements.addRequirements(new SuperbVoteVotesRequirement(this)); } } @@ -445,94 +446,25 @@ public class RankupPlugin extends JavaPlugin { if (messages == null || !messages.isSet(message.getName())) { messages = this.messages; } - return MessageBuilder.of(messages, message); + return newMessageBuilder(messages.getString(message.getName())); } public MessageBuilder getMessage(Message message) { - return MessageBuilder.of(messages, message); + return newMessageBuilder(messages.getString(message.getName())); } - public MessageBuilder replaceMoneyRequirements(MessageBuilder builder, CommandSender sender, - Rank rank) { - if (builder instanceof NullMessageBuilder || rank == null) { - return builder; - } - - Requirement money = rank.getRequirement(sender instanceof Player ? (Player) sender : null, "money"); - if (money != null) { - Double amount = null; - Double total = null; - if (sender instanceof Player && rank.isIn((Player) sender)) { - if (economy != null) { - amount = money.getRemaining((Player) sender); - total = money.getTotal((Player) sender); - } - } else { - amount = money.getValueDouble(); - total = 0D; - } - if (amount != null && economy != null) { - builder.replace(Variable.MONEY_NEEDED, formatMoney(amount)); - builder.replace(Variable.MONEY, formatMoney(money.getValueDouble())); - builder.replace(Variable.MONEY_DONE, formatMoney(total - amount)); - } - } - if (sender instanceof Player) { - replaceRequirements(builder, (Player) sender, rank); - } - return builder; - } - - public MessageBuilder replaceRequirements(MessageBuilder builder, Player player, Rank rank) { - DecimalFormat simpleFormat = placeholders.getSimpleFormat(); - DecimalFormat percentFormat = placeholders.getPercentFormat(); - for (Requirement requirement : rank.getRequirements().getRequirements(player)) { - try { - replaceRequirements(builder, Variable.AMOUNT, requirement, - () -> simpleFormat.format(requirement.getTotal(player))); - if (rank.isIn(player)) { - replaceRequirements(builder, Variable.AMOUNT_NEEDED, requirement, - () -> simpleFormat.format(requirement.getRemaining(player))); - replaceRequirements(builder, Variable.PERCENT_LEFT, requirement, - () -> percentFormat.format(Math.max(0, - (requirement.getRemaining(player) / requirement.getTotal(player)) * 100))); - replaceRequirements(builder, Variable.PERCENT_DONE, requirement, - () -> percentFormat.format(Math.min(100, - (1 - (requirement.getRemaining(player) / requirement.getTotal(player))) * 100))); - replaceRequirements(builder, Variable.AMOUNT_DONE, requirement, - () -> simpleFormat - .format(requirement.getTotal(player) - requirement.getRemaining(player))); - } - } catch (NumberFormatException ignored) { - } - } - return builder; - } - - private void replaceRequirements(MessageBuilder builder, Variable variable, Requirement requirement, Supplier value) { - try { - builder.replace(variable + " " + requirement.getFullName(), value.get()); - } catch (Exception e) { - e.printStackTrace(); - } - } - - public MessageBuilder getMessage(CommandSender player, Message message, Rank oldRank, Rank rankName) { - String oldRankName; - String oldRankDisplayName; + public MessageBuilder getMessage(CommandSender player, Message message, Rank oldRank, Rank rank) { + Rank actualOldRank; if (oldRank instanceof Prestige && oldRank.getRank() == null) { - oldRankName = ((Prestige) oldRank).getFrom(); - oldRankDisplayName = rankups.getTree().last().getRank().getDisplayName(); + actualOldRank = rankups.getByName(((Prestige) oldRank).getFrom()).getRank(); } else { - oldRankName = oldRank.getRank(); - oldRankDisplayName = oldRank.getDisplayName(); + actualOldRank = oldRank; } - return replaceMoneyRequirements(getMessage(oldRank, message) - .replaceRanks(player, rankName) - .replace(Variable.OLD_RANK, oldRankName) - .replace(Variable.OLD_RANK_NAME, oldRankDisplayName), player, oldRank) - .replaceFromTo(oldRank); + return getMessage(oldRank, message) + .replacePlayer(player) + .replaceRank(rank) + .replaceOldRank(actualOldRank); } public void sendHeaderFooter(CommandSender sender, Rank rank, Message type) { @@ -540,12 +472,12 @@ public class RankupPlugin extends JavaPlugin { if (rank == null) { builder = getMessage(type) .failIfEmpty() - .replace(Variable.PLAYER, sender.getName()); + .replacePlayer(sender); } else { builder = getMessage(rank, type) .failIfEmpty() - .replaceRanks(sender, rank) - .replaceFromTo(rank); + .replacePlayer(sender) + .replaceRank(rank); } builder.send(sender); } diff --git a/src/main/java/sh/okx/rankup/commands/PrestigeCommand.java b/src/main/java/sh/okx/rankup/commands/PrestigeCommand.java index 375ca03..8155561 100644 --- a/src/main/java/sh/okx/rankup/commands/PrestigeCommand.java +++ b/src/main/java/sh/okx/rankup/commands/PrestigeCommand.java @@ -58,9 +58,10 @@ public class PrestigeCommand implements CommandExecutor { Prestige next = rankElement.getNext().getRank(); Rank nextRank = next == null ? prestiges.getTree().last().getRank() : next; - plugin.replaceMoneyRequirements(plugin.getMessage(prestige, Message.PRESTIGE_CONFIRMATION) - .replaceRanks(player, prestige, nextRank), player, prestige) - .replaceFromTo(prestige) + plugin.getMessage(prestige, Message.PRESTIGE_CONFIRMATION) + .replacePlayer(player) + .replaceOldRank(prestige) + .replaceRank(nextRank) .send(player); break; case "gui": diff --git a/src/main/java/sh/okx/rankup/commands/PrestigesCommand.java b/src/main/java/sh/okx/rankup/commands/PrestigesCommand.java index 210bf64..39b359a 100644 --- a/src/main/java/sh/okx/rankup/commands/PrestigesCommand.java +++ b/src/main/java/sh/okx/rankup/commands/PrestigesCommand.java @@ -7,6 +7,8 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import sh.okx.rankup.RankupPlugin; import sh.okx.rankup.messages.Message; +import sh.okx.rankup.messages.MessageBuilder; +import sh.okx.rankup.messages.Variable; import sh.okx.rankup.prestige.Prestige; import sh.okx.rankup.prestige.Prestiges; import sh.okx.rankup.ranks.RankElement; @@ -38,9 +40,12 @@ public class PrestigesCommand implements CommandExecutor { .send(sender); message = Message.PRESTIGES_INCOMPLETE; } else { - plugin.getMessage(sender, message, prestige.getRank(), next.getRank()) - .replaceFirstPrestige(prestige.getRank(), prestiges, prestige.getRank().getFrom()) - .send(sender); + MessageBuilder builder = plugin + .getMessage(sender, message, prestige.getRank(), next.getRank()); + if (prestiges.getFirst().equals(prestige.getRank())) { + builder.replaceKey(Variable.OLD_RANK.toString(), prestige.getRank().getFrom()); + } + builder.send(sender); } prestige = next; } diff --git a/src/main/java/sh/okx/rankup/commands/RankupCommand.java b/src/main/java/sh/okx/rankup/commands/RankupCommand.java index 18fbe37..47f9c71 100644 --- a/src/main/java/sh/okx/rankup/commands/RankupCommand.java +++ b/src/main/java/sh/okx/rankup/commands/RankupCommand.java @@ -58,8 +58,10 @@ public class RankupCommand implements CommandExecutor { switch (confirmationType) { case "text": confirming.put(player, System.currentTimeMillis()); - plugin.replaceMoneyRequirements(plugin.getMessage(rankElement.getRank(), Message.CONFIRMATION) - .replaceRanks(player, rankElement.getRank(), rankElement.getNext().getRank()), player, rankElement.getRank()) + plugin.getMessage(rankElement.getRank(), Message.CONFIRMATION) + .replacePlayer(player) + .replaceOldRank(rankElement.getRank()) + .replaceRank(rankElement.getNext().getRank()) .send(player); break; case "gui": diff --git a/src/main/java/sh/okx/rankup/gui/Gui.java b/src/main/java/sh/okx/rankup/gui/Gui.java index c77fb5c..c0bbebe 100644 --- a/src/main/java/sh/okx/rankup/gui/Gui.java +++ b/src/main/java/sh/okx/rankup/gui/Gui.java @@ -26,6 +26,7 @@ import sh.okx.rankup.util.ItemUtil; @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public class Gui implements InventoryHolder { + @Getter private final boolean returnToRanksGui; @Getter @@ -37,7 +38,8 @@ public class Gui implements InventoryHolder { @Getter private boolean prestige; - public static Gui of(Player player, Rank oldRank, Rank rank, RankupPlugin plugin, boolean returnToRanksGui) { + public static Gui of(Player player, Rank oldRank, Rank rank, RankupPlugin plugin, + boolean returnToRanksGui) { Gui gui = new Gui(returnToRanksGui); gui.prestige = oldRank instanceof Prestige; @@ -45,15 +47,19 @@ public class Gui implements InventoryHolder { String basePath = type + ".gui"; ConfigurationSection config = plugin.getSection(oldRank, basePath); if (config == null) { - plugin.getLogger().severe("You must update your config.yml and locale/en.yml to be able to use the GUI! Your configuration files are outdated."); + plugin.getLogger().severe( + "You must update your config.yml and locale/en.yml to be able to use the GUI! Your configuration files are outdated."); return null; } ItemStack[] items = new ItemStack[config.getInt("rows", 1) * 9]; - ItemStack fill = getItem(plugin, plugin.getSection(oldRank, basePath + ".fill"), player, oldRank, rank); - ItemStack cancel = getItem(plugin, plugin.getSection(oldRank, basePath + ".cancel"), player, oldRank, rank); - ItemStack rankup = getItem(plugin, plugin.getSection(oldRank, basePath + ".rankup"), player, oldRank, rank); + ItemStack fill = getItem(plugin, plugin.getSection(oldRank, basePath + ".fill"), player, + oldRank, rank); + ItemStack cancel = getItem(plugin, plugin.getSection(oldRank, basePath + ".cancel"), player, + oldRank, rank); + ItemStack rankup = getItem(plugin, plugin.getSection(oldRank, basePath + ".rankup"), player, + oldRank, rank); addItem(items, plugin.getSection(oldRank, basePath + ".rankup"), rankup); addItem(items, plugin.getSection(oldRank, basePath + ".cancel"), cancel); @@ -63,26 +69,30 @@ public class Gui implements InventoryHolder { gui.cancel = cancel; Inventory inventory = Bukkit.createInventory(gui, items.length, - Colour.translate(plugin.replaceMoneyRequirements( + Colour.translate( plugin.getMessage(oldRank, gui.prestige ? Message.PRESTIGE_TITLE : Message.TITLE) - .replaceRanks(player, oldRank, rank) - .replaceFromTo(oldRank), player, oldRank).toString())); + .replacePlayer(player) + .replaceOldRank(oldRank) + .replaceRank(rank).toString(player))); inventory.setContents(items); gui.inventory = inventory; return gui; } - public static ItemStack getItem(RankupPlugin plugin, ConfigurationSection section, Player player, RankElement element) { + public static ItemStack getItem(RankupPlugin plugin, ConfigurationSection section, Player player, + RankElement element) { if (element == null) { return getItem(plugin, section, player, null, null); } else { RankElement next = element.getNext(); - return getItem(plugin, section, player, element.getRank(), (next == null ? element : next).getRank()); + return getItem(plugin, section, player, element.getRank(), + (next == null ? element : next).getRank()); } } @SuppressWarnings("deprecation") - public static ItemStack getItem(RankupPlugin plugin, ConfigurationSection section, Player player, Rank oldRank, Rank rank) { + public static ItemStack getItem(RankupPlugin plugin, ConfigurationSection section, Player player, + Rank oldRank, Rank rank) { if (section == null) { return null; } @@ -111,23 +121,26 @@ public class Gui implements InventoryHolder { ItemMeta meta = item.getItemMeta(); if (section.contains("lore")) { - meta.setLore(Arrays.stream(format(plugin, section.getString("lore"), player, oldRank, rank).split("\n")) + meta.setLore(Arrays + .stream(format(plugin, section.getString("lore"), player, oldRank, rank).split("\n")) .map(string -> ChatColor.RESET + string) .collect(Collectors.toList())); } if (section.contains("name")) { - meta.setDisplayName(ChatColor.RESET + format(plugin, section.getString("name"), player, oldRank, rank)); + meta.setDisplayName( + ChatColor.RESET + format(plugin, section.getString("name"), player, oldRank, rank)); } item.setItemMeta(meta); return item; } - private static String format(RankupPlugin plugin, String message, Player player, Rank oldRank, Rank rank) { - MessageBuilder builder = new MessageBuilder(message); + private static String format(RankupPlugin plugin, String message, Player player, Rank oldRank, + Rank rank) { + MessageBuilder builder = plugin.newMessageBuilder(message); if (oldRank != null && rank != null) { - builder = builder.replaceRanks(player, oldRank, rank); + builder = builder.replacePlayer(player).replaceOldRank(oldRank).replaceRank(rank); } - return Colour.translate(plugin.replaceMoneyRequirements(builder, player, oldRank).toString()); + return builder.toString(player); } private static void addItem(ItemStack[] items, ConfigurationSection section, ItemStack item) { diff --git a/src/main/java/sh/okx/rankup/messages/MessageBuilder.java b/src/main/java/sh/okx/rankup/messages/MessageBuilder.java index 1841fd2..859627e 100644 --- a/src/main/java/sh/okx/rankup/messages/MessageBuilder.java +++ b/src/main/java/sh/okx/rankup/messages/MessageBuilder.java @@ -1,156 +1,30 @@ package sh.okx.rankup.messages; -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import me.clip.placeholderapi.PlaceholderAPI; -import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; -import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; -import sh.okx.rankup.prestige.Prestige; -import sh.okx.rankup.prestige.Prestiges; import sh.okx.rankup.ranks.Rank; -import sh.okx.rankup.util.Colour; -public class MessageBuilder { - private String message; +public interface MessageBuilder { + MessageBuilder replaceKey(String key, Object value); + MessageBuilder replacePlayer(CommandSender sender); + MessageBuilder replaceRank(Rank rank); + MessageBuilder replaceOldRank(Rank rank); - public MessageBuilder(String message) { - this.message = message; - } + void send(CommandSender sender); + void broadcast(); - public static MessageBuilder of(ConfigurationSection config, Message message) { - return MessageBuilder.of(config, message.getName()); - } + String toString(); - private static MessageBuilder of(ConfigurationSection config, String message) { - String string = config.getString(message); - Objects.requireNonNull(string, "Configuration message '" + message + "' not found!"); - return new MessageBuilder(Colour.translate(string)); - } - - public MessageBuilder replace(Variable variable, Object value) { - return replace(variable.name(), value); - } - - public MessageBuilder replace(String name, Object value) { - if (value == null) { - return this; - } - Pattern pattern = Pattern.compile("\\{" + name + "}", Pattern.CASE_INSENSITIVE); - Matcher matcher = pattern.matcher(message); - this.message = matcher.replaceAll(String.valueOf(value)); - return this; - } - - public MessageBuilder replaceFirstPrestige(Rank rank, Prestiges prestiges, String with) { - if (prestiges != null && prestiges.getFirst().equals(rank)) { - replace(Variable.OLD_RANK, with); - } - return this; - } - - @Deprecated - public MessageBuilder replaceRanks(CommandSender player, String rankName) { - replace(Variable.PLAYER, player.getName()); - replaceRanks(rankName); - return this; - } - - @Deprecated - public MessageBuilder replaceRanks(CommandSender player, Rank oldRank, String rankName) { - replace(Variable.PLAYER, player.getName()); - replaceRanks(oldRank, rankName); - return this; - } - - public MessageBuilder replaceRanks(CommandSender player, Rank rank) { - replace(Variable.PLAYER, player.getName()); - replaceRanks(rank); - return this; - } - - public MessageBuilder replaceRanks(CommandSender player, Rank oldRank, Rank rank) { - replace(Variable.PLAYER, player.getName()); - replaceRanks(oldRank, rank); - return this; - } - - @Deprecated - public MessageBuilder replaceRanks(String rankName) { - replace(Variable.RANK, rankName); - return this; - } - - public MessageBuilder replaceRanks(Rank rank) { - replace(Variable.RANK, rank.getRank()); - replace(Variable.RANK_NAME, rank.getDisplayName()); - return this; - } - - @Deprecated - public MessageBuilder replaceRanks(Rank oldRank, String rankName) { - replaceRanks(rankName); - replace(Variable.OLD_RANK, oldRank.getRank()); - replace(Variable.OLD_RANK_NAME, oldRank.getDisplayName()); - return this; - } - - public MessageBuilder replaceRanks(Rank oldRank, Rank rank) { - replaceRanks(rank); - replace(Variable.OLD_RANK, oldRank.getRank()); - replace(Variable.OLD_RANK_NAME, oldRank.getDisplayName()); - return this; - } - - public MessageBuilder replaceFromTo(Rank rank) { - if (rank instanceof Prestige) { - Prestige prestige = (Prestige) rank; - replace(Variable.FROM, prestige.getFrom()); - replace(Variable.TO, prestige.getTo()); - } - return this; - } - - /** - * Fails the MessageBuilder if the message is empty. - * if this fails, all subsequent calls to that MessageBuilder will do nothing - * @return a NullMessageBuilder if the message is empty, itself otherwise - */ - public MessageBuilder failIfEmpty() { - return failIf(message.isEmpty()); - } - - public MessageBuilder failIf(boolean value) { - if (value) { + MessageBuilder failIfEmpty(); + default MessageBuilder failIf(boolean b) { + if (b) { return new NullMessageBuilder(); } else { return this; } } - public void send(CommandSender sender) { - String msg = message; - if (sender instanceof Player && Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) { - msg = PlaceholderAPI.setPlaceholders((Player) sender, msg); - } - sender.sendMessage(msg); - } - - /** - * Sends the message to all players - * ie, calls MessageBuilder#send(Player) for all players online, and sends the message in the console. - */ - public void broadcast() { - for (Player player : Bukkit.getOnlinePlayers()) { - send(player); - } - send(Bukkit.getConsoleSender()); - } - - @Override - public String toString() { - return message; + default String toString(Player player) { + return toString(); } } diff --git a/src/main/java/sh/okx/rankup/messages/NullMessageBuilder.java b/src/main/java/sh/okx/rankup/messages/NullMessageBuilder.java index 373402f..1912cc5 100644 --- a/src/main/java/sh/okx/rankup/messages/NullMessageBuilder.java +++ b/src/main/java/sh/okx/rankup/messages/NullMessageBuilder.java @@ -1,35 +1,50 @@ package sh.okx.rankup.messages; import org.bukkit.command.CommandSender; +import sh.okx.rankup.ranks.Rank; /** * A no-op implementation of MessageBuilder */ -public class NullMessageBuilder extends MessageBuilder { - NullMessageBuilder() { - super(null); - } +public class NullMessageBuilder implements MessageBuilder { @Override - public MessageBuilder failIf(boolean value) { + public MessageBuilder replaceKey(String key, Object value) { return this; } @Override - public MessageBuilder replace(Variable variable, Object value) { + public MessageBuilder replacePlayer(CommandSender sender) { + return this; + } + + @Override + public MessageBuilder replaceRank(Rank rank) { + return this; + } + + @Override + public MessageBuilder replaceOldRank(Rank rank) { return this; } @Override public void send(CommandSender sender) { + } @Override public void broadcast() { + } @Override public String toString() { return null; } + + @Override + public MessageBuilder failIfEmpty() { + return null; + } } diff --git a/src/main/java/sh/okx/rankup/messages/StringMessageBuilder.java b/src/main/java/sh/okx/rankup/messages/StringMessageBuilder.java new file mode 100644 index 0000000..9a87d6e --- /dev/null +++ b/src/main/java/sh/okx/rankup/messages/StringMessageBuilder.java @@ -0,0 +1,119 @@ +package sh.okx.rankup.messages; + +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import me.clip.placeholderapi.PlaceholderAPI; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; +import sh.okx.rankup.prestige.Prestige; +import sh.okx.rankup.prestige.Prestiges; +import sh.okx.rankup.ranks.Rank; +import sh.okx.rankup.util.Colour; + +public class StringMessageBuilder implements MessageBuilder { + + private String message; + + public StringMessageBuilder(String message) { + this.message = message; + } + + public static StringMessageBuilder of(ConfigurationSection config, Message message) { + return StringMessageBuilder.of(config, message.getName()); + } + + private static StringMessageBuilder of(ConfigurationSection config, String message) { + String string = config.getString(message); + Objects.requireNonNull(string, "Configuration message '" + message + "' not found!"); + return new StringMessageBuilder(Colour.translate(string)); + } + + public StringMessageBuilder replace(Variable variable, Object value) { + return replaceKey(variable.name(), value); + } + + @Override + public StringMessageBuilder replaceKey(String name, Object value) { + if (value == null) { + return this; + } + Pattern pattern = Pattern.compile("\\{" + name + "}", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(message); + this.message = matcher.replaceAll(String.valueOf(value)); + return this; + } + + public StringMessageBuilder replaceFirstPrestige(Rank rank, Prestiges prestiges, String with) { + if (prestiges != null && prestiges.getFirst().equals(rank)) { + replace(Variable.OLD_RANK, with); + } + return this; + } + + /** + * Fails the MessageBuilder if the message is empty. if this fails, all subsequent calls to that + * MessageBuilder will do nothing + * + * @return a NullMessageBuilder if the message is empty, itself otherwise + */ + public MessageBuilder failIfEmpty() { + return failIf(message.isEmpty()); + } + + public MessageBuilder failIf(boolean value) { + if (value) { + return new NullMessageBuilder(); + } else { + return this; + } + } + + @Override + public MessageBuilder replacePlayer(CommandSender sender) { + return replace(Variable.PLAYER, sender.getName()); + } + + @Override + public MessageBuilder replaceRank(Rank rank) { + return replace(Variable.RANK, rank.getRank()) + .replace(Variable.RANK_NAME, rank.getDisplayName()); + } + + @Override + public MessageBuilder replaceOldRank(Rank rank) { + if (rank instanceof Prestige) { + Prestige prestige = (Prestige) rank; + replace(Variable.FROM, prestige.getFrom()); + replace(Variable.TO, prestige.getTo()); + } + return replace(Variable.OLD_RANK, rank.getRank()) + .replace(Variable.OLD_RANK_NAME, rank.getDisplayName()); + } + + public void send(CommandSender sender) { + String msg = message; + if (sender instanceof Player && Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) { + msg = PlaceholderAPI.setPlaceholders((Player) sender, msg); + } + sender.sendMessage(msg); + } + + /** + * Sends the message to all players ie, calls MessageBuilder#send(Player) for all players online, + * and sends the message in the console. + */ + public void broadcast() { + for (Player player : Bukkit.getOnlinePlayers()) { + send(player); + } + send(Bukkit.getConsoleSender()); + } + + @Override + public String toString() { + return message; + } +} diff --git a/src/main/java/sh/okx/rankup/messages/pebble/PebbleMessageBuilder.java b/src/main/java/sh/okx/rankup/messages/pebble/PebbleMessageBuilder.java new file mode 100644 index 0000000..98f88c1 --- /dev/null +++ b/src/main/java/sh/okx/rankup/messages/pebble/PebbleMessageBuilder.java @@ -0,0 +1,141 @@ +package sh.okx.rankup.messages.pebble; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import sh.okx.rankup.RankupPlugin; +import sh.okx.rankup.messages.MessageBuilder; +import sh.okx.rankup.messages.NullMessageBuilder; +import sh.okx.rankup.placeholders.Placeholders; +import sh.okx.rankup.prestige.Prestige; +import sh.okx.rankup.ranks.Rank; +import sh.okx.rankup.text.TextProcessor; +import sh.okx.rankup.text.TextProcessorBuilder; +import sh.okx.rankup.text.pebble.PebbleOptions; + +public class PebbleMessageBuilder implements MessageBuilder { + + private final RankupPlugin plugin; + private final String message; + private final Map context = new HashMap<>(); + private final Map> lastMinuteContext = new HashMap<>(); + + public PebbleMessageBuilder(RankupPlugin plugin, String message) { + this.plugin = plugin; + this.message = message; + replaceInitial(); + } + + private void replaceInitial() { + Function lastMinute = player -> { + List ranks = new ArrayList<>(); + for (Rank rank : plugin.getRankups().getTree()) { + ranks.add(new RankContext(plugin, player, rank)); + } + return ranks; + }; + lastMinuteContext.put("ranks", lastMinute); + lastMinuteContext.put("player", HumanEntity::getName); + } + + @Override + public PebbleMessageBuilder replaceKey(String key, Object value) { + context.put(key, value); + return this; + } + + @Override + public PebbleMessageBuilder replacePlayer(CommandSender sender) { + context.put("player", sender.getName()); + return this; + } + + @Override + public PebbleMessageBuilder replaceRank(Rank rank) { + lastMinuteContext.put("next", player -> new RankContext(plugin, player, rank)); + return this; + } + + @Override + public PebbleMessageBuilder replaceOldRank(Rank rank) { + Function object; + if (rank instanceof Prestige) { + object = player -> new PrestigeContext(plugin, player, (Prestige) rank); + } else { + object = player -> new RankContext(plugin, player, rank); + } + lastMinuteContext.put("rank", object); + return this; + } + + @Override + public void send(CommandSender sender) { + Player player = null; + if (sender instanceof Player) { + player = (Player) sender; + } + sender.sendMessage(processor(player).process(message)); + } + + @Override + public void broadcast() { + for (Player player : Bukkit.getOnlinePlayers()) { + send(player); + } + send(Bukkit.getConsoleSender()); + } + + @Override + public String toString() { + return processor(null).process(message); + } + + @Override + public String toString(Player player) { + return processor(player).process(message); + } + + private TextProcessor processor(Player player) { + Map context = getContext(player); + PebbleOptions options = getOptions(); + return new TextProcessorBuilder() + .legacy(context, options) + .papi(player) + .pebble(context, options) + .papi(player) + .colour() + .create(); + } + + private Map getContext(Player player) { + Map context = new HashMap<>(this.context); + if (player != null) { + for (Map.Entry> lastMinute : lastMinuteContext.entrySet()) { + context.putIfAbsent(lastMinute.getKey(), lastMinute.getValue().apply(player)); + } + } + return context; + } + + @Override + public MessageBuilder failIfEmpty() { + if (message.isEmpty()) { + return new NullMessageBuilder(); + } else { + return this; + } + } + + private PebbleOptions getOptions() { + Placeholders placeholders = plugin.getPlaceholders(); + return new PebbleOptions(placeholders.getMoneyFormat(), + placeholders.getPercentFormat(), + placeholders.getSimpleFormat()); + } +} diff --git a/src/main/java/sh/okx/rankup/messages/pebble/PrestigeContext.java b/src/main/java/sh/okx/rankup/messages/pebble/PrestigeContext.java new file mode 100644 index 0000000..60af03d --- /dev/null +++ b/src/main/java/sh/okx/rankup/messages/pebble/PrestigeContext.java @@ -0,0 +1,24 @@ +package sh.okx.rankup.messages.pebble; + + +import org.bukkit.entity.Player; +import sh.okx.rankup.RankupPlugin; +import sh.okx.rankup.prestige.Prestige; + +public class PrestigeContext extends RankContext { + + private final Prestige rank; + + public PrestigeContext(RankupPlugin plugin, Player player, Prestige rank) { + super(plugin, player, rank); + this.rank = rank; + } + + public String getFrom() { + return rank.getFrom(); + } + + public String getTo() { + return rank.getTo(); + } +} diff --git a/src/main/java/sh/okx/rankup/messages/pebble/RankContext.java b/src/main/java/sh/okx/rankup/messages/pebble/RankContext.java new file mode 100644 index 0000000..b32ee09 --- /dev/null +++ b/src/main/java/sh/okx/rankup/messages/pebble/RankContext.java @@ -0,0 +1,78 @@ +package sh.okx.rankup.messages.pebble; + +import java.util.ArrayList; +import java.util.List; +import org.bukkit.entity.Player; +import sh.okx.rankup.RankupPlugin; +import sh.okx.rankup.ranks.Rank; +import sh.okx.rankup.ranks.RankTree; +import sh.okx.rankup.requirements.Requirement; + +public class RankContext { + private final RankupPlugin plugin; + private final Player player; + private final Rank rank; + + public RankContext(RankupPlugin plugin, Player player, Rank rank) { + this.plugin = plugin; + this.player = player; + this.rank = rank; + } + + public String getRank() { + return rank.getRank(); + } + + public String getName() { + return rank.getDisplayName(); + } + + public RequirementContext getRequirement(String requirement) { + return new RequirementContext(player, rank.getRequirement(player, requirement)); + } + + public RequirementContext getRequirement(String requirement, String sub) { + return new RequirementContext(player, rank.getRequirement(player, requirement + "#" + sub)); + } + + public RequirementContext getReq(String requirement) { + return getRequirement(requirement); + } + + public RequirementContext getReq(String requirement, String sub) { + return getRequirement(requirement, sub); + } + + public List getRequirements() { + List list = new ArrayList<>(); + for (Requirement requirement : rank.getRequirements().getRequirements(player)) { + list.add(new RequirementContext(player, requirement)); + } + return list; + } + + public boolean getDone() { + for (RequirementContext context : getRequirements()) { + if (!context.getDone()) { + return false; + } + } + return true; + } + + public int getIndex() { + RankTree tree = plugin.getRankups().getTree(); + int index = 0; + for (Rank rank : tree) { + if (rank == this.rank) { + return index; + } + index++; + } + return -1; + } + + public String toString() { + return rank.getRank(); + } +} diff --git a/src/main/java/sh/okx/rankup/messages/pebble/RequirementContext.java b/src/main/java/sh/okx/rankup/messages/pebble/RequirementContext.java new file mode 100644 index 0000000..4979903 --- /dev/null +++ b/src/main/java/sh/okx/rankup/messages/pebble/RequirementContext.java @@ -0,0 +1,43 @@ +package sh.okx.rankup.messages.pebble; + +import org.bukkit.entity.Player; +import sh.okx.rankup.requirements.Requirement; + +public class RequirementContext { + + private final Player player; + private final Requirement requirement; + + public RequirementContext(Player player, Requirement requirement) { + this.player = player; + this.requirement = requirement; + } + + public double getTotal() { + return requirement.getTotal(player); + } + + public boolean getDone() { + return requirement.check(player); + } + + public double getRemaining() { + return requirement.getRemaining(player); + } + + public double getProgress() { + return requirement.getTotal(player) - requirement.getRemaining(player); + } + + public String getName() { + return requirement.getName(); + } + + public double getPercent() { + return getProgress() / getTotal(); + } + + public String toString() { + return "Requirement[" + requirement.getFullName() + "]"; + } +} diff --git a/src/main/java/sh/okx/rankup/placeholders/RankupExpansion.java b/src/main/java/sh/okx/rankup/placeholders/RankupExpansion.java index 36e3563..777658c 100644 --- a/src/main/java/sh/okx/rankup/placeholders/RankupExpansion.java +++ b/src/main/java/sh/okx/rankup/placeholders/RankupExpansion.java @@ -47,19 +47,19 @@ public class RankupExpansion implements Expansion { replacePattern(parts[1]), parts.length > 2 ? parts[2] : ""); } else if (params.startsWith("rank_requirement_")) { String[] parts = params.split("_", 5); - return getPlaceholderRequirement(player, rankups.getByName(parts[2]), + return getPlaceholderRequirement(player, rankups.getRankByName(parts[2]), replacePattern(parts[3]), parts.length > 4 ? parts[4] : ""); // return placeholders.getSimpleFormat().format(orElse(rankups.getByName(parts[2]).getRequirement(parts[3]), Requirement::getValueDouble, 0)); } else if (params.startsWith("rank_money_")) { String[] parts = params.split("_", 4); - double amount = Objects.requireNonNull(rankups.getByName(parts[2]), "Rankup " + parts[2] + " does not exist").getRequirement(player, "money").getValueDouble(); + double amount = Objects.requireNonNull(rankups.getRankByName(parts[2]), "Rankup " + parts[2] + " does not exist").getRequirement(player, "money").getValueDouble(); if (parts.length > 3 && parts[3].equalsIgnoreCase("left")) { amount = amount - plugin.getEconomy().getBalance(player); } return plugin.formatMoney(Math.max(0, amount)); } else if (params.startsWith("status_")) { String[] parts = params.split("_", 2); - Rank statusRank = rankups.getByName(parts[1]); + Rank statusRank = rankups.getRankByName(parts[1]); if (statusRank == null) { return null; diff --git a/src/main/java/sh/okx/rankup/prestige/Prestige.java b/src/main/java/sh/okx/rankup/prestige/Prestige.java index 3baa600..034efcb 100644 --- a/src/main/java/sh/okx/rankup/prestige/Prestige.java +++ b/src/main/java/sh/okx/rankup/prestige/Prestige.java @@ -9,7 +9,7 @@ import org.bukkit.Bukkit; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import sh.okx.rankup.RankupPlugin; -import sh.okx.rankup.messages.MessageBuilder; +import sh.okx.rankup.messages.pebble.PebbleMessageBuilder; import sh.okx.rankup.ranks.Rank; import sh.okx.rankup.ranks.requirements.ListRankRequirements; import sh.okx.rankup.ranks.requirements.RankRequirements; @@ -45,9 +45,10 @@ public class Prestige extends Rank { @Override public void runCommands(Player player, Rank next) { for (String command : commands) { - String string = new MessageBuilder(command) - .replaceRanks(player, this, next) - .replaceFromTo(this) + String string = new PebbleMessageBuilder(this.plugin, command) + .replacePlayer(player) + .replaceOldRank(this) + .replaceRank(next) .toString(); if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) { string = PlaceholderAPI.setPlaceholders(player, string); diff --git a/src/main/java/sh/okx/rankup/ranks/Rank.java b/src/main/java/sh/okx/rankup/ranks/Rank.java index b2f81c9..9fed31e 100644 --- a/src/main/java/sh/okx/rankup/ranks/Rank.java +++ b/src/main/java/sh/okx/rankup/ranks/Rank.java @@ -1,20 +1,17 @@ package sh.okx.rankup.ranks; +import java.util.List; import lombok.AccessLevel; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.RequiredArgsConstructor; -import me.clip.placeholderapi.PlaceholderAPI; import org.bukkit.Bukkit; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import sh.okx.rankup.RankupPlugin; -import sh.okx.rankup.messages.MessageBuilder; import sh.okx.rankup.ranks.requirements.RankRequirements; import sh.okx.rankup.requirements.Requirement; -import java.util.List; - @EqualsAndHashCode @RequiredArgsConstructor(access = AccessLevel.PROTECTED) public class Rank { @@ -50,10 +47,7 @@ public class Rank { public void runCommands(Player player, Rank next) { for (String command : commands) { - String string = new MessageBuilder(command).replaceRanks(player, this, next).toString(); - if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) { - string = PlaceholderAPI.setPlaceholders(player, string); - } + String string = plugin.newMessageBuilder(command).replacePlayer(player).replaceOldRank(this).replaceRank(next).toString(player); Bukkit.dispatchCommand(Bukkit.getConsoleSender(), string); } } diff --git a/src/main/java/sh/okx/rankup/ranks/RankList.java b/src/main/java/sh/okx/rankup/ranks/RankList.java index f0546d8..cf85f90 100644 --- a/src/main/java/sh/okx/rankup/ranks/RankList.java +++ b/src/main/java/sh/okx/rankup/ranks/RankList.java @@ -91,7 +91,7 @@ public abstract class RankList { return tree.getFirst().getRank(); } - public T getByName(String name) { + public T getRankByName(String name) { if (name == null) { return null; } @@ -103,6 +103,20 @@ public abstract class RankList { return null; } + public RankElement getByName(String name) { + if (name == null) { + return null; + } + List> rankElements = tree.asList(); + for (RankElement rank : rankElements) { + if (name.equalsIgnoreCase(rank.getRank().getRank())) { + return rank; + } + } + return null; + } + + public RankElement getByPlayer(Player player) { List> list = tree.asList(); Collections.reverse(list); diff --git a/src/main/java/sh/okx/rankup/requirements/requirement/mcmmo/McMMOSkillUtil.java b/src/main/java/sh/okx/rankup/requirements/requirement/mcmmo/McMMOSkillUtil.java index b792433..3b9d974 100644 --- a/src/main/java/sh/okx/rankup/requirements/requirement/mcmmo/McMMOSkillUtil.java +++ b/src/main/java/sh/okx/rankup/requirements/requirement/mcmmo/McMMOSkillUtil.java @@ -2,10 +2,9 @@ package sh.okx.rankup.requirements.requirement.mcmmo; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.util.player.UserManager; -import org.bukkit.entity.Player; - import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import org.bukkit.entity.Player; /** * Because mcMMO like changing the name of their skill types. diff --git a/src/main/java/sh/okx/rankup/text/ChainedTextProcessor.java b/src/main/java/sh/okx/rankup/text/ChainedTextProcessor.java new file mode 100644 index 0000000..8e9c303 --- /dev/null +++ b/src/main/java/sh/okx/rankup/text/ChainedTextProcessor.java @@ -0,0 +1,17 @@ +package sh.okx.rankup.text; + +public class ChainedTextProcessor implements TextProcessor { + private final TextProcessor[] processors; + + public ChainedTextProcessor(TextProcessor... processors) { + this.processors = processors; + } + + @Override + public String process(String string) { + for (TextProcessor processor : processors) { + string = processor.process(string); + } + return string; + } +} diff --git a/src/main/java/sh/okx/rankup/text/ColourTextProcessor.java b/src/main/java/sh/okx/rankup/text/ColourTextProcessor.java new file mode 100644 index 0000000..47047a0 --- /dev/null +++ b/src/main/java/sh/okx/rankup/text/ColourTextProcessor.java @@ -0,0 +1,11 @@ +package sh.okx.rankup.text; + +import sh.okx.rankup.util.Colour; + +public class ColourTextProcessor implements TextProcessor { + + @Override + public String process(String string) { + return Colour.translate(string); + } +} diff --git a/src/main/java/sh/okx/rankup/text/LegacyTextProcessor.java b/src/main/java/sh/okx/rankup/text/LegacyTextProcessor.java new file mode 100644 index 0000000..40099b9 --- /dev/null +++ b/src/main/java/sh/okx/rankup/text/LegacyTextProcessor.java @@ -0,0 +1,107 @@ +package sh.okx.rankup.text; + +import java.util.Map; +import java.util.function.Function; +import sh.okx.rankup.messages.pebble.RankContext; +import sh.okx.rankup.text.pebble.PebbleOptions; + +public class LegacyTextProcessor implements TextProcessor { + + private final Map pebbleContext; + private final PebbleOptions options; + + public LegacyTextProcessor(Map pebbleContext, PebbleOptions options) { + this.pebbleContext = pebbleContext; + this.options = options; + } + + + @Override + public String process(String string) { + StringBuilder output = new StringBuilder(); + StringBuilder buffer = new StringBuilder(); + boolean isPlaceholder = false; + + char[] chars = string.toCharArray(); + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + if (c == '{') { + if (i + 1 < chars.length) { + if (chars[i + 1] != '{') { + isPlaceholder = true; + } else { + output.append(c).append(chars[i + 1]); + i++; + } + } else { + output.append(c); + } + } else if (c == '}' && isPlaceholder) { + output.append(replacePlaceholder(buffer.toString())); + buffer.delete(0, buffer.length()); + isPlaceholder = false; + } else if (isPlaceholder) { + buffer.append(c); + } else { + output.append(c); + } + } + + return output.toString(); + } + + private String replacePlaceholder(String p) { + if ("player".equalsIgnoreCase(p)) { + return get("player", p); + } else if ("old_rank".equalsIgnoreCase(p)) { + return get("rank", p, o -> ((RankContext) o).getRank()); + } else if ("rank".equalsIgnoreCase(p)) { + return get("next", p, o -> ((RankContext) o).getRank()); + } else if ("old_rank_name".equalsIgnoreCase(p)) { + return get("rank", p, o -> ((RankContext) o).getName()); + } else if ("rank_name".equalsIgnoreCase(p)) { + return get("next", p, o -> ((RankContext) o).getName()); + } else if ("money".equalsIgnoreCase(p)) { + return get("rank", p, o -> this.options.getMoneyFormat().format( + ((RankContext) o).getReq("money").getTotal())); + } else if ("money_needed".equalsIgnoreCase(p)) { + return get("rank", p, o -> this.options.getMoneyFormat().format( + ((RankContext) o).getReq("money").getRemaining())); + } else if (p.toLowerCase().startsWith("amount ")) { + String requirement = p.substring("amount ".length()); + return get("rank", p, o -> this.options.getSimpleFormat() + .format(((RankContext) o).getReq(requirement).getTotal())); + } else if (p.toLowerCase().startsWith("amount_done ")) { + String requirement = p.substring("amount_done ".length()); + return get("rank", p, o -> this.options.getSimpleFormat() + .format(((RankContext) o).getReq(requirement).getProgress())); + } else if (p.toLowerCase().startsWith("amount_needed ")) { + String requirement = p.substring("amount_needed ".length()); + return get("rank", p, o -> this.options.getSimpleFormat() + .format(((RankContext) o).getReq(requirement).getRemaining())); + } else if (p.toLowerCase().startsWith("percent_done ")) { + String requirement = p.substring("percent_done ".length()); + return get("rank", p, o -> this.options.getPercentFormat() + .format(((RankContext) o).getReq(requirement).getPercent() * 100)); + } else if (p.toLowerCase().startsWith("percent_left ")) { + String requirement = p.substring("percent_left ".length()); + return get("rank", p, o -> this.options.getPercentFormat() + .format(100 - ((RankContext) o).getReq(requirement).getPercent() * 100)); + } + + return get(p, "{" + p + "}"); + } + + private String get(String key, String def) { + return get(key, def, String::valueOf); + } + + private String get(String key, String def, Function mapper) { + Object val = pebbleContext.get(key); + if (val == null) { + return def; + } else { + return mapper.apply(val); + } + } +} diff --git a/src/main/java/sh/okx/rankup/text/PlaceholderApiTextProcessor.java b/src/main/java/sh/okx/rankup/text/PlaceholderApiTextProcessor.java new file mode 100644 index 0000000..79707fa --- /dev/null +++ b/src/main/java/sh/okx/rankup/text/PlaceholderApiTextProcessor.java @@ -0,0 +1,23 @@ +package sh.okx.rankup.text; + +import me.clip.placeholderapi.PlaceholderAPI; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +public class PlaceholderApiTextProcessor implements TextProcessor { + + private final Player player; + + public PlaceholderApiTextProcessor(Player player) { + this.player = player; + } + + @Override + public String process(String string) { + if (player == null || !Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) { + return string; + } else { + return PlaceholderAPI.setPlaceholders(player, string); + } + } +} diff --git a/src/main/java/sh/okx/rankup/text/TextProcessor.java b/src/main/java/sh/okx/rankup/text/TextProcessor.java new file mode 100644 index 0000000..2a44a51 --- /dev/null +++ b/src/main/java/sh/okx/rankup/text/TextProcessor.java @@ -0,0 +1,5 @@ +package sh.okx.rankup.text; + +public interface TextProcessor { + String process(String string); +} diff --git a/src/main/java/sh/okx/rankup/text/TextProcessorBuilder.java b/src/main/java/sh/okx/rankup/text/TextProcessorBuilder.java new file mode 100644 index 0000000..88a9c69 --- /dev/null +++ b/src/main/java/sh/okx/rankup/text/TextProcessorBuilder.java @@ -0,0 +1,38 @@ +package sh.okx.rankup.text; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; +import sh.okx.rankup.text.pebble.PebbleOptions; +import sh.okx.rankup.text.pebble.PebbleTextProcessor; + +public class TextProcessorBuilder { + + private final List processors = new ArrayList<>(); + + public TextProcessorBuilder colour() { + processors.add(new ColourTextProcessor()); + return this; + } + + public TextProcessorBuilder pebble(Map context, PebbleOptions options) { + processors.add(new PebbleTextProcessor(context, options)); + return this; + } + + public TextProcessorBuilder papi(@Nullable Player player) { + processors.add(new PlaceholderApiTextProcessor(player)); + return this; + } + + public TextProcessorBuilder legacy(Map context, PebbleOptions options) { + processors.add(new LegacyTextProcessor(context, options)); + return this; + } + + public TextProcessor create() { + return new ChainedTextProcessor(processors.toArray(new TextProcessor[0])); + } +} diff --git a/src/main/java/sh/okx/rankup/text/pebble/DecimalFormatFilter.java b/src/main/java/sh/okx/rankup/text/pebble/DecimalFormatFilter.java new file mode 100644 index 0000000..c02b220 --- /dev/null +++ b/src/main/java/sh/okx/rankup/text/pebble/DecimalFormatFilter.java @@ -0,0 +1,38 @@ +package sh.okx.rankup.text.pebble; + +import com.mitchellbosecke.pebble.error.PebbleException; +import com.mitchellbosecke.pebble.extension.Filter; +import com.mitchellbosecke.pebble.template.EvaluationContext; +import com.mitchellbosecke.pebble.template.PebbleTemplate; +import java.text.DecimalFormat; +import java.util.List; +import java.util.Map; + +public class DecimalFormatFilter implements Filter { + + private final DecimalFormat format; + + public DecimalFormatFilter(DecimalFormat format) { + this.format = format; + } + + @Override + public List getArgumentNames() { + return null; + } + + @Override + public Object apply(Object input, Map args, PebbleTemplate self, + EvaluationContext context, int lineNumber) throws PebbleException { + if (input == null) { + return null; + } + if (!(input instanceof Number)) { + throw new PebbleException(null, "The input for the 'DecimalFormatFilter' filter has to be a number.", + lineNumber, self.getName()); + } + + Number number = (Number) input; + return this.format.format(number); + } +} diff --git a/src/main/java/sh/okx/rankup/text/pebble/PebbleOptions.java b/src/main/java/sh/okx/rankup/text/pebble/PebbleOptions.java new file mode 100644 index 0000000..4490ca7 --- /dev/null +++ b/src/main/java/sh/okx/rankup/text/pebble/PebbleOptions.java @@ -0,0 +1,11 @@ +package sh.okx.rankup.text.pebble; + +import java.text.DecimalFormat; +import lombok.Data; + +@Data +public class PebbleOptions { + private final DecimalFormat moneyFormat; + private final DecimalFormat percentFormat; + private final DecimalFormat simpleFormat; +} diff --git a/src/main/java/sh/okx/rankup/text/pebble/PebbleTextProcessor.java b/src/main/java/sh/okx/rankup/text/pebble/PebbleTextProcessor.java new file mode 100644 index 0000000..85208b2 --- /dev/null +++ b/src/main/java/sh/okx/rankup/text/pebble/PebbleTextProcessor.java @@ -0,0 +1,54 @@ +package sh.okx.rankup.text.pebble; + +import com.mitchellbosecke.pebble.PebbleEngine; +import com.mitchellbosecke.pebble.extension.AbstractExtension; +import com.mitchellbosecke.pebble.extension.Filter; +import com.mitchellbosecke.pebble.loader.StringLoader; +import java.io.IOException; +import java.io.StringWriter; +import java.text.DecimalFormat; +import java.util.HashMap; +import java.util.Map; +import sh.okx.rankup.text.TextProcessor; + +public class PebbleTextProcessor implements TextProcessor { + + private final Map context; + private final PebbleOptions options; + + public PebbleTextProcessor(Map context, PebbleOptions options) { + this.context = context; + this.options = options; + } + + @Override + public String process(String string) { + PebbleEngine engine = new PebbleEngine.Builder().autoEscaping(false).extension( + new AbstractExtension() { + @Override + public Map getFilters() { + Map filters = new HashMap<>(); + if (options != null) { + DecimalFormat moneyFormat = options.getMoneyFormat(); + if (moneyFormat != null) filters.put("money", new DecimalFormatFilter(moneyFormat)); + + DecimalFormat percentFormat = options.getPercentFormat(); + if (percentFormat != null) filters.put("percent", new DecimalFormatFilter(percentFormat)); + + DecimalFormat simpleFormat = options.getSimpleFormat(); + if (simpleFormat != null) filters.put("simple", new DecimalFormatFilter(simpleFormat)); + } + return filters; + } + }) + .loader(new StringLoader()).build(); + StringWriter writer = new StringWriter(); + try { + engine.getTemplate(string).evaluate(writer, context); + return writer.toString(); + } catch (IOException e) { + e.printStackTrace(); + return string; + } + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index beb731f..1937823 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,5 +1,5 @@ # this is used for letting you know that you need to update/change your config file -version: 9 +version: 10 # the locale to use for messages # all messages can be customised but this allows you to diff --git a/src/main/resources/locale/en.yml b/src/main/resources/locale/en.yml index 8900d05..2474396 100644 --- a/src/main/resources/locale/en.yml +++ b/src/main/resources/locale/en.yml @@ -1,23 +1,20 @@ # the messages in this section can be customised for each rankup in rankups.yml. rankup: - requirements-not-met: "&cYou need {MONEY} money to rankup." + requirements-not-met: "&cYou need {{rank.requirement('money').total | simple}} money to rankup." no-rankup: "&eYou are at the highest rank." # set to an empty string, ie: success-public: "" # to hide that message. - success-public: "&a{PLAYER} &ehas ranked up to: &d{RANK}" - success-private: "&aYou have ranked up to: &d{RANK}" + success-public: "&a{{player}} &ehas ranked up to: &d{{next.rank}}" + success-private: "&aYou have ranked up to: &d{{next.rank}}" # used for the text confirmation confirmation: |- - &eAre you sure you want to rankup to &a{RANK}&e? + &eAre you sure you want to rankup to &a{{next.rank}}&e? &eType &c/rankup &eagain to confirm. - # used for the GUI confirmation - title: "Rankup to {RANK}" - must-prestige: "&cYou must prestige to /rankup further!" gui: rows: 1 - title: "Rankup to {RANK}" + title: "Rankup to {{next.rank}}" rankup: material: EMERALD_BLOCK # index can be separated by spaces to show in multiple groups @@ -26,7 +23,7 @@ rankup: index: 0-3 name: '&a&lConfirm' # lore is optional - lore: '&6Rankup to &b{RANK}' + lore: '&6Rankup to &b{{next.rank}}' cancel: material: REDSTONE_BLOCK index: 5-8 @@ -45,13 +42,13 @@ rankup: width: 7 complete: material: GREEN_STAINED_GLASS_PANE - name: "&aRank &7{RANK} &a(completed)" + name: "&aRank &7{{next.rank}} &a(completed)" current: material: ORANGE_STAINED_GLASS_PANE - name: "&dRankup to &7{RANK}" + name: "&dRankup to &7{{next.rank}}" incomplete: material: RED_STAINED_GLASS_PANE - name: "&cRank &7{RANK} &c(requires rankup)" + name: "&cRank &7{{next.rank}} &c(requires rankup)" fill: material: BLACK_STAINED_GLASS_PANE name: ' ' @@ -60,43 +57,43 @@ rankup: # you can (and probably should) you override these in rankups.yml # to show the specific requirements for each rank. # however if you are just using money or don't need to change the message per rank, you can use any combination of: - # {MONEY} {MONEY_NEEDED} {PERCENT_DONE } {PERCENT_LEFT } {AMOUNT } {AMOUNT_NEEDED } - # {MONEY} and {MONEY_NEEDED} are different from {AMOUNT money} and {AMOUNT_NEEDED money} in that they use a different format. + # {{rank.requirement('money').total | simple}} {MONEY_NEEDED} {PERCENT_DONE } {PERCENT_LEFT } {AMOUNT } {AMOUNT_NEEDED } + # {{rank.requirement('money').total | simple}} and {MONEY_NEEDED} are different from {AMOUNT money} and {AMOUNT_NEEDED money} in that they use a different format. # here is an example of showing the requirements for just money: #list: - # complete: "&7{OLD_RANK} &8\xbb &7{RANK} &efor &7${MONEY}" - # current: "&c{OLD_RANK} &e\xbb &c{RANK} &efor &a${MONEY} &e{PERCENT_DONE money}%" - # incomplete: "&r{OLD_RANK} &e\xbb &r{RANK} &efor &a${MONEY}" + # complete: "&7{{rank.rank}} &8\xbb &7{{next.rank}} &efor &7${{rank.requirement('money').total | simple}}" + # current: "&c{{rank.rank}} &e\xbb &c{{next.rank}} &efor &a${{rank.requirement('money').total | simple}} &e{PERCENT_DONE money}%" + # incomplete: "&r{{rank.rank}} &e\xbb &r{{next.rank}} &efor &a${{rank.requirement('money').total | simple}}" list: - complete: "&7{OLD_RANK} &8\xbb &7{RANK}" - current: "&c{OLD_RANK} &e\xbb &c{RANK}" - incomplete: "&r{OLD_RANK} &e\xbb &r{RANK}" + complete: "&7{{rank.rank}} &8\xbb &7{{next.rank}}" + current: "&c{{rank.rank}} &e\xbb &c{{next.rank}}" + incomplete: "&r{{rank.rank}} &e\xbb &r{{next.rank}}" # an empty string disables the header/footer header: "" footer: "" # sent when a player tries to rankup when they are on cooldown cooldown: - singular: "&cYou must wait {SECONDS_LEFT} more second to rankup again." - plural: "&cYou must wait {SECONDS_LEFT} more seconds to rankup again." + singular: "&cYou must wait {{seconds_left}} more second to rankup again." + plural: "&cYou must wait {{seconds_left}} more seconds to rankup again." # prestige messages can also be customised prestige: - requirements-not-met: "&cYou need {MONEY} money to prestige." + requirements-not-met: "&cYou need {{rank.requirement('money').total | simple}} money to prestige." no-prestige: "&eYou are at the highest prestige." - success-public: "&a{PLAYER} &ehas prestiged to: &d{RANK}" - success-private: "&aYou have prestiged to: &d{RANK}" + success-public: "&a{{player}} &ehas prestiged to: &d{{next.rank}}" + success-private: "&aYou have prestiged to: &d{{next.rank}}" confirmation: |- - &eAre you sure you want to prestige to &a{RANK}&e? + &eAre you sure you want to prestige to &a{{next.rank}}&e? &eType &c/prestige &eagain to confirm. gui: - title: "Prestige to {RANK}" + title: "Prestige to {{next.rank}}" rankup: material: GOLD_BLOCK index: 0-3 name: '&a&lConfirm' - lore: '&6Prestige to &b{RANK}' + lore: '&6Prestige to &b{{next.rank}}' cancel: material: REDSTONE_BLOCK index: 5-8 @@ -109,14 +106,14 @@ prestige: material: BLACK_STAINED_GLASS_PANE list: - complete: "&7{OLD_RANK} &8\xbb &7{RANK}" - current: "&c{OLD_RANK} &e\xbb &c{RANK}" - incomplete: "&r{OLD_RANK} &e\xbb &r{RANK}" + complete: "&7{{rank.rank}} &8\xbb &7{{next.rank}}" + current: "&c{{rank.rank}} &e\xbb &c{{next.rank}}" + incomplete: "&r{{rank.rank}} &e\xbb &r{{next.rank}}" header: "" footer: "" cooldown: - singular: "&cYou must wait {SECONDS_LEFT} second to prestige again." - plural: "&cYou must wait {SECONDS_LEFT} more seconds to prestige again." + singular: "&cYou must wait {{seconds_left}} second to prestige again." + plural: "&cYou must wait {{seconds_left}} more seconds to prestige again." not-high-enough: "&cYou cannot prestige at your rank!" not-in-ladder: "&cSorry, but we could not find any rankups for the group(s) you are in. Use /ranks to list the rankups." diff --git a/src/main/resources/locale/es.yml b/src/main/resources/locale/es.yml index 14e2980..1894d50 100644 --- a/src/main/resources/locale/es.yml +++ b/src/main/resources/locale/es.yml @@ -4,20 +4,18 @@ rankup: no-rankup: "&eEstás en el último rango." # set to an empty string, ie: success-public: "" # to hide that message. - success-public: "&a{PLAYER} &eha subido de rango a: &d{RANK}" + success-public: "&a{{player}} &eha subido de rango a: &d{{next.rank}}" success-private: "" # used for the text confirmation confirmation: |- - &e¿Estás seguro de que quires subir de rango a &a{RANK}&e? + &e¿Estás seguro de que quires subir de rango a &a{{next.rank}}&e? &eEscribe /rankup para confirmar. - # used for the GUI confirmation - title: "Subir de rango a {RANK}" must-prestige: "&c¡Tienes que prestigiar para seguir subiendo de rango!" gui: rows: 1 - title: "Subir de rango a {RANK}" + title: "Subir de rango a {{next.rank}}" rankup: material: EMERALD_BLOCK # index can be separated by spaces to show in multiple groups @@ -26,7 +24,7 @@ rankup: index: 0-3 name: '&a&lConfirmar' # lore is optional - lore: '&6Subir de rango a {RANK}' + lore: '&6Subir de rango a {{next.rank}}' cancel: material: REDSTONE_BLOCK index: 5-8 @@ -45,56 +43,56 @@ rankup: width: 7 complete: material: GREEN_STAINED_GLASS_PANE - name: "&aRango &7{RANK} &a(completado)" + name: "&aRango &7{{next.rank}} &a(completado)" current: material: ORANGE_STAINED_GLASS_PANE - name: "&dSubir a &7{RANK}" + name: "&dSubir a &7{{next.rank}}" incomplete: material: RED_STAINED_GLASS_PANE - name: "&cRango &7{RANK} &c(requiere ascenso)" + name: "&cRango &7{{next.rank}} &c(requiere ascenso)" fill: material: BLACK_STAINED_GLASS_PANE name: ' ' # you can (and probably should) you override these in rankups.yml # to show the specific requirements for each rank. # however if you are just using money or don't need to change the message per rank, you can use any combination of: - # {MONEY} {MONEY_NEEDED} {PERCENT_DONE } {PERCENT_LEFT } {AMOUNT } {AMOUNT_NEEDED } - # {MONEY} and {MONEY_NEEDED} are different from {AMOUNT money} and {AMOUNT_NEEDED money} in that they use a different format. + # {{rank.requirement('money').total | simple}} {MONEY_NEEDED} {PERCENT_DONE } {PERCENT_LEFT } {AMOUNT } {AMOUNT_NEEDED } + # {{rank.requirement('money').total | simple}} and {MONEY_NEEDED} are different from {AMOUNT money} and {AMOUNT_NEEDED money} in that they use a different format. # here is an example of showing the requirements for just money: #list: - # complete: "&7{OLD_RANK} &8\xbb &7{RANK} &efor &7${MONEY}" - # current: "&c{OLD_RANK} &e\xbb &c{RANK} &efor &a${MONEY} &e{PERCENT_DONE money}%" - # incomplete: "&r{OLD_RANK} &e\xbb &r{RANK} &efor &a${MONEY}" + # complete: "&7{{rank.rank}} &8\xbb &7{{next.rank}} &efor &7${{rank.requirement('money').total | simple}}" + # current: "&c{{rank.rank}} &e\xbb &c{{next.rank}} &efor &a${{rank.requirement('money').total | simple}} &e{PERCENT_DONE money}%" + # incomplete: "&r{{rank.rank}} &e\xbb &r{{next.rank}} &efor &a${{rank.requirement('money').total | simple}}" list: - complete: "&7{OLD_RANK} &8\xbb &7{RANK}" - current: "&c{OLD_RANK} &e\xbb &c{RANK}" - incomplete: "&r{OLD_RANK} &e\xbb &r{RANK}" + complete: "&7{{rank.rank}} &8\xbb &7{{next.rank}}" + current: "&c{{rank.rank}} &e\xbb &c{{next.rank}}" + incomplete: "&r{{rank.rank}} &e\xbb &r{{next.rank}}" # an empty string disables the header/footer header: "" footer: "" # sent when a player tries to rankup when they are on cooldown cooldown: - singular: "&cTienes que esperar {SECONDS_LEFT} segundo más para subir de rango otra vez." - plural: "&cTienes que esperar {SECONDS_LEFT} segundos más para subir de rango otra vez." + singular: "&cTienes que esperar {{seconds_left}} segundo más para subir de rango otra vez." + plural: "&cTienes que esperar {{seconds_left}} segundos más para subir de rango otra vez." # prestige messages can also be customised prestige: - requirements-not-met: "&cNecesitas {MONEY} más dinero para prestigiar." + requirements-not-met: "&cNecesitas {{rank.requirement('money').total | simple}} más dinero para prestigiar." no-prestige: "&eYa estás en el prestigio más alto." - success-public: "&a{PLAYER} &eha prestigiado a: &d{RANK}" + success-public: "&a{{player}} &eha prestigiado a: &d{{next.rank}}" success-private: "" confirmation: |- - &eEstás seguro de que quieres prestigiar a &a{RANK}&e? + &eEstás seguro de que quieres prestigiar a &a{{next.rank}}&e? &eEscribe &c/prestige &otra vez para confirmar. gui: - title: "Prestigiar a {RANK}" + title: "Prestigiar a {{next.rank}}" rankup: material: GOLD_BLOCK index: 0-3 name: '&a&lConfirmar' - lore: '&6Prestigiar a &b{RANK}' + lore: '&6Prestigiar a &b{{next.rank}}' cancel: material: REDSTONE_BLOCK index: 5-8 @@ -107,14 +105,14 @@ prestige: material: BLACK_STAINED_GLASS_PANE list: - complete: "&7{OLD_RANK} &8\xbb &7{RANK}" - current: "&c{OLD_RANK} &e\xbb &c{RANK}" - incomplete: "&r{OLD_RANK} &e\xbb &r{RANK}" + complete: "&7{{rank.rank}} &8\xbb &7{{next.rank}}" + current: "&c{{rank.rank}} &e\xbb &c{{next.rank}}" + incomplete: "&r{{rank.rank}} &e\xbb &r{{next.rank}}" header: "" footer: "" cooldown: - singular: "&cTienes que esperar {SECONDS_LEFT} segundo más para prestigiar otra vez." - plural: "&cTienes que esperar {SECONDS_LEFT} segundos más para prestigiar otra vez." + singular: "&cTienes que esperar {{seconds_left}} segundo más para prestigiar otra vez." + plural: "&cTienes que esperar {{seconds_left}} segundos más para prestigiar otra vez." not-high-enough: "&c¡No puedes prestigiar a tu rango!" not-in-ladder: "&cPerdón, pero no hemos podido encontrar rangos en los grupos que perteneces. Usa /ranks para ver los rangos." diff --git a/src/main/resources/locale/fr.yml b/src/main/resources/locale/fr.yml index b00de9f..859f0ec 100644 --- a/src/main/resources/locale/fr.yml +++ b/src/main/resources/locale/fr.yml @@ -1,19 +1,19 @@ # the messages in this section can be customised for each rankup in rankups.yml. rankup: - requirements-not-met: "&cIl vous faut {MONEY} d'argent pour passer au rang suivant." + requirements-not-met: "&cIl vous faut {{rank.requirement('money').total | simple}} d'argent pour passer au rang suivant." no-rankup: "&eVous avez atteint le rang maximum." # set to an empty string, ie: success-public: "" # to hide that message. - success-public: "&a{PLAYER} &eest passé(e) au rang: &d{RANK}" - success-private: "&aVous êtes passé(e) au rang: &d{RANK}" + success-public: "&a{{player}} &eest passé(e) au rang: &d{{next.rank}}" + success-private: "&aVous êtes passé(e) au rang: &d{{next.rank}}" # used for the text confirmation confirmation: |- - &eÊtes-vous sûr(e) de vouloir passer au rang &a{RANK}&e? + &eÊtes-vous sûr(e) de vouloir passer au rang &a{{next.rank}}&e? &eTapez &c/rankup &eà nouveau pour confirmer. gui: rows: 1 - title: "Passer au rang {RANK}" + title: "Passer au rang {{next.rank}}" rankup: material: EMERALD_BLOCK # index can be separated by spaces to show in multiple groups @@ -22,7 +22,7 @@ rankup: index: 0-3 name: '&a&lConfirm' # lore is optional - lore: '&6Rankup to &b{RANK}' + lore: '&6Rankup to &b{{next.rank}}' cancel: material: REDSTONE_BLOCK index: 5-8 @@ -39,45 +39,45 @@ rankup: # you can (and probably should) you override these in rankups.yml # to show the specific requirements for each rank. # however if you are just using money or don't need to change the message per rank, you can use any combination of: - # {MONEY} {MONEY_NEEDED} {PERCENT_DONE } {PERCENT_LEFT } {AMOUNT } {AMOUNT_NEEDED } - # {MONEY} and {MONEY_NEEDED} are different from {AMOUNT money} and {AMOUNT_NEEDED money} in that they use a different format. + # {{rank.requirement('money').total | simple}} {MONEY_NEEDED} {PERCENT_DONE } {PERCENT_LEFT } {AMOUNT } {AMOUNT_NEEDED } + # {{rank.requirement('money').total | simple}} and {MONEY_NEEDED} are different from {AMOUNT money} and {AMOUNT_NEEDED money} in that they use a different format. # here is an example of showing the requirements for just money: #list: - # complete: "&7{OLD_RANK} &8\xbb &7{RANK} &efor &7${MONEY}" - # current: "&c{OLD_RANK} &e\xbb &c{RANK} &efor &a${MONEY} &e{PERCENT_DONE money}%" - # incomplete: "&r{OLD_RANK} &e\xbb &r{RANK} &efor &a${MONEY}" + # complete: "&7{{rank.rank}} &8\xbb &7{{next.rank}} &efor &7${{rank.requirement('money').total | simple}}" + # current: "&c{{rank.rank}} &e\xbb &c{{next.rank}} &efor &a${{rank.requirement('money').total | simple}} &e{PERCENT_DONE money}%" + # incomplete: "&r{{rank.rank}} &e\xbb &r{{next.rank}} &efor &a${{rank.requirement('money').total | simple}}" list: - complete: "&7{OLD_RANK} &8\xbb &7{RANK}" - current: "&c{OLD_RANK} &e\xbb &c{RANK}" - incomplete: "&r{OLD_RANK} &e\xbb &r{RANK}" + complete: "&7{{rank.rank}} &8\xbb &7{{next.rank}}" + current: "&c{{rank.rank}} &e\xbb &c{{next.rank}}" + incomplete: "&r{{rank.rank}} &e\xbb &r{{next.rank}}" # an empty string disables the header/footer header: "" footer: "" # sent when a player tries to rankup when they are on cooldown cooldown: - singular: "&cVous devez attendre encore {SECONDS_LEFT} seconde pour de nouveau passer un rang." - plural: "&cVous devez attendre encore {SECONDS_LEFT} secondes pour de nouveau passer un rang." + singular: "&cVous devez attendre encore {{seconds_left}} seconde pour de nouveau passer un rang." + plural: "&cVous devez attendre encore {{seconds_left}} secondes pour de nouveau passer un rang." # prestige messages can also be customised prestige: - requirements-not-met: "&cIl vous faut {MONEY} d'argent pour passer au rang suivant." + requirements-not-met: "&cIl vous faut {{rank.requirement('money').total | simple}} d'argent pour passer au rang suivant." no-prestige: "&eVous avez atteint le prestige maximum." - success-public: "&a{PLAYER} &eest passé(e) au prestige: &d{RANK}" - success-private: "&aVous êtes passé(e) au prestige: &d{RANK}" + success-public: "&a{{player}} &eest passé(e) au prestige: &d{{next.rank}}" + success-private: "&aVous êtes passé(e) au prestige: &d{{next.rank}}" confirmation: |- - &eÊtes-vous sûr(e) de vouloir passer au prestige &a{RANK}&e? + &eÊtes-vous sûr(e) de vouloir passer au prestige &a{{next.rank}}&e? &eTapez &c/prestige &eà nouveau pour confirmer. - title: "Passer au prestige {RANK}" + title: "Passer au prestige {{next.rank}}" gui: rows: 1 - title: "Prestige to {RANK}" + title: "Prestige to {{next.rank}}" rankup: material: GOLD_BLOCK index: 0-3 name: '&a&lConfirm' - lore: '&6Prestige to &b{RANK}' + lore: '&6Prestige to &b{{next.rank}}' cancel: material: REDSTONE_BLOCK index: 5-8 @@ -90,14 +90,14 @@ prestige: material: BLACK_STAINED_GLASS_PANE list: - complete: "&7{OLD_RANK} &8\xbb &7{RANK}" - current: "&c{OLD_RANK} &e\xbb &c{RANK}" - incomplete: "&r{OLD_RANK} &e\xbb &r{RANK}" + complete: "&7{{rank.rank}} &8\xbb &7{{next.rank}}" + current: "&c{{rank.rank}} &e\xbb &c{{next.rank}}" + incomplete: "&r{{rank.rank}} &e\xbb &r{{next.rank}}" header: "" footer: "" cooldown: - singular: "&cVous devez attendre encore {SECONDS_LEFT} seconde pour de nouveau passer un rang." - plural: "&cVous devez attendre encore {SECONDS_LEFT} secondes pour de nouveau passer un rang." + singular: "&cVous devez attendre encore {{seconds_left}} seconde pour de nouveau passer un rang." + plural: "&cVous devez attendre encore {{seconds_left}} secondes pour de nouveau passer un rang." not-high-enough: "&cVous ne pouvez pas passer de prestige à votre rang !" not-in-ladder: "&cDésolé, mais nous n'avons pas trouvé de rang pour le(s) groupe(s) dans lequel(s) vous êtes. Tapez /ranks pour voir la liste des rangs." diff --git a/src/main/resources/locale/it.yml b/src/main/resources/locale/it.yml index 97e8432..c78ec67 100644 --- a/src/main/resources/locale/it.yml +++ b/src/main/resources/locale/it.yml @@ -1,21 +1,19 @@ #traduzione italiana del locale per rankup rankup: - requirements-not-met: "&cHai bisogno di {MONEY} soldi per avanzare di rank." + requirements-not-met: "&cHai bisogno di {{rank.requirement('money').total | simple}} soldi per avanzare di rank." no-rankup: "&eSei al rank più alto." # imposta stringa vuota per nascondere il messaggio - success-public: "&a{PLAYER} &e\u00E8 avanzato di rank a: &d{RANK}" - success-private: "&aHai avanzato di rank a: &d{RANK}" + success-public: "&a{{player}} &e\u00E8 avanzato di rank a: &d{{next.rank}}" + success-private: "&aHai avanzato di rank a: &d{{next.rank}}" # conferma testuale confirmation: |- - &eSei sicuro di voler avanzare di rank a &a{RANK}&e? + &eSei sicuro di voler avanzare di rank a &a{{next.rank}}&e? &eScrivi &c/rankup &edi nuovo per confermare. - # conferma gui - title: "Avanzamento di rank a {RANK}" must-prestige: "&cDevi effettuare un prestige prima di avanzare di rank!" gui: - title: "Avanzamento di rank a {RANK}" + title: "Avanzamento di rank a {{next.rank}}" rankup: material: EMERALD_BLOCK # l'indice può essere separato da spazi per mostrarlo in più gruppi @@ -24,7 +22,7 @@ rankup: index: 0-3 name: '&a&lConferma' # lore opzionale - lore: '&6Avanzamento a &b{RANK}' + lore: '&6Avanzamento a &b{{next.rank}}' cancel: material: REDSTONE_BLOCK index: 5-8 @@ -43,13 +41,13 @@ rankup: width: 7 complete: material: GREEN_STAINED_GLASS_PANE - name: "&aRank &7{RANK} &a(completed)" + name: "&aRank &7{{next.rank}} &a(completed)" current: material: ORANGE_STAINED_GLASS_PANE - name: "&dRankup to &7{RANK}" + name: "&dRankup to &7{{next.rank}}" incomplete: material: RED_STAINED_GLASS_PANE - name: "&cRank &7{RANK} &c(requires rankup)" + name: "&cRank &7{{next.rank}} &c(requires rankup)" fill: material: BLACK_STAINED_GLASS_PANE name: ' ' @@ -57,43 +55,43 @@ rankup: # potresti e dovresti configurare questi in rankup.yml # per visualizzare i requisiti di ogni rank # ma se stai usando solo i soldi e non ti serve modificare il mesaggio puoi usapre questi - # {MONEY} {MONEY_NEEDED} {PERCENT_DONE } {PERCENT_LEFT } {AMOUNT } {AMOUNT_NEEDED } - # {MONEY} e {MONEY_NEEDED} sono diversi da {AMOUNT money} e {AMOUNT_NEEDED money}, usano un formato diverso + # {{rank.requirement('money').total | simple}} {MONEY_NEEDED} {PERCENT_DONE } {PERCENT_LEFT } {AMOUNT } {AMOUNT_NEEDED } + # {{rank.requirement('money').total | simple}} e {MONEY_NEEDED} sono diversi da {AMOUNT money} e {AMOUNT_NEEDED money}, usano un formato diverso # ESEMPIO: #list: - # complete: "&7{OLD_RANK} &8\xbb &7{RANK} &efor &7${MONEY}" - # current: "&c{OLD_RANK} &e\xbb &c{RANK} &efor &a${MONEY} &e{PERCENT_DONE money}%" - # incomplete: "&r{OLD_RANK} &e\xbb &r{RANK} &efor &a${MONEY}" + # complete: "&7{{rank.rank}} &8\xbb &7{{next.rank}} &efor &7${{rank.requirement('money').total | simple}}" + # current: "&c{{rank.rank}} &e\xbb &c{{next.rank}} &efor &a${{rank.requirement('money').total | simple}} &e{PERCENT_DONE money}%" + # incomplete: "&r{{rank.rank}} &e\xbb &r{{next.rank}} &efor &a${{rank.requirement('money').total | simple}}" list: - complete: "&7{OLD_RANK} &8\xbb &7{RANK}" - current: "&c{OLD_RANK} &e\xbb &c{RANK}" - incomplete: "&r{OLD_RANK} &e\xbb &r{RANK}" + complete: "&7{{rank.rank}} &8\xbb &7{{next.rank}}" + current: "&c{{rank.rank}} &e\xbb &c{{next.rank}}" + incomplete: "&r{{rank.rank}} &e\xbb &r{{next.rank}}" # stringa vuota per disattivare header: "" footer: "" # messaggi cooldown cooldown: - singular: "&cDevi aspettare ancora {SECONDS_LEFT} secondo per avanzare di rank." - plural: "&cDevi aspettare ancora {SECONDS_LEFT} secondi per avanzare di rank." + singular: "&cDevi aspettare ancora {{seconds_left}} secondo per avanzare di rank." + plural: "&cDevi aspettare ancora {{seconds_left}} secondi per avanzare di rank." # messaggi prestige prestige: - requirements-not-met: "&cHai bisogno di {MONEY} soldi per effettuare un prestige." + requirements-not-met: "&cHai bisogno di {{rank.requirement('money').total | simple}} soldi per effettuare un prestige." no-prestige: "&eSei al prestige più alto." - success-public: "&a{PLAYER} &eha effettuato un prestige a: &d{RANK}" - success-private: "&aHai effettuato un prestige a: &d{RANK}" + success-public: "&a{{player}} &eha effettuato un prestige a: &d{{next.rank}}" + success-private: "&aHai effettuato un prestige a: &d{{next.rank}}" confirmation: |- - &eSei sicuro di voler effettuare un prestige a &a{RANK}&e? + &eSei sicuro di voler effettuare un prestige a &a{{next.rank}}&e? &eScrivi &c/prestige &edi nuovo per confermare. gui: - title: "Prestige a {RANK}" + title: "Prestige a {{next.rank}}" rankup: material: GOLD_BLOCK index: 0-3 name: '&a&lConferma' - lore: '&6Prestige a &b{RANK}' + lore: '&6Prestige a &b{{next.rank}}' cancel: material: REDSTONE_BLOCK index: 5-8 @@ -104,14 +102,14 @@ prestige: material: BLACK_STAINED_GLASS_PANE list: - complete: "&7{OLD_RANK} &8\xbb &7{RANK}" - current: "&c{OLD_RANK} &e\xbb &c{RANK}" - incomplete: "&r{OLD_RANK} &e\xbb &r{RANK}" + complete: "&7{{rank.rank}} &8\xbb &7{{next.rank}}" + current: "&c{{rank.rank}} &e\xbb &c{{next.rank}}" + incomplete: "&r{{rank.rank}} &e\xbb &r{{next.rank}}" header: "" footer: "" cooldown: - singular: "&cDevi aspettare {SECONDS_LEFT} secondo per effettuare un altro prestige." - plural: "&cDevi aspettare {SECONDS_LEFT} secondi per effettuare un altro prestige." + singular: "&cDevi aspettare {{seconds_left}} secondo per effettuare un altro prestige." + plural: "&cDevi aspettare {{seconds_left}} secondi per effettuare un altro prestige." not-high-enough: "&cNon puoi effettuare un prestige al tuo rank!" not-in-ladder: "&cNon riusciamo a trovare nessun avanzamento di rank per il tuo gruppo. Scrivi /ranks per ottenere una lista degli avanzamenti disponibili." diff --git a/src/main/resources/locale/pt_br.yml b/src/main/resources/locale/pt_br.yml index 3a3b50a..8899c5e 100644 --- a/src/main/resources/locale/pt_br.yml +++ b/src/main/resources/locale/pt_br.yml @@ -1,21 +1,21 @@ # As mensagens nessa sessão podem ser customizadas para cada rankup em rankups.yml. rankup: - requirements-not-met: "&cVocê {MONEY} para poder dar subir de rank." + requirements-not-met: "&cVocê {{rank.requirement('money').total | simple}} para poder dar subir de rank." no-rankup: "&eVocê já está no maior rank." # coloque como uma String vazia, ex: success-public: "" # para ocultar a mensagem. - success-public: "&a{PLAYER} &eacaba de subir para: &d{RANK}" - success-private: "&aVocê subiu para: &d{RANK}" + success-public: "&a{{player}} &eacaba de subir para: &d{{next.rank}}" + success-private: "&aVocê subiu para: &d{{next.rank}}" # Utilizado para a confirmação via mensagem. confirmation: |- - &eVocê tem certeza que deseja subir para &a{RANK}&e? + &eVocê tem certeza que deseja subir para &a{{next.rank}}&e? &eDigite &c/rankup &enovamente para confirmar. must-prestige: "&cVocê deve subir de prestígio para dar /rankup a frente!" gui: rows: 1 - title: "Rankup to {RANK}" + title: "Rankup to {{next.rank}}" rankup: material: EMERALD_BLOCK # index can be separated by spaces to show in multiple groups @@ -24,7 +24,7 @@ rankup: index: 0-3 name: '&a&lConfirm' # lore is optional - lore: '&6Rankup to &b{RANK}' + lore: '&6Rankup to &b{{next.rank}}' cancel: material: REDSTONE_BLOCK index: 5-8 @@ -39,48 +39,48 @@ rankup: # Você pode (e deveria) substituir isto em rankups.yml # para mostrar os requerimentos específicos para cada rank. # contudo, se você está apenas usando dinheiro ou não precisa mudar as mensagens por rank, você pode usar qualquer combinação de: - # {MONEY} {MONEY_NEEDED} {PERCENT_DONE } {PERCENT_LEFT } {AMOUNT } {AMOUNT_NEEDED } - # {MONEY} e {MONEY_NEEDED} são diferentes de {AMOUNT money} e {AMOUNT_NEEDED money} pois eles usam diferentes formatos. + # {{rank.requirement('money').total | simple}} {MONEY_NEEDED} {PERCENT_DONE } {PERCENT_LEFT } {AMOUNT } {AMOUNT_NEEDED } + # {{rank.requirement('money').total | simple}} e {MONEY_NEEDED} são diferentes de {AMOUNT money} e {AMOUNT_NEEDED money} pois eles usam diferentes formatos. # here is an example of showing the requirements for just money: # Segue um exemplo de como mostrar um requerimento para apenas dinheiro. #list: - # complete: "&7{OLD_RANK} &8\xbb &7{RANK} &efor &7${MONEY}" - # current: "&c{OLD_RANK} &e\xbb &c{RANK} &efor &a${MONEY} &e{PERCENT_DONE money}%" - # incomplete: "&r{OLD_RANK} &e\xbb &r{RANK} &efor &a${MONEY}" + # complete: "&7{{rank.rank}} &8\xbb &7{{next.rank}} &efor &7${{rank.requirement('money').total | simple}}" + # current: "&c{{rank.rank}} &e\xbb &c{{next.rank}} &efor &a${{rank.requirement('money').total | simple}} &e{PERCENT_DONE money}%" + # incomplete: "&r{{rank.rank}} &e\xbb &r{{next.rank}} &efor &a${{rank.requirement('money').total | simple}}" list: - complete: "&7{OLD_RANK} &8\xbb &7{RANK}" - current: "&c{OLD_RANK} &e\xbb &c{RANK}" - incomplete: "&r{OLD_RANK} &e\xbb &r{RANK}" + complete: "&7{{rank.rank}} &8\xbb &7{{next.rank}}" + current: "&c{{rank.rank}} &e\xbb &c{{next.rank}}" + incomplete: "&r{{rank.rank}} &e\xbb &r{{next.rank}}" # Uma string vázia desativa o cabeçalho/rodapé header: "" footer: "" # sent when a player tries to rankup when they are on cooldown. # Enviado quando um jogador tenta dar rankup enquanto ele está em um cooldown (tempo de espera entre comandos, freeze). cooldown: - singular: "&cVocê precisa esperar {SECONDS_LEFT} segundo para dar rankup novamente." - plural: "&cVocê precisa {SECONDS_LEFT} segundos para dar rankup novamente." + singular: "&cVocê precisa esperar {{seconds_left}} segundo para dar rankup novamente." + plural: "&cVocê precisa {{seconds_left}} segundos para dar rankup novamente." # prestige messages can also be customised # Mensagens de prestígio também podem ser customizadas prestige: - requirements-not-met: "&cVocê precisa {MONEY} para dar prestigiar." + requirements-not-met: "&cVocê precisa {{rank.requirement('money').total | simple}} para dar prestigiar." no-prestige: "&eVocê está no maior prestígio." - success-public: "&a{PLAYER} &esubiu de prestígio para: &d{RANK}" - success-private: "&aVocê subiu de prestígio para: &d{RANK}" + success-public: "&a{{player}} &esubiu de prestígio para: &d{{next.rank}}" + success-private: "&aVocê subiu de prestígio para: &d{{next.rank}}" confirmation: |- - &eVocê tem certeza que deseja subir de prestígio para &a{RANK}&e? + &eVocê tem certeza que deseja subir de prestígio para &a{{next.rank}}&e? &eDigite &c/prestige &enovamente para confimar. - title: "Subiu de prestígio para {RANK}" + title: "Subiu de prestígio para {{next.rank}}" gui: rows: 1 - title: "Prestige to {RANK}" + title: "Prestige to {{next.rank}}" rankup: material: GOLD_BLOCK index: 0-3 name: '&a&lConfirm' - lore: '&6Prestige to &b{RANK}' + lore: '&6Prestige to &b{{next.rank}}' cancel: material: REDSTONE_BLOCK index: 5-8 @@ -93,14 +93,14 @@ prestige: material: BLACK_STAINED_GLASS_PANE list: - complete: "&7{OLD_RANK} &8\xbb &7{RANK}" - current: "&c{OLD_RANK} &e\xbb &c{RANK}" - incomplete: "&r{OLD_RANK} &e\xbb &r{RANK}" + complete: "&7{{rank.rank}} &8\xbb &7{{next.rank}}" + current: "&c{{rank.rank}} &e\xbb &c{{next.rank}}" + incomplete: "&r{{rank.rank}} &e\xbb &r{{next.rank}}" header: "" footer: "" cooldown: - singular: "&cVocê deve esperar {SECONDS_LEFT} segundo para subir de prestígio novamente." - plural: "&cVocê deve esperar {SECONDS_LEFT} segundos para subir de prestígio novamente." + singular: "&cVocê deve esperar {{seconds_left}} segundo para subir de prestígio novamente." + plural: "&cVocê deve esperar {{seconds_left}} segundos para subir de prestígio novamente." not-high-enough: "&cVocê não pode subir de prestígio nesse rank!" not-in-ladder: "&cDesculpa, não conseguimos achar nenhum rank para você subir." diff --git a/src/main/resources/locale/ru.yml b/src/main/resources/locale/ru.yml index e050121..d980d16 100644 --- a/src/main/resources/locale/ru.yml +++ b/src/main/resources/locale/ru.yml @@ -1,18 +1,18 @@ # Сообщения в этой секции могут быть изменены для каждого повышения уровня в rankups.yml rankup: - requirements-not-met: "&cВам нужно ещё {MONEY} денег для повышения уровня." + requirements-not-met: "&cВам нужно ещё {{rank.requirement('money').total | simple}} денег для повышения уровня." no-rankup: "&eУ Вас уже самый высокий ранг." # Оставьте это поле пустым, чтобы скрыть сообщение (success-public: '') - success-public: "&eРанг игрока &a{PLAYER} &eбыл повышен до: &d{RANK}" - success-private: "&aВаш ранг повысился до: &d{RANK}" + success-public: "&eРанг игрока &a{{player}} &eбыл повышен до: &d{{next.rank}}" + success-private: "&aВаш ранг повысился до: &d{{next.rank}}" # Используется как текст подтверждения confirmation: |- - &eВы уверены, что хотите ранг до &a{RANK}&e? + &eВы уверены, что хотите ранг до &a{{next.rank}}&e? &eВведите &c/rankup &eещё раз, чтобы подтвердить. gui: rows: 1 - title: "Повысить до {RANK}" + title: "Повысить до {{next.rank}}" rankup: material: EMERALD_BLOCK # index can be separated by spaces to show in multiple groups @@ -21,7 +21,7 @@ rankup: index: 0-3 name: '&a&lConfirm' # lore is optional - lore: '&6Rankup to &b{RANK}' + lore: '&6Rankup to &b{{next.rank}}' cancel: material: REDSTONE_BLOCK index: 5-8 @@ -37,47 +37,47 @@ rankup: # Вы можете (вам стоит ;D) переписать это в rankups.yml, чтобы показать специфичные требования для каждого ранга. # Тем не менее, если Вы просто используете деньги или не нуждаетесь в смене сообщений для кажого ранга - Вы - # можете использовать комбинации из: {MONEY} {MONEY_NEEDED} {PERCENT_DONE <требование>} {PERCENT_LEFT <требование>} - # {AMOUNT <требование>} {AMOUNT_NEDDED <требование>}. "{MONEY}" и "{MONEY_NEEDED}" отличаются от + # можете использовать комбинации из: {{rank.requirement('money').total | simple}} {MONEY_NEEDED} {PERCENT_DONE <требование>} {PERCENT_LEFT <требование>} + # {AMOUNT <требование>} {AMOUNT_NEDDED <требование>}. "{{rank.requirement('money').total | simple}}" и "{MONEY_NEEDED}" отличаются от # {AMOUNT сумма} {AMOUNT_NEDDED сумма} тем, что они используют разный формат. # Вот пример показа требований только для денег # P.S. \xbb - "»" в Unicode. #list: - # complete: "&7{OLD_RANK} &8\xbb &7{RANK} &eза &7${MONEY}" - # current: "&c{OLD_RANK} &e\xbb &c{RANK} &eза &a${MONEY} &e{PERCENT_DONE money}%" - # incomplete: "&r{OLD_RANK} &e\xbb &r{RANK} &eза &a${MONEY}" + # complete: "&7{{rank.rank}} &8\xbb &7{{next.rank}} &eза &7${{rank.requirement('money').total | simple}}" + # current: "&c{{rank.rank}} &e\xbb &c{{next.rank}} &eза &a${{rank.requirement('money').total | simple}} &e{PERCENT_DONE money}%" + # incomplete: "&r{{rank.rank}} &e\xbb &r{{next.rank}} &eза &a${{rank.requirement('money').total | simple}}" list: - complete: "&7{OLD_RANK} &8\xbb &7{RANK}" - current: "&c{OLD_RANK} &e\xbb &c{RANK}" - incomplete: "&r{OLD_RANK} &e\xbb &r{RANK}" + complete: "&7{{rank.rank}} &8\xbb &7{{next.rank}}" + current: "&c{{rank.rank}} &e\xbb &c{{next.rank}}" + incomplete: "&r{{rank.rank}} &e\xbb &r{{next.rank}}" # Пустая строка выключает заголовок/нижнюю часть header: '' footer: '' # Отправляется игроку, который попытался повысить уровень, когда ещё не прошёл кулдаун (откат/перезарядка) cooldown: - singular: "&cПожалуйста, подождите {SECONDS_LEFT} сек., чтобы повысить ранг снова." - plural: "&cПожалуйста, подождите {SECONDS_LEFT} сек., чтобы повысить ранг снова." + singular: "&cПожалуйста, подождите {{seconds_left}} сек., чтобы повысить ранг снова." + plural: "&cПожалуйста, подождите {{seconds_left}} сек., чтобы повысить ранг снова." # Сообщения престижа тоже могут быть изменены prestige: - requirements-not-met: "&cВам нужно {MONEY} денг для повышения престижа." + requirements-not-met: "&cВам нужно {{rank.requirement('money').total | simple}} денг для повышения престижа." no-prestige: "&eУ Вас уже самый высокий уровень престижа." - success-public: "Престиж игрока &a{PLAYER} &eповышен в: &d{RANK}" - success-private: "&aВаш престиж повышен в: &d{RANK}" + success-public: "Престиж игрока &a{{player}} &eповышен в: &d{{next.rank}}" + success-private: "&aВаш престиж повышен в: &d{{next.rank}}" confirmation: |- - &eВы уверены, что хотите престиж в &a{RANK}&e? + &eВы уверены, что хотите престиж в &a{{next.rank}}&e? &eВведите &c/prestige &eснова для подтверждения. - title: "Повысить престиж в {RANK}" + title: "Повысить престиж в {{next.rank}}" gui: rows: 1 - title: "Prestige to {RANK}" + title: "Prestige to {{next.rank}}" rankup: material: GOLD_BLOCK index: 0-3 name: '&a&lConfirm' - lore: '&6Prestige to &b{RANK}' + lore: '&6Prestige to &b{{next.rank}}' cancel: material: REDSTONE_BLOCK index: 5-8 @@ -90,14 +90,14 @@ prestige: material: BLACK_STAINED_GLASS_PANE list: - complete: "&7{OLD_RANK} &8\xbb &7{RANK}" - current: "&c{OLD_RANK} &e\xbb &c{RANK}" - incomplete: "&r{OLD_RANK} &e\xbb &r{RANK}" + complete: "&7{{rank.rank}} &8\xbb &7{{next.rank}}" + current: "&c{{rank.rank}} &e\xbb &c{{next.rank}}" + incomplete: "&r{{rank.rank}} &e\xbb &r{{next.rank}}" header: "" footer: "" cooldown: - singular: "&cПожалуйста, подождите {SECONDS_LEFT} сек., чтобы повысить ранг снова." - plural: "&cПожалуйста, подождите {SECONDS_LEFT} сек., чтобы повысить ранг снова." + singular: "&cПожалуйста, подождите {{seconds_left}} сек., чтобы повысить ранг снова." + plural: "&cПожалуйста, подождите {{seconds_left}} сек., чтобы повысить ранг снова." not-high-enough: "&cВы не можете поднять уровень престижа в этом ранге" not-in-ladder: "&cПростите, но мне не можем найти какие-нибудь повышения для Вашей группы." diff --git a/src/main/resources/locale/zh_cn.yml b/src/main/resources/locale/zh_cn.yml index ff02dfa..4947183 100644 --- a/src/main/resources/locale/zh_cn.yml +++ b/src/main/resources/locale/zh_cn.yml @@ -1,24 +1,22 @@ # 这部分的信息能在 rankups.yml 对不同的段位进行自定义。 rankup: - requirements-not-met: "&c您需要 {MONEY} 游戏币才能晋级。" + requirements-not-met: "&c您需要 {{rank.requirement('money').total | simple}} 游戏币才能晋级。" no-rankup: "&e您目前已处于段位的顶端。" # 想设置字符串为空? # 一个例子: success-public: "" # 这将隐藏那条信息。 - success-public: "&a{PLAYER} &e已经晋升至新的段位: &d{RANK}" - success-private: "&a您成功晋升到新的段位: &d{RANK}" + success-public: "&a{{player}} &e已经晋升至新的段位: &d{{next.rank}}" + success-private: "&a您成功晋升到新的段位: &d{{next.rank}}" # 用于二次确认的文本信息 confirmation: |- - &e您确定您要晋级至 &a{RANK}&e? + &e您确定您要晋级至 &a{{next.rank}}&e? &e再次输入 &c/rankup &e以确认操作。 - # 用于二次确认的 GUI 信息 - title: "晋级至 {RANK}" must-prestige: "&c您必须再次输入 /rankup !" gui: rows: 1 - title: "晋级至 {RANK}" + title: "晋级至 {{next.rank}}" rankup: material: EMERALD_BLOCK # 可以用空格分隔序号以显示在多个槽位组中 @@ -27,7 +25,7 @@ rankup: index: 0-3 name: '&a&l确认' # lore 可有可无 - lore: '&6晋级至 &b{RANK}' + lore: '&6晋级至 &b{{next.rank}}' cancel: material: REDSTONE_BLOCK index: 5-8 @@ -46,13 +44,13 @@ rankup: width: 7 complete: material: GREEN_STAINED_GLASS_PANE - name: "&a段位等级 &7{RANK} &a(已达成)" + name: "&a段位等级 &7{{next.rank}} &a(已达成)" current: material: ORANGE_STAINED_GLASS_PANE - name: "&d晋升段位等级至 &7{RANK}" + name: "&d晋升段位等级至 &7{{next.rank}}" incomplete: material: RED_STAINED_GLASS_PANE - name: "&c段位等级 &7{RANK} &c(需要晋升)" + name: "&c段位等级 &7{{next.rank}} &c(需要晋升)" fill: material: BLACK_STAINED_GLASS_PANE name: ' ' @@ -61,42 +59,42 @@ rankup: # 您可以(也可能应该)在 rankups.yml # 显示每个等级的具体要求。 # 但是,如果您只想使用游戏币作为晋升需求或不需要更改每个级别的消息,则可以使用以下任意组合: - # {MONEY} {MONEY_NEEDED} {PERCENT_DONE } {PERCENT_LEFT } {AMOUNT } {AMOUNT_NEEDED } - # {MONEY} 和 {MONEY_NEEDED} 相较于 {AMOUNT money} 和 {AMOUNT_NEEDED money} 在使用的格式上不尽相同。 + # {{rank.requirement('money').total | simple}} {MONEY_NEEDED} {PERCENT_DONE } {PERCENT_LEFT } {AMOUNT } {AMOUNT_NEEDED } + # {{rank.requirement('money').total | simple}} 和 {MONEY_NEEDED} 相较于 {AMOUNT money} 和 {AMOUNT_NEEDED money} 在使用的格式上不尽相同。 # 这里有一个只使用游戏币作为晋升条件的示例: #list: - # complete: "&7{OLD_RANK} &8\xbb &7{RANK} &e花费 &7${MONEY}" - # current: "&c{OLD_RANK} &e\xbb &c{RANK} &e花费 &a${MONEY} &e{PERCENT_DONE money}%" - # incomplete: "&r{OLD_RANK} &e\xbb &r{RANK} &e花费 &a${MONEY}" + # complete: "&7{{rank.rank}} &8\xbb &7{{next.rank}} &e花费 &7${{rank.requirement('money').total | simple}}" + # current: "&c{{rank.rank}} &e\xbb &c{{next.rank}} &e花费 &a${{rank.requirement('money').total | simple}} &e{PERCENT_DONE money}%" + # incomplete: "&r{{rank.rank}} &e\xbb &r{{next.rank}} &e花费 &a${{rank.requirement('money').total | simple}}" list: - complete: "&7{OLD_RANK} &8\xbb &7{RANK}" - current: "&c{OLD_RANK} &e\xbb &c{RANK}" - incomplete: "&r{OLD_RANK} &e\xbb &r{RANK}" + complete: "&7{{rank.rank}} &8\xbb &7{{next.rank}}" + current: "&c{{rank.rank}} &e\xbb &c{{next.rank}}" + incomplete: "&r{{rank.rank}} &e\xbb &r{{next.rank}}" # 一个空字符串可以关闭 header(页眉)/footer(页脚) header: "" footer: "" # 这些信息将在玩家处于晋升冷却期间尝试升级后发送 cooldown: - singular: "&c您必须等待 {SECONDS_LEFT} 秒才能进行晋升段位等级操作。" - plural: "&c您必须等待 {SECONDS_LEFT} 秒才能进行晋升段位等级操作。" + singular: "&c您必须等待 {{seconds_left}} 秒才能进行晋升段位等级操作。" + plural: "&c您必须等待 {{seconds_left}} 秒才能进行晋升段位等级操作。" # 声望信息也可以定制 prestige: - requirements-not-met: "&c您需要 {MONEY} 游戏币才能进行声望等级升级。" + requirements-not-met: "&c您需要 {{rank.requirement('money').total | simple}} 游戏币才能进行声望等级升级。" no-prestige: "&e您目前已经处在最高的声望等级了。" - success-public: "&a{PLAYER} &e已经晋升至: &d{RANK}" - success-private: "&a您成功晋升声望至: &d{RANK}" + success-public: "&a{{player}} &e已经晋升至: &d{{next.rank}}" + success-private: "&a您成功晋升声望至: &d{{next.rank}}" confirmation: |- - &e您确定要晋升声望至 &a{RANK}&e? + &e您确定要晋升声望至 &a{{next.rank}}&e? &e再次输入 &c/prestige &e确认操作。 gui: - title: "声望晋升至 {RANK}" + title: "声望晋升至 {{next.rank}}" rankup: material: GOLD_BLOCK index: 0-3 name: '&a&l确认' - lore: '&6声望晋升至 &b{RANK}' + lore: '&6声望晋升至 &b{{next.rank}}' cancel: material: REDSTONE_BLOCK index: 5-8 @@ -109,14 +107,14 @@ prestige: material: BLACK_STAINED_GLASS_PANE list: - complete: "&7{OLD_RANK} &8\xbb &7{RANK}" - current: "&c{OLD_RANK} &e\xbb &c{RANK}" - incomplete: "&r{OLD_RANK} &e\xbb &r{RANK}" + complete: "&7{{rank.rank}} &8\xbb &7{{next.rank}}" + current: "&c{{rank.rank}} &e\xbb &c{{next.rank}}" + incomplete: "&r{{rank.rank}} &e\xbb &r{{next.rank}}" header: "" footer: "" cooldown: - singular: "&c您必须等待 {SECONDS_LEFT} 秒才能再次晋升声望等级。" - plural: "&c您必须等待 {SECONDS_LEFT} 秒才能再次晋升声望等级。" + singular: "&c您必须等待 {{seconds_left}} 秒才能再次晋升声望等级。" + plural: "&c您必须等待 {{seconds_left}} 秒才能再次晋升声望等级。" not-high-enough: "&c您无法在您当前等级上进行声望晋升!" not-in-ladder: "&c抱歉,我们找不到您所在小组的任何段位等级。使用 /ranks 列出所有段位等级。" diff --git a/src/main/resources/rankups.yml b/src/main/resources/rankups.yml index a2ba234..567c59f 100644 --- a/src/main/resources/rankups.yml +++ b/src/main/resources/rankups.yml @@ -22,7 +22,7 @@ Aexample: # nb: groups are automatically changed with vault #commands: # this will run when a player ranks up from A to B. - #- 'say {PLAYER} well done for ranking up from {OLD_RANK} to {RANK}!' + #- 'say {{player}} well done for ranking up from {{rank.rank}} to {{next.rank}}!' Bexample: rank: 'B' next: 'C' @@ -39,6 +39,6 @@ Cexample: rankup: requirements-not-met: '&cYou need 5000 money and 2 levels of XP to rankup to D.' list: - complete: "&7{OLD_RANK} &8\xbb &7{RANK} &e(5000 money, 2 XP levels)" - current: "&c{OLD_RANK} &e\xbb &c{RANK} &e(5000 money, 2 XP levels)" - incomplete: "&r{OLD_RANK} &e\xbb &r{RANK} &e(5000 money, 2 XP levels)" \ No newline at end of file + complete: "&7{{rank.rank}} &8\xbb &7{{next.rank}} &e(5000 money, 2 XP levels)" + current: "&c{{rank.rank}} &e\xbb &c{{next.rank}} &e(5000 money, 2 XP levels)" + incomplete: "&r{{rank.rank}} &e\xbb &r{{next.rank}} &e(5000 money, 2 XP levels)" \ No newline at end of file diff --git a/src/test/java/sh/okx/rankup/RankupBasicsTest.java b/src/test/java/sh/okx/rankup/RankupBasicsTest.java new file mode 100644 index 0000000..a2dbe6e --- /dev/null +++ b/src/test/java/sh/okx/rankup/RankupBasicsTest.java @@ -0,0 +1,67 @@ +package sh.okx.rankup; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import be.seeseemelk.mockbukkit.entity.PlayerMock; +import org.junit.Test; +import sh.okx.rankup.messages.Message; +import sh.okx.rankup.ranks.Rank; +import sh.okx.rankup.ranks.RankElement; + +public class RankupBasicsTest extends RankupTest { + @Test + public void testAutoRankup() { + PlayerMock player = server.addPlayer(); + + // requirement of $1000 + plugin.getEconomy().setPlayer(player, 1000); + // give them group A + groupProvider.addGroup(player.getUniqueId(), "A"); + // give the permission to auto rankup + player.addAttachment(plugin, "rankup.auto", true); + + plugin.autoRankup.run(); + assertTrue(groupProvider.inGroup(player.getUniqueId(), "B")); + assertEquals(0, plugin.getEconomy().getBalance(player), 0); + } + + @Test + public void testNotInLadder() { + PlayerMock player = server.addPlayer(); + + plugin.getHelper().rankup(player); + + player.assertSaid(plugin.getMessage(Message.NOT_IN_LADDER).replacePlayer(player).toString()); + player.assertNoMoreSaid(); + } + + @Test + public void testLastRank() { + PlayerMock player = server.addPlayer(); + + groupProvider.addGroup(player.getUniqueId(), "D"); + + plugin.getHelper().rankup(player); + + player.assertSaid(plugin.getMessage(Message.NO_RANKUP).replacePlayer(player) + .replaceRank(plugin.getRankups().getTree().last().getRank()).toString()); + player.assertNoMoreSaid(); + } + + @Test + public void testMoneyRequirement() { + PlayerMock player = server.addPlayer(); + + plugin.getEconomy().setPlayer(player, 500); + + groupProvider.addGroup(player.getUniqueId(), "A"); + plugin.getHelper().rankup(player); + + RankElement element = plugin.getRankups().getTree().getFirst(); + Rank rank = element.getRank(); + + player.assertSaid(plugin.getMessage(rank, Message.REQUIREMENTS_NOT_MET).replacePlayer(player).replaceOldRank(rank).replaceRank(element.getNext().getRank()).toString(player)); + player.assertNoMoreSaid(); + } +} diff --git a/src/test/java/sh/okx/rankup/RankupTest.java b/src/test/java/sh/okx/rankup/RankupTest.java index 012b1f7..dcd68f4 100644 --- a/src/test/java/sh/okx/rankup/RankupTest.java +++ b/src/test/java/sh/okx/rankup/RankupTest.java @@ -1,101 +1,85 @@ package sh.okx.rankup; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - import be.seeseemelk.mockbukkit.MockBukkit; import be.seeseemelk.mockbukkit.ServerMock; -import be.seeseemelk.mockbukkit.entity.PlayerMock; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; import org.junit.After; import org.junit.Before; -import org.junit.Test; import sh.okx.rankup.economy.TestEconomyProvider; import sh.okx.rankup.hook.GroupProvider; import sh.okx.rankup.hook.TestGroupProvider; import sh.okx.rankup.hook.TestPermissionManager; -import sh.okx.rankup.messages.Message; -import sh.okx.rankup.messages.Variable; -import sh.okx.rankup.ranks.Rank; -import sh.okx.rankup.ranks.RankElement; -public class RankupTest { +public abstract class RankupTest { + private final File testResourceFolder; + + public RankupTest() { + this("default"); + } + + public RankupTest(String testResourceFolder) { + URL resource = this.getClass().getResource("/" + testResourceFolder); + if (resource != null) { + this.testResourceFolder = new File(resource.getPath()); + } else { + this.testResourceFolder = null; + } + } + protected GroupProvider groupProvider; protected ServerMock server; protected RankupPlugin plugin; @Before public void setup() { - System.setProperty("TEST", "true"); + System.setProperty("RANKUP_TEST", "true"); try { groupProvider = new TestGroupProvider(); server = MockBukkit.mock(); - plugin = MockBukkit.load(RankupPlugin.class, new TestPermissionManager(groupProvider), new TestEconomyProvider()); + plugin = (RankupPlugin) server.getPluginManager() + .loadPlugin(RankupPlugin.class, new Object[]{ + new TestPermissionManager(groupProvider), + new TestEconomyProvider() + }); + + if (this.testResourceFolder != null) { + Path testPath = this.testResourceFolder.toPath(); + Path pluginPath = plugin.getDataFolder().toPath(); + Files.walkFileTree(testPath, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + Path out = pluginPath.resolve(testPath.relativize(file)); + System.out.println("Copy " + file + " to " + out); + out.getParent().toFile().mkdirs(); + Files.copy(file, out); + return super.visitFile(file, attrs); + } + }); + } + + server.getPluginManager().enablePlugin(plugin); + // let rankup finish setting up server.getScheduler().performTicks(1); + } catch (Exception e) { e.printStackTrace(); } } - @Test - public void testAutoRankup() { - PlayerMock player = server.addPlayer(); - - // requirement of $1000 - plugin.getEconomy().setPlayer(player, 1000); - // give them group A - groupProvider.addGroup(player.getUniqueId(), "A"); - // give the permission to auto rankup - player.addAttachment(plugin, "rankup.auto", true); - - plugin.autoRankup.run(); - assertTrue(groupProvider.inGroup(player.getUniqueId(), "B")); - assertEquals(0, plugin.getEconomy().getBalance(player), 0); - } - - @Test - public void testNotInLadder() { - PlayerMock player = server.addPlayer(); - - plugin.getHelper().rankup(player); - - player.assertSaid(plugin.getMessage(Message.NOT_IN_LADDER).replace(Variable.PLAYER, player.getName()).toString()); - player.assertNoMoreSaid(); - } - - @Test - public void testLastRank() { - PlayerMock player = server.addPlayer(); - - groupProvider.addGroup(player.getUniqueId(), "D"); - - plugin.getHelper().rankup(player); - - player.assertSaid(plugin.getMessage(Message.NO_RANKUP).replaceRanks(player, plugin.getRankups().getTree().last().getRank()).toString()); - player.assertNoMoreSaid(); - } - - @Test - public void testMoneyRequirement() { - PlayerMock player = server.addPlayer(); - - plugin.getEconomy().setPlayer(player, 500); - - groupProvider.addGroup(player.getUniqueId(), "A"); - plugin.getHelper().rankup(player); - - RankElement element = plugin.getRankups().getTree().getFirst(); - Rank rank = element.getRank(); - - player.assertSaid(plugin.replaceMoneyRequirements(plugin.getMessage(rank, Message.REQUIREMENTS_NOT_MET).replaceRanks(player, rank, element.getNext().getRank()), player, rank).toString()); - player.assertNoMoreSaid(); - } - @After public void tearDown() { MockBukkit.unmock(); - System.clearProperty("TEST"); + System.clearProperty("RANKUP_TEST"); } } diff --git a/src/test/java/sh/okx/rankup/legacy/LegacyPlaceholderTest.java b/src/test/java/sh/okx/rankup/legacy/LegacyPlaceholderTest.java new file mode 100644 index 0000000..a27f717 --- /dev/null +++ b/src/test/java/sh/okx/rankup/legacy/LegacyPlaceholderTest.java @@ -0,0 +1,24 @@ +package sh.okx.rankup.legacy; + +import be.seeseemelk.mockbukkit.entity.PlayerMock; +import org.junit.Test; +import sh.okx.rankup.RankupTest; + +public class LegacyPlaceholderTest extends RankupTest { + public LegacyPlaceholderTest() { + super("legacy"); + } + + @Test + public void testLegacy() { + PlayerMock player = server.addPlayer("testPlayer"); + + plugin.getEconomy().setPlayer(player, 100); + player.setLevel(1); + + groupProvider.addGroup(player.getUniqueId(), "A"); + plugin.getHelper().rankup(player); + + player.assertSaid("testPlayer A B A-display last rank 1,000 900 4 1 3 25 75"); + } +} diff --git a/src/test/java/sh/okx/rankup/messages/MessageBuilderTest.java b/src/test/java/sh/okx/rankup/messages/MessageBuilderTest.java index f48d205..ee82f80 100644 --- a/src/test/java/sh/okx/rankup/messages/MessageBuilderTest.java +++ b/src/test/java/sh/okx/rankup/messages/MessageBuilderTest.java @@ -8,6 +8,6 @@ import static org.junit.Assert.assertThat; public class MessageBuilderTest { @Test public void testFailIfEmpty() { - assertThat(new MessageBuilder("").failIfEmpty(), instanceOf(NullMessageBuilder.class)); + assertThat(new StringMessageBuilder("").failIfEmpty(), instanceOf(NullMessageBuilder.class)); } } \ No newline at end of file diff --git a/src/test/java/sh/okx/rankup/messages/RankupPlaceholderTest.java b/src/test/java/sh/okx/rankup/messages/RankupPlaceholderTest.java new file mode 100644 index 0000000..66a49f5 --- /dev/null +++ b/src/test/java/sh/okx/rankup/messages/RankupPlaceholderTest.java @@ -0,0 +1,24 @@ +package sh.okx.rankup.messages; + +import be.seeseemelk.mockbukkit.entity.PlayerMock; +import org.junit.Test; +import sh.okx.rankup.RankupTest; + +public class RankupPlaceholderTest extends RankupTest { + @Test + public void testSuccessPublicIsSame() { + PlayerMock player = server.addPlayer(); + PlayerMock receiver = server.addPlayer(); + + plugin.getEconomy().setPlayer(player, 1000); + + groupProvider.addGroup(player.getUniqueId(), "A"); + plugin.getHelper().rankup(player); + + // success-public message must be the same for both players + player.assertSaid(receiver.nextMessage()); + + // receiver does not receive success-private + receiver.assertNoMoreSaid(); + } +} diff --git a/src/test/java/sh/okx/rankup/pebble/PebbleTest.java b/src/test/java/sh/okx/rankup/pebble/PebbleTest.java new file mode 100644 index 0000000..add5a02 --- /dev/null +++ b/src/test/java/sh/okx/rankup/pebble/PebbleTest.java @@ -0,0 +1,26 @@ +package sh.okx.rankup.pebble; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.Test; +import sh.okx.rankup.text.pebble.PebbleTextProcessor; + +public class PebbleTest { + @Test + public void testIndex() { + Map ctx = new HashMap<>(); + ctx.put("one", "2"); + List list = new ArrayList<>(); + list.add("L0"); + list.add("L1"); + list.add("L2"); + list.add("L3"); + ctx.put("list", list); + PebbleTextProcessor processor = new PebbleTextProcessor(ctx, null); + assertEquals("L2", processor.process("{{ list[one] }}")); + } +} diff --git a/src/test/resources/legacy/rankups.yml b/src/test/resources/legacy/rankups.yml new file mode 100644 index 0000000..46d7bfd --- /dev/null +++ b/src/test/resources/legacy/rankups.yml @@ -0,0 +1,10 @@ +A: + rank: 'A' + next: 'B' + display-name: 'A-display' + requirements: + - 'money 1000' + - 'xp-level 4' + rankup: + # Test legacy placeholders + requirements-not-met: '{PLAYER} {OLD_RANK} {RANK} {OLD_RANK_NAME} {RANK_NAME} {MONEY} {MONEY_NEEDED} {AMOUNT xp-level} {AMOUNT_DONE xp-level} {AMOUNT_NEEDED xp-level} {PERCENT_DONE xp-level} {PERCENT_LEFT xp-level}' \ No newline at end of file