建造一列可以移動的火車

TODO:這應該更新為最新的 Evennia 使用。

車輛是您可以進入然後在遊戲世界中移動的東西。在這裡我們將解釋如何建立火車,但這同樣可以應用於建立其他型別的車輛 (汽車、飛機、船、太空船、潛水艇…)。

Evennia 中的物件有一個有趣的屬性:您可以將任何物件放入另一個物件中。這在房間中最為明顯:Evennia 中的房間就像任何其他遊戲物件一樣(除了房間本身往往不在其他任何物體內)。

我們的火車將是類似的:它將是一個其他物體可以進入的物體。然後我們簡單地 移動火車,將所有人帶到車內。

建立我們的火車物件

我們需要做的第一步是建立火車物件,包括一個新的typeclass。 為此, 建立一個新檔案,例如在 mygame/typeclasses/train.py 中包含以下內容:

# in mygame/typeclasses/train.py

from evennia import DefaultObject

class TrainObject(DefaultObject):

    def at_object_creation(self):
        # We'll add in code here later.
        pass

現在我們可以在遊戲中建立火車:

create/drop train:train.TrainObject

現在這只是一個還沒有做太多事情的物件…但我們已經可以強行進入它內部 然後返回(假設我們在不確定的情況下建立了它)。

tel train 
tel limbo

進入和離開火車

使用如上所示的tel指令顯然不是我們想要的。 @tel是管理員指令,普通玩家將永遠無法進入火車!

使用 Exits 進出火車也不是一個好主意 - 出口也是(至少預設)物件。 它們指向一個特定的目的地。如果我們在這個房間放一個出口,通往火車內部,當火車開走時,它會留在這裡(仍然通往火車,就像一個神奇的portal!)。同樣,如果我們將 Exit 物件放在火車內,它總是指向這個房間,無論火車移動到哪裡。

現在,可以定義隨火車移動的自訂出口型別或以正確的方式更改目的地 - 但這似乎是一個相當麻煩的解決方案。

相反,我們要做的是建立一些新的指令:一個用於進入火車並 另一個是再次離開它。這些將被存放在火車物件上,因此將被製作 任何在火車內或與火車在同一房間的人都可以使用。

讓我們建立一個新的指令模組作為mygame/commands/train.py

# mygame/commands/train.py

from evennia import Command, CmdSet

class CmdEnterTrain(Command):
    """
    entering the train
    
    Usage:
      enter train

    This will be available to players in the same location
    as the train and allows them to embark. 
    """

    key = "enter train"

    def func(self):
        train = self.obj
        self.caller.msg("You board the train.")
        self.caller.move_to(train, move_type="board")


class CmdLeaveTrain(Command):
    """
    leaving the train 
 
    Usage:
      leave train

    This will be available to everyone inside the 
    train. It allows them to exit to the train's
    current location. 
    """

    key = "leave train"

    def func(self):
        train = self.obj
        parent = train.location
        self.caller.move_to(parent, move_type="disembark")


class CmdSetTrain(CmdSet):

    def at_cmdset_creation(self):
        self.add(CmdEnterTrain())
        self.add(CmdLeaveTrain())

請注意,雖然這看起來有很多文字,但這裡的大部分行都是由 文件。

這些指令的工作方式非常簡單:CmdEnterTrain 將玩家的位置移到火車內部,CmdLeaveTrain 則相反:它將玩家移回火車內部。 火車的目前位置(返回目前位置)。我們將它們堆疊在 cmdset CmdSetTrain 中,以便可以使用它們。

為了使指令起作用,我們需要將此 cmdset 新增到我們的訓練 typeclass 中:

# file mygame/typeclasses/train.py

from commands.train import CmdSetTrain
from typeclasses.objects import Object

class TrainObject(Object):

    def at_object_creation(self):        
        self.cmdset.add_default(CmdSetTrain)

如果我們現在reload我們的遊戲並重置我們的火車,這些指令應該會起作用,我們現在可以進入和離開火車:

reload
typeclass/force/reset train = train.TrainObject
enter train
leave train

請注意與 typeclass 指令一起使用的開關:/force 開關對於為我們的物件分配與我們已有的相同的 typeclass 是必需的。 /reset 重新觸發 typeclass’ at_object_creation() 掛鉤(否則僅在建立第一個例項時呼叫)。 如上所示,當在我們的火車上呼叫此鉤子時,我們的新 cmdset 將被載入。

