Skip to content

编写命令处理器

在 Minecraft 中,命令是普通玩家和服务器管理员与游戏进行底层交互的主要方式。它们可用于执行从简单的聊天消息到复杂的服务器管理等广泛任务。在本教程中,我们将创建一个基本的“石头剪刀布”命令处理器,让玩家可以与服务器进行游戏。

Pumpkin 拥有自己的命令处理系统,其核心在于每个命令都有一个“树形结构”,该结构定义了命令及其参数的精确语法。树中的每个节点代表一个命令或参数,系统通过遍历这棵树来确定要执行的命令及其参数。

Pumpkin 中的命令是异步执行的,这意味着它们在执行时不会阻塞主线程。这有助于更高效地利用资源并提供更流畅的用户体验。

我们还要感谢 ploxxxy 编写了最初的 “石头剪刀布 ”插件,本教程正是基于此插件而创作的。

添加基础功能

Pumpkin 中的每个命令都被定义为实现 CommandExecutor trait 的结构体。此 trait 要求实现一个 execute 方法,该方法以发送者、服务器和消耗的参数作为参数,并返回 R-> Result<(), CommandError>。现在我们来定义这个结构体。

rs
use pumpkin::{
    command::{ 
        args::ConsumedArgs, dispatcher::CommandError, tree::builder::literal, tree::CommandTree,
        CommandExecutor, CommandSender,
    },
    plugin::{player::player_join::PlayerJoinEvent, Context, EventHandler, EventPriority},
    server::Server,
};

struct RockPaperScissorsExecutor; 

impl CommandExecutor for RockPaperScissorsExecutor {
    fn execute<'a>(
        &self,
        sender: &mut CommandSender,
        _: &Server,
        _: &ConsumedArgs<'a>,
    ) -> Result<(), CommandError> {
        Box::pin(async {
            Ok(())
        })
    }
}

This code defines an empty structure RockPaperScissorsExecutor that implements the CommandExecutor trait. The execute method is defined to return Ok(()) when called.

这段代码定义了一个空结构体 RockPaperScissorsExecutor ,它实现了 CommandExecutor trait。其 execute 方法被定义为在调用时返回 Ok(())

添加辅助枚举

为了让我们的开发工作更轻松,我们将在插件代码中定义一些枚举来表示游戏中可能的选择和结果,同时添加几个函数来生成随机选择和检查游戏结果。将这些内容添加到您的插件代码中。

rs
use rand::{rng, Rng};

#[derive(PartialEq, Debug, Clone, Copy)]
enum Choice {
    Rock,
    Paper,
    Scissors,
}

enum Outcome {
    Win,
    Lose,
    Draw,
}

impl Choice {
    pub fn beats(&self, other: &Choice) -> Outcome {
        if self == other {
            return Outcome::Draw;
        }

        match (self, other) {
            (Choice::Rock, Choice::Scissors) => Outcome::Win,
            (Choice::Paper, Choice::Rock) => Outcome::Win,
            (Choice::Scissors, Choice::Paper) => Outcome::Win,
            _ => Outcome::Lose,
        }
    }
}

fn get_random_choice() -> Choice {
    let choices = [Choice::Rock, Choice::Paper, Choice::Scissors];
    let index = rng().random_range(0..3);
    choices[index]
}

好的,现在我们需要修改 RockPaperScissorsExecutor 结构体,使其能接受一个 Choice 参数并实现游戏逻辑。

rs
struct RockPaperScissorsExecutor(Choice); 
struct RockPaperScissorsExecutor; 

impl CommandExecutor for RockPaperScissorsExecutor {
    fn execute<'a>(
        &self,
        sender: &mut CommandSender,
        _: &Server,
        _: &ConsumedArgs<'a>,
    ) -> Result<(), CommandError> {
        Box::pin(async move { 
            let player_choice = self.0; 
            let computer_choice = get_random_choice();
            Ok(())
        })
    }
}

此代码将允许我们稍后传入玩家的选择并将其用于游戏逻辑中,同时将其与计算机的选择进行比较以确定游戏结果。

实现游戏逻辑

