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:
+8
-2
@@ -44,8 +44,13 @@ d'initialisation côté plugin de jeu :
|
||||
- **Framework GUI** — `AbstractInventoryGui` + `GuiListener` réutilisable
|
||||
pour tout GUI custom. Détection par holder, clic toujours annulé,
|
||||
`GuiItems` builder fluide avec codes couleur `&`.
|
||||
- **Bootstrap unique** — `new CRCore(this).enable()` dans le `onEnable()`
|
||||
du plugin de jeu, et tout est branché.
|
||||
- **Modération (skeleton)** — `/core admin` toggle pour passer en
|
||||
mod mode : snapshot complet du joueur (inv, XP, location, gamemode),
|
||||
vanish, hotbar dotée d'outils (tp joueur, inv spy, freeze, vanish
|
||||
toggle, exit). `ModeratorTool` + registry extensibles. Persistance
|
||||
SQLite à venir.
|
||||
- **Bootstrap unique** — `new CRCore(this, new CRCoreConfig().setupAll()).enable()`
|
||||
dans le `onEnable()` du plugin de jeu, et tout est branché.
|
||||
|
||||
## Structure de la documentation
|
||||
|
||||
@@ -72,6 +77,7 @@ d'initialisation côté plugin de jeu :
|
||||
| [broadcasts-class-diagram.puml](diagrams/broadcasts-class-diagram.puml) | Classe | Service de broadcasts YAML + listener |
|
||||
| [team-config-class-diagram.puml](diagrams/team-config-class-diagram.puml) | Classe | Paramètres d'équipe (cascade + GUI) |
|
||||
| [gui-class-diagram.puml](diagrams/gui-class-diagram.puml) | Classe | Framework GUI réutilisable |
|
||||
| [moderation-class-diagram.puml](diagrams/moderation-class-diagram.puml) | Classe | Feature modération (skeleton) |
|
||||
| [bootstrap-sequence.puml](diagrams/bootstrap-sequence.puml) | Séquence | `CRCore.enable()` côté plugin de jeu |
|
||||
|
||||
## Conventions
|
||||
|
||||
@@ -367,6 +367,45 @@ Format léger : une décision = un titre + contexte + choix + raison.
|
||||
bases existantes, ALTER TABLE manuel ou suppression du fichier
|
||||
(les bases d'event sont jetables).
|
||||
|
||||
## 2026-06-10 — Feature modération (skeleton)
|
||||
|
||||
- **Choix** : nouveau module `fr.luc.crcore.features.moderation`,
|
||||
opt-in via `CRCoreConfig.setupModeration()`. Skeleton complet :
|
||||
- `ModerationState` (snapshot total : inv, armor, offhand, XP, health,
|
||||
food, location, gamemode, flight/fly speed)
|
||||
- `ModerationService` (interface) + `ModerationServiceImpl` +
|
||||
`BukkitEventFiringModerationServiceImpl` (tire `ModerationEnterEvent`
|
||||
/ `ModerationExitEvent`)
|
||||
- `ModeratorTool` interface + `ModeratorToolRegistry`
|
||||
- 5 outils squelette (slots 0, 1, 2, 7, 8) :
|
||||
`TeleportRandomPlayerTool`, `InventorySpyTool`, `FreezeTool`,
|
||||
`VanishToggleTool`, `ExitTool`
|
||||
- `ModerationListener` unique : route les clics → tool, lock hotbar
|
||||
(drop, swap, inventory click sur self), vanish-on-join,
|
||||
freeze (PlayerMoveEvent cancel)
|
||||
- `/core admin` toggle on/off (perm `crcore.admin`, player-only)
|
||||
- **Vanish** : `Player.hidePlayer(plugin, vanished)` — propre, sans
|
||||
modif visuelle, pas de fake-quit. Le listener re-applique
|
||||
automatiquement aux joueurs qui join.
|
||||
- **Persistance in-memory uniquement** (skeleton). Si le serveur crash
|
||||
pendant qu'un mod est en mod mode, son inventaire est perdu. Une
|
||||
impl `SqliteModerationRepository` avec sérialisation
|
||||
`BukkitObjectOutputStream` → base64 est prévue pour plus tard. Pas
|
||||
bloquant pour la première itération.
|
||||
- **Mod mode → CREATIVE + allowFlight** par défaut. Choisi pour la
|
||||
visibilité totale + mobility. Override possible en sous-classant
|
||||
`ModerationServiceImpl.enter()`.
|
||||
- **Outils en slots fixes** plutôt qu'avec PersistentDataContainer.
|
||||
Plus simple ; le `ModerationListener` annule tout drop/swap pour
|
||||
garantir que les outils restent en place. Pour identification croisée
|
||||
(NBT) on pourra ajouter plus tard.
|
||||
- **Tools extensibles** : `core.moderation().getToolRegistry().register(...)`
|
||||
côté game plugin. Les slots libres (3, 4, 5, 6) sont prêts pour les
|
||||
outils custom.
|
||||
- **Listener responsibilities** : un seul `ModerationListener`
|
||||
centralise toutes les hooks (clicks, freeze, vanish, lock hotbar).
|
||||
Évite de disperser dans 5 listeners séparés.
|
||||
|
||||
## 2026-06-10 — Réorganisation : `util/` (toujours) vs `features/` (opt-in)
|
||||
|
||||
- **Choix** : séparation nette en deux couches :
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
@startuml moderation-class-diagram
|
||||
title CR-Core — Moderation feature (class diagram, skeleton)
|
||||
|
||||
skinparam classAttributeIconSize 0
|
||||
hide empty members
|
||||
|
||||
package "fr.luc.crcore.features.moderation" {
|
||||
|
||||
class ModerationState <<final>> {
|
||||
- playerId: UUID
|
||||
- enteredAt: Instant
|
||||
- inventoryContents: ItemStack[]
|
||||
- armorContents: ItemStack[]
|
||||
- offhandItem: ItemStack
|
||||
- xpLevel: int
|
||||
- xpProgress: float
|
||||
- health: double
|
||||
- foodLevel: int
|
||||
- saturation: float
|
||||
- location: Location
|
||||
- gameMode: GameMode
|
||||
- allowFlight / flying / walkSpeed / flySpeed
|
||||
--
|
||||
+ ModerationState(player)
|
||||
+ restoreTo(player): void
|
||||
}
|
||||
|
||||
interface ModerationRepository {
|
||||
+ findByPlayer(uuid): Optional<ModerationState>
|
||||
+ exists(uuid): boolean
|
||||
+ save(state): void
|
||||
+ delete(uuid): boolean
|
||||
+ findAll(): Collection<ModerationState>
|
||||
}
|
||||
|
||||
interface ModeratorTool {
|
||||
+ getKey(): String
|
||||
+ getSlot(): int ' 0..8
|
||||
+ buildIcon(): ItemStack
|
||||
+ onLeftClick(player): void
|
||||
+ onRightClick(player): void
|
||||
+ onInteractEntity(player, target): void
|
||||
}
|
||||
|
||||
class ModeratorToolRegistry {
|
||||
- bySlot: Map<Integer, ModeratorTool>
|
||||
- byKey: Map<String, ModeratorTool>
|
||||
+ register(tool): void
|
||||
+ unregister(key): boolean
|
||||
+ get(key): Optional<ModeratorTool>
|
||||
+ getBySlot(slot): Optional<ModeratorTool>
|
||||
+ all(): Collection<ModeratorTool>
|
||||
}
|
||||
|
||||
interface ModerationService {
|
||||
+ enter(player): void
|
||||
+ exit(player): void
|
||||
+ isInModeration(uuid): boolean
|
||||
+ getState(uuid): Optional<ModerationState>
|
||||
+ getActiveModerators(): Set<UUID>
|
||||
--
|
||||
+ vanish(player) / unvanish / isVanished / getVanishedPlayers
|
||||
+ freeze(uuid) / unfreeze / isFrozen / getFrozenPlayers
|
||||
+ getToolRegistry(): ModeratorToolRegistry
|
||||
}
|
||||
|
||||
package "fr.luc.crcore.features.moderation.impl" {
|
||||
class InMemoryModerationRepository
|
||||
InMemoryModerationRepository ..|> ModerationRepository
|
||||
|
||||
class ModerationServiceImpl {
|
||||
# plugin: Plugin
|
||||
# repository: ModerationRepository
|
||||
# toolRegistry: ModeratorToolRegistry
|
||||
# vanished: Set<UUID>
|
||||
# frozen: Set<UUID>
|
||||
--
|
||||
# onAfterEnter(player) ' hook
|
||||
# onAfterExit(player)
|
||||
}
|
||||
ModerationServiceImpl ..|> ModerationService
|
||||
|
||||
class BukkitEventFiringModerationServiceImpl
|
||||
BukkitEventFiringModerationServiceImpl --|> ModerationServiceImpl
|
||||
|
||||
class ModerationListener {
|
||||
+ registerOn(plugin): void
|
||||
--
|
||||
@ onInteract ' route → tool.onLeftClick / onRightClick
|
||||
@ onInteractEntity ' route → tool.onInteractEntity
|
||||
@ onDrop / onSwap ' lock hotbar
|
||||
@ onInventoryClick ' cancel sur self-inv
|
||||
@ onJoin ' re-hide vanished players
|
||||
@ onQuit ' unfreeze
|
||||
@ onMove ' cancel si frozen
|
||||
}
|
||||
ModerationListener ..|> "org.bukkit.event.Listener"
|
||||
ModerationListener --> ModerationService
|
||||
}
|
||||
|
||||
package "fr.luc.crcore.features.moderation.tool" {
|
||||
class ExitTool ' slot 8
|
||||
class VanishToggleTool ' slot 7
|
||||
class FreezeTool ' slot 2 (entity right-click)
|
||||
class InventorySpyTool ' slot 1 (entity right-click)
|
||||
class TeleportRandomPlayerTool ' slot 0
|
||||
ExitTool ..|> ModeratorTool
|
||||
VanishToggleTool ..|> ModeratorTool
|
||||
FreezeTool ..|> ModeratorTool
|
||||
InventorySpyTool ..|> ModeratorTool
|
||||
TeleportRandomPlayerTool ..|> ModeratorTool
|
||||
}
|
||||
|
||||
package "fr.luc.crcore.features.moderation.event" {
|
||||
abstract class ModerationEvent {
|
||||
- moderator: Player
|
||||
+ getModerator(): Player
|
||||
}
|
||||
ModerationEvent --|> "org.bukkit.event.Event"
|
||||
class ModerationEnterEvent
|
||||
class ModerationExitEvent
|
||||
ModerationEnterEvent --|> ModerationEvent
|
||||
ModerationExitEvent --|> ModerationEvent
|
||||
}
|
||||
|
||||
package "fr.luc.crcore.features.moderation.exception" {
|
||||
class ModerationException
|
||||
class ModerationAlreadyActiveException
|
||||
class ModerationNotActiveException
|
||||
ModerationException --|> RuntimeException
|
||||
ModerationAlreadyActiveException --|> ModerationException
|
||||
ModerationNotActiveException --|> ModerationException
|
||||
}
|
||||
|
||||
package "fr.luc.crcore.features.moderation.command" {
|
||||
class AdminToggleSubCommand {
|
||||
+ execute(ctx): CommandResult
|
||||
}
|
||||
AdminToggleSubCommand ..> ModerationService
|
||||
}
|
||||
|
||||
ModerationService ..> ModerationState : reads/writes
|
||||
ModerationService o--> ModerationRepository
|
||||
ModerationService o--> ModeratorToolRegistry
|
||||
ModeratorToolRegistry o--> "*" ModeratorTool
|
||||
}
|
||||
|
||||
package "fr.luc.crcore" {
|
||||
class CRCore {
|
||||
+ moderation(): ModerationService
|
||||
# buildModerationService(repo, registry): ModerationService
|
||||
# registerDefaultModeratorTools(registry, mod): void
|
||||
}
|
||||
CRCore "1" *-- "0..1" ModerationService : owns (if setupModeration)
|
||||
CRCore ..> ModerationListener : registers
|
||||
}
|
||||
|
||||
note bottom of ModerationServiceImpl
|
||||
enter(player) :
|
||||
1. ModerationState snapshot → repo.save
|
||||
2. clear inventory, set tools in hotbar
|
||||
3. gamemode CREATIVE + allowFlight
|
||||
4. vanish(player)
|
||||
5. onAfterEnter() → event fired
|
||||
|
||||
exit(player) :
|
||||
1. state.restoreTo(player) ' inv + xp + loc + gm + flight
|
||||
2. unvanish(player)
|
||||
3. unfreeze + repo.delete
|
||||
4. onAfterExit() → event fired
|
||||
|
||||
Skeleton : in-memory repository only.
|
||||
Persistance SQLite à venir.
|
||||
end note
|
||||
|
||||
@enduml
|
||||
+118
-1
@@ -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.
|
||||
|
||||
|
||||
+2
-1
@@ -86,7 +86,8 @@ public class MyGamePlugin extends JavaPlugin {
|
||||
// OPTION B — granularité, n'activer que ce qu'on veut
|
||||
// this.core = new CRCore(this, new CRCoreConfig()
|
||||
// .setupTeams()
|
||||
// .setupPlaceholders()) // pas de players
|
||||
// .setupPlaceholders() // pas de players
|
||||
// .setupModeration()) // ajoute le mod mode
|
||||
// .enable();
|
||||
|
||||
// OPTION C — par défaut + options
|
||||
|
||||
Reference in New Issue
Block a user