8. 添加自定义命令

在本课中,我们将学习如何创建自己的 Evennia 命令。如果您是 Python 新手,您还将学习一些关于如何操作字符串和从 Evennia 获取信息的基础知识。

命令是处理用户输入并导致结果发生的东西。 例如,look 命令会检查您当前的位置,并告诉您它看起来像什么以及里面有什么。

在 Evennia 中,命令是一个 Python 。如果您不确定类是什么,请查看关于它的上一课!命令继承自 evennia.Command 或其他命令类之一,例如 MuxCommand,这是大多数默认命令使用的。

所有命令都被分组在另一个称为 命令集 的类中。可以将命令集视为包含许多不同命令的袋子。例如,一个命令集可以包含所有战斗命令,另一个可以用于建筑等。

然后将命令集与对象关联,例如与您的角色关联。这样做会使该命令集中的命令对对象可用。默认情况下,Evennia 将所有角色命令分组到一个称为 CharacterCmdSet 的大命令集中。它位于 DefaultCharacter 上(因此,通过继承,位于 typeclasses.characters.Character 上)。

8.1. 创建自定义命令

打开 mygame/commands/command.py。此文件已经为您填充了一些内容。

"""
(module docstring)
"""

from evennia import Command as BaseCommand
# from evennia import default_cmds

class Command(BaseCommand):
    """
    (class docstring)
    """
    pass

# (lots of commented-out stuff)
# ...

忽略文档字符串(如果您愿意,可以阅读),这是模块中唯一真正活动的代码。

我们可以看到我们从 evennia 导入 Command,并使用 from ... import ... as ... 形式将其重命名为 BaseCommand。这样我们可以让子类也命名为 Command,以便更容易引用。类本身没有做任何事情,它只是有一个 pass。因此,与前几课中的 ObjectCharacter 一样,此类与其父类相同。

注释掉的 default_cmds 使我们可以轻松覆盖 Evennia 的默认命令。我们稍后会尝试一下。

我们可以直接修改此模块,但为了尝试,我们在单独的模块中工作。打开一个新文件 mygame/commands/mycommands.py 并添加以下代码:

# in mygame/commands/mycommands.py

from commands.command import Command

class CmdEcho(Command):
    key = "echo"

这是您能想象到的最简单的命令形式。它只是给自己一个名字,“echo”。这就是您稍后将用来调用此命令的方式。

接下来,我们需要将其放入一个 CmdSet。现在它将是一个单命令 CmdSet!将您的文件更改如下:

# in mygame/commands/mycommands.py

from commands.command import Command
from evennia import CmdSet

class CmdEcho(Command):
    key = "echo"


class MyCmdSet(CmdSet):

    def at_cmdset_creation(self):
        self.add(CmdEcho)

我们的 MyCmdSet 类必须有一个 at_cmdset_creation 方法,名字必须完全一样——这是 Evennia 在稍后设置 cmdset 时会寻找的东西,所以如果您没有设置它,它将使用父类的版本,该版本是空的。在内部,我们通过 self.add() 将命令类添加到 cmdset 中。如果您想将更多命令添加到此 CmdSet 中,只需在此之后添加更多 self.add 行即可。

最后,让我们将此命令添加到我们自己,以便我们可以尝试一下。在游戏中,您可以再次尝试 py

> py me.cmdset.add("commands.mycommands.MyCmdSet")

me.cmdset 是存储在我们身上的所有 cmdsets 的存储。通过提供我们的 CmdSet 类的路径,它将被添加。

现在尝试

> echo
Command "echo" has no defined `func()`. Available properties ...
...(lots of stuff)...

echo 工作了!您应该会看到一长串输出。您的 echo 函数实际上并没有“做”任何事情,因此默认功能是显示使用命令时可用的所有有用资源。让我们看看其中的一些:

Command "echo" has no defined `func()` method. Available properties on this command are:

     self.key (<class 'str'>): "echo"
     self.cmdname (<class 'str'>): "echo"
     self.raw_cmdname (<class 'str'>): "echo"
     self.raw_string (<class 'str'>): "echo
