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:
+170
-115
@@ -4,10 +4,9 @@
|
||||
|
||||
- **Type** : librairie Java (`jar`) — pas un plugin Bukkit
|
||||
- **artifactId Maven** : `CR-Core`
|
||||
- **Build** : Maven
|
||||
- **Java** : 16
|
||||
- **Build** : Maven, Java 16
|
||||
- **API serveur (provided)** : Paper 1.16.5
|
||||
(`com.destroystokyo.paper:paper-api:1.16.5-R0.1-SNAPSHOT`)
|
||||
- **SQLite (compile)** : `org.xerial:sqlite-jdbc:3.45.3.0`
|
||||
- **Package racine** : `fr.luc.crcore`
|
||||
|
||||
## Dépôts Maven
|
||||
@@ -22,12 +21,12 @@
|
||||
mvn clean install
|
||||
```
|
||||
|
||||
Cela publie `fr.luc:CR-Core:1.0-SNAPSHOT` dans le repo Maven local `~/.m2/`,
|
||||
Publie `fr.luc:CR-Core:1.0-SNAPSHOT` dans le repo Maven local `~/.m2/`,
|
||||
prêt à être consommé par les plugins de jeu.
|
||||
|
||||
## Utilisation depuis un plugin de jeu
|
||||
## Intégration dans un plugin de jeu
|
||||
|
||||
Dans le `pom.xml` du plugin de jeu :
|
||||
### `pom.xml`
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
@@ -38,106 +37,111 @@ Dans le `pom.xml` du plugin de jeu :
|
||||
</dependency>
|
||||
```
|
||||
|
||||
> Scope `compile` (et non `provided`) **si** le plugin de jeu shade CR-Core dans
|
||||
> son propre jar (recommandé pour de la pure librairie). Pensez à utiliser un
|
||||
> `<relocation>` dans le `maven-shade-plugin` du plugin de jeu pour éviter les
|
||||
> conflits si plusieurs plugins shadent CR-Core sur le même serveur.
|
||||
Le plugin de jeu doit **shader** CR-Core dans son jar final (avec
|
||||
`maven-shade-plugin`) pour que sqlite-jdbc + le code du noyau soient bien
|
||||
embarqués sur le serveur.
|
||||
|
||||
Côté code du plugin de jeu :
|
||||
### `plugin.yml` du plugin de jeu
|
||||
|
||||
```yaml
|
||||
name: MyGame
|
||||
main: fr.exemple.mygame.MyGamePlugin
|
||||
version: 1.0
|
||||
api-version: 1.16
|
||||
commands:
|
||||
core:
|
||||
description: Commandes CR-Core
|
||||
# aliases optionnels — équivalents au point de vue Bukkit
|
||||
aliases: [cr, crcore]
|
||||
```
|
||||
|
||||
### Code minimal
|
||||
|
||||
```java
|
||||
public final class MyGamePlugin extends JavaPlugin {
|
||||
public class MyGamePlugin extends JavaPlugin {
|
||||
|
||||
private TeamService teamService;
|
||||
private CRCore core;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
// Instancie le service team (chaque plugin a son propre registre)
|
||||
this.teamService = new TeamServiceImpl(new InMemoryTeamRepository());
|
||||
// 1 ligne = SQLite + services + /core team ... opérationnels
|
||||
this.core = new CRCore(this).enable();
|
||||
|
||||
// Enregistre une commande basée sur le framework de CR-Core
|
||||
getCommand("team").setExecutor(new TeamCommand(teamService));
|
||||
getCommand("team").setTabCompleter(new TeamCommand(teamService));
|
||||
// Listener custom sur les events team
|
||||
getServer().getPluginManager().registerEvents(new MyTeamListener(), this);
|
||||
|
||||
// Table custom pour stocker des données spécifiques au jeu
|
||||
core.getDatabase().table("my_kills")
|
||||
.ifNotExists()
|
||||
.column("player_id", ColumnType.UUID).primaryKey()
|
||||
.column("kills", ColumnType.INTEGER).notNull().defaultValue("0")
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (core != null) core.disable();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Exemple minimal de commande :
|
||||
### Écouter les évènements
|
||||
|
||||
```java
|
||||
public class TeamCommand extends BaseCommand {
|
||||
public class MyTeamListener implements Listener {
|
||||
|
||||
public TeamCommand(TeamService service) {
|
||||
super("team", "teams", "t");
|
||||
description("Manage teams");
|
||||
addSubCommand(new TeamCreateSub(service));
|
||||
addSubCommand(new TeamInfoSub(service));
|
||||
@EventHandler
|
||||
public void onTeamCreate(TeamCreateEvent event) {
|
||||
Team team = event.getTeam();
|
||||
Bukkit.broadcastMessage("Nouvelle équipe : " + team.getName());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoinTeam(PlayerJoinTeamEvent event) {
|
||||
// Auto-join uniquement (chef qui ajoute = TeamMemberAddEvent)
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onScoreChange(TeamScoreChangeEvent event) {
|
||||
// event.getScoreName(), event.getOldValue(), event.getNewValue()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
public class TeamCreateSub extends SubCommand {
|
||||
### Overrider une commande par défaut
|
||||
|
||||
private final TeamService service;
|
||||
|
||||
public TeamCreateSub(TeamService service) {
|
||||
super("create", "c", "new");
|
||||
this.service = service;
|
||||
description("Create a new team");
|
||||
permission("mygame.team.create");
|
||||
playerOnly();
|
||||
argument("name", ArgumentTypes.STRING);
|
||||
argument("tag", ArgumentTypes.STRING);
|
||||
argument("color", ArgumentTypes.enumOf(TeamColor.class));
|
||||
```java
|
||||
public class MyCreateCommand extends TeamCreateSubCommand {
|
||||
public MyCreateCommand(TeamService service) {
|
||||
super(service);
|
||||
permission("mygame.team.create"); // permission custom
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandResult execute(CommandContext ctx) {
|
||||
Player player = ctx.requirePlayer();
|
||||
String name = ctx.get("name");
|
||||
String tag = ctx.get("tag");
|
||||
TeamColor color = ctx.get("color");
|
||||
try {
|
||||
Team team = service.createTeam(name, tag, color, player.getUniqueId());
|
||||
return CommandResult.success("Équipe " + team.getName() + " créée !");
|
||||
} catch (TeamException ex) {
|
||||
return CommandResult.failure(ex.getMessage());
|
||||
}
|
||||
// logique custom
|
||||
return super.execute(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// Dans onEnable() :
|
||||
core.getCoreCommand().findSubCommand("team")
|
||||
.ifPresent(team -> team.replaceSubCommand("create", new MyCreateCommand(core.getTeamService())));
|
||||
```
|
||||
|
||||
Et dans le `plugin.yml` du plugin de jeu :
|
||||
|
||||
```yaml
|
||||
name: MyGame
|
||||
main: fr.luc.mygame.MyGamePlugin
|
||||
version: 1.0
|
||||
api-version: 1.16
|
||||
commands:
|
||||
team:
|
||||
description: Manage teams
|
||||
aliases: [teams, t]
|
||||
```
|
||||
|
||||
## Override
|
||||
|
||||
Toute classe du noyau peut être étendue :
|
||||
### Stocker / récupérer des données custom via SQLite
|
||||
|
||||
```java
|
||||
public class LoggingTeamService extends TeamServiceImpl {
|
||||
Database db = core.getDatabase();
|
||||
|
||||
public LoggingTeamService(TeamRepository repo) { super(repo); }
|
||||
db.update("INSERT OR REPLACE INTO my_kills (player_id, kills) VALUES (?, ?)",
|
||||
playerUuid, 42);
|
||||
|
||||
@Override
|
||||
protected void onAfterCreate(Team team) {
|
||||
getPlugin().getLogger().info("Team created: " + team.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Team newTeam(UUID id, String name, String tag, TeamColor color, UUID leaderId) {
|
||||
return new MyCustomTeam(id, name, tag, color, leaderId);
|
||||
}
|
||||
}
|
||||
int kills = db.queryOne(
|
||||
"SELECT kills FROM my_kills WHERE player_id = ?",
|
||||
rs -> rs.getInt("kills"),
|
||||
playerUuid
|
||||
).orElse(0);
|
||||
```
|
||||
|
||||
## Arborescence du projet
|
||||
@@ -146,61 +150,112 @@ public class LoggingTeamService extends TeamServiceImpl {
|
||||
CitesPlugin/ # dossier IntelliJ (renommer plus tard si voulu)
|
||||
├── pom.xml
|
||||
├── GEMINI.md
|
||||
├── docs/ # source de vérité
|
||||
├── docs/
|
||||
│ ├── README.md
|
||||
│ ├── setup.md
|
||||
│ ├── features.md
|
||||
│ ├── decisions.md
|
||||
│ └── diagrams/
|
||||
│ ├── team-class-diagram.puml
|
||||
│ ├── team-create-sequence.puml
|
||||
│ ├── team-create-activity.puml
|
||||
│ └── command-class-diagram.puml
|
||||
│ └── diagrams/*.puml
|
||||
└── src/main/java/fr/luc/crcore/
|
||||
├── common/ # abstractions partagées
|
||||
│ ├── Identifiable.java # interface
|
||||
│ ├── Named.java # interface
|
||||
│ ├── ScoreHolder.java # interface (impl. par Team et PlayerProfile)
|
||||
│ ├── AbstractEntity.java # abstract class
|
||||
│ └── Repository.java # interface
|
||||
├── command/ # framework de commandes
|
||||
│ ├── Command.java # interface
|
||||
│ ├── AbstractCommand.java # base partagée
|
||||
│ ├── BaseCommand.java # commande top-level (Bukkit-aware)
|
||||
│ ├── SubCommand.java # sous-commande
|
||||
├── CRCore.java # bootstrap orchestrator
|
||||
├── CRCoreConfig.java # config (sqlite, command name, …)
|
||||
├── common/
|
||||
│ ├── Identifiable.java
|
||||
│ ├── Named.java
|
||||
│ ├── ScoreHolder.java # contrat partagé Team + PlayerProfile
|
||||
│ ├── AbstractEntity.java
|
||||
│ └── Repository.java
|
||||
├── database/ # wrapper SQLite
|
||||
│ ├── Database.java
|
||||
│ ├── TableBuilder.java
|
||||
│ ├── ColumnType.java
|
||||
│ ├── RowMapper.java
|
||||
│ └── DatabaseException.java
|
||||
├── command/ # framework
|
||||
│ ├── Command.java (interface)
|
||||
│ ├── AbstractCommand.java # base partagée, nested sub-commands
|
||||
│ ├── BaseCommand.java # top-level Bukkit-aware
|
||||
│ ├── SubCommand.java
|
||||
│ ├── CommandContext.java
|
||||
│ ├── CommandResult.java
|
||||
│ ├── CommandException.java
|
||||
│ ├── ArgumentType.java
|
||||
│ ├── ArgumentTypes.java # STRING, INTEGER, BOOLEAN, ONLINE_PLAYER, enumOf, choice
|
||||
│ └── ArgumentDef.java # package-private
|
||||
├── team/ # domaine team
|
||||
│ ├── ArgumentTypes.java
|
||||
│ ├── ArgumentDef.java # package-private
|
||||
│ └── builtin/ # commandes prêtes à l'emploi
|
||||
│ ├── CoreCommand.java # /core
|
||||
│ └── team/
|
||||
│ ├── TeamGroupSubCommand.java # /core team (container)
|
||||
│ ├── TeamArgumentTypes.java # ArgumentType<Team> avec tab-complete
|
||||
│ ├── TeamCreateSubCommand.java # /core team create
|
||||
│ ├── TeamDeleteSubCommand.java # /core team delete
|
||||
│ ├── TeamAddSubCommand.java # /core team add
|
||||
│ ├── TeamRemoveSubCommand.java # /core team remove
|
||||
│ ├── TeamJoinSubCommand.java # /core team join
|
||||
│ ├── TeamLeaveSubCommand.java # /core team leave
|
||||
│ ├── TeamInfoSubCommand.java # /core team info
|
||||
│ ├── TeamListSubCommand.java # /core team list
|
||||
│ ├── TeamTransferSubCommand.java # /core team transfer
|
||||
│ ├── TeamVisibilitySubCommand.java # /core team visibility
|
||||
│ ├── TeamScoreSubCommand.java # /core team score (admin)
|
||||
│ ├── TeamTopSubCommand.java # /core team top
|
||||
│ └── TeamSetSpawnSubCommand.java # /core team setspawn
|
||||
├── team/
|
||||
│ ├── Team.java
|
||||
│ ├── TeamMember.java
|
||||
│ ├── TeamRole.java # enum
|
||||
│ ├── TeamColor.java # enum
|
||||
│ ├── TeamVisibility.java # enum (PUBLIC / PRIVATE)
|
||||
│ ├── TeamRanking.java # record (rank, team, score)
|
||||
│ ├── TeamRepository.java # interface
|
||||
│ ├── InMemoryTeamRepository.java # impl
|
||||
│ ├── TeamService.java # interface
|
||||
│ ├── TeamServiceImpl.java # impl avec hooks overridables
|
||||
│ ├── TeamRole.java
|
||||
│ ├── TeamColor.java
|
||||
│ ├── TeamVisibility.java
|
||||
│ ├── TeamRanking.java # record
|
||||
│ ├── TeamRepository.java
|
||||
│ ├── InMemoryTeamRepository.java
|
||||
│ ├── SqliteTeamRepository.java
|
||||
│ ├── TeamService.java
|
||||
│ ├── TeamServiceImpl.java
|
||||
│ ├── BukkitEventFiringTeamServiceImpl.java # impl par défaut
|
||||
│ ├── TeamException.java
|
||||
│ ├── TeamAlreadyExistsException.java
|
||||
│ ├── TeamNotFoundException.java
|
||||
│ └── TeamAccessException.java # auto-join refusé
|
||||
└── player/ # domaine player
|
||||
├── PlayerProfile.java # entité, scores par joueur
|
||||
├── PlayerRanking.java # record (rank, profile, score)
|
||||
├── PlayerProfileRepository.java # interface
|
||||
│ ├── TeamAccessException.java
|
||||
│ └── event/ # Bukkit events team
|
||||
│ ├── TeamEvent.java # base
|
||||
│ ├── TeamCreateEvent.java
|
||||
│ ├── TeamDissolveEvent.java
|
||||
│ ├── TeamMemberAddEvent.java
|
||||
│ ├── TeamMemberRemoveEvent.java
|
||||
│ ├── PlayerJoinTeamEvent.java
|
||||
│ ├── TeamLeadershipTransferEvent.java
|
||||
│ ├── TeamVisibilityChangeEvent.java
|
||||
│ ├── TeamScoreChangeEvent.java
|
||||
│ └── TeamSpawnPointChangeEvent.java
|
||||
└── player/
|
||||
├── PlayerProfile.java
|
||||
├── PlayerRanking.java
|
||||
├── PlayerProfileRepository.java
|
||||
├── InMemoryPlayerProfileRepository.java
|
||||
├── PlayerProfileService.java # interface
|
||||
├── PlayerProfileServiceImpl.java # impl avec hooks overridables
|
||||
├── SqlitePlayerProfileRepository.java
|
||||
├── PlayerProfileService.java
|
||||
├── PlayerProfileServiceImpl.java
|
||||
├── BukkitEventFiringPlayerProfileServiceImpl.java
|
||||
├── PlayerException.java
|
||||
└── PlayerProfileNotFoundException.java
|
||||
├── PlayerProfileNotFoundException.java
|
||||
└── event/ # Bukkit events player
|
||||
├── PlayerProfileEvent.java
|
||||
├── PlayerProfileCreateEvent.java
|
||||
├── PlayerProfileDeleteEvent.java
|
||||
└── PlayerScoreChangeEvent.java
|
||||
```
|
||||
|
||||
> **Note IntelliJ** : le dossier physique s'appelle encore `CitesPlugin/`. Pour
|
||||
> le renommer en `CR-Core/`, fermer IntelliJ, renommer, rouvrir. Le `pom.xml`
|
||||
> et les packages sont déjà à jour, le nom du dossier n'a aucun impact sur le
|
||||
> build.
|
||||
## Tables SQLite créées par CR-Core
|
||||
|
||||
Au premier `enable()`, les tables suivantes sont créées (en `IF NOT EXISTS`) :
|
||||
|
||||
- `crcore_teams` — métadonnées par équipe (id, name, tag, color, leader_id,
|
||||
visibility, spawn_world/x/y/z/yaw/pitch)
|
||||
- `crcore_team_members` — un membre = (team_id, player_id, role, joined_at)
|
||||
- `crcore_team_scores` — (team_id, score_name, value)
|
||||
- `crcore_player_profiles` — un profil = (id)
|
||||
- `crcore_player_scores` — (profile_id, score_name, value)
|
||||
|
||||
Le préfixe `crcore_` évite les collisions avec les tables custom du plugin
|
||||
de jeu.
|
||||
|
||||
Reference in New Issue
Block a user