Commit so i can branch it.

This commit is contained in:
thetrouper
2025-06-19 10:02:38 -05:00
committed by wolf
parent f23c44bbd2
commit e5adcd112f
41 changed files with 4474 additions and 376 deletions

View File

@@ -1,6 +1,7 @@
plugins { plugins {
id 'java' id 'java'
id("xyz.jpenilla.run-paper") version "2.3.1" id("xyz.jpenilla.run-paper") version "2.3.1"
id 'maven-publish'
} }
group = 'me.trouper' group = 'me.trouper'
@@ -24,9 +25,6 @@ dependencies {
tasks { tasks {
runServer { 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") minecraftVersion("1.21.5")
} }
} }
@@ -57,3 +55,18 @@ processResources {
expand props expand props
} }
} }
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
groupId = project.group
artifactId = 'alias'
version = project.version
}
}
repositories {
mavenLocal()
}
}

0
gradle.properties Normal file
View File

Binary file not shown.

View File

@@ -1,6 +1,7 @@
#Thu May 29 23:20:25 CDT 2025
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

41
gradlew vendored
View File

@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # 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. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +80,11 @@ do
esac esac
done done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # This is normally unused
# shellcheck disable=SC2034
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@@ -133,22 +131,29 @@ location of your Java installation."
fi fi
else else
JAVACMD=java JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 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 Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) 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 ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
case $MAX_FD in #( case $MAX_FD in #(
'' | soft) :;; #( '' | 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" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac esac
@@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then
done done
fi fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
# shell script including quotes and variable substitutions, so put them in DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded. # 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 -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
@@ -205,6 +214,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \ 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. # Use "xargs" to parse quoted args.
# #
# With -n1 it outputs one arg per line, with the quotes and backslashes removed. # With -n1 it outputs one arg per line, with the quotes and backslashes removed.

35
gradlew.bat vendored
View File

@@ -14,7 +14,7 @@
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@if "%DEBUG%" == "" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@@ -40,13 +41,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@@ -56,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd if %ERRORLEVEL% equ 0 goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 set EXIT_CODE=%ERRORLEVEL%
exit /b 1 if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal

View File

@@ -1 +1 @@
rootProject.name = 'Alias' rootProject.name = 'Alias'

View File

@@ -1,46 +1,46 @@
package me.trouper.alias; package me.trouper.alias;
import me.trouper.alias.server.Manager; import me.trouper.alias.data.Common;
import me.trouper.alias.utils.visual.BlockDisplayRaytracer; import me.trouper.alias.server.AutoRegistrar;
import org.bukkit.NamespacedKey; import me.trouper.alias.server.commands.QuickCommand;
import me.trouper.alias.server.events.GuiListener;
import me.trouper.alias.server.events.QuickListener;
import me.trouper.alias.server.systems.AbstractWand;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
public final class Alias extends JavaPlugin { public final class Alias extends JavaPlugin {
private static Alias instance; private static Class<? extends JavaPlugin> host;
private Manager manager; private static AutoRegistrar autoRegistrar;
private static Common common;
private static boolean enabled;
@Override
public void onLoad() { public static synchronized void register(JavaPlugin plugin, Common common) {
getLogger().info("Instantiating Plugin"); if (plugin == null || enabled) return;
instance = this; Alias.host = plugin.getClass();
Alias.common = common;
new GuiListener().register();
autoRegistrar = new AutoRegistrar(plugin);
autoRegistrar.loadAll(common.getPackageName());
enabled = true;
} }
@Override public static Class<? extends JavaPlugin> getHost() {
public void onEnable() { return host;
getLogger().info("Instantiating Manager");
manager = new Manager();
getLogger().info("Initializing Manager");
manager.init();
getLogger().info("Successfully enabled TrimAlias.");
} }
@Override public static AutoRegistrar getAutoRegistrar() {
public void onDisable() { return autoRegistrar;
getLogger().info("Saved all IO files.");
manager.io.saveAll();
getLogger().info("Saved all IO files.");
} }
public static me.trouper.alias.Alias getInstance() { public static Common getCommon() {
return instance; return common;
} }
public NamespacedKey getNameSpace() {
return new NamespacedKey(getInstance(),"_alias"); public static void updateCommon(Common common) {
} Alias.common = common;
public Manager getManager() {
return manager;
} }
} }

View File

@@ -0,0 +1,97 @@
package me.trouper.alias.data;
import java.util.HashSet;
import java.util.Set;
public class Common {
private final String packageName;
private int mainColor;
private int secondaryColor;
private String pluginName;
private String flatPrefix;
private boolean flat;
private boolean debugMode;
private final Set<String> debuggerExclusions;
public Common(String packageName, int mainColor, int secondaryColor, String pluginName, String flatPrefix, boolean flat) {
this.packageName = packageName;
this.mainColor = mainColor;
this.secondaryColor = secondaryColor;
this.pluginName = pluginName;
this.flatPrefix = flatPrefix;
this.flat = flat;
this.debugMode = false;
this.debuggerExclusions = new HashSet<>();
}
public String getPackageName() {
return packageName;
}
public int getMainColor() {
return mainColor;
}
public void setMainColor(int mainColor) {
this.mainColor = mainColor;
}
public String getPluginName() {
return pluginName;
}
public void setPluginName(String pluginName) {
this.pluginName = pluginName;
}
public String getFlatPrefix() {
return flatPrefix;
}
public void setFlatPrefix(String flatPrefix) {
this.flatPrefix = flatPrefix;
}
public boolean useFlat() {
return flat;
}
public void setFlat(boolean flat) {
this.flat = flat;
}
public int getSecondaryColor() {
return secondaryColor;
}
public void setSecondaryColor(int secondaryColor) {
this.secondaryColor = secondaryColor;
}
public boolean getDebugMode() {
return debugMode;
}
public void setDebugMode(boolean mode) {
this.debugMode = mode;
}
public Set<String> getDebuggerExclusions() {
return debuggerExclusions;
}
public boolean addDebuggerExclusion(String methodName) {
return this.debuggerExclusions.add(methodName);
}
public boolean removeDebuggerExclusion(String methodName) {
return this.debuggerExclusions.remove(methodName);
}
public String getTempTag() {
return "$/" + pluginName + "/ TEMP";
}
}

View File

@@ -0,0 +1,147 @@
package me.trouper.alias.data;
import com.google.gson.*;
import me.trouper.alias.utils.misc.FileValidationUtils;
import java.io.*;
public interface JsonSerializable<T> {
Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls().setStrictness(Strictness.LENIENT).create();
File getFile();
default String serialize(boolean pretty) {
Gson gson;
if (pretty) {
gson = new GsonBuilder().setPrettyPrinting().setStrictness(Strictness.LENIENT).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> O getOrDef(O val, O def) {
return val != null ? val : def;
}
static <T extends JsonSerializable<?>> T load(File file, Class<T> jsonSerializable, T fallback) {
if (FileValidationUtils.validate(file)) {
try {
FileReader fr = new FileReader(file);
BufferedReader br = new BufferedReader(fr);
T t = gson.fromJson(br, jsonSerializable);
if (t == null) {
throw new IllegalStateException("json parse failed!");
}
return t;
}
catch (Exception ex) {
ex.printStackTrace();
}
}
return fallback;
}
static <T extends JsonSerializable<?>> T load(String path, Class<T> jsonSerializable, T fallback) {
return load(new File(path), jsonSerializable, fallback);
}
}

View File

@@ -0,0 +1,78 @@
package me.trouper.alias.server;
import me.trouper.alias.server.commands.QuickCommand;
import me.trouper.alias.server.events.QuickListener;
import me.trouper.alias.server.systems.AbstractWand;
import me.trouper.alias.utils.misc.ReflectionUtils;
import org.bukkit.plugin.java.JavaPlugin;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
public class AutoRegistrar {
private final JavaPlugin plugin;
private final List<QuickCommand> quickCommands = new ArrayList<>();
private final List<QuickListener> quickListeners = new ArrayList<>();
private final List<AbstractWand> wands = new ArrayList<>();
public AutoRegistrar(JavaPlugin plugin) {
this.plugin = plugin;
}
public void loadAll(String basePackage) {
Set<Class<?>> classes = ReflectionUtils.getClassesInPackage(plugin, basePackage);
for (Class<?> clazz : classes) {
if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers()) || clazz.isEnum() || clazz.isAnnotation()) {
continue;
}
boolean isCommand = QuickCommand.class.isAssignableFrom(clazz);
boolean isWand = AbstractWand.class.isAssignableFrom(clazz);
boolean isListener = QuickListener.class.isAssignableFrom(clazz);
if (!isCommand && !isWand && !isListener) continue;
try {
Object instance = clazz.getDeclaredConstructor().newInstance();
if (instance instanceof QuickCommand command) {
command.register();
quickCommands.add(command);
plugin.getLogger().info("Registered QuickCommand: " + clazz.getSimpleName());
}
if (instance instanceof AbstractWand wand) {
wand.register();
wands.add(wand);
plugin.getLogger().info("Registered AbstractWand: " + clazz.getSimpleName());
}
else if (instance instanceof QuickListener listener) {
listener.register();
quickListeners.add(listener);
plugin.getLogger().info("Registered QuickListener: " + clazz.getSimpleName());
}
} catch (Throwable t) {
plugin.getLogger().log(Level.WARNING, "Failed to instantiate: " + clazz.getName(), t);
}
}
}
public List<QuickCommand> getQuickCommands() {
return quickCommands;
}
public List<QuickListener> getQuickListeners() {
return quickListeners;
}
public List<AbstractWand> getWands() {
return wands;
}
}

110
src/main/java/me/trouper/alias/server/Main.java Executable file → Normal file
View File

@@ -1,71 +1,83 @@
package me.trouper.alias.server; package me.trouper.alias.server;
import io.papermc.paper.registry.RegistryAccess; import io.papermc.paper.registry.RegistryAccess;
import me.trouper.alias.data.IO; import me.trouper.alias.Alias;
import me.trouper.alias.data.io.Config; import me.trouper.alias.data.Common;
import me.trouper.alias.data.io.Storage; import me.trouper.alias.server.systems.Text;
import me.trouper.alias.utils.Text; import me.trouper.alias.utils.misc.Randomizer;
import org.bukkit.command.CommandSender; import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.Random; import java.util.Random;
import java.util.function.BooleanSupplier;
public interface Main { public interface Main {
Main main = new Main() {}; Main main = new Main() {};
Random random = new Random();
default RegistryAccess getRegistryAccess() { default RegistryAccess getRegistryAccess() {
return RegistryAccess.registryAccess(); return RegistryAccess.registryAccess();
} }
default me.trouper.alias.Alias getPlugin() { default JavaPlugin getPlugin() {
return me.trouper.alias.Alias.getInstance(); Class<? extends JavaPlugin> host = Alias.getHost();
} if (host == null) throw new RuntimeException("Alias is not enabled. Make sure to call Alias#register() in your JavaPlugin#onLoad() method!");
return getPlugin(host);
default Manager man() {
return getPlugin().getManager();
}
default IO io() {
return man().io;
};
default Config config() {
return io().config;
}
default Storage storage() {
return io().storage;
}
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) { default <T extends JavaPlugin> T getPlugin(Class<T> pluginClass) {
Text.sendMessage(Text.Pallet.SUCCESS, player, message, args); return JavaPlugin.getPlugin(pluginClass);
} }
default void message(CommandSender player, String message, Object... args) { default Common getCommon() {
Text.sendMessage(Text.Pallet.NEUTRAL, player, message, args); return Alias.getCommon();
} }
default void checkPre(boolean check, String msg, Object... args) { default void infoAny(Audience player, String message, Object... args) {
if (!check) { Text.messageAny(Text.Pallet.INFO, player, message, args);
throw new IllegalArgumentException(msg.formatted(args));
}
} }
default void checkPre(BooleanSupplier check, String msg, Object... args) { default void errorAny(Audience player, String message, Object... args) {
checkPre(check.getAsBoolean(), msg, args); Text.messageAny(Text.Pallet.ERROR,player, message, args);
} }
default void warningAny(Audience player, String message, Object... args) {
Text.messageAny(Text.Pallet.WARNING, player, message, args);
}
default void successAny(Audience player, String message, Object... args) {
Text.messageAny(Text.Pallet.SUCCESS, player, message, args);
}
default void messageAny(Audience player, String message, Object... args) {
Text.messageAny(Text.Pallet.NEUTRAL, player, message, args);
}
default void info(Audience player, Component message, Component... args) {
Text.message(Text.Pallet.INFO, player, message, args);
}
default void error(Audience player, Component message, Component... args) {
Text.message(Text.Pallet.ERROR,player, message, args);
}
default void warning(Audience player, Component message, Component... args) {
Text.message(Text.Pallet.WARNING, player, message, args);
}
default void success(Audience player, Component message, Component... args) {
Text.message(Text.Pallet.SUCCESS, player, message, args);
}
default void message(Audience player, Component message, Component... args) {
Text.message(Text.Pallet.NEUTRAL, player, message, args);
}
default Random random() {
return new Random();
}
default Randomizer randomizer() {
return new Randomizer();
}
} }

View File

@@ -0,0 +1,115 @@
package me.trouper.alias.server.commands;
import java.util.function.Consumer;
public record Args(String... args) {
public Arg getAll() {
return getAll(0);
}
public Arg getAll(int beginIndex) {
String str = "";
for (int i = beginIndex; i < args.length; i++) {
str = str.concat(args[i] + " ");
}
return new Arg(str.trim());
}
public Arg get(int index) {
if (args.length == 0)
throw new IllegalArgumentException("not enough arguments: arguments are empty");
if (index < 0 || index >= args.length)
throw new IllegalArgumentException("not enough arguments: argument %s is missing".formatted(index + 1));
return new Arg(args[index]);
}
public Arg first() {
return get(0);
}
public Arg last() {
return get(args.length - 1);
}
public boolean match(int index, String arg) {
if (index < 0 || index >= args.length) {
return false;
}
return get(index).toString().equalsIgnoreCase(arg);
}
public void when(int index, String match, Consumer<Arg> action) {
if (match(index, match)) {
action.accept(get(index));
}
}
public int getSize() {
return args.length;
}
public boolean isEmpty() {
return args.length == 0;
}
public static class Arg {
private final String arg;
public Arg(String arg) {
this.arg = arg;
}
public int toInt() {
return Integer.parseInt(arg);
}
public long toLong() {
return Long.parseLong(arg);
}
public byte toByte() {
return Byte.parseByte(arg);
}
public short toShort() {
return Short.parseShort(arg);
}
public double toDouble() {
return Double.parseDouble(arg);
}
public float toFloat() {
return Float.parseFloat(arg);
}
public boolean toBool() {
return Boolean.parseBoolean(arg);
}
public char toChar() {
return arg.isEmpty() ? ' ' : arg.charAt(0);
}
@Override
public String toString() {
return arg;
}
public <T extends Enum<?>> T toEnum(Class<T> enumType) {
return toEnum(enumType, null);
}
public <T extends Enum<?>> T toEnum(Class<T> enumType, T fallback) {
String arg = this.arg.replace('-', '_');
for (T constant : enumType.getEnumConstants())
if (arg.equalsIgnoreCase(constant.name()))
return constant;
if (fallback == null)
throw new IllegalArgumentException("'%s' is not a value of %s".formatted(arg, enumType.getSimpleName()));
return fallback;
}
}
}

View File

@@ -0,0 +1,19 @@
package me.trouper.alias.server.commands;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CommandRegistry {
String value();
String usage() default "none";
Permission permission() default @Permission("");
boolean printStackTrace() default false;
boolean playersAllowed() default true;
boolean consoleAllowed() default true;
boolean blocksAllowed() default true;
}

View File

@@ -0,0 +1,14 @@
package me.trouper.alias.server.commands;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Permission {
String value();
String message() default "You do not have permission for this command!";
}

View File

@@ -0,0 +1,111 @@
package me.trouper.alias.server.commands;
import me.trouper.alias.server.Main;
import me.trouper.alias.server.commands.completions.CompletionBuilder;
import me.trouper.alias.server.commands.completions.CompletionNode;
import net.kyori.adventure.text.Component;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import org.checkerframework.checker.units.qual.A;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public interface QuickCommand extends TabExecutor, Main {
void handleCommand(CommandSender sender, Command command, String label, Args args);
void handleCompletion(CommandSender sender, Command command, String label, Args args, CompletionBuilder b);
default void register() {
CommandRegistry registry = this.getClass().getAnnotation(CommandRegistry.class);
PluginCommand command = getPlugin().getCommand(registry.value());
if (command != null) {
command.setExecutor(this);
command.setTabCompleter(this);
}
}
@Override
default boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String @NotNull [] args) {
CommandRegistry registry = this.getClass().getAnnotation(CommandRegistry.class);
if (registry == null) {
return true;
}
switch (sender) {
case Player player when !registry.playersAllowed() -> {
error(sender, Component.text("Players are not allowed to run this command!"));
return true;
}
case ConsoleCommandSender consoleCommandSender when !registry.consoleAllowed() -> {
error(sender, Component.text("This command cannot be ran from the console!"));
return true;
}
case BlockCommandSender blockCommandSender when !registry.blocksAllowed() -> {
error(sender, Component.text("This command cannot be ran from a command block!"));
return true;
}
default -> {
}
}
try {
String perm = registry.permission().value();
if (perm != null && !perm.isEmpty() && !sender.hasPermission(perm)) {
error(sender, Component.text(registry.permission().message()));
return true;
}
handleCommand(sender, command, label, new Args(args));
}
catch (Exception ex) {
if (registry.printStackTrace()) {
ex.printStackTrace();
}
error(sender, Component.text("Correct Usage: {0}"),Component.text(registry.usage()));
}
return true;
}
@Override
default @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String @NotNull [] args) {
try {
CompletionBuilder b = new CompletionBuilder(label);
handleCompletion(sender, command, label, new Args(args),b);
CompletionNode node = b.getRootNode();
if (args.length == 0) {
return node.getOptions();
}
for (int i = 0; i < args.length - 1; i++) {
node = node.next(args[i]);
}
String end = args[args.length - 1];
List<String> a = new ArrayList<>(node.getOptions());
if (node.isOptionsRegex()) {
List<String> regexResult = new ArrayList<>();
for (CompletionNode option : node.getNextOptions()) {
boolean regexMatches = CompletionNode.containsRegex(option, end) || end.isEmpty();
for (String s : option.getValues())
regexResult.add((regexMatches ? "§d" : "§c") + s + "§r");
}
return regexResult;
}
else {
a.removeIf(s -> !s.toLowerCase().contains(end.toLowerCase()));
return a;
}
}
catch (Exception ex) {
return new ArrayList<>();
}
}
default CommandRegistry getRegistry() {
return this.getClass().getAnnotation(CommandRegistry.class);
}
}

