三 詳談add_action()及其BUG

          
  几年前,add_action()的當機BUG是令眾多巫師極為頭痛的事情,如今各顯神通,有從MUDOS上解決,有從MUDLIB里調整。基本上已經看不到這種問題了。但是,如果未能了解這個BUG的產生原理,那么還有可能在其它的很多地方再次產生各種各樣的新BUG,結合本人的摸索感受,試圖全面介紹一下這方面的知識。

  首先我們來了解一下MUD對玩家輸入信息的處理流程。玩家在客戶端的指令行里輸入一些或長或短的字符后,系統接收到之后首先會調用在/feature/目錄下的alias.c里的process_input(string str)函數
進行預處理。而那些字符也就是參數str。
(例一:玩家輸入gall str==gall
 例二:玩家輸入c 我要go str==我要go
 例三:玩家輸入out  str==out
 例四:玩家輸入kill llm str==kill llm)
  這個函數首先要對玩家的信息進行一些過濾判斷,例如對于連續重復指令方面的判斷呀之類的,主要是對機器人的限制。然后就是調用玩家自己設定的alias以及系統設定的alias(主要由/adm/daemons/下的aliasd.c定義)看看str里面是否有事先設定的alias,有的話就要轉換成原先真正的指令,最后返回
這個經過處理過的新的字符串str。
(例一:gall 經檢查發現與玩家設定gall==get all,因此str==get all
例二:c 我要go 經檢查發現玩家設定c==chat,因此str==chat 我要go
例三:out 檢查沒發現alias,因此out==out
 例四:kill llm 檢查后沒發現alias,因此str==kill llm )

  在玩家進入MUD之后,連線程序logind.c在成功創造玩家的身體之后,會調用一個函數enable_player(),這個函數原型是在/feature/command.c里。該函數首先調用一個外部函數enable_commands(),允許它使用 add_action()所加入的命令。然后就add_action("command_hook", "", 1);
  add_action()這是一個外部函數,格式如add_action(A,B,C);就是表示如果玩家輸入指令第一個空格之前的單詞與B相同的話,就是調用函數A,后面的參數C一般用不著,這里不細講了。那么我們看看這里的就表示,如果玩家輸入的第一個單詞是"",其實就是所有的指令都符合這個條件的,那么就會調用到
