# 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 ```java public class MyGamePlugin extends JavaPlugin { private CRCore core; @Override public void onEnable() { // 1 ligne = SQLite + services + /core team ... opérationnels this.core = new CRCore(this).enable(); // 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(); } } ``` ### É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 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 │ ├── ArgumentDef.java # package-private │ └── builtin/ # commandes prêtes à l'emploi │ ├── CoreCommand.java # /core │ └── team/ │ ├── TeamGroupSubCommand.java # /core team (container) │ ├── TeamArgumentTypes.java # ArgumentType 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 ├── message/ # service de messages YAML │ ├── MessagesService.java # interface (contrat public) │ └── impl/ │ └── YamlMessagesService.java # impl par défaut ├── team/ # contrats + entités au top │ ├── Team.java # entité │ ├── TeamMember.java # entité │ ├── TeamRole.java # enum │ ├── TeamColor.java # enum │ ├── TeamVisibility.java # enum │ ├── TeamRanking.java # value │ ├── TeamService.java # interface │ ├── TeamRepository.java # interface │ ├── 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 │ ├── exception/ # hiérarchie d'exceptions team │ │ ├── TeamException.java # base │ │ ├── TeamAlreadyExistsException.java │ │ ├── TeamNotFoundException.java │ │ └── TeamAccessException.java │ └── impl/ # implémentations swappables │ ├── TeamServiceImpl.java # service de base │ ├── BukkitEventFiringTeamServiceImpl.java # impl par défaut │ ├── InMemoryTeamRepository.java # repo en mémoire │ └── SqliteTeamRepository.java # repo SQLite write-through └── player/ # contrats + entités au top ├── PlayerProfile.java # entité ├── PlayerRanking.java # value ├── PlayerProfileService.java # interface ├── PlayerProfileRepository.java # interface ├── event/ # Bukkit events player │ ├── PlayerProfileEvent.java │ ├── PlayerProfileCreateEvent.java │ ├── PlayerProfileDeleteEvent.java │ └── PlayerScoreChangeEvent.java ├── exception/ │ ├── PlayerException.java │ └── PlayerProfileNotFoundException.java └── impl/ ├── PlayerProfileServiceImpl.java ├── BukkitEventFiringPlayerProfileServiceImpl.java ├── InMemoryPlayerProfileRepository.java └── SqlitePlayerProfileRepository.java ``` ## Fichiers de config générés au premier `enable()` Au premier démarrage, CR-Core crée DEUX fichiers dans le dataFolder : | Fichier | Rôle | |---|---| | `-messages.yml` | Templates de tous les messages (commandes + broadcasts) | | `-broadcasts.yml` | Routes : qui reçoit quel event | 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_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.