feat: admin/chef/player permission model + leaderless teams + setleader

Commands:
- Drop all short aliases (c/i/t/j/vis/disband/...) — long names only.
- Every /core team <action> now has a crcore.team.<action> permission.
- Three-tier model:
  * Admin (perm only): create, delete, setleader, score
  * Chef (perm + chef-check in execute): add, remove, transfer, visibility, setspawn
  * Player (perm): join, leave, info, list, top
- delete now takes <team> as argument (admin); no more chef-disband shortcut.

New /core team setleader <team> <player>:
- Admin assigns or replaces a team's leader.
- More permissive than transfer: target may not yet be a member (auto-add),
  and works on leaderless teams.

Leaderless teams:
- Team.leaderId is now nullable.
- getLeaderId() and getLeader() return Optional<...>.
- hasLeader() and isLeader(UUID) helpers added.
- New constructors Team(id, name, tag, color [, visibility]) for leaderless.
- TeamService.createTeam overloads without leaderId.
- TeamService.setLeader(teamId, playerId): assigns/replaces leader (auto-adds
  as member if needed). Fires TeamLeadershipTransferEvent with optional old.
- TeamLeadershipTransferEvent.getOldLeaderId() returns Optional<UUID>.
- SqliteTeamRepository: leader_id column no longer NOT NULL.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Antone Barbaud
2026-06-09 14:56:07 +02:00
parent 5bd6e227d3
commit 002fefdc02
24 changed files with 472 additions and 138 deletions
+52 -38
View File
@@ -30,7 +30,7 @@ Architecture des domaines :
| `name` | `String` | Nom lisible. Unique (case-insensitive). |
| `tag` | `String` | Tag court (le « # »). Unique. Affiché entre `[# … ]`. |
| `color` | `TeamColor` | Couleur associée (enum). |
| `leaderId` | `UUID` | Identifiant du joueur **chef d'équipe**. |
| `leaderId` | `UUID` *(nullable)* | Identifiant du joueur **chef d'équipe**. Peut être `null` (équipe leaderless — typique après création par admin). |
| `visibility` | `TeamVisibility` | `PUBLIC` (les joueurs peuvent rejoindre) ou `PRIVATE` (seul le chef ajoute). Défaut : `PRIVATE`. |
| `members` | `Set<TeamMember>` | Ensemble des membres (inclut le chef). |
@@ -64,13 +64,16 @@ Architecture des domaines :
| Opération | Description |
|---|---|
| `createTeam(name, tag, color, leaderId)` | Crée une équipe `PRIVATE` avec ce joueur comme chef. Échoue si nom/tag/joueur déjà pris. |
| `createTeam(name, tag, color, leaderId, visibility)` | Surcharge : permet de créer directement en `PUBLIC` ou `PRIVATE`. |
| `createTeam(name, tag, color)` | Crée une équipe **leaderless** en `PRIVATE`. |
| `createTeam(name, tag, color, visibility)` | Crée une équipe **leaderless** avec la visibilité spécifiée. |
| `createTeam(name, tag, color, leaderId)` | Crée une équipe `PRIVATE` avec ce joueur comme chef. |
| `createTeam(name, tag, color, leaderId, visibility)` | Surcharge complète. `leaderId` peut être `null` (équivalent leaderless). |
| `dissolveTeam(teamId)` | Supprime l'équipe. |
| `addMember(teamId, playerId)` | **Action du chef** : ajoute un joueur comme `MEMBER` (marche en PUBLIC comme en PRIVATE). |
| `removeMember(teamId, playerId)` | Retire un joueur (interdit sur le chef). |
| `joinTeam(teamId, playerId)` | **Action du joueur** : auto-rejoindre une équipe `PUBLIC`. Lève `TeamAccessException` si la team est `PRIVATE` ou si le joueur est déjà dans une équipe. |
| `transferLeadership(teamId, newLeaderId)` | Le nouveau chef doit déjà être membre. |
| `transferLeadership(teamId, newLeaderId)` | Transfert chef→chef strict : le nouveau chef doit être membre, et la team doit avoir un chef actuel. |
| `setLeader(teamId, newLeaderId)` | Plus permissif : fonctionne sur team leaderless **et** auto-ajoute le joueur s'il n'est pas membre. C'est l'opération admin. |
| `setVisibility(teamId, visibility)` | Change la visibilité (typiquement appelée par le chef). |
| `addScore(teamId, name, delta)` / `setScore` / `getScore` / `resetScore` / `resetAllScores` | Gestion des scores (voir section Scores). |
| `getRankingByScore(name)` / `getGlobalRanking()` / `getTopRankingByScore(name, n)` / `getTopGlobalRanking(n)` | Classements (voir section Classements). |
@@ -326,54 +329,65 @@ Voir [setup.md](setup.md#utilisation-depuis-un-plugin-de-jeu).
## 4. Commandes built-in `/core team ...`
**Statut** : 13 sous-commandes prêtes à l'emploi, branchées par
**Statut** : 14 sous-commandes prêtes à l'emploi, branchées par
`CRCore.enable()`. Chaque sous-commande vit dans
`fr.luc.crcore.command.builtin.team` et est substituable individuellement par
sous-classe ou via `replaceSubCommand`.
**Pas d'aliases courts** : les commandes ont leur nom long uniquement
(`/core team create` et pas `/core team c`). Les aliases ont été retirés
pour réduire la friction de découverte et la confusion.
### Arborescence
```
/core (CoreCommand, BaseCommand racine, aliases: cr, crcore)
└── team (TeamGroupSubCommand, alias: t)
├── create <name> <tag> <color> [visibility] — créer une équipe
├── delete dissoudre son équipe (chef)
├── add <player> — chef ajoute un membre
├── remove <player> — chef retire un membre
├── join <name> — auto-join sur team PUBLIC
├── leave — quitter son équipe
├── info [name] — infos d'une équipe
├── list — liste toutes les équipes
├── transfer <player> — transfert de leadership
├── visibility <PUBLIC|PRIVATE> — changer la visibilité
├── score <team> <name> <add|set> <value> — [admin] modifier un score
├── top [score] — classement (par score ou global)
── setspawn — chef définit le spawn
/core (CoreCommand)
└── team (TeamGroupSubCommand)
├── create <name> <tag> <color> [leader] [admin] créer (chef optionnel)
├── delete <team> [admin] dissoudre une équipe
├── setleader <team> <player> [admin] (re)assigner le chef
├── score <team> <name> <add|set> <value> [admin] modifier un score
├── add <player> [chef] ajouter à son équipe
├── remove <player> [chef] retirer de son équipe
├── transfer <player> [chef] transférer leadership
├── visibility <PUBLIC|PRIVATE> [chef] changer visibilité
├── setspawn [chef] définir le spawn
├── join <team> [joueur] rejoindre PUBLIC
├── leave [joueur] quitter son équipe
├── info [team] [joueur] infos
── list [joueur] toutes les équipes
└── top [score] [joueur] classement
```
### Aliases supplémentaires (équivalents Bukkit)
### Permissions
| Sous-commande | Aliases |
|---|---|
| `create` | `c`, `new` |
| `delete` | `disband`, `dissolve` |
| `add` | `invite` |
| `remove` | `kick`, `expel` |
| `join` | `j` |
| `leave` | `quit` |
| `info` | `i` |
| `list` | `ls` |
| `visibility` | `vis` |
| `top` | `ranking`, `leaderboard` |
| `setspawn` | `spawn` |
Chaque sous-commande a une permission `crcore.team.<action>`. Modèle à 3 niveaux :
### Permissions par défaut
| Niveau | Commandes | Comportement |
|---|---|---|
| **Admin** | `create`, `delete`, `setleader`, `score` | Permission seule (pas de check chef). Cible une team via argument. |
| **Chef** | `add`, `remove`, `transfer`, `visibility`, `setspawn` | Permission **ET** check chef de sa propre équipe en plus. Cible la team de l'exécutant. |
| **Joueur** | `join`, `leave`, `info`, `list`, `top` | Permission seule (à granter par défaut côté LuckPerms si on veut que tout le monde y ait accès). |
| Sous-commande | Permission |
|---|---|
| `create` | `crcore.team.create` |
| `score` | `crcore.team.score.modify` (admin) |
| autres | aucune (gated par appartenance / rôle de chef) |
| `create` | `crcore.team.create` |
| `delete` | `crcore.team.delete` |
| `setleader` | `crcore.team.setleader` |
| `score` | `crcore.team.score` |
| `add` | `crcore.team.add` |
| `remove` | `crcore.team.remove` |
| `transfer` | `crcore.team.transfer` |
| `visibility` | `crcore.team.visibility` |
| `setspawn` | `crcore.team.setspawn` |
| `join` | `crcore.team.join` |
| `leave` | `crcore.team.leave` |
| `info` | `crcore.team.info` |
| `list` | `crcore.team.list` |
| `top` | `crcore.team.top` |
Le plugin de jeu ou le serveur configure les défauts via LuckPerms / Bukkit
permissions plugin.
### Override d'une sous-commande par défaut