diff --git a/build.gradle.kts b/build.gradle.kts index e15499c..e67401c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -108,6 +108,7 @@ project(":duels-plugin") { compileOnly("org.spigotmc:spigot-api:1.14.4-R0.1-SNAPSHOT") compileOnly("com.mojang:authlib:1.5.21") compileOnly("me.clip:placeholderapi:2.11.6") + compileOnly("net.luckperms:api:5.4") compileOnly("com.SirBlobman.combatlogx:CombatLogX-API:10.0.0.0-SNAPSHOT") compileOnly("net.essentialsx:EssentialsX:2.19.2") { diff --git a/duels-plugin/src/main/java/me/realized/duels/command/commands/duel/DuelCommand.java b/duels-plugin/src/main/java/me/realized/duels/command/commands/duel/DuelCommand.java index 48cc32f..2316088 100644 --- a/duels-plugin/src/main/java/me/realized/duels/command/commands/duel/DuelCommand.java +++ b/duels-plugin/src/main/java/me/realized/duels/command/commands/duel/DuelCommand.java @@ -185,6 +185,13 @@ public class DuelCommand extends BaseCommand { return true; } + // Reject a bet the opponent cannot cover so the match is not doomed to fail on start. + if (!diamondBank.has(amount, target)) { + lang.sendMessage(sender, "ERROR.command.opponent-not-enough-money", + "name", target.getName(), "max", diamondBank.getDiamonds(target), "bet_amount", amount); + return true; + } + settings.setBet(amount); } diff --git a/duels-plugin/src/main/java/me/realized/duels/gui/settings/SettingsGui.java b/duels-plugin/src/main/java/me/realized/duels/gui/settings/SettingsGui.java index fd31c79..53f3916 100644 --- a/duels-plugin/src/main/java/me/realized/duels/gui/settings/SettingsGui.java +++ b/duels-plugin/src/main/java/me/realized/duels/gui/settings/SettingsGui.java @@ -15,6 +15,7 @@ import me.realized.duels.gui.settings.buttons.MirrorTheirInventoryButton; import me.realized.duels.gui.settings.buttons.OwnInventoryButton; import me.realized.duels.gui.settings.buttons.RequestDetailsButton; import me.realized.duels.gui.settings.buttons.RequestSendButton; +import me.realized.duels.gui.settings.buttons.ShuffleAllButton; import me.realized.duels.util.compat.Items; import me.realized.duels.util.gui.SinglePageGui; import me.realized.duels.util.inventory.Slots; @@ -22,16 +23,16 @@ import org.bukkit.inventory.ItemStack; public class SettingsGui extends SinglePageGui { - // Option buttons are centered in the middle row (slots 9-16). Center is slot 13. + // Option buttons flank the middle row around the center slot 13, which is reserved for Shuffle All. // The bottom row is reserved for SEND / CANCEL, so options never collide with them. private static final int[][] PATTERNS = { - {13}, + {12}, {12, 14}, - {12, 13, 14}, + {11, 12, 14}, {11, 12, 14, 15}, - {11, 12, 13, 14, 15}, + {10, 11, 12, 14, 15}, {10, 11, 12, 14, 15, 16}, - {10, 11, 12, 13, 14, 15, 16} + {9, 10, 11, 12, 14, 15, 16} }; public SettingsGui(final DuelsPlugin plugin) { @@ -71,7 +72,7 @@ public class SettingsGui extends SinglePageGui { buttons.add(new ItemBettingButton(plugin)); } - // Middle row: option buttons, centered. PATTERNS supports up to 7 options (the max possible). + // Middle row: option buttons flanking the center. PATTERNS supports up to 7 options (the max possible). if (!buttons.isEmpty()) { final int[] pattern = PATTERNS[buttons.size() - 1]; @@ -80,6 +81,9 @@ public class SettingsGui extends SinglePageGui { } } + // Center of the window: Shuffle All (randomizes every option except the Diamond bet). + set(13, new ShuffleAllButton(plugin)); + // Bottom row: SEND on the left half, CANCEL on the right half, divider in the center. set(18, 22, 1, new RequestSendButton(plugin)); set(23, 27, 1, new CancelButton(plugin)); diff --git a/duels-plugin/src/main/java/me/realized/duels/gui/settings/buttons/MirrorMyInventoryButton.java b/duels-plugin/src/main/java/me/realized/duels/gui/settings/buttons/MirrorMyInventoryButton.java index ddedaf5..f9d77a8 100644 --- a/duels-plugin/src/main/java/me/realized/duels/gui/settings/buttons/MirrorMyInventoryButton.java +++ b/duels-plugin/src/main/java/me/realized/duels/gui/settings/buttons/MirrorMyInventoryButton.java @@ -3,6 +3,7 @@ package me.realized.duels.gui.settings.buttons; import me.realized.duels.DuelsPlugin; import me.realized.duels.Permissions; import me.realized.duels.gui.BaseButton; +import me.realized.duels.hook.hooks.LuckPermsHook; import me.realized.duels.setting.Settings; import me.realized.duels.util.compat.Items; import me.realized.duels.util.inventory.ItemBuilder; @@ -21,12 +22,15 @@ public class MirrorMyInventoryButton extends BaseButton { return; } - // Display the request sender's (challenger's) head so it is obvious whose inventory is cloned. + // Display the request sender's (challenger's) head and rank-colored name so it is obvious whose inventory is cloned. setOwner(player); + final String name = LuckPermsHook.coloredName(plugin, player); + setDisplayName(lang.getMessage("GUI.settings.buttons.use-mirror-my-inventory.name", "name", name)); + final Settings settings = settingManager.getSafely(player); final String value = settings.isMirrorMyInventory() ? lang.getMessage("GENERAL.enabled") : lang.getMessage("GENERAL.disabled"); - final String lore = plugin.getLang().getMessage("GUI.settings.buttons.use-mirror-my-inventory.lore", "mirror_my_inventory", value); + final String lore = plugin.getLang().getMessage("GUI.settings.buttons.use-mirror-my-inventory.lore", "name", name, "mirror_my_inventory", value); setLore(lore.split("\n")); } diff --git a/duels-plugin/src/main/java/me/realized/duels/gui/settings/buttons/MirrorTheirInventoryButton.java b/duels-plugin/src/main/java/me/realized/duels/gui/settings/buttons/MirrorTheirInventoryButton.java index 5fe3b78..e07b575 100644 --- a/duels-plugin/src/main/java/me/realized/duels/gui/settings/buttons/MirrorTheirInventoryButton.java +++ b/duels-plugin/src/main/java/me/realized/duels/gui/settings/buttons/MirrorTheirInventoryButton.java @@ -3,6 +3,7 @@ package me.realized.duels.gui.settings.buttons; import me.realized.duels.DuelsPlugin; import me.realized.duels.Permissions; import me.realized.duels.gui.BaseButton; +import me.realized.duels.hook.hooks.LuckPermsHook; import me.realized.duels.setting.Settings; import me.realized.duels.util.compat.Items; import me.realized.duels.util.inventory.ItemBuilder; @@ -25,18 +26,19 @@ public class MirrorTheirInventoryButton extends BaseButton { final Settings settings = settingManager.getSafely(player); - // Display the opponent's head so it is obvious whose inventory is cloned. + // Display the opponent's head and rank-colored name so it is obvious whose inventory is cloned. final UUID targetId = settings.getTarget(); + final Player target = targetId != null ? Bukkit.getPlayer(targetId) : null; + final String name = target != null ? LuckPermsHook.coloredName(plugin, target) : lang.getMessage("GENERAL.none"); - if (targetId != null) { - final Player target = Bukkit.getPlayer(targetId); - - if (target != null) { - setOwner(target); - } + if (target != null) { + setOwner(target); } + + setDisplayName(lang.getMessage("GUI.settings.buttons.use-mirror-their-inventory.name", "name", name)); + final String value = settings.isMirrorTheirInventory() ? lang.getMessage("GENERAL.enabled") : lang.getMessage("GENERAL.disabled"); - final String lore = plugin.getLang().getMessage("GUI.settings.buttons.use-mirror-their-inventory.lore", "mirror_their_inventory", value); + final String lore = plugin.getLang().getMessage("GUI.settings.buttons.use-mirror-their-inventory.lore", "name", name, "mirror_their_inventory", value); setLore(lore.split("\n")); } diff --git a/duels-plugin/src/main/java/me/realized/duels/gui/settings/buttons/RequestDetailsButton.java b/duels-plugin/src/main/java/me/realized/duels/gui/settings/buttons/RequestDetailsButton.java index 1a3d3b3..4f8a9b8 100644 --- a/duels-plugin/src/main/java/me/realized/duels/gui/settings/buttons/RequestDetailsButton.java +++ b/duels-plugin/src/main/java/me/realized/duels/gui/settings/buttons/RequestDetailsButton.java @@ -2,6 +2,7 @@ package me.realized.duels.gui.settings.buttons; import me.realized.duels.DuelsPlugin; import me.realized.duels.gui.BaseButton; +import me.realized.duels.hook.hooks.LuckPermsHook; import me.realized.duels.setting.Settings; import me.realized.duels.util.compat.Items; import me.realized.duels.util.inventory.ItemBuilder; @@ -27,7 +28,8 @@ public class RequestDetailsButton extends BaseButton { } final String lore = lang.getMessage("GUI.settings.buttons.details.lore", - "opponent", target.getName(), + "opponent", LuckPermsHook.coloredName(plugin, target), + "challenger", LuckPermsHook.coloredName(plugin, player), "kit", settings.getKit() != null ? settings.getKit().getName() : lang.getMessage("GENERAL.not-selected"), "own_inventory", settings.isOwnInventory() ? lang.getMessage("GENERAL.enabled") : lang.getMessage("GENERAL.disabled"), "mirror_my_inventory", settings.isMirrorMyInventory() ? lang.getMessage("GENERAL.enabled") : lang.getMessage("GENERAL.disabled"), diff --git a/duels-plugin/src/main/java/me/realized/duels/gui/settings/buttons/ShuffleAllButton.java b/duels-plugin/src/main/java/me/realized/duels/gui/settings/buttons/ShuffleAllButton.java new file mode 100644 index 0000000..7052144 --- /dev/null +++ b/duels-plugin/src/main/java/me/realized/duels/gui/settings/buttons/ShuffleAllButton.java @@ -0,0 +1,96 @@ +package me.realized.duels.gui.settings.buttons; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import me.realized.duels.DuelsPlugin; +import me.realized.duels.Permissions; +import me.realized.duels.api.kit.Kit; +import me.realized.duels.gui.BaseButton; +import me.realized.duels.kit.KitImpl; +import me.realized.duels.setting.Settings; +import me.realized.duels.util.inventory.ItemBuilder; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +public class ShuffleAllButton extends BaseButton { + + public ShuffleAllButton(final DuelsPlugin plugin) { + super(plugin, ItemBuilder.of(Material.NETHER_STAR).name(plugin.getLang().getMessage("GUI.settings.buttons.shuffle-all.name")).build()); + } + + @Override + public void update(final Player player) { + setLore(lang.getMessage("GUI.settings.buttons.shuffle-all.lore").split("\n")); + } + + @Override + public void onClick(final Player player) { + final Settings settings = settingManager.getSafely(player); + final ThreadLocalRandom random = ThreadLocalRandom.current(); + + // Randomly pick one of the enabled and permitted inventory/kit modes. They are mutually exclusive. + final List modes = new ArrayList<>(); + + if (config.isKitSelectingEnabled() && allowed(player, config.isKitSelectingUsePermission(), Permissions.KIT_SELECTING)) { + final KitImpl kit = randomKit(player, random); + + if (kit != null) { + modes.add(() -> settings.setKit(kit)); + } + } + + if (config.isOwnInventoryEnabled() && allowed(player, config.isOwnInventoryUsePermission(), Permissions.OWN_INVENTORY)) { + modes.add(() -> settings.setOwnInventory(true)); + } + + if (config.isMirrorInventoryEnabled() && allowed(player, config.isMirrorInventoryUsePermission(), Permissions.MIRROR_INVENTORY)) { + modes.add(() -> settings.setMirrorMyInventory(true)); + modes.add(() -> settings.setMirrorTheirInventory(true)); + } + + if (!modes.isEmpty()) { + modes.get(random.nextInt(modes.size())).run(); + } + + // Random arena for the chosen mode (null = Random, chosen when the match starts). + if (config.isArenaSelectingEnabled() && allowed(player, config.isArenaSelectingUsePermission(), Permissions.ARENA_SELECTING)) { + settings.setArena(arenaManager.randomArena(settings.getKit())); + } + + // Random item betting toggle. Diamond betting is intentionally left untouched. + if (config.isItemBettingEnabled() && allowed(player, config.isItemBettingUsePermission(), Permissions.ITEM_BETTING)) { + settings.setItemBetting(random.nextBoolean()); + } + + settings.updateGui(player); + lang.sendMessage(player, "GUI.settings.buttons.shuffle-all.shuffled"); + } + + private boolean allowed(final Player player, final boolean usePermission, final String permission) { + return !usePermission || player.hasPermission(permission) || player.hasPermission(Permissions.SETTING_ALL); + } + + private KitImpl randomKit(final Player player, final ThreadLocalRandom random) { + final List usable = new ArrayList<>(); + + for (final Kit kit : kitManager.getKits()) { + final KitImpl impl = (KitImpl) kit; + + if (canUseKit(player, impl)) { + usable.add(impl); + } + } + + return usable.isEmpty() ? null : usable.get(random.nextInt(usable.size())); + } + + private boolean canUseKit(final Player player, final KitImpl kit) { + if (!kit.isUsePermission()) { + return true; + } + + final String permission = String.format(Permissions.KIT, kit.getName().replace(" ", "-").toLowerCase()); + return player.hasPermission(Permissions.KIT_ALL) || player.hasPermission(permission); + } +} diff --git a/duels-plugin/src/main/java/me/realized/duels/hook/HookManager.java b/duels-plugin/src/main/java/me/realized/duels/hook/HookManager.java index 92bf80d..dabeac3 100644 --- a/duels-plugin/src/main/java/me/realized/duels/hook/HookManager.java +++ b/duels-plugin/src/main/java/me/realized/duels/hook/HookManager.java @@ -9,6 +9,7 @@ import me.realized.duels.hook.hooks.EternalCombatHook; import me.realized.duels.hook.hooks.FactionsHook; import me.realized.duels.hook.hooks.GameModeInventoriesHook; import me.realized.duels.hook.hooks.LeaderHeadsHook; +import me.realized.duels.hook.hooks.LuckPermsHook; import me.realized.duels.hook.hooks.MVdWPlaceholderHook; import me.realized.duels.hook.hooks.McMMOHook; import me.realized.duels.hook.hooks.PlaceholderHook; @@ -30,6 +31,7 @@ public class HookManager extends AbstractHookManager { register(FactionsHook.NAME, FactionsHook.class); register(GameModeInventoriesHook.NAME, GameModeInventoriesHook.class); register(LeaderHeadsHook.NAME, LeaderHeadsHook.class); + register(LuckPermsHook.NAME, LuckPermsHook.class); register(McMMOHook.NAME, McMMOHook.class); register(MVdWPlaceholderHook.NAME, MVdWPlaceholderHook.class); register(PlaceholderHook.NAME, PlaceholderHook.class); diff --git a/duels-plugin/src/main/java/me/realized/duels/hook/hooks/DiamondBankHook.java b/duels-plugin/src/main/java/me/realized/duels/hook/hooks/DiamondBankHook.java index 9399b6b..3e483ef 100644 --- a/duels-plugin/src/main/java/me/realized/duels/hook/hooks/DiamondBankHook.java +++ b/duels-plugin/src/main/java/me/realized/duels/hook/hooks/DiamondBankHook.java @@ -67,6 +67,18 @@ public class DiamondBankHook extends PluginHook { } } + /** + * @return the whole number of Diamonds the given player can currently spend (bank + inventory), or 0 on failure. + */ + public int getDiamonds(final Player player) { + try { + return ((Number) invoke(shardsToDiamonds, totalShards(player))).intValue(); + } catch (ReflectiveOperationException | RuntimeException ex) { + Log.warn("Failed to read DiamondBank-OG balance: " + ex.getMessage()); + return 0; + } + } + public boolean add(final int amount, final Player... players) { try { final long shards = toShards(amount); diff --git a/duels-plugin/src/main/java/me/realized/duels/hook/hooks/LuckPermsHook.java b/duels-plugin/src/main/java/me/realized/duels/hook/hooks/LuckPermsHook.java new file mode 100644 index 0000000..59bf844 --- /dev/null +++ b/duels-plugin/src/main/java/me/realized/duels/hook/hooks/LuckPermsHook.java @@ -0,0 +1,55 @@ +package me.realized.duels.hook.hooks; + +import me.realized.duels.DuelsPlugin; +import me.realized.duels.util.StringUtil; +import me.realized.duels.util.hook.PluginHook; +import net.luckperms.api.LuckPerms; +import net.luckperms.api.LuckPermsProvider; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +public class LuckPermsHook extends PluginHook { + + public static final String NAME = "LuckPerms"; + + private final LuckPerms api; + + public LuckPermsHook(final DuelsPlugin plugin) { + super(plugin, NAME); + this.api = LuckPermsProvider.get(); + } + + /** + * @return the player's name colored with the formatting of their LuckPerms prefix (e.g. their rank color). + */ + public String getColoredName(final Player player) { + return getColor(player) + player.getName(); + } + + private String getColor(final Player player) { + try { + final String prefix = api.getPlayerAdapter(Player.class).getMetaData(player).getPrefix(); + + if (prefix == null || prefix.isEmpty()) { + return ""; + } + + // Apply whatever color/format the prefix ends with to the name (e.g. "&c[Admin] " -> a red name). + return ChatColor.getLastColors(StringUtil.color(prefix)); + } catch (final Exception ex) { + return ""; + } + } + + /** + * Returns the player's rank-colored name if LuckPerms is hooked, otherwise their plain name. Null-safe. + */ + public static String coloredName(final DuelsPlugin plugin, final Player player) { + if (player == null) { + return ""; + } + + final LuckPermsHook hook = plugin.getHookManager().getHook(LuckPermsHook.class); + return hook != null ? hook.getColoredName(player) : player.getName(); + } +} diff --git a/duels-plugin/src/main/java/me/realized/duels/request/RequestManager.java b/duels-plugin/src/main/java/me/realized/duels/request/RequestManager.java index 7f4fff1..4e5f0f8 100644 --- a/duels-plugin/src/main/java/me/realized/duels/request/RequestManager.java +++ b/duels-plugin/src/main/java/me/realized/duels/request/RequestManager.java @@ -7,6 +7,7 @@ import me.realized.duels.DuelsPlugin; import me.realized.duels.api.event.request.RequestSendEvent; import me.realized.duels.config.Config; import me.realized.duels.config.Lang; +import me.realized.duels.hook.hooks.LuckPermsHook; import me.realized.duels.setting.Settings; import me.realized.duels.util.Loadable; import me.realized.duels.util.TextBuilder; @@ -20,11 +21,13 @@ import org.bukkit.event.player.PlayerQuitEvent; public class RequestManager implements Loadable, Listener { + private final DuelsPlugin plugin; private final Config config; private final Lang lang; private final Map> requests = new HashMap<>(); public RequestManager(final DuelsPlugin plugin) { + this.plugin = plugin; this.config = plugin.getConfiguration(); this.lang = plugin.getLang(); Bukkit.getPluginManager().registerEvents(this, plugin); @@ -66,13 +69,15 @@ public class RequestManager implements Loadable, Listener { final String arena = settings.getArena() != null ? settings.getArena().getName() : lang.getMessage("GENERAL.random"); final int betAmount = settings.getBet(); final String itemBetting = settings.isItemBetting() ? lang.getMessage("GENERAL.enabled") : lang.getMessage("GENERAL.disabled"); + final String challengerName = LuckPermsHook.coloredName(plugin, sender); + final String opponentName = LuckPermsHook.coloredName(plugin, target); lang.sendMessage(sender, "COMMAND.duel.request.send.sender", - "name", target.getName(), "kit", kit, + "name", opponentName, "challenger", challengerName, "opponent", opponentName, "kit", kit, "own_inventory", ownInventory, "mirror_my_inventory", mirrorMyInventory, "mirror_their_inventory", mirrorTheirInventory, "arena", arena, "bet_amount", betAmount, "item_betting", itemBetting); lang.sendMessage(target, "COMMAND.duel.request.send.receiver", - "name", sender.getName(), "kit", kit, + "name", challengerName, "challenger", challengerName, "opponent", opponentName, "kit", kit, "own_inventory", ownInventory, "mirror_my_inventory", mirrorMyInventory, "mirror_their_inventory", mirrorTheirInventory, "arena", arena, "bet_amount", betAmount, "item_betting", itemBetting); diff --git a/duels-plugin/src/main/java/me/realized/duels/setting/SettingsManager.java b/duels-plugin/src/main/java/me/realized/duels/setting/SettingsManager.java index e45a630..22dd8d9 100644 --- a/duels-plugin/src/main/java/me/realized/duels/setting/SettingsManager.java +++ b/duels-plugin/src/main/java/me/realized/duels/setting/SettingsManager.java @@ -22,6 +22,8 @@ public class SettingsManager implements Loadable, Listener { private final DuelsPlugin plugin; private final Map cache = new HashMap<>(); private final Set awaitingBetInput = ConcurrentHashMap.newKeySet(); + // Players who have already used their single "too high" retry for the current bet-input sequence. + private final Set betInputRetried = ConcurrentHashMap.newKeySet(); public SettingsManager(final DuelsPlugin plugin) { this.plugin = plugin; @@ -35,6 +37,7 @@ public class SettingsManager implements Loadable, Listener { public void handleUnload() { cache.clear(); awaitingBetInput.clear(); + betInputRetried.clear(); } // Only one Settings instance stays in memory while player is online; no need for manual removal of gui from GuiListener @@ -43,9 +46,18 @@ public class SettingsManager implements Loadable, Listener { } public void requestBetInput(final Player player) { + // Clicking the bet button starts a fresh sequence, so the one-time retry is available again. + betInputRetried.remove(player.getUniqueId()); awaitingBetInput.add(player.getUniqueId()); player.closeInventory(); - plugin.getLang().sendMessage(player, "GUI.settings.buttons.diamond-bet.prompt"); + + final Settings settings = getSafely(player); + final Player target = settings.getTarget() != null ? Bukkit.getPlayer(settings.getTarget()) : null; + final DiamondBankHook diamondBank = plugin.getHookManager().getHook(DiamondBankHook.class); + // The maximum bet is limited by the opponent's balance, since a bet they cannot cover is rejected. + final int max = (target != null && diamondBank != null) ? diamondBank.getDiamonds(target) : 0; + final String name = target != null ? target.getName() : plugin.getLang().getMessage("GENERAL.none"); + plugin.getLang().sendMessage(player, "GUI.settings.buttons.diamond-bet.prompt", "name", name, "max", max); } @EventHandler(priority = EventPriority.LOWEST) @@ -67,6 +79,7 @@ public class SettingsManager implements Loadable, Listener { final UUID uuid = event.getPlayer().getUniqueId(); cache.remove(uuid); awaitingBetInput.remove(uuid); + betInputRetried.remove(uuid); } private void handleBetInput(final UUID uuid, final String message) { @@ -79,6 +92,7 @@ public class SettingsManager implements Loadable, Listener { final Settings settings = getSafely(player); if (message.equalsIgnoreCase("cancel")) { + betInputRetried.remove(uuid); plugin.getLang().sendMessage(player, "GUI.settings.buttons.diamond-bet.cancelled"); settings.openGui(player); return; @@ -87,21 +101,49 @@ public class SettingsManager implements Loadable, Listener { final int bet = NumberUtil.parseInt(message).orElse(-1); if (bet < 0) { + betInputRetried.remove(uuid); plugin.getLang().sendMessage(player, "GUI.settings.buttons.diamond-bet.invalid"); settings.openGui(player); return; } - final DiamondBankHook diamondBank = plugin.getHookManager().getHook(DiamondBankHook.class); + if (bet > 0) { + final DiamondBankHook diamondBank = plugin.getHookManager().getHook(DiamondBankHook.class); - if (bet > 0 && (diamondBank == null || !diamondBank.has(bet, player))) { - plugin.getLang().sendMessage(player, "ERROR.command.not-enough-money"); - settings.openGui(player); - return; + if (diamondBank == null || !diamondBank.has(bet, player)) { + betTooHigh(player, () -> plugin.getLang().sendMessage(player, "ERROR.command.not-enough-money")); + return; + } + + final Player target = settings.getTarget() != null ? Bukkit.getPlayer(settings.getTarget()) : null; + + // Reject a bet the opponent cannot cover so the match is not doomed to fail on start. + if (target != null && !diamondBank.has(bet, target)) { + betTooHigh(player, () -> plugin.getLang().sendMessage(player, "ERROR.command.opponent-not-enough-money", + "name", target.getName(), "max", diamondBank.getDiamonds(target), "bet_amount", bet)); + return; + } } + betInputRetried.remove(uuid); settings.setBet(bet); plugin.getLang().sendMessage(player, "GUI.settings.buttons.diamond-bet.set", "bet_amount", bet); settings.openGui(player); } + + // A bet that exceeds either balance gives the player one more chance to type an amount in chat. + // Once that single retry is used, they must send a new duel request rather than reopening the GUI. + private void betTooHigh(final Player player, final Runnable reasonMessage) { + final UUID uuid = player.getUniqueId(); + reasonMessage.run(); + + if (betInputRetried.add(uuid)) { + awaitingBetInput.add(uuid); + plugin.getLang().sendMessage(player, "GUI.settings.buttons.diamond-bet.retry"); + return; + } + + betInputRetried.remove(uuid); + plugin.getLang().sendMessage(player, "GUI.settings.buttons.diamond-bet.retry-failed"); + } } diff --git a/duels-plugin/src/main/resources/lang.yml b/duels-plugin/src/main/resources/lang.yml index a1941a0..ae1cc3d 100644 --- a/duels-plugin/src/main/resources/lang.yml +++ b/duels-plugin/src/main/resources/lang.yml @@ -63,7 +63,7 @@ ERROR: target: '{PREFIX} &c%name% is already in a duel.' already-in-queue: '{PREFIX} &cYou cannot do this while in a queue.' prevent-teleportation: '{PREFIX} &cCannot teleport to a player in a duel.' - mode-unselected: '{PREFIX} &cChoose a kit or enable one of: Each Uses Own Inventory, Both Use My Inventory, Both Use Their Inventory.' + mode-unselected: '{PREFIX} &cChoose a kit or enable an inventory option (each their own, yours, or your opponent''s).' mode-fixed: '{PREFIX} &cYou are not allowed to change inventory mode here.' spectate: already-spectating: @@ -95,6 +95,7 @@ ERROR: invalid-option: '{PREFIX} &c''%option%'' is not a valid option. Available options are: %available_options%' invalid-action: '{PREFIX} &c''%action%'' is not a valid action. Available actions are: %available_actions%' not-enough-money: '{PREFIX} &cYou do not have enough Diamonds to bet!' + opponent-not-enough-money: '{PREFIX} &c%name% only has %max% Diamond(s) and cannot cover a bet of &B%bet_amount% Diamond(s)&c.' lobby-save-failure: '{PREFIX} &cCould not save lobby location. Please check the console for more information.' name-not-alphanumeric: '{PREFIX} &c''%name%'' is not alphanumeric.' @@ -114,21 +115,21 @@ COMMAND: send: sender: - '{LINE}' - - '&7Duel request sent to &4%name%&7.' + - '&7Duel request sent to %opponent%&7.' - '&6- &7Kit: &d%kit%' - '&6- &7Each Uses Own Inventory: &r%own_inventory%' - - '&6- &7Both Use My Inventory: &r%mirror_my_inventory%' - - '&6- &7Both Use Their Inventory: &r%mirror_their_inventory%' + - '&6- &7Both Use %challenger%&7''s Inventory: &r%mirror_my_inventory%' + - '&6- &7Both Use %opponent%&7''s Inventory: &r%mirror_their_inventory%' - '&6- &7Arena: &e%arena%' - '&6- &7Bet: &B%bet_amount% Diamond(s)' - '{LINE}' receiver: - '{LINE}' - - '&7Duel request received from &4%name%&7.' + - '&7Duel request received from %challenger%&7.' - '&6- &7Kit: &d%kit%' - '&6- &7Each Uses Own Inventory: &r%own_inventory%' - - '&6- &7Both Use My Inventory: &r%mirror_my_inventory%' - - '&6- &7Both Use Their Inventory: &r%mirror_their_inventory%' + - '&6- &7Both Use %challenger%&7''s Inventory: &r%mirror_my_inventory%' + - '&6- &7Both Use %opponent%&7''s Inventory: &r%mirror_their_inventory%' - '&6- &7Arena: &e%arena%' - '&6- &7Bet: &B%bet_amount% Diamond(s)' clickable-text: @@ -293,7 +294,7 @@ COMMAND: DUEL: start-failure: - mode-unselected: '{FAIL_PREFIX} &cChoose a kit or enable one of: Each Uses Own Inventory, Both Use My Inventory, Both Use Their Inventory.' + mode-unselected: '{FAIL_PREFIX} &cChoose a kit or enable an inventory option (each their own, yours, or your opponent''s).' player-is-dead: '{FAIL_PREFIX} You or your opponent is not alive!' in-blacklisted-world: '{FAIL_PREFIX} You or your opponent is in a blacklisted world!' is-tagged: '{FAIL_PREFIX} You or your opponent is in combat!' @@ -357,11 +358,11 @@ GUI: details: name: '&9Request Details' lore: - - '&7Opponent: &4%opponent%' + - '&7Opponent: %opponent%' - '&7Kit: &d%kit%' - '&7Each Uses Own Inventory: &r%own_inventory%' - - '&7Both Use My Inventory: &r%mirror_my_inventory%' - - '&7Both Use Their Inventory: &r%mirror_their_inventory%' + - '&7Both Use %challenger%&7''s Inventory: &r%mirror_my_inventory%' + - '&7Both Use %opponent%&7''s Inventory: &r%mirror_their_inventory%' - '&7Arena: &e%arena%' - '&7Bet: &B%bet_amount% Diamond(s)' kit-selector: @@ -389,11 +390,11 @@ GUI: - '&cYou do not have permission to' - '&cuse this option.' use-mirror-my-inventory: - name: '&bBoth Use My Inventory &7(Your Head)' + name: '&bBoth Use %name%&b''s Inventory' lore: - '&7Both duelists fight with a CLONE of' - - '&7YOUR inventory and armor (the challenger).' - - '&7You both get the same gear: yours.' + - '&7%name%&7''s inventory and armor (you, the challenger).' + - '&7You both get the same gear.' - '&7Originals are restored after the match.' - ' ' - '&7Currently: &r%mirror_my_inventory%' @@ -402,11 +403,11 @@ GUI: - '&cYou do not have permission to' - '&cuse this option.' use-mirror-their-inventory: - name: '&eBoth Use Their Inventory &7(Opponent Head)' + name: '&eBoth Use %name%&e''s Inventory' lore: - '&7Both duelists fight with a CLONE of' - - '&7the OPPONENT''s inventory and armor.' - - '&7You both get the same gear: theirs.' + - '&7%name%&7''s inventory and armor (your opponent).' + - '&7You both get the same gear.' - '&7Originals are restored after the match.' - ' ' - '&7Currently: &r%mirror_their_inventory%' @@ -429,8 +430,10 @@ GUI: - '&7Bet: &B%bet_amount% Diamond(s)' - ' ' - '&aClick to enter a bet amount.' - prompt: '{PREFIX} &7Type the Diamond bet amount in chat, or type &ccancel &7to keep the current amount.' + prompt: '{PREFIX} &7Type the Diamond bet amount in chat, or type &ccancel &7to keep the current amount. &7Max bet vs &4%name%&7: &B%max% Diamond(s) &7(their balance).' invalid: '{PREFIX} &cPlease enter a whole number of Diamonds.' + retry: '{PREFIX} &7Type a lower Diamond bet in chat, or type &ccancel&7. &cThis is your last try before you must send a new duel request.' + retry-failed: '{PREFIX} &cThat bet is still too high. Send a new duel request to try betting again.' cancelled: '{PREFIX} &7Diamond bet unchanged.' set: '{PREFIX} &7Diamond bet set to &B%bet_amount% Diamond(s)&7.' lore-no-permission: @@ -445,6 +448,15 @@ GUI: lore-no-permission: - '&cYou do not have permission to' - '&cuse this option.' + shuffle-all: + name: '&d&lShuffle All' + lore: + - '&7Randomly picks an inventory mode,' + - '&7arena, and item betting for you.' + - '&7Your Diamond bet is left unchanged.' + - ' ' + - '&aClick to shuffle!' + shuffled: '{PREFIX} &7Randomized all settings except your Diamond bet.' send: name: '&a&lSEND REQUEST' cancel: diff --git a/duels-plugin/src/main/resources/plugin.yml b/duels-plugin/src/main/resources/plugin.yml index 11042a0..fed4905 100644 --- a/duels-plugin/src/main/resources/plugin.yml +++ b/duels-plugin/src/main/resources/plugin.yml @@ -1,7 +1,7 @@ name: Duels-OG main: me.realized.duels.DuelsPlugin version: @VERSION@ -softdepend: [BetterChairs-OG, CombatLogX, CombatTagPlus, DiamondBank-OG, Essentials, EternalCombat, Factions, GameModeInventories-OG, LeaderHeads, mcMMO, MVdWPlaceholderAPI, MyWorlds, PlaceholderAPI, ProtocolLib, PvPManager, SimpleClans, WorldGuard] +softdepend: [BetterChairs-OG, CombatLogX, CombatTagPlus, DiamondBank-OG, Essentials, EternalCombat, Factions, GameModeInventories-OG, LeaderHeads, LuckPerms, mcMMO, MVdWPlaceholderAPI, MyWorlds, PlaceholderAPI, ProtocolLib, PvPManager, SimpleClans, WorldGuard] # MyWorlds added as soft-depend due to locations being null when Duels is loaded before managed worlds. # Add any world management plugins to softdepend list if arena doesn't load properly in the generated world. api-version: 1.14