View File

@@ -0,0 +1,123 @@
package me.trouper.alias.server.commands.completions;
import me.trouper.alias.utils.misc.ArrayUtils;
import java.util.*;
import java.util.function.Function;
public class CompletionBuilder {
private final CompletionNode root;
private final List<CompletionBuilder> options;
private boolean isBranch;
private String regex;
CompletionBuilder(List<String> names) {
this.root = new CompletionNode(names, new ArrayList<>(), null);
this.options = new ArrayList<>();
this.isBranch = false;
}
public CompletionBuilder(String names) {
this.root = new CompletionNode(names);
this.options = new ArrayList<>();
this.isBranch = false;
}
public CompletionBuilder(String regex, String details) {
this.root = new CompletionNode(Collections.singletonList(details), new ArrayList<>(), regex);
this.options = new ArrayList<>();
this.isBranch = false;
this.regex = regex;
}
public CompletionBuilder then(CompletionBuilder arg) {
options.add(arg);
root.nextOptions.add(arg.root);
return this;
}
public CompletionBuilder arg(List<String> name) {
CompletionBuilder b = new CompletionBuilder(name);
b.isBranch = true;
return b;
}
public CompletionBuilder argRegex(String regex, String details) {
CompletionBuilder b = new CompletionBuilder(regex, details);
b.isBranch = true;
return b;
}
public CompletionBuilder argInt(String details) {
return argRegex("^ *\\-?\\d+ *$", details);
}
public CompletionBuilder argPosInt(String details) {
return argRegex("^ *\\d+ *$", details);
}
public CompletionBuilder argDecimal(String details) {
return argRegex("^ *\\-?\\d*\\.?\\d+ *$", details);
}
public CompletionBuilder argPosDecimal(String details) {
return argRegex("^ *\\d*\\.?\\d+ *$", details);
}
public CompletionBuilder argBool() {
return arg("true", "false");
}
public CompletionBuilder argEnum(Class<? extends Enum<?>> type, boolean lowercase) {
return arg(ArrayUtils.enumNames(type, lowercase));
}
public CompletionBuilder argEnum(Class<? extends Enum<?>> type) {
return argEnum(type, true);
}
public CompletionBuilder argOnlinePlayers() {
return arg(ArrayUtils.playerNames());
}
public CompletionBuilder arg(String... names) {
return arg(Arrays.asList(names));
}
public CompletionBuilder arg(String name) {
return arg(Collections.singletonList(name));
}
public <T> CompletionBuilder arg(Collection<T> input, Function<T, String> toString) {
return arg(input.stream().map(toString).toList());
}
public CompletionBuilder next(String name) {
for (CompletionBuilder o : options) {
if (CompletionNode.strictContains(o.root, name)) {
return o;
}
}
return null;
}
public CompletionNode getRootNode() {
return root;
}
public CompletionNode build() {
if (this.isBranch) {
throw new IllegalArgumentException("build() cannot be called on branches!");
}
return root;
}
public boolean isBranch() {
return isBranch;
}
public boolean isRegex() {
return regex != null;
}
}

View File

@@ -0,0 +1,88 @@
package me.trouper.alias.server.commands.completions;
import java.util.ArrayList;
import java.util.List;
public class CompletionNode {
final List<String> values;
final List<CompletionNode> nextOptions;
final String regex;
CompletionNode(List<String> values, List<CompletionNode> nextOptions, String regex) {
this.values = values;
this.nextOptions = nextOptions;
this.regex = regex;
}
CompletionNode(String values) {
this(List.of(values), new ArrayList<>(), null);
}
public static boolean strictContains(CompletionNode parent, String subject) {
for (String value : parent.values)
if (value.equals(subject))
return true;
return false;
}
public static boolean contains(CompletionNode parent, String subject) {
for (String value : parent.values)
if (value.contains(subject))
return true;
return false;
}
public static boolean containsRegex(CompletionNode parent, String subject) {
if (parent.regex == null)
return false;
return subject.matches(parent.regex);
}
public boolean optionsRegexMatchesArg(String argument) {
for (CompletionNode option : nextOptions)
if (containsRegex(option, argument))
return true;
return false;
}
public CompletionNode next(String argument) {
for (CompletionNode option : nextOptions)
if (containsRegex(option, argument))
return option;
for (CompletionNode option : nextOptions)
if (strictContains(option, argument))
return option;
for (CompletionNode option : nextOptions)
if (contains(option, argument))
return option;
return null;
}
public List<String> getOptions() {
List<String> a = new ArrayList<>();
for (CompletionNode o : nextOptions) {
a.addAll(o.values);
}
return a;
}
public boolean isRegex() {
return regex != null;
}
public boolean isOptionsRegex() {
return nextOptions.stream().anyMatch(CompletionNode::isRegex);
}
public List<CompletionNode> getNextOptions() {
return nextOptions;
}
public List<String> getValues() {
return values;
}
}

View File

@@ -0,0 +1,25 @@
package me.trouper.alias.server.events;
import me.trouper.alias.server.systems.gui.QuickGui;
import org.bukkit.event.EventHandler;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
public class GuiListener implements QuickListener {
@EventHandler
public void onInventoryClick(InventoryClickEvent e) {
QuickGui.handleClick(e);
}
@EventHandler
public void onInventoryClose(InventoryCloseEvent e) {
QuickGui.handleClose(e);
}
@EventHandler
public void onInventoryDrag(InventoryDragEvent e) {
QuickGui.handleDrag(e);
}
}

View File

@@ -5,8 +5,8 @@ import org.bukkit.Bukkit;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
public interface QuickListener extends Listener, Main { public interface QuickListener extends Listener, Main {
default QuickListener registerEvents() { default QuickListener register() {
Bukkit.getPluginManager().registerEvents(this, this.getPlugin()); Bukkit.getPluginManager().registerEvents(this,this.getPlugin());
return this; return this;
} }
} }

View File

@@ -0,0 +1,97 @@
package me.trouper.alias.server.systems;
import me.trouper.alias.server.Main;
import me.trouper.alias.server.events.QuickListener;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.*;
import org.bukkit.inventory.ItemStack;
public abstract class AbstractWand implements QuickListener, Main {
private final String usePermission;
private final ItemStack wandItem;
public AbstractWand(String usePermission, ItemStack wandItem) {
this.wandItem = wandItem.clone();
this.usePermission = usePermission;
}
public String getUsePermission() {
return usePermission;
}
public ItemStack getWandItem() {
return wandItem.clone();
}
private boolean isWand(ItemStack item) {
return item != null && item.isSimilar(wandItem);
}
private boolean isHoldingWand(Player p) {
ItemStack inMain = p.getInventory().getItemInMainHand();
ItemStack inOff = p.getInventory().getItemInOffHand();
return isWand(inMain) || isWand(inOff);
}
@EventHandler
public void onSwapHands(PlayerSwapHandItemsEvent e) {
Player p = e.getPlayer();
if (!isHoldingWand(p) || !p.hasPermission(getUsePermission())) return;
e.setCancelled(true);
if (p.isSneaking()) onSwapHandSneak(p);
else onSwapHand(p);
}
@EventHandler
public void onAnimate(PlayerAnimationEvent e) {
Player p = e.getPlayer();
if (!isHoldingWand(p) || !p.hasPermission(getUsePermission())) return;
if (!e.getAnimationType().equals(PlayerAnimationType.ARM_SWING)) return;
if (e.getPlayer().getTargetEntity(5) == null) return;
e.setCancelled(true);
if (p.isSneaking()) onLeftClickSneak(p);
else onLeftClick(p);
}
@EventHandler
public void onInteract(PlayerInteractEvent e) {
Player p = e.getPlayer();
if (!isHoldingWand(p) || !p.hasPermission(getUsePermission())) return;
Action action = e.getAction();
if (action != Action.RIGHT_CLICK_AIR && action != Action.RIGHT_CLICK_BLOCK) return;
e.setCancelled(true);
if (p.isSneaking()) onRightClickSneak(p);
else onRightClick(p);
}
@EventHandler
public void onScroll(PlayerItemHeldEvent e) {
Player p = e.getPlayer();
if (!isHoldingWand(p) || !p.hasPermission(getUsePermission())) return;
int prev = e.getPreviousSlot();
int curr = e.getNewSlot();
if (!p.isSneaking() || !isWand(p.getInventory().getItem(prev))) return;
if (curr < prev) onScrollUp(e.getPlayer());
else if (curr > prev) onScrollDown(e.getPlayer());
}
protected void onSwapHand(Player player) {}
protected void onSwapHandSneak(Player player) {}
protected void onRightClick(Player player) {}
protected void onRightClickSneak(Player player) {}
protected void onLeftClick(Player player) {}
protected void onLeftClickSneak(Player player) {}
protected void onScrollUp(Player player) {}
protected void onScrollDown(Player player) {}
}

View File

@@ -0,0 +1,39 @@
package me.trouper.alias.server.systems;
import me.trouper.alias.server.Main;
import org.bukkit.Bukkit;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class TaskManager implements Main {
private final ConcurrentHashMap<Integer, Boolean> tasks = new ConcurrentHashMap<>();
private volatile boolean closed = false;
public int scheduleTask(Runnable task, long delay) {
if (closed) return -1;
int taskId = Bukkit.getScheduler().runTaskLater(main.getPlugin(), () -> {
if (!closed && tasks.containsKey(taskId)) {
task.run();
tasks.remove(taskId);
}
}, delay).getTaskId();
if (!closed) {
tasks.put(taskId, Boolean.TRUE);
return taskId;
} else {
Bukkit.getScheduler().cancelTask(taskId);
return -1;
}
}
public void close() {
closed = true;
tasks.keySet().forEach(Bukkit.getScheduler()::cancelTask);
tasks.clear();
}
}

View File

@@ -0,0 +1,469 @@
package me.trouper.alias.server.systems;
import me.trouper.alias.Alias;
import me.trouper.alias.server.Main;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.TextReplacementConfig;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.Sound;
import org.bukkit.SoundCategory;
import org.bukkit.entity.Player;
import java.util.*;
public class Text implements Main {
/**
* Messages an audience applying pallet formatting to the text and placeholders. Placeholders are zero-indexed and curly braced. {0}, {1}, {2}...
* Supports both flat messages and fancy wrapped messages based on Alias configuration.
* @param pallet The colors to use for text and arguments.
* @param playSound If the pallet's sound should be played.
* @param audience Any audience.
* @param text The message to format
* @param args Qualified placeholders to color.
*/
public static void messageAny(Pallet pallet, boolean playSound, Audience audience, String text, Object... args) {
message(pallet, playSound, audience, color(text), Arrays.stream(args).map(object -> Component.text(String.valueOf(object))).toArray(ComponentLike[]::new));
}
/**
* Messages an audience applying pallet formatting to the text and placeholders. Placeholders are zero-indexed and curly braced. {0}, {1}, {2}...
* Supports both flat messages and fancy wrapped messages based on Alias configuration.
* @param pallet The colors to use for text and arguments.
* @param audience Any audience.
* @param text The message to format
* @param args Qualified placeholders to color.
*/
public static void messageAny(Pallet pallet, Audience audience, String text, Object... args) {
messageAny(pallet,true,audience,text,args);
}
/**
* Messages an audience applying pallet formatting to the component and placeholders. Placeholders are zero-indexed and curly braced. {0}, {1}, {2}...
* Preserves existing formatting like click events and hover events.
* Supports both flat messages and fancy wrapped messages based on Alias configuration.
* @param pallet The colors to use for text and arguments.
* @param playSound If the pallet's sound should be played.
* @param audience Any audience.
* @param text The component message to format
* @param args Qualified placeholders to color.
*/
public static void message(Pallet pallet, boolean playSound, Audience audience, ComponentLike text, ComponentLike... args) {
Component message = getMessage(pallet, text, args);
audience.sendMessage(message);
if (playSound && audience instanceof Player p) p.playSound(p.getLocation(), pallet.sound.sound, SoundCategory.VOICE, 10f, pallet.sound.pitch);
}
/**
* Messages an audience applying pallet formatting to the component and placeholders. Placeholders are zero-indexed and curly braced. {0}, {1}, {2}...
* Preserves existing formatting like click events and hover events.
* Supports both flat messages and fancy wrapped messages based on Alias configuration.
* @param pallet The colors to use for text and arguments.
* @param audience Any audience.
* @param text The component message to format
* @param args Qualified placeholders to color.
*/
public static void message(Pallet pallet, Audience audience, ComponentLike text, ComponentLike... args) {
message(pallet,true,audience,text,args);
}
public static void sendWarning(Audience audience, String warning, Object... args) {
messageAny(Pallet.WARNING, audience, warning, args);
}
public static void sendError(Audience audience, String error, Object... args) {
messageAny(Pallet.ERROR, audience, error, args);
}
public static void sendInfo(Audience audience, String info, Object... args) {
messageAny(Pallet.INFO, audience, info, args);
}
public static void sendSuccess(Audience audience, String success, Object... args) {
messageAny(Pallet.SUCCESS, audience, success, args);
}
public static void sendMessage(Audience audience, String text, Object... args) {
messageAny(Pallet.NEUTRAL, audience, text, args);
}
/**
* Gets the component form of a message, applying pallet formatting to the text and placeholders. Placeholders are zero-indexed and curly braced. {0}, {1}, {2}...
* @param pallet The colors to use for text and arguments.
* @param text The message to format
* @param args Qualified placeholders to color.
* @return The final component, formatted according to flat/fancy setting.
*/
public static Component getMessageAny(Pallet pallet, String text, Object... args) {
return getMessage(pallet, color(text), Arrays.stream(args).map(arg -> color(String.valueOf(arg))).toArray(ComponentLike[]::new));
}
/**
* Gets the component form of a message, applying pallet formatting to the component and placeholders. Placeholders are zero-indexed and curly braced. {0}, {1}, {2}...
* Preserves existing formatting like click events and hover events.
* Supports both flat messages and fancy wrapped messages based on Alias configuration.
* @param pallet The colors to use for text and arguments.
* @param text The component message to format
* @param args Qualified placeholders to color.
* @return The final component, formatted according to flat/fancy setting.
*/
public static Component getMessage(Pallet pallet, ComponentLike text, ComponentLike... args) {
Component formattedMessage = format(pallet, text, args);
if (main.getCommon().useFlat()) {
return formatFlatMessage(formattedMessage);
} else {
return formatFancyMessage(formattedMessage);
}
}
/**
* Formats a message as a flat prefixed message.
* @param message The formatted message component
* @return The message with flat prefix applied
*/
private static Component formatFlatMessage(Component message) {
Component prefix = color(main.getCommon().getFlatPrefix());
return prefix.append(message);
}
/**
* Formats a message as a fancy wrapped message with line prefixes.
* Uses native Adventure API component processing for consistency.
* @param message The formatted message component
* @return The message with fancy formatting and line wrapping
*/
private static Component formatFancyMessage(Component message) {
List<Component> wrappedLines = wrapComponent(message, 50, (int) Math.round((main.getCommon().getPluginName().length() + 3) * 1.3));
// 50 is slightly below the average character width of someone's minecraft chat. The 3 is to account for the bolded "| " and the 1.3 is to account for bolding the plugin name.
if (wrappedLines.isEmpty()) {
wrappedLines.add(Component.empty());
}
Component result = Component.empty().appendNewline();
Component firstLine = Component.empty()
.append(Component.text("| ", TextColor.color(main.getCommon().getSecondaryColor())).decorate(TextDecoration.BOLD))
.append(Component.text(main.getCommon().getPluginName() + " ", TextColor.color(main.getCommon().getMainColor()), TextDecoration.BOLD))
.append(wrappedLines.get(0));
result = result.append(firstLine);
for (int i = 1; i < wrappedLines.size(); i++) {
Component line = Component.empty()
.append(Component.text("| ", TextColor.color(main.getCommon().getSecondaryColor())).decorate(TextDecoration.BOLD))
.append(wrappedLines.get(i));
result = result.appendNewline().append(line);
}
return result.appendNewline();
}
/**
* Wraps a component into multiple lines based on visible character count.
* Preserves all Adventure API formatting including colors, decorations, and events.
* @param component The component to wrap
* @param maxLineLength Maximum visible characters per line
* @param firstLineOffset Offset for the first line (plugin name length)
* @return List of wrapped component lines
*/
private static List<Component> wrapComponent(Component component, int maxLineLength, int firstLineOffset) {
List<Component> lines = new ArrayList<>();
List<ComponentWord> words = extractWords(component);
if (words.isEmpty()) {
lines.add(Component.empty());
return lines;
}
Component currentLine = Component.empty();
int currentLineLength = firstLineOffset;
boolean isFirstLine = true;
for (int i = 0; i < words.size(); i++) {
ComponentWord word = words.get(i);
int wordLength = word.visibleLength();
int spaceNeeded = (currentLine.equals(Component.empty()) ? 0 : 1) + wordLength;
if (currentLineLength + spaceNeeded > maxLineLength && !currentLine.equals(Component.empty())) {
lines.add(currentLine);
currentLine = Component.empty();
currentLineLength = 0;
}
if (!currentLine.equals(Component.empty())) {
currentLine = currentLine.append(Component.space());
currentLineLength++;
}
currentLine = currentLine.append(word.component());
currentLineLength += wordLength;
}
if (!currentLine.equals(Component.empty())) {
lines.add(currentLine);
}
return lines;
}
/**
* Extracts words from a component while preserving their formatting.
* @param component The component to extract words from
* @return List of ComponentWord objects
*/
private static List<ComponentWord> extractWords(Component component) {
List<ComponentWord> words = new ArrayList<>();
extractWordsRecursive(component, Style.empty(), words);
return words;
}
/**
* Recursively extracts words from a component tree, preserving inherited styles.
* @param component The current component
* @param inheritedStyle The style inherited from parent components
* @param words The list to add words to
*/
private static void extractWordsRecursive(Component component, Style inheritedStyle, List<ComponentWord> words) {
Style currentStyle = inheritedStyle.merge(component.style());
if (component instanceof TextComponent textComponent) {
String text = textComponent.content();
if (!text.isEmpty()) {
String[] textWords = text.split("\\s+");
for (String word : textWords) {
if (!word.isEmpty()) {
Component wordComponent = Component.text(word).style(currentStyle);
words.add(new ComponentWord(wordComponent, getVisibleLength(word)));
}
}
}
}
for (Component child : component.children()) {
extractWordsRecursive(child, currentStyle, words);
}
}
/**
* Gets the visible length of text, excluding formatting codes.
* @param text The text to measure
* @return The visible character count
*/
private static int getVisibleLength(String text) {
return PlainTextComponentSerializer.plainText().serialize(Component.text(text)).length();
}
/**
* Wrapper for LegacyComponentSerializer, using ampersand (&) codes.
* @param msg the legacy text
* @return The deserialized component
*/
public static Component color(String msg) {
if (msg.contains("§")) return LegacyComponentSerializer.legacySection().deserialize(msg);
return LegacyComponentSerializer.legacyAmpersand().deserialize(msg);
}
/**
* Converts ampersand codes to section codes.
* @param ampersands String with ampersand codes
* @return String with section codes
*/
public static String legacyAmpersandColor(String ampersands) {
return ampersands.replaceAll("&","§");
}
/**
* Formats a string message with pallet colors and argument replacement.
* @param pallet The color pallet to use
* @param text The message text
* @param args Arguments to replace placeholders
* @return Formatted component
*/
public static Component format(Pallet pallet, String text, Object... args) {
return format(pallet, Component.text(text), Arrays.stream(args).map(arg->Component.text(arg.toString())).toArray(Component[]::new));
}
/**
* Formats a component message with pallet colors and argument replacement.
* Placeholders are zero-indexed and curly braced: {0}, {1}, {2}...
* @param pallet The color pallet to use
* @param text The message component
* @param args Argument components to replace placeholders
* @return Formatted component with colors applied
*/
public static Component format(Pallet pallet, ComponentLike text, ComponentLike... args) {
Component resultComponent = text.asComponent().color(pallet.mainText);
if (args == null || args.length == 0) {
return resultComponent;
}
for (int i = 0; i < args.length; i++) {
Component argument = args[i].asComponent();
if (shouldRecolor(argument)) {
TextColor newColor = getArgColor(pallet, i);
argument = argument.color(newColor);
}
TextReplacementConfig replacementConfig = TextReplacementConfig.builder()
.matchLiteral("{" + i + "}")
.replacement(argument)
.build();
resultComponent = resultComponent.replaceText(replacementConfig);
}
return resultComponent;
}
/**
* Determines if an argument component should have its color overridden by the pallet.
* @param component The component to check.
* @return Currently always returns true, indicating recoloring should occur.
*/
private static boolean shouldRecolor(Component component) {
Set<TextColor> colors = new HashSet<>();
collectColors(component,colors);
return colors.size() > 1;
}
/**
* Recursively checks a component, adding its colors to a set.
* @param component The component to collect.
* @param colors A mutable HashSet of colors.
*/
private static void collectColors(Component component, Set<TextColor> colors) {
if (component.color() != null) {
colors.add(component.color());
}
for (Component child : component.children()) {
collectColors(child, colors);
}
}
/**
* Removes color codes from a string.
* @param input The input string
* @return String with color codes removed
*/
public static String removeColors(String input) {
if (input == null) return null;
input = input.replaceAll("(?i)[&§][0-9a-fk-or]", ""); // Legacy colors
input = input.replaceAll("(?i)[&§]#[a-f0-9]{6}", ""); // Legacy hex colors
input = input.replaceAll("(?i)§x(§[a-f0-9]){6}", ""); // Old hex colors
return input;
}
/**
* Removes color codes from a component, returning plain text.
* @param input The input component
* @return Component with plain text only
*/
public static Component removeColors(ComponentLike input) {
if (input == null) return Component.text("");
String plainText = PlainTextComponentSerializer.plainText().serialize(input.asComponent());
return Component.text(plainText);
}
/**
* Gets the appropriate argument color based on the argument index.
* @param pallet The color pallet
* @param argIndex The argument index (0-indexed)
* @return The appropriate TextColor for the argument
*/
private static TextColor getArgColor(Pallet pallet, int argIndex) {
return switch (argIndex) {
case 1 -> pallet.arg2;
case 2 -> pallet.arg3;
default -> pallet.argDefault;
};
}
/**
* Represents a word extracted from a component with its formatting preserved.
*/
private static record ComponentWord(Component component, int visibleLength) {}
/**
* Color pallets for different message types with appropriate colors and sounds.
*/
public enum Pallet {
ERROR(
TextColor.color(0xD3A6A4), // Soft red for main text
TextColor.color(0xFFF1AE), // Light yellow for default args
TextColor.color(0xFF796D), // Coral for second arg
TextColor.color(0xC62828), // Dark red for third arg
new SoundData(Sound.BLOCK_NOTE_BLOCK_BASS, 1)
),
WARNING(
TextColor.color(0xFFF3CD), // Light yellow for main text
TextColor.color(0xFFF9F5), // Very light cream for default args
TextColor.color(0xFFD54F), // Gold for second arg
TextColor.color(0xFFA000), // Orange for third arg
new SoundData(Sound.BLOCK_NOTE_BLOCK_BIT, 0.5F)
),
INFO(
TextColor.color(0xBBDEFB), // Light blue for main text
TextColor.color(0xD2D0EA), // Light lavender for default args
TextColor.color(0x64B5F6), // Medium blue for second arg
TextColor.color(0x1976D2), // Dark blue for third arg
new SoundData(Sound.BLOCK_NOTE_BLOCK_CHIME, 0.7F)
),
SUCCESS(
TextColor.color(0xCDFFC7), // Light green for main text
TextColor.color(0xFFFFFF), // White for default args
TextColor.color(0xB0FFE3), // Light mint for second arg
TextColor.color(0x63CD83), // Medium green for third arg
new SoundData(Sound.BLOCK_NOTE_BLOCK_PLING, 1.5F)
),
NEUTRAL(
TextColor.color(0xD3D3D3), // Light gray for main text
TextColor.color(0xFFFFFF), // White for default args
TextColor.color(0xFFB3F8), // Light pink for second arg
TextColor.color(0xE280FF), // Purple for third arg
new SoundData(Sound.BLOCK_NOTE_BLOCK_BELL, 1)
),
LOCATION(
TextColor.color(0xAAAAAA), // Gray for main text
TextColor.color(0xFFB0C1), // Light pink for default args
TextColor.color(0xB6F5B6), // Light green for second arg
TextColor.color(0xB0C1FF), // Light blue for third arg
new SoundData(Sound.UI_HUD_BUBBLE_POP, 2)
);
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;
}
}
/**
* Sound data for message types.
* @param sound The sound to play
* @param pitch The pitch of the sound
*/
public record SoundData(Sound sound, float pitch) {}
}

