命令集合

命令集合与 命令 密切相关,您应在阅读此页面之前对命令有一般了解。为了方便阅读,这两个页面被分开。

命令集合(通常称为 CmdSet 或 cmdset)是存储一个或多个 命令 的基本单位。给定命令可以进入多个不同的命令集合。将命令类存储在命令集合中是使命令在您的游戏中可用的方法。

将 CmdSet 存储在一个对象上时,您将使该命令集合中的命令对该对象可用。例如,新角色上存储的默认命令集合。该命令集合包含所有实用命令,从 lookinventory@dig@reload权限 限制了哪些玩家可以使用它们,但这是一个单独的话题)。

当一个帐户输入一个命令时,来自帐户、角色、其位置等的命令集合将汇总为一个 合并栈。此栈按照特定顺序合并成一个“合并”命令集,表示当前可用命令池。

例如,有一个 Window 对象,命令集合中有两个命令:look through windowopen window。该命令集合对房间中有窗户的玩家可见,使他们能够仅在此处使用这些命令。您可以想象各种聪明的用法,如具有多个命令以查看、切换频道等的 Television 对象。Evennia 附带的教程世界展示了一个黑暗房间,在该房间中替换某些关键命令的版本,因为角色看不见。

如果您想快速开始定义第一个命令并使用它们与命令集合一起,可以访问 添加命令教程,其中逐步讲解了过程,而无需解释。

定义命令集合

CmdSet 类,如同 Evennia 中的其他大多数内容,定义为继承自正确父级 (evennia.CmdSet,其是 evennia.commands.cmdset.CmdSet 的快捷方式)。CmdSet 类只需定义一个方法,称为 at_cmdset_creation()。所有其他类参数都是可选的,但用于更高级的集合操作和编码(请参见 合并规则 部分)。

# 文件 mygame/commands/mycmdset.py

from evennia import CmdSet

# 这是一个理论上的自定义模块,包含我们之前创建的命令:mygame/commands/mycommands.py
from commands import mycommands

class MyCmdSet(CmdSet):
    def at_cmdset_creation(self):
        """
        此方法需要做的只是添加命令到集合中。
        """
        self.add(mycommands.MyCommand1())
        self.add(mycommands.MyCommand2())
        self.add(mycommands.MyCommand3())

CmdSet 的 add() 方法也可以接受另一个 CmdSet 作为输入。在这种情况下,从该 CmdSet 中的所有命令将作为逐行添加到该集合中:

    def at_cmdset_creation():
        ...
        self.add(AdditionalCmdSet) # 将该集合中的所有命令添加到当前集合
        ...

如果您将命令添加到现有命令集合中(如默认命令集合),则该集合已被加载到内存中。您需要使服务器意识到代码更改:

@reload

现在您应该能够使用该命令。

如果您创建了一个新的、全新的命令集合,必须将其添加到一个对象中,以使其中的命令可用。在自己身上临时测试命令集合的一种简单方法是使用 @py 命令执行一段 Python 代码:

@py self.cmdset.add('commands.mycmdset.MyCmdSet')

这将保持在您身上,直到您 @reset@shutdown 服务器,或者您运行

@py self.cmdset.delete('commands.mycmdset.MyCmdSet')

在上面的示例中,特定的 Cmdset 类被移除。调用 delete 无需参数将删除最近添加的命令集合。

注意:使用 cmdset.add 添加的命令集合在数据库中默认 持久化。

如果您希望命令集合在重载后存活,可以执行:

@py self.cmdset.add(commands.mycmdset.MyCmdSet, persistent=True)

或者您可以将命令集合添加为 默认 命令集合:

@py self.cmdset.add_default(commands.mycmdset.MyCmdSet)

一个对象只能有一个“默认”命令集合(但也可以没有)。这被视为安全的后备,即使其他所有命令集合失败或被删除。它始终是持久的,并且不会被 cmdset.delete() 影响。要删除一个默认命令集合,您必须显式调用 cmdset.remove_default()

命令集合通常在对象的 at_object_creation 方法中添加。有关添加命令的更多示例,请阅读 逐步教程。通常,您可以使用 self.cmdset.add()self.cmdset.add_default() 自定义添加到对象的命令集合。

重要:命令通过键 别名唯一标识(请参见 命令)。如果存在任何重叠,则两个命令被视为相同。将命令添加到已经具有相同命令的命令集合中将 替换 先前的命令。这一点非常重要。您在尝试用自己的命令重载任何默认 Evennia 命令时,必须考虑到这种行为。否则,您可能会意外地在添加带有匹配别名的新命令时“隐藏”自己的命令。

