diff --git a/build.gradle b/sponge/build.gradle similarity index 100% rename from build.gradle rename to sponge/build.gradle diff --git a/gradle.properties b/sponge/gradle.properties similarity index 100% rename from gradle.properties rename to sponge/gradle.properties diff --git a/gradle/wrapper/gradle-wrapper.jar b/sponge/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from gradle/wrapper/gradle-wrapper.jar rename to sponge/gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.properties b/sponge/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from gradle/wrapper/gradle-wrapper.properties rename to sponge/gradle/wrapper/gradle-wrapper.properties diff --git a/gradlew b/sponge/gradlew old mode 100755 new mode 100644 similarity index 100% rename from gradlew rename to sponge/gradlew diff --git a/gradlew.bat b/sponge/gradlew.bat similarity index 96% rename from gradlew.bat rename to sponge/gradlew.bat index 24467a1..9618d8d 100644 --- a/gradlew.bat +++ b/sponge/gradlew.bat @@ -1,100 +1,100 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@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="-Xmx64m" "-Xms64m" - -@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 +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@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="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/sponge/settings.gradle similarity index 100% rename from settings.gradle rename to sponge/settings.gradle diff --git a/src/main/java/tv/voidstar/altimeter/Altimeter.java b/sponge/src/main/java/tv/voidstar/altimeter/Altimeter.java similarity index 100% rename from src/main/java/tv/voidstar/altimeter/Altimeter.java rename to sponge/src/main/java/tv/voidstar/altimeter/Altimeter.java diff --git a/src/main/java/tv/voidstar/altimeter/AltimeterConfig.java b/sponge/src/main/java/tv/voidstar/altimeter/AltimeterConfig.java similarity index 100% rename from src/main/java/tv/voidstar/altimeter/AltimeterConfig.java rename to sponge/src/main/java/tv/voidstar/altimeter/AltimeterConfig.java diff --git a/src/main/java/tv/voidstar/altimeter/AltimeterData.java b/sponge/src/main/java/tv/voidstar/altimeter/AltimeterData.java similarity index 100% rename from src/main/java/tv/voidstar/altimeter/AltimeterData.java rename to sponge/src/main/java/tv/voidstar/altimeter/AltimeterData.java diff --git a/src/main/java/tv/voidstar/altimeter/command/ClearExecutor.java b/sponge/src/main/java/tv/voidstar/altimeter/command/ClearExecutor.java similarity index 100% rename from src/main/java/tv/voidstar/altimeter/command/ClearExecutor.java rename to sponge/src/main/java/tv/voidstar/altimeter/command/ClearExecutor.java diff --git a/src/main/java/tv/voidstar/altimeter/command/MainExecutor.java b/sponge/src/main/java/tv/voidstar/altimeter/command/MainExecutor.java similarity index 100% rename from src/main/java/tv/voidstar/altimeter/command/MainExecutor.java rename to sponge/src/main/java/tv/voidstar/altimeter/command/MainExecutor.java diff --git a/src/main/java/tv/voidstar/altimeter/command/OverrideExecutor.java b/sponge/src/main/java/tv/voidstar/altimeter/command/OverrideExecutor.java similarity index 100% rename from src/main/java/tv/voidstar/altimeter/command/OverrideExecutor.java rename to sponge/src/main/java/tv/voidstar/altimeter/command/OverrideExecutor.java diff --git a/velocity/.run/Debug Velocity.run.xml b/velocity/.run/Debug Velocity.run.xml new file mode 100644 index 0000000..5a15d87 --- /dev/null +++ b/velocity/.run/Debug Velocity.run.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/velocity/.run/Velocity Server.run.xml b/velocity/.run/Velocity Server.run.xml new file mode 100644 index 0000000..96badc9 --- /dev/null +++ b/velocity/.run/Velocity Server.run.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/velocity/build.gradle b/velocity/build.gradle new file mode 100644 index 0000000..0e134c6 --- /dev/null +++ b/velocity/build.gradle @@ -0,0 +1,45 @@ +plugins { + id 'com.github.johnrengelman.shadow' version '4.0.4' +} + +apply plugin: 'java' + +group = 'tv.voidstar' +version = '1.0-SNAPSHOT' + +sourceCompatibility = '1.8' +targetCompatibility = '1.8' + +dependencies { + compileOnly 'com.velocitypowered:velocity-api:3.0.0' + annotationProcessor 'com.velocitypowered:velocity-api:3.0.0' + shadow 'com.github.Eufranio:StorageUtils:2.4' + //shadow 'org.xerial:sqlite-jdbc:3.36.0.2' + //shadow 'mysql:mysql-connector-java:8.0.26' +} + +configurations { + compile.extendsFrom shadow +} + +shadowJar { + configurations = [project.configurations.shadow] + + archiveClassifier = null + from sourceSets.main.output + + dependencies { + exclude(dependency('org.spongepowered:configurate-core')) + exclude(dependency('org.spongepowered:configurate-hocon')) + } +} + +repositories { + maven { + name 'velocity' + url 'https://nexus.velocitypowered.com/repository/maven-public/' + } + maven { url = 'https://jitpack.io' } +} + +build.dependsOn shadowJar diff --git a/velocity/gradle.properties b/velocity/gradle.properties new file mode 100644 index 0000000..e69de29 diff --git a/velocity/gradle/wrapper/gradle-wrapper.jar b/velocity/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..5c2d1cf Binary files /dev/null and b/velocity/gradle/wrapper/gradle-wrapper.jar differ diff --git a/velocity/gradle/wrapper/gradle-wrapper.properties b/velocity/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..59b5f89 --- /dev/null +++ b/velocity/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/velocity/gradlew b/velocity/gradlew new file mode 100644 index 0000000..83f2acf --- /dev/null +++ b/velocity/gradlew @@ -0,0 +1,188 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/velocity/gradlew.bat b/velocity/gradlew.bat new file mode 100644 index 0000000..9618d8d --- /dev/null +++ b/velocity/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@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="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/velocity/settings.gradle b/velocity/settings.gradle new file mode 100644 index 0000000..3338661 --- /dev/null +++ b/velocity/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'altimeter' diff --git a/velocity/src/main/java/tv/voidstar/altimeter/Altimeter.java b/velocity/src/main/java/tv/voidstar/altimeter/Altimeter.java new file mode 100644 index 0000000..1f1189e --- /dev/null +++ b/velocity/src/main/java/tv/voidstar/altimeter/Altimeter.java @@ -0,0 +1,157 @@ +package tv.voidstar.altimeter; + +import com.google.inject.Inject; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.tree.LiteralCommandNode; +import com.velocitypowered.api.command.BrigadierCommand; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.event.PostOrder; +import com.velocitypowered.api.event.ResultedEvent; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.LoginEvent; +import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; +import com.velocitypowered.api.event.proxy.ProxyReloadEvent; +import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; +import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.plugin.annotation.DataDirectory; +import com.velocitypowered.api.proxy.ProxyServer; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.slf4j.Logger; +import tv.voidstar.altimeter.command.ClearExecutor; +import tv.voidstar.altimeter.command.MainExecutor; +import tv.voidstar.altimeter.command.OverrideExecutor; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Instant; +import java.util.UUID; +import java.util.stream.Stream; + +@Plugin( + id = "altimeter", + name = "Altimeter", + version = "1.0", + description = "Limit number of accounts joining per IP address", + authors = {"ZeHeisenberg"} +) +public class Altimeter { + + private static Altimeter plugin; + + @Inject + @DataDirectory + private Path defaultConfigDir; + + @Inject + private Logger logger; + + @Inject + ProxyServer server; + + private static Altimeter getInstance() { + return plugin; + } + + public static Logger getLogger() { + return plugin.logger; + } + + @Subscribe + public void onInit(ProxyInitializeEvent event) throws IOException { + plugin = this; + + File rootDir = defaultConfigDir.toFile(); //new File(defaultConfigDir.toFile(), "altimeter"); + if (!rootDir.exists()) { + if (!rootDir.mkdirs()) { + Altimeter.getLogger().error("Unable to create root config dir"); + } + } + + logger.info("Altimeter is starting"); + + this.loadLibraries(); + + AltimeterConfig.init(rootDir); + AltimeterData.init(rootDir); + + //AltimeterConfig.load(); + AltimeterData.load(); + + registerCommands(); + + this.server.getScheduler() + .buildTask(this, () -> AltimeterData.checkAndClearAccounts(Instant.now())) + .repeat(AltimeterConfig.getCheckIntervalValue(), AltimeterConfig.getCheckIntervalUnit()) + .schedule(); + } + + @Subscribe + public void onReload(ProxyReloadEvent event) { + AltimeterConfig.load(); + AltimeterData.reload(); + getLogger().info("Altimeter reloaded"); + } + + @Subscribe(order = PostOrder.FIRST) + public void onLogin(LoginEvent event) { + UUID player = event.getPlayer().getUniqueId(); + String ip = event.getPlayer().getRemoteAddress().getAddress().getHostAddress(); + Altimeter.getLogger().info("{} logging in from {}", player, ip); + if (!AltimeterData.canLogIn(player, ip)) { + event.setResult(ResultedEvent.ComponentResult.denied(Component.text( + AltimeterConfig.getDisconnectMessage(), + NamedTextColor.RED + ))); + } + } + + @Subscribe + public void onStop(ProxyShutdownEvent event) { + AltimeterData.save(); + AltimeterConfig.save(); + } + + private void loadLibraries() { + try { + logger.info("Loading libraries"); + Path libraries = this.defaultConfigDir.resolve("libraries"); + Files.createDirectories(libraries); + + try (Stream stream = Files.walk(libraries)) { + stream.forEach(filePath -> { + logger.info("Adding library file " + filePath.getFileName() + " to the classpath"); + server.getPluginManager().addToClasspath(this, filePath); + }); + } + } catch (Exception e) { + logger.error("Could not load libraries."); + } + } + + private void registerCommands() { + LiteralCommandNode altimeterCommand = LiteralArgumentBuilder + .literal("altimeter") + .executes(new MainExecutor()) + .then(LiteralArgumentBuilder.literal("clear") + .then(RequiredArgumentBuilder.argument("target", StringArgumentType.word()) + .executes(new ClearExecutor()) + ) + ).then(LiteralArgumentBuilder.literal("override") + .then(RequiredArgumentBuilder.argument("ip", StringArgumentType.word()) + .then(RequiredArgumentBuilder.argument("limit", IntegerArgumentType.integer()) + .executes(new OverrideExecutor()) + ) + ) + ) + .build(); + + BrigadierCommand altimeter = new BrigadierCommand(altimeterCommand); + server.getCommandManager().register(altimeter); + } +} diff --git a/velocity/src/main/java/tv/voidstar/altimeter/AltimeterConfig.java b/velocity/src/main/java/tv/voidstar/altimeter/AltimeterConfig.java new file mode 100644 index 0000000..f0a83fe --- /dev/null +++ b/velocity/src/main/java/tv/voidstar/altimeter/AltimeterConfig.java @@ -0,0 +1,215 @@ +package tv.voidstar.altimeter; + +import com.google.common.net.InetAddresses; +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.ConfigurationOptions; +import ninja.leaping.configurate.commented.CommentedConfigurationNode; +import ninja.leaping.configurate.hocon.HoconConfigurationLoader; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.io.File; +import java.io.IOException; +import java.time.Duration; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +public class AltimeterConfig { + + private static final HashMap accountLimitOverrides = new HashMap<>(); + private static File configFile; + private static @NonNull HoconConfigurationLoader loader; + private static CommentedConfigurationNode configs; + private static Duration accountTTL; + private static int accountLimit; + private static long checkIntervalValue; + private static TimeUnit checkIntervalUnit; + private static String disconnectMessage; + private static String databaseUrl; + private static boolean enableDatabase; + + public static void init(File rootDir) { + configFile = new File(rootDir, "altimeter.conf"); + loader = HoconConfigurationLoader.builder() + .setFile(configFile) + .setDefaultOptions(ConfigurationOptions.defaults().withShouldCopyDefaults(true)) + .build(); + load(); + } + + public static void load() { + try { + if (!configFile.exists()) { + configFile.createNewFile(); + } + configs = loader.load(); + } catch (IOException e) { + Altimeter.getLogger().error("Unable to load config file.", e); + } + + // defaults + configs.getNode("altimeter").setComment("General Altimeter configurations."); + + disconnectMessage = configs.getNode("altimeter", "disconnectMessage") + .getString("Too many accounts have logged in from this address. Contact a server admin."); + enableDatabase = configs.getNode("altimeter", "enableDatabase").getBoolean(true); + databaseUrl = configs.getNode("altimeter", "databaseUrl") + .getString("jdbc:sqlite:Altimeter.db"); + + CommentedConfigurationNode checkInterval = configs.getNode("altimeter", "checkInterval") + .setComment("How often accounts should be checked and cleared from IP lists."); + getValOrSetDefault(checkInterval.getNode("value"), 5); + getValOrSetDefault(checkInterval.getNode("unit").setComment("DAYS, HOURS, MINUTES, or SECONDS"), "MINUTES"); + + checkIntervalValue = checkInterval.getNode("value").getLong(); + + String checkIU = checkInterval.getNode("unit").getString(); + try { + checkIntervalUnit = TimeUnit.valueOf(checkIU.toUpperCase()); + } catch (IllegalArgumentException | NullPointerException e) { + checkIntervalValue = 5; + checkIntervalUnit = TimeUnit.MINUTES; + Altimeter.getLogger().error("Invalid checkInterval unit in config: {}. Setting checkInterval to default 5 MINUTES", checkIU, e); + Altimeter.getLogger().error("Read https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/TimeUnit.html#enum_constant_detail for allowed values."); + } + + CommentedConfigurationNode accountTTLNode = configs.getNode("altimeter", "ttl") + .setComment("How long after an account is added to the queue is it removed."); + getValOrSetDefault(accountTTLNode.getNode("value"), 30); + getValOrSetDefault(accountTTLNode.getNode("unit").setComment("DAYS, HOURS, MINUTES, or SECONDS"), "DAYS"); + + long accountTTLValue = accountTTLNode.getNode("value").getLong(); + ChronoUnit accountTTLUnit; + + try { + accountTTLUnit = ChronoUnit.valueOf(accountTTLNode.getNode("unit").getString().toUpperCase()); + } catch (IllegalArgumentException | NullPointerException e) { + accountTTLValue = 30; + accountTTLUnit = ChronoUnit.DAYS; + Altimeter.getLogger().error("Invalid TTL unit in config: {}. Setting ttl to default 30 DAYS", checkIU, e); + Altimeter.getLogger().error("Read https://docs.oracle.com/javase/8/docs/api/java/time/temporal/ChronoUnit.html#enum.constant.summary for allowed values."); + } + + accountTTL = Duration.of(accountTTLValue, accountTTLUnit); + + ConfigurationNode accountLimitNode = configs.getNode("altimeter", "accountLimit") + .setComment("How many accounts from one IP can log in."); + getValOrSetDefault(accountLimitNode, 5); + accountLimit = accountLimitNode.getInt(); + + Altimeter.getLogger().info("load limit overrides data"); + ConfigurationNode limitOverridesNode = configs.getNode("altimeter", "limitOverrides") + .setComment("Override account limit for specific IPs"); + if (!limitOverridesNode.isList()) { + ConfigurationNode override = limitOverridesNode.appendListNode(); + override.getNode("ip").setValue("127.0.0.1"); + override.getNode("limit").setValue(50); + } else { + for (ConfigurationNode overrideNode : limitOverridesNode.getChildrenList()) { + String ip = overrideNode.getNode("ip").getString("in.va.li.d"); + int limit = overrideNode.getNode("limit").getInt(accountLimit); + if (InetAddresses.isInetAddress(ip)) { + accountLimitOverrides.put(ip, limit); + Altimeter.getLogger().info("{} has {} account limit", ip, limit); + } else { + Altimeter.getLogger().error("Invalid IP in limitOverrides configuration {}", ip); + } + } + } + + save(); + } + + public static void save() { + configs.getNode("altimeter").removeChild("limitOverrides"); + ConfigurationNode limitOverridesNode = configs.getNode("altimeter", "limitOverrides"); + for (Map.Entry entry : accountLimitOverrides.entrySet()) { + ConfigurationNode limitOverrideElement = limitOverridesNode.appendListNode(); + limitOverrideElement.getNode("ip").setValue(entry.getKey()); + limitOverrideElement.getNode("limit").setValue(entry.getValue()); + } + try { + loader.save(configs); + } catch (IOException e) { + Altimeter.getLogger().error("Unable to save config file.", e); + } + } + + public static int getAccountLimit(String ip) { + int limit = accountLimitOverrides.getOrDefault(ip, accountLimit); + Altimeter.getLogger().info("account limit for {} is {}", ip, limit); + return limit; + } + + public static void setAccountLimit(int accountLimit) { + AltimeterConfig.accountLimit = accountLimit; + } + + public static Duration getAccountTTL() { + return accountTTL; + } + + public static void setAccountTTL(String accountTTL) { + try { + AltimeterConfig.accountTTL = Duration.parse(accountTTL); + } catch (DateTimeParseException e) { + Altimeter.getLogger().error("Invalid Duration.parse() string: {}", accountTTL, e); + Altimeter.getLogger().error("Consider reading https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html#parse-java.lang.CharSequence-"); + } + } + + private static void getValOrSetDefault(ConfigurationNode node, boolean def) { + if (!(node.getValue() instanceof Boolean)) + node.setValue(def); + } + + private static void getValOrSetDefault(ConfigurationNode node, String def) { + if (node.getString() == null) + node.setValue(def); + } + + private static void getValOrSetDefault(ConfigurationNode node, Number def) { + if (!(node.getValue() instanceof Number)) + node.setValue(def); + } + + public static TimeUnit getCheckIntervalUnit() { + return checkIntervalUnit; + } + + public static void setCheckIntervalUnit(TimeUnit checkIntervalUnit) { + AltimeterConfig.checkIntervalUnit = checkIntervalUnit; + } + + public static long getCheckIntervalValue() { + return checkIntervalValue; + } + + public static void setCheckIntervalValue(long checkIntervalValue) { + AltimeterConfig.checkIntervalValue = checkIntervalValue; + } + + public static String getDisconnectMessage() { + return disconnectMessage; + } + + public static boolean isDatabaseEnabled() { + return enableDatabase; + } + + public static String getDatabaseUrl() { + return databaseUrl; + } + + public static boolean setOverride(String ip, int limit) { + boolean replace = accountLimitOverrides.putIfAbsent(ip, limit) != null; + if (replace) { + accountLimitOverrides.replace(ip, limit); + } + AltimeterData.trimAccountList(ip); + save(); + return replace; + } +} diff --git a/velocity/src/main/java/tv/voidstar/altimeter/AltimeterData.java b/velocity/src/main/java/tv/voidstar/altimeter/AltimeterData.java new file mode 100644 index 0000000..0245277 --- /dev/null +++ b/velocity/src/main/java/tv/voidstar/altimeter/AltimeterData.java @@ -0,0 +1,243 @@ +package tv.voidstar.altimeter; + +import com.j256.ormlite.table.TableUtils; +import io.github.eufranio.storage.Persistable; +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.hocon.HoconConfigurationLoader; +import tv.voidstar.altimeter.data.AccountData; +import tv.voidstar.altimeter.data.IPData; + +import java.io.File; +import java.io.IOException; +import java.time.Instant; +import java.util.*; + +public class AltimeterData { + private static final Map> ipAccountMap = Collections.synchronizedMap(new HashMap<>()); + + private static File ipAccountListsFile; + private static HoconConfigurationLoader loader; + + private static ConfigurationNode ipAccountsMap; + + static Persistable ips; + static Persistable accounts; + + public static void init(File rootDir) throws IOException { + if (AltimeterConfig.isDatabaseEnabled()) { + Altimeter.getLogger().info("Initializing database storage"); + ips = Persistable.create(IPData.class, AltimeterConfig.getDatabaseUrl()); + ips.idFieldName = "ip"; + + accounts = Persistable.create(AccountData.class, AltimeterConfig.getDatabaseUrl()); + } else { + Altimeter.getLogger().info("Initializing flat file storage"); + ipAccountListsFile = new File(rootDir, "accounts.conf"); + if (!ipAccountListsFile.exists()) + ipAccountListsFile.createNewFile(); + + loader = HoconConfigurationLoader.builder().setFile(ipAccountListsFile).build(); + ipAccountsMap = loader.load(); + } + } + + public static void load() { + if (AltimeterConfig.isDatabaseEnabled()) + return; + + ipAccountMap.clear(); + for (ConfigurationNode ipNode : ipAccountsMap.getNode("ips").getChildrenList()) { + String ip = ipNode.getNode("ip").getString(); + HashMap accountsForIP = new HashMap<>(); + for (ConfigurationNode accountEntry : ipNode.getNode("accounts").getChildrenList()) { + UUID uuid = UUID.fromString(accountEntry.getNode("uuid").getString()); + Instant ttl = Instant.ofEpochMilli(accountEntry.getNode("ttl").getLong()); + accountsForIP.put(uuid, ttl); + } + ipAccountMap.put(ip, accountsForIP); + } + } + + public static void save() { + if (AltimeterConfig.isDatabaseEnabled()) + return; + + ipAccountsMap.removeChild("ips"); + ConfigurationNode list = ipAccountsMap.getNode("ips"); + for (String ip : ipAccountMap.keySet()) { + ConfigurationNode ipNode = list.appendListNode(); + ipNode.getNode("ip").setValue(ip); + ConfigurationNode accountList = ipNode.getNode("accounts"); + HashMap accountsForIP = ipAccountMap.get(ip); + for (UUID uuid : accountsForIP.keySet()) { + ConfigurationNode accountForIP = accountList.appendListNode(); + accountForIP.getNode("uuid").setValue(uuid.toString()); + accountForIP.getNode("ttl").setValue(accountsForIP.get(uuid).toEpochMilli()); + } + } + try { + loader.save(ipAccountsMap); + } catch (IOException e) { + Altimeter.getLogger().error("Could not save Account/IP lists to disk", e); + } + } + + public static void reload() { + if (AltimeterConfig.isDatabaseEnabled()) + return; + + ipAccountMap.clear(); + load(); + } + + public static boolean canLogIn(UUID player, String ip) { + if (AltimeterConfig.isDatabaseEnabled()) { + IPData ipData = ips.getOrCreate(ip); + try { ipData.refresh(); } catch (Exception e) { e.printStackTrace(); } + + if (ipData.accounts.stream().anyMatch(acc -> acc.uuid.equals(player))) + return true; + + if (ipData.accounts.size() < AltimeterConfig.getAccountLimit(ip)) { + Altimeter.getLogger().info("\tAllowed login and logged account for IP"); + AccountData data = new AccountData() {{ + ip = ipData; + uuid = player; + ttl = Instant.now().plus(AltimeterConfig.getAccountTTL()); + }}; + accounts.save(data); + return true; + } + + Altimeter.getLogger().info("\tDenied login. too many accounts from this ip"); + return false; + } else { + HashMap accounts = ipAccountMap.computeIfAbsent(ip, k -> new HashMap<>()); + if (accounts.containsKey(player)) { + return true; + } else if (accounts.size() < AltimeterConfig.getAccountLimit(ip)) { + Altimeter.getLogger().info("\tAllowed login and logged account for IP"); + accounts.put(player, Instant.now().plus(AltimeterConfig.getAccountTTL())); + save(); + return true; + } else { + Altimeter.getLogger().info("\tDenied login. too many accounts from this ip"); + return false; + } + } + } + + public static void checkAndClearAccounts(Instant now) { + if (AltimeterConfig.isDatabaseEnabled()) { + try { + for (AccountData data : accounts.objDao) { + if (now.isAfter(data.ttl)) + accounts.delete(data); + } + } catch (Exception e) { + Altimeter.getLogger().error("Error clearing IPs"); + e.printStackTrace(); + } + } else { + Iterator>> ipIterator = ipAccountMap.entrySet().iterator(); + boolean dataChanged = false; + while (ipIterator.hasNext()) { + Map.Entry> ipEntry = ipIterator.next(); + HashMap accounts = ipEntry.getValue(); + accounts.forEach((key, value) -> { + if (now.isAfter(value)) { + accounts.remove(key); + } + }); + dataChanged = true; + if (accounts.isEmpty()) + ipIterator.remove(); + } + boolean trimmed = trimAllAccountLists(); + if (dataChanged || trimmed) + save(); + } + } + + static boolean trimAllAccountLists() { + boolean trimmed = false; + Iterator>> ipIterator = ipAccountMap.entrySet().iterator(); + while (ipIterator.hasNext()) { + Map.Entry> ipEntry = ipIterator.next(); + if (trimAccountList(ipEntry.getKey())) { + trimmed = true; + } + if (ipEntry.getValue().isEmpty()) + ipIterator.remove(); + } + return trimmed; + } + + public static boolean trimAccountList(String ip) { + if (AltimeterConfig.isDatabaseEnabled()) { + IPData ipData = ips.get(ip); + if (ipData == null) + return false; + + try { ipData.refresh(); } catch (Exception e) { e.printStackTrace(); } + + if (ipData.accounts.isEmpty() || AltimeterConfig.getAccountLimit(ip) >= ipData.accounts.size()) + return false; + + int elementsToRemove = ipData.accounts.size() - AltimeterConfig.getAccountLimit(ip); + Comparator comparator = Comparator.comparingLong(acc -> acc.ttl.toEpochMilli()); + ipData.accounts.stream() + .sorted(comparator.reversed()) + .limit(elementsToRemove) + .forEach(acc -> accounts.delete(acc)); + } else { + HashMap accounts = ipAccountMap.get(ip); + if (accounts == null || AltimeterConfig.getAccountLimit(ip) > accounts.size()) + return false; + + int elementsToRemove = accounts.size() - AltimeterConfig.getAccountLimit(ip); + accounts.entrySet().stream() + .sorted(Comparator.comparingLong(e -> ((Map.Entry) e).getValue().toEpochMilli()).reversed()) + .limit(elementsToRemove) + .forEach(e -> accounts.remove(e.getKey())); + } + return true; + } + + public static boolean clear(String target) { + if (AltimeterConfig.isDatabaseEnabled()) { + if (target.equals("all")) { + try { + TableUtils.clearTable(ips.objDao.getConnectionSource(), IPData.class); + return true; + } catch (Exception e) { + Altimeter.getLogger().error("Error clearing the database:"); + e.printStackTrace(); + return false; + } + } else { + IPData ipData = ips.get(target); + if (ipData == null) + return false; + + try { ipData.refresh(); } catch (Exception e) { e.printStackTrace(); } + ipData.accounts.clear(); + ips.delete(ipData); + return true; + } + } else { + boolean success; + if (target.equals("all")) { + ipAccountMap.clear(); + success = true; + } else { + success = ipAccountMap.remove(target) != null; + if (success) { + trimAccountList(target); + } + } + save(); + return success; + } + } +} diff --git a/velocity/src/main/java/tv/voidstar/altimeter/command/ClearExecutor.java b/velocity/src/main/java/tv/voidstar/altimeter/command/ClearExecutor.java new file mode 100644 index 0000000..ce01c5e --- /dev/null +++ b/velocity/src/main/java/tv/voidstar/altimeter/command/ClearExecutor.java @@ -0,0 +1,41 @@ +package tv.voidstar.altimeter.command; + +import com.google.common.net.InetAddresses; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.velocitypowered.api.command.CommandSource; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import tv.voidstar.altimeter.AltimeterData; + +public class ClearExecutor implements Command { + + @Override + public int run(CommandContext context) throws CommandSyntaxException { + CommandSource src = context.getSource(); + String target = StringArgumentType.getString(context, "target"); + if (target.equals("all")) { + if (!src.hasPermission("altimeter.clear.all")) { + src.sendMessage(Component.text("[Altimeter] You don't have permission to do that.", NamedTextColor.RED)); + return 1; + } + } else if (InetAddresses.isInetAddress(target)) { + if (!src.hasPermission("altimeter.clear.ip")) { + src.sendMessage(Component.text("[Altimeter] You don't have permission to do that.", NamedTextColor.RED)); + return 1; + } + } else { + src.sendMessage(Component.text("[Altimeter] You must supply 'all' or an IP address.", NamedTextColor.RED)); + return 1; + } + + if (AltimeterData.clear(target)) { + src.sendMessage(Component.text("[Altimeter] Successfully cleared account entries for " + target, NamedTextColor.GREEN)); + } else { + src.sendMessage(Component.text("[Altimeter] Unable to clear account entries for " + target, NamedTextColor.RED)); + } + return 1; + } +} diff --git a/velocity/src/main/java/tv/voidstar/altimeter/command/MainExecutor.java b/velocity/src/main/java/tv/voidstar/altimeter/command/MainExecutor.java new file mode 100644 index 0000000..72dbdda --- /dev/null +++ b/velocity/src/main/java/tv/voidstar/altimeter/command/MainExecutor.java @@ -0,0 +1,22 @@ +package tv.voidstar.altimeter.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.velocitypowered.api.command.CommandSource; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; + +public class MainExecutor implements Command { + + @Override + public int run(CommandContext context) throws CommandSyntaxException { + CommandSource src = context.getSource(); + src.sendMessage(Component.text("------ Altimeter command list ------", NamedTextColor.AQUA)); + src.sendMessage(Component.text("/altimeter clear [all|x.x.x.x] - clear accounts for all IPs, or for a specific IP.", NamedTextColor.GOLD)); + src.sendMessage(Component.text("/altimeter override - sets a limit for a given IP. (ignores the global limit)", NamedTextColor.GOLD)); + src.sendMessage(Component.text("-------------------------------------", NamedTextColor.AQUA)); + return 1; + } + +} diff --git a/velocity/src/main/java/tv/voidstar/altimeter/command/OverrideExecutor.java b/velocity/src/main/java/tv/voidstar/altimeter/command/OverrideExecutor.java new file mode 100644 index 0000000..91d264c --- /dev/null +++ b/velocity/src/main/java/tv/voidstar/altimeter/command/OverrideExecutor.java @@ -0,0 +1,45 @@ +package tv.voidstar.altimeter.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.velocitypowered.api.command.CommandSource; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import tv.voidstar.altimeter.Altimeter; +import tv.voidstar.altimeter.AltimeterConfig; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +public class OverrideExecutor implements Command { + + @Override + public int run(CommandContext context) throws CommandSyntaxException { + CommandSource src = context.getSource(); + if (!src.hasPermission("altimeter.override")) { + src.sendMessage(Component.text("You don't have permission to do this.", NamedTextColor.RED)); + return 1; + } + + InetAddress address; + try { + address = InetAddress.getByName(StringArgumentType.getString(context, "ip")); + } catch (UnknownHostException var8) { + src.sendMessage(Component.text("Invalid IP address!", NamedTextColor.RED)); + return 1; + } + + String ip = address.getHostAddress(); + int limit = IntegerArgumentType.getInteger(context, "limit"); + + Altimeter.getLogger().info("ip: " + ip + ", limit: " + limit); + String verb = AltimeterConfig.setOverride(ip, limit) ? "updated" : "set"; + + src.sendMessage(Component.text("[Altimeter] Successfully " + verb + " override limit to " + limit + " for " + ip, NamedTextColor.GREEN)); + return 1; + } + +} diff --git a/velocity/src/main/java/tv/voidstar/altimeter/data/AccountData.java b/velocity/src/main/java/tv/voidstar/altimeter/data/AccountData.java new file mode 100644 index 0000000..a705155 --- /dev/null +++ b/velocity/src/main/java/tv/voidstar/altimeter/data/AccountData.java @@ -0,0 +1,26 @@ +package tv.voidstar.altimeter.data; + +import com.j256.ormlite.field.DataType; +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.misc.BaseDaoEnabled; +import com.j256.ormlite.table.DatabaseTable; + +import java.time.Instant; +import java.util.UUID; + +@DatabaseTable(tableName = "accounts") +public class AccountData extends BaseDaoEnabled { + + @DatabaseField(generatedId = true) + public int id; + + @DatabaseField(foreign = true) + public IPData ip; + + @DatabaseField + public UUID uuid; + + @DatabaseField(dataType = DataType.SERIALIZABLE) + public Instant ttl; + +} diff --git a/velocity/src/main/java/tv/voidstar/altimeter/data/IPData.java b/velocity/src/main/java/tv/voidstar/altimeter/data/IPData.java new file mode 100644 index 0000000..1586f99 --- /dev/null +++ b/velocity/src/main/java/tv/voidstar/altimeter/data/IPData.java @@ -0,0 +1,18 @@ +package tv.voidstar.altimeter.data; + +import com.j256.ormlite.dao.ForeignCollection; +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.field.ForeignCollectionField; +import com.j256.ormlite.misc.BaseDaoEnabled; +import com.j256.ormlite.table.DatabaseTable; + +@DatabaseTable(tableName = "ips") +public class IPData extends BaseDaoEnabled { + + @DatabaseField(id = true) + public String ip; + + @ForeignCollectionField(eager = true) + public ForeignCollection accounts; + +}