View File

@@ -0,0 +1,52 @@
package me.trouper.alias.server.systems;
import me.trouper.alias.server.Main;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
public class Verbose implements Main {
/**
* A dynamic verbose system which uses the format from the {@link Text} system.
* @param backtrace The number of calls up the stacktrace to go.
* @param verbose A message with 0 indexed curly brace placeholders. {0}, {1}, {2}...
* @param args Qualified placeholder values.
*/
public static void send(int backtrace, String verbose, Object... args) {
if (!main.getCommon().getDebugMode()) 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 (main.getCommon().getDebuggerExclusions().contains(callerInfo)) return;
}
Component message = Text.format(Text.Pallet.INFO,verbose,args);
message = Text.format(Text.Pallet.INFO,Component.text("{0} [DEBUG ^ {1}] [{2}] » {3}"),Component.text(main.getCommon().getPluginName()), Component.text(backtrace), Component.text(callerInfo), message);
main.getPlugin().getComponentLogger().info(message);
for (Player operator : Bukkit.getOnlinePlayers()) {
if (!operator.isOp()) continue;
operator.sendMessage(message);
}
}
/**
* A dynamic verbose system which uses the format from the {@link Text} system.
* @param verbose A message with 0 indexed curly brace placeholders. {0}, {1}, {2}...
* @param args Qualified placeholder values.
*/
public static void send(String verbose, Object... args) {
send(1,verbose,args);
}
}

View File

@@ -0,0 +1,145 @@
package me.trouper.alias.server.systems.burning;
import me.trouper.alias.server.Main;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockSupport;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Waterlogged;
import java.io.Closeable;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
public class BlockBurner implements Closeable, Main {
private final BurnOptions options;
private final BurnPalette palette;
private boolean isClosed = false;
private final Set<Block> visited = new HashSet<>();
private final Map<Block, Material> burning = new HashMap<>();
private final Set<Integer> tasks = new HashSet<>();
public BlockBurner(BurnOptions options) {
this.options = options;
this.palette = new BurnPalette();
}
@Override
public void close() {
tasks.forEach(task -> Bukkit.getScheduler().cancelTask(task));
visited.clear();
burning.clear();
isClosed = true;
}
public void burn(Block block, float heat) {
if (isOccluded(block)) return;
if (visited.contains(block)) return;
visited.add(block);
if (options.isDisabled()) return;
if (block.isLiquid() || hasWater(block)) return;
Block blockBelow = block.getRelative(0, -1, 0);
if (block.getType().isAir() && canPlaceFireOn(blockBelow)) {
if (ThreadLocalRandom.current().nextFloat() > options.getSetFireChance()) return;
if (isClosed()) return;
setBlock(block, palette.getFirePalette().getFirst());
if (isClosed()) return;
int taskId = Bukkit.getScheduler().runTaskLater(main.getPlugin(), ()->{
if (!canPlaceFireOn(blockBelow)) {
setBlock(block, Material.AIR);
}
},20 * 10).getTaskId();
if (!isClosed()) {
tasks.add(taskId);
} else {
tasks.add(taskId);
Bukkit.getScheduler().cancelTask(taskId);
}
return;
}
if (block.getType().isAir()) return;
List<BurnStage> burnStages = palette.burn(block, heat);
if (burnStages == null) return;
burning.put(block, block.getType());
scheduleBurnStages(block, burnStages);
}
private void scheduleBurnStages(Block block, List<BurnStage> stages) {
long totalDelay = 0;
for (BurnStage stage : stages) {
totalDelay += stage.getDelay();
if (isClosed()) return;
int taskId = Bukkit.getScheduler().runTaskLater(main.getPlugin(), ()->{
if (block.getType().isAir()) return;
setBlock(block, stage.getBlockData());
},totalDelay).getTaskId();
if (!isClosed()) {
tasks.add(taskId);
} else {
tasks.add(taskId);
Bukkit.getScheduler().cancelTask(taskId);
}
}
}
private boolean isOccluded(Block block) {
return isOccluding(block.getRelative(0, 1, 0)) &&
isOccluding(block.getRelative(0, -1, 0)) &&
isOccluding(block.getRelative(1, 0, 0)) &&
isOccluding(block.getRelative(-1, 0, 0)) &&
isOccluding(block.getRelative(0, 0, 1)) &&
isOccluding(block.getRelative(0, 0, -1));
}
private boolean isOccluding(Block block) {
Material material = burning.getOrDefault(block, block.getType());
return material.isOccluding();
}
private boolean canPlaceFireOn(Block block) {
return block.getBlockData().isFaceSturdy(BlockFace.UP, BlockSupport.RIGID);
}
private boolean hasWater(Block block) {
Material type = block.getType();
if (type == Material.KELP || type == Material.KELP_PLANT ||
type == Material.SEAGRASS || type == Material.TALL_SEAGRASS) {
return true;
}
BlockData data = block.getBlockData();
if (data instanceof Waterlogged) {
return ((Waterlogged) data).isWaterlogged();
}
return false;
}
private void setBlock(Block block, Material material) {
if (isClosed()) return;
block.setType(material);
}
private void setBlock(Block block, BlockData data) {
if (isClosed()) return;
block.setBlockData(data);
}
public boolean isClosed() {
return isClosed;
}
}

View File

@@ -0,0 +1,12 @@
package me.trouper.alias.server.systems.burning;
public class BurnOptions {
private boolean disabled = false;
private double setFireChance = 1.0 / 80;
public boolean isDisabled() { return disabled; }
public void setDisabled(boolean disabled) { this.disabled = disabled; }
public double getSetFireChance() { return setFireChance; }
public void setSetFireChance(double setFireChance) { this.setFireChance = setFireChance; }
}

View File

