gpt4 book ai didi

java - 设计 Bukkit 插件框架 - 通过注释处理子命令

转载 作者:行者123 更新时间:2023-12-02 07:45:25 25 4
gpt4 key购买 nike

一些话来介绍情况。

上下文:为了在编写 Bukkit 插件时简化我的工作流程(在 Sponge 实现它之前基本上是 Minecraft 服务器的事实上的 API),我决定为自己组装一个“迷你框架”,而不必重复相同的任务再三,一而再再而三。 (另外,我试图将它设计为不太依赖 Bukkit,所以我可以通过改变我的实现来继续在 Sponge 上使用它)

意向:坦率地说,Bukkit 中的命令处理一团糟。您必须在 YML 文件(而不是调用某种工厂?)中定义您的根命令(例如,您要运行/test ingame,“test”是根),子命令处理不存在,实现细节是隐藏所以很难产生 100% 可靠的结果。这是 Bukkit 唯一让我恼火的部分,也是我决定编写框架的主要发起者。

目标:抽象掉讨厌的 Bukkit 命令处理,并用干净的东西替换它。

为之努力:

这将是一个很长的段落,我将在其中解释 Bukkit 命令处理最初是如何实现的,因为这将使对重要命令参数等有更深入的了解。

任何连接到 Minecraft 服务器的用户都可以使用“/”开始聊天消息,这将导致它被解析为命令。

举个例子,Minecraft 中的任何玩家都有一个生命条,默认上限为 10 颗心,并在受到伤害时耗尽。服务器可以随时设置最大和当前的“心脏”(读取:健康)。

假设我们想定义一个这样的命令:

/sethealth <current/maximum> <player or * for all> <value>

开始实现这个......哦,男孩。如果你喜欢干净的代码,我会说跳过这个......我会评论来解释,每当我觉得 Bukkit 做错了。

强制性的 plugin.yml:
# Full name of the file extending JavaPlugin
# My best guess? Makes lazy-loading the plugin possible
# (aka: just load classes that are actually used by replacing classloader methods)
main: com.gmail.zkfreddit.sampleplugin.SampleJavaPlugin

# Name of the plugin.
# Why not have this as an annotation on the plugin class?
name: SamplePlugin

# Version of the plugin. Why is this even required? Default could be 1.0.
# And again, could be an annotation on the plugin class...
version: 1.0

# Command section. Instead of calling some sort of factory method...
commands:
# Our '/sethealth' command, which we want to have registered.
sethealth:
# The command description to appear in Help Topics
# (available via '/help' on almost any Bukkit implementation)
description: Set the maximum or current health of the player

