feat: chef commands moved to admin + PlaceholderAPI integration
Chef → admin: the chef role no longer grants any command privilege. All team-management subcommands now take <team> as an argument and are gated by their crcore.team.<action> permission only: - add <team> <player> - remove <team> <player> - transfer <team> <player> - visibility <team> <PUBLIC|PRIVATE> - setspawn <team> (still player-only — needs admin's location) The LEADER role is kept in the data model (Team / TeamMember) and remains usable by game plugins via the API, but does not unlock any default command. Future work can re-introduce chef-specific commands if needed. PlaceholderAPI: auto-detected at CRCore.enable(). If the PAPI plugin is present on the server, CRCorePlaceholderExpansion registers automatically; otherwise the lib runs without it (no NoClassDefFoundError thanks to the indirection through doRegisterPlaceholderHook). Placeholders exposed: - Team: %crcore_team%, %crcore_team_name/tag/color/color_chat/size/ visibility/leader_name/total_score%, %crcore_team_score_<name>% - Player: %crcore_player_score_<name>%, %crcore_player_score_total% Dependency: me.clip:placeholderapi:2.11.6, scope provided. New repo: https://repo.extendedclip.com/content/repositories/placeholderapi/. docs/features.md, decisions.md and the builtin-commands diagram updated to reflect the simpler admin/player two-tier model and the PAPI section. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -367,6 +367,52 @@ Format léger : une décision = un titre + contexte + choix + raison.
|
|||||||
bases existantes, ALTER TABLE manuel ou suppression du fichier
|
bases existantes, ALTER TABLE manuel ou suppression du fichier
|
||||||
(les bases d'event sont jetables).
|
(les bases d'event sont jetables).
|
||||||
|
|
||||||
|
## 2026-06-09 — Toutes les commandes "chef" deviennent admin (révision)
|
||||||
|
|
||||||
|
- **Révision** de la décision "Refonte permissions + modèle admin/chef/joueur"
|
||||||
|
prise plus tôt aujourd'hui.
|
||||||
|
- **Choix** : le rôle chef n'apporte plus aucun privilège de commande pour
|
||||||
|
l'instant. Toutes les opérations de gestion d'équipe (`add`, `remove`,
|
||||||
|
`transfer`, `visibility`, `setspawn`) deviennent **admin** :
|
||||||
|
- Signature avec `<team>` en argument (au lieu d'implicite "ma team").
|
||||||
|
- Permission `crcore.team.<action>` requise.
|
||||||
|
- Plus de check `isLeader(...)` dans `execute()`.
|
||||||
|
- **Raison** : le user a explicitement décidé que pour l'instant le chef
|
||||||
|
n'a pas plus de privilèges qu'un joueur lambda côté commandes. Le rôle
|
||||||
|
`LEADER` reste dans le modèle de données (utile pour les game plugins qui
|
||||||
|
pourraient l'exploiter via l'API, ou pour de futures commandes), mais il
|
||||||
|
ne gate plus rien au niveau du framework de commandes.
|
||||||
|
- **Conséquences** :
|
||||||
|
- `TeamRemoveSubCommand` : refuse de retirer le chef (l'admin doit
|
||||||
|
`setleader` d'abord). Pas un check chef, juste une garde de cohérence.
|
||||||
|
- `TeamTransferSubCommand` : devient l'équivalent admin "strict" de
|
||||||
|
`setleader` (membre existant uniquement). Les deux cohabitent ; doc dit
|
||||||
|
quand préférer l'un ou l'autre.
|
||||||
|
- `TeamSetSpawnSubCommand` : reste `playerOnly` car nécessite la
|
||||||
|
`Location` de l'exécutant — mais c'est désormais l'admin qui se place
|
||||||
|
à l'endroit voulu et tape `/core team setspawn <team>`.
|
||||||
|
|
||||||
|
## 2026-06-09 — Intégration PlaceholderAPI (optionnelle, auto-détectée)
|
||||||
|
|
||||||
|
- **Choix** : `CRCore.enable()` détecte la présence du plugin PlaceholderAPI
|
||||||
|
via `pluginManager.getPlugin("PlaceholderAPI")` et enregistre
|
||||||
|
automatiquement `CRCorePlaceholderExpansion` si présent. Aucune action
|
||||||
|
requise côté plugin de jeu.
|
||||||
|
- **Dépendance Maven** : `me.clip:placeholderapi:2.11.6` en scope
|
||||||
|
`provided` (depuis `https://repo.extendedclip.com/...`). Le jar PAPI
|
||||||
|
n'est PAS embarqué — c'est un plugin runtime indépendant.
|
||||||
|
- **Indirection de chargement** : la méthode privée
|
||||||
|
`doRegisterPlaceholderHook()` isole la référence à
|
||||||
|
`CRCorePlaceholderExpansion`. Si PAPI est absent, la méthode n'est jamais
|
||||||
|
appelée et le bytecode référençant `me.clip.placeholderapi.*` n'est pas
|
||||||
|
vérifié → pas de `NoClassDefFoundError`.
|
||||||
|
- **Placeholders exposés** :
|
||||||
|
- Team : `%crcore_team%`, `%crcore_team_name/tag/color/color_chat/size/`
|
||||||
|
`visibility/leader_name/total_score%`, `%crcore_team_score_<name>%`
|
||||||
|
- Player : `%crcore_player_score_<name>%`, `%crcore_player_score_total%`
|
||||||
|
- **Override** : `CRCore.registerPlaceholderHook()` est `protected` — une
|
||||||
|
sous-classe peut ajouter des placeholders ou skipper la hook.
|
||||||
|
|
||||||
## 2026-06-09 — Nouvelle commande `/core team setleader`
|
## 2026-06-09 — Nouvelle commande `/core team setleader`
|
||||||
|
|
||||||
- **Choix** : ajout de `TeamSetLeaderSubCommand` (`/core team setleader
|
- **Choix** : ajout de `TeamSetLeaderSubCommand` (`/core team setleader
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@startuml builtin-commands-diagram
|
@startuml builtin-commands-diagram
|
||||||
title CR-Core — Default /core team commands (admin / chef / joueur)
|
title CR-Core — Default /core team commands (admin / joueur)
|
||||||
|
|
||||||
skinparam classAttributeIconSize 0
|
skinparam classAttributeIconSize 0
|
||||||
hide empty members
|
hide empty members
|
||||||
@@ -11,16 +11,13 @@ package "fr.luc.crcore.command" {
|
|||||||
|
|
||||||
package "fr.luc.crcore.command.builtin" {
|
package "fr.luc.crcore.command.builtin" {
|
||||||
|
|
||||||
class CoreCommand {
|
class CoreCommand
|
||||||
+ CoreCommand(teamSvc, playerSvc)
|
|
||||||
}
|
|
||||||
CoreCommand --|> BaseCommand
|
CoreCommand --|> BaseCommand
|
||||||
|
|
||||||
package "fr.luc.crcore.command.builtin.team" {
|
package "fr.luc.crcore.command.builtin.team" {
|
||||||
|
|
||||||
class TeamGroupSubCommand {
|
class TeamGroupSubCommand {
|
||||||
+ TeamGroupSubCommand(service)
|
+ TeamGroupSubCommand(service)
|
||||||
# registerDefaults(): void
|
|
||||||
}
|
}
|
||||||
TeamGroupSubCommand --|> SubCommand
|
TeamGroupSubCommand --|> SubCommand
|
||||||
|
|
||||||
@@ -28,7 +25,7 @@ package "fr.luc.crcore.command.builtin" {
|
|||||||
+ {static} teamByName(service): ArgumentType<Team>
|
+ {static} teamByName(service): ArgumentType<Team>
|
||||||
}
|
}
|
||||||
|
|
||||||
' === ADMIN commands (permission seule) ===
|
' ─── ADMIN commands (permission seule, team par argument) ───
|
||||||
package "admin" <<Rectangle>> {
|
package "admin" <<Rectangle>> {
|
||||||
class TeamCreateSubCommand {
|
class TeamCreateSubCommand {
|
||||||
perm: crcore.team.create
|
perm: crcore.team.create
|
||||||
@@ -46,32 +43,30 @@ package "fr.luc.crcore.command.builtin" {
|
|||||||
perm: crcore.team.score
|
perm: crcore.team.score
|
||||||
args: <team> <name> <add|set> <value>
|
args: <team> <name> <add|set> <value>
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
' === CHEF commands (permission + check chef) ===
|
|
||||||
package "chef" <<Rectangle>> {
|
|
||||||
class TeamAddSubCommand {
|
class TeamAddSubCommand {
|
||||||
perm: crcore.team.add
|
perm: crcore.team.add
|
||||||
args: <player>
|
args: <team> <player>
|
||||||
}
|
}
|
||||||
class TeamRemoveSubCommand {
|
class TeamRemoveSubCommand {
|
||||||
perm: crcore.team.remove
|
perm: crcore.team.remove
|
||||||
args: <player>
|
args: <team> <player>
|
||||||
}
|
}
|
||||||
class TeamTransferSubCommand {
|
class TeamTransferSubCommand {
|
||||||
perm: crcore.team.transfer
|
perm: crcore.team.transfer
|
||||||
args: <player>
|
args: <team> <player>
|
||||||
}
|
}
|
||||||
class TeamVisibilitySubCommand {
|
class TeamVisibilitySubCommand {
|
||||||
perm: crcore.team.visibility
|
perm: crcore.team.visibility
|
||||||
args: <vis>
|
args: <team> <vis>
|
||||||
}
|
}
|
||||||
class TeamSetSpawnSubCommand {
|
class TeamSetSpawnSubCommand {
|
||||||
perm: crcore.team.setspawn
|
perm: crcore.team.setspawn
|
||||||
|
args: <team>
|
||||||
|
playerOnly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
' === PLAYER commands ===
|
' ─── PLAYER commands ───
|
||||||
package "player" <<Rectangle>> {
|
package "player" <<Rectangle>> {
|
||||||
class TeamJoinSubCommand {
|
class TeamJoinSubCommand {
|
||||||
perm: crcore.team.join
|
perm: crcore.team.join
|
||||||
@@ -114,6 +109,11 @@ package "fr.luc.crcore.command.builtin" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
note bottom of TeamGroupSubCommand
|
note bottom of TeamGroupSubCommand
|
||||||
|
Le rôle LEADER reste dans le modèle Team
|
||||||
|
(utilisable par les game plugins via l'API)
|
||||||
|
mais n'accorde aucun privilège de commande
|
||||||
|
dans le set par défaut.
|
||||||
|
|
||||||
Override d'une feuille :
|
Override d'une feuille :
|
||||||
core.getCoreCommand()
|
core.getCoreCommand()
|
||||||
.findSubCommand("team")
|
.findSubCommand("team")
|
||||||
|
|||||||
+73
-15
@@ -335,8 +335,14 @@ Voir [setup.md](setup.md#utilisation-depuis-un-plugin-de-jeu).
|
|||||||
sous-classe ou via `replaceSubCommand`.
|
sous-classe ou via `replaceSubCommand`.
|
||||||
|
|
||||||
**Pas d'aliases courts** : les commandes ont leur nom long uniquement
|
**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
|
(`/core team create` et pas `/core team c`).
|
||||||
pour réduire la friction de découverte et la confusion.
|
|
||||||
|
**Modèle simplifié à 2 rôles** : toutes les opérations de gestion d'équipe
|
||||||
|
sont **admin** (perm requise + team passée en argument). Les opérations
|
||||||
|
joueur (`join`, `leave`, `info`, `list`, `top`) sont gated par permission
|
||||||
|
mais ne nécessitent pas le rôle chef. Le rôle `LEADER` reste présent dans
|
||||||
|
le modèle de données (utilisable par les game plugins via l'API) mais
|
||||||
|
n'accorde aucun privilège de commande pour l'instant.
|
||||||
|
|
||||||
### Arborescence
|
### Arborescence
|
||||||
|
|
||||||
@@ -347,12 +353,12 @@ pour réduire la friction de découverte et la confusion.
|
|||||||
├── delete <team> [admin] dissoudre une équipe
|
├── delete <team> [admin] dissoudre une équipe
|
||||||
├── setleader <team> <player> [admin] (re)assigner le chef
|
├── setleader <team> <player> [admin] (re)assigner le chef
|
||||||
├── score <team> <name> <add|set> <value> [admin] modifier un score
|
├── score <team> <name> <add|set> <value> [admin] modifier un score
|
||||||
├── add <player> [chef] ajouter à son équipe
|
├── add <team> <player> [admin] ajouter un joueur
|
||||||
├── remove <player> [chef] retirer de son équipe
|
├── remove <team> <player> [admin] retirer un joueur
|
||||||
├── transfer <player> [chef] transférer leadership
|
├── transfer <team> <player> [admin] transfert chef→membre existant
|
||||||
├── visibility <PUBLIC|PRIVATE> [chef] changer visibilité
|
├── visibility <team> <PUBLIC|PRIVATE> [admin] changer visibilité
|
||||||
├── setspawn [chef] définir le spawn
|
├── setspawn <team> [admin] spawn à la position de l'admin
|
||||||
├── join <team> [joueur] rejoindre PUBLIC
|
├── join <team> [joueur] rejoindre une PUBLIC
|
||||||
├── leave [joueur] quitter son équipe
|
├── leave [joueur] quitter son équipe
|
||||||
├── info [team] [joueur] infos
|
├── info [team] [joueur] infos
|
||||||
├── list [joueur] toutes les équipes
|
├── list [joueur] toutes les équipes
|
||||||
@@ -361,13 +367,12 @@ pour réduire la friction de découverte et la confusion.
|
|||||||
|
|
||||||
### Permissions
|
### Permissions
|
||||||
|
|
||||||
Chaque sous-commande a une permission `crcore.team.<action>`. Modèle à 3 niveaux :
|
Chaque sous-commande a une permission `crcore.team.<action>` :
|
||||||
|
|
||||||
| Niveau | Commandes | Comportement |
|
| Niveau | Commandes |
|
||||||
|---|---|---|
|
|---|---|
|
||||||
| **Admin** | `create`, `delete`, `setleader`, `score` | Permission seule (pas de check chef). Cible une team via argument. |
|
| **Admin** | `create`, `delete`, `setleader`, `score`, `add`, `remove`, `transfer`, `visibility`, `setspawn` |
|
||||||
| **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` |
|
||||||
| **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 |
|
| Sous-commande | Permission |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -512,7 +517,60 @@ db.table("my_kills")
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Bootstrap `CRCore`
|
## 7. Intégration PlaceholderAPI (optionnelle)
|
||||||
|
|
||||||
|
**Statut** : implémentée. Auto-détectée par `CRCore.enable()` — si le plugin
|
||||||
|
**PlaceholderAPI** est installé sur le serveur, les placeholders `%crcore_*%`
|
||||||
|
sont enregistrés automatiquement. Si PAPI est absent, la lib reste
|
||||||
|
fonctionnelle, juste sans placeholders.
|
||||||
|
|
||||||
|
### Placeholders Team
|
||||||
|
|
||||||
|
Renvoient vides si le joueur n'est dans aucune équipe.
|
||||||
|
|
||||||
|
| Placeholder | Renvoie | Exemple |
|
||||||
|
|---|---|---|
|
||||||
|
| `%crcore_team%` | récap formaté coloré | `§c[#WOLF] Wolves` |
|
||||||
|
| `%crcore_team_name%` | nom de l'équipe | `Wolves` |
|
||||||
|
| `%crcore_team_tag%` | tag court | `WOLF` |
|
||||||
|
| `%crcore_team_color%` | nom de la couleur | `Red` |
|
||||||
|
| `%crcore_team_color_chat%` | code couleur ChatColor | `§c` |
|
||||||
|
| `%crcore_team_size%` | nombre de membres | `5` |
|
||||||
|
| `%crcore_team_visibility%` | `PUBLIC` ou `PRIVATE` | `PRIVATE` |
|
||||||
|
| `%crcore_team_leader_name%` | nom du chef (vide si leaderless) | `Alice` |
|
||||||
|
| `%crcore_team_total_score%` | somme des scores de l'équipe | `42` |
|
||||||
|
| `%crcore_team_score_<name>%` | score nommé de l'équipe | `%crcore_team_score_kills%` → `12` |
|
||||||
|
|
||||||
|
### Placeholders Player
|
||||||
|
|
||||||
|
| Placeholder | Renvoie |
|
||||||
|
|---|---|
|
||||||
|
| `%crcore_player_score_<name>%` | score nommé du joueur (0 si pas set) |
|
||||||
|
| `%crcore_player_score_total%` | somme de tous les scores du joueur |
|
||||||
|
|
||||||
|
### Usage côté plugin de jeu / config
|
||||||
|
|
||||||
|
Pas d'action à faire côté plugin de jeu — la hook s'enregistre toute seule.
|
||||||
|
Les placeholders sont disponibles partout où PAPI les résout (scoreboard,
|
||||||
|
tablist, chat, hologrammes via DecentHolograms, etc.) :
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Exemple de scoreboard config (FeatherBoard / Scoreboard plugin)
|
||||||
|
lines:
|
||||||
|
- "&aÉquipe : %crcore_team%"
|
||||||
|
- "&aChef : %crcore_team_leader_name%"
|
||||||
|
- "&aKills : %crcore_player_score_kills% (total équipe %crcore_team_score_kills%)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Override
|
||||||
|
|
||||||
|
`CRCore.registerPlaceholderHook()` est `protected`. Override dans une
|
||||||
|
sous-classe de `CRCore` pour ajouter ses propres placeholders ou désactiver
|
||||||
|
la hook.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Bootstrap `CRCore`
|
||||||
|
|
||||||
**Statut** : implémenté. Point d'entrée unique pour les plugins de jeu.
|
**Statut** : implémenté. Point d'entrée unique pour les plugins de jeu.
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
- **Type** : librairie Java (`jar`) — pas un plugin Bukkit
|
- **Type** : librairie Java (`jar`) — pas un plugin Bukkit
|
||||||
- **artifactId Maven** : `CR-Core`
|
- **artifactId Maven** : `CR-Core`
|
||||||
- **Build** : Maven, Java 11
|
- **Build** : Maven, Java 11
|
||||||
|
- **Intégrations optionnelles** : PlaceholderAPI (auto-détectée si installée)
|
||||||
- **API serveur (provided)** : Paper 1.16.5
|
- **API serveur (provided)** : Paper 1.16.5
|
||||||
- **SQLite (compile)** : `org.xerial:sqlite-jdbc:3.45.3.0`
|
- **SQLite (compile)** : `org.xerial:sqlite-jdbc:3.45.3.0`
|
||||||
- **Package racine** : `fr.luc.crcore`
|
- **Package racine** : `fr.luc.crcore`
|
||||||
|
|||||||
@@ -37,6 +37,10 @@
|
|||||||
<id>sonatype</id>
|
<id>sonatype</id>
|
||||||
<url>https://oss.sonatype.org/content/groups/public/</url>
|
<url>https://oss.sonatype.org/content/groups/public/</url>
|
||||||
</repository>
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>placeholderapi</id>
|
||||||
|
<url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
|
||||||
|
</repository>
|
||||||
</repositories>
|
</repositories>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -58,6 +62,18 @@
|
|||||||
<version>${sqlite.version}</version>
|
<version>${sqlite.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!--
|
||||||
|
PlaceholderAPI — intégration OPTIONNELLE. Scope provided : si PAPI
|
||||||
|
est installé sur le serveur, on enregistre nos placeholders ; sinon,
|
||||||
|
la hook reste dormante. Le plugin de jeu n'a pas besoin de l'ajouter
|
||||||
|
lui-même.
|
||||||
|
-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>me.clip</groupId>
|
||||||
|
<artifactId>placeholderapi</artifactId>
|
||||||
|
<version>2.11.6</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|||||||
@@ -117,11 +117,43 @@ public class CRCore {
|
|||||||
this.coreCommand = buildCoreCommand(teamService, playerProfileService);
|
this.coreCommand = buildCoreCommand(teamService, playerProfileService);
|
||||||
registerCommand();
|
registerCommand();
|
||||||
|
|
||||||
|
registerPlaceholderHook();
|
||||||
|
|
||||||
plugin.getLogger().info("CR-Core activé.");
|
plugin.getLogger().info("CR-Core activé.");
|
||||||
enabled = true;
|
enabled = true;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enregistre l'expansion PlaceholderAPI {@code %crcore_*%} si le plugin
|
||||||
|
* PAPI est installé sur le serveur. Si absent, no-op silencieux — la
|
||||||
|
* lib reste fonctionnelle sans.
|
||||||
|
*
|
||||||
|
* <p>Le chargement de la classe d'expansion est différé via une indirection
|
||||||
|
* (méthode {@code doRegisterPlaceholderHook}) pour que le bytecode
|
||||||
|
* référençant {@code me.clip.placeholderapi.*} ne soit pas vérifié si
|
||||||
|
* PAPI n'est pas présent.
|
||||||
|
*/
|
||||||
|
protected void registerPlaceholderHook() {
|
||||||
|
if (plugin.getServer().getPluginManager().getPlugin("PlaceholderAPI") == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
doRegisterPlaceholderHook();
|
||||||
|
plugin.getLogger().info("PlaceholderAPI détecté — placeholders %crcore_*% enregistrés.");
|
||||||
|
} catch (Throwable t) {
|
||||||
|
plugin.getLogger().warning(
|
||||||
|
"Échec d'enregistrement des placeholders PAPI : " + t.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Indirection pour différer le chargement des classes PAPI (cf. {@link #registerPlaceholderHook}). */
|
||||||
|
private void doRegisterPlaceholderHook() {
|
||||||
|
new fr.luc.crcore.placeholder.CRCorePlaceholderExpansion(
|
||||||
|
teamService, playerProfileService, plugin.getDescription().getVersion()
|
||||||
|
).register();
|
||||||
|
}
|
||||||
|
|
||||||
/** Libère les ressources (ferme la DB notamment). Idempotent. */
|
/** Libère les ressources (ferme la DB notamment). Idempotent. */
|
||||||
public void disable() {
|
public void disable() {
|
||||||
if (!enabled) return;
|
if (!enabled) return;
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import org.bukkit.entity.Player;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code /core team add <player>}
|
* {@code /core team add <team> <player>}
|
||||||
*
|
*
|
||||||
* <p>Le chef ajoute un joueur à son équipe. Marche que la team soit PUBLIC ou
|
* <p><b>Admin uniquement</b>. Ajoute un joueur connecté à l'équipe spécifiée,
|
||||||
* PRIVATE — c'est une action chef, pas un auto-join.
|
* quelle que soit sa visibilité.
|
||||||
*/
|
*/
|
||||||
public class TeamAddSubCommand extends SubCommand {
|
public class TeamAddSubCommand extends SubCommand {
|
||||||
|
|
||||||
@@ -23,24 +23,16 @@ public class TeamAddSubCommand extends SubCommand {
|
|||||||
public TeamAddSubCommand(TeamService service) {
|
public TeamAddSubCommand(TeamService service) {
|
||||||
super("add");
|
super("add");
|
||||||
this.service = Objects.requireNonNull(service, "service");
|
this.service = Objects.requireNonNull(service, "service");
|
||||||
description("Ajouter un joueur à son équipe (chef uniquement)");
|
description("Ajouter un joueur à une équipe (admin)");
|
||||||
permission("crcore.team.add");
|
permission("crcore.team.add");
|
||||||
playerOnly();
|
argument("team", TeamArgumentTypes.teamByName(service));
|
||||||
argument("player", ArgumentTypes.ONLINE_PLAYER);
|
argument("player", ArgumentTypes.ONLINE_PLAYER);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandResult execute(CommandContext ctx) {
|
public CommandResult execute(CommandContext ctx) {
|
||||||
Player executor = ctx.requirePlayer();
|
Team team = ctx.get("team");
|
||||||
Player target = ctx.get("player");
|
Player target = ctx.get("player");
|
||||||
|
|
||||||
Team team = service.getTeamOfPlayer(executor.getUniqueId()).orElse(null);
|
|
||||||
if (team == null) {
|
|
||||||
return CommandResult.failure("Vous n'appartenez à aucune équipe.");
|
|
||||||
}
|
|
||||||
if (!team.isLeader(executor.getUniqueId())) {
|
|
||||||
return CommandResult.failure("Seul le chef peut ajouter des membres.");
|
|
||||||
}
|
|
||||||
if (service.getTeamOfPlayer(target.getUniqueId()).isPresent()) {
|
if (service.getTeamOfPlayer(target.getUniqueId()).isPresent()) {
|
||||||
return CommandResult.failure(target.getName() + " est déjà dans une équipe.");
|
return CommandResult.failure(target.getName() + " est déjà dans une équipe.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,15 @@ import fr.luc.crcore.team.Team;
|
|||||||
import fr.luc.crcore.team.TeamService;
|
import fr.luc.crcore.team.TeamService;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.OfflinePlayer;
|
import org.bukkit.OfflinePlayer;
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code /core team remove <player>}
|
* {@code /core team remove <team> <player>}
|
||||||
*
|
*
|
||||||
* <p>Le chef retire un membre. Accepte les joueurs offline (utilise leur nom
|
* <p><b>Admin uniquement</b>. Retire un joueur (online ou offline) de
|
||||||
* pour résoudre l'UUID via {@link Bukkit#getOfflinePlayer(String)}).
|
* 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}.
|
||||||
*/
|
*/
|
||||||
public class TeamRemoveSubCommand extends SubCommand {
|
public class TeamRemoveSubCommand extends SubCommand {
|
||||||
|
|
||||||
@@ -25,34 +25,27 @@ public class TeamRemoveSubCommand extends SubCommand {
|
|||||||
public TeamRemoveSubCommand(TeamService service) {
|
public TeamRemoveSubCommand(TeamService service) {
|
||||||
super("remove");
|
super("remove");
|
||||||
this.service = Objects.requireNonNull(service, "service");
|
this.service = Objects.requireNonNull(service, "service");
|
||||||
description("Retirer un joueur de son équipe (chef uniquement)");
|
description("Retirer un joueur d'une équipe (admin)");
|
||||||
permission("crcore.team.remove");
|
permission("crcore.team.remove");
|
||||||
playerOnly();
|
argument("team", TeamArgumentTypes.teamByName(service));
|
||||||
argument("player", ArgumentTypes.STRING);
|
argument("player", ArgumentTypes.STRING);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandResult execute(CommandContext ctx) {
|
public CommandResult execute(CommandContext ctx) {
|
||||||
Player executor = ctx.requirePlayer();
|
Team team = ctx.get("team");
|
||||||
String targetName = ctx.get("player");
|
String targetName = ctx.get("player");
|
||||||
|
|
||||||
Team team = service.getTeamOfPlayer(executor.getUniqueId()).orElse(null);
|
|
||||||
if (team == null) {
|
|
||||||
return CommandResult.failure("Vous n'appartenez à aucune équipe.");
|
|
||||||
}
|
|
||||||
if (!team.isLeader(executor.getUniqueId())) {
|
|
||||||
return CommandResult.failure("Seul le chef peut retirer des membres.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
OfflinePlayer target = Bukkit.getOfflinePlayer(targetName);
|
OfflinePlayer target = Bukkit.getOfflinePlayer(targetName);
|
||||||
if (target.getUniqueId().equals(executor.getUniqueId())) {
|
|
||||||
return CommandResult.failure("Pour quitter l'équipe en tant que chef, transférez d'abord le leadership.");
|
|
||||||
}
|
|
||||||
if (!team.hasMember(target.getUniqueId())) {
|
if (!team.hasMember(target.getUniqueId())) {
|
||||||
return CommandResult.failure(targetName + " n'est pas dans votre équipe.");
|
return CommandResult.failure(targetName + " n'est pas dans l'équipe " + team.getName() + ".");
|
||||||
|
}
|
||||||
|
if (team.isLeader(target.getUniqueId())) {
|
||||||
|
return CommandResult.failure(
|
||||||
|
"Impossible de retirer le chef. Réassignez-le d'abord via /core team setleader.");
|
||||||
}
|
}
|
||||||
service.removeMember(team.getId(), target.getUniqueId());
|
service.removeMember(team.getId(), target.getUniqueId());
|
||||||
return CommandResult.success(targetName + " retiré de l'équipe.");
|
return CommandResult.success(targetName + " retiré de l'équipe " + team.getName() + ".");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ import org.bukkit.entity.Player;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code /core team setspawn}
|
* {@code /core team setspawn <team>}
|
||||||
*
|
*
|
||||||
* <p>Définit le point de spawn de l'équipe à la position courante du chef.
|
* <p><b>Admin uniquement, player-only</b>. 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.
|
||||||
*/
|
*/
|
||||||
public class TeamSetSpawnSubCommand extends SubCommand {
|
public class TeamSetSpawnSubCommand extends SubCommand {
|
||||||
|
|
||||||
@@ -21,22 +23,17 @@ public class TeamSetSpawnSubCommand extends SubCommand {
|
|||||||
public TeamSetSpawnSubCommand(TeamService service) {
|
public TeamSetSpawnSubCommand(TeamService service) {
|
||||||
super("setspawn");
|
super("setspawn");
|
||||||
this.service = Objects.requireNonNull(service, "service");
|
this.service = Objects.requireNonNull(service, "service");
|
||||||
description("Définir le point de spawn de l'équipe (chef uniquement)");
|
description("Définir le point de spawn d'une équipe (admin, en jeu)");
|
||||||
permission("crcore.team.setspawn");
|
permission("crcore.team.setspawn");
|
||||||
playerOnly();
|
playerOnly();
|
||||||
|
argument("team", TeamArgumentTypes.teamByName(service));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandResult execute(CommandContext ctx) {
|
public CommandResult execute(CommandContext ctx) {
|
||||||
Player player = ctx.requirePlayer();
|
Player admin = ctx.requirePlayer();
|
||||||
Team team = service.getTeamOfPlayer(player.getUniqueId()).orElse(null);
|
Team team = ctx.get("team");
|
||||||
if (team == null) {
|
service.setSpawnPoint(team.getId(), admin.getLocation());
|
||||||
return CommandResult.failure("Vous n'appartenez à aucune équipe.");
|
return CommandResult.success("Spawn de " + team.getName() + " défini à votre position.");
|
||||||
}
|
|
||||||
if (!team.isLeader(player.getUniqueId())) {
|
|
||||||
return CommandResult.failure("Seul le chef peut définir le spawn.");
|
|
||||||
}
|
|
||||||
service.setSpawnPoint(team.getId(), player.getLocation());
|
|
||||||
return CommandResult.success("Spawn de l'équipe défini à votre position.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,14 +8,19 @@ import fr.luc.crcore.team.Team;
|
|||||||
import fr.luc.crcore.team.TeamService;
|
import fr.luc.crcore.team.TeamService;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.OfflinePlayer;
|
import org.bukkit.OfflinePlayer;
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code /core team transfer <player>}
|
* {@code /core team transfer <team> <player>}
|
||||||
*
|
*
|
||||||
* <p>Le chef transmet son rôle à un autre membre existant de son équipe.
|
* <p><b>Admin uniquement</b>. Transfère le rôle de chef à un membre existant
|
||||||
|
* de l'équipe. <i>Strict</i> : le joueur cible doit déjà être membre, et
|
||||||
|
* l'équipe doit avoir un chef actuel.
|
||||||
|
*
|
||||||
|
* <p>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}.
|
||||||
*/
|
*/
|
||||||
public class TeamTransferSubCommand extends SubCommand {
|
public class TeamTransferSubCommand extends SubCommand {
|
||||||
|
|
||||||
@@ -24,29 +29,29 @@ public class TeamTransferSubCommand extends SubCommand {
|
|||||||
public TeamTransferSubCommand(TeamService service) {
|
public TeamTransferSubCommand(TeamService service) {
|
||||||
super("transfer");
|
super("transfer");
|
||||||
this.service = Objects.requireNonNull(service, "service");
|
this.service = Objects.requireNonNull(service, "service");
|
||||||
description("Transférer le rôle de chef à un autre membre (chef uniquement)");
|
description("Transférer le rôle de chef à un membre (admin, strict)");
|
||||||
permission("crcore.team.transfer");
|
permission("crcore.team.transfer");
|
||||||
playerOnly();
|
argument("team", TeamArgumentTypes.teamByName(service));
|
||||||
argument("player", ArgumentTypes.STRING);
|
argument("player", ArgumentTypes.STRING);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandResult execute(CommandContext ctx) {
|
public CommandResult execute(CommandContext ctx) {
|
||||||
Player executor = ctx.requirePlayer();
|
Team team = ctx.get("team");
|
||||||
String targetName = ctx.get("player");
|
String targetName = ctx.get("player");
|
||||||
Team team = service.getTeamOfPlayer(executor.getUniqueId()).orElse(null);
|
|
||||||
if (team == null) {
|
|
||||||
return CommandResult.failure("Vous n'appartenez à aucune équipe.");
|
|
||||||
}
|
|
||||||
if (!team.isLeader(executor.getUniqueId())) {
|
|
||||||
return CommandResult.failure("Seul le chef peut transférer le leadership.");
|
|
||||||
}
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
OfflinePlayer target = Bukkit.getOfflinePlayer(targetName);
|
OfflinePlayer target = Bukkit.getOfflinePlayer(targetName);
|
||||||
if (!team.hasMember(target.getUniqueId())) {
|
if (!team.hasMember(target.getUniqueId())) {
|
||||||
return CommandResult.failure(targetName + " n'est pas dans votre équipe.");
|
return CommandResult.failure(
|
||||||
|
targetName + " n'est pas membre de " + team.getName() +
|
||||||
|
" — utilisez /core team setleader pour un cas plus général.");
|
||||||
}
|
}
|
||||||
service.transferLeadership(team.getId(), target.getUniqueId());
|
try {
|
||||||
return CommandResult.success("Leadership transféré à " + targetName + ".");
|
service.transferLeadership(team.getId(), target.getUniqueId());
|
||||||
|
} catch (IllegalStateException ex) {
|
||||||
|
return CommandResult.failure(ex.getMessage());
|
||||||
|
}
|
||||||
|
return CommandResult.success(targetName + " est désormais chef de " + team.getName() + ".");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,15 +7,14 @@ import fr.luc.crcore.command.SubCommand;
|
|||||||
import fr.luc.crcore.team.Team;
|
import fr.luc.crcore.team.Team;
|
||||||
import fr.luc.crcore.team.TeamService;
|
import fr.luc.crcore.team.TeamService;
|
||||||
import fr.luc.crcore.team.TeamVisibility;
|
import fr.luc.crcore.team.TeamVisibility;
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code /core team visibility <PUBLIC|PRIVATE>}
|
* {@code /core team visibility <team> <PUBLIC|PRIVATE>}
|
||||||
*
|
*
|
||||||
* <p>Le chef change la visibilité de son équipe. PUBLIC = les autres joueurs
|
* <p><b>Admin uniquement</b>. Change la visibilité d'une équipe. PUBLIC permet
|
||||||
* peuvent rejoindre avec {@code /core team join}.
|
* aux joueurs de la rejoindre avec {@code /core team join}.
|
||||||
*/
|
*/
|
||||||
public class TeamVisibilitySubCommand extends SubCommand {
|
public class TeamVisibilitySubCommand extends SubCommand {
|
||||||
|
|
||||||
@@ -24,24 +23,17 @@ public class TeamVisibilitySubCommand extends SubCommand {
|
|||||||
public TeamVisibilitySubCommand(TeamService service) {
|
public TeamVisibilitySubCommand(TeamService service) {
|
||||||
super("visibility");
|
super("visibility");
|
||||||
this.service = Objects.requireNonNull(service, "service");
|
this.service = Objects.requireNonNull(service, "service");
|
||||||
description("Changer la visibilité de son équipe (chef uniquement)");
|
description("Changer la visibilité d'une équipe (admin)");
|
||||||
permission("crcore.team.visibility");
|
permission("crcore.team.visibility");
|
||||||
playerOnly();
|
argument("team", TeamArgumentTypes.teamByName(service));
|
||||||
argument("visibility", ArgumentTypes.enumOf(TeamVisibility.class));
|
argument("visibility", ArgumentTypes.enumOf(TeamVisibility.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandResult execute(CommandContext ctx) {
|
public CommandResult execute(CommandContext ctx) {
|
||||||
Player player = ctx.requirePlayer();
|
Team team = ctx.get("team");
|
||||||
TeamVisibility visibility = ctx.get("visibility");
|
TeamVisibility visibility = ctx.get("visibility");
|
||||||
Team team = service.getTeamOfPlayer(player.getUniqueId()).orElse(null);
|
|
||||||
if (team == null) {
|
|
||||||
return CommandResult.failure("Vous n'appartenez à aucune équipe.");
|
|
||||||
}
|
|
||||||
if (!team.isLeader(player.getUniqueId())) {
|
|
||||||
return CommandResult.failure("Seul le chef peut changer la visibilité.");
|
|
||||||
}
|
|
||||||
service.setVisibility(team.getId(), visibility);
|
service.setVisibility(team.getId(), visibility);
|
||||||
return CommandResult.success("Visibilité réglée sur " + visibility + ".");
|
return CommandResult.success("Visibilité de " + team.getName() + " réglée sur " + visibility + ".");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package fr.luc.crcore.placeholder;
|
||||||
|
|
||||||
|
import fr.luc.crcore.player.PlayerProfileService;
|
||||||
|
import fr.luc.crcore.team.Team;
|
||||||
|
import fr.luc.crcore.team.TeamService;
|
||||||
|
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expansion PlaceholderAPI exposant les données CR-Core (équipe et profil
|
||||||
|
* joueur) via des placeholders {@code %crcore_*%}.
|
||||||
|
*
|
||||||
|
* <h2>Placeholders Team</h2>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code %crcore_team%} — récap formaté avec couleur :
|
||||||
|
* {@code §c[#WOLF] Wolves}</li>
|
||||||
|
* <li>{@code %crcore_team_name%} — nom de l'équipe</li>
|
||||||
|
* <li>{@code %crcore_team_tag%} — tag court</li>
|
||||||
|
* <li>{@code %crcore_team_color%} — nom de la couleur ({@code Red},
|
||||||
|
* {@code Blue}, …)</li>
|
||||||
|
* <li>{@code %crcore_team_color_chat%} — code couleur ChatColor (§c, §9, …)</li>
|
||||||
|
* <li>{@code %crcore_team_size%} — nombre de membres</li>
|
||||||
|
* <li>{@code %crcore_team_visibility%} — {@code PUBLIC} ou {@code PRIVATE}</li>
|
||||||
|
* <li>{@code %crcore_team_leader_name%} — nom du chef (vide si leaderless)</li>
|
||||||
|
* <li>{@code %crcore_team_score_<name>%} — score nommé de l'équipe
|
||||||
|
* (ex. {@code %crcore_team_score_kills%})</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <h2>Placeholders Player</h2>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code %crcore_player_score_<name>%} — score nommé du joueur
|
||||||
|
* (ex. {@code %crcore_player_score_kills%})</li>
|
||||||
|
* <li>{@code %crcore_player_score_total%} — somme de tous les scores du joueur</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>Si le joueur n'est dans aucune équipe, tous les {@code %crcore_team_*%}
|
||||||
|
* renvoient une chaîne vide. Si le placeholder est inconnu, on renvoie
|
||||||
|
* {@code null} → PAPI laisse le placeholder brut.
|
||||||
|
*/
|
||||||
|
public class CRCorePlaceholderExpansion extends PlaceholderExpansion {
|
||||||
|
|
||||||
|
private final TeamService teamService;
|
||||||
|
private final PlayerProfileService playerProfileService;
|
||||||
|
private final String version;
|
||||||
|
|
||||||
|
public CRCorePlaceholderExpansion(TeamService teamService,
|
||||||
|
PlayerProfileService playerProfileService,
|
||||||
|
String version) {
|
||||||
|
this.teamService = Objects.requireNonNull(teamService, "teamService");
|
||||||
|
this.playerProfileService = Objects.requireNonNull(playerProfileService, "playerProfileService");
|
||||||
|
this.version = Objects.requireNonNullElse(version, "1.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getIdentifier() {
|
||||||
|
return "crcore";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthor() {
|
||||||
|
return "luc";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Garde l'enregistrement vivant à travers les {@code /papi reload}. */
|
||||||
|
@Override
|
||||||
|
public boolean persist() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String onPlaceholderRequest(Player player, String params) {
|
||||||
|
if (player == null || params == null) return "";
|
||||||
|
String p = params.toLowerCase();
|
||||||
|
|
||||||
|
// Player scores : %crcore_player_score_<name>% / %crcore_player_score_total%
|
||||||
|
if (p.startsWith("player_score_")) {
|
||||||
|
String scoreName = p.substring("player_score_".length());
|
||||||
|
if (scoreName.equals("total")) {
|
||||||
|
return playerProfileService.getProfile(player.getUniqueId())
|
||||||
|
.map(profile -> String.valueOf(profile.getTotalScore()))
|
||||||
|
.orElse("0");
|
||||||
|
}
|
||||||
|
return String.valueOf(playerProfileService.getScore(player.getUniqueId(), scoreName));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Team placeholders : récap %crcore_team% ou détail %crcore_team_*%
|
||||||
|
Optional<Team> teamOpt = teamService.getTeamOfPlayer(player.getUniqueId());
|
||||||
|
if (p.equals("team")) {
|
||||||
|
return teamOpt.map(team ->
|
||||||
|
team.getColor().getChatColor() + "[#" + team.getTag() + "] " + team.getName()
|
||||||
|
).orElse("");
|
||||||
|
}
|
||||||
|
if (p.startsWith("team_")) {
|
||||||
|
if (teamOpt.isEmpty()) return "";
|
||||||
|
Team team = teamOpt.get();
|
||||||
|
String key = p.substring("team_".length());
|
||||||
|
switch (key) {
|
||||||
|
case "name": return team.getName();
|
||||||
|
case "tag": return team.getTag();
|
||||||
|
case "color": return team.getColor().getDisplayName();
|
||||||
|
case "color_chat": return team.getColor().getChatColor().toString();
|
||||||
|
case "size": return String.valueOf(team.size());
|
||||||
|
case "visibility": return team.getVisibility().name();
|
||||||
|
case "leader_name": return resolveLeaderName(team);
|
||||||
|
case "total_score": return String.valueOf(team.getTotalScore());
|
||||||
|
}
|
||||||
|
if (key.startsWith("score_")) {
|
||||||
|
String scoreName = key.substring("score_".length());
|
||||||
|
return String.valueOf(team.getScore(scoreName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder inconnu — laisse PAPI le rendre brut.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveLeaderName(Team team) {
|
||||||
|
return team.getLeaderId()
|
||||||
|
.map(id -> {
|
||||||
|
OfflinePlayer leader = Bukkit.getOfflinePlayer(id);
|
||||||
|
String name = leader.getName();
|
||||||
|
return name != null ? name : "";
|
||||||
|
})
|
||||||
|
.orElse("");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user