@@ -0,0 +1,274 @@
package me.trouper.alias.server.systems.burning;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Waterlogged;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
public class BurnPalette {
private final List<List<BlockData>> burnWave;
private final List<Material> firePalette;
private final List<BlockData> preBurn;
private final List<BlockData> midBurn;
private final List<BlockData> midHeatPalette;
private final List<BlockData> highHeat;
private final List<BlockData> shortGrassPalette;
private final List<BlockData> grassPalette;
private final List<BlockData> stonePalette;
private final List<BlockData> leavesPalette;
private final List<BlockData> stoneBrickSlabPalette;
private final List<BlockData> stoneBrickStairsPalette;
private final List<BlockData> darkPrismarinePalette;
private final List<BlockData> burnWaveTrail;
public BurnPalette() {
// Initialize burn wave trail
this.burnWaveTrail = Arrays.asList(
Material.ORANGE_STAINED_GLASS.createBlockData(),
Material.BLACK_STAINED_GLASS.createBlockData(),
Material.GRAY_STAINED_GLASS.createBlockData(),
Material.LIGHT_GRAY_STAINED_GLASS.createBlockData()
);
// Initialize main burn wave
List<BlockData> baseBurnWave = new ArrayList<>();
addRepeated(baseBurnWave, Material.ORANGE_STAINED_GLASS, 3);
addRepeated(baseBurnWave, Material.SHROOMLIGHT, 2);
baseBurnWave.add(Material.ORANGE_TERRACOTTA.createBlockData());
baseBurnWave.add(Material.ORANGE_CONCRETE.createBlockData());
baseBurnWave.add(Material.HONEYCOMB_BLOCK.createBlockData());
this.burnWave = new ArrayList<>();
for (BlockData block : baseBurnWave) {
List<BlockData> waveStage = new ArrayList<>();
waveStage.add(block);
waveStage.add(block);
waveStage.add(block);
waveStage.addAll(burnWaveTrail);
this.burnWave.add(waveStage);
}
this.firePalette = Arrays.asList(Material.FIRE);
this.preBurn = Arrays.asList(Material.ORANGE_TERRACOTTA.createBlockData());
this.midBurn = Arrays.asList(
Material.MAGMA_BLOCK.createBlockData(),
Material.ORANGE_TERRACOTTA.createBlockData()
);
this.midHeatPalette = Arrays.asList(Material.MAGMA_BLOCK.createBlockData());
this.highHeat = Arrays.asList(
Material.MAGMA_BLOCK.createBlockData(),
Material.BLACKSTONE.createBlockData(),
Material.MUD.createBlockData(),
Material.TUFF.createBlockData()
);
this.shortGrassPalette = createShortGrassPalette();
this.grassPalette = createGrassPalette();
this.stonePalette = createStonePalette();
this.leavesPalette = createLeavesPalette();
this.stoneBrickSlabPalette = createStoneBrickSlabPalette();
this.stoneBrickStairsPalette = createStoneBrickStairsPalette();
this.darkPrismarinePalette = createDarkPrismarinePalette();
}
public List<BurnStage> burn(Block block, float heat) {
Material type = block.getType();
ThreadLocalRandom random = ThreadLocalRandom.current();
if (type == Material.SHORT_GRASS) {
return Arrays.asList(new BurnStage(
random.nextLong(1, 10),
getRandomElement(shortGrassPalette)
));
}
if (Tag.LOGS_THAT_BURN.isTagged(type)) {
BlockData preservedData = Material.POLISHED_BASALT.createBlockData();
block.getBlockData().copyTo(preservedData);
return Arrays.asList(
new BurnStage(random.nextLong(0, 1), getRandomElement(preBurn)),
new BurnStage(random.nextLong(1, 10), getRandomElement(midBurn)),
new BurnStage(random.nextLong(20, 200), preservedData)
);
}
if (Tag.LEAVES.isTagged(type)) {
return Arrays.asList(
new BurnStage(random.nextLong(1, 10), getRandomElement(preBurn)),
new BurnStage(random.nextLong(1, 10), getRandomElement(midBurn)),
new BurnStage(random.nextLong(10, 80), getRandomElement(leavesPalette))
);
}
if (type == Material.STONE_BRICK_STAIRS) {
BlockData preservedData = getRandomElement(stoneBrickStairsPalette).clone();
block.getBlockData().copyTo(preservedData);
return Arrays.asList(new BurnStage(random.nextLong(10, 60), preservedData));
}
if (type == Material.STONE_BRICK_SLAB) {
BlockData preservedData = getRandomElement(stoneBrickSlabPalette).clone();
block.getBlockData().copyTo(preservedData);
return Arrays.asList(new BurnStage(random.nextLong(10, 60), preservedData));
}
Set<Material> grassTypes = Set.of(Material.GRASS_BLOCK, Material.DIRT, Material.SNOW_BLOCK,
Material.SAND, Material.PODZOL, Material.COARSE_DIRT);
if (grassTypes.contains(type)) {
return Arrays.asList(
new BurnStage(random.nextLong(1, 5), getRandomElement(midBurn)),
new BurnStage(random.nextLong(10, 60), getRandomElement(applyHeat(grassPalette, heat)))
);
}
Set<Material> stoneTypes = Set.of(Material.STONE, Material.ANDESITE, Material.GRAVEL, Material.COBBLESTONE);
if (stoneTypes.contains(type)) {
return Arrays.asList(
new BurnStage(random.nextLong(1, 5), getRandomElement(midBurn)),
new BurnStage(random.nextLong(10, 60), getRandomElement(applyHeat(stonePalette, heat)))
);
}
if (type == Material.DARK_PRISMARINE) {
return Arrays.asList(new BurnStage(
random.nextLong(10, 60),
getRandomElement(applyHeat(darkPrismarinePalette, heat))
));
}
if (Tag.WOODEN_FENCES.isTagged(type) || Tag.WOODEN_DOORS.isTagged(type) || Tag.WOODEN_TRAPDOORS.isTagged(type)) {
if (random.nextFloat() < 0.2) return null;
return Arrays.asList(new BurnStage(random.nextLong(10, 60), Material.AIR.createBlockData()));
}
if (!type.isSolid()) {
return Arrays.asList(new BurnStage(random.nextLong(1, 10), Material.AIR.createBlockData()));
}
return null;
}
public List<Material> getFirePalette() {
return firePalette;
}
public List<BlockData> getSmokePalette() {
ThreadLocalRandom random = ThreadLocalRandom.current();
if (random.nextFloat() < 0.25) {
List<Material> options = Arrays.asList(Material.BLACK_CONCRETE, Material.ORANGE_TERRACOTTA);
return Arrays.asList(
Material.SHROOMLIGHT.createBlockData(),
Material.ORANGE_CONCRETE.createBlockData(),
Material.ORANGE_TERRACOTTA.createBlockData(),
options.get(random.nextInt(options.size())).createBlockData()
);
}
return Arrays.asList(
Material.SHROOMLIGHT.createBlockData(),
Material.ORANGE_CONCRETE.createBlockData(),
Material.ORANGE_STAINED_GLASS.createBlockData(),
Material.BLACK_STAINED_GLASS.createBlockData()
);
}
private List<BlockData> applyHeat(List<BlockData> list, float heat) {
if (heat > 0.9) return highHeat;
if (heat < 0.2) return list;
List<BlockData> result = new ArrayList<>(list);
result.addAll(midHeatPalette);
return result;
}
private void addRepeated(List<BlockData> list, Material material, int count) {
for (int i = 0; i < count; i++) {
list.add(material.createBlockData());
}
}
private <T> T getRandomElement(List<T> list) {
return list.get(ThreadLocalRandom.current().nextInt(list.size()));
}
private List<BlockData> createShortGrassPalette() {
List<BlockData> palette = new ArrayList<>();
addRepeated(palette, Material.DEAD_BUSH, 12);
palette.add(Material.DEAD_BRAIN_CORAL_FAN.createBlockData());
palette.add(Material.DEAD_BRAIN_CORAL.createBlockData());
palette.add(Material.DEAD_BUBBLE_CORAL_FAN.createBlockData());
palette.add(Material.DEAD_FIRE_CORAL_FAN.createBlockData());
palette.add(Material.DEAD_FIRE_CORAL.createBlockData());
palette.add(Material.DEAD_HORN_CORAL_FAN.createBlockData());
palette.add(Material.DEAD_TUBE_CORAL_FAN.createBlockData());
for (BlockData data : palette) {
if (data instanceof Waterlogged) {
((Waterlogged) data).setWaterlogged(false);
}
}
return palette;
}
private List<BlockData> createGrassPalette() {
List<BlockData> palette = new ArrayList<>();
addRepeated(palette, Material.COARSE_DIRT, 4);
addRepeated(palette, Material.ROOTED_DIRT, 4);
addRepeated(palette, Material.TUFF, 2);
palette.add(Material.DEAD_HORN_CORAL_BLOCK.createBlockData());
palette.add(Material.DEAD_FIRE_CORAL_BLOCK.createBlockData());
return palette;
}
private List<BlockData> createStonePalette() {
List<BlockData> palette = new ArrayList<>();
addRepeated(palette, Material.TUFF, 3);
palette.add(Material.ANDESITE.createBlockData());
palette.add(Material.DEAD_HORN_CORAL_BLOCK.createBlockData());
palette.add(Material.DEEPSLATE.createBlockData());
return palette;
}
private List<BlockData> createLeavesPalette() {
List<BlockData> palette = new ArrayList<>();
palette.add(Material.MANGROVE_ROOTS.createBlockData());
addRepeated(palette, Material.AIR, 5);
return palette;
}
private List<BlockData> createStoneBrickSlabPalette() {
List<BlockData> palette = new ArrayList<>();
addRepeated(palette, Material.STONE_BRICK_SLAB, 15);
palette.add(Material.ANDESITE_SLAB.createBlockData());
palette.add(Material.COBBLESTONE_SLAB.createBlockData());
palette.add(Material.TUFF_SLAB.createBlockData());
return palette;
}
private List<BlockData> createStoneBrickStairsPalette() {
List<BlockData> palette = new ArrayList<>();
addRepeated(palette, Material.STONE_BRICK_STAIRS, 15);
palette.add(Material.ANDESITE_STAIRS.createBlockData());
palette.add(Material.COBBLESTONE_STAIRS.createBlockData());
palette.add(Material.TUFF_STAIRS.createBlockData());
return palette;
}
private List<BlockData> createDarkPrismarinePalette() {
List<BlockData> palette = new ArrayList<>();
addRepeated(palette, Material.DARK_PRISMARINE, 15);
palette.add(Material.DEEPSLATE.createBlockData());
palette.add(Material.COBBLED_DEEPSLATE.createBlockData());
return palette;
}
}

View File

@@ -0,0 +1,16 @@
package me.trouper.alias.server.systems.burning;
import org.bukkit.block.data.BlockData;
public class BurnStage {
private final long delay;
private final BlockData blockData;
public BurnStage(long delay, BlockData blockData) {
this.delay = delay;
this.blockData = blockData;
}
public long getDelay() { return delay; }
public BlockData getBlockData() { return blockData; }
}

View File

@@ -0,0 +1,500 @@
package me.trouper.alias.server.systems.gui;
import me.trouper.alias.server.Main;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitTask;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
public class QuickGui implements InventoryHolder, Main {
private static final Map<String, QuickGui> registry = new ConcurrentHashMap<>();
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
private final Map<Integer, GuiAction> slotActions;
private final Map<Integer, ItemStack> slotItems;
private final Map<Integer, BukkitTask> animations;
private final GuiAction globalAction;
private final GuiCreateAction createAction;
private final GuiCloseAction closeAction;
private final GuiDragAction dragAction;
private final Component title;
private final int size;
private final boolean preventDrag;
private final Sound clickSound;
private final float soundVolume;
private final float soundPitch;
private Inventory inventory;
private final Set<Player> viewers;
private QuickGui(Component title, int size, GuiAction globalAction,
Map<Integer, GuiAction> slotActions, Map<Integer, ItemStack> slotItems,
GuiCreateAction createAction, GuiCloseAction closeAction, GuiDragAction dragAction,
boolean preventDrag, Sound clickSound, float soundVolume, float soundPitch) {
this.title = title;
this.size = size;
this.globalAction = globalAction;
this.slotActions = new HashMap<>(slotActions);
this.slotItems = new HashMap<>(slotItems);
this.createAction = createAction;
this.closeAction = closeAction;
this.dragAction = dragAction;
this.preventDrag = preventDrag;
this.clickSound = clickSound;
this.soundVolume = soundVolume;
this.soundPitch = soundPitch;
this.animations = new HashMap<>();
this.viewers = ConcurrentHashMap.newKeySet();
}
public static QuickGui register(String id, QuickGui gui) {
if (gui != null && id != null && !id.isEmpty()) {
registry.put(id, gui);
}
return gui;
}
public static Optional<QuickGui> getRegistered(String id) {
return Optional.ofNullable(registry.get(id));
}
public static Map<String, QuickGui> getRegistries() {
return new HashMap<>(registry);
}
public static void handleClick(InventoryClickEvent event) {
if (event.getInventory().getHolder() instanceof QuickGui gui) {
gui.onInventoryClick(event);
}
}
public static void handleClose(InventoryCloseEvent event) {
if (event.getInventory().getHolder() instanceof QuickGui gui) {
gui.onInventoryClose(event);
}
}
public static void handleDrag(InventoryDragEvent event) {
if (event.getInventory().getHolder() instanceof QuickGui gui) {
gui.onInventoryDrag(event);
}
}
@Override
public Inventory getInventory() {
if (inventory == null) {
int actualSize = calculateSize();
inventory = Bukkit.createInventory(this, actualSize, title);
createAction.onCreate(this, inventory);
populateInventory();
}
return inventory;
}
public void open(Player player) {
if (player != null && player.isOnline()) {
player.openInventory(getInventory());
viewers.add(player);
}
}
public void closeAll() {
new ArrayList<>(viewers).forEach(Player::closeInventory);
}
public void updateItem(int slot, ItemStack item) {
if (slot >= 0 && slot < getInventory().getSize()) {
slotItems.put(slot, item);
getInventory().setItem(slot, item);
}
}
public void updateItem(int slot, ItemStack item, GuiAction action) {
updateItem(slot, item);
if (action != null) {
slotActions.put(slot, action);
}
}
public void removeItem(int slot) {
if (slot >= 0 && slot < getInventory().getSize()) {
slotItems.remove(slot);
slotActions.remove(slot);
getInventory().setItem(slot, null);
}
}
public void startAnimation(int slot, List<ItemStack> frames, long interval) {
stopAnimation(slot);
if (frames.isEmpty()) return;
BukkitTask task = new BukkitRunnable() {
private int frameIndex = 0;
@Override
public void run() {
if (getInventory().getViewers().isEmpty()) {
cancel();
return;
}
ItemStack frame = frames.get(frameIndex);
getInventory().setItem(slot, frame);
frameIndex = (frameIndex + 1) % frames.size();
}
}.runTaskTimer(main.getPlugin(), 0L, interval);
animations.put(slot, task);
}
public void stopAnimation(int slot) {
BukkitTask task = animations.remove(slot);
if (task != null && !task.isCancelled()) {
task.cancel();
}
}
public void stopAllAnimations() {
animations.values().forEach(task -> {
if (!task.isCancelled()) {
task.cancel();
}
});
animations.clear();
}
private int calculateSize() {
if (size > 0 && size % 9 == 0) {
return Math.min(size, 54);
}
int maxSlot = slotItems.keySet().stream()
.mapToInt(Integer::intValue)
.max()
.orElse(8);
int rows = (maxSlot / 9) + 1;
return Math.min(rows * 9, 54);
}
private void populateInventory() {
slotItems.forEach((slot, item) -> {
if (slot < inventory.getSize()) {
inventory.setItem(slot, item);
}
});
}
private void onInventoryClick(InventoryClickEvent event) {
if (event.getClickedInventory() == null ||
event.getClickedInventory().getType() == InventoryType.PLAYER) {
return;
}
event.setCancelled(true);
if (!(event.getWhoClicked() instanceof Player player)) {
return;
}
if (clickSound != null) {
player.playSound(player.getLocation(), clickSound, soundVolume, soundPitch);
}
globalAction.onClick(this, event);
int slot = event.getSlot();
GuiAction action = slotActions.get(slot);
if (action != null) {
action.onClick(this, event);
}
}
private void onInventoryClose(InventoryCloseEvent event) {
if (event.getPlayer() instanceof Player player) {
viewers.remove(player);
}
if (viewers.isEmpty()) {
stopAllAnimations();
}
closeAction.onClose(this, event);
}
private void onInventoryDrag(InventoryDragEvent event) {
if (preventDrag) {
event.setCancelled(true);
}
dragAction.onDrag(this, event);
}
public Component getTitle() { return title; }
public int getSize() { return size; }
public Set<Player> getViewers() { return new HashSet<>(viewers); }
public Map<Integer, ItemStack> getSlotItems() { return new HashMap<>(slotItems); }
public static GuiBuilder create() {
return new GuiBuilder();
}
public static class GuiBuilder {
private Component title;
private int size = -1;
private GuiAction globalAction = (gui, event) -> {};
private GuiCreateAction createAction = (gui, inv) -> {};
private GuiCloseAction closeAction = (gui, event) -> {};
private GuiDragAction dragAction = (gui, event) -> {};
private final Map<Integer, GuiAction> slotActions = new HashMap<>();
private final Map<Integer, ItemStack> slotItems = new HashMap<>();
private boolean preventDrag = true;
private Sound clickSound = Sound.UI_BUTTON_CLICK;
private float soundVolume = 0.5f;
private float soundPitch = 1.0f;
public GuiBuilder title(String title) {
this.title = Component.text(title);
return this;
}
public GuiBuilder title(Component title) {
this.title = title;
return this;
}
public GuiBuilder titleMini(String miniMessageTitle) {
this.title = miniMessage.deserialize(miniMessageTitle);
return this;
}
public GuiBuilder size(int size) {
this.size = size;
return this;
}
public GuiBuilder rows(int rows) {
this.size = Math.max(1, Math.min(6, rows)) * 9;
return this;
}
public GuiBuilder onGlobalClick(GuiAction action) {
this.globalAction = action != null ? action : (gui, event) -> {};
return this;
}
public GuiBuilder onCreate(GuiCreateAction action) {
this.createAction = action != null ? action : (gui, inv) -> {};
return this;
}
public GuiBuilder onClose(GuiCloseAction action) {
this.closeAction = action != null ? action : (gui, event) -> {};
return this;
}
public GuiBuilder onDrag(GuiDragAction action) {
this.dragAction = action != null ? action : (gui, event) -> {};
return this;
}
public GuiBuilder allowDrag() {
this.preventDrag = false;
return this;
}
public GuiBuilder preventDrag() {
this.preventDrag = true;
return this;
}
public GuiBuilder clickSound(Sound sound, float volume, float pitch) {
this.clickSound = sound;
this.soundVolume = volume;
this.soundPitch = pitch;
return this;
}
public GuiBuilder noClickSound() {
this.clickSound = null;
return this;
}
public GuiBuilder item(int slot, ItemStack item) {
return item(slot, item, null);
}
public GuiBuilder item(int slot, ItemStack item, GuiAction action) {
if (slot >= 0 && slot < 54 && item != null) {
slotItems.put(slot, item);
if (action != null) {
slotActions.put(slot, action);
}
}
return this;
}
public GuiBuilder item(int slot, Material material, String name) {
return item(slot, material, name, null);
}
public GuiBuilder item(int slot, Material material, String name, GuiAction action) {
ItemStack item = new ItemStack(material);
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.displayName(Component.text(name).decoration(TextDecoration.ITALIC, false));
item.setItemMeta(meta);
}
return item(slot, item, action);
}
public GuiBuilder itemMini(int slot, Material material, String miniMessageName) {
return itemMini(slot, material, miniMessageName, null);
}
public GuiBuilder itemMini(int slot, Material material, String miniMessageName, GuiAction action) {
ItemStack item = new ItemStack(material);
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.displayName(miniMessage.deserialize(miniMessageName).decoration(TextDecoration.ITALIC, false));
item.setItemMeta(meta);
}
return item(slot, item, action);
}
public GuiBuilder fillBorder(Material material) {
return fillBorder(new ItemStack(material));
}
public GuiBuilder fillBorder(ItemStack item) {
int actualSize = size > 0 ? size : 54;
int rows = actualSize / 9;
for (int i = 0; i < 9; i++) {
slotItems.put(i, item);
if (rows > 1) {
slotItems.put((rows - 1) * 9 + i, item);
}
}
for (int row = 1; row < rows - 1; row++) {
slotItems.put(row * 9, item);
slotItems.put(row * 9 + 8, item);
}
return this;
}
public GuiBuilder fillEmpty(Material material) {
return fillEmpty(new ItemStack(material));
}
public GuiBuilder fillEmpty(ItemStack item) {
int actualSize = size > 0 ? size : 54;
for (int i = 0; i < actualSize; i++) {
if (!slotItems.containsKey(i)) {
slotItems.put(i, item);
}
}
return this;
}
public QuickGui build() {
Component finalTitle = title != null ? title : Component.text("Untitled GUI");
return new QuickGui(finalTitle, size, globalAction, slotActions, slotItems,
createAction, closeAction, dragAction, preventDrag,
clickSound, soundVolume, soundPitch);
}
public QuickGui buildAndRegister(String id) {
QuickGui gui = build();
return register(id, gui);
}
}
@FunctionalInterface
public interface GuiAction {
void onClick(QuickGui gui, InventoryClickEvent event);
}
@FunctionalInterface
public interface GuiCreateAction {
void onCreate(QuickGui gui, Inventory inventory);
}
@FunctionalInterface
public interface GuiCloseAction {
void onClose(QuickGui gui, InventoryCloseEvent event);
}
@FunctionalInterface
public interface GuiDragAction {
void onDrag(QuickGui gui, InventoryDragEvent event);
}
public static class GuiUtils {
public static QuickGui createConfirmDialog(String title, Consumer<Boolean> callback) {
return create()
.titleMini("<green>" + title)
.rows(3)
.fillBorder(Material.GRAY_STAINED_GLASS_PANE)
.itemMini(11, Material.GREEN_CONCRETE, "<green><bold>CONFIRM",
(gui, event) -> {
callback.accept(true);
event.getWhoClicked().closeInventory();
})
.itemMini(15, Material.RED_CONCRETE, "<red><bold>CANCEL",
(gui, event) -> {
callback.accept(false);
event.getWhoClicked().closeInventory();
})
.build();
}
public static GuiBuilder createPaginated(String title, List<ItemStack> items, int itemsPerPage) {
GuiBuilder builder = create()
.titleMini(title)
.rows(6)
.fillBorder(Material.GRAY_STAINED_GLASS_PANE);
int totalPages = (int) Math.ceil((double) items.size() / itemsPerPage);
if (totalPages > 1) {
builder.itemMini(45, Material.ARROW, "<yellow>Previous Page")
.itemMini(53, Material.ARROW, "<yellow>Next Page");
}
return builder;
}
public static ItemStack createSeparator(NamedTextColor color) {
ItemStack pane = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);
ItemMeta meta = pane.getItemMeta();
if (meta != null) {
meta.displayName(Component.text(" ").color(color));
pane.setItemMeta(meta);
}
return pane;
}
}
}

