背景:
阅读文章

新巫师学习

[日期:2007-05-05] 来源:  作者:佚名 [字体: ]

这篇说明是为了新上任的巫师所写的, 我假设读著这篇说明文件的
新巫师已经读过 help new_wiz 中的内容, 并对巫师专有的指令如 clone,
update, cd, ls, cp... 等能熟练地加以使用, 但对如何开始写作自己
的区域感到茫然, 不知所措的新进巫师  [在开始制作之前]
让我们大略看一下在 LP MUD 中, 世界的构成方式 这个世界是由
一个个的物件 (object) 所组成, 每个物件有一个对应的程式档案来描


述它的特性 我们可以藉由写作一段程式来创造出一个全新的物件, 可
以利用 update 来更新物件所属的程式, 用 clone 来实际造出一个可
用的物件 
在这里, 我们有各式各样的物件, 但是可以将之区分为三大类: 房
间 (ROOM), 物品 (OBJECT), 与生物 (LIVING)  在我们制作区域的惯
例上, 我们习惯将房间的档案直接摆在区域的目录下, 生物与物品则摆
在这个区域中名为 monster 及 obj (也有人喜欢用 object 或 item
为名, 看个人习惯 ) 的子目录中 
以下我将以这三大类物件来分别说明该如何实作出区域 但在这之
前先提出一个忠告: 最好让你的每个档案都 #include 一个自定的 .h
档, 然後在这个 .h 档内做一些 #define 来定义档案的绝对目录 例

#define EGA="/u/e/ega/"
#define MOB="/u/e/ega/monster/"

如此一来在有需要用到房间或是怪物档名时, 可以用 EGA"chat_room"
或是 MOB"troll" 的形式来表示 这除了可以让你少打不少字以外, 对
於以後要将整个区域搬家搬到某个目录下时会产生相当大的便利 

如何制作房间]
一个房间必定继承了 ROOM (inherit ROOM), 这是在
有的房间都必须继承它. 才能拥有属於房间的一切特性 

一个房间有三个非常重要的函式: create(), init(), 与 refresh()
这三个函数会在某些特定的时机被系统所呼叫, 并且可以由你自行改写
, 以达成千变万化的效果 

[create 函数]
  create() 是房间在一被创造出来时必定要呼叫的一个函数, 通常我
们都在里面做一大堆设定初值的动作 随便找一个房间来看, 我们可以
发现 create() 函数中总是有一大堆的 set("something", somevalue);
这些 set 的意义在此不详述, 你可以自己猜, 也可以问问较资深的巫
师 
有时你的房间并不直接继承 ROOM 而是继承了一个有继承 ROOM 的
特别房间, 像是商店或是公会房间什麽的 这时候你所写的 create()
函数会覆盖掉原先继承来的房间中的 create() 函数, 而导致不能正常
的动作 这时候你最好在你的 create() 函数中加入 ::create(); 这
行指令, 它表示要去执行原先继承来的那个 ROOM 中的 create() 函数
 
如果你有写 refresh() 函数, 那麽在 create() 函数的最後加上
一行 refresh(); 来呼叫它会是个好主意 

[init 函数]
init() 函数被呼叫的时机在於有生物 (怪物及玩家) 进入这个房
间的时候 这时有个常用的函数 this_player() 会传回走进房间的这
个人, 或是怪物 this_player() 的概念容後再谈, 你现在只要记住这
个函数在每个生物走进来时都会被呼叫一次就可以了 

在 init() 中最常见的的函数莫过於 add_action("function", "action");
了 它的作用是在进来的生物身上添加上一个指令 (注意, 系统只认指
令的第一个字), 并在玩家下达这个指令时去呼叫 "function" 中所给
定名称的函数 举例而言, 如果我们写了这样的 init():

init() {
add_action("do_climb", "climb");
}

