feat: dynamic command registration + Maven publication setup
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>
This commit is contained in:
@@ -12,10 +12,16 @@ import fr.luc.crcore.team.InMemoryTeamRepository;
|
||||
import fr.luc.crcore.team.SqliteTeamRepository;
|
||||
import fr.luc.crcore.team.TeamRepository;
|
||||
import fr.luc.crcore.team.TeamService;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandMap;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -147,16 +153,69 @@ public class CRCore {
|
||||
return new CoreCommand(teamService, playerProfileService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre {@link #coreCommand} sous le nom configuré, avec fallback
|
||||
* dynamique. Stratégie :
|
||||
* <ol>
|
||||
* <li>Si la commande est déclarée dans le {@code plugin.yml} du plugin
|
||||
* hôte ({@code plugin.getCommand(name)} non null), on s'y branche
|
||||
* classiquement via {@code setExecutor} / {@code setTabCompleter}.</li>
|
||||
* <li>Sinon, on l'enregistre <b>dynamiquement</b> via le
|
||||
* {@link CommandMap} interne du serveur (accédé par réflexion sur
|
||||
* le champ {@code commandMap} de {@code CraftServer}). Le plugin
|
||||
* hôte n'a alors rien à mettre dans son {@code plugin.yml}.</li>
|
||||
* </ol>
|
||||
*/
|
||||
private void registerCommand() {
|
||||
PluginCommand cmd = plugin.getCommand(config.getCommandName());
|
||||
if (cmd == null) {
|
||||
plugin.getLogger().warning("Commande '" + config.getCommandName() +
|
||||
"' absente du plugin.yml — /" + config.getCommandName() +
|
||||
" ne sera pas reconnue.");
|
||||
String name = config.getCommandName();
|
||||
PluginCommand cmd = plugin.getCommand(name);
|
||||
if (cmd != null) {
|
||||
cmd.setExecutor(coreCommand);
|
||||
cmd.setTabCompleter(coreCommand);
|
||||
return;
|
||||
}
|
||||
cmd.setExecutor(coreCommand);
|
||||
cmd.setTabCompleter(coreCommand);
|
||||
registerDynamicCommand(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre la commande sans entrée plugin.yml en passant par le
|
||||
* {@link CommandMap} interne du serveur. Wrappe le {@link CoreCommand}
|
||||
* dans une {@link org.bukkit.command.Command} anonyme qui délègue à
|
||||
* {@code onCommand} / {@code onTabComplete}.
|
||||
*/
|
||||
private void registerDynamicCommand(String name) {
|
||||
try {
|
||||
CommandMap commandMap = resolveCommandMap();
|
||||
final CoreCommand executor = coreCommand;
|
||||
org.bukkit.command.Command bukkitCommand = new org.bukkit.command.Command(name) {
|
||||
@Override
|
||||
public boolean execute(CommandSender sender, String label, String[] args) {
|
||||
return executor.onCommand(sender, this, label, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> tabComplete(CommandSender sender, String alias, String[] args) {
|
||||
List<String> result = executor.onTabComplete(sender, this, alias, args);
|
||||
return result != null ? result : Collections.emptyList();
|
||||
}
|
||||
};
|
||||
bukkitCommand.setDescription(executor.getDescription());
|
||||
bukkitCommand.setAliases(executor.getAliases());
|
||||
commandMap.register(plugin.getName().toLowerCase(), bukkitCommand);
|
||||
plugin.getLogger().info("Commande /" + name + " enregistrée dynamiquement (plugin.yml non requis).");
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().severe("Échec d'enregistrement dynamique de /" + name + " : " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère le {@link CommandMap} interne du serveur via réflexion sur
|
||||
* {@code CraftServer.commandMap}. Stable sur Paper 1.16.5.
|
||||
*/
|
||||
private CommandMap resolveCommandMap() throws ReflectiveOperationException {
|
||||
Field field = Bukkit.getServer().getClass().getDeclaredField("commandMap");
|
||||
field.setAccessible(true);
|
||||
return (CommandMap) field.get(Bukkit.getServer());
|
||||
}
|
||||
|
||||
// ---- Getters ----
|
||||
|
||||
Reference in New Issue
Block a user