This commit is contained in:
Oliver
2025-03-03 21:57:54 +01:00
parent 1c8a53e778
commit ffc4c52c9a
197 changed files with 11722 additions and 13 deletions

View File

@@ -1,3 +1,12 @@
plugins {
id("com.gradleup.shadow") version "9.0.0-beta9" apply false
id("io.papermc.paperweight.userdev") version "2.0.0-beta.14" apply false
id("xyz.jpenilla.run-paper") version "2.3.1" apply false
id("net.minecrell.plugin-yml.paper") version "0.6.0" apply false
id("io.papermc.hangar-publish-plugin") version "0.1.2" apply false
id("com.modrinth.minotaur") version "2.+" apply false
}
allprojects {
group = "de.oliver"
description = "Minecraft plugins of FancyInnovations"

3
gradle.properties Normal file
View File

@@ -0,0 +1,3 @@
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configuration-cache=true

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

122
libraries/common/.gitignore vendored Normal file
View File

@@ -0,0 +1,122 @@
# User-specific stuff
.idea/
*.iml
*.ipr
*.iws
# IntelliJ
out/
# Eclipse
.classpath
.project
.settings/
plugin/bin/
api/bin/
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Package Files #
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
.flattened-pom.xml
# Common working directory
run/
build
.gradle
/*.jar

View File

@@ -0,0 +1,13 @@
# How to use
```kotlin
repositories {
maven("https://repo.fancyplugins.de/releases/")
// or
maven("https://repo.fancyplugins.de/snapshots/")
}
dependencies {
implementation("de.oliver:FancyLib:<version>")
}
```

View File

@@ -0,0 +1,91 @@
plugins {
id("java")
id("maven-publish")
id("com.github.johnrengelman.shadow")
}
group = "de.oliver"
version = "35"
description = "Library for all Fancy plugins"
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(17))
}
repositories {
mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://repo.fancyplugins.de/releases")
}
dependencies {
compileOnly("dev.folia:folia-api:1.20.4-R0.1-SNAPSHOT")
compileOnly("de.oliver.FancyAnalytics:logger:0.0.5")
// database drivers
compileOnly("org.xerial:sqlite-jdbc:3.46.0.0")
compileOnly("mysql:mysql-connector-java:8.0.33")
// testing
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.11.1")
testImplementation("com.google.code.gson:gson:2.11.0")
}
tasks {
publishing {
repositories {
maven {
name = "fancypluginsReleases"
url = uri("https://repo.fancyplugins.de/releases")
credentials(PasswordCredentials::class)
authentication {
isAllowInsecureProtocol = true
create<BasicAuthentication>("basic")
}
}
maven {
name = "fancypluginsSnapshots"
url = uri("https://repo.fancyplugins.de/snapshots")
credentials(PasswordCredentials::class)
authentication {
isAllowInsecureProtocol = true
create<BasicAuthentication>("basic")
}
}
}
publications {
create<MavenPublication>("maven") {
groupId = project.group.toString()
artifactId = project.name
version = project.version.toString()
from(project.components["java"])
}
}
}
compileJava {
options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything
// Set the release flag. This configures what version bytecode the compiler will emit, as well as what JDK APIs are usable.
// See https://openjdk.java.net/jeps/247 for more information.
options.release.set(17)
}
java {
withSourcesJar()
withJavadocJar()
}
javadoc {
options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything
}
processResources {
filteringCharset = Charsets.UTF_8.name() // We want UTF-8 for everything
}
test {
useJUnitPlatform()
}
}

View File

@@ -0,0 +1,16 @@
package de.oliver.fancylib;
import org.bukkit.configuration.file.FileConfiguration;
public class ConfigHelper {
public static Object getOrDefault(FileConfiguration config, String path, Object defaultVal) {
if (!config.contains(path)) {
config.set(path, defaultVal);
return defaultVal;
}
return config.get(path);
}
}

View File

@@ -0,0 +1,59 @@
package de.oliver.fancylib;
import de.oliver.fancyanalytics.logger.ExtendedFancyLogger;
import de.oliver.fancylib.gui.inventoryClick.InventoryClickListener;
import de.oliver.fancylib.gui.inventoryClick.impl.CancelInventoryItemClick;
import de.oliver.fancylib.gui.inventoryClick.impl.ChangePageInventoryItemClick;
import de.oliver.fancylib.itemClick.PlayerInteractListener;
import de.oliver.fancylib.serverSoftware.ServerSoftware;
import de.oliver.fancylib.serverSoftware.schedulers.BukkitScheduler;
import de.oliver.fancylib.serverSoftware.schedulers.FancyScheduler;
import de.oliver.fancylib.serverSoftware.schedulers.FoliaScheduler;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.ApiStatus;
public class FancyLib {
private static FancyLib instance;
private static ExtendedFancyLogger logger = new ExtendedFancyLogger("FancyLib");
private final JavaPlugin plugin;
private final FancyScheduler scheduler;
public FancyLib(JavaPlugin plugin) {
instance = this;
this.plugin = plugin;
this.scheduler = ServerSoftware.isFolia()
? new FoliaScheduler(plugin)
: new BukkitScheduler(plugin);
}
public static FancyLib getInstance() {
return instance;
}
public static ExtendedFancyLogger getLogger() {
return logger;
}
/**
* Registers the listeners for the inventory click and player interact events.
*/
public void registerListeners() {
CancelInventoryItemClick.INSTANCE.register();
ChangePageInventoryItemClick.INSTANCE.register();
Bukkit.getPluginManager().registerEvents(new InventoryClickListener(), plugin);
Bukkit.getPluginManager().registerEvents(new PlayerInteractListener(), plugin);
}
@ApiStatus.Internal
public FancyScheduler getScheduler() {
return scheduler;
}
public JavaPlugin getPlugin() {
return plugin;
}
}

View File

