7ee349f206
CRCore.registerCommand() now falls back to dynamic registration via the server's internal CommandMap (reflection on CraftServer.commandMap) when the command is absent from the host plugin's plugin.yml. Game plugins can now use CR-Core with zero plugin.yml changes — just instantiate CRCore. If the command IS declared in plugin.yml, CR-Core detects it and uses setExecutor/setTabCompleter as before. pom.xml: distributionManagement targeting Gitea Packages (https://gitea.luc-rival.fr/api/packages/admin/maven), plus maven-source-plugin (3.3.0) and maven-javadoc-plugin (3.6.3) so each mvn deploy publishes the main jar, a -sources.jar and a -javadoc.jar. doclint=none on javadoc to tolerate partial doc. docs/setup.md: clarifies that the commands: entry in plugin.yml is now optional. docs/decisions.md: new decision logged for the dynamic registration approach. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
272 lines
9.6 KiB
Markdown
272 lines
9.6 KiB
Markdown
# Setup technique
|
|
|
|
## Stack
|
|
|
|
- **Type** : librairie Java (`jar`) — pas un plugin Bukkit
|
|
- **artifactId Maven** : `CR-Core`
|
|
- **Build** : Maven, Java 16
|
|
- **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
|
|
├── team/
|
|
│ ├── Team.java
|
|
│ ├── TeamMember.java
|
|
│ ├── TeamRole.java
|
|
│ ├── TeamColor.java
|
|
│ ├── TeamVisibility.java
|
|
│ ├── TeamRanking.java # record
|
|
│ ├── TeamRepository.java
|
|
│ ├── InMemoryTeamRepository.java
|
|
│ ├── SqliteTeamRepository.java
|
|
│ ├── TeamService.java
|
|
│ ├── TeamServiceImpl.java
|
|
│ ├── BukkitEventFiringTeamServiceImpl.java # impl par défaut
|
|
│ ├── TeamException.java
|
|
│ ├── TeamAlreadyExistsException.java
|
|
│ ├── TeamNotFoundException.java
|
|
│ ├── TeamAccessException.java
|
|
│ └── 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
|
|
└── player/
|
|
├── PlayerProfile.java
|
|
├── PlayerRanking.java
|
|
├── PlayerProfileRepository.java
|
|
├── InMemoryPlayerProfileRepository.java
|
|
├── SqlitePlayerProfileRepository.java
|
|
├── PlayerProfileService.java
|
|
├── PlayerProfileServiceImpl.java
|
|
├── BukkitEventFiringPlayerProfileServiceImpl.java
|
|
├── PlayerException.java
|
|
├── PlayerProfileNotFoundException.java
|
|
└── event/ # Bukkit events player
|
|
├── PlayerProfileEvent.java
|
|
├── PlayerProfileCreateEvent.java
|
|
├── PlayerProfileDeleteEvent.java
|
|
└── PlayerScoreChangeEvent.java
|
|
```
|
|
|
|
## 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.
|