feat: moderation feature skeleton (/core admin + mod mode + tools)

New feature fr.luc.crcore.features.moderation, opt-in via
CRCoreConfig.setupModeration() (also enabled by setupAll()).

Core abstractions:
- ModerationState: full player snapshot (inv + armor + offhand, XP,
  health, food, location, gamemode, allowFlight/flying, walk/fly speed).
  Immutable, restoreTo(player) restores everything.
- ModerationService interface + ModerationServiceImpl (with
  protected onAfterEnter/onAfterExit hooks) +
  BukkitEventFiringModerationServiceImpl (fires ModerationEnterEvent /
  ModerationExitEvent).
- ModerationRepository interface + InMemoryModerationRepository
  (skeleton — SQLite impl with BukkitObjectOutputStream serialization
  planned).
- ModeratorTool interface: getKey/getSlot(0..8)/buildIcon +
  onLeftClick/onRightClick/onInteractEntity. ModeratorToolRegistry
  preserves registration order, slot collision = replace.
- Exceptions: ModerationException base + AlreadyActive + NotActive.
- Events: ModerationEvent base + Enter + Exit.

5 skeleton tools in the hotbar:
- slot 0: TeleportRandomPlayerTool (compass, right-click → tp random)
- slot 1: InventorySpyTool (chest, right-click on player → open inv)
- slot 2: FreezeTool (ice, right-click on player → toggle freeze)
- slot 7: VanishToggleTool (ender eye, click → toggle vanish)
- slot 8: ExitTool (barrier, click → exit mod mode)
Slots 3-6 free for custom tools.

ModerationListener routes interactions and locks hotbar:
- PlayerInteractEvent → tool.onLeftClick / onRightClick (with cancel).
- PlayerInteractEntityEvent → tool.onInteractEntity (with cancel).
- PlayerDropItemEvent / PlayerSwapHandItemsEvent: cancel for mods.
- InventoryClickEvent: cancel only when top inv is the mod's own inv
  (preserves InventorySpyTool's ability to manipulate target's inv).
- PlayerJoinEvent: re-applies vanish for already-vanished mods.
- PlayerQuitEvent: cleanup freeze state.
- PlayerMoveEvent: cancel block-position changes for frozen players,
  keeping head rotation free.

Mod mode lifecycle:
- enter: snapshot + clear inv + populate hotbar + CREATIVE +
  allowFlight + vanish + ModerationEnterEvent.
- exit: state.restoreTo(player) + unvanish + unfreeze + repo delete +
  ModerationExitEvent.

/core admin (perm crcore.admin, player-only): toggle on/off.
Messages moderation.enter.success / moderation.exit.success added
to crcore-messages.yml.

CRCoreConfig.setupModeration() + isModerationEnabled() flag.
CRCore: buildModerationService() and registerDefaultModeratorTools()
override points, moderation() / getModerationService() getters with
IllegalStateException guard. Builds + registers ModerationListener at
enable() when feature on. CoreCommand extended to take ModerationService;
registers AdminToggleSubCommand only when service non-null.

Skeleton limitations documented in features.md:
- In-memory repo only (server crash = lost inv) — SQLite planned.
- InventorySpyTool opens real inv (no read-only wrapping yet).
- TeleportRandomPlayerTool is a placeholder for a future player-picker
  GUI.
- No moderation.*.broadcast keys yet.
- No /core admin <player> (self-toggle only).

