11. 回合製戰鬥¶
在本課程中,我們將在戰鬥基礎 的基礎上實現一個輪流執行的戰鬥系統,您可以在選單中選擇操作,如下所示:
> attack Troll
______________________________________________________________________________
You (Perfect) vs Troll (Perfect)
Your queued action: [attack] (22s until next round,
or until all combatants have chosen their next action).
______________________________________________________________________________
1: attack an enemy
2: Stunt - gain a later advantage against a target
3: Stunt - give an enemy disadvantage against yourself or an ally
4: Use an item on yourself or an ally
5: Use an item on an enemy
6: Wield/swap with an item from inventory
7: flee!
8: hold, doing nothing
> 4
_______________________________________________________________________________
Select the item
_______________________________________________________________________________
1: Potion of Strength
2. Potion of Dexterity
3. Green Apple
4. Throwing Daggers
back
abort
> 1
_______________________________________________________________________________
Choose an ally to target.
_______________________________________________________________________________
1: Yourself
back
abort
> 1
_______________________________________________________________________________
You (Perfect) vs Troll (Perfect)
Your queued action: [use] (6s until next round,
or until all combatants have chosen their next action).
_______________________________________________________________________________
1: attack an enemy
2: Stunt - gain a later advantage against a target
3: Stunt - give an enemy disadvantage against yourself or an ally
4: Use an item on yourself or an ally
5: Use an item on an enemy
6: Wield/swap with an item from inventory
7: flee!
8: hold, doing nothing
Troll attacks You with Claws: Roll vs armor (12):
rolled 4 on d20 + strength(+3) vs 12 -> Fail
Troll missed you.
You use Potion of Strength.
Renewed strength coarses through your body!
Potion of Strength was used up.
請注意,本文件不顯示遊戲中的顏色。另外,如果您對替代方案感興趣,請參閱上一課,其中我們透過為每個動作輸入直接指令來實現類似「抽搐」的戰鬥系統。
對於「回合製」戰鬥,我們指的是以較慢的速度「滴答作響」的戰鬥,速度足夠慢,足以讓參與者在選單中選擇他們的選項(選單並不是絕對必要的,但它也是學習如何製作選單的好方法)。他們的行動會排隊,並在回合計時器用完時執行。為了避免不必要的等待,當大家做出選擇後,我們也會進入下一輪。
回合製系統的優點是它消除了玩家的速度。你的戰鬥能力並不取決於你輸入指令的速度。對於 RPG- 重型遊戲,您還可以讓玩家有時間在戰鬥回合中做出 RP 表情,以充實動作。
使用選單的優點是您可以直接執行所有可能的操作,這使得初學者友好且易於知道您可以做什麼。這也意味著更少的寫作,這對某些玩家來說可能是一個優勢。
11.1. 一般原則¶
以下是回合製戰鬥處理程式的一般原理:
CombatHandler 的回合製版本將儲存在_目前位置_。這意味著每個地點只會發生一場戰鬥。任何其他開始戰鬥的人都會加入同一個處理者並被分配到一邊戰鬥。
處理程式將執行 30 秒的中央計時器(在本例中)。當它觸發時,所有排隊的操作都將被執行。如果每個人都提交了他們的操作,那麼這將在最後一個提交時立即發生。
在戰鬥中你將無法走動——你被困在房間裡。逃離戰鬥是一個單獨的動作,需要幾個回合才能完成(我們需要建立這個)。
開始戰鬥是透過
attack <target>指令完成的。之後,您將進入戰鬥選單,並將使用該選單執行所有後續操作。
11.2. 回合製戰鬥處理程式¶
建立一個新模組
evadventure/combat_turnbased.py。
# in evadventure/combat_turnbased.py
from .combat_base import (
CombatActionAttack,
CombatActionHold,
CombatActionStunt,
CombatActionUseItem,
CombatActionWield,
EvAdventureCombatBaseHandler,
)
from .combat_base import EvAdventureCombatBaseHandler
class EvadventureTurnbasedCombatHandler(EvAdventureCombatBaseHandler):
action_classes = {
"hold": CombatActionHold,
"attack": CombatActionAttack,
"stunt": CombatActionStunt,
"use": CombatActionUseItem,
"wield": CombatActionWield,
"flee": None # we will add this soon!
}
# fallback action if not selecting anything
fallback_action_dict = AttributeProperty({"key": "hold"}, autocreate=False)
# track which turn we are on
turn = AttributeProperty(0)
# who is involved in combat, and their queued action
# as {combatant: actiondict, ...}
combatants = AttributeProperty(dict)
# who has advantage against whom. This is a structure
# like {"combatant": {enemy1: True, enemy2: True}}
advantage_matrix = AttributeProperty(defaultdict(dict))
# same for disadvantages
disadvantage_matrix = AttributeProperty(defaultdict(dict))
# how many turns you must be fleeing before escaping
flee_timeout = AttributeProperty(1, autocreate=False)
# track who is fleeing as {combatant: turn_they_started_fleeing}
fleeing_combatants = AttributeProperty(dict)
# list of who has been defeated so far
defeated_combatants = AttributeProperty(list)
我們為 "flee" 操作留下一個佔位符,因為我們還沒有建立它。
由於回合製戰鬥處理程式在所有戰鬥人員之間共享,因此我們需要在處理程式上儲存對這些戰鬥人員的引用,在 combatants Attribute 中。 以同樣的方式,我們必須儲存一個誰對誰有優勢/劣勢的_矩陣_。我們還必須追蹤誰在逃跑,特別是他們逃跑了多長時間,因為在那之後他們就會離開戰鬥。
11.2.1. 取得戰鬥雙方¶
雙方的差異取決於我們是否在PvP房間中:在PvP房間中,其他人都是你的敵人。否則,戰鬥中只有 NPCs 是你的敵人(假設你與其他玩家組隊)。
# in evadventure/combat_turnbased.py
# ...
class EvadventureTurnbasedCombatHandler(EvAdventureCombatBaseHandler):
# ...
def get_sides(self, combatant):
"""
Get a listing of the two 'sides' of this combat,
from the perspective of the provided combatant.
"""
if self.obj.allow_pvp:
# in pvp, everyone else is an ememy
allies = [combatant]
enemies = [comb for comb in self.combatants if comb != combatant]
else:
# otherwise, enemies/allies depend on who combatant is
pcs = [comb for comb in self.combatants if inherits_from(comb, EvAdventureCharacter)]
npcs = [comb for comb in self.combatants if comb not in pcs]
if combatant in pcs:
# combatant is a PC, so NPCs are all enemies
allies = pcs
enemies = npcs
else:
# combatant is an NPC, so PCs are all enemies
allies = npcs
enemies = pcs
return allies, enemies
請注意,由於 EvadventureCombatBaseHandler(我們的回合處理程式所基於的)是 Script,因此它提供了許多有用的功能。例如,self.obj 是 Script “所在”的實體。由於我們計劃將此處理程式放在目前位置,因此 self.obj 將是該房間。
我們在這裡所做的就是檢查它是否是 PvP 房間,並用它來確定誰是盟友還是敵人。請注意,combatant _不_包含在 allies 回傳值中 - 我們需要記住這一點。
11.2.2. 追蹤優勢/劣勢¶
# in evadventure/combat_turnbased.py
# ...
class EvadventureTurnbasedCombatHandler(EvAdventureCombatBaseHandler):
# ...
def give_advantage(self, combatant, target):
self.advantage_matrix[combatant][target] = True
def give_disadvantage(self, combatant, target, **kwargs):
self.disadvantage_matrix[combatant][target] = True
def has_advantage(self, combatant, target, **kwargs):
return (
target in self.fleeing_combatants
or bool(self.advantage_matrix[combatant].pop(target, False))
)
def has_disadvantage(self, combatant, target):
return bool(self.disadvantage_matrix[combatant].pop(target, False))
我們使用 advantage/disadvantage_matrix 屬性來追蹤誰對誰有優勢。
在 has dis/advantage 方法中,我們從矩陣中提取 pop 目標,這將導致值 True 或 False(如果目標不在矩陣中,我們給出的預設值為 pop)。這意味著優勢一旦獲得,就只能使用一次。
我們也認為每個人在對抗逃跑的戰鬥人員時都具有優勢。
11.2.3. 新增和刪除戰鬥人員¶
由於戰鬥處理程式是共享的,我們必須能夠輕鬆新增和刪除戰鬥人員。 與基本處理程式相比,這是新的。
# in evadventure/combat_turnbased.py
# ...
class EvadventureTurnbasedCombatHandler(EvAdventureCombatBaseHandler):
# ...
def add_combatant(self, combatant):
"""
Add a new combatant to the battle. Can be called multiple times safely.
"""
if combatant not in self.combatants:
self.combatants[combatant] = self.fallback_action_dict
return True
return False
def remove_combatant(self, combatant):
"""
Remove a combatant from the battle.
"""
self.combatants.pop(combatant, None)
# clean up menu if it exists
# TODO!
我們只需新增帶有後備動作字典的戰鬥人員即可。我們從add_combatant返回bool,以便呼叫函式知道它們是否實際上是重新新增的(如果它們是新的,我們可能需要做一些額外的設定)。
現在我們只是 pop 戰鬥人員,但將來我們需要在戰鬥結束時對選單進行一些額外的清理(我們會做到這一點)。
11.2.4. 逃跑行動¶
由於你不能只是離開房間來逃離回合製戰鬥,我們需要新增一個新的 CombatAction 子類,就像我們在 基礎戰鬥課程 中建立的子類一樣。
# in evadventure/combat_turnbased.py
from .combat_base import CombatAction
# ...
class CombatActionFlee(CombatAction):
"""
Start (or continue) fleeing/disengaging from combat.
action_dict = {
"key": "flee",
}
"""
def execute(self):
combathandler = self.combathandler
if self.combatant not in combathandler.fleeing_combatants:
# we record the turn on which we started fleeing
combathandler.fleeing_combatants[self.combatant] = self.combathandler.turn
# show how many turns until successful flight
current_turn = combathandler.turn
started_fleeing = combathandler.fleeing_combatants[self.combatant]
flee_timeout = combathandler.flee_timeout
time_left = flee_timeout - (current_turn - started_fleeing) - 1
if time_left > 0:
self.msg(
"$You() $conj(retreat), being exposed to attack while doing so (will escape in "
f"{time_left} $pluralize(turn, {time_left}))."
)
class EvadventureTurnbasedCombatHandler(EvAdventureCombatBaseHandler):
action_classes = {
"hold": CombatActionHold,
"attack": CombatActionAttack,
"stunt": CombatActionStunt,
"use": CombatActionUseItem,
"wield": CombatActionWield,
"flee": CombatActionFlee # < ---- added!
}
# ...
我們建立動作來利用我們在戰鬥處理程式中設定的 fleeing_combatants 字典。該指令儲存了逃跑的戰鬥人員及其逃跑開始的turn。如果多次執行 flee 操作,我們將只顯示剩餘的回合數。
最後,我們確保將新的 CombatActionFlee 新增至戰鬥處理程式的 action_classes 登錄檔中。
11.2.5. 佇列動作¶
# in evadventure/combat_turnbased.py
# ...
class EvadventureTurnbasedCombatHandler(EvAdventureCombatBaseHandler):
# ...
def queue_action(self, combatant, action_dict):
self.combatants[combatant] = action_dict
# track who inserted actions this turn (non-persistent)
did_action = set(self.ndb.did_action or set())
did_action.add(combatant)
if len(did_action) >= len(self.combatants):
# everyone has inserted an action. Start next turn without waiting!
self.force_repeat()
為了對一個動作進行排隊,我們只需將其 action_dict 與戰鬥者一起存放在 combatants Attribute 中。
我們使用 Python set() 來追蹤本回合誰已將操作排隊。如果本回合所有戰鬥人員都輸入了新的(或更新的)動作,我們將使用 .force_repeat() 方法,該方法適用於所有 Scripts。當呼叫此函式時,下一輪將立即觸發,而不是等到逾時。
11.2.6. 執行一個動作並勾選該回合¶
1# in evadventure/combat_turnbased.py
2
3import random
4
5# ...
6
7class EvadventureTurnbasedCombatHandler(EvAdventureCombatBaseHandler):
8
9 # ...
10
11 def execute_next_action(self, combatant):
12 # this gets the next dict and rotates the queue
13 action_dict = self.combatants.get(combatant, self.fallback_action_dict)
14
15 # use the action-dict to select and create an action from an action class
16 action_class = self.action_classes[action_dict["key"]]
17 action = action_class(self, combatant, action_dict)
18
19 action.execute()
20 action.post_execute()
21
22 if action_dict.get("repeat", False):
23 # queue the action again *without updating the
24 # *.ndb.did_action list* (otherwise
25 # we'd always auto-end the turn if everyone used
26 # repeating actions and there'd be
27 # no time to change it before the next round)
28 self.combatants[combatant] = action_dict
29 else:
30 # if not a repeat, set the fallback action
31 self.combatants[combatant] = self.fallback_action_dict
32
33
34 def at_repeat(self):
35 """
36 This method is called every time Script repeats
37 (every `interval` seconds). Performs a full turn of
38 combat, performing everyone's actions in random order.
39 """
40 self.turn += 1
41 # random turn order
42 combatants = list(self.combatants.keys())
43 random.shuffle(combatants) # shuffles in place
44
45 # do everyone's next queued combat action
46 for combatant in combatants:
47 self.execute_next_action(combatant)
48
49 self.ndb.did_action = set()
50
51 # check if one side won the battle
52 self.check_stop_combat()
我們的操作執行由兩部分組成 - execute_next_action(在父類中定義供我們實現)和 at_repeat 方法,該方法是 Script 的一部分
對於execute_next_action:
第 13 行:我們從
combatantsAttribute 得到action_dict。如果沒有任何內容排隊,我們將返回fallback_action_dict(預設為hold)。第 16 行:我們使用
action_dict的key(類似「攻擊」、「使用」、「揮舞」等)從action_classes字典中取得符合 Action 的類別。第 17 行:這裡使用戰鬥人員和動作字典例項化動作類,使其準備好執行。然後在以下幾行執行此操作。
第 22 行:我們在這裡引入一個新的可選
action-dict,即布林值repeat鍵。這允許我們重新排隊操作。如果不是,將使用後備操作。
Script 觸發後,每 interval 秒重複呼叫 at_repeat。這是我們用來追蹤每輪結束時間的方法。
第 43 行:在此範例中,我們的操作之間沒有內部順序。所以我們只是隨機化它們的發射順序。
第 49 行:這個
set被分配給queue_action方法,以瞭解每個人何時提交新作業。我們必須確保在下一輪之前在這裡取消設定。
11.2.7. 檢查並停止戰鬥¶
1# in evadventure/combat_turnbased.py
2
3import random
4from evennia.utils.utils import list_to_string
5
6# ...
7
8class EvadventureTurnbasedCombatHandler(EvAdventureCombatBaseHandler):
9
10 # ...
11
12 def stop_combat(self):
13 """
14 Stop the combat immediately.
15
16 """
17 for combatant in self.combatants:
18 self.remove_combatant(combatant)
19 self.stop()
20 self.delete()
21
22 def check_stop_combat(self):
23 """Check if it's time to stop combat"""
24
25 # check if anyone is defeated
26 for combatant in list(self.combatants.keys()):
27 if combatant.hp <= 0:
28 # PCs roll on the death table here, NPCs die.
29 # Even if PCs survive, they
30 # are still out of the fight.
31 combatant.at_defeat()
32 self.combatants.pop(combatant)
33 self.defeated_combatants.append(combatant)
34 self.msg("|r$You() $conj(fall) to the ground, defeated.|n", combatant=combatant)
35 else:
36 self.combatants[combatant] = self.fallback_action_dict
37
38 # check if anyone managed to flee
39 flee_timeout = self.flee_timeout
40 for combatant, started_fleeing in self.fleeing_combatants.items():
41 if self.turn - started_fleeing >= flee_timeout - 1:
42 # if they are still alive/fleeing and have been fleeing long enough, escape
43 self.msg("|y$You() successfully $conj(flee) from combat.|n", combatant=combatant)
44 self.remove_combatant(combatant)
45
46 # check if one side won the battle
47 if not self.combatants:
48 # noone left in combat - maybe they killed each other or all fled
49 surviving_combatant = None
50 allies, enemies = (), ()
51 else:
52 # grab a random survivor and check if they have any living enemies.
53 surviving_combatant = random.choice(list(self.combatants.keys()))
54 allies, enemies = self.get_sides(surviving_combatant)
55
56 if not enemies:
57 # if one way or another, there are no more enemies to fight
58 still_standing = list_to_string(f"$You({comb.key})" for comb in allies)
59 knocked_out = list_to_string(comb for comb in self.defeated_combatants if comb.hp > 0)
60 killed = list_to_string(comb for comb in self.defeated_combatants if comb.hp <= 0)
61
62 if still_standing:
63 txt = [f"The combat is over. {still_standing} are still standing."]
64 else:
65 txt = ["The combat is over. No-one stands as the victor."]
66 if knocked_out:
67 txt.append(f"{knocked_out} were taken down, but will live.")
68 if killed:
69 txt.append(f"{killed} were killed.")
70 self.msg(txt)
71 self.stop_combat()
check_stop_combat 在回合結束時被呼叫。我們想弄清楚誰死了以及“一方”是否獲勝。
第28-38行:我們檢查所有戰鬥人員並確定他們是否在HP之外。如果是這樣,我們觸發相關的鉤子並將它們新增到
defeated_combatantsAttribute 中。第 38 行:對於所有倖存的戰鬥人員,我們確保給他們
fallback_action_dict。第 41-46 行:
fleeing_combatantAttribute 是{fleeing_combatant: turn_number}形式的字典,追蹤他們第一次開始逃跑的時間。我們將其與當前回合數和flee_timeout進行比較,看看他們是否現在逃跑並應該被允許從戰鬥中移除。第 49-56 行:這裡我們正在確定衝突的一方是否擊敗了另一方。
第 60 行:
list_to_stringEvennia 實用程式將條目清單(例如["a", "b", "c")轉換為漂亮的字串"a, b and c"。我們用它來向戰鬥人員呈現一些美好的結局訊息。
11.2.8. 開始戰鬥¶
由於我們使用 Script 的計時器元件來計時我們的戰鬥,因此我們還需要一個輔助方法來「啟動」它。
from evennia.utils.utils import list_to_string
# in evadventure/combat_turnbased.py
# ...
class EvadventureTurnbasedCombatHandler(EvAdventureCombatBaseHandler):
# ...
def start_combat(self, **kwargs):
"""
This actually starts the combat. It's safe to run this multiple times
since it will only start combat if it isn't already running.
"""
if not self.is_active:
self.start(**kwargs)
start(**kwargs) 方法是 Script 上的方法,並且將使其開始每 interval 秒呼叫 at_repeat。我們將在 kwargs 內傳遞 interval(例如,我們稍後將傳遞 combathandler.start_combat(interval=30))。
11.5. 攻擊指令¶
我們只需要一個指令來執行回合製戰鬥系統。這是attack 指令。一旦你使用它一次,你就會進入選單。
# in evadventure/combat_turnbased.py
from evennia import Command, CmdSet, EvMenu
# ...
class CmdTurnAttack(Command):
"""
Start or join combat.
Usage:
attack [<target>]
"""
key = "attack"
aliases = ["hit", "turnbased combat"]
turn_timeout = 30 # seconds
flee_time = 3 # rounds
def parse(self):
super().parse()
self.args = self.args.strip()
def func(self):
if not self.args:
self.msg("What are you attacking?")
return
target = self.caller.search(self.args)
if not target:
return
if not hasattr(target, "hp"):
self.msg("You can't attack that.")
return
elif target.hp <= 0:
self.msg(f"{target.get_display_name(self.caller)} is already down.")
return
if target.is_pc and not target.location.allow_pvp:
self.msg("PvP combat is not allowed here!")
return
combathandler = _get_combathandler(
self.caller, self.turn_timeout, self.flee_time)
# add combatants to combathandler. this can be done safely over and over
combathandler.add_combatant(self.caller)
combathandler.queue_action(self.caller, {"key": "attack", "target": target})
combathandler.add_combatant(target)
target.msg("|rYou are attacked by {self.caller.get_display_name(self.caller)}!|n")
combathandler.start_combat()
# build and start the menu
EvMenu(
self.caller,
{
"node_choose_enemy_target": node_choose_enemy_target,
"node_choose_allied_target": node_choose_allied_target,
"node_choose_enemy_recipient": node_choose_enemy_recipient,
"node_choose_allied_recipient": node_choose_allied_recipient,
"node_choose_ability": node_choose_ability,
"node_choose_use_item": node_choose_use_item,
"node_choose_wield_item": node_choose_wield_item,
"node_combat": node_combat,
},
startnode="node_combat",
combathandler=combathandler,
auto_look=False,
# cmdset_mergetype="Union",
persistent=True,
)
class TurnCombatCmdSet(CmdSet):
"""
CmdSet for the turn-based combat.
"""
def at_cmdset_creation(self):
self.add(CmdTurnAttack())
attack target指令將確定目標是否有生命值(只有有生命值的物體才能被攻擊)以及房間是否允許戰鬥。如果目標是 PC,它會檢查是否允許 PvP。
然後,它繼續啟動一個新的指令處理程式或重複使用一個新的指令處理程式,同時向其中新增攻擊者和目標。如果目標已經處於戰鬥狀態,則不會執行任何操作(與 .start_combat() 呼叫相同)。
當我們建立 EvMenu 時,我們將其傳遞給我們之前討論過的“選單索引”,現在每個槽中都有實際的節點功能。 我們使選單持久化,以便它在重新載入後仍然存在。
要使該指令可用,請將 TurnCombatCmdSet 新增至角色的預設 cmdset 中。
11.7. 測試¶
Turnbased 戰鬥處理程式的單元測試非常簡單,您可以按照前面課程的程式來測試處理程式上的每個方法是否返回您所期望的模擬輸入。
對選單進行單元測試更加複雜。您可以在 evennia.utils.tests.test_evmenu 中找到執行此操作的範例。
11.8. 實戰小測試¶
對程式碼進行單元測試不足以看出戰鬥是否有效。我們還需要進行一些「功能」測試,看看它在實踐中是如何運作的。
這是我們進行最小測試所需的:
一個可以進行戰鬥的房間。
NPC 進行攻擊(它不會做任何反擊,因為我們還沒有增加任何 AI)
我們可以
wield的武器。一個物品(例如藥水)我們可以
use。
在Twitch實戰課中,我們使用了批次指令script來創造遊戲中的測試環境。這將按順序執行遊戲中的 Evennia 指令。出於演示目的,我們將使用 batch-code script,它以可重複的方式執行原始 Python 程式碼。批次程式碼 script 比批次指令 script 靈活得多。
建立一個新的子資料夾
evadventure/batchscripts/(如果它尚不存在)
建立一個新的Python模組
evadventure/batchscripts/combat_demo.py
批次程式碼檔案是有效的 Python 模組。唯一的區別是它有一個 # HEADER 區塊和一個或多個 # CODE 部分。當處理器執行時,在單獨執行該程式碼區塊之前,# HEADER 部分將新增到每個 # CODE 部分的頂部。由於您可以在遊戲中執行該檔案(包括在不重新載入伺服器的情況下重新整理它),因此可以按需執行更長的 Python 程式碼。
# Evadventure (Turnbased) combat demo - using a batch-code file.
#
# Sets up a combat area for testing turnbased combat.
#
# First add mygame/server/conf/settings.py:
#
# BASE_BATCHPROCESS_PATHS += ["evadventure.batchscripts"]
#
# Run from in-game as `batchcode turnbased_combat_demo`
#
# HEADER
from evennia import DefaultExit, create_object, search_object
from evennia.contrib.tutorials.evadventure.characters import EvAdventureCharacter
from evennia.contrib.tutorials.evadventure.combat_turnbased import TurnCombatCmdSet
from evennia.contrib.tutorials.evadventure.npcs import EvAdventureNPC
from evennia.contrib.tutorials.evadventure.rooms import EvAdventureRoom
# CODE
# Make the player an EvAdventureCharacter
player = caller # caller is injected by the batchcode runner, it's the one running this script # E: undefined name 'caller'
player.swap_typeclass(EvAdventureCharacter)
# add the Turnbased cmdset
player.cmdset.add(TurnCombatCmdSet, persistent=True)
# create a weapon and an item to use
create_object(
"contrib.tutorials.evadventure.objects.EvAdventureWeapon",
key="Sword",
location=player,
attributes=[("desc", "A sword.")],
)
create_object(
"contrib.tutorials.evadventure.objects.EvAdventureConsumable",
key="Potion",
location=player,
attributes=[("desc", "A potion.")],
)
# start from limbo
limbo = search_object("#2")[0]
arena = create_object(EvAdventureRoom, key="Arena", attributes=[("desc", "A large arena.")])
# Create the exits
arena_exit = create_object(DefaultExit, key="Arena", location=limbo, destination=arena)
back_exit = create_object(DefaultExit, key="Back", location=arena, destination=limbo)
# create the NPC dummy
create_object(
EvAdventureNPC,
key="Dummy",
location=arena,
attributes=[("desc", "A training dummy."), ("hp", 1000), ("hp_max", 1000)],
)
如果在 IDE 中編輯此內容,您可能會在 player = caller 行上收到錯誤。這是因為 caller 未在此檔案中的任何位置定義。相反,caller(執行script的那個)由batchcode執行器注入。
但除了 # HEADER 和 # CODE 特殊之外,這只是一系列正常的 Evennia api 呼叫。
使用開發者/超級使用者帳戶登入遊戲並執行
> batchcode evadventure.batchscripts.turnbased_combat_demo
這應該會將您置於與虛擬物件一起的競技場中(如果沒有,請檢查輸出中是否有錯誤!如果需要重新開始,請使用 objects 和 delete 指令列出並刪除物件。)
現在您可以嘗試attack dummy,並且應該能夠猛擊假人(降低其生命值以測試摧毀它)。如果您需要修復某些內容,請使用 q 退出選單並存取 reload 指令(對於最終戰鬥,您可以在建立 EvMenu 時透過傳遞 auto_quit=False 來停用此功能)。
11.9. 結論¶
至此,我們已經介紹了一些關於如何實現基於抽搐和回合的戰鬥系統的想法。在這個過程中,您已經接觸到了許多概念,例如類別、scripts 和處理程式、指令、EvMenus 等等。
在我們的戰鬥系統真正可用之前,我們需要敵人真正反擊。我們接下來會討論這個問題。