View File

@@ -0,0 +1,351 @@
package me.trouper.alias.server.systems.tracing;
import me.trouper.alias.server.Main;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.entity.BlockDisplay;
import org.bukkit.entity.Display;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.Transformation;
import org.bukkit.util.Vector;
import org.bukkit.util.VoxelShape;
import org.joml.AxisAngle4f;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class BlockDisplayRaytracer implements Main {
public static void cleanup() {
JavaPlugin plugin = main.getPlugin();
List<World> worlds = plugin.getServer().getWorlds();
List<Entity> entities = new ArrayList<>();
for (World world : worlds) {
entities.addAll(world.getEntities().stream().filter(entity -> entity.getScoreboardTags().contains(main.getCommon().getTempTag())).toList());
entities.forEach(entity -> {
if (entity != null) entity.remove();
});
}
}
public static void outline(Material display, Location location, long stayTime, List<Player> viewers) {
outline(display, location, 0.05, stayTime, viewers);
}
public static void outline(Material display, Location corner1, Location corner2, double thickness, long stayTime, List<Player> viewers) {
World world = corner1.getWorld();
int minX = Math.min(corner1.getBlockX(), corner2.getBlockX());
int minY = Math.min(corner1.getBlockY(), corner2.getBlockY());
int minZ = Math.min(corner1.getBlockZ(), corner2.getBlockZ());
int maxX = Math.max(corner1.getBlockX(), corner2.getBlockX());
int maxY = Math.max(corner1.getBlockY(), corner2.getBlockY());
int maxZ = Math.max(corner1.getBlockZ(), corner2.getBlockZ());
Location a1 = new Location(world, minX, minY, minZ);
Location a2 = new Location(world, maxX + 1, minY, minZ);
Location a3 = new Location(world, maxX + 1, minY, maxZ + 1);
Location a4 = new Location(world, minX, minY, maxZ + 1);
Location b1 = new Location(world, minX, maxY + 1, minZ);
Location b2 = new Location(world, maxX + 1, maxY + 1, minZ);
Location b3 = new Location(world, maxX + 1, maxY + 1, maxZ + 1);
Location b4 = new Location(world, minX, maxY + 1, maxZ + 1);
trace(display, a1, a2, thickness, stayTime, viewers);
trace(display, a2, a3, thickness, stayTime, viewers);
trace(display, a3, a4, thickness, stayTime, viewers);
trace(display, a4, a1, thickness, stayTime, viewers);
trace(display, b1, b2, thickness, stayTime, viewers);
trace(display, b2, b3, thickness, stayTime, viewers);
trace(display, b3, b4, thickness, stayTime, viewers);
trace(display, b4, b1, thickness, stayTime, viewers);
trace(display, a1, b1, thickness, stayTime, viewers);
trace(display, a2, b2, thickness, stayTime, viewers);
trace(display, a3, b3, thickness, stayTime, viewers);
trace(display, a4, b4, thickness, stayTime, viewers);
}
public static void outline(Material display, Location location, double thickness, long stayTime, List<Player> viewers) {
Location og = location.getBlock().getLocation();
Location a1 = og.clone().add(0, 0, 0);
Location a2 = og.clone().add(1, 0, 0);
Location a3 = og.clone().add(1, 0, 1);
Location a4 = og.clone().add(0, 0, 1);
Location b1 = og.clone().add(0, 1, 0);
Location b2 = og.clone().add(1, 1, 0);
Location b3 = og.clone().add(1, 1, 1);
Location b4 = og.clone().add(0, 1, 1);
trace(display, a1, a2, thickness, stayTime, viewers);
trace(display, a2, a3, thickness, stayTime, viewers);
trace(display, a3, a4, thickness, stayTime, viewers);
trace(display, a4, a1, thickness, stayTime, viewers);
trace(display, b1, b2, thickness, stayTime, viewers);
trace(display, b2, b3, thickness, stayTime, viewers);
trace(display, b3, b4, thickness, stayTime, viewers);
trace(display, b4, b1, thickness, stayTime, viewers);
trace(display, a1, b1, thickness, stayTime, viewers);
trace(display, a2, b2, thickness, stayTime, viewers);
trace(display, a3, b3, thickness, stayTime, viewers);
trace(display, a4, b4, thickness, stayTime, viewers);
}
public static void trace(Material display, Location start, Location end, long stayTime, List<Player> viewers) {
trace(display, start, end.toVector().subtract(start.toVector()), 0.05, end.distance(start), stayTime, viewers);
}
public static void trace(Material display, Location start, Location end, double thickness, long stayTime, List<Player> viewers) {
trace(display, start, end.toVector().subtract(start.toVector()), thickness, end.distance(start), stayTime, viewers);
}
public static void trace(Material display, Location start, Vector direction, double thickness, double distance, long stayTime, List<Player> viewers) {
World world = start.getWorld();
BlockDisplay beam = world.spawn(start, BlockDisplay.class, entity -> {
AxisAngle4f angle = new AxisAngle4f(0, 0, 0, 1);
Vector3f transition = new Vector3f(-(float)(thickness / 2F));
Vector3f scale = new Vector3f((float)thickness, (float)thickness, (float)distance);
Transformation trans = new Transformation(transition, angle, scale, angle);
Location vector = entity.getLocation();
vector.setDirection(direction);
entity.teleport(vector);
entity.setBlock(display.createBlockData());
entity.setBrightness(new Display.Brightness(15, 15));
entity.setInterpolationDelay(0);
entity.setTransformation(trans);
entity.addScoreboardTag(main.getCommon().getTempTag());
for (Player player : Bukkit.getOnlinePlayers()) {
if (!viewers.contains(player)) {
player.hideEntity(main.getPlugin(), entity);
}
}
Bukkit.getScheduler().runTaskLater(main.getPlugin(), entity::remove, stayTime);
});
}
public static void trace(Material display, Location start, Vector direction, double thickness, double distance, long stayTime, Consumer<BlockDisplay> onEntitySpawn, List<Player> viewers) {
World world = start.getWorld();
BlockDisplay beam = world.spawn(start, BlockDisplay.class, entity -> {
AxisAngle4f angle = new AxisAngle4f(0, 0, 0, 1);
Vector3f transition = new Vector3f(-(float)(thickness / 2F));
Vector3f scale = new Vector3f((float)thickness, (float)thickness, (float)distance);
Transformation trans = new Transformation(transition, angle, scale, angle);
Location vector = entity.getLocation();
vector.setDirection(direction);
entity.teleport(vector);
entity.setBlock(display.createBlockData());
entity.setBrightness(new Display.Brightness(15, 15));
entity.setInterpolationDelay(0);
entity.setTransformation(trans);
entity.addScoreboardTag(main.getCommon().getTempTag());
for (Player player : Bukkit.getOnlinePlayers()) {
if (!viewers.contains(player)) {
player.hideEntity(main.getPlugin(), entity);
}
}
Bukkit.getScheduler().runTaskLater(main.getPlugin(), entity::remove, stayTime);
Bukkit.getScheduler().runTaskLater(main.getPlugin(), () -> onEntitySpawn.accept(entity), 5);
});
}
public static List<BlockDisplay> outline(Material display, Location location, long stayTime) {
return outline(display, location, 0.05, stayTime);
}
public static List<BlockDisplay> outline(Material display, Location location, double thickness, long stayTime) {
Location og = location.getBlock().getLocation();
Location a1 = og.clone().add(0, 0, 0);
Location a2 = og.clone().add(1, 0, 0);
Location a3 = og.clone().add(1, 0, 1);
Location a4 = og.clone().add(0, 0, 1);
Location b1 = og.clone().add(0, 1, 0);
Location b2 = og.clone().add(1, 1, 0);
Location b3 = og.clone().add(1, 1, 1);
Location b4 = og.clone().add(0, 1, 1);
List<BlockDisplay> a = new ArrayList<>();
a.add(trace(display, a1, a2, thickness, stayTime));
a.add(trace(display, a2, a3, thickness, stayTime));
a.add(trace(display, a3, a4, thickness, stayTime));
a.add(trace(display, a4, a1, thickness, stayTime));
a.add(trace(display, b1, b2, thickness, stayTime));
a.add(trace(display, b2, b3, thickness, stayTime));
a.add(trace(display, b3, b4, thickness, stayTime));
a.add(trace(display, b4, b1, thickness, stayTime));
a.add(trace(display, a1, b1, thickness, stayTime));
a.add(trace(display, a2, b2, thickness, stayTime));
a.add(trace(display, a3, b3, thickness, stayTime));
a.add(trace(display, a4, b4, thickness, stayTime));
return a;
}
public static void highlightCollisions(Block block, Color color, long stayTime) {
if (block == null || block.isEmpty() || !block.isCollidable())
return;
VoxelShape shape = block.getCollisionShape();
World world = block.getWorld();
Vector offset = block.getLocation().toVector();
for (BoundingBox box : shape.getBoundingBoxes()) {
highlight(box, offset, world, color, stayTime);
}
}
public static void highlight(BoundingBox box, Vector offset, World world, Color color, long stayTime) {
double x1 = box.getMinX() + offset.getX();
double y1 = box.getMinY() + offset.getY();
double z1 = box.getMinZ() + offset.getZ();
double x2 = box.getMaxX() + offset.getX();
double y2 = box.getMaxY() + offset.getY();
double z2 = box.getMaxZ() + offset.getZ();
traceGlowing(world, x1, y1, z1, x2, y1, z1, color, stayTime);
traceGlowing(world, x2, y1, z1, x2, y1, z2, color, stayTime);
traceGlowing(world, x2, y1, z2, x1, y1, z2, color, stayTime);
traceGlowing(world, x1, y1, z2, x1, y1, z1, color, stayTime);
traceGlowing(world, x1, y2, z1, x2, y2, z1, color, stayTime);
traceGlowing(world, x2, y2, z1, x2, y2, z2, color, stayTime);
traceGlowing(world, x2, y2, z2, x1, y2, z2, color, stayTime);
traceGlowing(world, x1, y2, z2, x1, y2, z1, color, stayTime);
traceGlowing(world, x1, y1, z1, x1, y2, z1, color, stayTime);
traceGlowing(world, x2, y1, z1, x2, y2, z1, color, stayTime);
traceGlowing(world, x2, y1, z2, x2, y2, z2, color, stayTime);
traceGlowing(world, x1, y1, z2, x1, y2, z2, color, stayTime);
}
public static void traceGlowing(World world, double x1, double y1, double z1, double x2, double y2, double z2, Color color, long stayTime) {
Location loc1 = new Location(world, x1, y1, z1);
Location loc2 = new Location(world, x2, y2, z2);
BlockDisplay ent = trace(Material.WHITE_CONCRETE, loc1, loc2, 0.01, stayTime);
ent.setGlowColorOverride(color);
ent.setGlowing(true);
}
public static BlockDisplay trace(Material display, Location start, Location end, long stayTime) {
return trace(display, start, end.toVector().subtract(start.toVector()), 0.05, end.distance(start), stayTime);
}
public static BlockDisplay trace(Material display, Location start, Location end, double thickness, long stayTime) {
return trace(display, start, end.toVector().subtract(start.toVector()), thickness, end.distance(start), stayTime);
}
public static BlockDisplay trace(Material display, Location start, Vector direction, double thickness, double distance, long stayTime) {
World world = start.getWorld();
BlockDisplay entity = world.spawn(start, BlockDisplay.class);
AxisAngle4f angle = new AxisAngle4f(0, 0, 0, 1);
Vector3f transition = new Vector3f(-(float)(thickness / 2F));
Vector3f scale = new Vector3f((float)thickness, (float)thickness, (float)distance);
Transformation trans = new Transformation(transition, angle, scale, angle);
Location vector = entity.getLocation();
vector.setDirection(direction);
entity.teleport(vector);
entity.setBlock(display.createBlockData());
entity.setBrightness(new Display.Brightness(15, 15));
entity.setInterpolationDelay(0);
entity.setTransformation(trans);
entity.addScoreboardTag(main.getCommon().getTempTag());
Bukkit.getScheduler().runTaskLater(main.getPlugin(), entity::remove, stayTime);
return entity;
}
public static void transform(BlockDisplay display, Location start, Location end, double thickness) {
Vector direction = end.toVector().subtract(start.toVector());
double distance = direction.length();
Location loc = start.clone();
loc.setDirection(direction);
display.teleport(loc);
Vector3f translation = new Vector3f(-(float)(thickness / 2F), 0, 0); // Centered
Vector3f scale = new Vector3f((float)thickness, (float)thickness, (float)distance);
AxisAngle4f rotation = new AxisAngle4f(0, 0, 0, 1);
Transformation transformation = new Transformation(translation, rotation, scale, rotation);
display.setTransformation(transformation);
}
public static void transform(BlockDisplay display, Location start, Vector direction, double distance, double thickness) {
Location loc = start.clone();
loc.setDirection(direction);
display.teleport(loc);
Vector3f translation = new Vector3f(-(float)(thickness / 2F), 0, 0);
Vector3f scale = new Vector3f((float)thickness, (float)thickness, (float)distance);
AxisAngle4f rotation = new AxisAngle4f(0, 0, 0, 1);
Transformation transformation = new Transformation(translation, rotation, scale, rotation);
display.setTransformation(transformation);
}
public static void translate(BlockDisplay display, Vector3f offset) {
Transformation current = display.getTransformation();
Vector3f translation = new Vector3f(current.getTranslation()).add(offset);
display.setTransformation(new Transformation(
translation,
current.getLeftRotation(),
current.getScale(),
current.getRightRotation()
));
}
public static void scale(BlockDisplay display, Vector3f scale) {
Transformation current = display.getTransformation();
display.setTransformation(new Transformation(
current.getTranslation(),
current.getLeftRotation(),
scale,
current.getRightRotation()
));
}
public static void rotate(BlockDisplay display, AxisAngle4f rotation) {
Transformation current = display.getTransformation();
display.setTransformation(new Transformation(
current.getTranslation(),
rotation,
current.getScale(),
rotation
));
}
public static void alignToDirection(BlockDisplay display, Vector direction) {
Location loc = display.getLocation().clone();
loc.setDirection(direction);
display.teleport(loc);
}
}

View File

