feat: add anti-pvp, anti-piston, anti-fluid protections, /guild list, /guild decline, interactive clickable invites and help menu

This commit is contained in:
2026-08-20 19:30:45 +02:00
parent fdb5e7b484
commit db0ee45b45
4 changed files with 421 additions and 63 deletions
@@ -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 -> 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 -> {
context.reply("<gradient:#00C9FF:#92FE9D><bold>=== SYSTÈME DE GUILDE ===</bold></gradient>");
context.reply("<yellow>/guild create <nom> [tag]</yellow> <gray>- Fonder une nouvelle guilde</gray>");
context.reply("<yellow>/guild disband</yellow> <gray>- Dissoudre votre guilde</gray>");
context.reply("<yellow>/guild color <#hex></yellow> <gray>- Modifier la couleur hexadécimale</gray>");
context.reply("<yellow>/guild tag <tag></yellow> <gray>- Modifier le tag court</gray>");
context.reply("<yellow>/guild invite <joueur></yellow> <gray>- Inviter un joueur</gray>");
context.reply("<yellow>/guild join <guilde></yellow> <gray>- Rejoindre une guilde invitante</gray>");
context.reply("<yellow>/guild leave</yellow> <gray>- Quitter votre guilde</gray>");
context.reply("<yellow>/guild kick <joueur></yellow> <gray>- Expulser un membre</gray>");
context.reply("<yellow>/guild info [guilde]</yellow> <gray>- Afficher les détails d'une guilde</gray>");
context.reply("<yellow>/guild members [guilde]</yellow> <gray>- Liste des membres</gray>");
context.reply("<yellow>/guild claim</yellow> <gray>- Revendiquer le chunk actuel</gray>");
context.reply("<yellow>/guild unclaim</yellow> <gray>- Libérer le chunk actuel</gray>");
context.reply("<yellow>/guild deposit <montant></yellow> <gray>- Déposer dans la banque</gray>");
context.reply("<yellow>/guild withdraw <montant></yellow> <gray>- Retirer de la banque</gray>");
context.reply("<yellow>/guild sethome</yellow> <gray>- Définir le point de ralliement</gray>");
context.reply("<yellow>/guild home</yellow> <gray>- Se téléporter au home de guilde</gray>");
if (context.getSender().hasPermission("guild.admin.reload")) {
context.reply("<red>/guild reload</red> <gray>- Recharger les configurations et messages</gray>");
}
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<Guild> 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 <nom> [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 <guilde>
// -------------------------------------------------------------
.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("<gradient:#00C9FF:#92FE9D><bold>=== INFORMATIONS DE GUILDE ===</bold></gradient>");
context.reply("<gray>Nom :</gray> " + targetGuild.getFormattedName() + " " + targetGuild.getFormattedTag());
@@ -716,7 +817,7 @@ public class GuildCommands {
return;
}
context.reply("<gradient:#00C9FF:#92FE9D><bold>=== MEMBRES : " + targetGuild.getName() + " ===</bold></gradient>");
context.reply("<gradient:#00C9FF:#92FE9D><bold>=== MEMBRES : </bold></gradient>" + 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<HelpEntry> entries = new ArrayList<>();
entries.add(new HelpEntry("/guild create <nom> [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 <tag>", "Modifier le tag court de la guilde (2-6 car)", "/guild tag "));
entries.add(new HelpEntry("/guild invite <joueur>", "Inviter un joueur connecté à rejoindre la guilde", "/guild invite "));
entries.add(new HelpEntry("/guild join <guilde>", "Accepter une invitation et rejoindre une guilde", "/guild join "));
entries.add(new HelpEntry("/guild decline <guilde>", "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 <joueur>", "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 <montant>", "Déposer des fonds dans la banque de guilde", "/guild deposit "));
entries.add(new HelpEntry("/guild withdraw <montant>", "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 = "<click:suggest_command:'" + entry.suggestCommand + "'>" +
"<hover:show_text:'<green>Cliquez pour insérer : </green><yellow>" + entry.usage + "</yellow>'>" +
"<yellow>" + entry.usage + "</yellow></hover></click> <gray>- " + entry.description + "</gray>";
context.reply(line);
}
int prevPage = Math.max(1, page - 1);
int nextPage = Math.min(maxPage, page + 1);
String footer = "<click:run_command:'/guild help " + prevPage + "'><hover:show_text:'<yellow>Page précédente</yellow>'><aqua>[◀ Précédent]</aqua></hover></click> " +
"<dark_gray>---</dark_gray> " +
"<click:run_command:'/guild help " + nextPage + "'><hover:show_text:'<yellow>Page suivante</yellow>'><aqua>[Suivant ▶]</aqua></hover></click>";
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;
}
}
}
@@ -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");
@@ -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);
}
+30 -13
View File
@@ -10,6 +10,11 @@ general:
no_permission: "<red>Vous n'avez pas la permission requise.</red>"
player_not_found: "<red>Joueur introuvable ou hors ligne.</red>"
reloaded: "<green>La configuration et les messages ont été rechargés avec succès !</green>"
invalid_page: "<red>Numéro de page invalide (1 à {max_page}).</red>"
help:
header: "<gradient:#00C9FF:#92FE9D><bold>=== SYSTÈME DE GUILDE (Aide {page}/{max_page}) ===</bold></gradient>"
footer: "<gray>Cliquez sur une commande pour la suggérer dans le chat.</gray>"
guild:
already_in_guild: "<red>Vous appartenez déjà à une guilde.</red>"
@@ -22,33 +27,35 @@ guild:
creation:
insufficient_funds: "<red>Fonds insuffisants pour créer une guilde. Requis : <yellow>{cost}</yellow>.</red>"
chunk_already_claimed: "<red>Le chunk où vous vous trouvez est déjà revendiqué par une guilde.</red>"
confirm_prompt: "<gradient:#00C9FF:#92FE9D>[Guilde]</gradient> <yellow>Création de la guilde</yellow> <white><bold>{guild}</bold></white> <dark_gray>[{tag}]</dark_gray> :\n<gray>• Coût : <yellow>{cost}</yellow>\n• Territoire initial : <aqua>{world} ({chunk_x}, {chunk_z})</aqua>\n<gold>⚠ Exécutez à nouveau la commande pour confirmer la création !</gold>"
success: "<green>Félicitations ! La guilde <white><bold>{guild}</bold></white> <dark_gray>[{tag}]</dark_gray> a été fondée !</green>\n<gray>• Coût prélevé : <yellow>{cost}</yellow>\n• Chunk initial revendiqué : <aqua>{world} [{chunk_x}, {chunk_z}]</aqua>\n• Point de ralliement (Home) défini à votre position.</gray>"
confirm_prompt: "<gradient:#00C9FF:#92FE9D>[Guilde]</gradient> <yellow>Création de la guilde</yellow> {guild} {tag} :\n<gray>• Coût : <yellow>{cost}</yellow>\n• Territoire initial : <aqua>{world} ({chunk_x}, {chunk_z})</aqua>\n<gold>⚠ Exécutez à nouveau la commande pour confirmer la création !</gold>"
success: "<green>Félicitations ! La guilde {guild} {tag} a été fondée !</green>\n<gray>• Coût prélevé : <yellow>{cost}</yellow>\n• Chunk initial revendiqué : <aqua>{world} [{chunk_x}, {chunk_z}]</aqua>\n• Point de ralliement (Home) défini à votre position.</gray>"
disband:
not_leader: "<red>Seul le Chef de la guilde peut dissoudre la guilde.</red>"
confirm_prompt: "<red><bold>⚠ ATTENTION :</bold> Vous allez dissoudre définitivement la guilde <white>{guild}</white> et libérer tous ses territoires.\n<gold>Exécutez à nouveau <yellow>/guild disband</yellow> pour confirmer.</gold></red>"
success: "<yellow>La guilde <white><bold>{guild}</bold></white> a été dissoute avec succès.</yellow>"
confirm_prompt: "<red><bold>⚠ ATTENTION :</bold> Vous allez dissoudre définitivement la guilde {guild} et libérer tous ses territoires.\n<gold>Exécutez à nouveau <yellow>/guild disband</yellow> pour confirmer.</gold></red>"
success: "<yellow>La guilde {guild} a été dissoute avec succès.</yellow>"
leave:
leader_cannot_leave: "<red>En tant que Chef, vous ne pouvez pas quitter la guilde. Transférez le lead ou dissolvez la guilde.</red>"
success: "<yellow>Vous avez quitté la guilde <white>{guild}</white>.</yellow>"
success: "<yellow>Vous avez quitté la guilde {guild}.</yellow>"
kick:
cannot_kick_self: "<red>Vous ne pouvez pas vous expulser vous-même. Utilisez <yellow>/guild leave</yellow>.</red>"
insufficient_rank: "<red>Vous ne pouvez pas expulser un membre ayant un rang supérieur ou égal au vôtre.</red>"
no_permission: "<red>Votre rang ne vous autorise pas à expulser des membres.</red>"
success: "<yellow><white>{player}</white> a été expulsé de la guilde.</yellow>"
target_notify: "<red>Vous avez été expulsé de la guilde <yellow>{guild}</yellow>.</red>"
target_notify: "<red>Vous avez été expulsé de la guilde {guild}.</red>"
invite:
no_permission: "<red>Votre rang ne vous autorise pas à envoyer des invitations.</red>"
cannot_invite_self: "<red>Vous ne pouvez pas vous inviter vous-même.</red>"
target_already_in_guild: "<red><white>{player}</white> appartient déjà à une guilde.</red>"
sent: "<green>Invitation envoyée à <white>{player}</white> (Expire dans <yellow>{seconds}s</yellow>).</green>"
received: "<gradient:#00C9FF:#92FE9D>[Guilde]</gradient> Vous avez été invité à rejoindre la guilde <white><bold>{guild}</bold></white> par <yellow>{sender}</yellow> !\n<gray>Tapez <yellow>/guild join {guild}</yellow> pour accepter.</gray>"
no_pending_invite: "<red>Vous n'avez aucune invitation valide en attente pour la guilde <yellow>{guild}</yellow>.</red>"
received_interactive: "<gradient:#00C9FF:#92FE9D>[Guilde]</gradient> Vous avez été invité à rejoindre la guilde {guild} {tag} par <yellow>{sender}</yellow> !\n<click:run_command:'/guild join {raw_guild}'><hover:show_text:'<green>Cliquez pour accepter l''invitation et rejoindre la guilde !</green>'><green><bold>[ ✅ ACCEPTER ]</bold></green></hover></click> <click:run_command:'/guild decline {raw_guild}'><hover:show_text:'<red>Cliquez pour refuser l''invitation.</red>'><red><bold>[ ❌ REFUSER ]</bold></red></hover></click>"
no_pending_invite: "<red>Vous n'avez aucune invitation valide en attente pour la guilde {guild}.</red>"
accepted: "<green>Le joueur <white>{player}</white> a rejoint la guilde !</green>"
declined_sender: "<yellow><white>{player}</white> a refusé votre invitation.</yellow>"
declined_target: "<gray>Vous avez refusé l'invitation de la guilde {guild}.</gray>"
claim:
no_permission: "<red>Votre rang ne vous autorise pas à revendiquer des territoires.</red>"
@@ -76,14 +83,24 @@ guild:
not_set: "<red>Aucun home n'est défini pour votre guilde.</red>"
teleport_success: "<green>Téléportation au point de ralliement de guilde réussie !</green>"
list:
header: "<gradient:#00C9FF:#92FE9D><bold>=== LISTE DES GUILDES ({page}/{max_page}) ===</bold></gradient>"
item: "<dark_gray>#{index}</dark_gray> {guild} {tag} <dark_gray>»</dark_gray> <gray>Chef:</gray> <gold>{leader}</gold> <dark_gray>•</dark_gray> <gray>Membres:</gray> <yellow>{members}</yellow> <dark_gray>•</dark_gray> <gray>Claims:</gray> <aqua>{claims}</aqua>"
empty: "<gray>Aucune guilde n'a été créée pour le moment.</gray>"
pagination: "<click:run_command:'/guild list {prev_page}'><hover:show_text:'<yellow>Page précédente</yellow>'><aqua>[◀ Précédent]</aqua></hover></click> <dark_gray>---</dark_gray> <click:run_command:'/guild list {next_page}'><hover:show_text:'<yellow>Page suivante</yellow>'><aqua>[Suivant ▶]</aqua></hover></click>"
protection:
territory_enter_own: "<gray>Territoire de guilde : </gray><green><bold>{guild}</bold></green> <dark_gray>[{tag}]</dark_gray>"
territory_enter_other: "<gray>Territoire étranger : </gray><yellow><bold>{guild}</bold></yellow> <dark_gray>[{tag}]</dark_gray>"
territory_enter_own: "<gray>Territoire de guilde : </gray>{guild} {tag}"
territory_enter_other: "<gray>Territoire étranger : </gray>{guild} {tag}"
territory_leave: "<gray>Vous entrez en <green>Zone Libre</green> (Wilderness).</gray>"
cannot_break: "<red>⛔ Ce territoire appartient à la guilde <yellow>{guild}</yellow> !</red>"
cannot_break: "<red>⛔ Ce territoire appartient à la guilde {guild} !</red>"
rank_cannot_break: "<red>Votre rang de guilde ne vous autorise pas à casser des blocs ici.</red>"
cannot_build: "<red>⛔ Ce territoire appartient à la guilde <yellow>{guild}</yellow> !</red>"
cannot_build: "<red>⛔ Ce territoire appartient à la guilde {guild} !</red>"
rank_cannot_build: "<red>Votre rang de guilde ne vous autorise pas à poser des blocs ici.</red>"
cannot_interact: "<red>⛔ Ce territoire appartient à la guilde <yellow>{guild}</yellow> !</red>"
cannot_interact: "<red>⛔ Ce territoire appartient à la guilde {guild} !</red>"
rank_cannot_container: "<red>Votre rang ne vous autorise pas à ouvrir les conteneurs de guilde.</red>"
rank_cannot_interact: "<red>Votre rang ne vous autorise pas à utiliser ces mécanismes.</red>"
pvp_denied: "<red>⚔ Le combat entre joueurs (PvP) est strictement interdit sur les territoires de guilde.</red>"
piston_denied: "<red>⛔ Les pistons ne peuvent pas déplacer de blocs à travers les frontières d'un territoire de guilde.</red>"
fluid_denied: "<red>⛔ L'écoulement de fluides vers un territoire protégé est bloqué.</red>"
hanging_denied: "<red>⛔ Vous ne pouvez pas modifier ou casser les entités de décoration dans ce territoire.</red>"