"
     self.aliases (<class 'list'>): []
     self.args (<class 'str'>): ""
     self.caller (<class 'typeclasses.characters.Character'>): YourName
     self.obj (<class 'typeclasses.characters.Character'>): YourName
     self.session (<class 'evennia.server.serversession.ServerSession'>): YourName(#1)@1:2:7:.:0:.:0:.:1
     self.locks (<class 'str'>): "cmd:all();"
     self.help_category (<class 'str'>): "general"
     self.cmdset (... a long list of commands ...)

这些都是您可以在命令实例上通过 . 访问的属性,例如 .key.args 等。Evennia 使这些对您可用,并且每次运行命令时它们都会有所不同。我们现在将使用的最重要的几个是:

  • caller - 这是“您”,调用命令的人。

  • args - 这是命令的所有参数。现在它是空的,但如果您尝试 echo foo bar,您会发现它将是 " foo bar"(包括 echofoo 之间的额外空格,您可能想要去掉它)。

  • obj - 这是此命令(和 CmdSet)“位于”其上的对象。所以在这种情况下是您。

  • raw_string 不常用,但它是用户的完全未修改的输入。它甚至包括用于将命令发送到服务器的换行符(这就是为什么结束引号出现在下一行的原因)。

我们的命令还没有做任何事情的原因是因为它缺少一个 func 方法。这是 Evennia 用来确定命令实际做什么的方法。修改您的 CmdEcho 类:

# in mygame/commands/mycommands.py
# ...

class CmdEcho(Command):
    """
    A simple echo command

    Usage:
        echo <something>

    """
    key = "echo"

    def func(self):
        self.caller.msg(f"Echo: '{self.args}'")

# ...

首先,我们添加了一个文档字符串。这通常是一个好习惯,但对于命令类,它也会自动成为游戏中的帮助条目!

接下来,我们添加了 func 方法。它有一个活动行,利用了命令类提供给我们的那些变量之一。如果您完成了 基本 Python 教程,您会认识到 .msg - 这将向附加到我们的对象发送消息 - 在这种情况下是 self.caller,也就是我们。我们获取 self.args 并将其包含在消息中。

由于我们没有更改 MyCmdSet,它将像以前一样工作。重新加载并重新将此命令添加到我们自己以尝试新版本:

> reload
> py self.cmdset.add("commands.mycommands.MyCmdSet")
> echo
Echo: ''

尝试传递一个参数:

> echo Woo Tang!
Echo: ' Woo Tang!'

请注意,Woo 前面有一个额外的空格。这是因为 self.args 包含命令名称之后的 所有内容,包括空格。让我们通过一个小调整去掉那个额外的空格:

# in mygame/commands/mycommands.py
# ...

class CmdEcho(Command):
    """
    A simple echo command

    Usage:
        echo <something>

    """
    key = "echo"

    def func(self):
        self.caller.msg(f"Echo: '{self.args.strip()}'")

# ...

唯一的区别是我们在 self.args 上调用了 .strip()。这是所有字符串上可用的辅助方法 - 它会去掉字符串前后的所有空白。现在命令参数前面将不再有任何空格。

> reload
> py self.cmdset.add("commands.mycommands.MyCmdSet")
> echo Woo Tang!
Echo: 'Woo Tang!'

不要忘记查看 echo 命令的帮助:

> help echo

您将获得您在命令类中放置的文档字符串!

8.1.1. 使我们的 cmdset 持久化

每次重新加载时都必须重新添加我们的 cmdset 有点烦人,对吧?不过,将 echo 设为 持久性 更改很简单:

> py self.cmdset.add("commands.mycommands.MyCmdSet", persistent=True)

现在您可以随意 reload,您的代码更改将直接可用,而无需再次重新添加 MyCmdSet。

我们将以另一种方式添加此 cmdset,因此手动将其删除:

> py self.cmdset.remove("commands.mycommands.MyCmdSet")

8.1.2. 将 echo 命令添加到默认 cmdset

上面我们将 echo 命令添加到我们自己。这将 对我们可用,而对游戏中的其他人不可用。但 Evennia 中的所有命令都是命令集的一部分,包括我们一直在使用的正常 lookpy 命令。您可以轻松地将默认命令集扩展为您的 echo 命令 - 这样游戏中的 每个人 都可以访问它!

mygame/commands/ 中,您会发现一个名为 default_cmdsets.py 的现有模块。打开它,您会发现四个空的 cmdset 类:

  • CharacterCmdSet - 它位于所有角色上(这是我们通常想要修改的)

  • AccountCmdSet - 它位于所有帐户上(在角色之间共享,例如 logout 等)

  • UnloggedCmdSet - 登录前可用的命令,例如用于创建密码和连接到游戏的命令。

  • SessionCmdSet - 您的会话(您的特定客户端连接)唯一的命令。默认情况下未使用。

按如下方式调整此文件:

# in mygame/commands/default_cmdsets.py 

# ... 

from . import mycommands    # <-------  

class CharacterCmdSet(default_cmds.CharacterCmdSet):
    """
    The `CharacterCmdSet` contains general in-game commands like `look`,
    `get`, etc available on in-game Character objects. It is merged with
    the `AccountCmdSet` when an Account puppets a Character.
    """
 
    key = "DefaultCharacter"
 
    def at_cmdset_creation(self):
        """
        Populates the cmdset
        """
        super().at_cmdset_creation()
        #
        # any commands you add below will overload the default ones.
        #
        self.add(mycommands.CmdEcho)    # <-----------
# ... 

这与您将 CmdEcho 添加到 MyCmdSet 的方式相同。唯一的区别是 cmdsets 会自动添加到所有角色/帐户等,因此您不必手动执行此操作。我们还必须确保从您的 mycommands 模块中导入 CmdEcho 以便此模块了解它。from . import mycommands 中的句号 ‘’.‘’ 表示我们告诉 Python mycommands.py 位于与当前模块相同的目录中。我们想要导入整个模块。稍后我们访问 mycommands.CmdEcho 以将其添加到角色 cmdset 中。

只需 reload 服务器,您的 echo 命令将再次可用。一个给定命令可以成为多少个 cmdsets 的一部分是没有限制的。

要删除,只需注释掉或删除 self.add() 行。不过现在保持这样 - 我们将在下面扩展它。

8.1.3. 确定要击打的对象

让我们尝试一些比 echo 更令人兴奋的东西。让我们制作一个 hit 命令,用来打某人的脸!我们希望它的工作方式如下:

> hit <target>
You hit <target> with full force!

不仅如此,我们还希望 <target> 看到

You got hit by <hitter> with full force!

这里,<hitter> 是使用 hit 命令的人,而 <target> 是进行击打的人;所以如果你的名字是 Anna,你打了一个名叫 Bob 的人,这看起来像这样:

> hit bob
You hit Bob with full force!

而 Bob 会看到

You got hit by Anna with full force!

仍然在 mygame/commands/mycommands.py 中,在 CmdEchoMyCmdSet 之间添加一个新类。

# in mygame/commands/mycommands.py

# ...

class CmdHit(Command):
    """
    Hit a target.

    Usage:
      hit <target>

    """
    key = "hit"

    def func(self):
        args = self.args.strip()
        if not args:
            self.caller.msg("Who do you want to hit?")
            return
        target = self.caller.search(args)
        if not target:
            return
        self.caller.msg(f"You hit {target.key} with full force!")
        target.msg(f"You got hit by {self.caller.key} with full force!")

# ...

这里有很多事情需要剖析:

  • 第 5 行:正常的 class 头。我们继承自 Command,我们在此文件顶部导入。

  • 第 6-12 行:命令的文档字符串和帮助条目。您可以根据需要尽可能多地扩展此内容。

  • 第 13 行:我们希望编写 hit 来使用此命令。

  • 第 16 行:我们像以前一样去掉参数中的空白。由于我们不想一遍又一遍地执行 self.args.strip(),因此我们将去掉空白的版本存储在 局部变量 args 中。请注意,通过这样做,我们不会修改 self.argsself.args 仍然会有空白,并且在此示例中与 args 不同。

  • 第 17 行 有我们的第一个 条件,一个 if 语句。它的写法是 if <condition>:,只有当该条件为“真”时,if 语句下的缩进代码块才会运行。要了解 Python 中的真值,通常更容易学习什么是“假值”:

    • False - 这是 Python 中的保留布尔词。相反的是 True

    • None - 另一个保留字。这表示没有结果或值。

    • 00.0

    • 空字符串 ""'',或空的三引号字符串如 """"""''''''

    • 我们尚未使用的空 可迭代对象,如空列表 []、空元组 () 和空字典 {}

    • 其他一切都是“真值”。

    第 16 行 的条件是 not argsnot反转 结果,因此如果 args 是空字符串(假值),整个条件就会变为真值。让我们继续看代码:

  • 第 16-17 行:此代码仅在 if 语句为真时运行,在这种情况下,如果 args 是空字符串。

  • 第 19 行return 是一个保留的 Python 词,立即退出 func

  • 第 20 行:我们使用 self.caller.search 在当前位置查找目标。

  • 第 21-22 行.search 的一个功能是它已经会通知 self.caller 如果找不到目标。在这种情况下,target 将为 None,我们应该直接 return

  • 第 23-24 行:此时我们有一个合适的目标,可以向每个目标发送我们的击打字符串。

最后,我们还必须将其添加到 CmdSet 中。让我们将其添加到 MyCmdSet

# in mygame/commands/mycommands.py

# ...
class MyCmdSet(CmdSet):

    def at_cmdset_creation(self):
        self.add(CmdEcho)
        self.add(CmdHit)

请注意,由于我们之前执行了 py self.cmdset.remove("commands.mycommands.MyCmdSet"),此 cmdset 不再在我们的角色上可用。相反,我们将这些命令直接添加到我们的默认 cmdset 中。

# in mygame/commands/default_cmdsets.py 

# ,.. 

from . import mycommands    

class CharacterCmdSet(default_cmds.CharacterCmdSet):
    """
    The `CharacterCmdSet` contains general in-game commands like `look`,
    `get`, etc available on in-game Character objects. It is merged with
    the `AccountCmdSet` when an Account puppets a Character.
    """
 
    key = "DefaultCharacter"
 
    def at_cmdset_creation(self):
        """
        Populates the cmdset
        """
        super().at_cmdset_creation()
        #
        # any commands you add below will overload the default ones.
        #
        self.add(mycommands.MyCmdSet)    # <-----------
# ... 

我们从添加单个 echo 命令更改为一次性添加整个 MyCmdSet!这将把该 cmdset 中的所有命令添加到 CharacterCmdSet 中,是一次性添加大量命令的实用方法。一旦您进一步探索 Evennia,您会发现 Evennia contribs 都在 cmdsets 中分发他们的新命令,因此您可以像这样轻松地将它们添加到您的游戏中。

接下来我们重新加载,让 Evennia 知道这些代码更改并尝试一下:

> reload
hit
Who do you want to hit?
hit me
You hit YourName with full force!
You got hit by YourName with full force!

没有目标,我们打了自己。如果您还有前一课中的一条龙,您可以尝试击打它(如果您敢的话):

hit smaug
You hit Smaug with full force!

您不会看到第二个字符串。只有 Smaug 看到了(而且不高兴)。

8.2. 总结

在本课中,我们学习了如何创建自己的命令,将其添加到 CmdSet 中,然后添加到我们自己身上。我们还激怒了一条龙。

在下一课中,我们将学习如何用不同的武器打击 Smaug。我们还将了解如何替换和扩展 Evennia 的默认命令。