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:
Antone Barbaud
2026-06-09 10:54:00 +02:00
parent ffc77c4213
commit c1b414f400
63 changed files with 3632 additions and 400 deletions
+41 -49
View File
@@ -1,5 +1,5 @@
@startuml command-class-diagram
title CR-Core — Command framework (class diagram)
title CR-Core — Command framework (class diagram, nested sub-commands)
skinparam classAttributeIconSize 0
hide empty members
@@ -13,7 +13,7 @@ package "fr.luc.crcore.command" {
+ isPlayerOnly(): boolean
+ getDescription(): String
+ execute(ctx: CommandContext): CommandResult
+ tabComplete(sender, argIndex, partial): List<String>
+ tabComplete(sender, args: String[]): List<String>
+ matches(label: String): boolean
}
@@ -25,46 +25,41 @@ package "fr.luc.crcore.command" {
- description: String
- usage: String
- arguments: List<ArgumentDef>
# addAlias(...): void
# permission(p): void
# playerOnly(): void
# description(d): void
# usage(u): void
# argument(name, type): void
# optionalArgument(name, type): void
# buildContext(sender, label, subArgs): CommandContext
+ getRequiredArgumentCount(): int
+ getTotalArgumentCount(): int
+ getUsage(): String
- subCommandsByName: Map<String, SubCommand>
- subCommandsByAlias: Map<String, SubCommand>
--
# addAlias(...) / permission / playerOnly / description / usage
# argument(name, type) / optionalArgument(name, type)
# addSubCommand(sub: SubCommand): void
--
+ findSubCommand(label): Optional<SubCommand>
+ getSubCommands(): Collection<SubCommand>
+ replaceSubCommand(name, newSub): Optional<SubCommand>
+ hasSubCommands(): boolean
--
+ dispatch(sender, label, args): CommandResult
+ tabComplete(sender, args): List<String>
+ execute(ctx): CommandResult
# listSubCommands(ctx): CommandResult
# checkAccess(sender): boolean
# buildContext(sender, label, rawArgs): CommandContext
}
abstract class BaseCommand {
- subCommandsByName: Map<String, SubCommand>
- subCommandsByAlias: Map<String, SubCommand>
# addSubCommand(sub: SubCommand): void
+ findSubCommand(label: String): Optional<SubCommand>
+ getSubCommands(): Collection<SubCommand>
# execute(ctx): CommandResult
+ onCommand(sender, cmd, label, args): boolean
+ onTabComplete(sender, cmd, alias, args): List<String>
# checkAccess(sender, target): boolean
# handleResult(sender, result): void
}
abstract class SubCommand {
+ {abstract} execute(ctx: CommandContext): CommandResult
}
abstract class SubCommand
class CommandContext {
- sender: CommandSender
- label: String
- rawArgs: String[]
- parsedArgs: Map<String, Object>
+ getSender(): CommandSender
+ isPlayer(): boolean
+ getPlayer(): Optional<Player>
+ requirePlayer(): Player
+ get(name: String): T
+ getSender / isPlayer / getPlayer / requirePlayer
+ get(name): T
+ getOptional(name): Optional<T>
+ has(name): boolean
+ reply(msg): void
@@ -73,15 +68,7 @@ package "fr.luc.crcore.command" {
class CommandResult {
- type: Type
- message: String
+ getType(): Type
+ getMessage(): String
+ isSuccess(): boolean
+ {static} success(): CommandResult
+ {static} success(msg): CommandResult
+ {static} failure(msg): CommandResult
+ {static} invalidUsage(): CommandResult
+ {static} noPermission(): CommandResult
+ {static} playerOnly(): CommandResult
+ {static} success / failure / invalidUsage / noPermission / playerOnly
}
enum "CommandResult.Type" as ResultType {
@@ -99,17 +86,13 @@ package "fr.luc.crcore.command" {
+ suggestions(sender, partial): List<String>
}
class ArgumentTypes << (S, #FFC107) static >> {
+ {static} STRING: ArgumentType<String>
+ {static} INTEGER: ArgumentType<Integer>
+ {static} DOUBLE: ArgumentType<Double>
+ {static} BOOLEAN: ArgumentType<Boolean>
+ {static} ONLINE_PLAYER: ArgumentType<Player>
+ {static} enumOf(type): ArgumentType<E>
+ {static} choice(choices): ArgumentType<String>
class ArgumentTypes <<utility>> {
+ STRING / INTEGER / DOUBLE / BOOLEAN / ONLINE_PLAYER
+ enumOf(Class<E>): ArgumentType<E>
+ choice(String...): ArgumentType<String>
}
class ArgumentDef << (P, #BBBBBB) package-private >> {
class ArgumentDef <<package-private>> {
- name: String
- type: ArgumentType<?>
- required: boolean
@@ -118,15 +101,24 @@ package "fr.luc.crcore.command" {
AbstractCommand ..|> Command
BaseCommand --|> AbstractCommand
SubCommand --|> AbstractCommand
BaseCommand "1" o-- "*" SubCommand : subCommands
BaseCommand ..|> "org.bukkit.command.CommandExecutor"
BaseCommand ..|> "org.bukkit.command.TabCompleter"
AbstractCommand "1" o-- "*" SubCommand : sub-commands\n(recursive)
AbstractCommand "1" *-- "*" ArgumentDef : arguments
ArgumentDef --> ArgumentType
CommandResult +-- ResultType
CommandException --|> RuntimeException
BaseCommand ..> CommandContext : creates
AbstractCommand ..> CommandContext : creates
SubCommand ..> CommandResult : returns
AbstractCommand ..> CommandResult : returns
}
note bottom of AbstractCommand
Le routage est récursif :
/core team create → CoreCommand.dispatch("team", ["create",...])
→ TeamGroup.dispatch("create", [...])
→ TeamCreate.execute(ctx)
end note
@enduml