9. 解析指令輸入¶
在本課中,我們學習一些關於解析指令輸入的基礎知識。我們會 也瞭解如何新增、修改和擴充套件 Evennia 的預設指令。
9.1. 更進階的解析¶
在上一課中,我們發出了hit指令並用它攻擊了一條龍。您應該還保留著該程式碼。
讓我們擴充套件簡單的 hit 指令來接受更複雜的輸入:
hit <target> [[with] <weapon>]
也就是說,我們要支援所有這些形式
hit target
hit target weapon
hit target with weapon
如果你沒有指定武器,你就會使用拳頭。能夠跳過“with”if也很好
你很著急。是時候再修改mygame/commands/mycommands.py了。讓我們用一種新方法 parse 稍微分解解析一下:
1#...
2
3class CmdHit(Command):
4 """
5 Hit a target.
6
7 Usage:
8 hit <target>
9
10 """
11 key = "hit"
12
13 def parse(self):
14 self.args = self.args.strip()
15 target, *weapon = self.args.split(" with ", 1)
16 if not weapon:
17 target, *weapon = target.split(" ", 1)
18 self.target = target.strip()
19 if weapon:
20 self.weapon = weapon[0].strip()
21 else:
22 self.weapon = ""
23
24 def func(self):
25 if not self.args:
26 self.caller.msg("Who do you want to hit?")
27 return
28 # get the target for the hit
29 target = self.caller.search(self.target)
30 if not target:
31 return
32 # get and handle the weapon
33 weapon = None
34 if self.weapon:
35 weapon = self.caller.search(self.weapon)
36 if weapon:
37 weaponstr = f"{weapon.key}"
38 else:
39 weaponstr = "bare fists"
40
41 self.caller.msg(f"You hit {target.key} with {weaponstr}!")
42 target.msg(f"You got hit by {self.caller.key} with {weaponstr}!")
43# ...
parse 方法是一種特殊的方法,Evennia 知道在 before func 之前呼叫。此時,它可以存取與 func 相同的所有指令變數。使用 parse 不僅使內容更容易閱讀,還意味著您可以輕鬆地讓其他指令_繼承_您的解析 - 如果您希望其他指令也理解 <arg> with <arg> 形式的輸入,您可以從此類繼承,並且只需實現該指令所需的 func 而無需重新實現 parse。
第 14 行 - 我們在這裡一勞永逸地剝離
self.args。我們還將剝離版本儲存回來 到self.args中,覆蓋它。所以從現在開始就沒有辦法找回未剝離的版本,這很好 對於這個指令。第 15 行 - 這使用了字串的
.split方法。.split將依照某種標準分割字串。.split(" with ", 1)means “split the string once, around the substring" with "if it exists”. The result of this split is a list. Just how that list looks depends on the string we are trying to split:If we entered just
hit smaug, we’d be splitting just"smaug"which would give the result["smaug"].hit smaug swordgives["smaug sword"]hit smaug with swordgives["smaug", "sword"]
So we get a list of 1 or 2 elements. We assign it to two variables like this,
target, *weapon =. That asterisk in*weaponis a nifty trick - it will automatically become a tuple of 0 or more values. It sorts of “soaks” up everything left over.targetbecomes"smaug"andweaponbecomes()(an empty tuple)targetbecomes"smaug sword"andweaponbecomes()targetbecomes"smaug"andweaponbecomes("sword",)(this is a tuple with one element, the comma is required to indicate this).
第 16-17 行 - 在這個
if條件下,我們檢查weapon是否為假(即空清單)。這可能會發生 under two conditions (from the example above):targetis simplysmaugtargetissmaug sword
To separate these cases we split
targetonce again, this time by empty space" ". Again we store the result back withtarget, *weapon =. The result will be one of the following:targetremains"smaug"andweaponremains[]targetbecomes"smaug"andweaponbecomes("sword",)
第 18-22 行 - 我們現在將
target和weapon儲存到self.target和self.weapon中。我們必須儲存在self上,以便這些區域性變數稍後在func中可用。請注意,一旦我們知道weapon存在,它一定是一個元組(如("sword",)),因此我們使用weapon[0]來獲取該元組的第一個元素(Python 中的元組和列表從 0 開始索引)。指令weapon[0].strip()可以理解為「取得儲存在元組weapon中的第一個字串,並使用.strip()刪除其上的所有多餘空格」。如果我們忘記了[0],我們會得到一個錯誤,因為元組(與元組中的字串不同)沒有.strip()方法。
現在進入 func 方法。主要差異是我們現在有 self.target 和 self.weapon 可供方便使用。
第 29 行和第 35 行 - 我們利用先前解析的目標和武器搜尋字詞來找出 respective resource.
第 34-39 行 - 由於武器是可選的,因此如果未設定,我們需要提供預設值(使用我們的拳頭!)。我們 use this to create a
weaponstrthat is different depending on if we have a weapon or not.第 41-42 行 - 我們將
weaponstr與我們的攻擊文字合併,並將其分別傳送給攻擊者和目標。
我們來嘗試一下吧!
> reload
> hit smaug with sword
Could not find 'sword'.
You hit smaug with bare fists!
糟糕,self.caller.search(self.weapon) 告訴我們它沒有找到劍。這是合理的(我們沒有劍)。因為我們不會像找不到target時那樣return找不到武器,所以我們仍然繼續徒手戰鬥。
這不行。讓我們為自己打造一把劍:
> create sword
由於我們沒有指定 /drop,劍最終會出現在我們的庫存中,並且可以使用 i 或 inventory 指令看到。 .search 助手仍會在那裡找到它。無需重新載入即可看到此更改(未更改程式碼,僅更改資料庫中的內容)。
> hit smaug with sword
You hit smaug with sword!
可憐的史矛革。
9.2. 向物件新增指令¶
正如我們在新增指令 課程中學到的,指令被分組在指令集中。此類指令集附加到具有 obj.cmdset.add() 的物件,然後可供該物件使用。
我們之前沒有提到的是,預設情況下,這些指令_也可供與該物件位於相同位置的指令使用_。如果您上過建立快速入門課程,您會看到帶有「紅色按鈕」物件的範例。 教學世界 還有許多帶有指令的物件範例。
為了展示這是如何運作的,讓我們將「hit」指令放在上一節中的簡單 sword 物件上。
> py self.search("sword").cmdset.add("commands.mycommands.MyCmdSet", persistent=True)
我們找到了劍(它仍在我們的庫存中,所以self.search應該能夠找到它),然後
新增 MyCmdSet 到它。這實際上給劍增加了hit和echo,這很好。
讓我們試著搖擺它吧!
> hit
More than one match for 'hit' (please narrow target):
hit-1 (sword #11)
hit-2
哇哦,事情沒有照計劃進行。 Evennia 實際上找到_兩個_ hit 指令,但不知道該使用哪一個(_我們_知道它們是相同的,但 Evennia 無法確定)。正如我們所看到的,hit-1是在劍上發現的。另一種是之前給我們自己加上MyCmdSet。很容易告訴 Evennia 你指的是哪一個:
> hit-1
Who do you want to hit?
> hit-2
Who do you want to hit?
在這種情況下,我們不需要這兩個指令集,我們應該放棄我們自己的 hit 版本。
轉到mygame/commands/default_cmdsets.py並找到您新增的行
MyCmdSet 在上一堂課中。刪除或註解掉:
# mygame/commands/default_cmdsets.py
# ...
class CharacterCmdSet(default_cmds.CharacterCmdSet):
# ...
def at_object_creation(self):
# self.add(MyCmdSet) # <---------
接下來reload,您將只有一個可用的hit指令:
> hit
Who do you want to hit?
現在嘗試建立一個新位置,然後將劍放入其中。
> tunnel n = kitchen
> n
> drop sword
> s
> hit
Command 'hit' is not available. Maybe you meant ...
> n
> hit
Who do you want to hit?
只有當您持有或與劍位於同一房間時,hit 指令才可用。
9.2.1. 你需要握緊劍!¶
讓我們稍微超前一點,讓你必須握住劍才能使用 hit 指令。這涉及到Lock。稍後我們將更詳細地介紹鎖,只需知道它們對於限制您可以對物件執行的操作型別非常有用,包括限制您何時可以對其呼叫指令。
> py self.search("sword").locks.add("call:holds()")
我們為劍增加了一個新的lock。 lockstring "call:holds()" 表示如果您_持有_該物件(也就是說,它在您的庫存中),您只能_呼叫_該物件上的指令。
要使鎖發揮作用,您不能成為_超級使用者_,因為超級使用者會傳遞所有鎖。你需要先quell自己:
> quell
如果劍落在地上,請嘗試
> hit
Command 'hit' is not available. ..
> get sword
> hit
> Who do you want to hit?
在我們揮舞劍(擊中一兩條龍)後,我們將擺脫我們的劍,這樣我們就可以重新開始,不再有 hit 指令漂浮在周圍。我們可以透過兩種方式做到這一點:
delete sword
或者
py self.search("sword").delete()
9.3. 將指令新增到預設Cmdset¶
正如我們所看到的,我們可以使用 obj.cmdset.add() 向物件新增新的 cmdset,無論該對像是我們自己 (self) 還是其他物件,例如 sword。雖然這樣做有點麻煩。最好將這個新增到所有字元中。
預設的 cmdset 在 mygame/commands/default_cmdsets.py 中定義。現在開啟該檔案:
"""
(module docstring)
"""
from evennia import default_cmds
class CharacterCmdSet(default_cmds.CharacterCmdSet):
key = "DefaultCharacter"
def at_cmdset_creation(self):
super().at_cmdset_creation()
#
# any commands you add below will overload the default ones
#
class AccountCmdSet(default_cmds.AccountCmdSet):
key = "DefaultAccount"
def at_cmdset_creation(self):
super().at_cmdset_creation()
#
# any commands you add below will overload the default ones
#
class UnloggedinCmdSet(default_cmds.UnloggedinCmdSet):
key = "DefaultUnloggedin"
def at_cmdset_creation(self):
super().at_cmdset_creation()
#
# any commands you add below will overload the default ones
#
class SessionCmdSet(default_cmds.SessionCmdSet):
key = "DefaultSession"
def at_cmdset_creation(self):
super().at_cmdset_creation()
#
# any commands you add below will overload the default ones
#
evennia.default_cmds 是一個容器,包含Evennia 的所有預設指令和cmdsets。在此模組中,我們可以看到它已匯入,然後為每個 cmdset 建立了一個新的子類別。每個類別看起來都很熟悉(key 除外,它主要用於輕鬆識別清單中的 cmdset)。在每個 at_cmdset_creation 中,我們所做的就是呼叫 super().at_cmdset_creation,這表示我們在 parent CmdSet 上呼叫 `at_cmdset_creation()。
這就是將所有預設指令新增到每個 CmdSet 的原因。
當建立 DefaultCharacter (或其子層級)時,您會發現呼叫了 self.cmdset.add("default_cmdsets.CharacterCmdSet, persistent=True") 的等效項。這意味著所有新角色都會獲得此 cmdset。新增更多指令後,您只需重新載入即可讓所有角色看到它。
角色(即遊戲世界中的「你」)具有
CharacterCmdSet。帳戶(代表您在伺服器上的異常存在的事物)具有
AccountCmdSetSessions(代表一個用戶端連線)具有
SessionCmdSet在您登入之前(在連線畫面上),您的 Session 可以存取
UnloggedinCmdSet。
現在,讓我們將自己的 hit 和 echo 指令加入 CharacterCmdSet 中:
# ...
from commands import mycommands
class CharacterCmdSet(default_cmds.CharacterCmdSet):
key = "DefaultCharacter"
def at_cmdset_creation(self):
super().at_cmdset_creation()
#
# any commands you add below will overload the default ones
#
self.add(mycommands.CmdEcho)
self.add(mycommands.CmdHit)
> reload
> hit
Who do you want to hit?
您的新指令現在可用於遊戲中的所有玩家角色。還有另一種方法可以一次加一堆指令,那就是把你自己的_CmdSet_加到其他cmdset中。
from commands import mycommands
class CharacterCmdSet(default_cmds.CharacterCmdSet):
key = "DefaultCharacter"
def at_cmdset_creation(self):
super().at_cmdset_creation()
#
# any commands you add below will overload the default ones
#
self.add(mycommands.MyCmdSet)
你使用哪種方式取決於你想要多少控制權,但如果你已經有CmdSet, 這是實用的。一個指令可以是任意數量的不同CmdSets的一部分。
9.3.1. 刪除指令¶
要再次刪除自訂指令,您當然只需刪除您所做的更改即可
mygame/commands/default_cmdsets.py。但是如果您想刪除預設指令怎麼辦?
我們已經知道我們使用 cmdset.remove() 來刪除 cmdset。事實證明你可以
在 at_cmdset_creation 中執行相同操作。例如,讓我們刪除預設的 get 指令
從Evennia開始。如果您調查 default_cmds.CharacterCmdSet 父級,您會發現它的類別是 default_cmds.CmdGet(「真實」位置是 evennia.commands.default.general.CmdGet)。
# ...
from commands import mycommands
class CharacterCmdSet(default_cmds.CharacterCmdSet):
key = "DefaultCharacter"
def at_cmdset_creation(self):
super().at_cmdset_creation()
#
# any commands you add below will overload the default ones
#
self.add(mycommands.MyCmdSet)
self.remove(default_cmds.CmdGet)
# ...
> reload
> get
Command 'get' is not available ...
9.4. 替換預設指令¶
此時,您已經掌握瞭如何執行此操作的所有內容!我們只需要增加一個新的
指令中的CharacterCmdSet用相同的key來替換預設的。
讓我們將其與我們對類別的瞭解以及如何_覆蓋_父類結合。開啟 mygame/commands/mycommands.py 並建立一個新的 get 指令:
1# up top, by the other imports
2from evennia import default_cmds
3
4# somewhere below
5class MyCmdGet(default_cmds.CmdGet):
6
7 def func(self):
8 super().func()
9 self.caller.msg(str(self.caller.location.contents))
第2行:我們匯入
default_cmds,這樣我們就可以獲得父類別。 我們建立了一個新類,並使其_繼承_default_cmds.CmdGet。我們不 需要設定.key或.parse,這已由父級處理。 在func中,我們呼叫super().func()讓父行程做正常的事情,第 7 行:透過新增我們自己的
func,我們取代了父級中的func。第 8 行:對於這個簡單的更改,我們仍然希望該指令能夠正常工作 和以前一樣,所以我們使用
super()來呼叫父級上的func。第 9 行:
.location是物件所在的位置。.contents包含,嗯, contents of an object. If you triedpy self.contentsyou’d get a list that equals your inventory. For a room, the contents is everything in it. Soself.caller.location.contentsgets the contents of our current location. This is a list. In order send this to us with.msgwe turn the list into a string. Python has a special functionstr()to do this.
我們現在只需新增此指令即可替換預設的 get 指令。開啟
再次mygame/commands/default_cmdsets.py:
# ...
from commands import mycommands
class CharacterCmdSet(default_cmds.CharacterCmdSet):
key = "DefaultCharacter"
def at_cmdset_creation(self):
super().at_cmdset_creation()
#
# any commands you add below will overload the default ones
#
self.add(mycommands.MyCmdSet)
self.add(mycommands.MyCmdGet)
# ...
我們不需要先使用self.remove();只需新增具有相同key (get) 的指令將替換我們之前的預設get。
> reload
> get
Get What?
[smaug, fluffy, YourName, ...]
我們剛剛建立了一個新的 get 指令,它告訴我們可以拾取的所有內容(好吧,我們無法拾取自己,所以還有一些改進的空間…)。
9.5. 概括¶
在本課中,我們學習了一些更高階的字串格式 - 其中許多技巧將對您將來有很大幫助!我們還製作了一把實用的劍。最後我們討論瞭如何新增、擴充套件和替換我們自己的預設指令。知道新增指令是製作遊戲的重要組成部分!
我們已經打敗可憐的史矛革太久了。接下來我們將創造更多可以玩的東西。