鎖定指令

如果您玩過一些,您可能已經發現可以在以下情況下使用 leave train 火車外時為enter train,車內時為enter train。這沒有任何意義……所以我們繼續吧 並解決這個問題。 我們需要告訴Evennia,當你已經在車內時,你不能進入火車 或在外出時離開火車。解決這個問題的一種方法是鎖定:我們將lock下調指令,這樣只有當玩家位於正確的位置時才能呼叫它們。

由於我們沒有在指令上設定 lock 屬性,因此它預設為 cmd:all()。這意味著只要在同一個房間_或火車內_,每個人都可以使用該指令。

首先我們需要建立一個新的 lock 函式。 Evennia 帶有許多內建的 lock 函式 已經存在,但在這種特殊情況下我們無法使用它來鎖定指令。在 mygame/server/conf/lockfuncs.py 中建立一個新條目:


# file mygame/server/conf/lockfuncs.py

def cmdinside(accessing_obj, accessed_obj, *args, **kwargs):
    """
    Usage: cmdinside() 
    Used to lock commands and only allows access if the command
    is defined on an object which accessing_obj is inside of.     
    """
    return accessed_obj.obj == accessing_obj.location

如果您不知道,Evennia 預設為使用此模組中的所有函式作為 lock 函式(有一個指向它的設定變數)。

我們的新 lock 函式 cmdinside 將由指令使用。 accessed_obj 是 Command 物件(在我們的例子中,這將是 CmdEnterTrainCmdLeaveTrain) — 每個指令都有一個 obj 屬性:這是指令「所在」的物件。 由於我們將這些指令新增到了火車物件中,因此 .obj 屬性將設定為火車物件。相反,accessing_obj 是呼叫指令的物件:在我們的例子中,它是嘗試進入或離開火車的角色。

此函式的作用是檢查玩家的位置是否與火車物件相同。如果 是的,這意味著玩家在火車內。否則就意味著玩家在其他地方並且 檢查將會失敗。

下一步是實際使用這個新的 lock 函式來建立 cmd 型別的 lock:

# file commands/train.py
...
class CmdEnterTrain(Command):
    key = "enter train"
    locks = "cmd:not cmdinside()"
    # ...

class CmdLeaveTrain(Command):
    key = "leave train"
    locks = "cmd:cmdinside()"
    # ...

注意我們如何在這裡使用not,以便我們可以使用相同的cmdinside來檢查我們是否在裡面 和外部,無需建立兩個單獨的 lock 函式。 @reload 之後我們的指令 應適當鎖定,並且您應該只能在正確的地方使用它們。

