commit 828aa76a905c3680e4554243f147fe04f9caf6e3 Author: wolf Date: Wed Jul 30 13:59:16 2025 -0500 Server Splitter diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d5f737e --- /dev/null +++ b/.gitignore @@ -0,0 +1,119 @@ +# User-specific stuff +.idea/ + +*.iml +*.ipr +*.iws + +# IntelliJ +out/ +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +.gradle +build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Cache of project +.gradletasknamecache + +**/build/ + +# Common working directory +run/ +runs/ + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..0f3a155 --- /dev/null +++ b/build.gradle @@ -0,0 +1,59 @@ +plugins { + id 'java' + id("xyz.jpenilla.run-paper") version "2.3.1" +} + +group = 'me.trouper' +version = '1.0-SNAPSHOT' + +repositories { + mavenCentral() + maven { + name = "papermc-repo" + url = "https://repo.papermc.io/repository/maven-public/" + } + maven { + name = "sonatype" + url = "https://oss.sonatype.org/content/groups/public/" + } +} + +dependencies { + compileOnly("io.papermc.paper:paper-api:1.21.5-R0.1-SNAPSHOT") +} + +tasks { + runServer { + // Configure the Minecraft version for our task. + // This is the only required configuration besides applying the plugin. + // Your plugin's jar (or shadowJar if present) will be used automatically. + minecraftVersion("1.21.5") + } +} + +def targetJavaVersion = 21 +java { + def javaVersion = JavaVersion.toVersion(targetJavaVersion) + sourceCompatibility = javaVersion + targetCompatibility = javaVersion + if (JavaVersion.current() < javaVersion) { + toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) + } +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' + + if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { + options.release.set(targetJavaVersion) + } +} + +processResources { + def props = [version: version] + inputs.properties props + filteringCharset 'UTF-8' + filesMatching('plugin.yml') { + expand props + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..e69de29 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e644113 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a441313 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..b740cf1 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 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 POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +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 + if ! command -v java >/dev/null 2>&1 + then + 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 +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# 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"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..25da30d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@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=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +: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 %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..55cac3d --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'ServerSplitter' diff --git a/src/main/java/me/trouper/serversplitter/ServerSplitter.java b/src/main/java/me/trouper/serversplitter/ServerSplitter.java new file mode 100644 index 0000000..f48f4ce --- /dev/null +++ b/src/main/java/me/trouper/serversplitter/ServerSplitter.java @@ -0,0 +1,42 @@ +package me.trouper.serversplitter; + +import me.trouper.serversplitter.server.Manager; +import org.bukkit.plugin.java.JavaPlugin; + +public final class ServerSplitter extends JavaPlugin { + + private static ServerSplitter instance; + private Manager manager; + + @Override + public void onLoad() { + getLogger().info("Instantiating Plugin"); + instance = this; + } + + @Override + public void onEnable() { + getLogger().info("Instantiating Manager"); + manager = new Manager(); + + getLogger().info("Initializing Manager"); + manager.init(); + + getLogger().info("Successfully enabled Alias."); + } + + @Override + public void onDisable() { + getLogger().info("Saved all IO files."); + manager.io.saveAll(); + getLogger().info("Saved all IO files."); + } + + public static ServerSplitter getInstance() { + return instance; + } + + public Manager getManager() { + return manager; + } +} diff --git a/src/main/java/me/trouper/serversplitter/data/IO.java b/src/main/java/me/trouper/serversplitter/data/IO.java new file mode 100644 index 0000000..6e0824c --- /dev/null +++ b/src/main/java/me/trouper/serversplitter/data/IO.java @@ -0,0 +1,33 @@ +package me.trouper.serversplitter.data; + +import me.trouper.serversplitter.ServerSplitter; +import me.trouper.serversplitter.data.io.Config; +import me.trouper.serversplitter.utils.JsonSerializable; +import org.bukkit.Server; + +import java.io.File; + +public class IO { + public final File DATA_FOLDER; + public final File CONFIG_FILE; + public final File STORAGE_FILE; + public Config config; + + public IO() { + DATA_FOLDER = ServerSplitter.getInstance().getDataFolder(); + CONFIG_FILE = new File(DATA_FOLDER,"/config.json"); + STORAGE_FILE = new File(DATA_FOLDER,"/storage.json"); + config = JsonSerializable.load(CONFIG_FILE, Config.class,new Config()); + + } + + public void loadAll() { + ServerSplitter.getInstance().getLogger().info("Loading all IO Files"); + config = JsonSerializable.load(CONFIG_FILE,Config.class,new Config()); + } + + public void saveAll() { + ServerSplitter.getInstance().getLogger().info("Saving all IO Files"); + config.save(); + } +} diff --git a/src/main/java/me/trouper/serversplitter/data/io/Config.java b/src/main/java/me/trouper/serversplitter/data/io/Config.java new file mode 100644 index 0000000..068954b --- /dev/null +++ b/src/main/java/me/trouper/serversplitter/data/io/Config.java @@ -0,0 +1,59 @@ +package me.trouper.serversplitter.data.io; + +import me.trouper.serversplitter.ServerSplitter; +import me.trouper.serversplitter.utils.JsonSerializable; +import me.trouper.serversplitter.utils.Verbose; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class Config implements JsonSerializable { + + public boolean debugMode = false; + public List debuggerExclusions = new ArrayList<>(); + + @Override + public File getFile() { + return ServerSplitter.getInstance().getManager().io.CONFIG_FILE; + } + + @Override + public void save() { + Verbose.send(1,"Saving Config..."); + JsonSerializable.super.save(); + } + + public Messages messages = new Messages(); + + public class Messages { + public String mainColor = "&#ffaaff"; + public String prefix = "&9Splitter> &7"; + public String pluginName = "ServerSplitter"; + public boolean fancyAlerts = true; + } + + public List splitProcesses = new ArrayList<>(List.of( + new JavaVirtualMachine("data-aux","velocity-3.4.0-SNAPSHOT-509.jar",List.of( + "-Xms1024M", + "-Xmx1024M", + "-XX:+UseG1GC", + "-XX:G1HeapRegionSize=4M", + "-XX:+UnlockExperimentalVMOptions", + "-XX:+ParallelRefProcEnabled", + "-XX:+AlwaysPreTouch", + "-XX:MaxInlineLevel=15" + ), List.of("--nogui")), + new JavaVirtualMachine("data-minehut","velocity-3.4.0-SNAPSHOT-509.jar",List.of( + "-Xms1024M", + "-Xmx1024M", + "-XX:+UseG1GC", + "-XX:G1HeapRegionSize=4M", + "-XX:+UnlockExperimentalVMOptions", + "-XX:+ParallelRefProcEnabled", + "-XX:+AlwaysPreTouch", + "-XX:MaxInlineLevel=15", + "-Dmojang.sessionserver=https://api.minehut.com/mitm/proxy/session/minecraft/hasJoined" + ), List.of("--nogui")) + )); +} diff --git a/src/main/java/me/trouper/serversplitter/data/io/JavaVirtualMachine.java b/src/main/java/me/trouper/serversplitter/data/io/JavaVirtualMachine.java new file mode 100644 index 0000000..c6d170e --- /dev/null +++ b/src/main/java/me/trouper/serversplitter/data/io/JavaVirtualMachine.java @@ -0,0 +1,76 @@ +package me.trouper.serversplitter.data.io; + +import me.trouper.serversplitter.server.processes.Spawner; +import me.trouper.serversplitter.utils.FileValidationUtils; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class JavaVirtualMachine { + private String workingDirPath; + private String jarFilePath; + private List jvmArgs; + private List programArgs; + + public JavaVirtualMachine() { + this.workingDirPath = ""; + this.jarFilePath = ""; + this.jvmArgs = new ArrayList<>(List.of("")); + this.programArgs = new ArrayList<>(List.of("")); + } + + public JavaVirtualMachine(String workingDirPath, String jarFilePath, List jvmArgs, List programArgs) { + this.workingDirPath = workingDirPath; + this.jarFilePath = jarFilePath; + this.jvmArgs = jvmArgs; + this.programArgs = programArgs; + } + + public File getWorkingDir() { + File workingDir = new File(workingDirPath); + FileValidationUtils.validate(workingDir); + return workingDir; + } + + public File getJarFile() { + return new File(jarFilePath); + } + + public String getWorkingDirPath() { + return workingDirPath; + } + + public void setWorkingDirPath(String workingDirPath) { + this.workingDirPath = workingDirPath; + } + + public String getJarFilePath() { + return jarFilePath; + } + + public void setJarFilePath(String jarFilePath) { + this.jarFilePath = jarFilePath; + } + + public List getJvmArgs() { + return jvmArgs; + } + + public void setJvmArgs(List jvmArgs) { + this.jvmArgs = jvmArgs; + } + + public List getProgramArgs() { + return programArgs; + } + + public void setProgramArgs(List programArgs) { + this.programArgs = programArgs; + } + + public Process start() throws IOException { + return Spawner.launchJVM(this.getWorkingDir(),this.getJarFile(),this.getJvmArgs(),getProgramArgs()); + } +} diff --git a/src/main/java/me/trouper/serversplitter/server/Main.java b/src/main/java/me/trouper/serversplitter/server/Main.java new file mode 100644 index 0000000..4650b9e --- /dev/null +++ b/src/main/java/me/trouper/serversplitter/server/Main.java @@ -0,0 +1,67 @@ +package me.trouper.serversplitter.server; + +import io.papermc.paper.registry.RegistryAccess; +import me.trouper.serversplitter.ServerSplitter; +import me.trouper.serversplitter.data.IO; +import me.trouper.serversplitter.data.io.Config; +import me.trouper.serversplitter.utils.Text; +import org.bukkit.command.CommandSender; + +import java.util.Random; +import java.util.function.BooleanSupplier; + +public interface Main { + Main main = new Main() {}; + + Random random = new Random(); + + default RegistryAccess getRegistryAccess() { + return RegistryAccess.registryAccess(); + } + + default ServerSplitter getPlugin() { + return ServerSplitter.getInstance(); + } + + default Manager man() { + return getPlugin().getManager(); + } + + default IO io() { + return man().io; + }; + + default Config config() { + return io().config; + } + + default void info(CommandSender player, String message, Object... args) { + Text.sendMessage(Text.Pallet.INFO, player, message, args); + } + + default void error(CommandSender player, String message, Object... args) { + Text.sendMessage(Text.Pallet.ERROR, player, message, args); + } + + default void warning(CommandSender player, String message, Object... args) { + Text.sendMessage(Text.Pallet.WARNING, player, message, args); + } + + default void success(CommandSender player, String message, Object... args) { + Text.sendMessage(Text.Pallet.SUCCESS, player, message, args); + } + + default void message(CommandSender player, String message, Object... args) { + Text.sendMessage(Text.Pallet.NEUTRAL, player, message, args); + } + + default void checkPre(boolean check, String msg, Object... args) { + if (!check) { + throw new IllegalArgumentException(msg.formatted(args)); + } + } + + default void checkPre(BooleanSupplier check, String msg, Object... args) { + checkPre(check.getAsBoolean(), msg, args); + } +} diff --git a/src/main/java/me/trouper/serversplitter/server/Manager.java b/src/main/java/me/trouper/serversplitter/server/Manager.java new file mode 100644 index 0000000..45c331b --- /dev/null +++ b/src/main/java/me/trouper/serversplitter/server/Manager.java @@ -0,0 +1,19 @@ +package me.trouper.serversplitter.server; + +import me.trouper.serversplitter.data.IO; +import me.trouper.serversplitter.server.processes.ProcessManager; + +public class Manager { + public IO io; + public ProcessManager processManager; + + public Manager() { + io = new IO(); + processManager = new ProcessManager(); + } + + public void init() { + io.loadAll(); + processManager.startJVMs(); + } +} \ No newline at end of file diff --git a/src/main/java/me/trouper/serversplitter/server/processes/ProcessManager.java b/src/main/java/me/trouper/serversplitter/server/processes/ProcessManager.java new file mode 100644 index 0000000..0828847 --- /dev/null +++ b/src/main/java/me/trouper/serversplitter/server/processes/ProcessManager.java @@ -0,0 +1,17 @@ +package me.trouper.serversplitter.server.processes; + +import me.trouper.serversplitter.data.io.JavaVirtualMachine; +import me.trouper.serversplitter.server.Main; + +public class ProcessManager implements Main { + public void startJVMs() { + for (JavaVirtualMachine vm : main.io().config.splitProcesses) { + try { + vm.start(); + } catch (Exception ex) { + main.getPlugin().getLogger().severe("Warning. A JVM process has had an error running or initializing!"); + ex.printStackTrace(); + } + } + } +} diff --git a/src/main/java/me/trouper/serversplitter/server/processes/Spawner.java b/src/main/java/me/trouper/serversplitter/server/processes/Spawner.java new file mode 100644 index 0000000..28e6e50 --- /dev/null +++ b/src/main/java/me/trouper/serversplitter/server/processes/Spawner.java @@ -0,0 +1,31 @@ +package me.trouper.serversplitter.server.processes; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class Spawner { + + public static Process launchJVM(File workingDir, File jarFile, List jvmArgs, List programArgs) throws IOException { + if (!jarFile.exists() || !jarFile.isFile()) throw new IllegalArgumentException("Jar file does not exist: " + jarFile.getAbsolutePath()); + if (!workingDir.exists()) workingDir.mkdirs(); + + List command = new ArrayList<>(); + command.add("java"); + + if (jvmArgs != null) command.addAll(jvmArgs); + + command.add("-jar"); + command.add(jarFile.getAbsolutePath()); + + if (programArgs != null) command.addAll(programArgs); + + ProcessBuilder proc = new ProcessBuilder(command) + .directory(workingDir) + .redirectOutput(ProcessBuilder.Redirect.INHERIT) + .redirectError(ProcessBuilder.Redirect.INHERIT); + + return proc.start(); + } +} diff --git a/src/main/java/me/trouper/serversplitter/utils/ArrayUtils.java b/src/main/java/me/trouper/serversplitter/utils/ArrayUtils.java new file mode 100644 index 0000000..7e5ee35 --- /dev/null +++ b/src/main/java/me/trouper/serversplitter/utils/ArrayUtils.java @@ -0,0 +1,71 @@ +package me.trouper.serversplitter.utils; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + +public final class ArrayUtils { + + /** + * Transforms an array to another one + * @param e iterable list + * @param a action + * @return new transformed list + * @param input + * @param output + */ + public static List map(Iterable e, Function a) { + List list = new ArrayList<>(); + e.forEach(i -> list.add(a.apply(i))); + return list; + } + + public static String toPrettyString(List list) { + return "§7[§e" + String.join("§7, §e", ArrayUtils.map(list, Object::toString)) + "§7]"; + } + + public static > List enumNames(Class type, boolean lowercase) { + List names = new ArrayList<>(); + for (E constant : type.getEnumConstants()) { + String name = constant.name(); + names.add(lowercase ? name.toLowerCase() : name); + } + return names; + } + + public static > List enumNames(Class type) { + return enumNames(type, true); + } + + public static List playerNames() { + return map(Bukkit.getOnlinePlayers(), Player::getName); + } + + @SafeVarargs + public static List bind(Iterable tList, T... ts) { + List list = Arrays.asList(ts); + tList.forEach(list::add); + return list; + } + + public static List reversed(List input) { + Collections.reverse(input); + return input; + } + + public static List reversed(Iterable input) { + List list = new ArrayList<>(); + input.forEach(list::add); + return reversed(list); + } + + public static void reverseForEach(Iterable input, Consumer action) { + reversed(input).forEach(action); + } +} diff --git a/src/main/java/me/trouper/serversplitter/utils/Cooldown.java b/src/main/java/me/trouper/serversplitter/utils/Cooldown.java new file mode 100644 index 0000000..434087b --- /dev/null +++ b/src/main/java/me/trouper/serversplitter/utils/Cooldown.java @@ -0,0 +1,42 @@ +package me.trouper.serversplitter.utils; + +import java.util.HashMap; +import java.util.Map; + +public class Cooldown { + + private final Map timer; + + public Cooldown() { + this.timer = new HashMap<>(); + } + + private O getOrDefault(O value, O def) { + return value != null ? value : def; + } + + public long getCooldown(T obj) { + return Math.max(getOrDefault(timer.get(obj), 0L) - System.currentTimeMillis(), 0L); + } + + public double getCooldownSec(T obj) { + final long cooldown = this.getCooldown(obj); + return Math.floor(cooldown / 10.0) / 100.0; + } + + public boolean isOnCooldown(T obj) { + return getCooldown(obj) > 0L; + } + + public void setCooldown(T obj, long millis) { + timer.put(obj, System.currentTimeMillis() + millis); + } + + public void addCooldown(T obj, long millis) { + setCooldown(obj, getCooldown(obj) + millis); + } + + public void removeCooldown(T obj) { + timer.remove(obj); + } +} diff --git a/src/main/java/me/trouper/serversplitter/utils/FileValidationUtils.java b/src/main/java/me/trouper/serversplitter/utils/FileValidationUtils.java new file mode 100644 index 0000000..60ba451 --- /dev/null +++ b/src/main/java/me/trouper/serversplitter/utils/FileValidationUtils.java @@ -0,0 +1,21 @@ +package me.trouper.serversplitter.utils; + +import java.io.File; + +public final class FileValidationUtils { + + public static boolean validate(File file) { + try { + if (!file.getParentFile().exists()) + if (!file.getParentFile().mkdirs()) + return false; + if (!file.exists()) + if (!file.createNewFile()) + return false; + return true; + } + catch (Exception ex) { + return false; + } + } +} diff --git a/src/main/java/me/trouper/serversplitter/utils/JsonSerializable.java b/src/main/java/me/trouper/serversplitter/utils/JsonSerializable.java new file mode 100644 index 0000000..f7538c7 --- /dev/null +++ b/src/main/java/me/trouper/serversplitter/utils/JsonSerializable.java @@ -0,0 +1,169 @@ +package me.trouper.serversplitter.utils; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.io.*; + +public interface JsonSerializable { + + Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls().setLenient().create(); + + /** + * Get the file this JsonSerializable should be saved to/loaded from + * @return The file for this JsonSerializable + */ + File getFile(); + + default String serialize(boolean pretty) { + Gson gson; + if (pretty) { + gson = new GsonBuilder().setPrettyPrinting().setLenient().create(); + } + else { + gson = new Gson(); + } + + try { + String json = gson.toJson(this); + if (json == null) { + throw new IllegalStateException("json parse failed for " + this.getClass().getSimpleName()); + } + return json; + } + catch (Exception ex) { + return "{}"; + } + } + + @SuppressWarnings("unchecked") + default T deserialize(String json) { + try { + JsonSerializable v = gson.fromJson(json, this.getClass()); + if (v == null) { + throw new IllegalStateException("json parse failed"); + } + return (T)v; + } + catch (Exception ex) { + return null; + } + } + + default JsonObject getJson() { + return gson.toJsonTree(this).getAsJsonObject(); + } + + /** + * Gets a json element given the specified member path + * @param path Path separated by a period . between each member name + * @return the JsonElement at the end of the path, otherwise null + */ + default JsonElement get(String path) { + JsonElement root = gson.toJsonTree(this); + JsonElement json = root; + + for (String memberName : path.split("\\.")) { + JsonElement e = json.getAsJsonObject().get(memberName); + if (e != null) + json = e; + else + break; + } + + return json == root ? null : json; + } + + /** + * Gets a json element given the specified member path + * @param path Path separated by a period . between each member name + */ + default boolean set(String path, Object obj) { + JsonElement root = gson.toJsonTree(this); + JsonElement json = root; + String[] paths = path.split("\\."); + + if (paths.length == 0) + return false; + if (paths.length == 1) { + root.getAsJsonObject().add(path, gson.toJsonTree(obj)); + return true; + } + + for (int i = 0; i < paths.length - 1; i++) { + JsonElement e = json.getAsJsonObject().get(paths[i]); + if (e != null) + json = e; + else + break; + } + + if (json != root) { + json.getAsJsonObject().add(paths[paths.length - 1], gson.toJsonTree(obj)); + return true; + } + return false; + } + + default void save() { + String json = serialize(true); + File f = getFile(); + + if (FileValidationUtils.validate(f)) { + try { + FileWriter fw = new FileWriter(f); + BufferedWriter bw = new BufferedWriter(fw); + bw.write(json); + bw.close(); + } + catch (Exception ex) { + ex.printStackTrace(); + } + } + } + + default O getOrDef(O val, O def) { + return val != null ? val : def; + } + + /** + * Load a JsonSerializable from a file + * @param file The file to load from + * @param jsonSerializable The class type to deserialize to + * @param fallback Fallback instance if loading fails + * @return The loaded instance or fallback + */ + static > T load(File file, Class jsonSerializable, T fallback) { + if (FileValidationUtils.validate(file)) { + try { + FileReader fr = new FileReader(file); + BufferedReader br = new BufferedReader(fr); + T t = gson.fromJson(br, jsonSerializable); + br.close(); + + if (t == null) { + throw new IllegalStateException("json parse failed!"); + } + + return t; + } + catch (Exception ex) { + ex.printStackTrace(); + } + } + return fallback; + } + + /** + * Load a JsonSerializable from a file path + * @param path The file path to load from + * @param jsonSerializable The class type to deserialize to + * @param fallback Fallback instance if loading fails + * @return The loaded instance or fallback + */ + static > T load(String path, Class jsonSerializable, T fallback) { + return load(new File(path), jsonSerializable, fallback); + } +} \ No newline at end of file diff --git a/src/main/java/me/trouper/serversplitter/utils/Randomizer.java b/src/main/java/me/trouper/serversplitter/utils/Randomizer.java new file mode 100644 index 0000000..f6ce0ce --- /dev/null +++ b/src/main/java/me/trouper/serversplitter/utils/Randomizer.java @@ -0,0 +1,81 @@ +package me.trouper.serversplitter.utils; + +import java.util.List; + +public class Randomizer { + + public Randomizer() { + + } + + public T getRandomElement(List list) { + if (list == null || list.isEmpty()) { + return null; + } + return list.get(getRandomIndex(list.size())); + } + + @SafeVarargs + public final T getRandomElement(T... list) { + if (list == null || list.length == 0) { + return null; + } + return list[getRandomIndex(list.length)]; + } + + private int getRandomIndex(int listSize) { + if (listSize < 0) { + listSize = 0; + } + return (int)(Math.ceil(Math.random() * listSize) - 1); + } + + public boolean getRandomBoolean() { + return Math.random() < 0.5; + } + + /** + * 'Percentage' means an integer from 0-100. You should not divide this value by 100, as this does it for you. + * @param percentage an integer 0-100 + * @return true if chance hit, false otherwise + */ + public boolean getRandomChance(int percentage) { + return Math.random() < percentage / 100.0; + } + + public int getRandomInt(int min, int max) { + if (min > max) { + throw new IllegalArgumentException("min cannot be greater than max!"); + } + int range = max - min + 1; + return min + (int)(Math.random() * range); + } + + public int getRandomInt(int max) { + return getRandomInt(0, max); + } + + public double getRandomDouble(double min, double max) { + if (min > max) { + throw new IllegalArgumentException("min cannot be greater than max!"); + } + double range = max - min; + return min + Math.random() * range; + } + + public double getRandomDouble(double max) { + return getRandomDouble(0.0, max); + } + + public float getRandomFloat(float min, float max) { + if (min > max) { + throw new IllegalArgumentException("min cannot be greater than max!"); + } + float range = max - min; + return (float)(min + Math.random() * range); + } + + public float getRandomFloat(float max) { + return getRandomFloat(0.0F, max); + } +} \ No newline at end of file diff --git a/src/main/java/me/trouper/serversplitter/utils/Text.java b/src/main/java/me/trouper/serversplitter/utils/Text.java new file mode 100644 index 0000000..ce0b808 --- /dev/null +++ b/src/main/java/me/trouper/serversplitter/utils/Text.java @@ -0,0 +1,312 @@ +package me.trouper.serversplitter.utils; + +import me.trouper.serversplitter.server.Main; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.bukkit.Sound; +import org.bukkit.SoundCategory; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Text implements Main { + + public static String legacyColor(String msg) { + return msg.replaceAll("&","§"); + } + + public static Component color(String msg) { + return LegacyComponentSerializer.legacyAmpersand().deserialize(msg); + } + + public static void sendWarning(CommandSender sender, String warning, Object... args) { + sendMessage(Pallet.WARNING, sender, warning, args); + } + + public static void sendError(CommandSender sender, String error, Object... args) { + sendMessage(Pallet.ERROR, sender, error, args); + } + + public static void sendMessage(CommandSender sender, String text, Object... args) { + sendMessage(Pallet.NEUTRAL, sender, text, args); + } + + public static void sendMessage(Pallet pallet, CommandSender sender, String text, Object... args) { + text = formatArgsLegacy(pallet, text, args); + sendMessage(sender, text); + if (sender instanceof Player p) p.playSound(p.getLocation(),pallet.sound.sound, SoundCategory.VOICE,10f,pallet.sound.pitch); + } + + public static void sendMessage(CommandSender sender, String text) { + Component message = getMessage(text); + sender.sendMessage(message); + } + + public static Component getMessage(Pallet pallet, String text, Object... args) { + return getMessage(formatArgsLegacy(pallet, text, args)); + } + + public static Component getMessage(String text) { + if (main.config().messages.fancyAlerts) { + return formatFancyMessage(text); + } else { + return color(main.config().messages.prefix + text); + } + } + + public static String formatArgsLegacy(Pallet pallet, String format, Object... args) { + return LegacyComponentSerializer.legacyAmpersand().serialize(formatArgs(pallet,format,args)); + } + + public static Component formatArgs(Pallet pallet, String format, Object... args) { + Component message = Component.empty(); + Pattern pattern = Pattern.compile("\\{(\\d+)}"); + Matcher matcher = pattern.matcher(format); + int lastIndex = 0; + + while (matcher.find()) { + String prefix = format.substring(lastIndex, matcher.start()); + if (!prefix.isEmpty()) { + message = message.append(Component.text(prefix).color(pallet.mainText)); + } + + int argIndex = Integer.parseInt(matcher.group(1)); + TextColor argColor = getArgColor(pallet, argIndex); + + if (argIndex >= 0 && argIndex < args.length) { + String argText = args[argIndex].toString(); + message = message.append(Component.text(argText).color(argColor)); + } else { + message = message.append(Component.text(matcher.group()).color(pallet.mainText)); + } + + lastIndex = matcher.end(); + } + + String suffix = format.substring(lastIndex); + if (!suffix.isEmpty()) { + message = message.append(Component.text(suffix).color(pallet.mainText)); + } + + return message; + } + + public static Component formatFancyMessage(String text) { + Component message = Component.empty().appendNewline(); + + List wrappedLines = wrapText(text, 50, (int) Math.round((main.config().messages.pluginName.length() + 3) * 1.3)); + + message = message + .append(color(main.config().messages.mainColor + "| ").decorate(TextDecoration.BOLD)) + .append(Component.text(main.config().messages.pluginName + " ", NamedTextColor.WHITE, TextDecoration.BOLD)) + .append(color(wrappedLines.getFirst())); + + String active = getActiveFormatting(wrappedLines.getFirst()); + + wrappedLines.removeFirst(); + + for (String wrappedLine : wrappedLines) { + wrappedLine = active + wrappedLine; + + active = getActiveFormatting(wrappedLine); + message = message + .appendNewline() + .append(color(main.config().messages.mainColor + "| ").decorate(TextDecoration.BOLD)) + .append(color(wrappedLine)); + } + + return message.appendNewline(); + } + + public static List wrapText(String text, int maxLineLength, int offset) { + List lines = new ArrayList<>(); + + if (text == null || text.isEmpty() || maxLineLength <= 0) { + return lines; + } + + String[] words = text.split("\\s+"); + StringBuilder currentLine = new StringBuilder(); + int currentLineLength = offset; + + for (String word : words) { + if (currentLineLength + word.length() + 1 > maxLineLength) { + lines.add(currentLine.toString()); + currentLine = new StringBuilder(); + currentLineLength = 0; + } + + if (!currentLine.isEmpty()) { + currentLine.append(" "); + currentLineLength++; + } + + currentLine.append(word); + currentLineLength += word.length(); + } + + if (!currentLine.isEmpty()) { + lines.add(currentLine.toString()); + } + + return lines; + } + + public static String getActiveFormatting(String text) { + final Pattern pattern = Pattern.compile("&[0-9a-fk-or]"); + final Matcher matcher = pattern.matcher(text); + + String lastColor = ""; + Set activeFormats = new HashSet<>(); + + while (matcher.find()) { + String code = matcher.group(); + char identifier = code.charAt(1); + + if (identifier >= '0' && identifier <= '9' || identifier >= 'a' && identifier <= 'f') { + lastColor = code; + activeFormats.clear(); + } else if (identifier >= 'k' && identifier <= 'o') { + activeFormats.add(identifier); + } else if (identifier == 'r') { + lastColor = ""; + activeFormats.clear(); + } + } + + StringBuilder result = new StringBuilder(lastColor); + for (char format : activeFormats) { + result.append("&").append(format); + } + + return result.toString(); + } + + private static TextColor getArgColor(Pallet pallet, int argIndex) { + return switch (argIndex) { + case 1 -> pallet.arg2; + case 2 -> pallet.arg3; + default -> pallet.argDefault; + }; + } + + public enum Pallet { + ERROR( + NamedTextColor.RED, + NamedTextColor.YELLOW, + NamedTextColor.GOLD, + NamedTextColor.DARK_RED, + new SoundData(Sound.BLOCK_NOTE_BLOCK_BASS,1)), + WARNING( + NamedTextColor.YELLOW, + NamedTextColor.GOLD, + NamedTextColor.RED, + NamedTextColor.DARK_RED, + new SoundData(Sound.BLOCK_NOTE_BLOCK_BIT,0.5F)), + INFO( + NamedTextColor.GRAY, + NamedTextColor.WHITE, + NamedTextColor.AQUA, + NamedTextColor.DARK_AQUA, + new SoundData(Sound.BLOCK_NOTE_BLOCK_BELL,1)), + SUCCESS( + NamedTextColor.GREEN, + NamedTextColor.DARK_GREEN, + NamedTextColor.YELLOW, + NamedTextColor.GOLD, + new SoundData(Sound.BLOCK_NOTE_BLOCK_CHIME,1)), + NEUTRAL( + NamedTextColor.GRAY, + NamedTextColor.WHITE, + NamedTextColor.DARK_AQUA, + NamedTextColor.BLUE, + new SoundData(Sound.BLOCK_NOTE_BLOCK_BELL,1)); + + private final TextColor mainText; + private final TextColor argDefault; + private final TextColor arg2; + private final TextColor arg3; + private final SoundData sound; + + Pallet(TextColor mainText, TextColor argDefault, TextColor arg2, TextColor arg3, SoundData sound) { + this.mainText = mainText; + this.argDefault = argDefault; + this.arg2 = arg2; + this.arg3 = arg3; + this.sound = sound; + } + } + + public record SoundData(Sound sound, float pitch){}; + + public static String generateProgressBar(int length, int max, int current) { + if (max <= 0) { + throw new IllegalArgumentException("Max value must be greater than 0"); + } + + current = Math.max(0, Math.min(current, max)); + double percent = (double) current / max; + int filledBars = (int) Math.round(percent * length); + + StringBuilder progressBar = new StringBuilder(); + for (int i = 0; i < length; i++) { + if (i < filledBars) { + progressBar.append("&a|"); + } else { + progressBar.append("&7|"); + } + } + + return progressBar.toString(); + } + + public static String formatEnum(Enum obj) { + if (obj == null) return "Null"; + String name = obj.name(); + String[] words = name.toLowerCase().split("_"); + + StringBuilder formatted = new StringBuilder(); + + for (String word : words) { + if (!word.isEmpty()) { + formatted.append(Character.toUpperCase(word.charAt(0))) + .append(word.substring(1)) + .append(" "); + } + } + + return formatted.toString().trim(); + } + + public static int getLevel(String s) { + HashMap romanToInt = new HashMap(); + romanToInt.put(Character.valueOf('I'), 1); + romanToInt.put(Character.valueOf('V'), 5); + romanToInt.put(Character.valueOf('X'), 10); + romanToInt.put(Character.valueOf('L'), 50); + romanToInt.put(Character.valueOf('C'), 100); + romanToInt.put(Character.valueOf('D'), 500); + romanToInt.put(Character.valueOf('M'), 1000); + int result = 0; + for (int i = 0; i < s.length(); ++i) { + if (i > 0 && romanToInt.get(Character.valueOf(s.charAt(i))) > romanToInt.get(Character.valueOf(s.charAt(i - 1)))) { + result += romanToInt.get(Character.valueOf(s.charAt(i))) - 2 * romanToInt.get(Character.valueOf(s.charAt(i - 1))); + continue; + } + if (romanToInt.get(Character.valueOf(s.charAt(i))) == null) { + return 0; + } + result += (romanToInt.get(Character.valueOf(s.charAt(i)))).intValue(); + } + if (result > 255) { + result = 1; + } + return result; + } +} \ No newline at end of file diff --git a/src/main/java/me/trouper/serversplitter/utils/Verbose.java b/src/main/java/me/trouper/serversplitter/utils/Verbose.java new file mode 100644 index 0000000..e556c97 --- /dev/null +++ b/src/main/java/me/trouper/serversplitter/utils/Verbose.java @@ -0,0 +1,71 @@ +package me.trouper.serversplitter.utils; + +import me.trouper.serversplitter.ServerSplitter; +import me.trouper.serversplitter.server.Main; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +public class Verbose implements Main { + + public static void send(int backtrace, String message, Object... args) { + if (!ServerSplitter.getInstance().getManager().io.config.debugMode) return; + String callerInfo = "Unknown Caller"; + + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + if (stackTrace.length > 2 + backtrace) { + StackTraceElement caller = stackTrace[2 + backtrace]; + + String className = caller.getClassName(); + className = className.substring(className.lastIndexOf(".") + 1); + if (className.contains("-")) { + callerInfo = "Protected"; + } else { + callerInfo = className + "." + caller.getMethodName(); + } + + if (ServerSplitter.getInstance().getManager().io.config.debuggerExclusions.contains(callerInfo)) { + return; + } + } + + String formattedMessage = message.formatted(args); + String log = "[DEBUG ^ %s] [%s]: %s".formatted(backtrace, callerInfo, formattedMessage); + ServerSplitter.getInstance().getLogger().info(log); + + for (Player operator : Bukkit.getOnlinePlayers()) { + if (operator.isOp()) operator.sendMessage("§d§l%s §7[§bDEBUG ^ %s§7] §7[§e%s§7] §8» §7%s" + .formatted(main.config().messages.pluginName,backtrace, callerInfo, formattedMessage)); + } + } + + public static void send(String message, Object... args) { + if (!ServerSplitter.getInstance().getManager().io.config.debugMode) return; + String callerInfo = "Unknown Caller"; + + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + if (stackTrace.length > 2) { + StackTraceElement caller = stackTrace[2]; + + String className = caller.getClassName(); + className = className.substring(className.lastIndexOf(".") + 1); + if (className.contains("-")) { + callerInfo = "Protected"; + } else { + callerInfo = className + "." + caller.getMethodName(); + } + + if (main.config().debuggerExclusions.contains(callerInfo)) { + return; + } + } + + String formattedMessage = message.formatted(args); + String log = "[DEBUG] [%s]: %s".formatted(callerInfo, formattedMessage); + ServerSplitter.getInstance().getLogger().info(log); + + for (Player operator : Bukkit.getOnlinePlayers()) { + if (operator.isOp()) operator.sendMessage("§d§l%s §7[§bDEBUG§7] §7[§e%s§7] §8» §7%s" + .formatted(main.config().messages.pluginName,callerInfo, formattedMessage)); + } + } +} diff --git a/src/main/java/me/trouper/serversplitter/utils/Voidable.java b/src/main/java/me/trouper/serversplitter/utils/Voidable.java new file mode 100644 index 0000000..66e1dcf --- /dev/null +++ b/src/main/java/me/trouper/serversplitter/utils/Voidable.java @@ -0,0 +1,59 @@ +package me.trouper.serversplitter.utils; + +import me.trouper.serversplitter.server.Main; + +import java.util.function.Consumer; +import java.util.function.Function; + +public class Voidable implements Main { + + private final T value; + + private Voidable(T value) { + this.value = value; + } + + public T get() { + return value; + } + + public boolean isPresent() { + return value != null; + } + + public Voidable map(Function function) { + return isPresent() ? of(function.apply(value)) : of(null); + } + + public void accept(Consumer action) { + if (isPresent()) { + action.accept(value); + } + } + + public void accept(Consumer action, Runnable orElse) { + if (isPresent()) { + action.accept(value); + } + else { + orElse.run(); + } + } + + public T getOrDef(T fallback) { + return isPresent() ? value : fallback; + } + + public T getOrThrow(String msg, Object... args) { + checkPre(isPresent(), msg, args); + return value; + } + + public T getOrThrow() { + return getOrThrow("value is not present."); + } + + public static Voidable of(T value) { + return new Voidable<>(value); + } +} \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..68f8c49 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,7 @@ +name: ServerSplitter +version: '1.0-SNAPSHOT' +main: me.trouper.serversplitter.ServerSplitter +api-version: '1.21' +load: STARTUP +authors: [ obvWolf ] +description: Spawns new JVMs from your Bukkit Server