add code
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'net.ltgt.apt' version '0.10'
|
||||
}
|
||||
|
||||
group 'sh.okx'
|
||||
version '3.0-alpha'
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
maven {
|
||||
url 'http://nexus.hc.to/content/repositories/pub_releases'
|
||||
}
|
||||
maven {
|
||||
url 'https://hub.spigotmc.org/nexus/content/groups/public/'
|
||||
}
|
||||
maven {
|
||||
url 'http://repo.extendedclip.com/content/repositories/placeholderapi/'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testCompile group: 'junit', name: 'junit', version: '4.12'
|
||||
testCompile 'org.mockito:mockito-core:2.+'
|
||||
|
||||
compileOnly 'org.projectlombok:lombok:1.18.2'
|
||||
apt "org.projectlombok:lombok:1.18.2"
|
||||
|
||||
compile 'org.spigotmc:spigot-api:1.13-R0.1-SNAPSHOT'
|
||||
compile('net.milkbowl.vault:VaultAPI:1.6') {
|
||||
exclude group: 'org.bukkit'
|
||||
}
|
||||
compile 'me.clip:placeholderapi:2.9.+'
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
+6
@@ -0,0 +1,6 @@
|
||||
#Tue Aug 21 18:52:26 BST 2018
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
|
||||
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
Vendored
+84
@@ -0,0 +1,84 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -0,0 +1,2 @@
|
||||
rootProject.name = 'Rankup'
|
||||
|
||||
@@ -0,0 +1,926 @@
|
||||
package sh.okx.rankup;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.ServicePriority;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
/**
|
||||
* bStats collects some data for plugin authors.
|
||||
* <p>
|
||||
* Check out https://bStats.org/ to learn more about bStats!
|
||||
*/
|
||||
public class Metrics {
|
||||
|
||||
// The version of this bStats class
|
||||
public static final int B_STATS_VERSION = 1;
|
||||
|
||||
// The url to which the data is sent
|
||||
private static final String URL = "https://bStats.org/submitData";
|
||||
|
||||
// Should failed requests be logged?
|
||||
private static boolean logFailedRequests;
|
||||
|
||||
// The uuid of the server
|
||||
private static String serverUUID;
|
||||
|
||||
// The plugin
|
||||
private final JavaPlugin plugin;
|
||||
|
||||
// A list with all custom charts
|
||||
private final List<CustomChart> charts = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param plugin The plugin which stats should be submitted.
|
||||
*/
|
||||
public Metrics(JavaPlugin plugin) {
|
||||
if (plugin == null) {
|
||||
throw new IllegalArgumentException("Plugin cannot be null!");
|
||||
}
|
||||
this.plugin = plugin;
|
||||
|
||||
// Get the config file
|
||||
File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
|
||||
File configFile = new File(bStatsFolder, "config.yml");
|
||||
YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
|
||||
|
||||
// Check if the config file exists
|
||||
if (!config.isSet("serverUuid")) {
|
||||
|
||||
// Add default values
|
||||
config.addDefault("enabled", true);
|
||||
// Every server gets it's unique random id.
|
||||
config.addDefault("serverUuid", UUID.randomUUID().toString());
|
||||
// Should failed request be logged?
|
||||
config.addDefault("logFailedRequests", false);
|
||||
|
||||
// Inform the server owners about bStats
|
||||
config.options().header(
|
||||
"bStats collects some data for plugin authors like how many servers are using their plugins.\n" +
|
||||
"To honor their work, you should not disable it.\n" +
|
||||
"This has nearly no effect on the server performance!\n" +
|
||||
"Check out https://bStats.org/ to learn more :)"
|
||||
).copyDefaults(true);
|
||||
try {
|
||||
config.save(configFile);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
// Load the data
|
||||
serverUUID = config.getString("serverUuid");
|
||||
logFailedRequests = config.getBoolean("logFailedRequests", false);
|
||||
if (config.getBoolean("enabled", true)) {
|
||||
boolean found = false;
|
||||
// Search for all other bStats Metrics classes to see if we are the first one
|
||||
for (Class<?> service : Bukkit.getServicesManager().getKnownServices()) {
|
||||
try {
|
||||
service.getField("B_STATS_VERSION"); // Our identifier :)
|
||||
found = true; // We aren't the first
|
||||
break;
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
}
|
||||
}
|
||||
// Register our service
|
||||
Bukkit.getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal);
|
||||
if (!found) {
|
||||
// We are the first!
|
||||
startSubmitting();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a custom chart.
|
||||
*
|
||||
* @param chart The chart to add.
|
||||
*/
|
||||
public void addCustomChart(CustomChart chart) {
|
||||
if (chart == null) {
|
||||
throw new IllegalArgumentException("Chart cannot be null!");
|
||||
}
|
||||
charts.add(chart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the Scheduler which submits our data every 30 minutes.
|
||||
*/
|
||||
private void startSubmitting() {
|
||||
final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags
|
||||
timer.scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!plugin.isEnabled()) { // Plugin was disabled
|
||||
timer.cancel();
|
||||
return;
|
||||
}
|
||||
// Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler
|
||||
// Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;)
|
||||
Bukkit.getScheduler().runTask(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
submitData();
|
||||
}
|
||||
});
|
||||
}
|
||||
}, 1000 * 60 * 5, 1000 * 60 * 30);
|
||||
// Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start
|
||||
// WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted!
|
||||
// WARNING: Just don't do it!
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin specific data.
|
||||
* This method is called using Reflection.
|
||||
*
|
||||
* @return The plugin specific data.
|
||||
*/
|
||||
public JSONObject getPluginData() {
|
||||
JSONObject data = new JSONObject();
|
||||
|
||||
String pluginName = plugin.getDescription().getName();
|
||||
String pluginVersion = plugin.getDescription().getVersion();
|
||||
|
||||
data.put("pluginName", pluginName); // Append the name of the plugin
|
||||
data.put("pluginVersion", pluginVersion); // Append the version of the plugin
|
||||
JSONArray customCharts = new JSONArray();
|
||||
for (CustomChart customChart : charts) {
|
||||
// Add the data of the custom charts
|
||||
JSONObject chart = customChart.getRequestJsonObject();
|
||||
if (chart == null) { // If the chart is null, we skip it
|
||||
continue;
|
||||
}
|
||||
customCharts.add(chart);
|
||||
}
|
||||
data.put("customCharts", customCharts);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the server specific data.
|
||||
*
|
||||
* @return The server specific data.
|
||||
*/
|
||||
private JSONObject getServerData() {
|
||||
// Minecraft specific data
|
||||
int playerAmount = Bukkit.getOnlinePlayers().size();
|
||||
int onlineMode = Bukkit.getOnlineMode() ? 1 : 0;
|
||||
String bukkitVersion = org.bukkit.Bukkit.getVersion();
|
||||
bukkitVersion = bukkitVersion.substring(bukkitVersion.indexOf("MC: ") + 4, bukkitVersion.length() - 1);
|
||||
|
||||
// OS/Java specific data
|
||||
String javaVersion = System.getProperty("java.version");
|
||||
String osName = System.getProperty("os.name");
|
||||
String osArch = System.getProperty("os.arch");
|
||||
String osVersion = System.getProperty("os.version");
|
||||
int coreCount = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
JSONObject data = new JSONObject();
|
||||
|
||||
data.put("serverUUID", serverUUID);
|
||||
|
||||
data.put("playerAmount", playerAmount);
|
||||
data.put("onlineMode", onlineMode);
|
||||
data.put("bukkitVersion", bukkitVersion);
|
||||
|
||||
data.put("javaVersion", javaVersion);
|
||||
data.put("osName", osName);
|
||||
data.put("osArch", osArch);
|
||||
data.put("osVersion", osVersion);
|
||||
data.put("coreCount", coreCount);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects the data and sends it afterwards.
|
||||
*/
|
||||
private void submitData() {
|
||||
final JSONObject data = getServerData();
|
||||
|
||||
JSONArray pluginData = new JSONArray();
|
||||
// Search for all other bStats Metrics classes to get their plugin data
|
||||
for (Class<?> service : Bukkit.getServicesManager().getKnownServices()) {
|
||||
try {
|
||||
service.getField("B_STATS_VERSION"); // Our identifier :)
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
continue; // Continue "searching"
|
||||
}
|
||||
// Found one!
|
||||
try {
|
||||
pluginData.add(service.getMethod("getPluginData").invoke(Bukkit.getServicesManager().load(service)));
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
data.put("plugins", pluginData);
|
||||
|
||||
// Create a new thread for the connection to the bStats server
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// Send the data
|
||||
sendData(data);
|
||||
} catch (Exception e) {
|
||||
// Something went wrong! :(
|
||||
if (logFailedRequests) {
|
||||
plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the data to the bStats server.
|
||||
*
|
||||
* @param data The data to send.
|
||||
* @throws Exception If the request failed.
|
||||
*/
|
||||
private static void sendData(JSONObject data) throws Exception {
|
||||
if (data == null) {
|
||||
throw new IllegalArgumentException("Data cannot be null!");
|
||||
}
|
||||
if (Bukkit.isPrimaryThread()) {
|
||||
throw new IllegalAccessException("This method must not be called from the main thread!");
|
||||
}
|
||||
HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection();
|
||||
|
||||
// Compress the data to save bandwidth
|
||||
byte[] compressedData = compress(data.toString());
|
||||
|
||||
// Add headers
|
||||
connection.setRequestMethod("POST");
|
||||
connection.addRequestProperty("Accept", "application/json");
|
||||
connection.addRequestProperty("Connection", "close");
|
||||
connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request
|
||||
connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length));
|
||||
connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format
|
||||
connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION);
|
||||
|
||||
// Send data
|
||||
connection.setDoOutput(true);
|
||||
DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
|
||||
outputStream.write(compressedData);
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
|
||||
connection.getInputStream().close(); // We don't care about the response - Just send our data :)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gzips the given String.
|
||||
*
|
||||
* @param str The string to gzip.
|
||||
* @return The gzipped String.
|
||||
* @throws IOException If the compression failed.
|
||||
*/
|
||||
private static byte[] compress(final String str) throws IOException {
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
GZIPOutputStream gzip = new GZIPOutputStream(outputStream);
|
||||
gzip.write(str.getBytes("UTF-8"));
|
||||
gzip.close();
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a custom chart.
|
||||
*/
|
||||
public static abstract class CustomChart {
|
||||
|
||||
// The id of the chart
|
||||
protected final String chartId;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
*/
|
||||
public CustomChart(String chartId) {
|
||||
if (chartId == null || chartId.isEmpty()) {
|
||||
throw new IllegalArgumentException("ChartId cannot be null or empty!");
|
||||
}
|
||||
this.chartId = chartId;
|
||||
}
|
||||
|
||||
protected JSONObject getRequestJsonObject() {
|
||||
JSONObject chart = new JSONObject();
|
||||
chart.put("chartId", chartId);
|
||||
try {
|
||||
JSONObject data = getChartData();
|
||||
if (data == null) {
|
||||
// If the data is null we don't send the chart.
|
||||
return null;
|
||||
}
|
||||
chart.put("data", data);
|
||||
} catch (Throwable t) {
|
||||
if (logFailedRequests) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return chart;
|
||||
}
|
||||
|
||||
protected abstract JSONObject getChartData();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a custom simple pie.
|
||||
*/
|
||||
public static abstract class SimplePie extends CustomChart {
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
*/
|
||||
public SimplePie(String chartId) {
|
||||
super(chartId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the pie.
|
||||
*
|
||||
* @return The value of the pie.
|
||||
*/
|
||||
public abstract String getValue();
|
||||
|
||||
@Override
|
||||
protected JSONObject getChartData() {
|
||||
JSONObject data = new JSONObject();
|
||||
String value = getValue();
|
||||
if (value == null || value.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
data.put("value", value);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a custom advanced pie.
|
||||
*/
|
||||
public static abstract class AdvancedPie extends CustomChart {
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
*/
|
||||
public AdvancedPie(String chartId) {
|
||||
super(chartId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the values of the pie.
|
||||
*
|
||||
* @param valueMap Just an empty map. The only reason it exists is to make your life easier.
|
||||
* You don't have to create a map yourself!
|
||||
* @return The values of the pie.
|
||||
*/
|
||||
public abstract HashMap<String, Integer> getValues(HashMap<String, Integer> valueMap);
|
||||
|
||||
@Override
|
||||
protected JSONObject getChartData() {
|
||||
JSONObject data = new JSONObject();
|
||||
JSONObject values = new JSONObject();
|
||||
HashMap<String, Integer> map = getValues(new HashMap<String, Integer>());
|
||||
if (map == null || map.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
boolean allSkipped = true;
|
||||
for (Map.Entry<String, Integer> entry : map.entrySet()) {
|
||||
if (entry.getValue() == 0) {
|
||||
continue; // Skip this invalid
|
||||
}
|
||||
allSkipped = false;
|
||||
values.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
if (allSkipped) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
data.put("values", values);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a custom single line chart.
|
||||
*/
|
||||
public static abstract class SingleLineChart extends CustomChart {
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
*/
|
||||
public SingleLineChart(String chartId) {
|
||||
super(chartId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the chart.
|
||||
*
|
||||
* @return The value of the chart.
|
||||
*/
|
||||
public abstract int getValue();
|
||||
|
||||
@Override
|
||||
protected JSONObject getChartData() {
|
||||
JSONObject data = new JSONObject();
|
||||
int value = getValue();
|
||||
if (value == 0) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
data.put("value", value);
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a custom multi line chart.
|
||||
*/
|
||||
public static abstract class MultiLineChart extends CustomChart {
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
*/
|
||||
public MultiLineChart(String chartId) {
|
||||
super(chartId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the values of the chart.
|
||||
*
|
||||
* @param valueMap Just an empty map. The only reason it exists is to make your life easier.
|
||||
* You don't have to create a map yourself!
|
||||
* @return The values of the chart.
|
||||
*/
|
||||
public abstract HashMap<String, Integer> getValues(HashMap<String, Integer> valueMap);
|
||||
|
||||
@Override
|
||||
protected JSONObject getChartData() {
|
||||
JSONObject data = new JSONObject();
|
||||
JSONObject values = new JSONObject();
|
||||
HashMap<String, Integer> map = getValues(new HashMap<String, Integer>());
|
||||
if (map == null || map.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
boolean allSkipped = true;
|
||||
for (Map.Entry<String, Integer> entry : map.entrySet()) {
|
||||
if (entry.getValue() == 0) {
|
||||
continue; // Skip this invalid
|
||||
}
|
||||
allSkipped = false;
|
||||
values.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
if (allSkipped) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
data.put("values", values);
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a custom simple map chart.
|
||||
*/
|
||||
public static abstract class SimpleMapChart extends CustomChart {
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
*/
|
||||
public SimpleMapChart(String chartId) {
|
||||
super(chartId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the chart.
|
||||
*
|
||||
* @return The value of the chart.
|
||||
*/
|
||||
public abstract Country getValue();
|
||||
|
||||
@Override
|
||||
protected JSONObject getChartData() {
|
||||
JSONObject data = new JSONObject();
|
||||
Country value = getValue();
|
||||
|
||||
if (value == null) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
data.put("value", value.getCountryIsoTag());
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a custom advanced map chart.
|
||||
*/
|
||||
public static abstract class AdvancedMapChart extends CustomChart {
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
*/
|
||||
public AdvancedMapChart(String chartId) {
|
||||
super(chartId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the chart.
|
||||
*
|
||||
* @param valueMap Just an empty map. The only reason it exists is to make your life easier.
|
||||
* You don't have to create a map yourself!
|
||||
* @return The value of the chart.
|
||||
*/
|
||||
public abstract HashMap<Country, Integer> getValues(HashMap<Country, Integer> valueMap);
|
||||
|
||||
@Override
|
||||
protected JSONObject getChartData() {
|
||||
JSONObject data = new JSONObject();
|
||||
JSONObject values = new JSONObject();
|
||||
HashMap<Country, Integer> map = getValues(new HashMap<Country, Integer>());
|
||||
if (map == null || map.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
boolean allSkipped = true;
|
||||
for (Map.Entry<Country, Integer> entry : map.entrySet()) {
|
||||
if (entry.getValue() == 0) {
|
||||
continue; // Skip this invalid
|
||||
}
|
||||
allSkipped = false;
|
||||
values.put(entry.getKey().getCountryIsoTag(), entry.getValue());
|
||||
}
|
||||
if (allSkipped) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
data.put("values", values);
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A enum which is used for custom maps.
|
||||
*/
|
||||
public enum Country {
|
||||
|
||||
/**
|
||||
* bStats will use the country of the server.
|
||||
*/
|
||||
AUTO_DETECT("AUTO", "Auto Detected"),
|
||||
|
||||
ANDORRA("AD", "Andorra"),
|
||||
UNITED_ARAB_EMIRATES("AE", "United Arab Emirates"),
|
||||
AFGHANISTAN("AF", "Afghanistan"),
|
||||
ANTIGUA_AND_BARBUDA("AG", "Antigua and Barbuda"),
|
||||
ANGUILLA("AI", "Anguilla"),
|
||||
ALBANIA("AL", "Albania"),
|
||||
ARMENIA("AM", "Armenia"),
|
||||
NETHERLANDS_ANTILLES("AN", "Netherlands Antilles"),
|
||||
ANGOLA("AO", "Angola"),
|
||||
ANTARCTICA("AQ", "Antarctica"),
|
||||
ARGENTINA("AR", "Argentina"),
|
||||
AMERICAN_SAMOA("AS", "American Samoa"),
|
||||
AUSTRIA("AT", "Austria"),
|
||||
AUSTRALIA("AU", "Australia"),
|
||||
ARUBA("AW", "Aruba"),
|
||||
ÅLAND_ISLANDS("AX", "Åland Islands"),
|
||||
AZERBAIJAN("AZ", "Azerbaijan"),
|
||||
BOSNIA_AND_HERZEGOVINA("BA", "Bosnia and Herzegovina"),
|
||||
BARBADOS("BB", "Barbados"),
|
||||
BANGLADESH("BD", "Bangladesh"),
|
||||
BELGIUM("BE", "Belgium"),
|
||||
BURKINA_FASO("BF", "Burkina Faso"),
|
||||
BULGARIA("BG", "Bulgaria"),
|
||||
BAHRAIN("BH", "Bahrain"),
|
||||
BURUNDI("BI", "Burundi"),
|
||||
BENIN("BJ", "Benin"),
|
||||
SAINT_BARTHÉLEMY("BL", "Saint Barthélemy"),
|
||||
BERMUDA("BM", "Bermuda"),
|
||||
BRUNEI("BN", "Brunei"),
|
||||
BOLIVIA("BO", "Bolivia"),
|
||||
BONAIRE_SINT_EUSTATIUS_AND_SABA("BQ", "Bonaire, Sint Eustatius and Saba"),
|
||||
BRAZIL("BR", "Brazil"),
|
||||
BAHAMAS("BS", "Bahamas"),
|
||||
BHUTAN("BT", "Bhutan"),
|
||||
BOUVET_ISLAND("BV", "Bouvet Island"),
|
||||
BOTSWANA("BW", "Botswana"),
|
||||
BELARUS("BY", "Belarus"),
|
||||
BELIZE("BZ", "Belize"),
|
||||
CANADA("CA", "Canada"),
|
||||
COCOS_ISLANDS("CC", "Cocos Islands"),
|
||||
THE_DEMOCRATIC_REPUBLIC_OF_CONGO("CD", "The Democratic Republic Of Congo"),
|
||||
CENTRAL_AFRICAN_REPUBLIC("CF", "Central African Republic"),
|
||||
CONGO("CG", "Congo"),
|
||||
SWITZERLAND("CH", "Switzerland"),
|
||||
CÔTE_D_IVOIRE("CI", "Côte d'Ivoire"),
|
||||
COOK_ISLANDS("CK", "Cook Islands"),
|
||||
CHILE("CL", "Chile"),
|
||||
CAMEROON("CM", "Cameroon"),
|
||||
CHINA("CN", "China"),
|
||||
COLOMBIA("CO", "Colombia"),
|
||||
COSTA_RICA("CR", "Costa Rica"),
|
||||
CUBA("CU", "Cuba"),
|
||||
CAPE_VERDE("CV", "Cape Verde"),
|
||||
CURAÇAO("CW", "Curaçao"),
|
||||
CHRISTMAS_ISLAND("CX", "Christmas Island"),
|
||||
CYPRUS("CY", "Cyprus"),
|
||||
CZECH_REPUBLIC("CZ", "Czech Republic"),
|
||||
GERMANY("DE", "Germany"),
|
||||
DJIBOUTI("DJ", "Djibouti"),
|
||||
DENMARK("DK", "Denmark"),
|
||||
DOMINICA("DM", "Dominica"),
|
||||
DOMINICAN_REPUBLIC("DO", "Dominican Republic"),
|
||||
ALGERIA("DZ", "Algeria"),
|
||||
ECUADOR("EC", "Ecuador"),
|
||||
ESTONIA("EE", "Estonia"),
|
||||
EGYPT("EG", "Egypt"),
|
||||
WESTERN_SAHARA("EH", "Western Sahara"),
|
||||
ERITREA("ER", "Eritrea"),
|
||||
SPAIN("ES", "Spain"),
|
||||
ETHIOPIA("ET", "Ethiopia"),
|
||||
FINLAND("FI", "Finland"),
|
||||
FIJI("FJ", "Fiji"),
|
||||
FALKLAND_ISLANDS("FK", "Falkland Islands"),
|
||||
MICRONESIA("FM", "Micronesia"),
|
||||
FAROE_ISLANDS("FO", "Faroe Islands"),
|
||||
FRANCE("FR", "France"),
|
||||
GABON("GA", "Gabon"),
|
||||
UNITED_KINGDOM("GB", "United Kingdom"),
|
||||
GRENADA("GD", "Grenada"),
|
||||
GEORGIA("GE", "Georgia"),
|
||||
FRENCH_GUIANA("GF", "French Guiana"),
|
||||
GUERNSEY("GG", "Guernsey"),
|
||||
GHANA("GH", "Ghana"),
|
||||
GIBRALTAR("GI", "Gibraltar"),
|
||||
GREENLAND("GL", "Greenland"),
|
||||
GAMBIA("GM", "Gambia"),
|
||||
GUINEA("GN", "Guinea"),
|
||||
GUADELOUPE("GP", "Guadeloupe"),
|
||||
EQUATORIAL_GUINEA("GQ", "Equatorial Guinea"),
|
||||
GREECE("GR", "Greece"),
|
||||
SOUTH_GEORGIA_AND_THE_SOUTH_SANDWICH_ISLANDS("GS", "South Georgia And The South Sandwich Islands"),
|
||||
GUATEMALA("GT", "Guatemala"),
|
||||
GUAM("GU", "Guam"),
|
||||
GUINEA_BISSAU("GW", "Guinea-Bissau"),
|
||||
GUYANA("GY", "Guyana"),
|
||||
HONG_KONG("HK", "Hong Kong"),
|
||||
HEARD_ISLAND_AND_MCDONALD_ISLANDS("HM", "Heard Island And McDonald Islands"),
|
||||
HONDURAS("HN", "Honduras"),
|
||||
CROATIA("HR", "Croatia"),
|
||||
HAITI("HT", "Haiti"),
|
||||
HUNGARY("HU", "Hungary"),
|
||||
INDONESIA("ID", "Indonesia"),
|
||||
IRELAND("IE", "Ireland"),
|
||||
ISRAEL("IL", "Israel"),
|
||||
ISLE_OF_MAN("IM", "Isle Of Man"),
|
||||
INDIA("IN", "India"),
|
||||
BRITISH_INDIAN_OCEAN_TERRITORY("IO", "British Indian Ocean Territory"),
|
||||
IRAQ("IQ", "Iraq"),
|
||||
IRAN("IR", "Iran"),
|
||||
ICELAND("IS", "Iceland"),
|
||||
ITALY("IT", "Italy"),
|
||||
JERSEY("JE", "Jersey"),
|
||||
JAMAICA("JM", "Jamaica"),
|
||||
JORDAN("JO", "Jordan"),
|
||||
JAPAN("JP", "Japan"),
|
||||
KENYA("KE", "Kenya"),
|
||||
KYRGYZSTAN("KG", "Kyrgyzstan"),
|
||||
CAMBODIA("KH", "Cambodia"),
|
||||
KIRIBATI("KI", "Kiribati"),
|
||||
COMOROS("KM", "Comoros"),
|
||||
SAINT_KITTS_AND_NEVIS("KN", "Saint Kitts And Nevis"),
|
||||
NORTH_KOREA("KP", "North Korea"),
|
||||
SOUTH_KOREA("KR", "South Korea"),
|
||||
KUWAIT("KW", "Kuwait"),
|
||||
CAYMAN_ISLANDS("KY", "Cayman Islands"),
|
||||
KAZAKHSTAN("KZ", "Kazakhstan"),
|
||||
LAOS("LA", "Laos"),
|
||||
LEBANON("LB", "Lebanon"),
|
||||
SAINT_LUCIA("LC", "Saint Lucia"),
|
||||
LIECHTENSTEIN("LI", "Liechtenstein"),
|
||||
SRI_LANKA("LK", "Sri Lanka"),
|
||||
LIBERIA("LR", "Liberia"),
|
||||
LESOTHO("LS", "Lesotho"),
|
||||
LITHUANIA("LT", "Lithuania"),
|
||||
LUXEMBOURG("LU", "Luxembourg"),
|
||||
LATVIA("LV", "Latvia"),
|
||||
LIBYA("LY", "Libya"),
|
||||
MOROCCO("MA", "Morocco"),
|
||||
MONACO("MC", "Monaco"),
|
||||
MOLDOVA("MD", "Moldova"),
|
||||
MONTENEGRO("ME", "Montenegro"),
|
||||
SAINT_MARTIN("MF", "Saint Martin"),
|
||||
MADAGASCAR("MG", "Madagascar"),
|
||||
MARSHALL_ISLANDS("MH", "Marshall Islands"),
|
||||
MACEDONIA("MK", "Macedonia"),
|
||||
MALI("ML", "Mali"),
|
||||
MYANMAR("MM", "Myanmar"),
|
||||
MONGOLIA("MN", "Mongolia"),
|
||||
MACAO("MO", "Macao"),
|
||||
NORTHERN_MARIANA_ISLANDS("MP", "Northern Mariana Islands"),
|
||||
MARTINIQUE("MQ", "Martinique"),
|
||||
MAURITANIA("MR", "Mauritania"),
|
||||
MONTSERRAT("MS", "Montserrat"),
|
||||
MALTA("MT", "Malta"),
|
||||
MAURITIUS("MU", "Mauritius"),
|
||||
MALDIVES("MV", "Maldives"),
|
||||
MALAWI("MW", "Malawi"),
|
||||
MEXICO("MX", "Mexico"),
|
||||
MALAYSIA("MY", "Malaysia"),
|
||||
MOZAMBIQUE("MZ", "Mozambique"),
|
||||
NAMIBIA("NA", "Namibia"),
|
||||
NEW_CALEDONIA("NC", "New Caledonia"),
|
||||
NIGER("NE", "Niger"),
|
||||
NORFOLK_ISLAND("NF", "Norfolk Island"),
|
||||
NIGERIA("NG", "Nigeria"),
|
||||
NICARAGUA("NI", "Nicaragua"),
|
||||
NETHERLANDS("NL", "Netherlands"),
|
||||
NORWAY("NO", "Norway"),
|
||||
NEPAL("NP", "Nepal"),
|
||||
NAURU("NR", "Nauru"),
|
||||
NIUE("NU", "Niue"),
|
||||
NEW_ZEALAND("NZ", "New Zealand"),
|
||||
OMAN("OM", "Oman"),
|
||||
PANAMA("PA", "Panama"),
|
||||
PERU("PE", "Peru"),
|
||||
FRENCH_POLYNESIA("PF", "French Polynesia"),
|
||||
PAPUA_NEW_GUINEA("PG", "Papua New Guinea"),
|
||||
PHILIPPINES("PH", "Philippines"),
|
||||
PAKISTAN("PK", "Pakistan"),
|
||||
POLAND("PL", "Poland"),
|
||||
SAINT_PIERRE_AND_MIQUELON("PM", "Saint Pierre And Miquelon"),
|
||||
PITCAIRN("PN", "Pitcairn"),
|
||||
PUERTO_RICO("PR", "Puerto Rico"),
|
||||
PALESTINE("PS", "Palestine"),
|
||||
PORTUGAL("PT", "Portugal"),
|
||||
PALAU("PW", "Palau"),
|
||||
PARAGUAY("PY", "Paraguay"),
|
||||
QATAR("QA", "Qatar"),
|
||||
REUNION("RE", "Reunion"),
|
||||
ROMANIA("RO", "Romania"),
|
||||
SERBIA("RS", "Serbia"),
|
||||
RUSSIA("RU", "Russia"),
|
||||
RWANDA("RW", "Rwanda"),
|
||||
SAUDI_ARABIA("SA", "Saudi Arabia"),
|
||||
SOLOMON_ISLANDS("SB", "Solomon Islands"),
|
||||
SEYCHELLES("SC", "Seychelles"),
|
||||
SUDAN("SD", "Sudan"),
|
||||
SWEDEN("SE", "Sweden"),
|
||||
SINGAPORE("SG", "Singapore"),
|
||||
SAINT_HELENA("SH", "Saint Helena"),
|
||||
SLOVENIA("SI", "Slovenia"),
|
||||
SVALBARD_AND_JAN_MAYEN("SJ", "Svalbard And Jan Mayen"),
|
||||
SLOVAKIA("SK", "Slovakia"),
|
||||
SIERRA_LEONE("SL", "Sierra Leone"),
|
||||
SAN_MARINO("SM", "San Marino"),
|
||||
SENEGAL("SN", "Senegal"),
|
||||
SOMALIA("SO", "Somalia"),
|
||||
SURINAME("SR", "Suriname"),
|
||||
SOUTH_SUDAN("SS", "South Sudan"),
|
||||
SAO_TOME_AND_PRINCIPE("ST", "Sao Tome And Principe"),
|
||||
EL_SALVADOR("SV", "El Salvador"),
|
||||
SINT_MAARTEN_DUTCH_PART("SX", "Sint Maarten (Dutch part)"),
|
||||
SYRIA("SY", "Syria"),
|
||||
SWAZILAND("SZ", "Swaziland"),
|
||||
TURKS_AND_CAICOS_ISLANDS("TC", "Turks And Caicos Islands"),
|
||||
CHAD("TD", "Chad"),
|
||||
FRENCH_SOUTHERN_TERRITORIES("TF", "French Southern Territories"),
|
||||
TOGO("TG", "Togo"),
|
||||
THAILAND("TH", "Thailand"),
|
||||
TAJIKISTAN("TJ", "Tajikistan"),
|
||||
TOKELAU("TK", "Tokelau"),
|
||||
TIMOR_LESTE("TL", "Timor-Leste"),
|
||||
TURKMENISTAN("TM", "Turkmenistan"),
|
||||
TUNISIA("TN", "Tunisia"),
|
||||
TONGA("TO", "Tonga"),
|
||||
TURKEY("TR", "Turkey"),
|
||||
TRINIDAD_AND_TOBAGO("TT", "Trinidad and Tobago"),
|
||||
TUVALU("TV", "Tuvalu"),
|
||||
TAIWAN("TW", "Taiwan"),
|
||||
TANZANIA("TZ", "Tanzania"),
|
||||
UKRAINE("UA", "Ukraine"),
|
||||
UGANDA("UG", "Uganda"),
|
||||
UNITED_STATES_MINOR_OUTLYING_ISLANDS("UM", "United States Minor Outlying Islands"),
|
||||
UNITED_STATES("US", "United States"),
|
||||
URUGUAY("UY", "Uruguay"),
|
||||
UZBEKISTAN("UZ", "Uzbekistan"),
|
||||
VATICAN("VA", "Vatican"),
|
||||
SAINT_VINCENT_AND_THE_GRENADINES("VC", "Saint Vincent And The Grenadines"),
|
||||
VENEZUELA("VE", "Venezuela"),
|
||||
BRITISH_VIRGIN_ISLANDS("VG", "British Virgin Islands"),
|
||||
U_S__VIRGIN_ISLANDS("VI", "U.S. Virgin Islands"),
|
||||
VIETNAM("VN", "Vietnam"),
|
||||
VANUATU("VU", "Vanuatu"),
|
||||
WALLIS_AND_FUTUNA("WF", "Wallis And Futuna"),
|
||||
SAMOA("WS", "Samoa"),
|
||||
YEMEN("YE", "Yemen"),
|
||||
MAYOTTE("YT", "Mayotte"),
|
||||
SOUTH_AFRICA("ZA", "South Africa"),
|
||||
ZAMBIA("ZM", "Zambia"),
|
||||
ZIMBABWE("ZW", "Zimbabwe");
|
||||
|
||||
private String isoTag;
|
||||
private String name;
|
||||
|
||||
Country(String isoTag, String name) {
|
||||
this.isoTag = isoTag;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the country.
|
||||
*
|
||||
* @return The name of the country.
|
||||
*/
|
||||
public String getCountryName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the iso tag of the country.
|
||||
*
|
||||
* @return The iso tag of the country.
|
||||
*/
|
||||
public String getCountryIsoTag() {
|
||||
return isoTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a country by it's iso tag.
|
||||
*
|
||||
* @param isoTag The iso tag of the county.
|
||||
* @return The country with the given iso tag or <code>null</code> if unknown.
|
||||
*/
|
||||
public static Country byIsoTag(String isoTag) {
|
||||
for (Country country : Country.values()) {
|
||||
if (country.getCountryIsoTag().equals(isoTag)) {
|
||||
return country;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a country by a locale.
|
||||
*
|
||||
* @param locale The locale.
|
||||
* @return The country from the giben locale or <code>null</code> if unknown country or
|
||||
* if the locale does not contain a country.
|
||||
*/
|
||||
public static Country byLocale(Locale locale) {
|
||||
return byIsoTag(locale.getCountry());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package sh.okx.rankup;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import net.milkbowl.vault.economy.Economy;
|
||||
import net.milkbowl.vault.permission.Permission;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.inventory.InventoryView;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import sh.okx.rankup.commands.InfoCommand;
|
||||
import sh.okx.rankup.commands.RankListCommand;
|
||||
import sh.okx.rankup.commands.RankupCommand;
|
||||
import sh.okx.rankup.gui.Gui;
|
||||
import sh.okx.rankup.gui.GuiListener;
|
||||
import sh.okx.rankup.messages.Message;
|
||||
import sh.okx.rankup.messages.MessageBuilder;
|
||||
import sh.okx.rankup.messages.Variable;
|
||||
import sh.okx.rankup.placeholders.Placeholders;
|
||||
import sh.okx.rankup.ranks.Rank;
|
||||
import sh.okx.rankup.ranks.Rankups;
|
||||
import sh.okx.rankup.ranks.requirements.PlaytimeHoursRequirement;
|
||||
import sh.okx.rankup.ranks.requirements.XpLevelRequirement;
|
||||
import sh.okx.rankup.ranks.requirements.MoneyRequirement;
|
||||
import sh.okx.rankup.ranks.requirements.RequirementRegistry;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class Rankup extends JavaPlugin {
|
||||
@Getter
|
||||
private Permission permissions;
|
||||
@Getter
|
||||
private Economy economy;
|
||||
/**
|
||||
* The registry for listing the requirements to /rankup.
|
||||
*/
|
||||
@Getter
|
||||
private RequirementRegistry requirementRegistry = new RequirementRegistry();
|
||||
@Getter
|
||||
private FileConfiguration messages;
|
||||
@Getter
|
||||
private FileConfiguration config;
|
||||
@Getter
|
||||
private Rankups rankups;
|
||||
@Getter
|
||||
private Placeholders placeholders;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
registerRequirements();
|
||||
setupPermissions();
|
||||
setupEconomy();
|
||||
reload();
|
||||
|
||||
Metrics metrics = new Metrics(this);
|
||||
metrics.addCustomChart(new Metrics.SimplePie("confirmation") {
|
||||
@Override
|
||||
public String getValue() {
|
||||
return getConfig().getString("confirmation.type");
|
||||
}
|
||||
});
|
||||
|
||||
getCommand("rankup").setExecutor(new RankupCommand(this));
|
||||
getCommand("rankup3").setExecutor(new InfoCommand(this));
|
||||
getCommand("ranks").setExecutor(new RankListCommand(this));
|
||||
getServer().getPluginManager().registerEvents(new GuiListener(this), this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
closeInventories();
|
||||
PlaceholderAPI.unregisterExpansion(placeholders);
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
closeInventories();
|
||||
loadConfigs();
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
|
||||
placeholders = new Placeholders(this);
|
||||
placeholders.register();
|
||||
}
|
||||
|
||||
if(config.getInt("version") != YamlConfiguration.loadConfiguration(getTextResource("config.yml")).getInt("version")) {
|
||||
getLogger().severe("You are using an outdated config!");
|
||||
getLogger().severe("This means that some things might not work!");
|
||||
getLogger().severe("To update, please rename your config files (or the folder they are in),");
|
||||
getLogger().severe("and run /rankup3 reload to generate a new config file.");
|
||||
getLogger().severe("If that does not work, restart your server.");
|
||||
getLogger().severe("You may then copy in your config values from the old config.");
|
||||
getLogger().severe("Check the changelog on the Rankup spigot page to see the changes.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes all rankup inventories on disable
|
||||
* so players cannot grab items from the inventory
|
||||
* on a plugin reload.
|
||||
*/
|
||||
private void closeInventories() {
|
||||
for(Player player : Bukkit.getOnlinePlayers()) {
|
||||
InventoryView view = player.getOpenInventory();
|
||||
if(view.getType() == InventoryType.CHEST
|
||||
&& view.getTopInventory().getHolder() instanceof Gui) {
|
||||
player.closeInventory();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadConfigs() {
|
||||
messages = loadConfig("messages.yml");
|
||||
config = loadConfig("config.yml");
|
||||
rankups = new Rankups(this, loadConfig("rankups.yml"));
|
||||
}
|
||||
|
||||
private FileConfiguration loadConfig(String name) {
|
||||
File file = new File(getDataFolder(), name);
|
||||
if (!file.exists()) {
|
||||
saveResource(name, false);
|
||||
}
|
||||
return YamlConfiguration.loadConfiguration(file);
|
||||
}
|
||||
|
||||
private void registerRequirements() {
|
||||
requirementRegistry.addRequirement(new MoneyRequirement(this, "money"));
|
||||
requirementRegistry.addRequirement(new XpLevelRequirement(this, "xp-level"));
|
||||
requirementRegistry.addRequirement(new PlaytimeHoursRequirement(this, "playtime-hours"));
|
||||
}
|
||||
|
||||
private void setupPermissions() {
|
||||
RegisteredServiceProvider<Permission> rsp = getServer().getServicesManager().getRegistration(Permission.class);
|
||||
permissions = rsp.getProvider();
|
||||
}
|
||||
|
||||
private void setupEconomy() {
|
||||
RegisteredServiceProvider<Economy> rsp = getServer().getServicesManager().getRegistration(Economy.class);
|
||||
if (rsp != null) {
|
||||
economy = rsp.getProvider();
|
||||
} else {
|
||||
getLogger().warning("No economy found.");
|
||||
}
|
||||
}
|
||||
|
||||
public MessageBuilder getMessage(Rank rank, Message message) {
|
||||
ConfigurationSection messages = rankups.getConfig()
|
||||
.getConfigurationSection(rank.getName());
|
||||
if(messages == null || !messages.isSet(message.getName())) {
|
||||
messages = this.messages;
|
||||
}
|
||||
return MessageBuilder.of(messages, message);
|
||||
}
|
||||
|
||||
public MessageBuilder getMessage(Message message) {
|
||||
return MessageBuilder.of(messages, message);
|
||||
}
|
||||
|
||||
public void rankup(Player player) {
|
||||
if(!checkRankup(player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Rank oldRank = rankups.getRank(player);
|
||||
Rank rank = rankups.nextRank(oldRank);
|
||||
|
||||
oldRank.applyRequirements(player);
|
||||
|
||||
permissions.playerRemoveGroup(null, player, oldRank.getRank());
|
||||
permissions.playerAddGroup(null, player, rank.getRank());
|
||||
|
||||
getMessage(oldRank, Message.SUCCESS_PUBLIC)
|
||||
.failIfEmpty()
|
||||
.replaceAll(player, oldRank, rank)
|
||||
.broadcast();
|
||||
getMessage(oldRank, Message.SUCCESS_PRIVATE)
|
||||
.failIfEmpty()
|
||||
.replaceAll(player, oldRank, rank)
|
||||
.send(player);
|
||||
|
||||
oldRank.runCommands(player, rank);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a player can rankup,
|
||||
* and if they can't, sends the player a message and returns false
|
||||
* @param player the player to check if they can rankup
|
||||
* @return true if the player can rankup, false otherwise
|
||||
*/
|
||||
public boolean checkRankup(Player player) {
|
||||
Rank rank = rankups.getRank(player);
|
||||
if (rank == null) { // check if in ladder
|
||||
getMessage(Message.NOT_IN_LADDER)
|
||||
.replace(Variable.PLAYER, player.getName())
|
||||
.send(player);
|
||||
return false;
|
||||
} else if (rank.isLastRank()) { // check if they are at the highest rank
|
||||
getMessage(rank, Message.NO_RANKUP)
|
||||
.replaceAll(player, rank)
|
||||
.send(player);
|
||||
return false;
|
||||
} else if (!rank.checkRequirements(player)) { // check if they can afford it
|
||||
MessageBuilder builder =
|
||||
getMessage(rank, Message.REQUIREMENTS_NOT_MET)
|
||||
.replaceAll(player, rank);
|
||||
if (economy != null) {
|
||||
double balance = economy.getBalance(player);
|
||||
builder = builder
|
||||
.replace(Variable.MONEY, balance)
|
||||
.replace(Variable.MONEY_NEEDED, rank.getRequirement("money").getAmount() - balance);
|
||||
}
|
||||
builder.send(player);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package sh.okx.rankup.commands;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.io.CharStreams;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.plugin.PluginDescriptionFile;
|
||||
import sh.okx.rankup.Rankup;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class InfoCommand implements CommandExecutor {
|
||||
private String versionMessage;
|
||||
private final Rankup plugin;
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if(args.length > 0) {
|
||||
if(args[0].equalsIgnoreCase("reload") && sender.hasPermission("rankup.reload")) {
|
||||
plugin.reload();
|
||||
sender.sendMessage(ChatColor.GREEN + "" + ChatColor.BOLD + "Rankup " + ChatColor.YELLOW + "Reloaded configuration files.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
PluginDescriptionFile description = plugin.getDescription();
|
||||
sender.sendMessage(
|
||||
ChatColor.GREEN + "" + ChatColor.BOLD + description.getName() + " " + description.getVersion() +
|
||||
ChatColor.YELLOW + " by " + ChatColor.BLUE + ChatColor.BOLD + String.join(", ", description.getAuthors()));
|
||||
if(sender.hasPermission("rankup.reload")) {
|
||||
sender.sendMessage(ChatColor.GREEN + "/" + label + " reload " + ChatColor.YELLOW + "Reloads configuration files.");
|
||||
}
|
||||
if(sender.hasPermission("rankup.checkversion")) {
|
||||
if(versionMessage == null) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "Checking version...");
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
String message;
|
||||
try {
|
||||
String latest = getLatestVersion();
|
||||
if (description.getVersion().equals(latest)) {
|
||||
message = ChatColor.GREEN + "You are on the latest version.";
|
||||
} else {
|
||||
message = ChatColor.YELLOW + "A new version is available: " + ChatColor.GOLD + latest
|
||||
+ "\nhttps://www.spigotmc.org/resources/rankup.17933/";
|
||||
}
|
||||
} catch (IOException e) {
|
||||
message = ChatColor.RED + "Error while checking version.";
|
||||
}
|
||||
versionMessage = message;
|
||||
Bukkit.getScheduler().runTask(plugin, () -> sender.sendMessage(versionMessage));
|
||||
});
|
||||
} else {
|
||||
sender.sendMessage(versionMessage);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getLatestVersion() throws IOException {
|
||||
URL url = new URL("https://api.spigotmc.org/legacy/update.php?resource=17933");
|
||||
String result = CharStreams.toString(new InputStreamReader(url.openStream(), Charsets.UTF_8));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package sh.okx.rankup.commands;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import sh.okx.rankup.Rankup;
|
||||
import sh.okx.rankup.messages.Message;
|
||||
import sh.okx.rankup.messages.MessageBuilder;
|
||||
import sh.okx.rankup.messages.Variable;
|
||||
import sh.okx.rankup.ranks.Rank;
|
||||
import sh.okx.rankup.ranks.Rankups;
|
||||
import sh.okx.rankup.ranks.requirements.Requirement;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class RankListCommand implements CommandExecutor {
|
||||
private final Rankup plugin;
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
Rankups rankups = plugin.getRankups();
|
||||
Rank playerRank = null;
|
||||
if(sender instanceof Player) {
|
||||
playerRank = rankups.getRank((Player) sender);
|
||||
}
|
||||
|
||||
sendHeaderFooter(sender, playerRank, Message.RANKS_HEADER);
|
||||
|
||||
int state = playerRank == null ? 2 : 0;
|
||||
Rank rank = rankups.getFirstRank();
|
||||
do {
|
||||
Rank next = rankups.nextRank(rank);
|
||||
if(rank.equals(playerRank)) {
|
||||
sendMessage(sender, 1, rank, next);
|
||||
state = 2;
|
||||
} else {
|
||||
sendMessage(sender, state, rank, next);
|
||||
}
|
||||
rank = next;
|
||||
} while(!rank.isLastRank());
|
||||
|
||||
sendHeaderFooter(sender, playerRank, Message.RANKS_FOOTER);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void sendHeaderFooter(CommandSender sender, Rank rank, Message type) {
|
||||
MessageBuilder builder = plugin.getMessage(type)
|
||||
.failIfEmpty();
|
||||
if(rank == null) {
|
||||
builder.replace(Variable.PLAYER, sender.getName());
|
||||
} else {
|
||||
builder.replaceAll(sender, rank);
|
||||
}
|
||||
builder.send(sender);
|
||||
}
|
||||
|
||||
private void sendMessage(CommandSender player, int state, Rank oldRank, Rank rank) {
|
||||
if(state == 0) {
|
||||
replaceCost(plugin.getMessage(oldRank, Message.RANKS_COMPLETE)
|
||||
.replaceAll(player, oldRank, rank), player, oldRank)
|
||||
.send(player);
|
||||
} else if(state == 1) {
|
||||
replaceCost(plugin.getMessage(oldRank, Message.RANKS_CURRENT)
|
||||
.replaceAll(player, oldRank, rank), player, oldRank)
|
||||
.send(player);
|
||||
} else if(state == 2) {
|
||||
replaceCost(plugin.getMessage(oldRank, Message.RANKS_INCOMPLETE)
|
||||
.replaceAll(player, oldRank, rank), player, oldRank)
|
||||
.send(player);
|
||||
}
|
||||
}
|
||||
|
||||
private MessageBuilder replaceCost(MessageBuilder builder, CommandSender sender, Rank rank) {
|
||||
Requirement money = rank.getRequirement("money");
|
||||
if(money == null || plugin.getEconomy() == null) {
|
||||
return builder;
|
||||
}
|
||||
double amount;
|
||||
if(sender instanceof Player && rank.isInRank((Player) sender)) {
|
||||
amount = money.getRemaining((Player) sender);
|
||||
} else {
|
||||
amount = money.getAmount();
|
||||
}
|
||||
DecimalFormat moneyFormat = plugin.getPlaceholders().getMoneyFormat();
|
||||
DecimalFormat percentFormat = plugin.getPlaceholders().getPercentFormat();
|
||||
return builder
|
||||
.replace(Variable.MONEY_NEEDED, moneyFormat.format(amount))
|
||||
.replace(Variable.PERCENT_LEFT, percentFormat.format((amount / money.getAmount()) * 100))
|
||||
.replace(Variable.PERCENT_DONE, percentFormat.format((1-(amount / money.getAmount())) * 100))
|
||||
.replace(Variable.MONEY, moneyFormat.format(money.getAmount()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package sh.okx.rankup.commands;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import sh.okx.rankup.Rankup;
|
||||
import sh.okx.rankup.gui.Gui;
|
||||
import sh.okx.rankup.messages.Message;
|
||||
import sh.okx.rankup.messages.Variable;
|
||||
import sh.okx.rankup.ranks.Rank;
|
||||
import sh.okx.rankup.ranks.Rankups;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
public class RankupCommand implements CommandExecutor {
|
||||
private final Map<Player, Long> confirming = new WeakHashMap<>();
|
||||
private final Rankup plugin;
|
||||
|
||||
public RankupCommand(Rankup plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
// check if player
|
||||
if (!(sender instanceof Player)) {
|
||||
return false;
|
||||
}
|
||||
Player player = (Player) sender;
|
||||
|
||||
Rankups rankups = plugin.getRankups();
|
||||
Rank rank = rankups.getRank(player);
|
||||
if (!plugin.checkRankup(player)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
String confirmationType = config.getString("confirmation-type").toLowerCase();
|
||||
|
||||
// if they are on text confirming, rank them up
|
||||
if (confirmationType.equals("text") && confirming.containsKey(player)) {
|
||||
long time = System.currentTimeMillis() - confirming.remove(player);
|
||||
if (time < config.getInt("text.timeout") * 1000) {
|
||||
plugin.rankup(player);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
switch (confirmationType) {
|
||||
case "text":
|
||||
confirming.put(player, System.currentTimeMillis());
|
||||
plugin.getMessage(rank, Message.CONFIRMATION)
|
||||
.replace(Variable.PLAYER, player.getName())
|
||||
.replace(Variable.RANK, rank.getRank())
|
||||
.replace(Variable.RANK_NAME, rank.getName())
|
||||
.send(player);
|
||||
break;
|
||||
case "gui":
|
||||
Gui.of(player, rank, rankups.nextRank(rank), plugin).open(player);
|
||||
break;
|
||||
case "none":
|
||||
plugin.rankup(player);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid confirmation type " + confirmationType);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package sh.okx.rankup.gui;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import sh.okx.rankup.Rankup;
|
||||
import sh.okx.rankup.messages.Message;
|
||||
import sh.okx.rankup.messages.MessageBuilder;
|
||||
import sh.okx.rankup.messages.Variable;
|
||||
import sh.okx.rankup.ranks.Rank;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class Gui implements InventoryHolder {
|
||||
@Getter
|
||||
private Inventory inventory;
|
||||
@Getter
|
||||
private ItemStack rankup;
|
||||
@Getter
|
||||
private ItemStack cancel;
|
||||
|
||||
public void open(Player player) {
|
||||
player.openInventory(inventory);
|
||||
}
|
||||
|
||||
public static Gui of(Player player, Rank oldRank, Rank rank, Rankup plugin) {
|
||||
ConfigurationSection config = plugin.getConfig().getConfigurationSection("gui");
|
||||
ItemStack[] items = new ItemStack[config.getInt("rows") * 9];
|
||||
addItem(items, config.getConfigurationSection("rankup"), player, oldRank, rank);
|
||||
addItem(items, config.getConfigurationSection("cancel"), player, oldRank, rank);
|
||||
addItem(items, config.getConfigurationSection("fill"), player, oldRank, rank);
|
||||
|
||||
Gui gui = new Gui();
|
||||
gui.rankup = getItem(config.getConfigurationSection("rankup"), player, oldRank, rank);
|
||||
gui.cancel = getItem(config.getConfigurationSection("cancel"), player, oldRank, rank);
|
||||
Inventory inventory = Bukkit.createInventory(gui,
|
||||
items.length,
|
||||
plugin.getMessage(rank, Message.TITLE)
|
||||
.replaceAll(player, oldRank, rank)
|
||||
.toString());
|
||||
inventory.setContents(items);
|
||||
gui.inventory = inventory;
|
||||
return gui;
|
||||
}
|
||||
|
||||
private static ItemStack getItem(ConfigurationSection section, Player player, Rank oldRank, Rank rank) {
|
||||
boolean legacy = !Bukkit.getVersion().contains("1.13");
|
||||
|
||||
String materialName = section.getString("material").toUpperCase();
|
||||
// handle default material correctly on older versions
|
||||
if (legacy && materialName.equals("BLACK_STAINED_GLASS_PANE")) {
|
||||
materialName = "STAINED_GLASS_PANE:15";
|
||||
}
|
||||
|
||||
ItemStack item;
|
||||
if (legacy) {
|
||||
String[] parts = materialName.split(":");
|
||||
Material material = Material.valueOf(parts[0]);
|
||||
|
||||
short type = parts.length > 1 ? Short.parseShort(parts[1]) : 0;
|
||||
item = new ItemStack(material, 1, type);
|
||||
} else {
|
||||
Material material = Material.valueOf(materialName);
|
||||
item = new ItemStack(material);
|
||||
}
|
||||
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (section.contains("lore")) {
|
||||
meta.setLore(Arrays.stream(format(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(section.getString("name"), player, oldRank, rank));
|
||||
}
|
||||
item.setItemMeta(meta);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private static String format(String message, Player player, Rank oldRank, Rank rank) {
|
||||
return new MessageBuilder(ChatColor.translateAlternateColorCodes('&', message))
|
||||
.replaceAll(player, oldRank, rank)
|
||||
.toString();
|
||||
}
|
||||
|
||||
private static void addItem(ItemStack[] items, ConfigurationSection section, Player player, Rank oldRank, Rank rank) {
|
||||
ItemStack item = getItem(section, player, oldRank, rank);
|
||||
if (section.getName().equalsIgnoreCase("fill")) {
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
if (items[i] == null) {
|
||||
items[i] = item;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
String[] locations = section.getString("index").split(" ");
|
||||
for (String location : locations) {
|
||||
String[] parts = location.split("-");
|
||||
if (parts.length == 1) {
|
||||
items[Integer.parseInt(parts[0])] = item;
|
||||
} else {
|
||||
for (int i = Integer.parseInt(parts[0]); i <= Integer.parseInt(parts[1]); i++) {
|
||||
items[i] = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package sh.okx.rankup.gui;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import sh.okx.rankup.Rankup;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class GuiListener implements Listener {
|
||||
private final Rankup plugin;
|
||||
|
||||
@EventHandler
|
||||
public void on(InventoryClickEvent e) {
|
||||
Inventory inventory = e.getInventory();
|
||||
if (inventory == null
|
||||
|| !(inventory.getHolder() instanceof Gui)
|
||||
|| !e.getInventory().equals(e.getClickedInventory())) {
|
||||
return;
|
||||
}
|
||||
e.setCancelled(true);
|
||||
|
||||
Player player = (Player) e.getWhoClicked();
|
||||
Gui gui = (Gui) inventory.getHolder();
|
||||
|
||||
if (gui.getRankup().isSimilar(e.getCurrentItem())) {
|
||||
Bukkit.getScheduler().runTask(plugin, player::closeInventory);
|
||||
plugin.rankup(player);
|
||||
} else if (gui.getCancel().isSimilar(e.getCurrentItem())) {
|
||||
Bukkit.getScheduler().runTask(plugin, player::closeInventory);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package sh.okx.rankup.messages;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
class EmptyMessageBuilder extends MessageBuilder {
|
||||
EmptyMessageBuilder() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageBuilder failIfEmpty() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageBuilder replace(Variable variable, Object value) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(CommandSender sender) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void broadcast() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package sh.okx.rankup.messages;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public enum Message {
|
||||
NOT_IN_LADDER("not-in-ladder"),
|
||||
REQUIREMENTS_NOT_MET("rankup.requirements-not-met"),
|
||||
NO_RANKUP("rankup.no-rankup"),
|
||||
SUCCESS_PUBLIC("rankup.success-public"),
|
||||
SUCCESS_PRIVATE("rankup.success-private"),
|
||||
CONFIRMATION("rankup.confirmation"),
|
||||
TITLE("rankup.title"),
|
||||
RANKS_HEADER("ranks.header"),
|
||||
RANKS_FOOTER("ranks.footer"),
|
||||
RANKS_COMPLETE("rankup.ranks.complete"),
|
||||
RANKS_CURRENT("rankup.ranks.current"),
|
||||
RANKS_INCOMPLETE("rankup.ranks.incomplete");
|
||||
|
||||
@Getter
|
||||
private final String name;
|
||||
|
||||
Message(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package sh.okx.rankup.messages;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import net.milkbowl.vault.economy.Economy;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import sh.okx.rankup.ranks.Rank;
|
||||
import sh.okx.rankup.ranks.requirements.Requirement;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
public class MessageBuilder {
|
||||
private String message;
|
||||
|
||||
public MessageBuilder(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public static MessageBuilder of(ConfigurationSection config, Message message) {
|
||||
return new MessageBuilder(ChatColor.translateAlternateColorCodes('&', config.getString(message.getName())));
|
||||
}
|
||||
|
||||
public MessageBuilder replace(Variable variable, Object value) {
|
||||
this.message = variable.replace(message, String.valueOf(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessageBuilder replaceAll(CommandSender player, Rank rank) {
|
||||
replace(Variable.PLAYER, player.getName());
|
||||
replaceAll(rank);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessageBuilder replaceAll(CommandSender player, Rank oldRank, Rank rank) {
|
||||
replace(Variable.PLAYER, player.getName());
|
||||
replaceAll(oldRank, rank);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessageBuilder replaceAll(Rank rank) {
|
||||
replace(Variable.RANK, rank.getRank());
|
||||
replace(Variable.RANK_NAME, rank.getName());
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessageBuilder replaceAll(Rank oldRank, Rank rank) {
|
||||
replace(Variable.RANK, rank.getRank());
|
||||
replace(Variable.RANK_NAME, rank.getName());
|
||||
replace(Variable.OLD_RANK, oldRank.getRank());
|
||||
replace(Variable.OLD_RANK_NAME, oldRank.getName());
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessageBuilder replaceCost(CommandSender sender, Economy economy, Rank rank) {
|
||||
Requirement money = rank.getRequirement("money");
|
||||
if(money == null || economy == null) {
|
||||
return this;
|
||||
}
|
||||
replace(Variable.MONEY, money.getAmount());
|
||||
if(sender instanceof Player && rank.isInRank((Player) sender)) {
|
||||
replace(Variable.MONEY_NEEDED, money.getRemaining((Player) sender));
|
||||
} else {
|
||||
replace(Variable.MONEY_NEEDED, money.getAmount());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fails the MessageBuilder if the message is empty.
|
||||
* if this fails, all subsequent calls to that MessageBuilder will do nothing
|
||||
* @return an EmptyMessageBuilder if the message is empty, itself otherwise
|
||||
*/
|
||||
public MessageBuilder failIfEmpty() {
|
||||
if(message.isEmpty()) {
|
||||
return new EmptyMessageBuilder();
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public void send(CommandSender sender) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package sh.okx.rankup.messages;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public enum Variable {
|
||||
PLAYER,
|
||||
OLD_RANK,
|
||||
OLD_RANK_NAME,
|
||||
RANK,
|
||||
RANK_NAME,
|
||||
MONEY,
|
||||
MONEY_NEEDED,
|
||||
PERCENT_DONE,
|
||||
PERCENT_LEFT;
|
||||
|
||||
public static Variable getVariable(String name) {
|
||||
for(Variable variable : values()) {
|
||||
if(variable.toString().equalsIgnoreCase(name)) {
|
||||
return variable;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String replace(String message, String value) {
|
||||
Pattern pattern = Pattern.compile("\\{" + this + "}", Pattern.CASE_INSENSITIVE);
|
||||
Matcher matcher = pattern.matcher(message);
|
||||
return matcher.replaceAll(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package sh.okx.rankup.placeholders;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import org.bukkit.entity.Player;
|
||||
import sh.okx.rankup.Rankup;
|
||||
import sh.okx.rankup.ranks.Rank;
|
||||
import sh.okx.rankup.ranks.Rankups;
|
||||
import sh.okx.rankup.ranks.requirements.Requirement;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class Placeholders extends PlaceholderExpansion {
|
||||
private final Rankup plugin;
|
||||
@Getter
|
||||
private final DecimalFormat moneyFormat;
|
||||
@Getter
|
||||
private final DecimalFormat percentFormat;
|
||||
@Getter
|
||||
private final DecimalFormat simpleFormat;
|
||||
|
||||
public Placeholders(Rankup plugin) {
|
||||
this.plugin = plugin;
|
||||
this.moneyFormat = new DecimalFormat(plugin.getConfig().getString("placeholders.money-format"));
|
||||
this.percentFormat = new DecimalFormat(plugin.getConfig().getString("placeholders.percent-format"));
|
||||
this.simpleFormat = new DecimalFormat(plugin.getConfig().getString("placeholders.simple-format"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onPlaceholderRequest(Player player, String params) {
|
||||
if (player == null) {
|
||||
return "";
|
||||
}
|
||||
params = params.toLowerCase();
|
||||
|
||||
Rankups rankups = plugin.getRankups();
|
||||
Rank rank = rankups.getRank(player);
|
||||
Rank next = null;
|
||||
if (rank != null) {
|
||||
next = rankups.nextRank(rank);
|
||||
}
|
||||
|
||||
if(params.startsWith("requirement_")) {
|
||||
String[] parts = params.split("_", 3);
|
||||
return getPlaceholderRequirement(player, rank,
|
||||
parts[1], parts.length > 2 ? parts[2] : "");
|
||||
}
|
||||
|
||||
switch (params) {
|
||||
case "current_rank":
|
||||
return orElsePlaceholder(rank, Rank::getRank, "not-in-ladder");
|
||||
case "current_rank_name":
|
||||
return orElsePlaceholder(rank, Rank::getRank, "not-in-ladder");
|
||||
case "current_rank_money":
|
||||
return orElsePlaceholder(rank, r -> simplify(r.getRequirement("money").getAmount()), 0);
|
||||
case "current_rank_money_formatted":
|
||||
return moneyFormat.format(orElsePlaceholder(rank, r -> r.getRequirement("money").getAmount(), 0));
|
||||
case "next_rank":
|
||||
if (rank == null) {
|
||||
return getPlaceholder("not-in-ladder");
|
||||
} else if (next == null) {
|
||||
return getPlaceholder("highest-rank");
|
||||
} else {
|
||||
return next.getRank();
|
||||
}
|
||||
case "next_rank_name":
|
||||
if (rank == null) {
|
||||
return getPlaceholder("not-in-ladder");
|
||||
} else if (next == null) {
|
||||
return getPlaceholder("highest-rank");
|
||||
} else {
|
||||
return next.getName();
|
||||
}
|
||||
case "next_rank_money":
|
||||
return orElsePlaceholder(next, r -> simplify(r.getRequirement("money").getAmount()), 0);
|
||||
case "next_rank_money_formatted":
|
||||
return moneyFormat.format(orElsePlaceholder(next, r -> r.getRequirement("money").getAmount(), 0));
|
||||
case "next_rank_money_left":
|
||||
return orElsePlaceholder(next, r -> simplify(plugin.getEconomy().getBalance(player) - r.getRequirement("money").getAmount()), 0);
|
||||
case "next_rank_money_left_formatted":
|
||||
return moneyFormat.format(orElsePlaceholder(next, r -> plugin.getEconomy().getBalance(player) - r.getRequirement("money").getAmount(), 0));
|
||||
case "next_rank_percent_left":
|
||||
return orElsePlaceholder(next, r -> (1-(plugin.getEconomy().getBalance(player) / r.getRequirement("money").getAmount())) * 100, 0);
|
||||
case "next_rank_percent_left_formatted":
|
||||
return percentFormat.format(orElsePlaceholder(next, r -> (1-(plugin.getEconomy().getBalance(player) / r.getRequirement("money").getAmount())) * 100, 0));
|
||||
case "next_rank_percent_done":
|
||||
return orElsePlaceholder(next, r -> (plugin.getEconomy().getBalance(player) / r.getRequirement("money").getAmount()) * 100, 0);
|
||||
case "next_rank_percent_done_formatted":
|
||||
return percentFormat.format(orElsePlaceholder(next, r -> (plugin.getEconomy().getBalance(player) / r.getRequirement("money").getAmount()) * 100, 0));
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String getPlaceholderRequirement(Player player, Rank rank, String requirementName, String params) {
|
||||
if(rank == null) {
|
||||
return "";
|
||||
}
|
||||
Requirement requirement = rank.getRequirement(requirementName);
|
||||
switch(params) {
|
||||
case "":
|
||||
return simpleFormat.format(orElse(requirement, Requirement::getAmount, 0));
|
||||
case "left":
|
||||
return simpleFormat.format(orElse(requirement, r -> r.getRemaining(player), 0));
|
||||
case "percent_left":
|
||||
return percentFormat.format(orElse(requirement, r -> (r.getRemaining(player) / r.getAmount()) * 100, 0));
|
||||
case "percent_done":
|
||||
return percentFormat.format(orElse(requirement, r -> (1-(r.getRemaining(player) / r.getAmount())) * 100, 100));
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Number simplify(Number number) {
|
||||
if (number instanceof Float) {
|
||||
return (float) number % 1 == 0 ? (int) number : number;
|
||||
} else if (number instanceof Double) {
|
||||
return (double) number % 1 == 0 ? (long) number : number;
|
||||
} else {
|
||||
return number;
|
||||
}
|
||||
}
|
||||
|
||||
private <T> String orElsePlaceholder(T t, Function<T, Object> value, Object fallback) {
|
||||
if (t == null) {
|
||||
return getPlaceholder(String.valueOf(fallback));
|
||||
}
|
||||
|
||||
try {
|
||||
return String.valueOf(value.apply(t));
|
||||
} catch (NullPointerException ex) {
|
||||
return getPlaceholder(String.valueOf(fallback));
|
||||
}
|
||||
}
|
||||
|
||||
private <T, R> R orElse(T t, Function<T, R> value, R fallback) {
|
||||
if (t == null) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
try {
|
||||
return value.apply(t);
|
||||
} catch (NullPointerException ex) {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
private String getPlaceholder(String name) {
|
||||
return plugin.getConfig().getString("placeholders." + name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
return "rankup";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthor() {
|
||||
return String.join(", ", plugin.getDescription().getAuthors());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return plugin.getDescription().getVersion();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package sh.okx.rankup.ranks;
|
||||
|
||||
public class Prestige {
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package sh.okx.rankup.ranks;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import sh.okx.rankup.Rankup;
|
||||
import sh.okx.rankup.messages.MessageBuilder;
|
||||
import sh.okx.rankup.messages.Variable;
|
||||
import sh.okx.rankup.ranks.requirements.Requirement;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BinaryOperator;
|
||||
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class Rank {
|
||||
private final Rankup plugin;
|
||||
@Getter
|
||||
private final String name;
|
||||
@Getter
|
||||
private final String next;
|
||||
@Getter
|
||||
private final String rank;
|
||||
private final Set<Requirement> requirements;
|
||||
private final BinaryOperator<Boolean> reducer;
|
||||
private final List<String> commands;
|
||||
|
||||
public static Rank deserialize(Rankup plugin, ConfigurationSection section) {
|
||||
String rank = section.getString("rank");
|
||||
|
||||
Set<Requirement> requirements = new HashSet<>();
|
||||
BinaryOperator<Boolean> reducer = null;
|
||||
ConfigurationSection requirementsSection = section.getConfigurationSection("requirements");
|
||||
if(requirementsSection != null) {
|
||||
for (Map.Entry<String, Object> entry : requirementsSection.getValues(false).entrySet()) {
|
||||
String name = entry.getKey();
|
||||
double amount = Double.parseDouble(String.valueOf(entry.getValue()));
|
||||
|
||||
Requirement requirement = plugin.getRequirementRegistry().newRequirement(name, amount);
|
||||
if (requirement == null) {
|
||||
plugin.getLogger().warning("Unknown requirement " + name);
|
||||
} else {
|
||||
requirements.add(requirement);
|
||||
}
|
||||
}
|
||||
|
||||
String operation = section.getString("operation");
|
||||
if (operation == null) {
|
||||
operation = "and";
|
||||
}
|
||||
switch (operation) {
|
||||
case "and":
|
||||
reducer = (a, b) -> a && b;
|
||||
break;
|
||||
case "or":
|
||||
reducer = (a, b) -> a || b;
|
||||
break;
|
||||
case "xor":
|
||||
reducer = (a, b) -> (a && !b) || (b && !a);
|
||||
break;
|
||||
case "none":
|
||||
reducer = (a, b) -> !a && !b;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid operation type for rank " + rank);
|
||||
}
|
||||
}
|
||||
|
||||
return new Rank(plugin,
|
||||
section.getName(),
|
||||
section.getString("next"),
|
||||
rank,
|
||||
requirements,
|
||||
reducer,
|
||||
section.getStringList("commands"));
|
||||
}
|
||||
|
||||
public boolean checkRequirements(Player player) {
|
||||
return requirements.stream()
|
||||
.map(requirement -> requirement.check(player))
|
||||
.reduce(reducer)
|
||||
.orElse(true);
|
||||
}
|
||||
|
||||
public boolean isInRank(Player player) {
|
||||
String[] groups = plugin.getPermissions().getPlayerGroups(player);
|
||||
for (String group : groups) {
|
||||
if(group.equalsIgnoreCase(rank)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isLastRank() {
|
||||
return next == null;
|
||||
}
|
||||
|
||||
public Requirement getRequirement(String name) {
|
||||
for(Requirement requirement : requirements) {
|
||||
if(requirement.getName().equalsIgnoreCase(name)) {
|
||||
return requirement;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void applyRequirements(Player player) {
|
||||
for(Requirement requirement : requirements) {
|
||||
requirement.apply(player);
|
||||
}
|
||||
}
|
||||
|
||||
public void runCommands(Player player, Rank nextRank) {
|
||||
for (String command : commands) {
|
||||
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), new MessageBuilder(command)
|
||||
.replace(Variable.PLAYER, player.getName())
|
||||
.replace(Variable.OLD_RANK, rank)
|
||||
.replace(Variable.OLD_RANK_NAME, name)
|
||||
.replace(Variable.RANK, nextRank.rank)
|
||||
.replace(Variable.RANK_NAME, nextRank.name)
|
||||
.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if(!(o instanceof Rank)) {
|
||||
return false;
|
||||
}
|
||||
return ((Rank) o).name.equals(name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package sh.okx.rankup.ranks;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import sh.okx.rankup.Rankup;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class Rankups {
|
||||
@Getter
|
||||
private final FileConfiguration config;
|
||||
private final Set<Rank> ranks = new HashSet<>();
|
||||
|
||||
public Rankups(Rankup plugin, FileConfiguration config) {
|
||||
this.config = config;
|
||||
for (Map.Entry<String, Object> entry : config.getValues(false).entrySet()) {
|
||||
ConfigurationSection rankSection = (ConfigurationSection) entry.getValue();
|
||||
ranks.add(Rank.deserialize(plugin, rankSection));
|
||||
}
|
||||
}
|
||||
|
||||
public Rank getFirstRank() {
|
||||
OUTER:
|
||||
for(Rank rank : ranks) {
|
||||
// see if anything ranks up to this
|
||||
for(Rank rank0 : ranks) {
|
||||
if(!rank0.isLastRank() && rank0.getNext().equals(rank.getName())) {
|
||||
continue OUTER;
|
||||
}
|
||||
}
|
||||
// nothing ranks up to this
|
||||
return rank;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Rank getRank(String name) {
|
||||
for(Rank rank : ranks) {
|
||||
if(rank.getName().equals(name)) {
|
||||
return rank;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Rank getRank(Player player) {
|
||||
return ranks.stream()
|
||||
.filter(rank -> rank.isInRank(player))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public Rank nextRank(Rank rank) {
|
||||
if(rank.isLastRank()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for(Rank nextRank : ranks) {
|
||||
if (rank.getNext().equalsIgnoreCase(nextRank.getName())) {
|
||||
return nextRank;
|
||||
}
|
||||
}
|
||||
// this shouldn't happen but whatever
|
||||
return null;
|
||||
}
|
||||
|
||||
// public boolean hasNext(Rank start, Rank rank) {
|
||||
// while(!start.isLastRank()) {
|
||||
// start = nextRank(rank);
|
||||
// if(start.equals(rank)) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package sh.okx.rankup.ranks.requirements;
|
||||
|
||||
import net.milkbowl.vault.economy.Economy;
|
||||
import org.bukkit.entity.Player;
|
||||
import sh.okx.rankup.Rankup;
|
||||
|
||||
public class MoneyRequirement extends Requirement {
|
||||
public MoneyRequirement(Rankup plugin, String name) {
|
||||
super(plugin, name);
|
||||
}
|
||||
|
||||
protected MoneyRequirement(Requirement clone) {
|
||||
super(clone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(Player player) {
|
||||
Economy economy = plugin.getEconomy();
|
||||
double balance = economy.getBalance(player);
|
||||
return balance >= amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(Player player) {
|
||||
Economy economy = plugin.getEconomy();
|
||||
economy.withdrawPlayer(player, amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getRemaining(Player player) {
|
||||
return Math.max(0, amount - plugin.getEconomy().getBalance(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement clone() {
|
||||
return new MoneyRequirement(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package sh.okx.rankup.ranks.requirements;
|
||||
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.entity.Player;
|
||||
import sh.okx.rankup.Rankup;
|
||||
|
||||
public class PlaytimeHoursRequirement extends Requirement {
|
||||
private static final int TICKS_PER_HOUR = 20 * 60 * 60;
|
||||
|
||||
public PlaytimeHoursRequirement(Rankup plugin, String name) {
|
||||
super(plugin, name);
|
||||
}
|
||||
|
||||
protected PlaytimeHoursRequirement(Requirement clone) {
|
||||
super(clone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(Player player) {
|
||||
return player.getStatistic(Statistic.PLAY_ONE_MINUTE) * TICKS_PER_HOUR >= amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(Player player) {
|
||||
// well, we can't really take hours of playtime away, can we?
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getRemaining(Player player) {
|
||||
return amount - (player.getStatistic(Statistic.PLAY_ONE_MINUTE) * TICKS_PER_HOUR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement clone() {
|
||||
return new PlaytimeHoursRequirement(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package sh.okx.rankup.ranks.requirements;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.bukkit.entity.Player;
|
||||
import sh.okx.rankup.Rankup;
|
||||
|
||||
public abstract class Requirement implements Cloneable {
|
||||
protected Rankup plugin;
|
||||
@Getter
|
||||
protected String name;
|
||||
@Getter
|
||||
@Setter
|
||||
protected double amount;
|
||||
|
||||
public Requirement(Rankup plugin, String name) {
|
||||
this.plugin = plugin;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
protected Requirement(Requirement clone) {
|
||||
if(clone != null) {
|
||||
this.plugin = clone.plugin;
|
||||
this.name = clone.name;
|
||||
this.amount = clone.amount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a player meets this requirement
|
||||
* @param player the player to check
|
||||
* @return true if they meet the requirement, false otherwise
|
||||
*/
|
||||
public abstract boolean check(Player player);
|
||||
|
||||
/**
|
||||
* Apply the effect of this requirement to the player.
|
||||
* For money, this could be taking money away from the player.
|
||||
* You can assume that <code>Requirement#check(Player)</code> has been called,
|
||||
* and has returned true immediately prior to this.
|
||||
* @param player the player to take from
|
||||
*/
|
||||
public abstract void apply(Player player);
|
||||
|
||||
/**
|
||||
* Get the remaining amount needed for <code>Requirement#check(Player)</code> to yield true.
|
||||
* This is not required and is only used in placeholders.
|
||||
* @param player the player to find the remaining amount of
|
||||
* @return the remaining amount needed. Should be non-negative.
|
||||
*/
|
||||
public double getRemaining(Player player) {
|
||||
return amount;
|
||||
}
|
||||
public abstract Requirement clone();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package sh.okx.rankup.ranks.requirements;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class RequirementRegistry {
|
||||
private Set<Requirement> requirements = new HashSet<>();
|
||||
|
||||
public void addRequirement(Requirement requirement) {
|
||||
requirements.add(requirement);
|
||||
}
|
||||
|
||||
public Requirement newRequirement(String name, double amount) {
|
||||
for(Requirement requirement : requirements) {
|
||||
if(requirement.getName().equalsIgnoreCase(name)) {
|
||||
Requirement newRequirement = requirement.clone();
|
||||
newRequirement.setAmount(amount);
|
||||
return newRequirement;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package sh.okx.rankup.ranks.requirements;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import sh.okx.rankup.Rankup;
|
||||
|
||||
public class XpLevelRequirement extends Requirement {
|
||||
public XpLevelRequirement(Rankup plugin, String name) {
|
||||
super(plugin, name);
|
||||
}
|
||||
|
||||
protected XpLevelRequirement(Requirement clone) {
|
||||
super(clone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAmount(double amount) {
|
||||
// experience level should be a whole number
|
||||
super.setAmount(Math.round(amount));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(Player player) {
|
||||
return player.getLevel() >= amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(Player player) {
|
||||
player.setLevel(player.getLevel() - (int) amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getRemaining(Player player) {
|
||||
return Math.max(0, amount - player.getLevel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement clone() {
|
||||
return new XpLevelRequirement(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
# this is used for letting you know that you need to update/change your config file
|
||||
version: 0
|
||||
|
||||
# how people should confirm ranking up
|
||||
# options are: gui, text or none
|
||||
confirmation-type: 'gui'
|
||||
|
||||
gui:
|
||||
rows: 1
|
||||
rankup:
|
||||
material: EMERALD_BLOCK
|
||||
# index can be separated by spaces to show in multiple ways
|
||||
# ie, 0-3 9-12 18-21
|
||||
# you can also just use a single number instead of a range.
|
||||
index: 0-3
|
||||
name: '&a&lConfirm'
|
||||
# lore is optional
|
||||
lore: '&6Rankup to &b{RANK}'
|
||||
cancel:
|
||||
material: REDSTONE_BLOCK
|
||||
index: 5-8
|
||||
name: '&c&lCancel'
|
||||
fill:
|
||||
name: ' '
|
||||
# if you are using a 1.8-1.12 and you want to change this
|
||||
# you can use MATERIAL:data, for example STAINED_GLASS_PANE:8
|
||||
# this works for both the rankup and cancel blocks as well
|
||||
material: BLACK_STAINED_GLASS_PANE
|
||||
|
||||
text:
|
||||
# the time in seconds for a player to
|
||||
# confirm ranking up by typing the command again
|
||||
timeout: 10
|
||||
|
||||
placeholders:
|
||||
# format for money. for more information, see
|
||||
# https://docs.oracle.com/javase/8/docs/api/java/text/DecimalFormat.html
|
||||
money-format: "#,##0.##"
|
||||
percent-format: "0.##"
|
||||
# the format used for requirements
|
||||
simple-format: "#.##"
|
||||
# used for current_rank and next_rank placeholders when a player not in anything in rankups.yml
|
||||
not-in-ladder: "None"
|
||||
# used in next_rank placeholders when there is no rankup
|
||||
highest-rank: "None"
|
||||
@@ -0,0 +1,35 @@
|
||||
# the messages in this section can be customised for each rankup in rankups.yml.
|
||||
rankup:
|
||||
# NOTE: if you are using requirements for your ranks that are NOT money,
|
||||
# you will want to change this for each rank!
|
||||
requirements-not-met: "&cYou need {MONEY} 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}"
|
||||
# used for the text confirmation
|
||||
confirmation: |-
|
||||
&eAre you sure you want to rankup to &a{RANK}&e?
|
||||
&eType &c/rankup &eagain to confirm.
|
||||
# used for the GUI confirmation
|
||||
title: "Rankup to {RANK}"
|
||||
|
||||
# It is HIGHLY RECOMMENDED you override these in rankups.yml
|
||||
# to show the specific requirements for each rank.
|
||||
# however if you are just using money, you can use {MONEY} or {MONEY_NEEDED} or {PERCENT_DONE} or {PERCENT_LEFT}
|
||||
# for example:
|
||||
#ranks:
|
||||
# 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}%"
|
||||
# incomplete: "&r{OLD_RANK} &e\xbb &r{RANK} &efor &a${MONEY}"
|
||||
ranks:
|
||||
complete: "&7{OLD_RANK} &8\xbb &7{RANK}"
|
||||
current: "&c{OLD_RANK} &e\xbb &c{RANK}"
|
||||
incomplete: "&r{OLD_RANK} &e\xbb &r{RANK}"
|
||||
ranks:
|
||||
# an empty string disables the header/footer
|
||||
header: ''
|
||||
footer: ''
|
||||
|
||||
not-in-ladder: "&cSorry, but we could not find any rankups for the group(s) you are in."
|
||||
@@ -0,0 +1,41 @@
|
||||
name: Rankup
|
||||
version: 3.0-alpha
|
||||
main: sh.okx.rankup.Rankup
|
||||
author: Okx
|
||||
depend: [Vault]
|
||||
softdepend: [PlaceholderAPI]
|
||||
api-version: 1.13
|
||||
|
||||
commands:
|
||||
rankup:
|
||||
permission: rankup.rankup
|
||||
description: Rankup.
|
||||
rankup3:
|
||||
permission: rankup3.info
|
||||
description: View Rankup version and perform some administrative commands.
|
||||
# support the old command
|
||||
aliases: [pru]
|
||||
ranks:
|
||||
permission: rankup.ranks
|
||||
description: List all the ranks.
|
||||
permissions:
|
||||
rankup.*:
|
||||
children:
|
||||
rankup.info: true
|
||||
rankup.rankup: true
|
||||
rankup.checkversion: true
|
||||
rankup.ranks: true
|
||||
rankup.reload: true
|
||||
rankup.ranks: true
|
||||
rankup.info:
|
||||
default: true
|
||||
rankup.rankup:
|
||||
default: true
|
||||
rankup.checkversion:
|
||||
default: op
|
||||
rankup.ranks:
|
||||
default: true
|
||||
rankup.reload:
|
||||
default: op
|
||||
rankup.ranks:
|
||||
default: true
|
||||
@@ -0,0 +1,49 @@
|
||||
Aexample:
|
||||
# the name of the rank in your permissions plugin
|
||||
rank: 'A'
|
||||
# the next rank a player can rank up to.
|
||||
# this must be the name of the configuration section.
|
||||
# for example, the name of this configuration section is "Aexample".
|
||||
# this is not required.
|
||||
next: 'Bexample'
|
||||
# List of requirements to go to the next rank
|
||||
# (ie, this example will charge 1000 money to rankup from A to B)
|
||||
# money: money from the server economy
|
||||
# xp-level: amount of experience levels
|
||||
# playtime-hours: hours a player has played
|
||||
# custom requirements can also be added by other plugins.
|
||||
requirements:
|
||||
money: 1000
|
||||
# What requirements players need to match to /rankup.
|
||||
# this is optional - if you don't use it, it defaults to "and"
|
||||
# n.b. if there are no requirements players will always be able to /rankup.
|
||||
# and: all requirements
|
||||
# or: at least one requirement
|
||||
# xor: only one requirement
|
||||
# none: no requirements
|
||||
operation: and
|
||||
# the console will run these commands when a player ranks up
|
||||
#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}!'
|
||||
Bexample:
|
||||
rank: 'B'
|
||||
next: 'Cexample'
|
||||
requirements:
|
||||
money: 2500
|
||||
Cexample:
|
||||
rank: 'C'
|
||||
next: 'Dexample'
|
||||
requirements:
|
||||
money: 5000
|
||||
xp-level: 2
|
||||
# you can have a custom messages too.
|
||||
# you can use this to list the requirements needed.
|
||||
rankup:
|
||||
requirements-not-met: '&cYou need 5000 money and 2 levels of XP to rankup to D.'
|
||||
ranks:
|
||||
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)"
|
||||
Dexample:
|
||||
rank: 'D'
|
||||
@@ -0,0 +1,9 @@
|
||||
package sh.okx.rankup;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class RankupTest {
|
||||
}
|
||||
Reference in New Issue
Block a user