diff --git a/README.md b/README.md index 1218083..dfae066 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Built for [DemocracyCraft](https://www.democracycraft.net/) and [StateCraft](htt | `database-settings.yml` | MariaDB URL, username, password | | `settings.yml` | Shop icon templates, lore, click command, preview scale | | `item-code-groupings.yml` | Item code aliases mapped to canonical item codes | -| `messages.yml` | Player-facing messages | +| `messages.yml` | Player-facing messages (MiniMessage) | ## Building diff --git a/adapters/geyser-floodgate/build.gradle.kts b/adapters/geyser-floodgate/build.gradle.kts new file mode 100644 index 0000000..33a520c --- /dev/null +++ b/adapters/geyser-floodgate/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("chestshop-database.library-conventions") +} + +repositories { + maven { + name = "opencollab" + url = uri("https://repo.opencollab.dev/main/") + } +} + +dependencies { + compileOnlyApi(libs.geyserApi) + compileOnlyApi(libs.floodgateApi) +} diff --git a/adapters/geyser-floodgate/src/main/java/io/github/md5sha256/chestshopdatabase/adapters/geyserfloodgate/FloodgateBedrockDetector.java b/adapters/geyser-floodgate/src/main/java/io/github/md5sha256/chestshopdatabase/adapters/geyserfloodgate/FloodgateBedrockDetector.java new file mode 100644 index 0000000..954d1e3 --- /dev/null +++ b/adapters/geyser-floodgate/src/main/java/io/github/md5sha256/chestshopdatabase/adapters/geyserfloodgate/FloodgateBedrockDetector.java @@ -0,0 +1,19 @@ +package io.github.md5sha256.chestshopdatabase.adapters.geyserfloodgate; + +import org.geysermc.floodgate.api.FloodgateApi; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +/** + * Bedrock detection via Floodgate (typically when Floodgate is installed on this backend). + */ +public final class FloodgateBedrockDetector { + + private FloodgateBedrockDetector() { + } + + public static boolean isBedrockPlayer(@NotNull UUID uuid) { + return FloodgateApi.getInstance().isFloodgatePlayer(uuid); + } +} diff --git a/adapters/geyser-floodgate/src/main/java/io/github/md5sha256/chestshopdatabase/adapters/geyserfloodgate/GeyserBedrockDetector.java b/adapters/geyser-floodgate/src/main/java/io/github/md5sha256/chestshopdatabase/adapters/geyserfloodgate/GeyserBedrockDetector.java new file mode 100644 index 0000000..61850e7 --- /dev/null +++ b/adapters/geyser-floodgate/src/main/java/io/github/md5sha256/chestshopdatabase/adapters/geyserfloodgate/GeyserBedrockDetector.java @@ -0,0 +1,20 @@ +package io.github.md5sha256.chestshopdatabase.adapters.geyserfloodgate; + +import org.geysermc.geyser.api.GeyserApi; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +/** + * Bedrock detection via Geyser when the Geyser plugin is present on this server. + */ +public final class GeyserBedrockDetector { + + private GeyserBedrockDetector() { + } + + public static boolean isBedrockPlayer(@NotNull UUID uuid) { + var api = GeyserApi.api(); + return api != null && api.isBedrockPlayer(uuid); + } +} diff --git a/chestshop-database-bukkit/build.gradle.kts b/chestshop-database-bukkit/build.gradle.kts index aabe0c5..44bcfb0 100644 --- a/chestshop-database-bukkit/build.gradle.kts +++ b/chestshop-database-bukkit/build.gradle.kts @@ -32,6 +32,9 @@ dependencies { implementation(projects.adapters.worldguard) { isTransitive = false } + implementation(projects.adapters.geyserFloodgate) { + isTransitive = false + } // Libraries implementation("org.mybatis:mybatis:3.5.19") diff --git a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/ChestshopDatabasePlugin.java b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/ChestshopDatabasePlugin.java index d4c4b50..f5f9ed8 100644 --- a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/ChestshopDatabasePlugin.java +++ b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/ChestshopDatabasePlugin.java @@ -2,6 +2,8 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import io.github.md5sha256.chestshopdatabase.adapters.fawe.FAWEHandler; +import io.github.md5sha256.chestshopdatabase.adapters.geyserfloodgate.FloodgateBedrockDetector; +import io.github.md5sha256.chestshopdatabase.adapters.geyserfloodgate.GeyserBedrockDetector; import io.github.md5sha256.chestshopdatabase.adapters.worldedit.WorldEditHandler; import io.github.md5sha256.chestshopdatabase.adapters.worldguard.WorldGuardHandler; import io.github.md5sha256.chestshopdatabase.command.CommandBean; @@ -31,6 +33,7 @@ import org.apache.ibatis.session.SqlSessionFactory; import org.bukkit.Chunk; import org.bukkit.World; +import org.bukkit.entity.Player; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitScheduler; @@ -51,6 +54,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import java.util.function.Predicate; import java.util.function.Supplier; import java.util.logging.Logger; @@ -133,6 +137,7 @@ public void onDisable() { } private void registerCommands(@NotNull SqlSessionFactory sessionFactory) { + PluginManager pluginManager = getServer().getPluginManager(); Supplier sessionSupplier = () -> new DatabaseSession(sessionFactory, MariaChestshopMapper.class, MariaPreferenceMapper.class); FindTaskFactory findTaskFactory = new FindTaskFactory(sessionSupplier, executorState); @@ -142,6 +147,7 @@ private void registerCommands(@NotNull SqlSessionFactory sessionFactory) { executorState, this, this.previewHandler); + Predicate isBedrockPlayer = bedrockPlayerPredicate(pluginManager); var findCommand = new FindCommand(this.shopState, this.discoverer, findTaskFactory, @@ -149,7 +155,9 @@ private void registerCommands(@NotNull SqlSessionFactory sessionFactory) { this, this.previewHandler, sessionSupplier, - this.executorState); + this.executorState, + isBedrockPlayer, + this.messageContainer); List commands = List.of( findCommand, new ResyncCommand(this, resyncTaskFactory), @@ -170,6 +178,19 @@ private void registerCommands(@NotNull SqlSessionFactory sessionFactory) { ); } + private @NotNull Predicate bedrockPlayerPredicate(@NotNull PluginManager pluginManager) { + Predicate predicate = player -> false; + if (pluginManager.isPluginEnabled("floodgate")) { + predicate = predicate.or( + player -> FloodgateBedrockDetector.isBedrockPlayer(player.getUniqueId())); + } + if (pluginManager.isPluginEnabled("Geyser-Spigot")) { + predicate = predicate.or( + player -> GeyserBedrockDetector.isBedrockPlayer(player.getUniqueId())); + } + return predicate; + } + private void registerAdapters() { PluginManager pluginManager = getServer().getPluginManager(); if (pluginManager.isPluginEnabled("WorldEdit")) { @@ -246,7 +267,7 @@ private ConfigurationNode copyDefaultsYaml(@NotNull String resourceName) throws try (FileOutputStream fileOutputStream = new FileOutputStream(file); InputStream inputStream = getResource(fileName)) { if (inputStream == null) { - getLogger().severe("Failed to copy default messages!"); + getLogger().severe("Failed to copy default resource: " + fileName); } else { inputStream.transferTo(fileOutputStream); } @@ -270,15 +291,15 @@ private Settings loadSettings() throws IOException { return settingsRoot.get(Settings.class); } + private ConfigurationNode loadMessages() throws IOException { + return copyDefaultsYaml("messages"); + } + private DatabaseSettings loadDatabaseSettings() throws IOException { ConfigurationNode settingsRoot = copyDefaultsYaml("database-settings"); return settingsRoot.get(DatabaseSettings.class); } - public ConfigurationNode loadMessages() throws IOException { - return copyDefaultsYaml("messages"); - } - public ItemCodeGroupings loadItemCodeGroupings() throws IOException { ConfigurationNode settingsRoot = copyDefaultsYaml("item-code-groupings"); ItemCodeGroupings groupings = settingsRoot.get(ItemCodeGroupings.class); @@ -302,12 +323,13 @@ public CompletableFuture reload() { private CompletableFuture reloadMessagesAndSettings() { CompletableFuture future = new CompletableFuture<>(); getServer().getScheduler().runTaskAsynchronously(this, () -> { - ConfigurationNode messagesNode; Settings settings; + ConfigurationNode messagesNode; ItemCodeGroupings groupings; try { + ConfigurationNode settingsRoot = copyDefaultsYaml("settings"); + settings = settingsRoot.get(Settings.class); messagesNode = loadMessages(); - settings = loadSettings(); groupings = loadItemCodeGroupings(); } catch (IOException ex) { ex.printStackTrace(); diff --git a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/command/FindCommand.java b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/command/FindCommand.java index 514fdff..d5fa1c8 100644 --- a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/command/FindCommand.java +++ b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/command/FindCommand.java @@ -16,6 +16,7 @@ import io.github.md5sha256.chestshopdatabase.gui.dialog.FindDialog; import io.github.md5sha256.chestshopdatabase.model.ChestshopItem; import io.github.md5sha256.chestshopdatabase.preview.PreviewHandler; +import io.github.md5sha256.chestshopdatabase.settings.MessageContainer; import io.github.md5sha256.chestshopdatabase.util.BlockPosition; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; @@ -33,6 +34,7 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; import java.util.function.Supplier; public record FindCommand(@NotNull ChestShopState shopState, @@ -42,7 +44,9 @@ public record FindCommand(@NotNull ChestShopState shopState, @NotNull Plugin plugin, @NotNull PreviewHandler previewHandler, @NotNull Supplier session, - @NotNull ExecutorState executorState) implements CommandBean.Single { + @NotNull ExecutorState executorState, + @NotNull Predicate isBedrockPlayer, + @NotNull MessageContainer messages) implements CommandBean.Single { @Override @@ -276,7 +280,8 @@ private void processCommandWithItem(@NotNull Player player, @NotNull ItemStack i ); findState.setWorld(queryPosition.world()); findState.setQueryPosition(queryPosition); - Dialog dialog = FindDialog.createMainPageDialog(findState, taskFactory, gui, plugin); + Dialog dialog = FindDialog.createMainPageDialog(findState, taskFactory, gui, plugin, + isBedrockPlayer, messages); player.showDialog(dialog); }); } @@ -303,7 +308,8 @@ private void processCommandWithItemCode(@NotNull Player player, @NotNull String ); findState.setWorld(queryPosition.world()); findState.setQueryPosition(queryPosition); - Dialog dialog = FindDialog.createMainPageDialog(findState, taskFactory, gui, plugin); + Dialog dialog = FindDialog.createMainPageDialog(findState, taskFactory, gui, plugin, + isBedrockPlayer, messages); player.showDialog(dialog); }); } diff --git a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/gui/dialog/FindDialog.java b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/gui/dialog/FindDialog.java index 4c1fb1f..4163cb4 100644 --- a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/gui/dialog/FindDialog.java +++ b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/gui/dialog/FindDialog.java @@ -7,6 +7,7 @@ import io.github.md5sha256.chestshopdatabase.model.ChestshopItem; import io.github.md5sha256.chestshopdatabase.model.ShopAttribute; import io.github.md5sha256.chestshopdatabase.model.ShopType; +import io.github.md5sha256.chestshopdatabase.settings.MessageContainer; import io.github.md5sha256.chestshopdatabase.util.DialogUtil; import io.github.md5sha256.chestshopdatabase.util.SortDirection; import io.papermc.paper.datacomponent.DataComponentTypes; @@ -28,9 +29,12 @@ import org.jetbrains.annotations.Nullable; import java.util.List; +import java.util.function.Predicate; public class FindDialog { + private static final String MESSAGE_FIND_QUERYING = "find.querying"; + @NotNull private static DialogBase createMainPageBase(@Nullable ChestshopItem item) { if (item == null) { @@ -70,11 +74,18 @@ private static void submit( @NotNull FindState findState, @NotNull FindTaskFactory taskFactory, @NotNull ShopResultsGUI resultsGUI, - @NotNull Plugin plugin) { - audience.showDialog(waitScreen()); + @NotNull Plugin plugin, + @NotNull Predicate isBedrockPlayer, + @NotNull MessageContainer messages) { if (!(audience instanceof Player player)) { + audience.showDialog(waitScreen()); return; } + if (isBedrockPlayer.test(player)) { + player.sendMessage(messages.messageFor(MESSAGE_FIND_QUERYING)); + } else { + audience.showDialog(waitScreen()); + } taskFactory.findTask(findState).whenComplete((res, ex) -> { audience.closeDialog(); if (ex != null) { @@ -98,10 +109,12 @@ public static Dialog createMainPageDialog( @NotNull FindState findState, @NotNull FindTaskFactory taskFactory, @NotNull ShopResultsGUI resultsGUI, - @NotNull Plugin plugin + @NotNull Plugin plugin, + @NotNull Predicate isBedrockPlayer, + @NotNull MessageContainer messages ) { DialogAction submitAction = DialogAction.customClick((view, audience) -> { - submit(view, audience, findState, taskFactory, resultsGUI, plugin); + submit(view, audience, findState, taskFactory, resultsGUI, plugin, isBedrockPlayer, messages); }, ClickCallback.Options.builder().uses(1).build()); DialogAction buyCheapAction = DialogAction.customClick((view, audience) -> { findState.setShopTypes(List.of(ShopType.BUY, ShopType.BOTH)); @@ -110,7 +123,7 @@ public static Dialog createMainPageDialog( findState.setSortDirection(ShopAttribute.UNIT_BUY_PRICE, SortDirection.ASCENDING); findState.setSortDirection(ShopAttribute.DISTANCE, SortDirection.ASCENDING); findState.setHideEmptyShops(true); - submit(view, audience, findState, taskFactory, resultsGUI, plugin); + submit(view, audience, findState, taskFactory, resultsGUI, plugin, isBedrockPlayer, messages); }, ClickCallback.Options.builder().uses(1).build()); DialogAction buyNearbyAction = DialogAction.customClick((view, audience) -> { findState.setShopTypes(List.of(ShopType.BUY, ShopType.BOTH)); @@ -119,7 +132,7 @@ public static Dialog createMainPageDialog( findState.setSortDirection(ShopAttribute.DISTANCE, SortDirection.ASCENDING); findState.setSortDirection(ShopAttribute.UNIT_BUY_PRICE, SortDirection.ASCENDING); findState.setHideEmptyShops(true); - submit(view, audience, findState, taskFactory, resultsGUI, plugin); + submit(view, audience, findState, taskFactory, resultsGUI, plugin, isBedrockPlayer, messages); }, ClickCallback.Options.builder().uses(1).build()); DialogAction sellBestPriceAction = DialogAction.customClick((view, audience) -> { findState.setShopTypes(List.of(ShopType.SELL, ShopType.BOTH)); @@ -130,7 +143,7 @@ public static Dialog createMainPageDialog( findState.setSortDirection(ShopAttribute.REMAINING_CAPACITY, SortDirection.DESCENDING); findState.setSortDirection(ShopAttribute.DISTANCE, SortDirection.ASCENDING); findState.setHideFullShops(true); - submit(view, audience, findState, taskFactory, resultsGUI, plugin); + submit(view, audience, findState, taskFactory, resultsGUI, plugin, isBedrockPlayer, messages); }, ClickCallback.Options.builder().uses(1).build()); ActionButton submitButton = ActionButton.builder(Component.text("Search", NamedTextColor.GREEN)) @@ -157,12 +170,14 @@ public static Dialog createMainPageDialog( ActionButton.builder(Component.text("Filters")) .action(DialogUtil.openDialogAction(() -> FilterDialog.createFiltersDialog( findState, - () -> createMainPageDialog(findState, taskFactory, resultsGUI, plugin)))) + () -> createMainPageDialog(findState, taskFactory, resultsGUI, plugin, + isBedrockPlayer, messages)))) .build(), ActionButton.builder(Component.text("Sorting")) .action(DialogUtil.openDialogAction(() -> SortDialog.createSortDialog( findState, - () -> createMainPageDialog(findState, taskFactory, resultsGUI, plugin)))) + () -> createMainPageDialog(findState, taskFactory, resultsGUI, plugin, + isBedrockPlayer, messages)))) .build(), submitButton ); diff --git a/chestshop-database-bukkit/src/main/resources/paper-plugin.yml b/chestshop-database-bukkit/src/main/resources/paper-plugin.yml index f401dac..5e94629 100644 --- a/chestshop-database-bukkit/src/main/resources/paper-plugin.yml +++ b/chestshop-database-bukkit/src/main/resources/paper-plugin.yml @@ -21,6 +21,12 @@ dependencies: AsyncWorldEdit: join-classpath: true required: false + floodgate: + join-classpath: true + required: false + Geyser-Spigot: + join-classpath: true + required: false permissions: csdb.find: diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4841647..46dc176 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,6 +6,8 @@ minecraft = "1.21.8-R0.1-SNAPSHOT" worldedit = "7.3.16" worldguard = "7.0.14" fawe = "2.14.0" +geyserApi = "2.9.0-SNAPSHOT" +floodgateApi = "2.2.4-SNAPSHOT" [libraries] # Platform @@ -14,4 +16,6 @@ paper = { group = "io.papermc.paper", name = "paper-api", version.ref = "minecra # Minecraft Plugins worldeditCore = { group = "com.sk89q.worldedit", name = "worldedit-core", version.ref = "worldedit" } fastasyncworldeditCore = { group = "com.fastasyncworldedit", name = "FastAsyncWorldEdit-Core", version.ref = "fawe" } -worldguardBukkit = { group = "com.sk89q.worldguard", name = "worldguard-bukkit", version.ref = "worldguard" } \ No newline at end of file +worldguardBukkit = { group = "com.sk89q.worldguard", name = "worldguard-bukkit", version.ref = "worldguard" } +geyserApi = { group = "org.geysermc.geyser", name = "api", version.ref = "geyserApi" } +floodgateApi = { group = "org.geysermc.floodgate", name = "api", version.ref = "floodgateApi" } \ No newline at end of file diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/settings.gradle.kts b/settings.gradle.kts index 5e89569..d321f76 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,6 +4,7 @@ rootProject.name = "chestshop-database" include(":adapters:worldedit") include(":adapters:fawe") include(":adapters:worldguard") +include(":adapters:geyser-floodgate") // Core include(":core")