Files
Cites_Plugins/docs/setup.md
T
Antone Barbaud 75d2fa866d feat: typed team settings (cascade per-team → global → default) + in-game GUI
New fr.luc.crcore.team.config module:
- TeamSetting<T> (typed, with key/type/default/parser/serializer; factories
  ofBoolean/ofInt/ofString/ofEnum).
- TeamSettings registry: 8 standard settings (FRIENDLY_FIRE,
  PVP_PROTECTION_SECONDS, MAX_SIZE, MIN_SIZE, RESPAWN_AT_TEAM_SPAWN,
  TEAM_CHAT_ENABLED, SHOW_TAG_ABOVE_HEAD, TEAM_COLOR_IN_NAME), extensible
  via register() for game plugins.
- TeamConfigService interface with cascade get(team, setting) →
  per-team override (SQLite) → global YAML → hard default. Persists per-
  team via TeamRepository.save(), global via YamlConfiguration.save().
- YamlTeamConfigService default impl with bundled crcore-team-config.yml.

Storage:
- Team.getSettings() Map<String, Object> for per-team overrides.
- New SQLite table crcore_team_settings (team_id, key, value, type) with
  load + write-through persist in SqliteTeamRepository.
- Global YAML <plugin>-team-config.yml in dataFolder, auto-created at
  first boot (template from game plugin's resource of the same name
  takes priority).

New reusable GUI framework fr.luc.crcore.gui:
- AbstractInventoryGui (implements InventoryHolder, rebuild() abstract,
  setButton/setDecoration/clearSlot helpers, onClose hook, openTo()).
- GuiClickHandler FunctionalInterface.
- GuiListener (single Bukkit listener, detects via getHolder(), ALWAYS
  cancels clicks even on slots without handlers).
- GuiItems builder (named/of/filler + lore/amount/build, '&' color codes
  translated).

Concrete settings GUIs (fr.luc.crcore.team.config.gui):
- AbstractSettingsGui base renderer: 27 slots, settings in row 2,
  booleans = LIME_DYE / GRAY_DYE toggle, integers = BOOK with left +1 /
  right -1 (shift × 10), strings/enums display-only.
- GlobalSettingsGui: writes to YAML on each change.
- TeamSettingsGui: writes to per-team overrides, "override active" flag
  in lore when value differs from global, "Reset all overrides" footer
  button.

New /core team settings [team] subcommand:
- No arg → GlobalSettingsGui (perm crcore.team.settings.global).
- With arg → TeamSettingsGui (perm crcore.team.settings).
- Player-only (Bukkit needs HumanEntity to open inventory).
- Lives under /core team to stay modular (objective: split into modules
  later; everything team-related under /core team).

CRCore: buildTeamConfigService() override point, teamConfig()/getTeamConfig()
getters, GuiListener.registerOn(plugin) at enable(). CoreCommand,
TeamGroupSubCommand and CoreReloadSubCommand extended to receive
TeamConfigService. /core reload now reloads messages + broadcasts +
team-config.

Docs: new section 10 "Paramètres d'équipe", new decisions logged,
setup.md tree updated, two new diagrams (team-config + gui).

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

330 lines
12 KiB
Markdown

# 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
<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
```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<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 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 :
```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.