@@ -0,0 +1,111 @@
package de.oliver.fancylib;
import org.bukkit.plugin.Plugin;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class FileUtils {
public static String getSHA256Checksum(File file) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
try (FileInputStream fis = new FileInputStream(file)) {
byte[] byteArray = new byte[1024];
int bytesCount;
// Read the file data and update the MessageDigest
while ((bytesCount = fis.read(byteArray)) != -1) {
digest.update(byteArray, 0, bytesCount);
}
}
// Get the final hash bytes
byte[] hashBytes = digest.digest();
// Convert hash bytes to hex format
StringBuilder hexString = new StringBuilder();
for (byte hashByte : hashBytes) {
String hex = Integer.toHexString(0xff & hashByte);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
} catch (IOException | NoSuchAlgorithmException e) {
return "N/A";
}
}
public static File findFirstFileByName(File directory, String name) {
File[] files = directory.listFiles();
if (files == null) {
return null;
}
for (File file : files) {
if (file.getName().toLowerCase().contains(name.toLowerCase())) {
return file;
}
}
return null;
}
public String readResource(String name) {
URL url = getClass().getClassLoader().getResource(name);
if (url == null) {
return null;
}
URLConnection connection = null;
try {
connection = url.openConnection();
} catch (IOException e) {
e.printStackTrace();
}
connection.setUseCaches(false);
try (InputStream inputStream = connection.getInputStream()) {
byte[] file_raw = new byte[inputStream.available()];
inputStream.read(file_raw);
inputStream.close();
return new String(file_raw, StandardCharsets.UTF_8);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public void saveFile(Plugin plugin, String name) {
URL url = getClass().getClassLoader().getResource(name);
if (url == null) {
return;
}
File file = new File(plugin.getDataFolder() + File.separator + name);
if (file.exists()) return;
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
URLConnection connection = null;
try {
connection = url.openConnection();
} catch (IOException e) {
e.printStackTrace();
}
connection.setUseCaches(false);
try (FileOutputStream outputStream = new FileOutputStream(file)) {
int read;
InputStream inputStream = connection.getInputStream();
byte[] bytes = new byte[1024];
while ((read = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, read);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,129 @@
package de.oliver.fancylib;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@Deprecated
public class MessageHelper {
private static String primaryColor = "#59bdff";
private static String secondaryColor = "#6696e3";
private static String successColor = "#81e366";
private static String warningColor = "#e3ca66";
private static String errorColor = "#e36666";
@Deprecated
public static @NotNull String transform(@NotNull String message) {
message = message.replace("<primaryColor>", "<color:" + primaryColor + ">")
.replace("<secondaryColor>", "<color:" + secondaryColor + ">")
.replace("<successColor>", "<color:" + successColor + ">")
.replace("<warningColor>", "<color:" + warningColor + ">")
.replace("<errorColor>", "<color:" + errorColor + ">");
return message;
}
@Deprecated
public static @NotNull Component send(@Nullable CommandSender receiver, @NotNull String message) {
Component msg = MiniMessage.miniMessage().deserialize(transform(message));
if (receiver != null) {
receiver.sendMessage(msg);
}
return msg;
}
@Deprecated
public static @NotNull Component info(@Nullable CommandSender receiver, @NotNull String message) {
Component msg = MiniMessage.miniMessage().deserialize(transform("<primaryColor>" + message));
if (receiver != null) {
receiver.sendMessage(msg);
}
return msg;
}
@Deprecated
public static @NotNull Component success(@Nullable CommandSender receiver, @NotNull String message) {
Component msg = MiniMessage.miniMessage().deserialize(transform("<successColor>" + message));
if (receiver != null) {
receiver.sendMessage(msg);
}
return msg;
}
@Deprecated
public static @NotNull Component warning(@Nullable CommandSender receiver, @NotNull String message) {
Component msg = MiniMessage.miniMessage().deserialize(transform("<warningColor>" + message));
if (receiver != null) {
receiver.sendMessage(msg);
}
return msg;
}
@Deprecated
public static @NotNull Component error(@Nullable CommandSender receiver, @NotNull String message) {
Component msg = MiniMessage.miniMessage().deserialize(transform("<errorColor>" + message));
if (receiver != null) {
receiver.sendMessage(msg);
}
return msg;
}
@Deprecated
public static String getPrimaryColor() {
return primaryColor;
}
@Deprecated
public static void setPrimaryColor(String primaryColor) {
MessageHelper.primaryColor = primaryColor;
}
@Deprecated
public static String getSecondaryColor() {
return secondaryColor;
}
@Deprecated
public static void setSecondaryColor(String secondaryColor) {
MessageHelper.secondaryColor = secondaryColor;
}
@Deprecated
public static String getSuccessColor() {
return successColor;
}
@Deprecated
public static void setSuccessColor(String successColor) {
MessageHelper.successColor = successColor;
}
@Deprecated
public static String getWarningColor() {
return warningColor;
}
@Deprecated
public static void setWarningColor(String warningColor) {
MessageHelper.warningColor = warningColor;
}
@Deprecated
public static String getErrorColor() {
return errorColor;
}
@Deprecated
public static void setErrorColor(String errorColor) {
MessageHelper.errorColor = errorColor;
}
@Deprecated
public static Component removeDecoration(Component component, TextDecoration decoration) {
return component.decoration(decoration, TextDecoration.State.FALSE);
}
}

View File

@@ -0,0 +1,881 @@
/*
* This Metrics class was auto-generated and can be copied into your project if you are
* not using a build tool like Gradle or Maven for dependency management.
*
* IMPORTANT: You are not allowed to modify this class, except changing the package.
*
* Disallowed modifications include but are not limited to:
* - Remove the option for users to opt-out
* - Change the frequency for data submission
* - Obfuscate the code (every obfuscator should allow you to make an exception for specific files)
* - Reformat the code (if you use a linter, add an exception)
*
* Violations will result in a ban of your plugin and account from bStats.
*/
package de.oliver.fancylib;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.zip.GZIPOutputStream;
import javax.net.ssl.HttpsURLConnection;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
public class Metrics {
private final Plugin plugin;
private final MetricsBase metricsBase;
/**
* Creates a new Metrics instance.
*
* @param plugin Your plugin instance.
* @param serviceId The id of the service. It can be found at <a
* href="https://bstats.org/what-is-my-plugin-id">What is my plugin id?</a>
*/
public Metrics(JavaPlugin plugin, int serviceId) {
this.plugin = plugin;
// Get the config file
File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
File configFile = new File(bStatsFolder, "config.yml");
YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
if (!config.isSet("serverUuid")) {
config.addDefault("enabled", true);
config.addDefault("serverUuid", UUID.randomUUID().toString());
config.addDefault("logFailedRequests", false);
config.addDefault("logSentData", false);
config.addDefault("logResponseStatusText", false);
// Inform the server owners about bStats
config
.options()
.header(
"bStats (https://bStats.org) collects some basic information for plugin authors, like how\n"
+ "many people use their plugin and their total player count. It's recommended to keep bStats\n"
+ "enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n"
+ "performance penalty associated with having metrics enabled, and data sent to bStats is fully\n"
+ "anonymous.")
.copyDefaults(true);
try {
config.save(configFile);
} catch (IOException ignored) {
}
}
// Load the data
boolean enabled = config.getBoolean("enabled", true);
String serverUUID = config.getString("serverUuid");
boolean logErrors = config.getBoolean("logFailedRequests", false);
boolean logSentData = config.getBoolean("logSentData", false);
boolean logResponseStatusText = config.getBoolean("logResponseStatusText", false);
metricsBase =
new MetricsBase(
"bukkit",
serverUUID,
serviceId,
enabled,
this::appendPlatformData,
this::appendServiceData,
submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask),
plugin::isEnabled,
(message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error),
(message) -> this.plugin.getLogger().log(Level.INFO, message),
logErrors,
logSentData,
logResponseStatusText);
}
/** Shuts down the underlying scheduler service. */
public void shutdown() {
metricsBase.shutdown();
}
/**
* Adds a custom chart.
*
* @param chart The chart to add.
*/
public void addCustomChart(CustomChart chart) {
metricsBase.addCustomChart(chart);
}
private void appendPlatformData(JsonObjectBuilder builder) {
builder.appendField("playerAmount", getPlayerAmount());
builder.appendField("onlineMode", Bukkit.getOnlineMode() ? 1 : 0);
builder.appendField("bukkitVersion", Bukkit.getVersion());
builder.appendField("bukkitName", Bukkit.getName());
builder.appendField("javaVersion", System.getProperty("java.version"));
builder.appendField("osName", System.getProperty("os.name"));
builder.appendField("osArch", System.getProperty("os.arch"));
builder.appendField("osVersion", System.getProperty("os.version"));
builder.appendField("coreCount", Runtime.getRuntime().availableProcessors());
}
private void appendServiceData(JsonObjectBuilder builder) {
builder.appendField("pluginVersion", plugin.getDescription().getVersion());
}
private int getPlayerAmount() {
try {
// Around MC 1.8 the return type was changed from an array to a collection,
// This fixes java.lang.NoSuchMethodError:
// org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection;
Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers");
return onlinePlayersMethod.getReturnType().equals(Collection.class)
? ((Collection<?>) onlinePlayersMethod.invoke(Bukkit.getServer())).size()
: ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length;
} catch (Exception e) {
// Just use the new method if the reflection failed
return Bukkit.getOnlinePlayers().size();
}
}
public static class MetricsBase {
/** The version of the Metrics class. */
public static final String METRICS_VERSION = "3.0.2";
private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s";
private final ScheduledExecutorService scheduler;
private final String platform;
private final String serverUuid;
private final int serviceId;
private final Consumer<JsonObjectBuilder> appendPlatformDataConsumer;
private final Consumer<JsonObjectBuilder> appendServiceDataConsumer;
private final Consumer<Runnable> submitTaskConsumer;
private final Supplier<Boolean> checkServiceEnabledSupplier;
private final BiConsumer<String, Throwable> errorLogger;
private final Consumer<String> infoLogger;
private final boolean logErrors;
private final boolean logSentData;
private final boolean logResponseStatusText;
private final Set<CustomChart> customCharts = new HashSet<>();
private final boolean enabled;
/**
* Creates a new MetricsBase class instance.
*
* @param platform The platform of the service.
* @param serviceId The id of the service.
* @param serverUuid The server uuid.
* @param enabled Whether or not data sending is enabled.
* @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and
* appends all platform-specific data.
* @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and
* appends all service-specific data.
* @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be
* used to delegate the data collection to a another thread to prevent errors caused by
* concurrency. Can be {@code null}.
* @param checkServiceEnabledSupplier A supplier to check if the service is still enabled.
* @param errorLogger A consumer that accepts log message and an error.
* @param infoLogger A consumer that accepts info log messages.
* @param logErrors Whether or not errors should be logged.
* @param logSentData Whether or not the sent data should be logged.
* @param logResponseStatusText Whether or not the response status text should be logged.
*/
public MetricsBase(
String platform,
String serverUuid,
int serviceId,
boolean enabled,
Consumer<JsonObjectBuilder> appendPlatformDataConsumer,
Consumer<JsonObjectBuilder> appendServiceDataConsumer,
Consumer<Runnable> submitTaskConsumer,
Supplier<Boolean> checkServiceEnabledSupplier,
BiConsumer<String, Throwable> errorLogger,
Consumer<String> infoLogger,
boolean logErrors,
boolean logSentData,
boolean logResponseStatusText) {
ScheduledThreadPoolExecutor scheduler =
new ScheduledThreadPoolExecutor(1, task -> new Thread(task, "bStats-Metrics"));
// We want delayed tasks (non-periodic) that will execute in the future to be
// cancelled when the scheduler is shutdown.
// Otherwise, we risk preventing the server from shutting down even when
// MetricsBase#shutdown() is called
scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
this.scheduler = scheduler;
this.platform = platform;
this.serverUuid = serverUuid;
this.serviceId = serviceId;
this.enabled = enabled;
this.appendPlatformDataConsumer = appendPlatformDataConsumer;
this.appendServiceDataConsumer = appendServiceDataConsumer;
this.submitTaskConsumer = submitTaskConsumer;
this.checkServiceEnabledSupplier = checkServiceEnabledSupplier;
this.errorLogger = errorLogger;
this.infoLogger = infoLogger;
this.logErrors = logErrors;
this.logSentData = logSentData;
this.logResponseStatusText = logResponseStatusText;
checkRelocation();
if (enabled) {
// WARNING: Removing the option to opt-out will get your plugin banned from
// bStats
startSubmitting();
}
}
public void addCustomChart(CustomChart chart) {
this.customCharts.add(chart);
}
public void shutdown() {
scheduler.shutdown();
}
private void startSubmitting() {
final Runnable submitTask =
() -> {
if (!enabled || !checkServiceEnabledSupplier.get()) {
// Submitting data or service is disabled
scheduler.shutdown();
return;
}
if (submitTaskConsumer != null) {
submitTaskConsumer.accept(this::submitData);
} else {
this.submitData();
}
};
// Many servers tend to restart at a fixed time at xx:00 which causes an uneven
// distribution of requests on the
// bStats backend. To circumvent this problem, we introduce some randomness into
// the initial and second delay.
// WARNING: You must not modify and part of this Metrics class, including the
// submit delay or frequency!
// WARNING: Modifying this code will get your plugin banned on bStats. Just
// don't do it!
long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3));
long secondDelay = (long) (1000 * 60 * (Math.random() * 30));
scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS);
scheduler.scheduleAtFixedRate(
submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS);
}
private void submitData() {
final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder();
appendPlatformDataConsumer.accept(baseJsonBuilder);
final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder();
appendServiceDataConsumer.accept(serviceJsonBuilder);
JsonObjectBuilder.JsonObject[] chartData =
customCharts.stream()
.map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors))
.filter(Objects::nonNull)
.toArray(JsonObjectBuilder.JsonObject[]::new);
serviceJsonBuilder.appendField("id", serviceId);
serviceJsonBuilder.appendField("customCharts", chartData);
baseJsonBuilder.appendField("service", serviceJsonBuilder.build());
baseJsonBuilder.appendField("serverUUID", serverUuid);
baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION);
JsonObjectBuilder.JsonObject data = baseJsonBuilder.build();
scheduler.execute(
() -> {
try {
// Send the data
sendData(data);
} catch (Exception e) {
// Something went wrong! :(
if (logErrors) {
errorLogger.accept("Could not submit bStats metrics data", e);
}
}
});
}
private void sendData(JsonObjectBuilder.JsonObject data) throws Exception {
if (logSentData) {
infoLogger.accept("Sent bStats metrics data: " + data.toString());
}
String url = String.format(REPORT_URL, platform);
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
// Compress the data to save bandwidth
byte[] compressedData = compress(data.toString());
connection.setRequestMethod("POST");
connection.addRequestProperty("Accept", "application/json");
connection.addRequestProperty("Connection", "close");
connection.addRequestProperty("Content-Encoding", "gzip");
connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length));
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("User-Agent", "Metrics-Service/1");
connection.setDoOutput(true);
try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) {
outputStream.write(compressedData);
}
StringBuilder builder = new StringBuilder();
try (BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
builder.append(line);
}
}
if (logResponseStatusText) {
infoLogger.accept("Sent data to bStats and received response: " + builder);
}
}
/** Checks that the class was properly relocated. */
private void checkRelocation() {
// You can use the property to disable the check in your test environment
if (System.getProperty("bstats.relocatecheck") == null
|| !System.getProperty("bstats.relocatecheck").equals("false")) {
// Maven's Relocate is clever and changes strings, too. So we have to use this
// little "trick" ... :D
final String defaultPackage =
new String(new byte[] {'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'});
final String examplePackage =
new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'});
// We want to make sure no one just copy & pastes the example and uses the wrong
// package names
if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage)
|| MetricsBase.class.getPackage().getName().startsWith(examplePackage)) {
throw new IllegalStateException("bStats Metrics class has not been relocated correctly!");
}
}
}
/**
* Gzips the given string.
*
* @param str The string to gzip.
* @return The gzipped string.
*/
private static byte[] compress(final String str) throws IOException {
if (str == null) {
return null;
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) {
gzip.write(str.getBytes(StandardCharsets.UTF_8));
}
return outputStream.toByteArray();
}
}
public static class SimplePie extends CustomChart {
private final Callable<String> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimplePie(String chartId, Callable<String> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
String value = callable.call();
if (value == null || value.isEmpty()) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("value", value).build();
}
}
public static class MultiLineChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public MultiLineChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
// Skip this invalid
continue;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class AdvancedPie extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedPie(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
// Skip this invalid
continue;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class SimpleBarChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimpleBarChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
for (Map.Entry<String, Integer> entry : map.entrySet()) {
valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()});
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class AdvancedBarChart extends CustomChart {
private final Callable<Map<String, int[]>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedBarChart(String chartId, Callable<Map<String, int[]>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, int[]> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, int[]> entry : map.entrySet()) {
if (entry.getValue().length == 0) {
// Skip this invalid
continue;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class DrilldownPie extends CustomChart {
private final Callable<Map<String, Map<String, Integer>>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public DrilldownPie(String chartId, Callable<Map<String, Map<String, Integer>>> callable) {
super(chartId);
this.callable = callable;
}
@Override
public JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Map<String, Integer>> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean reallyAllSkipped = true;
for (Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) {
JsonObjectBuilder valueBuilder = new JsonObjectBuilder();
boolean allSkipped = true;
for (Map.Entry<String, Integer> valueEntry : map.get(entryValues.getKey()).entrySet()) {
valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue());
allSkipped = false;
}
if (!allSkipped) {
reallyAllSkipped = false;
valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build());
}
}
if (reallyAllSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public abstract static class CustomChart {
private final String chartId;
protected CustomChart(String chartId) {
if (chartId == null) {
throw new IllegalArgumentException("chartId must not be null");
}
this.chartId = chartId;
}
public JsonObjectBuilder.JsonObject getRequestJsonObject(
BiConsumer<String, Throwable> errorLogger, boolean logErrors) {
JsonObjectBuilder builder = new JsonObjectBuilder();
builder.appendField("chartId", chartId);
try {
JsonObjectBuilder.JsonObject data = getChartData();
if (data == null) {
// If the data is null we don't send the chart.
return null;
}
builder.appendField("data", data);
} catch (Throwable t) {
if (logErrors) {
errorLogger.accept("Failed to get data for custom chart with id " + chartId, t);
}
return null;
}
return builder.build();
}
protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception;
}
public static class SingleLineChart extends CustomChart {
private final Callable<Integer> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SingleLineChart(String chartId, Callable<Integer> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
int value = callable.call();
if (value == 0) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("value", value).build();
}
}
/**
* An extremely simple JSON builder.
*
* <p>While this class is neither feature-rich nor the most performant one, it's sufficient enough
* for its use-case.
*/
public static class JsonObjectBuilder {
private StringBuilder builder = new StringBuilder();
private boolean hasAtLeastOneField = false;
public JsonObjectBuilder() {
builder.append("{");
}
/**
* Appends a null field to the JSON.
*
* @param key The key of the field.
* @return A reference to this object.
*/
public JsonObjectBuilder appendNull(String key) {
appendFieldUnescaped(key, "null");
return this;
}
/**
* Appends a string field to the JSON.
*
* @param key The key of the field.
* @param value The value of the field.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, String value) {
if (value == null) {
throw new IllegalArgumentException("JSON value must not be null");
}
appendFieldUnescaped(key, "\"" + escape(value) + "\"");
return this;
}
/**
* Appends an integer field to the JSON.
*
* @param key The key of the field.
* @param value The value of the field.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, int value) {
appendFieldUnescaped(key, String.valueOf(value));
return this;
}
/**
* Appends an object to the JSON.
*
* @param key The key of the field.
* @param object The object.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, JsonObject object) {
if (object == null) {
throw new IllegalArgumentException("JSON object must not be null");
}
appendFieldUnescaped(key, object.toString());
return this;
}
/**
* Appends a string array to the JSON.
*
* @param key The key of the field.
* @param values The string array.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, String[] values) {
if (values == null) {
throw new IllegalArgumentException("JSON values must not be null");
}
String escapedValues =
Arrays.stream(values)
.map(value -> "\"" + escape(value) + "\"")
.collect(Collectors.joining(","));
appendFieldUnescaped(key, "[" + escapedValues + "]");
return this;
}
/**
* Appends an integer array to the JSON.
*
* @param key The key of the field.
* @param values The integer array.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, int[] values) {
if (values == null) {
throw new IllegalArgumentException("JSON values must not be null");
}
String escapedValues =
Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(","));
appendFieldUnescaped(key, "[" + escapedValues + "]");
return this;
}
/**
* Appends an object array to the JSON.
*
* @param key The key of the field.
* @param values The integer array.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, JsonObject[] values) {
if (values == null) {
throw new IllegalArgumentException("JSON values must not be null");
}
String escapedValues =
Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(","));
appendFieldUnescaped(key, "[" + escapedValues + "]");
return this;
}
/**
* Appends a field to the object.
*
* @param key The key of the field.
* @param escapedValue The escaped value of the field.
*/
private void appendFieldUnescaped(String key, String escapedValue) {
if (builder == null) {
throw new IllegalStateException("JSON has already been built");
}
if (key == null) {
throw new IllegalArgumentException("JSON key must not be null");
}
if (hasAtLeastOneField) {
builder.append(",");
}
builder.append("\"").append(escape(key)).append("\":").append(escapedValue);
hasAtLeastOneField = true;
}
/**
* Builds the JSON string and invalidates this builder.
*
* @return The built JSON string.
*/
public JsonObject build() {
if (builder == null) {
throw new IllegalStateException("JSON has already been built");
}
JsonObject object = new JsonObject(builder.append("}").toString());
builder = null;
return object;
}
/**
* Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt.
*
* <p>This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'.
* Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n").
*
* @param value The value to escape.
* @return The escaped value.
*/
private static String escape(String value) {
final StringBuilder builder = new StringBuilder();
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (c == '"') {
builder.append("\\\"");
} else if (c == '\\') {
builder.append("\\\\");
} else if (c <= '\u000F') {
builder.append("\\u000").append(Integer.toHexString(c));
} else if (c <= '\u001F') {
builder.append("\\u00").append(Integer.toHexString(c));
} else {
builder.append(c);
}
}
return builder.toString();
}
/**
* A super simple representation of a JSON object.
*
* <p>This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not
* allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String,
* JsonObject)}.
*/
public static class JsonObject {
private final String value;
private JsonObject(String value) {
this.value = value;
}
@Override
public String toString() {
return value;
}
}
}
}

View File

@@ -0,0 +1,16 @@
package de.oliver.fancylib;
import java.util.concurrent.ThreadLocalRandom;
public class RandomUtils {
public static boolean random(double percentage) {
if (ThreadLocalRandom.current().nextDouble(0, 100) <= percentage) return true;
return false;
}
public static double randomInRange(double min, double max) {
return (ThreadLocalRandom.current().nextDouble() * (max - min)) + min;
}
}

View File

@@ -0,0 +1,54 @@
package de.oliver.fancylib;
import java.lang.reflect.Field;
public class ReflectionUtils {
public static Object getValue(Object instance, String name) {
Object result = null;
try {
Field field = instance.getClass().getDeclaredField(name);
field.setAccessible(true);
result = field.get(instance);
field.setAccessible(false);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
public static Object getStaticValue(Class clazz, String name) {
Object result = null;
try {
Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
result = field.get(clazz);
field.setAccessible(false);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
public static void setValue(Object instance, String name, Object value) {
try {
Field field = instance.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(instance, value);
field.setAccessible(false);
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,154 @@
package de.oliver.fancylib;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
/*
From: https://gist.github.com/Jofkos/d0c469528b032d820f42
*/
public class UUIDFetcher {
/**
* Date when name changes were introduced
*
* @see UUIDFetcher#getUUIDAt(String, long)
*/
public static final long FEBRUARY_2015 = 1422748800000L;
private static final String UUID_URL = "https://api.mojang.com/users/profiles/minecraft/%s?at=%d";
private static final String NAME_URL = "https://api.mojang.com/user/profile/%s";
private static Gson gson = new GsonBuilder().registerTypeAdapter(UUID.class, new UUIDTypeAdapter()).create();
private static Map<String, UUID> uuidCache = new HashMap<String, UUID>();
private static Map<UUID, String> nameCache = new HashMap<UUID, String>();
private static ExecutorService pool = Executors.newCachedThreadPool();
private String name;
private UUID id;
/**
* Fetches the uuid asynchronously and passes it to the consumer
*
* @param name The name
* @param action Do what you want to do with the uuid her
*/
public static void getUUID(String name, Consumer<UUID> action) {
pool.execute(() -> action.accept(getUUID(name)));
}
/**
* Fetches the uuid synchronously and returns it
*
* @param name The name
* @return The uuid
*/
public static UUID getUUID(String name) {
return getUUIDAt(name, System.currentTimeMillis());
}
/**
* Fetches the uuid synchronously for a specified name and time and passes the result to the consumer
*
* @param name The name
* @param timestamp Time when the player had this name in milliseconds
* @param action Do what you want to do with the uuid her
*/
public static void getUUIDAt(String name, long timestamp, Consumer<UUID> action) {
pool.execute(() -> action.accept(getUUIDAt(name, timestamp)));
}
/**
* Fetches the uuid synchronously for a specified name and time
*
* @param name The name
* @param timestamp Time when the player had this name in milliseconds
* @see UUIDFetcher#FEBRUARY_2015
*/
public static UUID getUUIDAt(String name, long timestamp) {
name = name.toLowerCase();
if (uuidCache.containsKey(name)) {
return uuidCache.get(name);
}
try {
HttpURLConnection connection = (HttpURLConnection) new URL(String.format(UUID_URL, name, timestamp / 1000)).openConnection();
connection.setReadTimeout(5000);
UUIDFetcher data = gson.fromJson(new BufferedReader(new InputStreamReader(connection.getInputStream())), UUIDFetcher.class);
uuidCache.put(name, data.id);
nameCache.put(data.id, data.name);
return data.id;
} catch (Exception e) {
return null;
}
}
/**
* Fetches the name asynchronously and passes it to the consumer
*
* @param uuid The uuid
* @param action Do what you want to do with the name her
*/
public static void getName(UUID uuid, Consumer<String> action) {
pool.execute(() -> action.accept(getName(uuid)));
}
/**
* Fetches the name synchronously and returns it
*
* @param uuid The uuid
* @return The name
*/
public static String getName(UUID uuid) {
if (nameCache.containsKey(uuid)) {
return nameCache.get(uuid);
}
try {
HttpURLConnection connection = (HttpURLConnection) new URL(String.format(NAME_URL, UUIDTypeAdapter.fromUUID(uuid))).openConnection();
connection.setReadTimeout(5000);
UUIDFetcher currentNameData = gson.fromJson(new BufferedReader(new InputStreamReader(connection.getInputStream())), UUIDFetcher.class);
uuidCache.put(currentNameData.name.toLowerCase(), uuid);
nameCache.put(uuid, currentNameData.name);
return currentNameData.name;
} catch (Exception e) {
return null;
}
}
}
class UUIDTypeAdapter extends TypeAdapter<UUID> {
public static String fromUUID(UUID value) {
return value.toString().replace("-", "");
}
public static UUID fromString(String input) {
return UUID.fromString(input.replaceFirst("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5"));
}
public void write(JsonWriter out, UUID value) throws IOException {
out.value(fromUUID(value));
}
public UUID read(JsonReader in) throws IOException {
return fromString(in.nextString());
}
}

View File

@@ -0,0 +1,111 @@
package de.oliver.fancylib;
import de.oliver.fancylib.versionFetcher.VersionFetcher;
import org.apache.maven.artifact.versioning.ComparableVersion;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
import java.util.function.Function;
public class VersionConfig {
private final Plugin plugin;
private final VersionFetcher fetcher;
private String version;
private String build;
private String hash;
public VersionConfig(Plugin plugin, VersionFetcher fetcher) {
this.plugin = plugin;
this.fetcher = fetcher;
}
public void load() {
YamlConfiguration config = new YamlConfiguration();
try {
config.loadFromString(new FileUtils().readResource("version.yml"));
} catch (InvalidConfigurationException e) {
e.printStackTrace();
}
version = config.getString("version");
build = config.getString("build");
hash = config.getString("hash");
}
public void checkVersionAndDisplay(CommandSender sender, boolean displayOnlyIfOutdated) {
String newestVersion = usingLatestVersion();
if (newestVersion == null) {
MessageHelper.error(sender, "Could not fetch latest version");
return;
}
if (!newestVersion.isEmpty()) {
MessageHelper.warning(sender, outdatedVersion(newestVersion, fetcher.getDownloadUrl()));
return;
}
if (!displayOnlyIfOutdated) {
MessageHelper.success(sender, latestVersion());
}
}
/**
* @return null if could not fetch, empty string if it's the newest, the newest version if it's not the current
*/
private String usingLatestVersion() {
ComparableVersion newestVersion = fetcher.fetchNewestVersion();
ComparableVersion currentVersion = new ComparableVersion(version);
if (newestVersion == null) {
return null;
}
if (newestVersion.compareTo(currentVersion) <= 0) {
return "";
}
return newestVersion.toString();
}
private String latestVersion() {
String result = "This server is using the latest version of {plugin}!\n" +
"Version: {version} (Git: {hash})" +
"{build}";
result = result.replace("{plugin}", plugin.getName())
.replace("{version}", version)
.replace("{hash}", hash.substring(0, 7))
.replace("{build}", build.equalsIgnoreCase("undefined") ? "" : "\nBuild: " + build);
return result;
}
private String outdatedVersion(String latestVersion, String downloadUrl) {
String result = "This server is using an outdated version of {plugin}\n" +
"Current version: {current_ver}\n" +
"Latest version: {latest_ver}\n" +
"Download latest version: <click:open_url:'{download_url}'><u>click here</u></click>";
result = result.replace("{plugin}", plugin.getName())
.replace("{current_ver}", version)
.replace("{latest_ver}", latestVersion)
.replace("{download_url}", downloadUrl);
return result;
}
public String getVersion() {
return version;
}
public String getBuild() {
return build;
}
public String getHash() {
return hash;
}
}

View File

@@ -0,0 +1,45 @@
package de.oliver.fancylib.databases;
import java.sql.Connection;
import java.sql.ResultSet;
public interface Database {
/**
* Connects to the database
*
* @return true if success, false if failed
*/
boolean connect();
/**
* Closes the database connection
*
* @return true if success, false if failed
*/
boolean close();
/**
* @return true if connected, false if not
*/
boolean isConnected();
/**
* @return the connection object, null if not connected
*/
Connection getConnection();
/**
* Executes a statement on the database
*
* @return true if success, false if failed
*/
boolean executeNonQuery(String sql);
/**
* Executes a query on the database
*
* @return the result or null if failed
*/
ResultSet executeQuery(String query);
}

View File

@@ -0,0 +1,88 @@
package de.oliver.fancylib.databases;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
public class MySqlDatabase implements Database {
protected final String host;
protected final String port;
protected final String database;
protected final String username;
protected final String password;
protected Connection connection;
public MySqlDatabase(String host, String port, String database, String username, String password) {
this.host = host;
this.port = port;
this.database = database;
this.username = username;
this.password = password;
this.connection = null;
}
@Override
public boolean connect() {
try {
if (isConnected()) {
return true;
}
// Class.forName("com.mysql/.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://" + host + ":" + port + "/" + database, username, password);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean close() {
try {
if (!isConnected()) {
return true;
}
connection.close();
connection = null;
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean isConnected() {
return connection != null;
}
@Override
public Connection getConnection() {
return connection;
}
@Override
public boolean executeNonQuery(String sql) {
try {
connection.createStatement().execute(sql);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public ResultSet executeQuery(String query) {
try {
ResultSet resultSet = connection.createStatement().executeQuery(query);
return resultSet;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

View File

@@ -0,0 +1,41 @@
package de.oliver.fancylib.databases;
import org.bukkit.Bukkit;
import java.io.File;
import java.sql.DriverManager;
public class SqliteDatabase extends MySqlDatabase {
protected final String path;
protected File file;
public SqliteDatabase(String path) {
super(null, null, null, null, null);
this.path = path;
this.file = new File(path);
}
@Override
public boolean connect() {
try {
if (isConnected()) {
return true;
}
if (!file.exists()) {
if (!file.createNewFile()) {
Bukkit.getLogger().warning("Could not create database file.");
return false;
}
}
Class.forName("org.sqlite.JDBC");
connection = DriverManager.getConnection("jdbc:sqlite:" + file.getPath());
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}

View File

@@ -0,0 +1,38 @@
package de.oliver.fancylib.featureFlags;
public class FeatureFlag {
private final String name;
private final String description;
private boolean enabled;
private final boolean forceDisabled;
public FeatureFlag(String name, String description, boolean forceDisabled) {
this.name = name;
this.description = description;
this.forceDisabled = forceDisabled;
this.enabled = false;
}
public boolean isEnabled() {
if(forceDisabled) return false;
return enabled;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isForceDisabled() {
return forceDisabled;
}
}

View File

@@ -0,0 +1,79 @@
package de.oliver.fancylib.featureFlags;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class FeatureFlagConfig {
private final Plugin plugin;
private final File configFile;
private final List<FeatureFlag> featureFlags;
public FeatureFlagConfig(Plugin plugin) {
this.plugin = plugin;
this.configFile = new File("plugins" + File.separator + plugin.getName() + File.separator + "featureFlags.yml");
this.featureFlags = new ArrayList<>();
}
public void load() {
if (!configFile.exists()) {
try {
new File(configFile.getParent()).mkdirs();
configFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
for (FeatureFlag featureFlag : featureFlags) {
config.setInlineComments("feature-flags." + featureFlag.getName(), List.of(featureFlag.getDescription()));
if (config.isSet("feature-flags." + featureFlag.getName())) {
continue;
}
config.set("feature-flags." + featureFlag.getName(), false);
}
for (String flagName : config.getConfigurationSection("feature-flags").getKeys(false)) {
boolean enabled = config.getBoolean("feature-flags." + flagName, false);
FeatureFlag flag = getFeatureFlag(flagName);
if (flag == null) {
flag = new FeatureFlag(flagName, "", false);
featureFlags.add(flag);
}
flag.setEnabled(enabled);
}
try {
config.save(configFile);
} catch (IOException e) {
e.printStackTrace();
}
}
public FeatureFlag getFeatureFlag(String name) {
for (FeatureFlag featureFlag : featureFlags) {
if (featureFlag.getName().equalsIgnoreCase(name.toLowerCase())) {
return featureFlag;
}
}
return null;
}
public void addFeatureFlag(FeatureFlag featureFlag) {
if (!featureFlags.contains(featureFlag)) {
featureFlags.add(featureFlag);
}
}
}

View File

@@ -0,0 +1,47 @@
package de.oliver.fancylib.gui.customInventories;
import de.oliver.fancylib.gui.inventoryClick.InventoryItemClick;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
public abstract class CustomPlayerInventory implements InventoryHolder {
protected final Player player;
protected Inventory inventory;
protected CustomPlayerInventory(Player player, int amountRows, Component title) {
this.player = player;
this.inventory = Bukkit.createInventory(this, amountRows * 9, title);
}
public static ItemStack getPlaceholder() {
ItemStack item = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);
item.editMeta(itemMeta -> {
itemMeta.displayName(Component.empty());
itemMeta.getPersistentDataContainer().set(InventoryItemClick.ON_CLICK_KEY, PersistentDataType.STRING, "cancelClick");
});
return item;
}
public Player getPlayer() {
return player;
}
@NotNull
@Override
public Inventory getInventory() {
return inventory;
}
public void open() {
player.openInventory(inventory);
}
}

View File

@@ -0,0 +1,43 @@
package de.oliver.fancylib.gui.customInventories;
import de.oliver.fancylib.FancyLib;
import de.oliver.fancylib.MessageHelper;
import de.oliver.fancylib.gui.inventoryClick.InventoryItemClick;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
public interface PageInventory {
NamespacedKey PAGE_KEY = new NamespacedKey(FancyLib.getInstance().getPlugin(), "page");
static ItemStack previousPage(int currentPage) {
ItemStack previousPage = new ItemStack(Material.ARROW);
previousPage.editMeta(itemMeta -> {
itemMeta.displayName(MessageHelper.removeDecoration(MiniMessage.miniMessage().deserialize("<gradient:gold:yellow:gold>Previous page</gradient>"), TextDecoration.ITALIC));
itemMeta.getPersistentDataContainer().set(PageInventory.PAGE_KEY, PersistentDataType.INTEGER, currentPage - 1);
itemMeta.getPersistentDataContainer().set(InventoryItemClick.ON_CLICK_KEY, PersistentDataType.STRING, "changePage");
});
return previousPage;
}
static ItemStack nextPage(int currentPage) {
ItemStack nextPage = new ItemStack(Material.ARROW);
nextPage.editMeta(itemMeta -> {
itemMeta.displayName(MessageHelper.removeDecoration(MiniMessage.miniMessage().deserialize("<gradient:gold:yellow:gold>Next page</gradient>"), TextDecoration.ITALIC));
itemMeta.getPersistentDataContainer().set(PageInventory.PAGE_KEY, PersistentDataType.INTEGER, currentPage + 1);
itemMeta.getPersistentDataContainer().set(InventoryItemClick.ON_CLICK_KEY, PersistentDataType.STRING, "changePage");
});
return nextPage;
}
void loadPage(int page);
}

View File

@@ -0,0 +1,28 @@
package de.oliver.fancylib.gui.inventoryClick;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
public class InventoryClickListener implements Listener {
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
try {
ItemStack item = event.getCurrentItem();
if (item.getItemMeta().getPersistentDataContainer().has(InventoryItemClick.ON_CLICK_KEY)) {
String id = item.getItemMeta().getPersistentDataContainer().get(InventoryItemClick.ON_CLICK_KEY, PersistentDataType.STRING);
InventoryClickRegistry.getInventoryItemClick(id).onClick(event, (Player) event.getWhoClicked());
}
} catch (NullPointerException ignore) {
}
}
}

View File

@@ -0,0 +1,19 @@
package de.oliver.fancylib.gui.inventoryClick;
import java.util.HashMap;
import java.util.Map;
public class InventoryClickRegistry {
private static final Map<String, InventoryItemClick> inventoryItemClickMap = new HashMap<>();
public static InventoryItemClick getInventoryItemClick(String id) {
return inventoryItemClickMap.getOrDefault(id, InventoryItemClick.EMPTY);
}
public static void registerInventoryItemClick(InventoryItemClick inventoryItemClick) {
inventoryItemClickMap.put(inventoryItemClick.getId(), inventoryItemClick);
}
}

View File

@@ -0,0 +1,46 @@
package de.oliver.fancylib.gui.inventoryClick;
import de.oliver.fancylib.FancyLib;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataContainer;
import java.util.List;
public interface InventoryItemClick {
NamespacedKey ON_CLICK_KEY = new NamespacedKey(FancyLib.getInstance().getPlugin(), "onclick");
InventoryItemClick EMPTY = new InventoryItemClick() {
@Override
public String getId() {
return "";
}
@Override
public void onClick(InventoryClickEvent event, Player player) {
}
};
static boolean hasKeys(ItemStack item, List<NamespacedKey> keys) {
PersistentDataContainer data = item.getItemMeta().getPersistentDataContainer();
for (NamespacedKey key : keys) {
if (!data.has(key)) {
return false;
}
}
return true;
}
String getId();
void onClick(InventoryClickEvent event, Player player);
default void register() {
InventoryClickRegistry.registerInventoryItemClick(this);
}
}

View File

@@ -0,0 +1,23 @@
package de.oliver.fancylib.gui.inventoryClick.impl;
import de.oliver.fancylib.gui.inventoryClick.InventoryItemClick;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
public class CancelInventoryItemClick implements InventoryItemClick {
public static final CancelInventoryItemClick INSTANCE = new CancelInventoryItemClick();
private CancelInventoryItemClick() {
}
@Override
public String getId() {
return "cancelClick";
}
@Override
public void onClick(InventoryClickEvent event, Player player) {
event.setCancelled(true);
}
}

View File

@@ -0,0 +1,47 @@
package de.oliver.fancylib.gui.inventoryClick.impl;
import de.oliver.fancylib.gui.customInventories.PageInventory;
import de.oliver.fancylib.gui.inventoryClick.InventoryItemClick;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
import java.util.Collections;
import java.util.List;
public class ChangePageInventoryItemClick implements InventoryItemClick {
public static final ChangePageInventoryItemClick INSTANCE = new ChangePageInventoryItemClick();
private final static List<NamespacedKey> REQUIRED_KEYS = Collections.singletonList(
PageInventory.PAGE_KEY
);
private ChangePageInventoryItemClick() {
}
@Override
public String getId() {
return "changePage";
}
@Override
public void onClick(InventoryClickEvent event, Player player) {
ItemStack item = event.getCurrentItem();
if (item != null && InventoryItemClick.hasKeys(item, REQUIRED_KEYS)) {
event.setCancelled(true);
int page = item.getItemMeta().getPersistentDataContainer().get(PageInventory.PAGE_KEY, PersistentDataType.INTEGER);
if (event.getInventory().getHolder() == null || !(event.getInventory().getHolder() instanceof PageInventory pageInventory)) {
return;
}
pageInventory.loadPage(page);
}
}
}

View File

@@ -0,0 +1,30 @@
package de.oliver.fancylib.itemClick;
import de.oliver.fancylib.FancyLib;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerInteractEvent;
public interface ItemClick {
NamespacedKey ON_CLICK_KEY = new NamespacedKey(FancyLib.getInstance().getPlugin(), "oninteract");
ItemClick EMPTY = new ItemClick() {
@Override
public String getId() {
return null;
}
@Override
public void onClick(PlayerInteractEvent event, Player player) {
}
};
String getId();
void onClick(PlayerInteractEvent event, Player player);
default void register() {
ItemClickRegistry.registerItemClick(this);
}
}

View File

@@ -0,0 +1,18 @@
package de.oliver.fancylib.itemClick;
import java.util.HashMap;
import java.util.Map;
public class ItemClickRegistry {
private static final Map<String, ItemClick> itemClickMap = new HashMap<>();
public static ItemClick getItemClick(String id) {
return itemClickMap.getOrDefault(id, ItemClick.EMPTY);
}
public static void registerItemClick(ItemClick itemClick) {
itemClickMap.put(itemClick.getId(), itemClick);
}
}

View File

@@ -0,0 +1,25 @@
package de.oliver.fancylib.itemClick;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
public class PlayerInteractListener implements Listener {
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
try {
ItemStack item = event.getItem();
if (event.getHand() == EquipmentSlot.HAND && item.getItemMeta().getPersistentDataContainer().has(ItemClick.ON_CLICK_KEY)) {
String id = item.getItemMeta().getPersistentDataContainer().get(ItemClick.ON_CLICK_KEY, PersistentDataType.STRING);
ItemClickRegistry.getItemClick(id).onClick(event, event.getPlayer());
}
} catch (NullPointerException ignore) {
}
}
}

View File

@@ -0,0 +1,137 @@
package de.oliver.fancylib.jdb;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.jetbrains.annotations.NotNull;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* The JDB class provides a simple JSON document-based storage system in a specified directory.
*/
public class JDB {
private final static Gson GSON = new GsonBuilder()
.serializeNulls()
.setPrettyPrinting()
.create();
private static final String FILE_EXTENSION = ".json";
private final @NotNull String basePath;
private final @NotNull File baseDirectory;
/**
* Constructs a new JDB instance with the specified base path.
*
* @param basePath the base directory path where documents will be stored
*/
public JDB(@NotNull String basePath) {
this.basePath = basePath;
this.baseDirectory = new File(basePath);
}
/**
* Retrieves a document from the specified path, deserializing it into the given class type.
*
* @param <T> the type of the object to be returned
* @param path the relative path (excluding .json extension) where the document is located
* @param clazz the class type to which the document should be deserialized
* @return a JDocument containing the deserialized object and its path, or null if the file does not exist
* @throws IOException if an I/O error occurs during file reading
*/
public <T> T get(@NotNull String path, @NotNull Class<T> clazz) throws IOException {
File documentFile = new File(baseDirectory, createFilePath(path));
if (!documentFile.exists()) {
return null;
}
BufferedReader bufferedReader = Files.newBufferedReader(documentFile.toPath());
return GSON.fromJson(bufferedReader, clazz);
}
/**
* Retrieves a document from the specified path.
*
* @param path the relative path (excluding .json extension) where the document is located
* @return a JDocument containing the deserialized data or null if the file does not exist
* @throws IOException if an I/O error occurs during file reading
*/
public JDocument getDocument(@NotNull String path) throws IOException {
File documentFile = new File(baseDirectory, createFilePath(path));
if (!documentFile.exists()) {
return null;
}
BufferedReader bufferedReader = Files.newBufferedReader(documentFile.toPath());
Map<String, Object> data = (Map<String, Object>) GSON.fromJson(bufferedReader, Map.class);
return new JDocument(data);
}
/**
* Retrieves all documents from the specified directory path, deserializing them into the given class type.
*
* @param <T> the type of objects to be returned
* @param path the relative directory path containing the documents
* @param clazz the class type to which the documents should be deserialized
* @return a List of JDocument objects containing the deserialized objects and their paths, or null if the directory or files do not exist
* @throws IOException if an I/O error occurs during file reading
*/
public <T> List<T> getAll(@NotNull String path, @NotNull Class<T> clazz) throws IOException {
File directory = new File(baseDirectory, path);
if (!directory.exists()) {
return new ArrayList<>();
}
File[] files = directory.listFiles();
if (files == null) {
return new ArrayList<>();
}
List<T> documents = new ArrayList<>(files.length);
for (File file : files) {
documents.add(get(path + "/" + file.getName().replace(FILE_EXTENSION, ""), clazz));
}
return documents;
}
/**
* Saves the given value as a document at the specified path.
*
* @param <T> the type of the object to be saved
* @param path the relative path (excluding .json extension) where the document will be saved
* @param value the object to be saved as a JSON document
* @throws IOException if an I/O error occurs during file writing
*/
public <T> void set(@NotNull String path, @NotNull T value) throws IOException {
File documentFile = new File(baseDirectory, createFilePath(path));
if (!documentFile.exists()) {
documentFile.getParentFile().mkdirs();
documentFile.createNewFile();
}
String json = GSON.toJson(value);
Files.write(documentFile.toPath(), json.getBytes());
}
/**
* Deletes the document(s) at the specified path.
*
* @param path the relative path (excluding .json extension) where the document(s) are located
*/
public void delete(@NotNull String path) {
File documentFile = new File(baseDirectory, createFilePath(path));
if (documentFile.exists()) {
documentFile.delete();
}
}
/**
* Creates the file path by appending the base path, provided path, and the file extension.
*
* @param path the relative path (excluding .json extension)
* @return the full file path
*/
private String createFilePath(@NotNull String path) {
return path + FILE_EXTENSION;
}
}

View File

@@ -0,0 +1,152 @@
package de.oliver.fancylib.jdb;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Represents a document that holds a map of key-value pairs with support for nested keys.
*/
public class JDocument {
private final @NotNull Map<String, Object> data;
public JDocument(@NotNull Map<String, Object> data) {
this.data = data;
}
/**
* Retrieves a value from the document using the given key.
*
* @param key the dot-separated key used to locate the value in the document (e.g. "foo.bar.baz")
* @return the value associated with the given key, or null if the key is not found
*/
public Object get(String key) {
return getValue(key, Object.class);
}
/**
* Checks if the document contains a value associated with the given key.
*
* @param key the dot-separated key used to locate the value in the document (e.g. "foo.bar.baz")
* @return true if the given key exists in the document, otherwise false
*/
public boolean contains(String key) {
return get(key) != null;
}
/**
* Retrieves the set of keys from a map value associated with a given key in the document.
*
* @param key the dot-separated key used to locate the map value in the document (e.g. "foo.bar.baz")
* @return a set of keys from the map associated with the given key, or an empty set if the key
* is not found or the value is not a map
*/
public Set<String> getKeys(String key) {
Map<String, Object> map = (Map<String, Object>) getValue(key, Map.class);
return map != null ? map.keySet() : new HashSet<>();
}
/**
* Retrieves a string value from the document using the given key.
*
* @param key the dot-separated key used to locate the value in the document (e.g. "foo.bar.baz")
* @return the string value associated with the given key, or an empty string if the key is not found or the value is not a string
*/
public String getString(String key) {
return (String) getValueOrDefault(key, String.class, "");
}
/**
* Retrieves a boolean value associated with the given key.
*
* @param key the dot-separated key used to locate the value in the document (e.g. "foo.bar.baz")
* @return the boolean value associated with the given key, or false if the key is not found or the value is not a boolean
*/
public boolean getBoolean(String key) {
return (boolean) getValueOrDefault(key, Boolean.class, false);
}
/**
* Retrieves a byte value associated with the given key.
*
* @param key the dot-separated key used to locate the value in the document (e.g. "foo.bar.baz")
* @return the byte value associated with the given key, or 0 if the key is not found or the value is not a byte
*/
public byte getByte(String key) {
return (byte) getValueOrDefault(key, Byte.class, (byte) 0);
}
/**
* Retrieves a short value associated with the given key.
*
* @param key the dot-separated key used to locate the value in the document (e.g. "foo.bar.baz")
* @return the short value associated with the given key, or 0 if the key is not found or the value is not a short
*/
public short getShort(String key) {
return (short) getValueOrDefault(key, Short.class, (short) 0);
}
/**
* Retrieves an integer value associated with the given key from the document.
*
* @param key the dot-separated key used to locate the value in the document (e.g. "foo.bar.baz")
* @return the integer value associated with the given key, or 0 if the key is not found or the value is not an integer
*/
public int getInt(String key) {
return (int) getValueOrDefault(key, Integer.class, 0);
}
/**
* Retrieves a long value associated with the given key.
*
* @param key the dot-separated key used to locate the value in the document (e.g. "foo.bar.baz")
* @return the long value associated with the given key, or 0 if the key is not found or the value is not a long
*/
public long getLong(String key) {
return (long) getValueOrDefault(key, Long.class, 0L);
}
/**
* Retrieves a float value associated with the given key.
*
* @param key the dot-separated key used to locate the value in the document (e.g. "foo.bar.baz")
* @return the float value associated with the given key, or 0 if the key is not found or the value is not a float
*/
public float getFloat(String key) {
return (float) getValueOrDefault(key, Float.class, 0f);
}
/**
* Retrieves a double value associated with the given key.
*
* @param key the dot-separated key used to locate the value in the document (e.g. "foo.bar.baz")
* @return the double value associated with the given key, or 0 if the key is not found or the value is not a double
*/
public double getDouble(String key) {
return (double) getValueOrDefault(key, Double.class, 0d);
}
private Object getValue(String key, Class<?> clazz) {
String[] parts = key.split("\\.");
Map<String, Object> current = data;
for (int i = 0; i < parts.length; i++) {
Object value = current.get(parts[i]);
if (value == null || (i < parts.length - 1 && !(value instanceof Map))) {
return null;
}
if (i == parts.length - 1) {
return clazz.isInstance(value) ? value : null;
}
current = (Map<String, Object>) value;
}
return null;
}
private <T> T getValueOrDefault(String key, Class<T> clazz, T defaultValue) {
T value = (T) getValue(key, clazz);
return value != null ? value : defaultValue;
}
}

View File

@@ -0,0 +1,46 @@
package de.oliver.fancylib.serverSoftware;
import de.oliver.fancylib.serverSoftware.schedulers.BukkitScheduler;
import de.oliver.fancylib.serverSoftware.schedulers.FancyScheduler;
import de.oliver.fancylib.serverSoftware.schedulers.FoliaScheduler;
import io.papermc.paper.plugin.configuration.PluginMeta;
import org.bukkit.plugin.java.JavaPlugin;
import java.lang.reflect.Method;
import java.util.Arrays;
public class ServerSoftware {
public static boolean isFolia() {
return Arrays.stream(PluginMeta.class.getDeclaredMethods())
.map(Method::getName)
.anyMatch(s -> s.equals("isFoliaSupported"));
}
public static boolean isPaper() {
try {
Class.forName("io.papermc.paper.event.player.AsyncChatEvent");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
public static boolean isBukkit() {
try {
Class.forName("org.bukkit.Bukkit");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
public static FancyScheduler getCorrectScheduler(JavaPlugin plugin) {
if (isFolia()) {
return new FoliaScheduler(plugin);
}
return new BukkitScheduler(plugin);
}
}

View File

@@ -0,0 +1,61 @@
package de.oliver.fancylib.serverSoftware.schedulers;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitTask;
import org.jetbrains.annotations.NotNull;
public class BukkitScheduler implements FancyScheduler {
BukkitTask bukkitTask;
JavaPlugin plugin;
public BukkitScheduler(JavaPlugin plugin) {
this.plugin = plugin;
}
@Override
public @NotNull FancyScheduler runTask(Location location, Runnable task) {
bukkitTask = Bukkit.getScheduler().runTask(plugin, task);
return this;
}
@Override
public @NotNull FancyScheduler runTaskAsynchronously(Runnable task) {
bukkitTask = Bukkit.getScheduler().runTaskAsynchronously(plugin, task);
return this;
}
@Override
public @NotNull FancyScheduler runTaskLater(Location location, long delay, Runnable task) {
bukkitTask = Bukkit.getScheduler().runTaskLater(plugin, task, delay);
return this;
}
@Override
public @NotNull FancyScheduler runTaskLaterAsynchronously(long delay, Runnable task) {
bukkitTask = Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, task, delay);
return this;
}
@Override
public @NotNull FancyScheduler runTaskTimer(Location location, long delay, long period, Runnable task) {
bukkitTask = Bukkit.getScheduler().runTaskTimer(plugin, task, delay, period);
return this;
}
@Override
public @NotNull FancyScheduler runTaskTimerAsynchronously(long delay, long period, Runnable task) {
bukkitTask = Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, task, delay, period);
return this;
}
@Override
public void cancel() {
if (!bukkitTask.isCancelled()) {
bukkitTask.cancel();
}
}
}

View File

@@ -0,0 +1,69 @@
package de.oliver.fancylib.serverSoftware.schedulers;
import org.bukkit.Location;
import org.jetbrains.annotations.NotNull;
public interface FancyScheduler {
/**
* Run the task.
*
* @param task task...
* @param location required for Folia, in Bukkit can be null
* @return The created {@link FancyScheduler}.
*/
@NotNull FancyScheduler runTask(Location location, Runnable task);
/**
* Run the task asynchronously.
*
* @param task task...
* @return The created {@link FancyScheduler}
*/
@NotNull FancyScheduler runTaskAsynchronously(Runnable task);
/**
* Run the task after a specified number of ticks.
*
* @param location required for Folia, in Bukkit can be null
* @param task task...
* @param delay The number of ticks to wait.
* @return The created {@link FancyScheduler}
*/
@NotNull FancyScheduler runTaskLater(Location location, long delay, Runnable task);
/**
* Run the task asynchronously after a specified number of ticks.
*
* @param task task...
* @param delay The number of ticks to wait.
* @return The created {@link FancyScheduler}
*/
@NotNull FancyScheduler runTaskLaterAsynchronously(long delay, Runnable task);
/**
* Run the task repeatedly on a timer.
*
* @param location required for Folia, in Bukkit can be null
* @param task task...
* @param delay The delay before the task is first run (in ticks).
* @param period The ticks elapsed before the task is run again.
* @return The created {@link FancyScheduler}
*/
@NotNull FancyScheduler runTaskTimer(Location location, long delay, long period, Runnable task);
/**
* Run the task repeatedly on a timer asynchronously.
*
* @param task task...
* @param delay The delay before the task is first run (in ticks).
* @param period The ticks elapsed before the task is run again.
* @return The created {@link FancyScheduler}
*/
@NotNull FancyScheduler runTaskTimerAsynchronously(long delay, long period, Runnable task);
/**
* Cancel the task.
*/
void cancel();
}

View File

@@ -0,0 +1,71 @@
package de.oliver.fancylib.serverSoftware.schedulers;
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
import org.bukkit.Location;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.TimeUnit;
public class FoliaScheduler implements FancyScheduler {
private static final long NANOSECONDS_PER_TICK = 50000000L;
private final JavaPlugin plugin;
private ScheduledTask scheduledTask;
public FoliaScheduler(JavaPlugin plugin) {
this.plugin = plugin;
}
@Override
public @NotNull FancyScheduler runTask(Location location, Runnable task) {
if (location != null) {
scheduledTask = plugin.getServer().getRegionScheduler().run(plugin, location, scheduledTask1 -> task.run());
} else {
scheduledTask = plugin.getServer().getGlobalRegionScheduler().run(plugin, scheduledTask1 -> task.run());
}
return this;
}
@Override
public @NotNull FancyScheduler runTaskAsynchronously(Runnable task) {
scheduledTask = plugin.getServer().getAsyncScheduler().runNow(plugin, scheduledTask1 -> task.run());
return this;
}
@Override
public @NotNull FancyScheduler runTaskLater(Location location, long delay, Runnable task) {
if (location != null) {
scheduledTask = plugin.getServer().getRegionScheduler().runDelayed(plugin, location, scheduledTask1 -> task.run(), delay);
} else {
scheduledTask = plugin.getServer().getGlobalRegionScheduler().runDelayed(plugin, scheduledTask1 -> task.run(), delay);
}
return this;
}
@Override
public @NotNull FancyScheduler runTaskLaterAsynchronously(long delay, Runnable task) {
scheduledTask = plugin.getServer().getAsyncScheduler().runDelayed(plugin, scheduledTask1 -> task.run(), delay * NANOSECONDS_PER_TICK, TimeUnit.NANOSECONDS);
return this;
}
@Override
public @NotNull FancyScheduler runTaskTimer(Location location, long delay, long period, Runnable task) {
scheduledTask = plugin.getServer().getRegionScheduler().runAtFixedRate(plugin, location, scheduledTask1 -> task.run(), delay, period);
return this;
}
@Override
public @NotNull FancyScheduler runTaskTimerAsynchronously(long delay, long period, Runnable task) {
scheduledTask = plugin.getServer().getAsyncScheduler().runAtFixedRate(plugin, scheduledTask1 -> task.run(), delay * NANOSECONDS_PER_TICK, period * NANOSECONDS_PER_TICK, TimeUnit.NANOSECONDS);
return this;
}
@Override
public void cancel() {
if (!scheduledTask.isCancelled()) {
scheduledTask.cancel();
}
}
}

View File

@@ -0,0 +1,243 @@
package de.oliver.fancylib.tests;
/**
* A generic class for making assertions on the expected values.
*
* @param <T> the type of the value to be asserted.
*/
public class Expectable<T> {
/**
* The value that is being wrapped by this Expectable instance.
* This is the object against which all expectations will be verified.
*/
private final T t;
private Expectable(T t) {
this.t = t;
}
/**
* Creates a new instance of Expectable for the given value.
*
* @param <T> the type of the value being tested
* @param t the actual value to create an expectation for
* @return a new Expectable instance for the given value
*/
public static <T> Expectable<T> expect(T t) {
return new Expectable<>(t);
}
/**
* Ensures that the actual value is not null.
* <p>
* Throws an AssertionError if the value of the field 't' is null,
* indicating that the actual value is expected to be non-null.
*
* @throws AssertionError if the value of the field 't' is null
*/
public void toBeDefined() {
if (t == null) {
throw new AssertionError("Expected not null but got null");
}
}
/**
* Asserts that the value of the field 't' is null.
* <p>
* Throws an AssertionError if the value of 't' is not null,
* indicating the expectation that the value should be null.
*
* @throws AssertionError if the value of 't' is not null
*/
public void toBeNull() {
if (t != null) {
throw new AssertionError("Expected null but got not null");
}
}
/**
* Asserts that the actual value is equal to the expected value.
*
* @param expected the value that the actual value is expected to be equal to
* @throws AssertionError if the actual value is not equal to the expected value
*/
public void toBe(T expected) {
if (t != expected) {
throw new AssertionError("Expected " + expected + " but got " + t);
}
}
/**
* Asserts that the actual value is equal to the expected value using the {@code equals} method.
*
* @param expected the value that the actual value is expected to be equal to
* @throws AssertionError if the actual value is not equal to the expected value
*/
public void toEqual(T expected) {
if (!t.equals(expected)) {
throw new AssertionError("Expected " + expected + " but got " + t);
}
}
/**
* Asserts that the actual value is greater than the expected value.
*
* @param expected the value that the actual value is expected to be greater than
* @throws AssertionError if the actual value is not greater than the expected value,
* or if the type of the actual value is not one of Integer, Long, Float, or Double
*/
public void toBeGreaterThan(T expected) {
if (t instanceof Integer) {
if ((Integer) t <= (Integer) expected) {
throw new AssertionError("Expected " + t + " to be greater than " + expected);
} else {
return;
}
} else if (t instanceof Long) {
if ((Long) t <= (Long) expected) {
throw new AssertionError("Expected " + t + " to be greater than " + expected);
} else {
return;
}
} else if (t instanceof Float) {
if ((Float) t <= (Float) expected) {
throw new AssertionError("Expected " + t + " to be greater than " + expected);
} else {
return;
}
} else if (t instanceof Double) {
if ((Double) t <= (Double) expected) {
throw new AssertionError("Expected " + t + " to be greater than " + expected);
} else {
return;
}
}
throw new AssertionError("toBeGreaterThan can only be used on Integers, Longs, Floats, and Doubles");
}
/**
* Asserts that the actual value is less than the expected value.
*
* @param expected the value that the actual value is expected to be less than
* @throws AssertionError if the actual value is not less than the expected value,
* or if the type of the actual value is not one of Integer, Long, Float, or Double
*/
public void toBeLessThan(T expected) {
if (t instanceof Integer) {
if ((Integer) t >= (Integer) expected) {
throw new AssertionError("Expected " + t + " to be less than " + expected);
} else {
return;
}
} else if (t instanceof Long) {
if ((Long) t >= (Long) expected) {
throw new AssertionError("Expected " + t + " to be less than " + expected);
} else {
return;
}
} else if (t instanceof Float) {
if ((Float) t >= (Float) expected) {
throw new AssertionError("Expected " + t + " to be less than " + expected);
} else {
return;
}
} else if (t instanceof Double) {
if ((Double) t >= (Double) expected) {
throw new AssertionError("Expected " + t + " to be less than " + expected);
} else {
return;
}
}
throw new AssertionError("toBeLessThan can only be used on Integers, Longs, Floats, and Doubles");
}
/**
* Asserts that the actual value is an instance of the expected class.
* This method checks whether the value held in the field 't' is an instance of the provided Class.
*
* @param expected the Class object that the actual value is expected to be an instance of
* @throws AssertionError if the actual value is not an instance of the expected class
*/
public void toBeInstanceOf(Class<?> expected) {
if (!expected.isInstance(t)) {
throw new AssertionError("Expected " + t + " to be an instance of " + expected);
}
}
/**
* Asserts that the given expected value is contained within the actual value.
* <p>
* This method checks if the expected value is present in a String, Iterable, or Array.
* If the actual value is a String, it uses the contains method to check if the expected value
* is a substring. If the actual value is an Iterable, it checks if the expected value is an element.
* If the actual value is an Array, it checks if the expected value is present in the array.
*
* @param expected the value that is expected to be contained within the actual value
* @throws AssertionError if the expected value is not contained within the actual value
*/
public void toContain(Object expected) {
if (t instanceof String) {
if (!((String) t).contains((String) expected)) {
throw new AssertionError("Expected " + expected + " to be contained in " + t);
} else {
return;
}
} else if (t instanceof Iterable) {
if (!((Iterable<?>) t).spliterator().tryAdvance(o -> {
if (o.equals(expected)) {
return;
}
throw new AssertionError("Expected " + expected + " to be contained in " + t);
})) {
throw new AssertionError("Expected " + expected + " to be contained in " + t);
} else {
return;
}
} else if (t instanceof Object[]) {
for (Object o : (Object[]) t) {
if (o.equals(expected)) {
return;
}
}
throw new AssertionError("Expected " + expected + " to be contained in " + t);
}
throw new AssertionError("toContain can only be used on Strings, Iterables and Arrays");
}
/**
* Asserts that the actual value has the expected length.
* This method checks if the actual value is a String, Iterable, or Array,
* and compares their length or size to the given expected length.
*
* @param expected the expected length of the actual value
* @throws AssertionError if the actual value does not have the expected length,
* or if the actual value is not of type String, Iterable, or Array
*/
public void toHaveLength(int expected) {
if (t instanceof String) {
if (((String) t).length() != expected) {
throw new AssertionError("Expected " + expected + " but got " + ((String) t).length());
} else {
return;
}
} else if (t instanceof Iterable) {
if (((Iterable<?>) t).spliterator().getExactSizeIfKnown() != expected) {
throw new AssertionError("Expected " + expected + " but got " + ((Iterable<?>) t).spliterator().getExactSizeIfKnown());
} else {
return;
}
} else if (t instanceof Object[]) {
if (((Object[]) t).length != expected) {
throw new AssertionError("Expected " + expected + " but got " + ((Object[]) t).length);
} else {
return;
}
}
throw new AssertionError("toHaveLength can only be used on Strings");
}
}

View File

@@ -0,0 +1,147 @@
package de.oliver.fancylib.tests;
import de.oliver.fancylib.tests.annotations.FPAfterEach;
import de.oliver.fancylib.tests.annotations.FPBeforeEach;
import de.oliver.fancylib.tests.annotations.FPTest;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.entity.Player;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
/**
* FPTestClass is a record that encapsulates information about a test class and its associated test methods.
* This class supports running tests annotated with {@link FPTest}.
*
* @param testClass the test class to run tests for (must be annotated with {@link FPTest})
* @param beforeEach the method annotated with {@link FPBeforeEach} to run before each test
* @param afterEach the method annotated with {@link FPAfterEach} to run after each test
* @param testMethods the list of test methods annotated with {@link FPTest}
*/
public record FPTestClass(
Class<?> testClass,
Method beforeEach,
Method afterEach,
List<Method> testMethods
) {
private static final Logger logger = Logger.getLogger(FPTestClass.class.getName());
/**
* Creates an instance of FPTestClass by inspecting the provided test class for methods annotated
* with FPTest, FPBeforeEach, and FPAfterEach annotations.
* These methods are used to define the setup, teardown, and test methods for the class.
*
* @param testClass the class to be inspected for annotated methods
* @return an instance of FPTestClass containing the test class and its annotated methods
*/
public static FPTestClass fromClass(Class<?> testClass) {
Method beforeEach = null;
Method afterEach = null;
List<Method> testMethods = new ArrayList<>();
for (Method method : testClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(FPTest.class)) {
if (method.getParameterCount() != 1) continue;
if (method.getParameterTypes()[0] != Player.class) continue;
testMethods.add(method);
continue;
}
if (method.isAnnotationPresent(FPBeforeEach.class)) {
if (method.getParameterCount() != 1) continue;
if (method.getParameterTypes()[0] != Player.class) continue;
beforeEach = method;
continue;
}
if (method.isAnnotationPresent(FPAfterEach.class)) {
if (method.getParameterCount() != 1) continue;
if (method.getParameterTypes()[0] != Player.class) continue;
afterEach = method;
}
}
return new FPTestClass(testClass, beforeEach, afterEach, testMethods);
}
/**
* Runs the test methods belonging to the test class, performing any necessary setup and teardown operations.
*
* @param player The player context to pass to the test methods.
* @return true if all tests completed successfully, false if any test failed or an unexpected exception occurred.
*/
public boolean runTests(Player player) {
logger.info("Running tests for " + testClass.getSimpleName());
player.sendMessage(MiniMessage.miniMessage().deserialize("<green>Running tests for " + testClass.getSimpleName()));
for (Method testMethod : testMethods) {
Object testClassObj;
try {
testClassObj = testClass.getDeclaredConstructor().newInstance();
} catch (Exception e) {
logger.warning("Failed to create test class instance: " + e.getMessage());
return false;
}
FPTest fpTest = testMethod.getAnnotation(FPTest.class);
if (fpTest.skip()) {
logger.info("Skipping test " + displayName(testMethod));
player.sendMessage(MiniMessage.miniMessage().deserialize("<gold>Skipping test " + displayName(testMethod)));
continue;
}
long testStart = System.currentTimeMillis();
try {
if (beforeEach != null) beforeEach.invoke(testClassObj, player);
testMethod.invoke(testClassObj, player);
if (afterEach != null) afterEach.invoke(testClassObj, player);
} catch (InvocationTargetException e) {
logger.warning("Test " + displayName(testMethod) + " failed with exception: " + e.getCause().getMessage());
player.sendMessage(MiniMessage.miniMessage().deserialize("<red>Test " + displayName(testMethod) + " failed with exception: " + e.getCause().getMessage()));
return false;
} catch (Exception e) {
logger.warning("Unexpected exception in test " + fpTest.name() + ": " + e.getMessage());
return false;
}
long testEnd = System.currentTimeMillis();
logger.info("Test " + displayName(testMethod) + " took " + (testEnd - testStart) + "ms");
player.sendMessage(MiniMessage.miniMessage().deserialize("<green>Test " + displayName(testMethod) + " took " + (testEnd - testStart) + "ms"));
try {
Thread.sleep(100);
} catch (InterruptedException e) {
logger.warning("Thread interrupted while waiting between tests: " + e.getMessage());
}
}
return true;
}
/**
* Generates a display name for a given test method, incorporating annotation details if present.
*
* @param m the method for which to generate the display name
* @return a display name that includes the test class and method name, and optionally the value of the FPTest annotation's name attribute if the annotation is present
*/
public String displayName(Method m) {
if (!m.isAnnotationPresent(FPTest.class)) {
return testClass.getSimpleName() + "#" + m.getName();
}
FPTest fpTest = m.getAnnotation(FPTest.class);
return testClass.getSimpleName() + "#" + m.getName() + " (" + fpTest.name() + ")";
}
}

View File

@@ -0,0 +1,16 @@
package de.oliver.fancylib.tests.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to mark a method that should be executed after each test case in a test class.
* This annotation is used to identify methods that perform teardown operations, ensuring
* that the test environment is cleaned up and reset after each individual test method is executed.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface FPAfterEach {
}

View File

@@ -0,0 +1,16 @@
package de.oliver.fancylib.tests.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* FPBeforeEach is a custom annotation designed to be used on methods that should be executed before each test method.
* Methods annotated with FPBeforeEach are typically used to perform setup operations needed before executing each test case.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface FPBeforeEach {
}

View File

@@ -0,0 +1,31 @@
package de.oliver.fancylib.tests.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* FPTest is a custom annotation designed to be used on methods for marking them as test cases.
* It helps to identify methods that should be treated as test cases in the testing framework.
* The annotation's attributes allow for providing a human-readable test name and an optional flag to skip the test.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface FPTest {
/**
* Specifies the name of the test case. This name is used to identify the test case
* in reports, logs, and other contexts where the test case is referenced.
*
* @return the name of the test case
*/
String name();
/**
* Indicates whether the annotated test case should be skipped during test execution.
*
* @return true if the test case should be skipped, false otherwise
*/
boolean skip() default false;
}

View File

@@ -0,0 +1,35 @@
package de.oliver.fancylib.translations;
import de.oliver.fancylib.translations.message.Message;
import java.util.HashMap;
import java.util.Map;
public class Language {
private final String languageCode;
private final String languageName;
private final Map<String, Message> messages;
public Language(String languageCode, String languageName) {
this.languageCode = languageCode;
this.languageName = languageName;
this.messages = new HashMap<>();
}
public void addMessage(String key, Message message) {
messages.put(key, message);
}
public Message getMessage(String key) {
return messages.getOrDefault(key, null);
}
public String getLanguageCode() {
return languageCode;
}
public String getLanguageName() {
return languageName;
}
}

View File

@@ -0,0 +1,12 @@
package de.oliver.fancylib.translations;
public record TextConfig(
String primaryColor,
String secondaryColor,
String successColor,
String warningColor,
String errorColor,
String prefix
) {
}

View File

@@ -0,0 +1,135 @@
package de.oliver.fancylib.translations;
import de.oliver.fancylib.translations.message.Message;
import de.oliver.fancylib.translations.message.MultiMessage;
import de.oliver.fancylib.translations.message.SimpleMessage;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
public class Translator {
private final TextConfig textConfig;
private final List<Language> languages;
private Language selectedLanguage;
private Language fallbackLanguage;
public Translator(TextConfig textConfig) {
this.textConfig = textConfig;
this.languages = new ArrayList<>();
}
public void loadLanguages(String pluginFolderPath) {
languages.clear();
selectedLanguage = null;
fallbackLanguage = null;
File langFolder = new File(pluginFolderPath + File.separator + "languages");
if (!langFolder.exists()) {
if (!langFolder.mkdirs()) {
throw new RuntimeException("Could not create languages folder");
}
}
File defaultFile = new File(langFolder, "default.yml");
try {
InputStream defaultStream = getClass().getResourceAsStream("/languages/default.yml");
if (defaultStream == null) {
throw new RuntimeException("Could not find default language file");
}
// only copy if hash is different
Files.copy(defaultStream, defaultFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
throw new RuntimeException("Could not copy default language file");
}
File[] langFiles = langFolder.listFiles();
if (langFiles == null) {
throw new RuntimeException("Could not list language files");
}
for (File langFile : langFiles) {
languages.add(loadLanguageFile(langFile));
}
fallbackLanguage = languages.stream()
.filter(language -> language.getLanguageCode().equals("default"))
.findFirst()
.orElse(null);
if (fallbackLanguage == null) {
throw new RuntimeException("Could not find fallback language");
}
}
private Language loadLanguageFile(File langFile) {
String fileName = langFile.getName();
String languageCode = fileName.substring(0, fileName.lastIndexOf('.'));
YamlConfiguration lang = YamlConfiguration.loadConfiguration(langFile);
String languageName = lang.getString("language_name", languageCode);
Language language = new Language(languageCode, languageName);
ConfigurationSection messages = lang.getConfigurationSection("messages");
if (messages == null) {
throw new RuntimeException("Language file " + langFile.getName() + " does not contain a messages section");
}
for (String key : messages.getKeys(true)) {
if (messages.isString(key)) {
SimpleMessage message = new SimpleMessage(textConfig, messages.getString(key));
language.addMessage(key, message);
continue;
}
if (messages.isList(key)) {
List<String> list = messages.getStringList(key);
language.addMessage(key, new MultiMessage(textConfig, list));
}
}
return language;
}
public Message translate(String key) {
Message message = selectedLanguage.getMessage(key);
if (message == null) {
message = fallbackLanguage.getMessage(key);
}
if (message == null) {
return new SimpleMessage(textConfig, "<red>Missing translation for key <i>" + key);
}
return message.copy();
}
public List<Language> getLanguages() {
return languages;
}
public Language getSelectedLanguage() {
return selectedLanguage;
}
public Translator setSelectedLanguage(Language selectedLanguage) {
this.selectedLanguage = selectedLanguage;
return this;
}
public Language getFallbackLanguage() {
return fallbackLanguage;
}
}

View File

@@ -0,0 +1,129 @@
package de.oliver.fancylib.translations.message;
import de.oliver.fancylib.translations.TextConfig;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public abstract class Message {
protected final TextConfig config;
protected TagResolver.Builder tagResolverBuilder = TagResolver.builder();
public Message(TextConfig config) {
this.config = config;
}
public Message addTagResolver(TagResolver resolver) {
tagResolverBuilder = tagResolverBuilder.resolver(resolver);
return this;
}
protected void applyColorPlaceholders() {
replace("primaryColor", "<color:" + config.primaryColor() + ">");
replace("secondaryColor", "<color:" + config.secondaryColor() + ">");
replace("successColor", "<color:" + config.successColor() + ">");
replace("warningColor", "<color:" + config.warningColor() + ">");
replace("errorColor", "<color:" + config.errorColor() + ">");
}
/**
* Replaces a placeholder in the message
*
* @param placeholder the placeholder to replace
* @param replacement the replacement
* @return this message
*/
public abstract Message replace(String placeholder, String replacement);
/**
* Replaces a placeholder in the message, all known tags are stripped from the replacement
*
* @param placeholder the placeholder to replace
* @param replacement the replacement
* @return this message
*/
public abstract Message replaceStripped(String placeholder, String replacement);
/**
* Adds the prefix to the message
*
* @return this message
*/
public abstract Message withPrefix();
/**
* Adds the primary color to the message
*
* @return this message
*/
public abstract Message primary();
/**
* Adds the secondary color to the message
*
* @return this message
*/
public abstract Message secondary();
/**
* Adds the success color to the message
*
* @return this message
*/
public abstract Message success();
/**
* Adds the warning color to the message
*
* @return this message
*/
public abstract Message warning();
/**
* Adds the error color to the message
*
* @return this message
*/
public abstract Message error();
/**
* Applies custom placeholders to the message
*
* @return this message
*/
public abstract Message applyCustomPlaceholders();
/**
* Builds the message as a component
*
* @return the built component
*/
public abstract Component buildComponent();
/**
* Copies the message
*
* @return the copied message
*/
public abstract Message copy();
public void send(CommandSender receiver) {
Component msg = buildComponent();
if (Component.empty().equals(msg)) {
return;
}
receiver.sendMessage(msg);
}
public void actionbar(Player receiver) {
Component msg = buildComponent();
if (Component.empty().equals(msg)) {
return;
}
receiver.sendActionBar(msg);
}
}

View File

@@ -0,0 +1,120 @@
package de.oliver.fancylib.translations.message;
import de.oliver.fancylib.translations.TextConfig;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import java.util.ArrayList;
import java.util.List;
public class MultiMessage extends Message {
private final List<String> messages;
public MultiMessage(TextConfig config, List<String> messages) {
super(config);
this.messages = new ArrayList<>(messages);
applyColorPlaceholders();
}
@Override
public Message replace(String placeholder, String replacement) {
messages.replaceAll(s -> s
.replace("{" + placeholder + "}", replacement)
.replace("%" + placeholder + "%", replacement));
return this;
}
@Override
public Message replaceStripped(String placeholder, String replacement) {
return replace(placeholder, MiniMessage.miniMessage().stripTags(replacement, tagResolverBuilder.build()));
}
@Override
public Message withPrefix() {
messages.replaceAll(s -> config.prefix() + s);
return this;
}
@Override
public Message primary() {
messages.replaceAll(s -> "<color:" + config.primaryColor() + ">" + s);
return this;
}
@Override
public Message secondary() {
messages.replaceAll(s -> "<color:" + config.secondaryColor() + ">" + s);
return this;
}
@Override
public Message success() {
messages.replaceAll(s -> "<color:" + config.successColor() + ">" + s);
return this;
}
@Override
public Message warning() {
messages.replaceAll(s -> "<color:" + config.warningColor() + ">" + s);
return this;
}
@Override
public Message error() {
messages.replaceAll(s -> "<color:" + config.errorColor() + ">" + s);
return this;
}
@Override
public Message applyCustomPlaceholders() {
//TODO: add ChatColorHandler support
return this;
}
@Override
public Component buildComponent() {
String joined = String.join("\n", messages);
return MiniMessage.miniMessage().deserialize(joined, tagResolverBuilder.build());
}
@Override
public Message copy() {
return new MultiMessage(config, messages);
}
public String build() {
return String.join("\n", messages);
}
public List<String> getRawMessages() {
return messages;
}
public List<SimpleMessage> getSimpleMessages() {
List<SimpleMessage> messages = new ArrayList<>();
for (String s : this.messages) {
messages.add(new SimpleMessage(config, s));
}
return messages;
}
public MultiMessage page(int page, int messagesPerPage) {
List<String> pageMessages = new ArrayList<>();
int start = (page - 1) * messagesPerPage;
int end = Math.min(start + messagesPerPage, messages.size());
for (int i = start; i < end; i++) {
pageMessages.add(messages.get(i));
}
return new MultiMessage(config, pageMessages);
}
public int getPages(int messagesPerPage) {
return (int) Math.ceil((double) messages.size() / messagesPerPage);
}
}

View File

@@ -0,0 +1,97 @@
package de.oliver.fancylib.translations.message;
import de.oliver.fancylib.translations.TextConfig;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.entity.Player;
public class SimpleMessage extends Message {
private String message;
public SimpleMessage(TextConfig config, String message) {
super(config);
this.message = message;
applyColorPlaceholders();
}
@Override
public SimpleMessage replace(String placeholder, String replacement) {
message = message
.replace("{" + placeholder + "}", replacement)
.replace("%" + placeholder + "%", replacement);
return this;
}
@Override
public Message replaceStripped(String placeholder, String replacement) {
return replace(placeholder, MiniMessage.miniMessage().stripTags(replacement, tagResolverBuilder.build()));
}
@Override
public SimpleMessage withPrefix() {
message = config.prefix() + message;
return this;
}
@Override
public SimpleMessage primary() {
message = "<color:" + config.primaryColor() + ">" + message;
return this;
}
@Override
public SimpleMessage secondary() {
message = "<color:" + config.secondaryColor() + ">" + message;
return this;
}
@Override
public SimpleMessage success() {
message = "<color:" + config.successColor() + ">" + message;
return this;
}
@Override
public SimpleMessage warning() {
message = "<color:" + config.warningColor() + ">" + message;
return this;
}
@Override
public SimpleMessage error() {
message = "<color:" + config.errorColor() + ">" + message;
return this;
}
@Override
public SimpleMessage applyCustomPlaceholders() {
// TODO: add ChatColorHandler support
return this;
}
@Override
public Component buildComponent() {
return MiniMessage.miniMessage().deserialize(message, tagResolverBuilder.build());
}
@Override
public Message copy() {
return new SimpleMessage(config, message);
}
public String build() {
return message;
}
public void actionbar(Player receiver) {
receiver.sendActionBar(buildComponent());
}
public String getMessage() {
return message;
}
}

View File

@@ -0,0 +1,32 @@
package de.oliver.fancylib.versionFetcher;
import org.apache.maven.artifact.versioning.ComparableVersion;
public class HangarVersionFetcher implements VersionFetcher{
private final String pluginName;
private ComparableVersion newestVersion;
public HangarVersionFetcher(String pluginName) {
this.pluginName = pluginName;
this.newestVersion = null;
}
@Override
public ComparableVersion fetchNewestVersion() {
if(newestVersion != null) return newestVersion;
String versionStr = VersionFetcher.getDataFromUrl("https://hangar.papermc.io/api/v1/projects/" + pluginName + "/latestrelease");
if(versionStr == null || versionStr.isEmpty()){
return null;
}
newestVersion = new ComparableVersion(versionStr);
return newestVersion;
}
@Override
public String getDownloadUrl() {
return "https://hangar.papermc.io/Oliver/" + pluginName;
}
}

View File

@@ -0,0 +1,36 @@
package de.oliver.fancylib.versionFetcher;
import org.apache.maven.artifact.versioning.ComparableVersion;
import java.util.LinkedList;
public class MasterVersionFetcher implements VersionFetcher{
private final String pluginName;
private ComparableVersion newestVersion;
private LinkedList<VersionFetcher> fetchers;
public MasterVersionFetcher(String pluginName) {
this.pluginName = pluginName;
this.fetchers = new LinkedList<>();
fetchers.push(new ReposiliteVersionFetcher(pluginName));
fetchers.push(new ModrinthVersionFetcher(pluginName));
fetchers.push(new HangarVersionFetcher(pluginName));
}
@Override
public ComparableVersion fetchNewestVersion() {
for (VersionFetcher fetcher : fetchers) {
ComparableVersion version = fetcher.fetchNewestVersion();
if(version == null) continue;
newestVersion = version;
return newestVersion;
}
return null;
}
@Override
public String getDownloadUrl() {
return "https://modrinth.com/plugin/" + pluginName;
}
}

View File

@@ -0,0 +1,48 @@
package de.oliver.fancylib.versionFetcher;
import com.google.gson.Gson;
import org.apache.maven.artifact.versioning.ComparableVersion;
import java.util.Map;
public class ModrinthVersionFetcher implements VersionFetcher {
private final String pluginName;
private ComparableVersion newestVersion;
public ModrinthVersionFetcher(String pluginName) {
this.pluginName = pluginName;
this.newestVersion = null;
}
@Override
public ComparableVersion fetchNewestVersion() {
if (newestVersion != null) return newestVersion;
String jsonString = de.oliver.fancylib.versionFetcher.VersionFetcher.getDataFromUrl("https://api.modrinth.com/v2/project/" + pluginName.toLowerCase() + "/version");
if (jsonString == null || jsonString.isEmpty()) {
return null;
}
Gson gson = new Gson();
Map<String, Object>[] versions = gson.fromJson(jsonString, Map[].class);
for (Map<String, Object> version : versions) {
if (!version.get("version_type").equals("release")) {
continue;
}
String versionNumber = (String) version.get("version_number");
newestVersion = new ComparableVersion(versionNumber);
break;
}
return newestVersion;
}
@Override
public String getDownloadUrl() {
return "https://modrinth.com/plugin/" + pluginName;
}
}

View File

@@ -0,0 +1,39 @@
package de.oliver.fancylib.versionFetcher;
import com.google.gson.Gson;
import org.apache.maven.artifact.versioning.ComparableVersion;
import java.util.Map;
public class ReposiliteVersionFetcher implements VersionFetcher{
private final String pluginName;
private ComparableVersion newestVersion;
public ReposiliteVersionFetcher(String pluginName) {
this.pluginName = pluginName;
this.newestVersion = null;
}
@Override
public ComparableVersion fetchNewestVersion() {
if (newestVersion != null) return newestVersion;
String jsonString = VersionFetcher.getDataFromUrl("https://repo.fancyplugins.de/api/maven/latest/version/releases/de/oliver/" + pluginName);
if (jsonString == null || jsonString.isEmpty()) {
return null;
}
Gson gson = new Gson();
Map<String, Object> data = gson.fromJson(jsonString, Map.class);
String versionStr = (String) data.get("version");
newestVersion = new ComparableVersion(versionStr);
return newestVersion;
}
@Override
public String getDownloadUrl() {
return "https://modrinth.com/plugin/" + pluginName;
}
}

View File

@@ -0,0 +1,31 @@
package de.oliver.fancylib.versionFetcher;
import org.apache.maven.artifact.versioning.ComparableVersion;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public interface VersionFetcher {
ComparableVersion fetchNewestVersion();
String getDownloadUrl();
static String getDataFromUrl(String urlString) {
try {
URL url = new URL(urlString);
URLConnection connection = url.openConnection();
connection.setConnectTimeout(300);
Scanner scanner = new Scanner(connection.getInputStream(), StandardCharsets.UTF_8).useDelimiter("\\A");
return scanner.hasNext() ? scanner.next() : "";
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
}

View File

@@ -0,0 +1,232 @@
package de.oliver.fancylib.jdb;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
public class JDBTest {
//This class tests the get operation of the JDB class,
//which is supposed to retrieve and deserialize a JSON document
//from a given path in the file system.
private final String basePath = "./test_files/";
public static void cleanUpDirectory(String path) throws IOException {
Path directory = Paths.get(path);
if (Files.exists(directory)) {
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}
}
@BeforeEach
void setUp() throws IOException {
cleanUpDirectory(basePath);
}
@AfterEach
void tearDown() throws IOException {
cleanUpDirectory(basePath);
}
@Test
public void testGetObject() throws IOException {
// Prepare
String basePath = "./test_files/";
JDB jdb = new JDB(basePath);
String path = "test_file";
jdb.set(path, "Test message");
// Act
String result = jdb.get(path, String.class);
// Assert
assertEquals("Test message", result);
}
@Test
public void testGetObjectNonExisting() throws IOException {
// Prepare
JDB jdb = new JDB("./test_files/");
String path = "does_not_exist";
// Act
Object result = jdb.get(path, Object.class);
// Assert
assertNull(result);
}
@Test
public void testGetAllObjects() throws IOException {
// Prepare
String basePath = "./test_files/";
JDB jdb = new JDB(basePath);
String path = "test_files";
jdb.set(path + "/obj1", "Test message 1");
jdb.set(path + "/obj2", "Test message 2");
jdb.set(path + "/obj3", "Test message 3");
// Act
List<String> result = jdb.getAll(path, String.class);
// Assert
assertEquals(3, result.size());
assertTrue(result.contains("Test message 1"));
assertTrue(result.contains("Test message 2"));
assertTrue(result.contains("Test message 3"));
}
@Test
public void testGetAllObjectsNonExisting() throws IOException {
// Prepare
JDB jdb = new JDB("./test_files/");
String path = "does_not_exist";
// Act
List<Object> result = jdb.getAll(path, Object.class);
// Assert
assertTrue(result.isEmpty());
}
@Test
public void testSetNewObject() throws IOException {
// Prepare
String basePath = "./test_files/";
JDB jdb = new JDB(basePath);
String path = "new_object";
String value = "New message";
// Act
jdb.set(path, value);
String result = jdb.get(path, String.class);
// Assert
assertEquals(value, result);
}
@Test
public void testSetExistingObject() throws IOException {
// Prepare
String basePath = "./test_files/";
JDB jdb = new JDB(basePath);
String path = "existing_object";
String value = "Existing message";
jdb.set(path, "Old message");
// Act
jdb.set(path, value);
String result = jdb.get(path, String.class);
// Assert
assertEquals(value, result);
}
@Test
public void testSetObjectNull() throws IOException {
// Prepare
String basePath = "./test_files/";
JDB jdb = new JDB(basePath);
String path = "null_object";
// Act
jdb.set(path, null);
String result = jdb.get(path, String.class);
// Assert
assertNull(result);
}
@Test
public void testDeleteWhenFileExists() throws IOException {
// Prepare
String basePath = "./test_files/";
JDB jdb = new JDB(basePath);
String path = "existing_file";
String value = "Test message";
jdb.set(path, value);
// Act
jdb.delete(path);
String result = jdb.get(path, String.class);
// Assert
assertNull(result);
File file = new File(basePath + path + ".json");
assertFalse(file.exists());
}
@Test
public void testDeleteWhenFileNotExists() {
// Prepare
String basePath = "./test_files/";
JDB jdb = new JDB(basePath);
String path = "non_existing_file";
// Act
jdb.delete(path);
// Assert
File file = new File(basePath + path + ".json");
assertFalse(file.exists());
}
// The getDocument method in the JDB class is supposed to retrieve and deserialize a JSON document(but encapsulated in a JDocument) from a given path in the file system.
// Testing getDocument method when the file exists
@Test
public void testGetDocumentWhenFileExists() throws IOException {
// Prepare
String basePath = "./test_files/";
JDB jdb = new JDB(basePath);
String path = "existing_file";
TestObject value = new TestObject("Test message");
jdb.set(path, value);
// Act
JDocument document = jdb.getDocument(path);
// Assert
assertNotNull(document);
assertEquals(value.message(), document.getString("message"));
}
// Testing the getDocument method when the file does not exist
@Test
public void testGetDocumentWhenFileDoesNotExist() throws IOException {
// Prepare
String basePath = "./test_files/";
JDB jdb = new JDB(basePath);
String path = "non_existing_file";
// Act
JDocument document = jdb.getDocument(path);
// Assert
assertNull(document);
}
record TestObject(String message) {
}
}

View File

@@ -0,0 +1,619 @@
package de.oliver.fancylib.jdb;
import org.junit.jupiter.api.Test;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.*;
public class JDocumentTest {
/**
* The JDocumentTest class contains unit tests for the JDocument class.
* The get method in the JDocument class is being tested here.
*/
@Test
public void testGet_Success_SingleKey() {
Map<String, Object> data = new HashMap<>();
data.put("key1", "value1");
JDocument jDocument = new JDocument(data);
Object result = jDocument.get("key1");
assertNotNull(result);
assertEquals("value1", result.toString());
}
@Test
public void testGet_Failure_KeyNotFound() {
JDocument jDocument = new JDocument(Collections.emptyMap());
Object result = jDocument.get("key1");
assertNull(result);
}
@Test
public void testGet_Success_NestedKey() {
Map<String, Object> nestedData = new HashMap<>();
nestedData.put("key2", "value2");
Map<String, Object> data = new HashMap<>();
data.put("key1", nestedData);
JDocument jDocument = new JDocument(data);
Object result = jDocument.get("key1.key2");
assertNotNull(result);
assertEquals("value2", result.toString());
}
@Test
public void testGet_Failure_NonExistentNestedKey() {
Map<String, Object> nestedData = new HashMap<>();
nestedData.put("key2", "value2");
Map<String, Object> data = new HashMap<>();
data.put("key1", nestedData);
JDocument jDocument = new JDocument(data);
Object result = jDocument.get("key1.key3");
assertNull(result);
}
/**
* The contains method in the JDocument class is being tested here.
* It checks whether a given key is present in the JDocument's data or not.
*/
@Test
public void testContains_Success_SingleKey() {
Map<String, Object> data = new HashMap<>();
data.put("key1", "value1");
JDocument jDocument = new JDocument(data);
boolean result = jDocument.contains("key1");
assertTrue(result);
}
@Test
public void testContains_Failure_KeyNotFound() {
JDocument jDocument = new JDocument(Collections.emptyMap());
boolean result = jDocument.contains("key1");
assertFalse(result);
}
@Test
public void testContains_Success_NestedKey() {
Map<String, Object> nestedData = new HashMap<>();
nestedData.put("key2", "value2");
Map<String, Object> data = new HashMap<>();
data.put("key1", nestedData);
JDocument jDocument = new JDocument(data);
boolean result = jDocument.contains("key1.key2");
assertTrue(result);
}
@Test
public void testContains_Failure_NonExistentNestedKey() {
Map<String, Object> nestedData = new HashMap<>();
nestedData.put("key2", "value2");
Map<String, Object> data = new HashMap<>();
data.put("key1", nestedData);
JDocument jDocument = new JDocument(data);
boolean result = jDocument.contains("key1.key3");
assertFalse(result);
}
/**
* The getKeys method in the JDocument class is being tested here.
* It retrieves the keys of the nested Map present within the data.
*/
@Test
public void testGetKeys_Success_SingleKey() {
Map<String, Object> innerData = new HashMap<>();
innerData.put("innerKey1", "value1");
Map<String, Object> data = new HashMap<>();
data.put("key1", innerData);
JDocument jDocument = new JDocument(data);
Set<String> keys = jDocument.getKeys("key1");
assertNotNull(keys);
assertEquals(1, keys.size());
assertTrue(keys.contains("innerKey1"));
}
@Test
public void testGetKeys_Failure_KeyNotFound() {
JDocument jDocument = new JDocument(Collections.emptyMap());
Set<String> keys = jDocument.getKeys("key1");
assertNotNull(keys);
assertTrue(keys.isEmpty());
}
@Test
public void testGetKeys_Success_MultipleKeys() {
Map<String, Object> innerData = new HashMap<>();
innerData.put("innerKey1", "value1");
innerData.put("innerKey2", "value2");
Map<String, Object> data = new HashMap<>();
data.put("key1", innerData);
JDocument jDocument = new JDocument(data);
Set<String> keys = jDocument.getKeys("key1");
assertNotNull(keys);
assertEquals(2, keys.size());
assertTrue(keys.contains("innerKey1"));
assertTrue(keys.contains("innerKey2"));
}
/**
* The getString method in the JDocument class is being tested here.
* It returns a String value of the specified key from the JDocument's data.
*/
@Test
public void testGetString_Success_SingleKey() {
Map<String, Object> data = new HashMap<>();
data.put("key1", "value1");
JDocument jDocument = new JDocument(data);
String result = jDocument.getString("key1");
assertNotNull(result);
assertEquals("value1", result);
}
@Test
public void testGetString_Failure_KeyNotFound() {
JDocument jDocument = new JDocument(Collections.emptyMap());
String result = jDocument.getString("key1");
assertNotNull(result);
assertEquals("", result);
}
@Test
public void testGetString_Success_NestedKey() {
Map<String, Object> nestedData = new HashMap<>();
nestedData.put("key2", "value2");
Map<String, Object> data = new HashMap<>();
data.put("key1", nestedData);
JDocument jDocument = new JDocument(data);
String result = jDocument.getString("key1.key2");
assertNotNull(result);
assertEquals("value2", result);
}
@Test
public void testGetString_Failure_NonExistentNestedKey() {
Map<String, Object> nestedData = new HashMap<>();
nestedData.put("key2", "value2");
Map<String, Object> data = new HashMap<>();
data.put("key1", nestedData);
JDocument jDocument = new JDocument(data);
String result = jDocument.getString("key1.key3");
assertNotNull(result);
assertEquals("", result);
}
/**
* The getBoolean method in the JDocument class is being tested here.
* It retrieves a boolean value of the specified key from the JDocument's data.
*/
@Test
public void testGetBoolean_Success_SingleKey() {
Map<String, Object> data = new HashMap<>();
data.put("key1", true);
JDocument jDocument = new JDocument(data);
boolean result = jDocument.getBoolean("key1");
assertTrue(result);
}
@Test
public void testGetBoolean_Failure_KeyNotFound() {
JDocument jDocument = new JDocument(Collections.emptyMap());
boolean result = jDocument.getBoolean("key1");
assertFalse(result);
}
@Test
public void testGetBoolean_Success_NestedKey() {
Map<String, Object> nestedData = new HashMap<>();
nestedData.put("key2", true);
Map<String, Object> data = new HashMap<>();
data.put("key1", nestedData);
JDocument jDocument = new JDocument(data);
boolean result = jDocument.getBoolean("key1.key2");
assertTrue(result);
}
@Test
public void testGetBoolean_Failure_NonExistentNestedKey() {
Map<String, Object> nestedData = new HashMap<>();
nestedData.put("key2", true);
Map<String, Object> data = new HashMap<>();
data.put("key1", nestedData);
JDocument jDocument = new JDocument(data);
boolean result = jDocument.getBoolean("key1.key3");
assertFalse(result);
}
/**
* The getByte method in the JDocument class is being tested here.
* It retrieves a byte value of the specified key from the JDocument's data.
*/
@Test
public void testGetByte_Success_SingleKey() {
Map<String, Object> data = new HashMap<>();
data.put("key1", (byte) 1);
JDocument jDocument = new JDocument(data);
byte result = jDocument.getByte("key1");
assertEquals((byte) 1, result);
}
@Test
public void testGetByte_Failure_KeyNotFound() {
JDocument jDocument = new JDocument(Collections.emptyMap());
byte result = jDocument.getByte("key1");
assertEquals((byte) 0, result);
}
@Test
public void testGetByte_Success_NestedKey() {
Map<String, Object> nestedData = new HashMap<>();
nestedData.put("key2", (byte) 2);
Map<String, Object> data = new HashMap<>();
data.put("key1", nestedData);
JDocument jDocument = new JDocument(data);
byte result = jDocument.getByte("key1.key2");
assertEquals((byte) 2, result);
}
@Test
public void testGetByte_Failure_NonExistentNestedKey() {
Map<String, Object> nestedData = new HashMap<>();
nestedData.put("key2", (byte) 2);
Map<String, Object> data = new HashMap<>();
data.put("key1", nestedData);
JDocument jDocument = new JDocument(data);
byte result = jDocument.getByte("key1.key3");
assertEquals((byte) 0, result);
}
/**
* The getShort method in the JDocument class is being tested here.
* It retrieves a short value of the specified key from the JDocument's data.
*/
@Test
public void testGetShort_Success_SingleKey() {
Map<String, Object> data = new HashMap<>();
data.put("key1", (short) 1);
JDocument jDocument = new JDocument(data);
short result = jDocument.getShort("key1");
assertEquals((short) 1, result);
}
@Test
public void testGetShort_Failure_KeyNotFound() {
JDocument jDocument = new JDocument(Collections.emptyMap());
short result = jDocument.getShort("key1");
assertEquals((short) 0, result);
}
@Test
public void testGetShort_Success_NestedKey() {
Map<String, Object> nestedData = new HashMap<>();
nestedData.put("key2", (short) 2);
Map<String, Object> data = new HashMap<>();
data.put("key1", nestedData);
JDocument jDocument = new JDocument(data);
short result = jDocument.getShort("key1.key2");
assertEquals((short) 2, result);
}
@Test
public void testGetShort_Failure_NonExistentNestedKey() {
Map<String, Object> nestedData = new HashMap<>();
nestedData.put("key2", (short) 2);
Map<String, Object> data = new HashMap<>();
data.put("key1", nestedData);
JDocument jDocument = new JDocument(data);
short result = jDocument.getShort("key1.key3");
assertEquals((short) 0, result);
}
/**
* The getInt method in the JDocument class is being tested here.
* It retrieves an integer value of the specified key from the JDocument's data.
*/
@Test
public void testGetInt_Success_SingleKey() {
Map<String, Object> data = new HashMap<>();
data.put("key1", 123);
JDocument jDocument = new JDocument(data);
int result = jDocument.getInt("key1");
assertEquals(123, result);
}
@Test
public void testGetInt_Failure_KeyNotFound() {
JDocument jDocument = new JDocument(Collections.emptyMap());
int result = jDocument.getInt("key1");
assertEquals(0, result);
}
@Test
public void testGetInt_Success_NestedKey() {
Map<String, Object> nestedData = new HashMap<>();
nestedData.put("key2", 456);
Map<String, Object> data = new HashMap<>();
data.put("key1", nestedData);
JDocument jDocument = new JDocument(data);
int result = jDocument.getInt("key1.key2");
assertEquals(456, result);
}
@Test
public void testGetInt_Failure_NonExistentNestedKey() {
Map<String, Object> nestedData = new HashMap<>();
nestedData.put("key2", 456);
Map<String, Object> data = new HashMap<>();
data.put("key1", nestedData);
JDocument jDocument = new JDocument(data);
int result = jDocument.getInt("key1.key3");
assertEquals(0, result);
}
/**
* The getLong method in the JDocument class is being tested here.
* It retrieves a long value of the specified key from the JDocument's data.
*/
@Test
public void testGetLong_Success_SingleKey() {
Map<String, Object> data = new HashMap<>();
data.put("key1", 123L);
JDocument jDocument = new JDocument(data);
long result = jDocument.getLong("key1");
assertEquals(123L, result);
}
@Test
public void testGetLong_Failure_KeyNotFound() {
JDocument jDocument = new JDocument(Collections.emptyMap());
long result = jDocument.getLong("key1");
assertEquals(0L, result);
}
@Test
public void testGetLong_Success_NestedKey() {
Map<String, Object> nestedData = new HashMap<>();
nestedData.put("key2", 456L);
Map<String, Object> data = new HashMap<>();
data.put("key1", nestedData);
JDocument jDocument = new JDocument(data);
long result = jDocument.getLong("key1.key2");
assertEquals(456L, result);
}
@Test
public void testGetLong_Failure_NonExistentNestedKey() {
Map<String, Object> nestedData = new HashMap<>();
nestedData.put("key2", 456L);
Map<String, Object> data = new HashMap<>();
data.put("key1", nestedData);
JDocument jDocument = new JDocument(data);
long result = jDocument.getLong("key1.key3");
assertEquals(0L, result);
}
/**
* The getFloat method in the JDocument class is being tested here.
* It retrieves a float value of the specified key from the JDocument's data.
*/
@Test
public void testGetFloat_Success_SingleKey() {
Map<String, Object> data = new HashMap<>();
data.put("key1", 1.23f);
JDocument jDocument = new JDocument(data);
float result = jDocument.getFloat("key1");
assertEquals(1.23f, result);
}
@Test
public void testGetFloat_Failure_KeyNotFound() {
JDocument jDocument = new JDocument(Collections.emptyMap());
float result = jDocument.getFloat("key1");
assertEquals(0f, result);
}
@Test
public void testGetFloat_Success_NestedKey() {
Map<String, Object> nestedData = new HashMap<>();
nestedData.put("key2", 4.56f);
Map<String, Object> data = new HashMap<>();
data.put("key1", nestedData);
JDocument jDocument = new JDocument(data);
float result = jDocument.getFloat("key1.key2");
assertEquals(4.56f, result);
}
@Test
public void testGetFloat_Failure_NonExistentNestedKey() {
Map<String, Object> nestedData = new HashMap<>();
nestedData.put("key2", 4.56f);
Map<String, Object> data = new HashMap<>();
data.put("key1", nestedData);
JDocument jDocument = new JDocument(data);
float result = jDocument.getFloat("key1.key3");
assertEquals(0f, result);
}
/**
* The getDouble method in the JDocument class is being tested here.
* It retrieves a double value of the specified key from the JDocument's data.
*/
@Test
public void testGetDouble_Success_SingleKey() {
Map<String, Object> data = new HashMap<>();
data.put("key1", 1.23d);
JDocument jDocument = new JDocument(data);
double result = jDocument.getDouble("key1");
assertEquals(1.23d, result);
}
@Test
public void testGetDouble_Failure_KeyNotFound() {
JDocument jDocument = new JDocument(Collections.emptyMap());
double result = jDocument.getDouble("key1");
assertEquals(0d, result);
}
@Test
public void testGetDouble_Success_NestedKey() {
Map<String, Object> nestedData = new HashMap<>();
nestedData.put("key2", 4.56d);
Map<String, Object> data = new HashMap<>();
data.put("key1", nestedData);
JDocument jDocument = new JDocument(data);
double result = jDocument.getDouble("key1.key2");
assertEquals(4.56d, result);
}
@Test
public void testGetDouble_Failure_NonExistentNestedKey() {
Map<String, Object> nestedData = new HashMap<>();
nestedData.put("key2", 4.56d);
Map<String, Object> data = new HashMap<>();
data.put("key1", nestedData);
JDocument jDocument = new JDocument(data);
double result = jDocument.getDouble("key1.key3");
assertEquals(0d, result);
}
}

46
libraries/packets/.gitignore vendored Normal file
View File

@@ -0,0 +1,46 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
## run paper
**/run/
### IntelliJ IDEA ###
.idea/
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

View File

@@ -0,0 +1,58 @@
![](fancysitula_title.png)
## Using Minecraft internals made easier.
Initially developed for FancyNpcs and FancyHolograms, FancySitula is a library that simplifies the process of
interacting with Minecraft internals. It provides a simple and easy to use API to interact with Minecraft packets and
entities.
## Features
- Easy to use API for creating and sending packets
- Powerful factory classes for creating packets
- Wrapper classes for Minecraft packets and entities
- Support for multiple Minecraft versions
- No third-party dependencies and works out of the box
## Supported packets
The following packets are currently supported:
- ClientboundAddEntityPacket
- ClientboundPlayerInfoRemovePacket
- ClientboundPlayerInfoUpdatePacket
- ClientboundRemoveEntitiesPacket
- ClientboundRotateHeadPacket
- ClientboundSetEntityDataPacket
- ClientboundSetEquipmentPacket
- ClientboundTeleportEntityPacket
More packets will be added when needed / requested (contributions are welcome).
## Supported Minecraft versions
FancySitula will support the latest Minecraft version and additional older versions. The following versions are
supported:
- [x] 1.21.1
- [x] 1.21
- [x] 1.20.6
- [x] 1.20.5
- [ ] 1.20.4
- [ ] 1.20.2
- [ ] 1.20.1
## Missing Packets for FancyNpcs
- [x] ClientboundPlayerInfoUpdatePacket
- [x] ClientboundAddEntityPacket
- [x] ClientboundPlayerInfoRemovePacket
- [x] ClientboundRemoveEntitiesPacket
- [x] ClientboundTeleportEntityPacket
- [x] ClientboundRotateHeadPacket
- [x] ClientboundSetEquipmentPacket
- [x] ClientboundSetEntityDataPacket
- [x] ClientboundSetPlayerTeamPacket
- [ ] ClientboundUpdateAttributesPacket
- [ ] ClientboundAnimatePacket
- [ ] ClientboundSetPassengersPacket

View File

@@ -0,0 +1,25 @@
plugins {
id("java-library")
}
dependencies {
compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT")
compileOnly("de.oliver.FancyAnalytics:logger:0.0.4")
}
tasks {
java {
withSourcesJar()
withJavadocJar()
}
javadoc {
options.encoding = Charsets.UTF_8.name()
}
compileJava {
options.encoding = Charsets.UTF_8.name()
options.release = 21
}
}

View File

@@ -0,0 +1,7 @@
package de.oliver.fancysitula.api;
import de.oliver.fancyanalytics.logger.ExtendedFancyLogger;
public interface IFancySitula {
ExtendedFancyLogger LOGGER = new ExtendedFancyLogger("FancySitula");
}

View File

@@ -0,0 +1,31 @@
package de.oliver.fancysitula.api.entities;
import de.oliver.fancysitula.api.packets.FS_ClientboundSetEntityDataPacket;
import de.oliver.fancysitula.api.utils.entityData.FS_BlockDisplayData;
import org.bukkit.entity.EntityType;
import java.util.List;
public class FS_BlockDisplay extends FS_Display {
protected FS_ClientboundSetEntityDataPacket.EntityData blockData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_BlockDisplayData.BLOCK, null);
public FS_BlockDisplay() {
super(EntityType.BLOCK_DISPLAY);
}
public org.bukkit.block.BlockState getBlock() {
return (org.bukkit.block.BlockState) this.blockData.getValue();
}
public void setBlock(org.bukkit.block.BlockState block) {
this.blockData.setValue(block);
}
@Override
public List<FS_ClientboundSetEntityDataPacket.EntityData> getEntityData() {
List<FS_ClientboundSetEntityDataPacket.EntityData> entityData = super.getEntityData();
entityData.add(this.blockData);
return entityData;
}
}

View File

@@ -0,0 +1,214 @@
package de.oliver.fancysitula.api.entities;
import de.oliver.fancysitula.api.packets.FS_ClientboundSetEntityDataPacket;
import de.oliver.fancysitula.api.utils.entityData.FS_DisplayData;
import org.bukkit.entity.EntityType;
import java.util.List;
public class FS_Display extends FS_Entity {
protected FS_ClientboundSetEntityDataPacket.EntityData transformationInterpolationStartDeltaTicksData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_DisplayData.TRANSFORMATION_INTERPOLATION_START_DELTA_TICKS, null);
protected FS_ClientboundSetEntityDataPacket.EntityData transformationInterpolationDurationData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_DisplayData.TRANSFORMATION_INTERPOLATION_DURATION, null);
protected FS_ClientboundSetEntityDataPacket.EntityData posRotInterpolationDurationData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_DisplayData.POS_ROT_INTERPOLATION_DURATION, null);
protected FS_ClientboundSetEntityDataPacket.EntityData translationData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_DisplayData.TRANSLATION, null);
protected FS_ClientboundSetEntityDataPacket.EntityData scaleData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_DisplayData.SCALE, null);
protected FS_ClientboundSetEntityDataPacket.EntityData leftRotationData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_DisplayData.LEFT_ROTATION, null);
protected FS_ClientboundSetEntityDataPacket.EntityData rightRotationData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_DisplayData.RIGHT_ROTATION, null);
protected FS_ClientboundSetEntityDataPacket.EntityData billboardRenderConstraintsData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_DisplayData.BILLBOARD_RENDER_CONSTRAINTS, null);
protected FS_ClientboundSetEntityDataPacket.EntityData brightnessOverrideData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_DisplayData.BRIGHTNESS_OVERRIDE, null);
protected FS_ClientboundSetEntityDataPacket.EntityData viewRangeData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_DisplayData.VIEW_RANGE, null);
protected FS_ClientboundSetEntityDataPacket.EntityData shadowRadiusData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_DisplayData.SHADOW_RADIUS, null);
protected FS_ClientboundSetEntityDataPacket.EntityData shadowStrengthData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_DisplayData.SHADOW_STRENGTH, null);
protected FS_ClientboundSetEntityDataPacket.EntityData widthData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_DisplayData.WIDTH, null);
protected FS_ClientboundSetEntityDataPacket.EntityData heightData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_DisplayData.HEIGHT, null);
protected FS_ClientboundSetEntityDataPacket.EntityData glowColorOverrideData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_DisplayData.GLOW_COLOR_OVERRIDE, null);
public FS_Display(EntityType type) {
super(type);
}
public int getTransformationInterpolationStartDeltaTicks() {
return (int) this.transformationInterpolationStartDeltaTicksData.getValue();
}
public void setTransformationInterpolationStartDeltaTicks(int transformationInterpolationStartDeltaTicks) {
this.transformationInterpolationStartDeltaTicksData.setValue(transformationInterpolationStartDeltaTicks);
}
public int getTransformationInterpolationDuration() {
return (int) this.transformationInterpolationDurationData.getValue();
}
public void setTransformationInterpolationDuration(int transformationInterpolationDuration) {
this.transformationInterpolationDurationData.setValue(transformationInterpolationDuration);
}
public int getPosRotInterpolationDuration() {
return (int) this.posRotInterpolationDurationData.getValue();
}
public void setPosRotInterpolationDuration(int posRotInterpolationDuration) {
this.posRotInterpolationDurationData.setValue(posRotInterpolationDuration);
}
public org.joml.Vector3f getTranslation() {
return (org.joml.Vector3f) this.translationData.getValue();
}
public void setTranslation(org.joml.Vector3f translation) {
this.translationData.setValue(translation);
}
public org.joml.Vector3f getScale() {
return (org.joml.Vector3f) this.scaleData.getValue();
}
public void setScale(org.joml.Vector3f scale) {
this.scaleData.setValue(scale);
}
public org.joml.Quaternionf getLeftRotation() {
return (org.joml.Quaternionf) this.leftRotationData.getValue();
}
public void setLeftRotation(org.joml.Quaternionf leftRotation) {
this.leftRotationData.setValue(leftRotation);
}
public org.joml.Quaternionf getRightRotation() {
return (org.joml.Quaternionf) this.rightRotationData.getValue();
}
public void setRightRotation(org.joml.Quaternionf rightRotation) {
this.rightRotationData.setValue(rightRotation);
}
public byte getBillboardRenderConstraints() {
return (byte) this.billboardRenderConstraintsData.getValue();
}
public void setBillboardRenderConstraints(byte billboardRenderConstraints) {
this.billboardRenderConstraintsData.setValue(billboardRenderConstraints);
}
public Billboard getBillboard() {
return Billboard.getById(getBillboardRenderConstraints());
}
public void setBillboard(Billboard billboard) {
this.billboardRenderConstraintsData.setValue(billboard.getId());
}
public int getBrightnessOverride() {
return (int) this.brightnessOverrideData.getValue();
}
public void setBrightnessOverride(int brightnessOverride) {
this.brightnessOverrideData.setValue(brightnessOverride);
}
public float getViewRange() {
return (float) this.viewRangeData.getValue();
}
public void setViewRange(float viewRange) {
this.viewRangeData.setValue(viewRange);
}
public float getShadowRadius() {
return (float) this.shadowRadiusData.getValue();
}
public void setShadowRadius(float shadowRadius) {
this.shadowRadiusData.setValue(shadowRadius);
}
public float getShadowStrength() {
return (float) this.shadowStrengthData.getValue();
}
public void setShadowStrength(float shadowStrength) {
this.shadowStrengthData.setValue(shadowStrength);
}
public int getWidth() {
return (int) this.widthData.getValue();
}
public void setWidth(int width) {
this.widthData.setValue(width);
}
public int getHeight() {
return (int) this.heightData.getValue();
}
public void setHeight(int height) {
this.heightData.setValue(height);
}
public int getGlowColorOverride() {
return (int) this.glowColorOverrideData.getValue();
}
public void setGlowColorOverride(int glowColorOverride) {
this.glowColorOverrideData.setValue(glowColorOverride);
}
@Override
public List<FS_ClientboundSetEntityDataPacket.EntityData> getEntityData() {
List<FS_ClientboundSetEntityDataPacket.EntityData> entityData = super.getEntityData();
entityData.add(this.transformationInterpolationStartDeltaTicksData);
entityData.add(this.transformationInterpolationDurationData);
entityData.add(this.posRotInterpolationDurationData);
entityData.add(this.translationData);
entityData.add(this.scaleData);
entityData.add(this.leftRotationData);
entityData.add(this.rightRotationData);
entityData.add(this.billboardRenderConstraintsData);
entityData.add(this.brightnessOverrideData);
entityData.add(this.viewRangeData);
entityData.add(this.shadowRadiusData);
entityData.add(this.shadowStrengthData);
entityData.add(this.widthData);
entityData.add(this.heightData);
entityData.add(this.glowColorOverrideData);
return entityData;
}
public enum Billboard {
FIXED((byte) 0, "fixed"),
VERTICAL((byte) 1, "vertical"),
HORIZONTAL((byte) 2, "horizontal"),
CENTER((byte) 3, "center"),
;
private final byte id;
private final String name;
Billboard(byte id, String name) {
this.id = id;
this.name = name;
}
public static Billboard getById(byte id) {
for (Billboard value : values()) {
if (value.getId() == id) {
return value;
}
}
return null;
}
public byte getId() {
return id;
}
public String getName() {
return name;
}
}
}

View File

@@ -0,0 +1,246 @@
package de.oliver.fancysitula.api.entities;
import de.oliver.fancysitula.api.packets.FS_ClientboundSetEntityDataPacket;
import de.oliver.fancysitula.api.utils.entityData.FS_EntityData;
import net.kyori.adventure.text.Component;
import org.bukkit.Location;
import org.bukkit.entity.EntityType;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public class FS_Entity {
private static int entityCount = Integer.MAX_VALUE / 2;
protected int id = entityCount++;
protected UUID uuid = UUID.randomUUID();
protected EntityType type;
protected double x = 0;
protected double y = 0;
protected double z = 0;
protected float yaw = 0;
protected float pitch = 0;
protected float headYaw = 0;
protected int velocityX = 0;
protected int velocityY = 0;
protected int velocityZ = 0;
protected int data = 0;
protected FS_ClientboundSetEntityDataPacket.EntityData sharedFlagsData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_EntityData.SHARED_FLAGS, null);
protected FS_ClientboundSetEntityDataPacket.EntityData airSupplyData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_EntityData.AIR_SUPPLY, null);
protected FS_ClientboundSetEntityDataPacket.EntityData customNameData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_EntityData.CUSTOM_NAME, null);
protected FS_ClientboundSetEntityDataPacket.EntityData customNameVisibleData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_EntityData.CUSTOM_NAME_VISIBLE, null);
protected FS_ClientboundSetEntityDataPacket.EntityData silentData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_EntityData.SILENT, null);
protected FS_ClientboundSetEntityDataPacket.EntityData noGravityData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_EntityData.NO_GRAVITY, null);
protected FS_ClientboundSetEntityDataPacket.EntityData ticksFrozenData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_EntityData.TICKS_FROZEN, null);
public FS_Entity(EntityType type) {
this.type = type;
}
// Entity data
public byte getSharedFlags() {
return (byte) sharedFlagsData.getValue();
}
public void setSharedFlags(byte sharedFlags) {
this.sharedFlagsData.setValue(sharedFlags);
}
public byte getAirSupply() {
return (byte) airSupplyData.getValue();
}
public void setAirSupply(byte airSupply) {
this.airSupplyData.setValue(airSupply);
}
public Optional<Component> getCustomName() {
return (Optional<Component>) customNameData.getValue();
}
public void setCustomName(Optional<Component> customName) {
this.customNameData.setValue(customName);
}
public boolean getCustomNameVisible() {
return (boolean) customNameVisibleData.getValue();
}
public void setCustomNameVisible(boolean customNameVisible) {
this.customNameVisibleData.setValue(customNameVisible);
}
public boolean getSilent() {
return (boolean) silentData.getValue();
}
public void setSilent(boolean silent) {
this.silentData.setValue(silent);
}
public boolean getNoGravity() {
return (boolean) noGravityData.getValue();
}
public void setNoGravity(boolean noGravity) {
this.noGravityData.setValue(noGravity);
}
public int getTicksFrozen() {
return (int) ticksFrozenData.getValue();
}
public void setTicksFrozen(int ticksFrozen) {
this.ticksFrozenData.setValue(ticksFrozen);
}
public List<FS_ClientboundSetEntityDataPacket.EntityData> getEntityData() {
List<FS_ClientboundSetEntityDataPacket.EntityData> entityData = new ArrayList<>();
entityData.add(this.sharedFlagsData);
entityData.add(this.airSupplyData);
entityData.add(this.customNameData);
entityData.add(this.customNameVisibleData);
entityData.add(this.silentData);
entityData.add(this.noGravityData);
entityData.add(this.ticksFrozenData);
return entityData;
}
// Getter and Setter for all fields
public void setLocation(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
public void setLocation(Location location) {
setLocation(location.getX(), location.getY(), location.getZ());
setRotation(location.getYaw(), location.getPitch());
}
public void setRotation(float yaw, float pitch) {
this.yaw = yaw;
this.pitch = pitch;
}
public void setVelocity(int velocityX, int velocityY, int velocityZ) {
this.velocityX = velocityX;
this.velocityY = velocityY;
this.velocityZ = velocityZ;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public UUID getUuid() {
return uuid;
}
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
public EntityType getType() {
return type;
}
public void setType(EntityType type) {
this.type = type;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public double getZ() {
return z;
}
public void setZ(double z) {
this.z = z;
}
public float getYaw() {
return yaw;
}
public void setYaw(float yaw) {
this.yaw = yaw;
}
public float getPitch() {
return pitch;
}
public void setPitch(float pitch) {
this.pitch = pitch;
}
public float getHeadYaw() {
return headYaw;
}
public void setHeadYaw(float headYaw) {
this.headYaw = headYaw;
}
public int getVelocityX() {
return velocityX;
}
public void setVelocityX(int velocityX) {
this.velocityX = velocityX;
}
public int getVelocityY() {
return velocityY;
}
public void setVelocityY(int velocityY) {
this.velocityY = velocityY;
}
public int getVelocityZ() {
return velocityZ;
}
public void setVelocityZ(int velocityZ) {
this.velocityZ = velocityZ;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
}

View File

@@ -0,0 +1,31 @@
package de.oliver.fancysitula.api.entities;
import de.oliver.fancysitula.api.packets.FS_ClientboundSetEntityDataPacket;
import de.oliver.fancysitula.api.utils.entityData.FS_ItemDisplayData;
import org.bukkit.entity.EntityType;
import java.util.List;
public class FS_ItemDisplay extends FS_Display {
protected FS_ClientboundSetEntityDataPacket.EntityData itemData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_ItemDisplayData.ITEM, null);
public FS_ItemDisplay() {
super(EntityType.ITEM_DISPLAY);
}
public org.bukkit.inventory.ItemStack getItem() {
return (org.bukkit.inventory.ItemStack) this.itemData.getValue();
}
public void setItem(org.bukkit.inventory.ItemStack item) {
this.itemData.setValue(item);
}
@Override
public List<FS_ClientboundSetEntityDataPacket.EntityData> getEntityData() {
List<FS_ClientboundSetEntityDataPacket.EntityData> entityData = super.getEntityData();
entityData.add(this.itemData);
return entityData;
}
}

View File

@@ -0,0 +1,31 @@
package de.oliver.fancysitula.api.entities;
import de.oliver.fancysitula.api.utils.FS_EquipmentSlot;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class FS_Player extends FS_Entity {
protected Map<FS_EquipmentSlot, ItemStack> equipment;
public FS_Player() {
super(EntityType.PLAYER);
this.equipment = new ConcurrentHashMap<>();
}
public Map<FS_EquipmentSlot, ItemStack> getEquipment() {
return equipment;
}
public void setEquipment(Map<FS_EquipmentSlot, ItemStack> equipment) {
this.equipment = equipment;
}
public void setEquipment(FS_EquipmentSlot slot, ItemStack item) {
this.equipment.put(slot, item);
}
}

View File

@@ -0,0 +1,40 @@
package de.oliver.fancysitula.api.entities;
import de.oliver.fancysitula.api.packets.FS_ClientboundPacket;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Represents a real player
*/
public class FS_RealPlayer {
@NotNull
private final Player bukkitPlayer;
/**
* Creates a new FS_RealPlayer instance
* Must have a real bukkit player instance
* Used for sending packets to the player
*
* @param bukkitPlayer the bukkit player instance
*/
public FS_RealPlayer(@NotNull Player bukkitPlayer) {
this.bukkitPlayer = bukkitPlayer;
}
/**
* Sends a packet to the player
* Must have a real bukkit player instance
*
* @param packet the packet to send
*/
public void sendPacket(FS_ClientboundPacket packet) {
packet.send(this);
}
public @NotNull Player getBukkitPlayer() {
return bukkitPlayer;
}
}

View File

@@ -0,0 +1,123 @@
package de.oliver.fancysitula.api.entities;
import de.oliver.fancysitula.api.packets.FS_ClientboundSetEntityDataPacket;
import de.oliver.fancysitula.api.utils.entityData.FS_TextDisplayData;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.EntityType;
import java.util.List;
public class FS_TextDisplay extends FS_Display {
protected FS_ClientboundSetEntityDataPacket.EntityData textData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_TextDisplayData.TEXT, null);
protected FS_ClientboundSetEntityDataPacket.EntityData lineWidthData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_TextDisplayData.LINE_WIDTH, null);
protected FS_ClientboundSetEntityDataPacket.EntityData backgroundData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_TextDisplayData.BACKGROUND, null);
protected FS_ClientboundSetEntityDataPacket.EntityData textOpacityData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_TextDisplayData.TEXT_OPACITY, null);
protected FS_ClientboundSetEntityDataPacket.EntityData styleFlagsData = new FS_ClientboundSetEntityDataPacket.EntityData(FS_TextDisplayData.STYLE_FLAGS, null);
public FS_TextDisplay() {
super(EntityType.TEXT_DISPLAY);
}
public Component getText() {
return (Component) this.textData.getValue();
}
public void setText(Component text) {
this.textData.setValue(text);
}
public int getLineWidth() {
return (int) this.lineWidthData.getValue();
}
public void setLineWidth(int lineWidth) {
this.lineWidthData.setValue(lineWidth);
}
public int getBackground() {
return (int) this.backgroundData.getValue();
}
public void setBackground(int background) {
this.backgroundData.setValue(background);
}
public byte getTextOpacity() {
return (byte) this.textOpacityData.getValue();
}
public void setTextOpacity(byte textOpacity) {
this.textOpacityData.setValue(textOpacity);
}
public byte getStyleFlags() {
return (byte) this.styleFlagsData.getValue();
}
public void setStyleFlags(byte styleFlags) {
this.styleFlagsData.setValue(styleFlags);
}
public void setStyleFlag(byte flag, boolean value) {
byte styleFlags = getStyleFlags();
if (value) {
this.styleFlagsData.setValue((byte) (styleFlags | flag));
} else {
this.styleFlagsData.setValue((byte) (styleFlags & ~flag));
}
}
public boolean hasStyleFlag(byte flag) {
return (getStyleFlags() & flag) == flag;
}
public void setShadow(boolean shadow) {
setStyleFlag((byte) 1, shadow);
}
public boolean hasShadow() {
return hasStyleFlag((byte) 1);
}
public boolean isSeeThrough() {
return hasStyleFlag((byte) 2);
}
public void setSeeThrough(boolean seeThrough) {
setStyleFlag((byte) 2, seeThrough);
}
public void setUseDefaultBackground(boolean defaultBackground) {
setStyleFlag((byte) 4, defaultBackground);
}
public boolean isUsingDefaultBackground() {
return hasStyleFlag((byte) 4);
}
public boolean isAlignLeft() {
return hasStyleFlag((byte) 8);
}
public void setAlignLeft(boolean alignLeft) {
setStyleFlag((byte) 8, alignLeft);
}
public void setAlignRight(boolean alignRight) {
setStyleFlag((byte) 16, alignRight);
}
@Override
public List<FS_ClientboundSetEntityDataPacket.EntityData> getEntityData() {
List<FS_ClientboundSetEntityDataPacket.EntityData> entityData = super.getEntityData();
entityData.add(this.textData);
entityData.add(this.lineWidthData);
entityData.add(this.backgroundData);
entityData.add(this.textOpacityData);
entityData.add(this.styleFlagsData);
return entityData;
}
}