@@ -0,0 +1,315 @@
package me.trouper.alias.server.systems.tracing;
import org.bukkit.FluidCollisionMode;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.RayTraceResult;
import org.bukkit.util.Vector;
import org.bukkit.util.VoxelShape;
import java.util.List;
import java.util.Random;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
public class CustomDisplayRaytracer {
public static final Predicate<Point> HIT_BLOCK = point -> {
Block b = point.getBlock();
Location l = point.getLoc();
if (b == null || b.isEmpty() || !b.isCollidable())
return false;
Vector vec = l.toVector().subtract(b.getLocation().toVector());
VoxelShape shape = b.getCollisionShape();
for (BoundingBox box : shape.getBoundingBoxes())
if (box.contains(vec))
return true;
return false;
};
public static final Predicate<Point> HIT_ENTITY = point -> {
return !point.getNearbyEntities(null, 5, true, 0.1, e -> e instanceof LivingEntity le && !le.isDead()).isEmpty();
};
public static final Predicate<Point> HIT_BLOCK_OR_ENTITY = point -> {
return HIT_BLOCK.test(point) || HIT_ENTITY.test(point);
};
public static final Predicate<Point> HIT_BLOCK_AND_ENTITY = point -> {
return HIT_BLOCK.test(point) && HIT_ENTITY.test(point);
};
public static Predicate<Point> hitEntityExclude(Entity exclude) {
return point -> !point.getNearbyEntities(exclude, 5, true, 0.1, e -> e instanceof LivingEntity le && !le.isDead()).isEmpty();
}
public static Predicate<Point> hitAnythingExclude(Entity exclude) {
return point -> HIT_BLOCK.test(point) || !point.getNearbyEntities(exclude, 5, true, 0.1, e -> e instanceof LivingEntity le && !le.isDead()).isEmpty();
}
public static Predicate<Point> hitEverythingExclude(Entity exclude) {
return point -> HIT_BLOCK.test(point) && !point.getNearbyEntities(exclude, 5, true, 0.1, e -> e instanceof LivingEntity le && !le.isDead()).isEmpty();
}
public static Predicate<Point> hitEntityIf(Predicate<Entity> condition) {
return point -> !point.getNearbyEntities(null, 5, true, 0.1, e -> e instanceof LivingEntity le && !le.isDead() && condition.test(e)).isEmpty();
}
public static Predicate<Point> hitBlockIf(Predicate<Block> condition) {
return point -> HIT_BLOCK.test(point) && condition.test(point.getBlock());
}
public static Predicate<Point> hitAnythingIf(Predicate<Entity> condition) {
return point -> HIT_BLOCK.test(point) || !point.getNearbyEntities(null, 5, true, 0.1, e -> e instanceof LivingEntity le && !le.isDead() && condition.test(e)).isEmpty();
}
public static Predicate<Point> hitEverythingIf(Predicate<Entity> condition) {
return point -> HIT_BLOCK.test(point) && !point.getNearbyEntities(null, 5, true, 0.1, e -> e instanceof LivingEntity le && !le.isDead() && condition.test(e)).isEmpty();
}
public static Point trace(Location start, Location end, Predicate<Point> hitCondition) {
return trace(start, end, 0.5, hitCondition);
}
public static Point trace(Location start, Location end, double interval, Predicate<Point> hitCondition) {
return trace(start, end.toVector().subtract(start.toVector()), end.distance(start), interval, hitCondition);
}
public static Point trace(Location start, Vector direction, double distance, Predicate<Point> hitCondition) {
return trace(start, direction, distance, 0.5, hitCondition);
}
public static Point trace(Location start, Vector direction, double distance, double interval, Predicate<Point> hitCondition) {
if (interval < 0) throw new IllegalArgumentException("interval cannot be zero!");
if (distance < 0) throw new IllegalArgumentException("distance cannot be zero!");
for (double i = 0.0; i < distance; i += interval) {
Point point = blocksInFrontOf(start, direction, i, false);
if (hitCondition.test(point)) {
return point;
}
}
return blocksInFrontOf(start, direction, distance, true);
}
public static BukkitTask traceDelayed(Plugin plugin, Location start, Vector direction, double distance, double interval, long tickDelay, int pointsPerTick, Predicate<Point> hitCondition) {
if (interval <= 0) throw new IllegalArgumentException("interval cannot be zero or negative!");
if (distance <= 0) throw new IllegalArgumentException("distance cannot be zero or negative!");
if (tickDelay < 0) throw new IllegalArgumentException("tickDelay cannot be negative!");
Vector normalizedDir = direction.clone().normalize();
return new BukkitRunnable() {
private double currentDistance = 0.0;
private boolean hit = false;
@Override
public void run() {
if (hit || currentDistance > distance) {
if (!hit) {
Point finalPoint = blocksInFrontOf(start, normalizedDir, distance, true);
hitCondition.test(finalPoint);
}
this.cancel();
return;
}
for (int i = 0; i < pointsPerTick && currentDistance <= distance; i++) {
Point point = blocksInFrontOf(start, normalizedDir, currentDistance, false);
if (hitCondition.test(point)) {
hit = true;
break;
}
currentDistance += interval;
}
}
}.runTaskTimer(plugin, 0, tickDelay);
}
public static BukkitTask traceDelayed(Plugin plugin, Location start, Location end, double interval, long tickDelay, int pointsPerTick, Predicate<Point> hitCondition) {
Vector direction = end.toVector().subtract(start.toVector()).normalize();
double distance = start.distance(end);
return traceDelayed(plugin, start, direction, distance, interval, tickDelay,pointsPerTick, hitCondition);
}
public static BukkitTask traceDelayed(Plugin plugin,
Location start,
Location end,
long tickDelay,
Predicate<Point> hitCondition) {
return traceDelayed(plugin, start, end,0.5, tickDelay, 1, hitCondition);
}
public static BukkitTask traceDelayed(Plugin plugin,
Location start,
Vector direction,
double distance,
long tickDelay,
Predicate<Point> hitCondition) {
return traceDelayed(plugin, start, direction, distance, 0.5, tickDelay,1, hitCondition);
}
public static Point traceWithReflection(Location start, Vector direction, double distance, double interval,
int maxReflections, Predicate<Point> hitCondition,
BiPredicate<Point, Block> blockReflectCondition,
BiPredicate<Point, Entity> entityReflectCondition) {
if (interval <= 0) throw new IllegalArgumentException("interval cannot be zero or negative!");
if (distance <= 0) throw new IllegalArgumentException("distance cannot be zero or negative!");
Vector normalizedDir = direction.clone().normalize();
Location currentLocation = start.clone();
Vector currentDirection = normalizedDir.clone();
double remainingDistance = distance;
int reflections = 0;
while (remainingDistance > 0 && reflections <= maxReflections) {
for (double i = 0.0; i < remainingDistance; i += interval) {
Point point = blocksInFrontOf(currentLocation, currentDirection, i, false);
if (hitCondition.test(point)) {
return point;
}
boolean shouldReflect = false;
Vector newDirection = null;
if (HIT_BLOCK.test(point) && point.getBlock() != null) {
Block hitBlock = point.getBlock();
if (blockReflectCondition.test(point, hitBlock)) {
Point previousPoint = blocksInFrontOf(currentLocation, currentDirection, Math.max(0, i - interval), false);
BlockFace hitFace = traceBlockFace(previousPoint.getLoc(), currentDirection, interval * 2);
if (hitFace != null) {
Vector faceNormal = getFaceNormal(hitFace);
newDirection = calculateReflection(currentDirection, faceNormal);
shouldReflect = true;
}
}
}
List<Entity> nearbyEntities = point.getNearbyEntities(null, 5, true, 0.1, e -> e instanceof LivingEntity le && !le.isDead());
if (!nearbyEntities.isEmpty()) {
for (Entity entity : nearbyEntities) {
if (entityReflectCondition.test(point, entity)) {
Point previousPoint = blocksInFrontOf(currentLocation, currentDirection, Math.max(0, i - interval), false);
newDirection = glanceReflect(currentDirection);
shouldReflect = true;
break;
}
}
}
if (shouldReflect) {
double backStep = Math.max(0, i - interval);
currentLocation = blocksInFrontOf(currentLocation, currentDirection, backStep, false).getLoc();
currentDirection = newDirection;
remainingDistance -= backStep;
reflections++;
currentLocation = currentLocation.add(currentDirection.clone().multiply(interval * 0.1));
remainingDistance -= interval * 0.1;
break;
}
if (i + interval >= remainingDistance) {
Point finalPoint = blocksInFrontOf(currentLocation, currentDirection, remainingDistance, true);
return finalPoint;
}
}
if (reflections > maxReflections) {
Point finalPoint = blocksInFrontOf(currentLocation, currentDirection, remainingDistance, true);
return finalPoint;
}
}
return blocksInFrontOf(start, normalizedDir, distance, true);
}
private static Vector glanceReflect(Vector incident) {
return offsetVector(incident,4).multiply(-1);
}
private static BlockFace traceBlockFace(Location startLocation, Vector direction, double maxDistance) {
Predicate<Block> blockPredicate = block -> true;
Predicate<Entity> entityPredicate = entity -> false;
RayTraceResult result = startLocation.getWorld().rayTrace(startLocation, direction, maxDistance, FluidCollisionMode.NEVER,true,0.1,entityPredicate, blockPredicate);
if (result != null && result.getHitBlock() != null && result.getHitBlockFace() != null) {
return result.getHitBlockFace();
}
return null;
}
private static Vector calculateReflection(Vector incident, Vector normal) {
// r = i - 2(i dot n)n
double dot = incident.dot(normal);
Vector reflection = incident.clone().subtract(normal.clone().multiply(2 * dot));
return reflection.normalize();
}
private static Vector getFaceNormal(BlockFace face) {
return switch (face) {
case DOWN -> new Vector(0, -1, 0);
case NORTH -> new Vector(0, 0, -1);
case SOUTH -> new Vector(0, 0, 1);
case EAST -> new Vector(1, 0, 0);
case WEST -> new Vector(-1, 0, 0);
default -> new Vector(0, 1, 0);
};
}
public static Point blocksInFrontOf(Location loc, Vector dir, double blocks, boolean missed) {
return new Point(loc.clone().add(dir.getX() * blocks, dir.getY() * blocks, dir.getZ() * blocks), blocks, missed);
}
public static Vector offsetVector(Vector original, double angleDegrees) {
Random random = new Random();
original = original.clone().normalize();
double yaw = Math.toDegrees(Math.atan2(-original.getX(), original.getZ()));
double pitch = Math.toDegrees(Math.asin(-original.getY()));
double yawOffset = (random.nextDouble() * 2 - 1) * angleDegrees;
double pitchOffset = (random.nextDouble() * 2 - 1) * angleDegrees;
yaw += yawOffset;
pitch += pitchOffset;
pitch = Math.max(-90, Math.min(90, pitch));
double pitchRad = Math.toRadians(pitch);
double yawRad = Math.toRadians(yaw);
double x = -Math.sin(yawRad) * Math.cos(pitchRad);
double y = -Math.sin(pitchRad);
double z = Math.cos(yawRad) * Math.cos(pitchRad);
return new Vector(x, y, z);
}
}

View File

@@ -0,0 +1,88 @@
package me.trouper.alias.server.systems.tracing;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class Point {
private final Location loc;
private final World world;
private final Block block;
private final boolean missed;
private final double traveledDist;
private List<Point> rayPath;
public Point(Location loc, double traveledDist, boolean missed) {
this.loc = loc;
this.world = loc.getWorld();
this.block = loc.getBlock();
this.missed = missed;
this.traveledDist = traveledDist;
if (world == null) {
throw new IllegalArgumentException("point world cannot be null!");
}
}
public List<Entity> getNearbyEntities(Entity exclude, int range, boolean requireContact, double expansionX, double expansionY, double expansionZ, Predicate<Entity> filter) {
return new ArrayList<>(world.getNearbyEntities(loc, range, range, range, e -> {
if (requireContact && !e.getBoundingBox().expand(expansionX, expansionY, expansionZ).contains(loc.toVector())) {
return false;
}
return filter.test(e) && e != exclude;
}));
}
public List<Entity> getNearbyEntities(Entity exclude, int range, boolean requireContact, double expansion, Predicate<Entity> filter) {
return getNearbyEntities(exclude, range, requireContact, expansion, expansion, expansion, filter);
}
public List<Entity> getNearbyEntities(Entity exclude, int range, boolean requireContact, Predicate<Entity> filter) {
return getNearbyEntities(exclude, range, requireContact, 0, filter);
}
public List<Entity> getNearbyEntities(Entity exclude, int range, Predicate<Entity> filter) {
return getNearbyEntities(exclude, range, false, filter);
}
public double getTraveledDist() {
return traveledDist;
}
public boolean wasMissed() {
return missed;
}
public Block getBlock() {
return block;
}
public Location getLoc() {
return loc;
}
public World getWorld() {
return world;
}
public double distance(Location other) {
return other.distance(loc);
}
public Point addRayPoint(Point point) {
rayPath.add(point);
return point;
}
public List<Point> setRayPath(List<Point> points) {
rayPath = points;
return rayPath;
}
public List<Point> getRayPath = new ArrayList<>();
}

View File

@@ -0,0 +1,27 @@
package me.trouper.alias.server.systems.tracing;
import org.bukkit.util.Vector;
public class ReflectionResult {
private final Point hitPoint;
private final boolean shouldReflect;
private final Vector reflectedDirection;
public ReflectionResult(Point hitPoint, boolean shouldReflect, Vector reflectedDirection) {
this.hitPoint = hitPoint;
this.shouldReflect = shouldReflect;
this.reflectedDirection = reflectedDirection;
}
public Point getHitPoint() {
return hitPoint;
}
public boolean shouldReflect() {
return shouldReflect;
}
public Vector getReflectedDirection() {
return reflectedDirection;
}
}

View File

@@ -0,0 +1,256 @@
package me.trouper.alias.server.systems.visual;
import me.trouper.alias.server.Main;
import me.trouper.alias.utils.misc.Randomizer;
import org.bukkit.Bukkit;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.util.Vector;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
public class DisplayUtils implements Main {
// This will 100% get Javadoc in the future when I try to use it. Everything in here is so convoluted.
public static final Function<Particle, Consumer<Location>> PARTICLE_FACTORY = particle -> l -> l.getWorld().spawnParticle(particle, l, 1, 0, 0, 0, 0);
public static final BiFunction<Color, Float, Consumer<Location>> DUST_PARTICLE_FACTORY = (color, thickness) -> {
Particle.DustOptions dust = new Particle.DustOptions(color, thickness);
return l -> l.getWorld().spawnParticle(Particle.DUST, l, 1, 0, 0, 0, 0, dust);
};
public static final Function<Boolean, Consumer<Location>> FLAME_PARTICLE_FACTORY = soul -> {
Particle flame = soul ? Particle.SOUL_FIRE_FLAME : Particle.FLAME;
return l -> l.getWorld().spawnParticle(flame, l, 1, 0, 0, 0, 0);
};
public static void ring(Location loc, double radius, Color color, float thickness) {
ring(loc, radius, DUST_PARTICLE_FACTORY.apply(color, thickness));
}
public static void sphere(Location center, double radius, double pointDistance, Consumer<Location> action) {
double dPhi = pointDistance / radius;
for (double phi = 0.0; phi <= Math.PI; phi += dPhi) {
double yOffset = radius * Math.cos(phi);
double ringRadius = radius * Math.sin(phi);
if (ringRadius < 1e-6) {
Location loc = center.clone().add(0, yOffset, 0);
action.accept(loc);
} else {
double dTheta = pointDistance / ringRadius;
for (double theta = 0.0; theta < 2 * Math.PI; theta += dTheta) {
double xOffset = ringRadius * Math.cos(theta);
double zOffset = ringRadius * Math.sin(theta);
Location loc = center.clone().add(xOffset, yOffset, zOffset);
action.accept(loc);
}
}
}
}
public static void sphereWave(Location center, double maxRadius, double radialStep, double maxDistanceBetweenPoints, Consumer<Location> action) {
AtomicReference<Double> currentRadius = new AtomicReference<>(radialStep);
Bukkit.getScheduler().runTaskTimer(main.getPlugin(), (task) -> {
double r = currentRadius.get();
if (r > maxRadius) {
task.cancel();
return;
}
sphere(center, r, maxDistanceBetweenPoints, action);
currentRadius.set(r + radialStep);
}, 0L, 1L);
}
public static void ring(Location loc, double radius, Consumer<Location> action) {
for (int theta = 0; theta < 360; theta += 10) {
double x = Math.cos(Math.toRadians(theta)) * radius;
double z = Math.sin(Math.toRadians(theta)) * radius;
Location newLoc = loc.clone().add(x, 0, z);
action.accept(newLoc);
}
}
public static void ring(Location loc, double radius, double maxDistanceBetweenPoints, Consumer<Location> action) {
arc(loc, radius, 0, 360, maxDistanceBetweenPoints, action);
}
public static void wave(Location loc, double radius, Color color, float thickness, double gap) {
wave(loc, radius, DUST_PARTICLE_FACTORY.apply(color, thickness), gap);
}
public static void wave(Location loc, double radius, Consumer<Location> action, double gap) {
AtomicReference<Double> i = new AtomicReference<>(gap);
Bukkit.getScheduler().runTaskTimer(main.getPlugin(), (task) -> {
if (i.get() >= radius) {
task.cancel();
return;
}
ring(loc, i.get(), action);
i.set(i.get() + gap);
}, 0, 1);
}
public static void wave(Location loc, double radius, double radialGap, double maxDistanceBetweenPoints, Consumer<Location> action) {
AtomicReference<Double> r = new AtomicReference<>(radialGap);
Bukkit.getScheduler().runTaskTimer(main.getPlugin(), (task) -> {
if (r.get() > radius) {
task.cancel();
return;
}
ring(loc, r.get(), maxDistanceBetweenPoints, action);
r.set(r.get() + radialGap);
}, 0, 1);
}
public static void disc(Location loc, double radius, Consumer<Location> action, double gap) {
for (double i = gap; i < radius; i += gap) {
ring(loc, i, action);
}
}
public static void disc(Location loc, double radius, double radialGap, double maxDistanceBetweenPoints, Consumer<Location> action) {
for (double r = radialGap; r <= radius; r += radialGap) {
ring(loc, r, maxDistanceBetweenPoints, action);
}
}
public static void helix(Location loc, double radius, Consumer<Location> action, double gap, int height) {
int theta = 0;
for (double y = 0; y <= height; y += gap) {
double x = Math.cos(Math.toRadians(theta)) * radius;
double z = Math.sin(Math.toRadians(theta)) * radius;
Location newLoc = loc.clone().add(x, y, z);
action.accept(newLoc);
theta += 10;
}
}
public static void vortex(Location loc, double radius, Consumer<Location> action, double gapH, double gapV, int height) {
double r = radius;
int theta = 0;
for (double y = 0; y <= height; y += gapV) {
double x = Math.cos(Math.toRadians(theta)) * r;
double z = Math.sin(Math.toRadians(theta)) * r;
Location newLoc = loc.clone().add(x, y, z);
action.accept(newLoc);
r += gapH;
theta += 10;
}
}
public static void beam(Location loc, Consumer<Location> action, double gap, int height) {
for (double y = 0; y <= height; y += gap) {
Location newLoc = loc.clone().add(0, y, 0);
action.accept(newLoc);
}
}
public static void arc(Location loc, double radius, int angleFrom, int angleTo, Consumer<Location> action) {
for (int theta = angleFrom; theta < angleTo; theta += 10) {
double x = Math.cos(Math.toRadians(theta)) * radius;
double z = Math.sin(Math.toRadians(theta)) * radius;
Location newLoc = loc.clone().add(x, 0, z);
action.accept(newLoc);
}
}
public static void arc(Location loc, double radius, int angleFrom, int angleTo, double maxDistanceBetweenPoints, Consumer<Location> action) {
int angleSpan = angleTo - angleFrom;
if (angleSpan <= 0) return;
int points = Math.max(2, (int) ((2 * Math.PI * radius * (angleSpan / 360.0)) / maxDistanceBetweenPoints));
double angleStep = (double) angleSpan / points;
for (int i = 0; i <= points; i++) {
double theta = angleFrom + (i * angleStep);
double x = Math.cos(Math.toRadians(theta)) * radius;
double z = Math.sin(Math.toRadians(theta)) * radius;
Location point = loc.clone().add(x, 0, z);
action.accept(point);
}
}
public static void fan(Location loc, double radius, int angleFrom, int angleTo, Consumer<Location> action, double gap) {
for (double i = gap; i < radius; i += gap) {
arc(loc, i, angleFrom, angleTo, action);
}
}
public static void fan(Location loc, double radius, int angleFrom, int angleTo, double maxDistanceBetweenPoints, Consumer<Location> action, double radialGap) {
for (double r = radialGap; r < radius; r += radialGap) {
arc(loc, r, angleFrom, angleTo, maxDistanceBetweenPoints, action);
}
}
public static void fanWave(Location loc, double radius, int sections, Consumer<Location> action, double gap) {
double arcLength = 360.0 / sections;
AtomicReference<Double> i = new AtomicReference<>(0.0);
Bukkit.getScheduler().runTaskTimer(main.getPlugin(), (task) -> {
if (i.get() >= 360) {
task.cancel();
return;
}
double start = i.get();
fan(loc, radius, (int)start, (int)(start + arcLength), action, gap);
i.set(i.get() + arcLength);
}, 0, 5);
}
public static void fanWaveRandom(Location loc, double radius, int sections, Consumer<Location> action, double gap) {
double arcLength = 360.0 / sections;
List<Double> ints = new ArrayList<>();
for (double start = 0; start < 360; start += arcLength) {
ints.add(start);
}
AtomicInteger i = new AtomicInteger(0);
Randomizer random = new Randomizer();
Bukkit.getScheduler().runTaskTimer(main.getPlugin(), (task) -> {
if (i.get() >= sections) {
task.cancel();
return;
}
double start = random.getRandomElement(ints);
ints.remove(start);
fan(loc, radius, (int)start, (int)(start + arcLength), action, gap);
i.getAndIncrement();
}, 0, 5);
}
public static void waveFan(Location loc, double radius, int angleFrom, int angleTo, double maxDistanceBetweenPoints, Consumer<Location> action, double radialGap) {
AtomicReference<Double> r = new AtomicReference<>(radialGap);
Bukkit.getScheduler().runTaskTimer(main.getPlugin(), (task) -> {
if (r.get() >= radius) {
task.cancel();
return;
}
arc(loc, r.get(), angleFrom, angleTo, maxDistanceBetweenPoints, action);
r.set(r.get() + radialGap);
}, 0, 1);
}
public static void waveFan(Location loc, double radius, Vector direction, int angle, double maxDistanceBetweenPoints, Consumer<Location> action, double radialGap) {
double baseAngle = Math.toDegrees(Math.atan2(direction.getZ(), direction.getX()));
int angleFrom = (int) (baseAngle - angle / 2.0);
int angleTo = (int) (baseAngle + angle / 2.0);
waveFan(loc, radius, angleFrom, angleTo, maxDistanceBetweenPoints, action, radialGap);
}
}

