diff --git a/build.gradle.kts b/build.gradle.kts index 3f323e83..34898d0e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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" diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..3620c405 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configuration-cache=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9355b415..37f853b1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/libraries/common/.gitignore b/libraries/common/.gitignore new file mode 100644 index 00000000..1dec6e1b --- /dev/null +++ b/libraries/common/.gitignore @@ -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 \ No newline at end of file diff --git a/libraries/common/README.md b/libraries/common/README.md new file mode 100644 index 00000000..0407ae23 --- /dev/null +++ b/libraries/common/README.md @@ -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:") +} +``` \ No newline at end of file diff --git a/libraries/common/build.gradle.kts b/libraries/common/build.gradle.kts new file mode 100644 index 00000000..fdc9a3e2 --- /dev/null +++ b/libraries/common/build.gradle.kts @@ -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("basic") + } + } + + maven { + name = "fancypluginsSnapshots" + url = uri("https://repo.fancyplugins.de/snapshots") + credentials(PasswordCredentials::class) + authentication { + isAllowInsecureProtocol = true + create("basic") + } + } + } + publications { + create("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() + } +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/ConfigHelper.java b/libraries/common/src/main/java/de/oliver/fancylib/ConfigHelper.java new file mode 100644 index 00000000..4002d8f1 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/ConfigHelper.java @@ -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); + } + +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/FancyLib.java b/libraries/common/src/main/java/de/oliver/fancylib/FancyLib.java new file mode 100644 index 00000000..dd2cd863 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/FancyLib.java @@ -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; + } +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/FileUtils.java b/libraries/common/src/main/java/de/oliver/fancylib/FileUtils.java new file mode 100644 index 00000000..7499da21 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/FileUtils.java @@ -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(); + } + } + +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/MessageHelper.java b/libraries/common/src/main/java/de/oliver/fancylib/MessageHelper.java new file mode 100644 index 00000000..723e8dfb --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/MessageHelper.java @@ -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("", "") + .replace("", "") + .replace("", "") + .replace("", "") + .replace("", ""); + + 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("" + 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("" + 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("" + 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("" + 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); + } +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/Metrics.java b/libraries/common/src/main/java/de/oliver/fancylib/Metrics.java new file mode 100644 index 00000000..8f89741c --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/Metrics.java @@ -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 What is my plugin id? + */ + 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 appendPlatformDataConsumer; + + private final Consumer appendServiceDataConsumer; + + private final Consumer submitTaskConsumer; + + private final Supplier checkServiceEnabledSupplier; + + private final BiConsumer errorLogger; + + private final Consumer infoLogger; + + private final boolean logErrors; + + private final boolean logSentData; + + private final boolean logResponseStatusText; + + private final Set 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 appendPlatformDataConsumer, + Consumer appendServiceDataConsumer, + Consumer submitTaskConsumer, + Supplier checkServiceEnabledSupplier, + BiConsumer errorLogger, + Consumer 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 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 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> 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> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry 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> 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> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry 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> 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> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + for (Map.Entry 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> 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> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry 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>> 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>> callable) { + super(chartId); + this.callable = callable; + } + + @Override + public JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map> map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean reallyAllSkipped = true; + for (Map.Entry> entryValues : map.entrySet()) { + JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); + boolean allSkipped = true; + for (Map.Entry 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 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 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 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. + * + *

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. + * + *

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. + * + *

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; + } + } + } +} \ No newline at end of file diff --git a/libraries/common/src/main/java/de/oliver/fancylib/RandomUtils.java b/libraries/common/src/main/java/de/oliver/fancylib/RandomUtils.java new file mode 100644 index 00000000..1ada0b11 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/RandomUtils.java @@ -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; + } + +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/ReflectionUtils.java b/libraries/common/src/main/java/de/oliver/fancylib/ReflectionUtils.java new file mode 100644 index 00000000..4a097b00 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/ReflectionUtils.java @@ -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(); + } + } + +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/UUIDFetcher.java b/libraries/common/src/main/java/de/oliver/fancylib/UUIDFetcher.java new file mode 100644 index 00000000..6ecbe10e --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/UUIDFetcher.java @@ -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 uuidCache = new HashMap(); + private static Map nameCache = new HashMap(); + + 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 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 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 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 { + 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()); + } +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/VersionConfig.java b/libraries/common/src/main/java/de/oliver/fancylib/VersionConfig.java new file mode 100644 index 00000000..b3117f21 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/VersionConfig.java @@ -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 here"; + + 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; + } +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/databases/Database.java b/libraries/common/src/main/java/de/oliver/fancylib/databases/Database.java new file mode 100644 index 00000000..d2f6dd8d --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/databases/Database.java @@ -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); +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/databases/MySqlDatabase.java b/libraries/common/src/main/java/de/oliver/fancylib/databases/MySqlDatabase.java new file mode 100644 index 00000000..d7c2dfe7 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/databases/MySqlDatabase.java @@ -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; + } + } +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/databases/SqliteDatabase.java b/libraries/common/src/main/java/de/oliver/fancylib/databases/SqliteDatabase.java new file mode 100644 index 00000000..375c86cc --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/databases/SqliteDatabase.java @@ -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; + } + } +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/featureFlags/FeatureFlag.java b/libraries/common/src/main/java/de/oliver/fancylib/featureFlags/FeatureFlag.java new file mode 100644 index 00000000..6c80b119 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/featureFlags/FeatureFlag.java @@ -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; + } +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/featureFlags/FeatureFlagConfig.java b/libraries/common/src/main/java/de/oliver/fancylib/featureFlags/FeatureFlagConfig.java new file mode 100644 index 00000000..1fd54bc4 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/featureFlags/FeatureFlagConfig.java @@ -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 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); + } + } + +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/gui/customInventories/CustomPlayerInventory.java b/libraries/common/src/main/java/de/oliver/fancylib/gui/customInventories/CustomPlayerInventory.java new file mode 100644 index 00000000..5c7430da --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/gui/customInventories/CustomPlayerInventory.java @@ -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); + } +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/gui/customInventories/PageInventory.java b/libraries/common/src/main/java/de/oliver/fancylib/gui/customInventories/PageInventory.java new file mode 100644 index 00000000..0ea3c403 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/gui/customInventories/PageInventory.java @@ -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("Previous page"), 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("Next page"), 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); + +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/gui/inventoryClick/InventoryClickListener.java b/libraries/common/src/main/java/de/oliver/fancylib/gui/inventoryClick/InventoryClickListener.java new file mode 100644 index 00000000..920a8832 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/gui/inventoryClick/InventoryClickListener.java @@ -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) { + } + + } + +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/gui/inventoryClick/InventoryClickRegistry.java b/libraries/common/src/main/java/de/oliver/fancylib/gui/inventoryClick/InventoryClickRegistry.java new file mode 100644 index 00000000..91cfc5e8 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/gui/inventoryClick/InventoryClickRegistry.java @@ -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 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); + } + +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/gui/inventoryClick/InventoryItemClick.java b/libraries/common/src/main/java/de/oliver/fancylib/gui/inventoryClick/InventoryItemClick.java new file mode 100644 index 00000000..253c5b97 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/gui/inventoryClick/InventoryItemClick.java @@ -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 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); + } + +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/gui/inventoryClick/impl/CancelInventoryItemClick.java b/libraries/common/src/main/java/de/oliver/fancylib/gui/inventoryClick/impl/CancelInventoryItemClick.java new file mode 100644 index 00000000..4aae89f9 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/gui/inventoryClick/impl/CancelInventoryItemClick.java @@ -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); + } +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/gui/inventoryClick/impl/ChangePageInventoryItemClick.java b/libraries/common/src/main/java/de/oliver/fancylib/gui/inventoryClick/impl/ChangePageInventoryItemClick.java new file mode 100644 index 00000000..ef2d8c36 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/gui/inventoryClick/impl/ChangePageInventoryItemClick.java @@ -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 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); + } + + } +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/itemClick/ItemClick.java b/libraries/common/src/main/java/de/oliver/fancylib/itemClick/ItemClick.java new file mode 100644 index 00000000..c8cad356 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/itemClick/ItemClick.java @@ -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); + } + +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/itemClick/ItemClickRegistry.java b/libraries/common/src/main/java/de/oliver/fancylib/itemClick/ItemClickRegistry.java new file mode 100644 index 00000000..502cda94 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/itemClick/ItemClickRegistry.java @@ -0,0 +1,18 @@ +package de.oliver.fancylib.itemClick; + +import java.util.HashMap; +import java.util.Map; + +public class ItemClickRegistry { + + private static final Map 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); + } + +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/itemClick/PlayerInteractListener.java b/libraries/common/src/main/java/de/oliver/fancylib/itemClick/PlayerInteractListener.java new file mode 100644 index 00000000..c8c44d51 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/itemClick/PlayerInteractListener.java @@ -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) { + } + } + +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/jdb/JDB.java b/libraries/common/src/main/java/de/oliver/fancylib/jdb/JDB.java new file mode 100644 index 00000000..fbba075d --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/jdb/JDB.java @@ -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 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 get(@NotNull String path, @NotNull Class 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 data = (Map) 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 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 List getAll(@NotNull String path, @NotNull Class 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 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 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 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; + } +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/jdb/JDocument.java b/libraries/common/src/main/java/de/oliver/fancylib/jdb/JDocument.java new file mode 100644 index 00000000..d23082bc --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/jdb/JDocument.java @@ -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 data; + + public JDocument(@NotNull Map 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 getKeys(String key) { + Map map = (Map) 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 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) value; + } + return null; + } + + private T getValueOrDefault(String key, Class clazz, T defaultValue) { + T value = (T) getValue(key, clazz); + return value != null ? value : defaultValue; + } +} \ No newline at end of file diff --git a/libraries/common/src/main/java/de/oliver/fancylib/serverSoftware/ServerSoftware.java b/libraries/common/src/main/java/de/oliver/fancylib/serverSoftware/ServerSoftware.java new file mode 100644 index 00000000..82cefdb4 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/serverSoftware/ServerSoftware.java @@ -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); + } + +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/serverSoftware/schedulers/BukkitScheduler.java b/libraries/common/src/main/java/de/oliver/fancylib/serverSoftware/schedulers/BukkitScheduler.java new file mode 100644 index 00000000..d1362602 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/serverSoftware/schedulers/BukkitScheduler.java @@ -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(); + } + } + +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/serverSoftware/schedulers/FancyScheduler.java b/libraries/common/src/main/java/de/oliver/fancylib/serverSoftware/schedulers/FancyScheduler.java new file mode 100644 index 00000000..2f0cdbc1 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/serverSoftware/schedulers/FancyScheduler.java @@ -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(); +} \ No newline at end of file diff --git a/libraries/common/src/main/java/de/oliver/fancylib/serverSoftware/schedulers/FoliaScheduler.java b/libraries/common/src/main/java/de/oliver/fancylib/serverSoftware/schedulers/FoliaScheduler.java new file mode 100644 index 00000000..ed65f1a2 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/serverSoftware/schedulers/FoliaScheduler.java @@ -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(); + } + } + +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/tests/Expectable.java b/libraries/common/src/main/java/de/oliver/fancylib/tests/Expectable.java new file mode 100644 index 00000000..ed9e6efc --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/tests/Expectable.java @@ -0,0 +1,243 @@ +package de.oliver.fancylib.tests; + +/** + * A generic class for making assertions on the expected values. + * + * @param the type of the value to be asserted. + */ +public class Expectable { + + /** + * 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 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 Expectable expect(T t) { + return new Expectable<>(t); + } + + /** + * Ensures that the actual value is not null. + *

+ * 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. + *

+ * 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. + *

+ * 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"); + } +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/tests/FPTestClass.java b/libraries/common/src/main/java/de/oliver/fancylib/tests/FPTestClass.java new file mode 100644 index 00000000..c9f221a8 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/tests/FPTestClass.java @@ -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 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 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("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("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("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("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() + ")"; + } + +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/tests/annotations/FPAfterEach.java b/libraries/common/src/main/java/de/oliver/fancylib/tests/annotations/FPAfterEach.java new file mode 100644 index 00000000..e32279b1 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/tests/annotations/FPAfterEach.java @@ -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 { +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/tests/annotations/FPBeforeEach.java b/libraries/common/src/main/java/de/oliver/fancylib/tests/annotations/FPBeforeEach.java new file mode 100644 index 00000000..c8ba9f8a --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/tests/annotations/FPBeforeEach.java @@ -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 { +} + diff --git a/libraries/common/src/main/java/de/oliver/fancylib/tests/annotations/FPTest.java b/libraries/common/src/main/java/de/oliver/fancylib/tests/annotations/FPTest.java new file mode 100644 index 00000000..3add29bb --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/tests/annotations/FPTest.java @@ -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; +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/translations/Language.java b/libraries/common/src/main/java/de/oliver/fancylib/translations/Language.java new file mode 100644 index 00000000..61f74850 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/translations/Language.java @@ -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 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; + } +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/translations/TextConfig.java b/libraries/common/src/main/java/de/oliver/fancylib/translations/TextConfig.java new file mode 100644 index 00000000..f9e6756c --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/translations/TextConfig.java @@ -0,0 +1,12 @@ +package de.oliver.fancylib.translations; + +public record TextConfig( + String primaryColor, + String secondaryColor, + String successColor, + String warningColor, + String errorColor, + String prefix +) { + +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/translations/Translator.java b/libraries/common/src/main/java/de/oliver/fancylib/translations/Translator.java new file mode 100644 index 00000000..1504e39b --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/translations/Translator.java @@ -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 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 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, "Missing translation for key " + key); + } + + return message.copy(); + } + + public List getLanguages() { + return languages; + } + + public Language getSelectedLanguage() { + return selectedLanguage; + } + + public Translator setSelectedLanguage(Language selectedLanguage) { + this.selectedLanguage = selectedLanguage; + return this; + } + + public Language getFallbackLanguage() { + return fallbackLanguage; + } +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/translations/message/Message.java b/libraries/common/src/main/java/de/oliver/fancylib/translations/message/Message.java new file mode 100644 index 00000000..ee55ed27 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/translations/message/Message.java @@ -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", ""); + replace("secondaryColor", ""); + replace("successColor", ""); + replace("warningColor", ""); + replace("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); + } +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/translations/message/MultiMessage.java b/libraries/common/src/main/java/de/oliver/fancylib/translations/message/MultiMessage.java new file mode 100644 index 00000000..f1f74f5f --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/translations/message/MultiMessage.java @@ -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 messages; + + public MultiMessage(TextConfig config, List 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 -> "" + s); + return this; + } + + @Override + public Message secondary() { + messages.replaceAll(s -> "" + s); + return this; + } + + @Override + public Message success() { + messages.replaceAll(s -> "" + s); + return this; + } + + @Override + public Message warning() { + messages.replaceAll(s -> "" + s); + return this; + } + + @Override + public Message error() { + messages.replaceAll(s -> "" + 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 getRawMessages() { + return messages; + } + + public List getSimpleMessages() { + List messages = new ArrayList<>(); + for (String s : this.messages) { + messages.add(new SimpleMessage(config, s)); + } + + return messages; + } + + public MultiMessage page(int page, int messagesPerPage) { + List 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); + } +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/translations/message/SimpleMessage.java b/libraries/common/src/main/java/de/oliver/fancylib/translations/message/SimpleMessage.java new file mode 100644 index 00000000..96f9b177 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/translations/message/SimpleMessage.java @@ -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 = "" + message; + return this; + } + + @Override + public SimpleMessage secondary() { + message = "" + message; + return this; + } + + @Override + public SimpleMessage success() { + message = "" + message; + return this; + } + + @Override + public SimpleMessage warning() { + message = "" + message; + return this; + } + + @Override + public SimpleMessage error() { + message = "" + 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; + } +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/versionFetcher/HangarVersionFetcher.java b/libraries/common/src/main/java/de/oliver/fancylib/versionFetcher/HangarVersionFetcher.java new file mode 100644 index 00000000..21ced68a --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/versionFetcher/HangarVersionFetcher.java @@ -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; + } +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/versionFetcher/MasterVersionFetcher.java b/libraries/common/src/main/java/de/oliver/fancylib/versionFetcher/MasterVersionFetcher.java new file mode 100644 index 00000000..5fdd1dcc --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/versionFetcher/MasterVersionFetcher.java @@ -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 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; + } +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/versionFetcher/ModrinthVersionFetcher.java b/libraries/common/src/main/java/de/oliver/fancylib/versionFetcher/ModrinthVersionFetcher.java new file mode 100644 index 00000000..7c556b7b --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/versionFetcher/ModrinthVersionFetcher.java @@ -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[] versions = gson.fromJson(jsonString, Map[].class); + + + for (Map 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; + } +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/versionFetcher/ReposiliteVersionFetcher.java b/libraries/common/src/main/java/de/oliver/fancylib/versionFetcher/ReposiliteVersionFetcher.java new file mode 100644 index 00000000..2502f4e6 --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/versionFetcher/ReposiliteVersionFetcher.java @@ -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 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; + } +} diff --git a/libraries/common/src/main/java/de/oliver/fancylib/versionFetcher/VersionFetcher.java b/libraries/common/src/main/java/de/oliver/fancylib/versionFetcher/VersionFetcher.java new file mode 100644 index 00000000..fe038e4c --- /dev/null +++ b/libraries/common/src/main/java/de/oliver/fancylib/versionFetcher/VersionFetcher.java @@ -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 ""; + } +} diff --git a/libraries/common/src/test/java/de/oliver/fancylib/jdb/JDBTest.java b/libraries/common/src/test/java/de/oliver/fancylib/jdb/JDBTest.java new file mode 100644 index 00000000..32a0034e --- /dev/null +++ b/libraries/common/src/test/java/de/oliver/fancylib/jdb/JDBTest.java @@ -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() { + @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 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 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) { + } +} diff --git a/libraries/common/src/test/java/de/oliver/fancylib/jdb/JDocumentTest.java b/libraries/common/src/test/java/de/oliver/fancylib/jdb/JDocumentTest.java new file mode 100644 index 00000000..a9bacccf --- /dev/null +++ b/libraries/common/src/test/java/de/oliver/fancylib/jdb/JDocumentTest.java @@ -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 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 nestedData = new HashMap<>(); + nestedData.put("key2", "value2"); + + Map 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 nestedData = new HashMap<>(); + nestedData.put("key2", "value2"); + + Map 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 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 nestedData = new HashMap<>(); + nestedData.put("key2", "value2"); + + Map 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 nestedData = new HashMap<>(); + nestedData.put("key2", "value2"); + + Map 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 innerData = new HashMap<>(); + innerData.put("innerKey1", "value1"); + + Map data = new HashMap<>(); + data.put("key1", innerData); + + JDocument jDocument = new JDocument(data); + + Set 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 keys = jDocument.getKeys("key1"); + + assertNotNull(keys); + assertTrue(keys.isEmpty()); + } + + @Test + public void testGetKeys_Success_MultipleKeys() { + Map innerData = new HashMap<>(); + innerData.put("innerKey1", "value1"); + innerData.put("innerKey2", "value2"); + + Map data = new HashMap<>(); + data.put("key1", innerData); + + JDocument jDocument = new JDocument(data); + + Set 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 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 nestedData = new HashMap<>(); + nestedData.put("key2", "value2"); + + Map 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 nestedData = new HashMap<>(); + nestedData.put("key2", "value2"); + + Map 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 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 nestedData = new HashMap<>(); + nestedData.put("key2", true); + + Map 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 nestedData = new HashMap<>(); + nestedData.put("key2", true); + + Map 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 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 nestedData = new HashMap<>(); + nestedData.put("key2", (byte) 2); + + Map 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 nestedData = new HashMap<>(); + nestedData.put("key2", (byte) 2); + + Map 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 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 nestedData = new HashMap<>(); + nestedData.put("key2", (short) 2); + + Map 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 nestedData = new HashMap<>(); + nestedData.put("key2", (short) 2); + + Map 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 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 nestedData = new HashMap<>(); + nestedData.put("key2", 456); + + Map 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 nestedData = new HashMap<>(); + nestedData.put("key2", 456); + + Map 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 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 nestedData = new HashMap<>(); + nestedData.put("key2", 456L); + + Map 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 nestedData = new HashMap<>(); + nestedData.put("key2", 456L); + + Map 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 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 nestedData = new HashMap<>(); + nestedData.put("key2", 4.56f); + + Map 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 nestedData = new HashMap<>(); + nestedData.put("key2", 4.56f); + + Map 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 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 nestedData = new HashMap<>(); + nestedData.put("key2", 4.56d); + + Map 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 nestedData = new HashMap<>(); + nestedData.put("key2", 4.56d); + + Map data = new HashMap<>(); + data.put("key1", nestedData); + + JDocument jDocument = new JDocument(data); + + double result = jDocument.getDouble("key1.key3"); + + assertEquals(0d, result); + } +} diff --git a/libraries/packets/.gitignore b/libraries/packets/.gitignore new file mode 100644 index 00000000..b7642c01 --- /dev/null +++ b/libraries/packets/.gitignore @@ -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 \ No newline at end of file diff --git a/libraries/packets/README.md b/libraries/packets/README.md new file mode 100644 index 00000000..d1fac8a0 --- /dev/null +++ b/libraries/packets/README.md @@ -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 \ No newline at end of file diff --git a/libraries/packets/api/build.gradle.kts b/libraries/packets/api/build.gradle.kts new file mode 100644 index 00000000..b92d2d31 --- /dev/null +++ b/libraries/packets/api/build.gradle.kts @@ -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 + + } +} \ No newline at end of file diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/IFancySitula.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/IFancySitula.java new file mode 100644 index 00000000..55eff40f --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/IFancySitula.java @@ -0,0 +1,7 @@ +package de.oliver.fancysitula.api; + +import de.oliver.fancyanalytics.logger.ExtendedFancyLogger; + +public interface IFancySitula { + ExtendedFancyLogger LOGGER = new ExtendedFancyLogger("FancySitula"); +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/entities/FS_BlockDisplay.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/entities/FS_BlockDisplay.java new file mode 100644 index 00000000..9f139f4c --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/entities/FS_BlockDisplay.java @@ -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 getEntityData() { + List entityData = super.getEntityData(); + entityData.add(this.blockData); + return entityData; + } +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/entities/FS_Display.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/entities/FS_Display.java new file mode 100644 index 00000000..870cf887 --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/entities/FS_Display.java @@ -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 getEntityData() { + List 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; + } + } + +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/entities/FS_Entity.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/entities/FS_Entity.java new file mode 100644 index 00000000..c29ba2af --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/entities/FS_Entity.java @@ -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 getCustomName() { + return (Optional) customNameData.getValue(); + } + + public void setCustomName(Optional 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 getEntityData() { + List 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; + } +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/entities/FS_ItemDisplay.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/entities/FS_ItemDisplay.java new file mode 100644 index 00000000..ba1ebae9 --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/entities/FS_ItemDisplay.java @@ -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 getEntityData() { + List entityData = super.getEntityData(); + entityData.add(this.itemData); + return entityData; + } +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/entities/FS_Player.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/entities/FS_Player.java new file mode 100644 index 00000000..4eff544d --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/entities/FS_Player.java @@ -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 equipment; + + public FS_Player() { + super(EntityType.PLAYER); + + this.equipment = new ConcurrentHashMap<>(); + } + + public Map getEquipment() { + return equipment; + } + + public void setEquipment(Map equipment) { + this.equipment = equipment; + } + + public void setEquipment(FS_EquipmentSlot slot, ItemStack item) { + this.equipment.put(slot, item); + } +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/entities/FS_RealPlayer.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/entities/FS_RealPlayer.java new file mode 100644 index 00000000..3ced4551 --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/entities/FS_RealPlayer.java @@ -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; + } + +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/entities/FS_TextDisplay.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/entities/FS_TextDisplay.java new file mode 100644 index 00000000..af710a41 --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/entities/FS_TextDisplay.java @@ -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 getEntityData() { + List 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; + } +} + diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundAddEntityPacket.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundAddEntityPacket.java new file mode 100644 index 00000000..ef9ac54e --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundAddEntityPacket.java @@ -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; + } +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundCreateOrUpdateTeamPacket.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundCreateOrUpdateTeamPacket.java new file mode 100644 index 00000000..9a9bf1e7 --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundCreateOrUpdateTeamPacket.java @@ -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 entities; + + public CreateTeam(Component displayName, boolean allowFriendlyFire, boolean canSeeFriendlyInvisibles, FS_NameTagVisibility nameTagVisibility, FS_CollisionRule collisionRule, FS_Color color, Component prefix, Component suffix, List 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 getEntities() { + return entities; + } + + public void setEntities(List 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 entities; + + public AddEntity(List entities) { + this.entities = entities; + } + + public List getEntities() { + return entities; + } + + public void setEntities(List entities) { + this.entities = entities; + } + } + + public static class RemoveEntity { + private List entities; + + public RemoveEntity(List entities) { + this.entities = entities; + } + + public List getEntities() { + return entities; + } + + public void setEntities(List entities) { + this.entities = entities; + } + } + +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundPacket.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundPacket.java new file mode 100644 index 00000000..e8950514 --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundPacket.java @@ -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); + } +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundPlayerInfoRemovePacket.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundPlayerInfoRemovePacket.java new file mode 100644 index 00000000..359c0dbc --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundPlayerInfoRemovePacket.java @@ -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 uuids; + + public FS_ClientboundPlayerInfoRemovePacket(List uuids) { + this.uuids = uuids; + } + + public List getUuids() { + return uuids; + } + + public void setUuids(List uuids) { + this.uuids = uuids; + } +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundPlayerInfoUpdatePacket.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundPlayerInfoUpdatePacket.java new file mode 100644 index 00000000..4e15b7e3 --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundPlayerInfoUpdatePacket.java @@ -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 actions; + protected List entries; + + public FS_ClientboundPlayerInfoUpdatePacket(EnumSet actions, List entries) { + this.actions = actions; + this.entries = entries; + } + + public EnumSet getActions() { + return actions; + } + + public void setActions(EnumSet actions) { + this.actions = actions; + } + + public List getEntries() { + return entries; + } + + public void setEntries(List 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) { + + } + +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundRemoveEntitiesPacket.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundRemoveEntitiesPacket.java new file mode 100644 index 00000000..b307c102 --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundRemoveEntitiesPacket.java @@ -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 entityIds; + + /** + * @param entityIds IDs of the entities to remove + */ + public FS_ClientboundRemoveEntitiesPacket(List entityIds) { + this.entityIds = entityIds; + } + + public List getEntityIds() { + return entityIds; + } + + public void setEntityIds(List entityIds) { + this.entityIds = entityIds; + } +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundRotateHeadPacket.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundRotateHeadPacket.java new file mode 100644 index 00000000..95634821 --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundRotateHeadPacket.java @@ -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; + } +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundSetEntityDataPacket.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundSetEntityDataPacket.java new file mode 100644 index 00000000..a433475d --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundSetEntityDataPacket.java @@ -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; + + public FS_ClientboundSetEntityDataPacket(int entityId, List entityData) { + this.entityId = entityId; + this.entityData = entityData; + } + + public int getEntityId() { + return entityId; + } + + public void setEntityId(int entityId) { + this.entityId = entityId; + } + + public List getEntityData() { + return entityData; + } + + public void setEntityData(List 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) { + } +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundSetEquipmentPacket.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundSetEquipmentPacket.java new file mode 100644 index 00000000..8cad16cf --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundSetEquipmentPacket.java @@ -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 equipment; + + public FS_ClientboundSetEquipmentPacket(int entityId, Map equipment) { + this.entityId = entityId; + this.equipment = equipment; + } + + public int getEntityId() { + return entityId; + } + + public void setEntityId(int entityId) { + this.entityId = entityId; + } + + public Map getEquipment() { + return equipment; + } + + public void setEquipment(Map equipment) { + this.equipment = equipment; + } +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundSetPassengersPacket.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundSetPassengersPacket.java new file mode 100644 index 00000000..34d61971 --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundSetPassengersPacket.java @@ -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 passengers; + + public FS_ClientboundSetPassengersPacket(int entityId, List passengers) { + this.entityId = entityId; + this.passengers = passengers; + } + + public int getEntityId() { + return entityId; + } + + public void setEntityId(int entityId) { + this.entityId = entityId; + } + + public List getPassengers() { + return passengers; + } + + public void setPassengers(List passengers) { + this.passengers = passengers; + } +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundTeleportEntityPacket.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundTeleportEntityPacket.java new file mode 100644 index 00000000..d9495ae7 --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundTeleportEntityPacket.java @@ -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; + } +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_Color.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_Color.java new file mode 100644 index 00000000..7fc2d9f9 --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/packets/FS_Color.java @@ -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; + } +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/teams/FS_CollisionRule.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/teams/FS_CollisionRule.java new file mode 100644 index 00000000..d64ec068 --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/teams/FS_CollisionRule.java @@ -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; + } +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/teams/FS_NameTagVisibility.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/teams/FS_NameTagVisibility.java new file mode 100644 index 00000000..355026c1 --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/teams/FS_NameTagVisibility.java @@ -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; + } +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/teams/FS_Team.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/teams/FS_Team.java new file mode 100644 index 00000000..9302d3bb --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/teams/FS_Team.java @@ -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 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 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 getEntities() { + return entities; + } + + public void setEntities(List entities) { + this.entities = entities; + } +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/AngelConverter.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/AngelConverter.java new file mode 100644 index 00000000..21bc110e --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/AngelConverter.java @@ -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); + } + +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/FS_EquipmentSlot.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/FS_EquipmentSlot.java new file mode 100644 index 00000000..88e8d474 --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/FS_EquipmentSlot.java @@ -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; + }; + } +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/FS_GameProfile.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/FS_GameProfile.java new file mode 100644 index 00000000..e6be6298 --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/FS_GameProfile.java @@ -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 properties; + + public FS_GameProfile(UUID uuid, String name, Map 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 getProperties() { + return properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + + public record Property(String name, String value, String signature) { + } +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/FS_GameType.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/FS_GameType.java new file mode 100644 index 00000000..d48b9e2e --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/FS_GameType.java @@ -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; + } +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/ServerVersion.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/ServerVersion.java new file mode 100644 index 00000000..c3fe97be --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/ServerVersion.java @@ -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 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; + } +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/entityData/FS_BlockDisplayData.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/entityData/FS_BlockDisplayData.java new file mode 100644 index 00000000..50a5c740 --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/entityData/FS_BlockDisplayData.java @@ -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"); + +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/entityData/FS_DisplayData.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/entityData/FS_DisplayData.java new file mode 100644 index 00000000..b9469654 --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/entityData/FS_DisplayData.java @@ -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"); + +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/entityData/FS_EntityData.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/entityData/FS_EntityData.java new file mode 100644 index 00000000..79463d31 --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/entityData/FS_EntityData.java @@ -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} 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"); + +} + diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/entityData/FS_ItemDisplayData.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/entityData/FS_ItemDisplayData.java new file mode 100644 index 00000000..012cf46b --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/entityData/FS_ItemDisplayData.java @@ -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"); + +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/entityData/FS_TextDisplayData.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/entityData/FS_TextDisplayData.java new file mode 100644 index 00000000..7c7ecb30 --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/entityData/FS_TextDisplayData.java @@ -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"); +} diff --git a/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/reflections/ReflectionUtils.java b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/reflections/ReflectionUtils.java new file mode 100644 index 00000000..a708eda0 --- /dev/null +++ b/libraries/packets/api/src/main/java/de/oliver/fancysitula/api/utils/reflections/ReflectionUtils.java @@ -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 createUnsafeInstance(Class clazz) throws Exception { + Unsafe unsafe = getUnsafe(); + return (T) unsafe.allocateInstance(clazz); + } + + public static T createInstance(Class clazz) throws Exception { + Constructor 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 getStaticField(Class clazz, String fieldName) throws NoSuchFieldException, IllegalAccessException { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(null); + } +} diff --git a/libraries/packets/build.gradle.kts b/libraries/packets/build.gradle.kts new file mode 100644 index 00000000..711f4ddf --- /dev/null +++ b/libraries/packets/build.gradle.kts @@ -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("basic") + } + } + + maven { + name = "fancypluginsSnapshots" + url = uri("https://repo.fancyplugins.de/snapshots") + credentials(PasswordCredentials::class) + authentication { + isAllowInsecureProtocol = true + create("basic") + } + } + } + publications { + create("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)) +} \ No newline at end of file diff --git a/libraries/packets/factories/build.gradle.kts b/libraries/packets/factories/build.gradle.kts new file mode 100644 index 00000000..a3d118a1 --- /dev/null +++ b/libraries/packets/factories/build.gradle.kts @@ -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 + + } +} \ No newline at end of file diff --git a/libraries/packets/factories/src/main/java/de/oliver/fancysitula/factories/EntityFactory.java b/libraries/packets/factories/src/main/java/de/oliver/fancysitula/factories/EntityFactory.java new file mode 100644 index 00000000..c6f91d54 --- /dev/null +++ b/libraries/packets/factories/src/main/java/de/oliver/fancysitula/factories/EntityFactory.java @@ -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); + } + +} diff --git a/libraries/packets/factories/src/main/java/de/oliver/fancysitula/factories/FancySitula.java b/libraries/packets/factories/src/main/java/de/oliver/fancysitula/factories/FancySitula.java new file mode 100644 index 00000000..e16f4bd3 --- /dev/null +++ b/libraries/packets/factories/src/main/java/de/oliver/fancysitula/factories/FancySitula.java @@ -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(); +} diff --git a/libraries/packets/factories/src/main/java/de/oliver/fancysitula/factories/PacketFactory.java b/libraries/packets/factories/src/main/java/de/oliver/fancysitula/factories/PacketFactory.java new file mode 100644 index 00000000..75bdc266 --- /dev/null +++ b/libraries/packets/factories/src/main/java/de/oliver/fancysitula/factories/PacketFactory.java @@ -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 actions, + List 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 actions, + List 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 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 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 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 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 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 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 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 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 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 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); + } +} diff --git a/libraries/packets/factories/src/main/java/de/oliver/fancysitula/factories/TeamFactory.java b/libraries/packets/factories/src/main/java/de/oliver/fancysitula/factories/TeamFactory.java new file mode 100644 index 00000000..7141507b --- /dev/null +++ b/libraries/packets/factories/src/main/java/de/oliver/fancysitula/factories/TeamFactory.java @@ -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 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 entities) { + FS_ClientboundCreateOrUpdateTeamPacket packet = FancySitula.PACKET_FACTORY.createCreateOrUpdateTeamPacket( + team.getTeamName(), + new FS_ClientboundCreateOrUpdateTeamPacket.RemoveEntity(entities) + ); + + player.sendPacket(packet); + } +} diff --git a/libraries/packets/fancysitula_title.png b/libraries/packets/fancysitula_title.png new file mode 100644 index 00000000..dd52e704 Binary files /dev/null and b/libraries/packets/fancysitula_title.png differ diff --git a/libraries/packets/gradle.properties b/libraries/packets/gradle.properties new file mode 100644 index 00000000..94918500 --- /dev/null +++ b/libraries/packets/gradle.properties @@ -0,0 +1,2 @@ +minecraftVersion=1.21.3 +fancyLoggerVersion=0.0.4 \ No newline at end of file diff --git a/libraries/packets/implementations/1_20_6/build.gradle.kts b/libraries/packets/implementations/1_20_6/build.gradle.kts new file mode 100644 index 00000000..7aefac23 --- /dev/null +++ b/libraries/packets/implementations/1_20_6/build.gradle.kts @@ -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() + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundAddEntityPacketImpl.java b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundAddEntityPacketImpl.java new file mode 100644 index 00000000..c73996c6 --- /dev/null +++ b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundAddEntityPacketImpl.java @@ -0,0 +1,48 @@ +package de.oliver.fancysitula.versions.v1_20_6.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundAddEntityPacket; +import de.oliver.fancysitula.api.utils.AngelConverter; +import de.oliver.fancysitula.versions.v1_20_6.utils.VanillaPlayerAdapter; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.phys.Vec3; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.bukkit.entity.EntityType; + +import java.util.UUID; + +public class ClientboundAddEntityPacketImpl extends FS_ClientboundAddEntityPacket { + + public ClientboundAddEntityPacketImpl(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) { + super(entityId, entityUUID, entityType, x, y, z, yaw, pitch, headYaw, velocityX, velocityY, velocityZ, data); + } + + @Override + public Object createPacket() { + net.minecraft.world.entity.EntityType vanillaType = BuiltInRegistries.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(entityType.getKey())); + + return new ClientboundAddEntityPacket( + entityId, + entityUUID, + x, + y, + z, + AngelConverter.degreesToVanillaByte(pitch), + AngelConverter.degreesToVanillaByte(yaw), + vanillaType, + data, + new Vec3(velocityX, velocityY, velocityZ), + AngelConverter.degreesToVanillaByte(headYaw) + ); + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundAddEntityPacket packet = (ClientboundAddEntityPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundCreateOrUpdateTeamPacketImpl.java b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundCreateOrUpdateTeamPacketImpl.java new file mode 100644 index 00000000..04aa823a --- /dev/null +++ b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundCreateOrUpdateTeamPacketImpl.java @@ -0,0 +1,128 @@ +package de.oliver.fancysitula.versions.v1_20_6.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundCreateOrUpdateTeamPacket; +import de.oliver.fancysitula.versions.v1_20_6.utils.VanillaPlayerAdapter; +import io.papermc.paper.adventure.PaperAdventure; +import net.minecraft.ChatFormatting; +import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.scores.PlayerTeam; +import net.minecraft.world.scores.Scoreboard; +import net.minecraft.world.scores.Team; + +public class ClientboundCreateOrUpdateTeamPacketImpl extends FS_ClientboundCreateOrUpdateTeamPacket { + + private static final Scoreboard SCOREBOARD = new Scoreboard(); + + public ClientboundCreateOrUpdateTeamPacketImpl(String teamName, CreateTeam createTeam) { + super(teamName, createTeam); + } + + public ClientboundCreateOrUpdateTeamPacketImpl(String teamName, RemoveTeam removeTeam) { + super(teamName, removeTeam); + } + + public ClientboundCreateOrUpdateTeamPacketImpl(String teamName, UpdateTeam updateTeam) { + super(teamName, updateTeam); + } + + public ClientboundCreateOrUpdateTeamPacketImpl(String teamName, AddEntity addEntity) { + super(teamName, addEntity); + } + + public ClientboundCreateOrUpdateTeamPacketImpl(String teamName, RemoveEntity removeEntity) { + super(teamName, removeEntity); + } + + @Override + public Object createPacket() { + return switch (method) { + case CREATE_TEAM -> createCreateTeamPacket(); + case REMOVE_TEAM -> createRemoveTeamPacket(); + case UPDATE_TEAM -> createUpdateTeamPacket(); + case ADD_ENTITY -> createAddEntityPacket(); + case REMOVE_ENTITY -> createRemoveEntityPacket(); + }; + } + + private Object createCreateTeamPacket() { + if (createTeam == null) { + return null; + } + + PlayerTeam playerTeam = new PlayerTeam(SCOREBOARD, teamName); + playerTeam.setDisplayName(PaperAdventure.asVanilla(createTeam.getDisplayName())); + playerTeam.setAllowFriendlyFire(createTeam.isAllowFriendlyFire()); + playerTeam.setSeeFriendlyInvisibles(createTeam.isCanSeeFriendlyInvisibles()); + playerTeam.setNameTagVisibility(Team.Visibility.byName(createTeam.getNameTagVisibility().getName())); + playerTeam.setCollisionRule(PlayerTeam.CollisionRule.byName(createTeam.getCollisionRule().getName())); + playerTeam.setColor(ChatFormatting.getById(createTeam.getColor().getId())); + playerTeam.setPlayerPrefix(PaperAdventure.asVanilla(createTeam.getPrefix())); + playerTeam.setPlayerSuffix(PaperAdventure.asVanilla(createTeam.getSuffix())); + for (String entity : createTeam.getEntities()) { + playerTeam.getPlayers().add(entity); + } + + return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(playerTeam, true); + } + + private Object createRemoveTeamPacket() { + if (removeTeam == null) { + return null; + } + + PlayerTeam playerTeam = new PlayerTeam(SCOREBOARD, teamName); + return ClientboundSetPlayerTeamPacket.createRemovePacket(playerTeam); + } + + private Object createUpdateTeamPacket() { + if (updateTeam == null) { + return null; + } + + PlayerTeam playerTeam = new PlayerTeam(SCOREBOARD, teamName); + playerTeam.setDisplayName(PaperAdventure.asVanilla(updateTeam.getDisplayName())); + playerTeam.setAllowFriendlyFire(updateTeam.isAllowFriendlyFire()); + playerTeam.setSeeFriendlyInvisibles(updateTeam.isCanSeeFriendlyInvisibles()); + playerTeam.setNameTagVisibility(Team.Visibility.byName(updateTeam.getNameTagVisibility().getName())); + playerTeam.setCollisionRule(PlayerTeam.CollisionRule.byName(updateTeam.getCollisionRule().getName())); + playerTeam.setColor(ChatFormatting.getById(updateTeam.getColor().getId())); + playerTeam.setPlayerPrefix(PaperAdventure.asVanilla(updateTeam.getPrefix())); + playerTeam.setPlayerSuffix(PaperAdventure.asVanilla(updateTeam.getSuffix())); + + return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(playerTeam, true); + } + + private Object createAddEntityPacket() { + if (addEntity == null) { + return null; + } + + PlayerTeam playerTeam = new PlayerTeam(SCOREBOARD, teamName); + for (String entity : addEntity.getEntities()) { + playerTeam.getPlayers().add(entity); + } + return ClientboundSetPlayerTeamPacket.createMultiplePlayerPacket(playerTeam, addEntity.getEntities(), ClientboundSetPlayerTeamPacket.Action.ADD); + } + + private Object createRemoveEntityPacket() { + if (removeEntity == null) { + return null; + } + + PlayerTeam playerTeam = new PlayerTeam(SCOREBOARD, teamName); + for (String entity : removeEntity.getEntities()) { + playerTeam.getPlayers().add(entity); + } + return ClientboundSetPlayerTeamPacket.createMultiplePlayerPacket(playerTeam, removeEntity.getEntities(), ClientboundSetPlayerTeamPacket.Action.REMOVE); + } + + @Override + protected void sendPacketTo(FS_RealPlayer player) { + ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundPlayerInfoRemovePacketImpl.java b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundPlayerInfoRemovePacketImpl.java new file mode 100644 index 00000000..8a4626f4 --- /dev/null +++ b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundPlayerInfoRemovePacketImpl.java @@ -0,0 +1,30 @@ +package de.oliver.fancysitula.versions.v1_20_6.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundPlayerInfoRemovePacket; +import de.oliver.fancysitula.versions.v1_20_6.utils.VanillaPlayerAdapter; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; +import net.minecraft.server.level.ServerPlayer; + +import java.util.List; +import java.util.UUID; + +public class ClientboundPlayerInfoRemovePacketImpl extends FS_ClientboundPlayerInfoRemovePacket { + + public ClientboundPlayerInfoRemovePacketImpl(List uuids) { + super(uuids); + } + + @Override + public Object createPacket() { + return new ClientboundPlayerInfoRemovePacket(uuids); + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundPlayerInfoRemovePacket packet = (ClientboundPlayerInfoRemovePacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundPlayerInfoUpdatePacketImpl.java b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundPlayerInfoUpdatePacketImpl.java new file mode 100644 index 00000000..b49c9854 --- /dev/null +++ b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundPlayerInfoUpdatePacketImpl.java @@ -0,0 +1,52 @@ +package de.oliver.fancysitula.versions.v1_20_6.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundPlayerInfoUpdatePacket; +import de.oliver.fancysitula.versions.v1_20_6.utils.GameProfileImpl; +import de.oliver.fancysitula.versions.v1_20_6.utils.VanillaPlayerAdapter; +import io.papermc.paper.adventure.PaperAdventure; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.GameType; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +public class ClientboundPlayerInfoUpdatePacketImpl extends FS_ClientboundPlayerInfoUpdatePacket { + + public ClientboundPlayerInfoUpdatePacketImpl(EnumSet actions, List entries) { + super(actions, entries); + } + + @Override + public Object createPacket() { + EnumSet vanillaActions = EnumSet.noneOf(ClientboundPlayerInfoUpdatePacket.Action.class); + for (FS_ClientboundPlayerInfoUpdatePacket.Action action : actions) { + vanillaActions.add(ClientboundPlayerInfoUpdatePacket.Action.valueOf(action.name())); + } + + List entries = new ArrayList<>(); + for (Entry entry : this.entries) { + entries.add(new ClientboundPlayerInfoUpdatePacket.Entry( + entry.uuid(), + GameProfileImpl.asVanilla(entry.profile()), + entry.listed(), + entry.latency(), + GameType.byId(entry.gameMode().getId()), + PaperAdventure.asVanilla(entry.displayName()), + null // TODO: Add ChatSession support + )); + } + + return new ClientboundPlayerInfoUpdatePacket(vanillaActions, entries); + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundPlayerInfoUpdatePacket packet = (ClientboundPlayerInfoUpdatePacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundRemoveEntitiesPacketImpl.java b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundRemoveEntitiesPacketImpl.java new file mode 100644 index 00000000..7b3c35a1 --- /dev/null +++ b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundRemoveEntitiesPacketImpl.java @@ -0,0 +1,37 @@ +package de.oliver.fancysitula.versions.v1_20_6.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundRemoveEntitiesPacket; +import de.oliver.fancysitula.versions.v1_20_6.utils.VanillaPlayerAdapter; +import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; +import net.minecraft.server.level.ServerPlayer; + +import java.util.List; + +public class ClientboundRemoveEntitiesPacketImpl extends FS_ClientboundRemoveEntitiesPacket { + + /** + * @param entityIds IDs of the entities to remove + */ + public ClientboundRemoveEntitiesPacketImpl(List entityIds) { + super(entityIds); + } + + @Override + public Object createPacket() { + int[] ids = new int[this.entityIds.size()]; + for (int i = 0; i < this.entityIds.size(); i++) { + ids[i] = this.entityIds.get(i); + } + + return new ClientboundRemoveEntitiesPacket(ids); + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundRemoveEntitiesPacket packet = (ClientboundRemoveEntitiesPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundRotateHeadPacketImpl.java b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundRotateHeadPacketImpl.java new file mode 100644 index 00000000..8d1a8c05 --- /dev/null +++ b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundRotateHeadPacketImpl.java @@ -0,0 +1,38 @@ +package de.oliver.fancysitula.versions.v1_20_6.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundRotateHeadPacket; +import de.oliver.fancysitula.api.utils.AngelConverter; +import de.oliver.fancysitula.api.utils.reflections.ReflectionUtils; +import de.oliver.fancysitula.versions.v1_20_6.utils.VanillaPlayerAdapter; +import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket; +import net.minecraft.server.level.ServerPlayer; + +public class ClientboundRotateHeadPacketImpl extends FS_ClientboundRotateHeadPacket { + + public ClientboundRotateHeadPacketImpl(int entityId, float headYaw) { + super(entityId, headYaw); + } + + @Override + public Object createPacket() { + ClientboundRotateHeadPacket packet = null; + + try { + packet = ReflectionUtils.createUnsafeInstance(ClientboundRotateHeadPacket.class); + ReflectionUtils.setFinalField(packet, "entityId", entityId); + ReflectionUtils.setFinalField(packet, "yHeadRot", AngelConverter.degreesToVanillaByte(headYaw)); + } catch (Exception e) { + e.printStackTrace(); + } + return packet; + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundRotateHeadPacket packet = (ClientboundRotateHeadPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetEntityDataPacketImpl.java b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetEntityDataPacketImpl.java new file mode 100644 index 00000000..573d7e8d --- /dev/null +++ b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetEntityDataPacketImpl.java @@ -0,0 +1,67 @@ +package de.oliver.fancysitula.versions.v1_20_6.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundSetEntityDataPacket; +import de.oliver.fancysitula.api.utils.reflections.ReflectionUtils; +import de.oliver.fancysitula.versions.v1_20_6.utils.VanillaPlayerAdapter; +import io.papermc.paper.adventure.PaperAdventure; +import net.kyori.adventure.text.Component; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.level.ServerPlayer; +import org.bukkit.block.BlockState; +import org.bukkit.craftbukkit.block.CraftBlockState; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; + +public class ClientboundSetEntityDataPacketImpl extends FS_ClientboundSetEntityDataPacket { + + public ClientboundSetEntityDataPacketImpl(int entityId, List entityData) { + super(entityId, entityData); + } + + @Override + public Object createPacket() { + List> dataValues = new ArrayList<>(); + for (EntityData data : entityData) { + try { + Class entityClass = Class.forName(data.getAccessor().entityClassName()); + net.minecraft.network.syncher.EntityDataAccessor accessor = ReflectionUtils.getStaticField(entityClass, data.getAccessor().accessorFieldName()); + + Object vanillaValue = data.getValue(); + + if (data.getValue() == null) { + continue; + } + + if (data.getValue() instanceof Component c) { + vanillaValue = PaperAdventure.asVanilla(c); + } + + if (data.getValue() instanceof ItemStack i) { + vanillaValue = net.minecraft.world.item.ItemStack.fromBukkitCopy(i); + } + + if (data.getValue() instanceof BlockState b) { + vanillaValue = ((CraftBlockState) b).getHandle(); + } + + dataValues.add(SynchedEntityData.DataValue.create(accessor, vanillaValue)); + } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + } + + return new ClientboundSetEntityDataPacket(entityId, dataValues); + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundSetEntityDataPacket packet = (ClientboundSetEntityDataPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetEquipmentPacketImpl.java b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetEquipmentPacketImpl.java new file mode 100644 index 00000000..e25b481e --- /dev/null +++ b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetEquipmentPacketImpl.java @@ -0,0 +1,45 @@ +package de.oliver.fancysitula.versions.v1_20_6.packets; + +import com.mojang.datafixers.util.Pair; +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundSetEquipmentPacket; +import de.oliver.fancysitula.api.utils.FS_EquipmentSlot; +import de.oliver.fancysitula.versions.v1_20_6.utils.VanillaPlayerAdapter; +import net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.EquipmentSlot; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class ClientboundSetEquipmentPacketImpl extends FS_ClientboundSetEquipmentPacket { + + public ClientboundSetEquipmentPacketImpl(int entityId, Map equipment) { + super(entityId, equipment); + } + + @Override + public Object createPacket() { + List> slots = new ArrayList<>(); + + for (Map.Entry entry : equipment.entrySet()) { + EquipmentSlot equipmentSlot = net.minecraft.world.entity.EquipmentSlot.byName(entry.getKey().name().toLowerCase()); + net.minecraft.world.item.ItemStack itemStack = CraftItemStack.asNMSCopy(entry.getValue()); + + slots.add(Pair.of(equipmentSlot, itemStack)); + } + + return new ClientboundSetEquipmentPacket(entityId, slots); + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundSetEquipmentPacket packet = (ClientboundSetEquipmentPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetPassengersPacketImpl.java b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetPassengersPacketImpl.java new file mode 100644 index 00000000..1b201810 --- /dev/null +++ b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetPassengersPacketImpl.java @@ -0,0 +1,45 @@ +package de.oliver.fancysitula.versions.v1_20_6.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundSetPassengersPacket; +import de.oliver.fancysitula.api.utils.reflections.ReflectionUtils; +import de.oliver.fancysitula.versions.v1_20_6.utils.VanillaPlayerAdapter; +import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket; +import net.minecraft.server.level.ServerPlayer; + +import java.util.List; + +public class ClientboundSetPassengersPacketImpl extends FS_ClientboundSetPassengersPacket { + + public ClientboundSetPassengersPacketImpl(int entityId, List passengers) { + super(entityId, passengers); + } + + + @Override + public Object createPacket() { + int[] passengers = new int[this.passengers.size()]; + for (int i = 0; i < this.passengers.size(); i++) { + passengers[i] = this.passengers.get(i); + } + + try { + ClientboundSetPassengersPacket packet = ReflectionUtils.createUnsafeInstance(ClientboundSetPassengersPacket.class); + ReflectionUtils.setFinalField(packet, "vehicle", entityId); + ReflectionUtils.setFinalField(packet, "passengers", passengers); + return packet; + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundSetPassengersPacket packet = (ClientboundSetPassengersPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundTeleportEntityPacketImpl.java b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundTeleportEntityPacketImpl.java new file mode 100644 index 00000000..9221e15d --- /dev/null +++ b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundTeleportEntityPacketImpl.java @@ -0,0 +1,43 @@ +package de.oliver.fancysitula.versions.v1_20_6.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundTeleportEntityPacket; +import de.oliver.fancysitula.api.utils.AngelConverter; +import de.oliver.fancysitula.api.utils.reflections.ReflectionUtils; +import de.oliver.fancysitula.versions.v1_20_6.utils.VanillaPlayerAdapter; +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; +import net.minecraft.server.level.ServerPlayer; + +public class ClientboundTeleportEntityPacketImpl extends FS_ClientboundTeleportEntityPacket { + + public ClientboundTeleportEntityPacketImpl(int entityId, double x, double y, double z, float yaw, float pitch, boolean onGround) { + super(entityId, x, y, z, yaw, pitch, onGround); + } + + @Override + public Object createPacket() { + ClientboundTeleportEntityPacket packet = null; + + try { + packet = ReflectionUtils.createUnsafeInstance(ClientboundTeleportEntityPacket.class); + ReflectionUtils.setFinalField(packet, "id", entityId); + ReflectionUtils.setFinalField(packet, "x", x); + ReflectionUtils.setFinalField(packet, "y", y); + ReflectionUtils.setFinalField(packet, "z", z); + ReflectionUtils.setFinalField(packet, "yRot", AngelConverter.degreesToVanillaByte(yaw)); + ReflectionUtils.setFinalField(packet, "xRot", AngelConverter.degreesToVanillaByte(pitch)); + ReflectionUtils.setFinalField(packet, "onGround", onGround); + } catch (Exception e) { + e.printStackTrace(); + } + return packet; + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundTeleportEntityPacket packet = (ClientboundTeleportEntityPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/utils/GameProfileImpl.java b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/utils/GameProfileImpl.java new file mode 100644 index 00000000..2fc36021 --- /dev/null +++ b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/utils/GameProfileImpl.java @@ -0,0 +1,33 @@ +package de.oliver.fancysitula.versions.v1_20_6.utils; + +import com.mojang.authlib.GameProfile; +import de.oliver.fancysitula.api.utils.FS_GameProfile; + +import java.util.Map; + +public class GameProfileImpl { + + public static GameProfile asVanilla(FS_GameProfile gameProfile) { + GameProfile gf = new GameProfile(gameProfile.getUUID(), gameProfile.getName()); + + for (Map.Entry entry : gameProfile.getProperties().entrySet()) { + FS_GameProfile.Property property = entry.getValue(); + + gf.getProperties().put(entry.getKey(), new com.mojang.authlib.properties.Property(property.name(), property.value(), property.signature())); + } + + return gf; + } + + public static FS_GameProfile fromVanilla(GameProfile gameProfile) { + FS_GameProfile fsGameProfile = new FS_GameProfile(gameProfile.getId(), gameProfile.getName()); + + for (Map.Entry entry : gameProfile.getProperties().entries()) { + com.mojang.authlib.properties.Property property = entry.getValue(); + + fsGameProfile.getProperties().put(entry.getKey(), new FS_GameProfile.Property(property.name(), property.value(), property.signature())); + } + + return fsGameProfile; + } +} diff --git a/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/utils/VanillaPlayerAdapter.java b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/utils/VanillaPlayerAdapter.java new file mode 100644 index 00000000..40bae3df --- /dev/null +++ b/libraries/packets/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/utils/VanillaPlayerAdapter.java @@ -0,0 +1,12 @@ +package de.oliver.fancysitula.versions.v1_20_6.utils; + +import net.minecraft.server.level.ServerPlayer; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; + +public class VanillaPlayerAdapter { + + public static ServerPlayer asVanilla(Player p) { + return ((CraftPlayer) p).getHandle(); + } +} diff --git a/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundAddEntityPacketImplTest.java b/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundAddEntityPacketImplTest.java new file mode 100644 index 00000000..90779a77 --- /dev/null +++ b/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundAddEntityPacketImplTest.java @@ -0,0 +1,64 @@ +package de.oliver.fancysitula.versions.v1_20_6.packets; + +import de.oliver.fancysitula.api.utils.AngelConverter; +import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; +import org.bukkit.entity.EntityType; + +import java.util.UUID; + +class ClientboundAddEntityPacketImplTest { + + //TODO: Fix this test (registry problems) + // @Test + void createPacket() { + int entityId = 10000; + UUID entityUUID = UUID.randomUUID(); + EntityType entityType = EntityType.PIG; + + double x = 5; + double y = 57; + double z = 203; + + float yaw = 142; + float pitch = 247; + float headYaw = 90; + + int velocityX = 0; + int velocityY = 0; + int velocityZ = 0; + + int data = 0; + + ClientboundAddEntityPacketImpl packet = new ClientboundAddEntityPacketImpl( + entityId, + entityUUID, + entityType, + x, + y, + z, + yaw, + pitch, + headYaw, + velocityX, + velocityY, + velocityZ, + data + ); + + ClientboundAddEntityPacket createdPacket = (ClientboundAddEntityPacket) packet.createPacket(); + + assert createdPacket.getId() == entityId; + assert createdPacket.getUUID().equals(entityUUID); + assert createdPacket.getType().getDescriptionId().equals(entityType.getKey().getKey()); + assert createdPacket.getX() == x; + assert createdPacket.getY() == y; + assert createdPacket.getZ() == z; + assert createdPacket.getYRot() == AngelConverter.degreesToVanillaByte(yaw); + assert createdPacket.getXRot() == AngelConverter.degreesToVanillaByte(pitch); + assert createdPacket.getYHeadRot() == AngelConverter.degreesToVanillaByte(headYaw); + assert createdPacket.getXa() == velocityX; + assert createdPacket.getYa() == velocityY; + assert createdPacket.getZa() == velocityZ; + assert createdPacket.getData() == data; + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundPlayerInfoRemovePacketImplTest.java b/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundPlayerInfoRemovePacketImplTest.java new file mode 100644 index 00000000..c741c052 --- /dev/null +++ b/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundPlayerInfoRemovePacketImplTest.java @@ -0,0 +1,22 @@ +package de.oliver.fancysitula.versions.v1_20_6.packets; + +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.UUID; + +class ClientboundPlayerInfoRemovePacketImplTest { + + @Test + void createPacket() { + List uuids = List.of(UUID.randomUUID(), UUID.randomUUID()); + + ClientboundPlayerInfoRemovePacketImpl packet = new ClientboundPlayerInfoRemovePacketImpl(uuids); + ClientboundPlayerInfoRemovePacket vanillaPacket = (ClientboundPlayerInfoRemovePacket) packet.createPacket(); + + for (UUID uuid : uuids) { + assert vanillaPacket.profileIds().contains(uuid); + } + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundPlayerInfoUpdatePacketImplTest.java b/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundPlayerInfoUpdatePacketImplTest.java new file mode 100644 index 00000000..d5e830cb --- /dev/null +++ b/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundPlayerInfoUpdatePacketImplTest.java @@ -0,0 +1,61 @@ +package de.oliver.fancysitula.versions.v1_20_6.packets; + +import de.oliver.fancysitula.api.packets.FS_ClientboundPlayerInfoUpdatePacket; +import de.oliver.fancysitula.api.utils.FS_GameProfile; +import de.oliver.fancysitula.api.utils.FS_GameType; +import net.kyori.adventure.text.Component; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.UUID; + +class ClientboundPlayerInfoUpdatePacketImplTest { + + @Test + void createPacket() { + // Setup packet + EnumSet actions = EnumSet.noneOf(FS_ClientboundPlayerInfoUpdatePacket.Action.class); + actions.add(FS_ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER); + actions.add(FS_ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME); + actions.add(FS_ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED); + + FS_GameProfile gameProfile = new FS_GameProfile(UUID.randomUUID(), "Test name"); + boolean listed = true; + int latency = 42; + FS_GameType gameMode = FS_GameType.SURVIVAL; + Component displayName = Component.text("Test displayname"); + + List entries = new ArrayList<>(); + entries.add(new FS_ClientboundPlayerInfoUpdatePacket.Entry( + gameProfile.getUUID(), + gameProfile, + listed, + latency, + gameMode, + displayName + )); + + ClientboundPlayerInfoUpdatePacketImpl packet = new ClientboundPlayerInfoUpdatePacketImpl(actions, entries); + + ClientboundPlayerInfoUpdatePacket createdPacket = (ClientboundPlayerInfoUpdatePacket) packet.createPacket(); + + assert createdPacket.entries().size() == 1; + assert createdPacket.actions().size() == 3; + + // check entry + ClientboundPlayerInfoUpdatePacket.Entry entry = createdPacket.entries().getFirst(); + assert entry.profile().getId().equals(gameProfile.getUUID()); + assert entry.profile().getName().equals(gameProfile.getName()); + assert entry.listed() == listed; + assert entry.latency() == latency; + assert entry.gameMode().getId() == gameMode.getId(); + + // check actions + assert createdPacket.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER); + assert createdPacket.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME); + assert createdPacket.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED); + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundRemoveEntitiesPacketImplTest.java b/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundRemoveEntitiesPacketImplTest.java new file mode 100644 index 00000000..1fadcfd1 --- /dev/null +++ b/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundRemoveEntitiesPacketImplTest.java @@ -0,0 +1,20 @@ +package de.oliver.fancysitula.versions.v1_20_6.packets; + +import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; +import org.junit.jupiter.api.Test; + +import java.util.List; + +class ClientboundRemoveEntitiesPacketImplTest { + + @Test + void createPacket() { + List entityIds = List.of(95, 120, 154, 187); + + ClientboundRemoveEntitiesPacketImpl packet = new ClientboundRemoveEntitiesPacketImpl(entityIds); + ClientboundRemoveEntitiesPacket createdPacket = (ClientboundRemoveEntitiesPacket) packet.createPacket(); + + assert createdPacket.getEntityIds().size() == entityIds.size(); + assert createdPacket.getEntityIds().containsAll(entityIds); + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundRotateHeadPacketImplTest.java b/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundRotateHeadPacketImplTest.java new file mode 100644 index 00000000..632cc6db --- /dev/null +++ b/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundRotateHeadPacketImplTest.java @@ -0,0 +1,21 @@ +package de.oliver.fancysitula.versions.v1_20_6.packets; + +import de.oliver.fancysitula.api.utils.AngelConverter; +import de.oliver.fancysitula.api.utils.reflections.ReflectionUtils; +import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket; +import org.junit.jupiter.api.Test; + +class ClientboundRotateHeadPacketImplTest { + + @Test + void createPacket() throws Exception { + int entityId = 184; + float headYaw = 45; + + ClientboundRotateHeadPacketImpl packet = new ClientboundRotateHeadPacketImpl(entityId, (byte) headYaw); + ClientboundRotateHeadPacket createdPacket = (ClientboundRotateHeadPacket) packet.createPacket(); + + assert ReflectionUtils.getField(createdPacket, "entityId").equals(entityId); + assert createdPacket.getYHeadRot() == AngelConverter.degreesToVanillaByte(headYaw); + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetEntityDataPacketImplTest.java b/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetEntityDataPacketImplTest.java new file mode 100644 index 00000000..96c8a24b --- /dev/null +++ b/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetEntityDataPacketImplTest.java @@ -0,0 +1,28 @@ +package de.oliver.fancysitula.versions.v1_20_6.packets; + +import de.oliver.fancysitula.api.packets.FS_ClientboundSetEntityDataPacket; +import de.oliver.fancysitula.api.utils.entityData.FS_TextDisplayData; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; + +import java.util.List; + +class ClientboundSetEntityDataPacketImplTest { + + //TODO: Fix this test (using registry) +// @Test + void createPacket() { + int entityId = 712; + List entityData = List.of( + new FS_ClientboundSetEntityDataPacket.EntityData( + FS_TextDisplayData.TEXT, + "Hello, World!" + ) + ); + + ClientboundSetEntityDataPacketImpl packet = new ClientboundSetEntityDataPacketImpl(entityId, entityData); + ClientboundSetEntityDataPacket createdPacket = (ClientboundSetEntityDataPacket) packet.createPacket(); + + assert createdPacket.id() == entityId; + assert createdPacket.packedItems().size() == 1; + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetEquipmentPacketImplTest.java b/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetEquipmentPacketImplTest.java new file mode 100644 index 00000000..4206614a --- /dev/null +++ b/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetEquipmentPacketImplTest.java @@ -0,0 +1,28 @@ +package de.oliver.fancysitula.versions.v1_20_6.packets; + +import de.oliver.fancysitula.api.utils.FS_EquipmentSlot; +import net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import java.util.Map; + +class ClientboundSetEquipmentPacketImplTest { + + //TODO: Fix this test (registry problems) + // @Test + void createPacket() { + // Setup packet + Map equipment = Map.of( + FS_EquipmentSlot.MAINHAND, new ItemStack(Material.DIAMOND_SWORD), + FS_EquipmentSlot.OFFHAND, new ItemStack(Material.SHIELD), + FS_EquipmentSlot.HEAD, new ItemStack(Material.DIAMOND_HELMET) + ); + + ClientboundSetEquipmentPacketImpl packet = new ClientboundSetEquipmentPacketImpl(42, equipment); + ClientboundSetEquipmentPacket createdPacket = (ClientboundSetEquipmentPacket) packet.createPacket(); + + assert createdPacket.getEntity() == 42; + assert createdPacket.getSlots().size() == 3; + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetPassengersPacketImplTest.java b/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetPassengersPacketImplTest.java new file mode 100644 index 00000000..91c2aa96 --- /dev/null +++ b/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetPassengersPacketImplTest.java @@ -0,0 +1,28 @@ +package de.oliver.fancysitula.versions.v1_20_6.packets; + +import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +public class ClientboundSetPassengersPacketImplTest { + + @Test + void createPacket() { + // Setup packet + int vehicleID = 712; + List passengers = new ArrayList<>(); + passengers.add(571); + passengers.add(572); + + ClientboundSetPassengersPacketImpl packet = new ClientboundSetPassengersPacketImpl(vehicleID, passengers); + ClientboundSetPassengersPacket createdPacket = (ClientboundSetPassengersPacket) packet.createPacket(); + + // Check packet + assert createdPacket.getVehicle() == vehicleID; + assert createdPacket.getPassengers().length == 2; + assert createdPacket.getPassengers()[0] == 571; + assert createdPacket.getPassengers()[1] == 572; + } +} diff --git a/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundTeleportEntityPacketImplTest.java b/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundTeleportEntityPacketImplTest.java new file mode 100644 index 00000000..84991ce9 --- /dev/null +++ b/libraries/packets/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundTeleportEntityPacketImplTest.java @@ -0,0 +1,31 @@ +package de.oliver.fancysitula.versions.v1_20_6.packets; + +import de.oliver.fancysitula.api.utils.AngelConverter; +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; +import org.junit.jupiter.api.Test; + +class ClientboundTeleportEntityPacketImplTest { + + @Test + void createPacket() { + int entityId = 4313; + double x = 15.0; + double y = 57.0; + double z = -27.0; + float yaw = 90.0f; + float pitch = 45.0f; + boolean onGround = true; + + ClientboundTeleportEntityPacketImpl packet = new ClientboundTeleportEntityPacketImpl(entityId, x, y, z, yaw, pitch, onGround); + ClientboundTeleportEntityPacket createdPacket = (ClientboundTeleportEntityPacket) packet.createPacket(); + + assert createdPacket != null; + assert createdPacket.getId() == entityId; + assert createdPacket.getX() == x; + assert createdPacket.getY() == y; + assert createdPacket.getZ() == z; + assert createdPacket.getyRot() == AngelConverter.degreesToVanillaByte(yaw); + assert createdPacket.getxRot() == AngelConverter.degreesToVanillaByte(pitch); + assert createdPacket.isOnGround() == onGround; + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21/build.gradle.kts b/libraries/packets/implementations/1_21/build.gradle.kts new file mode 100644 index 00000000..82bfe0e9 --- /dev/null +++ b/libraries/packets/implementations/1_21/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + id("java-library") + id("io.papermc.paperweight.userdev") +} + +val minecraftVersion = "1.21" + +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(project(":libraries:packets:implementations:1_20_6")) + testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.2") +} + +tasks { + test { + useJUnitPlatform() + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundAddEntityPacketImplTest.java b/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundAddEntityPacketImplTest.java new file mode 100644 index 00000000..49b7449a --- /dev/null +++ b/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundAddEntityPacketImplTest.java @@ -0,0 +1,65 @@ +package packets; + +import de.oliver.fancysitula.api.utils.AngelConverter; +import de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundAddEntityPacketImpl; +import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; +import org.bukkit.entity.EntityType; + +import java.util.UUID; + +class ClientboundAddEntityPacketImplTest { + + //TODO: Fix this test (registry problems) + // @Test + void createPacket() { + int entityId = 10000; + UUID entityUUID = UUID.randomUUID(); + EntityType entityType = EntityType.PIG; + + double x = 5; + double y = 57; + double z = 203; + + float yaw = 142; + float pitch = 247; + float headYaw = 90; + + int velocityX = 0; + int velocityY = 0; + int velocityZ = 0; + + int data = 0; + + ClientboundAddEntityPacketImpl packet = new ClientboundAddEntityPacketImpl( + entityId, + entityUUID, + entityType, + x, + y, + z, + yaw, + pitch, + headYaw, + velocityX, + velocityY, + velocityZ, + data + ); + + ClientboundAddEntityPacket createdPacket = (ClientboundAddEntityPacket) packet.createPacket(); + + assert createdPacket.getId() == entityId; + assert createdPacket.getUUID().equals(entityUUID); + assert createdPacket.getType().getDescriptionId().equals(entityType.getKey().getKey()); + assert createdPacket.getX() == x; + assert createdPacket.getY() == y; + assert createdPacket.getZ() == z; + assert createdPacket.getYRot() == AngelConverter.degreesToVanillaByte(yaw); + assert createdPacket.getXRot() == AngelConverter.degreesToVanillaByte(pitch); + assert createdPacket.getYHeadRot() == AngelConverter.degreesToVanillaByte(headYaw); + assert createdPacket.getXa() == velocityX; + assert createdPacket.getYa() == velocityY; + assert createdPacket.getZa() == velocityZ; + assert createdPacket.getData() == data; + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundPlayerInfoRemovePacketImplTest.java b/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundPlayerInfoRemovePacketImplTest.java new file mode 100644 index 00000000..e938af2f --- /dev/null +++ b/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundPlayerInfoRemovePacketImplTest.java @@ -0,0 +1,23 @@ +package packets; + +import de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundPlayerInfoRemovePacketImpl; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.UUID; + +class ClientboundPlayerInfoRemovePacketImplTest { + + @Test + void createPacket() { + List uuids = List.of(UUID.randomUUID(), UUID.randomUUID()); + + ClientboundPlayerInfoRemovePacketImpl packet = new ClientboundPlayerInfoRemovePacketImpl(uuids); + ClientboundPlayerInfoRemovePacket vanillaPacket = (ClientboundPlayerInfoRemovePacket) packet.createPacket(); + + for (UUID uuid : uuids) { + assert vanillaPacket.profileIds().contains(uuid); + } + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundPlayerInfoUpdatePacketImplTest.java b/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundPlayerInfoUpdatePacketImplTest.java new file mode 100644 index 00000000..440e284b --- /dev/null +++ b/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundPlayerInfoUpdatePacketImplTest.java @@ -0,0 +1,62 @@ +package packets; + +import de.oliver.fancysitula.api.packets.FS_ClientboundPlayerInfoUpdatePacket; +import de.oliver.fancysitula.api.utils.FS_GameProfile; +import de.oliver.fancysitula.api.utils.FS_GameType; +import de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundPlayerInfoUpdatePacketImpl; +import net.kyori.adventure.text.Component; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.UUID; + +class ClientboundPlayerInfoUpdatePacketImplTest { + + @Test + void createPacket() { + // Setup packet + EnumSet actions = EnumSet.noneOf(FS_ClientboundPlayerInfoUpdatePacket.Action.class); + actions.add(FS_ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER); + actions.add(FS_ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME); + actions.add(FS_ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED); + + FS_GameProfile gameProfile = new FS_GameProfile(UUID.randomUUID(), "Test name"); + boolean listed = true; + int latency = 42; + FS_GameType gameMode = FS_GameType.SURVIVAL; + Component displayName = Component.text("Test displayname"); + + List entries = new ArrayList<>(); + entries.add(new FS_ClientboundPlayerInfoUpdatePacket.Entry( + gameProfile.getUUID(), + gameProfile, + listed, + latency, + gameMode, + displayName + )); + + ClientboundPlayerInfoUpdatePacketImpl packet = new ClientboundPlayerInfoUpdatePacketImpl(actions, entries); + + ClientboundPlayerInfoUpdatePacket createdPacket = (ClientboundPlayerInfoUpdatePacket) packet.createPacket(); + + assert createdPacket.entries().size() == 1; + assert createdPacket.actions().size() == 3; + + // check entry + ClientboundPlayerInfoUpdatePacket.Entry entry = createdPacket.entries().getFirst(); + assert entry.profile().getId().equals(gameProfile.getUUID()); + assert entry.profile().getName().equals(gameProfile.getName()); + assert entry.listed() == listed; + assert entry.latency() == latency; + assert entry.gameMode().getId() == gameMode.getId(); + + // check actions + assert createdPacket.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER); + assert createdPacket.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME); + assert createdPacket.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED); + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundRemoveEntitiesPacketImplTest.java b/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundRemoveEntitiesPacketImplTest.java new file mode 100644 index 00000000..3a8833df --- /dev/null +++ b/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundRemoveEntitiesPacketImplTest.java @@ -0,0 +1,21 @@ +package packets; + +import de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundRemoveEntitiesPacketImpl; +import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; +import org.junit.jupiter.api.Test; + +import java.util.List; + +class ClientboundRemoveEntitiesPacketImplTest { + + @Test + void createPacket() { + List entityIds = List.of(95, 120, 154, 187); + + ClientboundRemoveEntitiesPacketImpl packet = new ClientboundRemoveEntitiesPacketImpl(entityIds); + ClientboundRemoveEntitiesPacket createdPacket = (ClientboundRemoveEntitiesPacket) packet.createPacket(); + + assert createdPacket.getEntityIds().size() == entityIds.size(); + assert createdPacket.getEntityIds().containsAll(entityIds); + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundRotateHeadPacketImplTest.java b/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundRotateHeadPacketImplTest.java new file mode 100644 index 00000000..630897c1 --- /dev/null +++ b/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundRotateHeadPacketImplTest.java @@ -0,0 +1,22 @@ +package packets; + +import de.oliver.fancysitula.api.utils.AngelConverter; +import de.oliver.fancysitula.api.utils.reflections.ReflectionUtils; +import de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundRotateHeadPacketImpl; +import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket; +import org.junit.jupiter.api.Test; + +class ClientboundRotateHeadPacketImplTest { + + @Test + void createPacket() throws Exception { + int entityId = 184; + float headYaw = 45; + + ClientboundRotateHeadPacketImpl packet = new ClientboundRotateHeadPacketImpl(entityId, (byte) headYaw); + ClientboundRotateHeadPacket createdPacket = (ClientboundRotateHeadPacket) packet.createPacket(); + + assert ReflectionUtils.getField(createdPacket, "entityId").equals(entityId); + assert createdPacket.getYHeadRot() == AngelConverter.degreesToVanillaByte(headYaw); + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundSetEntityDataPacketImplTest.java b/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundSetEntityDataPacketImplTest.java new file mode 100644 index 00000000..c92cf6f6 --- /dev/null +++ b/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundSetEntityDataPacketImplTest.java @@ -0,0 +1,29 @@ +package packets; + +import de.oliver.fancysitula.api.packets.FS_ClientboundSetEntityDataPacket; +import de.oliver.fancysitula.api.utils.entityData.FS_TextDisplayData; +import de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundSetEntityDataPacketImpl; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; + +import java.util.List; + +class ClientboundSetEntityDataPacketImplTest { + + //TODO: Fix this test (using registry) +// @Test + void createPacket() { + int entityId = 712; + List entityData = List.of( + new FS_ClientboundSetEntityDataPacket.EntityData( + FS_TextDisplayData.TEXT, + "Hello, World!" + ) + ); + + ClientboundSetEntityDataPacketImpl packet = new ClientboundSetEntityDataPacketImpl(entityId, entityData); + ClientboundSetEntityDataPacket createdPacket = (ClientboundSetEntityDataPacket) packet.createPacket(); + + assert createdPacket.id() == entityId; + assert createdPacket.packedItems().size() == 1; + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundSetEquipmentPacketImplTest.java b/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundSetEquipmentPacketImplTest.java new file mode 100644 index 00000000..0f8f151d --- /dev/null +++ b/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundSetEquipmentPacketImplTest.java @@ -0,0 +1,29 @@ +package packets; + +import de.oliver.fancysitula.api.utils.FS_EquipmentSlot; +import de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundSetEquipmentPacketImpl; +import net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import java.util.Map; + +class ClientboundSetEquipmentPacketImplTest { + + //TODO: Fix this test (registry problems) + // @Test + void createPacket() { + // Setup packet + Map equipment = Map.of( + FS_EquipmentSlot.MAINHAND, new ItemStack(Material.DIAMOND_SWORD), + FS_EquipmentSlot.OFFHAND, new ItemStack(Material.SHIELD), + FS_EquipmentSlot.HEAD, new ItemStack(Material.DIAMOND_HELMET) + ); + + ClientboundSetEquipmentPacketImpl packet = new ClientboundSetEquipmentPacketImpl(42, equipment); + ClientboundSetEquipmentPacket createdPacket = (ClientboundSetEquipmentPacket) packet.createPacket(); + + assert createdPacket.getEntity() == 42; + assert createdPacket.getSlots().size() == 3; + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundTeleportEntityPacketImplTest.java b/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundTeleportEntityPacketImplTest.java new file mode 100644 index 00000000..e7ee990d --- /dev/null +++ b/libraries/packets/implementations/1_21/src/test/java/packets/ClientboundTeleportEntityPacketImplTest.java @@ -0,0 +1,32 @@ +package packets; + +import de.oliver.fancysitula.api.utils.AngelConverter; +import de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundTeleportEntityPacketImpl; +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; +import org.junit.jupiter.api.Test; + +class ClientboundTeleportEntityPacketImplTest { + + @Test + void createPacket() { + int entityId = 4313; + double x = 15.0; + double y = 57.0; + double z = -27.0; + float yaw = 90.0f; + float pitch = 45.0f; + boolean onGround = true; + + ClientboundTeleportEntityPacketImpl packet = new ClientboundTeleportEntityPacketImpl(entityId, x, y, z, yaw, pitch, onGround); + ClientboundTeleportEntityPacket createdPacket = (ClientboundTeleportEntityPacket) packet.createPacket(); + + assert createdPacket != null; + assert createdPacket.getId() == entityId; + assert createdPacket.getX() == x; + assert createdPacket.getY() == y; + assert createdPacket.getZ() == z; + assert createdPacket.getyRot() == AngelConverter.degreesToVanillaByte(yaw); + assert createdPacket.getxRot() == AngelConverter.degreesToVanillaByte(pitch); + assert createdPacket.isOnGround() == onGround; + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_1/build.gradle.kts b/libraries/packets/implementations/1_21_1/build.gradle.kts new file mode 100644 index 00000000..579f7752 --- /dev/null +++ b/libraries/packets/implementations/1_21_1/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + id("java-library") + id("io.papermc.paperweight.userdev") +} + +val minecraftVersion = "1.21.1" + +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(project(":libraries:packets:implementations:1_20_6")) + testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.2") +} + +tasks { + test { + useJUnitPlatform() + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundAddEntityPacketImplTest.java b/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundAddEntityPacketImplTest.java new file mode 100644 index 00000000..49b7449a --- /dev/null +++ b/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundAddEntityPacketImplTest.java @@ -0,0 +1,65 @@ +package packets; + +import de.oliver.fancysitula.api.utils.AngelConverter; +import de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundAddEntityPacketImpl; +import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; +import org.bukkit.entity.EntityType; + +import java.util.UUID; + +class ClientboundAddEntityPacketImplTest { + + //TODO: Fix this test (registry problems) + // @Test + void createPacket() { + int entityId = 10000; + UUID entityUUID = UUID.randomUUID(); + EntityType entityType = EntityType.PIG; + + double x = 5; + double y = 57; + double z = 203; + + float yaw = 142; + float pitch = 247; + float headYaw = 90; + + int velocityX = 0; + int velocityY = 0; + int velocityZ = 0; + + int data = 0; + + ClientboundAddEntityPacketImpl packet = new ClientboundAddEntityPacketImpl( + entityId, + entityUUID, + entityType, + x, + y, + z, + yaw, + pitch, + headYaw, + velocityX, + velocityY, + velocityZ, + data + ); + + ClientboundAddEntityPacket createdPacket = (ClientboundAddEntityPacket) packet.createPacket(); + + assert createdPacket.getId() == entityId; + assert createdPacket.getUUID().equals(entityUUID); + assert createdPacket.getType().getDescriptionId().equals(entityType.getKey().getKey()); + assert createdPacket.getX() == x; + assert createdPacket.getY() == y; + assert createdPacket.getZ() == z; + assert createdPacket.getYRot() == AngelConverter.degreesToVanillaByte(yaw); + assert createdPacket.getXRot() == AngelConverter.degreesToVanillaByte(pitch); + assert createdPacket.getYHeadRot() == AngelConverter.degreesToVanillaByte(headYaw); + assert createdPacket.getXa() == velocityX; + assert createdPacket.getYa() == velocityY; + assert createdPacket.getZa() == velocityZ; + assert createdPacket.getData() == data; + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundPlayerInfoRemovePacketImplTest.java b/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundPlayerInfoRemovePacketImplTest.java new file mode 100644 index 00000000..e938af2f --- /dev/null +++ b/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundPlayerInfoRemovePacketImplTest.java @@ -0,0 +1,23 @@ +package packets; + +import de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundPlayerInfoRemovePacketImpl; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.UUID; + +class ClientboundPlayerInfoRemovePacketImplTest { + + @Test + void createPacket() { + List uuids = List.of(UUID.randomUUID(), UUID.randomUUID()); + + ClientboundPlayerInfoRemovePacketImpl packet = new ClientboundPlayerInfoRemovePacketImpl(uuids); + ClientboundPlayerInfoRemovePacket vanillaPacket = (ClientboundPlayerInfoRemovePacket) packet.createPacket(); + + for (UUID uuid : uuids) { + assert vanillaPacket.profileIds().contains(uuid); + } + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundPlayerInfoUpdatePacketImplTest.java b/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundPlayerInfoUpdatePacketImplTest.java new file mode 100644 index 00000000..440e284b --- /dev/null +++ b/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundPlayerInfoUpdatePacketImplTest.java @@ -0,0 +1,62 @@ +package packets; + +import de.oliver.fancysitula.api.packets.FS_ClientboundPlayerInfoUpdatePacket; +import de.oliver.fancysitula.api.utils.FS_GameProfile; +import de.oliver.fancysitula.api.utils.FS_GameType; +import de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundPlayerInfoUpdatePacketImpl; +import net.kyori.adventure.text.Component; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.UUID; + +class ClientboundPlayerInfoUpdatePacketImplTest { + + @Test + void createPacket() { + // Setup packet + EnumSet actions = EnumSet.noneOf(FS_ClientboundPlayerInfoUpdatePacket.Action.class); + actions.add(FS_ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER); + actions.add(FS_ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME); + actions.add(FS_ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED); + + FS_GameProfile gameProfile = new FS_GameProfile(UUID.randomUUID(), "Test name"); + boolean listed = true; + int latency = 42; + FS_GameType gameMode = FS_GameType.SURVIVAL; + Component displayName = Component.text("Test displayname"); + + List entries = new ArrayList<>(); + entries.add(new FS_ClientboundPlayerInfoUpdatePacket.Entry( + gameProfile.getUUID(), + gameProfile, + listed, + latency, + gameMode, + displayName + )); + + ClientboundPlayerInfoUpdatePacketImpl packet = new ClientboundPlayerInfoUpdatePacketImpl(actions, entries); + + ClientboundPlayerInfoUpdatePacket createdPacket = (ClientboundPlayerInfoUpdatePacket) packet.createPacket(); + + assert createdPacket.entries().size() == 1; + assert createdPacket.actions().size() == 3; + + // check entry + ClientboundPlayerInfoUpdatePacket.Entry entry = createdPacket.entries().getFirst(); + assert entry.profile().getId().equals(gameProfile.getUUID()); + assert entry.profile().getName().equals(gameProfile.getName()); + assert entry.listed() == listed; + assert entry.latency() == latency; + assert entry.gameMode().getId() == gameMode.getId(); + + // check actions + assert createdPacket.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER); + assert createdPacket.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME); + assert createdPacket.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED); + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundRemoveEntitiesPacketImplTest.java b/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundRemoveEntitiesPacketImplTest.java new file mode 100644 index 00000000..3a8833df --- /dev/null +++ b/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundRemoveEntitiesPacketImplTest.java @@ -0,0 +1,21 @@ +package packets; + +import de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundRemoveEntitiesPacketImpl; +import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; +import org.junit.jupiter.api.Test; + +import java.util.List; + +class ClientboundRemoveEntitiesPacketImplTest { + + @Test + void createPacket() { + List entityIds = List.of(95, 120, 154, 187); + + ClientboundRemoveEntitiesPacketImpl packet = new ClientboundRemoveEntitiesPacketImpl(entityIds); + ClientboundRemoveEntitiesPacket createdPacket = (ClientboundRemoveEntitiesPacket) packet.createPacket(); + + assert createdPacket.getEntityIds().size() == entityIds.size(); + assert createdPacket.getEntityIds().containsAll(entityIds); + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundRotateHeadPacketImplTest.java b/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundRotateHeadPacketImplTest.java new file mode 100644 index 00000000..630897c1 --- /dev/null +++ b/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundRotateHeadPacketImplTest.java @@ -0,0 +1,22 @@ +package packets; + +import de.oliver.fancysitula.api.utils.AngelConverter; +import de.oliver.fancysitula.api.utils.reflections.ReflectionUtils; +import de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundRotateHeadPacketImpl; +import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket; +import org.junit.jupiter.api.Test; + +class ClientboundRotateHeadPacketImplTest { + + @Test + void createPacket() throws Exception { + int entityId = 184; + float headYaw = 45; + + ClientboundRotateHeadPacketImpl packet = new ClientboundRotateHeadPacketImpl(entityId, (byte) headYaw); + ClientboundRotateHeadPacket createdPacket = (ClientboundRotateHeadPacket) packet.createPacket(); + + assert ReflectionUtils.getField(createdPacket, "entityId").equals(entityId); + assert createdPacket.getYHeadRot() == AngelConverter.degreesToVanillaByte(headYaw); + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundSetEntityDataPacketImplTest.java b/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundSetEntityDataPacketImplTest.java new file mode 100644 index 00000000..c92cf6f6 --- /dev/null +++ b/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundSetEntityDataPacketImplTest.java @@ -0,0 +1,29 @@ +package packets; + +import de.oliver.fancysitula.api.packets.FS_ClientboundSetEntityDataPacket; +import de.oliver.fancysitula.api.utils.entityData.FS_TextDisplayData; +import de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundSetEntityDataPacketImpl; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; + +import java.util.List; + +class ClientboundSetEntityDataPacketImplTest { + + //TODO: Fix this test (using registry) +// @Test + void createPacket() { + int entityId = 712; + List entityData = List.of( + new FS_ClientboundSetEntityDataPacket.EntityData( + FS_TextDisplayData.TEXT, + "Hello, World!" + ) + ); + + ClientboundSetEntityDataPacketImpl packet = new ClientboundSetEntityDataPacketImpl(entityId, entityData); + ClientboundSetEntityDataPacket createdPacket = (ClientboundSetEntityDataPacket) packet.createPacket(); + + assert createdPacket.id() == entityId; + assert createdPacket.packedItems().size() == 1; + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundSetEquipmentPacketImplTest.java b/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundSetEquipmentPacketImplTest.java new file mode 100644 index 00000000..0f8f151d --- /dev/null +++ b/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundSetEquipmentPacketImplTest.java @@ -0,0 +1,29 @@ +package packets; + +import de.oliver.fancysitula.api.utils.FS_EquipmentSlot; +import de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundSetEquipmentPacketImpl; +import net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import java.util.Map; + +class ClientboundSetEquipmentPacketImplTest { + + //TODO: Fix this test (registry problems) + // @Test + void createPacket() { + // Setup packet + Map equipment = Map.of( + FS_EquipmentSlot.MAINHAND, new ItemStack(Material.DIAMOND_SWORD), + FS_EquipmentSlot.OFFHAND, new ItemStack(Material.SHIELD), + FS_EquipmentSlot.HEAD, new ItemStack(Material.DIAMOND_HELMET) + ); + + ClientboundSetEquipmentPacketImpl packet = new ClientboundSetEquipmentPacketImpl(42, equipment); + ClientboundSetEquipmentPacket createdPacket = (ClientboundSetEquipmentPacket) packet.createPacket(); + + assert createdPacket.getEntity() == 42; + assert createdPacket.getSlots().size() == 3; + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundTeleportEntityPacketImplTest.java b/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundTeleportEntityPacketImplTest.java new file mode 100644 index 00000000..e7ee990d --- /dev/null +++ b/libraries/packets/implementations/1_21_1/src/test/java/packets/ClientboundTeleportEntityPacketImplTest.java @@ -0,0 +1,32 @@ +package packets; + +import de.oliver.fancysitula.api.utils.AngelConverter; +import de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundTeleportEntityPacketImpl; +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; +import org.junit.jupiter.api.Test; + +class ClientboundTeleportEntityPacketImplTest { + + @Test + void createPacket() { + int entityId = 4313; + double x = 15.0; + double y = 57.0; + double z = -27.0; + float yaw = 90.0f; + float pitch = 45.0f; + boolean onGround = true; + + ClientboundTeleportEntityPacketImpl packet = new ClientboundTeleportEntityPacketImpl(entityId, x, y, z, yaw, pitch, onGround); + ClientboundTeleportEntityPacket createdPacket = (ClientboundTeleportEntityPacket) packet.createPacket(); + + assert createdPacket != null; + assert createdPacket.getId() == entityId; + assert createdPacket.getX() == x; + assert createdPacket.getY() == y; + assert createdPacket.getZ() == z; + assert createdPacket.getyRot() == AngelConverter.degreesToVanillaByte(yaw); + assert createdPacket.getxRot() == AngelConverter.degreesToVanillaByte(pitch); + assert createdPacket.isOnGround() == onGround; + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_3/build.gradle.kts b/libraries/packets/implementations/1_21_3/build.gradle.kts new file mode 100644 index 00000000..551ef7ca --- /dev/null +++ b/libraries/packets/implementations/1_21_3/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + id("java-library") + id("io.papermc.paperweight.userdev") +} + +val minecraftVersion = "1.21.3" + +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() + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundAddEntityPacketImpl.java b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundAddEntityPacketImpl.java new file mode 100644 index 00000000..44572a02 --- /dev/null +++ b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundAddEntityPacketImpl.java @@ -0,0 +1,48 @@ +package de.oliver.fancysitula.versions.v1_21_3.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundAddEntityPacket; +import de.oliver.fancysitula.api.utils.AngelConverter; +import de.oliver.fancysitula.versions.v1_21_3.utils.VanillaPlayerAdapter; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.phys.Vec3; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.bukkit.entity.EntityType; + +import java.util.UUID; + +public class ClientboundAddEntityPacketImpl extends FS_ClientboundAddEntityPacket { + + public ClientboundAddEntityPacketImpl(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) { + super(entityId, entityUUID, entityType, x, y, z, yaw, pitch, headYaw, velocityX, velocityY, velocityZ, data); + } + + @Override + public Object createPacket() { + net.minecraft.world.entity.EntityType vanillaType = BuiltInRegistries.ENTITY_TYPE.getValue(CraftNamespacedKey.toMinecraft(entityType.getKey())); + + return new ClientboundAddEntityPacket( + entityId, + entityUUID, + x, + y, + z, + AngelConverter.degreesToVanillaByte(pitch), + AngelConverter.degreesToVanillaByte(yaw), + vanillaType, + data, + new Vec3(velocityX, velocityY, velocityZ), + AngelConverter.degreesToVanillaByte(headYaw) + ); + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundAddEntityPacket packet = (ClientboundAddEntityPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundCreateOrUpdateTeamPacketImpl.java b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundCreateOrUpdateTeamPacketImpl.java new file mode 100644 index 00000000..39201c61 --- /dev/null +++ b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundCreateOrUpdateTeamPacketImpl.java @@ -0,0 +1,128 @@ +package de.oliver.fancysitula.versions.v1_21_3.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundCreateOrUpdateTeamPacket; +import de.oliver.fancysitula.versions.v1_21_3.utils.VanillaPlayerAdapter; +import io.papermc.paper.adventure.PaperAdventure; +import net.minecraft.ChatFormatting; +import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.scores.PlayerTeam; +import net.minecraft.world.scores.Scoreboard; +import net.minecraft.world.scores.Team; + +public class ClientboundCreateOrUpdateTeamPacketImpl extends FS_ClientboundCreateOrUpdateTeamPacket { + + private static final Scoreboard SCOREBOARD = new Scoreboard(); + + public ClientboundCreateOrUpdateTeamPacketImpl(String teamName, CreateTeam createTeam) { + super(teamName, createTeam); + } + + public ClientboundCreateOrUpdateTeamPacketImpl(String teamName, RemoveTeam removeTeam) { + super(teamName, removeTeam); + } + + public ClientboundCreateOrUpdateTeamPacketImpl(String teamName, UpdateTeam updateTeam) { + super(teamName, updateTeam); + } + + public ClientboundCreateOrUpdateTeamPacketImpl(String teamName, AddEntity addEntity) { + super(teamName, addEntity); + } + + public ClientboundCreateOrUpdateTeamPacketImpl(String teamName, RemoveEntity removeEntity) { + super(teamName, removeEntity); + } + + @Override + public Object createPacket() { + return switch (method) { + case CREATE_TEAM -> createCreateTeamPacket(); + case REMOVE_TEAM -> createRemoveTeamPacket(); + case UPDATE_TEAM -> createUpdateTeamPacket(); + case ADD_ENTITY -> createAddEntityPacket(); + case REMOVE_ENTITY -> createRemoveEntityPacket(); + }; + } + + private Object createCreateTeamPacket() { + if (createTeam == null) { + return null; + } + + PlayerTeam playerTeam = new PlayerTeam(SCOREBOARD, teamName); + playerTeam.setDisplayName(PaperAdventure.asVanilla(createTeam.getDisplayName())); + playerTeam.setAllowFriendlyFire(createTeam.isAllowFriendlyFire()); + playerTeam.setSeeFriendlyInvisibles(createTeam.isCanSeeFriendlyInvisibles()); + playerTeam.setNameTagVisibility(Team.Visibility.byName(createTeam.getNameTagVisibility().getName())); + playerTeam.setCollisionRule(PlayerTeam.CollisionRule.byName(createTeam.getCollisionRule().getName())); + playerTeam.setColor(ChatFormatting.getById(createTeam.getColor().getId())); + playerTeam.setPlayerPrefix(PaperAdventure.asVanilla(createTeam.getPrefix())); + playerTeam.setPlayerSuffix(PaperAdventure.asVanilla(createTeam.getSuffix())); + for (String entity : createTeam.getEntities()) { + playerTeam.getPlayers().add(entity); + } + + return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(playerTeam, true); + } + + private Object createRemoveTeamPacket() { + if (removeTeam == null) { + return null; + } + + PlayerTeam playerTeam = new PlayerTeam(SCOREBOARD, teamName); + return ClientboundSetPlayerTeamPacket.createRemovePacket(playerTeam); + } + + private Object createUpdateTeamPacket() { + if (updateTeam == null) { + return null; + } + + PlayerTeam playerTeam = new PlayerTeam(SCOREBOARD, teamName); + playerTeam.setDisplayName(PaperAdventure.asVanilla(updateTeam.getDisplayName())); + playerTeam.setAllowFriendlyFire(updateTeam.isAllowFriendlyFire()); + playerTeam.setSeeFriendlyInvisibles(updateTeam.isCanSeeFriendlyInvisibles()); + playerTeam.setNameTagVisibility(Team.Visibility.byName(updateTeam.getNameTagVisibility().getName())); + playerTeam.setCollisionRule(PlayerTeam.CollisionRule.byName(updateTeam.getCollisionRule().getName())); + playerTeam.setColor(ChatFormatting.getById(updateTeam.getColor().getId())); + playerTeam.setPlayerPrefix(PaperAdventure.asVanilla(updateTeam.getPrefix())); + playerTeam.setPlayerSuffix(PaperAdventure.asVanilla(updateTeam.getSuffix())); + + return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(playerTeam, true); + } + + private Object createAddEntityPacket() { + if (addEntity == null) { + return null; + } + + PlayerTeam playerTeam = new PlayerTeam(SCOREBOARD, teamName); + for (String entity : addEntity.getEntities()) { + playerTeam.getPlayers().add(entity); + } + return ClientboundSetPlayerTeamPacket.createMultiplePlayerPacket(playerTeam, addEntity.getEntities(), ClientboundSetPlayerTeamPacket.Action.ADD); + } + + private Object createRemoveEntityPacket() { + if (removeEntity == null) { + return null; + } + + PlayerTeam playerTeam = new PlayerTeam(SCOREBOARD, teamName); + for (String entity : removeEntity.getEntities()) { + playerTeam.getPlayers().add(entity); + } + return ClientboundSetPlayerTeamPacket.createMultiplePlayerPacket(playerTeam, removeEntity.getEntities(), ClientboundSetPlayerTeamPacket.Action.REMOVE); + } + + @Override + protected void sendPacketTo(FS_RealPlayer player) { + ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundPlayerInfoRemovePacketImpl.java b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundPlayerInfoRemovePacketImpl.java new file mode 100644 index 00000000..b8fe8066 --- /dev/null +++ b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundPlayerInfoRemovePacketImpl.java @@ -0,0 +1,30 @@ +package de.oliver.fancysitula.versions.v1_21_3.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundPlayerInfoRemovePacket; +import de.oliver.fancysitula.versions.v1_21_3.utils.VanillaPlayerAdapter; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; +import net.minecraft.server.level.ServerPlayer; + +import java.util.List; +import java.util.UUID; + +public class ClientboundPlayerInfoRemovePacketImpl extends FS_ClientboundPlayerInfoRemovePacket { + + public ClientboundPlayerInfoRemovePacketImpl(List uuids) { + super(uuids); + } + + @Override + public Object createPacket() { + return new ClientboundPlayerInfoRemovePacket(uuids); + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundPlayerInfoRemovePacket packet = (ClientboundPlayerInfoRemovePacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundPlayerInfoUpdatePacketImpl.java b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundPlayerInfoUpdatePacketImpl.java new file mode 100644 index 00000000..fe4d6c13 --- /dev/null +++ b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundPlayerInfoUpdatePacketImpl.java @@ -0,0 +1,53 @@ +package de.oliver.fancysitula.versions.v1_21_3.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundPlayerInfoUpdatePacket; +import de.oliver.fancysitula.versions.v1_21_3.utils.GameProfileImpl; +import de.oliver.fancysitula.versions.v1_21_3.utils.VanillaPlayerAdapter; +import io.papermc.paper.adventure.PaperAdventure; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.GameType; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +public class ClientboundPlayerInfoUpdatePacketImpl extends FS_ClientboundPlayerInfoUpdatePacket { + + public ClientboundPlayerInfoUpdatePacketImpl(EnumSet actions, List entries) { + super(actions, entries); + } + + @Override + public Object createPacket() { + EnumSet vanillaActions = EnumSet.noneOf(ClientboundPlayerInfoUpdatePacket.Action.class); + for (FS_ClientboundPlayerInfoUpdatePacket.Action action : actions) { + vanillaActions.add(ClientboundPlayerInfoUpdatePacket.Action.valueOf(action.name())); + } + + List entries = new ArrayList<>(); + for (Entry entry : this.entries) { + entries.add(new ClientboundPlayerInfoUpdatePacket.Entry( + entry.uuid(), + GameProfileImpl.asVanilla(entry.profile()), + entry.listed(), + entry.latency(), + GameType.byId(entry.gameMode().getId()), + PaperAdventure.asVanilla(entry.displayName()), + -1, + null // TODO: Add ChatSession support + )); + } + + return new ClientboundPlayerInfoUpdatePacket(vanillaActions, entries); + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundPlayerInfoUpdatePacket packet = (ClientboundPlayerInfoUpdatePacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundRemoveEntitiesPacketImpl.java b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundRemoveEntitiesPacketImpl.java new file mode 100644 index 00000000..90671fd0 --- /dev/null +++ b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundRemoveEntitiesPacketImpl.java @@ -0,0 +1,37 @@ +package de.oliver.fancysitula.versions.v1_21_3.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundRemoveEntitiesPacket; +import de.oliver.fancysitula.versions.v1_21_3.utils.VanillaPlayerAdapter; +import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; +import net.minecraft.server.level.ServerPlayer; + +import java.util.List; + +public class ClientboundRemoveEntitiesPacketImpl extends FS_ClientboundRemoveEntitiesPacket { + + /** + * @param entityIds IDs of the entities to remove + */ + public ClientboundRemoveEntitiesPacketImpl(List entityIds) { + super(entityIds); + } + + @Override + public Object createPacket() { + int[] ids = new int[this.entityIds.size()]; + for (int i = 0; i < this.entityIds.size(); i++) { + ids[i] = this.entityIds.get(i); + } + + return new ClientboundRemoveEntitiesPacket(ids); + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundRemoveEntitiesPacket packet = (ClientboundRemoveEntitiesPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundRotateHeadPacketImpl.java b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundRotateHeadPacketImpl.java new file mode 100644 index 00000000..0ccd04e5 --- /dev/null +++ b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundRotateHeadPacketImpl.java @@ -0,0 +1,38 @@ +package de.oliver.fancysitula.versions.v1_21_3.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundRotateHeadPacket; +import de.oliver.fancysitula.api.utils.AngelConverter; +import de.oliver.fancysitula.api.utils.reflections.ReflectionUtils; +import de.oliver.fancysitula.versions.v1_21_3.utils.VanillaPlayerAdapter; +import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket; +import net.minecraft.server.level.ServerPlayer; + +public class ClientboundRotateHeadPacketImpl extends FS_ClientboundRotateHeadPacket { + + public ClientboundRotateHeadPacketImpl(int entityId, float headYaw) { + super(entityId, headYaw); + } + + @Override + public Object createPacket() { + ClientboundRotateHeadPacket packet = null; + + try { + packet = ReflectionUtils.createUnsafeInstance(ClientboundRotateHeadPacket.class); + ReflectionUtils.setFinalField(packet, "entityId", entityId); + ReflectionUtils.setFinalField(packet, "yHeadRot", AngelConverter.degreesToVanillaByte(headYaw)); + } catch (Exception e) { + e.printStackTrace(); + } + return packet; + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundRotateHeadPacket packet = (ClientboundRotateHeadPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundSetEntityDataPacketImpl.java b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundSetEntityDataPacketImpl.java new file mode 100644 index 00000000..09667e6f --- /dev/null +++ b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundSetEntityDataPacketImpl.java @@ -0,0 +1,67 @@ +package de.oliver.fancysitula.versions.v1_21_3.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundSetEntityDataPacket; +import de.oliver.fancysitula.api.utils.reflections.ReflectionUtils; +import de.oliver.fancysitula.versions.v1_21_3.utils.VanillaPlayerAdapter; +import io.papermc.paper.adventure.PaperAdventure; +import net.kyori.adventure.text.Component; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.level.ServerPlayer; +import org.bukkit.block.BlockState; +import org.bukkit.craftbukkit.block.CraftBlockState; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; + +public class ClientboundSetEntityDataPacketImpl extends FS_ClientboundSetEntityDataPacket { + + public ClientboundSetEntityDataPacketImpl(int entityId, List entityData) { + super(entityId, entityData); + } + + @Override + public Object createPacket() { + List> dataValues = new ArrayList<>(); + for (EntityData data : entityData) { + try { + Class entityClass = Class.forName(data.getAccessor().entityClassName()); + net.minecraft.network.syncher.EntityDataAccessor accessor = ReflectionUtils.getStaticField(entityClass, data.getAccessor().accessorFieldName()); + + Object vanillaValue = data.getValue(); + + if (data.getValue() == null) { + continue; + } + + if (data.getValue() instanceof Component c) { + vanillaValue = PaperAdventure.asVanilla(c); + } + + if (data.getValue() instanceof ItemStack i) { + vanillaValue = net.minecraft.world.item.ItemStack.fromBukkitCopy(i); + } + + if (data.getValue() instanceof BlockState b) { + vanillaValue = ((CraftBlockState) b).getHandle(); + } + + dataValues.add(SynchedEntityData.DataValue.create(accessor, vanillaValue)); + } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + } + + return new ClientboundSetEntityDataPacket(entityId, dataValues); + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundSetEntityDataPacket packet = (ClientboundSetEntityDataPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundSetEquipmentPacketImpl.java b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundSetEquipmentPacketImpl.java new file mode 100644 index 00000000..cee9a61b --- /dev/null +++ b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundSetEquipmentPacketImpl.java @@ -0,0 +1,45 @@ +package de.oliver.fancysitula.versions.v1_21_3.packets; + +import com.mojang.datafixers.util.Pair; +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundSetEquipmentPacket; +import de.oliver.fancysitula.api.utils.FS_EquipmentSlot; +import de.oliver.fancysitula.versions.v1_21_3.utils.VanillaPlayerAdapter; +import net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.EquipmentSlot; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class ClientboundSetEquipmentPacketImpl extends FS_ClientboundSetEquipmentPacket { + + public ClientboundSetEquipmentPacketImpl(int entityId, Map equipment) { + super(entityId, equipment); + } + + @Override + public Object createPacket() { + List> slots = new ArrayList<>(); + + for (Map.Entry entry : equipment.entrySet()) { + EquipmentSlot equipmentSlot = net.minecraft.world.entity.EquipmentSlot.byName(entry.getKey().name().toLowerCase()); + net.minecraft.world.item.ItemStack itemStack = CraftItemStack.asNMSCopy(entry.getValue()); + + slots.add(Pair.of(equipmentSlot, itemStack)); + } + + return new ClientboundSetEquipmentPacket(entityId, slots); + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundSetEquipmentPacket packet = (ClientboundSetEquipmentPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundSetPassengersPacketImpl.java b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundSetPassengersPacketImpl.java new file mode 100644 index 00000000..20b84206 --- /dev/null +++ b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundSetPassengersPacketImpl.java @@ -0,0 +1,45 @@ +package de.oliver.fancysitula.versions.v1_21_3.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundSetPassengersPacket; +import de.oliver.fancysitula.api.utils.reflections.ReflectionUtils; +import de.oliver.fancysitula.versions.v1_21_3.utils.VanillaPlayerAdapter; +import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket; +import net.minecraft.server.level.ServerPlayer; + +import java.util.List; + +public class ClientboundSetPassengersPacketImpl extends FS_ClientboundSetPassengersPacket { + + public ClientboundSetPassengersPacketImpl(int entityId, List passengers) { + super(entityId, passengers); + } + + + @Override + public Object createPacket() { + int[] passengers = new int[this.passengers.size()]; + for (int i = 0; i < this.passengers.size(); i++) { + passengers[i] = this.passengers.get(i); + } + + try { + ClientboundSetPassengersPacket packet = ReflectionUtils.createUnsafeInstance(ClientboundSetPassengersPacket.class); + ReflectionUtils.setFinalField(packet, "vehicle", entityId); + ReflectionUtils.setFinalField(packet, "passengers", passengers); + return packet; + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundSetPassengersPacket packet = (ClientboundSetPassengersPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundTeleportEntityPacketImpl.java b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundTeleportEntityPacketImpl.java new file mode 100644 index 00000000..390fe39a --- /dev/null +++ b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundTeleportEntityPacketImpl.java @@ -0,0 +1,43 @@ +package de.oliver.fancysitula.versions.v1_21_3.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundTeleportEntityPacket; +import de.oliver.fancysitula.versions.v1_21_3.utils.VanillaPlayerAdapter; +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.PositionMoveRotation; +import net.minecraft.world.phys.Vec3; + +import java.util.Set; + +public class ClientboundTeleportEntityPacketImpl extends FS_ClientboundTeleportEntityPacket { + + public ClientboundTeleportEntityPacketImpl(int entityId, double x, double y, double z, float yaw, float pitch, boolean onGround) { + super(entityId, x, y, z, yaw, pitch, onGround); + } + + @Override + public Object createPacket() { + ClientboundTeleportEntityPacket packet = new ClientboundTeleportEntityPacket( + entityId, + new PositionMoveRotation( + new Vec3(x, y, z), + Vec3.ZERO, + yaw, + pitch + ), + Set.of(), + onGround + ); + + return packet; + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundTeleportEntityPacket packet = (ClientboundTeleportEntityPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/utils/GameProfileImpl.java b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/utils/GameProfileImpl.java new file mode 100644 index 00000000..0cc5fb32 --- /dev/null +++ b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/utils/GameProfileImpl.java @@ -0,0 +1,33 @@ +package de.oliver.fancysitula.versions.v1_21_3.utils; + +import com.mojang.authlib.GameProfile; +import de.oliver.fancysitula.api.utils.FS_GameProfile; + +import java.util.Map; + +public class GameProfileImpl { + + public static GameProfile asVanilla(FS_GameProfile gameProfile) { + GameProfile gf = new GameProfile(gameProfile.getUUID(), gameProfile.getName()); + + for (Map.Entry entry : gameProfile.getProperties().entrySet()) { + FS_GameProfile.Property property = entry.getValue(); + + gf.getProperties().put(entry.getKey(), new com.mojang.authlib.properties.Property(property.name(), property.value(), property.signature())); + } + + return gf; + } + + public static FS_GameProfile fromVanilla(GameProfile gameProfile) { + FS_GameProfile fsGameProfile = new FS_GameProfile(gameProfile.getId(), gameProfile.getName()); + + for (Map.Entry entry : gameProfile.getProperties().entries()) { + com.mojang.authlib.properties.Property property = entry.getValue(); + + fsGameProfile.getProperties().put(entry.getKey(), new FS_GameProfile.Property(property.name(), property.value(), property.signature())); + } + + return fsGameProfile; + } +} diff --git a/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/utils/VanillaPlayerAdapter.java b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/utils/VanillaPlayerAdapter.java new file mode 100644 index 00000000..9d6f0007 --- /dev/null +++ b/libraries/packets/implementations/1_21_3/src/main/java/de/oliver/fancysitula/versions/v1_21_3/utils/VanillaPlayerAdapter.java @@ -0,0 +1,12 @@ +package de.oliver.fancysitula.versions.v1_21_3.utils; + +import net.minecraft.server.level.ServerPlayer; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; + +public class VanillaPlayerAdapter { + + public static ServerPlayer asVanilla(Player p) { + return ((CraftPlayer) p).getHandle(); + } +} diff --git a/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundAddEntityPacketImplTest.java b/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundAddEntityPacketImplTest.java new file mode 100644 index 00000000..1cb95bb3 --- /dev/null +++ b/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundAddEntityPacketImplTest.java @@ -0,0 +1,65 @@ +package de.oliver.fancysitula.versions.v1_21_3.packets; + +import de.oliver.fancysitula.api.utils.AngelConverter; +import de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundAddEntityPacketImpl; +import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; +import org.bukkit.entity.EntityType; + +import java.util.UUID; + +class ClientboundAddEntityPacketImplTest { + + //TODO: Fix this test (registry problems) + // @Test + void createPacket() { + int entityId = 10000; + UUID entityUUID = UUID.randomUUID(); + EntityType entityType = EntityType.PIG; + + double x = 5; + double y = 57; + double z = 203; + + float yaw = 142; + float pitch = 247; + float headYaw = 90; + + int velocityX = 0; + int velocityY = 0; + int velocityZ = 0; + + int data = 0; + + ClientboundAddEntityPacketImpl packet = new ClientboundAddEntityPacketImpl( + entityId, + entityUUID, + entityType, + x, + y, + z, + yaw, + pitch, + headYaw, + velocityX, + velocityY, + velocityZ, + data + ); + + ClientboundAddEntityPacket createdPacket = (ClientboundAddEntityPacket) packet.createPacket(); + + assert createdPacket.getId() == entityId; + assert createdPacket.getUUID().equals(entityUUID); + assert createdPacket.getType().getDescriptionId().equals(entityType.getKey().getKey()); + assert createdPacket.getX() == x; + assert createdPacket.getY() == y; + assert createdPacket.getZ() == z; + assert createdPacket.getYRot() == AngelConverter.degreesToVanillaByte(yaw); + assert createdPacket.getXRot() == AngelConverter.degreesToVanillaByte(pitch); + assert createdPacket.getYHeadRot() == AngelConverter.degreesToVanillaByte(headYaw); + assert createdPacket.getXa() == velocityX; + assert createdPacket.getYa() == velocityY; + assert createdPacket.getZa() == velocityZ; + assert createdPacket.getData() == data; + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundPlayerInfoRemovePacketImplTest.java b/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundPlayerInfoRemovePacketImplTest.java new file mode 100644 index 00000000..cc94a4d5 --- /dev/null +++ b/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundPlayerInfoRemovePacketImplTest.java @@ -0,0 +1,23 @@ +package de.oliver.fancysitula.versions.v1_21_3.packets; + +import de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundPlayerInfoRemovePacketImpl; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.UUID; + +class ClientboundPlayerInfoRemovePacketImplTest { + + @Test + void createPacket() { + List uuids = List.of(UUID.randomUUID(), UUID.randomUUID()); + + ClientboundPlayerInfoRemovePacketImpl packet = new ClientboundPlayerInfoRemovePacketImpl(uuids); + ClientboundPlayerInfoRemovePacket vanillaPacket = (ClientboundPlayerInfoRemovePacket) packet.createPacket(); + + for (UUID uuid : uuids) { + assert vanillaPacket.profileIds().contains(uuid); + } + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundPlayerInfoUpdatePacketImplTest.java b/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundPlayerInfoUpdatePacketImplTest.java new file mode 100644 index 00000000..a6ad9e34 --- /dev/null +++ b/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundPlayerInfoUpdatePacketImplTest.java @@ -0,0 +1,62 @@ +package de.oliver.fancysitula.versions.v1_21_3.packets; + +import de.oliver.fancysitula.api.packets.FS_ClientboundPlayerInfoUpdatePacket; +import de.oliver.fancysitula.api.utils.FS_GameProfile; +import de.oliver.fancysitula.api.utils.FS_GameType; +import de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundPlayerInfoUpdatePacketImpl; +import net.kyori.adventure.text.Component; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.UUID; + +class ClientboundPlayerInfoUpdatePacketImplTest { + + @Test + void createPacket() { + // Setup packet + EnumSet actions = EnumSet.noneOf(FS_ClientboundPlayerInfoUpdatePacket.Action.class); + actions.add(FS_ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER); + actions.add(FS_ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME); + actions.add(FS_ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED); + + FS_GameProfile gameProfile = new FS_GameProfile(UUID.randomUUID(), "Test name"); + boolean listed = true; + int latency = 42; + FS_GameType gameMode = FS_GameType.SURVIVAL; + Component displayName = Component.text("Test displayname"); + + List entries = new ArrayList<>(); + entries.add(new FS_ClientboundPlayerInfoUpdatePacket.Entry( + gameProfile.getUUID(), + gameProfile, + listed, + latency, + gameMode, + displayName + )); + + ClientboundPlayerInfoUpdatePacketImpl packet = new ClientboundPlayerInfoUpdatePacketImpl(actions, entries); + + ClientboundPlayerInfoUpdatePacket createdPacket = (ClientboundPlayerInfoUpdatePacket) packet.createPacket(); + + assert createdPacket.entries().size() == 1; + assert createdPacket.actions().size() == 3; + + // check entry + ClientboundPlayerInfoUpdatePacket.Entry entry = createdPacket.entries().getFirst(); + assert entry.profile().getId().equals(gameProfile.getUUID()); + assert entry.profile().getName().equals(gameProfile.getName()); + assert entry.listed() == listed; + assert entry.latency() == latency; + assert entry.gameMode().getId() == gameMode.getId(); + + // check actions + assert createdPacket.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER); + assert createdPacket.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME); + assert createdPacket.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED); + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundRemoveEntitiesPacketImplTest.java b/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundRemoveEntitiesPacketImplTest.java new file mode 100644 index 00000000..bb71e5c8 --- /dev/null +++ b/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundRemoveEntitiesPacketImplTest.java @@ -0,0 +1,21 @@ +package de.oliver.fancysitula.versions.v1_21_3.packets; + +import de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundRemoveEntitiesPacketImpl; +import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; +import org.junit.jupiter.api.Test; + +import java.util.List; + +class ClientboundRemoveEntitiesPacketImplTest { + + @Test + void createPacket() { + List entityIds = List.of(95, 120, 154, 187); + + ClientboundRemoveEntitiesPacketImpl packet = new ClientboundRemoveEntitiesPacketImpl(entityIds); + ClientboundRemoveEntitiesPacket createdPacket = (ClientboundRemoveEntitiesPacket) packet.createPacket(); + + assert createdPacket.getEntityIds().size() == entityIds.size(); + assert createdPacket.getEntityIds().containsAll(entityIds); + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundRotateHeadPacketImplTest.java b/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundRotateHeadPacketImplTest.java new file mode 100644 index 00000000..cf0106d9 --- /dev/null +++ b/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundRotateHeadPacketImplTest.java @@ -0,0 +1,22 @@ +package de.oliver.fancysitula.versions.v1_21_3.packets; + +import de.oliver.fancysitula.api.utils.AngelConverter; +import de.oliver.fancysitula.api.utils.reflections.ReflectionUtils; +import de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundRotateHeadPacketImpl; +import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket; +import org.junit.jupiter.api.Test; + +class ClientboundRotateHeadPacketImplTest { + + @Test + void createPacket() throws Exception { + int entityId = 184; + float headYaw = 45; + + ClientboundRotateHeadPacketImpl packet = new ClientboundRotateHeadPacketImpl(entityId, (byte) headYaw); + ClientboundRotateHeadPacket createdPacket = (ClientboundRotateHeadPacket) packet.createPacket(); + + assert ReflectionUtils.getField(createdPacket, "entityId").equals(entityId); + assert createdPacket.getYHeadRot() == AngelConverter.degreesToVanillaByte(headYaw); + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundSetEntityDataPacketImplTest.java b/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundSetEntityDataPacketImplTest.java new file mode 100644 index 00000000..97bdaaa5 --- /dev/null +++ b/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundSetEntityDataPacketImplTest.java @@ -0,0 +1,29 @@ +package de.oliver.fancysitula.versions.v1_21_3.packets; + +import de.oliver.fancysitula.api.packets.FS_ClientboundSetEntityDataPacket; +import de.oliver.fancysitula.api.utils.entityData.FS_TextDisplayData; +import de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundSetEntityDataPacketImpl; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; + +import java.util.List; + +class ClientboundSetEntityDataPacketImplTest { + + //TODO: Fix this test (using registry) +// @Test + void createPacket() { + int entityId = 712; + List entityData = List.of( + new FS_ClientboundSetEntityDataPacket.EntityData( + FS_TextDisplayData.TEXT, + "Hello, World!" + ) + ); + + ClientboundSetEntityDataPacketImpl packet = new ClientboundSetEntityDataPacketImpl(entityId, entityData); + ClientboundSetEntityDataPacket createdPacket = (ClientboundSetEntityDataPacket) packet.createPacket(); + + assert createdPacket.id() == entityId; + assert createdPacket.packedItems().size() == 1; + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundSetEquipmentPacketImplTest.java b/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundSetEquipmentPacketImplTest.java new file mode 100644 index 00000000..943c9cc7 --- /dev/null +++ b/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundSetEquipmentPacketImplTest.java @@ -0,0 +1,29 @@ +package de.oliver.fancysitula.versions.v1_21_3.packets; + +import de.oliver.fancysitula.api.utils.FS_EquipmentSlot; +import de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundSetEquipmentPacketImpl; +import net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import java.util.Map; + +class ClientboundSetEquipmentPacketImplTest { + + //TODO: Fix this test (registry problems) + // @Test + void createPacket() { + // Setup packet + Map equipment = Map.of( + FS_EquipmentSlot.MAINHAND, new ItemStack(Material.DIAMOND_SWORD), + FS_EquipmentSlot.OFFHAND, new ItemStack(Material.SHIELD), + FS_EquipmentSlot.HEAD, new ItemStack(Material.DIAMOND_HELMET) + ); + + ClientboundSetEquipmentPacketImpl packet = new ClientboundSetEquipmentPacketImpl(42, equipment); + ClientboundSetEquipmentPacket createdPacket = (ClientboundSetEquipmentPacket) packet.createPacket(); + + assert createdPacket.getEntity() == 42; + assert createdPacket.getSlots().size() == 3; + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundSetPassengersPacketImplTest.java b/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundSetPassengersPacketImplTest.java new file mode 100644 index 00000000..34bc26ee --- /dev/null +++ b/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundSetPassengersPacketImplTest.java @@ -0,0 +1,29 @@ +package de.oliver.fancysitula.versions.v1_21_3.packets; + +import de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundSetPassengersPacketImpl; +import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +public class ClientboundSetPassengersPacketImplTest { + + @Test + void createPacket() { + // Setup packet + int vehicleID = 712; + List passengers = new ArrayList<>(); + passengers.add(571); + passengers.add(572); + + ClientboundSetPassengersPacketImpl packet = new ClientboundSetPassengersPacketImpl(vehicleID, passengers); + ClientboundSetPassengersPacket createdPacket = (ClientboundSetPassengersPacket) packet.createPacket(); + + // Check packet + assert createdPacket.getVehicle() == vehicleID; + assert createdPacket.getPassengers().length == 2; + assert createdPacket.getPassengers()[0] == 571; + assert createdPacket.getPassengers()[1] == 572; + } +} diff --git a/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundTeleportEntityPacketImplTest.java b/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundTeleportEntityPacketImplTest.java new file mode 100644 index 00000000..758e55ce --- /dev/null +++ b/libraries/packets/implementations/1_21_3/src/test/java/de/oliver/fancysitula/versions/v1_21_3/packets/ClientboundTeleportEntityPacketImplTest.java @@ -0,0 +1,32 @@ +package de.oliver.fancysitula.versions.v1_21_3.packets; + +import de.oliver.fancysitula.api.utils.AngelConverter; +import de.oliver.fancysitula.versions.v1_21_3.packets.ClientboundTeleportEntityPacketImpl; +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; +import org.junit.jupiter.api.Test; + +class ClientboundTeleportEntityPacketImplTest { + + @Test + void createPacket() { + int entityId = 4313; + double x = 15.0; + double y = 57.0; + double z = -27.0; + float yaw = 90.0f; + float pitch = 45.0f; + boolean onGround = true; + + ClientboundTeleportEntityPacketImpl packet = new ClientboundTeleportEntityPacketImpl(entityId, x, y, z, yaw, pitch, onGround); + ClientboundTeleportEntityPacket createdPacket = (ClientboundTeleportEntityPacket) packet.createPacket(); + + assert createdPacket != null; + assert createdPacket.getId() == entityId; + assert createdPacket.getX() == x; + assert createdPacket.getY() == y; + assert createdPacket.getZ() == z; + assert createdPacket.getyRot() == AngelConverter.degreesToVanillaByte(yaw); + assert createdPacket.getxRot() == AngelConverter.degreesToVanillaByte(pitch); + assert createdPacket.isOnGround() == onGround; + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_4/build.gradle.kts b/libraries/packets/implementations/1_21_4/build.gradle.kts new file mode 100644 index 00000000..966293a8 --- /dev/null +++ b/libraries/packets/implementations/1_21_4/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + id("java-library") + id("io.papermc.paperweight.userdev") +} + +val minecraftVersion = "1.21.4" + +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() + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundAddEntityPacketImpl.java b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundAddEntityPacketImpl.java new file mode 100644 index 00000000..62136d8b --- /dev/null +++ b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundAddEntityPacketImpl.java @@ -0,0 +1,48 @@ +package de.oliver.fancysitula.versions.v1_21_4.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundAddEntityPacket; +import de.oliver.fancysitula.api.utils.AngelConverter; +import de.oliver.fancysitula.versions.v1_21_4.utils.VanillaPlayerAdapter; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.phys.Vec3; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.bukkit.entity.EntityType; + +import java.util.UUID; + +public class ClientboundAddEntityPacketImpl extends FS_ClientboundAddEntityPacket { + + public ClientboundAddEntityPacketImpl(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) { + super(entityId, entityUUID, entityType, x, y, z, yaw, pitch, headYaw, velocityX, velocityY, velocityZ, data); + } + + @Override + public Object createPacket() { + net.minecraft.world.entity.EntityType vanillaType = BuiltInRegistries.ENTITY_TYPE.getValue(CraftNamespacedKey.toMinecraft(entityType.getKey())); + + return new ClientboundAddEntityPacket( + entityId, + entityUUID, + x, + y, + z, + AngelConverter.degreesToVanillaByte(pitch), + AngelConverter.degreesToVanillaByte(yaw), + vanillaType, + data, + new Vec3(velocityX, velocityY, velocityZ), + AngelConverter.degreesToVanillaByte(headYaw) + ); + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundAddEntityPacket packet = (ClientboundAddEntityPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundCreateOrUpdateTeamPacketImpl.java b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundCreateOrUpdateTeamPacketImpl.java new file mode 100644 index 00000000..3a54a586 --- /dev/null +++ b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundCreateOrUpdateTeamPacketImpl.java @@ -0,0 +1,128 @@ +package de.oliver.fancysitula.versions.v1_21_4.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundCreateOrUpdateTeamPacket; +import de.oliver.fancysitula.versions.v1_21_4.utils.VanillaPlayerAdapter; +import io.papermc.paper.adventure.PaperAdventure; +import net.minecraft.ChatFormatting; +import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.scores.PlayerTeam; +import net.minecraft.world.scores.Scoreboard; +import net.minecraft.world.scores.Team; + +public class ClientboundCreateOrUpdateTeamPacketImpl extends FS_ClientboundCreateOrUpdateTeamPacket { + + private static final Scoreboard SCOREBOARD = new Scoreboard(); + + public ClientboundCreateOrUpdateTeamPacketImpl(String teamName, CreateTeam createTeam) { + super(teamName, createTeam); + } + + public ClientboundCreateOrUpdateTeamPacketImpl(String teamName, RemoveTeam removeTeam) { + super(teamName, removeTeam); + } + + public ClientboundCreateOrUpdateTeamPacketImpl(String teamName, UpdateTeam updateTeam) { + super(teamName, updateTeam); + } + + public ClientboundCreateOrUpdateTeamPacketImpl(String teamName, AddEntity addEntity) { + super(teamName, addEntity); + } + + public ClientboundCreateOrUpdateTeamPacketImpl(String teamName, RemoveEntity removeEntity) { + super(teamName, removeEntity); + } + + @Override + public Object createPacket() { + return switch (method) { + case CREATE_TEAM -> createCreateTeamPacket(); + case REMOVE_TEAM -> createRemoveTeamPacket(); + case UPDATE_TEAM -> createUpdateTeamPacket(); + case ADD_ENTITY -> createAddEntityPacket(); + case REMOVE_ENTITY -> createRemoveEntityPacket(); + }; + } + + private Object createCreateTeamPacket() { + if (createTeam == null) { + return null; + } + + PlayerTeam playerTeam = new PlayerTeam(SCOREBOARD, teamName); + playerTeam.setDisplayName(PaperAdventure.asVanilla(createTeam.getDisplayName())); + playerTeam.setAllowFriendlyFire(createTeam.isAllowFriendlyFire()); + playerTeam.setSeeFriendlyInvisibles(createTeam.isCanSeeFriendlyInvisibles()); + playerTeam.setNameTagVisibility(Team.Visibility.byName(createTeam.getNameTagVisibility().getName())); + playerTeam.setCollisionRule(PlayerTeam.CollisionRule.byName(createTeam.getCollisionRule().getName())); + playerTeam.setColor(ChatFormatting.getById(createTeam.getColor().getId())); + playerTeam.setPlayerPrefix(PaperAdventure.asVanilla(createTeam.getPrefix())); + playerTeam.setPlayerSuffix(PaperAdventure.asVanilla(createTeam.getSuffix())); + for (String entity : createTeam.getEntities()) { + playerTeam.getPlayers().add(entity); + } + + return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(playerTeam, true); + } + + private Object createRemoveTeamPacket() { + if (removeTeam == null) { + return null; + } + + PlayerTeam playerTeam = new PlayerTeam(SCOREBOARD, teamName); + return ClientboundSetPlayerTeamPacket.createRemovePacket(playerTeam); + } + + private Object createUpdateTeamPacket() { + if (updateTeam == null) { + return null; + } + + PlayerTeam playerTeam = new PlayerTeam(SCOREBOARD, teamName); + playerTeam.setDisplayName(PaperAdventure.asVanilla(updateTeam.getDisplayName())); + playerTeam.setAllowFriendlyFire(updateTeam.isAllowFriendlyFire()); + playerTeam.setSeeFriendlyInvisibles(updateTeam.isCanSeeFriendlyInvisibles()); + playerTeam.setNameTagVisibility(Team.Visibility.byName(updateTeam.getNameTagVisibility().getName())); + playerTeam.setCollisionRule(PlayerTeam.CollisionRule.byName(updateTeam.getCollisionRule().getName())); + playerTeam.setColor(ChatFormatting.getById(updateTeam.getColor().getId())); + playerTeam.setPlayerPrefix(PaperAdventure.asVanilla(updateTeam.getPrefix())); + playerTeam.setPlayerSuffix(PaperAdventure.asVanilla(updateTeam.getSuffix())); + + return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(playerTeam, true); + } + + private Object createAddEntityPacket() { + if (addEntity == null) { + return null; + } + + PlayerTeam playerTeam = new PlayerTeam(SCOREBOARD, teamName); + for (String entity : addEntity.getEntities()) { + playerTeam.getPlayers().add(entity); + } + return ClientboundSetPlayerTeamPacket.createMultiplePlayerPacket(playerTeam, addEntity.getEntities(), ClientboundSetPlayerTeamPacket.Action.ADD); + } + + private Object createRemoveEntityPacket() { + if (removeEntity == null) { + return null; + } + + PlayerTeam playerTeam = new PlayerTeam(SCOREBOARD, teamName); + for (String entity : removeEntity.getEntities()) { + playerTeam.getPlayers().add(entity); + } + return ClientboundSetPlayerTeamPacket.createMultiplePlayerPacket(playerTeam, removeEntity.getEntities(), ClientboundSetPlayerTeamPacket.Action.REMOVE); + } + + @Override + protected void sendPacketTo(FS_RealPlayer player) { + ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundPlayerInfoRemovePacketImpl.java b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundPlayerInfoRemovePacketImpl.java new file mode 100644 index 00000000..7a758046 --- /dev/null +++ b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundPlayerInfoRemovePacketImpl.java @@ -0,0 +1,30 @@ +package de.oliver.fancysitula.versions.v1_21_4.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundPlayerInfoRemovePacket; +import de.oliver.fancysitula.versions.v1_21_4.utils.VanillaPlayerAdapter; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; +import net.minecraft.server.level.ServerPlayer; + +import java.util.List; +import java.util.UUID; + +public class ClientboundPlayerInfoRemovePacketImpl extends FS_ClientboundPlayerInfoRemovePacket { + + public ClientboundPlayerInfoRemovePacketImpl(List uuids) { + super(uuids); + } + + @Override + public Object createPacket() { + return new ClientboundPlayerInfoRemovePacket(uuids); + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundPlayerInfoRemovePacket packet = (ClientboundPlayerInfoRemovePacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundPlayerInfoUpdatePacketImpl.java b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundPlayerInfoUpdatePacketImpl.java new file mode 100644 index 00000000..3143b436 --- /dev/null +++ b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundPlayerInfoUpdatePacketImpl.java @@ -0,0 +1,54 @@ +package de.oliver.fancysitula.versions.v1_21_4.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundPlayerInfoUpdatePacket; +import de.oliver.fancysitula.versions.v1_21_4.utils.GameProfileImpl; +import de.oliver.fancysitula.versions.v1_21_4.utils.VanillaPlayerAdapter; +import io.papermc.paper.adventure.PaperAdventure; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.GameType; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +public class ClientboundPlayerInfoUpdatePacketImpl extends FS_ClientboundPlayerInfoUpdatePacket { + + public ClientboundPlayerInfoUpdatePacketImpl(EnumSet actions, List entries) { + super(actions, entries); + } + + @Override + public Object createPacket() { + EnumSet vanillaActions = EnumSet.noneOf(ClientboundPlayerInfoUpdatePacket.Action.class); + for (FS_ClientboundPlayerInfoUpdatePacket.Action action : actions) { + vanillaActions.add(ClientboundPlayerInfoUpdatePacket.Action.valueOf(action.name())); + } + + List entries = new ArrayList<>(); + for (Entry entry : this.entries) { + entries.add(new ClientboundPlayerInfoUpdatePacket.Entry( + entry.uuid(), + GameProfileImpl.asVanilla(entry.profile()), + entry.listed(), + entry.latency(), + GameType.byId(entry.gameMode().getId()), + PaperAdventure.asVanilla(entry.displayName()), + true, + -1, + null // TODO: Add ChatSession support + )); + } + + return new ClientboundPlayerInfoUpdatePacket(vanillaActions, entries); + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundPlayerInfoUpdatePacket packet = (ClientboundPlayerInfoUpdatePacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundRemoveEntitiesPacketImpl.java b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundRemoveEntitiesPacketImpl.java new file mode 100644 index 00000000..04f0ebec --- /dev/null +++ b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundRemoveEntitiesPacketImpl.java @@ -0,0 +1,37 @@ +package de.oliver.fancysitula.versions.v1_21_4.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundRemoveEntitiesPacket; +import de.oliver.fancysitula.versions.v1_21_4.utils.VanillaPlayerAdapter; +import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; +import net.minecraft.server.level.ServerPlayer; + +import java.util.List; + +public class ClientboundRemoveEntitiesPacketImpl extends FS_ClientboundRemoveEntitiesPacket { + + /** + * @param entityIds IDs of the entities to remove + */ + public ClientboundRemoveEntitiesPacketImpl(List entityIds) { + super(entityIds); + } + + @Override + public Object createPacket() { + int[] ids = new int[this.entityIds.size()]; + for (int i = 0; i < this.entityIds.size(); i++) { + ids[i] = this.entityIds.get(i); + } + + return new ClientboundRemoveEntitiesPacket(ids); + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundRemoveEntitiesPacket packet = (ClientboundRemoveEntitiesPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundRotateHeadPacketImpl.java b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundRotateHeadPacketImpl.java new file mode 100644 index 00000000..a37a77c0 --- /dev/null +++ b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundRotateHeadPacketImpl.java @@ -0,0 +1,38 @@ +package de.oliver.fancysitula.versions.v1_21_4.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundRotateHeadPacket; +import de.oliver.fancysitula.api.utils.AngelConverter; +import de.oliver.fancysitula.api.utils.reflections.ReflectionUtils; +import de.oliver.fancysitula.versions.v1_21_4.utils.VanillaPlayerAdapter; +import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket; +import net.minecraft.server.level.ServerPlayer; + +public class ClientboundRotateHeadPacketImpl extends FS_ClientboundRotateHeadPacket { + + public ClientboundRotateHeadPacketImpl(int entityId, float headYaw) { + super(entityId, headYaw); + } + + @Override + public Object createPacket() { + ClientboundRotateHeadPacket packet = null; + + try { + packet = ReflectionUtils.createUnsafeInstance(ClientboundRotateHeadPacket.class); + ReflectionUtils.setFinalField(packet, "entityId", entityId); + ReflectionUtils.setFinalField(packet, "yHeadRot", AngelConverter.degreesToVanillaByte(headYaw)); + } catch (Exception e) { + e.printStackTrace(); + } + return packet; + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundRotateHeadPacket packet = (ClientboundRotateHeadPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundSetEntityDataPacketImpl.java b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundSetEntityDataPacketImpl.java new file mode 100644 index 00000000..98e346d5 --- /dev/null +++ b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundSetEntityDataPacketImpl.java @@ -0,0 +1,67 @@ +package de.oliver.fancysitula.versions.v1_21_4.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundSetEntityDataPacket; +import de.oliver.fancysitula.api.utils.reflections.ReflectionUtils; +import de.oliver.fancysitula.versions.v1_21_4.utils.VanillaPlayerAdapter; +import io.papermc.paper.adventure.PaperAdventure; +import net.kyori.adventure.text.Component; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.level.ServerPlayer; +import org.bukkit.block.BlockState; +import org.bukkit.craftbukkit.block.CraftBlockState; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; + +public class ClientboundSetEntityDataPacketImpl extends FS_ClientboundSetEntityDataPacket { + + public ClientboundSetEntityDataPacketImpl(int entityId, List entityData) { + super(entityId, entityData); + } + + @Override + public Object createPacket() { + List> dataValues = new ArrayList<>(); + for (EntityData data : entityData) { + try { + Class entityClass = Class.forName(data.getAccessor().entityClassName()); + net.minecraft.network.syncher.EntityDataAccessor accessor = ReflectionUtils.getStaticField(entityClass, data.getAccessor().accessorFieldName()); + + Object vanillaValue = data.getValue(); + + if (data.getValue() == null) { + continue; + } + + if (data.getValue() instanceof Component c) { + vanillaValue = PaperAdventure.asVanilla(c); + } + + if (data.getValue() instanceof ItemStack i) { + vanillaValue = net.minecraft.world.item.ItemStack.fromBukkitCopy(i); + } + + if (data.getValue() instanceof BlockState b) { + vanillaValue = ((CraftBlockState) b).getHandle(); + } + + dataValues.add(SynchedEntityData.DataValue.create(accessor, vanillaValue)); + } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + } + + return new ClientboundSetEntityDataPacket(entityId, dataValues); + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundSetEntityDataPacket packet = (ClientboundSetEntityDataPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundSetEquipmentPacketImpl.java b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundSetEquipmentPacketImpl.java new file mode 100644 index 00000000..21d6acfd --- /dev/null +++ b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundSetEquipmentPacketImpl.java @@ -0,0 +1,45 @@ +package de.oliver.fancysitula.versions.v1_21_4.packets; + +import com.mojang.datafixers.util.Pair; +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundSetEquipmentPacket; +import de.oliver.fancysitula.api.utils.FS_EquipmentSlot; +import de.oliver.fancysitula.versions.v1_21_4.utils.VanillaPlayerAdapter; +import net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.EquipmentSlot; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class ClientboundSetEquipmentPacketImpl extends FS_ClientboundSetEquipmentPacket { + + public ClientboundSetEquipmentPacketImpl(int entityId, Map equipment) { + super(entityId, equipment); + } + + @Override + public Object createPacket() { + List> slots = new ArrayList<>(); + + for (Map.Entry entry : equipment.entrySet()) { + EquipmentSlot equipmentSlot = net.minecraft.world.entity.EquipmentSlot.byName(entry.getKey().name().toLowerCase()); + net.minecraft.world.item.ItemStack itemStack = CraftItemStack.asNMSCopy(entry.getValue()); + + slots.add(Pair.of(equipmentSlot, itemStack)); + } + + return new ClientboundSetEquipmentPacket(entityId, slots); + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundSetEquipmentPacket packet = (ClientboundSetEquipmentPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundSetPassengersPacketImpl.java b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundSetPassengersPacketImpl.java new file mode 100644 index 00000000..152690fd --- /dev/null +++ b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundSetPassengersPacketImpl.java @@ -0,0 +1,45 @@ +package de.oliver.fancysitula.versions.v1_21_4.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundSetPassengersPacket; +import de.oliver.fancysitula.api.utils.reflections.ReflectionUtils; +import de.oliver.fancysitula.versions.v1_21_4.utils.VanillaPlayerAdapter; +import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket; +import net.minecraft.server.level.ServerPlayer; + +import java.util.List; + +public class ClientboundSetPassengersPacketImpl extends FS_ClientboundSetPassengersPacket { + + public ClientboundSetPassengersPacketImpl(int entityId, List passengers) { + super(entityId, passengers); + } + + + @Override + public Object createPacket() { + int[] passengers = new int[this.passengers.size()]; + for (int i = 0; i < this.passengers.size(); i++) { + passengers[i] = this.passengers.get(i); + } + + try { + ClientboundSetPassengersPacket packet = ReflectionUtils.createUnsafeInstance(ClientboundSetPassengersPacket.class); + ReflectionUtils.setFinalField(packet, "vehicle", entityId); + ReflectionUtils.setFinalField(packet, "passengers", passengers); + return packet; + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundSetPassengersPacket packet = (ClientboundSetPassengersPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundTeleportEntityPacketImpl.java b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundTeleportEntityPacketImpl.java new file mode 100644 index 00000000..0c2853cc --- /dev/null +++ b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundTeleportEntityPacketImpl.java @@ -0,0 +1,43 @@ +package de.oliver.fancysitula.versions.v1_21_4.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundTeleportEntityPacket; +import de.oliver.fancysitula.versions.v1_21_4.utils.VanillaPlayerAdapter; +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.PositionMoveRotation; +import net.minecraft.world.phys.Vec3; + +import java.util.Set; + +public class ClientboundTeleportEntityPacketImpl extends FS_ClientboundTeleportEntityPacket { + + public ClientboundTeleportEntityPacketImpl(int entityId, double x, double y, double z, float yaw, float pitch, boolean onGround) { + super(entityId, x, y, z, yaw, pitch, onGround); + } + + @Override + public Object createPacket() { + ClientboundTeleportEntityPacket packet = new ClientboundTeleportEntityPacket( + entityId, + new PositionMoveRotation( + new Vec3(x, y, z), + Vec3.ZERO, + yaw, + pitch + ), + Set.of(), + onGround + ); + + return packet; + } + + @Override + public void sendPacketTo(FS_RealPlayer player) { + ClientboundTeleportEntityPacket packet = (ClientboundTeleportEntityPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/utils/GameProfileImpl.java b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/utils/GameProfileImpl.java new file mode 100644 index 00000000..fda827e6 --- /dev/null +++ b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/utils/GameProfileImpl.java @@ -0,0 +1,33 @@ +package de.oliver.fancysitula.versions.v1_21_4.utils; + +import com.mojang.authlib.GameProfile; +import de.oliver.fancysitula.api.utils.FS_GameProfile; + +import java.util.Map; + +public class GameProfileImpl { + + public static GameProfile asVanilla(FS_GameProfile gameProfile) { + GameProfile gf = new GameProfile(gameProfile.getUUID(), gameProfile.getName()); + + for (Map.Entry entry : gameProfile.getProperties().entrySet()) { + FS_GameProfile.Property property = entry.getValue(); + + gf.getProperties().put(entry.getKey(), new com.mojang.authlib.properties.Property(property.name(), property.value(), property.signature())); + } + + return gf; + } + + public static FS_GameProfile fromVanilla(GameProfile gameProfile) { + FS_GameProfile fsGameProfile = new FS_GameProfile(gameProfile.getId(), gameProfile.getName()); + + for (Map.Entry entry : gameProfile.getProperties().entries()) { + com.mojang.authlib.properties.Property property = entry.getValue(); + + fsGameProfile.getProperties().put(entry.getKey(), new FS_GameProfile.Property(property.name(), property.value(), property.signature())); + } + + return fsGameProfile; + } +} diff --git a/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/utils/VanillaPlayerAdapter.java b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/utils/VanillaPlayerAdapter.java new file mode 100644 index 00000000..c8036600 --- /dev/null +++ b/libraries/packets/implementations/1_21_4/src/main/java/de/oliver/fancysitula/versions/v1_21_4/utils/VanillaPlayerAdapter.java @@ -0,0 +1,12 @@ +package de.oliver.fancysitula.versions.v1_21_4.utils; + +import net.minecraft.server.level.ServerPlayer; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; + +public class VanillaPlayerAdapter { + + public static ServerPlayer asVanilla(Player p) { + return ((CraftPlayer) p).getHandle(); + } +} diff --git a/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundAddEntityPacketImplTest.java b/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundAddEntityPacketImplTest.java new file mode 100644 index 00000000..66b15bf5 --- /dev/null +++ b/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundAddEntityPacketImplTest.java @@ -0,0 +1,65 @@ +package de.oliver.fancysitula.versions.v1_21_4.packets; + +import de.oliver.fancysitula.api.utils.AngelConverter; +import de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundAddEntityPacketImpl; +import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; +import org.bukkit.entity.EntityType; + +import java.util.UUID; + +class ClientboundAddEntityPacketImplTest { + + //TODO: Fix this test (registry problems) + // @Test + void createPacket() { + int entityId = 10000; + UUID entityUUID = UUID.randomUUID(); + EntityType entityType = EntityType.PIG; + + double x = 5; + double y = 57; + double z = 203; + + float yaw = 142; + float pitch = 247; + float headYaw = 90; + + int velocityX = 0; + int velocityY = 0; + int velocityZ = 0; + + int data = 0; + + ClientboundAddEntityPacketImpl packet = new ClientboundAddEntityPacketImpl( + entityId, + entityUUID, + entityType, + x, + y, + z, + yaw, + pitch, + headYaw, + velocityX, + velocityY, + velocityZ, + data + ); + + ClientboundAddEntityPacket createdPacket = (ClientboundAddEntityPacket) packet.createPacket(); + + assert createdPacket.getId() == entityId; + assert createdPacket.getUUID().equals(entityUUID); + assert createdPacket.getType().getDescriptionId().equals(entityType.getKey().getKey()); + assert createdPacket.getX() == x; + assert createdPacket.getY() == y; + assert createdPacket.getZ() == z; + assert createdPacket.getYRot() == AngelConverter.degreesToVanillaByte(yaw); + assert createdPacket.getXRot() == AngelConverter.degreesToVanillaByte(pitch); + assert createdPacket.getYHeadRot() == AngelConverter.degreesToVanillaByte(headYaw); + assert createdPacket.getXa() == velocityX; + assert createdPacket.getYa() == velocityY; + assert createdPacket.getZa() == velocityZ; + assert createdPacket.getData() == data; + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundPlayerInfoRemovePacketImplTest.java b/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundPlayerInfoRemovePacketImplTest.java new file mode 100644 index 00000000..b5af37c6 --- /dev/null +++ b/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundPlayerInfoRemovePacketImplTest.java @@ -0,0 +1,23 @@ +package de.oliver.fancysitula.versions.v1_21_4.packets; + +import de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundPlayerInfoRemovePacketImpl; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.UUID; + +class ClientboundPlayerInfoRemovePacketImplTest { + + @Test + void createPacket() { + List uuids = List.of(UUID.randomUUID(), UUID.randomUUID()); + + ClientboundPlayerInfoRemovePacketImpl packet = new ClientboundPlayerInfoRemovePacketImpl(uuids); + ClientboundPlayerInfoRemovePacket vanillaPacket = (ClientboundPlayerInfoRemovePacket) packet.createPacket(); + + for (UUID uuid : uuids) { + assert vanillaPacket.profileIds().contains(uuid); + } + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundPlayerInfoUpdatePacketImplTest.java b/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundPlayerInfoUpdatePacketImplTest.java new file mode 100644 index 00000000..2bb28e13 --- /dev/null +++ b/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundPlayerInfoUpdatePacketImplTest.java @@ -0,0 +1,62 @@ +package de.oliver.fancysitula.versions.v1_21_4.packets; + +import de.oliver.fancysitula.api.packets.FS_ClientboundPlayerInfoUpdatePacket; +import de.oliver.fancysitula.api.utils.FS_GameProfile; +import de.oliver.fancysitula.api.utils.FS_GameType; +import de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundPlayerInfoUpdatePacketImpl; +import net.kyori.adventure.text.Component; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.UUID; + +class ClientboundPlayerInfoUpdatePacketImplTest { + + @Test + void createPacket() { + // Setup packet + EnumSet actions = EnumSet.noneOf(FS_ClientboundPlayerInfoUpdatePacket.Action.class); + actions.add(FS_ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER); + actions.add(FS_ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME); + actions.add(FS_ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED); + + FS_GameProfile gameProfile = new FS_GameProfile(UUID.randomUUID(), "Test name"); + boolean listed = true; + int latency = 42; + FS_GameType gameMode = FS_GameType.SURVIVAL; + Component displayName = Component.text("Test displayname"); + + List entries = new ArrayList<>(); + entries.add(new FS_ClientboundPlayerInfoUpdatePacket.Entry( + gameProfile.getUUID(), + gameProfile, + listed, + latency, + gameMode, + displayName + )); + + ClientboundPlayerInfoUpdatePacketImpl packet = new ClientboundPlayerInfoUpdatePacketImpl(actions, entries); + + ClientboundPlayerInfoUpdatePacket createdPacket = (ClientboundPlayerInfoUpdatePacket) packet.createPacket(); + + assert createdPacket.entries().size() == 1; + assert createdPacket.actions().size() == 3; + + // check entry + ClientboundPlayerInfoUpdatePacket.Entry entry = createdPacket.entries().getFirst(); + assert entry.profile().getId().equals(gameProfile.getUUID()); + assert entry.profile().getName().equals(gameProfile.getName()); + assert entry.listed() == listed; + assert entry.latency() == latency; + assert entry.gameMode().getId() == gameMode.getId(); + + // check actions + assert createdPacket.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER); + assert createdPacket.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME); + assert createdPacket.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED); + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundRemoveEntitiesPacketImplTest.java b/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundRemoveEntitiesPacketImplTest.java new file mode 100644 index 00000000..6a32fc05 --- /dev/null +++ b/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundRemoveEntitiesPacketImplTest.java @@ -0,0 +1,21 @@ +package de.oliver.fancysitula.versions.v1_21_4.packets; + +import de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundRemoveEntitiesPacketImpl; +import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; +import org.junit.jupiter.api.Test; + +import java.util.List; + +class ClientboundRemoveEntitiesPacketImplTest { + + @Test + void createPacket() { + List entityIds = List.of(95, 120, 154, 187); + + ClientboundRemoveEntitiesPacketImpl packet = new ClientboundRemoveEntitiesPacketImpl(entityIds); + ClientboundRemoveEntitiesPacket createdPacket = (ClientboundRemoveEntitiesPacket) packet.createPacket(); + + assert createdPacket.getEntityIds().size() == entityIds.size(); + assert createdPacket.getEntityIds().containsAll(entityIds); + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundRotateHeadPacketImplTest.java b/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundRotateHeadPacketImplTest.java new file mode 100644 index 00000000..8084414d --- /dev/null +++ b/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundRotateHeadPacketImplTest.java @@ -0,0 +1,22 @@ +package de.oliver.fancysitula.versions.v1_21_4.packets; + +import de.oliver.fancysitula.api.utils.AngelConverter; +import de.oliver.fancysitula.api.utils.reflections.ReflectionUtils; +import de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundRotateHeadPacketImpl; +import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket; +import org.junit.jupiter.api.Test; + +class ClientboundRotateHeadPacketImplTest { + + @Test + void createPacket() throws Exception { + int entityId = 184; + float headYaw = 45; + + ClientboundRotateHeadPacketImpl packet = new ClientboundRotateHeadPacketImpl(entityId, (byte) headYaw); + ClientboundRotateHeadPacket createdPacket = (ClientboundRotateHeadPacket) packet.createPacket(); + + assert ReflectionUtils.getField(createdPacket, "entityId").equals(entityId); + assert createdPacket.getYHeadRot() == AngelConverter.degreesToVanillaByte(headYaw); + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundSetEntityDataPacketImplTest.java b/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundSetEntityDataPacketImplTest.java new file mode 100644 index 00000000..9ada3736 --- /dev/null +++ b/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundSetEntityDataPacketImplTest.java @@ -0,0 +1,29 @@ +package de.oliver.fancysitula.versions.v1_21_4.packets; + +import de.oliver.fancysitula.api.packets.FS_ClientboundSetEntityDataPacket; +import de.oliver.fancysitula.api.utils.entityData.FS_TextDisplayData; +import de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundSetEntityDataPacketImpl; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; + +import java.util.List; + +class ClientboundSetEntityDataPacketImplTest { + + //TODO: Fix this test (using registry) +// @Test + void createPacket() { + int entityId = 712; + List entityData = List.of( + new FS_ClientboundSetEntityDataPacket.EntityData( + FS_TextDisplayData.TEXT, + "Hello, World!" + ) + ); + + ClientboundSetEntityDataPacketImpl packet = new ClientboundSetEntityDataPacketImpl(entityId, entityData); + ClientboundSetEntityDataPacket createdPacket = (ClientboundSetEntityDataPacket) packet.createPacket(); + + assert createdPacket.id() == entityId; + assert createdPacket.packedItems().size() == 1; + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundSetEquipmentPacketImplTest.java b/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundSetEquipmentPacketImplTest.java new file mode 100644 index 00000000..e14c3c3b --- /dev/null +++ b/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundSetEquipmentPacketImplTest.java @@ -0,0 +1,29 @@ +package de.oliver.fancysitula.versions.v1_21_4.packets; + +import de.oliver.fancysitula.api.utils.FS_EquipmentSlot; +import de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundSetEquipmentPacketImpl; +import net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import java.util.Map; + +class ClientboundSetEquipmentPacketImplTest { + + //TODO: Fix this test (registry problems) + // @Test + void createPacket() { + // Setup packet + Map equipment = Map.of( + FS_EquipmentSlot.MAINHAND, new ItemStack(Material.DIAMOND_SWORD), + FS_EquipmentSlot.OFFHAND, new ItemStack(Material.SHIELD), + FS_EquipmentSlot.HEAD, new ItemStack(Material.DIAMOND_HELMET) + ); + + ClientboundSetEquipmentPacketImpl packet = new ClientboundSetEquipmentPacketImpl(42, equipment); + ClientboundSetEquipmentPacket createdPacket = (ClientboundSetEquipmentPacket) packet.createPacket(); + + assert createdPacket.getEntity() == 42; + assert createdPacket.getSlots().size() == 3; + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundSetPassengersPacketImplTest.java b/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundSetPassengersPacketImplTest.java new file mode 100644 index 00000000..daca61a7 --- /dev/null +++ b/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundSetPassengersPacketImplTest.java @@ -0,0 +1,29 @@ +package de.oliver.fancysitula.versions.v1_21_4.packets; + +import de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundSetPassengersPacketImpl; +import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +public class ClientboundSetPassengersPacketImplTest { + + @Test + void createPacket() { + // Setup packet + int vehicleID = 712; + List passengers = new ArrayList<>(); + passengers.add(571); + passengers.add(572); + + ClientboundSetPassengersPacketImpl packet = new ClientboundSetPassengersPacketImpl(vehicleID, passengers); + ClientboundSetPassengersPacket createdPacket = (ClientboundSetPassengersPacket) packet.createPacket(); + + // Check packet + assert createdPacket.getVehicle() == vehicleID; + assert createdPacket.getPassengers().length == 2; + assert createdPacket.getPassengers()[0] == 571; + assert createdPacket.getPassengers()[1] == 572; + } +} diff --git a/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundTeleportEntityPacketImplTest.java b/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundTeleportEntityPacketImplTest.java new file mode 100644 index 00000000..b1827c61 --- /dev/null +++ b/libraries/packets/implementations/1_21_4/src/test/java/de/oliver/fancysitula/versions/v1_21_4/packets/ClientboundTeleportEntityPacketImplTest.java @@ -0,0 +1,32 @@ +package de.oliver.fancysitula.versions.v1_21_4.packets; + +import de.oliver.fancysitula.api.utils.AngelConverter; +import de.oliver.fancysitula.versions.v1_21_4.packets.ClientboundTeleportEntityPacketImpl; +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; +import org.junit.jupiter.api.Test; + +class ClientboundTeleportEntityPacketImplTest { + + @Test + void createPacket() { + int entityId = 4313; + double x = 15.0; + double y = 57.0; + double z = -27.0; + float yaw = 90.0f; + float pitch = 45.0f; + boolean onGround = true; + + ClientboundTeleportEntityPacketImpl packet = new ClientboundTeleportEntityPacketImpl(entityId, x, y, z, yaw, pitch, onGround); + ClientboundTeleportEntityPacket createdPacket = (ClientboundTeleportEntityPacket) packet.createPacket(); + + assert createdPacket != null; + assert createdPacket.getId() == entityId; + assert createdPacket.getX() == x; + assert createdPacket.getY() == y; + assert createdPacket.getZ() == z; + assert createdPacket.getyRot() == AngelConverter.degreesToVanillaByte(yaw); + assert createdPacket.getxRot() == AngelConverter.degreesToVanillaByte(pitch); + assert createdPacket.isOnGround() == onGround; + } +} \ No newline at end of file diff --git a/libraries/packets/implementations/build.gradle.kts b/libraries/packets/implementations/build.gradle.kts new file mode 100644 index 00000000..9af39935 --- /dev/null +++ b/libraries/packets/implementations/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id("java-library") +} \ No newline at end of file diff --git a/libraries/packets/settings.gradle.kts b/libraries/packets/settings.gradle.kts new file mode 100644 index 00000000..eb06bfd3 --- /dev/null +++ b/libraries/packets/settings.gradle.kts @@ -0,0 +1,12 @@ +rootProject.name = "FancySitula" + +include(":api") +include(":factories") +include(":implementations") +include(":implementations:1_20_6") +include(":implementations:1_21") +include(":implementations:1_21_1") +include(":implementations:1_21_3") +include(":implementations:1_21_4") + +include(":test_plugin") \ No newline at end of file diff --git a/libraries/packets/test_plugin/build.gradle.kts b/libraries/packets/test_plugin/build.gradle.kts new file mode 100644 index 00000000..00af0041 --- /dev/null +++ b/libraries/packets/test_plugin/build.gradle.kts @@ -0,0 +1,66 @@ +plugins { + id("java-library") + id("maven-publish") + + id("xyz.jpenilla.run-paper") + id("com.gradleup.shadow") + id("net.minecrell.plugin-yml.paper") +} + +runPaper.folia.registerTask() + +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_20_6")) + implementation(project(":libraries:packets:implementations:1_21_3")) + implementation("de.oliver.FancyAnalytics:logger:0.0.4") +} + +paper { + name = "FancySitulaTestPlugin" + main = "de.oliver.fancysitula.FancySitulaPlugin" + bootstrapper = "de.oliver.fancysitula.loaders.FancySitulaPluginBootstrapper" + loader = "de.oliver.fancysitula.loaders.FancySitulaPluginLoader" + foliaSupported = true + version = "1.0.0" + description = "Test plugin for FancySitula" + apiVersion = "1.19" +} + +tasks { + runServer { + minecraftVersion(findProperty("minecraftVersion").toString()) +// minecraftVersion("1.20.4") + } + + shadowJar { + archiveClassifier.set("") + } + + 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)) +} \ No newline at end of file diff --git a/libraries/packets/test_plugin/src/main/java/de/oliver/fancysitula/FancySitulaPlugin.java b/libraries/packets/test_plugin/src/main/java/de/oliver/fancysitula/FancySitulaPlugin.java new file mode 100644 index 00000000..413d61e8 --- /dev/null +++ b/libraries/packets/test_plugin/src/main/java/de/oliver/fancysitula/FancySitulaPlugin.java @@ -0,0 +1,16 @@ +package de.oliver.fancysitula; + +import de.oliver.fancyanalytics.logger.LogLevel; +import de.oliver.fancysitula.api.IFancySitula; +import de.oliver.fancysitula.commands.FancySitulaCMD; +import org.bukkit.plugin.java.JavaPlugin; + +public class FancySitulaPlugin extends JavaPlugin { + + @Override + public void onEnable() { + IFancySitula.LOGGER.setCurrentLevel(LogLevel.DEBUG); + + getServer().getCommandMap().register("fancysitula", new FancySitulaCMD()); + } +} diff --git a/libraries/packets/test_plugin/src/main/java/de/oliver/fancysitula/commands/FancySitulaCMD.java b/libraries/packets/test_plugin/src/main/java/de/oliver/fancysitula/commands/FancySitulaCMD.java new file mode 100644 index 00000000..e3027524 --- /dev/null +++ b/libraries/packets/test_plugin/src/main/java/de/oliver/fancysitula/commands/FancySitulaCMD.java @@ -0,0 +1,118 @@ +package de.oliver.fancysitula.commands; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundPlayerInfoUpdatePacket; +import de.oliver.fancysitula.api.packets.FS_Color; +import de.oliver.fancysitula.api.teams.FS_CollisionRule; +import de.oliver.fancysitula.api.teams.FS_NameTagVisibility; +import de.oliver.fancysitula.api.teams.FS_Team; +import de.oliver.fancysitula.api.utils.FS_GameProfile; +import de.oliver.fancysitula.api.utils.FS_GameType; +import de.oliver.fancysitula.factories.FancySitula; +import net.kyori.adventure.text.Component; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.EnumSet; +import java.util.List; +import java.util.UUID; + +public class FancySitulaCMD extends Command { + + public FancySitulaCMD() { + super("fancysitula"); + } + + @Override + public boolean execute(@NotNull CommandSender sender, @NotNull String s, @NotNull String[] args) { + if (!(sender instanceof Player p)) { + sender.sendMessage("You must be a player to execute this command!"); + return true; + } + + // Wrap the real player into an FS_Player instance + FS_RealPlayer fsPlayer = new FS_RealPlayer(p); + testTeam(p); + +// FS_TextDisplay fakeTextDisplay = new FS_TextDisplay(); +// fakeTextDisplay.setBillboardRenderConstraints((byte) 3); +// fakeTextDisplay.setScale(new Vector3f(5f)); +// fakeTextDisplay.setLocation(p.getLocation()); +// fakeTextDisplay.setText(Component.text("Hello, World!")); +// fakeTextDisplay.setBackground(0xFF00C8FF); +// FancySitula.ENTITY_FACTORY.spawnEntityFor(fsPlayer, fakeTextDisplay); + +// FS_ItemDisplay fakeItemDisplay = new FS_ItemDisplay(); +// fakeItemDisplay.setBillboardRenderConstraints((byte) 3); +// fakeItemDisplay.setScale(new Vector3f(5f)); +// fakeItemDisplay.setLocation(p.getLocation()); +// fakeItemDisplay.setItem(p.getInventory().getItemInMainHand()); +// FancySitula.ENTITY_FACTORY.spawnEntityFor(fsPlayer, fakeItemDisplay); + +// FS_BlockDisplay fakeBlockDisplay = new FS_BlockDisplay(); +// fakeBlockDisplay.setBillboardRenderConstraints((byte) 3); +// fakeBlockDisplay.setScale(new Vector3f(5f)); +// fakeBlockDisplay.setLocation(p.getLocation()); +// fakeBlockDisplay.setBlock(Material.DIAMOND_BLOCK.createBlockData().createBlockState()); +// FancySitula.ENTITY_FACTORY.spawnEntityFor(fsPlayer, fakeBlockDisplay); + + return true; + } + + private void testTeam(Player to) { + FS_RealPlayer fsPlayer = new FS_RealPlayer(to); + + + FS_Team team = new FS_Team( + "myTeam", + Component.text("My Team"), + true, + true, + FS_NameTagVisibility.ALWAYS, + FS_CollisionRule.ALWAYS, + FS_Color.AQUA, + Component.text("Prefix"), + Component.text("Suffix"), + List.of("OliverHD", "OliverHD_2") + ); + + + FancySitula.TEAM_FACTORY.createTeamFor(fsPlayer, team); +// FancySitula.TEAM_FACTORY.updateTeamFor(fsPlayer, team); +// FancySitula.TEAM_FACTORY.addEntitiesToTeamFor(fsPlayer, team, List.of("OliverHD", "OliverHD_2")); + } + + private void fakeTablistEntries(Player to) { +// Wrap the real player into an FS_Player instance + FS_RealPlayer fsPlayer = new FS_RealPlayer(to); + + + UUID uuid1 = UUID.randomUUID(); + FS_ClientboundPlayerInfoUpdatePacket.Entry entry1 = new FS_ClientboundPlayerInfoUpdatePacket.Entry( + uuid1, + new FS_GameProfile(uuid1, ""), + true, + 69, + FS_GameType.SURVIVAL, + Component.text("player1") + ); + + UUID uuid2 = UUID.randomUUID(); + FS_ClientboundPlayerInfoUpdatePacket.Entry entry2 = new FS_ClientboundPlayerInfoUpdatePacket.Entry( + uuid2, + new FS_GameProfile(uuid2, ""), + true, + 69, + FS_GameType.SURVIVAL, + Component.text("player2") + ); + + FS_ClientboundPlayerInfoUpdatePacket playerInfoUpdatePacket = FancySitula.PACKET_FACTORY.createPlayerInfoUpdatePacket( + EnumSet.of(FS_ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, FS_ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED, FS_ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME), + List.of(entry1, entry2) + ); + playerInfoUpdatePacket.send(fsPlayer); + } +} diff --git a/libraries/packets/test_plugin/src/main/java/de/oliver/fancysitula/loaders/FancySitulaPluginBootstrapper.java b/libraries/packets/test_plugin/src/main/java/de/oliver/fancysitula/loaders/FancySitulaPluginBootstrapper.java new file mode 100644 index 00000000..4123aa79 --- /dev/null +++ b/libraries/packets/test_plugin/src/main/java/de/oliver/fancysitula/loaders/FancySitulaPluginBootstrapper.java @@ -0,0 +1,19 @@ +package de.oliver.fancysitula.loaders; + +import io.papermc.paper.plugin.bootstrap.BootstrapContext; +import io.papermc.paper.plugin.bootstrap.PluginBootstrap; +import io.papermc.paper.plugin.bootstrap.PluginProviderContext; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; + +public class FancySitulaPluginBootstrapper implements PluginBootstrap { + @Override + public void bootstrap(@NotNull BootstrapContext bootstrapContext) { + + } + + @Override + public @NotNull JavaPlugin createPlugin(@NotNull PluginProviderContext context) { + return PluginBootstrap.super.createPlugin(context); + } +} diff --git a/libraries/packets/test_plugin/src/main/java/de/oliver/fancysitula/loaders/FancySitulaPluginLoader.java b/libraries/packets/test_plugin/src/main/java/de/oliver/fancysitula/loaders/FancySitulaPluginLoader.java new file mode 100644 index 00000000..187b901d --- /dev/null +++ b/libraries/packets/test_plugin/src/main/java/de/oliver/fancysitula/loaders/FancySitulaPluginLoader.java @@ -0,0 +1,12 @@ +package de.oliver.fancysitula.loaders; + +import io.papermc.paper.plugin.loader.PluginClasspathBuilder; +import io.papermc.paper.plugin.loader.PluginLoader; +import org.jetbrains.annotations.NotNull; + +public class FancySitulaPluginLoader implements PluginLoader { + @Override + public void classloader(@NotNull PluginClasspathBuilder pluginClasspathBuilder) { + + } +} diff --git a/plugins/fancyholograms/api/build.gradle.kts b/plugins/fancyholograms/api/build.gradle.kts index cbfaba55..31c75233 100644 --- a/plugins/fancyholograms/api/build.gradle.kts +++ b/plugins/fancyholograms/api/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id("java-library") id("maven-publish") - id("com.gradleup.shadow") version "8.3.6" + id("com.gradleup.shadow") } val minecraftVersion = "1.19.4" diff --git a/plugins/fancyholograms/build.gradle.kts b/plugins/fancyholograms/build.gradle.kts index 33ad3ecb..639194d1 100644 --- a/plugins/fancyholograms/build.gradle.kts +++ b/plugins/fancyholograms/build.gradle.kts @@ -7,11 +7,11 @@ plugins { id("java-library") id("maven-publish") - id("xyz.jpenilla.run-paper") version "2.3.1" - id("com.gradleup.shadow") version "8.3.6" - id("net.minecrell.plugin-yml.paper") version "0.6.0" - id("io.papermc.hangar-publish-plugin") version "0.1.2" - id("com.modrinth.minotaur") version "2.+" + id("xyz.jpenilla.run-paper") + id("com.gradleup.shadow") + id("net.minecrell.plugin-yml.paper") + id("io.papermc.hangar-publish-plugin") + id("com.modrinth.minotaur") } runPaper.folia.registerTask() @@ -63,7 +63,7 @@ dependencies { implementation(project(":plugins:fancyholograms::implementation_1_20_1", configuration = "reobf")) implementation(project(":plugins:fancyholograms::implementation_1_19_4", configuration = "reobf")) - implementation("de.oliver:FancyLib:35") + implementation(project(":libraries:common")) implementation("de.oliver:FancySitula:0.0.13") implementation("de.oliver.FancyAnalytics:api:0.1.6") implementation("de.oliver.FancyAnalytics:logger:0.0.6") diff --git a/plugins/fancyholograms/implementation_1_19_4/build.gradle.kts b/plugins/fancyholograms/implementation_1_19_4/build.gradle.kts index 3b8b11a4..53cbf599 100644 --- a/plugins/fancyholograms/implementation_1_19_4/build.gradle.kts +++ b/plugins/fancyholograms/implementation_1_19_4/build.gradle.kts @@ -1,6 +1,6 @@ plugins { id("java-library") - id("io.papermc.paperweight.userdev") version "1.7.7" + id("io.papermc.paperweight.userdev") } diff --git a/plugins/fancyholograms/implementation_1_20_1/build.gradle.kts b/plugins/fancyholograms/implementation_1_20_1/build.gradle.kts index 305839c0..c92130a0 100644 --- a/plugins/fancyholograms/implementation_1_20_1/build.gradle.kts +++ b/plugins/fancyholograms/implementation_1_20_1/build.gradle.kts @@ -1,6 +1,6 @@ plugins { id("java-library") - id("io.papermc.paperweight.userdev") version "1.7.7" + id("io.papermc.paperweight.userdev") } diff --git a/plugins/fancyholograms/implementation_1_20_2/build.gradle.kts b/plugins/fancyholograms/implementation_1_20_2/build.gradle.kts index 95da256b..4dba7b5d 100644 --- a/plugins/fancyholograms/implementation_1_20_2/build.gradle.kts +++ b/plugins/fancyholograms/implementation_1_20_2/build.gradle.kts @@ -1,6 +1,6 @@ plugins { id("java-library") - id("io.papermc.paperweight.userdev") version "1.7.7" + id("io.papermc.paperweight.userdev") } diff --git a/plugins/fancyholograms/implementation_1_20_4/build.gradle.kts b/plugins/fancyholograms/implementation_1_20_4/build.gradle.kts index 13e190ce..8f96f62a 100644 --- a/plugins/fancyholograms/implementation_1_20_4/build.gradle.kts +++ b/plugins/fancyholograms/implementation_1_20_4/build.gradle.kts @@ -1,6 +1,6 @@ plugins { id("java-library") - id("io.papermc.paperweight.userdev") version "1.7.7" + id("io.papermc.paperweight.userdev") } diff --git a/settings.gradle.kts b/settings.gradle.kts index 71715b29..57a10994 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,4 +5,16 @@ include(":plugins:fancyholograms:api") include(":plugins:fancyholograms:implementation_1_20_4") include(":plugins:fancyholograms:implementation_1_20_2") include(":plugins:fancyholograms:implementation_1_20_1") -include(":plugins:fancyholograms:implementation_1_19_4") \ No newline at end of file +include(":plugins:fancyholograms:implementation_1_19_4") + +include(":libraries:common") + +include("libraries:packets:api") +include("libraries:packets:factories") +include("libraries:packets:implementations") +include("libraries:packets:implementations:1_20_6") +include("libraries:packets:implementations:1_21") +include("libraries:packets:implementations:1_21_1") +include("libraries:packets:implementations:1_21_3") +include("libraries:packets:implementations:1_21_4") +include("libraries:packets:test_plugin") \ No newline at end of file