现在,我们可以继续前进,实际实现游戏逻辑,并向玩家展示游戏结果。

首先,我们将向玩家展示他们自己和计算机的选择。将此代码添加到您的插件中:

rs
impl CommandExecutor for RockPaperScissorsExecutor {
    fn execute<'a>(
        &self,
        sender: &mut CommandSender,
        _: &Server,
        _: &ConsumedArgs<'a>,
    ) -> Result<(), CommandError> {
        Box::pin(async move {
            let player_choice = self.0;
            let computer_choice = get_random_choice();
            
            sender 
                .send_message(
                    TextComponent::text("你选择了: ")
                        .add_text(format!("{:?}", player_choice))
                        .color_named(NamedColor::Aqua),
                )
                .await;

            sender
                .send_message(
                    TextComponent::text("我选择了: ")
                        .add_text(format!("{:?}", computer_choice))
                        .color_named(NamedColor::Gold),
                )
                .await;
            
            Ok(())
        })
    }
}

接下来,我们可以计算游戏结果并将其展示给玩家。将此代码添加到您的插件中:

rs
impl CommandExecutor for RockPaperScissorsExecutor {
    fn execute<'a>(
        &self,
        sender: &mut CommandSender,
        _: &Server,
        _: &ConsumedArgs<'a>,
    ) -> Result<(), CommandError> {
        Box::pin(async move {
            // Existing code
            

            match player_choice.beats(&computer_choice) { 
                Outcome::Win => {
                    sender
                        .send_message(TextComponent::text("你赢了!").color_named(NamedColor::Green))
                        .await;
                }
                Outcome::Lose => {
                    sender
                        .send_message(TextComponent::text("你输了!").color_named(NamedColor::Red))
                        .await;
                }
                Outcome::Draw => {
                    sender
                        .send_message(
                            TextComponent::text("平局了!").color_named(NamedColor::Yellow),
                        )
                        .await;
                }
            }
            
            Ok(())
        })
    }
}

就这样!核心逻辑已完成。现在我们只剩下最后一件事要做。

构建与注册命令树

如前所述,我们需要构建一个命令树并将其注册到服务器。这将允许玩家执行我们插件的命令。

构建命令树并不难,但您必须知道命令及其参数的确切结构。在本例中,我们有一个名为 rock-paper-scissors 的命令,它将接受一个必需参数(玩家的选择)。

命令树使用 CommandTree::new() 函数进行初始化。此函数接受两个参数:一个名称列表,其中第一个是主命令名称,其余的是该命令的别名;以及一个用于在帮助菜单中描述命令的命令描述。然后,我们可以使用 .then() 方法向树中添加“分支”。此方法接受一个“叶节点”,该节点可以使用 literal(), argument(), 或 require() 函数构建。

对于石头剪刀布命令,我们将创建 3 个独立的分支,每个分支都有一个代表玩家选择的 literal() 叶节点。我们还将以 ZeroPermissionLvl 向服务器注册该命令树,这将允许任何人执行该命令。请将以下代码添加到您的 on_load() 方法中。

rs
use pumpkin_util::PermissionLvl; 

const NAMES: [&str; 2] = ["rps", "rockpaperscissors"]; 
const DESCRIPTION: &str = "与服务器玩石头剪刀布。";

#[plugin_method]
async fn on_load(&mut self, server: Arc<Context>) -> Result<(), String> {
    server.init_log();

    log::info!("你好, Pumpkin!");

    server.register_event(Arc::new(MyJoinHandler), EventPriority::Lowest, true).await;
    
    let command = CommandTree::new(NAMES, DESCRIPTION) 
        .then(literal("石头").execute(RockPaperScissorsExecutor(Choice::Rock)))
        .then(literal("布").execute(RockPaperScissorsExecutor(Choice::Paper)))
        .then(literal("剪刀").execute(RockPaperScissorsExecutor(Choice::Scissors)));

    server.register_command(command, PermissionLvl::Zero).await;

    Ok(())
}

就这样完成了!如果您已编译该插件,可以通过运行以下命令来测试它:

bash
/rps rock

基于 MIT 许可证发布。