feat: SQLite persistence, default /core commands, Bukkit events, bootstrap
CRCore bootstrap class: one-line setup for game plugins (new CRCore(this).enable()).
Wires SQLite, services with event firing, and the /core command tree.
SQLite layer (fr.luc.crcore.database): Database wrapper exposing execute/update/
queryOne/query plus a fluent TableBuilder. ColumnType enum, RowMapper interface,
DatabaseException. Game plugins create their own tables in 2 lines via
db.table("foo").ifNotExists().column(...).create().
Repositories: SqliteTeamRepository and SqlitePlayerProfileRepository extend their
InMemory counterparts (write-through cache). 5 internal tables prefixed crcore_.
Command framework refactored for nested sub-commands: subcommand storage moved
from BaseCommand to AbstractCommand, recursive dispatch() and tabComplete(),
replaceSubCommand() for plugin overrides.
Default /core team commands (13 leaf sub-commands): create, delete, add, remove,
join, leave, info, list, transfer, visibility, score, top, setspawn. Each in its
own class under fr.luc.crcore.command.builtin.team, fully substitutable.
Bukkit events: 9 team events (Create/Dissolve/MemberAdd/MemberRemove/PlayerJoin/
LeadershipTransfer/VisibilityChange/ScoreChange/SpawnPointChange) + 3 player
events (ProfileCreate/Delete/ScoreChange). All post-only, non-cancellable.
BukkitEventFiringTeamServiceImpl and BukkitEventFiringPlayerProfileServiceImpl
override the on* hooks to call Bukkit.getPluginManager().callEvent.
JavaDoc on all new public classes and key existing ones. docs/, GEMINI.md and
PUML diagrams synced: new sections (built-in commands, events, database,
bootstrap), 4 new diagrams (builtin-commands, events, database, bootstrap-
sequence), and 7 new architecture decisions logged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+254
-15
@@ -1,7 +1,19 @@
|
||||
# Domaines fonctionnels
|
||||
|
||||
CR-Core est une librairie. Chaque domaine est autonome ; le plugin de jeu
|
||||
downstream pioche ce qu'il utilise.
|
||||
CR-Core est une librairie. Le plugin de jeu downstream l'instancie en une
|
||||
ligne via `new CRCore(this).enable()` dans son `onEnable()`, et tout est
|
||||
branché : SQLite, services team + player, commandes `/core team ...`,
|
||||
évènements Bukkit.
|
||||
|
||||
Architecture des domaines :
|
||||
|
||||
1. **Team** — équipes (membres, leader, visibilité, scores, classements, spawn)
|
||||
2. **Player** — profils joueurs (scores nommés, classements individuels)
|
||||
3. **Framework de commandes** — `BaseCommand` / `SubCommand` imbriqués
|
||||
4. **Commandes built-in** — `/core team [create|delete|add|remove|join|leave|...]`
|
||||
5. **Évènements Bukkit** — 9 events team + 3 events player
|
||||
6. **Database** — wrapper SQLite + table builder pour les plugins downstream
|
||||
7. **Bootstrap** — classe `CRCore` qui câble tout
|
||||
|
||||
---
|
||||
|
||||
@@ -245,24 +257,29 @@ UUID pour rester déterministe).
|
||||
|
||||
## 3. Framework de commandes
|
||||
|
||||
**Statut** : framework implémenté. Pas de commande Team intégrée — c'est au
|
||||
plugin de jeu de définir ses commandes en utilisant les briques fournies.
|
||||
**Statut** : framework implémenté avec **sous-commandes imbriquées récursives**
|
||||
(supporte `/core team create`, `/core team join`, etc.). Les commandes par
|
||||
défaut sont fournies en section 4.
|
||||
|
||||
### Architecture
|
||||
|
||||
- **`Command`** (interface) — contrat partagé : `getName()`, `getAliases()`,
|
||||
`getPermission()`, `isPlayerOnly()`, `getDescription()`, `execute(ctx)`,
|
||||
`tabComplete(sender, argIndex, partial)`, `matches(label)`.
|
||||
`tabComplete(sender, args)`, `matches(label)`.
|
||||
- **`AbstractCommand`** (abstract, implémente `Command`) — porte tous les
|
||||
champs : nom, aliases, permission, player-only, description, usage,
|
||||
arguments. Méthodes builder en `protected` : `addAlias`, `permission`,
|
||||
`playerOnly`, `description`, `usage`, `argument`, `optionalArgument`.
|
||||
- **`BaseCommand extends AbstractCommand`** — implémente aussi
|
||||
`CommandExecutor` et `TabCompleter` de Bukkit. Conteneur de `SubCommand`,
|
||||
fait le routage `args[0]` → sous-commande, gère permissions, player-only,
|
||||
invalid usage, affichage de l'aide par défaut.
|
||||
- **`SubCommand extends AbstractCommand`** — sous-commande sans logique
|
||||
Bukkit. La méthode abstraite `execute(CommandContext)` est à implémenter.
|
||||
champs ET la table des sous-commandes (imbrication). Méthodes builder en
|
||||
`protected` : `addAlias`, `permission`, `playerOnly`, `description`, `usage`,
|
||||
`argument`, `optionalArgument`, `addSubCommand`. Routage récursif via
|
||||
`dispatch(...)` et `tabComplete(...)`.
|
||||
- **`BaseCommand extends AbstractCommand`** — implémente `CommandExecutor` et
|
||||
`TabCompleter` de Bukkit, branche `onCommand` → `dispatch`. À utiliser pour
|
||||
la racine d'un arbre (`/core`).
|
||||
- **`SubCommand extends AbstractCommand`** — sous-commande ; peut être
|
||||
feuille (override `execute`) ou groupe (appelle `addSubCommand` dans son
|
||||
constructeur).
|
||||
- **`replaceSubCommand(name, newSub)`** sur `AbstractCommand` — permet aux
|
||||
plugins de jeu de remplacer une sous-commande par défaut par leur propre
|
||||
implémentation (pattern standard d'override).
|
||||
|
||||
### Types d'arguments (`ArgumentTypes`)
|
||||
|
||||
@@ -307,6 +324,228 @@ 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
|
||||
`CRCore.enable()`. Chaque sous-commande vit dans
|
||||
`fr.luc.crcore.command.builtin.team` et est substituable individuellement par
|
||||
sous-classe ou via `replaceSubCommand`.
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
### Aliases supplémentaires (équivalents Bukkit)
|
||||
|
||||
| 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` |
|
||||
|
||||
### Permissions par défaut
|
||||
|
||||
| Sous-commande | Permission |
|
||||
|---|---|
|
||||
| `create` | `crcore.team.create` |
|
||||
| `score` | `crcore.team.score.modify` (admin) |
|
||||
| autres | aucune (gated par appartenance / rôle de chef) |
|
||||
|
||||
### Override d'une sous-commande par défaut
|
||||
|
||||
```java
|
||||
// Option A : remplacer une feuille
|
||||
core.getCoreCommand().findSubCommand("team")
|
||||
.ifPresent(team -> team.replaceSubCommand("create",
|
||||
new MyCustomTeamCreate(core.getTeamService())));
|
||||
|
||||
// Option B : sous-classer et override execute()
|
||||
public class MyTeamCreate extends TeamCreateSubCommand {
|
||||
public MyTeamCreate(TeamService service) { super(service); }
|
||||
@Override public CommandResult execute(CommandContext ctx) {
|
||||
// règles métier custom puis fallback super
|
||||
return super.execute(ctx);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Diagramme
|
||||
|
||||
- Classes : [builtin-commands-diagram.puml](diagrams/builtin-commands-diagram.puml)
|
||||
|
||||
---
|
||||
|
||||
## 5. Évènements Bukkit
|
||||
|
||||
**Statut** : 12 évènements implémentés, tirés automatiquement par les services
|
||||
par défaut (`BukkitEventFiringTeamServiceImpl` et `BukkitEventFiringPlayerProfileServiceImpl`).
|
||||
|
||||
Tous les évènements sont **post** (non-cancellable) — la validation se fait en
|
||||
amont dans les services via les hooks `validate*`. Pour bloquer un comportement,
|
||||
override le hook ou la sous-commande, pas l'évènement.
|
||||
|
||||
### Évènements Team (`fr.luc.crcore.team.event`)
|
||||
|
||||
| Évènement | Quand | Champs spécifiques |
|
||||
|---|---|---|
|
||||
| `TeamCreateEvent` | Après création + persist | — |
|
||||
| `TeamDissolveEvent` | Après dissolution | — |
|
||||
| `TeamMemberAddEvent` | Après ajout d'un membre (chef OU auto-join) | `getMember()` |
|
||||
| `TeamMemberRemoveEvent` | Après retrait | `getPlayerId()` |
|
||||
| `PlayerJoinTeamEvent` | Spécifique auto-join (joueur lui-même) | `getMember()` |
|
||||
| `TeamLeadershipTransferEvent` | Après transfert | `getOldLeaderId()`, `getNewLeaderId()` |
|
||||
| `TeamVisibilityChangeEvent` | Après changement effectif | `getOldVisibility()`, `getNewVisibility()` |
|
||||
| `TeamScoreChangeEvent` | Après changement effectif d'un score | `getScoreName()`, `getOldValue()`, `getNewValue()`, `getDelta()` |
|
||||
| `TeamSpawnPointChangeEvent` | Après changement du spawn | `getOldLocation()`, `getNewLocation()` (nullable) |
|
||||
|
||||
Tous étendent `TeamEvent` (porte la `Team`).
|
||||
|
||||
### Évènements Player (`fr.luc.crcore.player.event`)
|
||||
|
||||
| Évènement | Quand | Champs spécifiques |
|
||||
|---|---|---|
|
||||
| `PlayerProfileCreateEvent` | Après création (lazy ou explicite) | — |
|
||||
| `PlayerProfileDeleteEvent` | Après suppression | — |
|
||||
| `PlayerScoreChangeEvent` | Après changement effectif d'un score joueur | `getScoreName()`, `getOldValue()`, `getNewValue()`, `getDelta()` |
|
||||
|
||||
Tous étendent `PlayerProfileEvent` (porte le `PlayerProfile`).
|
||||
|
||||
### Usage
|
||||
|
||||
```java
|
||||
@EventHandler
|
||||
public void onTeamCreate(TeamCreateEvent e) {
|
||||
Team t = e.getTeam();
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Diagramme
|
||||
|
||||
- Classes : [events-diagram.puml](diagrams/events-diagram.puml)
|
||||
|
||||
---
|
||||
|
||||
## 6. Persistance SQLite (`fr.luc.crcore.database`)
|
||||
|
||||
**Statut** : wrapper minimal + table builder fluide. Repositories SQLite
|
||||
write-through pour Team et PlayerProfile activés par défaut via `CRCore`.
|
||||
|
||||
### API
|
||||
|
||||
- **`Database`** (AutoCloseable) — 4 méthodes principales :
|
||||
- `execute(sql, params...)` — DDL ou statement sans résultat
|
||||
- `update(sql, params...)` — INSERT/UPDATE/DELETE, renvoie le nombre de lignes affectées
|
||||
- `queryOne(sql, mapper, params...)` — au plus une ligne (Optional)
|
||||
- `query(sql, mapper, params...)` — plusieurs lignes
|
||||
- `inTransaction(Runnable)` — commit/rollback auto
|
||||
- `table(name)` — démarre un `TableBuilder` fluide
|
||||
- `tableExists(name)` — check
|
||||
- **`TableBuilder`** — `.ifNotExists().column(name, type).primaryKey().notNull()...create()`
|
||||
- **`ColumnType`** — enum : INTEGER, REAL, TEXT, BLOB, BOOLEAN, UUID
|
||||
- **`RowMapper<T>`** — `T map(ResultSet rs) throws SQLException` (lambda-friendly)
|
||||
- **`DatabaseException`** — runtime exception, wrap les `SQLException` JDBC
|
||||
|
||||
Les paramètres `Object...` sont liés via PreparedStatement (anti-injection),
|
||||
avec conversion auto pour `UUID` (→ TEXT), `Enum<?>` (→ name() TEXT), `Boolean`
|
||||
(→ 0/1).
|
||||
|
||||
### Tables internes CR-Core
|
||||
|
||||
Le préfixe `crcore_` évite les collisions :
|
||||
|
||||
- `crcore_teams`, `crcore_team_members`, `crcore_team_scores`
|
||||
- `crcore_player_profiles`, `crcore_player_scores`
|
||||
|
||||
### Tables custom côté plugin de jeu
|
||||
|
||||
```java
|
||||
Database db = core.getDatabase();
|
||||
db.table("my_kills")
|
||||
.ifNotExists()
|
||||
.column("player_id", ColumnType.UUID).primaryKey()
|
||||
.column("kills", ColumnType.INTEGER).notNull().defaultValue("0")
|
||||
.create();
|
||||
```
|
||||
|
||||
### Diagramme
|
||||
|
||||
- Classes : [database-diagram.puml](diagrams/database-diagram.puml)
|
||||
|
||||
---
|
||||
|
||||
## 7. Bootstrap `CRCore`
|
||||
|
||||
**Statut** : implémenté. Point d'entrée unique pour les plugins de jeu.
|
||||
|
||||
### Usage
|
||||
|
||||
```java
|
||||
public class MyGamePlugin extends JavaPlugin {
|
||||
private CRCore core;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
core = new CRCore(this).enable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (core != null) core.disable();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
`CRCoreConfig` en builder :
|
||||
|
||||
```java
|
||||
new CRCore(this, new CRCoreConfig()
|
||||
.withSqliteFile("mygame.db") // défaut : crcore.db
|
||||
.withCommandName("game")) // défaut : core
|
||||
.enable();
|
||||
```
|
||||
|
||||
`withInMemoryStorage()` désactive SQLite (tests, ou contexte stateless).
|
||||
|
||||
### Override de la construction des services
|
||||
|
||||
Sous-classer `CRCore` et redéfinir :
|
||||
- `buildTeamService(repo)` — pour utiliser une impl custom du service team
|
||||
- `buildPlayerProfileService(repo)` — idem player
|
||||
- `buildCoreCommand(...)` — pour ajouter des groupes top-level
|
||||
|
||||
### Diagramme
|
||||
|
||||
- Séquence : [bootstrap-sequence.puml](diagrams/bootstrap-sequence.puml)
|
||||
|
||||
---
|
||||
|
||||
## Backlog / idées de futurs domaines
|
||||
|
||||
_(à remplir — ex. score, profil joueur, gestion d'event/round, ...)_
|
||||
_(à remplir — ex. inventaires partagés d'équipe, kits, gestion de rounds, …)_
|
||||
|
||||
Reference in New Issue
Block a user