命令集合的属性

您可以在 CmdSet 上设置几个额外的标志,以修改它们的工作方式。所有这些都是可选的,如果不设置,则将其置为默认值。由于许多与 合并 命令集合有关,您可能希望阅读 添加和合并命令集合 部分,以便理解其中的一些内容。

  • key(字符串) - 命令集合的标识符。这是可选的,但应该是唯一的。它用于在列表中显示,也用于通过 key_mergetype 字典识别特殊合并行为。

  • mergetype(字符串) - 允许以下字符串值之一:“Union”、“Intersect”、“Replace”或“Remove”。

  • priority(整数) - 这定义了合并栈的合并顺序 - 命令集合将按优先级的升序进行合并,优先级最高的集合最后合并。在合并过程中,来自优先级较高的集合的命令占据优先地位(具体发生的情况取决于 合并类型)。如果优先级相同,则合并栈中的顺序确定优先级。优先级值必须大于或等于 -100。大多数游戏内集合通常应具有 0100 之间的优先级。Evennia 默认集合的优先级如下(如果需要不同的分布,可以更改这些优先级):

    • EmptySet: -101(应低于所有其他集合)

    • SessionCmdSet: -20

    • AccountCmdSet: -10

    • CharacterCmdSet: 0

    • ExitCmdSet: 101(通常应始终可用)

    • ChannelCmdSet: 101(通常应始终可用) - 由于出口永远不接受参数,因此与命令名称相同的出口不会与频道发生冲突,即使命令“碰撞”。

  • key_mergetype(字典) - key:mergetype 对的字典。如果要合并的命令集合的 keykey_mergetype 中的一项匹配,则不会根据 mergetype 中的设置合并,而是根据该字典中的模式合并。请注意,由于命令集合的 合并顺序,这比看起来更复杂。请在使用 key_mergetype 之前参考该部分。

  • duplicates(bool/None 默认 None) - 当合并具有相同优先级的相同键命令的命令集合时,决定会发生什么。duplicates 选项 在将命令集合合并到具有相同优先级的命令集合时应用。结果命令集合 不会 保留该 duplicate 设置。

    • None(默认):不允许重复,合并“到”旧集合的命令集合将占优。结果将是唯一的命令。然而,系统将假设此值为 True,适用于对象上的命令集合,以避免危险的冲突。这通常是安全的选择。

    • False:像 None 一样,只是系统不会为对象上定义的命令集合自动假设任何值。

    • True:同名、相同优先级的命令将合并到同一命令集合中。这将导致多重匹配错误(用户将收到可能性的列表,以便指定他们的意图命令)。这在对象命令集合中很有用(例如:房间中有一个 red button 和一个 green button。两者都有相同的 press button 命令,且在相同优先级的命令集合中。此标志确保仅输入 press button 将强制玩家定义所指的对象的命令)。

  • no_objs:这是 cmdhandler 的标志,该标志构建每时每刻可用的命令集合。它告知处理程序在构建合并集时不包括周围对象的命令集合(也不包括房间或物品中的命令集合)。出口命令仍将包括。此选项可以具有三个值:

    • None(默认):传递之前在合并栈中显式设置的任何值。如果以前没有显式设置,则此方法的行为与 False 相同。

    • True/False:显式开启/关闭。如果两个带有显式 no_objs 的集合合并,则优先级将决定使用什么。

  • no_exits:这是 cmdhandler 的标志,该标志构建每时每刻可用的命令集合。它告知处理程序在构建合并集时不包括出口的命令集合。此标志可以具有三个值:

    • None(默认):传递之前在合并栈中显式设置的任何值。如果以前没有显式设置,则此方法的行为与 False 相同。

    • True/False:显式开启/关闭。如果两个带有显式 no_exits 的集合合并,则优先级将决定使用什么。

  • no_channels(布尔值) - 这是 cmdhandler 的标志,该标志构建每时每刻可用的命令集合。它告知处理程序在构建合并集时不包括来自当前连接的所有游戏频道的命令集合。此标志可以具有三个值:

    • None(默认):传递之前在合并栈中显式设置的任何值。如果以前没有显式设置,则此方法的行为与 False 相同。

    • True/False:显式开启/关闭。如果两个带有显式 no_channels 的集合合并,则优先级将决定使用什么。

搜索的命令集合

当用户发出命令时,它将与玩家此时可用的 合并 命令集合进行匹配。哪些命令集合可能会随时变化(例如,当玩家走进带有 Window 对象的房间时)。