# Usage of the command (will explain later)
usage: /sethealth <current/maximum> <player/* for all> <newValue>

# Bukkit has a simple string-based permission system,
# this will be the command permission
# (and as no default is specified,
# will default to "everybody has it")
permission: sampleplugin.sethealth

主要插件类:
package com.gmail.zkfreddit.sampleplugin;

import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.java.JavaPlugin;

public class SampleJavaPlugin extends JavaPlugin {

//Called when the server enables our plugin
@Override
public void onEnable() {
//Get the command object for our "sethealth" command.
//This basically ties code to configuration, and I'm pretty sure is considered bad practice...
PluginCommand command = getCommand("sethealth");

//Set the executor of that command to our executor.
command.setExecutor(new SampleCommandExecutor());
}
}

命令执行器:
package com.gmail.zkfreddit.sampleplugin;

import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;

public class SampleCommandExecutor implements CommandExecutor {

private static enum HealthOperationType {
CURRENT,
MAXIMUM;

public void executeOn(Player player, double newHealth) {
switch (this) {
case CURRENT:
player.setHealth(newHealth);
break;
case MAXIMUM:
player.setMaxHealth(newHealth);
break;
}
}
}

@Override
public boolean onCommand(
//The sender of the command - may be a player, but might also be the console
CommandSender commandSender,

//The command object representing this command
//Why is this included? We know this is our SetHealth executor,
//so why add this as another parameter?
Command command,

//This is the "label" of the command - when a command gets registered,
//it's name may have already been taken, so it gets prefixed with the plugin name
//(example: 'sethealth' unavailable, our command will be registered as 'SamplePlugin:sethealth')
String label,

//The command arguments - everything after the command name gets split by spaces.
//If somebody would run "/sethealth a c b", this would be {"a", "c", "b"}.
String[] args) {
if (args.length != 3) {
//Our command does not match the requested form {"<current/maximum>", "<player>", "<value>"},
//returning false will, ladies and gentleman...

//display the usage message defined in plugin.yml. Hooray for some documented code /s
return false;
}

HealthOperationType operationType;
double newHealth;

try {
//First argument: <current/maximum>
operationType = HealthOperationType.valueOf(args[0].toUpperCase());
} catch (IllegalArgumentException e) {
return false;
}

try {
//Third argument: The new health value
newHealth = Double.parseDouble(args[2]);
} catch (NumberFormatException e) {
return false;
}

//Second argument: Player to operate on (or all)
if (args[1].equalsIgnoreCase("*")) {
//Run for all players
for (Player player : Bukkit.getOnlinePlayers()) {
operationType.executeOn(player, newHealth);
}
} else {
//Run for a specific player
Player player = Bukkit.getPlayerExact(args[1]);

if (player == null) {
//Player offline
return false;
}

operationType.executeOn(player, newHealth);
}

//Handled successfully, return true to not display usage message
return true;
}
}

现在您可能明白为什么我选择在我的框架中抽象命令处理了。我不认为只有我一个人认为这种方式是 不是自我记录 以这种方式处理子命令感觉不对 .

我的意向:

类似于 Bukkit Event System有效,我想开发一个框架/API 来抽象它。

我的想法是 注释命令方法 使用包含所有必要信息的相应注释,并使用某种注册器(在事件情况下: Bukkit.getPluginManager().registerEvents(Listener, Plugin) )来注册命令。

再次类似于事件 API,命令方法将具有定义的签名。由于处理多个参数很烦人,我决定将其全部打包在上下文接口(interface)中(而且,这样我就不会破坏以前的所有代码,以防我需要向上下文添加内容!)。但是,我还需要一个返回类型,以防我想快速显示用法(但我不会选择 boolean 值,这是肯定的!),或者做一些其他的事情。所以,我的想法签名归结为 CommandResult <anyMethodName>(CommandContext) .

命令注册然后将为带注释的方法创建命令实例并注册它们。

我的基本轮廓形成了。请注意,我还没有开始编写 JavaDoc,我添加了一些关于非自记录代码的快速评论。

命令注册:
package com.gmail.zkfreddit.pluginframework.api.command;

public interface CommandRegistration {

public static enum ResultType {
REGISTERED,
RENAMED_AND_REGISTERED,
FAILURE
}

public static interface Result {
ResultType getType();

//For RENAMED_AND_REGISTERED
Command getConflictCommand();

//For FAILURE
Throwable getException();

//If the command got registered in some way
boolean registered();
}

Result register(Object commandObject);

}

命令结果枚举:
package com.gmail.zkfreddit.pluginframework.api.command;

public enum CommandResult {

//Command executed and handlded
HANDLED,

//Show the usage for this command as some parameter is wrong
SHOW_USAGE,

//Possibly more?
}

命令上下文:
package com.gmail.zkfreddit.pluginframework.api.command;

import org.bukkit.command.CommandSender;

import java.util.List;

public interface CommandContext {

CommandSender getSender();

List<Object> getArguments();

@Deprecated
String getLabel();

@Deprecated
//Get the command annotation of the executed command
Command getCommand();
}

要放在命令方法上的主要命令注释:
package com.gmail.zkfreddit.pluginframework.api.command;

import org.bukkit.permissions.PermissionDefault;

public @interface Command {

public static final String DEFAULT_STRING = "";

String name();

String description() default DEFAULT_STRING;

String usageMessage() default DEFAULT_STRING;

String permission() default DEFAULT_STRING;

PermissionDefault permissionDefault() default PermissionDefault.TRUE;

Class[] autoParse() default {};
}

autoParse 的目的是我可以快速定义一些东西,如果解析失败,它只是显示命令的使用消息。

现在,一旦我编写了我的实现,我就可以将提到的“sethealth”命令执行程序重写为这样的:
package com.gmail.zkfreddit.sampleplugin;

import de.web.paulschwandes.pluginframework.api.command.Command;
import de.web.paulschwandes.pluginframework.api.command.CommandContext;
import org.bukkit.entity.Player;
import org.bukkit.permissions.PermissionDefault;

public class BetterCommandExecutor {

public static enum HealthOperationType {
CURRENT,
MAXIMUM;

public void executeOn(Player player, double newHealth) {
switch (this) {
case CURRENT:
player.setHealth(newHealth);
break;
case MAXIMUM:
player.setMaxHealth(newHealth);
break;
}
}
}

@Command(
name = "sethealth",
description = "Set health values for any or all players",
usageMessage = "/sethealth <current/maximum> <player/* for all> <newHealth>",
permission = "sampleplugin.sethealth",
autoParse = {HealthOperationType.class, Player[].class, Double.class} //Player[] as there may be multiple players matched
)
public CommandResult setHealth(CommandContext context) {
HealthOperationType operationType = (HealthOperationType) context.getArguments().get(0);
Player[] matchedPlayers = (Player[]) context.getArguments().get(1);
double newHealth = (Double) context.getArguments().get(2);

for (Player player : matchedPlayers) {
operationType.executeOn(player, newHealth);
}

return CommandResult.HANDLED;
}
}

我相信我在这里代表大多数人说这种方式感觉更干净。

那么我在哪里问问题呢?

我被卡住的地方。

子命令处理。

在这个例子中,我能够根据第一个参数的两种情况使用一个简单的枚举。

在某些情况下,我必须创建许多类似于“当前/最大值”的子命令。一个很好的例子可能是处理将玩家作为一个团队聚集在一起的东西 - 我需要:
/team create ...
/team delete ...
/team addmember/join ...
/team removemember/leave ...

等等 - 我希望能够为这些子命令创建单独的类。

我究竟将如何引入一种干净的方式来说“嘿,当 this 的第一个参数匹配某事时,做这个和那个!” - 哎呀,“匹配”部分甚至不必是硬编码的字符串,我可能想要类似的东西
/team [player] info

同时,仍然匹配所有以前的子命令。

我不仅必须链接到子命令方法,还必须以某种方式链接所需的对象 - 毕竟,我的( future )命令注册将采用实例化对象(在示例中为 BetterCommandExecutor)并注册它。我将如何告诉“使用此子命令实例!”传入对象时注册?

我一直在考虑说“**** 一切,链接到子命令类,然后实例化它的无参数构造函数”,但是虽然这可能会产生最少的代码,但它并不能深入了解究竟如何子命令实例被创建。如果我决定走那条路,我可能只是定义一个 childs我的 Command 中的参数批注,并使其采用某种@ChildCommand 批注列表(批注中的批注?天啊,为什么不呢?)。

所以毕竟这一切,问题是:有了这个设置,有没有办法可以干净地定义子命令,或者我是否必须完全改变我的立足点?我想从某种抽象的 BaseCommand 扩展(使用抽象的 getChildCommands() 方法),但是注释方法的优点是能够处理来自一个类的多个命令。另外,就我到目前为止所获得的开源代码而言,我的印象是 extends是 2011 年和 implements是今年的味道,所以每次我创建某种命令处理程序时,我不应该强制自己扩展某些东西。

我很抱歉这篇很长的帖子。这比我预期的要长:/

编辑 #1:

我刚刚意识到我基本上创建的是某种......树?命令。然而,仅仅使用某种 CommandTreeBuilder 就会失效,因为它违背了我从这个想法中想要的一件事:能够在一个类中定义多个命令处理程序。回到头脑 Storm 。

最佳答案

我唯一能想到的就是拆分您的注释。您将拥有一个将 Base Command 作为注释的类,然后该类中的方法使用不同的子命令:

@Command("/test")
class TestCommands {

@Command("sub1"// + more parameters and stuff)
public Result sub1Command(...) {
// do stuff
}

@Command("sub2"// + more parameters and stuff)
public Result sub2Command(...) {
// do stuff
}
}

如果您想要更大的灵活性,您也可以考虑继承层次结构,但我不确定那时的自文档化程度(因为部分命令将隐藏在父类中)。

此解决方案不能解决您的 /team [player] info例子虽然,但我认为这是一件小事。无论如何,在命令的不同参数中显示子命令都会令人困惑。

关于java - 设计 Bukkit 插件框架 - 通过注释处理子命令,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28557059/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com