docs: organize diagrams to mirror code layout (util/ + features/)

Move flat docs/diagrams/*.puml into a hierarchy matching the source
package structure:

  docs/diagrams/
  ├── bootstrap-sequence.puml         (cross-cutting)
  ├── events-diagram.puml             (cross-feature)
  ├── util/
  │   ├── command-class-diagram.puml
  │   ├── database-diagram.puml
  │   ├── messages-class-diagram.puml
  │   ├── broadcasts-class-diagram.puml
  │   └── gui-class-diagram.puml
  └── features/
      ├── team/
      │   ├── team-class-diagram.puml
      │   ├── team-config-class-diagram.puml
      │   ├── builtin-commands-diagram.puml
      │   ├── team-create-sequence.puml
      │   ├── team-create-activity.puml
      │   └── team-join-sequence.puml
      ├── player/
      │   └── player-class-diagram.puml
      └── moderation/
          └── moderation-class-diagram.puml

README.md diagram index split into 4 sections (overview, util,
features/team, features/player, features/moderation) for readability;
all links updated. features.md auto-updated by sed for the new paths.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Antone Barbaud
2026-06-10 13:58:48 +02:00
parent 4efaa5bbde
commit b02e532563
15 changed files with 53 additions and 27 deletions
@@ -0,0 +1,102 @@
@startuml broadcasts-class-diagram
title CR-Core — Broadcast service (class diagram)
skinparam classAttributeIconSize 0
hide empty members
package "fr.luc.crcore.util.broadcast" {
enum BroadcastAudience {
NONE
LEADER
TEAM
ADMIN
ALL
}
class BroadcastContext {
- team: Team
- involvedPlayerId: UUID
- placeholders: Map<String, String>
--
+ {static} of(team): BroadcastContext
+ {static} empty(): BroadcastContext
+ involving(playerId): BroadcastContext
+ with(key, value): BroadcastContext
+ getTeam(): Optional<Team>
+ getInvolvedPlayerId(): Optional<UUID>
+ getPlaceholders(): Map<String, String>
+ toPlaceholderPairs(): Object[]
}
interface BroadcastService {
+ broadcast(eventKey, context): void
+ getAudiences(eventKey): List<BroadcastAudience>
+ reload(): void
}
class CRCoreBroadcastListener {
- broadcasts: BroadcastService
+ registerOn(plugin: JavaPlugin): void
--
@ onTeamCreate(TeamCreateEvent)
@ onTeamDissolve(TeamDissolveEvent)
@ onTeamMemberAdd(TeamMemberAddEvent)
@ onTeamMemberRemove(TeamMemberRemoveEvent)
@ onPlayerJoinTeam(PlayerJoinTeamEvent)
@ onLeadershipTransfer(TeamLeadershipTransferEvent)
@ onVisibilityChange(TeamVisibilityChangeEvent)
@ onTeamScoreChange(TeamScoreChangeEvent)
@ onTeamSpawnChange(TeamSpawnPointChangeEvent)
@ onProfileCreate(PlayerProfileCreateEvent)
@ onProfileDelete(PlayerProfileDeleteEvent)
@ onPlayerScoreChange(PlayerScoreChangeEvent)
}
CRCoreBroadcastListener ..|> "org.bukkit.event.Listener"
package "fr.luc.crcore.util.broadcast.impl" {
class YamlBroadcastService {
- plugin: JavaPlugin
- messages: MessagesService
- defaults: Map<String, List<BroadcastAudience>>
- audiences: Map<String, List<BroadcastAudience>>
- userFile: File
--
+ YamlBroadcastService(plugin, messages)
- loadDefaultsFromResource(): void
- ensureUserFile(): void
- rebuildEffectiveAudiences(): void
- resolveRecipients(list, ctx): Set<Player>
}
YamlBroadcastService ..|> BroadcastService
}
BroadcastService ..> BroadcastContext : consumes
BroadcastContext --> BroadcastAudience
YamlBroadcastService --> "fr.luc.crcore.util.message.MessagesService" : reads templates
CRCoreBroadcastListener --> BroadcastService : delegates
CRCoreBroadcastListener ..> BroadcastContext : builds
}
package "fr.luc.crcore" {
class CRCore {
+ broadcasts(): BroadcastService
# buildBroadcastService(messages): BroadcastService
}
CRCore "1" *-- "1" BroadcastService : owns
CRCore ..> CRCoreBroadcastListener : registers
}
note bottom of YamlBroadcastService
Modèle "un seul fichier par plugin" :
Sources en mémoire :
1. crcore-broadcasts.yml ← jar (fallback)
2. <plugin>-broadcasts.yml ← dataFolder (édité par l'admin)
Séparation routes / templates :
- Routes = ce fichier (qui reçoit quoi)
- Templates = MessagesService (clés <event>.broadcast)
end note
@enduml
@@ -0,0 +1,124 @@
@startuml command-class-diagram
title CR-Core — Command framework (class diagram, nested sub-commands)
skinparam classAttributeIconSize 0
hide empty members
package "fr.luc.crcore.util.command" {
interface Command {
+ getName(): String
+ getAliases(): List<String>
+ getPermission(): String
+ isPlayerOnly(): boolean
+ getDescription(): String
+ execute(ctx: CommandContext): CommandResult
+ tabComplete(sender, args: String[]): List<String>
+ matches(label: String): boolean
}
abstract class AbstractCommand {
- name: String
- aliases: List<String>
- permission: String
- playerOnly: boolean
- description: String
- usage: String
- arguments: List<ArgumentDef>
- 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 {
+ onCommand(sender, cmd, label, args): boolean
+ onTabComplete(sender, cmd, alias, args): List<String>
# handleResult(sender, result): void
}
abstract class SubCommand
class CommandContext {
- sender: CommandSender
- label: String
- rawArgs: String[]
- parsedArgs: Map<String, Object>
+ getSender / isPlayer / getPlayer / requirePlayer
+ get(name): T
+ getOptional(name): Optional<T>
+ has(name): boolean
+ reply(msg): void
}
class CommandResult {
- type: Type
- message: String
+ {static} success / failure / invalidUsage / noPermission / playerOnly
}
enum "CommandResult.Type" as ResultType {
SUCCESS
FAILURE
INVALID_USAGE
NO_PERMISSION
PLAYER_ONLY
}
class CommandException
interface "ArgumentType<T>" as ArgumentType {
+ parse(input: String): T
+ suggestions(sender, partial): List<String>
}
class ArgumentTypes <<utility>> {
+ STRING / INTEGER / DOUBLE / BOOLEAN / ONLINE_PLAYER
+ enumOf(Class<E>): ArgumentType<E>
+ choice(String...): ArgumentType<String>
}
class ArgumentDef <<package-private>> {
- name: String
- type: ArgumentType<?>
- required: boolean
}
AbstractCommand ..|> Command
BaseCommand --|> AbstractCommand
SubCommand --|> AbstractCommand
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
AbstractCommand ..> CommandContext : creates
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
+86
View File
@@ -0,0 +1,86 @@
@startuml database-diagram
title CR-Core — Database (SQLite wrapper)
skinparam classAttributeIconSize 0
hide empty members
package "fr.luc.crcore.util.database" {
class Database {
- connection: Connection
+ Database(file: File)
+ execute(sql, params...): void
+ update(sql, params...): int
+ queryOne(sql, mapper, params...): Optional<T>
+ query(sql, mapper, params...): List<T>
+ inTransaction(block: Runnable): void
+ table(name: String): TableBuilder
+ tableExists(name: String): boolean
+ getConnection(): Connection
+ close(): void
}
Database ..|> "java.lang.AutoCloseable"
class TableBuilder {
- database: Database
- name: String
- columns: List<ColumnDef>
- ifNotExists: boolean
+ ifNotExists(): TableBuilder
+ column(name, type): ColumnDef
+ create(): void
}
class "TableBuilder.ColumnDef" as ColumnDef {
- name: String
- type: ColumnType
- primaryKey: boolean
- notNull: boolean
- unique: boolean
- defaultValue: String
+ primaryKey(): ColumnDef
+ notNull(): ColumnDef
+ unique(): ColumnDef
+ defaultValue(expr: String): ColumnDef
+ column(name, type): ColumnDef
+ create(): void
}
enum ColumnType {
INTEGER
REAL
TEXT
BLOB
BOOLEAN
UUID
--
+ getSqlType(): String
}
interface "RowMapper<T>" as RowMapper {
+ map(rs: ResultSet): T
}
class DatabaseException
DatabaseException --|> RuntimeException
Database "1" *-- "*" TableBuilder : creates
TableBuilder "1" *-- "*" ColumnDef : contains
ColumnDef --> ColumnType : type
Database ..> RowMapper : uses
Database ..> DatabaseException : throws
}
note right of Database
Repositories SQLite de CR-Core
(SqliteTeamRepository,
SqlitePlayerProfileRepository)
utilisent Database pour
persister state team/player.
Les plugins de jeu utilisent
Database.table(...) pour
créer leurs tables custom.
end note
@enduml
+82
View File
@@ -0,0 +1,82 @@
@startuml gui-class-diagram
title CR-Core — GUI framework (class diagram, réutilisable)
skinparam classAttributeIconSize 0
hide empty members
package "fr.luc.crcore.util.gui" {
abstract class AbstractInventoryGui {
- inventory: Inventory
- handlers: Map<Integer, GuiClickHandler>
--
# setInventory(Inventory): void
+ getInventory(): Inventory
+ {abstract} rebuild(): void
+ onClose(HumanEntity): void
+ openTo(HumanEntity): void
# setButton(slot, item, handler): void
# setDecoration(slot, item): void
# clearSlot(slot): void
+ handleClick(event): void ' appelé par GuiListener
+ handleClose(event): void
}
AbstractInventoryGui ..|> "org.bukkit.inventory.InventoryHolder"
interface GuiClickHandler <<FunctionalInterface>> {
+ onClick(InventoryClickEvent): void
}
class GuiListener {
+ registerOn(JavaPlugin): void
--
@ onClick(InventoryClickEvent)
@ onClose(InventoryCloseEvent)
}
GuiListener ..|> "org.bukkit.event.Listener"
class GuiItems <<utility>> {
+ {static} named(material, name): Builder
+ {static} of(material): Builder
+ {static} filler(): ItemStack
+ {static} item(builder): ItemStack
}
class "GuiItems.Builder" as Builder {
- stack: ItemStack
- meta: ItemMeta
+ name(text): Builder
+ lore(lines...): Builder
+ lore(List<String>): Builder
+ amount(int): Builder
+ build(): ItemStack
+ asItem(): ItemStack
}
GuiItems +-- Builder
GuiListener ..> AbstractInventoryGui : dispatches via getHolder()
AbstractInventoryGui --> GuiClickHandler : per-slot
}
note right of GuiListener
Détection par holder :
if (e.getInventory().getHolder()
instanceof AbstractInventoryGui gui) {
e.setCancelled(true); // ← TOUJOURS, même slot vide
gui.handleClick(e);
}
Enregistré une fois dans CRCore.enable().
end note
note right of AbstractInventoryGui
Pattern :
1. extends AbstractInventoryGui
2. constructeur :
Inventory inv = Bukkit.createInventory(this, 27, "&eTitre");
setInventory(inv);
3. override rebuild() pour peindre
4. setButton(slot, GuiItems.named(...).build(), handler)
end note
@enduml
@@ -0,0 +1,63 @@
@startuml messages-class-diagram
title CR-Core — Messages service (class diagram)
skinparam classAttributeIconSize 0
hide empty members
package "fr.luc.crcore.util.message" {
interface MessagesService {
+ get(key, placeholderPairs...): String
+ raw(key): String
+ has(key): boolean
+ set(key, template): void
+ reload(): void
+ loadAdditional(resourceName): void
+ setApplyColorCodes(enabled): void
+ isApplyColorCodes(): boolean
+ getUserFile(): File
}
class YamlMessagesService {
- plugin: JavaPlugin
- defaults: Map<String, String> ' in-memory (jar resource)
- messages: Map<String, String> ' effective (defaults + user file)
- userFileName: String ' <plugin>-messages.yml
- userFile: File
- applyColorCodes: boolean
--
+ YamlMessagesService(plugin: JavaPlugin)
--
- loadDefaultsFromResource(): void ' charge crcore-messages.yml (jar)
- ensureUserFile(): void ' copie template si absent
- rebuildEffectiveMessages(): void ' defaults + user file
- {static} flatten(section, prefix, out): void
}
YamlMessagesService ..|> MessagesService
}
package "fr.luc.crcore" {
class CRCore {
+ messages(): MessagesService
# buildMessagesService(): MessagesService ' override point
}
CRCore "1" *-- "1" MessagesService : owns
}
note bottom of YamlMessagesService
Modèle "un seul fichier par plugin" :
Sources en mémoire (la 2e écrase la 1ère sur clés communes) :
1. crcore-messages.yml ← jar (toujours présent, fallback)
2. <plugin>-messages.yml ← dataFolder (édité par l'admin)
Création du fichier user au 1er boot :
Priorité 1 : ressource du plugin de jeu sous le même nom
Priorité 2 : copie des defaults CR-Core
Placeholders : {name} via varargs key/value
Codes couleur : &a → §a (toggle)
end note
@enduml