View File

@@ -0,0 +1,166 @@
package de.oliver.fancysitula.api.packets;
import org.bukkit.entity.EntityType;
import java.util.UUID;
/**
* Used to add an entity to the client's world.
*/
public abstract class FS_ClientboundAddEntityPacket extends FS_ClientboundPacket {
protected int entityId;
protected UUID entityUUID;
protected EntityType entityType;
protected double x;
protected double y;
protected double z;
protected float yaw;
protected float pitch;
protected float headYaw;
protected int velocityX;
protected int velocityY;
protected int velocityZ;
protected int data;
/**
* @param pitch in degrees (0 - 360)
* @param headYaw in degrees (0 - 360)
*/
public FS_ClientboundAddEntityPacket(
int entityId,
UUID entityUUID,
EntityType entityType,
double x,
double y,
double z,
float yaw,
float pitch,
float headYaw,
int velocityX,
int velocityY,
int velocityZ,
int data) {
this.entityId = entityId;
this.entityUUID = entityUUID;
this.entityType = entityType;
this.x = x;
this.y = y;
this.z = z;
this.yaw = yaw;
this.pitch = pitch;
this.headYaw = headYaw;
this.velocityX = velocityX;
this.velocityY = velocityY;
this.velocityZ = velocityZ;
this.data = data;
}
public int getEntityId() {
return entityId;
}
public void setEntityId(int entityId) {
this.entityId = entityId;
}
public UUID getEntityUUID() {
return entityUUID;
}
public void setEntityUUID(UUID entityUUID) {
this.entityUUID = entityUUID;
}
public EntityType getEntityType() {
return entityType;
}
public void setEntityType(EntityType entityType) {
this.entityType = entityType;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public double getZ() {
return z;
}
public void setZ(double z) {
this.z = z;
}
public float getYaw() {
return yaw;
}
public void setYaw(float yaw) {
this.yaw = yaw;
}
public float getPitch() {
return pitch;
}
public void setPitch(float pitch) {
this.pitch = pitch;
}
public float getHeadYaw() {
return headYaw;
}
public void setHeadYaw(float headYaw) {
this.headYaw = headYaw;
}
public int getVelocityX() {
return velocityX;
}
public void setVelocityX(int velocityX) {
this.velocityX = velocityX;
}
public int getVelocityY() {
return velocityY;
}
public void setVelocityY(int velocityY) {
this.velocityY = velocityY;
}
public int getVelocityZ() {
return velocityZ;
}
public void setVelocityZ(int velocityZ) {
this.velocityZ = velocityZ;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
}

View File

@@ -0,0 +1,324 @@
package de.oliver.fancysitula.api.packets;
import de.oliver.fancysitula.api.teams.FS_CollisionRule;
import de.oliver.fancysitula.api.teams.FS_NameTagVisibility;
import net.kyori.adventure.text.Component;
import java.util.List;
public abstract class FS_ClientboundCreateOrUpdateTeamPacket extends FS_ClientboundPacket {
protected Method method;
protected String teamName;
protected CreateTeam createTeam;
protected RemoveTeam removeTeam;
protected UpdateTeam updateTeam;
protected AddEntity addEntity;
protected RemoveEntity removeEntity;
public FS_ClientboundCreateOrUpdateTeamPacket(String teamName, CreateTeam createTeam) {
this.method = Method.CREATE_TEAM;
this.teamName = teamName;
this.createTeam = createTeam;
}
public FS_ClientboundCreateOrUpdateTeamPacket(String teamName, RemoveTeam removeTeam) {
this.method = Method.REMOVE_TEAM;
this.teamName = teamName;
this.removeTeam = removeTeam;
}
public FS_ClientboundCreateOrUpdateTeamPacket(String teamName, UpdateTeam updateTeam) {
this.method = Method.UPDATE_TEAM;
this.teamName = teamName;
this.updateTeam = updateTeam;
}
public FS_ClientboundCreateOrUpdateTeamPacket(String teamName, AddEntity addEntity) {
this.method = Method.ADD_ENTITY;
this.teamName = teamName;
this.addEntity = addEntity;
}
public FS_ClientboundCreateOrUpdateTeamPacket(String teamName, RemoveEntity removeEntity) {
this.method = Method.REMOVE_ENTITY;
this.teamName = teamName;
this.removeEntity = removeEntity;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public String getTeamName() {
return teamName;
}
public void setTeamName(String teamName) {
this.teamName = teamName;
}
public CreateTeam getCreateTeam() {
return createTeam;
}
public void setCreateTeam(CreateTeam createTeam) {
this.createTeam = createTeam;
}
public UpdateTeam getUpdateTeam() {
return updateTeam;
}
public void setUpdateTeam(UpdateTeam updateTeam) {
this.updateTeam = updateTeam;
}
public AddEntity getAddEntity() {
return addEntity;
}
public void setAddEntity(AddEntity addEntity) {
this.addEntity = addEntity;
}
public RemoveEntity getRemoveEntity() {
return removeEntity;
}
public void setRemoveEntity(RemoveEntity removeEntity) {
this.removeEntity = removeEntity;
}
public enum Method {
CREATE_TEAM,
REMOVE_TEAM,
UPDATE_TEAM,
ADD_ENTITY,
REMOVE_ENTITY;
}
public static class CreateTeam {
private Component displayName;
private boolean allowFriendlyFire;
private boolean canSeeFriendlyInvisibles;
private FS_NameTagVisibility nameTagVisibility;
private FS_CollisionRule collisionRule;
private FS_Color color;
private Component prefix;
private Component suffix;
private List<String> entities;
public CreateTeam(Component displayName, boolean allowFriendlyFire, boolean canSeeFriendlyInvisibles, FS_NameTagVisibility nameTagVisibility, FS_CollisionRule collisionRule, FS_Color color, Component prefix, Component suffix, List<String> entities) {
this.displayName = displayName;
this.allowFriendlyFire = allowFriendlyFire;
this.canSeeFriendlyInvisibles = canSeeFriendlyInvisibles;
this.nameTagVisibility = nameTagVisibility;
this.collisionRule = collisionRule;
this.color = color;
this.prefix = prefix;
this.suffix = suffix;
this.entities = entities;
}
public Component getDisplayName() {
return displayName;
}
public void setDisplayName(Component displayName) {
this.displayName = displayName;
}
public boolean isAllowFriendlyFire() {
return allowFriendlyFire;
}
public void setAllowFriendlyFire(boolean allowFriendlyFire) {
this.allowFriendlyFire = allowFriendlyFire;
}
public boolean isCanSeeFriendlyInvisibles() {
return canSeeFriendlyInvisibles;
}
public void setCanSeeFriendlyInvisibles(boolean canSeeFriendlyInvisibles) {
this.canSeeFriendlyInvisibles = canSeeFriendlyInvisibles;
}
public FS_NameTagVisibility getNameTagVisibility() {
return nameTagVisibility;
}
public void setNameTagVisibility(FS_NameTagVisibility nameTagVisibility) {
this.nameTagVisibility = nameTagVisibility;
}
public FS_CollisionRule getCollisionRule() {
return collisionRule;
}
public void setCollisionRule(FS_CollisionRule collisionRule) {
this.collisionRule = collisionRule;
}
public FS_Color getColor() {
return color;
}
public void setColor(FS_Color color) {
this.color = color;
}
public Component getPrefix() {
return prefix;
}
public void setPrefix(Component prefix) {
this.prefix = prefix;
}
public Component getSuffix() {
return suffix;
}
public void setSuffix(Component suffix) {
this.suffix = suffix;
}
public List<String> getEntities() {
return entities;
}
public void setEntities(List<String> entities) {
this.entities = entities;
}
}
public static class RemoveTeam {
}
public static class UpdateTeam {
private Component displayName;
private boolean allowFriendlyFire;
private boolean canSeeFriendlyInvisibles;
private FS_NameTagVisibility nameTagVisibility;
private FS_CollisionRule collisionRule;
private FS_Color color;
private Component prefix;
private Component suffix;
public UpdateTeam(Component displayName, boolean allowFriendlyFire, boolean canSeeFriendlyInvisibles, FS_NameTagVisibility nameTagVisibility, FS_CollisionRule collisionRule, FS_Color color, Component prefix, Component suffix) {
this.displayName = displayName;
this.allowFriendlyFire = allowFriendlyFire;
this.canSeeFriendlyInvisibles = canSeeFriendlyInvisibles;
this.nameTagVisibility = nameTagVisibility;
this.collisionRule = collisionRule;
this.color = color;
this.prefix = prefix;
this.suffix = suffix;
}
public Component getDisplayName() {
return displayName;
}
public void setDisplayName(Component displayName) {
this.displayName = displayName;
}
public boolean isAllowFriendlyFire() {
return allowFriendlyFire;
}
public void setAllowFriendlyFire(boolean allowFriendlyFire) {
this.allowFriendlyFire = allowFriendlyFire;
}
public boolean isCanSeeFriendlyInvisibles() {
return canSeeFriendlyInvisibles;
}
public void setCanSeeFriendlyInvisibles(boolean canSeeFriendlyInvisibles) {
this.canSeeFriendlyInvisibles = canSeeFriendlyInvisibles;
}
public FS_NameTagVisibility getNameTagVisibility() {
return nameTagVisibility;
}
public void setNameTagVisibility(FS_NameTagVisibility nameTagVisibility) {
this.nameTagVisibility = nameTagVisibility;
}
public FS_CollisionRule getCollisionRule() {
return collisionRule;
}
public void setCollisionRule(FS_CollisionRule collisionRule) {
this.collisionRule = collisionRule;
}
public FS_Color getColor() {
return color;
}
public void setColor(FS_Color color) {
this.color = color;
}
public Component getPrefix() {
return prefix;
}
public void setPrefix(Component prefix) {
this.prefix = prefix;
}
public Component getSuffix() {
return suffix;
}
public void setSuffix(Component suffix) {
this.suffix = suffix;
}
}
public static class AddEntity {
private List<String> entities;
public AddEntity(List<String> entities) {
this.entities = entities;
}
public List<String> getEntities() {
return entities;
}
public void setEntities(List<String> entities) {
this.entities = entities;
}
}
public static class RemoveEntity {
private List<String> entities;
public RemoveEntity(List<String> entities) {
this.entities = entities;
}
public List<String> getEntities() {
return entities;
}
public void setEntities(List<String> entities) {
this.entities = entities;
}
}
}

View File

@@ -0,0 +1,31 @@
package de.oliver.fancysitula.api.packets;
import de.oliver.fancysitula.api.IFancySitula;
import de.oliver.fancysitula.api.entities.FS_RealPlayer;
import org.jetbrains.annotations.ApiStatus;
public abstract class FS_ClientboundPacket {
/**
* Creates the packet object.
* For internal use only.
*/
@ApiStatus.Internal
public abstract Object createPacket();
/**
* Sends the packet to the player.
* For internal use only.
*/
@ApiStatus.Internal
protected abstract void sendPacketTo(FS_RealPlayer player);
/**
* Sends the packet to the player.
*/
public final void send(FS_RealPlayer player) {
IFancySitula.LOGGER.debug("Sending packet '" + this.getClass().getSimpleName() + "' to " + player.getBukkitPlayer().getName());
sendPacketTo(player);
}
}

View File

@@ -0,0 +1,24 @@
package de.oliver.fancysitula.api.packets;
import java.util.List;
import java.util.UUID;
/**
* Used to remove players from the client's player list.
*/
public abstract class FS_ClientboundPlayerInfoRemovePacket extends FS_ClientboundPacket {
protected List<UUID> uuids;
public FS_ClientboundPlayerInfoRemovePacket(List<UUID> uuids) {
this.uuids = uuids;
}
public List<UUID> getUuids() {
return uuids;
}
public void setUuids(List<UUID> uuids) {
this.uuids = uuids;
}
}

View File

@@ -0,0 +1,59 @@
package de.oliver.fancysitula.api.packets;
import de.oliver.fancysitula.api.utils.FS_GameProfile;
import de.oliver.fancysitula.api.utils.FS_GameType;
import net.kyori.adventure.text.Component;
import javax.annotation.Nullable;
import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
/**
* Used to update the player list of the client.
*/
public abstract class FS_ClientboundPlayerInfoUpdatePacket extends FS_ClientboundPacket {
protected EnumSet<Action> actions;
protected List<Entry> entries;
public FS_ClientboundPlayerInfoUpdatePacket(EnumSet<Action> actions, List<Entry> entries) {
this.actions = actions;
this.entries = entries;
}
public EnumSet<Action> getActions() {
return actions;
}
public void setActions(EnumSet<Action> actions) {
this.actions = actions;
}
public List<Entry> getEntries() {
return entries;
}
public void setEntries(List<Entry> entries) {
this.entries = entries;
}
public enum Action {
ADD_PLAYER,
INITIALIZE_CHAT,
UPDATE_GAME_MODE,
UPDATE_LISTED,
UPDATE_LATENCY,
UPDATE_DISPLAY_NAME,
}
public record Entry(UUID uuid,
FS_GameProfile profile,
boolean listed,
int latency,
FS_GameType gameMode,
@Nullable Component displayName) {
}
}

View File

@@ -0,0 +1,26 @@
package de.oliver.fancysitula.api.packets;
import java.util.List;
/**
* Used to remove entities from the client's world.
*/
public abstract class FS_ClientboundRemoveEntitiesPacket extends FS_ClientboundPacket {
protected List<Integer> entityIds;
/**
* @param entityIds IDs of the entities to remove
*/
public FS_ClientboundRemoveEntitiesPacket(List<Integer> entityIds) {
this.entityIds = entityIds;
}
public List<Integer> getEntityIds() {
return entityIds;
}
public void setEntityIds(List<Integer> entityIds) {
this.entityIds = entityIds;
}
}

View File

@@ -0,0 +1,28 @@
package de.oliver.fancysitula.api.packets;
public abstract class FS_ClientboundRotateHeadPacket extends FS_ClientboundPacket {
protected int entityId;
protected float headYaw;
public FS_ClientboundRotateHeadPacket(int entityId, float headYaw) {
this.entityId = entityId;
this.headYaw = headYaw;
}
public int getEntityId() {
return entityId;
}
public void setEntityId(int entityId) {
this.entityId = entityId;
}
public float getHeadYaw() {
return headYaw;
}
public void setHeadYaw(float headYaw) {
this.headYaw = headYaw;
}
}

View File

@@ -0,0 +1,67 @@
package de.oliver.fancysitula.api.packets;
import java.util.List;
public abstract class FS_ClientboundSetEntityDataPacket extends FS_ClientboundPacket {
protected int entityId;
protected List<EntityData> entityData;
public FS_ClientboundSetEntityDataPacket(int entityId, List<EntityData> entityData) {
this.entityId = entityId;
this.entityData = entityData;
}
public int getEntityId() {
return entityId;
}
public void setEntityId(int entityId) {
this.entityId = entityId;
}
public List<EntityData> getEntityData() {
return entityData;
}
public void setEntityData(List<EntityData> entityData) {
this.entityData = entityData;
}
public static class EntityData {
private EntityDataAccessor accessor;
private Object value;
/**
* @param accessor can be found in {@link de.oliver.fancysitula.api.utils.entityData}
* @param value must be the correct type for the accessor (see accessor javadoc)
*/
public EntityData(EntityDataAccessor accessor, Object value) {
this.accessor = accessor;
this.value = value;
}
public EntityDataAccessor getAccessor() {
return accessor;
}
public void setAccessor(EntityDataAccessor accessor) {
this.accessor = accessor;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
/**
* @param entityClassName the class name of the entity (e.g. "net.minecraft.world.entity.Display$TextDisplay")
* @param accessorFieldName the field name of the accessor (typically starts with "DATA_" and ends with "_ID")
*/
public record EntityDataAccessor(String entityClassName, String accessorFieldName) {
}
}

View File

@@ -0,0 +1,33 @@
package de.oliver.fancysitula.api.packets;
import de.oliver.fancysitula.api.utils.FS_EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import java.util.Map;
public abstract class FS_ClientboundSetEquipmentPacket extends FS_ClientboundPacket {
protected int entityId;
protected Map<FS_EquipmentSlot, ItemStack> equipment;
public FS_ClientboundSetEquipmentPacket(int entityId, Map<FS_EquipmentSlot, ItemStack> equipment) {
this.entityId = entityId;
this.equipment = equipment;
}
public int getEntityId() {
return entityId;
}
public void setEntityId(int entityId) {
this.entityId = entityId;
}
public Map<FS_EquipmentSlot, ItemStack> getEquipment() {
return equipment;
}
public void setEquipment(Map<FS_EquipmentSlot, ItemStack> equipment) {
this.equipment = equipment;
}
}

View File

@@ -0,0 +1,30 @@
package de.oliver.fancysitula.api.packets;
import java.util.List;
public abstract class FS_ClientboundSetPassengersPacket extends FS_ClientboundPacket {
protected int entityId;
protected List<Integer> passengers;
public FS_ClientboundSetPassengersPacket(int entityId, List<Integer> passengers) {
this.entityId = entityId;
this.passengers = passengers;
}
public int getEntityId() {
return entityId;
}
public void setEntityId(int entityId) {
this.entityId = entityId;
}
public List<Integer> getPassengers() {
return passengers;
}
public void setPassengers(List<Integer> passengers) {
this.passengers = passengers;
}
}

View File

@@ -0,0 +1,78 @@
package de.oliver.fancysitula.api.packets;
public abstract class FS_ClientboundTeleportEntityPacket extends FS_ClientboundPacket {
protected int entityId;
protected double x;
protected double y;
protected double z;
protected float yaw;
protected float pitch;
protected boolean onGround;
public FS_ClientboundTeleportEntityPacket(int entityId, double x, double y, double z, float yaw, float pitch, boolean onGround) {
this.entityId = entityId;
this.x = x;
this.y = y;
this.z = z;
this.yaw = yaw;
this.pitch = pitch;
this.onGround = onGround;
}
public int getEntityId() {
return entityId;
}
public void setEntityId(int entityId) {
this.entityId = entityId;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public double getZ() {
return z;
}
public void setZ(double z) {
this.z = z;
}
public float getYaw() {
return yaw;
}
public void setYaw(float yaw) {
this.yaw = yaw;
}
public float getPitch() {
return pitch;
}
public void setPitch(float pitch) {
this.pitch = pitch;
}
public boolean isOnGround() {
return onGround;
}
public void setOnGround(boolean onGround) {
this.onGround = onGround;
}
}

View File

@@ -0,0 +1,31 @@
package de.oliver.fancysitula.api.packets;
public enum FS_Color {
BLACK(0),
DARK_BLUE(1),
DARK_GREEN(2),
DARK_AQUA(3),
DARK_RED(4),
DARK_PURPLE(5),
GOLD(6),
GRAY(7),
DARK_GRAY(8),
BLUE(9),
GREEN(10),
AQUA(11),
RED(12),
LIGHT_PURPLE(13),
YELLOW(14),
WHITE(15);
private final int id;
FS_Color(int id) {
this.id = id;
}
public int getId() {
return id;
}
}

View File

@@ -0,0 +1,18 @@
package de.oliver.fancysitula.api.teams;
public enum FS_CollisionRule {
ALWAYS("always"),
NEVER("never"),
PUSH_OTHER_TEAMS("pushOtherTeams"),
PUSH_OWN_TEAM("pushOwnTeam");
private final String name;
FS_CollisionRule(String name) {
this.name = name;
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,18 @@
package de.oliver.fancysitula.api.teams;
public enum FS_NameTagVisibility {
ALWAYS("always"),
NEVER("never"),
HIDE_FOR_OTHER_TEAMS("hideForOtherTeams"),
HIDE_FOR_OWN_TEAM("hideForOwnTeam");
private final String name;
FS_NameTagVisibility(String name) {
this.name = name;
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,113 @@
package de.oliver.fancysitula.api.teams;
import de.oliver.fancysitula.api.packets.FS_Color;
import net.kyori.adventure.text.Component;
import java.util.List;
public class FS_Team {
private String teamName;
private Component displayName;
private boolean allowFriendlyFire;
private boolean canSeeFriendlyInvisibles;
private FS_NameTagVisibility nameTagVisibility;
private FS_CollisionRule collisionRule;
private FS_Color color;
private Component prefix;
private Component suffix;
private List<String> entities;
public FS_Team(String teamName, Component displayName, boolean allowFriendlyFire, boolean canSeeFriendlyInvisibles, FS_NameTagVisibility nameTagVisibility, FS_CollisionRule collisionRule, FS_Color color, Component prefix, Component suffix, List<String> entities) {
this.teamName = teamName;
this.displayName = displayName;
this.allowFriendlyFire = allowFriendlyFire;
this.canSeeFriendlyInvisibles = canSeeFriendlyInvisibles;
this.nameTagVisibility = nameTagVisibility;
this.collisionRule = collisionRule;
this.color = color;
this.prefix = prefix;
this.suffix = suffix;
this.entities = entities;
}
public String getTeamName() {
return teamName;
}
public void setTeamName(String teamName) {
this.teamName = teamName;
}
public Component getDisplayName() {
return displayName;
}
public void setDisplayName(Component displayName) {
this.displayName = displayName;
}
public boolean isAllowFriendlyFire() {
return allowFriendlyFire;
}
public void setAllowFriendlyFire(boolean allowFriendlyFire) {
this.allowFriendlyFire = allowFriendlyFire;
}
public boolean isCanSeeFriendlyInvisibles() {
return canSeeFriendlyInvisibles;
}
public void setCanSeeFriendlyInvisibles(boolean canSeeFriendlyInvisibles) {
this.canSeeFriendlyInvisibles = canSeeFriendlyInvisibles;
}
public FS_NameTagVisibility getNameTagVisibility() {
return nameTagVisibility;
}
public void setNameTagVisibility(FS_NameTagVisibility nameTagVisibility) {
this.nameTagVisibility = nameTagVisibility;
}
public FS_CollisionRule getCollisionRule() {
return collisionRule;
}
public void setCollisionRule(FS_CollisionRule collisionRule) {
this.collisionRule = collisionRule;
}
public FS_Color getColor() {
return color;
}
public void setColor(FS_Color color) {
this.color = color;
}
public Component getPrefix() {
return prefix;
}
public void setPrefix(Component prefix) {
this.prefix = prefix;
}
public Component getSuffix() {
return suffix;
}
public void setSuffix(Component suffix) {
this.suffix = suffix;
}
public List<String> getEntities() {
return entities;
}
public void setEntities(List<String> entities) {
this.entities = entities;
}
}

View File

@@ -0,0 +1,9 @@
package de.oliver.fancysitula.api.utils;
public class AngelConverter {
public static byte degreesToVanillaByte(float degrees) {
return (byte) Math.floor(degrees * 256.0F / 360.0F);
}
}

View File

@@ -0,0 +1,24 @@
package de.oliver.fancysitula.api.utils;
public enum FS_EquipmentSlot {
MAINHAND,
OFFHAND,
FEET,
LEGS,
CHEST,
HEAD,
BODY,
;
public static FS_EquipmentSlot fromBukkit(org.bukkit.inventory.EquipmentSlot equipmentSlot) {
return switch (equipmentSlot) {
case HAND -> MAINHAND;
case OFF_HAND -> OFFHAND;
case FEET -> FEET;
case LEGS -> LEGS;
case CHEST -> CHEST;
case HEAD -> HEAD;
case BODY -> BODY;
};
}
}

View File

@@ -0,0 +1,62 @@
package de.oliver.fancysitula.api.utils;
import com.destroystokyo.paper.profile.PlayerProfile;
import com.destroystokyo.paper.profile.ProfileProperty;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class FS_GameProfile {
private UUID uuid;
private String name;
private Map<String, Property> properties;
public FS_GameProfile(UUID uuid, String name, Map<String, Property> properties) {
this.uuid = uuid;
this.name = name;
this.properties = properties;
}
public FS_GameProfile(UUID uuid, String name) {
this(uuid, name, new HashMap<>());
}
public static FS_GameProfile fromBukkit(PlayerProfile gameProfile) {
FS_GameProfile fsGameProfile = new FS_GameProfile(gameProfile.getId(), gameProfile.getName());
for (ProfileProperty property : gameProfile.getProperties()) {
fsGameProfile.getProperties().put(property.getName(), new FS_GameProfile.Property(property.getName(), property.getValue(), property.getSignature()));
}
return fsGameProfile;
}
public UUID getUUID() {
return uuid;
}
public void setUUID(UUID uuid) {
this.uuid = uuid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Map<String, Property> getProperties() {
return properties;
}
public void setProperties(Map<String, Property> properties) {
this.properties = properties;
}
public record Property(String name, String value, String signature) {
}
}

View File

@@ -0,0 +1,24 @@
package de.oliver.fancysitula.api.utils;
public enum FS_GameType {
SURVIVAL(0, "survival"),
CREATIVE(1, "creative"),
ADVENTURE(2, "adventure"),
SPECTATOR(3, "spectator");
private final int id;
private final String name;
FS_GameType(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,70 @@
package de.oliver.fancysitula.api.utils;
import org.bukkit.Bukkit;
import java.util.Arrays;
import java.util.List;
public enum ServerVersion {
v1_21_4("1.21.4", 769),
v1_21_3("1.21.3", 768),
v1_21_1("1.21.1", 767),
v1_21("1.21", 767),
v1_20_6("1.20.6", 766),
v1_20_5("1.20.5", 766),
;
private final String version;
private final int protocolVersion;
ServerVersion(String version, int protocolVersion) {
this.version = version;
this.protocolVersion = protocolVersion;
}
public static ServerVersion getByProtocolVersion(int protocolVersion) {
for (ServerVersion version : values()) {
if (version.getProtocolVersion() == protocolVersion) {
return version;
}
}
return null;
}
public static ServerVersion getByVersion(String version) {
for (ServerVersion serverVersion : values()) {
if (serverVersion.getVersion().equals(version)) {
return serverVersion;
}
}
return null;
}
public static List<String> getSupportedVersions() {
return Arrays.stream(values())
.map(ServerVersion::getVersion)
.toList();
}
public static boolean isVersionSupported(String version) {
return getByVersion(version) != null;
}
/**
* @return the current server version of the server the plugin is running on
*/
public static ServerVersion getCurrentVersion() {
return getByVersion(Bukkit.getMinecraftVersion());
}
public String getVersion() {
return version;
}
public int getProtocolVersion() {
return protocolVersion;
}
}

View File

@@ -0,0 +1,12 @@
package de.oliver.fancysitula.api.utils.entityData;
import de.oliver.fancysitula.api.packets.FS_ClientboundSetEntityDataPacket;
public class FS_BlockDisplayData {
/**
* Use {@link org.bukkit.block.Block} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor BLOCK = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display$BlockDisplay", "DATA_BLOCK_STATE_ID");
}

View File

@@ -0,0 +1,82 @@
package de.oliver.fancysitula.api.utils.entityData;
import de.oliver.fancysitula.api.packets.FS_ClientboundSetEntityDataPacket;
public class FS_DisplayData {
/**
* Use {@link Integer} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor TRANSFORMATION_INTERPOLATION_START_DELTA_TICKS = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display", "DATA_TRANSFORMATION_INTERPOLATION_START_DELTA_TICKS_ID");
/**
* Use {@link Integer} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor TRANSFORMATION_INTERPOLATION_DURATION = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display", "DATA_TRANSFORMATION_INTERPOLATION_DURATION_ID");
/**
* Use {@link Integer} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor POS_ROT_INTERPOLATION_DURATION = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display", "DATA_POS_ROT_INTERPOLATION_DURATION_ID");
/**
* Use {@link org.joml.Vector3f} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor TRANSLATION = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display", "DATA_TRANSLATION_ID");
/**
* Use {@link org.joml.Vector3f} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor SCALE = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display", "DATA_SCALE_ID");
/**
* Use {@link org.joml.Quaternionf} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor LEFT_ROTATION = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display", "DATA_LEFT_ROTATION_ID");
/**
* Use {@link org.joml.Quaternionf} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor RIGHT_ROTATION = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display", "DATA_RIGHT_ROTATION_ID");
/**
* Use {@link Byte} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor BILLBOARD_RENDER_CONSTRAINTS = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display", "DATA_BILLBOARD_RENDER_CONSTRAINTS_ID");
/**
* Use {@link Integer} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor BRIGHTNESS_OVERRIDE = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display", "DATA_BRIGHTNESS_OVERRIDE_ID");
/**
* Use {@link Float} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor VIEW_RANGE = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display", "DATA_VIEW_RANGE_ID");
/**
* Use {@link Float} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor SHADOW_RADIUS = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display", "DATA_SHADOW_RADIUS_ID");
/**
* Use {@link Float} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor SHADOW_STRENGTH = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display", "DATA_SHADOW_STRENGTH_ID");
/**
* Use {@link Float} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor WIDTH = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display", "DATA_WIDTH_ID");
/**
* Use {@link Float} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor HEIGHT = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display", "DATA_HEIGHT_ID");
/**
* Use {@link Integer} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor GLOW_COLOR_OVERRIDE = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display", "DATA_GLOW_COLOR_OVERRIDE_ID");
}

View File

@@ -0,0 +1,45 @@
package de.oliver.fancysitula.api.utils.entityData;
import de.oliver.fancysitula.api.packets.FS_ClientboundSetEntityDataPacket;
public class FS_EntityData {
/**
* Use {@link Byte} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor SHARED_FLAGS = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Entity", "DATA_SHARED_FLAGS_ID");
/**
* Use {@link Integer} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor AIR_SUPPLY = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Entity", "DATA_AIR_SUPPLY_ID");
/**
* Use {@link java.util.Optional<net.kyori.adventure.text.Component>} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor CUSTOM_NAME = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Entity", "DATA_CUSTOM_NAME");
/**
* Use {@link Boolean} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor CUSTOM_NAME_VISIBLE = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Entity", "DATA_CUSTOM_NAME_VISIBLE");
/**
* Use {@link Boolean} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor SILENT = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Entity", "DATA_SILENT");
/**
* Use {@link Boolean} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor NO_GRAVITY = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Entity", "DATA_NO_GRAVITY");
// TODO: Add DATA_POSE
/**
* Use {@link Integer} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor TICKS_FROZEN = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Entity", "DATA_TICKS_FROZEN");
}

View File

@@ -0,0 +1,12 @@
package de.oliver.fancysitula.api.utils.entityData;
import de.oliver.fancysitula.api.packets.FS_ClientboundSetEntityDataPacket;
public class FS_ItemDisplayData {
/**
* Use {@link org.bukkit.inventory.ItemStack} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor ITEM = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display$ItemDisplay", "DATA_ITEM_STACK_ID");
}

View File

@@ -0,0 +1,31 @@
package de.oliver.fancysitula.api.utils.entityData;
import de.oliver.fancysitula.api.packets.FS_ClientboundSetEntityDataPacket;
public class FS_TextDisplayData {
/**
* Use {@link net.kyori.adventure.text.Component} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor TEXT = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display$TextDisplay", "DATA_TEXT_ID");
/**
* Use {@link Integer} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor LINE_WIDTH = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display$TextDisplay", "DATA_LINE_WIDTH_ID");
/**
* Use {@link Integer} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor BACKGROUND = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display$TextDisplay", "DATA_BACKGROUND_COLOR_ID");
/**
* Use {@link Byte} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor TEXT_OPACITY = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display$TextDisplay", "DATA_TEXT_OPACITY_ID");
/**
* Use {@link Byte} as value
*/
public static final FS_ClientboundSetEntityDataPacket.EntityDataAccessor STYLE_FLAGS = new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display$TextDisplay", "DATA_STYLE_FLAGS_ID");
}

View File

@@ -0,0 +1,72 @@
package de.oliver.fancysitula.api.utils.reflections;
import sun.misc.Unsafe;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class ReflectionUtils {
private static Unsafe getUnsafe() throws Exception {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
}
public static <T> T createUnsafeInstance(Class<T> clazz) throws Exception {
Unsafe unsafe = getUnsafe();
return (T) unsafe.allocateInstance(clazz);
}
public static <T> T createInstance(Class<T> clazz) throws Exception {
Constructor<T> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
return constructor.newInstance();
}
public static Object getField(Object object, String fieldName) throws Exception {
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(object);
}
public static void setField(Object object, String fieldName, Object value) throws Exception {
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
}
public static void setFinalField(Object target, String fieldName, Object value) throws Exception {
Field field = target.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
// Use Unsafe to modify the final field
Unsafe unsafe = getUnsafe();
long offset = unsafe.objectFieldOffset(field);
if (field.getType() == int.class) {
unsafe.putInt(target, offset, (int) value);
} else if (field.getType() == long.class) {
unsafe.putLong(target, offset, (long) value);
} else if (field.getType() == double.class) {
unsafe.putDouble(target, offset, (double) value);
} else if (field.getType() == float.class) {
unsafe.putFloat(target, offset, (float) value);
} else if (field.getType() == boolean.class) {
unsafe.putBoolean(target, offset, (boolean) value);
} else if (field.getType() == byte.class) {
unsafe.putByte(target, offset, (byte) value);
} else if (field.getType() == short.class) {
unsafe.putShort(target, offset, (short) value);
} else if (field.getType() == char.class) {
unsafe.putChar(target, offset, (char) value);
} else {
unsafe.putObject(target, offset, value);
}
}
public static <T> T getStaticField(Class<?> clazz, String fieldName) throws NoSuchFieldException, IllegalAccessException {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return (T) field.get(null);
}
}

View File

@@ -0,0 +1,87 @@
plugins {
id("java-library")
id("maven-publish")
id("com.gradleup.shadow")
}
allprojects {
group = "de.oliver"
version = "0.0.13"
description = "Simple, lightweight and fast library for minecraft internals"
repositories {
mavenLocal()
mavenCentral()
maven(url = "https://repo.papermc.io/repository/maven-public/")
maven(url = "https://repo.fancyplugins.de/releases")
}
}
dependencies {
compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT")
implementation(project(":libraries:packets:api"))
implementation(project(":libraries:packets:factories"))
implementation(project(":libraries:packets:implementations:1_21_4"))
implementation(project(":libraries:packets:implementations:1_21_3"))
implementation(project(":libraries:packets:implementations:1_20_6"))
implementation("de.oliver.FancyAnalytics:logger:0.0.4")
}
tasks {
shadowJar {
archiveClassifier.set("")
configurations = listOf(project.configurations["runtimeClasspath"])
dependencies {
include(dependency("de.oliver:.*"))
}
}
publishing {
repositories {
maven {
name = "fancypluginsReleases"
url = uri("https://repo.fancyplugins.de/releases")
credentials(PasswordCredentials::class)
authentication {
isAllowInsecureProtocol = true
create<BasicAuthentication>("basic")
}
}
maven {
name = "fancypluginsSnapshots"
url = uri("https://repo.fancyplugins.de/snapshots")
credentials(PasswordCredentials::class)
authentication {
isAllowInsecureProtocol = true
create<BasicAuthentication>("basic")
}
}
}
publications {
create<MavenPublication>("shadow") {
groupId = project.group.toString()
version = project.version.toString()
artifact(shadowJar)
}
}
}
compileJava {
options.encoding = Charsets.UTF_8.name()
options.release = 21
}
javadoc {
options.encoding = Charsets.UTF_8.name()
}
processResources {
filteringCharset = Charsets.UTF_8.name()
}
}
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
}

View File

@@ -0,0 +1,28 @@
plugins {
id("java-library")
}
dependencies {
compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT")
compileOnly(project(":libraries:packets:api"))
compileOnly(project(":libraries:packets:implementations:1_20_6"))
compileOnly(project(":libraries:packets:implementations:1_21_3"))
compileOnly(project(":libraries:packets:implementations:1_21_4"))
}
tasks {
java {
withSourcesJar()
withJavadocJar()
}
javadoc {
options.encoding = Charsets.UTF_8.name()
}
compileJava {
options.encoding = Charsets.UTF_8.name()
options.release = 21
}
}

View File

@@ -0,0 +1,72 @@
package de.oliver.fancysitula.factories;
import de.oliver.fancysitula.api.entities.FS_Entity;
import de.oliver.fancysitula.api.entities.FS_Player;
import de.oliver.fancysitula.api.entities.FS_RealPlayer;
import java.util.List;
public class EntityFactory {
public void spawnEntityFor(FS_RealPlayer player, FS_Entity entity) {
if (entity == null) {
return;
}
FancySitula.PACKET_FACTORY.createAddEntityPacket(
entity.getId(),
entity.getUuid(),
entity.getType(),
entity.getX(),
entity.getY(),
entity.getZ(),
entity.getYaw(),
entity.getPitch(),
entity.getHeadYaw(),
entity.getVelocityX(),
entity.getVelocityY(),
entity.getVelocityZ(),
entity.getData()
).send(player);
setEntityDataFor(player, entity);
}
public void despawnEntityFor(FS_RealPlayer player, FS_Entity entity) {
if (entity == null) {
return;
}
FancySitula.PACKET_FACTORY.createRemoveEntitiesPacket(
List.of(entity.getId())
)
.send(player);
}
public void setEntityDataFor(FS_RealPlayer player, FS_Entity entity) {
if (entity == null) {
return;
}
if (entity.getEntityData().isEmpty()) {
return;
}
FancySitula.PACKET_FACTORY.createSetEntityDataPacket(
entity.getId(),
entity.getEntityData()
).send(player);
}
public void setEntityEquipmentFor(FS_RealPlayer player, FS_Player entity) {
if (entity == null) {
return;
}
FancySitula.PACKET_FACTORY.createSetEquipmentPacket(
entity.getId(),
entity.getEquipment()
).send(player);
}
}

View File

@@ -0,0 +1,8 @@
package de.oliver.fancysitula.factories;
public class FancySitula {
public static final PacketFactory PACKET_FACTORY = new PacketFactory();
public static final EntityFactory ENTITY_FACTORY = new EntityFactory();
public static final TeamFactory TEAM_FACTORY = new TeamFactory();
}

View File

@@ -0,0 +1,549 @@
package de.oliver.fancysitula.factories;
import de.oliver.fancysitula.api.packets.*;
import de.oliver.fancysitula.api.utils.FS_EquipmentSlot;
import de.oliver.fancysitula.api.utils.ServerVersion;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* Factory class for creating packet instances based on the server version
*/
public class PacketFactory {
/**
* Creates a new FS_ClientboundPlayerInfoUpdatePacket instance based on the server version
*
* @param actions EnumSet of {@link FS_ClientboundPlayerInfoUpdatePacket.Action} to perform
* @param entries List of {@link FS_ClientboundPlayerInfoUpdatePacket.Entry} to update
*/
public FS_ClientboundPlayerInfoUpdatePacket createPlayerInfoUpdatePacket(
ServerVersion serverVersion, EnumSet<FS_ClientboundPlayerInfoUpdatePacket.Action> actions,
List<FS_ClientboundPlayerInfoUpdatePacket.Entry> entries) {
switch (serverVersion) {
case v1_21_4 -> {
return new de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundPlayerInfoUpdatePacketImpl(actions, entries);
}
case v1_21_3 -> {
return new de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundPlayerInfoUpdatePacketImpl(actions, entries);
}
case v1_20_5, v1_20_6, v1_21, v1_21_1 -> {
return new de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundPlayerInfoUpdatePacketImpl(actions, entries);
}
default -> throw new IllegalArgumentException("Unsupported server version: " + serverVersion.getVersion());
}
}
/**
* Creates a new FS_ClientboundPlayerInfoUpdatePacket instance based on the current server version
*
* @param actions EnumSet of {@link FS_ClientboundPlayerInfoUpdatePacket.Action} to perform
* @param entries List of {@link FS_ClientboundPlayerInfoUpdatePacket.Entry} to update
*/
public FS_ClientboundPlayerInfoUpdatePacket createPlayerInfoUpdatePacket(
EnumSet<FS_ClientboundPlayerInfoUpdatePacket.Action> actions,
List<FS_ClientboundPlayerInfoUpdatePacket.Entry> entries) {
return createPlayerInfoUpdatePacket(ServerVersion.getCurrentVersion(), actions, entries);
}
/**
* Creates a new FS_ClientboundAddEntityPacket instance based on the server version
*
* @param yaw in degrees (0 - 360)
* @param pitch in degrees (0 - 360)
* @param headYaw in degrees (0 - 360)
*/
public FS_ClientboundAddEntityPacket createAddEntityPacket(
ServerVersion serverVersion,
int entityId,
UUID entityUUID,
EntityType entityType,
double x,
double y,
double z,
float yaw,
float pitch,
float headYaw,
int velocityX,
int velocityY,
int velocityZ,
int data) {
switch (serverVersion) {
case v1_21_4 -> {
return new de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundAddEntityPacketImpl(entityId, entityUUID, entityType, x, y, z, yaw, pitch, headYaw, velocityX, velocityY, velocityZ, data);
}
case v1_21_3 -> {
return new de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundAddEntityPacketImpl(entityId, entityUUID, entityType, x, y, z, yaw, pitch, headYaw, velocityX, velocityY, velocityZ, data);
}
case v1_20_5, v1_20_6, v1_21, v1_21_1 -> {
return new de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundAddEntityPacketImpl(entityId, entityUUID, entityType, x, y, z, yaw, pitch, headYaw, velocityX, velocityY, velocityZ, data);
}
default -> throw new IllegalArgumentException("Unsupported server version: " + serverVersion.getVersion());
}
}
/**
* Creates a new FS_ClientboundAddEntityPacket instance based on the current server version
*
* @param yaw in degrees (0 - 360)
* @param pitch in degrees (0 - 360)
* @param headYaw in degrees (0 - 360)
*/
public FS_ClientboundAddEntityPacket createAddEntityPacket(
int entityId,
UUID entityUUID,
EntityType entityType,
double x,
double y,
double z,
float yaw,
float pitch,
float headYaw,
int velocityX,
int velocityY,
int velocityZ,
int data) {
return createAddEntityPacket(ServerVersion.getCurrentVersion(), entityId, entityUUID, entityType, x, y, z, yaw, pitch, headYaw, velocityX, velocityY, velocityZ, data);
}
/**
* Creates a new FS_ClientboundPlayerInfoRemovePacket instance based on the server version
*
* @param uuids UUIDs of the players to remove
*/
public FS_ClientboundPlayerInfoRemovePacket createPlayerInfoRemovePacket(ServerVersion serverVersion, List<UUID> uuids) {
switch (serverVersion) {
case v1_21_4 -> {
return new de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundPlayerInfoRemovePacketImpl(uuids);
}
case v1_21_3 -> {
return new de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundPlayerInfoRemovePacketImpl(uuids);
}
case v1_20_5, v1_20_6, v1_21, v1_21_1 -> {
return new de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundPlayerInfoRemovePacketImpl(uuids);
}
default -> throw new IllegalArgumentException("Unsupported server version: " + serverVersion.getVersion());
}
}
/**
* Creates a new FS_ClientboundPlayerInfoRemovePacket instance based on the current server version
*
* @param uuids UUIDs of the players to remove
*/
public FS_ClientboundPlayerInfoRemovePacket createPlayerInfoRemovePacket(List<UUID> uuids) {
return createPlayerInfoRemovePacket(ServerVersion.getCurrentVersion(), uuids);
}
/**
* Creates a new FS_ClientboundPlayerInfoRemovePacket instance based on the server version
*
* @param uuid UUID of the player to remove
*/
public FS_ClientboundPlayerInfoRemovePacket createPlayerInfoRemovePacket(ServerVersion serverVersion, UUID uuid) {
return createPlayerInfoRemovePacket(serverVersion, List.of(uuid));
}
/**
* Creates a new FS_ClientboundPlayerInfoRemovePacket instance based on the current server version
*
* @param uuid UUID of the player to remove
*/
public FS_ClientboundPlayerInfoRemovePacket createPlayerInfoRemovePacket(UUID uuid) {
return createPlayerInfoRemovePacket(ServerVersion.getCurrentVersion(), uuid);
}
/**
* Creates a new FS_ClientboundRemoveEntitiesPacket instance based on the server version
*
* @param entityIds IDs of the entities to remove
*/
public FS_ClientboundRemoveEntitiesPacket createRemoveEntitiesPacket(ServerVersion serverVersion, List<Integer> entityIds) {
switch (serverVersion) {
case v1_21_4 -> {
return new de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundRemoveEntitiesPacketImpl(entityIds);
}
case v1_21_3 -> {
return new de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundRemoveEntitiesPacketImpl(entityIds);
}
case v1_20_5, v1_20_6, v1_21, v1_21_1 -> {
return new de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundRemoveEntitiesPacketImpl(entityIds);
}
default -> throw new IllegalArgumentException("Unsupported server version: " + serverVersion.getVersion());
}
}
/**
* Creates a new FS_ClientboundRemoveEntitiesPacket instance based on the current server version
*
* @param entityIds IDs of the entities to remove
*/
public FS_ClientboundRemoveEntitiesPacket createRemoveEntitiesPacket(List<Integer> entityIds) {
return createRemoveEntitiesPacket(ServerVersion.getCurrentVersion(), entityIds);
}
/**
* Creates a new FS_ClientboundTeleportEntityPacket instance based on the server version
*
* @param entityId ID of the entity to teleport
* @param x X coordinate
* @param y Y coordinate
* @param z Z coordinate
* @param yaw Yaw in degrees (0 - 360)
* @param pitch Pitch in degrees (0 - 360)
* @param onGround Whether the entity is on the ground
*/
public FS_ClientboundTeleportEntityPacket createTeleportEntityPacket(
ServerVersion serverVersion,
int entityId,
double x,
double y,
double z,
float yaw,
float pitch,
boolean onGround
) {
switch (serverVersion) {
case v1_21_4 -> {
return new de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundTeleportEntityPacketImpl(entityId, x, y, z, yaw, pitch, onGround);
}
case v1_21_3 -> {
return new de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundTeleportEntityPacketImpl(entityId, x, y, z, yaw, pitch, onGround);
}
case v1_20_5, v1_20_6, v1_21, v1_21_1 -> {
return new de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundTeleportEntityPacketImpl(entityId, x, y, z, yaw, pitch, onGround);
}
default -> throw new IllegalArgumentException("Unsupported server version: " + serverVersion.getVersion());
}
}
/**
* Creates a new FS_ClientboundTeleportEntityPacket instance based on the current server version
*
* @param entityId ID of the entity to teleport
* @param x X coordinate
* @param y Y coordinate
* @param z Z coordinate
* @param yaw Yaw in degrees (0 - 360)
* @param pitch Pitch in degrees (0 - 360)
* @param onGround Whether the entity is on the ground
*/
public FS_ClientboundTeleportEntityPacket createTeleportEntityPacket(
int entityId,
double x,
double y,
double z,
float yaw,
float pitch,
boolean onGround
) {
return createTeleportEntityPacket(ServerVersion.getCurrentVersion(), entityId, x, y, z, yaw, pitch, onGround);
}
/**
* Creates a new FS_ClientboundRotateHeadPacket instance based on the server version
*
* @param entityId ID of the entity to rotate the head of
* @param headYaw Yaw of the head in degrees (0 - 360)
*/
public FS_ClientboundRotateHeadPacket createRotateHeadPacket(ServerVersion serverVersion, int entityId, float headYaw) {
switch (serverVersion) {
case v1_21_4 -> {
return new de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundRotateHeadPacketImpl(entityId, headYaw);
}
case v1_21_3 -> {
return new de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundRotateHeadPacketImpl(entityId, headYaw);
}
case v1_20_5, v1_20_6, v1_21, v1_21_1 -> {
return new de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundRotateHeadPacketImpl(entityId, headYaw);
}
default -> throw new IllegalArgumentException("Unsupported server version: " + serverVersion.getVersion());
}
}
/**
* Creates a new FS_ClientboundRotateHeadPacket instance based on the current server version
*
* @param entityId ID of the entity to rotate the head of
* @param headYaw Yaw of the head in degrees (0 - 360)
*/
public FS_ClientboundRotateHeadPacket createRotateHeadPacket(int entityId, float headYaw) {
return createRotateHeadPacket(ServerVersion.getCurrentVersion(), entityId, headYaw);
}
/**
* Creates a new FS_ClientboundSetEntityDataPacket instance based on the server version
*
* @param entityId ID of the entity to set the data of
* @param entityData List of {@link FS_ClientboundSetEntityDataPacket.EntityData} to set
*/
public FS_ClientboundSetEntityDataPacket createSetEntityDataPacket(
ServerVersion serverVersion, int entityId, List<FS_ClientboundSetEntityDataPacket.EntityData> entityData) {
switch (serverVersion) {
case v1_21_4 -> {
return new de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundSetEntityDataPacketImpl(entityId, entityData);
}
case v1_21_3 -> {
return new de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundSetEntityDataPacketImpl(entityId, entityData);
}
case v1_20_5, v1_20_6, v1_21, v1_21_1 -> {
return new de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundSetEntityDataPacketImpl(entityId, entityData);
}
default -> throw new IllegalArgumentException("Unsupported server version: " + serverVersion.getVersion());
}
}
/**
* Creates a new FS_ClientboundSetEntityDataPacket instance based on the current server version
*
* @param entityId ID of the entity to set the data of
* @param entityData List of {@link FS_ClientboundSetEntityDataPacket.EntityData} to set
*/
public FS_ClientboundSetEntityDataPacket createSetEntityDataPacket(int entityId, List<FS_ClientboundSetEntityDataPacket.EntityData> entityData) {
return createSetEntityDataPacket(ServerVersion.getCurrentVersion(), entityId, entityData);
}
/**
* Creates a new FS_ClientboundSetEquipmentPacket instance based on the server version
*
* @param entityId ID of the entity to set the equipment of
* @param equipment Map of {@link org.bukkit.inventory.EquipmentSlot} and {@link org.bukkit.inventory.ItemStack} to set
*/
public FS_ClientboundSetEquipmentPacket createSetEquipmentPacket(ServerVersion serverVersion, int entityId, Map<FS_EquipmentSlot, ItemStack> equipment) {
switch (serverVersion) {
case v1_21_4 -> {
return new de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundSetEquipmentPacketImpl(entityId, equipment);
}
case v1_21_3 -> {
return new de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundSetEquipmentPacketImpl(entityId, equipment);
}
case v1_20_5, v1_20_6, v1_21, v1_21_1 -> {
return new de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundSetEquipmentPacketImpl(entityId, equipment);
}
default -> throw new IllegalArgumentException("Unsupported server version: " + serverVersion.getVersion());
}
}
/**
* Creates a new FS_ClientboundSetEquipmentPacket instance based on the current server version
*
* @param entityId ID of the entity to set the equipment of
* @param equipment Map of {@link org.bukkit.inventory.EquipmentSlot} and {@link org.bukkit.inventory.ItemStack} to set
*/
public FS_ClientboundSetEquipmentPacket createSetEquipmentPacket(int entityId, Map<FS_EquipmentSlot, ItemStack> equipment) {
return createSetEquipmentPacket(ServerVersion.getCurrentVersion(), entityId, equipment);
}
/**
* Creates a new FS_ClientboundSetPassengersPacket instance based on the server version
*
* @param entityId ID of the vehicle entity
* @param passengers List of entity IDs to set as passengers
*/
public FS_ClientboundSetPassengersPacket createSetPassengersPacket(
ServerVersion serverVersion, int entityId, List<Integer> passengers) {
switch (serverVersion) {
case v1_21_4 -> {
return new de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundSetPassengersPacketImpl(entityId, passengers);
}
case v1_21_3 -> {
return new de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundSetPassengersPacketImpl(entityId, passengers);
}
case v1_20_5, v1_20_6, v1_21, v1_21_1 -> {
return new de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundSetPassengersPacketImpl(entityId, passengers);
}
default -> throw new IllegalArgumentException("Unsupported server version: " + serverVersion.getVersion());
}
}
/**
* Creates a new FS_ClientboundSetPassengersPacket instance based on the current server version
*
* @param entityId ID of the vehicle entity
* @param passengers List of entity IDs to set as passengers
*/
public FS_ClientboundSetPassengersPacket createSetPassengersPacket(int entityId, List<Integer> passengers) {
return createSetPassengersPacket(ServerVersion.getCurrentVersion(), entityId, passengers);
}
/**
* Creates and returns a FS_ClientboundCreateOrUpdateTeamPacket based on the given server version and team information.
*
* @param serverVersion the version of the server for which the packet is to be created
* @param teamName the name of the team to be created or updated
* @param createTeam an instance of FS_ClientboundCreateOrUpdateTeamPacket.CreateTeam containing the team creation or update details
* @return a FS_ClientboundCreateOrUpdateTeamPacket instance corresponding to the specified server version and team details
* @throws IllegalArgumentException if the provided server version is not supported
*/
public FS_ClientboundCreateOrUpdateTeamPacket createCreateOrUpdateTeamPacket(ServerVersion serverVersion, String teamName, FS_ClientboundCreateOrUpdateTeamPacket.CreateTeam createTeam) {
switch (serverVersion) {
case v1_21_4 -> {
return new de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundCreateOrUpdateTeamPacketImpl(teamName, createTeam);
}
case v1_21_3 -> {
return new de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundCreateOrUpdateTeamPacketImpl(teamName, createTeam);
}
case v1_20_5, v1_20_6, v1_21, v1_21_1 -> {
return new de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundCreateOrUpdateTeamPacketImpl(teamName, createTeam);
}
default -> throw new IllegalArgumentException("Unsupported server version: " + serverVersion.getVersion());
}
}
/**
* Creates an FS_ClientboundCreateOrUpdateTeamPacket for the given team name and creation details.
*
* @param teamName The name of the team to create or update.
* @param createTeam The details of the team creation or update.
* @return An instance of FS_ClientboundCreateOrUpdateTeamPacket containing the creation or update details.
*/
public FS_ClientboundCreateOrUpdateTeamPacket createCreateOrUpdateTeamPacket(String teamName, FS_ClientboundCreateOrUpdateTeamPacket.CreateTeam createTeam) {
return createCreateOrUpdateTeamPacket(ServerVersion.getCurrentVersion(), teamName, createTeam);
}
/**
* Creates a packet for creating or updating a team based on the specified server version.
*
* @param serverVersion The version of the server.
* @param teamName The name of the team.
* @param removeTeam Information about whether to remove the team.
* @return The packet for creating or updating the team.
* @throws IllegalArgumentException if the server version is unsupported.
*/
public FS_ClientboundCreateOrUpdateTeamPacket createCreateOrUpdateTeamPacket(ServerVersion serverVersion, String teamName, FS_ClientboundCreateOrUpdateTeamPacket.RemoveTeam removeTeam) {
switch (serverVersion) {
case v1_21_4 -> {
return new de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundCreateOrUpdateTeamPacketImpl(teamName, removeTeam);
}
case v1_21_3 -> {
return new de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundCreateOrUpdateTeamPacketImpl(teamName, removeTeam);
}
case v1_20_5, v1_20_6, v1_21, v1_21_1 -> {
return new de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundCreateOrUpdateTeamPacketImpl(teamName, removeTeam);
}
default -> throw new IllegalArgumentException("Unsupported server version: " + serverVersion.getVersion());
}
}
/**
* Creates a packet to create or update a team with the specified name and removal flag.
*
* @param teamName the name of the team to create or update
* @param removeTeam the flag indicating whether to remove the team
* @return a packet for creating or updating the team
*/
public FS_ClientboundCreateOrUpdateTeamPacket createCreateOrUpdateTeamPacket(String teamName, FS_ClientboundCreateOrUpdateTeamPacket.RemoveTeam removeTeam) {
return createCreateOrUpdateTeamPacket(ServerVersion.getCurrentVersion(), teamName, removeTeam);
}
/**
* Creates an instance of FS_ClientboundCreateOrUpdateTeamPacket based on the provided server version.
*
* @param serverVersion The server version for which the packet should be created.
* @param teamName The name of the team that is being created or updated.
* @param updateTeam The update team details which contain information about the team.
* @return A new instance of FS_ClientboundCreateOrUpdateTeamPacket tailored for the specified server version.
* @throws IllegalArgumentException If the provided server version is not supported.
*/
public FS_ClientboundCreateOrUpdateTeamPacket createCreateOrUpdateTeamPacket(ServerVersion serverVersion, String teamName, FS_ClientboundCreateOrUpdateTeamPacket.UpdateTeam updateTeam) {
switch (serverVersion) {
case v1_21_4 -> {
return new de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundCreateOrUpdateTeamPacketImpl(teamName, updateTeam);
}
case v1_21_3 -> {
return new de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundCreateOrUpdateTeamPacketImpl(teamName, updateTeam);
}
case v1_20_5, v1_20_6, v1_21, v1_21_1 -> {
return new de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundCreateOrUpdateTeamPacketImpl(teamName, updateTeam);
}
default -> throw new IllegalArgumentException("Unsupported server version: " + serverVersion.getVersion());
}
}
/**
* Creates a new FS_ClientboundCreateOrUpdateTeamPacket for creating or updating a team.
*
* @param teamName the name of the team to be created or updated
* @param updateTeam the update information for the team
* @return a new instance of FS_ClientboundCreateOrUpdateTeamPacket
*/
public FS_ClientboundCreateOrUpdateTeamPacket createCreateOrUpdateTeamPacket(String teamName, FS_ClientboundCreateOrUpdateTeamPacket.UpdateTeam updateTeam) {
return createCreateOrUpdateTeamPacket(ServerVersion.getCurrentVersion(), teamName, updateTeam);
}
/**
* Creates a new instance of FS_ClientboundCreateOrUpdateTeamPacket based on the given server version, team name, and addEntity parameters.
*
* @param serverVersion the version of the server for which the packet will be created
* @param teamName the name of the team to be created or updated
* @param addEntity the add entity information needed for packet creation
* @return an instance of FS_ClientboundCreateOrUpdateTeamPacket appropriate for the specified server version
* @throws IllegalArgumentException if the server version is not supported
*/
public FS_ClientboundCreateOrUpdateTeamPacket createCreateOrUpdateTeamPacket(ServerVersion serverVersion, String teamName, FS_ClientboundCreateOrUpdateTeamPacket.AddEntity addEntity) {
switch (serverVersion) {
case v1_21_4 -> {
return new de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundCreateOrUpdateTeamPacketImpl(teamName, addEntity);
}
case v1_21_3 -> {
return new de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundCreateOrUpdateTeamPacketImpl(teamName, addEntity);
}
case v1_20_5, v1_20_6, v1_21, v1_21_1 -> {
return new de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundCreateOrUpdateTeamPacketImpl(teamName, addEntity);
}
default -> throw new IllegalArgumentException("Unsupported server version: " + serverVersion.getVersion());
}
}
/**
* Creates a packet for creating or updating a team with the specified name and entity.
*
* @param teamName the name of the team to create or update
* @param addEntity the entity representing the addition details for the team
* @return the packet representing the create or update operation on the team
*/
public FS_ClientboundCreateOrUpdateTeamPacket createCreateOrUpdateTeamPacket(String teamName, FS_ClientboundCreateOrUpdateTeamPacket.AddEntity addEntity) {
return createCreateOrUpdateTeamPacket(ServerVersion.getCurrentVersion(), teamName, addEntity);
}
/**
* Creates an instance of FS_ClientboundCreateOrUpdateTeamPacket based on the server version.
*
* @param serverVersion The version of the server.
* @param teamName The name of the team to create or update.
* @param removeEntity The entity removal configuration for the packet.
* @return A new instance of FS_ClientboundCreateOrUpdateTeamPacket for the specified server version.
* @throws IllegalArgumentException If the server version is unsupported.
*/
public FS_ClientboundCreateOrUpdateTeamPacket createCreateOrUpdateTeamPacket(ServerVersion serverVersion, String teamName, FS_ClientboundCreateOrUpdateTeamPacket.RemoveEntity removeEntity) {
switch (serverVersion) {
case v1_21_4 -> {
return new de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundCreateOrUpdateTeamPacketImpl(teamName, removeEntity);
}
case v1_21_3 -> {
return new de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundCreateOrUpdateTeamPacketImpl(teamName, removeEntity);
}
case v1_20_5, v1_20_6, v1_21, v1_21_1 -> {
return new de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundCreateOrUpdateTeamPacketImpl(teamName, removeEntity);
}
default -> throw new IllegalArgumentException("Unsupported server version: " + serverVersion.getVersion());
}
}
/**
* Creates a packet for creating or updating a team with the specified name and entity removal configuration.
*
* @param teamName the name of the team to create or update
* @param removeEntity the entity removal configuration for the team
* @return the packet for creating or updating the team
*/
public FS_ClientboundCreateOrUpdateTeamPacket createCreateOrUpdateTeamPacket(String teamName, FS_ClientboundCreateOrUpdateTeamPacket.RemoveEntity removeEntity) {
return createCreateOrUpdateTeamPacket(ServerVersion.getCurrentVersion(), teamName, removeEntity);
}
}

View File

@@ -0,0 +1,77 @@
package de.oliver.fancysitula.factories;
import de.oliver.fancysitula.api.entities.FS_RealPlayer;
import de.oliver.fancysitula.api.packets.FS_ClientboundCreateOrUpdateTeamPacket;
import de.oliver.fancysitula.api.teams.FS_Team;
import java.util.List;
public class TeamFactory {
public void createTeamFor(FS_RealPlayer player, FS_Team team) {
FS_ClientboundCreateOrUpdateTeamPacket packet = FancySitula.PACKET_FACTORY.createCreateOrUpdateTeamPacket(
team.getTeamName(),
new FS_ClientboundCreateOrUpdateTeamPacket.CreateTeam(
team.getDisplayName(),
team.isAllowFriendlyFire(),
team.isCanSeeFriendlyInvisibles(),
team.getNameTagVisibility(),
team.getCollisionRule(),
team.getColor(),
team.getPrefix(),
team.getSuffix(),
team.getEntities()
)
);
player.sendPacket(packet);
}
public void removeTeamFor(FS_RealPlayer player, FS_Team team) {
FS_ClientboundCreateOrUpdateTeamPacket packet = FancySitula.PACKET_FACTORY.createCreateOrUpdateTeamPacket(
team.getTeamName(),
new FS_ClientboundCreateOrUpdateTeamPacket.RemoveTeam()
);
player.sendPacket(packet);
}
/**
* Updates the team for the player. But does not add or remove entities.
*/
public void updateTeamFor(FS_RealPlayer player, FS_Team team) {
FS_ClientboundCreateOrUpdateTeamPacket packet = FancySitula.PACKET_FACTORY.createCreateOrUpdateTeamPacket(
team.getTeamName(),
new FS_ClientboundCreateOrUpdateTeamPacket.UpdateTeam(
team.getDisplayName(),
team.isAllowFriendlyFire(),
team.isCanSeeFriendlyInvisibles(),
team.getNameTagVisibility(),
team.getCollisionRule(),
team.getColor(),
team.getPrefix(),
team.getSuffix()
)
);
player.sendPacket(packet);
}
public void addEntitiesToTeamFor(FS_RealPlayer player, FS_Team team, List<String> entities) {
FS_ClientboundCreateOrUpdateTeamPacket packet = FancySitula.PACKET_FACTORY.createCreateOrUpdateTeamPacket(
team.getTeamName(),
new FS_ClientboundCreateOrUpdateTeamPacket.AddEntity(entities)
);
player.sendPacket(packet);
}
public void removeEntitiesFromTeamFor(FS_RealPlayer player, FS_Team team, List<String> entities) {
FS_ClientboundCreateOrUpdateTeamPacket packet = FancySitula.PACKET_FACTORY.createCreateOrUpdateTeamPacket(
team.getTeamName(),
new FS_ClientboundCreateOrUpdateTeamPacket.RemoveEntity(entities)
);
player.sendPacket(packet);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

View File

@@ -0,0 +1,2 @@
minecraftVersion=1.21.3
fancyLoggerVersion=0.0.4

View File

@@ -0,0 +1,23 @@
plugins {
id("java-library")
id("io.papermc.paperweight.userdev")
}
val minecraftVersion = "1.20.6"
paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION
dependencies {
paperweight.paperDevBundle("$minecraftVersion-R0.1-SNAPSHOT")
compileOnly(project(":libraries:packets:api"))
testImplementation(project(":libraries:packets:api"))
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.2")
}
tasks {
test {
useJUnitPlatform()
}
}

Some files were not shown because too many files have changed in this diff Show More