View File

@@ -0,0 +1,348 @@
package me.trouper.alias.server.systems.world;
import me.trouper.alias.server.Main;
import me.trouper.alias.server.systems.Verbose;
import me.trouper.alias.server.systems.burning.BlockBurner;
import me.trouper.alias.server.systems.burning.BurnOptions;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import java.io.Closeable;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
public class ExplosionUtils implements Main {
public static class ExplosionOptions {
private double coreRadius = 3.0;
private double falloffRadius = 8.0;
private double maxBurnRadius = 15.0;
private double destructionDelay = 0.0; // SECONDS
private double burnDelay = 0.5; // SECONDS
private double maxHeat = 1.0;
private double minHeat = 0.1;
private boolean createParticles = true;
private boolean playSound = true;
private BurnOptions burnOptions = new BurnOptions();
public double getCoreRadius() { return coreRadius; }
public void setCoreRadius(double coreRadius) { this.coreRadius = coreRadius; }
public double getFalloffRadius() { return falloffRadius; }
public void setFalloffRadius(double falloffRadius) { this.falloffRadius = falloffRadius; }
public double getMaxBurnRadius() { return maxBurnRadius; }
public void setMaxBurnRadius(double maxBurnRadius) { this.maxBurnRadius = maxBurnRadius; }
public double getDestructionDelay() { return destructionDelay; }
public void setDestructionDelay(double destructionDelay) { this.destructionDelay = destructionDelay; }
public double getBurnDelay() { return burnDelay; }
public void setBurnDelay(double burnDelay) { this.burnDelay = burnDelay; }
public double getMaxHeat() { return maxHeat; }
public void setMaxHeat(double maxHeat) { this.maxHeat = maxHeat; }
public double getMinHeat() { return minHeat; }
public void setMinHeat(double minHeat) { this.minHeat = minHeat; }
public boolean isCreateParticles() { return createParticles; }
public void setCreateParticles(boolean createParticles) { this.createParticles = createParticles; }
public boolean isPlaySound() { return playSound; }
public void setPlaySound(boolean playSound) { this.playSound = playSound; }
public BurnOptions getBurnOptions() { return burnOptions; }
public void setBurnOptions(BurnOptions burnOptions) { this.burnOptions = burnOptions; }
}
public static class ExplosionResult {
private final Map<Block, BlockState> originalStates = new HashMap<>();
private final Map<Block, ItemStack[]> originalInventories = new HashMap<>();
private final List<Integer> scheduledTaskIds = new ArrayList<>();
private final BlockBurner burner;
public ExplosionResult(BlockBurner burner) {
this.burner = burner;
}
public void cleanup() {
if (burner != null) {
burner.close();
}
scheduledTaskIds.forEach(task -> Bukkit.getScheduler().cancelTask(task));
}
void recordSnapshot(Block block) {
BlockState state = block.getState();
originalStates.put(block, state);
if (state instanceof InventoryHolder) {
Inventory inv = ((InventoryHolder) state).getInventory();
originalInventories.put(block, inv.getContents());
}
}
void addScheduledTask(int taskId) {
scheduledTaskIds.add(taskId);
}
public void restore() {
for (int taskId : scheduledTaskIds) {
Bukkit.getScheduler().cancelTask(taskId);
}
cleanup();
for (Map.Entry<Block, BlockState> entry : originalStates.entrySet()) {
Block block = entry.getKey();
BlockState snapshot = entry.getValue();
block.setBlockData(snapshot.getBlockData(), false);
snapshot.update(true, false);
ItemStack[] contents = originalInventories.get(block);
if (contents != null && block.getState() instanceof InventoryHolder) {
Inventory inv = ((InventoryHolder) block.getState()).getInventory();
inv.setContents(contents);
}
}
}
public BlockBurner getBurner() { return burner; }
public Map<Block, BlockState> getOriginalStates() {
return originalStates;
}
public Map<Block, ItemStack[]> getOriginalInventories() {
return originalInventories;
}
public List<Integer> getScheduledTaskIds() {
return scheduledTaskIds;
}
}
public static ExplosionResult createExplosion(Location center, ExplosionOptions options) {
World world = center.getWorld();
if (world == null) throw new IllegalArgumentException("Center location must have a valid world");
Map<Block, Double> affectedBlocks = getBlocksInRadius(center, options.getMaxBurnRadius());
ExplosionResult result = new ExplosionResult(new BlockBurner(options.getBurnOptions()));
for (Block block : affectedBlocks.keySet()) {
if (block.getType().isAir()) continue;
result.recordSnapshot(block);
}
Set<Block> blocksToDestroy = new HashSet<>();
Set<Block> blocksToBurn = new HashSet<>();
Map<Block, Float> blocksHeatMap = new HashMap<>();
categorizeBlocks(affectedBlocks, options, blocksToDestroy, blocksToBurn, blocksHeatMap);
BlockBurner burner = new BlockBurner(options.getBurnOptions());
scheduleDestruction(blocksToDestroy, options,result);
scheduleBurning(blocksToBurn, blocksHeatMap, burner, center, options,result);
if (options.isCreateParticles() || options.isPlaySound()) createExplosionEffects(center, options);
return result;
}
private static Map<Block, Double> getBlocksInRadius(Location center, double radius) {
Map<Block, Double> blocks = new HashMap<>();
World world = center.getWorld();
int radiusInt = (int) Math.ceil(radius);
Vector centerVec = center.toVector();
for (int x = -radiusInt; x <= radiusInt; x++) {
for (int y = -radiusInt; y <= radiusInt; y++) {
for (int z = -radiusInt; z <= radiusInt; z++) {
Block block = world.getBlockAt(
center.getBlockX() + x,
center.getBlockY() + y,
center.getBlockZ() + z
);
double distance = block.getLocation().toVector().distance(centerVec);
if (distance <= radius) {
blocks.put(block, distance);
}
}
}
}
return blocks;
}
private static void categorizeBlocks(Map<Block, Double> affectedBlocks, ExplosionOptions options,
Set<Block> blocksToDestroy, Set<Block> blocksToBurn,
Map<Block, Float> blocksHeatMap) {
ThreadLocalRandom random = ThreadLocalRandom.current();
for (Map.Entry<Block, Double> entry : affectedBlocks.entrySet()) {
Block block = entry.getKey();
double distance = entry.getValue();
if (block.getType().isAir() || !block.getType().isBlock()) continue;
float heat = calculateHeat(distance, options);
blocksHeatMap.put(block, heat);
if (distance <= options.getCoreRadius()) blocksToDestroy.add(block);
if (distance <= options.getFalloffRadius()) {
double destructionChance = 1.0 - ((distance - options.getCoreRadius()) /
(options.getFalloffRadius() - options.getCoreRadius()));
destructionChance *= (0.7 + random.nextDouble() * 0.6);
if (random.nextDouble() < destructionChance) blocksToDestroy.add(block);
else blocksToBurn.add(block);
} else {
double burnChance = 1.0 - ((distance - options.getFalloffRadius()) /
(options.getMaxBurnRadius() - options.getFalloffRadius()));
burnChance *= (0.8 + random.nextDouble() * 0.6);
if (random.nextDouble() < burnChance) blocksToBurn.add(block);
}
}
}
private static float calculateHeat(double distance, ExplosionOptions options) {
double normalizedDistance = distance / options.getMaxBurnRadius();
double heatRange = options.getMaxHeat() - options.getMinHeat();
double heatFactor = Math.pow(1.0 - normalizedDistance, 2.0);
return (float) (options.getMinHeat() + heatRange * heatFactor);
}
private static void scheduleDestruction(Set<Block> blocksToDestroy, ExplosionOptions options, ExplosionResult result) {
if (blocksToDestroy.isEmpty()) return;
long destructionDelayTicks = (long) (options.getDestructionDelay() * 20);
int outerTask = Bukkit.getScheduler().runTaskLater(main.getPlugin(),()->{
List<Block> blockList = new ArrayList<>(blocksToDestroy);
Collections.shuffle(blockList);
int blocksPerWave = Math.max(1, blockList.size() / 5);
for (int wave = 0; wave < 5; wave++) {
int startIndex = wave * blocksPerWave;
int endIndex = Math.min(startIndex + blocksPerWave, blockList.size());
if (startIndex >= blockList.size()) break;
int innerTask = Bukkit.getScheduler().runTaskLater(main.getPlugin(),()->{
for (int i = startIndex; i < endIndex; i++) {
Block block = blockList.get(i);
if (!block.getType().isAir()) {
block.setType(Material.AIR);
}
}
},wave * 2).getTaskId();
result.addScheduledTask(innerTask);
}
},destructionDelayTicks).getTaskId();
result.addScheduledTask(outerTask);
}
private static void scheduleBurning(Set<Block> blocksToMaybeBurn, Map<Block, Float> blocksHeatMap,
BlockBurner burner, Location center, ExplosionOptions options,
ExplosionResult result) {
if (blocksToMaybeBurn.isEmpty()) return;
long burnDelayTicks = (long) (options.getBurnDelay() * 20);
int outerTask = Bukkit.getScheduler().runTaskLater(main.getPlugin(),()->{
List<Block> blockList = new ArrayList<>(blocksToMaybeBurn);
Collections.shuffle(blockList);
Map<Integer, List<Block>> burnWaves = new HashMap<>();
for (Block block : blockList) {
double distance = block.getLocation().distance(center);
int waveIndex = (int) (distance / 2.0);
burnWaves.computeIfAbsent(waveIndex, k -> new ArrayList<>()).add(block);
}
for (Map.Entry<Integer, List<Block>> waveEntry : burnWaves.entrySet()) {
int waveDelay = waveEntry.getKey() * 3;
List<Block> waveBlocks = waveEntry.getValue();
int middleTask = Bukkit.getScheduler().runTaskLater(main.getPlugin(),()->{
for (Block block : waveBlocks) {
if (burner.isClosed()) continue;
float heat = blocksHeatMap.getOrDefault(block, 0.1f);
ThreadLocalRandom random = ThreadLocalRandom.current();
int randomDelay = random.nextInt(0, 10);
int innerTask = Bukkit.getScheduler().runTaskLater(main.getPlugin(),()->{
if (!burner.isClosed() && !block.getType().isAir()) {
burner.burn(block, heat);
}
},randomDelay).getTaskId();
result.addScheduledTask(innerTask);
}
},waveDelay).getTaskId();
result.addScheduledTask(middleTask);
}
},burnDelayTicks).getTaskId();
result.addScheduledTask(outerTask);
}
private static void createExplosionEffects(Location center, ExplosionOptions options) {
World world = center.getWorld();
if (world == null) return;
if (options.isPlaySound()) {
world.playSound(center, Sound.ENTITY_GENERIC_EXPLODE, 1.0f, 0.8f);
world.playSound(center, Sound.ENTITY_LIGHTNING_BOLT_THUNDER, 0.5f, 1.2f);
}
if (options.isCreateParticles()) {
world.spawnParticle(Particle.EXPLOSION_EMITTER, center, 3);
world.spawnParticle(Particle.EXPLOSION, center, 20, 2, 2, 2, 0.1);
world.spawnParticle(Particle.LARGE_SMOKE, center, 15, 1, 1, 1, 0.05);
world.spawnParticle(Particle.FLAME, center, 30, 3, 3, 3, 0.1);
Bukkit.getScheduler().runTaskLater(main.getPlugin(),()->{
world.spawnParticle(Particle.SMOKE, center, 50, 4, 4, 4, 0.02);
},20);
}
}
public static ExplosionResult createExplosion(Location center) {
return createExplosion(center, new ExplosionOptions());
}
public static ExplosionResult createExplosion(Location center, double coreRadius, double falloffRadius, double maxBurnRadius) {
ExplosionOptions options = new ExplosionOptions();
options.setCoreRadius(coreRadius);
options.setFalloffRadius(falloffRadius);
options.setMaxBurnRadius(maxBurnRadius);
options.setBurnDelay(0);
options.setDestructionDelay(0);
return createExplosion(center, options);
}
}

View File

@@ -0,0 +1,262 @@
package me.trouper.alias.utils;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Material;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeModifier;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.components.CustomModelDataComponent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
public class ItemBuilder {
private ItemStack stack;
private ItemMeta meta;
private CustomModelDataComponent modelData;
private final MiniMessage miniMessage = MiniMessage.miniMessage();
public ItemBuilder() {
this(new ItemStack(Material.STONE));
}
public ItemBuilder(ItemStack stack) {
this.stack = stack.clone();
this.meta = this.stack.getItemMeta();
if (this.meta == null) {
throw new IllegalArgumentException("ItemStack must have ItemMeta");
}
}
public ItemBuilder(Material material) {
this(new ItemStack(material));
}
public ItemBuilder(Material material, int amount) {
this(new ItemStack(material, amount));
}
public ItemBuilder material(Material material) {
this.stack = this.stack.withType(material);
return this;
}
public ItemBuilder amount(int amount) {
this.stack.setAmount(Math.max(1, Math.min(64, amount)));
return this;
}
public ItemBuilder displayName(Component name) {
this.meta.displayName(name);
return this;
}
public ItemBuilder displayName(String miniMessageText) {
Component component = miniMessage.deserialize(miniMessageText)
.decoration(TextDecoration.ITALIC, false);
return displayName(component);
}
public ItemBuilder displayNameRaw(String text) {
Component component = Component.text(text)
.decoration(TextDecoration.ITALIC, false);
return displayName(component);
}
public ItemBuilder loreComponent(Component line) {
List<Component> lore = this.meta.hasLore() ? this.meta.lore() : new ArrayList<>();
if (lore == null) lore = new ArrayList<>();
lore.add(line.decoration(TextDecoration.ITALIC, false));
this.meta.lore(lore);
return this;
}
public ItemBuilder loreComponent(List<Component> lines) {
List<Component> processedLore = new ArrayList<>();
for (Component line : lines) {
processedLore.add(line.decoration(TextDecoration.ITALIC, false));
}
this.meta.lore(processedLore);
return this;
}
public ItemBuilder loreComponent(Component... lines) {
return loreComponent(Arrays.asList(lines));
}
public ItemBuilder loreMiniMessage(String line) {
Component component = miniMessage.deserialize(line)
.decoration(TextDecoration.ITALIC, false);
return loreComponent(component);
}
public ItemBuilder loreMiniMessage(List<String> lines) {
List<Component> components = new ArrayList<>();
for (String line : lines) {
components.add(miniMessage.deserialize(line)
.decoration(TextDecoration.ITALIC, false));
}
return loreComponent(components);
}
public ItemBuilder loreMiniMessage(String... lines) {
return loreMiniMessage(Arrays.asList(lines));
}
public ItemBuilder loreRaw(String line) {
Component component = Component.text(line)
.decoration(TextDecoration.ITALIC, false);
return loreComponent(component);
}
public ItemBuilder loreRaw(List<String> lines) {
List<Component> components = new ArrayList<>();
for (String line : lines) {
components.add(Component.text(line)
.decoration(TextDecoration.ITALIC, false));
}
return loreComponent(components);
}
public ItemBuilder loreRaw(String... lines) {
return loreRaw(Arrays.asList(lines));
}
public ItemBuilder clearLore() {
this.meta.lore(new ArrayList<>());
return this;
}
public ItemBuilder enchant(Enchantment enchantment, int level) {
this.meta.addEnchant(enchantment, level, true);
return this;
}
public ItemBuilder enchant(Enchantment enchantment) {
return enchant(enchantment, 1);
}
public ItemBuilder removeEnchant(Enchantment enchantment) {
this.meta.removeEnchant(enchantment);
return this;
}
public ItemBuilder clearEnchants() {
this.meta.getEnchants().keySet().forEach(this.meta::removeEnchant);
return this;
}
public ItemBuilder flags(ItemFlag... flags) {
this.meta.addItemFlags(flags);
return this;
}
public ItemBuilder removeFlags(ItemFlag... flags) {
this.meta.removeItemFlags(flags);
return this;
}
public ItemBuilder hideAllFlags() {
return flags(ItemFlag.values());
}
public ItemBuilder attribute(Attribute attribute, AttributeModifier modifier) {
this.meta.addAttributeModifier(attribute, modifier);
return this;
}
public ItemBuilder removeAttribute(Attribute attribute) {
this.meta.removeAttributeModifier(attribute);
return this;
}
public ItemBuilder unbreakable(boolean unbreakable) {
this.meta.setUnbreakable(unbreakable);
return this;
}
public ItemBuilder unbreakable() {
return unbreakable(true);
}
public ItemBuilder customModelData(CustomModelDataComponent data) {
this.modelData = data;
return this;
}
public ItemBuilder clearCustomModelData() {
this.modelData = null;
return this;
}
public ItemBuilder modifyStack(Function<ItemStack, ItemStack> modifier) {
this.stack = modifier.apply(this.build());
this.meta = this.stack.getItemMeta();
return this;
}
public ItemBuilder modifyMeta(Function<ItemMeta, ItemMeta> modifier) {
this.meta = modifier.apply(this.meta);
return this;
}
public <T extends ItemMeta> ItemBuilder modifyMeta(Class<T> metaClass, Function<T, T> modifier) {
if (metaClass.isInstance(this.meta)) {
this.meta = modifier.apply(metaClass.cast(this.meta));
}
return this;
}
public ItemStack build() {
this.meta.setCustomModelDataComponent(this.modelData);
this.stack.setItemMeta(this.meta);
return this.stack.clone();
}
public ItemStack buildAndGet() {
return build();
}
public ItemBuilder clone() {
return new ItemBuilder(this.build());
}
public Material getMaterial() {
return this.stack.getType();
}
public int getAmount() {
return this.stack.getAmount();
}
public static ItemBuilder create() {
return new ItemBuilder();
}
public static ItemBuilder create(ItemStack stack) {
return new ItemBuilder(stack);
}
public static ItemBuilder create(Material material) {
return new ItemBuilder(material);
}
public static ItemBuilder create(Material material, int amount) {
return new ItemBuilder(material, amount);
}
public static ItemBuilder of(Material material) {
return create(material);
}
public static ItemBuilder of(ItemStack stack) {
return create(stack);
}
}

