diff --git a/README.md b/README.md index 1b1108e..7447a25 100644 --- a/README.md +++ b/README.md @@ -619,3 +619,45 @@ Java: ```java UtilitiesOG.logToConsole("[MyPlugin-OG]", "&6This is a &*message with color codes!"); ``` + +**[ItemStack[]] getInventoryData(UUID uuid)** + +Gets the inventory contents of an offline player using the TrueOG Purpur player data API. + +Kotlin: +```kotlin +val inventoryData = UtilitiesOG.getInventoryData(uuid) +``` + +Java: +```java +ItemStack[] inventoryData = UtilitiesOG.getInventoryData(uuid); +``` + +**[void] setInventoryData(UUID uuid, ItemStack[] items)** + +Sets the inventory contents of an offline player using the TrueOG Purpur player data API. + +Kotlin: +```kotlin +UtilitiesOG.setInventoryData(uuid, inventoryData) +``` + +Java: +```java +UtilitiesOG.setInventoryData(uuid, inventoryData); +``` + +**[int] getHeldItemSlot(UUID uuid)** + +Gets the held item slot of an offline player using the TrueOG Purpur player data API. + +Kotlin: +```kotlin +val heldItemSlot = UtilitiesOG.getHeldItemSlot(uuid) +``` + +Java: +```java +int heldItemSlot = UtilitiesOG.getHeldItemSlot(uuid); +``` diff --git a/build.gradle.kts b/build.gradle.kts index cdcaee3..8cd6baa 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,6 +6,7 @@ plugins { id("java-library") // Import Java Library plugin. id("com.diffplug.spotless") version "8.1.0" // Import Spotless plugin. id("com.gradleup.shadow") version "8.3.9" // Import Shadow plugin. + id("io.papermc.paperweight.userdev") version "2.0.0-beta.17" id("checkstyle") // Import Checkstyle plugin. eclipse // Import Eclipse plugin. kotlin("jvm") version "2.1.21" // Import Kotlin JVM plugin. @@ -49,14 +50,13 @@ repositories { /* ---------------------- Java project deps ---------------------------- */ dependencies { - compileOnly("org.purpurmc.purpur:purpur-api:1.19.4-R0.1-SNAPSHOT") // Declare Purpur API version to be packaged. + paperweightDevelopmentBundle("org.purpurmc.purpur:dev-bundle:1.19.4-R0.1-SNAPSHOT") compileOnly("com.sk89q.worldguard:worldguard-bukkit:7.0.8") // Import WorldGuard API. compileOnly("de.tr7zw:item-nbt-api-plugin:2.14.1") // Import NBT API. compileOnly("io.github.miniplaceholders:miniplaceholders-api:2.2.3") // Import MiniPlaceholders API. compileOnly("net.luckperms:api:5.4") // Import LuckPerms API. implementation(kotlin("stdlib")) // Import and package Kotlin standard library. implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") // Import and package Kotlin async library. - implementation("com.github.jasync-sql:jasync-postgresql:2.2.4") // Import and package Jasync API. testImplementation("org.junit.jupiter:junit-jupiter:5.13.3") // Add JUnit API to testing environment. testImplementation("org.mockito:mockito-core:5.18.0") // Add Mockito API to testing environment. testImplementation("com.github.seeseemelk:MockBukkit-v1.19:2.29.0") // Add MockBukkit API to testing environment. @@ -79,6 +79,8 @@ tasks.shadowJar { tasks.jar { archiveClassifier.set("part") } // Applies to root jarfile only. +tasks.assemble { dependsOn(tasks.reobfJar) } + tasks.build { dependsOn(tasks.spotlessApply, tasks.shadowJar) } // Build depends on spotless and shadow. /* --------------------------- Javac opts ------------------------------- */ diff --git a/src/main/java/net/trueog/utilitiesog/FastOfflinePlayer.java b/src/main/java/net/trueog/utilitiesog/FastOfflinePlayer.java deleted file mode 100644 index 46f41db..0000000 --- a/src/main/java/net/trueog/utilitiesog/FastOfflinePlayer.java +++ /dev/null @@ -1,568 +0,0 @@ -// This is free and unencumbered software released into the public domain. -// Authors: NotAlexNoyle. -package net.trueog.utilitiesog; - -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; - -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.OfflinePlayer; -import org.bukkit.Statistic; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import com.destroystokyo.paper.profile.PlayerProfile; -import com.github.jasync.sql.db.Connection; - -// FAST async Postgres and cache based re-implementation of Bukkit's OfflinePlayer. -// Designed for 1.19.4, OfflinePlayer behavior may be different on other versions. -// TODO: Add in-game integration testing. -public class FastOfflinePlayer implements OfflinePlayer { - - // Container for player UUID (always available). - private final UUID uuid; - // Container for player username (requires lookup). - private String cachedName; - // Container for safe concurrent updates to async futures. - private final AtomicReference> lookup = new AtomicReference<>(); - // Fake Postgres connection without using UtilitiesOG.java. - private static volatile Supplier connectionSupplier; - - // Test-only supplier hook. - static void overrideConnectionSupplierForTests(Supplier supplier) { - - // Enable the test supplier. - connectionSupplier = supplier; - - } - - // Supply a connection to jasync Postgres. - private Connection connection() { - - // If a test supplier is available, do this... - final Supplier s = connectionSupplier; - return s != null ? s.get() : Internal.getPostgres(); - - } - - // Runs after each individual unit test. - static void resetConnectionSupplierForTests() { - - // Reset the jasync Postgres connection supplier. - connectionSupplier = null; - - } - - // Class constructor. - public FastOfflinePlayer(UUID inputtedUUID) { - - // Cache the provided UUID. - this.uuid = inputtedUUID; - - } - - // Internal constructor for deserialization. - private FastOfflinePlayer(@NotNull UUID uuid, @Nullable String username) { - - // Pass the provided UUID. - this.uuid = uuid; - // Pass the username (will be set to 'usernameLookupError' if not found). - this.cachedName = username; - - } - - @Override - public boolean isOp() { - - // TODO Auto-generated method stub. - return false; - - } - - @Override - public void setOp(boolean value) { - - // TODO Auto-generated method stub. - - } - - // Fast re-implementation of OfflinePlayer.serialize() from Bukkit API. - @Override - public @NotNull Map serialize() { - - // Construct a map to hold player data. - final Map map = new LinkedHashMap<>(); - - // Add the player data to the map. - map.put("name", getName()); - - // Return the combined serialized object. - return map; - - } - - // Fast re-implementation of OfflinePlayer.deserialize() from Bukkit API. - public static @NotNull FastOfflinePlayer deserialize(@NotNull UUID uuid, @NotNull Map values) { - - // Extract the player name from the serialized map as a String. - final String name = (String) values.get("name"); - - // Return the UUID from cache. - return new FastOfflinePlayer(uuid, name); - - } - - @Override - public boolean isOnline() { - - // TODO Auto-generated method stub. - return false; - - } - - // Fast re-implementation of OfflinePlayer.getName() from Bukkit API. - @Override - public @Nullable String getName() { - - // If the name is already in the cache, do this... - if (cachedName != null) { - - // Return the name from cache instead of querying the database. - return cachedName; - - } - - // Get existing lookup future if present. - CompletableFuture future = lookup.get(); - - // If there is no existing lookup future, do this... - if (future == null) { - - // Create new jasync Postgres query for username. - CompletableFuture created = connection() - .sendPreparedStatement("SELECT username FROM offline_players WHERE uuid = $1 LIMIT 1;", - Collections.singletonList(uuid)) - // On success, cache and return username. - .thenApply(res -> - { - - // If the query returned rows, do this... - if (!res.getRows().isEmpty()) { - - // Extract the "username" value from the first row. - final String name = res.getRows().get(0).getString("username"); - - // Store the retrieved username in the cache. - cachedName = name; - - // Return the retrieved username. - return name; - - } - - // If no rows are found, return null. - return null; - - }) - - // If there was an error with finding the name, do this... - .exceptionally(ex -> - { - - // Set a placeholder name to avoid a crash. - cachedName = "usernameLookupError"; - - UtilitiesOG.logToConsole(Internal.getPrefix(), - "ERROR: FastOfflinePlayer getName() failed!" + ex.getMessage()); - - // Return the placeholder name. - return cachedName; - - }); - - // Attempt to set the lookup future only if it is currently null. - if (!lookup.compareAndSet(null, created)) { - - // If another thread already set the future, use the existing one instead. - created = lookup.get(); - - } - - // Store created future. - future = created; - - } - - // Return cached name (may still be null until async query completes). - return cachedName; - - } - - // Non-cached version of getName() for testing - public @NotNull CompletableFuture<@Nullable String> getNameAsync() { - - // If cached name exists, return it as completed future - if (cachedName != null) { - - return CompletableFuture.completedFuture(cachedName); - - } - - // Ensure lookup starts by calling getName() - getName(); - - // Return lookup future - return lookup.get(); - - } - - // Fast re-implementation OfflinePlayer.getUniqueId() from Bukkit API. - @Override - public @NotNull UUID getUniqueId() { - - // Pass on UUID from constructor. - return uuid; - - } - - @Override - public @NotNull PlayerProfile getPlayerProfile() { - - // TODO Auto-generated method stub. - return null; - - } - - @Override - public boolean isBanned() { - - // TODO Auto-generated method stub. - return false; - - } - - @Override - public boolean isWhitelisted() { - - // TODO Auto-generated method stub. - return false; - - } - - @Override - public void setWhitelisted(boolean value) { - // TODO Auto-generated method stub. - - } - - @Override - public @Nullable Player getPlayer() { - - // TODO Auto-generated method stub. - return null; - - } - - @Override - public long getFirstPlayed() { - - // TODO Auto-generated method stub. - return 0; - - } - - /** - * @deprecated The getLastPlayed API contract is ambiguous and the - * implementation may or may not return the correct value. Use - * {@link #getLastSeen()} instead. - */ - @Deprecated - @Override - public long getLastPlayed() { - - // TODO Auto-generated method stub. - return getLastSeen(); - - } - - @Override - public boolean hasPlayedBefore() { - - // TODO Auto-generated method stub. - return false; - - } - - @Override - public @Nullable Location getBedSpawnLocation() { - - // TODO Auto-generated method stub. - return null; - - } - - @Override - public long getLastLogin() { - - // TODO Auto-generated method stub. - return 0; - - } - - @Override - public long getLastSeen() { - - // TODO Auto-generated method stub. - return 0; - - } - - @Override - public void incrementStatistic(@NotNull Statistic statistic) throws IllegalArgumentException { - // TODO Auto-generated method stub. - - } - - @Override - public void decrementStatistic(@NotNull Statistic statistic) throws IllegalArgumentException { - // TODO Auto-generated method stub. - - } - - @Override - public void incrementStatistic(@NotNull Statistic statistic, int amount) throws IllegalArgumentException { - // TODO Auto-generated method stub. - - } - - @Override - public void decrementStatistic(@NotNull Statistic statistic, int amount) throws IllegalArgumentException { - // TODO Auto-generated method stub. - - } - - @Override - public void setStatistic(@NotNull Statistic statistic, int newValue) throws IllegalArgumentException { - // TODO Auto-generated method stub. - - } - - @Override - public int getStatistic(@NotNull Statistic statistic) throws IllegalArgumentException { - - // TODO Auto-generated method stub. - return 0; - - } - - @Override - public void incrementStatistic(@NotNull Statistic statistic, @NotNull Material material) - throws IllegalArgumentException - { - // TODO Auto-generated method stub. - - } - - @Override - public void decrementStatistic(@NotNull Statistic statistic, @NotNull Material material) - throws IllegalArgumentException - { - // TODO Auto-generated method stub. - - } - - @Override - public int getStatistic(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException { - - // TODO Auto-generated method stub. - return 0; - - } - - @Override - public void incrementStatistic(@NotNull Statistic statistic, @NotNull Material material, int amount) - throws IllegalArgumentException - { - // TODO Auto-generated method stub. - - } - - @Override - public void decrementStatistic(@NotNull Statistic statistic, @NotNull Material material, int amount) - throws IllegalArgumentException - { - // TODO Auto-generated method stub. - - } - - @Override - public void setStatistic(@NotNull Statistic statistic, @NotNull Material material, int newValue) - throws IllegalArgumentException - { - // TODO Auto-generated method stub. - - } - - @Override - public void incrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) - throws IllegalArgumentException - { - // TODO Auto-generated method stub. - - } - - @Override - public void decrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) - throws IllegalArgumentException - { - // TODO Auto-generated method stub. - - } - - @Override - public int getStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) - throws IllegalArgumentException - { - - // TODO Auto-generated method stub. - return 0; - - } - - @Override - public void incrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int amount) - throws IllegalArgumentException - { - // TODO Auto-generated method stub. - - } - - @Override - public void decrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int amount) { - // TODO Auto-generated method stub. - - } - - @Override - public void setStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int newValue) { - // TODO Auto-generated method stub. - - } - - @Override - public @Nullable Location getLastDeathLocation() { - - // TODO Auto-generated method stub. - return null; - - } - - @Override - public boolean getAllowFlight() { - - // TODO Auto-generated method stub. - return false; - - } - - @Override - public void setAllowFlight(boolean flight) { - - // TODO Auto-generated method stub. - - } - - @Override - public boolean isFlying() { - - // TODO Auto-generated method stub. - return false; - - } - - @Override - public void setFlying(boolean value) { - // TODO Auto-generated method stub. - - } - - @Override - public void setFlySpeed(float value) throws IllegalArgumentException { - // TODO Auto-generated method stub. - - } - - @Override - public void setWalkSpeed(float value) throws IllegalArgumentException { - // TODO Auto-generated method stub. - - } - - @Override - public float getFlySpeed() { - - // TODO Auto-generated method stub. - return 0; - - } - - @Override - public float getWalkSpeed() { - - // TODO Auto-generated method stub. - return 0; - - } - - @Override - public @Nullable Location getLocation() { - - // TODO Auto-generated method stub. - return null; - - } - - @Override - public boolean teleportOffline(@NotNull Location destination) { - - // TODO Auto-generated method stub. - return false; - - } - - @Override - public boolean teleportOffline(@NotNull Location destination, @NotNull TeleportCause cause) { - - // TODO Auto-generated method stub. - return false; - - } - - @Override - public @NotNull CompletableFuture teleportOfflineAsync(@NotNull Location destination) { - - // TODO Auto-generated method stub. - return null; - - } - - @Override - public @NotNull CompletableFuture teleportOfflineAsync(@NotNull Location destination, - @NotNull TeleportCause cause) - { - - // TODO Auto-generated method stub. - return null; - - } - -} \ No newline at end of file diff --git a/src/main/java/net/trueog/utilitiesog/Internal.java b/src/main/java/net/trueog/utilitiesog/Internal.java index 6bbf444..56ed146 100644 --- a/src/main/java/net/trueog/utilitiesog/Internal.java +++ b/src/main/java/net/trueog/utilitiesog/Internal.java @@ -2,9 +2,6 @@ // Authors: NotAlexNoyle. package net.trueog.utilitiesog; -import com.github.jasync.sql.db.pool.ConnectionPool; -import com.github.jasync.sql.db.postgresql.PostgreSQLConnection; - // Internal-only accessors for states owned by the main UtilitiesOG class. public final class Internal { @@ -26,13 +23,6 @@ public final class Internal { } - // Shared jasync Postgres connection pool. - public static ConnectionPool getPostgres() { - - return UtilitiesOG.getPluginPostgres(); - - } - // Three-argument functional interface used for the relational-with-args // placeholder registration API. @FunctionalInterface diff --git a/src/main/java/net/trueog/utilitiesog/UtilitiesOG.java b/src/main/java/net/trueog/utilitiesog/UtilitiesOG.java index 2916c64..8ac9ae2 100644 --- a/src/main/java/net/trueog/utilitiesog/UtilitiesOG.java +++ b/src/main/java/net/trueog/utilitiesog/UtilitiesOG.java @@ -9,14 +9,16 @@ import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +import net.trueog.utilitiesog.utils.PlayerDataUtils; import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; -import com.github.jasync.sql.db.pool.ConnectionPool; -import com.github.jasync.sql.db.postgresql.PostgreSQLConnection; -import com.github.jasync.sql.db.postgresql.PostgreSQLConnectionBuilder; - import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.luckperms.api.LuckPerms; @@ -50,6 +52,8 @@ import net.trueog.utilitiesog.modules.RandomDropsModule; import net.trueog.utilitiesog.utils.MessageFormat; import net.trueog.utilitiesog.utils.PlaceholderUtils; import net.trueog.utilitiesog.utils.TextUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; // Declare main plugin class. public final class UtilitiesOG extends JavaPlugin { @@ -61,8 +65,7 @@ public final class UtilitiesOG extends JavaPlugin { // Declare live plugin instance to be initialized in onEnable(). private static UtilitiesOG instance; - // Declare live PostgreSQL connection pool to be initialized in onEnable(). - private static ConnectionPool POOL; + private static PlayerDataUtils playerDataUtils; // Package-private hooks consumed by net.trueog.utilitiesog.Internal. static UtilitiesOG getPluginInstance() { @@ -77,17 +80,13 @@ public final class UtilitiesOG extends JavaPlugin { } - static ConnectionPool getPluginPostgres() { - - return POOL; - - } - @Override public void onEnable() { instance = this; + playerDataUtils = new PlayerDataUtils(); + final File configFile = new File(this.getDataFolder(), "config.yml"); if (!configFile.exists()) { @@ -95,8 +94,6 @@ public final class UtilitiesOG extends JavaPlugin { } - POOL = initPsql(); - if (this.getConfig().getBoolean("ChainArmor")) { ChainArmorModule.Enable(); @@ -500,19 +497,62 @@ public final class UtilitiesOG extends JavaPlugin { } - // Initialize postgres connection; - private ConnectionPool initPsql() { + public static @Nullable ItemStack @NotNull [] getInventoryData(UUID uuid) { - // Reads the values from config.yml; - final String baseUrl = getConfig().getString("postgresUrl"); // e.g. jdbc:postgresql://localhost:5432/diamond - final String user = getConfig().getString("postgresUser"); // e.g. postgres - final String password = getConfig().getString("postgresPassword"); // e.g. postgresPassword + final ListTag inventoryData = playerDataUtils.getPlayerData(uuid).getList("Inventory", Tag.TAG_COMPOUND); + final ItemStack[] inventoryContents = new ItemStack[41]; + for (int i = 0; i < inventoryData.size(); i++) { - // Appends credentials to the URL (only if they are not already present) - final String jdbcUrl = "%s?user=%s&password=%s".formatted(baseUrl, user, password); + final CompoundTag slotTag = inventoryData.getCompound(i); + final int slot = slotTag.getByte("Slot") & 0xFF; + final String id = slotTag.getString("id"); + final int count = slotTag.getByte("Count") & 0xFF; + final Material material = Material.matchMaterial(id); + if (material == null) { - // Create postgres connection. - return PostgreSQLConnectionBuilder.createConnectionPool(jdbcUrl); + continue; + + } + + if (slot < inventoryContents.length) { + + inventoryContents[slot] = new ItemStack(material, count); + + } + + } + + return inventoryContents; + + } + + public static void setInventoryData(UUID uuid, @Nullable ItemStack @NotNull [] items) { + + final ListTag inventoryData = new ListTag(); + for (int i = 0; i < items.length; i++) { + + final ItemStack item = items[i]; + if (item == null) { + + continue; + + } + + final CompoundTag slotTag = new CompoundTag(); + slotTag.putByte("Slot", (byte) i); + slotTag.putString("id", item.getType().getKey().toString()); + slotTag.putByte("Count", (byte) item.getAmount()); + inventoryData.add(slotTag); + + } + + playerDataUtils.setInventoryData(uuid, inventoryData); + + } + + public static int getHeldItemSlot(UUID uuid) { + + return playerDataUtils.getPlayerData(uuid).getInt("SelectedItemSlot"); } diff --git a/src/main/java/net/trueog/utilitiesog/listeners/FastOfflinePlayerListener.java b/src/main/java/net/trueog/utilitiesog/listeners/FastOfflinePlayerListener.java deleted file mode 100644 index e2022bb..0000000 --- a/src/main/java/net/trueog/utilitiesog/listeners/FastOfflinePlayerListener.java +++ /dev/null @@ -1,57 +0,0 @@ -// This is free and unencumbered software released into the public domain. -// Authors: christianniehaus, NotAlexNoyle. -package net.trueog.utilitiesog.listeners; - -import java.util.Arrays; -import java.util.concurrent.CompletableFuture; - -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.player.PlayerJoinEvent; - -import com.github.jasync.sql.db.QueryResult; - -import net.trueog.utilitiesog.Internal; -import net.trueog.utilitiesog.UtilitiesOG; - -// A class for storing and caching FastOfflinePlayers during player join and player leave. -public class FastOfflinePlayerListener { - - // When a player joins the server, do this... - @EventHandler(priority = EventPriority.NORMAL) - public void onPlayerJoin(PlayerJoinEvent event) { - - // Get the player from the event. - final Player player = event.getPlayer(); - - // Add or update player data in the offline player database. - upsertOrUpdatePlayer(player); - - // Inform the player of the result. - UtilitiesOG.trueogMessage(player, "Your data is cached!"); - - } - - // Insert new user or update user metadata in one query depending on need. - private CompletableFuture upsertOrUpdatePlayer(Player player) { - - // Direct postgres query for inserting and/or updating a player. - final String sql = "INSERT INTO offline_players (uuid, username) " + "VALUES ($1, $2) " - + "ON CONFLICT (uuid) DO UPDATE " + " SET username = EXCLUDED.username, " - + " last_seen = NOW();"; - - // Add player UUID and player name key-value pair to database. - return query(sql, player.getUniqueId(), player.getName()); - - } - - // Send an async query using jasync Postgres. - private CompletableFuture query(String sql, Object... params) { - - // Gift wrap around jasync’s sendPreparedStatement. - return Internal.getPostgres().sendPreparedStatement(sql, Arrays.asList(params)); - - } - -} \ No newline at end of file diff --git a/src/main/java/net/trueog/utilitiesog/utils/PlayerDataUtils.java b/src/main/java/net/trueog/utilitiesog/utils/PlayerDataUtils.java new file mode 100644 index 0000000..0b74ddf --- /dev/null +++ b/src/main/java/net/trueog/utilitiesog/utils/PlayerDataUtils.java @@ -0,0 +1,84 @@ +package net.trueog.utilitiesog.utils; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.world.level.storage.PlayerDataStorage; +import net.trueog.utilitiesog.Internal; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_19_R3.CraftServer; + +import java.lang.reflect.Method; +import java.util.UUID; + +public class PlayerDataUtils { + + private final PlayerDataStorage playerIo = ((CraftServer) Bukkit.getServer()).getServer().getPlayerList().playerIo; + private Method getPlayerData; + private Method saveCompoundTag; + + public PlayerDataUtils() { + + try { + + getPlayerData = playerIo.getClass().getDeclaredMethod("getPlayerData", UUID.class); + saveCompoundTag = playerIo.getClass().getDeclaredMethod("saveCompoundTag", UUID.class, CompoundTag.class); + + } catch (NoSuchMethodException e) { + + getPlayerData = null; + saveCompoundTag = null; + Internal.getPlugin().getLogger().warning("Not running TrueOG Purpur jar, player data APIs are unavailable"); + + } + + } + + public CompoundTag getPlayerData(UUID uuid) { + + if (getPlayerData == null) { + + throw new UnsupportedOperationException("Not running TrueOG Purpur jar, player data APIs are unavailable"); + + } + + try { + + return (CompoundTag) getPlayerData.invoke(playerIo, uuid); + + } catch (Exception e) { + + throw new RuntimeException(e); + + } + + } + + private void saveCompoundTag(UUID uuid, CompoundTag tag) { + + if (saveCompoundTag == null) { + + throw new UnsupportedOperationException("Not running TrueOG Purpur jar, player data APIs are unavailable"); + + } + + try { + + saveCompoundTag.invoke(playerIo, uuid, tag); + + } catch (Exception e) { + + throw new RuntimeException(e); + + } + + } + + public void setInventoryData(UUID uuid, ListTag inventoryData) { + + final CompoundTag playerData = getPlayerData(uuid); + playerData.put("Inventory", inventoryData); + saveCompoundTag(uuid, playerData); + + } + +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 275a6a8..9fc9f77 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -26,11 +26,4 @@ RanksMenu: true # Should advancements be restricted to survival mode? Default: true. AdvancementsOnlyInSurvival: true # Registers stub redirect/denial commands (/wild, /rtp, /seed, /f, /faction, /guild, /kit). Default: true. -MissingCommands: true -# PostgreSQL Setup for FastOfflinePlayer database. -postgresUrl: jdbc:postgresql://localhost:5432/fastofflineplayer -postgresUser: ^{POSTGRES_USER} -postgresPassword: ^{POSTGRES_PASSWORD} -postgresTable: fastofflineplayer -postgresLogTable: fastofflineplayer_log -postgresLogLimit: 500 +MissingCommands: true \ No newline at end of file diff --git a/src/test/java/net/trueog/utilitiesog/FastOfflinePlayerTest.java b/src/test/java/net/trueog/utilitiesog/FastOfflinePlayerTest.java deleted file mode 100644 index 67c68c3..0000000 --- a/src/test/java/net/trueog/utilitiesog/FastOfflinePlayerTest.java +++ /dev/null @@ -1,498 +0,0 @@ -// This is free and unencumbered software released into the public domain. -// Authors: NotAlexNoyle. -package net.trueog.utilitiesog; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import com.github.jasync.sql.db.QueryResult; -import com.github.jasync.sql.db.ResultSet; -import com.github.jasync.sql.db.RowData; -import com.github.jasync.sql.db.pool.ConnectionPool; - -import be.seeseemelk.mockbukkit.MockBukkit; -import be.seeseemelk.mockbukkit.ServerMock; - -// Test cases for FastOfflinePlayer.java -class FastOfflinePlayerTest { - - // Declare a test UUID. - final UUID testUUID = UUID.randomUUID(); - // Declare a test username. - final String testName = "NotAlexNoyle"; - // Declare a mock server container using MockBukkit. - private static ServerMock server; - - // Runs before all tests. - @BeforeAll - static void setUp() { - - // Initialize the MockBukkit server. - server = MockBukkit.mock(); - - } - - // Runs after each test. - @AfterEach - void resetDb() { - - // Ensure each test gets a clean supplier. - FastOfflinePlayer.resetConnectionSupplierForTests(); - - } - - // Runs after all tests. - @AfterAll - static void tearDown() { - - // Bring down MockBukkit server. - MockBukkit.unmock(); - - } - - // TODO: Uncomment and finish - /* - * @Test void testIsOp() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testSetOp() { - * - * fail("Not yet implemented"); - * - * } - */ - - // FastOfflinePlayer.serialize() test. - @Test - void testSerialize() { - - // Declare a test player. - final FastOfflinePlayer testSerializePlayer = FastOfflinePlayer.deserialize(testUUID, Map.of("name", testName)); - - // JUnit: Compare the expected map to the FastOfflinePlayer map. - assertEquals(Map.of("name", testName), testSerializePlayer.serialize()); - - } - - // FastOfflinePlayer.deserialize() test. - @Test - void testDeserialize() { - - // Compose a deserialization control group map. - final Map yaml = Map.of("name", testName); - - // Declare a test player. - final FastOfflinePlayer testDeserializePlayer = FastOfflinePlayer.deserialize(testUUID, yaml); - - // JUnit: Compare the expected UUID to the FastOfflinePlayer UUID. - assertEquals(testUUID, testDeserializePlayer.getUniqueId()); - // JUnit: Compare the expected name to the FastOfflinePlayer name. - assertEquals(testName, testDeserializePlayer.getName()); - - } - - // TODO: Uncomment and finish - /* - * @Test void testIsOnline() { - * - * fail("Not yet implemented"); - * - * } - */ - - // FastOfflinePlayer.getName() test. - @Test - void testGetName() { - - // Declare a jasync Postgres connection pool. - final ConnectionPool pool = mockPoolReturning( - mockQueryResult(mockResultSet(mockRow(Map.of("username", testName))))); - installMockDb(pool); - - // Declare a test player. - final FastOfflinePlayer testGetNamePlayer = new FastOfflinePlayer(UUID.randomUUID()); - - // JUnit: Compare the expected name to the actual name from database. - assertEquals(testName, testGetNamePlayer.getNameAsync().join()); - // JUnit: Compare the expected name to the actual name from cache. - assertEquals(testName, testGetNamePlayer.getName()); - - // Verify that multiple queries are not overlapping. - verify(pool, times(1)).sendPreparedStatement(anyString(), anyList()); - - } - - // FastOfflinePlayer.getUniqueID() test. - @Test - void testGetUniqueId() { - - // Declare a test player. - final FastOfflinePlayer uuidTestPlayer = new FastOfflinePlayer(testUUID); - - // JUnit: Compare the expected UUID to the FastOfflinePlayer UUID. - assertEquals(testUUID, uuidTestPlayer.getUniqueId()); - - } - - // TODO: Uncomment and finish - /* - * @Test void testGetPlayerProfile() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testIsBanned() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testIsWhitelisted() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testSetWhitelisted() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testGetPlayer() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testGetFirstPlayed() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testGetLastPlayed() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testHasPlayedBefore() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testGetBedSpawnLocation() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testGetLastLogin() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testGetLastSeen() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testIncrementStatisticStatistic() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testDecrementStatisticStatistic() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testIncrementStatisticStatisticInt() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testDecrementStatisticStatisticInt() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testSetStatisticStatisticInt() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testGetStatisticStatistic() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testIncrementStatisticStatisticMaterial() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testDecrementStatisticStatisticMaterial() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testGetStatisticStatisticMaterial() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testIncrementStatisticStatisticMaterialInt() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testDecrementStatisticStatisticMaterialInt() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testSetStatisticStatisticMaterialInt() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testIncrementStatisticStatisticEntityType() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testDecrementStatisticStatisticEntityType() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testGetStatisticStatisticEntityType() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testIncrementStatisticStatisticEntityTypeInt() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testDecrementStatisticStatisticEntityTypeInt() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testSetStatisticStatisticEntityTypeInt() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testGetLastDeathLocation() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testGetAllowFlight() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testSetAllowFlight() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testIsFlying() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testSetFlying() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testSetFlySpeed() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testSetWalkSpeed() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testGetFlySpeed() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testGetWalkSpeed() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testGetLocation() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testTeleportOfflineLocation() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testTeleportOfflineLocationTeleportCause() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testTeleportOfflineAsyncLocation() { - * - * fail("Not yet implemented"); - * - * } - * - * @Test void testTeleportOfflineAsyncLocationTeleportCause() { - * - * fail("Not yet implemented"); - * - * } - */ - - // Declare a fake jasync Postgres row for tests to use. - private static RowData mockRow(Map cols) { - - // Declare a Mockito mock for a single jasync Postgres row. - final RowData row = mock(RowData.class); - - // When getString(columnName) is called, look up the value from the map. - when(row.getString(anyString())).thenAnswer(inv -> { - - // If absent, return null; otherwise, coerce to String to match the API. - final Object v = cols.get(inv.getArgument(0)); - return v == null ? null : String.valueOf(v); - - }); - - // Return the configured mock row. - return row; - - } - - // Declare a fake jasync Postgres result set for tests to use. - private static ResultSet mockResultSet(RowData... rowArray) { - - // Create a Mockito mock for a jasync Postgres ResultSet. - final ResultSet rs = mock(ResultSet.class); - - // Mock a result set. isEmpty() should reflect whether any rows have been passed - // in. - when(rs.isEmpty()).thenReturn(rowArray.length == 0); - - // Loop that runs once for each result row. - for (int i = 0; i < rowArray.length; i++) { - - // Stub indexed access to return the corresponding RowData from the array. - when(rs.get(i)).thenReturn(rowArray[i]); - - } - - // Return the configured mock result set. - return rs; - - } - - // Declare a fake jasync Postgres query result for tests to use. - private static QueryResult mockQueryResult(ResultSet rows) { - - // Create a Mockito mock for a jasync Postgres QueryResult that wraps a given - // ResultSet. - final QueryResult qr = mock(QueryResult.class); - - // Mock a query result. - when(qr.getRows()).thenReturn(rows); - - // Return the mocked query result. - return qr; - - } - - // Declare a fake jasync Postgres ConnectionPool for tests to use. - private static ConnectionPool mockPoolReturning(QueryResult result) { - - // Create a Mockito mock for a jasync Postgres ConnectionPool. - final ConnectionPool pool = mock(ConnectionPool.class); - - // Mock a ConnectionPool. - when(pool.sendPreparedStatement(anyString(), anyList())).thenReturn(CompletableFuture.completedFuture(result)); - - // Return the mocked mock pool. - return pool; - - } - - // Initialize the mocked jasync Postgres database. - private static void installMockDb(ConnectionPool pool) { - - // Install the mocked pool into FastOfflinePlayer via its test seam so code - // under test uses this pool instead of touching UtilitiesOG. This prevents - // missing class errors due to not all UtilitiesOG.java calls being on the test - // class path. - FastOfflinePlayer.overrideConnectionSupplierForTests(() -> pool); - - } - -} \ No newline at end of file