# 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 ```bash 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` ```xml fr.luc CR-Core 1.0-SNAPSHOT compile ``` 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 ```yaml 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 le `CommandMap` du 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 : > > ```yaml > 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 instance > `new CRCoreConfig()` n'active rien par défaut — il faut appeler > `setupAll()` ou les `setupX()` individuels. ```java 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 ```java 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 ```java 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 ```java 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 ├── GEMINI.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 | |---|---| | `-messages.yml` | Templates de tous les messages (commandes + broadcasts) | | `-broadcasts.yml` | Routes : qui reçoit quel event | | `-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 : ``` /-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 : ```java 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-team - `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.