函數command_hook()。而command_hook()函數就是在command.c里。str如果超過一個單詞,也就是有空格,就會分成第一個為verb,后面的為arg。開始按順序判斷verb是否是方向、固定指令、emote動作、頻道指令,如果是的話,就會把arg作為相應的參數傳入。如果都不是,就會返回0,也就是出現“什麼”的
字樣。
(例一:str==get all,get為一固定指令,調用get.c->main()參數是"all"
例二:str==chat 我要go,chat為頻道名,調用channeld.c里的do_chat,
  參數arg是"我要go"
例三:str==out,玩家所在場景發現有名叫out的出口,因此調用go.c->main()
  參數arg是"out"
 例四:str==kill llm,kill是一固定指令,調kill.c->main(),arg是"llm" )

  以上是MUD處理信息的經過。
  因此,MUD里所有的指令都是通過add_action()來實現的。而add_action()可以增加相同名稱的指令,如果指令相同,則后加的會先執行,請注意這里,并不是說后加的“覆蓋”先加的,而是“先執行”。關于一個同樣的動作單詞就可以有好几層的add_action。那么在上一層調用的函數如果是返回0的情況下,系統會自動再去執行下一層的add_action()調用的函數,如果是其中任意一層返回是1,就表示到此中止,不會再執行下一層的add_action(),關于這一點特性可以靈活地使用。比如一個kill指令,本身通過command_hook已經加了一個,有的房間里再次調用一個add_action("do_kill","kill"),后來進來一個NPC,NPC身上也帶有一個新的add_action("do_kill","kill"),那么只要進入這個房間后,玩家身上就會有了三層有關kill的add_action()。如果這里輸入kill,自然是先執行NPC身上的do_kill()。返回是0的話,再執行房間里的do_kill(),再是0的話再執行kill.c。所以,在一般我們在房間,NPC以及OBJ里做的add_action()如果與/cmds目錄下的指令相同的話,都會優先于指令先行。而且如果是后加的,肯定優先于前加的。而其中任意一層一旦有返回1的話,就會立即中止。
  LPMUD里基本上所有的謎題和很多特殊效果都需要借助add_action()來實現,認真理解并掌握它的用法是相當重要的。
  下面我們來談談add_action()的 BUG吧!很多老的玩家都知道它的用法,先由一個A買一只雞腿(包子也可以),由另一個B打昏它,然后B從A身上搜走雞腿,再吃光雞腿扔掉。等A醒來輸入eat jitui指令,系統便會立即當機。
  原因分析,傳統MUDLIB的eat是一個add_action(),做在食物的標准繼承food.c里。玩家買下一只雞腿,那么這個eat的add_action()就加到了玩家身上。在正常情況下,這個物體消失(比如吃掉)或離開玩家所處環境(比如扔掉并離開),那么add_action()都會正常去掉。但是在玩家昏迷時,其它人從它身上拿走這個帶add_action()的物體,這個add_action()并不能正常地從玩家身上去掉。而當玩家蘇醒后,這個eat的add_action()依舊存在于他身上,他依舊可以執行這個指令,如果執行的對象,比如雞腿還在游戲中,不論這只雞腿被玩家B帶到了多遠的地方,A都可以通過eat jitui吃得到這只雞腿。這種情況只是有點滑稽而已。但是如果這個雞腿已經消失了,比如B吃掉了,那么它只能消除在B身上的add_action(),這時A再執行eat,系統一下子找不到jitui這個物體,就會從內存中載入一大堆莫名其妙亂七八糟的東西,迅速進入死循環,直接導致當機。所以要實現這個BUG的條件有二,一是找一件有add_action()并且可以通過正常方法摧毀的,比如食物,不值錢的東西。二是用兩個ID執行。
  目前新版本的MUDOS據說已經從底層上修改掉了這個BUG。同時明白了其中的原理也可以在MUDLIB上用很多方法來避免這種情況的發生。
  再下面就談一下較少有人知的另一個BUG,這個BUG表面上看起來問題不大,實際運用中有時會產生很大的問題,就是sleep對add_action()的影響。大家可以仔細看看sleep.c文件,玩家進入睡眠狀態就會調用一個函數me->disable_player();這個函數原型在/feature/command.c里,最終調用disable_commands();這個外部函數,disable_commands()的用處就是讓一個活物件變成「非活著」,一是add_actions 失效,二是livingp()返回0值......也就是說,去掉了身上所有的add_action()。然
后在醒來之后,再次調用me->enable_player();這個函數我在前面文章的第五段里介紹過用法與作用,它只是恢復了玩家的add_action("command_hook", "", 1);也就是所有的系統固定指令。比如玩家身上物品的add_action,所處環境的add_action都是在init()里加載的,玩家在sleep之后并沒有呼叫到init(),自然就沒有這些。那么問題就會出現了。假如我們在一個房間里或是在一個物體上作了一個企圖覆蓋掉正常指令的add_action(想覆蓋掉正常指令,只要讓這個add_action()調用的函數總是返回1就行)。那么,玩家只需sleep一下之后,就會讓這個覆蓋無效。無效之后產生的問題大小就與你當初覆蓋的目的有關了。要解決這一問題,可以修改sleep.c,在玩家醒來后的一瞬間,讓玩家離開原地再重新move回到所處的環境,再讓玩家身上所有的東西也同樣移出去再重新move回到玩家身上,這樣就讓系統再次加載玩家身上應該加載的add_action。
  總之,MUDOS當初對于add_action的考慮并不是很完善。再加MUDLIB里的處理手法,對于象過去的昏迷、睡覺以及今后要發展的點穴、捆綁等等的處理都需要屏蔽掉指令,那么處理的前后則一定要小心,否則新東東一上,新BUG也就隆重登場。
         誰與爭鋒 叮當 2001、04、21