From db0ee45b45ac7e373a8a9a1756860cda27976a00 Mon Sep 17 00:00:00 2001 From: GeminiAntigravityCLI Date: Thu, 20 Aug 2026 19:30:45 +0200 Subject: [PATCH] feat: add anti-pvp, anti-piston, anti-fluid protections, /guild list, /guild decline, interactive clickable invites and help menu --- .../luc/gamingcore/command/GuildCommands.java | 245 +++++++++++++++--- .../listener/TerritoryProtectionListener.java | 194 +++++++++++++- .../worldguard/WorldGuardManager.java | 2 +- src/main/resources/messages.yml | 43 ++- 4 files changed, 421 insertions(+), 63 deletions(-) diff --git a/src/main/java/fr/luc/gamingcore/command/GuildCommands.java b/src/main/java/fr/luc/gamingcore/command/GuildCommands.java index 226db7c..9b21910 100644 --- a/src/main/java/fr/luc/gamingcore/command/GuildCommands.java +++ b/src/main/java/fr/luc/gamingcore/command/GuildCommands.java @@ -15,17 +15,19 @@ import org.bukkit.Location; import org.bukkit.entity.Player; import java.time.Duration; -import java.util.Optional; +import java.util.*; import java.util.regex.Pattern; import java.util.stream.Collectors; /** * Enregistrement et définition de la suite complète de commandes `/guild` - * utilisant les validateurs métier et les confirmations de **betterMcCommands**. + * utilisant les validateurs métier, les confirmations et les composants interactifs de **betterMcCommands**. */ public class GuildCommands { private static final Pattern GUILD_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_]{3,16}$"); + private static final int HELP_PAGE_SIZE = 8; + private static final int GUILD_LIST_PAGE_SIZE = 6; private final Main plugin; private final GuildManager guildManager; @@ -56,30 +58,83 @@ public class GuildCommands { .aliases("g", "guilde", "clan") // ------------------------------------------------------------- - // Racine /guild : Affichage du panneau d'aide + // Racine /guild : Affichage du panneau d'aide interactif // ------------------------------------------------------------- - .executes(context -> { - context.reply("=== SYSTÈME DE GUILDE ==="); - context.reply("/guild create [tag] - Fonder une nouvelle guilde"); - context.reply("/guild disband - Dissoudre votre guilde"); - context.reply("/guild color <#hex> - Modifier la couleur hexadécimale"); - context.reply("/guild tag - Modifier le tag court"); - context.reply("/guild invite - Inviter un joueur"); - context.reply("/guild join - Rejoindre une guilde invitante"); - context.reply("/guild leave - Quitter votre guilde"); - context.reply("/guild kick - Expulser un membre"); - context.reply("/guild info [guilde] - Afficher les détails d'une guilde"); - context.reply("/guild members [guilde] - Liste des membres"); - context.reply("/guild claim - Revendiquer le chunk actuel"); - context.reply("/guild unclaim - Libérer le chunk actuel"); - context.reply("/guild deposit - Déposer dans la banque"); - context.reply("/guild withdraw - Retirer de la banque"); - context.reply("/guild sethome - Définir le point de ralliement"); - context.reply("/guild home - Se téléporter au home de guilde"); - if (context.getSender().hasPermission("guild.admin.reload")) { - context.reply("/guild reload - Recharger les configurations et messages"); - } - }) + .executes(context -> sendHelpMenu(context, 1)) + + // ------------------------------------------------------------- + // Sous-commande : /guild help [page] + // ------------------------------------------------------------- + .subcommand(BetterMcCommands.subBuilder("help") + .description("Afficher le menu d'aide interactif") + .aliases("aide", "?") + .argument(Arguments.integer("page", 1, 10).optional().description("Numéro de page")) + .executes(context -> { + int page = context.getOptional("page", Integer.class).orElse(1); + sendHelpMenu(context, page); + }) + ) + + // ------------------------------------------------------------- + // Sous-commande : /guild list [page] + // ------------------------------------------------------------- + .subcommand(BetterMcCommands.subBuilder("list") + .description("Afficher la liste des guildes du serveur") + .aliases("liste", "top") + .argument(Arguments.integer("page", 1, 100).optional().description("Numéro de page")) + .executes(context -> { + int page = context.getOptional("page", Integer.class).orElse(1); + List allGuilds = new ArrayList<>(guildManager.getAllGuilds()); + + if (allGuilds.isEmpty()) { + context.reply(msg.get("guild.list.empty")); + return; + } + + // Trier par nombre de membres décroissant + allGuilds.sort((g1, g2) -> Integer.compare(g2.getMemberCount(), g1.getMemberCount())); + + int totalGuilds = allGuilds.size(); + int maxPage = (int) Math.ceil((double) totalGuilds / GUILD_LIST_PAGE_SIZE); + if (page > maxPage) page = maxPage; + if (page < 1) page = 1; + + context.reply(msg.get("guild.list.header", + "{page}", String.valueOf(page), + "{max_page}", String.valueOf(maxPage) + )); + + int startIndex = (page - 1) * GUILD_LIST_PAGE_SIZE; + int endIndex = Math.min(startIndex + GUILD_LIST_PAGE_SIZE, totalGuilds); + + for (int i = startIndex; i < endIndex; i++) { + Guild g = allGuilds.get(i); + GuildMember leader = g.getLeaderMember(); + String leaderName = leader != null && leader.getOfflinePlayer() != null && leader.getOfflinePlayer().getName() != null + ? leader.getOfflinePlayer().getName() + : "Inconnu"; + + String line = msg.get("guild.list.item", + "{index}", String.valueOf(i + 1), + "{guild}", g.getFormattedName(), + "{tag}", g.getFormattedTag(), + "{leader}", leaderName, + "{members}", String.valueOf(g.getMemberCount()), + "{claims}", String.valueOf(g.getClaimCount()) + ); + context.reply(line); + } + + int prevPage = Math.max(1, page - 1); + int nextPage = Math.min(maxPage, page + 1); + + String pagination = msg.get("guild.list.pagination", + "{prev_page}", String.valueOf(prevPage), + "{next_page}", String.valueOf(nextPage) + ); + context.reply(pagination); + }) + ) // ------------------------------------------------------------- // Sous-commande : /guild create [tag] @@ -90,7 +145,6 @@ public class GuildCommands { .argument(Arguments.string("nom").description("Nom de la guilde (3-16 car)")) .argument(Arguments.string("tag").optional().description("Tag court (ex: LEG)")) - // Validations dynamiques .validate(context -> { if (guildManager.isInGuild(context.getPlayer().getUniqueId())) { return ValidationResult.invalid(msg.get("guild.already_in_guild")); @@ -136,9 +190,13 @@ public class GuildCommands { GuildResult result = guildManager.createGuild(player, name, tag, creationCost); if (result.isSuccess()) { + Guild createdGuild = guildManager.getGuildByName(name); + String formattedName = createdGuild != null ? createdGuild.getFormattedName() : name; + String formattedTag = createdGuild != null ? createdGuild.getFormattedTag() : "[" + tag + "]"; + String successMsg = msg.get("guild.creation.success", - "{guild}", name, - "{tag}", tag, + "{guild}", formattedName, + "{tag}", formattedTag, "{cost}", guildManager.getEconomyManager().format(creationCost), "{world}", loc.getWorld().getName(), "{chunk_x}", String.valueOf(chunk.getX()), @@ -177,10 +235,10 @@ public class GuildCommands { Guild guild = guildManager.getGuildByPlayer(player.getUniqueId()); if (guild == null) return; - String guildName = guild.getName(); + String formattedName = guild.getFormattedName(); GuildResult result = guildManager.disbandGuild(player, guild); if (result.isSuccess()) { - context.reply(msg.get("guild.disband.success", "{guild}", guildName)); + context.reply(msg.get("guild.disband.success", "{guild}", formattedName)); } else { context.replyError(result.getMessage()); } @@ -222,10 +280,13 @@ public class GuildCommands { "{seconds}", String.valueOf(invite.getRemainingSeconds()) )); - TextUtil.sendMessage(target, msg.get("guild.invite.received", - "{guild}", guild.getName(), + String interactiveMsg = msg.get("guild.invite.received_interactive", + "{guild}", guild.getFormattedName(), + "{tag}", guild.getFormattedTag(), + "{raw_guild}", guild.getName(), "{sender}", sender.getName() - )); + ); + TextUtil.sendMessage(target, interactiveMsg); }) ) @@ -255,7 +316,7 @@ public class GuildCommands { GuildInvite invite = inviteManager.getValidInvite(player, targetGuild.getId()); if (invite == null) { - context.reply(msg.get("guild.invite.no_pending_invite", "{guild}", targetGuild.getName())); + context.reply(msg.get("guild.invite.no_pending_invite", "{guild}", targetGuild.getFormattedName())); return; } @@ -269,6 +330,44 @@ public class GuildCommands { }) ) + // ------------------------------------------------------------- + // Sous-commande : /guild decline + // ------------------------------------------------------------- + .subcommand(BetterMcCommands.subBuilder("decline") + .description("Refuser une invitation de guilde reçue") + .aliases("refuser", "deny") + .playerOnly() + .argument(Arguments.string("guilde").description("Nom de la guilde")) + .executes(context -> { + Player player = context.getPlayer(); + String guildName = context.getString("guilde"); + Guild targetGuild = guildManager.getGuildByName(guildName); + + if (targetGuild == null) { + context.reply(msg.get("guild.guild_not_found", "{guild}", guildName)); + return; + } + + GuildInvite invite = inviteManager.getValidInvite(player, targetGuild.getId()); + if (invite == null) { + context.reply(msg.get("guild.invite.no_pending_invite", "{guild}", targetGuild.getFormattedName())); + return; + } + + inviteManager.removeInvite(player, targetGuild.getId()); + context.reply(msg.get("guild.invite.declined_target", "{guild}", targetGuild.getFormattedName())); + + // Notifier l'émetteur si en ligne + GuildMember leader = targetGuild.getLeaderMember(); + if (leader != null && leader.getOfflinePlayer() != null && leader.getOfflinePlayer().isOnline()) { + Player leaderPlayer = leader.getOfflinePlayer().getPlayer(); + if (leaderPlayer != null) { + TextUtil.sendMessage(leaderPlayer, msg.get("guild.invite.declined_sender", "{player}", player.getName())); + } + } + }) + ) + // ------------------------------------------------------------- // Sous-commande : /guild leave // ------------------------------------------------------------- @@ -295,10 +394,10 @@ public class GuildCommands { Guild guild = guildManager.getGuildByPlayer(player.getUniqueId()); if (guild == null) return; - String guildName = guild.getName(); + String formattedName = guild.getFormattedName(); GuildResult result = guildManager.leaveGuild(player, guild); if (result.isSuccess()) { - context.reply(msg.get("guild.leave.success", "{guild}", guildName)); + context.reply(msg.get("guild.leave.success", "{guild}", formattedName)); } else { context.replyError(result.getMessage()); } @@ -343,7 +442,7 @@ public class GuildCommands { GuildResult result = guildManager.kickMember(player, guild, target.getUniqueId()); if (result.isSuccess()) { context.reply(msg.get("guild.kick.success", "{player}", target.getName())); - TextUtil.sendMessage(target, msg.get("guild.kick.target_notify", "{guild}", guild.getName())); + TextUtil.sendMessage(target, msg.get("guild.kick.target_notify", "{guild}", guild.getFormattedName())); } else { context.replyError(result.getMessage()); } @@ -676,7 +775,9 @@ public class GuildCommands { double nextPrice = guildManager.getPriceCalculator().calculateNextChunkPrice(targetGuild); GuildMember leader = targetGuild.getLeaderMember(); - String leaderName = leader != null && leader.getOfflinePlayer() != null ? leader.getOfflinePlayer().getName() : "Inconnu"; + String leaderName = leader != null && leader.getOfflinePlayer() != null && leader.getOfflinePlayer().getName() != null + ? leader.getOfflinePlayer().getName() + : "Inconnu"; context.reply("=== INFORMATIONS DE GUILDE ==="); context.reply("Nom : " + targetGuild.getFormattedName() + " " + targetGuild.getFormattedTag()); @@ -716,7 +817,7 @@ public class GuildCommands { return; } - context.reply("=== MEMBRES : " + targetGuild.getName() + " ==="); + context.reply("=== MEMBRES : " + targetGuild.getFormattedName()); for (GuildRank rank : targetGuild.getRanks()) { String membersInRank = targetGuild.getMembers().stream() .filter(m -> m.getRankId().equalsIgnoreCase(rank.getId())) @@ -744,4 +845,70 @@ public class GuildCommands { .register(); } + + /** + * Envoie le menu d'aide interactif et paginé avec tooltips au survol et suggestions de commande au clic. + */ + private void sendHelpMenu(fr.luc.bettermccommands.api.CommandContext context, int page) { + List entries = new ArrayList<>(); + entries.add(new HelpEntry("/guild create [tag]", "Fonder une nouvelle guilde et revendiquer le 1er chunk", "/guild create ")); + entries.add(new HelpEntry("/guild list [page]", "Afficher la liste des guildes du serveur", "/guild list ")); + entries.add(new HelpEntry("/guild info [guilde]", "Afficher les statistiques détaillées d'une guilde", "/guild info ")); + entries.add(new HelpEntry("/guild members [guilde]", "Afficher la liste des membres classés par rang", "/guild members ")); + entries.add(new HelpEntry("/guild color <#hex>", "Modifier la couleur hexadécimale de la guilde", "/guild color ")); + entries.add(new HelpEntry("/guild tag ", "Modifier le tag court de la guilde (2-6 car)", "/guild tag ")); + entries.add(new HelpEntry("/guild invite ", "Inviter un joueur connecté à rejoindre la guilde", "/guild invite ")); + entries.add(new HelpEntry("/guild join ", "Accepter une invitation et rejoindre une guilde", "/guild join ")); + entries.add(new HelpEntry("/guild decline ", "Refuser une invitation de guilde reçue", "/guild decline ")); + entries.add(new HelpEntry("/guild leave", "Quitter votre guilde actuelle", "/guild leave")); + entries.add(new HelpEntry("/guild kick ", "Expulser un membre ayant un rang inférieur", "/guild kick ")); + entries.add(new HelpEntry("/guild disband", "Dissoudre définitivement votre guilde (Chef uniquement)", "/guild disband")); + entries.add(new HelpEntry("/guild claim", "Revendiquer le chunk actuel pour votre guilde", "/guild claim")); + entries.add(new HelpEntry("/guild unclaim", "Libérer le chunk actuel possédé par la guilde", "/guild unclaim")); + entries.add(new HelpEntry("/guild deposit ", "Déposer des fonds dans la banque de guilde", "/guild deposit ")); + entries.add(new HelpEntry("/guild withdraw ", "Retirer des fonds de la banque de guilde", "/guild withdraw ")); + entries.add(new HelpEntry("/guild sethome", "Définir le point de ralliement de la guilde", "/guild sethome")); + entries.add(new HelpEntry("/guild home", "Se téléporter au point de ralliement de la guilde", "/guild home")); + if (context.getSender().hasPermission("guild.admin.reload")) { + entries.add(new HelpEntry("/guild reload", "Recharger les configurations et messages du plugin", "/guild reload")); + } + + int totalEntries = entries.size(); + int maxPage = (int) Math.ceil((double) totalEntries / HELP_PAGE_SIZE); + if (page > maxPage) page = maxPage; + if (page < 1) page = 1; + + context.reply(msg.get("help.header", "{page}", String.valueOf(page), "{max_page}", String.valueOf(maxPage))); + + int startIndex = (page - 1) * HELP_PAGE_SIZE; + int endIndex = Math.min(startIndex + HELP_PAGE_SIZE, totalEntries); + + for (int i = startIndex; i < endIndex; i++) { + HelpEntry entry = entries.get(i); + String line = "" + + "Cliquez pour insérer : " + entry.usage + "'>" + + "" + entry.usage + " - " + entry.description + ""; + context.reply(line); + } + + int prevPage = Math.max(1, page - 1); + int nextPage = Math.min(maxPage, page + 1); + + String footer = "Page précédente'>[◀ Précédent] " + + "--- " + + "Page suivante'>[Suivant ▶]"; + context.reply(footer); + } + + private static class HelpEntry { + final String usage; + final String description; + final String suggestCommand; + + HelpEntry(String usage, String description, String suggestCommand) { + this.usage = usage; + this.description = description; + this.suggestCommand = suggestCommand; + } + } } diff --git a/src/main/java/fr/luc/gamingcore/listener/TerritoryProtectionListener.java b/src/main/java/fr/luc/gamingcore/listener/TerritoryProtectionListener.java index 9ca14e9..794ab56 100644 --- a/src/main/java/fr/luc/gamingcore/listener/TerritoryProtectionListener.java +++ b/src/main/java/fr/luc/gamingcore/listener/TerritoryProtectionListener.java @@ -8,22 +8,27 @@ import fr.luc.gamingcore.util.MessageManager; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Hanging; import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -import org.bukkit.event.block.Action; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockExplodeEvent; -import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.block.*; +import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.hanging.HangingBreakByEntityEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerMoveEvent; import java.util.Objects; /** - * Écouteur d'événements garantissant la protection territoriale des chunks de guilde contre le grief et les intrusions. + * Écouteur d'événements garantissant la protection territoriale complète des chunks de guilde : + * anti-grief, anti-pvp, anti-pistons transfrontaliers, anti-fluides et protection des entités/conteneurs. */ public class TerritoryProtectionListener implements Listener { @@ -63,7 +68,7 @@ public class TerritoryProtectionListener implements Listener { Guild playerGuild = guildManager.getGuildByPlayer(player.getUniqueId()); if (playerGuild == null || !playerGuild.getId().equals(guildAtBlock.getId())) { event.setCancelled(true); - messageManager.sendActionBar(player, "protection.cannot_break", "{guild}", guildAtBlock.getName(), "{tag}", guildAtBlock.getTag()); + messageManager.sendActionBar(player, "protection.cannot_break", "{guild}", guildAtBlock.getFormattedName(), "{tag}", guildAtBlock.getFormattedTag()); return; } @@ -95,7 +100,7 @@ public class TerritoryProtectionListener implements Listener { Guild playerGuild = guildManager.getGuildByPlayer(player.getUniqueId()); if (playerGuild == null || !playerGuild.getId().equals(guildAtBlock.getId())) { event.setCancelled(true); - messageManager.sendActionBar(player, "protection.cannot_build", "{guild}", guildAtBlock.getName(), "{tag}", guildAtBlock.getTag()); + messageManager.sendActionBar(player, "protection.cannot_build", "{guild}", guildAtBlock.getFormattedName(), "{tag}", guildAtBlock.getFormattedTag()); return; } @@ -138,7 +143,7 @@ public class TerritoryProtectionListener implements Listener { if (!isSameGuild) { if (isContainer || isMechanism) { event.setCancelled(true); - messageManager.sendActionBar(player, "protection.cannot_interact", "{guild}", guildAtBlock.getName(), "{tag}", guildAtBlock.getTag()); + messageManager.sendActionBar(player, "protection.cannot_interact", "{guild}", guildAtBlock.getFormattedName(), "{tag}", guildAtBlock.getFormattedTag()); } return; } @@ -153,6 +158,175 @@ public class TerritoryProtectionListener implements Listener { } } + /** + * Intercepte et bloque le PvP dans tous les claims de guilde. + * + * @param event L'événement EntityDamageByEntityEvent. + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onEntityDamageByEntity(EntityDamageByEntityEvent event) { + Entity victim = event.getEntity(); + Entity attacker = event.getDamager(); + + Player attackerPlayer = null; + if (attacker instanceof Player p) { + attackerPlayer = p; + } else if (attacker instanceof Projectile proj && proj.getShooter() instanceof Player p) { + attackerPlayer = p; + } + + // Si la victime est un joueur et qu'un joueur attaque + if (victim instanceof Player && attackerPlayer != null) { + if (attackerPlayer.hasPermission("guild.bypass")) return; + + Location victimLoc = victim.getLocation(); + Location attackerLoc = attackerPlayer.getLocation(); + + boolean victimInGuild = guildManager.isChunkClaimed(victimLoc.getWorld().getName(), victimLoc.getBlockX() >> 4, victimLoc.getBlockZ() >> 4); + boolean attackerInGuild = guildManager.isChunkClaimed(attackerLoc.getWorld().getName(), attackerLoc.getBlockX() >> 4, attackerLoc.getBlockZ() >> 4); + + if (victimInGuild || attackerInGuild) { + event.setCancelled(true); + messageManager.sendActionBar(attackerPlayer, "protection.pvp_denied"); + } + return; + } + + // Protéger les ArmorStands contre les dégâts des non-membres + if (victim instanceof ArmorStand && attackerPlayer != null) { + if (attackerPlayer.hasPermission("guild.bypass")) return; + + Location loc = victim.getLocation(); + Guild guildAtLoc = guildManager.getGuildAt(loc.getWorld().getName(), loc.getBlockX() >> 4, loc.getBlockZ() >> 4); + if (guildAtLoc != null) { + Guild playerGuild = guildManager.getGuildByPlayer(attackerPlayer.getUniqueId()); + if (playerGuild == null || !playerGuild.getId().equals(guildAtLoc.getId()) || !guildAtLoc.hasPermission(attackerPlayer.getUniqueId(), GuildPermission.BREAK)) { + event.setCancelled(true); + messageManager.sendActionBar(attackerPlayer, "protection.hanging_denied"); + } + } + } + } + + /** + * Protège les cadres (ItemFrame) et tableaux (Painting) contre la casse par des non-membres. + * + * @param event L'événement HangingBreakByEntityEvent. + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onHangingBreak(HangingBreakByEntityEvent event) { + if (event.getRemover() instanceof Player player) { + if (player.hasPermission("guild.bypass")) return; + + Hanging hanging = event.getEntity(); + Location loc = hanging.getLocation(); + Guild guildAtLoc = guildManager.getGuildAt(loc.getWorld().getName(), loc.getBlockX() >> 4, loc.getBlockZ() >> 4); + if (guildAtLoc != null) { + Guild playerGuild = guildManager.getGuildByPlayer(player.getUniqueId()); + if (playerGuild == null || !playerGuild.getId().equals(guildAtLoc.getId()) || !guildAtLoc.hasPermission(player.getUniqueId(), GuildPermission.BREAK)) { + event.setCancelled(true); + messageManager.sendActionBar(player, "protection.hanging_denied"); + } + } + } + } + + /** + * Protège les interactions avec ArmorStands et cadres d'objets. + * + * @param event L'événement PlayerInteractEntityEvent. + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { + Entity target = event.getRightClicked(); + if (target instanceof ArmorStand || target instanceof Hanging) { + Player player = event.getPlayer(); + if (player.hasPermission("guild.bypass")) return; + + Location loc = target.getLocation(); + Guild guildAtLoc = guildManager.getGuildAt(loc.getWorld().getName(), loc.getBlockX() >> 4, loc.getBlockZ() >> 4); + if (guildAtLoc != null) { + Guild playerGuild = guildManager.getGuildByPlayer(player.getUniqueId()); + if (playerGuild == null || !playerGuild.getId().equals(guildAtLoc.getId()) || !guildAtLoc.hasPermission(player.getUniqueId(), GuildPermission.INTERACT)) { + event.setCancelled(true); + messageManager.sendActionBar(player, "protection.hanging_denied"); + } + } + } + } + + /** + * Empêche les pistons de pousser des blocs à travers les frontières d'un claim étranger. + * + * @param event L'événement BlockPistonExtendEvent. + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onPistonExtend(BlockPistonExtendEvent event) { + Block piston = event.getBlock(); + String worldName = piston.getWorld().getName(); + Guild guildPiston = guildManager.getGuildAt(worldName, piston.getX() >> 4, piston.getZ() >> 4); + + for (Block block : event.getBlocks()) { + Block targetBlock = block.getRelative(event.getDirection()); + Guild guildBlock = guildManager.getGuildAt(worldName, block.getX() >> 4, block.getZ() >> 4); + Guild guildTarget = guildManager.getGuildAt(worldName, targetBlock.getX() >> 4, targetBlock.getZ() >> 4); + + // Si le piston ou les blocs déplacés traversent une frontière de guilde différente + if (!Objects.equals(guildPiston, guildBlock) || !Objects.equals(guildPiston, guildTarget)) { + event.setCancelled(true); + return; + } + } + } + + /** + * Empêche les pistons collants de rétracter (tirer) des blocs depuis un claim étranger. + * + * @param event L'événement BlockPistonRetractEvent. + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onPistonRetract(BlockPistonRetractEvent event) { + Block piston = event.getBlock(); + String worldName = piston.getWorld().getName(); + Guild guildPiston = guildManager.getGuildAt(worldName, piston.getX() >> 4, piston.getZ() >> 4); + + for (Block block : event.getBlocks()) { + Guild guildBlock = guildManager.getGuildAt(worldName, block.getX() >> 4, block.getZ() >> 4); + if (!Objects.equals(guildPiston, guildBlock)) { + event.setCancelled(true); + return; + } + } + } + + /** + * Empêche l'écoulement des fluides (eau, lave) depuis l'extérieur vers un claim de guilde protégé. + * + * @param event L'événement BlockFromToEvent. + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onBlockFromTo(BlockFromToEvent event) { + Block from = event.getBlock(); + Block to = event.getToBlock(); + + int fromChunkX = from.getX() >> 4; + int fromChunkZ = from.getZ() >> 4; + int toChunkX = to.getX() >> 4; + int toChunkZ = to.getZ() >> 4; + + if (fromChunkX == toChunkX && fromChunkZ == toChunkZ) { + return; // Même chunk + } + + String worldName = from.getWorld().getName(); + Guild guildFrom = guildManager.getGuildAt(worldName, fromChunkX, fromChunkZ); + Guild guildTo = guildManager.getGuildAt(worldName, toChunkX, toChunkZ); + + if (guildTo != null && !Objects.equals(guildFrom, guildTo)) { + event.setCancelled(true); + } + } + /** * Protège les blocs de guilde contre les explosions de créatures ou de TNT. * @@ -224,9 +398,9 @@ public class TerritoryProtectionListener implements Listener { Guild playerGuild = guildManager.getGuildByPlayer(player.getUniqueId()); boolean isOwn = playerGuild != null && playerGuild.getId().equals(currentGuild.getId()); if (isOwn) { - messageManager.sendActionBar(player, "protection.territory_enter_own", "{guild}", currentGuild.getName(), "{tag}", currentGuild.getTag()); + messageManager.sendActionBar(player, "protection.territory_enter_own", "{guild}", currentGuild.getFormattedName(), "{tag}", currentGuild.getFormattedTag()); } else { - messageManager.sendActionBar(player, "protection.territory_enter_other", "{guild}", currentGuild.getName(), "{tag}", currentGuild.getTag()); + messageManager.sendActionBar(player, "protection.territory_enter_other", "{guild}", currentGuild.getFormattedName(), "{tag}", currentGuild.getFormattedTag()); } } else if (previousGuild != null) { messageManager.sendActionBar(player, "protection.territory_leave"); diff --git a/src/main/java/fr/luc/gamingcore/worldguard/WorldGuardManager.java b/src/main/java/fr/luc/gamingcore/worldguard/WorldGuardManager.java index cb18819..b142450 100644 --- a/src/main/java/fr/luc/gamingcore/worldguard/WorldGuardManager.java +++ b/src/main/java/fr/luc/gamingcore/worldguard/WorldGuardManager.java @@ -122,7 +122,7 @@ public class WorldGuardManager { region.setFlag(Flags.PASSTHROUGH, StateFlag.State.DENY); region.setFlag(Flags.TNT, StateFlag.State.DENY); region.setFlag(Flags.CREEPER_EXPLOSION, StateFlag.State.DENY); - region.setFlag(Flags.PVP, StateFlag.State.ALLOW); + region.setFlag(Flags.PVP, StateFlag.State.DENY); regionManager.addRegion(region); } diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml index 1aeb90c..bc8611a 100644 --- a/src/main/resources/messages.yml +++ b/src/main/resources/messages.yml @@ -10,6 +10,11 @@ general: no_permission: "Vous n'avez pas la permission requise." player_not_found: "Joueur introuvable ou hors ligne." reloaded: "La configuration et les messages ont été rechargés avec succès !" + invalid_page: "Numéro de page invalide (1 à {max_page})." + +help: + header: "=== SYSTÈME DE GUILDE (Aide {page}/{max_page}) ===" + footer: "Cliquez sur une commande pour la suggérer dans le chat." guild: already_in_guild: "Vous appartenez déjà à une guilde." @@ -22,33 +27,35 @@ guild: creation: insufficient_funds: "Fonds insuffisants pour créer une guilde. Requis : {cost}." chunk_already_claimed: "Le chunk où vous vous trouvez est déjà revendiqué par une guilde." - confirm_prompt: "[Guilde] Création de la guilde {guild} [{tag}] :\n• Coût : {cost}\n• Territoire initial : {world} ({chunk_x}, {chunk_z})\n⚠ Exécutez à nouveau la commande pour confirmer la création !" - success: "Félicitations ! La guilde {guild} [{tag}] a été fondée !\n• Coût prélevé : {cost}\n• Chunk initial revendiqué : {world} [{chunk_x}, {chunk_z}]\n• Point de ralliement (Home) défini à votre position." + confirm_prompt: "[Guilde] Création de la guilde {guild} {tag} :\n• Coût : {cost}\n• Territoire initial : {world} ({chunk_x}, {chunk_z})\n⚠ Exécutez à nouveau la commande pour confirmer la création !" + success: "Félicitations ! La guilde {guild} {tag} a été fondée !\n• Coût prélevé : {cost}\n• Chunk initial revendiqué : {world} [{chunk_x}, {chunk_z}]\n• Point de ralliement (Home) défini à votre position." disband: not_leader: "Seul le Chef de la guilde peut dissoudre la guilde." - confirm_prompt: "⚠ ATTENTION : Vous allez dissoudre définitivement la guilde {guild} et libérer tous ses territoires.\nExécutez à nouveau /guild disband pour confirmer." - success: "La guilde {guild} a été dissoute avec succès." + confirm_prompt: "⚠ ATTENTION : Vous allez dissoudre définitivement la guilde {guild} et libérer tous ses territoires.\nExécutez à nouveau /guild disband pour confirmer." + success: "La guilde {guild} a été dissoute avec succès." leave: leader_cannot_leave: "En tant que Chef, vous ne pouvez pas quitter la guilde. Transférez le lead ou dissolvez la guilde." - success: "Vous avez quitté la guilde {guild}." + success: "Vous avez quitté la guilde {guild}." kick: cannot_kick_self: "Vous ne pouvez pas vous expulser vous-même. Utilisez /guild leave." insufficient_rank: "Vous ne pouvez pas expulser un membre ayant un rang supérieur ou égal au vôtre." no_permission: "Votre rang ne vous autorise pas à expulser des membres." success: "{player} a été expulsé de la guilde." - target_notify: "Vous avez été expulsé de la guilde {guild}." + target_notify: "Vous avez été expulsé de la guilde {guild}." invite: no_permission: "Votre rang ne vous autorise pas à envoyer des invitations." cannot_invite_self: "Vous ne pouvez pas vous inviter vous-même." target_already_in_guild: "{player} appartient déjà à une guilde." sent: "Invitation envoyée à {player} (Expire dans {seconds}s)." - received: "[Guilde] Vous avez été invité à rejoindre la guilde {guild} par {sender} !\nTapez /guild join {guild} pour accepter." - no_pending_invite: "Vous n'avez aucune invitation valide en attente pour la guilde {guild}." + received_interactive: "[Guilde] Vous avez été invité à rejoindre la guilde {guild} {tag} par {sender} !\nCliquez pour accepter l''invitation et rejoindre la guilde !'>[ ✅ ACCEPTER ] Cliquez pour refuser l''invitation.'>[ ❌ REFUSER ]" + no_pending_invite: "Vous n'avez aucune invitation valide en attente pour la guilde {guild}." accepted: "Le joueur {player} a rejoint la guilde !" + declined_sender: "{player} a refusé votre invitation." + declined_target: "Vous avez refusé l'invitation de la guilde {guild}." claim: no_permission: "Votre rang ne vous autorise pas à revendiquer des territoires." @@ -76,14 +83,24 @@ guild: not_set: "Aucun home n'est défini pour votre guilde." teleport_success: "Téléportation au point de ralliement de guilde réussie !" + list: + header: "=== LISTE DES GUILDES ({page}/{max_page}) ===" + item: "#{index} {guild} {tag} » Chef: {leader} Membres: {members} Claims: {claims}" + empty: "Aucune guilde n'a été créée pour le moment." + pagination: "Page précédente'>[◀ Précédent] --- Page suivante'>[Suivant ▶]" + protection: - territory_enter_own: "Territoire de guilde : {guild} [{tag}]" - territory_enter_other: "Territoire étranger : {guild} [{tag}]" + territory_enter_own: "Territoire de guilde : {guild} {tag}" + territory_enter_other: "Territoire étranger : {guild} {tag}" territory_leave: "Vous entrez en Zone Libre (Wilderness)." - cannot_break: "⛔ Ce territoire appartient à la guilde {guild} !" + cannot_break: "⛔ Ce territoire appartient à la guilde {guild} !" rank_cannot_break: "Votre rang de guilde ne vous autorise pas à casser des blocs ici." - cannot_build: "⛔ Ce territoire appartient à la guilde {guild} !" + cannot_build: "⛔ Ce territoire appartient à la guilde {guild} !" rank_cannot_build: "Votre rang de guilde ne vous autorise pas à poser des blocs ici." - cannot_interact: "⛔ Ce territoire appartient à la guilde {guild} !" + cannot_interact: "⛔ Ce territoire appartient à la guilde {guild} !" rank_cannot_container: "Votre rang ne vous autorise pas à ouvrir les conteneurs de guilde." rank_cannot_interact: "Votre rang ne vous autorise pas à utiliser ces mécanismes." + pvp_denied: "⚔ Le combat entre joueurs (PvP) est strictement interdit sur les territoires de guilde." + piston_denied: "⛔ Les pistons ne peuvent pas déplacer de blocs à travers les frontières d'un territoire de guilde." + fluid_denied: "⛔ L'écoulement de fluides vers un territoire protégé est bloqué." + hanging_denied: "⛔ Vous ne pouvez pas modifier ou casser les entités de décoration dans ce territoire."