当前有效的命令集合来自以下来源:

  • 当前活动 会话 存储的命令集合。默认是空的 SessionCmdSet,合并优先级为 -20

  • 定义在 帐户 上的命令集合。默认是合并优先级为 -10 的 AccountCmdSet。

  • 所有角色/对象的命令集合(假设帐户当前正在操纵这样的角色/对象)。合并优先级为 0

  • 角色携带的所有对象的命令集合(检查 call 锁)。如果在合并栈中启用了 no_objs 选项,则不会包括。

  • 角色当前位置的命令集合(检查 call 锁)。如果在合并栈中启用了 no_objs 选项,则不会包括。

  • 当前地点中对象的命令集合(检查 call 锁)。如果在合并栈中启用了 no_objs 选项,则不会包括。

  • 该位置中出口的命令集合。合并优先级为 +101。如果在合并栈中启用了 no_exits no_objs 选项,则不会包括。

  • 当前帐户或角色连接的所有频道的 频道 命令集合。合并优先级为 +101。如果在合并栈中启用了 no_channels 选项,则不会包括。

请注意,对象不 与其周围共享其命令。字符的命令集合不应共享,例如,否则所有其他字符在同一房间中都会因为发生多重匹配错误。对象共享其命令集合的能力由其 call 管理。例如, 字符对象 默认值为 call:false(),因此它们上的任何命令集合只能被自身访问,而无法被周围的其他对象访问。另一个例子可能是用 call:inside() 锁定对象,仅向在其内部的对象提供其命令,或用 cmd:holds() 使其命令仅在被持有时可用。

添加和合并命令集合

注意:这是一个高级主题。虽然了解这一点非常有用,但如果这是您第一次学习命令,您可能想跳过它。

命令集合具有可以将它们 合并 到新集合的特殊能力。哪些即将加入的命令最终会进入合并集合,由 合并规则 和两个集合的相对 优先级 定义。删除最后添加的集合将恢复到添加前的状态。

命令集合以无损坏的方式存储在对象的 cmdset 处理程序内部的堆栈中。此堆栈用于创建当前活动的“组合”命令集合。来自其他来源的命令集合也包括在合并中,例如同一房间中的对象(例如,供按下的按钮)或状态变化(例如,进入菜单)引入的集合。这些命令集合的顺序是在优先级之后,然后逆序合并。也就是说,优先级较高的集合将“合并到”优先级较低的集合之上。通过定义一个优先级在两个其他集合之间的命令集合,您可以确保它会在两者之间合并。

此堆栈中的第一个命令集合称为 默认命令集合,并受到意外删除的保护。运行 obj.cmdset.delete() 永远不会删除默认集合。相反,应在默认集合上添加新的命令集合以“隐藏”它,如下面所述。仅在您真正知道自己在做什么的情况下,才应使用特殊的 obj.cmdset.delete_default()

命令集合合并是一个高级功能,适用于实现强大的游戏效果。例如,想象一下玩家进入一个黑暗房间。您不想让玩家一下子找到房间中的所有东西 - 也许您甚至希望他们很难在背包中找到东西!然后,您可以定义一个不同的命令集合,其中的命令覆盖正常命令。在他们处于黑暗房间时,也许 lookinv 命令现在只是告诉玩家他们什么都看不见!另一个示例是在玩家战斗时仅提供特殊战斗命令。或者当在船上时。或者在获得超级增强时。所有这些都可以通过合并命令集合随时完成。

合并规则

基本规则是命令集合按 反优先级顺序 合并。也就是说,较低优先级的集合先合并,而较高优先级的集合则合并在其“上面”。可以将其视为分层蛋糕,优先级最高位于顶部。

为了更好地理解集合如何合并,我们需要定义一些示例。我们称第一个命令集合为 A,第二个命令集合为 B。我们假设 B 是我们对象上已经激活的命令集合,且将要将 A 合并到 B 中。在代码上,这将通过 object.cmdset.add(A) 完成。请记住,B 之前已在 object 上处于活跃状态。

我们让 A 集合具有比 B 更高的优先级。优先级只是一个整数。正如上述列表所示,Evennia 的默认命令集合优先级在 -101120 之间。通常,您可以安全地使用 01 的优先级来实现大多数游戏效果。

在我们的示例中,两个集合都包含多个命令,我们将用数字标识它们,例如 A1, A2 为集合 A,而 B1, B2, B3, B4 为集合 B。因此,在该示例中,两个集合都包含相同键(或别名)的命令“1”和“2”(在真实游戏中,这可能是“look”和“get”),而命令 34B 特有的。为了描述这些集合之间的合并,我们将写 A1,A2 + B1,B2,B3,B4 = ? ,其中 ? 是根据 A 的合并类型和两个集合的相对优先级得出的命令列表。根据约定,我们将此语句理解为“新的命令集合 A 合并到旧的命令集合 B 中形成 ?”。

