Files
Cites_Plugins/docs/setup.md
T
Antone Barbaud a94bc56a5b feat: configurable broadcasts + /core reload
New fr.luc.crcore.broadcast module:
- BroadcastAudience enum (NONE, LEADER, TEAM, ADMIN, ALL).
- BroadcastContext (fluent: team + involvedPlayerId + placeholders).
- BroadcastService interface + YamlBroadcastService impl.
- CRCoreBroadcastListener (Bukkit listener) wires the 12 native events
  (9 team + 3 player) to broadcasts.broadcast(eventKey, ctx).

Same single-per-plugin file pattern as messages:
<plugin-dataFolder>/<plugin-name-lowercase>-broadcasts.yml. Defaults
bundled at resources/crcore-broadcasts.yml, copied on first boot (game
plugin's own resource of the same name takes priority as the template).
In-memory fallback so new CR-Core keys work without admin edit.

Routes (who) vs templates (what) are separated: broadcasts.yml lists
audiences per eventKey, messages.yml contains the templates under keys
<eventKey>.broadcast. Admin can change either independently.

12 new *.broadcast keys added to crcore-messages.yml with sensible
French defaults and color codes. Listener injects standard placeholders
(name, team_name, tag, color, visibility, player, new_leader,
old/new_value, etc.).

ADMIN audience resolved via crcore.broadcast.admin permission. Multi-
audiences via YAML list (e.g., [TEAM, ADMIN]); union of resolved
players, no duplicate.

New /core reload subcommand (permission crcore.reload) hot-reloads
both messages and broadcasts from disk without restart.

CRCore: protected buildBroadcastService() override point, getter
broadcasts(), wire of CRCoreBroadcastListener at enable(). CoreCommand
constructor extended to take BroadcastService, registers the reload
subcommand.

Docs/features.md: new section 9 "Service de broadcasts". docs/setup.md:
updated to mention both YAML files. decisions.md logs the routes-vs-
templates split, the audience model, and the reload semantics.
New diagram broadcasts-class-diagram.puml. README updated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-10 11:16:34 +02:00

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

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 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 :

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

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

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
├── 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<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
    ├── 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
<plugin-name-lowercase>-messages.yml Templates de tous les messages (commandes + broadcasts)
<plugin-name-lowercase>-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 :

<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_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.