feat: Interactive chat UI for rank permissions, contiguous claims rule, and cascade orphaned unclaim
This commit is contained in:
@@ -1005,23 +1005,41 @@ public class GuildCommands {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
// /guild rank setperm <id> <permission> <true/false>
|
// /guild rank setperm <id> [permission] [valeur]
|
||||||
.subcommand(BetterMcCommands.subBuilder("setperm")
|
.subcommand(BetterMcCommands.subBuilder("setperm")
|
||||||
.description("Modifier une permission pour un rang")
|
.description("Afficher ou modifier les permissions d'un rang avec boutons cliquables")
|
||||||
|
.aliases("perm", "perms", "permission", "permissions")
|
||||||
.playerOnly()
|
.playerOnly()
|
||||||
.argument(Arguments.string("id").description("ID du rang"))
|
.argument(Arguments.string("id").description("ID du rang"))
|
||||||
.argument(Arguments.enumOf("permission", GuildPermission.class).description("Permission"))
|
.argument(Arguments.enumOf("permission", GuildPermission.class).optional().description("Permission"))
|
||||||
.argument(Arguments.bool("valeur").description("true ou false"))
|
.argument(Arguments.bool("valeur").optional().description("true ou false"))
|
||||||
.executes(context -> {
|
.executes(context -> {
|
||||||
Player player = context.getPlayer();
|
Player player = context.getPlayer();
|
||||||
Guild guild = guildManager.getGuildByPlayer(player.getUniqueId());
|
Guild guild = guildManager.getGuildByPlayer(player.getUniqueId());
|
||||||
String id = context.getString("id");
|
String id = context.getString("id");
|
||||||
GuildPermission perm = context.get("permission", GuildPermission.class);
|
|
||||||
boolean state = context.getBoolean("valeur");
|
GuildRank rank = guild.getRank(id);
|
||||||
|
if (rank == null) {
|
||||||
|
context.reply(msg.get("guild.ranks.rank_not_found", "{id}", id));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<GuildPermission> permOpt = context.getOptional("permission", GuildPermission.class);
|
||||||
|
Optional<Boolean> valOpt = context.getOptional("valeur", Boolean.class);
|
||||||
|
|
||||||
|
if (permOpt.isEmpty() || valOpt.isEmpty()) {
|
||||||
|
// Afficher le menu interactif avec boutons cliquables
|
||||||
|
sendRankPermissionMenu(context, guild, rank);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GuildPermission perm = permOpt.get();
|
||||||
|
boolean state = valOpt.get();
|
||||||
|
|
||||||
GuildResult res = guildManager.setRankPermission(player, guild, id, perm, state);
|
GuildResult res = guildManager.setRankPermission(player, guild, id, perm, state);
|
||||||
if (res.isSuccess()) {
|
if (res.isSuccess()) {
|
||||||
context.replySuccess(res.getMessage());
|
context.reply(res.getMessage());
|
||||||
|
sendRankPermissionMenu(context, guild, rank);
|
||||||
} else {
|
} else {
|
||||||
context.replyError(res.getMessage());
|
context.replyError(res.getMessage());
|
||||||
}
|
}
|
||||||
@@ -1132,6 +1150,34 @@ public class GuildCommands {
|
|||||||
context.reply(footer);
|
context.reply(footer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Envoie le menu interactif de gestion des permissions d'un rang avec boutons cliquables OUI / NON.
|
||||||
|
*/
|
||||||
|
private void sendRankPermissionMenu(fr.luc.bettermccommands.api.CommandContext context, Guild guild, GuildRank rank) {
|
||||||
|
context.reply(msg.get("guild.ranks.perm_menu_header",
|
||||||
|
"{rank}", rank.getName(),
|
||||||
|
"{priority}", String.valueOf(rank.getPriority())
|
||||||
|
));
|
||||||
|
context.reply(msg.get("guild.ranks.perm_menu_hint"));
|
||||||
|
|
||||||
|
for (GuildPermission perm : GuildPermission.values()) {
|
||||||
|
boolean has = rank.hasPermission(perm);
|
||||||
|
if (has) {
|
||||||
|
context.reply(msg.get("guild.ranks.perm_entry_active",
|
||||||
|
"{desc}", perm.getDescription(),
|
||||||
|
"{perm}", perm.name(),
|
||||||
|
"{id}", rank.getId()
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
context.reply(msg.get("guild.ranks.perm_entry_inactive",
|
||||||
|
"{desc}", perm.getDescription(),
|
||||||
|
"{perm}", perm.name(),
|
||||||
|
"{id}", rank.getId()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class HelpEntry {
|
private static class HelpEntry {
|
||||||
final String usage;
|
final String usage;
|
||||||
final String description;
|
final String description;
|
||||||
|
|||||||
@@ -595,10 +595,16 @@ public class GuildManager {
|
|||||||
return GuildResult.failure("Votre rang ne vous autorise pas à revendiquer de nouveaux territoires.");
|
return GuildResult.failure("Votre rang ne vous autorise pas à revendiquer de nouveaux territoires.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Vérifier si le chunk est déjà claimé
|
||||||
if (isChunkClaimed(worldName, chunkX, chunkZ)) {
|
if (isChunkClaimed(worldName, chunkX, chunkZ)) {
|
||||||
return GuildResult.failure("Ce chunk est déjà revendiqué.");
|
return GuildResult.failure("Ce chunk est déjà revendiqué.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. bis Vérifier l'adjacence obligatoire si la guilde possède déjà des claims
|
||||||
|
if (guild.getClaimCount() > 0 && !isAdjacentToGuildClaims(guild, worldName, chunkX, chunkZ)) {
|
||||||
|
return GuildResult.failure(messageManager.get("guild.claim.must_be_adjacent"));
|
||||||
|
}
|
||||||
|
|
||||||
double price = priceCalculator.calculateNextChunkPrice(guild);
|
double price = priceCalculator.calculateNextChunkPrice(guild);
|
||||||
|
|
||||||
if (!guild.hasBalance(price)) {
|
if (!guild.hasBalance(price)) {
|
||||||
@@ -650,12 +656,124 @@ public class GuildManager {
|
|||||||
chunkGuildMap.remove(getChunkKey(worldName, chunkX, chunkZ));
|
chunkGuildMap.remove(getChunkKey(worldName, chunkX, chunkZ));
|
||||||
repository.deleteClaimAsync(worldName, chunkX, chunkZ);
|
repository.deleteClaimAsync(worldName, chunkX, chunkZ);
|
||||||
|
|
||||||
logAction(guild, player, "UNCLAIM", "Abandon du chunk (" + chunkX + ", " + chunkZ + ")");
|
// Nettoyage automatique en cascade des chunks orphelins
|
||||||
|
int orphansCount = removeOrphanedClaims(guild, worldName);
|
||||||
|
|
||||||
|
logAction(guild, player, "UNCLAIM", "Abandon du chunk (" + chunkX + ", " + chunkZ + ")" + (orphansCount > 0 ? " + " + orphansCount + " orphelin(s)" : ""));
|
||||||
broadcastToGuild(guild, "guild.broadcast.chunk_unclaimed", "{player}", player.getName(), "{chunk_x}", String.valueOf(chunkX), "{chunk_z}", String.valueOf(chunkZ));
|
broadcastToGuild(guild, "guild.broadcast.chunk_unclaimed", "{player}", player.getName(), "{chunk_x}", String.valueOf(chunkX), "{chunk_z}", String.valueOf(chunkZ));
|
||||||
|
|
||||||
|
if (orphansCount > 0) {
|
||||||
|
return GuildResult.success(messageManager.get("guild.claim.unclaim_cascade", "{count}", String.valueOf(orphansCount)));
|
||||||
|
}
|
||||||
|
|
||||||
return GuildResult.success("Le chunk a été libéré.");
|
return GuildResult.success("Le chunk a été libéré.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si les coordonnées de chunk données sont directement adjacentes (Nord, Sud, Est, Ouest)
|
||||||
|
* à au moins un chunk possédé par la guilde dans le même monde.
|
||||||
|
*
|
||||||
|
* @param guild La guilde concernée.
|
||||||
|
* @param worldName Le monde du claim.
|
||||||
|
* @param chunkX Coordonnée X du chunk.
|
||||||
|
* @param chunkZ Coordonnée Z du chunk.
|
||||||
|
* @return true si au moins un territoire adjacent appartient à la guilde.
|
||||||
|
*/
|
||||||
|
public boolean isAdjacentToGuildClaims(Guild guild, String worldName, int chunkX, int chunkZ) {
|
||||||
|
if (guild == null || guild.getClaimCount() == 0) return true;
|
||||||
|
|
||||||
|
for (GuildClaim c : guild.getClaims()) {
|
||||||
|
if (c.getWorldName().equalsIgnoreCase(worldName)) {
|
||||||
|
int dx = Math.abs(c.getChunkX() - chunkX);
|
||||||
|
int dz = Math.abs(c.getChunkZ() - chunkZ);
|
||||||
|
if (dx + dz == 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Détecte et libère automatiquement tous les territoires orphelins (qui ne sont plus rattachés
|
||||||
|
* au bloc principal / Home de la guilde suite à un unclaim).
|
||||||
|
*
|
||||||
|
* @param guild La guilde concernée.
|
||||||
|
* @param worldName Le monde où effectuer la vérification de contiguïté.
|
||||||
|
* @return Le nombre de territoires orphelins libérés en cascade.
|
||||||
|
*/
|
||||||
|
public int removeOrphanedClaims(Guild guild, String worldName) {
|
||||||
|
if (guild == null || guild.getClaimCount() == 0) return 0;
|
||||||
|
|
||||||
|
List<GuildClaim> worldClaims = guild.getClaims().stream()
|
||||||
|
.filter(c -> c.getWorldName().equalsIgnoreCase(worldName))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (worldClaims.size() <= 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
GuildClaim anchor = null;
|
||||||
|
if (guild.getHomeWorld() != null && guild.getHomeWorld().equalsIgnoreCase(worldName)) {
|
||||||
|
int homeChunkX = ((int) Math.floor(guild.getHomeX())) >> 4;
|
||||||
|
int homeChunkZ = ((int) Math.floor(guild.getHomeZ())) >> 4;
|
||||||
|
for (GuildClaim c : worldClaims) {
|
||||||
|
if (c.getChunkX() == homeChunkX && c.getChunkZ() == homeChunkZ) {
|
||||||
|
anchor = c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anchor == null) {
|
||||||
|
anchor = worldClaims.stream()
|
||||||
|
.min(Comparator.comparingLong(GuildClaim::getClaimedAt))
|
||||||
|
.orElse(worldClaims.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<GuildClaim> connected = new HashSet<>();
|
||||||
|
Queue<GuildClaim> queue = new ArrayDeque<>();
|
||||||
|
queue.add(anchor);
|
||||||
|
connected.add(anchor);
|
||||||
|
|
||||||
|
while (!queue.isEmpty()) {
|
||||||
|
GuildClaim curr = queue.poll();
|
||||||
|
for (GuildClaim candidate : worldClaims) {
|
||||||
|
if (!connected.contains(candidate)) {
|
||||||
|
int dx = Math.abs(candidate.getChunkX() - curr.getChunkX());
|
||||||
|
int dz = Math.abs(candidate.getChunkZ() - curr.getChunkZ());
|
||||||
|
if (dx + dz == 1) {
|
||||||
|
connected.add(candidate);
|
||||||
|
queue.add(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<GuildClaim> orphans = new ArrayList<>();
|
||||||
|
for (GuildClaim c : worldClaims) {
|
||||||
|
if (!connected.contains(c)) {
|
||||||
|
orphans.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orphans.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
org.bukkit.World world = org.bukkit.Bukkit.getWorld(worldName);
|
||||||
|
for (GuildClaim orphan : orphans) {
|
||||||
|
if (worldGuardManager != null && world != null) {
|
||||||
|
worldGuardManager.removeRegion(guild, world, orphan.getChunkX(), orphan.getChunkZ());
|
||||||
|
}
|
||||||
|
guild.removeClaim(worldName, orphan.getChunkX(), orphan.getChunkZ());
|
||||||
|
chunkGuildMap.remove(getChunkKey(worldName, orphan.getChunkX(), orphan.getChunkZ()));
|
||||||
|
repository.deleteClaimAsync(worldName, orphan.getChunkX(), orphan.getChunkZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
return orphans.size();
|
||||||
|
}
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// GESTION DE LA BANQUE
|
// GESTION DE LA BANQUE
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ guild:
|
|||||||
claim:
|
claim:
|
||||||
no_permission: "<red>Votre rang ne vous autorise pas à revendiquer des territoires.</red>"
|
no_permission: "<red>Votre rang ne vous autorise pas à revendiquer des territoires.</red>"
|
||||||
already_claimed: "<red>Ce chunk est déjà revendiqué.</red>"
|
already_claimed: "<red>Ce chunk est déjà revendiqué.</red>"
|
||||||
|
must_be_adjacent: "<red>Votre nouveau territoire doit être directement collé (adjacent) à un territoire déjà possédé par votre guilde.</red>"
|
||||||
insufficient_bank: "<red>La banque de guilde n'a pas assez d'argent (Requis : <yellow>{cost}</yellow>, Solde : <green>{balance}</green>).</red>"
|
insufficient_bank: "<red>La banque de guilde n'a pas assez d'argent (Requis : <yellow>{cost}</yellow>, Solde : <green>{balance}</green>).</red>"
|
||||||
success: "<green>Chunk revendiqué avec succès pour <yellow>{cost}</yellow> ! (Total : <aqua>{count}</aqua> chunks)</green>"
|
success: "<green>Chunk revendiqué avec succès pour <yellow>{cost}</yellow> ! (Total : <aqua>{count}</aqua> chunks)</green>"
|
||||||
unclaim_no_permission: "<red>Votre rang ne vous autorise pas à abandonner des territoires.</red>"
|
unclaim_no_permission: "<red>Votre rang ne vous autorise pas à abandonner des territoires.</red>"
|
||||||
@@ -67,6 +68,7 @@ guild:
|
|||||||
unclaim_last_chunk: "<red>Vous ne pouvez pas abandonner le dernier territoire de votre guilde.</red>"
|
unclaim_last_chunk: "<red>Vous ne pouvez pas abandonner le dernier territoire de votre guilde.</red>"
|
||||||
unclaim_confirm_prompt: "<red><bold>⚠ ATTENTION :</bold> Vous allez libérer le chunk <aqua>{world} ({chunk_x}, {chunk_z})</aqua>.\n<gold>Exécutez à nouveau <yellow>/guild unclaim</yellow> pour confirmer l'abandon du territoire.</gold></red>"
|
unclaim_confirm_prompt: "<red><bold>⚠ ATTENTION :</bold> Vous allez libérer le chunk <aqua>{world} ({chunk_x}, {chunk_z})</aqua>.\n<gold>Exécutez à nouveau <yellow>/guild unclaim</yellow> pour confirmer l'abandon du territoire.</gold></red>"
|
||||||
unclaim_success: "<yellow>Le chunk a été libéré.</yellow>"
|
unclaim_success: "<yellow>Le chunk a été libéré.</yellow>"
|
||||||
|
unclaim_cascade: "<yellow>Le chunk a été libéré. <gold>{count}</gold> territoire(s) orphelin(s) ont également été libérés automatiquement.</yellow>"
|
||||||
|
|
||||||
bank:
|
bank:
|
||||||
deposit_no_permission: "<red>Votre rang ne vous autorise pas à déposer dans la banque.</red>"
|
deposit_no_permission: "<red>Votre rang ne vous autorise pas à déposer dans la banque.</red>"
|
||||||
@@ -115,6 +117,10 @@ guild:
|
|||||||
created: "<green>Le rang <yellow>{name}</yellow> (ID: {id}, Priorité: {priority}) a été créé avec succès !</green>"
|
created: "<green>Le rang <yellow>{name}</yellow> (ID: {id}, Priorité: {priority}) a été créé avec succès !</green>"
|
||||||
deleted: "<yellow>Le rang <white>{name}</white> a été supprimé.</yellow>"
|
deleted: "<yellow>Le rang <white>{name}</white> a été supprimé.</yellow>"
|
||||||
perm_updated: "<green>Permission <yellow>{perm}</yellow> mise à <aqua>{state}</aqua> pour le rang <yellow>{rank}</yellow>.</green>"
|
perm_updated: "<green>Permission <yellow>{perm}</yellow> mise à <aqua>{state}</aqua> pour le rang <yellow>{rank}</yellow>.</green>"
|
||||||
|
perm_menu_header: "<gradient:#00C9FF:#92FE9D><bold>=== PERMISSIONS : {rank} (Priorité: {priority}) ===</bold></gradient>"
|
||||||
|
perm_menu_hint: "<gray>Cliquez sur <green>[ OUI ]</green> ou <red>[ NON ]</red> pour basculer instantanément chaque droit.</gray>"
|
||||||
|
perm_entry_active: "<gray>• {desc} <dark_gray>({perm})</dark_gray> :</gray>\n <green><bold>[ ✅ OUI ]</bold></green> <click:run_command:'/guild rank setperm {id} {perm} false'><hover:show_text:'<red>Cliquez pour DÉSACTIVER la permission</red>'><gray>[ NON ]</gray></hover></click>"
|
||||||
|
perm_entry_inactive: "<gray>• {desc} <dark_gray>({perm})</dark_gray> :</gray>\n <click:run_command:'/guild rank setperm {id} {perm} true'><hover:show_text:'<green>Cliquez pour ACTIVER la permission</green>'><gray>[ OUI ]</gray></hover></click> <red><bold>[ ❌ NON ]</bold></red>"
|
||||||
no_permission: "<red>Votre rang ne vous autorise pas à gérer les rangs de la guilde.</red>"
|
no_permission: "<red>Votre rang ne vous autorise pas à gérer les rangs de la guilde.</red>"
|
||||||
cannot_modify_leader: "<red>Vous ne pouvez pas modifier ou supprimer le rang de Chef.</red>"
|
cannot_modify_leader: "<red>Vous ne pouvez pas modifier ou supprimer le rang de Chef.</red>"
|
||||||
cannot_delete_default: "<red>Vous ne pouvez pas supprimer le rang par défaut des recrues.</red>"
|
cannot_delete_default: "<red>Vous ne pouvez pas supprimer le rang par défaut des recrues.</red>"
|
||||||
|
|||||||
Reference in New Issue
Block a user