以下是可用的合并类型及其工作方式。名称部分借用了 集合理论

  • Union(默认) - 这两个命令集合合并,以便尽可能多的集合中的命令进入合并集合。相同键的命令由优先级进行合并。

       # Union
       A1,A2 + B1,B2,B3,B4 = A1,A2,B3,B4
    
  • Intersect - 仅在 两个 命令集合中找到的命令(即具有相同键的命令)将最终出现在合并命令集合中,并且优先级更高的命令集合将替换较低的命令集合的命令。

       # Intersect
       A1,A3,A5 + B1,B2,B4,B5 = A1,A5
    
  • Replace - 高优先级命令集合的命令完全替换低优先级命令集合的命令,无论是否存在相同键命令。

       # Replace
       A1,A3 + B1,B2,B4,B5 = A1,A3
    
  • Remove - 高优先级命令集合从低优先级命令集合中移除相同键命令。它们不会被其他命令替换,因此这是一种使用高优先级命令集合作为模板修剪低优先级集合的过滤器。

       # Remove
       A1,A3 + B1,B2,B3,B4,B5 = B2,B4,B5
    

除了 prioritymergetype 外,命令集合还采用一些其他变量,以控制它们的合并方式:

  • duplicates(布尔值) - 确定当两个相同优先级的集合合并时会发生什么。默认情况下,合并中的新集合(即上面的 A)自动占优。但是如果 duplicates 为真,则结果将是匹配的每个名称都有多个。这通常会导致玩家在后续操作中遇到多重匹配错误,但对命令集合在房间内的非玩家对象等情况非常有用,以允许系统发出警告,指出房间中多个’球’具有相同的’踢’命令,并提供选择要踢哪个球的机会…… 仅在 UnionIntersect 的情况下允许重复,其他合并类型将忽略该设置。

  • key_mergetypes(字典) - 允许命令集合为特定命令集合定义唯一的合并类型,通过它们的命令集合 key 识别。格式为 {CmdSetkey:mergetype}。示例:{'Myevilcmdset':'Replace'},这确保该集合在合并时始终对其命令集合使用“Replace”,无论主 mergetype 设置为何。

警告:key_mergetypes 字典 只能在我们合并到的命令集合上工作。使用 key_mergetypes 时,因此需要考虑合并优先级 - 您必须确保选择的优先级 位于 您要检测的命令集合和下一个更高的命令集合之间,如果有的话。也就是说,如果我们定义一个优先级较高的命令集合,并将其设置为影响优先级较低的命令集合,我们将不会在合并时“看到”该集合。例如,合并栈为 A(prio=-10), B(prio=-5), C(prio=0), D(prio=5)。现在我们将优先级为 10 的命令集合 E 合并到这个堆栈中,并使用 key_mergetype={"B":"Replace"}。但优先级决定我们不会在 B 上合并,我们将在 E 上合并(此时是较低优先级集合的合并)。由于我们合并到 E 而不是 B,因此我们的 key_mergetype 指令不会被触发。为确保它起作用,我们必须确保在 B 上合并。将 E 的优先级设置为,例如 -4,将确保它被合并到 B 上并适当地影响它。

高级 CmdSet 示例:

from commands import mycommands

class MyCmdSet(CmdSet):

    key = "MyCmdSet"
    priority = 4
    mergetype = "Replace"
    key_mergetypes = {'MyOtherCmdSet':'Union'}

    def at_cmdset_creation(self):
        """
        此方法需要做的只是添加命令到集合中。
        """
        self.add(mycommands.MyCommand1())
        self.add(mycommands.MyCommand2())
        self.add(mycommands.MyCommand3())

其他注意事项

非常重要的是要记住,两个命令 通过它们的 key 属性和它们的 aliases 属性进行比较。如果键或别名中的任意一个匹配,则这两个命令被视为 相同。因此,考虑这两个命令:

  • 一个命令以键 “kick” 和别名 “fight” 存在

  • 另一个命令以键 “punch” 也存在,且有一个别名 “fight”

在命令集合合并过程中(这会一直发生,因为还会合并诸如频道命令和出口命令等),这两个命令将被视为 相同,因为它们共享别名。它意味着在合并后只会保留其中一个。每个命令还将与所有其他具有任意组合键和/或别名为 “kick”、“punch” 或 “fight” 的命令进行比较。

… 所以请避免重复的别名,这只会导致混淆。