Docs: section 11 in features.md, decision logged in decisions.md
(skeleton scope + rationale), setup.md snippet updated,
new moderation-class-diagram.puml, README.md updated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Antone Barbaud
2026-06-10 12:19:21 +02:00
parent 84735221f1
commit 4efaa5bbde
30 changed files with 1630 additions and 20 deletions
+118 -1
View File
@@ -920,7 +920,124 @@ est déjà enregistré par `CRCore.enable()`.
---
## 11. Bootstrap `CRCore`
## 11. Modération (`fr.luc.crcore.features.moderation`)
**Statut** : skeleton. Architecture complète, 5 outils squelette, vanish
+ freeze fonctionnels. Persistance SQLite et outils avancés à venir.
### Mod mode
`/core admin` (perm `crcore.admin`, player-only) toggle on/off le mod
mode pour l'exécutant :
- **À l'entrée** :
1. Snapshot complet du joueur (`ModerationState`) : inventory + armor
+ offhand, XP level + progress, health, food, location, gamemode,
allowFlight + flying, walk/fly speed.
2. Inventaire vidé, hotbar peuplée avec les outils du
`ModeratorToolRegistry`.
3. Passage en `CREATIVE` + `allowFlight=true`.
4. Vanish actif (caché de tous les joueurs en ligne).
5. Event Bukkit `ModerationEnterEvent` tiré.
- **À la sortie** : restauration intégrale du snapshot + retrait du
vanish + cleanup freeze + `ModerationExitEvent`.
### Outils squelette (hotbar)
| Slot | Outil | Action |
|---|---|---|
| 0 | `TeleportRandomPlayerTool` (compass) | clic droit → tp à un joueur aléatoire |
| 1 | `InventorySpyTool` (chest) | clic droit sur joueur → ouvre son inv |
| 2 | `FreezeTool` (ice) | clic droit sur joueur → toggle freeze |
| 7 | `VanishToggleTool` (ender eye) | clic → toggle vanish |
| 8 | `ExitTool` (barrier) | clic → exit mod mode |
Slots libres (3, 4, 5, 6) prêts pour des outils custom du game plugin.
### Ajouter un outil custom
```java
public class WarnTool implements ModeratorTool {
@Override public String getKey() { return "warn"; }
@Override public int getSlot() { return 4; }
@Override public ItemStack buildIcon() {
return GuiItems.named(Material.PAPER, "&eWarn").build();
}
@Override public void onInteractEntity(Player mod, Entity target) {
if (target instanceof Player) {
((Player) target).sendMessage("§eTu as reçu un warn !");
}
}
}
// Côté game plugin onEnable() :
core.moderation().getToolRegistry().register(new WarnTool());
```
### Vanish
Le vanish utilise `Player.hidePlayer(plugin, vanished)` pour cacher de
tous les autres joueurs. Le `ModerationListener` re-applique
automatiquement le vanish aux joueurs qui rejoignent le serveur.
### Freeze
`moderation.freeze(uuid)` ajoute l'UUID à un `Set` en mémoire. Le
`ModerationListener` cancel les `PlayerMoveEvent` qui changent de bloc.
La rotation de la tête reste libre (UX). Le freeze est retiré
automatiquement à la déconnexion du joueur.
### `ModerationListener` — verrouillage hotbar
Tant qu'un joueur est en mod mode :
- Les drops d'items sont annulés.
- Le swap main/offhand est annulé.
- Les clics dans son propre inventaire sont annulés. Les clics dans un
inventaire ouvert par un outil (ex. `InventorySpy`) restent libres.
- Les `PlayerInteractEvent` sur la hotbar routent vers le tool du slot
tenu, avec annulation pour éviter toute interaction réelle avec le
monde.
### Events Bukkit
- `ModerationEnterEvent` — après snapshot + vanish + équipement
- `ModerationExitEvent` — après restore + retrait vanish
### Limites du skeleton — TODO
- **Persistance** : `InMemoryModerationRepository` uniquement. Si le
serveur crash pendant qu'un mod est en mod mode, son inventaire est
perdu. À ajouter : `SqliteModerationRepository` avec sérialisation
Bukkit (`BukkitObjectOutputStream` → base64) des `ItemStack[]`.
- **InventorySpyTool** : ouvre l'inventaire réel — toute modif est
appliquée. Pour de l'audit pur, wrap dans une `Inventory` cloné en
read-only.
- **TeleportRandomPlayerTool** : placeholder. Remplacer par un GUI
sélecteur de joueurs.
- **Pas de broadcasts** : pas encore d'entrées
`moderation.enter.broadcast` / `moderation.exit.broadcast`. Trivial à
ajouter au système broadcasts en suivant le pattern existant.
- **Pas d'autre toggle** que self — `/core admin <player>` n'existe pas
encore.
### Setup côté plugin de jeu
```java
this.core = new CRCore(this,
new CRCoreConfig().setupModeration()) // ou setupAll()
.enable();
// Ajouter un outil custom (optionnel)
core.moderation().getToolRegistry().register(new MyWarnTool());
```
### Diagramme
- [moderation-class-diagram.puml](diagrams/moderation-class-diagram.puml)
---
## 12. Bootstrap `CRCore`
**Statut** : implémenté. Point d'entrée unique pour les plugins de jeu.