注意:如果您以超級使用者(使用者#1)登入,則此lock將無法運作:超級使用者 使用者忽略lock功能。為了使用此功能,您需要先@quell

讓我們的火車開動

現在我們可以正確地進出火車了,是時候讓它移動了。 有不同的 為此我們需要考慮的事情:

  • 誰可以控制您的車輛?第一個進入的玩家,只有具有一定「駕駛」技能的玩家,自動?

  • 它應該去哪裡?玩家可以駕駛車輛前往其他地方還是會始終遵循相同的路線?

對於我們的範例火車,我們將透過預先定義的路線(其軌道)自動移動。火車會在路線的起點和終點停留一段時間,以便玩家可以進出。

繼續為我們的火車建立一些房間。列出沿途的房間 ID(使用 xe 指令)。

> dig/tel South station
> ex              # note the id of the station
> tunnel/tel n = Following a railroad
> ex              # note the id of the track
> tunnel/tel n = Following a railroad
> ...
> tunnel/tel n = North Station

將火車放到鐵軌上:

tel south station
tel train = here

接下來我們將告訴火車如何移動以及走哪條路線。

# file typeclasses/train.py

from evennia import DefaultObject, search_object

from commands.train import CmdSetTrain

class TrainObject(DefaultObject):

    def at_object_creation(self):
        self.cmdset.add_default(CmdSetTrain)
        self.db.driving = False
        # The direction our train is driving (1 for forward, -1 for backwards)
        self.db.direction = 1
        # The rooms our train will pass through (change to fit your game)
        self.db.rooms = ["#2", "#47", "#50", "#53", "#56", "#59"]

    def start_driving(self):
        self.db.driving = True

    def stop_driving(self):
        self.db.driving = False

    def goto_next_room(self):
        currentroom = self.location.dbref
        idx = self.db.rooms.index(currentroom) + self.db.direction

        if idx < 0 or idx >= len(self.db.rooms):
            # We reached the end of our path
            self.stop_driving()
            # Reverse the direction of the train
            self.db.direction *= -1
        else:
            roomref = self.db.rooms[idx]
            room = search_object(roomref)[0]
            self.move_to(room)
            self.msg_contents(f"The train is moving forward to {room.name}.")

我們在這裡新增了很多程式碼。由於我們更改了 at_object_creation 以新增變數,因此我們必須像以前一樣重置火車物件(使用 @typeclass/force/reset 指令)。

我們現在正在追蹤一些不同的事情:火車是在移動還是靜止不動, 火車開往哪個方向以及經過哪些房間。

我們還新增了一些方法:一個開始移動火車,另一個停止,第三個將火車實際移動到清單中的下一個房間。或在到達最後一站時使其停止行駛。

讓我們嘗試一下,使用 py 呼叫新的火車功能:

> reload
> typeclass/force/reset train = train.TrainObject
> enter train
> py here.goto_next_room()

您應該看到火車沿著鐵路向前移動了一步。

新增scripts

如果我們想要完全控制火車,我們現在只需新增一個指令即可在需要時沿著軌道行駛。不過,我們希望火車自行移動,而不必透過手動呼叫 goto_next_room 方法來強制它。

為此,我們將建立兩個 scripts:一個 script 在火車停在 一個車站,負責在一段時間後再次啟動火車。其餘script將佔用 照顧駕駛。

讓我們在mygame/typeclasses/trainscript.py中建立一個新檔案

# file mygame/typeclasses/trainscript.py

from evennia import DefaultScript

class TrainStoppedScript(DefaultScript):

    def at_script_creation(self):
        self.key = "trainstopped"
        self.interval = 30
        self.persistent = True
        self.repeats = 1
        self.start_delay = True

    def at_repeat(self):
        self.obj.start_driving()        

    def at_stop(self):
        self.obj.scripts.add(TrainDrivingScript)


class TrainDrivingScript(DefaultScript):

    def at_script_creation(self):
        self.key = "traindriving"
        self.interval = 1
        self.persistent = True

    def is_valid(self):
        return self.obj.db.driving

    def at_repeat(self):
        if not self.obj.db.driving:
            self.stop()
        else:
            self.obj.goto_next_room()

    def at_stop(self):
        self.obj.scripts.add(TrainStoppedScript)

那些 scripts 作為狀態系統工作:當火車停止時,它會等待 30 秒,然後 再次開始。火車行駛時,每秒鐘都會移動到下一個房間。火車總是 在這兩種狀態之一 - scripts 完成後都會再增加一種狀態。

最後一步,我們需要將停止狀態 script 連線到我們的火車,重新載入遊戲並重置我們的 再次訓練,我們已經準備好騎它到處跑了!

# file typeclasses/train.py

from typeclasses.trainscript import TrainStoppedScript

class TrainObject(DefaultObject):

    def at_object_creation(self):
        # ...
        self.scripts.add(TrainStoppedScript)
> reload
> typeclass/force/reset train = train.TrainObject
> enter train

# output:
< The train is moving forward to Following a railroad.
< The train is moving forward to Following a railroad.
< The train is moving forward to Following a railroad.
...
< The train is moving forward to Following a railroad.
< The train is moving forward to North station.

leave train

我們的火車將在每個終點站停靠 30 秒,然後掉頭返回另一端。

擴充

這列火車非常基礎,但仍存在一些缺陷。還有一些事情要做:

  • 讓它看起來像一列火車。

  • 使乘客無法在中途退出和進入火車。這可以透過檢查進入/退出指令來實現,以便在允許呼叫者繼續之前火車不會移動。

  • 擁有可以覆蓋自動啟動/停止的列車售票員指令。

  • 允許在起點站和終點站之間進行中途停靠

  • 擁有鐵路軌道,而不是對火車物件中的房間進行硬編碼。例如,這可以是隻能由火車穿過的自訂出口。火車將沿著軌道行駛。有些軌道段可以分開,通往兩個不同的房間,玩家可以切換方向到哪個房間。

  • 創造另一種車輛!