当玩家走进这个房间时, 系统会帮他多出 climb 这个指令 当他下达
了 climb tree 这个指令时, 系统会去寻找 do_climb() 这个函数, 并
加以执行 同时, 系统会将玩家所输入的 "climb" 这个指令後的所有
文字以字串型别的引数传给 do_climb()  你可以将 do_climb 这个函
数宣告为 int do_climb(string s)  这样一来, 当玩家下达 climb
tree, 或是 climb the red wall 这种指令时, "tree" 或是 "the red
wall" 就会被存进字串变数 s 之中供你处理 
由 add_action() 所宣告的函数必定要是一个整数型别的函数, 因
为系统会根据这个函数的传回值采取不同的动作 如果你传回的是 0,
那麽系统会认定这个命令与你这个处理函数无关, 而对其他也有 climb
命令的函数一个一个尝试著去执行, 直到所有的 climb 命令都传回 0
时, 系统会当这个指令不合法, 而丢出一个错误讯息(what?) 给玩家 
若你的函数传回值为 1, 表示这个指令已经由你所写的函数处理掉
了, 系统不会再尝试著往下面继续寻找其他的 climb 指令 
在你的函式侦测到玩家输入的引数有问题时 (例如你要他们 climb
tree, 但他们却输入了一些错误的指令如 climb three 之类的)  想
给他们一些特别的错误讯息时, 你可以用 notify_fail(string errormsg)
来写这个讯息, 如 notify_fail("climb what?\n"); notify_fail()
这个函数也是 int 型别, 固定会传回 0, 所以我们最常用的写法是:

if (条件不合)
return notify_fail(错误讯息);
if (另一个条件不合)
return notify_fail(另一个错误讯息);
.............................
<所有可能导致错误的输入都过滤光了>
开始真正干活的部份....
return 1;


