命令集


命令集是和命令紧密联系在一起的,在阅读本页面内容之前你应该先熟悉命令文档的内容。将这两篇文档分开只是为了便于阅读。

命令集(通常被称为 CmdSet 或 cmdset)是存储一条或多条命令的基本单元。一条命令可以归入任意数量的不同命令集。要让命令在你的游戏中可用,唯一途径就是将命令的类放入命令集中。

你将命令集添加到对象上之后,对象就可以使用这个命令集中的命令了。加上特定标志后,同一房间的其它对象也可以使用这些命令。

一个例子是添加在新角色上的默认命令集。这个命令集包含了所有有用的命令,从 look 和 inventory 到 @dig 和 @reload(权限会限制玩家可以使用哪些命令,不过这是另外的话题了)。

另一个例子是一个窗户对象,在它的命令集中有两个命令:look through window(望出窗外) 和 open window(打开窗子)。这个命令集在有窗户的房间中对玩家可用,这样就能够限制玩家只能在这样的房间中使用这些命令。你可以想出许多灵活的用途,比如有多个频道的电视对象,可以对它切换频道等等。

命令集可以加在游戏会话物体玩家上。还有一种特殊的(会话)命令集,供玩家在登录进游戏之前使用。

如果你想赶快开始定义你的第一个命令,并在命令集中使用它们,你可以前往添加命令教程,它会一步步地教你该如何做,而且没有多余的解释。


命令集的搜索范围

当用户发出了一个命令,它会和合并后的玩家当前有效的命令集做匹配,这可能随时发生变化(比如当玩家走进带有前面所说的窗户对象的房间时)。

当前有效的命令集从下列地方获取:

  • 角色对象的有效命令集
  • 角色所携带物品的命令集
  • 当前位置的命令集
  • 处于当前位置的其它对象的命令集(包括出口)
  • 控制角色的玩家的命令集(在前面所举的例子中我们说的是“角色”对象,由于角色对象也只是普通的对象,实际上这种查找适用于任何对象。一个NPC或一个花盆都可以通过同样的方式取得它们的有效命令集)。
  • 有效频道的命令集

它们按照各自的优先级合并在一起,在后面会进一步讨论。


默认命令集

Evennia有四个默认命令集,在游戏的不同部分单独或一起使用。如果你需要,可以自由添加命令集或扩展这些命令集。在添加命令集时,下面所说的“合并优先级”很重要:为了让命令集与某个命令集合并而不和另一个合并,你要让新命令集的优先级处于这两者之间。在后面会有更多的解释。

  • DefaultSession(src.commands.default.cmdset_session.SessionCmdSet)这会在登录时加到会话上。这个命令集默认是没有命令的。它的目的是加载与会话相关的非角色命令,比如角色创造系统。默认使用的命令集由settings.CMDSET_SESSION设定,合并优先级为-20。
  • DefaultPlayer(src.commands.default.cmdset_player.PlayerCmdSet)这是添加在玩家上的命令集,包含了以账户为核心的非角色命令,如发送文字到频道的命令及工作人员的管理命令等。它由settings.CMDSET_PLAYER设定,合并优先级为-10。
  • DefaultCharacter(src.commands.default.cmdset_character.CharacterCmdSet)这是最终添加在角色对象上的命令集,包含了该角色可用的所有角色内控制命令。这是最大的命令集。玩家要使用这些命令必须要先操纵角色。命令集由settings.CMDSET_CHARACTER设定,合并优先级为0。
  • DefaultUnloggedin(src.commands.default.cmdset_unloggedin.UnloggedinCmdSet)这个命令集包含了在你登录到服务器之前所用的命令,如欢迎界面、连接、创建角色等。你可以在settings.CMDSET_UNLOGGEDIN中设定命令集的路径。这个命令集永远不会和其它命令集合并,所以它的合并优先级(为0)没什么用处,除非你想扩展未登录状态下的操作。

除了未登录命令集,这些不同层次的命令集会按照层级“向下”合并。所以在合并(优先级)时,会话命令集会最先合并,然后是玩家,最后是角色(因此角色命令集会覆盖玩家命令集,而玩家命令集又会覆盖会话命令集)。合并的详细规则请参阅下一节内容。


定义命令集

命令集和Evennia中的大多数东西一样,都是从其父类(ev.CmdSet 或 src.commands.cmdset.CmdSet)继承的Python类。命令集类只需要定义一个名为 at_cmdset_creation() 的方法。其它所有的类参数都是可选的,用于更高级的集合操作和编码(见合并规则部分)。
    from ev import CmdSet
    from game.gamesrc.commands import mycommands
    class MyCmdSet(CmdSet):    
        def at_cmdset_creation(self):
            """
            The only thing this method should need
            to do is to add commands to the set.
            """ 
            self.add(mycommands.MyCommand1())
            self.add(mycommands.MyCommand2())
            self.add(mycommands.MyCommand3())