285
src/main/java/me/trouper/alias/utils/SoundPlayer.java Executable file → Normal file
View File

@@ -1,266 +1,75 @@
package me.trouper.alias.utils; package me.trouper.alias.utils;
import me.trouper.alias.server.Main;
import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Sound; import org.bukkit.Sound;
import org.bukkit.SoundCategory;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
public class SoundPlayer implements Main { import java.util.Collection;
private Location location; public class SoundPlayer {
private Sound sound; private final Sound sound;
private float volume; private final float volume;
private float pitch; private final float pitch;
private final SoundCategory category;
/** public SoundPlayer(Sound sound) {
* Constructs a new sound, this aims to add more methods to this(sound, 1.0f, 1.0f);
* the Bukkit APIs Sound class, as they don't have many }
* methods to use.
* public SoundPlayer(Sound sound, float volume, float pitch) {
* @param location Location this(sound, volume, pitch, SoundCategory.MASTER);
* @param sound Sound }
* @param volume float
* @param pitch float public SoundPlayer(Sound sound, float volume, float pitch, SoundCategory category) {
*/
public SoundPlayer(Location location, Sound sound, float volume, float pitch) {
this.location = location;
this.sound = sound; this.sound = sound;
this.pitch = pitch;
this.volume = volume; this.volume = volume;
this.pitch = pitch;
this.category = category;
} }
public void playTo(Player player) {
/** player.playSound(player.getLocation(), sound, category, volume, pitch);
* Plays a sound to a player but at the store location
*
* @param player Player
*/
public void play(Player player) {
player.playSound(this.location,this.sound,this.volume,this.pitch);
} }
/** public void playTo(Collection<? extends Player> players) {
* Plays a sound to a player but at the player's location for (Player player : players) {
* playTo(player);
* @param player Player
*/
public void playIndividually(Player player) {
player.playSound(player.getLocation(),this.sound,this.volume,this.pitch);
}
/**
* Plays the sound to all players within a distance, but at the stored location.
*
* @param distance double
*/
public void playWithin(double distance) {
for (Player p : Bukkit.getOnlinePlayers()) {
if (p != null && p.getWorld() == this.location.getWorld() && p.getLocation().distance(this.location) < distance) {
p.playSound(this.location,this.sound,this.volume,this.pitch);
}
} }
} }
/** public void playAt(Location location, double radius) {
* Plays the sound to all players within a distance, but at the players' location. Collection<? extends Player> nearby = location.getWorld().getPlayers().stream()
* .filter(player -> player.getLocation().distanceSquared(location) <= radius * radius)
* @param distance double .toList();
*/
public void playWithinIndividually(double distance) { for (Player player : nearby) {
for (Player p : Bukkit.getOnlinePlayers()) { player.playSound(location, sound, category, volume, pitch);
if (p != null && p.getWorld() == this.location.getWorld() && p.getLocation().distance(this.location) < distance) {
p.playSound(p.getLocation(),this.sound,this.volume,this.pitch);
}
} }
} }
public static void play(Player player, Sound sound) {
/** player.playSound(player.getLocation(), sound, SoundCategory.MASTER, 1.0f, 1.0f);
* Plays the sound to all players on the server, but at the stored location.
*/
public void playAll() {
for (Player p : Bukkit.getOnlinePlayers()) p.playSound(this.location,this.sound,this.volume,this.pitch);
} }
/** public static void play(Player player, Sound sound, float volume, float pitch) {
* Plays the sound to all players on the server, but at the players' location. player.playSound(player.getLocation(), sound, SoundCategory.MASTER, volume, pitch);
*/
public void playAllIndividually() {
for (Player p : Bukkit.getOnlinePlayers()) p.playSound(p.getLocation(),this.sound,this.volume,this.pitch);
} }
/** public static void play(Location location, Sound sound, float volume, float pitch, double radius) {
* Repeats a sound to a player, but at the stored location. Collection<? extends Player> nearby = location.getWorld().getPlayers().stream()
* .filter(player -> player.getLocation().distanceSquared(location) <= radius * radius)
* @param player Player .toList();
* @param times int
* @param tickDelay int for (Player player : nearby) {
*/ player.playSound(location, sound, SoundCategory.MASTER, volume, pitch);
public void repeat(Player player, int times, int tickDelay) { }
new BukkitRunnable() {
int i = 0;
@Override
public void run() {
if (i < times) {
play(player);
i ++;
} else {
this.cancel();
}
}
}.runTaskTimer(getPlugin(),0,tickDelay);
} }
/** public static void play(Collection<? extends Player> players, Sound sound, float volume, float pitch) {
* Repeats a sound to a player, but at the player's location. for (Player player : players) {
* player.playSound(player.getLocation(), sound, SoundCategory.MASTER, volume, pitch);
* @param player Player }
* @param times int
* @param tickDelay int
*/
public void repeatIndividually(Player player, int times, int tickDelay) {
new BukkitRunnable() {
int i = 0;
@Override
public void run() {
if (i < times) {
playIndividually(player);
i ++;
} else {
this.cancel();
}
}
}.runTaskTimer(getPlugin(),0,tickDelay);
}
/**
* Repeats a sound to all players on the server, but at the stored location.
*
* @param times int
* @param tickDelay int
*/
public void repeatAll(int times, int tickDelay) {
new BukkitRunnable() {
int i = 0;
@Override
public void run() {
if (i < times) {
playAll();
i ++;
} else {
this.cancel();
}
}
}.runTaskTimer(getPlugin(),0,tickDelay);
}
/**
* Repeats a sound to all players on the server, but at the players' location.
*
* @param times int
* @param tickDelay int
*/
public void repeatAllIndividually(int times, int tickDelay) {
new BukkitRunnable() {
int i = 0;
@Override
public void run() {
if (i < times) {
playAllIndividually();
i ++;
} else {
this.cancel();
}
}
}.runTaskTimer(getPlugin(),0,tickDelay);
}
/**
* Repeats a sound to all players within a radius, but at the stored location.
*
* @param radius double
* @param times int
* @param tickDelay int
*/
public void repeatAll(double radius,int times, int tickDelay) {
new BukkitRunnable() {
int i = 0;
@Override
public void run() {
if (i < times) {
playWithin(radius);
i ++;
} else {
this.cancel();
}
}
}.runTaskTimer(getPlugin(),0,tickDelay);
}
/**
* Repeats a sound to all players within a radius, but at the players' location.
*
* @param distance double
* @param times int
* @param tickDelay int
*/
public void repeatAllIndividually(double distance, int times, int tickDelay) {
new BukkitRunnable() {
int i = 0;
@Override
public void run() {
if (i < times) {
playWithinIndividually(distance);
i ++;
} else {
this.cancel();
}
}
}.runTaskTimer(getPlugin(),0,tickDelay);
}
public Sound getSound() {
return sound;
}
public float getPitch() {
return pitch;
}
public float getVolume() {
return volume;
}
public Location getLocation() {
return location;
}
public void setPitch(float pitch) {
this.pitch = pitch;
}
public void setVolume(float volume) {
this.volume = volume;
}
public void setSound(Sound sound) {
this.sound = sound;
}
public void setLocation(Location location) {
this.location = location;
}
public void changePlayer(Location location, Sound sound, float volume, float pitch) {
this.location = location;
this.sound = sound;
this.volume = volume;
this.pitch = pitch;
}
public void changePlayer(Sound sound, float volume, float pitch) {
changePlayer(location, sound, volume, pitch);
} }
} }

View File

@@ -4,6 +4,7 @@ import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.LivingEntity; import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import java.util.ArrayList; import java.util.ArrayList;
@@ -225,8 +226,8 @@ public class TargetingUtils {
* @return An {@link Optional} containing the {@link LivingEntity} closest to the aim vector, * @return An {@link Optional} containing the {@link LivingEntity} closest to the aim vector,
* or an empty Optional if no suitable entity is found or world is null. * or an empty Optional if no suitable entity is found or world is null.
*/ */
public static Optional<LivingEntity> getLivingEntityClosestToVector(Location originEyeLocation, Vector direction, double maxDistance, double maxAngleRadians) { public static Optional<LivingEntity> livingClosestAngle(Location originEyeLocation, Vector direction, double maxDistance, double maxAngleRadians) {
return getLivingEntityClosestToVector(originEyeLocation, direction, maxDistance, maxAngleRadians, entity -> true); return livingClosestAngle(originEyeLocation, direction, maxDistance, maxAngleRadians, entity -> true);
} }
/** /**
@@ -242,7 +243,7 @@ public class TargetingUtils {
* @return An {@link Optional} containing the {@link LivingEntity} closest to the aim vector and matching the filter, * @return An {@link Optional} containing the {@link LivingEntity} closest to the aim vector and matching the filter,
* or an empty Optional if no suitable entity is found or world is null. * or an empty Optional if no suitable entity is found or world is null.
*/ */
public static Optional<LivingEntity> getLivingEntityClosestToVector(Location originEyeLocation, Vector direction, double maxDistance, double maxAngleRadians, Predicate<LivingEntity> filter) { public static Optional<LivingEntity> livingClosestAngle(Location originEyeLocation, Vector direction, double maxDistance, double maxAngleRadians, Predicate<LivingEntity> filter) {
if (originEyeLocation == null || originEyeLocation.getWorld() == null || direction == null || filter == null) { if (originEyeLocation == null || originEyeLocation.getWorld() == null || direction == null || filter == null) {
return Optional.empty(); return Optional.empty();
} }
@@ -279,4 +280,15 @@ public class TargetingUtils {
} }
return Optional.ofNullable(bestTarget); return Optional.ofNullable(bestTarget);
} }
/**
* Returns all living entities inside the bounding box.
* @param w World to check.
* @param box Box inside the world, entities must be colliding with it.
* @param filter A predicate applied to each living entity. Return true to accept.
* @return A list of living entities colliding with the box.
*/
public static List<LivingEntity> livingInBound(World w, BoundingBox box, Predicate<LivingEntity> filter) {
return w.getNearbyEntities(box).stream().filter(entity -> entity instanceof LivingEntity liv && filter.test(liv)).map(entity -> (LivingEntity) entity).toList();
}
} }

78
src/main/java/me/trouper/alias/utils/misc/Cooldown.java Executable file → Normal file
View File

@@ -2,41 +2,81 @@ package me.trouper.alias.utils.misc;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit;
public class Cooldown<T> { public class Cooldown<T> {
private final Map<T, Long> timer; private final Map<T, Long> cooldowns = new HashMap<>();
public Cooldown() { public void setCooldown(T holder, long duration) {
this.timer = new HashMap<>(); cooldowns.put(holder, System.currentTimeMillis() + duration);
} }
private <O> O getOrDefault(O value, O def) { public boolean isOnCooldown(T holder) {
return value != null ? value : def; return getRemaining(holder) > 0;
} }
public long getCooldown(T obj) { public long getRemaining(T holder) {
return Math.max(getOrDefault(timer.get(obj), 0L) - System.currentTimeMillis(), 0L); return Math.max(0, cooldowns.getOrDefault(holder, 0L) - System.currentTimeMillis());
} }
public double getCooldownSec(T obj) { public void clearCooldown(T holder) {
final long cooldown = this.getCooldown(obj); cooldowns.remove(holder);
return Math.floor(cooldown / 10.0) / 100.0;
} }
public boolean isOnCooldown(T obj) { public String formatShort(T holder) {
return getCooldown(obj) > 0L; return formatShort(getRemaining(holder));
} }
public void setCooldown(T obj, long millis) { public String formatLong(T holder) {
timer.put(obj, System.currentTimeMillis() + millis); return formatLong(getRemaining(holder));
} }
public void addCooldown(T obj, long millis) { public String formatColon(T holder) {
setCooldown(obj, getCooldown(obj) + millis); return formatColon(getRemaining(holder));
} }
public void removeCooldown(T obj) { public static String formatShort(long ms) {
timer.remove(obj); long seconds = TimeUnit.MILLISECONDS.toSeconds(ms);
long minutes = seconds / 60;
seconds %= 60;
long hours = minutes / 60;
minutes %= 60;
StringBuilder sb = new StringBuilder();
if (hours > 0) sb.append(hours).append("h ");
if (minutes > 0 || hours > 0) sb.append(minutes).append("m ");
sb.append(seconds).append("s");
return sb.toString().trim();
}
public static String formatLong(long ms) {
long seconds = TimeUnit.MILLISECONDS.toSeconds(ms);
long minutes = seconds / 60;
seconds %= 60;
long hours = minutes / 60;
minutes %= 60;
StringBuilder sb = new StringBuilder();
if (hours > 0) sb.append(hours).append(" hour").append(hours == 1 ? "" : "s").append(", ");
if (minutes > 0) sb.append(minutes).append(" minute").append(minutes == 1 ? "" : "s").append(", ");
sb.append(seconds).append(" second").append(seconds == 1 ? "" : "s");
return sb.toString();
}
public static String formatColon(long ms) {
long totalSeconds = TimeUnit.MILLISECONDS.toSeconds(ms);
long hours = totalSeconds / 3600;
long minutes = (totalSeconds % 3600) / 60;
long seconds = totalSeconds % 60;
if (hours > 0)
return String.format("%02d:%02d:%02d", hours, minutes, seconds);
else
return String.format("%02d:%02d", minutes, seconds);
} }
} }

View File

@@ -0,0 +1,36 @@
package me.trouper.alias.utils.misc;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class ReflectionUtils {
public static Set<Class<?>> getClassesInPackage(JavaPlugin plugin, String pkg) {
Set<Class<?>> classes = new HashSet<>();
String path = pkg.replace('.', '/');
try {
File file = new File(plugin.getClass().getProtectionDomain().getCodeSource().getLocation().toURI());
try (JarFile jar = new JarFile(file)) {
for (JarEntry entry : jar.stream().toList()) {
String name = entry.getName();
if (!name.endsWith(".class") || !name.startsWith(path)) continue;
String className = name.replace('/', '.').replace(".class", "");
Class<?> clazz = Class.forName(className);
classes.add(clazz);
}
}
} catch (Exception e) {
plugin.getLogger().severe("Failed to scan package: " + pkg);
e.printStackTrace();
}
return classes;
}
}

View File

@@ -0,0 +1,89 @@
package me.trouper.alias.utils.misc;
public class Timer {
public static final long MILLIS_IN_SECOND = 1000L;
public static final long MILLIS_IN_MINUTE = MILLIS_IN_SECOND * 60L;
public static final long MILLIS_IN_HOUR = MILLIS_IN_MINUTE * 60L;
public static final long MILLIS_IN_DAY = MILLIS_IN_HOUR * 24L;
private long start;
private Timer() {
this.start = System.currentTimeMillis();
}
public static Timer start() {
return new Timer();
}
public static End zero() {
return new End(0);
}
public End end() {
return new End(start);
}
public static class End {
private final long start;
private final long end;
private End(long start) {
this.end = System.currentTimeMillis();
this.start = start;
}
public long timePassed() {
return end - start;
}
public String getStamp(boolean day, boolean hr, boolean min, boolean sec, boolean ms) {
long time = timePassed();
String stamp = "";
if (day) {
long l = (long)Math.floor((double)time / (double)MILLIS_IN_DAY);
time -= l * MILLIS_IN_DAY;
if (l > 0L) stamp += l + "d";
}
if (hr) {
long l = (long)Math.floor((double)time / (double)MILLIS_IN_HOUR);
time -= l * MILLIS_IN_HOUR;
if (l > 0L) stamp += " " + l + "hr";
}
if (min) {
long l = (long)Math.floor((double)time / (double)MILLIS_IN_MINUTE);
time -= l * MILLIS_IN_MINUTE;
if (l > 0L) stamp += " " + l + "min";
}
if (sec) {
long l = (long)Math.floor((double)time / (double)MILLIS_IN_SECOND);
time -= l * MILLIS_IN_SECOND;
if (l > 0L) stamp += " " + l + "sec";
}
if (ms) {
if (time > 0L) stamp += " " + time + "ms";
}
return stamp.trim();
}
public String getStampStandard() {
return getStamp(false, true, true, false, false);
}
public String getStampLogger() {
return getStamp(false, true, true, true, false);
}
public String getStampPrecise() {
return getStamp(false, false, true, true, true);
}
public String getStampFull() {
return getStamp(true, true, true, true, true);
}
}
}