[refresh() 函数]
refresh 呼叫的时机是系统定时 (约每半个小时一次) 呼叫 主
要的用途在於房间中怪物 物品的再生 如果你改写了 refresh()
函数, 千万记得要串接 ::refresh(), 否则可能导致严重的後果 (门
一打开就不会自动关上, 怪物打死後也不会再生...
由於有 set("objects", (["name1" : "file1", "name2" :
"file2",... ]) ); 这种写法的存在 (在 create() 里面这麽写) 所
以 refresh() 被用到的机会不多了  (因为 set("objects", ) 这
个写法可以帮你作出自动定时 refresh 怪物 物品) 但是在制作一
些必须定时回复原始状态的小机关时, 仍然有必要用到这个函数 

有关房间的部份就写到这里, 接下来是物品 

[物品的制造]
要制作物品, 首先必须 inherit OBJECT; 理由与做房间时必须
inherit ROOM 一样 OBJECT 是最基本的物品, 如果你要做的东西是武
器 防具 地图等, 你必须 inherit WEAPON, ARMOR, MAP 等等才能获
得这种类别的物品所拥有的特性 现在请注意一下, 我们现有的系统在
这里有些小臭虫, 在你 inherit WEAPON 或其他子类别时, 请你把 inherit
OBJECT 这行消掉, 否则会造成错误 同时, 你不可以同时继承 WEAPON,
又继承 ARMOR, 这也会造成错误, 理由跟上面的错误相同 
物品的重要函数只有 create() 与 init(), 作用与 ROOM 中的同
名函数大致相同 

[create() 函数]
要写 create() 函数, 最好的方法是拿现成的同类物品来修改 因
为不同类的物品往往可以 set 不同的属性, 而且特性极多, 有重量 
价格 攻击力(武器) 防御能力(防具) 使用寿命(火把)等等 很难记
得完整, 所以我劝你找一个较完整档案来修改 

[init() 函数]
与 ROOM 中的 init 函数类似, 但是被呼叫的时机多了许多, 共有
下列的几种情况:
1. 物品摆在房间中, 有一个玩家走进来 
2. 一个物品突然出现在某个玩家所在的房间中 
3. 一个物品突然出现在某个玩家的物品栏中 
物品的 init 函数大多还是用在写 add_action 上面, 这些 action 会
生效的场合归结起来很简单, 就是:

「玩家用 l 或是 i 指令看得到这个物品的时候」

同一个房间中他人或怪物身上的东西时不算, 装在袋子的东西不算 这
点要注意一下 

[怪物]
简单的怪物很好做, 连 init 都不用写, 只需要写 create(), 唯
一的问题是属性太多了, 要一一理解得花上相当的时间才行 

会做复杂动作的怪物则需要相当的技巧, 并且了解有哪些变数可以
被拦截下来改写利用 等你有一定的程度时, 再来找个怪物参考参考较
好 

怪物要 inherit MONSTER; 它也没有 refresh() 这个函数 

[程式必须的概念]
你必须了解, 在 LPC 中最重要的一个概念是物件(object) 当你想
做任何动作时, 都要考虑到这个动作是哪一个 object 所做的, 不然很
容易导致错误 LPC 的语法并不严谨, 有些场合为了省事可以将函数是
由哪个物件所作的省略掉, 例如我们在 create() 函数中最常看到的
set(), 事实上最严谨的写法应为 this_object()->set() write() 则
为 this_player()->write() 
说这麽多只是为了强调一件事: 你能抓出一个物品的 object 变数
就能让他干一切他所能做的事 

[this_object() 与 this_player()]
这两个函数是系统所提供的函数, 也是最最好用的两个函数 在你
写作一个物件 (房间 物品...etc.)时, this_object() 表示自己这个
物件 
this_player() 则比较复杂, 它会传回一个属於玩家型别的物件 
这个玩家在 init 中就是触发 init 的那个玩家 this_player() 会跟
著函数呼叫一直传递给所有被 init 呼叫的函数, 包括 add_action 中
所定义出来的函数, 在这些函数中, this_player() 就是表示做动作的
那个人 

[present() 函数]
常常, 我们只知道一个物件的名字, 却不能用个 object 型别的变
数指向它  object present(string "id",object env) 函数在此时就
可以派上用场, 你给定你要找的物件的名字, 与它的所在地 (某个房间
或某个人), 函数就会传回他所找到的物件 
简单的想, present 函数其实就是在一个房间里找出某个名字的物
品的函数 它是同类型找物品的函数中最有用的一个, 其余的函数还有
find_player(), find_living() 等等 

[environment(), first_inventory(), next_inventory(), all_inventory()]
这一组函数跟物件所处在的位置有关  environment(object ob)
传回了物件 ob 所处在的地点, 例如 ob 是个玩家或生物, 那麽这个函
数会传回 ob 所在的房间; 如果 ob 是个物品, 那麽传回的就是携带著
ob 的生物, 或是 ob 所在的房间 (如果没有任何人带著它)
first_inventory(object ob) 所传回的是 ob 中的第一个物件,
如果 ob 是房间, 则传回第一个物品或是生物, 如果 ob 是生物, 则传
回他身上所带的第一个物品 
next_inventory(object ob) 通常跟著 first_inventory() 一起
使用 它的功用是传回 ob 的下一个物品, 在同一个 environment 中 
all_inventory(object ob) 类似於 first_inventory(), 但是它
所传回的是包含了所有物品的一整个阵列 

[更进一步的提示]
LPC 的函数群有三个, efun, lfun, simul_efun 它们提供了绝大
部分的功能 有空不妨多多 help efun, help lfun... 看看, 这会给你
带来非常大的收获 

[关於输出输入讯息的各个函数的提示]
can_read_chinese
printf, sprintf
scanf, sscanf
write, say, shout
tell_object, tell_room

[关於物件操作的函数]
clone, new
destruct, remove
move, move_player, move_around

收藏 推荐 打印 | 录入:sbso | 阅读:
相关内容      
本文评论   [发表评论]   全部评论 (0)
内容推送
52mud提供
一起回忆泥巴游戏QQ群68186072
52mud官方微信公众平台
热门评论