-----------------------------------------------------------------------------
標題: 如何寫一個房間?
作者: 江海瑜
日期: 中華民國八十二年十一月五日
-----------------------------------------------------------------------------

■□■  第一章 基礎篇  ■□■

第一節: 基本房間
----------------
首先我們先來看一個最基本的房間是如何寫出來的。一個基本的房間需要有短敘述
(short description)、長敘述(long description)、出口(exits) 這三個特性。
以下是一個範例:

/* 非常基本的房間 basic1.c */
#include <mudlib.h>

inherit ROOM;

void create()
{
   ::create() ;

   set ("short", "一個基本的房間") ;
   set ("long", "這是一個非常" + "簡單的房間。\n") ;
   set ("exits", ([
                    "north":"/d/Fooland/hall"
   ])  ) ;
}
/* 程式結束 */

以上就是一個基本的房間, 現在讓我們一行一行地來分析這個程式。

#include <mudlib.h>
-------------------
這個敘述和 C 語言的 #include 敘述一樣, 編譯器會將此檔案先做處理後, 再開始
編譯。mudlib.h 這個檔案內包括一些定義, 如以下的 ROOM 就定義在 mudlib.h 內。

inherit ROOM;
-------------
繼承 ROOM 這個物件。這是物件導向的特性。繼承該物件之後, 使你寫的房間有一些
基本的函式及變數, 如 set 這個函式就是定義在 ROOM 這個物件內, 詳細的物件導
向觀念請參考其他說明。

void create()
-------------
定義 create 這個函式, void 表示此函式不傳回任何值。create 這個函式和 C 語言
的 main 函式類似。當 game driver 載入一個新的物件(檔案) 時, 必定會先執行此
函式。此函式將做一些起始設定及資訊。

::create() ;
------------
呼叫本物件所繼承物件(在此指 ROOM 這個物件) 的 create 函式, 先做一些所有房間
都必須設定的特性的起始值。例如: 房間的體積(bulk)、重量(mass)、可容納的體積
(volumn)、可容納的重量(capacity)等一些基本的特性。使你不用每次都要去設定這
些特性。如果你不呼叫此函式, 你必須要自己設定這些值。否則可能會造成玩家無法
進入此房間, 因為你沒有設定此房間可容納的體積。

set ("short", "一個基本的房間") ;
---------------------------------
設定此房間的簡短訊息。這是當玩家使用短訊息模式時所看到的訊息。

set ("long", "這是一個非常" + "簡單的房間。\n") ;
--------------------------------------------
設定此房間的長訊息。這是當玩家使用長訊息模式時所看到的訊息。在 LPC 中, 字串
可以用 "+" 來將兩個字串結合在一起。例如:
       "這是一個非常" + "簡單的房間。\n"
等於
       "這是一個非常簡單的房間。\n"
字串中的 \n 與 C 語言中的 printf 函式所用的一樣, 用來換行。

set ("exits", ([
                 "north":"/d/Fooland/hall"
])  ) ;
------------------------------------------
設定此房間的出口。"north" 表示該出口的方向。"/d/Fooland/hall" 表示通過該出口
所可到達的房間(檔案)。
註: 在 LPC 中每個檔案代表一個物件(object)。


第二節: 有物品敘述的房間
------------------------
現在我們已經知道如何寫一個基本房間, 現在讓我們再進一步, 寫一個有物品敘述的房
間, 讓房間更多采多姿。現在讓我們來看兩個房間, 一個僅有物品敘述, 另一個多加了
物品函式。

/* 有物品敘述的房間 basic2.c */
#include <mudlib.h>

inherit ROOM;

void create()
{
   ::create();

   set ("short", "有物品敘述的房間") ;
   set ("long", "這是一個有物品敘述的房間。這裡有個椅子。\n") ;
   set ("exits", ([
                    "north":"/d/Fooland/hall"
   ])  ) ;
   set ("item_desc", ([
        "chair" : "這是一個有美麗雕刻的椅子。\n"
   ])  ) ;
}
/* 程式結束 */

這個程式大部分我們都已經瞭解, 主要的不同是此程式多設定了 item_desc (物品敘述)
這個特性。item_desc 這項特性可讓我們加一些物品敘述到此房間, 讓玩家能用 look
這個命令來看這些物品的敘述。

讓我們看看房間裡發生什麼事。

----------------------------------------
這是一個有物品敘述的房間。這裡有個椅子。
        這裡唯一的明顯出口是 north.
>look at chair
這是一個有美麗雕刻的椅子。
>
----------------------------------------

讓我們更進一步來看下面這個指令:
   set ("item_desc", ([
        "chair" : "這是一個有美麗雕刻的椅子。\n",
        "chairs" : "這裡有一些椅子。\n",
        "table" : "這是一個大桌子。\n"
   ])  );

讓我們再看看房間裡發生什麼事。

----------------------------------------
這是一個有物品敘述的房間。這裡有個椅子。
        這裡唯一的明顯出口是 north.
