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:
@@ -232,3 +232,93 @@ Format léger : une décision = un titre + contexte + choix + raison.
|
||||
pour ~8 méthodes simples. Choix actuel : duplication contrôlée des
|
||||
implémentations (Map + getters/setters), interface commune pour le
|
||||
contrat.
|
||||
|
||||
## 2026-06-09 — CRCore = bootstrap library, pas un plugin
|
||||
|
||||
- **Choix** : CR-Core reste une **librairie** (pas de `plugin.yml`, pas de
|
||||
`JavaPlugin`). Le plugin de jeu downstream instancie `new CRCore(this)` dans
|
||||
son `onEnable()` et appelle `.enable()` — c'est ce qui câble SQLite,
|
||||
services, commandes et events.
|
||||
- **Alternative écartée** : faire de CR-Core un plugin standalone (à
|
||||
installer côté serveur). Refusé pour deux raisons : (1) chaque jeu a son
|
||||
propre état (registre d'équipes, scores) — on ne veut pas partager entre
|
||||
jeux par défaut ; (2) la friction de déploiement (2 jars sur le serveur)
|
||||
est inutile pour des plugins shadés.
|
||||
- **Conséquence** : chaque plugin de jeu shade CR-Core, a sa propre DB SQLite
|
||||
dans son `dataFolder`, et déclare la commande Bukkit racine (`core` par
|
||||
défaut) dans son `plugin.yml`.
|
||||
|
||||
## 2026-06-09 — Sous-commandes imbriquées récursives
|
||||
|
||||
- **Choix** : `AbstractCommand` porte la table des sous-commandes
|
||||
(pas seulement `BaseCommand`). `SubCommand` peut donc avoir ses propres
|
||||
sous-commandes (récursion). Routage via la méthode `dispatch(...)`
|
||||
récursive.
|
||||
- **Raison** : c'est ce qui permet `/core team create` (3 niveaux : root /
|
||||
group / leaf). Sans ça, il faudrait flatter en `/core team-create` ou faire
|
||||
du routage manuel dans chaque groupe.
|
||||
- **Conséquence** : `BaseCommand` ne fait plus que pont Bukkit
|
||||
(`CommandExecutor`/`TabCompleter` → `dispatch`) ; toute la logique de
|
||||
routage vit dans `AbstractCommand`.
|
||||
|
||||
## 2026-06-09 — Override par sous-classe + `replaceSubCommand`
|
||||
|
||||
- **Choix** : `AbstractCommand.replaceSubCommand(name, newSub)` permet aux
|
||||
plugins de jeu de remplacer une feuille (ex. `TeamCreateSubCommand`) par
|
||||
leur propre implémentation, sans tout recâbler.
|
||||
- **Raison** : le user a explicitement demandé "Les futures plugins ne
|
||||
feront qu'override les fonctions si besoin". Cette méthode + le fait que
|
||||
les classes ne soient pas `final` couvre les deux patterns :
|
||||
- **Remplacement par instance** : `team.replaceSubCommand("create", new MyCreate(svc))`
|
||||
- **Override par héritage** : `extends TeamCreateSubCommand` + `super.execute(ctx)`
|
||||
|
||||
## 2026-06-09 — Évènements Bukkit : post-only, non-cancellable
|
||||
|
||||
- **Choix** : tous les évènements CR-Core (team + player) sont **post-events**,
|
||||
tirés via les hooks `on*` après commit. Aucun n'implémente `Cancellable`.
|
||||
- **Raison** : la validation pré-action vit côté service dans les hooks
|
||||
`validate*` (overridables). Mélanger pré-cancellable côté event et hooks
|
||||
côté service dédoublerait les points de blocage. Pour bloquer un comportement,
|
||||
le pattern est : override le hook `validate*` du service.
|
||||
- **Boilerplate** : chaque event a sa propre `HandlerList` statique
|
||||
(contrainte Bukkit, pas de moyen de partager via héritage). 12 events =
|
||||
12 occurrences du même pattern, accepté pour rester idiomatique Bukkit.
|
||||
|
||||
## 2026-06-09 — SQLite write-through cache pour les repositories
|
||||
|
||||
- **Choix** : `SqliteTeamRepository` et `SqlitePlayerProfileRepository`
|
||||
**étendent** leurs jumeaux `InMemory*` et overrident `save`/`delete` pour
|
||||
persister synchronement vers SQLite. Au démarrage, `loadAll()` recharge
|
||||
tout le state depuis la DB dans le cache mémoire.
|
||||
- **Raison** : les lectures (findAll, findByName, classements en
|
||||
`Collection<Team>.stream().sorted(...)`) restent rapides — pas de hit DB.
|
||||
Les écritures vont en DB synchronement (acceptable au rythme des actions
|
||||
joueur, qui sont rares à l'échelle d'un event entre amis).
|
||||
- **Approche delete + reinsert pour les collections** : sur `save()`, on
|
||||
remplace en bloc les `team_members` et `team_scores` d'une équipe (DELETE
|
||||
puis INSERT). Plus simple et moins bug-prone qu'un diff fin, et négligeable
|
||||
en perf pour des équipes de quelques joueurs.
|
||||
|
||||
## 2026-06-09 — Type Database minimaliste plutôt qu'un ORM
|
||||
|
||||
- **Choix** : `Database` expose 4 méthodes (execute / update / queryOne /
|
||||
query) + un `TableBuilder` fluide. Pas d'ORM, pas d'annotations
|
||||
d'entités, pas de DSL SQL.
|
||||
- **Raison** : un ORM ajouterait une dépendance lourde (Hibernate / jOOQ /
|
||||
…), un poids de classloading non négligeable côté serveur Bukkit, et
|
||||
abstrairait des opérations qu'on veut garder triviales et lisibles. SQL
|
||||
brut + PreparedStatement est largement suffisant pour les volumes d'un
|
||||
serveur d'event.
|
||||
- **Le `TableBuilder`** existe pour répondre au "pouvoir rapidement et
|
||||
simplement créer des tables" — c'est l'API la plus user-friendly à proposer.
|
||||
Pour les cas avancés (FOREIGN KEY, contraintes composites), l'utilisateur
|
||||
passe par `db.execute("CREATE TABLE ...")` direct.
|
||||
|
||||
## 2026-06-09 — Préfixe `crcore_` sur toutes les tables internes
|
||||
|
||||
- **Choix** : `crcore_teams`, `crcore_team_members`, `crcore_team_scores`,
|
||||
`crcore_player_profiles`, `crcore_player_scores`.
|
||||
- **Raison** : éviter les collisions avec les tables custom que les plugins
|
||||
de jeu créent dans la même DB. CR-Core et le plugin de jeu partagent le
|
||||
même fichier SQLite (par défaut `<dataFolder>/crcore.db`) ; le préfixe
|
||||
isole proprement.
|
||||
|
||||
Reference in New Issue
Block a user