diff --git a/README.md b/README.md index 595aa439..fe38dba2 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ View the Spigot page (with FAQ and install instructions) [here](https://www.spig Check the [wiki](https://github.com/espidev/ProtectionStones/wiki) for plugin reference information. ### Dependencies -* ProtectionStones 2.10.5 +* ProtectionStones 2.10.6 * Spigot 1.20.6+ * WorldGuard 7.0.9+ * WorldEdit 7.2.6+ diff --git a/pom.xml b/pom.xml index 37940957..3e0ec5cf 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 dev.espi protectionstones - 2.10.5 + 2.10.7 ProtectionStones A grief prevention plugin for Spigot Minecraft servers. https://github.com/espidev/ProtectionStones @@ -207,13 +207,13 @@ org.spigotmc spigot-api - 1.20.6-R0.1-SNAPSHOT + 1.21.3-R0.1-SNAPSHOT provided com.sk89q.worldguard worldguard-bukkit - 7.0.9-SNAPSHOT + 7.0.14-SNAPSHOT provided @@ -231,7 +231,7 @@ com.sk89q.worldedit worldedit-bukkit - 7.2.6-SNAPSHOT + 7.3.16-SNAPSHOT provided @@ -240,6 +240,36 @@ + + net.kyori + adventure-api + 4.17.0 + compile + + + net.kyori + adventure-platform-bukkit + 4.3.2 + compile + + + net.kyori + adventure-text-serializer-plain + 4.17.0 + compile + + + net.kyori + adventure-text-serializer-bungeecord + 4.4.1 + compile + + + net.kyori + adventure-text-minimessage + 4.17.0 + compile + me.clip placeholderapi diff --git a/src/main/java/dev/espi/protectionstones/BlockHandler.java b/src/main/java/dev/espi/protectionstones/BlockHandler.java index 891a7650..c3d37003 100644 --- a/src/main/java/dev/espi/protectionstones/BlockHandler.java +++ b/src/main/java/dev/espi/protectionstones/BlockHandler.java @@ -30,6 +30,7 @@ import dev.espi.protectionstones.utils.MiscUtil; import dev.espi.protectionstones.utils.WGMerge; import dev.espi.protectionstones.utils.WGUtils; +import net.kyori.adventure.text.Component; import net.md_5.bungee.api.chat.TextComponent; import net.milkbowl.vault.economy.EconomyResponse; import org.bukkit.*; @@ -37,10 +38,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.block.BlockPlaceEvent; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; +import java.util.*; public class BlockHandler { private static HashMap lastProtectStonePlaced = new HashMap<>(); @@ -140,7 +138,7 @@ public static boolean createPSRegion(Player p, Location l, PSProtectBlock blockO if (ProtectionStones.getInstance().getConfigOptions().placingCooldown != -1) { String time = checkCooldown(p); if (time != null) { - PSL.msg(p, PSL.COOLDOWN.msg().replace("%time%", time)); + PSL.msg(p, PSL.COOLDOWN.replace("%time%", time)); return false; } } @@ -168,7 +166,7 @@ public static boolean createPSRegion(Player p, Location l, PSProtectBlock blockO // check if player has enough money if (ProtectionStones.getInstance().isVaultSupportEnabled() && blockOptions.costToPlace != 0 && !ProtectionStones.getInstance().getVaultEconomy().has(p, blockOptions.costToPlace)) { - PSL.msg(p, PSL.NOT_ENOUGH_MONEY.msg().replace("%price%", String.format("%.2f", blockOptions.costToPlace))); + PSL.msg(p, PSL.NOT_ENOUGH_MONEY.replace("%price%", String.format("%.2f", blockOptions.costToPlace))); return false; } @@ -183,10 +181,10 @@ public static boolean createPSRegion(Player p, Location l, PSProtectBlock blockO if (ProtectionStones.getInstance().isVaultSupportEnabled() && blockOptions.costToPlace != 0) { EconomyResponse er = ProtectionStones.getInstance().getVaultEconomy().withdrawPlayer(p, blockOptions.costToPlace); if (!er.transactionSuccess()) { - PSL.msg(p, er.errorMessage); + PSL.msg(p, Component.text(er.errorMessage)); return true; } - PSL.msg(p, PSL.PAID_MONEY.msg().replace("%price%", String.format("%.2f", blockOptions.costToPlace))); + PSL.msg(p, PSL.PAID_MONEY.replace("%price%", String.format("%.2f", blockOptions.costToPlace))); } return true; @@ -214,7 +212,7 @@ public static boolean createActualRegion(Player p, Location l, PSProtectBlock bl // check for minimum distance between claims by using fake region if (blockOptions.distanceBetweenClaims != -1 && !p.hasPermission("protectionstones.superowner")) { if (!isFarEnoughFromOtherClaims(blockOptions, p.getWorld(), lp, bx, by, bz)) { - PSL.msg(p, PSL.REGION_TOO_CLOSE.msg().replace("%num%", "" + blockOptions.distanceBetweenClaims)); + PSL.msg(p, PSL.REGION_TOO_CLOSE.replace("%num%", "" + blockOptions.distanceBetweenClaims)); return false; } } @@ -315,7 +313,7 @@ private static void playerMergeTask(Player p, PSRegion r) { Bukkit.getScheduler().runTaskAsynchronously(ProtectionStones.getInstance(), () -> { try { WGMerge.mergeRealRegions(p.getWorld(), r.getWGRegionManager(), finalMergeTo, Arrays.asList(finalMergeTo, r)); - PSL.msg(p, PSL.MERGE_AUTO_MERGED.msg().replace("%region%", finalMergeTo.getId())); + PSL.msg(p, PSL.MERGE_AUTO_MERGED.replace("%region%", finalMergeTo.getId())); } catch (WGMerge.RegionHoleException e) { PSL.msg(p, PSL.NO_REGION_HOLES.msg()); // TODO github issue #120, prevent holes even if showGUI is true } catch (WGMerge.RegionCannotMergeWhileRentedException e) { @@ -327,14 +325,21 @@ private static void playerMergeTask(Player p, PSRegion r) { // show merge gui if (showGUI) { - List tc = ArgMerge.getGUI(p, r); + List tc = ArgMerge.getGUI(p, r); if (!tc.isEmpty()) { // if there are regions you can merge into - p.sendMessage(ChatColor.WHITE + ""); // send empty line + PSL.msg(p, Component.empty()); PSL.msg(p, PSL.MERGE_INTO.msg()); - PSL.msg(p, PSL.MERGE_HEADER.msg().replace("%region%", r.getId())); - for (TextComponent t : tc) p.spigot().sendMessage(t); - p.sendMessage(ChatColor.WHITE + ""); // send empty line + PSL.msg(p, PSL.MERGE_HEADER.replaceAll(Map.of("%region%", r.getId()))); + + // GUI entries + for (Component t : tc) { + PSL.msg(p, t); + } + + // empty line again + PSL.msg(p, Component.empty()); } } + } } diff --git a/src/main/java/dev/espi/protectionstones/ListenerClass.java b/src/main/java/dev/espi/protectionstones/ListenerClass.java index 783de998..9c33db3a 100644 --- a/src/main/java/dev/espi/protectionstones/ListenerClass.java +++ b/src/main/java/dev/espi/protectionstones/ListenerClass.java @@ -29,14 +29,14 @@ import dev.espi.protectionstones.utils.RecipeUtil; import dev.espi.protectionstones.utils.UUIDCache; import dev.espi.protectionstones.utils.WGUtils; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.block.BlockState; -import org.bukkit.block.Furnace; +import org.bukkit.block.*; +import org.bukkit.block.data.type.Crafter; import org.bukkit.command.CommandSender; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; @@ -53,7 +53,7 @@ import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; -import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.*; import java.util.List; @@ -92,7 +92,7 @@ public void onPlayerJoin(PlayerJoinEvent e) { } if (amount != 0) { - PSL.msg(psp, PSL.TAX_JOIN_MSG_PENDING_PAYMENTS.msg().replace("%money%", "" + amount)); + PSL.msg(psp.getPlayer(), PSL.TAX_JOIN_MSG_PENDING_PAYMENTS.replace("%money%", "" + amount)); } }); } @@ -137,7 +137,7 @@ public void onBlockPlace(BlockPlaceEvent e) { // returns the error message, or "" if the player has permission to break the region // TODO: refactor and move this to PSRegion, so that /ps unclaim can use the same checks - private String checkPermissionToBreakProtection(Player p, PSRegion r) { + private Component checkPermissionToBreakProtection(Player p, PSRegion r) { // check for destroy permission if (!p.hasPermission("protectionstones.destroy")) { return PSL.NO_PERMISSION_DESTROY.msg(); @@ -153,7 +153,7 @@ private String checkPermissionToBreakProtection(Player p, PSRegion r) { return PSL.RENT_CANNOT_BREAK_WHILE_RENTING.msg(); } - return ""; + return Component.empty(); } // helper method for breaking protection blocks @@ -162,8 +162,8 @@ private boolean playerBreakProtection(Player p, PSRegion r) { PSProtectBlock blockOptions = r.getTypeOptions(); // check if player has permission to break the protection - String error = checkPermissionToBreakProtection(p, r); - if (!error.isEmpty()) { + Component error = checkPermissionToBreakProtection(p, r); + if (!error.contains(Component.empty())) { PSL.msg(p, error); return false; } @@ -220,6 +220,7 @@ public void onPlayerInteract(PlayerInteractEvent e) { // thus we should cancel the event here if possible (so other plugins don't start acting upon it) @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onBlockBreakLowPriority(BlockBreakEvent e) { + ProtectionStones.getInstance().debug("ListenerClass.java, onBlockBreakLowPriority"); Player p = e.getPlayer(); Block pb = e.getBlock(); @@ -228,8 +229,11 @@ public void onBlockBreakLowPriority(BlockBreakEvent e) { // check if player has permission to break the protection PSRegion r = PSRegion.fromLocation(pb.getLocation()); if (r != null) { - String error = checkPermissionToBreakProtection(p, r); - if (!error.isEmpty()) { + ProtectionStones.getInstance().debug("Player:"+ p.getName()+", Holding:"+ p.getPlayer().getInventory().getItemInMainHand().getType().name()+", Enchants:" +p.getPlayer().getInventory().getItemInMainHand().getEnchantments()); + + + Component error = checkPermissionToBreakProtection(p, r); + if (!error.contains(Component.empty())) { PSL.msg(p, error); e.setCancelled(true); } @@ -238,6 +242,7 @@ public void onBlockBreakLowPriority(BlockBreakEvent e) { @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onBlockBreak(BlockBreakEvent e) { + ProtectionStones.getInstance().debug("ListenerClass.java, onBlockBreak"); Player p = e.getPlayer(); Block pb = e.getBlock(); @@ -275,6 +280,7 @@ public void onBlockBreak(BlockBreakEvent e) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onFurnaceSmelt(FurnaceSmeltEvent e) { + ProtectionStones.getInstance().debug("ListenerClass.java, onFurnaceSmelt"); // prevent protect block item to be smelt PSProtectBlock options = ProtectionStones.getBlockOptions(e.getSource()); if (options != null && !options.allowSmeltItem) { @@ -284,6 +290,7 @@ public void onFurnaceSmelt(FurnaceSmeltEvent e) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onFurnaceBurnItem(FurnaceBurnEvent e) { + ProtectionStones.getInstance().debug("ListenerClass.java, onFurnaceBurnItem"); // prevent protect block item to be smelt Furnace f = (Furnace) e.getBlock().getState(); if (f.getInventory().getSmelting() != null) { @@ -299,6 +306,7 @@ public void onFurnaceBurnItem(FurnaceBurnEvent e) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onPrepareItemCraft(PrepareItemCraftEvent e) { + ProtectionStones.getInstance().debug("ListenerClass.java, onPrepareItemCraft"); for (ItemStack s : e.getInventory().getMatrix()) { PSProtectBlock options = ProtectionStones.getBlockOptions(s); if (options != null && !options.allowUseInCrafting) { @@ -307,12 +315,30 @@ public void onPrepareItemCraft(PrepareItemCraftEvent e) { } } + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onCrafter(CrafterCraftEvent e) { + Block block = e.getBlock(); + BlockState state = block.getState(); + if (block.getType() != Material.CRAFTER) return; + if (!(state instanceof Container container)) return; + Inventory inv = container.getInventory(); + for (ItemStack item : inv.getContents()) { + if (item == null) continue; + PSProtectBlock options = ProtectionStones.getBlockOptions(item); + if (options != null && !options.allowUseInCrafting) { + e.setCancelled(true); + e.setResult(new ItemStack(Material.AIR)); + } + } + } + // -=-=-=- disable grindstone inventory to prevent infinite exp exploit with enchanted_effect option -=-=-=- // see https://github.com/espidev/ProtectionStones/issues/324 @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onInventoryClickEvent(InventoryClickEvent e) { + ProtectionStones.getInstance().debug("ListenerClass.java, onInventoryClickEvent"); if (e.getInventory().getType() == InventoryType.GRINDSTONE) { if (ProtectionStones.isProtectBlockItem(e.getCurrentItem())) { e.setCancelled(true); @@ -325,6 +351,7 @@ public void onInventoryClickEvent(InventoryClickEvent e) { @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onPlayerBucketFill(PlayerBucketEmptyEvent e) { + ProtectionStones.getInstance().debug("ListenerClass.java, onPlayerBucketFill"); Block clicked = e.getBlockClicked(); BlockFace bf = e.getBlockFace(); Block check = clicked.getWorld().getBlockAt(clicked.getX() + e.getBlockFace().getModX(), clicked.getY() + bf.getModY(), clicked.getZ() + e.getBlockFace().getModZ()); @@ -335,6 +362,7 @@ public void onPlayerBucketFill(PlayerBucketEmptyEvent e) { @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onBlockIgnite(BlockIgniteEvent e) { + ProtectionStones.getInstance().debug("ListenerClass.java, onBlockIgnite"); if (ProtectionStones.isProtectBlock(e.getBlock())) { e.setCancelled(true); } @@ -425,16 +453,19 @@ private void pistonUtil(List pushedBlocks, BlockPistonEvent e) { @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onBlockExplode(BlockExplodeEvent e) { + ProtectionStones.getInstance().debug("ListenerClass.java, onBlockExplode"); explodeUtil(e.blockList(), e.getBlock().getLocation().getWorld()); } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onEntityExplode(EntityExplodeEvent e) { + ProtectionStones.getInstance().debug("ListenerClass.java, onEntityExplode"); explodeUtil(e.blockList(), e.getLocation().getWorld()); } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onEntityChangeBlock(EntityChangeBlockEvent e) { + ProtectionStones.getInstance().debug("ListenerClass.java, onEntityChangeBlock"); if (!ProtectionStones.isProtectBlock(e.getBlock())) return; // events like ender dragon block break, wither running into block break, etc. diff --git a/src/main/java/dev/espi/protectionstones/PSEconomy.java b/src/main/java/dev/espi/protectionstones/PSEconomy.java index f776e00f..ce8f9f2b 100644 --- a/src/main/java/dev/espi/protectionstones/PSEconomy.java +++ b/src/main/java/dev/espi/protectionstones/PSEconomy.java @@ -23,12 +23,14 @@ import net.milkbowl.vault.economy.EconomyResponse; import org.bukkit.Bukkit; import org.bukkit.World; +import org.bukkit.entity.Player; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; @@ -136,9 +138,13 @@ public static void processTaxes(PSRegion r) { EconomyResponse res = r.payTax(psp, psp.getBalance()); if (psp.getPlayer() != null && res.amount != 0) { - PSL.msg(psp.getPlayer(), PSL.TAX_PAID.msg() - .replace("%amount%", String.format("%.2f", res.amount)) - .replace("%region%", r.getName() == null ? r.getId() : r.getName() + " (" + r.getId() + ")")); + PSL.msg(psp.getPlayer(), + PSL.TAX_PAID.replaceAll(Map.of( + "%amount%", String.format("%.2f", res.amount), + "%region%", r.getName() == null ? r.getId() : r.getName() + " (" + r.getId() + ")" + )) + ); + } } @@ -157,49 +163,80 @@ public static void processTaxes(PSRegion r) { * @param r the region to perform the rent payment */ public static void doRentPayment(PSRegion r) { - PSPlayer tenant = PSPlayer.fromPlayer(Bukkit.getOfflinePlayer(r.getTenant())); + PSPlayer tenant = PSPlayer.fromPlayer(Bukkit.getOfflinePlayer(r.getTenant())); PSPlayer landlord = PSPlayer.fromPlayer(Bukkit.getOfflinePlayer(r.getLandlord())); + final String regionName = (r.getName() != null ? r.getName() : r.getId()); + final String priceStr = String.format("%.2f", r.getPrice()); + // not enough money for rent if (!tenant.hasAmount(r.getPrice())) { - if (tenant.getOfflinePlayer().isOnline()) { - PSL.msg(Bukkit.getPlayer(r.getTenant()), PSL.RENT_EVICT_NO_MONEY_TENANT.msg() - .replace("%region%", r.getName() != null ? r.getName() : r.getId()) - .replace("%price%", String.format("%.2f", r.getPrice()))); + Player tenantOnline = Bukkit.getPlayer(r.getTenant()); + if (tenantOnline != null) { + PSL.msg( + tenantOnline, + PSL.RENT_EVICT_NO_MONEY_TENANT.replaceAll(Map.of( + "%region%", regionName, + "%price%", priceStr + )) + ); } - if (landlord.getOfflinePlayer().isOnline()) { - PSL.msg(Bukkit.getPlayer(r.getLandlord()), PSL.RENT_EVICT_NO_MONEY_LANDLORD.msg() - .replace("%region%", r.getName() != null ? r.getName() : r.getId()) - .replace("%tenant%", tenant.getName())); + + Player landlordOnline = Bukkit.getPlayer(r.getLandlord()); + if (landlordOnline != null) { + PSL.msg( + landlordOnline, + PSL.RENT_EVICT_NO_MONEY_LANDLORD.replaceAll(Map.of( + "%region%", regionName, + "%tenant%", tenant.getName() + )) + ); } + r.removeRenting(); return; } // send payment messages - if (tenant.getOfflinePlayer().isOnline()) { - PSL.msg(Bukkit.getPlayer(r.getTenant()), PSL.RENT_PAID_TENANT.msg() - .replace("%price%", String.format("%.2f", r.getPrice())) - .replace("%landlord%", landlord.getName()) - .replace("%region%", r.getName() != null ? r.getName() : r.getId())); + Player tenantOnline = Bukkit.getPlayer(r.getTenant()); + if (tenantOnline != null) { + PSL.msg( + tenantOnline, + PSL.RENT_PAID_TENANT.replaceAll(Map.of( + "%price%", priceStr, + "%landlord%", landlord.getName(), + "%region%", regionName + )) + ); } - if (landlord.getOfflinePlayer().isOnline()) { - PSL.msg(Bukkit.getPlayer(r.getLandlord()), PSL.RENT_PAID_LANDLORD.msg() - .replace("%price%", String.format("%.2f", r.getPrice())) - .replace("%tenant%", tenant.getName()) - .replace("%region%", r.getName() != null ? r.getName() : r.getId())); + + Player landlordOnline = Bukkit.getPlayer(r.getLandlord()); + if (landlordOnline != null) { + PSL.msg( + landlordOnline, + PSL.RENT_PAID_LANDLORD.replaceAll(Map.of( + "%price%", priceStr, + "%tenant%", tenant.getName(), + "%region%", regionName + )) + ); } - // update money must be run in main thread - Bukkit.getScheduler().runTask(ProtectionStones.getInstance(), () -> tenant.pay(landlord, r.getPrice())); + // update balances (main thread) + Bukkit.getScheduler().runTask( + ProtectionStones.getInstance(), + () -> tenant.pay(landlord, r.getPrice()) + ); + r.setRentLastPaid(Instant.now().getEpochSecond()); - try { // must save region to persist last paid + try { r.getWGRegionManager().saveChanges(); } catch (StorageException e) { e.printStackTrace(); } } + /** * Get list of rented regions. * diff --git a/src/main/java/dev/espi/protectionstones/PSL.java b/src/main/java/dev/espi/protectionstones/PSL.java index b64c3248..825d16fa 100644 --- a/src/main/java/dev/espi/protectionstones/PSL.java +++ b/src/main/java/dev/espi/protectionstones/PSL.java @@ -1,4 +1,4 @@ -/* +package dev.espi.protectionstones;/* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -13,18 +13,21 @@ * along with this program. If not, see . */ -package dev.espi.protectionstones; - -import org.apache.commons.lang3.StringUtils; -import org.bukkit.ChatColor; +import dev.espi.protectionstones.utils.ChatUtil; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; import java.util.Arrays; +import java.util.Map; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -32,505 +35,517 @@ public enum PSL { // messages.yml - COOLDOWN("cooldown", ChatColor.GOLD + "Warning: " + ChatColor.GRAY + "Please wait for %time% seconds before placing again!"), - NO_SUCH_COMMAND("no_such_command", ChatColor.RED + "No such command. please type /ps help for more info"), - NO_ACCESS("no_access", ChatColor.RED + "You are not allowed to do that here."), - NO_ROOM_IN_INVENTORY("no_room_in_inventory", ChatColor.RED + "You don't have enough room in your inventory."), - NO_ROOM_DROPPING_ON_FLOOR("no_room_dropping_on_floor", ChatColor.RED + "You don't have enough room in your inventory. Dropping item on floor."), - INVALID_BLOCK("invalid_block", ChatColor.RED + "Invalid protection block."), - NOT_ENOUGH_MONEY("not_enough_money", ChatColor.RED + "You don't have enough money! The price is %price%."), - PAID_MONEY("paid_money", ChatColor.AQUA + "You've paid $%price%."), - INVALID_WORLD("invalid_world", ChatColor.RED + "Invalid world."), - MUST_BE_PLAYER("must_be_player", ChatColor.RED + "You must be a player to execute this command."), + // --- General --- + COOLDOWN("cooldown", "Warning: Please wait for %time% seconds before placing again!", "%time%"), + NO_SUCH_COMMAND("no_such_command", "No such command. please type /ps help for more info"), + NO_ACCESS("no_access", "You are not allowed to do that here."), + NO_ROOM_IN_INVENTORY("no_room_in_inventory", "You don't have enough room in your inventory."), + NO_ROOM_DROPPING_ON_FLOOR("no_room_dropping_on_floor", "You don't have enough room in your inventory. Dropping item on floor."), + INVALID_BLOCK("invalid_block", "Invalid protection block."), + NOT_ENOUGH_MONEY("not_enough_money", "You don't have enough money! The price is %price%.", "%price%"), + PAID_MONEY("paid_money", "You've paid $%price%.", "%price%"), + INVALID_WORLD("invalid_world", "Invalid world."), + MUST_BE_PLAYER("must_be_player", "You must be a player to execute this command."), GO_BACK_PAGE("go_back_page", "Go back a page."), GO_NEXT_PAGE("go_next_page", "Go to next page."), - PAGE_DOES_NOT_EXIST("page_does_not_exist", ChatColor.RED + "Page does not exist."), - - HELP("help", ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "=====" + ChatColor.RESET + " PS Help " + ChatColor.DARK_GRAY + ChatColor.STRIKETHROUGH + "=====\n" + ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps help"), - HELP_NEXT("help_next", ChatColor.GRAY + "Do /ps help %page% to go to the next page!"), - - COMMAND_REQUIRES_PLAYER_NAME("command_requires_player_name", ChatColor.RED + "This command requires a player name."), - - NO_PERMISSION_TOGGLE("no_permission_toggle", ChatColor.RED + "You don't have permission to use the toggle command."), - NO_PERMISSION_CREATE("no_permission_create", ChatColor.RED + "You don't have permission to place a protection block."), - NO_PERMISSION_CREATE_SPECIFIC("no_permission_create_specific", ChatColor.RED + "You don't have permission to place this protection block type."), - NO_PERMISSION_DESTROY("no_permission_destroy", ChatColor.RED + "You don't have permission to destroy a protection block."), - NO_PERMISSION_MEMBERS("no_permission_members", "&cYou don't have permission to use member commands."), - NO_PERMISSION_OWNERS("no_permission_owners", "&cYou don't have permission to use owner commands."), - NO_PERMISSION_ADMIN("no_permission_admin", ChatColor.RED + "You do not have permission to use that command."), - NO_PERMISSION_COUNT("no_permission_count", ChatColor.RED + "You do not have permission to use that command."), - NO_PERMISSION_COUNT_OTHERS("no_permission_count_others", ChatColor.RED + "You do not have permission to use that command."), - NO_PERMISSION_FLAGS("no_permission_flags", "&cYou do not have permission to use flag commands."), - NO_PERMISSION_PER_FLAG("no_permission_per_flag", ChatColor.RED + "You do not have permission to use that flag."), - NO_PERMISSION_RENT("no_permission_rent", ChatColor.RED + "You do not have permission for renting."), - NO_PERMISSION_TAX("no_permission_tax", ChatColor.RED + "You do not have permission to use the tax command."), - NO_PERMISSION_BUYSELL("no_permission_buysell", ChatColor.RED + "You do not have permission to buy and sell regions."), - NO_PERMISSION_UNHIDE("no_permission_unhide", ChatColor.RED + "You do not have permission to unhide protection blocks."), - NO_PERMISSION_HIDE("no_permission_hide", ChatColor.RED + "You do not have permission to hide protection blocks."), - NO_PERMISSION_INFO("no_permission_info", ChatColor.RED + "You do not have permission to use the region info command."), - NO_PERMISSION_PRIORITY("no_permission_priority", ChatColor.RED + "You do not have permission to use the priority command."), - NO_PERMISSION_REGION("no_permission_region", ChatColor.RED + "You do not have permission to use region commands."), - NO_PERMISSION_TP("no_permission_tp", ChatColor.RED + "You do not have permission to teleport to other players' protection blocks."), - NO_PERMISSION_HOME("no_permission_home", ChatColor.RED + "You do not have permission to teleport to your protection blocks."), - NO_PERMISSION_UNCLAIM("no_permission_unclaim", ChatColor.RED + "You do not have permission to use the unclaim command."), - NO_PERMISSION_UNCLAIM_REMOTE("no_permission_unclaim_remote", ChatColor.RED + "You do not have permission to use the unclaim remote command."), - NO_PERMISSION_VIEW("no_permission_view", ChatColor.RED + "You do not have permission to use the view command."), - NO_PERMISSION_GIVE("no_permission_give", ChatColor.RED + "You do not have permission to use the give command."), - NO_PERMISSION_GET("no_permission_get", ChatColor.RED + "You do not have permission to use the get command."), - NO_PERMISSION_SETHOME("no_permission_sethome", ChatColor.RED + "You do not have permission to use the sethome command."), - NO_PERMISSION_LIST("no_permission_list", ChatColor.RED + "You do not have permission to use the list command."), - NO_PERMISSION_LIST_OTHERS("no_permission_list_others", ChatColor.RED + "You do not have permission to use the list command for others."), - NO_PERMISSION_NAME("no_permission_name", ChatColor.RED + "You do not have permission to use the name command."), - NO_PERMISSION_SETPARENT("no_permission_setparent", ChatColor.RED + "You do not have permission to use the setparent command."), - NO_PERMISSION_SETPARENT_OTHERS("no_permission_setparent_others", ChatColor.RED + "You do not have permission to inherit from regions you don't own."), - NO_PERMISSION_MERGE("no_permission_merge", ChatColor.RED + "You do not have permission to use /ps merge."), - - ADDED_TO_REGION("psregion.added_to_region", ChatColor.AQUA + "%player%" + ChatColor.GRAY + " has been added to this region."), - ADDED_TO_REGION_SPECIFIC("psregion.added_to_region_specific", ChatColor.AQUA + "%player%" + ChatColor.GRAY + " has been added to region %region%."), - REMOVED_FROM_REGION("psregion.removed_from_region", ChatColor.AQUA + "%player%" + ChatColor.GRAY + " has been removed from region."), - REMOVED_FROM_REGION_SPECIFIC("psregion.removed_from_region_specific", ChatColor.AQUA + "%player%" + ChatColor.GRAY + " has been removed from region %region%."), - NOT_IN_REGION("psregion.not_in_region", ChatColor.RED + "You are not in a protection stones region!"), - PLAYER_NOT_FOUND("psregion.player_not_found", ChatColor.RED + "Player not found."), - NOT_PS_REGION("psregion.not_ps_region", ChatColor.RED + "Not a protection stones region."), - REGION_DOES_NOT_EXIST("psregion.region_does_not_exist", ChatColor.RED + "Region does not exist."), - NO_REGIONS_OWNED("psregion.no_regions_owned", ChatColor.RED + "You don't own any protected regions in this world!"), - NO_REGION_PERMISSION("psregion.no_region_permission", ChatColor.RED + "You do not have permission to do this in this region."), - PROTECTED("psregion.protected", ChatColor.AQUA + "This area is now protected."), - NO_LONGER_PROTECTED("psregion.no_longer_protected", ChatColor.YELLOW + "This area is no longer protected."), - CANT_PROTECT_THAT("psregion.cant_protect_that", ChatColor.RED + "You can't protect that area."), - REACHED_REGION_LIMIT("psregion.reached_region_limit", ChatColor.RED + "You can not have any more protected regions (%limit%)."), - REACHED_PER_BLOCK_REGION_LIMIT("psregion.reached_per_block_region_limit", ChatColor.RED + "You can not have any more regions of this type (%limit%)."), - WORLD_DENIED_CREATE("psregion.world_denied_create", ChatColor.RED + "You can not create protections in this world."), - REGION_OVERLAP("psregion.region_overlap", ChatColor.RED + "You can not place a protection block here as it overlaps another region."), - REGION_TOO_CLOSE("psregion.region_too_close", ChatColor.RED + "Your protection block must be a minimum of %num% blocks from the edge of other regions!"), - REGION_CANT_TELEPORT("psregion.cant_teleport", ChatColor.RED + "Your teleportation was blocked by a protection region!"), - SPECIFY_ID_INSTEAD_OF_ALIAS("psregion.specify_id_instead_of_alias", ChatColor.GRAY + "There were multiple regions found with this name! Please use an ID instead.\n Regions with this name: " + ChatColor.AQUA + "%regions%"), - REGION_NOT_ADJACENT("psregion.region_not_adjacent", ChatColor.RED + "You've passed the limit of non-adjacent regions! Try putting your protection block closer to other regions you already own."), - REGION_NOT_OVERLAPPING("psregion.not_overlapping", ChatColor.RED + "These regions don't overlap each other!"), + PAGE_DOES_NOT_EXIST("page_does_not_exist", "Page does not exist."), + + HELP("help", "===== PS Help =====\n> /ps help"), + HELP_NEXT("help_next", "Do /ps help %page% to go to the next page!", "%page%"), + + COMMAND_REQUIRES_PLAYER_NAME("command_requires_player_name", "This command requires a player name."), + + NO_PERMISSION_TOGGLE("no_permission_toggle", "You don't have permission to use the toggle command."), + NO_PERMISSION_CREATE("no_permission_create", "You don't have permission to place a protection block."), + NO_PERMISSION_CREATE_SPECIFIC("no_permission_create_specific", "You don't have permission to place this protection block type."), + NO_PERMISSION_DESTROY("no_permission_destroy", "You don't have permission to destroy a protection block."), + NO_PERMISSION_MEMBERS("no_permission_members", "You don't have permission to use member commands."), + NO_PERMISSION_OWNERS("no_permission_owners", "You don't have permission to use owner commands."), + NO_PERMISSION_ADMIN("no_permission_admin", "You do not have permission to use that command."), + NO_PERMISSION_COUNT("no_permission_count", "You do not have permission to use that command."), + NO_PERMISSION_COUNT_OTHERS("no_permission_count_others", "You do not have permission to use that command."), + NO_PERMISSION_FLAGS("no_permission_flags", "You do not have permission to use flag commands."), + NO_PERMISSION_PER_FLAG("no_permission_per_flag", "You do not have permission to use that flag."), + NO_PERMISSION_RENT("no_permission_rent", "You do not have permission for renting."), + NO_PERMISSION_TAX("no_permission_tax", "You do not have permission to use the tax command."), + NO_PERMISSION_BUYSELL("no_permission_buysell", "You do not have permission to buy and sell regions."), + NO_PERMISSION_UNHIDE("no_permission_unhide", "You do not have permission to unhide protection blocks."), + NO_PERMISSION_HIDE("no_permission_hide", "You do not have permission to hide protection blocks."), + NO_PERMISSION_INFO("no_permission_info", "You do not have permission to use the region info command."), + NO_PERMISSION_PRIORITY("no_permission_priority", "You do not have permission to use the priority command."), + NO_PERMISSION_REGION("no_permission_region", "You do not have permission to use region commands."), + NO_PERMISSION_TP("no_permission_tp", "You do not have permission to teleport to other players' protection blocks."), + NO_PERMISSION_HOME("no_permission_home", "You do not have permission to teleport to your protection blocks."), + NO_PERMISSION_UNCLAIM("no_permission_unclaim", "You do not have permission to use the unclaim command."), + NO_PERMISSION_UNCLAIM_REMOTE("no_permission_unclaim_remote", "You do not have permission to use the unclaim remote command."), + NO_PERMISSION_VIEW("no_permission_view", "You do not have permission to use the view command."), + NO_PERMISSION_GIVE("no_permission_give", "You do not have permission to use the give command."), + NO_PERMISSION_GET("no_permission_get", "You do not have permission to use the get command."), + NO_PERMISSION_SETHOME("no_permission_sethome", "You do not have permission to use the sethome command."), + NO_PERMISSION_LIST("no_permission_list", "You do not have permission to use the list command."), + NO_PERMISSION_LIST_OTHERS("no_permission_list_others", "You do not have permission to use the list command for others."), + NO_PERMISSION_NAME("no_permission_name", "You do not have permission to use the name command."), + NO_PERMISSION_SETPARENT("no_permission_setparent", "You do not have permission to use the setparent command."), + NO_PERMISSION_SETPARENT_OTHERS("no_permission_setparent_others", "You do not have permission to inherit from regions you don't own."), + NO_PERMISSION_MERGE("no_permission_merge", "You do not have permission to use /ps merge."), + + // --- Region --- + ADDED_TO_REGION("psregion.added_to_region", "%player% has been added to this region.", "%player%"), + ADDED_TO_REGION_SPECIFIC("psregion.added_to_region_specific", "%player% has been added to region %region%.", "%player%", "%region%"), + REMOVED_FROM_REGION("psregion.removed_from_region", "%player% has been removed from region.", "%player%"), + REMOVED_FROM_REGION_SPECIFIC("psregion.removed_from_region_specific", "%player% has been removed from region %region%.", "%player%", "%region%"), + NOT_IN_REGION("psregion.not_in_region", "You are not in a protection stones region!"), + PLAYER_NOT_FOUND("psregion.player_not_found", "Player not found."), + NOT_PS_REGION("psregion.not_ps_region", "Not a protection stones region."), + REGION_DOES_NOT_EXIST("psregion.region_does_not_exist", "Region does not exist."), + NO_REGIONS_OWNED("psregion.no_regions_owned", "You don't own any protected regions in this world!"), + NO_REGION_PERMISSION("psregion.no_region_permission", "You do not have permission to do this in this region."), + PROTECTED("psregion.protected", "This area is now protected."), + NO_LONGER_PROTECTED("psregion.no_longer_protected", "This area is no longer protected."), + CANT_PROTECT_THAT("psregion.cant_protect_that", "You can't protect that area."), + REACHED_REGION_LIMIT("psregion.reached_region_limit", "You can not have any more protected regions (%limit%).", "%limit%"), + REACHED_PER_BLOCK_REGION_LIMIT("psregion.reached_per_block_region_limit", "You can not have any more regions of this type (%limit%).", "%limit%"), + WORLD_DENIED_CREATE("psregion.world_denied_create", "You can not create protections in this world."), + REGION_OVERLAP("psregion.region_overlap", "You can not place a protection block here as it overlaps another region."), + REGION_TOO_CLOSE("psregion.region_too_close", "Your protection block must be a minimum of %num% blocks from the edge of other regions!", "%num%"), + REGION_CANT_TELEPORT("psregion.cant_teleport", "Your teleportation was blocked by a protection region!"), + SPECIFY_ID_INSTEAD_OF_ALIAS("psregion.specify_id_instead_of_alias", "There were multiple regions found with this name! Please use an ID instead.\n Regions with this name: %regions%", "%regions%"), + REGION_NOT_ADJACENT("psregion.region_not_adjacent", "You've passed the limit of non-adjacent regions! Try putting your protection block closer to other regions you already own."), + REGION_NOT_OVERLAPPING("psregion.not_overlapping", "These regions don't overlap each other!"), MULTI_REGION_DOES_NOT_EXIST("psregion.multi_region_does_not_exist", "One of these regions don't exist!"), - NO_REGION_HOLES("psregion.no_region_holes", ChatColor.RED + "Unprotected area detected inside region! This is not allowed!"), - DELETE_REGION_PREVENTED_NO_HOLES("psregion.delete_region_prevented", ChatColor.GRAY + "The region could not be removed, possibly because it creates a hole in the existing region."), - NOT_OWNER("psregion.not_owner", ChatColor.RED + "You are not an owner of this region!"), - CANNOT_MERGE_RENTED_REGION("psregion.cannot_merge_rented_region", ChatColor.RED + "Cannot merge regions because region %region% is in the process of being rented out!"), - NO_PERMISSION_REGION_TYPE("psregion.no_permission_region_type", ChatColor.RED + "You do not have permission to have this region type."), - REGION_HIDDEN("psregion.hidden", ChatColor.GRAY + "The protection block is now hidden."), - MUST_BE_PLACED_IN_EXISTING_REGION("psregion.must_be_placed_in_existing_region", ChatColor.RED + "This must be placed inside of an existing region!"), - REGION_ALREADY_IN_LOCATION_IS_HIDDEN("psregion.already_in_location_is_hidden", ChatColor.RED + "A region already exists in this location (is the protection block hidden?)"), - CANNOT_REMOVE_YOURSELF_LAST_OWNER("psregion.cannot_remove_yourself_last_owner", ChatColor.RED + "You cannot remove yourself as you are the last owner."), - CANNOT_REMOVE_YOURSELF_FROM_ALL_REGIONS("psregion.cannot_remove_yourself_all_regions", ChatColor.RED + "You cannot remove yourself from all of your regions at once, for safety reasons."), - - // ps toggle - TOGGLE_HELP("toggle.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps toggle|on|off"), + NO_REGION_HOLES("psregion.no_region_holes", "Unprotected area detected inside region! This is not allowed!"), + DELETE_REGION_PREVENTED_NO_HOLES("psregion.delete_region_prevented", "The region could not be removed, possibly because it creates a hole in the existing region."), + NOT_OWNER("psregion.not_owner", "You are not an owner of this region!"), + CANNOT_MERGE_RENTED_REGION("psregion.cannot_merge_rented_region", "Cannot merge regions because region %region% is in the process of being rented out!", "%region%"), + NO_PERMISSION_REGION_TYPE("psregion.no_permission_region_type", "You do not have permission to have this region type."), + REGION_HIDDEN("psregion.hidden", "The protection block is now hidden."), + MUST_BE_PLACED_IN_EXISTING_REGION("psregion.must_be_placed_in_existing_region", "This must be placed inside of an existing region!"), + REGION_ALREADY_IN_LOCATION_IS_HIDDEN("psregion.already_in_location_is_hidden", "A region already exists in this location (is the protection block hidden?)"), + CANNOT_REMOVE_YOURSELF_LAST_OWNER("psregion.cannot_remove_yourself_last_owner", "You cannot remove yourself as you are the last owner."), + CANNOT_REMOVE_YOURSELF_FROM_ALL_REGIONS("psregion.cannot_remove_yourself_all_regions", "You cannot remove yourself from all of your regions at once, for safety reasons."), + + // --- Toggle --- + TOGGLE_HELP("toggle.help", "> /ps toggle|on|off"), TOGGLE_HELP_DESC("toggle.help_desc", "Use this command to turn on or off placement of protection blocks."), - TOGGLE_ON("toggle.toggle_on", ChatColor.AQUA + "Protection block placement turned on."), - TOGGLE_OFF("toggle.toggle_off", ChatColor.AQUA + "Protection block placement turned off."), + TOGGLE_ON("toggle.toggle_on", "Protection block placement turned on."), + TOGGLE_OFF("toggle.toggle_off", "Protection block placement turned off."), - // ps count - COUNT_HELP("count.count_help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps count [player (optional)]"), + // --- Count --- + COUNT_HELP("count.count_help", "> /ps count [player (optional)]"), COUNT_HELP_DESC("count.count_help_desc", "Count the number of regions you own or another player."), - PERSONAL_REGION_COUNT("count.personal_region_count", ChatColor.GRAY + "Your region count in this world: " + ChatColor.AQUA + "%num%"), - PERSONAL_REGION_COUNT_MERGED("count.personal_region_count_merged", ChatColor.GRAY + "- Including each merged region: " + ChatColor.AQUA + "%num%"), - OTHER_REGION_COUNT("count.other_region_count", ChatColor.GRAY + "%player%'s region count in this world: " + ChatColor.AQUA + "%num%"), - OTHER_REGION_COUNT_MERGED("count.other_region_count_merged", ChatColor.GRAY + "- Including each merged region: " + ChatColor.AQUA + "%num%"), + PERSONAL_REGION_COUNT("count.personal_region_count", "Your region count in this world: %num%", "%num%"), + PERSONAL_REGION_COUNT_MERGED("count.personal_region_count_merged", "- Including each merged region: %num%", "%num%"), + OTHER_REGION_COUNT("count.other_region_count", "%player%'s region count in this world: %num%", "%player%", "%num%"), + OTHER_REGION_COUNT_MERGED("count.other_region_count_merged", "- Including each merged region: %num%", "%num%"), - // ps flag - FLAG_HELP("flag.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps flag [flagname] [value|null|default]"), + // --- Flag --- + FLAG_HELP("flag.help", "> /ps flag [flagname] [value|null|default]"), FLAG_HELP_DESC("flag.help_desc", "Use this command to set a flag in your protected region."), - FLAG_SET("flag.flag_set", ChatColor.AQUA + "%flag%" + ChatColor.GRAY + " flag has been set."), - FLAG_NOT_SET("flag.flag_not_set", ChatColor.AQUA + "%flag%" + ChatColor.GRAY + " flag has " + ChatColor.RED + "not" + ChatColor.GRAY + " been set. Check your values again."), - FLAG_PREVENT_EXPLOIT("flag.flag_prevent_exploit", ChatColor.RED + "This has been disabled to prevent exploits."), - FLAG_PREVENT_EXPLOIT_HOVER("flag.flag_prevent_exploit_hover", ChatColor.RED + "Disabled for security reasons."), - FLAG_GUI_HEADER("flag.gui_header", ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "=====" + ChatColor.RESET + " Flags (click to change) " + ChatColor.DARK_GRAY + ChatColor.STRIKETHROUGH + "====="), - FLAG_GUI_HOVER_SET("flag.gui_hover_set", ChatColor.AQUA + "Click to set."), - FLAG_GUI_HOVER_SET_TEXT("flag.gui_hover_set_text", ChatColor.AQUA + "Click to change." + ChatColor.WHITE + "\nCurrent value:\n%value%"), - FLAG_GUI_HOVER_CHANGE_GROUP("flag.hover_change_group", "Click to set this flag to apply to only %group%."), - FLAG_GUI_HOVER_CHANGE_GROUP_NULL("flag.hover_change_group_null", ChatColor.RED + "You must set this flag to a value before changing the group."), - - // ps rent - RENT_HELP("rent.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps rent"), + FLAG_SET("flag.flag_set", "%flag% flag has been set.", "%flag%"), + FLAG_NOT_SET("flag.flag_not_set", "%flag% flag has not been set. Check your values again.", "%flag%"), + FLAG_PREVENT_EXPLOIT("flag.flag_prevent_exploit", "This has been disabled to prevent exploits."), + FLAG_PREVENT_EXPLOIT_HOVER("flag.flag_prevent_exploit_hover", "Disabled for security reasons."), + FLAG_GUI_HEADER("flag.gui_header", "===== Flags (click to change) ====="), + FLAG_GUI_HOVER_SET("flag.gui_hover_set", "Click to set."), + FLAG_GUI_HOVER_SET_TEXT("flag.gui_hover_set_text", "Click to change.\nCurrent value:\n%value%", "%value%"), + FLAG_GUI_HOVER_CHANGE_GROUP("flag.hover_change_group", "Click to set this flag to apply to only %group%.", "%group%"), + FLAG_GUI_HOVER_CHANGE_GROUP_NULL("flag.hover_change_group_null", "You must set this flag to a value before changing the group."), + + // --- Rent --- + RENT_HELP("rent.help", "> /ps rent"), RENT_HELP_DESC("rent.help_desc", "Use this command to manage rents (buying and selling)."), - RENT_HELP_HEADER("rent.help_header", ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "=====" + ChatColor.RESET + " Rent Help " + ChatColor.DARK_GRAY + ChatColor.STRIKETHROUGH + "====="), - RENT_ALREADY_RENTING("rent.already_renting", ChatColor.RED + "The region is already being rented out! You must stop leasing the region first."), - RENT_NOT_RENTED("rent.not_rented", ChatColor.RED + "This region is not being rented."), - RENT_LEASE_SUCCESS("rent.lease_success", ChatColor.AQUA + "Region leasing terms set:\n" + ChatColor.AQUA + "Price: " + ChatColor.GRAY + "%price%\n" + ChatColor.AQUA + "Payment Term: " + ChatColor.GRAY + "%period%"), - RENT_STOPPED("rent.stopped", ChatColor.AQUA + "Leasing stopped."), - RENT_EVICTED("rent.evicted", ChatColor.GRAY + "Evicted tenant %tenant%."), - RENT_NOT_RENTING("rent.not_renting", ChatColor.RED + "This region is not being rented out to tenants."), - RENT_PAID_LANDLORD("rent.paid_landlord", ChatColor.AQUA + "%tenant%" + ChatColor.GRAY + " has paid " + ChatColor.AQUA + "$%price%" + ChatColor.GRAY + " for renting out " + ChatColor.AQUA + "%region%" + ChatColor.GRAY + "."), - RENT_PAID_TENANT("rent.paid_tenant", ChatColor.GRAY + "Paid " + ChatColor.AQUA + "$%price%" + ChatColor.GRAY + " to " + ChatColor.AQUA + "%landlord%" + ChatColor.GRAY + " for region " + ChatColor.AQUA + "%region%" + ChatColor.GRAY + "."), - RENT_RENTING_LANDLORD("rent.renting_landlord", ChatColor.AQUA + "%player%" + ChatColor.GRAY + " is now renting out region " + ChatColor.AQUA + "%region%" + ChatColor.GRAY + "."), - RENT_RENTING_TENANT("rent.renting_tenant", ChatColor.GRAY + "You are now renting out region " + ChatColor.AQUA + "%region%" + ChatColor.GRAY + " for " + ChatColor.AQUA + "%price%" + ChatColor.GRAY + " per " + ChatColor.AQUA + "%period%" + ChatColor.GRAY + "."), - RENT_NOT_TENANT("rent.not_tenant", ChatColor.RED + "You are not the tenant of this region!"), - RENT_TENANT_STOPPED_LANDLORD("rent.tenant_stopped_landlord", ChatColor.AQUA + "%player%" + ChatColor.GRAY + " has stopped renting out region " + ChatColor.AQUA + "%region%" + ChatColor.GRAY + ". It is now available for others to rent."), - RENT_TENANT_STOPPED_TENANT("rent.tenant_stopped_tenant", ChatColor.AQUA + "You have stopped renting out region %region%."), - RENT_BEING_SOLD("rent.being_sold", ChatColor.RED + "The region is being sold! Do /ps sell stop first."), - RENT_EVICT_NO_MONEY_TENANT("rent.evict_no_money_tenant", ChatColor.GRAY + "You have been " + ChatColor.RED + "evicted" + ChatColor.GRAY + " from region " + ChatColor.AQUA + "%region%" + ChatColor.GRAY + " because you do not have enough money (%price%) to pay for rent."), - RENT_EVICT_NO_MONEY_LANDLORD("rent.evict_no_money_landlord", ChatColor.AQUA + "%tenant%" + ChatColor.GRAY + " has been " + ChatColor.RED + "evicted" + ChatColor.GRAY + " from region " + ChatColor.AQUA + "%region%" + ChatColor.GRAY + " because they are unable to afford rent."), - RENT_CANNOT_RENT_OWN_REGION("rent.cannot_rent_own_region", ChatColor.RED + "You cannot rent your own region!"), - RENT_REACHED_LIMIT("rent.reached_limit", ChatColor.RED + "You've reached the limit of regions you are allowed to rent!"), - RENT_PRICE_TOO_LOW("rent.price_too_low", ChatColor.RED + "The rent price is too low (must be larger than %price%)."), - RENT_PRICE_TOO_HIGH("rent.price_too_high", ChatColor.RED + "The rent price is too high (must be lower than %price%)."), - RENT_PERIOD_TOO_SHORT("rent.period_too_short", ChatColor.RED + "The rent period is too short (must be longer than %period% seconds)."), - RENT_PERIOD_TOO_LONG("rent.period_too_long", ChatColor.RED + "The rent period is too long (must be shorter than %period% seconds)."), - RENT_PERIOD_INVALID("rent.period_invalid", ChatColor.RED + "Invalid period format! Example: 24h for once a day."), - RENT_CANNOT_BREAK_WHILE_RENTING("rent.cannot_break_while_renting", ChatColor.RED + "You cannot break the region when it is being rented out."), - - // ps tax - TAX_HELP("tax.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps tax"), + RENT_HELP_HEADER("rent.help_header", "===== Rent Help ====="), + RENT_ALREADY_RENTING("rent.already_renting", "The region is already being rented out! You must stop leasing the region first."), + RENT_NOT_RENTED("rent.not_rented", "This region is not being rented."), + RENT_LEASE_SUCCESS("rent.lease_success", "Region leasing terms set:\nPrice: %price%\nPayment Term: %period%", "%price%", "%period%"), + RENT_STOPPED("rent.stopped", "Leasing stopped."), + RENT_EVICTED("rent.evicted", "Evicted tenant %tenant%.", "%tenant%"), + RENT_NOT_RENTING("rent.not_renting", "This region is not being rented out to tenants."), + RENT_PAID_LANDLORD("rent.paid_landlord", "%tenant% has paid $%price% for renting out %region%.", "%tenant%", "%price%", "%region%"), + RENT_PAID_TENANT("rent.paid_tenant", "Paid $%price% to %landlord% for region %region%.", "%price%", "%landlord%", "%region%"), + RENT_RENTING_LANDLORD("rent.renting_landlord", "%player% is now renting out region %region%.", "%player%", "%region%"), + RENT_RENTING_TENANT("rent.renting_tenant", "You are now renting out region %region% for %price% per %period%.", "%region%", "%price%", "%period%"), + RENT_NOT_TENANT("rent.not_tenant", "You are not the tenant of this region!"), + RENT_TENANT_STOPPED_LANDLORD("rent.tenant_stopped_landlord", "%player% has stopped renting out region %region%. It is now available for others to rent.", "%player%", "%region%"), + RENT_TENANT_STOPPED_TENANT("rent.tenant_stopped_tenant", "You have stopped renting out region %region%.", "%region%"), + RENT_BEING_SOLD("rent.being_sold", "The region is being sold! Do /ps sell stop first."), + RENT_EVICT_NO_MONEY_TENANT("rent.evict_no_money_tenant", "You have been evicted from region %region% because you do not have enough money (%price%) to pay for rent.", "%region%", "%price%"), + RENT_EVICT_NO_MONEY_LANDLORD("rent.evict_no_money_landlord", "%tenant% has been evicted from region %region% because they are unable to afford rent.", "%tenant%", "%region%"), + RENT_CANNOT_RENT_OWN_REGION("rent.cannot_rent_own_region", "You cannot rent your own region!"), + RENT_REACHED_LIMIT("rent.reached_limit", "You've reached the limit of regions you are allowed to rent!"), + RENT_PRICE_TOO_LOW("rent.price_too_low", "The rent price is too low (must be larger than %price%).", "%price%"), + RENT_PRICE_TOO_HIGH("rent.price_too_high", "The rent price is too high (must be lower than %price%).", "%price%"), + RENT_PERIOD_TOO_SHORT("rent.period_too_short", "The rent period is too short (must be longer than %period% seconds).", "%period%"), + RENT_PERIOD_TOO_LONG("rent.period_too_long", "The rent period is too long (must be shorter than %period% seconds).", "%period%"), + RENT_PERIOD_INVALID("rent.period_invalid", "Invalid period format! Example: 24h for once a day."), + RENT_CANNOT_BREAK_WHILE_RENTING("rent.cannot_break_while_renting", "You cannot break the region when it is being rented out."), + + // --- Tax --- + TAX_HELP("tax.help", "> /ps tax"), TAX_HELP_DESC("tax.help_desc", "Use this command to manage and pay taxes."), - TAX_HELP_HEADER("tax.help_header", ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "=====" + ChatColor.RESET + " Taxes Help " + ChatColor.DARK_GRAY + ChatColor.STRIKETHROUGH + "====="), - TAX_DISABLED_REGION("tax.disabled_region", ChatColor.RED + "Taxes are disabled for this region."), - TAX_SET_AS_AUTOPAYER("tax.set_as_autopayer", ChatColor.GRAY + "Taxes for region " + ChatColor.AQUA + "%region%" + ChatColor.GRAY + " will now be automatically paid by you."), - TAX_SET_NO_AUTOPAYER("tax.set_no_autopayer", ChatColor.GRAY + "Taxes for region " + ChatColor.AQUA + "%region%" + ChatColor.GRAY + " now have to be manually paid for."), - TAX_PAID("tax.paid", ChatColor.GRAY + "Paid " + ChatColor.AQUA + "$%amount%" + ChatColor.GRAY + " in taxes for region " + ChatColor.AQUA + "%region%" + ChatColor.GRAY + "."), - TAX_INFO_HEADER("tax.info_header", ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "=====" + ChatColor.RESET + " Tax Info (click for more info) " + ChatColor.DARK_GRAY + ChatColor.STRIKETHROUGH + "====="), - TAX_JOIN_MSG_PENDING_PAYMENTS("tax.join_msg_pending_payments", ChatColor.GRAY + "You have " + ChatColor.AQUA + "$%money%" + ChatColor.GRAY + " in tax payments due on your regions!\nView them with /ps tax info."), - TAX_PLAYER_REGION_INFO("tax.player_region_info", ChatColor.GRAY + "> " + ChatColor.AQUA + "%region%" + ChatColor.GRAY + " - " + ChatColor.DARK_AQUA + "$%money% due"), - TAX_PLAYER_REGION_INFO_AUTOPAYER("tax.player_region_info_autopayer", ChatColor.GRAY + "> " + ChatColor.AQUA + "%region%" + ChatColor.GRAY + " - " + ChatColor.DARK_AQUA + "$%money% due" + ChatColor.GRAY + " (you autopay)"), + TAX_HELP_HEADER("tax.help_header", "===== Taxes Help ====="), + TAX_DISABLED_REGION("tax.disabled_region", "Taxes are disabled for this region."), + TAX_SET_AS_AUTOPAYER("tax.set_as_autopayer", "Taxes for region %region% will now be automatically paid by you.", "%region%"), + TAX_SET_NO_AUTOPAYER("tax.set_no_autopayer", "Taxes for region %region% now have to be manually paid for.", "%region%"), + TAX_PAID("tax.paid", "Paid $%amount% in taxes for region %region%.", "%amount%", "%region%"), + TAX_INFO_HEADER("tax.info_header", "===== Tax Info (click for more info) ====="), + TAX_JOIN_MSG_PENDING_PAYMENTS("tax.join_msg_pending_payments", "You have $%money% in tax payments due on your regions!\nView them with /ps tax info.", "%money%"), + TAX_PLAYER_REGION_INFO("tax.player_region_info", "> %region% - $%money% due", "%region%", "%money%"), + TAX_PLAYER_REGION_INFO_AUTOPAYER("tax.player_region_info_autopayer", "> %region% - $%money% due (you autopay)", "%region%", "%money%"), TAX_CLICK_TO_SHOW_MORE_INFO("tax.click_to_show_more_info", "Click to show more information."), - TAX_REGION_INFO_HEADER("tax.region_info_header", ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "=====" + ChatColor.RESET + " %region% Tax Info " + ChatColor.DARK_GRAY + ChatColor.STRIKETHROUGH + "====="), - TAX_REGION_INFO("tax.region_info", ChatColor.BLUE + "Tax Rate: " + ChatColor.GRAY + "$%taxrate% (sum of all merged regions)" + "\n" - + ChatColor.BLUE + "Time between tax cycles: " + ChatColor.GRAY + "%taxperiod%" + "\n" - + ChatColor.BLUE + "Time to pay taxes after cycle: " + ChatColor.GRAY + "%taxpaymentperiod%" + "\n" - + ChatColor.BLUE + "Tax Autopayer: " + ChatColor.GRAY + "%taxautopayer%" + "\n" - + ChatColor.BLUE + "Taxes Owed: " + ChatColor.GRAY + "$%taxowed%"), - TAX_NEXT("tax.next_page", ChatColor.GRAY + "Do /ps tax info -p %page% to go to the next page!"), - - // ps buy - BUY_HELP("buy.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps buy"), + TAX_REGION_INFO_HEADER("tax.region_info_header", "===== %region% Tax Info =====", "%region%"), + TAX_REGION_INFO("tax.region_info", "Tax Rate: $%taxrate% (sum of all merged regions)\n" + + "Time between tax cycles: %taxperiod%\n" + + "Time to pay taxes after cycle: %taxpaymentperiod%\n" + + "Tax Autopayer: %taxautopayer%\n" + + "Taxes Owed: $%taxowed%", "%taxrate%", "%taxperiod%", "%taxpaymentperiod%", "%taxautopayer%", "%taxowed%"), + TAX_NEXT("tax.next_page", "Do /ps tax info -p %page% to go to the next page!", "%page%"), + + // --- Buy --- + BUY_HELP("buy.help", "> /ps buy"), BUY_HELP_DESC("buy.help_desc", "Buy the region you are currently in."), - BUY_NOT_FOR_SALE("buy.not_for_sale", ChatColor.RED + "This region is not for sale."), - BUY_STOP_SELL("buy.stop_sell", ChatColor.GRAY + "The region is now not for sale."), - BUY_SOLD_BUYER("buy.sold_buyer", ChatColor.GRAY + "Bought region " + ChatColor.AQUA + "%region%" + ChatColor.GRAY + " for " + ChatColor.AQUA + "$%price%" + ChatColor.GRAY + " from " + ChatColor.AQUA + "%player%" + ChatColor.GRAY + "."), - BUY_SOLD_SELLER("buy.sold_seller", ChatColor.GRAY + "Sold region " + ChatColor.AQUA + "%region%" + ChatColor.GRAY + " for " + ChatColor.AQUA + "$%price%" + ChatColor.GRAY + " to " + ChatColor.AQUA + "%player%" + ChatColor.GRAY + "."), + BUY_NOT_FOR_SALE("buy.not_for_sale", "This region is not for sale."), + BUY_STOP_SELL("buy.stop_sell", "The region is now not for sale."), + BUY_SOLD_BUYER("buy.sold_buyer", "Bought region %region% for $%price% from %player%.", "%region%", "%price%", "%player%"), + BUY_SOLD_SELLER("buy.sold_seller", "Sold region %region% for $%price% to %player%.", "%region%", "%price%", "%player%"), - // ps sell - SELL_HELP("sell.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps sell [price|stop]"), + // --- Sell --- + SELL_HELP("sell.help", "> /ps sell [price|stop]"), SELL_HELP_DESC("sell.help_desc", "Sell the region you are currently in."), - SELL_RENTED_OUT("sell.rented_out", ChatColor.RED + "The region is being rented out! You must stop renting it out to sell."), - SELL_FOR_SALE("sell.for_sale", ChatColor.GRAY + "The region is now for sale for " + ChatColor.AQUA + "$%price%" + ChatColor.GRAY + "."), + SELL_RENTED_OUT("sell.rented_out", "The region is being rented out! You must stop renting it out to sell."), + SELL_FOR_SALE("sell.for_sale", "The region is now for sale for $%price%.", "%price%"), - // ps hide/unhide - VISIBILITY_HIDE_HELP("visibility.hide_help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps hide"), + // --- Hide/Unhide --- + VISIBILITY_HIDE_HELP("visibility.hide_help", "> /ps hide"), VISIBILITY_HIDE_HELP_DESC("visibility.hide_help_desc", "Use this command to hide or unhide your protection block."), - VISIBILITY_UNHIDE_HELP("visibility.unhide_help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps unhide"), + VISIBILITY_UNHIDE_HELP("visibility.unhide_help", "> /ps unhide"), VISIBILITY_UNHIDE_HELP_DESC("visibility.unhide_help_desc", "Use this command to hide or unhide your protection block."), - ALREADY_NOT_HIDDEN("visibility.already_not_hidden", ChatColor.GRAY + "The protection stone doesn't appear hidden..."), - ALREADY_HIDDEN("visibility.already_hidden", ChatColor.GRAY + "The protection stone appears to already be hidden..."), + ALREADY_NOT_HIDDEN("visibility.already_not_hidden", "The protection stone doesn't appear hidden..."), + ALREADY_HIDDEN("visibility.already_hidden", "The protection stone appears to already be hidden..."), - // ps info - INFO_HELP("info.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps info members|owners|flags"), + // --- Info --- + INFO_HELP("info.help", "> /ps info members|owners|flags"), INFO_HELP_DESC("info.help_desc", "Use this command inside a ps region to see more information about it."), - INFO_HEADER("info.header", ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "=====" + ChatColor.RESET + " PS Info " + ChatColor.DARK_GRAY + ChatColor.STRIKETHROUGH + "====="), - INFO_TYPE2("info.type2", "&9Type: &7%type%", "%type%"), + INFO_HEADER("info.header", "===== PS Info ====="), + INFO_TYPE2("info.type2", "Type: %type%", "%type%"), INFO_MAY_BE_MERGED("info.may_be_merged", "(may be merged with other types)"), - INFO_MERGED2("info.merged2", ChatColor.BLUE + "Merged regions: " + ChatColor.GRAY + "%merged%", "%merged%"), - INFO_MEMBERS2("info.members2", "&9Members: &7%members%", "%members%"), - INFO_NO_MEMBERS("info.no_members", ChatColor.RED + "(no members)"), - INFO_OWNERS2("info.owners2", "&9Owners: &7%owners%", "%owners%"), - INFO_NO_OWNERS("info.no_owners", ChatColor.RED + "(no owners)"), - INFO_FLAGS2("info.flags2", "&9Flags: &7%flags%", "%flags%"), + INFO_MERGED2("info.merged2", "Merged regions: %merged%", "%merged%"), + INFO_MEMBERS2("info.members2", "Members: %members%", "%members%"), + INFO_NO_MEMBERS("info.no_members", "(no members)"), + INFO_OWNERS2("info.owners2", "Owners: %owners%", "%owners%"), + INFO_NO_OWNERS("info.no_owners", "(no owners)"), + INFO_FLAGS2("info.flags2", "Flags: %flags%", "%flags%"), INFO_NO_FLAGS("info.no_flags", "(none)"), - INFO_REGION2("info.region2", "&9Region: &b%region%", "%region%"), - INFO_PRIORITY2("info.priority2", "&9Priority: &b%priority%", "%priority%"), - INFO_PARENT2("info.parent2", "&9Parent: &b%parentregion%", "%parentregion%"), - INFO_BOUNDS_XYZ("info.bounds_xyz", "&9Bounds: &b(%minx%,%miny%,%minz%) -> (%maxx%,%maxy%,%maxz%)", - "%minx%", "%miny%", "%minz%", "%maxx%", "%maxy%", "%maxz%" - ), - INFO_BOUNDS_XZ("info.bounds_xz", "&9Bounds: &b(%minx%, %minz%) -> (%maxx%, %maxz%)", - "%minx%", "%minz%", "%maxx%", "%maxz%" - ), - INFO_SELLER2("info.seller2", "&9Seller: &7%seller%", "%seller%"), - INFO_PRICE2("info.price2", "&9Price: &7%price%", "%price%"), - INFO_TENANT2("info.tenant2", "&9Tenant: &7%tenant%", "%tenant%"), - INFO_LANDLORD2("info.landlord2", "&9Landlord: &7%landlord%", "%landlord%"), - INFO_RENT2("info.rent2", "&9Rent: &7%rent%", "%rent%"), - INFO_AVAILABLE_FOR_SALE("info.available_for_sale", ChatColor.AQUA + "Region available for sale!"), - INFO_AVAILABLE_FOR_RENT("info.available_for_rent", ChatColor.AQUA + "Region available for rent!"), - - // ps priority - PRIORITY_HELP("priority.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps priority [number|null]"), + INFO_REGION2("info.region2", "Region: %region%", "%region%"), + INFO_PRIORITY2("info.priority2", "Priority: %priority%", "%priority%"), + INFO_PARENT2("info.parent2", "Parent: %parentregion%", "%parentregion%"), + INFO_BOUNDS_XYZ("info.bounds_xyz", "Bounds: (%minx%,%miny%,%minz%) -> (%maxx%,%maxy%,%maxz%)", + "%minx%", "%miny%", "%minz%", "%maxx%", "%maxy%", "%maxz%"), + INFO_BOUNDS_XZ("info.bounds_xz", "Bounds: (%minx%, %minz%) -> (%maxx%, %maxz%)", + "%minx%", "%minz%", "%maxx%", "%maxz%"), + INFO_SELLER2("info.seller2", "Seller: %seller%", "%seller%"), + INFO_PRICE2("info.price2", "Price: %price%", "%price%"), + INFO_TENANT2("info.tenant2", "Tenant: %tenant%", "%tenant%"), + INFO_LANDLORD2("info.landlord2", "Landlord: %landlord%", "%landlord%"), + INFO_RENT2("info.rent2", "Rent: %rent%", "%rent%"), + INFO_AVAILABLE_FOR_SALE("info.available_for_sale", "Region available for sale!"), + INFO_AVAILABLE_FOR_RENT("info.available_for_rent", "Region available for rent!"), + + // --- Priority --- + PRIORITY_HELP("priority.help", "> /ps priority [number|null]"), PRIORITY_HELP_DESC("priority.help_desc", "Use this command to set your region's priority."), - PRIORITY_INFO("priority.info", ChatColor.GRAY + "Priority: %priority%"), - PRIORITY_SET("priority.set", ChatColor.YELLOW + "Priority has been set."), - PRIORITY_ERROR("priority.error", ChatColor.RED + "Error parsing input, check it again?"), + PRIORITY_INFO("priority.info", "Priority: %priority%", "%priority%"), + PRIORITY_SET("priority.set", "Priority has been set."), + PRIORITY_ERROR("priority.error", "Error parsing input, check it again?"), - // ps region - REGION_HELP("region.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps region [list|remove|disown] [playername]"), + // --- Region (admin over players) --- + REGION_HELP("region.help", "> /ps region [list|remove|disown] [playername]"), REGION_HELP_DESC("region.help_desc", "Use this command to find information or edit other players' (or your own) protected regions."), - REGION_NOT_FOUND_FOR_PLAYER("region.not_found_for_player", ChatColor.GRAY + "No regions found for %player% in this world."), - REGION_LIST("region.list", ChatColor.GRAY + "%player%'s regions in this world: " + ChatColor.AQUA + "%regions%"), - REGION_REMOVE("region.remove", ChatColor.YELLOW + "%player%'s regions have been removed in this world, and they have been removed from regions they co-owned."), - REGION_DISOWN("region.disown", ChatColor.YELLOW + "%player% has been removed as owner from all regions on this world."), - REGION_ERROR_SEARCH("region.error_search", ChatColor.RED + "Error while searching for %player%'s regions. Please make sure you have entered the correct name."), - - // ps tp - TP_HELP("tp.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps tp [id/player] [num (optional)]"), + REGION_NOT_FOUND_FOR_PLAYER("region.not_found_for_player", "No regions found for %player% in this world.", "%player%"), + REGION_LIST("region.list", "%player%'s regions in this world: %regions%", "%player%", "%regions%"), + REGION_REMOVE("region.remove", "%player%'s regions have been removed in this world, and they have been removed from regions they co-owned.", "%player%"), + REGION_DISOWN("region.disown", "%player% has been removed as owner from all regions on this world.", "%player%"), + REGION_ERROR_SEARCH("region.error_search", "Error while searching for %player%'s regions. Please make sure you have entered the correct name.", "%player%"), + + // --- TP --- + TP_HELP("tp.help", "> /ps tp [id/player] [num (optional)]"), TP_HELP_DESC("tp.help_desc", "Teleports you to one of a given player's regions."), - NUMBER_ABOVE_ZERO("tp.number_above_zero", ChatColor.RED + "Please enter a number above 0."), - TP_VALID_NUMBER("tp.valid_number", ChatColor.RED + "Please enter a valid number."), - ONLY_HAS_REGIONS("tp.only_has_regions", ChatColor.RED + "%player% only has %num% protected regions in this world!"), - TPING("tp.tping", ChatColor.GREEN + "Teleporting..."), - TP_ERROR_NAME("tp.error_name", ChatColor.RED + "Error in teleporting to protected region! (parsing WG region name error)"), - TP_ERROR_TP("tp.error_tp", ChatColor.RED + "Error in finding the region to teleport to!"), - TP_IN_SECONDS("tp.in_seconds", ChatColor.GRAY + "Teleporting in " + ChatColor.AQUA + "%seconds%" + ChatColor.GRAY + " seconds."), - TP_CANCELLED_MOVED("tp.cancelled_moved", ChatColor.RED + "Teleport cancelled. You moved!"), - - // ps home - HOME_HELP("home.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps home [name/id]"), + NUMBER_ABOVE_ZERO("tp.number_above_zero", "Please enter a number above 0."), + TP_VALID_NUMBER("tp.valid_number", "Please enter a valid number."), + ONLY_HAS_REGIONS("tp.only_has_regions", "%player% only has %num% protected regions in this world!", "%player%", "%num%"), + TPING("tp.tping", "Teleporting..."), + TP_ERROR_NAME("tp.error_name", "Error in teleporting to protected region! (parsing WG region name error)"), + TP_ERROR_TP("tp.error_tp", "Error in finding the region to teleport to!"), + TP_IN_SECONDS("tp.in_seconds", "Teleporting in %seconds% seconds.", "%seconds%"), + TP_CANCELLED_MOVED("tp.cancelled_moved", "Teleport cancelled. You moved!"), + + // --- Home --- + HOME_HELP("home.help", "> /ps home [name/id]"), HOME_HELP_DESC("home.help_desc", "Teleports you to one of your protected regions."), - HOME_HEADER("home.header", ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "=====" + ChatColor.RESET + " Homes (click to teleport) " + ChatColor.DARK_GRAY + ChatColor.STRIKETHROUGH + "====="), + HOME_HEADER("home.header", "===== Homes (click to teleport) ====="), HOME_CLICK_TO_TP("home.click_to_tp", "Click to teleport!"), - HOME_NEXT("home.next_page", ChatColor.GRAY + "Do /ps home -p %page% to go to the next page!"), + HOME_NEXT("home.next_page", "Do /ps home -p %page% to go to the next page!", "%page%"), - // ps unclaim - UNCLAIM_HELP("unclaim.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps unclaim"), + // --- Unclaim --- + UNCLAIM_HELP("unclaim.help", "> /ps unclaim"), UNCLAIM_HELP_DESC("unclaim.help_desc", "Use this command to pickup a placed protection stone and remove the region."), - UNCLAIM_HEADER("unclaim.header",ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "=====" + ChatColor.RESET + " Unclaim (click to unclaim) " + ChatColor.DARK_GRAY + ChatColor.STRIKETHROUGH + "====="), + UNCLAIM_HEADER("unclaim.header", "===== Unclaim (click to unclaim) ====="), - // ps view - VIEW_HELP("view.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps view"), + // --- View --- + VIEW_HELP("view.help", "> /ps view"), VIEW_HELP_DESC("view.help_desc", "Use this command to view the borders of a protected region."), - VIEW_COOLDOWN("view.cooldown", ChatColor.RED + "Please wait a while before using /ps view again."), - VIEW_GENERATING("view.generating", ChatColor.GRAY + "Generating border..."), - VIEW_GENERATE_DONE("view.generate_done", ChatColor.GREEN + "Done! The border will disappear after 30 seconds!"), - VIEW_REMOVING("view.removing", ChatColor.AQUA + "Removing border...\n" + ChatColor.GREEN + "If you still see ghost blocks, relog!"), + VIEW_COOLDOWN("view.cooldown", "Please wait a while before using /ps view again."), + VIEW_GENERATING("view.generating", "Generating border..."), + VIEW_GENERATE_DONE("view.generate_done", "Done! The border will disappear after 30 seconds!"), + VIEW_REMOVING("view.removing", "Removing border...\nIf you still see ghost blocks, relog!"), - // ps admin - ADMIN_HELP("admin.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps admin"), + // --- Admin --- + ADMIN_HELP("admin.help", "> /ps admin"), ADMIN_HELP_DESC("admin.help_desc", "Do /ps admin help for more information."), - ADMIN_CLEANUP_HEADER("admin.cleanup_header", ChatColor.YELLOW + "Cleanup %arg% %days% days\n================"), - ADMIN_CLEANUP_FOOTER("admin.cleanup_footer", ChatColor.YELLOW + "================\nCompleted %arg% cleanup."), - ADMIN_HIDE_TOGGLED("admin.hide_toggled", ChatColor.YELLOW + "All protection stones have been %message% in this world."), - ADMIN_LAST_LOGON("admin.last_logon", ChatColor.YELLOW + "%player% last played %days% days ago."), - ADMIN_IS_BANNED("admin.is_banned", ChatColor.YELLOW + "%player% is banned."), - ADMIN_ERROR_PARSING("admin.error_parsing", ChatColor.RED + "Error parsing days, are you sure it is a number?"), - ADMIN_CONSOLE_WORLD("admin.console_world", ChatColor.RED + "Please specify the world as the last parameter."), - ADMIN_LASTLOGONS_HEADER("admin.lastlogons_header", ChatColor.YELLOW + "%days% Days Plus:\n================"), - ADMIN_LASTLOGONS_LINE("admin.lastlogons_line", ChatColor.YELLOW + "%player% %time% days"), - ADMIN_LASTLOGONS_FOOTER("admin.lastlogons_footer", ChatColor.YELLOW + "================\n%count% Total Players Shown\n%checked% Total Players Checked"), - - // ps reload - RELOAD_HELP("reload.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps reload"), + ADMIN_CLEANUP_HEADER("admin.cleanup_header", "Cleanup %arg% %days% days\n================", "%arg%", "%days%"), + ADMIN_CLEANUP_FOOTER("admin.cleanup_footer", "================\nCompleted %arg% cleanup.", "%arg%"), + ADMIN_HIDE_TOGGLED("admin.hide_toggled", "All protection stones have been %message% in this world.", "%message%"), + ADMIN_LAST_LOGON("admin.last_logon", "%player% last played %days% days ago.", "%player%", "%days%"), + ADMIN_IS_BANNED("admin.is_banned", "%player% is banned.", "%player%"), + ADMIN_ERROR_PARSING("admin.error_parsing", "Error parsing days, are you sure it is a number?"), + ADMIN_CONSOLE_WORLD("admin.console_world", "Please specify the world as the last parameter."), + ADMIN_LASTLOGONS_HEADER("admin.lastlogons_header", "%days% Days Plus:\n================", "%days%"), + ADMIN_LASTLOGONS_LINE("admin.lastlogons_line", "%player% %time% days", "%player%", "%time%"), + ADMIN_LASTLOGONS_FOOTER("admin.lastlogons_footer", "================\n%count% Total Players Shown\n%checked% Total Players Checked", "%count%", "%checked%"), + + // --- Reload --- + RELOAD_HELP("reload.help", "> /ps reload"), RELOAD_HELP_DESC("reload.help_desc", "Reload settings from the config."), - RELOAD_START("reload.start", ChatColor.AQUA + "Reloading config..."), - RELOAD_COMPLETE("reload.complete", ChatColor.AQUA + "Completed config reload!"), + RELOAD_START("reload.start", "Reloading config..."), + RELOAD_COMPLETE("reload.complete", "Completed config reload!"), - // ps add/remove - ADDREMOVE_HELP("addremove.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps add|remove [playername]"), + // --- Add/Remove --- + ADDREMOVE_HELP("addremove.help", "> /ps add|remove [playername]"), ADDREMOVE_HELP_DESC("addremove.help_desc", "Use this command to add or remove a member of your protected region."), - ADDREMOVE_OWNER_HELP("addremove.owner_help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps addowner|removeowner [playername]"), + ADDREMOVE_OWNER_HELP("addremove.owner_help", "> /ps addowner|removeowner [playername]"), ADDREMOVE_OWNER_HELP_DESC("addremove.owner_help_desc", "Use this command to add or remove an owner of your protected region."), - ADDREMOVE_PLAYER_REACHED_LIMIT("addremove.player_reached_limit", ChatColor.RED + "This player has reached their region limit."), - ADDREMOVE_PLAYER_NEEDS_TO_BE_ONLINE("addremove.player_needs_to_be_online", ChatColor.RED + "The player needs to be online to add them."), + ADDREMOVE_PLAYER_REACHED_LIMIT("addremove.player_reached_limit", "This player has reached their region limit."), + ADDREMOVE_PLAYER_NEEDS_TO_BE_ONLINE("addremove.player_needs_to_be_online", "The player needs to be online to add them."), - // ps get - GET_HELP("get.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps get [block]"), + // --- Get --- + GET_HELP("get.help", "> /ps get [block]"), GET_HELP_DESC("get.help_desc", "Use this command to get or purchase a protection block."), - GET_GOTTEN("get.gotten", ChatColor.AQUA + "Added protection block to inventory!"), - GET_NO_PERMISSION_BLOCK("get.no_permission_block", ChatColor.RED + "You don't have permission to get this block."), - GET_HEADER("get.header", ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "=====" + ChatColor.RESET + " Protect Blocks (click to get) " + ChatColor.DARK_GRAY + ChatColor.STRIKETHROUGH + "====="), - GET_GUI_BLOCK("get.gui_block", ChatColor.GRAY + "> " + ChatColor.AQUA + "%alias% " + ChatColor.GRAY + "- %description% (" + ChatColor.WHITE + "$%price%" + ChatColor.GRAY + ")"), - GET_GUI_HOVER("get.gui_hover", "Click to buy a %alias%!"), - - // ps give - GIVE_HELP("give.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps give [block] [player] [amount (optional)]"), + GET_GOTTEN("get.gotten", "Added protection block to inventory!"), + GET_NO_PERMISSION_BLOCK("get.no_permission_block", "You don't have permission to get this block."), + GET_HEADER("get.header", "===== Protect Blocks (click to get) ====="), + GET_GUI_BLOCK("get.gui_block", "> %alias% - %description% ($%price%)", "%alias%", "%description%", "%price%"), + GET_GUI_HOVER("get.gui_hover", "Click to buy a %alias%!", "%alias%"), + + // --- Give --- + GIVE_HELP("give.help", "> /ps give [block] [player] [amount (optional)]"), GIVE_HELP_DESC("give.help_desc", "Use this command to give a player a protection block."), - GIVE_GIVEN("give.given", ChatColor.GRAY + "Gave " + ChatColor.AQUA + "%block%" + ChatColor.GRAY + " to " + ChatColor.AQUA + "%player%" + ChatColor.GRAY + "."), - GIVE_NO_INVENTORY_ROOM("give.no_inventory_room", ChatColor.RED + "The player does not have enough inventory room."), + GIVE_GIVEN("give.given", "Gave %block% to %player%.", "%block%", "%player%"), + GIVE_NO_INVENTORY_ROOM("give.no_inventory_room", "The player does not have enough inventory room."), - // ps sethome - SETHOME_HELP("sethome.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps sethome"), + // --- Sethome --- + SETHOME_HELP("sethome.help", "> /ps sethome"), SETHOME_HELP_DESC("sethome.help_desc", "Use this command to set the home of a region to where you are right now."), - SETHOME_SET("sethome.set", ChatColor.GRAY + "The home for " + ChatColor.AQUA + "%psid%" + ChatColor.GRAY + " has been set to your location."), + SETHOME_SET("sethome.set", "The home for %psid% has been set to your location.", "%psid%"), - // ps list - LIST_HELP("list.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps list [player (optional)]"), + // --- List --- + LIST_HELP("list.help", "> /ps list [player (optional)]"), LIST_HELP_DESC("list.help_desc", "Use this command to list the regions you, or another player owns."), - LIST_HEADER("list.header", ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "=====" + ChatColor.RESET + " %player%'s Regions " + ChatColor.DARK_GRAY + ChatColor.STRIKETHROUGH + "====="), - LIST_OWNER("list.owner", ChatColor.GRAY + "Owner of:"), - LIST_MEMBER("list.member", ChatColor.GRAY + "Member of:"), - LIST_NO_REGIONS("list.no_regions", ChatColor.GRAY + "You currently do not own and are not a member of any regions."), - LIST_NO_REGIONS_PLAYER("list.no_regions_player", ChatColor.AQUA + "%player% " + ChatColor.GRAY + "does not own and is not a member of any regions."), - - // ps name - NAME_HELP("name.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps name [name|none]"), + LIST_HEADER("list.header", "===== %player%'s Regions =====", "%player%"), + LIST_OWNER("list.owner", "Owner of:"), + LIST_MEMBER("list.member", "Member of:"), + LIST_NO_REGIONS("list.no_regions", "You currently do not own and are not a member of any regions."), + LIST_NO_REGIONS_PLAYER("list.no_regions_player", "%player% does not own and is not a member of any regions.", "%player%"), + + // --- Name --- + NAME_HELP("name.help", "> /ps name [name|none]"), NAME_HELP_DESC("name.help_desc", "Use this command to give a nickname to your region, to make identifying your region easier."), - NAME_REMOVED("name.removed", ChatColor.GRAY + "Removed the name for %id%."), - NAME_SET_NAME("name.set_name", ChatColor.GRAY + "Set the name of %id% to " + ChatColor.AQUA + "%name%" + ChatColor.GRAY + "."), - NAME_TAKEN("name.taken", ChatColor.GRAY + "The region name " + ChatColor.AQUA + "%name%" + ChatColor.GRAY + " has already been taken! Try another one."), + NAME_REMOVED("name.removed", "Removed the name for %id%.", "%id%"), + NAME_SET_NAME("name.set_name", "Set the name of %id% to %name%.", "%id%", "%name%"), + NAME_TAKEN("name.taken", "The region name %name% has already been taken! Try another one.", "%name%"), - // ps setparent - SETPARENT_HELP("setparent.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps setparent [region|none]"), + // --- Setparent --- + SETPARENT_HELP("setparent.help", "> /ps setparent [region|none]"), SETPARENT_HELP_DESC("setparent.help_desc", "Use this command to allow this region to inherit properties from another region (owners, members, flags, etc.)."), - SETPARENT_SUCCESS("setparent.success", ChatColor.GRAY + "Successfully set the parent of " + ChatColor.AQUA + "%id%" + ChatColor.GRAY + " to " + ChatColor.AQUA + "%parent%" + ChatColor.GRAY + "."), - SETPARENT_SUCCESS_REMOVE("setparent.success_remove", ChatColor.GRAY + "Successfully removed the parent of " + ChatColor.AQUA + "%id%" + ChatColor.GRAY + "."), - SETPARENT_CIRCULAR_INHERITANCE("setparent.circular_inheritance", ChatColor.RED + "Detected circular inheritance (the parent already inherits from this region?). Parent not set."), + SETPARENT_SUCCESS("setparent.success", "Successfully set the parent of %id% to %parent%.", "%id%", "%parent%"), + SETPARENT_SUCCESS_REMOVE("setparent.success_remove", "Successfully removed the parent of %id%.", "%id%"), + SETPARENT_CIRCULAR_INHERITANCE("setparent.circular_inheritance", "Detected circular inheritance (the parent already inherits from this region?). Parent not set."), - // ps merge - MERGE_HELP("merge.help", ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps merge"), + // --- Merge --- + MERGE_HELP("merge.help", "> /ps merge"), MERGE_HELP_DESC("merge.help_desc", "Use this command to merge the region you are in with other overlapping regions."), MERGE_DISABLED("merge.disabled", "Merging regions is disabled in the config!"), - MERGE_MERGED("merge.merged", ChatColor.AQUA + "Regions were successfully merged!"), - MERGE_HEADER("merge.header", ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "=====" + ChatColor.RESET + " Merge %region% (click to merge) " + ChatColor.DARK_GRAY + ChatColor.STRIKETHROUGH + "====="), - MERGE_WARNING("merge.warning", ChatColor.GRAY + "Note: This will delete all of the settings for the current region!"), - MERGE_NOT_ALLOWED("merge.not_allowed", ChatColor.RED + "You are not allowed to merge this protection region type."), - MERGE_INTO("merge.into", ChatColor.AQUA + "This region overlaps other regions you can merge into!"), - MERGE_NO_REGIONS("merge.no_region", ChatColor.GRAY + "There are no overlapping regions to merge into."), - MERGE_CLICK_TO_MERGE("merge.click_to_merge", "Click to merge with %region%!"), - MERGE_AUTO_MERGED("merge.auto_merged", ChatColor.GRAY + "Region automatically merged with " + ChatColor.AQUA + "%region%" + ChatColor.GRAY + "."), - + MERGE_MERGED("merge.merged", "Regions were successfully merged!"), + MERGE_HEADER("merge.header", "===== Merge %region% (click to merge) =====", "%region%"), + MERGE_WARNING("merge.warning", "Note: This will delete all of the settings for the current region!"), + MERGE_NOT_ALLOWED("merge.not_allowed", "You are not allowed to merge this protection region type."), + MERGE_INTO("merge.into", "This region overlaps other regions you can merge into!"), + MERGE_NO_REGIONS("merge.no_region", "There are no overlapping regions to merge into."), + MERGE_CLICK_TO_MERGE("merge.click_to_merge", "Click to merge with %region%!", "%region%"), + MERGE_AUTO_MERGED("merge.auto_merged", "Region automatically merged with %region%.", "%region%"), ; + // ===== FIELDS ===== private final String path; private final String defaultMessage; - private final String[] placeholders; private final int placeholdersCount; - private String message; + + private Component message; private boolean isEmpty; - private static final File conf = new File(ProtectionStones.getInstance().getDataFolder(), "messages.yml"); + // NOTE: if your enum constants are declared ABOVE these fields (usual for enums), + // do NOT reference them in the constructor. That's why we call MiniMessage.miniMessage() inline there. + private static final File conf = + new File(ProtectionStones.getInstance().getDataFolder(), "messages.yml"); + private static final MiniMessage MINI = MiniMessage.miniMessage(); + private static final LegacyComponentSerializer LEGACY = LegacyComponentSerializer.legacyAmpersand(); + + // &#RRGGBB -> <#RRGGBB> + private static final Pattern HEX_HASH = Pattern.compile("(? &x (so legacy ampersand parser can handle them) + private static final Pattern SECTION = Pattern.compile("§([0-9a-frk-orA-FRK-OR])"); PSL(String path, String defaultMessage, String... placeholders) { this.path = path; this.defaultMessage = defaultMessage; - this.placeholders = placeholders; this.placeholdersCount = placeholders.length; - this.message = defaultMessage; - this.isEmpty = message.isEmpty(); + this.message = MiniMessage.miniMessage().deserialize(defaultMessage); + this.isEmpty = defaultMessage.isEmpty(); } - public String msg() { - return message; - } + public String path() { return path; } + public String def() { return defaultMessage; } + public String[] placeholders() { return placeholders; } - public boolean isEmpty() { - return isEmpty; - } + public Component msg() { return message; } + public boolean isEmpty() { return isEmpty; } @Nullable - public String format(final Object... args) { - if (isEmpty) { - return null; - } - - if (this.placeholdersCount == 0) { - return this.message; - } + public Component format(final Object... args) { + if (isEmpty) return null; + if (this.placeholdersCount == 0) return this.message; if (this.placeholdersCount != args.length) { throw new IllegalArgumentException("Expected " + this.placeholdersCount + " arguments but got " + args.length); } - return StringUtils.replaceEach( - this.message, - this.placeholders, - Arrays.stream(args).filter(Objects::nonNull).map(Object::toString).toArray(String[]::new) - ); + Component formatted = this.message; + for (int i = 0; i < placeholdersCount; i++) { + final String ph = this.placeholders[i]; // effectively final + final String rep = Objects.toString(args[i], ""); // effectively final + formatted = formatted.replaceText(b -> b.matchLiteral(ph).replacement(rep)); + } + return formatted; } - public boolean send(@NotNull final CommandSender receiver, @NotNull final Object... args) { - final String msg = this.format(args); + // Convenience: replace a single placeholder in THIS message and return a new Component + public Component replace(final String placeholder, final String value) { + if (isEmpty) return null; + final String ph = Objects.toString(placeholder, ""); + final String rep = Objects.toString(value, ""); + return this.message.replaceText(b -> b.matchLiteral(ph).replacement(rep)); + } - if (msg != null) { - receiver.sendMessage(msg); + // Convenience: replace many placeholders in THIS message (map keys are the literals like "%price%") + public Component replaceAll(final Map replacements) { + if (isEmpty) return null; + Component out = this.message; + for (Map.Entry e : replacements.entrySet()) { + final String ph = Objects.toString(e.getKey(), ""); + final String rep = Objects.toString(e.getValue(), ""); + out = out.replaceText(b -> b.matchLiteral(ph).replacement(rep)); } + return out; + } + public boolean send(@NotNull final CommandSender receiver, @NotNull final Object... args) { + final Component c = this.format(args); + if (c != null && receiver != null) { + ProtectionStones.getInstance().audiences().sender(receiver).sendMessage(c); + } return true; } public void append(@NotNull final StringBuilder builder, @NotNull final Object... args) { - final String msg = this.format(args); - - if (msg != null) { - builder.append(msg); + final Component c = this.format(args); + if (c != null) { + builder.append(MINI.serialize(c)); } } - // Sends a message to a commandsender if the string is not empty - - public static boolean msg(CommandSender p, String str) { - if (str != null && !str.isEmpty() && p != null) { - p.sendMessage(str); - } - return true; + // Static helpers + public static boolean msg(CommandSender p, Component comp) { + return ChatUtil.send(p, comp); } - - public static boolean msg(PSPlayer p, String str) { - return msg(p.getPlayer(), str); + // actionbar msgs + public static boolean action(Player p, String comp) { + return ChatUtil.sendActionBar(p, comp); } + // ====== CONFIG LOAD / AUTO-MIGRATE ====== public static void loadConfig() { - YamlConfiguration yml = new YamlConfiguration(); + final YamlConfiguration yml = new YamlConfiguration(); - // check if messages.yml exists if (!conf.exists()) { - try { - conf.createNewFile(); - } catch (IOException e) { - e.printStackTrace(); - } + try { conf.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } - // load config try { - yml.load(conf); // can throw error - for (PSL psl : PSL.values()) { + yml.load(conf); + boolean updated = false; - // fix message if need be - if (yml.getString(psl.path) == null) { // if msg not found in config - yml.set(psl.path, applyConfigColours(psl.defaultMessage)); + for (PSL psl : PSL.values()) { + String raw = yml.getString(psl.path); + + if (raw == null) { + // first time: write default MiniMessage + yml.set(psl.path, psl.defaultMessage); + psl.message = MINI.deserialize(psl.defaultMessage); + raw = psl.defaultMessage; + updated = true; } else { - // perform message upgrades - messageUpgrades(psl, yml); + // migrate legacy to MiniMessage if needed + String upgraded = upgradeToMini(raw); + if (!Objects.equals(upgraded, raw)) { + yml.set(psl.path, upgraded); + raw = upgraded; + updated = true; + } + psl.message = MINI.deserialize(raw); } - // load message - psl.message = applyInGameColours(yml.getString(psl.path)); - psl.isEmpty = psl.message.isEmpty(); + psl.isEmpty = raw.isEmpty(); } - try { - yml.save(conf); - } catch (IOException e) { - e.printStackTrace(); - } - } catch (Exception e) { // prevent bad messages.yml file from resetting the file - e.printStackTrace(); - } - } - // message upgrades over time - private static void messageUpgrades(PSL psl, YamlConfiguration yml) { - String value = yml.getString(psl.path); - assert(value != null); - - // psl upgrade conversions - if (psl == PSL.REACHED_REGION_LIMIT && value.equals("&cYou can not create any more protected regions.")) { - yml.set(psl.path, psl.defaultMessage); - } else if (psl == PSL.REACHED_PER_BLOCK_REGION_LIMIT && value.equals("&cYou can not create any more regions of this type.")) { - yml.set(psl.path, psl.defaultMessage); - } else if (value.contains("§")) { - yml.set(psl.path, applyConfigColours(value)); + if (updated) yml.save(conf); + } catch (Exception e) { + // don't wipe the file on error + e.printStackTrace(); } } - // match all {abc format for hex - private static final Pattern hexPattern = Pattern.compile("(?' + Matcher hex = HEX_HASH.matcher(s); + if (hex.find()) { + s = hex.replaceAll("<#$1>"); } - return msg.replace('&', '§'); - } + // 3) If any &-style legacy codes remain, convert to Components and serialize to MiniMessage + if (s.indexOf('&') >= 0) { + Component comp = LEGACY.deserialize(s); + s = MINI.serialize(comp); + } - private static String applyConfigColours(String msg) { - return msg.replace('§', '&'); + return s; } } diff --git a/src/main/java/dev/espi/protectionstones/PSStandardRegion.java b/src/main/java/dev/espi/protectionstones/PSStandardRegion.java index 243b1a40..8d0adcff 100644 --- a/src/main/java/dev/espi/protectionstones/PSStandardRegion.java +++ b/src/main/java/dev/espi/protectionstones/PSStandardRegion.java @@ -452,7 +452,7 @@ public void removeOwner(UUID uuid) { if (getTenant() != null) { PSPlayer tenant = PSPlayer.fromUUID(getTenant()); if (tenant.getOfflinePlayer().isOnline()) { - PSL.msg(Bukkit.getPlayer(getTenant()), PSL.RENT_TENANT_STOPPED_TENANT.msg() + PSL.msg(Bukkit.getPlayer(getTenant()), PSL.RENT_TENANT_STOPPED_TENANT .replace("%region%", getName() != null ? getName() : getId())); } } diff --git a/src/main/java/dev/espi/protectionstones/ProtectionStones.java b/src/main/java/dev/espi/protectionstones/ProtectionStones.java index bd1775f9..81b71e42 100644 --- a/src/main/java/dev/espi/protectionstones/ProtectionStones.java +++ b/src/main/java/dev/espi/protectionstones/ProtectionStones.java @@ -28,6 +28,7 @@ import dev.espi.protectionstones.utils.upgrade.LegacyUpgrade; import dev.espi.protectionstones.utils.UUIDCache; import dev.espi.protectionstones.utils.WGUtils; +import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.milkbowl.vault.economy.Economy; import org.bstats.bukkit.Metrics; import org.bukkit.*; @@ -44,6 +45,7 @@ import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.java.JavaPlugin; import net.luckperms.api.LuckPerms; +import org.jetbrains.annotations.NotNull; import java.io.File; import java.lang.reflect.Field; @@ -63,7 +65,7 @@ public class ProtectionStones extends JavaPlugin { public static final int CONFIG_VERSION = 16; private boolean debug = false; - + private static ProtectionStones instance; public static File configLocation, blockDataFolder; public static CommentedFileConfig config; @@ -76,6 +78,8 @@ public class ProtectionStones extends JavaPlugin { private PSConfig configOptions; static HashMap protectionStonesOptions = new HashMap<>(); + private BukkitAudiences adventure; + // ps alias to id cache // > @@ -547,8 +551,25 @@ public void onLoad() { FlagHandler.registerFlags(); } + public @NotNull BukkitAudiences audiences() { + if (adventure == null) { + throw new IllegalStateException("Tried to access Adventure audiences while plugin is disabled!"); + } + return adventure; + } + + @Override + + public void onDisable() { + if (this.adventure != null) { + this.adventure.close(); + this.adventure = null; + } + } + @Override public void onEnable() { + this.adventure = BukkitAudiences.create(this); FlagHandler.registerHandlers(); // register custom WG flag handlers TomlFormat.instance(); @@ -576,7 +597,7 @@ public void onEnable() { // check if Vault is enabled (for economy support) if (getServer().getPluginManager().getPlugin("Vault") != null && getServer().getPluginManager().getPlugin("Vault").isEnabled()) { - RegisteredServiceProvider econ = getServer().getServicesManager().getRegistration(net.milkbowl.vault.economy.Economy.class); + RegisteredServiceProvider econ = getServer().getServicesManager().getRegistration(Economy.class); if (econ == null) { getLogger().warning("No economy plugin found by Vault! There will be no economy support!"); vaultSupportEnabled = false; diff --git a/src/main/java/dev/espi/protectionstones/commands/ArgAddRemove.java b/src/main/java/dev/espi/protectionstones/commands/ArgAddRemove.java index b6f167e7..6b4ab777 100644 --- a/src/main/java/dev/espi/protectionstones/commands/ArgAddRemove.java +++ b/src/main/java/dev/espi/protectionstones/commands/ArgAddRemove.java @@ -20,6 +20,7 @@ import dev.espi.protectionstones.utils.LimitUtil; import dev.espi.protectionstones.utils.UUIDCache; import dev.espi.protectionstones.utils.WGUtils; +import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -121,28 +122,38 @@ public boolean executeArgument(CommandSender s, String[] args, HashMap UUIDCache.storeWGProfile(addPlayerUuid, addPlayerName)); + // add to WorldGuard profile cache asynchronously + Bukkit.getScheduler().runTaskAsynchronously( + ProtectionStones.getInstance(), + () -> UUIDCache.storeWGProfile(addPlayerUuid, addPlayerName) + ); } else if ((operationType.equals("remove") && r.isMember(addPlayerUuid)) || (operationType.equals("removeowner") && r.isOwner(addPlayerUuid))) { if (flags.containsKey("-a")) { - PSL.msg(p, PSL.REMOVED_FROM_REGION_SPECIFIC.msg() - .replace("%player%", addPlayerName) - .replace("%region%", r.getName() == null ? r.getId() : r.getName() + " (" + r.getId() + ")")); + PSL.msg(p, PSL.REMOVED_FROM_REGION_SPECIFIC.replaceAll(Map.of( + "%player%", addPlayerName, + "%region%", (r.getName() == null ? r.getId() : r.getName() + " (" + r.getId() + ")") + ))); } else { - PSL.msg(p, PSL.REMOVED_FROM_REGION.msg().replace("%player%", addPlayerName)); + PSL.msg(p, PSL.REMOVED_FROM_REGION.replaceAll(Map.of( + "%player%", addPlayerName + ))); } } + switch (operationType) { case "add" -> r.addMember(addPlayerUuid); case "remove" -> r.removeMember(addPlayerUuid); @@ -218,7 +229,7 @@ public boolean determinePlayerSurpassedLimit(Player commandSender, List { if (r instanceof PSGroupRegion) { return ((PSGroupRegion) r).getMergedRegions().stream(); @@ -227,11 +238,11 @@ public boolean determinePlayerSurpassedLimit(Player commandSender, List " + ChatColor.GRAY + "/" + ProtectionStones.getInstance().getConfigOptions().base_command + - " admin cleanup [remove|preview] [-t typealias (optional)] [days] [world (optional)]"; + // --- Cleanup --- + public static Component getCleanupHelp() { + return Component.text("> ", NamedTextColor.AQUA) + .append(Component.text("/", NamedTextColor.GRAY)) + .append(Component.text(ProtectionStones.getInstance().getConfigOptions().base_command + " admin cleanup [remove|preview] [-t typealias (optional)] [days] [world (optional)]", NamedTextColor.GRAY)); } - public static String getFlagHelp() { - return ChatColor.AQUA + "> " + ChatColor.GRAY + "/" + ProtectionStones.getInstance().getConfigOptions().base_command + - " admin flag [world] [flagname] [value|null|default]"; + // --- Flag --- + public static Component getFlagHelp() { + return Component.text("> ", NamedTextColor.AQUA) + .append(Component.text("/", NamedTextColor.GRAY)) + .append(Component.text(ProtectionStones.getInstance().getConfigOptions().base_command + " admin flag [world] [flagname] [value|null|default]", NamedTextColor.GRAY)); } - public static String getChangeBlockHelp() { - return ChatColor.AQUA + "> " + ChatColor.GRAY + "/" + ProtectionStones.getInstance().getConfigOptions().base_command + - " admin changeblock [world] [oldtypealias] [newtypealias]"; + // --- Change Block --- + public static Component getChangeBlockHelp() { + return Component.text("> ", NamedTextColor.AQUA) + .append(Component.text("/", NamedTextColor.GRAY)) + .append(Component.text(ProtectionStones.getInstance().getConfigOptions().base_command + " admin changeblock [world] [oldtypealias] [newtypealias]", NamedTextColor.GRAY)); } - public static String getChangeRegionTypeHelp() { - return ChatColor.AQUA + "> " + ChatColor.GRAY + "/" + ProtectionStones.getInstance().getConfigOptions().base_command + - " admin changeregiontype [world] [oldtype] [newtype]"; + // --- Change Region Type --- + public static Component getChangeRegionTypeHelp() { + return Component.text("> ", NamedTextColor.AQUA) + .append(Component.text("/", NamedTextColor.GRAY)) + .append(Component.text(ProtectionStones.getInstance().getConfigOptions().base_command + " admin changeregiontype [world] [oldtype] [newtype]", NamedTextColor.GRAY)); } - public static String getForceMergeHelp() { - return ChatColor.AQUA + "> " + ChatColor.GRAY + "/" + ProtectionStones.getInstance().getConfigOptions().base_command + - " admin forcemerge [world]"; + // --- Force Merge --- + public static Component getForceMergeHelp() { + return Component.text("> ", NamedTextColor.AQUA) + .append(Component.text("/", NamedTextColor.GRAY)) + .append(Component.text(ProtectionStones.getInstance().getConfigOptions().base_command + " admin forcemerge [world]", NamedTextColor.GRAY)); } @Override @@ -93,53 +104,74 @@ public boolean executeArgument(CommandSender s, String[] args, HashMap tabComplete(CommandSender sender, String alias, String[] args) { if (args.length == 2) { diff --git a/src/main/java/dev/espi/protectionstones/commands/ArgAdminChangeType.java b/src/main/java/dev/espi/protectionstones/commands/ArgAdminChangeType.java index 04981138..cc5bdc83 100644 --- a/src/main/java/dev/espi/protectionstones/commands/ArgAdminChangeType.java +++ b/src/main/java/dev/espi/protectionstones/commands/ArgAdminChangeType.java @@ -22,6 +22,7 @@ import dev.espi.protectionstones.FlagHandler; import dev.espi.protectionstones.PSL; import dev.espi.protectionstones.utils.WGUtils; +import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.World; @@ -45,7 +46,9 @@ static boolean argumentAdminChangeType(CommandSender p, String[] args) { RegionManager rgm = WGUtils.getRegionManagerWithWorld(w); if (rgm == null) { - return PSL.msg(p, ChatColor.GRAY + "The world does not have WorldGuard configured!"); + return PSL.msg(p, MiniMessage.miniMessage().deserialize( + "The world does not have WorldGuard configured!" + )); } String fromType = args[3], toType = args[4]; diff --git a/src/main/java/dev/espi/protectionstones/commands/ArgAdminChangeblock.java b/src/main/java/dev/espi/protectionstones/commands/ArgAdminChangeblock.java index 64ae5245..c487d025 100644 --- a/src/main/java/dev/espi/protectionstones/commands/ArgAdminChangeblock.java +++ b/src/main/java/dev/espi/protectionstones/commands/ArgAdminChangeblock.java @@ -19,6 +19,8 @@ import com.sk89q.worldguard.protection.regions.ProtectedRegion; import dev.espi.protectionstones.*; import dev.espi.protectionstones.utils.WGUtils; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.World; @@ -37,11 +39,13 @@ static boolean argumentAdminChangeblock(CommandSender p, String[] args) { String world = args[2], fromBlockAlias = args[3], toBlockAlias = args[4]; if (ProtectionStones.getProtectBlockFromAlias(fromBlockAlias) == null) { - PSL.msg(p, ChatColor.GRAY + "The type to change from is not a registered protection block!"); + PSL.msg(p, Component.text("The type to change from is not a registered protection block!", NamedTextColor.GRAY)); + return true; } if (ProtectionStones.getProtectBlockFromAlias(toBlockAlias) == null) { - PSL.msg(p, ChatColor.GRAY + "The type to change to is not a registered protection block!"); + PSL.msg(p, Component.text("The type to change to is not a registered protection block!", NamedTextColor.GRAY)); + return true; } @@ -62,7 +66,7 @@ static boolean argumentAdminChangeblock(CommandSender p, String[] args) { } RegionManager rgm = WGUtils.getRegionManagerWithWorld(w); if (rgm == null) { - return PSL.msg(p, ChatColor.GRAY + "The world does not have WorldGuard configured!"); + return PSL.msg(p, Component.text("The world does not have WorldGuard configured!", NamedTextColor.GRAY)); } for (ProtectedRegion r : rgm.getRegions().values()) { if (ProtectionStones.isPSRegion(r)) { diff --git a/src/main/java/dev/espi/protectionstones/commands/ArgAdminCleanup.java b/src/main/java/dev/espi/protectionstones/commands/ArgAdminCleanup.java index 4736246a..65e8ca8d 100644 --- a/src/main/java/dev/espi/protectionstones/commands/ArgAdminCleanup.java +++ b/src/main/java/dev/espi/protectionstones/commands/ArgAdminCleanup.java @@ -94,9 +94,10 @@ static boolean argumentAdminCleanup(CommandSender p, String[] preParseArgs) { Bukkit.getScheduler().runTaskAsynchronously(ProtectionStones.getInstance(), () -> { int days = (args.size() > 0) ? Integer.parseInt(args.get(0)) : 30; // 30 days is default if days aren't specified - PSL.msg(p, PSL.ADMIN_CLEANUP_HEADER.msg() - .replace("%arg%", cleanupOperation) - .replace("%days%", "" + days)); + PSL.msg(p, PSL.ADMIN_CLEANUP_HEADER.replaceAll(Map.of( + "%arg%", cleanupOperation, + "%days%", String.valueOf(days) + ))); HashSet activePlayers = new HashSet<>(); @@ -149,7 +150,7 @@ static private void regionLoop(Iterator deleteRegionsIterator, Command Bukkit.getScheduler().runTaskLater(ProtectionStones.getInstance(), () -> processRegion(deleteRegionsIterator, p, isRemoveOperation), 1); } else { // finished region iteration - PSL.msg(p, PSL.ADMIN_CLEANUP_FOOTER.msg() + PSL.msg(p, PSL.ADMIN_CLEANUP_FOOTER .replace("%arg%", isRemoveOperation ? "remove" : "preview")); // flush and close preview file diff --git a/src/main/java/dev/espi/protectionstones/commands/ArgAdminForceMerge.java b/src/main/java/dev/espi/protectionstones/commands/ArgAdminForceMerge.java index 92e7f3e6..ba1e8ca9 100644 --- a/src/main/java/dev/espi/protectionstones/commands/ArgAdminForceMerge.java +++ b/src/main/java/dev/espi/protectionstones/commands/ArgAdminForceMerge.java @@ -25,6 +25,8 @@ import dev.espi.protectionstones.ProtectionStones; import dev.espi.protectionstones.utils.WGMerge; import dev.espi.protectionstones.utils.WGUtils; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.World; @@ -87,7 +89,7 @@ public static boolean argumentAdminForceMerge(CommandSender p, String[] args) { Set options = new HashSet<>(); for (int i = 3; i < args.length; i++) { if (!flags.contains(args[i])) { - PSL.msg(p, ChatColor.RED + "Invalid option."); + PSL.msg(p, Component.text("Invalid option.", NamedTextColor.RED)); return true; } else { options.add(args[i]); diff --git a/src/main/java/dev/espi/protectionstones/commands/ArgAdminHelp.java b/src/main/java/dev/espi/protectionstones/commands/ArgAdminHelp.java index c224df2b..a28b1548 100644 --- a/src/main/java/dev/espi/protectionstones/commands/ArgAdminHelp.java +++ b/src/main/java/dev/espi/protectionstones/commands/ArgAdminHelp.java @@ -16,40 +16,57 @@ package dev.espi.protectionstones.commands; import dev.espi.protectionstones.ProtectionStones; -import net.md_5.bungee.api.chat.ComponentBuilder; -import net.md_5.bungee.api.chat.HoverEvent; -import net.md_5.bungee.api.chat.TextComponent; -import org.bukkit.ChatColor; +import dev.espi.protectionstones.PSL; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; import org.bukkit.command.CommandSender; public class ArgAdminHelp { - private static void send(CommandSender p, String text, String info) { - TextComponent tc = new TextComponent(text); - tc.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(info).create())); - p.spigot().sendMessage(tc); + private static void send(CommandSender sender, String command, String description, boolean run) { + Component line = Component.text("> ", NamedTextColor.AQUA) + .append(Component.text(command, NamedTextColor.GRAY)) + .hoverEvent(HoverEvent.showText(Component.text(description, NamedTextColor.WHITE))) + .clickEvent(run + ? ClickEvent.runCommand(command) + : ClickEvent.suggestCommand(command)); + + PSL.msg(sender, line); } - static boolean argumentAdminHelp(CommandSender p, String[] args) { - String bc = "/" + ProtectionStones.getInstance().getConfigOptions().base_command; - - p.sendMessage(ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "=====" + ChatColor.RESET + " PS Admin Help " + ChatColor.DARK_GRAY + ChatColor.STRIKETHROUGH + "=====\n" + ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps admin help"); - send(p, ChatColor.AQUA + "> " + ChatColor.GRAY + bc + " admin version", "Show the version number of the plugin."); - send(p, ChatColor.AQUA + "> " + ChatColor.GRAY + bc + " admin hide", "Hide all of the protection stone blocks in the world you are in."); - send(p, ChatColor.AQUA + "> " + ChatColor.GRAY + bc + " admin unhide", "Unhide all of the protection stone blocks in the world you are in."); - send(p, ChatColor.AQUA + "> " + ChatColor.GRAY + bc + " admin cleanup remove [days] [-t typealias (optional)] [world (console)]", "Remove inactive players that haven't joined within the last [days] days from protected regions in the world you are in (or specified). Then, remove any regions with no owners left."); - send(p, ChatColor.AQUA + "> " + ChatColor.GRAY + bc + " admin cleanup disown [days] [-t typealias (optional)] [world (console)]", "Remove inactive players that haven't joined within the last [days] days from protected regions in the world you are in (or specified)."); - send(p, ArgAdmin.getFlagHelp(), "Set a flag for all protection stone regions in a world."); - send(p, ChatColor.AQUA + "> " + ChatColor.GRAY + bc + " admin lastlogon [player]", "Get the last time a player logged on."); - send(p, ChatColor.AQUA + "> " + ChatColor.GRAY + bc + " admin lastlogons", "List all of the last logons of each player."); - send(p, ChatColor.AQUA + "> " + ChatColor.GRAY + bc + " admin stats [player (optional)]", "Show some statistics of the plugin."); - send(p, ChatColor.AQUA + "> " + ChatColor.GRAY + bc + " admin recreate", "Recreate all PS regions using radius set in config."); - send(p, ChatColor.AQUA + "> " + ChatColor.GRAY + bc + " admin debug", "Toggles debug mode."); - send(p, ChatColor.AQUA + "> " + ChatColor.GRAY + bc + " admin settaxautopayers", "Add a tax autopayer for every region on the server that does not have one."); - send(p, ArgAdmin.getForceMergeHelp(), "Merge overlapping PS regions together if they have the same owners, members and flags."); - send(p, ArgAdmin.getChangeBlockHelp(), "Change all of the PS blocks and regions in a world to a different block. Both blocks must be configured in config."); - send(p, ArgAdmin.getChangeRegionTypeHelp(), "Change the internal type of all PS regions of a certain type. Useful for error correction."); - send(p, ChatColor.AQUA + "> " + ChatColor.GRAY + bc + " admin fixregions", "Use this command to recalculate block types for PS regions in a world."); + static boolean argumentAdminHelp(CommandSender sender, String[] args) { + String baseCommand = "/" + ProtectionStones.getInstance().getConfigOptions().base_command; + + // Header + PSL.msg(sender, Component.empty().append(Component.text("===============", NamedTextColor.DARK_GRAY, TextDecoration.STRIKETHROUGH)) + .append(Component.space()) + .append(Component.text("PS Admin Help", NamedTextColor.AQUA).decoration(TextDecoration.STRIKETHROUGH, false)) + .append(Component.space()) + .append(Component.text("===============", NamedTextColor.DARK_GRAY, TextDecoration.STRIKETHROUGH))); + + // Entries + send(sender, baseCommand + " admin version", "Show the version number of the plugin.", false); + send(sender, baseCommand + " admin hide", "Hide all protection stone blocks in the current world.", false); + send(sender, baseCommand + " admin unhide", "Unhide all protection stone blocks in the current world.", false); + send(sender, baseCommand + " admin cleanup remove", "Remove inactive players, then remove empty regions.", false); + send(sender, baseCommand + " admin cleanup disown", "Remove inactive players from regions only.", false); + send(sender, baseCommand + " admin flag [world] [flagname] [value|null|default]", "Set a flag for all PS regions in a world.", false); + send(sender, baseCommand + " admin lastlogon [player]", "Get the last time a player logged on.", false); + send(sender, baseCommand + " admin lastlogons", "List last logons of all players.", false); + send(sender, baseCommand + " admin stats [player?]", "Show plugin statistics.", false); + send(sender, baseCommand + " admin recreate", "Recreate all PS regions using the configured radius.", false); + send(sender, baseCommand + " admin debug", "Toggle debug mode.", false); + send(sender, baseCommand + " admin settaxautopayers", "Assign a tax autopayer to all regions without one.", false); + send(sender, baseCommand + " admin forcemerge [world]", "Merge overlapping PS regions if owners/members/flags match.", false); + send(sender, baseCommand + " admin changeblock [world] [oldtypealias] [newtypealias]", "Change all PS blocks/regions in a world to a different block.", false); + send(sender, baseCommand + " admin changeregiontype [world] [oldtype] [newtype]", "Change the type of all PS regions of a certain type.", false); + send(sender, baseCommand + " admin fixregions", "Recalculate block types for PS regions in a world.", false); + + // Footer + PSL.msg(sender, Component.text("=============================================", NamedTextColor.DARK_GRAY, TextDecoration.STRIKETHROUGH)); return true; } diff --git a/src/main/java/dev/espi/protectionstones/commands/ArgAdminHide.java b/src/main/java/dev/espi/protectionstones/commands/ArgAdminHide.java index 30d1515e..2e7d8042 100644 --- a/src/main/java/dev/espi/protectionstones/commands/ArgAdminHide.java +++ b/src/main/java/dev/espi/protectionstones/commands/ArgAdminHide.java @@ -62,7 +62,7 @@ static boolean argumentAdminHide(CommandSender p, String[] args) { } String hMessage = args[1].equalsIgnoreCase("unhide") ? "unhidden" : "hidden"; - PSL.msg(p, PSL.ADMIN_HIDE_TOGGLED.msg() + PSL.msg(p, PSL.ADMIN_HIDE_TOGGLED .replace("%message%", hMessage)); }); diff --git a/src/main/java/dev/espi/protectionstones/commands/ArgAdminLastlogon.java b/src/main/java/dev/espi/protectionstones/commands/ArgAdminLastlogon.java index e91aa61d..c6f7a075 100644 --- a/src/main/java/dev/espi/protectionstones/commands/ArgAdminLastlogon.java +++ b/src/main/java/dev/espi/protectionstones/commands/ArgAdminLastlogon.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.Comparator; +import java.util.Map; class ArgAdminLastlogon { // /ps admin lastlogon @@ -34,7 +35,7 @@ public int compare(OfflinePlayer o1, OfflinePlayer o2) { } static boolean argumentAdminLastLogon(CommandSender p, String[] args) { if (args.length < 3) { - p.sendMessage(PSL.COMMAND_REQUIRES_PLAYER_NAME.msg()); + PSL.msg(p, PSL.COMMAND_REQUIRES_PLAYER_NAME.msg()); return true; } OfflinePlayer op = Bukkit.getOfflinePlayer(args[2]); @@ -42,15 +43,16 @@ static boolean argumentAdminLastLogon(CommandSender p, String[] args) { String playerName = args[2]; long lastPlayed = (System.currentTimeMillis() - op.getLastPlayed()) / 86400000L; - PSL.msg(p, PSL.ADMIN_LAST_LOGON.msg() - .replace("%player%", playerName) - .replace("%days%", "" +lastPlayed)); + PSL.msg(p, PSL.ADMIN_LAST_LOGON.replaceAll(Map.of( + "%player%", playerName, + "%days%", String.valueOf(lastPlayed) + ))); if (op.isBanned()) { - PSL.msg(p, PSL.ADMIN_IS_BANNED.msg() - .replace("%player%", playerName)); + PSL.msg(p, PSL.ADMIN_IS_BANNED.replace("%player%", playerName)); } + return true; } @@ -67,7 +69,7 @@ static boolean argumentAdminLastLogons(CommandSender p, String[] args) { } OfflinePlayer[] offlinePlayerList = Bukkit.getServer().getOfflinePlayers().clone(); int playerCounter = 0; - PSL.msg(p, PSL.ADMIN_LASTLOGONS_HEADER.msg() + PSL.msg(p, PSL.ADMIN_LASTLOGONS_HEADER .replace("%days%", "" + days)); Arrays.sort(offlinePlayerList, new PlayerComparator()); @@ -75,15 +77,17 @@ static boolean argumentAdminLastLogons(CommandSender p, String[] args) { long lastPlayed = (System.currentTimeMillis() - offlinePlayer.getLastPlayed()) / 86400000L; if (lastPlayed >= days) { playerCounter++; - PSL.msg(p, PSL.ADMIN_LASTLOGONS_LINE.msg() - .replace("%player%", offlinePlayer.getName()) - .replace("%time%", "" + lastPlayed)); + PSL.msg(p, PSL.ADMIN_LASTLOGONS_LINE.replaceAll(Map.of( + "%player%", offlinePlayer.getName(), + "%time%", String.valueOf(lastPlayed) + ))); } } - PSL.msg(p, PSL.ADMIN_LASTLOGONS_FOOTER.msg() - .replace("%count%", "" + playerCounter) - .replace("%checked%", "" + offlinePlayerList.length)); + PSL.msg(p, PSL.ADMIN_LASTLOGONS_FOOTER.replaceAll(Map.of( + "%count%", String.valueOf(playerCounter), + "%checked%", String.valueOf(offlinePlayerList.length) + ))); return true; } diff --git a/src/main/java/dev/espi/protectionstones/commands/ArgAdminRecreate.java b/src/main/java/dev/espi/protectionstones/commands/ArgAdminRecreate.java index 469bff5d..824a6490 100644 --- a/src/main/java/dev/espi/protectionstones/commands/ArgAdminRecreate.java +++ b/src/main/java/dev/espi/protectionstones/commands/ArgAdminRecreate.java @@ -20,6 +20,8 @@ import com.sk89q.worldguard.protection.regions.ProtectedRegion; import dev.espi.protectionstones.*; import dev.espi.protectionstones.utils.WGUtils; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.World; @@ -31,7 +33,7 @@ class ArgAdminRecreate { static boolean argumentAdminRecreate(CommandSender s, String[] args) { - s.sendMessage(ChatColor.YELLOW + "Recreating..."); + PSL.msg(s, Component.text("Recreating...", NamedTextColor.YELLOW)); HashMap m = WGUtils.getAllRegionManagers(); for (World w : m.keySet()) { @@ -62,7 +64,7 @@ static boolean argumentAdminRecreate(CommandSender s, String[] args) { } } - s.sendMessage(ChatColor.YELLOW + "Done."); + PSL.msg(s, Component.text("Done.", NamedTextColor.YELLOW)); return true; } } diff --git a/src/main/java/dev/espi/protectionstones/commands/ArgAdminSetTaxAutopayers.java b/src/main/java/dev/espi/protectionstones/commands/ArgAdminSetTaxAutopayers.java index 592e300e..4ef055d4 100644 --- a/src/main/java/dev/espi/protectionstones/commands/ArgAdminSetTaxAutopayers.java +++ b/src/main/java/dev/espi/protectionstones/commands/ArgAdminSetTaxAutopayers.java @@ -22,6 +22,8 @@ import dev.espi.protectionstones.PSRegion; import dev.espi.protectionstones.ProtectionStones; import dev.espi.protectionstones.utils.WGUtils; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; @@ -29,10 +31,15 @@ class ArgAdminSetTaxAutopayers { static boolean argumentAdminSetTaxAutopayers(CommandSender s, String[] args) { if (!ProtectionStones.getInstance().getConfigOptions().taxEnabled) { - return PSL.msg(s, ChatColor.RED + "Taxes are disabled! Enable it in the config."); + return PSL.msg(s, Component.text("Taxes are disabled! Enable it in the config.", NamedTextColor.RED)); } - PSL.msg(s, ChatColor.GRAY + "Scanning through regions, and setting tax autopayers for regions that don't have one..."); + PSL.msg(s, + Component.text( + "Scanning through regions, and setting tax autopayers for regions that don't have one...", + NamedTextColor.GRAY + ) + ); Bukkit.getScheduler().runTaskAsynchronously(ProtectionStones.getInstance(), () -> { WGUtils.getAllRegionManagers().forEach((w, rgm) -> { @@ -41,14 +48,21 @@ static boolean argumentAdminSetTaxAutopayers(CommandSender s, String[] args) { if (psr != null && psr.getTypeOptions() != null && psr.getTypeOptions().taxPeriod != -1 && psr.getTaxAutopayer() == null) { if (psr.getOwners().size() >= 1) { - PSL.msg(s, ChatColor.GRAY + "Configured tax autopayer to be " + psr.getOwners().get(0).toString() + " for region " + psr.getId()); + PSL.msg(s, + Component.text("Configured tax autopayer to be ", NamedTextColor.GRAY) + .append(Component.text(psr.getOwners().get(0).toString(), NamedTextColor.AQUA)) + .append(Component.text(" for region ", NamedTextColor.GRAY)) + .append(Component.text(psr.getId(), NamedTextColor.AQUA)) + ); + psr.setTaxAutopayer(psr.getOwners().get(0)); } } } }); - PSL.msg(s, ChatColor.GREEN + "Complete!"); + PSL.msg(s, Component.text("Complete!", NamedTextColor.GREEN)); + }); return true; diff --git a/src/main/java/dev/espi/protectionstones/commands/ArgBuySell.java b/src/main/java/dev/espi/protectionstones/commands/ArgBuySell.java index 8a4fef13..930a54f6 100644 --- a/src/main/java/dev/espi/protectionstones/commands/ArgBuySell.java +++ b/src/main/java/dev/espi/protectionstones/commands/ArgBuySell.java @@ -31,6 +31,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Map; public class ArgBuySell implements PSCommandArg { @Override @@ -81,21 +82,27 @@ public boolean executeArgument(CommandSender s, String[] args, HashMap b.matchLiteral("%limit%") + .replacement(String.valueOf(PSPlayer.fromPlayer(p).getGlobalRegionLimits())))); if (!PSPlayer.fromPlayer(p).hasAmount(r.getPrice())) - return PSL.msg(p, PSL.NOT_ENOUGH_MONEY.msg().replace("%price%", new DecimalFormat("#.##").format(r.getPrice()))); + return PSL.msg(p, PSL.NOT_ENOUGH_MONEY.replace("%price%", new DecimalFormat("#.##").format(r.getPrice()))); - PSL.msg(p, PSL.BUY_SOLD_BUYER.msg() - .replace("%region%", r.getName() == null ? r.getId() : r.getName()) - .replace("%price%", String.format("%.2f", r.getPrice())) - .replace("%player%", UUIDCache.getNameFromUUID(r.getLandlord()))); + PSL.msg(p, PSL.BUY_SOLD_BUYER.replaceAll(Map.of( + "%region%", (r.getName() == null ? r.getId() : r.getName()), + "%price%", String.format("%.2f", r.getPrice()), + "%player%", UUIDCache.getNameFromUUID(r.getLandlord()) + ))); if (Bukkit.getPlayer(r.getLandlord()) != null) { - PSL.msg(Bukkit.getPlayer(r.getLandlord()), PSL.BUY_SOLD_SELLER.msg() - .replace("%region%", r.getName() == null ? r.getId() : r.getName()) - .replace("%price%", String.format("%.2f", r.getPrice())) - .replace("%player%", p.getName())); + PSL.msg( + Bukkit.getPlayer(r.getLandlord()), + PSL.BUY_SOLD_SELLER.replaceAll(Map.of( + "%region%", (r.getName() == null ? r.getId() : r.getName()), + "%price%", String.format("%.2f", r.getPrice()), + "%player%", p.getName() + )) + ); } r.sell(p.getUniqueId()); @@ -118,7 +125,7 @@ public boolean executeArgument(CommandSender s, String[] args, HashMap allowedFlags = new ArrayList<>(r.getTypeOptions().allowedFlags.keySet()); + final List allowedFlags = new ArrayList<>(r.getTypeOptions().allowedFlags.keySet()); - // ensure the page is valid and in range - if (page < 0 || (page * GUI_SIZE) > allowedFlags.size()) { - PSL.msg(p, PSL.PAGE_DOES_NOT_EXIST.msg()); - return true; - } + // ensure the page is valid and in range + if (page < 0 || (page * GUI_SIZE) > allowedFlags.size()) { + PSL.msg(p, PSL.PAGE_DOES_NOT_EXIST.msg()); + return true; + } - // add blank space if gui not long enough - for (int i = 0; i < (GUI_SIZE * page + GUI_SIZE) - (Math.min(allowedFlags.size(), GUI_SIZE * page + GUI_SIZE) - GUI_SIZE * page); i++) { - PSL.msg(p, ChatColor.WHITE + ""); - } + // add blank space if gui not long enough + final int rowsStart = GUI_SIZE * page; + final int rowsEnd = Math.min(allowedFlags.size(), rowsStart + GUI_SIZE); + final int blanks = (rowsStart + GUI_SIZE) - (rowsEnd - rowsStart); + for (int i = 0; i < blanks; i++) { + PSL.msg(p, Component.text(" ")); + } - PSL.msg(p, PSL.FLAG_GUI_HEADER.msg()); + PSL.msg(p, PSL.FLAG_GUI_HEADER.msg()); - // send actual flags - for (int i = GUI_SIZE * page; i < Math.min(allowedFlags.size(), GUI_SIZE * page + GUI_SIZE); i++) { - if (i >= allowedFlags.size()) { - PSL.msg(p, ChatColor.WHITE + ""); - } else { - String flag = allowedFlags.get(i); - List currentFlagGroups = r.getTypeOptions().allowedFlags.get(flag); - TextComponent flagLine = new TextComponent(); + // send actual flags + for (int i = rowsStart; i < Math.min(allowedFlags.size(), rowsStart + GUI_SIZE); i++) { + if (i >= allowedFlags.size()) { + PSL.msg(p, Component.text(" ")); + continue; + } + + final String flagKey = allowedFlags.get(i); + final List currentFlagGroups = r.getTypeOptions().allowedFlags.get(flagKey); // calculate flag command - String suggestedCommand = "/" + ProtectionStones.getInstance().getConfigOptions().base_command + " flag "; + final String suggestedCommand = "/" + ProtectionStones.getInstance().getConfigOptions().base_command + " flag "; // match flag - Flag f = Flags.fuzzyMatchFlag(WGUtils.getFlagRegistry(), flag); + final Flag f = Flags.fuzzyMatchFlag(WGUtils.getFlagRegistry(), flagKey); if (f == null) continue; + Object fValue = r.getWGRegion().getFlag(f); - // check current flag's set group + // sanitize § -> & in String flag values to avoid "illegal characters" kicks + if (fValue instanceof String) { + fValue = ((String) fValue).replace("§", "&"); + } + + // current region group for this flag String groupfValue = "all"; if (f.getRegionGroupFlag() != null && r.getWGRegion().getFlag(f.getRegionGroupFlag()) != null) { groupfValue = r.getWGRegion().getFlag(f.getRegionGroupFlag()).toString() .toLowerCase().replace("_", ""); } - // add flag group if there is one set for the flag (for use in click commands) - String flagGroup = ""; - if (f.getRegionGroupFlag() != null && r.getWGRegion().getFlag(f.getRegionGroupFlag()) != null) { - flagGroup = "-g " + groupfValue + " "; - } + // if a group is set, include it in click commands + final String flagGroupArg = (f.getRegionGroupFlag() != null && r.getWGRegion().getFlag(f.getRegionGroupFlag()) != null) + ? "-g " + groupfValue + " " + : ""; - // replace § with & to prevent "illegal characters in chat" disconnection - if (fValue instanceof String) { - fValue = ((String) fValue).replace("§", "&"); - } - - // add line based on flag type - if (f instanceof StateFlag) { // allow/deny - boolean isGroupValueAll = groupfValue.equalsIgnoreCase("all") || groupfValue.isEmpty(); - - TextComponent allow = new TextComponent((fValue == StateFlag.State.ALLOW ? ChatColor.WHITE : ChatColor.DARK_GRAY) + "Allow"), - deny = new TextComponent((fValue == StateFlag.State.DENY ? ChatColor.WHITE : ChatColor.DARK_GRAY) + "Deny"); + // build the line + Component flagLine = Component.empty(); - allow.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(PSL.FLAG_GUI_HOVER_SET.msg()).create())); - deny.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(PSL.FLAG_GUI_HOVER_SET.msg()).create())); + final boolean isGroupValueAll = groupfValue.equalsIgnoreCase("all") || groupfValue.isEmpty(); - if (fValue == StateFlag.State.ALLOW) { - allow.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, suggestedCommand + flagGroup + page + ":" + flag + " none")); - deny.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, suggestedCommand + flagGroup + page + ":" + flag + " deny")); - } else if (fValue == StateFlag.State.DENY) { - allow.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, suggestedCommand + flagGroup + page + ":" + flag + " allow")); - deny.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, suggestedCommand + flagGroup + page + ":" + flag + " none")); + if (f instanceof StateFlag) { + // allow/deny widgets + final boolean isAllow = fValue == StateFlag.State.ALLOW; + final boolean isDeny = fValue == StateFlag.State.DENY; + + Component allow = Component.text("Allow", isAllow ? NamedTextColor.WHITE : NamedTextColor.DARK_GRAY); + Component deny = Component.text("Deny", isDeny ? NamedTextColor.WHITE : NamedTextColor.DARK_GRAY); + + allow = allow.hoverEvent(HoverEvent.showText(PSL.FLAG_GUI_HOVER_SET.msg())); + deny = deny.hoverEvent(HoverEvent.showText(PSL.FLAG_GUI_HOVER_SET.msg())); + + if (isAllow) { + allow = allow.clickEvent(ClickEvent.runCommand(suggestedCommand + flagGroupArg + page + ":" + flagKey + " none")); + deny = deny.clickEvent(ClickEvent.runCommand(suggestedCommand + flagGroupArg + page + ":" + flagKey + " deny")); + } else if (isDeny) { + allow = allow.clickEvent(ClickEvent.runCommand(suggestedCommand + flagGroupArg + page + ":" + flagKey + " allow")); + deny = deny.clickEvent(ClickEvent.runCommand(suggestedCommand + flagGroupArg + page + ":" + flagKey + " none")); } else { - allow.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, suggestedCommand + flagGroup + page + ":" + flag + " allow")); - deny.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, suggestedCommand + flagGroup + page + ":" + flag + " deny")); + allow = allow.clickEvent(ClickEvent.runCommand(suggestedCommand + flagGroupArg + page + ":" + flagKey + " allow")); + deny = deny.clickEvent(ClickEvent.runCommand(suggestedCommand + flagGroupArg + page + ":" + flagKey + " deny")); } - // HACK: Prevent pvp flag value from being changed to none/null, if it is set to a value with the group flag set to all - if (flag.equalsIgnoreCase("pvp") && isGroupValueAll) { - if (fValue == StateFlag.State.DENY) { - deny.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(PSL.FLAG_PREVENT_EXPLOIT_HOVER.msg()).create())); - } else if (fValue == StateFlag.State.ALLOW) { - allow.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(PSL.FLAG_PREVENT_EXPLOIT_HOVER.msg()).create())); - } - } - - flagLine.addExtra(allow); - flagLine.addExtra(" "); - flagLine.addExtra(deny); - flagLine.addExtra(getDots(5)); - } else if (f instanceof BooleanFlag) { // true/false - TextComponent allow = new TextComponent((fValue == Boolean.TRUE ? ChatColor.WHITE : ChatColor.DARK_GRAY) + "True"), - deny = new TextComponent((fValue == Boolean.FALSE ? ChatColor.WHITE : ChatColor.DARK_GRAY) + "False"); - - allow.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(PSL.FLAG_GUI_HOVER_SET.msg()).create())); - deny.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(PSL.FLAG_GUI_HOVER_SET.msg()).create())); - if (fValue == Boolean.TRUE) { - allow.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, suggestedCommand + flagGroup + page + ":" + flag + " none")); - deny.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, suggestedCommand + flagGroup + page + ":" + flag + " false")); - } else if (fValue == Boolean.FALSE) { - allow.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, suggestedCommand + flagGroup + page + ":" + flag + " true")); - deny.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, suggestedCommand + flagGroup + page + ":" + flag + " none")); + flagLine = flagLine.append(allow) + .append(space()) + .append(deny) + .append(dots(5)); + } else if (f instanceof BooleanFlag) { + // true/false widgets + final boolean isTrue = fValue == Boolean.TRUE; + final boolean isFalse = fValue == Boolean.FALSE; + + Component t = Component.text("True", isTrue ? NamedTextColor.WHITE : NamedTextColor.DARK_GRAY); + Component fC = Component.text("False", isFalse ? NamedTextColor.WHITE : NamedTextColor.DARK_GRAY); + + t = t.hoverEvent(HoverEvent.showText(PSL.FLAG_GUI_HOVER_SET.msg())); + fC = fC.hoverEvent(HoverEvent.showText(PSL.FLAG_GUI_HOVER_SET.msg())); + + if (isTrue) { + t = t.clickEvent(ClickEvent.runCommand(suggestedCommand + flagGroupArg + page + ":" + flagKey + " none")); + fC = fC.clickEvent(ClickEvent.runCommand(suggestedCommand + flagGroupArg + page + ":" + flagKey + " false")); + } else if (isFalse) { + t = t.clickEvent(ClickEvent.runCommand(suggestedCommand + flagGroupArg + page + ":" + flagKey + " true")); + fC = fC.clickEvent(ClickEvent.runCommand(suggestedCommand + flagGroupArg + page + ":" + flagKey + " none")); } else { - allow.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, suggestedCommand + flagGroup + page + ":" + flag + " true")); - deny.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, suggestedCommand + flagGroup + page + ":" + flag + " false")); + t = t.clickEvent(ClickEvent.runCommand(suggestedCommand + flagGroupArg + page + ":" + flagKey + " true")); + fC = fC.clickEvent(ClickEvent.runCommand(suggestedCommand + flagGroupArg + page + ":" + flagKey + " false")); } - flagLine.addExtra(allow); - flagLine.addExtra(" "); - flagLine.addExtra(deny); - flagLine.addExtra(getDots(5)); - } else { // text - TextComponent edit = new TextComponent(ChatColor.DARK_GRAY + "Edit"); - edit.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(PSL.FLAG_GUI_HOVER_SET_TEXT.msg() - .replace("%value%", fValue == null ? "none" : fValue.toString())).create())); - edit.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, suggestedCommand + flagGroup + flag + " ")); - flagLine.addExtra(edit); - flagLine.addExtra(getDots(22)); + flagLine = flagLine.append(t) + .append(space()) + .append(fC) + .append(dots(5)); + } else { + // text flag -> edit widget + final String currentVal = (fValue == null ? "none" : fValue.toString()); + Component edit = Component.text("Edit", NamedTextColor.DARK_GRAY) + .hoverEvent(HoverEvent.showText( + PSL.FLAG_GUI_HOVER_SET_TEXT.replace("%value%", currentVal) + )) + .clickEvent(ClickEvent.suggestCommand(suggestedCommand + flagGroupArg + flagKey + " ")); + flagLine = flagLine.append(edit) + .append(dots(22)); } - // put group it applies to - TextComponent groupChange = new TextComponent(ChatColor.DARK_GRAY + " [ " + ChatColor.WHITE + groupfValue + ChatColor.DARK_GRAY + " ]"); + // group switcher [ group ] + Component groupChange = Component.text(" [ ", NamedTextColor.DARK_GRAY) + .append(Component.text(groupfValue, NamedTextColor.WHITE)) + .append(Component.text(" ]", NamedTextColor.DARK_GRAY)); - String nextGroup; - if (currentFlagGroups.contains(groupfValue)) { // if the current flag group is an allowed flag group + // figure out next group + final String nextGroup; + if (currentFlagGroups.contains(groupfValue)) { nextGroup = currentFlagGroups.get((currentFlagGroups.indexOf(groupfValue) + 1) % currentFlagGroups.size()); - } else { // otherwise, just take the first allowed flag group + } else { nextGroup = currentFlagGroups.get(0); } - // set hover and click task for flag group - BaseComponent[] hover; - if (fValue == null) { - hover = new ComponentBuilder(PSL.FLAG_GUI_HOVER_CHANGE_GROUP_NULL.msg()).create(); + // hover/click for group change + // special-case pvp+all prevention + if (flagKey.equalsIgnoreCase("pvp") && isGroupValueAll) { + groupChange = groupChange + .hoverEvent(HoverEvent.showText(PSL.FLAG_PREVENT_EXPLOIT_HOVER.msg())); + // no click event (disabled on purpose) } else { - hover = new ComponentBuilder(PSL.FLAG_GUI_HOVER_CHANGE_GROUP.msg().replace("%group%", nextGroup)).create(); - } - if (!nextGroup.equals(groupfValue)) { // only display hover message if the group is not the same - groupChange.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, hover)); + if (fValue == null) { + groupChange = groupChange.hoverEvent(HoverEvent.showText(PSL.FLAG_GUI_HOVER_CHANGE_GROUP_NULL.msg())); + } else { + groupChange = groupChange + .hoverEvent(HoverEvent.showText(PSL.FLAG_GUI_HOVER_CHANGE_GROUP.replace("%group%", nextGroup))) + .clickEvent(ClickEvent.runCommand(suggestedCommand + "-g " + nextGroup + " " + page + ":" + flagKey + " " + (fValue == null ? "none" : fValue))); + } } - groupChange.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, suggestedCommand + "-g " + nextGroup + " " + page + ":" + flag + " " + fValue)); - flagLine.addExtra(groupChange); - // send message - flagLine.addExtra(getDots(40 - REGION_GROUP_KERNING_LENGTHS[FLAG_GROUPS.indexOf(groupfValue)]) + ChatColor.AQUA + " " + flag); + // append group and trailing dots + flag name + flagLine = flagLine.append(groupChange); + + // keep your kerning/dots layout + final int kerning = 40 - REGION_GROUP_KERNING_LENGTHS[FLAG_GROUPS.indexOf(groupfValue)]; + flagLine = flagLine.append(dots(Math.max(0, kerning))) + .append(space()) + .append(Component.text(flagKey, NamedTextColor.AQUA)); - p.spigot().sendMessage(flagLine); + // send + ProtectionStones.getInstance().audiences().player(p).sendMessage(flagLine); } + + return true; } - // create footer - TextComponent backPage = new TextComponent(ChatColor.AQUA + " <<"), nextPage = new TextComponent(ChatColor.AQUA + ">> "); - backPage.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(PSL.GO_BACK_PAGE.msg()).create())); - nextPage.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(PSL.GO_NEXT_PAGE.msg()).create())); - backPage.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/" + ProtectionStones.getInstance().getConfigOptions().base_command + " flag " + (page - 1))); - nextPage.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/" + ProtectionStones.getInstance().getConfigOptions().base_command + " flag " + (page + 1))); - - TextComponent footer = new TextComponent(ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "=====" + ChatColor.RESET); - // add back page button if the page isn't 0 - if (page != 0) footer.addExtra(backPage); - // add page number - footer.addExtra(new TextComponent(ChatColor.WHITE + " " + (page + 1) + " ")); - // add forward page button if the page isn't last - if (page * GUI_SIZE + GUI_SIZE < r.getTypeOptions().allowedFlags.size()) footer.addExtra(nextPage); - footer.addExtra(ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "====="); - - p.spigot().sendMessage(footer); - return true; + // helpers + private static Component dots(final int n) { + return (n <= 0) ? Component.empty() : Component.text(".".repeat(n), NamedTextColor.DARK_GRAY); + } + private static Component space() { + return Component.text(" "); } @Override @@ -378,7 +397,7 @@ static void setFlag(PSRegion r, CommandSender p, String flagName, String value, region.setFlag(flag.getRegionGroupFlag(), null); } - PSL.msg(p, PSL.FLAG_SET.msg().replace("%flag%", flagName)); + PSL.msg(p, PSL.FLAG_SET.replace("%flag%", flagName)); } else if (value.equalsIgnoreCase("null") || value.equalsIgnoreCase("none")) { // null flag (remove) @@ -396,7 +415,7 @@ static void setFlag(PSRegion r, CommandSender p, String flagName, String value, region.setFlag(flag.getRegionGroupFlag(), null); } - PSL.msg(p, PSL.FLAG_SET.msg().replace("%flag%", flagName)); + PSL.msg(p, PSL.FLAG_SET.replace("%flag%", flagName)); } else { // custom set flag using WG internal FlagContext fc = FlagContext.create().setInput(value).build(); @@ -404,12 +423,12 @@ static void setFlag(PSRegion r, CommandSender p, String flagName, String value, if (!groupValue.equals("") && flag.getRegionGroupFlag() != null) { region.setFlag(flag.getRegionGroupFlag(), flag.getRegionGroupFlag().detectValue(groupValue)); } - PSL.msg(p, PSL.FLAG_SET.msg().replace("%flag%", flagName)); + PSL.msg(p, PSL.FLAG_SET.replace("%flag%", flagName)); } } catch (InvalidFlagFormat invalidFlagFormat) { //invalidFlagFormat.printStackTrace(); - PSL.msg(p, PSL.FLAG_NOT_SET.msg().replace("%flag%", flagName)); + PSL.msg(p, PSL.FLAG_NOT_SET.replace("%flag%", flagName)); } } diff --git a/src/main/java/dev/espi/protectionstones/commands/ArgGet.java b/src/main/java/dev/espi/protectionstones/commands/ArgGet.java index c66a2832..9fdfc65b 100644 --- a/src/main/java/dev/espi/protectionstones/commands/ArgGet.java +++ b/src/main/java/dev/espi/protectionstones/commands/ArgGet.java @@ -19,10 +19,9 @@ import dev.espi.protectionstones.PSProtectBlock; import dev.espi.protectionstones.PSL; import dev.espi.protectionstones.ProtectionStones; -import net.md_5.bungee.api.chat.ClickEvent; -import net.md_5.bungee.api.chat.ComponentBuilder; -import net.md_5.bungee.api.chat.HoverEvent; -import net.md_5.bungee.api.chat.TextComponent; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; import net.milkbowl.vault.economy.EconomyResponse; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; @@ -55,32 +54,46 @@ public HashMap getRegisteredFlags() { } private boolean openGetGUI(Player p) { + // Send the header PSL.msg(p, PSL.GET_HEADER.msg()); + for (PSProtectBlock b : ProtectionStones.getInstance().getConfiguredBlocks()) { - if ((!b.permission.equals("") && !p.hasPermission(b.permission)) || (b.preventPsGet && !p.hasPermission("protectionstones.admin"))) { + if ((!b.permission.equals("") && !p.hasPermission(b.permission)) + || (b.preventPsGet && !p.hasPermission("protectionstones.admin"))) { continue; // no permission } String price = new DecimalFormat("#.##").format(b.price); - TextComponent tc = new TextComponent(PSL.GET_GUI_BLOCK.msg() - .replace("%alias%", b.alias) - .replace("%price%", price) - .replace("%description%", b.description) - .replace("%xradius%", ""+b.xRadius) - .replace("%yradius%", ""+b.yRadius) - .replace("%zradius%", ""+b.zRadius)); - - tc.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(PSL.GET_GUI_HOVER.msg() - .replace("%alias%", b.alias) - .replace("%price%", price) - .replace("%description%", b.description) - .replace("%xradius%", ""+b.xRadius) - .replace("%yradius%", ""+b.yRadius) - .replace("%zradius%", ""+b.zRadius)).create())); - tc.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/" + ProtectionStones.getInstance().getConfigOptions().base_command + " get " + b.alias)); - - p.spigot().sendMessage(tc); + // Build the line using placeholders + Component line = PSL.GET_GUI_BLOCK.replaceAll(Map.of( + "%alias%", b.alias, + "%price%", price, + "%description%", b.description, + "%xradius%", String.valueOf(b.xRadius), + "%yradius%", String.valueOf(b.yRadius), + "%zradius%", String.valueOf(b.zRadius) + )); + + // Build hover text + Component hover = PSL.GET_GUI_HOVER.replaceAll(Map.of( + "%alias%", b.alias, + "%price%", price, + "%description%", b.description, + "%xradius%", String.valueOf(b.xRadius), + "%yradius%", String.valueOf(b.yRadius), + "%zradius%", String.valueOf(b.zRadius) + )); + + // Add hover + click events + Component clickable = line + .hoverEvent(HoverEvent.showText(hover)) + .clickEvent(ClickEvent.runCommand( + "/" + ProtectionStones.getInstance().getConfigOptions().base_command + " get " + b.alias + )); + + // Send Adventure component to player + PSL.msg(p, clickable); } return true; } @@ -113,7 +126,7 @@ public boolean executeArgument(CommandSender s, String[] args, HashMap helpMenu = new ArrayList<>(); public static void initHelpMenu() { - String base = "/" + ProtectionStones.getInstance().getConfigOptions().base_command + " "; + final String base = "/" + ProtectionStones.getInstance().getConfigOptions().base_command + " "; helpMenu.clear(); + helpMenu.add(new HelpEntry(sendWithPerm(PSL.INFO_HELP.msg(), PSL.INFO_HELP_DESC.msg(), base + "info"), "protectionstones.info")); helpMenu.add(new HelpEntry(sendWithPerm(PSL.ADDREMOVE_HELP.msg(), PSL.ADDREMOVE_HELP_DESC.msg(), base), "protectionstones.members")); helpMenu.add(new HelpEntry(sendWithPerm(PSL.ADDREMOVE_OWNER_HELP.msg(), PSL.ADDREMOVE_OWNER_HELP_DESC.msg(), base), "protectionstones.owners")); @@ -78,6 +76,7 @@ public static void initHelpMenu() { helpMenu.add(new HelpEntry(sendWithPerm(PSL.REGION_HELP.msg(), PSL.REGION_HELP_DESC.msg(), base + "region"), "protectionstones.region")); helpMenu.add(new HelpEntry(sendWithPerm(PSL.ADMIN_HELP.msg(), PSL.ADMIN_HELP_DESC.msg(), base + "admin"), "protectionstones.admin")); helpMenu.add(new HelpEntry(sendWithPerm(PSL.RELOAD_HELP.msg(), PSL.RELOAD_HELP_DESC.msg(), base + "reload"), "protectionstones.admin")); + } @Override @@ -109,25 +108,37 @@ public boolean executeArgument(CommandSender p, String[] args, HashMap entries = new ArrayList<>(); + // Build visible entries based on permissions, skip “blank” (plain-text empty) lines + final PlainTextComponentSerializer plain = PlainTextComponentSerializer.plainText(); + List entries = new ArrayList<>(); for (HelpEntry he : helpMenu) { - // ignore blank lines - if (he.msg.getText().equals("")) { - continue; - } + Component line = he.msg; + if (line == null) continue; + + // ignore blank lines (no visible text) + if (plain.serialize(line).isBlank()) continue; + // check player permissions for (String perm : he.permission) { if (p.hasPermission(perm)) { - entries.add(he.msg); + entries.add(line); break; } } } - TextGUI.displayGUI(p, PSL.HELP.msg(), "/" + ProtectionStones.getInstance().getConfigOptions().base_command + " help %page%", page, GUI_SIZE, entries, false); + TextGUI.displayGUI( + p, + PSL.HELP.msg(), + "/" + ProtectionStones.getInstance().getConfigOptions().base_command + " help %page%", + page, + GUI_SIZE, + entries, + false + ); if (page >= 0 && page * GUI_SIZE + GUI_SIZE < entries.size()) { - PSL.msg(p, PSL.HELP_NEXT.msg().replace("%page%", page + 2 + "")); + PSL.msg(p, PSL.HELP_NEXT.replace("%page%", String.valueOf(page + 2))); } return true; @@ -138,10 +149,10 @@ public List tabComplete(CommandSender sender, String alias, String[] arg return null; } - private static TextComponent sendWithPerm(String msg, String desc, String cmd) { - TextComponent m = new TextComponent(msg); - m.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, cmd)); - m.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(desc).create())); - return m; + /** Adventure version: clickable + hoverable line. */ + private static Component sendWithPerm(Component title, Component description, String cmd) { + return title + .clickEvent(ClickEvent.suggestCommand(cmd)) + .hoverEvent(HoverEvent.showText(description)); } } diff --git a/src/main/java/dev/espi/protectionstones/commands/ArgHome.java b/src/main/java/dev/espi/protectionstones/commands/ArgHome.java index 44871a13..626acfa9 100644 --- a/src/main/java/dev/espi/protectionstones/commands/ArgHome.java +++ b/src/main/java/dev/espi/protectionstones/commands/ArgHome.java @@ -19,10 +19,10 @@ import dev.espi.protectionstones.utils.ChatUtil; import dev.espi.protectionstones.utils.MiscUtil; import dev.espi.protectionstones.utils.TextGUI; -import net.md_5.bungee.api.chat.ClickEvent; -import net.md_5.bungee.api.chat.ComponentBuilder; -import net.md_5.bungee.api.chat.HoverEvent; -import net.md_5.bungee.api.chat.TextComponent; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; @@ -91,30 +91,49 @@ public List tabComplete(CommandSender sender, String alias, String[] arg } private static final int GUI_SIZE = 17; + private static final MiniMessage MM = MiniMessage.miniMessage(); private void openHomeGUI(PSPlayer psp, List homes, int page) { - List entries = new ArrayList<>(); + final String base = ProtectionStones.getInstance().getConfigOptions().base_command; + + List entries = new ArrayList<>(); for (PSRegion r : homes) { - String msg; - if (r.getName() == null) { - msg = ChatColor.GRAY + "> " + ChatColor.AQUA + r.getId(); - } else { - msg = ChatColor.GRAY + "> " + ChatColor.AQUA + r.getName() + " (" + r.getId() + ")"; - } - TextComponent tc = new TextComponent(msg); - tc.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(PSL.HOME_CLICK_TO_TP.msg()).create())); - if (r.getName() == null) { - tc.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/" + ProtectionStones.getInstance().getConfigOptions().base_command + " home " + r.getId())); - } else { - tc.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/" + ProtectionStones.getInstance().getConfigOptions().base_command + " home " + r.getName())); - } - entries.add(tc); + final boolean hasName = r.getName() != null && !r.getName().isEmpty(); + // Build the visible label with MiniMessage so colors/hex work + final String label = hasName + ? "> " + r.getName() + " (" + r.getId() + ")" + : "> " + r.getId() + ""; + + Component line = MM.deserialize(label) + .hoverEvent(HoverEvent.showText(PSL.HOME_CLICK_TO_TP.msg())) + .clickEvent(ClickEvent.runCommand( + "/" + base + " home " + (hasName ? r.getName() : r.getId()) + )); + + entries.add(line); } - TextGUI.displayGUI(psp.getPlayer(), PSL.HOME_HEADER.msg(), "/" + ProtectionStones.getInstance().getConfigOptions().base_command + " home -p %page%", page, GUI_SIZE, entries, true); - - if (page * GUI_SIZE + GUI_SIZE < entries.size()) - PSL.msg(psp, PSL.HOME_NEXT.msg().replace("%page%", page + 2 + "")); + // Header is already a Component from PSL + Component header = PSL.HOME_HEADER.msg(); + + // If your TextGUI has been updated to Adventure: + TextGUI.displayGUI( + psp.getPlayer(), + header, + "/" + base + " home -p %page%", + page, + GUI_SIZE, + entries, + true + ); + + // Show "next page" helper if there are more entries beyond this page + if ((page + 1) * GUI_SIZE < entries.size()) { + Component nextMsg = PSL.HOME_NEXT.replaceAll(Map.of( + "%page%", String.valueOf(page + 2) + )); + PSL.msg(psp.getPlayer(), nextMsg); + } } @Override diff --git a/src/main/java/dev/espi/protectionstones/commands/ArgInfo.java b/src/main/java/dev/espi/protectionstones/commands/ArgInfo.java index bdcca788..3a3d26f7 100644 --- a/src/main/java/dev/espi/protectionstones/commands/ArgInfo.java +++ b/src/main/java/dev/espi/protectionstones/commands/ArgInfo.java @@ -24,6 +24,9 @@ import dev.espi.protectionstones.*; import dev.espi.protectionstones.utils.UUIDCache; import dev.espi.protectionstones.utils.WGUtils; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; @@ -65,7 +68,11 @@ public boolean executeArgument(CommandSender s, String[] args, HashMap regions, UUID pUUID, boolea if (isCurrentPlayer) { PSL.msg(s, PSL.LIST_NO_REGIONS.msg()); } else { - PSL.msg(s, PSL.LIST_NO_REGIONS_PLAYER.msg().replace("%player%", UUIDCache.getNameFromUUID(pUUID))); + PSL.msg(s, PSL.LIST_NO_REGIONS_PLAYER.replace("%player%", UUIDCache.getNameFromUUID(pUUID))); } return; } - PSL.msg(s, PSL.LIST_HEADER.msg().replace("%player%", UUIDCache.getNameFromUUID(pUUID))); + PSL.msg(s, PSL.LIST_HEADER.replace("%player%", UUIDCache.getNameFromUUID(pUUID))); if (!ownerOf.isEmpty()) { PSL.msg(s, PSL.LIST_OWNER.msg()); diff --git a/src/main/java/dev/espi/protectionstones/commands/ArgMerge.java b/src/main/java/dev/espi/protectionstones/commands/ArgMerge.java index 35a79fc7..6f3205b2 100644 --- a/src/main/java/dev/espi/protectionstones/commands/ArgMerge.java +++ b/src/main/java/dev/espi/protectionstones/commands/ArgMerge.java @@ -22,10 +22,10 @@ import dev.espi.protectionstones.*; import dev.espi.protectionstones.utils.WGMerge; import dev.espi.protectionstones.utils.WGUtils; -import net.md_5.bungee.api.chat.ClickEvent; -import net.md_5.bungee.api.chat.ComponentBuilder; -import net.md_5.bungee.api.chat.HoverEvent; -import net.md_5.bungee.api.chat.TextComponent; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; @@ -55,16 +55,35 @@ public HashMap getRegisteredFlags() { return null; } - public static List getGUI(Player p, PSRegion r) { + public static List getGUI(Player p, PSRegion r) { return r.getMergeableRegions(p).stream() .map(psr -> { - TextComponent tc = new TextComponent(ChatColor.AQUA + "> " + ChatColor.WHITE + psr.getId()); - if (psr.getName() != null) tc.addExtra(" (" + psr.getName() + ")"); // name - tc.addExtra(" (" + psr.getTypeOptions().alias + ")"); // region type + // Base label: > id + Component base = Component.text() + .append(Component.text("> ").color(NamedTextColor.AQUA)) + .append(Component.text(psr.getId(), NamedTextColor.WHITE)) + .build(); + + // Optional name + if (psr.getName() != null) { + base = base.append(Component.text(" (" + psr.getName() + ")", NamedTextColor.GRAY)); + } + + // Region type + base = base.append(Component.text(" (" + psr.getTypeOptions().alias + ")", NamedTextColor.GRAY)); + + // Add click + hover events + String cmd = "/" + ProtectionStones.getInstance().getConfigOptions().base_command + + " merge " + r.getId() + " " + psr.getId(); + + base = base.clickEvent(ClickEvent.runCommand(cmd)); - tc.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/" + ProtectionStones.getInstance().getConfigOptions().base_command + " merge " + r.getId() + " " + psr.getId())); - tc.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(PSL.MERGE_CLICK_TO_MERGE.msg().replace("%region%", psr.getId())).create())); - return tc; + // Hover: use your PSL message (converted to Component already) + base = base.hoverEvent(HoverEvent.showText( + PSL.MERGE_CLICK_TO_MERGE.replaceAll(Map.of("%region%", psr.getId())) + )); + + return base; }) .collect(Collectors.toList()); } @@ -85,7 +104,11 @@ public boolean executeArgument(CommandSender s, String[] args, HashMap components = getGUI(p, r); + List components = getGUI(p, r); if (components.isEmpty()) { PSL.msg(p, PSL.MERGE_NO_REGIONS.msg()); } else { - p.sendMessage(ChatColor.WHITE + ""); // send empty line - PSL.msg(p, PSL.MERGE_HEADER.msg().replace("%region%", r.getId())); + PSL.msg(p, Component.empty()); + PSL.msg(p, PSL.MERGE_HEADER.replaceAll(Map.of("%region%", r.getId()))); PSL.msg(p, PSL.MERGE_WARNING.msg()); - for (TextComponent tc : components) p.spigot().sendMessage(tc); - p.sendMessage(ChatColor.WHITE + ""); // send empty line + for (Component tc : components) { + PSL.msg(p, tc); + } + + // send empty line again + PSL.msg(p, Component.empty()); } } else if (args.length == 3) { // /ps merge [region] [root] @@ -136,7 +163,7 @@ public boolean executeArgument(CommandSender s, String[] args, HashMap " + ChatColor.GRAY + "/" + ProtectionStones.getInstance().getConfigOptions().base_command + - " rent lease [price] [period]"; + public static Component getLeaseHelp() { + return Component.text("> ", NamedTextColor.AQUA) + .append(Component.text("/" + ProtectionStones.getInstance().getConfigOptions().base_command + " rent lease [price] [period]", NamedTextColor.GRAY)); } - public static String getStopLeaseHelp() { - return ChatColor.AQUA + "> " + ChatColor.GRAY + "/" + ProtectionStones.getInstance().getConfigOptions().base_command + - " rent stoplease"; + public static Component getStopLeaseHelp() { + return Component.text("> ", NamedTextColor.AQUA) + .append(Component.text("/" + ProtectionStones.getInstance().getConfigOptions().base_command + " rent stoplease", NamedTextColor.GRAY)); } - public static String getRentHelp() { - return ChatColor.AQUA + "> " + ChatColor.GRAY + "/" + ProtectionStones.getInstance().getConfigOptions().base_command + - " rent rent"; + public static Component getRentHelp() { + return Component.text("> ", NamedTextColor.AQUA) + .append(Component.text("/" + ProtectionStones.getInstance().getConfigOptions().base_command + " rent rent", NamedTextColor.GRAY)); } - public static String getStopRentingHelp() { - return ChatColor.AQUA + "> " + ChatColor.GRAY + "/" + ProtectionStones.getInstance().getConfigOptions().base_command + - " rent stoprenting"; + public static Component getStopRentingHelp() { + return Component.text("> ", NamedTextColor.AQUA) + .append(Component.text("/" + ProtectionStones.getInstance().getConfigOptions().base_command + " rent stoprenting", NamedTextColor.GRAY)); } @Override @@ -129,27 +131,30 @@ public boolean executeArgument(CommandSender s, String[] args, HashMap ProtectionStones.getInstance().getConfigOptions().maxRentPrice) // if rent price is too high - return PSL.msg(p, PSL.RENT_PRICE_TOO_HIGH.msg().replace("%price%", ""+ProtectionStones.getInstance().getConfigOptions().maxRentPrice)); + return PSL.msg(p, PSL.RENT_PRICE_TOO_HIGH.replace("%price%", ""+ProtectionStones.getInstance().getConfigOptions().maxRentPrice)); String period = String.join(" ", Arrays.asList(args).subList(3, args.length)); try { Duration d = MiscUtil.parseRentPeriod(period); if (ProtectionStones.getInstance().getConfigOptions().minRentPeriod != -1 && d.getSeconds() < ProtectionStones.getInstance().getConfigOptions().minRentPeriod) { - return PSL.msg(p, PSL.RENT_PERIOD_TOO_SHORT.msg().replace("%period%", ""+ProtectionStones.getInstance().getConfigOptions().minRentPeriod)); + return PSL.msg(p, PSL.RENT_PERIOD_TOO_SHORT.replace("%period%", ""+ProtectionStones.getInstance().getConfigOptions().minRentPeriod)); } if (ProtectionStones.getInstance().getConfigOptions().maxRentPeriod != -1 && d.getSeconds() > ProtectionStones.getInstance().getConfigOptions().maxRentPeriod) { - return PSL.msg(p, PSL.RENT_PERIOD_TOO_LONG.msg().replace("%period%", ""+ProtectionStones.getInstance().getConfigOptions().maxRentPeriod)); + return PSL.msg(p, PSL.RENT_PERIOD_TOO_LONG.replace("%period%", ""+ProtectionStones.getInstance().getConfigOptions().maxRentPeriod)); } } catch (NumberFormatException e) { return PSL.msg(p, PSL.RENT_PERIOD_INVALID.msg()); } r.setRentable(p.getUniqueId(), period, price); - return PSL.msg(p, PSL.RENT_LEASE_SUCCESS.msg().replace("%price%", args[2]).replace("%period%", period)); + return PSL.msg(p, PSL.RENT_LEASE_SUCCESS.replaceAll(Map.of( + "%price%", args[2], + "%period%", period + ))); case "stoplease": if (r.getRentStage() == PSRegion.RentStage.NOT_RENTING) @@ -170,10 +175,10 @@ public boolean executeArgument(CommandSender s, String[] args, HashMap. - */ - package dev.espi.protectionstones.commands; import dev.espi.protectionstones.*; import dev.espi.protectionstones.utils.MiscUtil; import dev.espi.protectionstones.utils.TextGUI; import dev.espi.protectionstones.utils.UUIDCache; -import net.md_5.bungee.api.chat.ClickEvent; -import net.md_5.bungee.api.chat.ComponentBuilder; -import net.md_5.bungee.api.chat.HoverEvent; -import net.md_5.bungee.api.chat.TextComponent; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; import net.milkbowl.vault.economy.EconomyResponse; import org.apache.commons.lang3.math.NumberUtils; import org.bukkit.Bukkit; -import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.util.StringUtil; import java.util.*; +import java.util.stream.Collectors; public class ArgTax implements PSCommandArg { - static final String INFO_HELP = ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps tax info [region (optional)]", // maybe put in /ps info - PAY_HELP = ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps tax pay [amount] [region (optional)]", - AUTOPAY_HELP = ChatColor.AQUA + "> " + ChatColor.GRAY + "/ps tax autopay [region (optional)]"; + private static final Component INFO_HELP = + Component.text("> /ps tax info [region (optional)]", NamedTextColor.AQUA); + private static final Component PAY_HELP = + Component.text("> /ps tax pay [amount] [region (optional)]", NamedTextColor.AQUA); + private static final Component AUTOPAY_HELP = + Component.text("> /ps tax autopay [region (optional)]", NamedTextColor.AQUA); @Override public List getNames() { @@ -74,18 +62,18 @@ public boolean executeArgument(CommandSender s, String[] args, HashMap flags, PSPlayer p) { if (args.length == 2) { // /ps tax info Bukkit.getScheduler().runTaskAsynchronously(ProtectionStones.getInstance(), () -> { - int pageNum = (flags.get("-p") == null || !MiscUtil.isValidInteger(flags.get("-p")) ? 0 : Integer.parseInt(flags.get("-p"))-1); + int pageNum = (flags.get("-p") == null || !MiscUtil.isValidInteger(flags.get("-p")) + ? 0 + : Integer.parseInt(flags.get("-p")) - 1); - List entries = new ArrayList<>(); + List entries = new ArrayList<>(); for (PSRegion r : p.getTaxEligibleRegions()) { - double amountDue = 0; - for (PSRegion.TaxPayment tp : r.getTaxPaymentsDue()) { - amountDue += tp.getAmount(); - } - - TextComponent component; - if (r.getTaxAutopayer() != null & r.getTaxAutopayer() == p.getUuid()) { - component = new TextComponent(PSL.TAX_PLAYER_REGION_INFO_AUTOPAYER.msg() - .replace("%region%", (r.getName() == null ? r.getId() : r.getName() + " (" + r.getId() + ")")) - .replace("%money%", String.format("%.2f", amountDue))); - } else { - component = new TextComponent(PSL.TAX_PLAYER_REGION_INFO.msg() - .replace("%region%", (r.getName() == null ? r.getId() : r.getName() + " (" + r.getId() + ")")) - .replace("%money%", String.format("%.2f", amountDue))); - } - component.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/" + ProtectionStones.getInstance().getConfigOptions().base_command + " tax info " + r.getId())); - component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(PSL.TAX_CLICK_TO_SHOW_MORE_INFO.msg()).create())); - entries.add(component); + double amountDue = r.getTaxPaymentsDue().stream() + .mapToDouble(PSRegion.TaxPayment::getAmount).sum(); + + Component base = (r.getTaxAutopayer() != null && r.getTaxAutopayer().equals(p.getUuid())) + ? PSL.TAX_PLAYER_REGION_INFO_AUTOPAYER.replaceAll(Map.of( + "%region%", (r.getName() == null ? r.getId() : r.getName() + " (" + r.getId() + ")"), + "%money%", String.format("%.2f", amountDue) + )) + : PSL.TAX_PLAYER_REGION_INFO.replaceAll(Map.of( + "%region%", (r.getName() == null ? r.getId() : r.getName() + " (" + r.getId() + ")"), + "%money%", String.format("%.2f", amountDue) + )); + + Component clickable = base + .clickEvent(ClickEvent.runCommand("/" + ProtectionStones.getInstance().getConfigOptions().base_command + " tax info " + r.getId())) + .hoverEvent(HoverEvent.showText(PSL.TAX_CLICK_TO_SHOW_MORE_INFO.msg())); + + entries.add(clickable); } - TextGUI.displayGUI(p.getPlayer(), PSL.TAX_INFO_HEADER.msg(), "/" + ProtectionStones.getInstance().getConfigOptions().base_command + " tax info -p %page%", pageNum, GUI_SIZE, entries, true); - - if (pageNum * GUI_SIZE + GUI_SIZE < entries.size()) - PSL.msg(p, PSL.TAX_NEXT.msg().replace("%page%", pageNum + 2 + "")); + TextGUI.displayGUI( + p.getPlayer(), + PSL.TAX_INFO_HEADER.msg(), + "/" + ProtectionStones.getInstance().getConfigOptions().base_command + " tax info -p %page%", + pageNum, + GUI_SIZE, + entries, + true + ); + + if (pageNum * GUI_SIZE + GUI_SIZE < entries.size()) { + PSL.msg(p.getPlayer(), PSL.TAX_NEXT.replaceAll(Map.of("%page%", String.valueOf(pageNum + 2)))); + } }); } else if (args.length == 3) { // /ps tax info [region] List list = ProtectionStones.getPSRegions(p.getPlayer().getWorld(), args[2]); if (list.isEmpty()) { - return PSL.msg(p, PSL.REGION_DOES_NOT_EXIST.msg()); + return PSL.msg(p.getPlayer(), PSL.REGION_DOES_NOT_EXIST.msg()); } PSRegion r = list.get(0); - double taxesOwed = 0; - for (PSRegion.TaxPayment tp : r.getTaxPaymentsDue()) { - taxesOwed += tp.getAmount(); - } - - PSL.msg(p, PSL.TAX_REGION_INFO_HEADER.msg().replace("%region%", r.getName() == null ? r.getId() : r.getName() + " (" + r.getId() + ")")); - PSL.msg(p, PSL.TAX_REGION_INFO.msg() - .replace("%taxrate%", String.format("%.2f", r.getTaxRate())) - .replace("%taxperiod%", r.getTaxPeriod()) - .replace("%taxpaymentperiod%", r.getTaxPaymentPeriod()) - .replace("%taxautopayer%", r.getTaxAutopayer() == null ? "none" : UUIDCache.getNameFromUUID(r.getTaxAutopayer())) - .replace("%taxowed%", String.format("%.2f", taxesOwed))); + double taxesOwed = r.getTaxPaymentsDue().stream().mapToDouble(PSRegion.TaxPayment::getAmount).sum(); + + PSL.msg(p.getPlayer(), PSL.TAX_REGION_INFO_HEADER.replaceAll(Map.of( + "%region%", r.getName() == null ? r.getId() : r.getName() + " (" + r.getId() + ")" + ))); + PSL.msg(p.getPlayer(), PSL.TAX_REGION_INFO.replaceAll(Map.of( + "%taxrate%", String.format("%.2f", r.getTaxRate()), + "%taxperiod%", r.getTaxPeriod(), + "%taxpaymentperiod%", r.getTaxPaymentPeriod(), + "%taxautopayer%", r.getTaxAutopayer() == null ? "none" : UUIDCache.getNameFromUUID(r.getTaxAutopayer()), + "%taxowed%", String.format("%.2f", taxesOwed) + ))); } else { - PSL.msg(p, INFO_HELP); + runHelp(p.getPlayer()); } return true; } public boolean taxPay(String[] args, PSPlayer p) { if (args.length != 3 && args.length != 4) - return PSL.msg(p, PAY_HELP); - // the amount to pay must be a number + return PSL.msg(p.getPlayer(), PAY_HELP); + if (!NumberUtils.isNumber(args[2])) - return PSL.msg(p, PAY_HELP); + return PSL.msg(p.getPlayer(), PAY_HELP); PSRegion r = resolveRegion(args.length == 4 ? args[3] : null, p); if (r == null) return true; - // player must be owner to pay for taxes if (!r.isOwner(p.getUuid())) - return PSL.msg(p, PSL.NOT_OWNER.msg()); + return PSL.msg(p.getPlayer(), PSL.NOT_OWNER.msg()); double payment = Double.parseDouble(args[2]); - // must be higher than or equal to zero if (payment <= 0) - return PSL.msg(p, PAY_HELP); - // player must have this amount of money + return PSL.msg(p.getPlayer(), PAY_HELP); + if (!p.hasAmount(payment)) - return PSL.msg(p, PSL.NOT_ENOUGH_MONEY.msg().replace("%price%", String.format("%.2f", payment))); + return PSL.msg(p.getPlayer(), PSL.NOT_ENOUGH_MONEY.replaceAll(Map.of("%price%", String.format("%.2f", payment)))); - // pay tax amount EconomyResponse res = r.payTax(p, payment); - PSL.msg(p, PSL.TAX_PAID.msg() - .replace("%amount%", String.format("%.2f", res.amount)) - .replace("%region%", r.getName() == null ? r.getId() : r.getName() + "(" + r.getId() + ")")); + PSL.msg(p.getPlayer(), PSL.TAX_PAID.replaceAll(Map.of( + "%amount%", String.format("%.2f", res.amount), + "%region%", (r.getName() == null ? r.getId() : r.getName() + " (" + r.getId() + ")") + ))); return true; } public boolean taxAutoPay(String[] args, PSPlayer p) { if (args.length != 2 && args.length != 3) - return PSL.msg(p, AUTOPAY_HELP); + return PSL.msg(p.getPlayer(), AUTOPAY_HELP); PSRegion r = resolveRegion(args.length == 3 ? args[2] : null, p); if (r == null) return true; - // player must be the owner of the region if (!r.isOwner(p.getUuid())) - return PSL.msg(p, PSL.NOT_OWNER.msg()); + return PSL.msg(p.getPlayer(), PSL.NOT_OWNER.msg()); - if (r.getTaxAutopayer() != null && r.getTaxAutopayer().equals(p.getUuid())) { // if removing the the tax autopayer + if (r.getTaxAutopayer() != null && r.getTaxAutopayer().equals(p.getUuid())) { r.setTaxAutopayer(null); - PSL.msg(p, PSL.TAX_SET_NO_AUTOPAYER.msg().replace("%region%", r.getName() == null ? r.getId() : r.getName() + "(" + r.getId() + ")")); - } else { // if the player is setting themselves as the tax autopayer + PSL.msg(p.getPlayer(), PSL.TAX_SET_NO_AUTOPAYER.replaceAll(Map.of( + "%region%", (r.getName() == null ? r.getId() : r.getName() + " (" + r.getId() + ")") + ))); + } else { r.setTaxAutopayer(p.getUuid()); - PSL.msg(p, PSL.TAX_SET_AS_AUTOPAYER.msg().replace("%region%", r.getName() == null ? r.getId() : r.getName() + "(" + r.getId() + ")")); + PSL.msg(p.getPlayer() , PSL.TAX_SET_AS_AUTOPAYER.replaceAll(Map.of( + "%region%", (r.getName() == null ? r.getId() : r.getName() + " (" + r.getId() + ")") + ))); } return true; } public PSRegion resolveRegion(String region, PSPlayer p) { PSRegion r; - if (region == null) { // region the player is standing in + if (region == null) { r = PSRegion.fromLocationGroup(p.getPlayer().getLocation()); if (r == null) { - PSL.msg(p, PSL.NOT_IN_REGION.msg()); + PSL.msg(p.getPlayer(), PSL.NOT_IN_REGION.msg()); return null; } - // if taxes are disabled for this region if (r.getTypeOptions() == null || r.getTypeOptions().taxPeriod == -1) { - PSL.msg(p, PSL.TAX_DISABLED_REGION.msg()); + PSL.msg(p.getPlayer(), PSL.TAX_DISABLED_REGION.msg()); return null; } - } else { // region query + } else { List list = ProtectionStones.getPSRegions(p.getPlayer().getWorld(), region); if (list.isEmpty()) { - PSL.msg(p, PSL.REGION_DOES_NOT_EXIST.msg()); + PSL.msg(p.getPlayer(), PSL.REGION_DOES_NOT_EXIST.msg()); return null; } else { r = list.get(0); diff --git a/src/main/java/dev/espi/protectionstones/commands/ArgToggle.java b/src/main/java/dev/espi/protectionstones/commands/ArgToggle.java index b32f7610..d96e3709 100644 --- a/src/main/java/dev/espi/protectionstones/commands/ArgToggle.java +++ b/src/main/java/dev/espi/protectionstones/commands/ArgToggle.java @@ -50,9 +50,9 @@ public boolean executeArgument(CommandSender s, String[] args, HashMap regions.size()) { - PSL.msg(p, PSL.ONLY_HAS_REGIONS.msg() - .replace("%player%", tpName) - .replace("%num%", "" + regions.size())); + PSL.msg(p.getPlayer(), PSL.ONLY_HAS_REGIONS.replaceAll(Map.of( + "%player%", tpName, + "%num%", String.valueOf(regions.size()) + ))); return; } @@ -129,7 +131,11 @@ public List tabComplete(CommandSender sender, String alias, String[] arg static void teleportPlayer(Player p, PSRegion r) { if (r.getTypeOptions() == null) { - PSL.msg(p, ChatColor.RED + "This region is problematic, and the block type (" + r.getType() + ") is not configured. Please contact an administrator."); + PSL.msg(p, + Component.text("This region is problematic, and the block type (", NamedTextColor.RED) + .append(Component.text(r.getType(), NamedTextColor.AQUA)) + .append(Component.text(") is not configured. Please contact an administrator.", NamedTextColor.RED)) + ); Bukkit.getLogger().info(ChatColor.RED + "This region is problematic, and the block type (" + r.getType() + ") is not configured."); return; } @@ -141,7 +147,7 @@ static void teleportPlayer(Player p, PSRegion r) { Bukkit.getScheduler().runTask(ProtectionStones.getInstance(), () -> p.teleport(r.getHome())); // run on main thread, not async } else if (!r.getTypeOptions().noMovingWhenTeleportWaiting) { // teleport delay, but doesn't care about moving - p.sendMessage(PSL.TP_IN_SECONDS.msg().replace("%seconds%", "" + r.getTypeOptions().tpWaitingSeconds)); + PSL.msg(p, PSL.TP_IN_SECONDS.replace("%seconds%", String.valueOf(r.getTypeOptions().tpWaitingSeconds))); Bukkit.getScheduler().runTaskLater(ProtectionStones.getInstance(), () -> { PSL.msg(p, PSL.TPING.msg()); @@ -149,7 +155,7 @@ static void teleportPlayer(Player p, PSRegion r) { }, 20 * r.getTypeOptions().tpWaitingSeconds); } else {// delay and not allowed to move - PSL.msg(p, PSL.TP_IN_SECONDS.msg().replace("%seconds%", "" + r.getTypeOptions().tpWaitingSeconds)); + PSL.msg(p, PSL.TP_IN_SECONDS.replace("%seconds%", "" + r.getTypeOptions().tpWaitingSeconds)); Location l = p.getLocation().clone(); UUID uuid = p.getUniqueId(); diff --git a/src/main/java/dev/espi/protectionstones/commands/ArgUnclaim.java b/src/main/java/dev/espi/protectionstones/commands/ArgUnclaim.java index cbc633f6..eab607fe 100644 --- a/src/main/java/dev/espi/protectionstones/commands/ArgUnclaim.java +++ b/src/main/java/dev/espi/protectionstones/commands/ArgUnclaim.java @@ -17,10 +17,12 @@ import dev.espi.protectionstones.*; import dev.espi.protectionstones.utils.TextGUI; -import net.md_5.bungee.api.chat.ClickEvent; -import net.md_5.bungee.api.chat.ComponentBuilder; -import net.md_5.bungee.api.chat.HoverEvent; -import net.md_5.bungee.api.chat.TextComponent; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentBuilder; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -31,10 +33,12 @@ import java.util.HashMap; import java.util.List; + public class ArgUnclaim implements PSCommandArg { // /ps unclaim + private MiniMessage Mini; @Override public List getNames() { return Collections.singletonList("unclaim"); @@ -132,7 +136,7 @@ private int tryParseInt(String arg) { } private void displayPSRegions(CommandSender s, List regions, int page) { - List entries = new ArrayList<>(); + List entries = new ArrayList<>(); for (PSRegion rs : regions) { String msg; if (rs.getName() == null) { @@ -140,9 +144,11 @@ private void displayPSRegions(CommandSender s, List regions, int page) } else { msg = ChatColor.GRAY + "> " + ChatColor.AQUA + rs.getName() + " (" + rs.getId() + ")"; } - TextComponent tc = new TextComponent(ChatColor.AQUA + " [-] " + msg); - tc.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("Click to unclaim " + rs.getId()).create())); - tc.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + ProtectionStones.getInstance().getConfigOptions().base_command + " unclaim " + rs.getId())); + Component tc = Component.text(" [-] ", NamedTextColor.AQUA) + .append(Mini.deserialize(msg)) + .hoverEvent(HoverEvent.showText(Mini.deserialize("Click to unclaim " + rs.getId() + ""))) + .clickEvent(ClickEvent.suggestCommand("/" + ProtectionStones.getInstance().getConfigOptions().base_command + " unclaim " + rs.getId())); + entries.add(tc); } TextGUI.displayGUI(s, PSL.UNCLAIM_HEADER.msg(), "/" + ProtectionStones.getInstance().getConfigOptions().base_command + " unclaim list %page%", page, 17, entries, true); diff --git a/src/main/java/dev/espi/protectionstones/event/PSBreakProtectBlockEvent.java b/src/main/java/dev/espi/protectionstones/event/PSBreakProtectBlockEvent.java index 289a0fff..04455cda 100644 --- a/src/main/java/dev/espi/protectionstones/event/PSBreakProtectBlockEvent.java +++ b/src/main/java/dev/espi/protectionstones/event/PSBreakProtectBlockEvent.java @@ -6,7 +6,6 @@ import org.bukkit.event.Event; import org.bukkit.event.HandlerList; import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; import java.util.Objects; @@ -65,7 +64,6 @@ public void setCancelled(boolean cancel) { isCancelled = cancel; } - @NotNull @Override public HandlerList getHandlers() { return HANDLERS; diff --git a/src/main/java/dev/espi/protectionstones/flags/FarewellFlagHandler.java b/src/main/java/dev/espi/protectionstones/flags/FarewellFlagHandler.java index 86deb3c8..516dc2b4 100644 --- a/src/main/java/dev/espi/protectionstones/flags/FarewellFlagHandler.java +++ b/src/main/java/dev/espi/protectionstones/flags/FarewellFlagHandler.java @@ -25,8 +25,10 @@ import com.sk89q.worldguard.session.handler.FlagValueChangeHandler; import com.sk89q.worldguard.session.handler.Handler; import dev.espi.protectionstones.FlagHandler; +import dev.espi.protectionstones.PSL; import dev.espi.protectionstones.PSRegion; import dev.espi.protectionstones.ProtectionStones; +import dev.espi.protectionstones.utils.ChatUtil; import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.chat.TextComponent; import org.bukkit.Bukkit; @@ -63,7 +65,7 @@ protected boolean onSetValue(LocalPlayer localPlayer, Location from, Location to } } if (p != null && lastValue != null && !lastValue.equals(currentValue)) { - p.spigot().sendMessage(ChatMessageType.ACTION_BAR, new TextComponent(ChatColor.translateAlternateColorCodes('&', lastValue))); + PSL.action(p, lastValue); } return true; } @@ -72,7 +74,7 @@ protected boolean onSetValue(LocalPlayer localPlayer, Location from, Location to protected boolean onAbsentValue(LocalPlayer localPlayer, Location location, Location location1, ApplicableRegionSet applicableRegionSet, String lastValue, MoveType moveType) { Player p = Bukkit.getPlayer(localPlayer.getUniqueId()); if (p != null && lastValue != null) { - p.spigot().sendMessage(ChatMessageType.ACTION_BAR, new TextComponent(ChatColor.translateAlternateColorCodes('&', lastValue))); + PSL.action(p, lastValue); } return true; } diff --git a/src/main/java/dev/espi/protectionstones/utils/ChatUtil.java b/src/main/java/dev/espi/protectionstones/utils/ChatUtil.java index e4dbfcd3..4157f3a0 100644 --- a/src/main/java/dev/espi/protectionstones/utils/ChatUtil.java +++ b/src/main/java/dev/espi/protectionstones/utils/ChatUtil.java @@ -17,6 +17,17 @@ import dev.espi.protectionstones.PSL; import dev.espi.protectionstones.PSRegion; + +import dev.espi.protectionstones.ProtectionStones; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.ChatMessageType; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import java.util.List; @@ -29,6 +40,56 @@ public static void displayDuplicateRegionAliases(Player p, List r) { rep.append(String.format(", %s (%s)", r.get(i).getId(), r.get(i).getWorld().getName())); } - PSL.msg(p, PSL.SPECIFY_ID_INSTEAD_OF_ALIAS.msg().replace("%regions%", rep.toString())); + PSL.msg(p, PSL.SPECIFY_ID_INSTEAD_OF_ALIAS.replace("%regions%", rep.toString())); + } + + //Convert between Paper Kyori and Spigot safe Chat events + // legacy serializer for plain fallback + private static final LegacyComponentSerializer LEGACY_SERIALIZER = + LegacyComponentSerializer.legacySection(); + + private static final LegacyComponentSerializer AMPERSAND_SERIALIZER = + LegacyComponentSerializer.legacyAmpersand(); + + // full serializer for hover/click + private static final BungeeComponentSerializer BUNGEE_SERIALIZER = + BungeeComponentSerializer.get(); + + /** Convert Adventure → Spigot (BaseComponent[]) preserving events */ + public static BaseComponent[] toSpigotDeep(Component comp) { + return BUNGEE_SERIALIZER.serialize(comp); + } + + /** Convert Adventure → plain ChatColor string (no events) */ + public static String toChatColorString(Component comp) { + return LEGACY_SERIALIZER.serialize(comp); + } + + /** Universal send method */ + public static boolean send(CommandSender p, Component comp) { + if (p == null || comp == null) return false; + + if (Bukkit.getServer().getName().toLowerCase().contains("paper")) { + ProtectionStones.getInstance().audiences().sender(p).sendMessage(comp); + } else if (p instanceof Player player) { + player.spigot().sendMessage(toSpigotDeep(comp)); // keep hover/click + } else { + p.sendMessage(toChatColorString(comp)); // console fallback + } + return true; + } + public static boolean sendActionBar(Player p, String message) { + if (Bukkit.getServer().getName().toLowerCase().contains("paper")) { + // Paper: Adventure API native + Component comp = AMPERSAND_SERIALIZER.deserialize(message); + ProtectionStones.getInstance().audiences().player(p).sendActionBar(comp); + } else { + // Spigot: Use Bungee API fallback + p.spigot().sendMessage( + ChatMessageType.ACTION_BAR, + new TextComponent(ChatColor.translateAlternateColorCodes('&', message)) + ); + } + return false; } } diff --git a/src/main/java/dev/espi/protectionstones/utils/LimitUtil.java b/src/main/java/dev/espi/protectionstones/utils/LimitUtil.java index 6747dd2e..67233e36 100644 --- a/src/main/java/dev/espi/protectionstones/utils/LimitUtil.java +++ b/src/main/java/dev/espi/protectionstones/utils/LimitUtil.java @@ -19,6 +19,7 @@ import com.sk89q.worldguard.protection.managers.RegionManager; import com.sk89q.worldguard.protection.regions.ProtectedRegion; import dev.espi.protectionstones.*; +import net.kyori.adventure.text.Component; import org.bukkit.World; import org.bukkit.entity.Player; @@ -28,7 +29,7 @@ public class LimitUtil { // warning: group regions should be split into merged regions first - public static String checkAddOwner(PSPlayer psp, List blocksAdded) { + public static Component checkAddOwner(PSPlayer psp, List blocksAdded) { HashMap regionLimits = psp.getRegionLimits(); int maxPS = psp.getGlobalRegionLimits(); @@ -68,19 +69,17 @@ public static String checkAddOwner(PSPlayer psp, List blocksAdde return PSL.ADDREMOVE_PLAYER_REACHED_LIMIT.msg(); } } - return ""; + return Component.empty(); } public static boolean check(Player p, PSProtectBlock b) { if (!p.hasPermission("protectionstones.admin")) { - // check if player has limit on protection stones - String msg = LimitUtil.hasPlayerPassedRegionLimit(PSPlayer.fromPlayer(p), b); - if (!msg.isEmpty()) { - PSL.msg(p, msg); + Component msg = LimitUtil.hasPlayerPassedRegionLimit(PSPlayer.fromPlayer(p), b); + if (msg != null) { + PSL.msg(p, msg); // Component overload you already have return false; } } - return true; } @@ -148,7 +147,7 @@ private static HashMap getOwnedRegionTypeCounts(PSPlaye return counts; } - private static String hasPlayerPassedRegionLimit(PSPlayer psp, PSProtectBlock b) { + private static Component hasPlayerPassedRegionLimit(PSPlayer psp, PSProtectBlock b) { HashMap regionLimits = psp.getRegionLimits(); int maxPS = psp.getGlobalRegionLimits(); @@ -168,16 +167,16 @@ private static String hasPlayerPassedRegionLimit(PSPlayer psp, PSProtectBlock b) // check if player has passed region limit ProtectionStones.getInstance().debug(String.format("The player will have %d regions in total. Their limit is %d.", total, maxPS)); if (total >= maxPS && maxPS != -1) { - return PSL.REACHED_REGION_LIMIT.msg().replace("%limit%", ""+maxPS); + return PSL.REACHED_REGION_LIMIT.replace("%limit%", ""+maxPS); } // check if player has passed per block limit ProtectionStones.getInstance().debug(String.format("Of type %s: player will have %d regions - Player's limit is %d regions.", b.alias, bFound, regionLimits.get(b) == null ? -1 : regionLimits.get(b))); if (regionLimits.get(b) != null && bFound >= regionLimits.get(b)) { - return PSL.REACHED_PER_BLOCK_REGION_LIMIT.msg().replace("%limit%", ""+regionLimits.get(b)); + return PSL.REACHED_PER_BLOCK_REGION_LIMIT.replace("%limit%", ""+regionLimits.get(b)); } } - return ""; + return Component.empty(); } } diff --git a/src/main/java/dev/espi/protectionstones/utils/TextGUI.java b/src/main/java/dev/espi/protectionstones/utils/TextGUI.java index 6dba7141..c6a051ce 100644 --- a/src/main/java/dev/espi/protectionstones/utils/TextGUI.java +++ b/src/main/java/dev/espi/protectionstones/utils/TextGUI.java @@ -16,50 +16,93 @@ package dev.espi.protectionstones.utils; import dev.espi.protectionstones.PSL; -import net.md_5.bungee.api.chat.ClickEvent; -import net.md_5.bungee.api.chat.ComponentBuilder; -import net.md_5.bungee.api.chat.HoverEvent; +import dev.espi.protectionstones.ProtectionStones; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.md_5.bungee.api.chat.TextComponent; -import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; +import java.util.ArrayList; import java.util.List; +import static org.enginehub.piston.impl.LogManagerCompat.getLogger; + public class TextGUI { + private static final MiniMessage MM = MiniMessage.miniMessage(); + private static final PlainTextComponentSerializer PLAIN = PlainTextComponentSerializer.plainText(); // page starts at zero, but displays start at one // pageCommand will be replacing %page% - public static void displayGUI(CommandSender s, String header, String pageCommand, int currentPage, int guiSize, List lines, boolean sendBlankLines) { - int currentLine = currentPage * guiSize; - if (currentPage < 0 || currentLine > lines.size()) { - return; - } + public static void displayGUI( + CommandSender s, + Component header, + String pageCommand, + int currentPage, + int guiSize, + List lines, + boolean sendBlankLines + ){ + final int start = currentPage * guiSize; + final int end = Math.min(start + guiSize, lines.size()); + + if (currentPage < 0 || start > lines.size()) return; + // header PSL.msg(s, header); - for (int i = currentPage*guiSize; i < Math.min((currentPage+1) * guiSize, lines.size()); i++) { - if (sendBlankLines || !lines.get(i).equals(new TextComponent(""))) - s.spigot().sendMessage(lines.get(i)); + // page body + for (int i = start; i < end; i++) { + Component line = lines.get(i); + if (sendBlankLines || !isBlank(line)) { + ProtectionStones.getInstance().audiences().sender(s).sendMessage(line); + } + } + + // footer with paging + final boolean hasPrev = currentPage != 0; + final boolean hasNext = (currentPage + 1) * guiSize < lines.size(); + + if (lines.size() >= guiSize) { + Component footer = Component.empty().append(Component.text("=====", NamedTextColor.DARK_GRAY) + .decoration(TextDecoration.STRIKETHROUGH, true)); + + if (hasPrev) { + Component back = Component.text(" <<", NamedTextColor.AQUA) + .decoration(TextDecoration.STRIKETHROUGH, false) // turn off strikethrough + .hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(PSL.GO_BACK_PAGE.msg())) + .clickEvent(net.kyori.adventure.text.event.ClickEvent.runCommand(pageCommand.replace("%page%", String.valueOf(currentPage)))); + footer = footer.append(back); + } + + footer = footer.appendSpace() + .decoration(TextDecoration.STRIKETHROUGH, false) + .append(Component.text(currentPage + 1, NamedTextColor.WHITE)) + .appendSpace(); + + if (hasNext) { + Component next = Component.text(">> ", NamedTextColor.AQUA) + .decoration(TextDecoration.STRIKETHROUGH, false) + .hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(PSL.GO_NEXT_PAGE.msg())) + .clickEvent(net.kyori.adventure.text.event.ClickEvent.runCommand(pageCommand.replace("%page%", String.valueOf(currentPage + 2)))); + footer = footer.append(next); + } + + footer = footer.append(Component.text("=====", NamedTextColor.DARK_GRAY) + .decoration(TextDecoration.STRIKETHROUGH, true)); + + PSL.msg(s, footer); + getLogger().info(GsonComponentSerializer.gson().serialize(footer)); } - // footer page buttons - TextComponent backPage = new TextComponent(ChatColor.AQUA + " <<"), nextPage = new TextComponent(ChatColor.AQUA + ">> "); - backPage.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(PSL.GO_BACK_PAGE.msg()).create())); - nextPage.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(PSL.GO_NEXT_PAGE.msg()).create())); - backPage.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, pageCommand.replace("%page%", ""+currentPage))); - nextPage.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, pageCommand.replace("%page%", currentPage+2+""))); - - TextComponent footer = new TextComponent(ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "=====" + ChatColor.RESET); - // add back page button if the page isn't 0 - if (currentPage != 0) footer.addExtra(backPage); - // add page number - footer.addExtra(new TextComponent(ChatColor.WHITE + " " + (currentPage + 1) + " ")); - // add forward page button if the page isn't last - if (currentPage * guiSize + guiSize < lines.size()) footer.addExtra(nextPage); - footer.addExtra(ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "====="); - - // display footer only if there are more than one page of entries - if (lines.size() >= guiSize) s.spigot().sendMessage(footer); } + private static boolean isBlank(Component c) { + return PLAIN.serialize(c).isBlank(); + } } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index c5ad41a3..14c4232b 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,7 +1,7 @@ name: ProtectionStones version: ${version} description: ${description} -authors: [EspiDev] +authors: [EspiDev, Jerzean] depend: [WorldGuard, WorldEdit] softdepend: [Vault, PlaceholderAPI, LuckPerms]