>look at chair
這是一個有美麗雕刻的椅子。
>look at chairs
這裡有一些椅子。
>look at table
這是一個大桌子。
>
----------------------------------------

從上面的說明, 我們可以知道加一些物品敘述到房間裡是一件非常簡單的事。但寫程式
的人必須為一件物品寫很多程式碼。另一種較有彈性的方法是加 item_func 這項特性
到房間裡去。當玩家用 look 這個物品的時候, 將會執行該物品對應到的函式。

/* 有物品敘述的房間 basic3.c */
#include <mudlib.h>

inherit ROOM;

int callnum;

void create()
{
   ::create();

   set ("short", "有物品敘述的房間") ;
   set ("long", "這是一個有物品敘述的房間。這裡一個椅子和一個桌子。\n") ;
   set ("exits", ([
                    "north":"/d/Fooland/hall"
   ])  ) ;
   set ("item_desc", ([
        "chair" : "這是一個有美麗雕刻的椅子。\n"
   ])  ) ;
   set ("item_func", ([
        "table" : "handle_table"
   ])  ) ;
   callnum = 0;
}

int handle_table()
{
   callnum++;
   write ("你已經看了這個桌子 " + callnum + " 遍。\n");
   return 1;
}
/* 程式結束 */

讓我們來看一看 set ("item_func", ...) ; 這個函式。

set ("item_func", ([
     "table" : "handle_table"
])  ) ;
-----------------------------
這個函式設定 table 這個物品所對應的函式。當玩家以 look 指令看 table 這個物品時
它所對應的函式 handle_table。

和 C 語言一樣, 程式中除了主函式 create 之外, 我們可以在 create 這個函式之後,
寫一些自己的函式。現在就讓我們仔細看一看 handle_table 這個函式。

int handle_table()
------------------
定義 handle_table 這個函式。int 表示此函式必須傳回一個整數。如果沒有傳回值, 將
會發生錯誤。當我們在 set ("item_func", ...) ; 設定一個物品對應函式, 我們就必須
寫一個相同名稱的函式。

callnum++;
----------
將 callnum 這個變數的值加 1。這個敘述等於 ++callnum ; , callnum += 1 ; , 和
callnum = callnum + 1 ;。

write ("你已經看了這個桌子 " + callnum + " 遍。\n");
----------------------------------------------------
顯示訊息給看該物品的玩家。因為我們在每次印出訊息之前將 callnum 加一, 所以這個
訊息將會顯示出這個桌子被看了幾次。

return 1;
---------
傳回 1 表示執行成功。0 表示失敗。

讓我們再看看房間裡發生什麼事。

--------------------------------------------------
這是一個有物品敘述的房間。這裡一個椅子和一個桌子。
        這裡唯一的明顯出口是 north.
>look at chair
這是一個有美麗雕刻的椅子。
>look at table
你已經看了這個桌子 1 遍。
>look at table
你已經看了這個桌子 2 遍。
>look at table
你已經看了這個桌子 3 遍。
--------------------------------------------------



■□■  第二章 進階篇  ■□■

第一節: 房間搜尋及額外的入口
----------------------------
在第一章中, 我們已經討論標準出口、物品敘述、物品函數等。現在讓我們來討論在
玩家作了某個動作之後, 移去或加入某些出口。可能的結果是某個玩家搜尋或看了某
項物品之後, 發現地上多了一個出口。以下是程式碼:

/* 有暗門的房間 adv1.c */
#include <mudlib.h>

inherit ROOM;

void create()
{
   ::create();

   set ("short", "有暗門的房間") ;
   set ("long", "這是一個有暗門的房間。這裡一個椅子和一個桌子。\n") ;
   set ("exits", ([
                    "north":"/d/Fooland/hall"
   ])  ) ;
   set ("item_func", ([
        "chair" : "handle_chair"
        "table" : "handle_table"
   ])  ) ;
}

int handle_chair()
{
   write ("你太靠近這個桌子, 桌子被你推到北邊的出口!\n");
   delete ("exits/north") ;
   delete ("item_func/table") ;
   set ("item_desc", ([
        "table" : "這個桌子擋住了北邊的出口。\n"
        ])  ) ;
   return 1 ;
}

int handle_table()
{
   write ("當你仔細檢查這個椅子時, 你發現椅子下有一個洞。\n") ;
   set ("exits/down", "/d/Fooland/inferno") ;
   delete ("item_func/chair") ;
   set ("item_desc/chair", "這個椅子下有個洞。\n") ;
   return 1 ;
}

現在讓我們來看一下 handle_chair 和 handle_table 這兩個函式。

int handle_table()
------------------
宣告對應於 item_func 的函式 handle_table, 傳回整數值。

   write ("你太靠近這個桌子, 桌子被你推到北邊的出口!\n");
---------------------------------------------------------
印出訊息給看 table 的玩家。

   delete ("exits/north") ;
---------------------------
刪除北邊的出口。

   delete ("item_func/table") ;
-------------------------------
刪除 table 所對應的函式。

   set ("item_desc", ([
        "table" : "這個桌子擋住了北邊的出口。\n"
        ])  ) ;
