From 62b239b9cdd0a948aa39b62525743206ff187858 Mon Sep 17 00:00:00 2001 From: GeminiAntigravityCLI Date: Thu, 20 Aug 2026 18:03:17 +0200 Subject: [PATCH] feat(messages): add messages.yml, MessageManager, validation rules and double-confirmation for guild creation & disband --- src/main/java/fr/luc/gamingcore/Main.java | 44 ++- .../luc/gamingcore/command/GuildCommands.java | 261 +++++++++++------- .../listener/TerritoryProtectionListener.java | 51 ++-- .../luc/gamingcore/util/MessageManager.java | 130 +++++++++ src/main/resources/messages.yml | 89 ++++++ 5 files changed, 451 insertions(+), 124 deletions(-) create mode 100644 src/main/java/fr/luc/gamingcore/util/MessageManager.java create mode 100644 src/main/resources/messages.yml diff --git a/src/main/java/fr/luc/gamingcore/Main.java b/src/main/java/fr/luc/gamingcore/Main.java index 9d0fe90..2cb55ac 100644 --- a/src/main/java/fr/luc/gamingcore/Main.java +++ b/src/main/java/fr/luc/gamingcore/Main.java @@ -2,11 +2,15 @@ package fr.luc.gamingcore; import com.sk89q.worldguard.WorldGuard; import com.sk89q.worldguard.protection.regions.RegionContainer; +import fr.luc.bettermccommands.BetterMcCommands; +import fr.luc.gamingcore.command.GuildCommands; import fr.luc.gamingcore.database.DatabaseManager; import fr.luc.gamingcore.database.GuildRepository; +import fr.luc.gamingcore.economy.ChunkPriceCalculator; import fr.luc.gamingcore.economy.EconomyManager; import fr.luc.gamingcore.listener.TerritoryProtectionListener; import fr.luc.gamingcore.manager.GuildManager; +import fr.luc.gamingcore.util.MessageManager; import fr.luc.gamingcore.util.TextUtil; import net.milkbowl.vault.economy.Economy; import org.bukkit.plugin.PluginManager; @@ -23,11 +27,12 @@ public final class Main extends JavaPlugin { private Economy economy; private RegionContainer regionContainer; + private MessageManager messageManager; private DatabaseManager databaseManager; private GuildRepository guildRepository; private EconomyManager economyManager; private GuildManager guildManager; - private fr.luc.bettermccommands.BetterMcCommands betterMcCommands; + private BetterMcCommands betterMcCommands; /** * Méthode appelée lors de l'activation du plugin par le serveur Minecraft. @@ -37,8 +42,9 @@ public final class Main extends JavaPlugin { instance = this; saveDefaultConfig(); - // 1. Initialiser le préfixe de texte MiniMessage - TextUtil.setPrefix(getConfig().getString("prefix", "[Guilde] » ")); + // 1. Initialiser le gestionnaire de messages (messages.yml) + this.messageManager = new MessageManager(this); + this.messageManager.reload(); // 2. Initialiser les hooks externes setupWorldGuard(); @@ -58,12 +64,12 @@ public final class Main extends JavaPlugin { this.guildManager.loadData(); // 6. Initialiser le framework betterMcCommands et enregistrer les commandes - this.betterMcCommands = fr.luc.bettermccommands.BetterMcCommands.create(this); - new fr.luc.gamingcore.command.GuildCommands(this, guildManager).register(); + this.betterMcCommands = BetterMcCommands.create(this); + new GuildCommands(this, guildManager).register(); // 7. Enregistrer les écouteurs d'événements PluginManager pm = getServer().getPluginManager(); - pm.registerEvents(new TerritoryProtectionListener(guildManager), this); + pm.registerEvents(new TerritoryProtectionListener(this), this); getLogger().info("GamingCore (Système de Guilde - Cœur Métier & Commandes) a ete active avec succes !"); } @@ -86,6 +92,19 @@ public final class Main extends JavaPlugin { getLogger().info("GamingCore a ete desactive !"); } + /** + * Recharge à chaud tous les fichiers de configuration (config.yml et messages.yml) + * et réapplique les paramètres économiques aux calculateurs. + */ + public synchronized void reloadPlugin() { + reloadConfig(); + if (messageManager != null) { + messageManager.reload(); + } + loadConfigSettings(); + getLogger().info("Configuration et messages recharges avec succes !"); + } + /** * Charge et applique les paramètres de configuration dans les différents gestionnaires. */ @@ -101,7 +120,7 @@ public final class Main extends JavaPlugin { double delta = getConfig().getDouble("economy.chunk_pricing.delta", 1.15); long recentWindowMillis = getConfig().getLong("economy.chunk_pricing.recent_window_hours", 24) * 3600_000L; - fr.luc.gamingcore.economy.ChunkPriceCalculator calc = guildManager.getPriceCalculator(); + ChunkPriceCalculator calc = guildManager.getPriceCalculator(); calc.setBasePrice(basePrice); calc.setGrowthRate(growthRate); calc.setAlpha(alpha); @@ -152,6 +171,15 @@ public final class Main extends JavaPlugin { return instance; } + /** + * Obtient le gestionnaire de messages. + * + * @return L'instance MessageManager. + */ + public MessageManager getMessageManager() { + return messageManager; + } + /** * Obtient le fournisseur d'économie Vault. * @@ -220,7 +248,7 @@ public final class Main extends JavaPlugin { * * @return L'instance BetterMcCommands. */ - public fr.luc.bettermccommands.BetterMcCommands getBetterMcCommands() { + public BetterMcCommands getBetterMcCommands() { return betterMcCommands; } } diff --git a/src/main/java/fr/luc/gamingcore/command/GuildCommands.java b/src/main/java/fr/luc/gamingcore/command/GuildCommands.java index 0e2e392..d8e166f 100644 --- a/src/main/java/fr/luc/gamingcore/command/GuildCommands.java +++ b/src/main/java/fr/luc/gamingcore/command/GuildCommands.java @@ -7,23 +7,30 @@ import fr.luc.gamingcore.manager.GuildManager; import fr.luc.gamingcore.manager.GuildResult; import fr.luc.gamingcore.manager.InviteManager; import fr.luc.gamingcore.model.*; +import fr.luc.gamingcore.util.MessageManager; import fr.luc.gamingcore.util.TextUtil; import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; +import java.time.Duration; import java.util.Optional; +import java.util.regex.Pattern; import java.util.stream.Collectors; /** - * Enregistrement et définition des commandes de guilde via le framework fluent **betterMcCommands**. + * Enregistrement et définition de la suite complète de commandes `/guild` + * utilisant les validateurs métier et les confirmations de **betterMcCommands**. */ public class GuildCommands { + private static final Pattern GUILD_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_]{3,16}$"); + private final Main plugin; private final GuildManager guildManager; private final InviteManager inviteManager; + private final MessageManager msg; /** * Constructeur des commandes de guilde. @@ -35,6 +42,7 @@ public class GuildCommands { this.plugin = plugin; this.guildManager = guildManager; this.inviteManager = guildManager.getInviteManager(); + this.msg = plugin.getMessageManager(); } /** @@ -66,6 +74,9 @@ public class GuildCommands { 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"); + } }) // ------------------------------------------------------------- @@ -76,15 +87,44 @@ public class GuildCommands { .playerOnly() .argument(Arguments.string("nom").description("Nom de la guilde (3-16 car)")) .argument(Arguments.string("tag").optional().description("Tag court (ex: LEG)")) + + // Validations déclaratives + .validate(context -> !guildManager.isInGuild(context.getPlayer().getUniqueId()), + msg.get("guild.already_in_guild")) + .validate(context -> GUILD_NAME_PATTERN.matcher(context.getString("nom")).matches(), + msg.get("guild.invalid_name")) + .validate(context -> guildManager.getGuildByName(context.getString("nom")) == null, + msg.get("guild.name_taken", "{guild}", "")) + .validate(context -> { + Chunk chunk = context.getPlayer().getLocation().getChunk(); + return !guildManager.isChunkClaimed(chunk.getWorld().getName(), chunk.getX(), chunk.getZ()); + }, msg.get("guild.creation.chunk_already_claimed")) + .validate(context -> guildManager.getEconomyManager().has(context.getPlayer(), creationCost), + msg.get("guild.creation.insufficient_funds", "{cost}", guildManager.getEconomyManager().format(creationCost))) + + // Confirmation avec détails du claim et du coût + .requireConfirmation(Duration.ofSeconds(20)) + .executes(context -> { Player player = context.getPlayer(); String name = context.getString("nom"); Optional tagOpt = context.getOptional("tag", String.class); String tag = tagOpt.orElse(name.length() <= 4 ? name.toUpperCase() : name.substring(0, 4).toUpperCase()); + Location loc = player.getLocation(); + Chunk chunk = loc.getChunk(); + GuildResult result = guildManager.createGuild(player, name, tag, creationCost); if (result.isSuccess()) { - context.replySuccess(result.getMessage()); + String successMsg = msg.get("guild.creation.success", + "{guild}", name, + "{tag}", tag, + "{cost}", guildManager.getEconomyManager().format(creationCost), + "{world}", loc.getWorld().getName(), + "{chunk_x}", String.valueOf(chunk.getX()), + "{chunk_z}", String.valueOf(chunk.getZ()) + ); + context.reply(successMsg); } else { context.replyError(result.getMessage()); } @@ -98,17 +138,25 @@ public class GuildCommands { .description("Dissoudre définitivement votre guilde") .aliases("delete", "supprimer") .playerOnly() + .validate(context -> guildManager.isInGuild(context.getPlayer().getUniqueId()), + msg.get("guild.not_in_guild")) + .validate(context -> { + Guild g = guildManager.getGuildByPlayer(context.getPlayer().getUniqueId()); + if (g == null) return false; + GuildRank leaderRank = g.getLeaderRank(); + GuildMember member = g.getMember(context.getPlayer().getUniqueId()); + return member != null && leaderRank != null && member.getRankId().equalsIgnoreCase(leaderRank.getId()); + }, msg.get("guild.disband.not_leader")) + .requireConfirmation(Duration.ofSeconds(15)) .executes(context -> { Player player = context.getPlayer(); Guild guild = guildManager.getGuildByPlayer(player.getUniqueId()); - if (guild == null) { - context.replyError("Vous n'appartenez à aucune guilde."); - return; - } + if (guild == null) return; + String guildName = guild.getName(); GuildResult result = guildManager.disbandGuild(player, guild); if (result.isSuccess()) { - context.replySuccess(result.getMessage()); + context.reply(msg.get("guild.disband.success", "{guild}", guildName)); } else { context.replyError(result.getMessage()); } @@ -122,69 +170,64 @@ public class GuildCommands { .description("Inviter un joueur connecté à rejoindre votre guilde") .playerOnly() .argument(Arguments.player("joueur").description("Joueur cible")) + .validate(context -> guildManager.isInGuild(context.getPlayer().getUniqueId()), + msg.get("guild.not_in_guild")) + .validate(context -> { + Guild g = guildManager.getGuildByPlayer(context.getPlayer().getUniqueId()); + return g != null && g.hasPermission(context.getPlayer().getUniqueId(), GuildPermission.INVITE); + }, msg.get("guild.invite.no_permission")) + .validate(context -> !context.getTargetPlayer("joueur").getUniqueId().equals(context.getPlayer().getUniqueId()), + msg.get("guild.invite.cannot_invite_self")) + .validate(context -> !guildManager.isInGuild(context.getTargetPlayer("joueur").getUniqueId()), + msg.get("guild.invite.target_already_in_guild", "{player}", "")) .executes(context -> { Player sender = context.getPlayer(); Guild guild = guildManager.getGuildByPlayer(sender.getUniqueId()); - if (guild == null) { - context.replyError("Vous n'appartenez à aucune guilde."); - return; - } - - if (!guild.hasPermission(sender.getUniqueId(), GuildPermission.INVITE)) { - context.replyError("Votre rang ne vous autorise pas à inviter des joueurs."); - return; - } - Player target = context.getTargetPlayer("joueur"); - if (target.getUniqueId().equals(sender.getUniqueId())) { - context.replyError("Vous ne pouvez pas vous inviter vous-même."); - return; - } - - if (guildManager.isInGuild(target.getUniqueId())) { - context.replyError("Ce joueur appartient déjà à une guilde."); - return; - } GuildInvite invite = inviteManager.sendInvite(guild, sender, target); - context.replySuccess("Invitation envoyée à " + target.getName() + " (Expire dans " + invite.getRemainingSeconds() + "s)."); - TextUtil.sendMessage(target, "[Guilde] Vous avez été invité à rejoindre la guilde " + guild.getName() + " par " + sender.getName() + " !"); - TextUtil.sendMessage(target, "Tapez /guild join " + guild.getName() + " pour accepter."); + context.reply(msg.get("guild.invite.sent", + "{player}", target.getName(), + "{seconds}", String.valueOf(invite.getRemainingSeconds()) + )); + + TextUtil.sendMessage(target, msg.get("guild.invite.received", + "{guild}", guild.getName(), + "{sender}", sender.getName() + )); }) ) // ------------------------------------------------------------- - // Sous-commande : /guild join (ou /guild accept) + // Sous-commande : /guild join // ------------------------------------------------------------- .subcommand(BetterMcCommands.subBuilder("join") .description("Accepter une invitation et rejoindre une guilde") .aliases("accept", "rejoindre") .playerOnly() .argument(Arguments.string("guilde").description("Nom de la guilde")) + .validate(context -> !guildManager.isInGuild(context.getPlayer().getUniqueId()), + msg.get("guild.already_in_guild")) .executes(context -> { Player player = context.getPlayer(); - if (guildManager.isInGuild(player.getUniqueId())) { - context.replyError("Vous appartenez déjà à une guilde."); - return; - } - String guildName = context.getString("guilde"); Guild targetGuild = guildManager.getGuildByName(guildName); + if (targetGuild == null) { - context.replyError("La guilde '" + guildName + "' n'existe pas."); + context.reply(msg.get("guild.guild_not_found", "{guild}", guildName)); return; } GuildInvite invite = inviteManager.getValidInvite(player, targetGuild.getId()); if (invite == null) { - context.replyError("Vous n'avez aucune invitation valide en attente pour cette guilde."); + context.reply(msg.get("guild.invite.no_pending_invite", "{guild}", targetGuild.getName())); return; } inviteManager.removeInvite(player, targetGuild.getId()); GuildResult result = guildManager.addMember(targetGuild, player); if (result.isSuccess()) { - context.replySuccess(result.getMessage()); + context.reply(msg.get("guild.invite.accepted", "{player}", player.getName())); } else { context.replyError(result.getMessage()); } @@ -198,17 +241,17 @@ public class GuildCommands { .description("Quitter votre guilde actuelle") .aliases("quitter") .playerOnly() + .validate(context -> guildManager.isInGuild(context.getPlayer().getUniqueId()), + msg.get("guild.not_in_guild")) .executes(context -> { Player player = context.getPlayer(); Guild guild = guildManager.getGuildByPlayer(player.getUniqueId()); - if (guild == null) { - context.replyError("Vous n'appartenez à aucune guilde."); - return; - } + if (guild == null) return; + String guildName = guild.getName(); GuildResult result = guildManager.leaveGuild(player, guild); if (result.isSuccess()) { - context.replySuccess(result.getMessage()); + context.reply(msg.get("guild.leave.success", "{guild}", guildName)); } else { context.replyError(result.getMessage()); } @@ -222,18 +265,18 @@ public class GuildCommands { .description("Expulser un membre de votre guilde") .playerOnly() .argument(Arguments.player("joueur").description("Joueur à expulser")) + .validate(context -> guildManager.isInGuild(context.getPlayer().getUniqueId()), + msg.get("guild.not_in_guild")) .executes(context -> { Player player = context.getPlayer(); Guild guild = guildManager.getGuildByPlayer(player.getUniqueId()); - if (guild == null) { - context.replyError("Vous n'appartenez à aucune guilde."); - return; - } + if (guild == null) return; Player target = context.getTargetPlayer("joueur"); GuildResult result = guildManager.kickMember(player, guild, target.getUniqueId()); if (result.isSuccess()) { - context.replySuccess(result.getMessage()); + context.reply(msg.get("guild.kick.success", "{player}", target.getName())); + TextUtil.sendMessage(target, msg.get("guild.kick.target_notify", "{guild}", guild.getName())); } else { context.replyError(result.getMessage()); } @@ -246,19 +289,30 @@ public class GuildCommands { .subcommand(BetterMcCommands.subBuilder("claim") .description("Revendiquer le chunk actuel pour votre guilde") .playerOnly() + .validate(context -> guildManager.isInGuild(context.getPlayer().getUniqueId()), + msg.get("guild.not_in_guild")) + .validate(context -> { + Guild g = guildManager.getGuildByPlayer(context.getPlayer().getUniqueId()); + return g != null && g.hasPermission(context.getPlayer().getUniqueId(), GuildPermission.CLAIM); + }, msg.get("guild.claim.no_permission")) + .validate(context -> { + Chunk chunk = context.getPlayer().getLocation().getChunk(); + return !guildManager.isChunkClaimed(chunk.getWorld().getName(), chunk.getX(), chunk.getZ()); + }, msg.get("guild.claim.already_claimed")) .executes(context -> { Player player = context.getPlayer(); Guild guild = guildManager.getGuildByPlayer(player.getUniqueId()); - if (guild == null) { - context.replyError("Vous n'appartenez à aucune guilde."); - return; - } - Location loc = player.getLocation(); Chunk chunk = loc.getChunk(); + + double price = guildManager.getPriceCalculator().calculateNextChunkPrice(guild); GuildResult result = guildManager.claimChunk(player, guild, loc.getWorld(), chunk.getX(), chunk.getZ()); + if (result.isSuccess()) { - context.replySuccess(result.getMessage()); + context.reply(msg.get("guild.claim.success", + "{cost}", guildManager.getEconomyManager().format(price), + "{count}", String.valueOf(guild.getClaimCount()) + )); } else { context.replyError(result.getMessage()); } @@ -271,19 +325,21 @@ public class GuildCommands { .subcommand(BetterMcCommands.subBuilder("unclaim") .description("Libérer le chunk actuel") .playerOnly() + .validate(context -> guildManager.isInGuild(context.getPlayer().getUniqueId()), + msg.get("guild.not_in_guild")) + .validate(context -> { + Guild g = guildManager.getGuildByPlayer(context.getPlayer().getUniqueId()); + return g != null && g.hasPermission(context.getPlayer().getUniqueId(), GuildPermission.UNCLAIM); + }, msg.get("guild.claim.unclaim_no_permission")) .executes(context -> { Player player = context.getPlayer(); Guild guild = guildManager.getGuildByPlayer(player.getUniqueId()); - if (guild == null) { - context.replyError("Vous n'appartenez à aucune guilde."); - return; - } - Location loc = player.getLocation(); Chunk chunk = loc.getChunk(); + GuildResult result = guildManager.unclaimChunk(player, guild, loc.getWorld(), chunk.getX(), chunk.getZ()); if (result.isSuccess()) { - context.replySuccess(result.getMessage()); + context.reply(msg.get("guild.claim.unclaim_success")); } else { context.replyError(result.getMessage()); } @@ -298,18 +354,19 @@ public class GuildCommands { .aliases("deposer", "don") .playerOnly() .argument(Arguments.decimal("montant", 1.0, 1000000000.0).description("Somme à déposer")) + .validate(context -> guildManager.isInGuild(context.getPlayer().getUniqueId()), + msg.get("guild.not_in_guild")) .executes(context -> { Player player = context.getPlayer(); Guild guild = guildManager.getGuildByPlayer(player.getUniqueId()); - if (guild == null) { - context.replyError("Vous n'appartenez à aucune guilde."); - return; - } - double amount = context.getDouble("montant"); + GuildResult result = guildManager.depositMoney(player, guild, amount); if (result.isSuccess()) { - context.replySuccess(result.getMessage()); + context.reply(msg.get("guild.bank.deposit_success", + "{amount}", guildManager.getEconomyManager().format(amount), + "{balance}", guildManager.getEconomyManager().format(guild.getBalance()) + )); } else { context.replyError(result.getMessage()); } @@ -324,18 +381,18 @@ public class GuildCommands { .aliases("retirer") .playerOnly() .argument(Arguments.decimal("montant", 1.0, 1000000000.0).description("Somme à retirer")) + .validate(context -> guildManager.isInGuild(context.getPlayer().getUniqueId()), + msg.get("guild.not_in_guild")) .executes(context -> { Player player = context.getPlayer(); Guild guild = guildManager.getGuildByPlayer(player.getUniqueId()); - if (guild == null) { - context.replyError("Vous n'appartenez à aucune guilde."); - return; - } - double amount = context.getDouble("montant"); + GuildResult result = guildManager.withdrawMoney(player, guild, amount); if (result.isSuccess()) { - context.replySuccess(result.getMessage()); + context.reply(msg.get("guild.bank.withdraw_success", + "{amount}", guildManager.getEconomyManager().format(amount) + )); } else { context.replyError(result.getMessage()); } @@ -348,29 +405,26 @@ public class GuildCommands { .subcommand(BetterMcCommands.subBuilder("sethome") .description("Définir le home de guilde à votre position") .playerOnly() + .validate(context -> guildManager.isInGuild(context.getPlayer().getUniqueId()), + msg.get("guild.not_in_guild")) + .validate(context -> { + Guild g = guildManager.getGuildByPlayer(context.getPlayer().getUniqueId()); + return g != null && g.hasPermission(context.getPlayer().getUniqueId(), GuildPermission.SET_HOME); + }, msg.get("guild.home.set_no_permission")) .executes(context -> { Player player = context.getPlayer(); Guild guild = guildManager.getGuildByPlayer(player.getUniqueId()); - if (guild == null) { - context.replyError("Vous n'appartenez à aucune guilde."); - return; - } - - if (!guild.hasPermission(player.getUniqueId(), GuildPermission.SET_HOME)) { - context.replyError("Votre rang ne vous autorise pas à modifier le home de guilde."); - return; - } - Location loc = player.getLocation(); Chunk chunk = loc.getChunk(); + if (!guild.isClaimed(loc.getWorld().getName(), chunk.getX(), chunk.getZ())) { - context.replyError("Le point de ralliement doit impérativement se situer sur un territoire possédé par la guilde !"); + context.reply(msg.get("guild.home.set_must_be_in_claim")); return; } guild.setHome(loc); plugin.getGuildRepository().saveGuildAsync(guild); - context.replySuccess("Le home de guilde a été mis à jour avec succès !"); + context.reply(msg.get("guild.home.set_success")); }) ) @@ -380,28 +434,25 @@ public class GuildCommands { .subcommand(BetterMcCommands.subBuilder("home") .description("Se téléporter au point de ralliement de la guilde") .playerOnly() + .validate(context -> guildManager.isInGuild(context.getPlayer().getUniqueId()), + msg.get("guild.not_in_guild")) + .validate(context -> { + Guild g = guildManager.getGuildByPlayer(context.getPlayer().getUniqueId()); + return g != null && g.hasPermission(context.getPlayer().getUniqueId(), GuildPermission.HOME); + }, msg.get("guild.home.teleport_no_permission")) .executes(context -> { Player player = context.getPlayer(); Guild guild = guildManager.getGuildByPlayer(player.getUniqueId()); - if (guild == null) { - context.replyError("Vous n'appartenez à aucune guilde."); - return; - } - - if (!guild.hasPermission(player.getUniqueId(), GuildPermission.HOME)) { - context.replyError("Votre rang ne vous autorise pas à vous téléporter au home de guilde."); - return; - } - Location homeLoc = guild.getHomeLocation(); + if (homeLoc == null) { - context.replyError("Aucun home n'est défini pour votre guilde."); + context.reply(msg.get("guild.home.not_set")); return; } player.teleportAsync(homeLoc).thenAccept(success -> { if (success) { - TextUtil.sendActionBar(player, "Téléportation au home de guilde réussie !"); + msg.sendActionBar(player, "guild.home.teleport_success"); } }); }) @@ -421,12 +472,12 @@ public class GuildCommands { } else if (context.isPlayer()) { targetGuild = guildManager.getGuildByPlayer(context.getPlayer().getUniqueId()); } else { - context.replyError("Veuillez préciser le nom de la guilde depuis la console."); + context.replyError("Veuillez préciser le nom de la guilde."); return; } if (targetGuild == null) { - context.replyError("Guilde introuvable."); + context.reply(msg.get("guild.guild_not_found", "{guild}", nameOpt.orElse(""))); return; } @@ -462,7 +513,7 @@ public class GuildCommands { } if (targetGuild == null) { - context.replyError("Guilde introuvable."); + context.reply(msg.get("guild.guild_not_found", "{guild}", nameOpt.orElse(""))); return; } @@ -480,6 +531,18 @@ public class GuildCommands { }) ) + // ------------------------------------------------------------- + // Sous-commande : /guild reload + // ------------------------------------------------------------- + .subcommand(BetterMcCommands.subBuilder("reload") + .description("Recharger les fichiers de configuration et de messages") + .permission("guild.admin.reload") + .executes(context -> { + plugin.reloadPlugin(); + context.reply(msg.get("general.reloaded")); + }) + ) + .register(); } } diff --git a/src/main/java/fr/luc/gamingcore/listener/TerritoryProtectionListener.java b/src/main/java/fr/luc/gamingcore/listener/TerritoryProtectionListener.java index 6928a97..9ca14e9 100644 --- a/src/main/java/fr/luc/gamingcore/listener/TerritoryProtectionListener.java +++ b/src/main/java/fr/luc/gamingcore/listener/TerritoryProtectionListener.java @@ -1,16 +1,14 @@ package fr.luc.gamingcore.listener; +import fr.luc.gamingcore.Main; import fr.luc.gamingcore.manager.GuildManager; import fr.luc.gamingcore.model.Guild; import fr.luc.gamingcore.model.GuildPermission; -import fr.luc.gamingcore.util.TextUtil; -import org.bukkit.Chunk; +import fr.luc.gamingcore.util.MessageManager; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.entity.Player; - -import java.util.Objects; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -22,20 +20,26 @@ import org.bukkit.event.entity.EntityExplodeEvent; 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. */ public class TerritoryProtectionListener implements Listener { + private final Main plugin; private final GuildManager guildManager; + private final MessageManager messageManager; /** * Constructeur de l'écouteur de protection territoriale. * - * @param guildManager Le gestionnaire de guildes. + * @param plugin Instance principale du plugin. */ - public TerritoryProtectionListener(GuildManager guildManager) { - this.guildManager = guildManager; + public TerritoryProtectionListener(Main plugin) { + this.plugin = plugin; + this.guildManager = plugin.getGuildManager(); + this.messageManager = plugin.getMessageManager(); } /** @@ -59,14 +63,14 @@ public class TerritoryProtectionListener implements Listener { Guild playerGuild = guildManager.getGuildByPlayer(player.getUniqueId()); if (playerGuild == null || !playerGuild.getId().equals(guildAtBlock.getId())) { event.setCancelled(true); - TextUtil.sendActionBar(player, "⛔ Ce territoire appartient à la guilde " + guildAtBlock.getName() + " !"); + messageManager.sendActionBar(player, "protection.cannot_break", "{guild}", guildAtBlock.getName(), "{tag}", guildAtBlock.getTag()); return; } // Le joueur est dans sa propre guilde : vérifier la permission BREAK if (!guildAtBlock.hasPermission(player.getUniqueId(), GuildPermission.BREAK)) { event.setCancelled(true); - TextUtil.sendActionBar(player, "Votre rang de guilde ne vous autorise pas à casser des blocs."); + messageManager.sendActionBar(player, "protection.rank_cannot_break"); } } @@ -91,14 +95,14 @@ public class TerritoryProtectionListener implements Listener { Guild playerGuild = guildManager.getGuildByPlayer(player.getUniqueId()); if (playerGuild == null || !playerGuild.getId().equals(guildAtBlock.getId())) { event.setCancelled(true); - TextUtil.sendActionBar(player, "⛔ Ce territoire appartient à la guilde " + guildAtBlock.getName() + " !"); + messageManager.sendActionBar(player, "protection.cannot_build", "{guild}", guildAtBlock.getName(), "{tag}", guildAtBlock.getTag()); return; } // Vérifier la permission BUILD if (!guildAtBlock.hasPermission(player.getUniqueId(), GuildPermission.BUILD)) { event.setCancelled(true); - TextUtil.sendActionBar(player, "Votre rang de guilde ne vous autorise pas à poser des blocs."); + messageManager.sendActionBar(player, "protection.rank_cannot_build"); } } @@ -134,7 +138,7 @@ public class TerritoryProtectionListener implements Listener { if (!isSameGuild) { if (isContainer || isMechanism) { event.setCancelled(true); - TextUtil.sendActionBar(player, "⛔ Ce territoire appartient à la guilde " + guildAtBlock.getName() + " !"); + messageManager.sendActionBar(player, "protection.cannot_interact", "{guild}", guildAtBlock.getName(), "{tag}", guildAtBlock.getTag()); } return; } @@ -142,10 +146,10 @@ public class TerritoryProtectionListener implements Listener { // Vérification des permissions spécifiques de rang if (isContainer && !guildAtBlock.hasPermission(player.getUniqueId(), GuildPermission.CONTAINER)) { event.setCancelled(true); - TextUtil.sendActionBar(player, "Votre rang ne vous autorise pas à ouvrir les conteneurs de guilde."); + messageManager.sendActionBar(player, "protection.rank_cannot_container"); } else if (isMechanism && !guildAtBlock.hasPermission(player.getUniqueId(), GuildPermission.INTERACT)) { event.setCancelled(true); - TextUtil.sendActionBar(player, "Votre rang ne vous autorise pas à interagir avec ces mécanismes."); + messageManager.sendActionBar(player, "protection.rank_cannot_interact"); } } @@ -156,6 +160,9 @@ public class TerritoryProtectionListener implements Listener { */ @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onEntityExplode(EntityExplodeEvent event) { + if (!plugin.getConfig().getBoolean("protection.prevent_explosions", true)) { + return; + } event.blockList().removeIf(block -> guildManager.isChunkClaimed( block.getWorld().getName(), block.getX() >> 4, @@ -170,6 +177,9 @@ public class TerritoryProtectionListener implements Listener { */ @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onBlockExplode(BlockExplodeEvent event) { + if (!plugin.getConfig().getBoolean("protection.prevent_explosions", true)) { + return; + } event.blockList().removeIf(block -> guildManager.isChunkClaimed( block.getWorld().getName(), block.getX() >> 4, @@ -184,6 +194,10 @@ public class TerritoryProtectionListener implements Listener { */ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onPlayerMove(PlayerMoveEvent event) { + if (!plugin.getConfig().getBoolean("protection.show_actionbar_on_move", true)) { + return; + } + Location from = event.getFrom(); Location to = event.getTo(); if (to == null) return; @@ -209,10 +223,13 @@ public class TerritoryProtectionListener implements Listener { if (currentGuild != null) { Guild playerGuild = guildManager.getGuildByPlayer(player.getUniqueId()); boolean isOwn = playerGuild != null && playerGuild.getId().equals(currentGuild.getId()); - String color = isOwn ? "" : ""; - TextUtil.sendActionBar(player, "Entrée dans le territoire : " + color + "" + currentGuild.getName() + " (" + currentGuild.getTag() + ")"); + if (isOwn) { + messageManager.sendActionBar(player, "protection.territory_enter_own", "{guild}", currentGuild.getName(), "{tag}", currentGuild.getTag()); + } else { + messageManager.sendActionBar(player, "protection.territory_enter_other", "{guild}", currentGuild.getName(), "{tag}", currentGuild.getTag()); + } } else if (previousGuild != null) { - TextUtil.sendActionBar(player, "Vous entrez en Zone Libre (Wilderness)."); + messageManager.sendActionBar(player, "protection.territory_leave"); } } diff --git a/src/main/java/fr/luc/gamingcore/util/MessageManager.java b/src/main/java/fr/luc/gamingcore/util/MessageManager.java new file mode 100644 index 0000000..909943c --- /dev/null +++ b/src/main/java/fr/luc/gamingcore/util/MessageManager.java @@ -0,0 +1,130 @@ +package fr.luc.gamingcore.util; + +import fr.luc.gamingcore.Main; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; + +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; + +/** + * Gestionnaire centralisé des messages configurables et rechargeables depuis `messages.yml`. + */ +public class MessageManager { + + private final Main plugin; + private final File messagesFile; + private FileConfiguration messagesConfig; + private final Map cachedMessages = new ConcurrentHashMap<>(); + private String prefix = "[Guilde] » "; + + /** + * Constructeur du MessageManager. + * + * @param plugin Instance principale du plugin. + */ + public MessageManager(Main plugin) { + this.plugin = plugin; + this.messagesFile = new File(plugin.getDataFolder(), "messages.yml"); + } + + /** + * Charge ou recharge le fichier `messages.yml` depuis le disque. + */ + public synchronized void reload() { + if (!messagesFile.exists()) { + plugin.saveResource("messages.yml", false); + } + + this.messagesConfig = YamlConfiguration.loadConfiguration(messagesFile); + this.cachedMessages.clear(); + + // Charger les valeurs par défaut depuis le jar si des clés manquent + InputStream defStream = plugin.getResource("messages.yml"); + if (defStream != null) { + YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(new InputStreamReader(defStream, StandardCharsets.UTF_8)); + messagesConfig.setDefaults(defConfig); + } + + this.prefix = messagesConfig.getString("prefix", prefix); + TextUtil.setPrefix(this.prefix); + + // Mettre en cache toutes les chaînes trouvées + for (String key : messagesConfig.getKeys(true)) { + if (messagesConfig.isString(key)) { + cachedMessages.put(key, Objects.requireNonNull(messagesConfig.getString(key))); + } + } + + plugin.getLogger().info("Fichier messages.yml charge avec succes (" + cachedMessages.size() + " messages en cache)."); + } + + /** + * Récupère un message par sa clé et applique le remplacement des placeholders. + * + * @param key La clé du message dans messages.yml. + * @param placeholders Paires clé/valeur pour le remplacement (ex: "{guild}", "Valhalla", "{cost}", "1 000 $"). + * @return Le message formaté avec placeholders appliqués. + */ + public String get(String key, String... placeholders) { + String msg = cachedMessages.get(key); + if (msg == null) { + msg = "[Message manquant: " + key + "]"; + } + + if (placeholders != null && placeholders.length >= 2) { + for (int i = 0; i < placeholders.length - 1; i += 2) { + String phKey = placeholders[i]; + String phVal = placeholders[i + 1]; + if (phKey != null && phVal != null) { + msg = msg.replace(phKey, phVal); + } + } + } + + return msg; + } + + /** + * Envoie un message formaté avec MiniMessage à un émetteur de commande. + * + * @param sender Le destinataire du message. + * @param key La clé du message. + * @param placeholders Paires de placeholders. + */ + public void send(CommandSender sender, String key, String... placeholders) { + if (sender == null) return; + String formatted = get(key, placeholders); + TextUtil.sendMessage(sender, formatted); + } + + /** + * Envoie une notification dans l'Action Bar d'un joueur. + * + * @param player Le joueur destinataire. + * @param key La clé du message. + * @param placeholders Paires de placeholders. + */ + public void sendActionBar(Player player, String key, String... placeholders) { + if (player == null) return; + String formatted = get(key, placeholders); + TextUtil.sendActionBar(player, formatted); + } + + /** + * Obtient le préfixe global actuel. + * + * @return La chaîne MiniMessage du préfixe. + */ + public String getPrefix() { + return prefix; + } +} diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml new file mode 100644 index 0000000..1aeb90c --- /dev/null +++ b/src/main/resources/messages.yml @@ -0,0 +1,89 @@ +# ========================================================== +# GAMINGCORE - FICHIER DE MESSAGES +# Format : Kyori Adventure MiniMessage (Gradients, Hex, etc.) +# ========================================================== + +prefix: "[Guilde] » " + +general: + only_players: "Cette commande est réservée aux joueurs en jeu." + 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 !" + +guild: + already_in_guild: "Vous appartenez déjà à une guilde." + not_in_guild: "Vous n'appartenez à aucune guilde." + not_member: "Ce joueur n'est pas membre de votre guilde." + guild_not_found: "La guilde {guild} est introuvable." + invalid_name: "Le nom de guilde doit comporter entre 3 et 16 caractères alphanumériques." + name_taken: "Une guilde portant le nom {guild} existe déjà." + + 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." + + 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." + + 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}." + + 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}." + + 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}." + accepted: "Le joueur {player} a rejoint la guilde !" + + claim: + no_permission: "Votre rang ne vous autorise pas à revendiquer des territoires." + already_claimed: "Ce chunk est déjà revendiqué." + insufficient_bank: "La banque de guilde n'a pas assez d'argent (Requis : {cost}, Solde : {balance})." + success: "Chunk revendiqué avec succès pour {cost} ! (Total : {count} chunks)" + unclaim_no_permission: "Votre rang ne vous autorise pas à abandonner des territoires." + unclaim_not_owned: "Ce chunk n'appartient pas à votre guilde." + unclaim_last_chunk: "Vous ne pouvez pas abandonner le dernier territoire de votre guilde." + unclaim_success: "Le chunk a été libéré." + + bank: + deposit_no_permission: "Votre rang ne vous autorise pas à déposer dans la banque." + deposit_insufficient_player: "Vous n'avez pas assez d'argent sur votre compte personnel." + deposit_success: "Vous avez déposé {amount} dans la banque de guilde ! (Nouveau solde : {balance})" + withdraw_no_permission: "Votre rang ne vous autorise pas à retirer de l'argent de la banque." + withdraw_insufficient_bank: "Le solde de la banque de guilde est insuffisant (Solde actuel : {balance})." + withdraw_success: "Vous avez retiré {amount} de la banque de guilde." + + home: + set_no_permission: "Votre rang ne vous autorise pas à modifier le home de guilde." + set_must_be_in_claim: "Le point de ralliement doit obligatoirement être situé sur un chunk de la guilde !" + set_success: "Le point de ralliement (Home) a été mis à jour avec succès !" + teleport_no_permission: "Votre rang ne vous autorise pas à vous téléporter au home de guilde." + not_set: "Aucun home n'est défini pour votre guilde." + teleport_success: "Téléportation au point de ralliement de guilde réussie !" + +protection: + 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} !" + rank_cannot_break: "Votre rang de guilde ne vous autorise pas à casser des blocs ici." + 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} !" + 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."