diff --git a/src/main/java/fr/luc/crcore/CRCore.java b/src/main/java/fr/luc/crcore/CRCore.java index 865bd27..fe6189c 100644 --- a/src/main/java/fr/luc/crcore/CRCore.java +++ b/src/main/java/fr/luc/crcore/CRCore.java @@ -2,6 +2,8 @@ package fr.luc.crcore; import fr.luc.crcore.command.builtin.CoreCommand; import fr.luc.crcore.database.Database; +import fr.luc.crcore.message.MessagesService; +import fr.luc.crcore.message.YamlMessagesService; import fr.luc.crcore.player.BukkitEventFiringPlayerProfileServiceImpl; import fr.luc.crcore.player.InMemoryPlayerProfileRepository; import fr.luc.crcore.player.PlayerProfileRepository; @@ -77,6 +79,7 @@ public class CRCore { private TeamService teamService; private PlayerProfileRepository playerProfileRepository; private PlayerProfileService playerProfileService; + private MessagesService messages; private CoreCommand coreCommand; private boolean enabled = false; @@ -114,7 +117,9 @@ public class CRCore { this.teamService = buildTeamService(teamRepository); this.playerProfileService = buildPlayerProfileService(playerProfileRepository); - this.coreCommand = buildCoreCommand(teamService, playerProfileService); + this.messages = buildMessagesService(); + + this.coreCommand = buildCoreCommand(teamService, playerProfileService, messages); registerCommand(); registerPlaceholderHook(); @@ -181,8 +186,15 @@ public class CRCore { } /** Construit le {@link CoreCommand}. Override pour ajouter des groupes top-level. */ - protected CoreCommand buildCoreCommand(TeamService teamService, PlayerProfileService playerProfileService) { - return new CoreCommand(teamService, playerProfileService); + protected CoreCommand buildCoreCommand(TeamService teamService, + PlayerProfileService playerProfileService, + MessagesService messages) { + return new CoreCommand(teamService, playerProfileService, messages); + } + + /** Construit le {@link MessagesService}. Override pour utiliser une impl custom. */ + protected MessagesService buildMessagesService() { + return new YamlMessagesService(plugin); } /** @@ -259,6 +271,8 @@ public class CRCore { public TeamService getTeamService() { return teamService; } public PlayerProfileRepository getPlayerProfileRepository() { return playerProfileRepository; } public PlayerProfileService getPlayerProfileService() { return playerProfileService; } + public MessagesService getMessages() { return messages; } + public MessagesService messages() { return messages; } public CoreCommand getCoreCommand() { return coreCommand; } public boolean isEnabled() { return enabled; } } diff --git a/src/main/java/fr/luc/crcore/command/builtin/CoreCommand.java b/src/main/java/fr/luc/crcore/command/builtin/CoreCommand.java index 5492913..b25424a 100644 --- a/src/main/java/fr/luc/crcore/command/builtin/CoreCommand.java +++ b/src/main/java/fr/luc/crcore/command/builtin/CoreCommand.java @@ -1,9 +1,12 @@ package fr.luc.crcore.command.builtin; import fr.luc.crcore.command.BaseCommand; +import fr.luc.crcore.command.CommandResult; import fr.luc.crcore.command.builtin.team.TeamGroupSubCommand; +import fr.luc.crcore.message.MessagesService; import fr.luc.crcore.player.PlayerProfileService; import fr.luc.crcore.team.TeamService; +import org.bukkit.command.CommandSender; import java.util.Objects; @@ -11,38 +14,71 @@ import java.util.Objects; * Commande racine {@code /core}. Container des groupes par défaut. * *
Branchée par {@code CRCore.enable()} sur la {@code PluginCommand "core"} - * du plugin de jeu (qui doit l'avoir déclarée dans son {@code plugin.yml}). - * - *
Sans arguments, affiche l'aide des groupes disponibles. Avec {@code team
- * Admin uniquement. Ajoute un joueur connecté à l'équipe spécifiée,
- * quelle que soit sa visibilité.
- */
+/** {@code /core team add Admin uniquement.
- *
- * Les deux derniers arguments sont optionnels mais positionnels :
- * pour passer un {@code leader}, il faut aussi taper la {@code visibility}
- * d'abord (PUBLIC ou PRIVATE). Variantes valides :
- *
- * Admin uniquement. Crée une équipe ; visibilité par défaut PRIVATE,
+ * chef optionnel (sinon team leaderless).
*/
public class TeamCreateSubCommand extends SubCommand {
protected final TeamService service;
+ protected final MessagesService messages;
- public TeamCreateSubCommand(TeamService service) {
+ public TeamCreateSubCommand(TeamService service, MessagesService messages) {
super("create");
this.service = Objects.requireNonNull(service, "service");
+ this.messages = Objects.requireNonNull(messages, "messages");
description("Créer une équipe (admin)");
permission("crcore.team.create");
argument("name", ArgumentTypes.STRING);
@@ -60,10 +53,13 @@ public class TeamCreateSubCommand extends SubCommand {
try {
Team team = service.createTeam(name, tag, color, leaderId, visibility);
String chefPart = leaderOpt.isPresent()
- ? "chef : " + leaderOpt.get().getName()
- : "sans chef";
- return CommandResult.success("Équipe " + team.getName() + " [#" + team.getTag()
- + "] créée (" + visibility + ", " + chefPart + ").");
+ ? messages.get("team.create.with-leader", "leader", leaderOpt.get().getName())
+ : messages.get("team.create.no-leader");
+ return CommandResult.success(messages.get("team.create.success",
+ "name", team.getName(),
+ "tag", team.getTag(),
+ "visibility", team.getVisibility().name(),
+ "chef", chefPart));
} catch (TeamException ex) {
return CommandResult.failure(ex.getMessage());
}
diff --git a/src/main/java/fr/luc/crcore/command/builtin/team/TeamDeleteSubCommand.java b/src/main/java/fr/luc/crcore/command/builtin/team/TeamDeleteSubCommand.java
index 249ef32..4350d88 100644
--- a/src/main/java/fr/luc/crcore/command/builtin/team/TeamDeleteSubCommand.java
+++ b/src/main/java/fr/luc/crcore/command/builtin/team/TeamDeleteSubCommand.java
@@ -3,24 +3,22 @@ package fr.luc.crcore.command.builtin.team;
import fr.luc.crcore.command.CommandContext;
import fr.luc.crcore.command.CommandResult;
import fr.luc.crcore.command.SubCommand;
+import fr.luc.crcore.message.MessagesService;
import fr.luc.crcore.team.Team;
import fr.luc.crcore.team.TeamService;
import java.util.Objects;
-/**
- * {@code /core team delete Admin uniquement. Dissout l'équipe spécifiée. Aucun check de chef
- * — l'action est gated par la permission {@code crcore.team.delete}.
- */
+/** {@code /core team delete Pour overrider une sous-commande, un plugin de jeu fait :
* Ou sous-classe {@code TeamGroupSubCommand} et redéfinit son constructeur
- * pour swap ce qu'il faut.
*/
public class TeamGroupSubCommand extends SubCommand {
protected final TeamService service;
+ protected final MessagesService messages;
- public TeamGroupSubCommand(TeamService service) {
+ public TeamGroupSubCommand(TeamService service, MessagesService messages) {
super("team");
this.service = Objects.requireNonNull(service, "service");
+ this.messages = Objects.requireNonNull(messages, "messages");
description("Gestion des équipes");
registerDefaults();
}
@@ -34,19 +34,19 @@ public class TeamGroupSubCommand extends SubCommand {
* ou ajouter des sous-commandes au lieu du jeu standard.
*/
protected void registerDefaults() {
- addSubCommand(new TeamCreateSubCommand(service));
- addSubCommand(new TeamDeleteSubCommand(service));
- addSubCommand(new TeamAddSubCommand(service));
- addSubCommand(new TeamRemoveSubCommand(service));
- addSubCommand(new TeamJoinSubCommand(service));
- addSubCommand(new TeamLeaveSubCommand(service));
- addSubCommand(new TeamInfoSubCommand(service));
- addSubCommand(new TeamListSubCommand(service));
- addSubCommand(new TeamTransferSubCommand(service));
- addSubCommand(new TeamSetLeaderSubCommand(service));
- addSubCommand(new TeamVisibilitySubCommand(service));
- addSubCommand(new TeamScoreSubCommand(service));
- addSubCommand(new TeamTopSubCommand(service));
- addSubCommand(new TeamSetSpawnSubCommand(service));
+ addSubCommand(new TeamCreateSubCommand(service, messages));
+ addSubCommand(new TeamDeleteSubCommand(service, messages));
+ addSubCommand(new TeamAddSubCommand(service, messages));
+ addSubCommand(new TeamRemoveSubCommand(service, messages));
+ addSubCommand(new TeamJoinSubCommand(service, messages));
+ addSubCommand(new TeamLeaveSubCommand(service, messages));
+ addSubCommand(new TeamInfoSubCommand(service, messages));
+ addSubCommand(new TeamListSubCommand(service, messages));
+ addSubCommand(new TeamTransferSubCommand(service, messages));
+ addSubCommand(new TeamSetLeaderSubCommand(service, messages));
+ addSubCommand(new TeamVisibilitySubCommand(service, messages));
+ addSubCommand(new TeamScoreSubCommand(service, messages));
+ addSubCommand(new TeamTopSubCommand(service, messages));
+ addSubCommand(new TeamSetSpawnSubCommand(service, messages));
}
}
diff --git a/src/main/java/fr/luc/crcore/command/builtin/team/TeamInfoSubCommand.java b/src/main/java/fr/luc/crcore/command/builtin/team/TeamInfoSubCommand.java
index 773a482..80b2629 100644
--- a/src/main/java/fr/luc/crcore/command/builtin/team/TeamInfoSubCommand.java
+++ b/src/main/java/fr/luc/crcore/command/builtin/team/TeamInfoSubCommand.java
@@ -3,29 +3,25 @@ package fr.luc.crcore.command.builtin.team;
import fr.luc.crcore.command.CommandContext;
import fr.luc.crcore.command.CommandResult;
import fr.luc.crcore.command.SubCommand;
+import fr.luc.crcore.message.MessagesService;
import fr.luc.crcore.team.Team;
-import fr.luc.crcore.team.TeamMember;
import fr.luc.crcore.team.TeamService;
import org.bukkit.Bukkit;
-import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import java.util.Objects;
import java.util.stream.Collectors;
-/**
- * {@code /core team info [name]}
- *
- * Affiche les infos d'une équipe. Si aucun nom n'est donné, affiche celle
- * de l'exécutant.
- */
+/** {@code /core team info [team]} — joueur, infos d'une équipe (ou la sienne). */
public class TeamInfoSubCommand extends SubCommand {
protected final TeamService service;
+ protected final MessagesService messages;
- public TeamInfoSubCommand(TeamService service) {
+ public TeamInfoSubCommand(TeamService service, MessagesService messages) {
super("info");
this.service = Objects.requireNonNull(service, "service");
+ this.messages = Objects.requireNonNull(messages, "messages");
description("Afficher les infos d'une équipe");
permission("crcore.team.info");
optionalArgument("name", TeamArgumentTypes.teamByName(service));
@@ -42,31 +38,36 @@ public class TeamInfoSubCommand extends SubCommand {
});
if (team == null) {
- return CommandResult.failure("Aucune équipe spécifiée et vous n'êtes pas dans une équipe.");
+ return CommandResult.failure(messages.get("team.info.not-in-team"));
}
+ String colorCode = team.getColor().getChatColor().toString();
StringBuilder sb = new StringBuilder();
- ChatColor c = team.getColor().getChatColor();
- sb.append(c).append("=== ").append(team.getName())
- .append(" [#").append(team.getTag()).append("] ===\n");
- sb.append(ChatColor.GRAY).append("Couleur : ").append(c).append(team.getColor().getDisplayName()).append('\n');
- sb.append(ChatColor.GRAY).append("Visibilité : ").append(ChatColor.WHITE).append(team.getVisibility()).append('\n');
- sb.append(ChatColor.GRAY).append("Membres (").append(team.size()).append(") : ").append(ChatColor.WHITE);
- sb.append(team.getMembers().stream()
- .map(m -> Bukkit.getOfflinePlayer(m.getPlayerId()).getName() +
- (m.isLeader() ? "★" : ""))
- .collect(Collectors.joining(", ")));
+ sb.append(messages.get("team.info.header",
+ "color", colorCode, "name", team.getName(), "tag", team.getTag()));
+ sb.append('\n').append(messages.get("team.info.color",
+ "color", colorCode, "color_name", team.getColor().getDisplayName()));
+ sb.append('\n').append(messages.get("team.info.visibility",
+ "visibility", team.getVisibility().name()));
+ String memberList = team.getMembers().stream()
+ .map(m -> {
+ String name = Bukkit.getOfflinePlayer(m.getPlayerId()).getName();
+ return (name != null ? name : m.getPlayerId().toString()) + (m.isLeader() ? "★" : "");
+ })
+ .collect(Collectors.joining(", "));
+ sb.append('\n').append(messages.get("team.info.members",
+ "count", String.valueOf(team.size()), "list", memberList));
if (!team.getScores().isEmpty()) {
- sb.append('\n').append(ChatColor.GRAY).append("Scores : ").append(ChatColor.WHITE)
- .append(team.getScores().entrySet().stream()
- .map(e -> e.getKey() + "=" + e.getValue())
- .collect(Collectors.joining(", ")));
- }
- if (team.hasSpawnPoint()) {
- team.getSpawnPoint().ifPresent(loc -> sb.append('\n').append(ChatColor.GRAY).append("Spawn : ")
- .append(ChatColor.WHITE).append(loc.getWorld().getName()).append(' ')
- .append((int) loc.getX()).append('/').append((int) loc.getY()).append('/').append((int) loc.getZ()));
+ String scoreList = team.getScores().entrySet().stream()
+ .map(e -> e.getKey() + "=" + e.getValue())
+ .collect(Collectors.joining(", "));
+ sb.append('\n').append(messages.get("team.info.scores", "list", scoreList));
}
+ team.getSpawnPoint().ifPresent(loc -> sb.append('\n').append(messages.get("team.info.spawn",
+ "world", loc.getWorld().getName(),
+ "x", String.valueOf((int) loc.getX()),
+ "y", String.valueOf((int) loc.getY()),
+ "z", String.valueOf((int) loc.getZ()))));
ctx.reply(sb.toString());
return CommandResult.success();
}
diff --git a/src/main/java/fr/luc/crcore/command/builtin/team/TeamJoinSubCommand.java b/src/main/java/fr/luc/crcore/command/builtin/team/TeamJoinSubCommand.java
index f0a0493..27a1cef 100644
--- a/src/main/java/fr/luc/crcore/command/builtin/team/TeamJoinSubCommand.java
+++ b/src/main/java/fr/luc/crcore/command/builtin/team/TeamJoinSubCommand.java
@@ -3,6 +3,7 @@ package fr.luc.crcore.command.builtin.team;
import fr.luc.crcore.command.CommandContext;
import fr.luc.crcore.command.CommandResult;
import fr.luc.crcore.command.SubCommand;
+import fr.luc.crcore.message.MessagesService;
import fr.luc.crcore.team.Team;
import fr.luc.crcore.team.TeamException;
import fr.luc.crcore.team.TeamService;
@@ -10,20 +11,16 @@ import org.bukkit.entity.Player;
import java.util.Objects;
-/**
- * {@code /core team join Auto-join sur une équipe PUBLIC. Lève une {@link TeamException} si la
- * team est privée ou si le joueur est déjà dans une équipe (rendu en
- * message d'erreur).
- */
+/** {@code /core team join Le joueur quitte volontairement son équipe. Refusé pour le chef (il doit
- * d'abord transférer le leadership ou dissoudre l'équipe).
- */
+/** {@code /core team leave} — joueur, refuse pour le chef. */
public class TeamLeaveSubCommand extends SubCommand {
protected final TeamService service;
+ protected final MessagesService messages;
- public TeamLeaveSubCommand(TeamService service) {
+ public TeamLeaveSubCommand(TeamService service, MessagesService messages) {
super("leave");
this.service = Objects.requireNonNull(service, "service");
+ this.messages = Objects.requireNonNull(messages, "messages");
description("Quitter son équipe");
permission("crcore.team.leave");
playerOnly();
@@ -32,13 +30,12 @@ public class TeamLeaveSubCommand extends SubCommand {
Player player = ctx.requirePlayer();
Team team = service.getTeamOfPlayer(player.getUniqueId()).orElse(null);
if (team == null) {
- return CommandResult.failure("Vous n'appartenez à aucune équipe.");
+ return CommandResult.failure(messages.get("team.not-in-team"));
}
if (team.isLeader(player.getUniqueId())) {
- return CommandResult.failure(
- "Vous êtes le chef. Demandez à un admin de réassigner le leadership (/core team setleader) ou de dissoudre l'équipe.");
+ return CommandResult.failure(messages.get("team.leave.is-leader"));
}
service.removeMember(team.getId(), player.getUniqueId());
- return CommandResult.success("Vous avez quitté l'équipe " + team.getName() + ".");
+ return CommandResult.success(messages.get("team.leave.success", "name", team.getName()));
}
}
diff --git a/src/main/java/fr/luc/crcore/command/builtin/team/TeamListSubCommand.java b/src/main/java/fr/luc/crcore/command/builtin/team/TeamListSubCommand.java
index 611a708..8200636 100644
--- a/src/main/java/fr/luc/crcore/command/builtin/team/TeamListSubCommand.java
+++ b/src/main/java/fr/luc/crcore/command/builtin/team/TeamListSubCommand.java
@@ -3,26 +3,23 @@ package fr.luc.crcore.command.builtin.team;
import fr.luc.crcore.command.CommandContext;
import fr.luc.crcore.command.CommandResult;
import fr.luc.crcore.command.SubCommand;
+import fr.luc.crcore.message.MessagesService;
import fr.luc.crcore.team.Team;
import fr.luc.crcore.team.TeamService;
-import org.bukkit.ChatColor;
import java.util.Collection;
import java.util.Objects;
-/**
- * {@code /core team list}
- *
- * Affiche toutes les équipes existantes avec leur tag, nom, taille et
- * visibilité.
- */
+/** {@code /core team list} — joueur, liste toutes les équipes. */
public class TeamListSubCommand extends SubCommand {
protected final TeamService service;
+ protected final MessagesService messages;
- public TeamListSubCommand(TeamService service) {
+ public TeamListSubCommand(TeamService service, MessagesService messages) {
super("list");
this.service = Objects.requireNonNull(service, "service");
+ this.messages = Objects.requireNonNull(messages, "messages");
description("Lister toutes les équipes");
permission("crcore.team.list");
}
@@ -31,16 +28,17 @@ public class TeamListSubCommand extends SubCommand {
public CommandResult execute(CommandContext ctx) {
Collection Admin uniquement. Retire un joueur (online ou offline) de
- * l'équipe spécifiée. Refuse si le joueur ciblé est le chef — l'admin doit
- * d'abord transférer ou réassigner via {@code setleader}.
- */
+/** {@code /core team remove Commande admin pour ajuster les scores d'une équipe à la main (debug,
- * fix, init). Le gameplay normal pilote les scores via le service, pas via
- * cette commande.
- *
- * Restreinte par défaut à la permission {@code crcore.team.score.modify}.
- */
+/** {@code /core team score Admin uniquement. Assigne un joueur comme chef d'une équipe :
- * L'évènement {@code TeamLeadershipTransferEvent} est tiré dans tous les
- * cas (avec {@code oldLeaderId} vide si la team était leaderless).
- */
+/** {@code /core team setleader Admin uniquement, player-only. Définit le point de spawn de
- * l'équipe spécifiée à la position courante de l'admin (où il/elle se trouve).
- * Doit être lancé en jeu — la console n'a pas de location.
- */
+/** {@code /core team setspawn Affiche le classement des équipes. Sans argument, classement global
- * (somme de tous les scores). Avec un nom de score, classement sur ce score
- * précis.
- */
+/** {@code /core team top [score]} — joueur, classement (par score précis ou global). */
public class TeamTopSubCommand extends SubCommand {
protected final TeamService service;
+ protected final MessagesService messages;
protected final int limit;
- public TeamTopSubCommand(TeamService service) {
- this(service, 10);
+ public TeamTopSubCommand(TeamService service, MessagesService messages) {
+ this(service, messages, 10);
}
- public TeamTopSubCommand(TeamService service, int limit) {
+ public TeamTopSubCommand(TeamService service, MessagesService messages, int limit) {
super("top");
this.service = Objects.requireNonNull(service, "service");
+ this.messages = Objects.requireNonNull(messages, "messages");
this.limit = limit;
description("Classement des équipes");
permission("crcore.team.top");
@@ -44,14 +40,18 @@ public class TeamTopSubCommand extends SubCommand {
: service.getTopRankingByScore(scoreName, limit);
if (ranking.isEmpty()) {
- return CommandResult.success("Aucune équipe à classer.");
+ return CommandResult.success(messages.get("team.top.empty"));
}
- StringBuilder sb = new StringBuilder(ChatColor.YELLOW + "Top " + ranking.size() +
- (scoreName == null ? " (global) :" : " (" + scoreName + ") :"));
+ StringBuilder sb = new StringBuilder(scoreName == null
+ ? messages.get("team.top.header-global", "count", String.valueOf(ranking.size()))
+ : messages.get("team.top.header-score",
+ "count", String.valueOf(ranking.size()), "score", scoreName));
for (TeamRanking r : ranking) {
- sb.append('\n').append(ChatColor.GRAY).append(" ").append(r.rank()).append(". ")
- .append(r.team().getColor().getChatColor()).append(r.team().getName())
- .append(ChatColor.GRAY).append(" — ").append(ChatColor.WHITE).append(r.score());
+ sb.append('\n').append(messages.get("team.top.entry",
+ "rank", String.valueOf(r.rank()),
+ "color", r.team().getColor().getChatColor().toString(),
+ "name", r.team().getName(),
+ "value", String.valueOf(r.score())));
}
ctx.reply(sb.toString());
return CommandResult.success();
diff --git a/src/main/java/fr/luc/crcore/command/builtin/team/TeamTransferSubCommand.java b/src/main/java/fr/luc/crcore/command/builtin/team/TeamTransferSubCommand.java
index cb6c43e..37e2222 100644
--- a/src/main/java/fr/luc/crcore/command/builtin/team/TeamTransferSubCommand.java
+++ b/src/main/java/fr/luc/crcore/command/builtin/team/TeamTransferSubCommand.java
@@ -4,6 +4,7 @@ import fr.luc.crcore.command.ArgumentTypes;
import fr.luc.crcore.command.CommandContext;
import fr.luc.crcore.command.CommandResult;
import fr.luc.crcore.command.SubCommand;
+import fr.luc.crcore.message.MessagesService;
import fr.luc.crcore.team.Team;
import fr.luc.crcore.team.TeamService;
import org.bukkit.Bukkit;
@@ -11,24 +12,16 @@ import org.bukkit.OfflinePlayer;
import java.util.Objects;
-/**
- * {@code /core team transfer Admin uniquement. Transfère le rôle de chef à un membre existant
- * de l'équipe. Strict : le joueur cible doit déjà être membre, et
- * l'équipe doit avoir un chef actuel.
- *
- * Pour un cas plus permissif (assigner un chef sur une équipe leaderless,
- * ou auto-ajouter un non-membre comme chef), utiliser
- * {@code /core team setleader}.
- */
+/** {@code /core team transfer Admin uniquement. Change la visibilité d'une équipe. PUBLIC permet
- * aux joueurs de la rejoindre avec {@code /core team join}.
- */
+/** {@code /core team visibility Constructeur auto-orchestré : charge les defaults CR-Core en mémoire,
+ * crée le fichier utilisateur à partir d'un template (bundlé par le plugin
+ * de jeu sous le même nom, ou défaut CR-Core), et le charge en couche
+ * d'override.
+ *
+ * Voir {@link MessagesService} pour le détail du modèle.
+ */
+public class YamlMessagesService implements MessagesService {
+
+ /** Nom de la ressource embarquée dans le jar CR-Core (et shadée dans le plugin de jeu). */
+ private static final String CRCORE_DEFAULTS_RESOURCE = "crcore-messages.yml";
+
+ private static final String MISSING_PREFIX = "[missing: ";
+ private static final String MISSING_SUFFIX = "]";
+
+ private final JavaPlugin plugin;
+ private final Logger logger;
+ /** Defaults immuables chargés depuis la ressource CR-Core (lookup en dernier recours). */
+ private final MapOverride
* Pour remplacer un groupe entier :
* {@code
- * core.getCoreCommand().replaceSubCommand("team", new MyTeamGroup(svc));
+ * core.getCoreCommand().replaceSubCommand("team", new MyTeamGroup(svc, msgs));
* }
* Pour remplacer une feuille :
* {@code
* core.getCoreCommand().findSubCommand("team")
- * .ifPresent(t -> t.replaceSubCommand("create", new MyCreate(svc)));
+ * .ifPresent(t -> t.replaceSubCommand("create", new MyCreate(svc, msgs)));
* }
*/
public class CoreCommand extends BaseCommand {
protected final TeamService teamService;
protected final PlayerProfileService playerProfileService;
+ protected final MessagesService messages;
- public CoreCommand(TeamService teamService, PlayerProfileService playerProfileService) {
+ public CoreCommand(TeamService teamService,
+ PlayerProfileService playerProfileService,
+ MessagesService messages) {
super("core");
this.teamService = Objects.requireNonNull(teamService, "teamService");
this.playerProfileService = Objects.requireNonNull(playerProfileService, "playerProfileService");
+ this.messages = Objects.requireNonNull(messages, "messages");
description("Commandes du noyau CR-Core");
registerDefaults();
}
/** Enregistre les groupes par défaut. Override pour ajouter / retirer des groupes. */
protected void registerDefaults() {
- addSubCommand(new TeamGroupSubCommand(teamService));
- // Futur : addSubCommand(new PlayerGroupSubCommand(playerProfileService));
+ addSubCommand(new TeamGroupSubCommand(teamService, messages));
+ }
+
+ /**
+ * Override de {@link BaseCommand#handleResult} pour utiliser
+ * {@link MessagesService} sur les cas génériques (no-permission,
+ * player-only, etc.) au lieu des strings hardcodés du framework.
+ */
+ @Override
+ protected void handleResult(CommandSender sender, CommandResult result) {
+ switch (result.getType()) {
+ case SUCCESS:
+ if (result.getMessage() != null) {
+ sender.sendMessage(result.getMessage());
+ }
+ break;
+ case FAILURE:
+ sender.sendMessage(result.getMessage() != null
+ ? result.getMessage()
+ : messages.get("common.failure"));
+ break;
+ case INVALID_USAGE:
+ sender.sendMessage(result.getMessage() != null
+ ? result.getMessage()
+ : messages.get("common.invalid-usage"));
+ break;
+ case NO_PERMISSION:
+ sender.sendMessage(messages.get("common.no-permission"));
+ break;
+ case PLAYER_ONLY:
+ sender.sendMessage(messages.get("common.player-only"));
+ break;
+ }
}
}
diff --git a/src/main/java/fr/luc/crcore/command/builtin/team/TeamAddSubCommand.java b/src/main/java/fr/luc/crcore/command/builtin/team/TeamAddSubCommand.java
index c7cad31..9e669a0 100644
--- a/src/main/java/fr/luc/crcore/command/builtin/team/TeamAddSubCommand.java
+++ b/src/main/java/fr/luc/crcore/command/builtin/team/TeamAddSubCommand.java
@@ -4,25 +4,23 @@ import fr.luc.crcore.command.ArgumentTypes;
import fr.luc.crcore.command.CommandContext;
import fr.luc.crcore.command.CommandResult;
import fr.luc.crcore.command.SubCommand;
+import fr.luc.crcore.message.MessagesService;
import fr.luc.crcore.team.Team;
import fr.luc.crcore.team.TeamService;
import org.bukkit.entity.Player;
import java.util.Objects;
-/**
- * {@code /core team add
- *
+ * {@code
* core.getCoreCommand().findSubCommand("team")
- * .ifPresent(team -> team.replaceSubCommand("create", new MyCustomCreate(svc)));
+ * .ifPresent(team -> team.replaceSubCommand("create", new MyCustomCreate(svc, msgs)));
* }
- *
- *
- *
- *
- * Modèle de chargement
+ *
+ * Une instance par {@code CRCore}. Au boot, deux couches sont chargées dans
+ * cet ordre (la deuxième écrase la première sur les mêmes clés) :
+ *
+ *
+ *
+ *
+ * Substitution de placeholders
+ *
+ * Format {@code {name}}, substitution via varargs paire-par-paire :
+ * {@code
+ * core.messages().get("team.create.success",
+ * "name", team.getName(),
+ * "tag", team.getTag(),
+ * "visibility", team.getVisibility());
+ * }
+ *
+ * Codes couleur
+ *
+ * Les {@code &a}, {@code &c}, … sont traduits automatiquement en {@code §a},
+ * {@code §c}, … (toggle via {@link #setApplyColorCodes(boolean)}).
+ *
+ * Clés manquantes
+ *
+ * Si une clé n'existe ni dans le fichier user ni dans les defaults,
+ * {@link #get} renvoie {@code "[missing: key]"} pour faciliter le debug.
+ */
+public interface MessagesService {
+
+ /**
+ * Récupère un message formaté.
+ *
+ * @param key clé du message (ex. {@code "team.create.success"})
+ * @param placeholderPairs paires {@code (nom, valeur, nom, valeur, …)}.
+ * Le nombre d'éléments DOIT être pair.
+ */
+ String get(String key, Object... placeholderPairs);
+
+ /** Template brut sans substitution ni traduction couleur. */
+ String raw(String key);
+
+ /** {@code true} si la clé existe (dans le fichier user ou dans les defaults). */
+ boolean has(String key);
+
+ /**
+ * Définit / écrase un message ponctuellement en mémoire. Utile pour des
+ * messages dynamiques ou injectés par un plugin de jeu. NON persisté
+ * dans le fichier user.
+ */
+ void set(String key, String template);
+
+ /**
+ * Recharge le fichier utilisateur depuis le disque. Les defaults CR-Core
+ * restent en mémoire (pas re-chargés depuis le jar — ils ne bougent pas).
+ */
+ void reload();
+
+ /**
+ * Charge un fichier YAML supplémentaire en plus du fichier user principal.
+ * Cas d'usage : un plugin de jeu qui veut séparer ses messages en plusieurs
+ * fichiers (ex. un par module). Le fichier est résolu dans le dataFolder
+ * du plugin et copié depuis ses ressources s'il n'existe pas encore.
+ */
+ void loadAdditional(String resourceName);
+
+ void setApplyColorCodes(boolean enabled);
+
+ boolean isApplyColorCodes();
+
+ /** Chemin du fichier user principal (informationnel). */
+ File getUserFile();
+}
diff --git a/src/main/java/fr/luc/crcore/message/YamlMessagesService.java b/src/main/java/fr/luc/crcore/message/YamlMessagesService.java
new file mode 100644
index 0000000..6b0d6c3
--- /dev/null
+++ b/src/main/java/fr/luc/crcore/message/YamlMessagesService.java
@@ -0,0 +1,235 @@
+package fr.luc.crcore.message;
+
+import org.bukkit.ChatColor;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Implémentation par défaut de {@link MessagesService}, basée sur les
+ * {@link YamlConfiguration} de Bukkit.
+ *
+ *
+ *
+ */
+ private void initialize() {
+ loadDefaultsFromResource();
+ ensureUserFile();
+ rebuildEffectiveMessages();
+ }
+
+ private void loadDefaultsFromResource() {
+ try (InputStream is = plugin.getResource(CRCORE_DEFAULTS_RESOURCE)) {
+ if (is == null) {
+ logger.warning("Ressource " + CRCORE_DEFAULTS_RESOURCE
+ + " introuvable dans le jar — defaults CR-Core indisponibles.");
+ return;
+ }
+ YamlConfiguration cfg = YamlConfiguration.loadConfiguration(
+ new InputStreamReader(is, StandardCharsets.UTF_8));
+ flatten(cfg, "", defaults);
+ } catch (IOException ex) {
+ logger.log(Level.WARNING, "Échec lecture defaults CR-Core", ex);
+ }
+ }
+
+ private void ensureUserFile() {
+ if (userFile.exists()) return;
+ if (!plugin.getDataFolder().exists() && !plugin.getDataFolder().mkdirs()) {
+ logger.warning("Impossible de créer " + plugin.getDataFolder());
+ return;
+ }
+ // Priorité 1 : ressource du plugin de jeu sous le même nom (le plugin
+ // bundle son propre template avec ses overrides + ses messages perso).
+ try (InputStream pluginResource = plugin.getResource(userFileName)) {
+ if (pluginResource != null) {
+ copyStreamToFile(pluginResource, userFile);
+ logger.info("Fichier messages créé depuis le template du plugin : " + userFileName);
+ return;
+ }
+ } catch (IOException ex) {
+ logger.log(Level.WARNING, "Échec copie du template plugin", ex);
+ }
+ // Priorité 2 : copie les defaults CR-Core comme starter.
+ try (InputStream coreResource = plugin.getResource(CRCORE_DEFAULTS_RESOURCE)) {
+ if (coreResource != null) {
+ copyStreamToFile(coreResource, userFile);
+ logger.info("Fichier messages créé depuis les defaults CR-Core : " + userFileName);
+ }
+ } catch (IOException ex) {
+ logger.log(Level.WARNING, "Échec copie des defaults CR-Core", ex);
+ }
+ }
+
+ private static void copyStreamToFile(InputStream in, File target) throws IOException {
+ try (FileOutputStream out = new FileOutputStream(target)) {
+ in.transferTo(out);
+ }
+ }
+
+ /** Recompose la map effective : defaults d'abord, fichier user par-dessus. */
+ private void rebuildEffectiveMessages() {
+ messages.clear();
+ messages.putAll(defaults);
+ if (userFile.exists()) {
+ try {
+ YamlConfiguration cfg = YamlConfiguration.loadConfiguration(userFile);
+ flatten(cfg, "", messages);
+ } catch (Exception ex) {
+ logger.log(Level.WARNING, "Échec chargement " + userFile, ex);
+ }
+ }
+ }
+
+ // ---- MessagesService API ----
+
+ @Override
+ public String get(String key, Object... placeholderPairs) {
+ Objects.requireNonNull(key, "key");
+ String template = raw(key);
+ if (placeholderPairs != null && placeholderPairs.length > 0) {
+ if (placeholderPairs.length % 2 != 0) {
+ throw new IllegalArgumentException(
+ "placeholderPairs must have even length, got " + placeholderPairs.length);
+ }
+ for (int i = 0; i < placeholderPairs.length; i += 2) {
+ String placeholderKey = String.valueOf(placeholderPairs[i]);
+ String placeholderValue = String.valueOf(placeholderPairs[i + 1]);
+ template = template.replace("{" + placeholderKey + "}", placeholderValue);
+ }
+ }
+ return applyColorCodes
+ ? ChatColor.translateAlternateColorCodes('&', template)
+ : template;
+ }
+
+ @Override
+ public String raw(String key) {
+ Objects.requireNonNull(key, "key");
+ String template = messages.get(key);
+ return template != null ? template : MISSING_PREFIX + key + MISSING_SUFFIX;
+ }
+
+ @Override
+ public boolean has(String key) {
+ return messages.containsKey(key);
+ }
+
+ @Override
+ public void set(String key, String template) {
+ Objects.requireNonNull(key, "key");
+ Objects.requireNonNull(template, "template");
+ messages.put(key, template);
+ }
+
+ @Override
+ public void reload() {
+ rebuildEffectiveMessages();
+ }
+
+ @Override
+ public void loadAdditional(String resourceName) {
+ Objects.requireNonNull(resourceName, "resourceName");
+ File extraFile = new File(plugin.getDataFolder(), resourceName);
+ if (!extraFile.exists()) {
+ try (InputStream is = plugin.getResource(resourceName)) {
+ if (is == null) {
+ logger.warning("Ressource additionnelle " + resourceName + " introuvable.");
+ return;
+ }
+ if (!plugin.getDataFolder().exists()) Files.createDirectories(plugin.getDataFolder().toPath());
+ copyStreamToFile(is, extraFile);
+ } catch (IOException ex) {
+ logger.log(Level.WARNING, "Échec copie de " + resourceName, ex);
+ return;
+ }
+ }
+ try {
+ YamlConfiguration cfg = YamlConfiguration.loadConfiguration(extraFile);
+ flatten(cfg, "", messages);
+ } catch (Exception ex) {
+ logger.log(Level.WARNING, "Échec chargement " + extraFile, ex);
+ }
+ }
+
+ @Override
+ public void setApplyColorCodes(boolean enabled) {
+ this.applyColorCodes = enabled;
+ }
+
+ @Override
+ public boolean isApplyColorCodes() {
+ return applyColorCodes;
+ }
+
+ @Override
+ public File getUserFile() {
+ return userFile;
+ }
+
+ /** Parcourt récursivement une section et pousse chaque feuille en clé plate. */
+ private static void flatten(ConfigurationSection section, String prefix, Map