------------------------------------------------
加入新的物品敘述。

   return 1 ;
-------------
傳回值為 1 表示執行成功。

int handle_chair()
------------------
宣告對應於 item_func 的函式 handle_chair, 傳回整數值。

   write ("當你仔細檢查這個椅子時, 你發現椅子下有一個洞。\n") ;
---------------------------------------------------------------
印出訊息給看 chair 的玩家。

   set ("exits/down", "/d/Fooland/inferno") ;
---------------------------------------------
加入新的出口向下。

   delete ("item_func/chair") ;
-------------------------------
刪除 table 所對應的函式。

   set ("item_desc/chair", "這個椅子下有個洞。\n") ;
----------------------------------------------------
加入新的物品敘述。

   return 1 ;
-------------
傳回值為 1 表示執行成功。


第二節: 在房間裡加一個新的命令
------------------------------
除了原有的命令之外, 有時候我們希望在某個特殊的房間內加入一個特殊的命令, 而且僅
能在此房間內執行。我們可以利用這個方法作一些特殊功能。
以下是一個程式範例:

// coder : Fish
// 1993/10/30
// adv2.c
#include <mudlib.h>

inherit ROOM;

void create()
{
    ::create();

    set("light",1);                    // 這個房間將會永遠亮著
    set("short","迪納鎮干騰路廣場");
    set("long","你現在走到位於干騰路的廣場, 在廣場的中央有一座噴水池。在\n" +
               "水池的中間, 你可以看到有一座雕像。這在雕像看起來莊嚴肅穆\n" +
               ", 雕像的手上拿著一個水瓶, 水從水瓶中潺潺流出。\n");

    set("exits", ([ "north":"/d/fish/rooms/in_gate" , 
                    "east":"/d/fish/rooms/1street4" ,
                    "west":"/d/fish/rooms/1street2"
                 ]) );
    set("item_desc", ([
"statue":"這是至高無上的萬能天神 Fish 的雕像。Fish 的雕像非常慈祥地看著你。\n",
"雕像":"這是至高無上的萬能天神 Fish 的雕像。Fish 的雕像非常慈祥地看著你。\n" 
                     ])   );
}

void init()
{
   add_action("drink_water","drink");
}

int drink_water(string str)
{
   if(!str || str == "")
   {
      notify_fail("想喝什麼?\n");
      return 0;
   }
   if(str == "water")
   {
      if((int)this_player()->query("level") < 5)
      {
         this_player()->receive_healing((int)this_player()->query("max_hp"));
         write("你彎下腰喝了一口水, 覺得體內生出一股莫名的力量。\n");
         write("你的體力恢復了!\n");
      }
      else
         write("你彎下腰喝了一口水, 覺得池水十分清涼。\n");
      tell_room(environment(this_player()), this_player()->query("cap_name") +
                " 走到水池邊, 喝了一口池水。\n",this_player());
      return 1;
   }
   return 0;
}
/* 程式結束 */

void init()
-----------

add_action("drink_water","drink");
----------------------------------

int drink_water(string str)
---------------------------

if(!str || str == "")
---------------------

   notify_fail("想喝什麼?\n");
------------------------------

   return 0;
------------------------------

if(str == "water")
------------------

      if((int)this_player()->query("level") < 5)
------------------------------------------------

         this_player()->receive_healing((int)this_player()->query("max_hp"));
-----------------------------------------------------------------------------

         write("你彎下腰喝了一口水, 覺得體內生出一股莫名的力量。\n");
---------------------------------------------------------------------

         write("你的體力恢復了!\n");
------------------------------------

      else
----------

         write("你彎下腰喝了一口水, 覺得池水十分清涼。\n");
-----------------------------------------------------------

      tell_room(environment(this_player()), this_player()->query("cap_name") +
                " 走到水池邊, 喝了一口池水。\n",this_player());
------------------------------------------------------------------------------

      return 1;
---------------

   return 0;
------------

以上的說明尚有部份未完成, 在看完以上說明之後, 有興趣 coding 的玩家可以寄
E-mail 給我。我們急需充滿想像力的巫師, 而這些巫師僅需要具備一點點 C 語言
能力即可加入。LPC 是相當簡單的, 讓我們共創 DOOM 美好的將來吧。

P.S. 請 e-mail 至 is81026@cis.nctu.edu.tw
     若你是 DOOM 的玩家, 請將你的角色名字寫在 mail 中。

--
○───────────────╮
║ Natinal Chiao-Tung University╰────────────────○
║   Computer & Info. Science     ROOM : 交通大學學生第十宿舍501R ║
║   交通大學資訊科學系85級       TEL : (035) 712121 - 6437       ║
║         江海瑜(Fish)           E-mail: is81026@cis.nctu.edu.tw ║
●───────────────╮        u8123026@cc.nctu.edu.tw ║
                                ╰────────────────●
--
※ 轉寄:‧台北科技大學紅樓資訊站 redbbs.cc.ntut.edu.tw‧