命令集的add()方法还可以将另一个命令集作为输入。在这种情况下,另一个命令集中的所有命令都会添加进来,就像你一行一行加入它们一样:
       def at_cmdset_creation(): 
           ...
           self.add(AdditionalCmdSet) # adds all command from this set
           ...
如果你将命令添加到已经加载到内存的现有命令集中(比如默认命令集),你需要让服务器知道代码发生了变化:
    @reload 
现在你应该可以使用命令了。

如果你创建了全新的命令集,为了让其中的命令生效,还必须将它添加到一个对象上。有一个简单的方法做临时测试,可以用 @py 命令执行一段Python代码,将命令集添加到你自己身上:
    @py self.cmdset.add('game.gamesrc.commands.mycmdset.MyCmdSet')
它会一直附在你身上,直到你对服务器使用 @reset 或 @shutdown,或执行
    @py self.cmdset.delete('game.gamesrc.commands.mycmdset.MyCmdSet')
阅读这篇手把手教你设置持久性命令的教程,可以快速学习以不同方式做设置。一般来说,你可以通过 self.cmdset.add() 或 self.cmdset.add_default() 将自定义的命令集添加到你的对象上。

命令集的属性
可以对命令集设置一些额外的标志以修改它们工作方式。这些都是可选的,需要另行设置为默认值。由于许多设置与合并命令集有关,你可以阅读下一节内容以初步了解相关信息。

  • key(字符串) —— 命令集的标识符。这是可选的,但它必须是唯一的。它可用于在列表中显示,也可以用在下面所述的“key_mergetype”词典中用来制定特殊的合并行为。
  • mergetype(字符串) —— “Union”(并集)、“Intersect”(交集)、“Replace”(替换)或“Remove”(去除)中的一个。
  • priority (整型) —— 它定义了合并的顺序,数字越大合并的优先级越高。这个值必须大于等于-100,游戏中大多数命令集的优先级应该设在0到100之间。Evennia默认命令集的优先级如下(如果你想分配不一样值,也可以修改它):
    • 空命令集:-101(应该比其它所有的命令集都低)
    • 会话命令集:-20
    • 玩家命令集:-10
    • 角色命令集:0
    • 出口命令集:101(通常总是有效的)
    • 频道命令集:120(应该总是有效的)
  • key_mergetype(字典) —— “关键字:合并类型”的字典。这可以让命令集在合并某些名字的命令集时,使用不同的方式来合并。如果合并命令集的关键字与 key_mergetype 中的条目匹配,它就不会按照 mergetype 中的合并方式合并,而是按照设定在 key_mergetype 中的合并方式合并。请注意,由于受到合并顺序的影响,实际操作起来要比想像得复杂。在使用 key_mergetype 之前请仔细阅读有关合并顺序的内容。
  • duplicates(布尔值,默认为false) —— 在合并相同优先级的命令集时,如果命令的关键字相同,只会保留合并进来的命令集中的命令,结果是只有一个命令留下来。如果设置了这个标志,则会保留多个具有相同关键字的命令。这样做通常会导致玩家在使用命令时报出多重匹配的错误信息。举个例子,这对添加在物体上的命令比较有用(比如在房间中有一个红色按钮和一个绿色按钮,它们都有 press button (按按钮)命令,它们的命令集具有相同的优先级。设置这个标志后,如果玩家只输入 press button,可以强制玩家选择到底按哪个物体上的按钮)。
  • no_objs 这个标志供命令处理程序在构建当前有效命令集时使用。它告诉处理程序不要包含玩家周围物体(或房间中其它物体)的命令集。出口的命令仍会被包含在内。
  • no_exits 这个标志供命令处理程序在构建当前有效命令集时使用。它告诉处理程序不要包含出口的命令集。
  • is_exit(布尔值) —— 表示该命令集被游戏中的出口使用。当其它命令集设置了 no_exits 时,这可以让命令处理程序快速忽略掉这个命令集。
  • no_channels(布尔值) —— 这个标志供命令处理程序在构建当前有效命令集时使用。它告诉处理程序不要包含游戏中频道的命令集。
  • is_channel(布尔值) —— 表示该命令集被游戏中的频道使用。当其它命令集设置了 no_channels 时,这可以让命令处理程序快速忽略掉这个命令集。这个值是频道对象在初始化命令集时直接设置的。


添加、合并命令集

注:这是进阶内容。了解这些内容非常有用,但如果你是第一次学习命令,你可以跳过它。

命令集具有特殊的能力,它们可以相互合并成新的命令集。举个例子,如果一个对象上已经定义了命令集,你再对它执行 object.cmdset.add(MyCmdSet) 就会发生合并。这两个命令集会被评估,并将其中的命令合并成一个临时集合。只有合并后的集合中的命令是有效的。

什么命令会留在合并后的集合中是由合并规则及两个命令集的相对优先级决定的。去除最新添加的命令集可以回到添加这个命令集之前的状态。

