Renamed with git mv to preserve history. Refreshed the content to match the current state of the project: - Java 11 build target (was 16) — explicit warning not to reintroduce records / pattern matching / switch expressions. - New three-layer architecture: util/ (always on) + features/ (opt-in via setupX()) + builtin/ (top-level commands). - New features documented: messages, broadcasts, GUI framework, team settings, moderation skeleton. - Diagrams layout: util/ + features/<feature>/ + cross-cutting at root. - File generation pattern documented: <plugin>-messages.yml, <plugin>-broadcasts.yml, <plugin>-team-config.yml in dataFolder. - Maven build + deploy commands and Gitea Packages publishing URL. - Git workflow conventions: HTTPS URL with token, co-author trailer. setup.md tree updated (GEMINI.md → CLAUDE.md reference). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
12 KiB
Setup technique
Stack
- Type : librairie Java (
jar) — pas un plugin Bukkit - artifactId Maven :
CR-Core - Build : Maven, Java 11
- Intégrations optionnelles : PlaceholderAPI (auto-détectée si installée)
- API serveur (provided) : Paper 1.16.5
- SQLite (compile) :
org.xerial:sqlite-jdbc:3.45.3.0 - Package racine :
fr.luc.crcore
Dépôts Maven
papermc— https://repo.papermc.io/repository/maven-public/spigot-repo— https://hub.spigotmc.org/nexus/content/repositories/snapshots/sonatype— https://oss.sonatype.org/content/groups/public/
Build & install local
mvn clean install
Publie fr.luc:CR-Core:1.0-SNAPSHOT dans le repo Maven local ~/.m2/,
prêt à être consommé par les plugins de jeu.
Intégration dans un plugin de jeu
pom.xml
<dependency>
<groupId>fr.luc</groupId>
<artifactId>CR-Core</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
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.
plugin.yml du plugin de jeu
name: MyGame
main: fr.exemple.mygame.MyGamePlugin
version: 1.0
api-version: 1.16
Pas besoin de déclarer la commande
core: CR-Core l'enregistre dynamiquement via leCommandMapdu serveur quand elle est absente du plugin.yml. Si tu préfères la déclarer quand même (pour customiser la description ou les aliases côté Bukkit), tu peux ajouter :commands: core: description: Commandes CR-Core aliases: [cr, crcore]Dans ce cas, CR-Core détecte la commande déclarée et s'y branche normalement via
setExecutor(pas d'enregistrement dynamique).
Code minimal — setup modulaire
⚠️ Important : depuis 2026-06-10, les features (teams, players, placeholders) sont opt-in via
CRCoreConfig.setupX(). Une instancenew CRCoreConfig()n'active rien par défaut — il faut appelersetupAll()ou lessetupX()individuels.
public class MyGamePlugin extends JavaPlugin {
private CRCore core;
@Override
public void onEnable() {
// OPTION A — tout activer en une ligne (teams + players + placeholders)
this.core = new CRCore(this, new CRCoreConfig().setupAll()).enable();
// OPTION B — granularité, n'activer que ce qu'on veut
// this.core = new CRCore(this, new CRCoreConfig()
// .setupTeams()
// .setupPlaceholders() // pas de players
// .setupModeration()) // ajoute le mod mode
// .enable();
// OPTION C — par défaut + options
// this.core = new CRCore(this, new CRCoreConfig()
// .withSqliteFile("mygame.db")
// .withCommandName("game")
// .setupTeams())
// .enable();
// Listener custom sur les events team (nécessite setupTeams)
getServer().getPluginManager().registerEvents(new MyTeamListener(), this);
// Table custom pour stocker des données spécifiques au jeu
// (database util est toujours disponible)
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();
}
}
Écouter les évènements
public class MyTeamListener implements Listener {
@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()
}
}
Overrider une commande par défaut
public class MyCreateCommand extends TeamCreateSubCommand {
public MyCreateCommand(TeamService service) {
super(service);
permission("mygame.team.create"); // permission custom
}
@Override
public CommandResult execute(CommandContext ctx) {
// logique custom
return super.execute(ctx);
}
}
// Dans onEnable() :
core.getCoreCommand().findSubCommand("team")
.ifPresent(team -> team.replaceSubCommand("create", new MyCreateCommand(core.getTeamService())));
Stocker / récupérer des données custom via SQLite
Database db = core.getDatabase();
db.update("INSERT OR REPLACE INTO my_kills (player_id, kills) VALUES (?, ?)",
playerUuid, 42);
int kills = db.queryOne(
"SELECT kills FROM my_kills WHERE player_id = ?",
rs -> rs.getInt("kills"),
playerUuid
).orElse(0);
Arborescence du projet
CitesPlugin/ # dossier IntelliJ (renommer plus tard si voulu)
├── pom.xml
├── CLAUDE.md
├── docs/
│ ├── README.md
│ ├── setup.md
│ ├── features.md
│ ├── decisions.md
│ └── diagrams/*.puml
└── src/main/java/fr/luc/crcore/
├── CRCore.java # bootstrap (setupAll/setupTeams/...)
├── CRCoreConfig.java # config builder + flags features
├── builtin/ # commandes top-level (toutes features)
│ ├── CoreCommand.java # /core
│ └── CoreReloadSubCommand.java # /core reload
├── util/ # ◆ couche util — toujours active ◆
│ ├── common/ # abstractions partagées
│ │ ├── Identifiable.java
│ │ ├── Named.java
│ │ ├── ScoreHolder.java
│ │ ├── AbstractEntity.java
│ │ └── Repository.java
│ ├── database/ # wrapper SQLite générique
│ │ ├── Database.java
│ │ ├── TableBuilder.java
│ │ ├── ColumnType.java
│ │ ├── RowMapper.java
│ │ └── DatabaseException.java
│ ├── command/ # framework de commandes
│ │ ├── Command.java (interface)
│ │ ├── AbstractCommand.java
│ │ ├── BaseCommand.java
│ │ ├── SubCommand.java
│ │ ├── CommandContext.java
│ │ ├── CommandResult.java
│ │ ├── CommandException.java
│ │ ├── ArgumentType.java
│ │ ├── ArgumentTypes.java
│ │ └── ArgumentDef.java (package-private)
│ ├── message/ # service messages YAML
│ │ ├── MessagesService.java
│ │ └── impl/YamlMessagesService.java
│ ├── broadcast/ # service broadcasts YAML + listener
│ │ ├── BroadcastService.java
│ │ ├── BroadcastAudience.java
│ │ ├── BroadcastContext.java
│ │ ├── CRCoreBroadcastListener.java
│ │ └── impl/YamlBroadcastService.java
│ ├── gui/ # framework GUI (InventoryHolder)
│ │ ├── AbstractInventoryGui.java
│ │ ├── GuiClickHandler.java
│ │ ├── GuiListener.java
│ │ └── GuiItems.java
│ └── placeholder/ # PAPI expansion (opt-in)
│ └── CRCorePlaceholderExpansion.java
└── features/ # ◆ features opt-in via setupX() ◆
├── team/ (setupTeams())
│ ├── Team.java, TeamMember, enums, TeamRanking
│ ├── TeamService.java (interface)
│ ├── TeamRepository.java (interface)
│ ├── event/ (9 events Bukkit)
│ ├── exception/ (4 exceptions)
│ ├── impl/ (TeamServiceImpl, BukkitEventFiring*, InMemory*, Sqlite*)
│ ├── config/ (settings typés)
│ │ ├── TeamSetting.java
│ │ ├── TeamSettings.java (registry)
│ │ ├── TeamConfigService.java
│ │ ├── impl/YamlTeamConfigService.java
│ │ └── gui/ (Global + TeamSettingsGui)
│ │ ├── AbstractSettingsGui.java
│ │ ├── GlobalSettingsGui.java
│ │ └── TeamSettingsGui.java
│ └── command/ (14 sous-commandes /core team)
│ ├── TeamGroupSubCommand.java
│ ├── TeamArgumentTypes.java
│ └── Team*SubCommand.java
└── player/ (setupPlayers())
├── PlayerProfile.java
├── PlayerRanking.java
├── PlayerProfileService.java (interface)
├── PlayerProfileRepository.java (interface)
├── event/ (3 events)
├── exception/ (2 exceptions)
└── impl/ (services + repositories)
Fichiers de config générés au premier enable()
Au premier démarrage, CR-Core crée TROIS fichiers dans le dataFolder :
| Fichier | Rôle |
|---|---|
<plugin-name-lowercase>-messages.yml |
Templates de tous les messages (commandes + broadcasts) |
<plugin-name-lowercase>-broadcasts.yml |
Routes : qui reçoit quel event |
<plugin-name-lowercase>-team-config.yml |
Paramètres globaux d'équipe (defaults appliqués à toutes les teams) |
Les deux suivent le même pattern : si ton plugin de jeu bundle un fichier au même nom dans ses ressources, c'est lui qui sert de template initial à la place des defaults CR-Core. Les defaults restent en mémoire en fallback — donc les clés non présentes dans le fichier user marchent quand même.
Hot reload : /core reload (permission crcore.reload) relit les deux
fichiers sans restart.
Fichier messages
Au premier enable(), CR-Core crée :
<plugin-dataFolder>/<plugin-name-lowercase>-messages.yml
(ex. cites-messages.yml si ton plugin s'appelle Cites)
Édite ce fichier pour customiser tous les messages des commandes /core team .... Les clés non présentes retombent sur les defaults CR-Core (dans le jar)
— donc tu peux n'inclure que ce que tu veux changer.
Pour pré-remplir ce fichier avec tes overrides + tes propres messages dès la
première exécution, bundle un fichier au même nom dans tes ressources
plugin (src/main/resources/cites-messages.yml). Si présent, c'est lui qui
sert de template au lieu des defaults CR-Core.
API en code :
core.messages().get("team.create.success",
"name", team.getName(), "tag", team.getTag());
core.messages().set("mygame.custom", "&aHello {player} !");
core.messages().loadAdditional("my-extras.yml");
core.messages().reload(); // hot reload depuis le disque
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_team_settings— (team_id, key, value, type) — overrides per-teamcrcore_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.