命令集被非破坏性地存储在命令集处理程序的内部堆栈中。解析这个堆栈可以构造出当前有效的“组合”命令集。其它来源的命令集也被合并在内,比如处于同一房间的对象(如可按的按钮)或那些由状态变化引入的命令集(如进入菜单时)。命令集按优先级排序,然后再按逆序合并。也就是说,高优先级的命令集会被合并到低优先级的命令集中。将某个命令集的优先级设在另外两个命令集之间,可以确保它在这两个命令集之间被合并。

这个堆栈中的第一个命令集被称为默认命令集,它被保护防止意外删除。执行 obj.cmdset.delete() 永远无法删除默认命令集。但你可以像下面所说的那样,通过在默认命令集顶部添加新的命令集来“隐藏”它。只有在你真的知道自己在做什么的时候,才能使用特殊的 obj.cmdset.delete_default() 命令。

命令集合并是一项高级功能,可以实现强大的游戏效果。想象一下,比如玩家走入一个黑暗的房间,你不希望房间里的东西对玩家都一目了然,你甚至可能想让玩家在自己的背包里找东西也会有困难!你可以定义不同的命令集来覆盖正常的命令集。当玩家处于黑暗的房间中时,也许 look 和 inv 命令只会告诉玩家他们什么都看不到!另一个例子是,只有当玩家进入战斗时才提供特殊的战斗命令。还有,当处于船上时或者取得了超能力时……这一切都可以通过合并命令集快速实现。

合并规则
基本规则是命令集按照优先级从低到高进行合并。也就是说,低优先级的命令集先合并,然后高优先级的命令集从“顶上”加入它们。可以把它想像成一个多层的蛋糕,最高优先级的在最顶上。

为了进一步说明命令集是如何合并的,我们需要做一些简单的定义:让我们把第一个命令集称为A,第二个称为B。我们假定B是先前已经添加到对象上的有效命令集,现在要将A合并到B上, 因此在代码中我们要用命令 object.cdmset.add(A)。

我们让A的优先级高于B。优先级是一个简单的整数,默认值是0。Evennia内置的高优先级命令(期望能覆盖其它命令集)的值是9或10。

这两个命令集都包含许多用数字标志的命令,比如A1、A2是A命令集的,B1、B2、B3、B4是B命令集的。假定A和B中都包含命令1和2,它们的关键字(或别名)相同;而命令3和4是B独有的。我们会用 {{{A1,A2 + B1,B2,B3,B4 = ?}}} 来描述这两个命令集的合并,其中 ? 是一个命令列表,它的值要按A的合并类型以及A、B的相对优先级而定。按惯例,我们将这条语句读为“新命令集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
    

除了优先级和合并类型,命令集中还有其他一些变量会影响合并的方式:
  • duplicates(布尔值) —— 决定当两个具有相同优先级的命令集合并时该如何处理。默认情况是新加入的命令集(即上面的A)会自动优先。但是如果 duplicates 为 True,合并后的命令集中可能出现某个名字有多个匹配的命令。这通常会导致玩家在输入命令时收到匹配多个命令的错误信息,但比如房间中有多个带有命令集的非玩家对象,这就很有用了。当房间中有多个带有相同“踢”命令的“球”时,系统会发出警告,提供机会让玩家来选择到底踢哪个球... duplicates 只对 Union 和 Intersect 有意义,其它合并类型会忽略这项设置。
  • key_mergetypes(字典) —— 允许对具有指定 key 的命令集设置特别的合并类型。格式为 {CmdSetkey:mergetype}。例如:{'Myevilcmdset','Replace'} 这可以确保该命令集只对关键字为 Myevilcmdset 的命令集使用“Replace”,而不管它的 mergetype 设置的是什么。key_mergetypes 字典只对当前合并到的命令集有效,因此在使用 key_mergetypes 时需要重点考虑合并的优先级 —— 你必须确保该命令集的优先级比需要检测 key_mergetypes 的命令集高,并且比之后的命令集低(如果有的话)。也就是说,如果我们定义了一个高优先级的命令集,却希望它影响远在合并堆栈深处的命令集,在合并时其实是无法“看到”那个命令集的。例如:堆栈中的命令集为A(优先级=-10),B(优先级=-5),C(优先级=0),D(优先级=5)。现在我们合并命令集E(优先级=10)到这个堆栈,且设置了 key_mergetype={"B":"Replace"}。但优先级决定了我们不会被合并到B,而是被合并到E(这是当前较低优先级命令集的合并结果)。由于我们合并到的是E而不是B,我们 key_mergetype 设置不会被触发。为了确保它生效,我们必须确保合并入B。比如将E的优先级设置为-4,这能确保它合并到B,并且产生合适的效果。

更高级的命令集例子:
    class MyCmdSet(CmdSet):

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

        def at_cmdset_creation(self):
            """
            The only thing this method should need
            to do is to add commands to the set.
            """     
            self.add(mycommands.MyCommand1())
            self.add(mycommands.MyCommand2())
            self.add(mycommands.MyCommand3())
有很重要的一点需要记住,两条命令是通过关键字和别名来匹配的,只要有一个相同就会认为这两条命令是相同的。


(原文:https://github.com/evennia/evennia/wiki/Command-Sets    翻译:卢铱俊)