Mud编辑修改入门教程1
MUDLIB各个目录是干什么的
现在大部分中文MUD都是在东方故事(esII)基础上发展起来的,其目录结构基本一样,也有个别MUD为了标新立异对个别目录换了个名字以示不同,但其实质没有什么变化。这个做的最可恶的是xkx,把一个好好的daemon目录换成了一个不土不洋的kungfu,里边却还是skill,condition这些洋名,简直让人faint!我实在看不出这种修改有什么好处,除了添麻烦(xkx对一些命令的汉化,什么cha,du…也是如此)。不过,也有一些是为了系统更合理而修改的。对MUDLIB目录的解释已经有许多版本了,基本也没什么区别,下边是我的解释,以我自己整理的一份MUDLIB为基础,如果有目录不同的地方适当贯通就是了。
顺便讲一下一个MUD启动的过程,MUD启动的初始化过程分以下几步:
载入防真函数simul_efun
载入主控物件master,这里是调用master的create()函数将其载入内存。
载入需要预先载入的守护程序,这里调用master的preload()函数,需要预先载入的文件定义在/adm/etc/preload里。
MUD进入多用户状态,允许玩家登录。
以上过程必须正确无误的执行完,否则游戏无法正确启动。
master.c和simul_efun.c这两个文件只能在游戏启动时由mudos自动载入,不可以在游戏中用update的方法载入,而且这两个物件也不允许非ROOT权限的物件摧毁(destruct)它,因为他们的摧毁就等于游戏关闭。/adm/simul_efun 防真函数目录,这里放的是定义各类防真函数的文件,这些文件由SIMUL_EFUN_OB(也就是/adm/obj/simul_efun)来载入内存。
/cmds 游戏命令目录,这里放的是游戏中可以使用的各种命令,只有admin才可以修改这个目录
/cmds/adm 只有天神(admin)可以使用的命令
/cmds/arch 只有大巫师(arch)以上才可以使用的命令
/cmds/wiz 只有巫师(wizard)以上才可以使用的命令
/cmds/app 只有巫师学徒(wizard)以上才可以使用的命令
/cmds/imm 只有荣誉玩家(immortal)以上才可以使用的命令
/cmds/usr 只有普通玩家(player)以上才可以使用的命令
/cmds/std 所有生物(npc and player)都可以使用的命令命令目录采用子集格式,权限高的命令完全包含权限低的命令,比如天神可以使用所有游戏命令,普通玩家只能使用/cmds/usr和/cmds/std目录下的命令。
/data 游戏数据目录,只有arch以上权限才可以修改这个目录
/data/board 各个留言板的数据
/data/login 玩家登录数据
/data/user 玩家档案,呵呵,这里记录的是每个玩家的所有劳动成果
/data/npc 一些特殊npc的数据,比如商店老板
/data/daemon 重要的系统数据,比如emote词,英汉翻译词典
/data/mail 玩家信件
/feature 标准继承函数。只有admin才可以对这个目录进行修改。这些函数是非常重要的,是MUD里各种物件,包括玩家,NPC,物品,房间等的各种功能的标准定义,这些函数反映了整个MUD的与众不同之处,一个好的MUDLIB必然要对这些函数进行优化。而要设计新的程序,必须对这个目录所有文件有所了解。
/obj 系统物件,这是经常在MUD里被调用的各种物件。这个目录下的物件被复制后跟调用他的物件具有相同的euid,也就是说具有相同的权限,所以,这里的一个npc如果经admin编译后复制出来,也具有了admin权限。这点非常重要,有些物件必须具有跟玩家相同的euid才能正常使用,比如符纸。而出于安全考虑,这个目录下的物件不可以随便复制。只有arch以上才可以修改此目录。
/quest 游戏里各种任务quest的文件,此目录需要wizard以上权限才可以修改
/binaries 文件编译过程生成的二进制文件,此目录ROOT权限才可以修改
/d 区域目录,游戏的地图门派和NPC存放的地方。此目录wizard以上权限可以修改。这里是整个MUDLIB最简单最常用的东西,也是玩家可以直接接触到的东西。玩家所呆的任何一个房间都应该对应这个目录下的一个文件,房间里每个NPC也都对应一个文件。这个目录下存在许多子目录,分别对应游戏的不同区域(domain)。
/include 头文件目录,只有admin可以修改。这里放的是游戏中各个文件所需要的头文件,其中以下几个特别重要:
/open 系统临时目录,ftp登陆目录,任何人都具有写权限。
/questobj FY3的task专门目录,存放各个task物品文件。wizard以上权限可以修改。
/u 巫师工作目录,存放各个巫师开发过程的半成品。这里的子目录名只能为各个巫师的euid,而只有对应euid的人才有对相应目录的修改权限。比如一个目录是lion,那么只有lion这个ID或者比lion权限高的人才可以修改它下边的内容。
/clone 物品目录,存放游戏中需要经常复制的各种物品,wizard以上权限可以写。这个目录下的物品一般被其他地方复制(clone)出来,但他们的euid是固定的,不会随调用它的物件的不同而改变,这就是这个目录和/obj目录的最大不同。此目录arch以上等级可以修改
/clone/armor 装备
/clone/drug 药品
/clone/fruit 水果,水果跟食物的不同是吃了可以同时加食物和饮水,而且有些水果具有养颜美容之功效。
/clone/liquid 容器,象酒袋茶壶一类
/clone/money 顾名思义,人见人爱的东西
/clone/user 玩家物件,包括login.c(link_ob)和user.c(body),此目录只有admin可以修改
/clone/books 各种秘籍
/clone/food 吃的
/clone/gift 各种礼物,一般是好东东
/clone/weapon 兵器
/clone/misc 其他杂物,比如尸体,头颅。
/daemon 呵呵,这个目录为什么叫这个名字我一直没明白,看下边的说明吧。此目录只有arch以上可以修改。
/daemon/skill 游戏中所有的技能
/daemon/condition 人物各种状态,比如中毒,恢复等。
/daemon/class 这个应该理解为“组织”或“工会”,是ESII从外国MUD里保留下来的东西,不过随着中文MUD的发展,工会的概念逐渐被淡化,其含义已经跟以前完全不同,现在他的意思有点象“身份”,比如道士,和尚,喇嘛,官兵,老百姓。。。。个人有个人的身份。很多情况下,这个跟门派类似,但又不完全相同。这里存放的是各种不同身份的人所具有的特殊东西,不过现在一般用来存放各种特殊技能的特殊用法(perform)
/doc 文档。 游戏中的各种文本文件,arch以上可以修改
/doc/help 各种帮助文件
/doc/efuns 各种系统函数的用法
/doc/story 各种背景故事
/log 系统日记,记录游戏过程各种重要事件,是巫师处理纠纷和剔除bug的主要依据来源。一个好的巫师应该养成经常看系统日记的习惯。本目录只有ROOT权限可以修改。
/p 玩家目录,这个目录下内容玩家具有写的权限。主要存放需要玩家修改的内容,比如玩家房间,自创武功等。
/std 标准对象目录,游戏中各种标准物件,只有admin可以修改游戏里其他物件都需要继承这里的相应文件。
/std/armor 装备物品的标准继承,比如衣服,盔甲等
/std/board 留言板
/std/char 生物的标准继承,MUD里的生物包含两种:玩家(player)和非玩家(npc)他们都需要继承/std/char/char.c这个对象,他们与其他物件的区别是具有心跳(heartbeat)。
/std/drug 药品的标准继承,其实这个现在一般不用了,这部分功能都放到了/feature的相应文件里,保留这个是为了跟旧的系统兼容。
/std/item 物品的标准继承
/std/room 房间的标准继承。有人曾问房间和物品到底有什么区别?说实话,这个问题还真不好说,这两个物件基本结构差不多,也没有什么可以区分的特征。现在的常用判断方法是房间是没有环境(environment)的,但也不是所有物品(包括生物)都有环境,所以,呵呵,这个问题还有待MUD程序的进一步完善,现在基本上无法判断。
/std/skill 技能的标准继承
/std/weapon 武器的标准继承
/std/misc 其他物件,比如钱,符纸。值得注意的是,/std这个目录跟一般MUDLIB的结构不太一样,一般MUDLIB并没有如此归类存放,大家可以根据自己的认识加以判断
以上是根据我的理解对各个目录的解释,未必是权威的说法,只是为了让大家有所认识,如果有什么错误请指正。
作为刚入门的新巫师,准确理解各个目录的作用是非常重要的,必须知道哪个目录下的东西是做什么的,哪个目录你可以去改,哪个目录你不能动。一般来说,要成为一个合格的巫师,至少要花半月时间来“读”程序,了解整个MUDLIB的结构,了解各个文件的作用(未必要完全看懂,但至少应该知道他是干什么的),这样不至于再以后的程序开放过程中不知道什么功能该如何实现,从而走许多弯路。当你了解了整个MUDLIB后就可以试着自己写程序了,一般你只能在自己的工作目录里写东西。刚开始不妨试着写一个简单的房间,然后试着写一个简单的npc,然后试着把这个npc放到这个房间里。。。。。。这样逐渐提高,你很快就成为一个合格的巫师了。
认 识 自 己
什么是巫师
巫师是创造者,程序设计师,负责创造玩家眼里的游戏世界。你可以创造任何东西, 这也使得巫师这个位置显得很重要且需担负很大的责任。当一个巫师代表你可以在瞬间取一个玩家的性命,对游戏系统造成很大的损害,或是建立一个人间天堂。任何你想得到的都可以做到。
在目前的大陆,专职的巫师是很少见的。这恐怕主要是因为MUD这个游戏还没有一套成熟的商业运营机制来使它产生经济效益,并以此养活那位专职的巫师吧!大多数巫师都是走的“玩而优则巫”的道路。很难想像:一个从未玩过MUD的人会有多高的热情和动力去承担如此艰巨、而且是无报酬的工作。但并非说玩得好的玩家就一定要去做巫师,有人就是天生的玩家。流行的对于巫师有专职与兼职之说,这是指在一个MUD与两个或多个MUD担任巫师之分。我们的观点是坚决反对兼职巫师的,以下摘录的一段文字很能代表我们的看法:
--------------------------------------------------------------------
: 对于目前大部分的wiz兼职实在对于大陆泥巴这个freeware 是最大侮辱了
: 我对于巫师的看法就是该诚恳的,勤奋的,付出在自己最热爱的一个mud
: 而不该去其他的mud寻找所谓的新的创意,或是一个等待开发处女,如果
: 大家的目的是需要提高中国泥巴的整体水平的话,大概你一个单兵作用不
: 大吧,那可需要的是一个整体的力量。
: 兼职的巫师本身就是一种浮华的类wiz ,他们有点技术,热忠于这个那个的
: mud串门做个嘴片子,本身精力也许很多,有任务也许完成,下网了就也许
: 满足于自己名片颇多。
: 如果要获得新的想法,需要在其他mud当巫师才能获得吗?那不是成了看别
: 人的工作成果,“启发“自己的新想法?这...
: 如果觉得另外一个mud比现在所在的mud 更能体现自己的想法思路或风格,
: 那: 坚决的全身心的投入到那个更理想的mud里哪,如果觉得现在呆的mud
: 没发展了。那呆着也是惹那一身烦嘛!居然有人在自己不喜欢的mud里当
: wiz ,这...
: mud 本身就是体现一个创作群体的思想,现在大家对于mud的观念特奇怪,
: 做wiz 的等待admin发任务,admin 抱死自己的权利,不去发现wiz们的长
: 处而只知道门派、地图、daemon的下任务,有技术的wiz抱怨工作简单、无
: 聊,需要提高的wiz抱怨石头太硬。这就导致了巫师这个群体成个很官僚的
: 状况,如果大家真心的做个mud,又何至如此呢。
: 还有就是兼职也导致侵犯某些codes的作者的权利,甚至有人拿着这边的程
: 序到那边去申请职务,这...
: 如果大家真心要把自己做的门派,或是得意的程序奉献给大家,那建议把
: 那些程序放到自己的主页上呀,好坏自有公论嘛,而且大家在你的主页上
: 经过讨论,或者对你是个更大的提高呢,这样大家知道是你的程序,觉得
: 保证你的权利。freeware,sigh,到了中国这个福地,什么都变味了,人们
: 换着法子的保护自己署名的权利,更大一群人拿着去掉开发者的署名的程
: 序做开发. 这... 是freeware 的精神吗?限制巫师兼职根本就不是一种可
: 以用来限制的条件,只是一相情愿的愿望而已。(有删改)
※ 来源:·BBS 水木清华站 bbs.net.tsinghua.edu.cn·[FROM: 166.111.54.141]
---------------------------------------------------------------------
巫师的观念
巫师是什么?巫师并不是神,神只是提供我们服务器、提供我们站点的人物,在我们这里,他们被定义为(gods),真正的巫师都是同玩家一样通过远程登录上去的用户——只是一个拥有较多权限的超级用户而已。因此本质上也是一种高级的玩家,但他所遵循的原则却要绝对地高于玩家。
争吵
巫师:永远不要与玩家争吵。如果你觉得自己错了,请于第一时间去改正它,第二时间再向玩家说明。要是觉得丢面子的话,就保持缄默吧。如果你觉得自己没错,就不要理会,对方再纠缠下去干脆 kickout….有些事情是越争越越乱、越争越糟糕。还有一点:你如果上线时手头有事情要处理或者没有二十分钟以上的闲功夫,奉劝你不要现身,隐身处理事情算了。
玩家:永远不要与巫师争吵。如果你觉得自己对的,尽可能地去post或mail,如果觉得自己未被重视受到了伤害又不大可能得到补偿,你可以拒玩这个站点。如果你发现是你自己错了……你完了………
要求
巫师:永远不要对玩家提出要求。如果你觉得确需限制,就直接设计出程序,只要他们在规则的允许之内,任由他们发展。一个好的巫师是完全通过他的程序来实现对世界的调控,而不是言语。话多的巫师工作将会很累很累。
玩家:请不要对巫师提出出格的要求。这是一种很不健康的思想,理论上,玩家与巫师应该是有某种正式的沟通管道,而尽量减少私底下的交易。也就说,巫师应该只是制订规则、写区域、管理维护MUD,而尽量减少介入玩家的生活或成长过程。玩家也应该要尽量不要要求不在巫师工作范围内的事情,最常见的就是向巫师索取仙果、询问打开暗门的口令、某个秘密人物的所在。这种要求直接导致你在巫师心目中的评价直线下降。如果牵涉到要求改武功、提经验的地步的话,可能会带来进行坐黑牢的惩罚。而在此,我们还得指出一点,对于巫师来说“勿以恶小而为之”,最好的办法就是做一个铁石心肠的家伙,拒绝一切超越工作范围的事,否则,你还是去做一个“新手指南”的玩家好了。
脾气
巫师理论上不应该有任何的脾气,这也许有点不尽人情,但是我们对每一个巫师都这样建议,上线前深呼吸几次。一上线先hihi,遇到事情先hehe,实在脾气上来压也压不住时,你干脆就quit再拍桌子跺脚大叫几声:“老子再也不做巫师了!”然后心平气和地再 login进去,巫师当然还得做、程序也还得写、丑话还得听。人们不是常说:“我们都有情绪因为我们不是神仙”,哈哈!现在你一在线,你就已经是神仙了。所以……..
动力
一个纯义务的、没有报酬的工作,初期的那份新鲜劲会随着没完没了的程序、函数而消磨怠尽。因此你得不时地给自己寻找动力。善始善终,巫师应该是程序员届最能考验人的工作之一。
水平
怎么你也得了解一下 MUDOS吧?实在不行,大致有个概念也成。对于 LPC你总弄懂吧?你所工作的MUDLIB不看完也说不过去吧?也许你目前还不行,但你若真的想当一名好巫师,你就得朝这个目标去做。
巫师权限简介
大总管(gods):这个级别的比较特殊,一般是由你服务器所在单位的工作人员或管理人员担当。严格意义上,只有他们是神。神可以什么事都不做,却可以决定任何事情。不要去向神要求什么?你所要做的事除了沟通就只有祈祷。当然,对于神来说,除了到处goto和参预巫师频道的讨论外,他们也不必要过多的档案处理权。因为服务器就在他们的身边,任何制约对于他们来说都是可笑的,“神打了个呵欠,地上就刮了三个月的风暴”………
天神(admin) :游戏的实际管理人,在游戏中所有事情的最后决定者。事实上他们只是神的使者的意义。天神唯一的限制就是不能(确切说应该是不准)修改 /LOG 下的文件,那里只有真正的神(大总管)才能通过服务器的操作进行删除。
大巫师(arch):天神的助手,一般都是由熟悉系统架构的人来担任的。大巫师也是执行多数区域的 QC 的人选。只有一些涉及系统更动的问题,才必须通过天神进行更新操作,大巫师与天神没有职责上的区别,只有处理权限的不同。
巫师(wizard):写区域的人, 请乖乖的遵守所有的巫师规则.
巫师学徒(apprentice):练习生。在这个阶段,你的工作是先熟悉环境和相关规定。调适一下你的心态,并确实的知道当巫师所负有的责任.
一些建议
当巫师好像是在当创造者,任何你想得到的东西都可以通过程序做到。因为这样,请你动手前务必多想想,先把后果想清楚了再做。如果不幸出了些差错,也请完完全全地负起责任并承担后果。永远记住,你的一举一动,不论大小,对这个游戏都将造成一些影响,请尽量让这些成为正面的影响。
善待你的同事并尊重你的上级,巫师级别的划分并不一定是完全反应着能力的高低。而实际上略通编程技巧的人也都知道,一个wizard可以很轻易地把自己改为 admin,而事实上你也别去动这个脑筋。巫师守则的遵守只是靠相互信任与自觉。原理上,用程序我们是可以完成任何事情。系统中的各种防御及记录体系也都是防君子不防小人的,但是我们不希望在程序中看到巫师物品,即使必须要有,一定要加上对使用的人的权限判断,我认为有写巫师物品的时间还不如多设计几个谜题算了。
认 识 工 作
在开始制作之前
让我们大略看一下在LP MUD,即所谓战斗 MUD中, 世界的构成方式。这个世界是由一个个的对象(object)所组成, 每个对象有一个对应的程序来描述它的特性。在游戏中所见到的每个房间,每个 npc,每个物品,甚至你自己,都是一个object,都是一段程序。
我们首先写出一段程序来创造出一个全新的对象,然后利用update来更新对象所属的程序, 再用 clone来实际造出一个可用的对象。在这里所谓更新,就是将硬盘里这个文件编译后形成一段代码,这段代码是存在于内存中的。在MUD中,程序只有进了内存方可执行。因此当你修改了或新写了一个文件,那只表明你在硬盘上改动或创造了这个文件,你必须做一下update,将它编译放入内存,你的修改和创造才正式生效。而clone命令其实就是update+move,因为它update的是一个物品或npc,这个物品或npc还需要有地方放。(看起来吃力吗?没关系,当你对 updata和clone的操作十分熟悉后,再想想这段话,你就会chat* oh)在系统里, 我们可以制作各式各样的对象, 但是都可以将之划分在三大类里面: 房间、物品与生物。在制作区域时, 我们习惯将区域放置在根目录下的/d 目录。房间的档案就直接摆在区域的目录下,生物与物品则摆在这个区域中名为 npc及obj的子目录中。
制作的基本品质要求
所有的 MUD都有自己的风格、发展方向、跟程序品质的要求。由于这些东东与程序是否能通常执行关系不大。有时仅仅只是一些个人习惯而已。作为MUD这么一个集体创作的作品,这种习惯就有必要有一个集体性的统一,这种统一大约是随著主持这个 MUD的admin而异。事实上又由于每个admin对mudlib的了解程度不一,所以对品质要求的深度也不同。以下是我们“无锡 MUD巫师组”对各位新加盟的巫师的品质要求:
命名
命名的一个基本原则就是简单直观。一般我们要求使用中文的拼音,如果其英文名很熟知并确实比拼音短小直观的情况下,也可使用英文,当然也包括那些已经约定俗成的如:room、eat、food、cloth等。在使用拼音时,要遵循下列要求:
请尽量保证发音准确,没把握请查字典;
两字词直接连写,例:大门 damen.c,超过两字请使用隔断符号进行间隔,以免出现难以辨读的情况。
隔断符号分“_”和“-”两种,它们两者的区别在于前者两边的关系是修饰的、而“-”两边之间的关系是并列的。东客房可以写成dong-kefang或者是kefang_dong,两者之间的区别相信不需要我再多说了吧。
四字词应在两字中间用“-”隔开,象wuxidayu应该写成wuxi-dayu。三字词请选择好隔断的部位。象老管家lao-guanjia就不能写成laoguan-jia,这些看起来似乎有些罗嗦,但的确是必须养成的一种良好习惯。
此外,命名最好形成统一的规范,不管在哪里当巫师,一定要先仔细看一看那里的大部分文件的命名格式,尽可能地与前面的文件相融合。
目录
目录原则上没有什么过多的限制,有一些传统,你可以通过阅读整个系统的文件来看明白。一般的区域放在/d目录下,每一区域中下面再包含npc与obj两级子目录,用于放置这一区域里的人物与物品,一般不要再增添其它的有关子目录,而可以教授武功的人物请放在/kungfu/class/下的同名称的目录下,我们所要提醒的一点是,在新的区域目录的设置上要相对合理,里面文件过少,就请合并至相近的一个目录中,如果太多,也要尽量拆成两个目录。以免给日后对该目录的操作造成不便。
程序
程序语言相当简单,但是良好的习惯必须在一开始养成。
例:请采用类似
if(...)
{
if(...)
{
...
}
else
{
...
}
}
else if(...)
{
...
}
else
{
...
}
的风格,不要采用
if(...){
...
}
这样的风格。有人也许会说,我看很多正规的程序员就不这样子嘛!但是你别忘了,LPC是一种特殊的编程语言,它还有有它特殊的工作环境。一是,它的程序有可能会经过很多的人阅读与修改,二是它经常要在zmud这样的客户端软件下进行远程的在线逐行修改,到了那时你就会对这样的规范体会颇深了。
// 该文件完整的绝对路径 中文名称
// 作者完成或者是最修改日期
// 如有对该文件的重要说明,请写在这一行
对于房间的描述应该整齐划一,至少在同一区域下的场景描述都需保持一致。每行控制在57至61个字节之间,建议人物20个字,房间30个字一行。对于人物的描述,出现的信息提示也遵守这种规则。
档案路径名称最好和绝对路径无关,这样当你的程序在整个目录被移到另一路径下后仍然能够正常运行,为此你可以用 DIR 这个由MudOS提供的巨集定义表示这个档案目前所在的目录(FILE表示目前这个档案的档名),即使移动之后必须做修正,最好也限於某个 .h 档案。
档案中尽量不要直接调用其它目录下的 NPC 或 OBJ ,为扩展性着想,至少房间一定要调用自己目录下的。当然 /CLONE 目录下的除外,如确需要,请直接在自己的目录下复制一份。如果确实发现各个目录对该文件的调用率很大,不妨申请大神直接在 /CLONE 目录下设置。
语言风格也许与写作的各个人有关,但是我们这里的金庸风格是十分明显的,什么该有,什么不该有,最笨的方法就是翻翻原著。我们不希望在你创造的区域发现一块德芙巧克力或者是冲锋枪。同时我们也希望区域的设计需要有相当的「原创性」,现在侠客行上衍生出的版本非常之多,我们不希望我们巫师只是在这其间东抄一点、西窃一点。能用自己想出来的东西,最好回避跟一些太出名的作品雷同的东西。在制作时,有关的地理、历史以及相关小说应该是一个好的巫师的必备参考书籍。
所有的讯息必须正确而且适当,所谓「正确」是指基干一般常识必须无误,例如:某人拉开一张桌子,自己看到的是“你拉开了一张桌子”,旁观的人也能看到讯息,但却应该是“某人拉一了一张桌”,各有不同。而如果是某人看到桌子心中不由一惊的讯息,旁观的人就不一定能看到了。所谓「适当」是指讯息出现的地方、讯息的长短、标点符号、颜色、出现时间必须尽量合乎真实世界的情形,例如人物add_action的讯息应该要能适当地表达出动作者的立场,不能有看起来怪怪的感觉。写作的时候,应该多想想:现实中应该是怎么样的?
景物或物品的设置必须合理,如一株可以爬的树你可以把它写成房间的景物,也可以用一个物品来表示树的存在(可以砍下来带走),但是一个可以钻进去的地洞就不应该写成一个物品,虽然在程序上是可行的,但却是绝不合式的。
人物的强度,也就是它的武功与经验必须合理,我们的MUD虽然采开放式的属性系统,但是另一个重点是:NPC和玩家是同一个世界的人,NPC 的作用不能作为它具有变态能力的理由,换句话说NPC的「强」必须有故事背景设定上的理由,比如根据天龙八部小说你可以把少林寺中的一个烧火头陀设成罕世高手,但这必须要控制在严格的情况下需要在游戏中给予足够的提示。任何应用上的牵强理由而设计的强力NPC会受到最严格的检验。( 请先参照各门派掌门的强度,作为假设的玩家强度水准上限,NPC没有特殊理由不应该强过这些人,而且这种强度的NPC应该是十分「少见」的。)一旦由于某种原因你要将某一个存在的NPC调高,你必须要给玩家足够的提示与声明。
武器装备的强度必须合理,和NPC相同,强力的装备也就是宝物应该是十分罕见的,如果没有适当的故事背景设定,强力的装备会受到最严格的检验,而且这些装备必须是极其罕见的,也要受到一定条件的限制。这里一个例子就是各种杂类的MUDLIB总会在一些好奇的巫师手中出现一些增加1000级的伏魔刀之类的东西,这些东西一旦出现,事实上将会对你(或者是原作者)精心构筑的整个MUD的武功系统的否定。
区域的大小与其中所含的「机关」必须成正比。在这里我们给出一个数字,如果你的区域每过10个房间,都全是一堆用房间编辑器做出来的改改叙述、名称和数字的房间、NPC 、装备或物品,那对于这种阳春区域,我劝你还是自己收回去欣赏、不要作任何通过你上级 QC (也就是对佻的程序进行品质鉴定)的可能性幻想。
此外,还有一些编程原则是必须遵守的:
自己定义的函数,在文件开始最好有一个函数原型的定义声明(声明的作法就是在文件的开关几句将这个文件里所用到了自己定义的函数头写上,注意:末尾要加分号),每个函数前有一个简单的说明也是基本的要求(也就是前面加
的注释行,告诉别人,这个函数是干什么的,是在什么地方调用,如果别人要修改要注意什么)。而一些复杂的地方也最好加上注释。因为在线解决问题时是经常要查看源程序的。
* 若要完成复杂的功能需要对/d、/kungfu以外的目录增加或修改文件,需要先征得大巫师的检查和同意。而在这之前,可以提请巫师组讨论,看看有没有更好的实现办法。
* 做复杂的物件时,尽可能将一些日后可以通用的功能做成inherit或include,以方便以后的编写。
* 为兼容性、稳定性考虑,除非迫不得已,不要直接调用你很少看见过的EFUN(就是一些由MUDOS定义的外部函数,你在整个MUDLIB是找不到它们的说明。)。
怎么开始工作,有人说先做起来再说,有人说先看起来再说,我们的意见是,从简单的房间和人物在编写开始,最简单的起步就是找一些相近的自己也能看得懂的程序来改,这样产生错误的可能最小。待涉及到机关与秘密的制作后,再开始全面地读看MUDLIB的程序,实在看不懂,先跳过去,一直看完,结合我们的中级工作手册(编纂中),我想接下去的自学历程应该是十分顺利的了吧?
为了使这本手册最大限度地通俗易懂,我尽量做到决不过早地向大家提及一大堆专业术语,以免吓跑我们的新巫师朋友,但是……这里,我先提一个,就一个……好不好?我们来理解一个概念--继承(inherit)。在程序中,继承的意义就在于把很多东东中间的共性提出来,设置成一些标准物。然后,别的程序就没有必要再一个个地重复设置,直接继承它就有了这些特性。继承共分两种:一是标准物件,就是具体的东东,比如说:npc(人物)、item(物品)、room(房间);第二种就是标准特征,比如说:F_MASTER(可收徒的)、F_EQUIP(可装备的)。多用继承可以大大减轻系统的记忆量,也对我们的工作带来很大的便利。好,下面我们开始写程序了。
—–
====== MUD编辑修改入门教程2 ======
我们新巫师不会一上手就去开发新的系统程序,先起手写的大都是一些简单的房间、物品、人物,这类程序首先必需要继承一个标准物件,如:房间要 inherit ROOM; 物品要 inherit ITEM; 人物要 inherit NPC; 道理前面已经讲清楚了。
接下来可能要碰到一大堆令人头疼的函数了,不过没关系,我们先不要去理会它们,做一些简单的房间呀、人物之类的只要用到一个最基本的函数:create()。create()的作用就是在你写的这个程序,不管是房间还是东西,当它被制造出来,它就会立刻启用这一函数,对自己进行一些最基本的设定。函数里的语句格式也很简单,就是:
<code>
set(“属性”,某个值);
</code>
到底有哪些属性可以set呢?你可以去找一些程序多看看,也可以问问较资深的巫师。下面为新巫师们列出了一些基本属性:()里的表示这个属性的类性。string表示是字符串,int表示是数字型。
<file>
房间属性
“short”(string) 房间的短叙述。
“long” (string) 房间的长叙述。
“item_desc”(string或函数) 房间中个别景物的叙述,格式为:([ <景物名称>:<景物叙述>, …. ])。其中<景物叙述>可以是字串或 function type。
“exits” (mapping) 房间的出口,包括有门的方向,格式为:([ <出口>:<房间档名>, …. ])。
“objects” (mapping) 房间中的物品、生物,格式:([ <物品或生物档名>:<数量>, …. ])。
“outdoors” (string) 房间是否为「户外」,户外房间可以看到天色变化与气候影响。字串的意义表示房间的气候区,通常和该区域的 domain (即 /d 下的目录名称) 同。
“no_fight”(int) 房间是为禁止作战区域。
“no_magic”(int) 房间是为禁止施法区域。
人物属性
“id” (string) 人物英文名,这个字可以用来识别玩家,通常 “id” 跟 “name” 都不直接用set() 设定,而是用 F_NAME 中的 set_name()。
“title”,
“nickname”,
“name” (string) 人物的称号、绰号、与中文姓名。
“age” (int) 人物的年龄。
“age_modify”(int) 这个数值会在 update_age() 中被加在人物的年龄上,可以是负数。
“jing”,
“eff_jing”,
“max_jing”(int) 精,当前精,最大精
“jingli”,
“eff_jingli”,
“max_jingli”(int) 精力,当前精力,最大精力
“neili”,
“eff_neili”,
“max_neili” (int) 内力,当前内力,最大内力
“qi”,
“eff_qi”,
“max_qi”(int) 气,当前气,最大气
“shen_type”, (int) 神的类型,0是负,1是正
“str”,
“int”,
“con”,
“dex” (int) 人物的天赋,依序分别为膂力、悟性、根骨、身法
“combat_exp”(int) 人物的实战经验
“jiali”(int) 表示人物打中别人并加的内力点数。
“family”(mapping) 人物的师承门派等记录
“skill”(string) 人物的武功
物品属性
“name” (string) 物品的中文名称。
“id” (string) 物品的英文名称。
“long” (string) 物品的详细描述。
“value” (int) 物品的价值,单位是「钱」(coin)。
“unit” (string) 物品的单位,如:个、把、枝…..
“no_get”(int) 表示物品是否可被捡起来。
“no_drop”(int string) 表示物品是否可被丢弃,如果型态是 string, 则当有人企图丢弃这个物品时会将该字串用 notify_fail 传回去。
武器属性
“skill_type”(string) 这个武器所属的技能种类,如 “sword”、“blade” 等,要注意在 /d/skill下必须有一个定义该武器技能的物件,否则装备这个武器战斗时会有错误讯息。
“rigidity” (int) 武器的硬度,当使用武器相斗时,硬度、武器的重量、持用者的力量将会影响武器受损的机率。
“weapon_prop”(mapping) 武器对持用者的状态影响,通常武器只影响 “damage”,这些状态影响会在武器被装备时用 add_temp() 加到持用者的 apply 上,并於卸除或 dest时减回来。
</file>
一个基本的房间,只要有 short <短叙述> 、 long <长叙述>、 exits <出口>就行了。就象:
<code>
void create()
{
set(“long”, @LONG
房间的叙述…….
LONG
);
set(“exits”,([
“west” : DIR“path3”,
]);
</code>
在这里DIR是什么意思?假如说我们写的这个房间的文件在/d/city目录下,那么这个DIR就是“/d/city/“也就是“”west”:“/d/city/path3””的意思。在上一章的品质要求里讲过,我们提倡采取这样的写法,因为这样写了以后,不管你这个区域的目录放在哪里、或者是更名都不会受到影响。接下来,我们可以给这个房间里添一点生动一些的东西,比方说,让玩家在这里能够look到什么:
<code>
set(“item_desc”, ([
“景物名称1” : “景物叙述”,
“景物名称2” : “景物叙述”,
………..(可以有很多)
]);
</code>
其中景物叙述可以是一句、几句话,或是一个自定义的函数,这个函数你就写在后面的某个地方,以根据不同的情况显示look景物名称后而出现的信息与效果。所以你可以利用这个功能加以变化,当玩家 look 一个景物时,可能看到叙述,也可能发生一些特殊的事件。
然后下面就再给这个房间加点东西。
<code>
set(“objects”, ([
“物品或生物的档名” : 数量,
………..
]);
</code>
如同前面所提到的,建议采用 DIR来编写你的路径,而数量则要用整数。
写到这里,有的巫师有点不满足,说,我看见有的房间有时某一方向的门是开的,有时又看不到,要使用open door指令打开门才行,这是怎么做呢?这其实也简单,只不过首先要复习一下前面的继承概念。因为这个门是房间里的一个特征继承,需要用到一些在/include/room.h里定义的一些变量,所以要有文件头记得必须先 #include <room.h>。然后在文件中写上:
<code>
create_door(“出口方向”, “门的名称”, “进入方向”, 预设状态);
</code>
比如说,这里明显的出口有 west、east 和 up。 而你要让西边有一个关上的红木门,你可以这样写:
<code>
create_door(“west”, “红木门”, “east”, DOOR_CLOSED);
</code>
当玩家进入这个房间时,他会看到:
这里明显的出口有 east 和 up。
而当他 look west 时,会看到:
这个红木门是关上的。
有关create_door()是如何让你的房间表现出这样的特性的,那就要去仔细看看学习它所继承的/inherit/room/room.c文件了。不过对于新巫师来说,还不必急着去看,只要知道要加上这些语句就可以实现,也就行了。
简单的人物原则上只要有 set_name<名字> 、 combat_exp <经验>就行了,当然我们总得稍微多添一点了。
<code>
inherit NPC;
void create()
{
set_name(<中文名>, ({ <英文id> }) );
set(“title”, <头衔>;
set(“gender”,<男性、女性或是无性>;
set(“age”, <年龄>;
set(“long”, <人物的长相描述>;
set(“combat_exp”, <人物的实战经验>;
set(“attitude”, <好战态度>;
set(“neili”, <内力值>;
set(“max_force”, <最大内力>;
/*你想让这个人物能够时不时说点什么或动点什么,就要*/
set(“chat_chance”, <随机动作概率的百分比>;
set(“chat_msg”, ({
“随机的动作或语言的描述”
……..
}) );
/*这时,如果还想让对玩家ask它时有些特殊的反应的话,就得:*/
set(“inquiry”, ([
“ask的关键词”: “回答的话”,
…….
]) );
}
</code>
物品要比上面两类更要简单一点,看懂了这些,再去调几个物品的文件,自己看看也就明白了。好了,现在你可以自己对自己写一个工作室,也可以在里面放一个傻乎乎的书童了。接下来我们就要深一步,了解文件中更富有变化。更有意思的基本函数之二:
* init() 函数
init()函数就是在有玩家或 npc等的活物进入房间时(可以是走进来,扔进来或clone 进来)被触发,从而实现函数功能。也就是说,如果你要对进入房间的玩家做一些动作,比如弄晕他或给他中毒、或者向他问好、显示出一些特殊的信息等等,那么就在init()函数里对那个玩家进行操作。
在 init() 中最常见的的函数莫过於add_action(“function”, “action”了,它的作用是在进来的生物身上添加上一个指令 (注意, 系统只认指令的第一个单词), 并在玩家下达这个指令时去呼叫那个名称的函数:举例而言, 如果我们写了这样的 init():
<code>
init()
{
add_action(“do_climb”, “climb”);
}
</code>
就是说,当玩家走进这个房间时, 系统会帮他多出 climb 这个指令,当他下达了climb tree 这个指令时, 系统会去寻找 do_climb() 这个函数, do_climb()这个函数当然是你去写了。同时, 系统会将玩家所输入的 “climb”这个指令后面的所有文字,作为一个检查的变量(是不是有些深了?没关系,看不懂就跳过去,知道是这么个意思就行了)传给 do_climb()。你可以将 do_climb这个函数宣告为
<code>
int do_climb(string arg)
</code>
这样一来,当玩家下达 climb tree,或是 climb the red wall这种指令时,“tree”或是 “the red wall”就会被存进变量 arg 之中供do_climb进行检查处理。如果判断后面的变量与我们设计的无关,也就是说,比如我们希望玩家应该是climb red tree,那么就应该写:
<code>
if(arg!=“red tree”) return 0;
</code>
“!=”就是不等于的意思,“!”代表不、否定的意思。return 传回的是 1,就表示通过;是0,则表示函数处理中止。所以,这句判断名就是当后面跟的那个变量不是“red tree”时,函数的处理就终止。反过来,你就可以设计并设置,条件符合时会发生的事,然后别忘了,在最后加上一句return 1;
在这里,我们给有一些基础的巫师开一个小灶,看不懂的就跳过去看下一段。有时有些新巫师会发现一个有点费些思量的事,就是当我们所加的add_action()的指令同时也是系统提供的cmds指令时,一般add_action的指令将优先于cmds进行执行。比如kill是一个cmds指令,但在你现在写的房间程序中将要进行某种程度的限制。一般的做法就是加add_action(“do_kill”,“kill”),那么,玩家一旦在这里发出kill指令,将首先寻找do_kill()这个函数,进行变量的检查。比如,发现玩家的某些条件不符合就给出类似“这里不允许对杀”的信息后return 1;就表示判断通过了,也就意味着这个指令已被你写的这个函数处理掉了。如果你在某个条件后给出“你心中一动,杀机已起,小小的一个地方立刻激荡出无边的战气”后return 0; 这就是告诉系统,我这个kill指令并没有完,刚才只是增加kill更多的信息,此后系统就会寻找本来的那个kill的cmds指令,也就是正式执行,这被称之为重载。(有点绕人?多想想,想通了后很有用的)
在你的函数检查到玩家输入的变量有问题时 (例如你要他们climb red tree, 但他们却输入了一些错误的指令如 climb three 之类的),你可以象上面一样直接return 0;终止这个函数,系统就会显现出“什么?”的错误信息。而如果你想给他们一些特别的、有些意思或提示性的错误讯息时, 你可以用 notify_fail(“…..”)来代替0写这个讯息, 比如:
<code>
return notify_fail(“你想爬什么?\n”);
</code>
return notify_fail()就是用来取代return 0的东西,这就是想让返回的错误信息更加丰富、或者有效而已。所以我们最常用的写法是:
<code>
if (条件不合)
return notify_fail(错误讯息);
if (另一个条件不合)
return notify_fail(另一个错误讯息);
………………………..
else
(所有可能导致错误的输入都过滤光了 开始真正干活的部份…. )
…………………………
return 1;
</code>
人物的、物品的init()与 ROOM 中的init()函数类似, 但物品中被呼叫的机会多了许多, 主要有下列的几种情况:
* 物品摆在房间中, 有一个玩家走进来,这很好理解;
* 一个物品突然出现在某个玩家所在的房间中,这就是象别人丢下的,机关触发出来的,或者是巫师变出来的;
* 一个物品突然出现在某个玩家的物品栏中,象别人给你的,你买到的东西,通过机关直接触发到身上的;
这些 action 会生效的场合归结起来很简单, 就是: 「玩家用 look 或是 i 指令看得到这个物品的时候」,但是同一个房间中他人或 npc身上的东西时不算,装在袋子的东西不算。
废话少说,下面还是拿出例子进行逐句的解释,这也是新巫师最需要的。首先说明:“/*”是表示后面是注释名,它表示后面所有行的东东都是程序不必理会执行的语句,一直到“*/” 结束。用在注释行较多的地方。这里为了区别,我们把教材中注释用淡一些的颜色标出。
<code>
/* Room: /d/wuxi/ximen.c 无锡西城门*/ /*程序开头注明一下 文件类型和绝对路径和中文名*/
/* llm by 99/05/21/* /*继续注释 谁写的和编写时间 有时是修改时间*/
#include <ansi.h> /*表明它继承了定义颜色的文件,后面不要分号*/
#include <room.h> /*表明它继承了定义房间的文件,因为门在这个文件里*/
string look_pai(object me); /*声明一下这个文件里有一个函数的原型定义*/
inherit ROOM; /*表明它是继承 ROOM 类*/
void create() /*开始创建函数void create() 在里面定义各种属性*/
{
set(“short”, “梁溪门”); /*short是指房间的短描述 系统会自动定义成亮青色*/
set(“long”, @LONG /*房间的长描述即场景描述*/
这是无锡城的西城门,缘由城门外的一条梁溪河而得名为梁溪
注意:系统会自动在开头一行空两格,所以你不要多事自己加空格
门,看城官兵比较懈怠,向西出城是一条尘土飞扬的大驿道,往东
便可进入热闹的无锡城了。
LONG
/*在长描述中,前后要加入 @LONG 和 LONG,它们是互相对应的,你可以用任何字接在 @ 後面,但是前后两个单词一定要一样,也就是说你也可以:set(“long”,@TXTE
*******
TXTX
);
这样系统才能判别,而房间的叙述写完时,一定要换行后再接第二个 LONG ,并且LONG或TXTE这一行不能再有其他任何的字元,不然系统无法判定叙述是否该结束了,会造成编译时的错误。而如果不加 @LONG和LONG时,字符串前后必须要加上“ “,中间也必须手动添上“\n”的换行符号。
*/
);
set(“valid_startroom”, 1); /*它的作用是如果在该房间退出游戏的话,就可以成为下一次进来的地方*/
set(“no_fight”,1); /*表明这是一个不允许战斗的房间,同理还有“no_beg、no_steal”*/
set(“item_desc”, ([ /*这用在某些可以用look看出机关或特殊描写的地方*/
”men”:“一座高大的城门,用上好的红木拼钉而成,十分威武”,
“pai”: (: look_pai ,
]) );
/*这里放了两个常用的用法,共同之处,就是“:”的左边是物品名(其实不是物品,也就是一个记号而已)或者叫可以让玩家用look指令看的字符,一旦look men后,就会由右边的来显现信息。第一种情况:右边直接是描述的语句,比如“look men”就会出现“一座高大的城门……”。第二种情况后面是(: 函数名 ,就调用这个函数。而这个函数名必须在文件头声明了,比如我们这里的“pai”就是调用(: look_pai ,在后面要对该个函数进行设置*/
set(“exits”, ([ /*设这个房间的出口*/
“east” : DIR“dajie1”, /*该方向要连到的房间文件名,注意不需加.c后缀*/
“west” : DIR“yidao1”, /*DIR的意思就是这个文件所在的当前目录*/
“northeast”:DIR“qiangdong”,
]));
set(“objects”, ([ /*设这个房间里东东,npc和物品*/
DIR“npc/bing”:2, /*后面的2,也可是1、3 表示数量*/
]));
create_door(“northeast”, “小门”, “southwest”, DOOR_CLOSED);
/*这是定义门,注意它的格式是:
create_door(“入口方向”,“门的名称”,“出口方向”,“预设状态”)
预设状态有两种,也就是DOOR_CLOSED和DOOR_OPENED,分别表示初始状时是关着或开着。因为这些都是写在/include/room.h文件里,所以我们一定在这个文件头加上inherit <room.h>。*/
set(“outdoors”, “wuxi”); /*指出它是室外,并在wuxi(区域目录名)这一区域*/
setup(); /*设置结束*/
}
/*注意:关于“replace_program(ROOM);”的用法,由于在房间的标准物件中有定义了如 init() 等其他的函式,而一个简单的没有机关的房间根本没有用到,所以就用replace_program() 来将原本的被继承的标准物件「重置」(或说取代)掉,以便最大限度地节约系统内存的耗用。但是一旦房间中用到了 init() 来编写时,就绝对不可以用 replace_program(),因为如果你写的是一个复杂的房间,就会在很多触发函数的地方,而这个文件加了这行后,又会把那些多于简单房间定义的函数清除掉了。于是一旦这些地方开始触发了,系统到时就会找不到那些触发的函数。一般地情况下,系统就随便呼叫一个记忆体中的位址而随便传进一些乱七八糟的东西,而情况严重时,可以让整个 mud宕机。我们现在这的这个例子中需要用到一些其它的函数,那当然不能用它喽。对于新巫师来说,这一行在不能确定是是否要加的情况下,还是选择不加为好,毕竟,浪费些空间与当机的比较是很明显的。
接下来我们来定义前面提到了look_pai的函数了*/
string look_pai(object me) /*me是一个对象,指作动作人,也就是this_player(),如果在这里不定义,那么就要在函数里用 object me; me = this_player();进行定义*/
{
if( wizardp(me) ) /*wizardp(me)是一个efun函数,判断me是否是巫师*/
return “大木牌写着:无锡城门。正在建设中,叮当留。\n”;/*根据上面的条件,这句话只有me是巫师时才能看到*/
else /*如果不是巫师*/
return “大木牌写着:无锡城。\n”;
}
</code>
这个程序到此结束。
接下来是一个较为复杂的人物例子
<code>
/* /kungfu/class/baituo/ouyang-feng.c 白驼开山祖师欧阳锋*/
#include <ansi.h> /*表明这个文件要用到颜色*/
#inherit NPC; /*继承NPC的属性*/
inherit F_MASTER; /*继承可收徒的NPC属性*/
int check_self(); /*声明,文件中有战斗行为函数的定义*/
int learn_message(object ob,string skill);/*声明:文件中有learn_message()函数的定义*/
string ask_zhang(); /*声明:文件中有谜题函数的定义*/
void create()
{
set_name(“欧阳锋”, ({ “ouyang feng”, “ouyang”, “feng” }));
/*注意,不要用set(“name”,“”)直接set_name*/
set(“long”, “他是白驼山庄主,号称”HIW““西毒””NOR“的欧阳锋。\n”
+“虽然由于习练「九阴真经」走火入魔,变得精神错乱,但\n”
+“是他那额头上的层层紫晕,令人不得不服他是一代高手!\n”);
/*这就是不用@LONG&LONG的例子,所以就必须在每句尾加上“\n”的换行标志*/
set(“nickname”, HIW“西毒”NOR); /*外号,用到了颜色,所以开头没有include <ansi.h>这里会出错*/
set(“gender”, “男性”); /*性别,太监是无性*/
set(“age”, 53); /*年龄*/
set(“shen_type”,-1); /*神的正负,如果没有set默认是1,用这个乘以exp/10得到神值*/
set(“attitude”, “peaceful”); /*指这个人物的好战态度*/
set(“str”, 30); /*膂力*/
set(“int”, 29); /*悟性*/
set(“con”, 30); /*根骨*/
set(“dex”, 28); /*身法,这些先天属性,可设可不设,但要符合原著精神*/
set(“qi”, 2500); /*当前气*/
set(“max_qi”, 2500); /*最大气,就是恢复满时*/
set(“jing”, 900); /*当前精*/
set(“max_jing”, 900); /*最大精*/
set(“neili”, 2000); /*当前内力*/
set(“max_neili”, 2000); /*最大内力*/
set(“jiali”, 50); /*相当于玩家的加力jiali*/
set(“combat_exp”, 1500000); /*经验*/
set_skill(“force”, 200); /*设置武功,这是基本内功*/
set_skill(“unarmed”, 170); /*反正一项项设,略…….*/
……
set_skill(“nilian-shengong”, 200);
……
map_skill(“force”, “nilian-shengong”); /*相当于玩家的jifa*/
……
create_family(“白驼山派”,1, “开山祖师”);/*门派头衔*/
set(“inquiry” ,([
“欧阳克”:“欧阳锋嘿嘿一笑:“那是我的乖侄子,你见过他了吗?”\n”,
“蛇杖”:ask_zhang,
]));
/*这个是设置当玩家ask sb about sth时的信息,“:”前就是sth,后面
则是返回的信息。这有点与房间里的set(“desc_item”)相似。所以也有关
于 (:ask_zhang的函数调用,这个函数我们在文件头已经定义过了,后
面将会有具体的内容。*/
set(“chat_chance”,2); /*设置随机动作的机率,这是指2%*/
set(“chat_msg”,({ /*设置随机动动作*/
“欧阳锋自言自语道:“我白驼山派神功一成,定能重霸江湖!!”\n”,
“欧阳锋道:“我儿欧阳克聪慧过人,必能够重振白驼山派雄风!”\n”,
“欧阳锋道:“江湖险恶,困难重重,我才是天下第一!”\n”,
}));
set(“chat_chance_combat”, 100); /*这是指战斗中的随机行为,注意区别。*/
set(“chat_msg_combat”, ({
(: command(“wield zhang”) ,
(: command(“wield zhang”) , /*装备武器*/
(: perform_action, “staff.shewu” ,
(: perform_action, “staff.shewu” ,/*使用绝招*/
(: command(“unwield zhang”) ,
(: check_self :), /*这是我们自定义的一个函数,在后面写着*/
}) );
setup();
carry_object(“/d/baituo/obj/shezhang”);/*身上的东西,加上“→wield”就是装备好了,如果没有这个,就会在身上,但没装备起来*/
carry_object(“/clone/misc/cloth”)→wear();/*这件衣服就是穿上的,也可不穿*/
add_money(“silver”,50); /*设置他身上的钱,可以gold,coin*/
}
void init()
{
::init();
add_action(“do_skills”,“skills”);
add_action(“do_skills”,“cha”); /*两个动作调用同一个函数do_skills*/
}
int do_skills(string arg)/*定义do_skills函数,并表明其类型*/
{
object ob ;
ob = this_player () ; /*定义ob是指的执行这个动作的人*/
if( !arg && arg!=“ouyang feng”&& arg!=“ouyang”&& arg!=“feng” )
return 0; /*如果对象不是欧阳锋,则返回调用skills的cmds指令*/
if(wizardp(ob)) /*return 0; 是巫师的话返回调用skills的cmds指令*/
if (ob→query(“family/master_name”)!=“欧阳锋”)
return 0; /*师父不是欧阳锋的话返回调用skills的cmds指令*/
if(!ob→query_skill(“nilian-shengong”,1))/*如果没学过逆练神功*/
{
write(“欧阳锋目前所学过的技能:\n”+
“ 基本内功 (force) - 深不可测 200/ 0\n”+
“□蛤蟆功 (hamagong) - 深不可测 200/ 0\n”+
……\n“); /*略*/
return 1; /*参考前面的懂了这里retrun 0和return 1的意思吗?*/
}
else /*相反则是学过*/
{
write(“欧阳锋目前所学过的技能:\n”+
” 基本内功 (force) - 深不可测 200/ 0\n“+
“□逆练神功 (nilian-shengong) - 深不可测 200/ 0\n”+
…….
\n”); /*这时才让他的徒弟能查看欧的逆练神功级别*/
return 1;
}
}
void attempt_apprentice(object ob)/*这个函数的原型就是在前面定义的inherit F_MASTER里*/
{
if1);
return;
}
int check_self() /*即我们自定义战斗中行为*/
{
int max_qi,eff_qi,qi; /*命名三个变量*/
object me;
me = this_object(); /*不多说了吧*/
max_qi = me→query(“max_qi”);
eff_qi = me→query(“eff_qi”);
qi = me→query(“qi”); /*对三个变量进行初始定义*/
if2) /*如果已经在提升战斗力状态*/
{
……. /*将干什么什么*/
return 1;
}
……; /*否则就怎么怎么*/
return 1;
}
}
string ask_zhang()/*定义解谜中的关于蛇杖的函数*/
{ /*string与int都可以,区别在于string函数return的是字符串*/
object me,weapon,obj,obn;
mapping fam;
me = this_player();
if(!(fam = me→query(“family”))|| fam[“family_name”] != “白驼山派”) /*不是白驼弟子*/
return “\n欧阳锋冲你阴阴地一笑:“你是不是想尝尝我西域灵蛇毒的厉害?”\n”;
if3) /*已经有了蛇杖*/
return “\n欧阳锋怒道:“小子也敢戏弄老夫,明明已有蛇杖,还要问什么?滚!!!\n”;
if( me→query_temp(“dixi-wan”)) /*已经要了药丸*/
return “\n欧阳锋大怒:“贪得无厌的家伙,拿了还想拿,再这样,老夫一杖叫你上西天!\n”;
/*到此为止,一切不符合解谜的条件都过滤完了,则开始执行*/
obn=new(“/d/baituo/obj/dixi-wan”); /*要取出的东西的路径文件名,相当于clone*/
obn→set(“sign”,me→query(“id”)); /*东西上设上给的人的记号*/
obn→move(me); /*这个东西放进问的人的身上*/
me→set_temp(“dixi-wan”,1); /*问的人做记号,以防他再去要,看前面*/
return “\n欧阳峰仰天哈哈一笑后,缓缓说道:“我灵蛇杖法奇妙无比,配合蛇杖上灵蛇\n”
“的攻击,可使对手防不胜防,我这有一颗通灵地犀丸,你拿去找蛇奴,他会知道的!”\n”
HIC“说完欧阳锋递过来一颗鸽蛋大小的药丸。\n”NOR;
}
int learn_message(object ob,string skill) /*定义学武功的条件*/
{
if4))
{
message_vision(“欧阳锋阴阴地对$N说道:“你如何能从我这学会这种没有一点功基的武功?”\n”,ob);
return 0;
}
else return 1;
}
</code>
—–
====== MUD编辑修改入门教程3 ======
人物也讲完了,其实根据这两个文件,你可只选其中一两点就可以改出很多你所需要的人物来,在初级阶段,把现成的文件改会避免太多的BUG的出现。但是我所要说的是:重在理解。理解了之后,什么都好办了。
以下列出一些定义在人物里的一些附加函数,以供参考。
<code>
void defeated_enemy(object victim)
当这名人物打昏一个敌人时会呼叫这个附加函数,victim 即是被打昏的人。
呼叫者: COMBAT_D
有预设定义此一函数的系统物件: none
void killed_enemy(object victim)
当这名人物杀死一个敌人时会呼叫这个附加函数,victim 是将要被杀死的人。
呼叫者: COMBAT_D
有预设定义此一函数的系统物件: none
int accept_fight(object who)
当有其他生物对这个人物下 fight 指令的时候,会呼叫这个附加函数,who是下
fight 指令的生物,只有当这个附加函数传回 1时才会接受挑战,否则显示某某不想
跟你较量的讯息。
呼叫者: “fight” 指令
有预设定义此一函数的系统物件: NPC
int accept_object(object who, object item)
当有人用 give 指令给这个非玩家人物东西时,会呼叫这个附加函数,传回 1
表示愿意接受这个东西,传回 0 表示不接受。
呼叫者: “give” 指令
有预设定义此一函数的系统物件: none
void recruit_apprentice(objct apprentice)
当一个人物收了另一个人物做弟子时会呼叫这个附加函数,你可以在这个函数里
修改弟子的 rank 或其他东西。
呼叫者: “apprentice” 指令
有预设定义此一函数的系统物件: none
</code>
有关房间、人物和物品这三种类型只是我们人为的划分,对于系统来说,它们应该都是一回事。为了实现某一种效果,既可以写在房间里进行执行,也可以写在人身上进行执行。看了这一章后,你可以尝试着写一些程序了。我们希望你的感性认识是建立在自己写了超过百个的编译通过的程序以后,下一章,我们就可以学习LPC的概念了。
编程终究是一件技术性的活,我们总不能一直通俗下去吧!在这一章,我们该开始回过头来,对我们工作系统里的一些基本的概念进行一一的理解,当然要接触一些深一些的概念了。
通常我们在大陆所见到的中文MUD大多是LpMUD,一般是一些角色养成型的游戏模式。LpMUD使用Unix的指令和文件结构。而很多版本也是运行在Unix环境下(目前WINNT的版本也不少)。
—-
===== 理解LPC =====
LPC是什么东东?就是写LPMUD程序的C语言啦!它看起来和一般的C语言区别不大,语法基本一样,只是简单得多啦,可以说,它是我所见到的最简单的一种C语言。他们的根本不同点在于,Lpc程序是编写一个一个的“Object”。 这有什么区别呢?一般的程序是在执行过程中,通常有一个明显的开始和结束。程序从一个地方开始,然后顺序执行下去,到了结束的地方就中断了。而Lpc的Object不是这样的。Lpc的Object可能没有明显的开始和结束的标志,它可能永远在工作。在有些游戏中,整个游戏包括Driver和游戏世界都用C写好,这样处理的速度能快一些,但是游戏的扩充性很差,巫师们不可能在游戏进行中添加任何东西。 LpMud则相反。Driver理论上应该和玩家所接触的世界几乎没有任何直接的联系。游戏世界应该是自己独立的,而且是“即玩即加”的。举个例子,我们在玩三国志系列时,在你没能取得赤壁之战的胜利之前,你是不可能去打六出祁山的,甚至连四川都去不了,但是在MUD游戏中,却可以任由玩家选择,而且巫师也可视其需要,在任何时候、在任何地方再加上一场另外的战役。所以,在你写完一个Lpc的文件时,它就存于主机的硬盘上。在游戏进行中,当需要整个Object时,Driver就从硬盘中读入这个文件,然后放在内存中,一个特殊的函数被调用来初始化这个Object的一些变量。当然这要建立在这个文件没有任何错误的基础上。否则,轻则出错,重则宕机。
在这里,既然我们不断谈到“变量”和“函数”,则有必要谈谈在LPC中它们之间的关系。变量就是一个能够变化的值,函数通常是用来操纵那些变量的一段程序。Lpc 的Object就是一些变量和函数的组合。在前面我们讲过,Object没有开始、也没有结束的地方,更不会有一个特别的地方让Driver去执行它。那当一个Object如果要被内存中的另一个Object调用时将怎么办呢?Driver一般会去找这个Object的那堆变量放在哪里。如果这些变量没有值,那么Driver就会调用一个特定的函数,即create()来初始化这些变量。(看到这里,你可以回去看看第二章的有关部分)。
但是要强调一点,create()并不是 Lpc代码开始执行的地方,只是大多数的Object从这里开始。事实上,create()是可以不存在的。如果这个Object不需要对变量初始化,那么create()可以不存在。那么这样的Object开始执行的地方就完全的不同于一般的Object,它可以从任何地方开始。所以在Lpc的Object中,函数的排列顺序是无所谓的,随便那个排在前面对这个Object的特性没有影响。只不过在各个MUD中的巫师品质要求中稍作规定而已。
—-
===== 理解Object =====
既然LPC中最不同的就是Object,因此,当你想在程序中设计任何动作时, 都应当要考虑到这个动作是哪一个 object 所做的, 不然很容易导致错误。LPC 的语法并不严谨, 有些场合为了省事可以将函数是由哪个Object所做的省略掉, 例如我们在 create() 函数中最常看到的 set(),事实上严谨的写法应为this_object()→set()。因此我们首先要学习的就是如何利用系统提供的函数,去正确快捷地寻找Object。
MUD系统为我们提供了两个最好用的两个函数this_object()与this_player(),在你写作一个对象(房间、物品……)时,this_object()表示自己这个对象(房间、物品……),也就是你写的这个文件本身啦。在这里,要提及一下object的所谓封闭性。每一个object有自己独立的数据结构,它能与其他object严格区分开来。即使是同一个长剑程序,在同一个房间里你复制了两把时,在内存中就会有两个完全独立的长剑object,你对其中一把无论进行改名、改威力都不会影响到另一把的数据,这就是object的封闭性。而this_player()则比较复杂, 它会传回一个属于玩家类型的对象。这个玩家在init中就是触发init的那个玩家。this_player()会跟著函数呼叫一直传递给所有被init呼叫的函数, 包括add_action中所定义出来的函数, 在这些函数中, 它又表示做动作的那个人。
除此之外,当我们只知道一个对象的名字,而不是它的文件名时,是无法用个 object 类型的变量指向它,这时我们就要用到[present() 函数]
用法:
<code>
object=present(string “id”,object env)
</code>
函数在此时就从名字(id)找到这个object,有了这个object才好对它做操作。就可以派上用场。简单的想, 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 的下一个物品。
很简单, all_inventory(object ob) 所传回的是包含了所有物品的一整个阵列。一个object(除了房间之外)都要有自己的环境,就是这个object在什么地方放着,是一间房间里、还是一口箱子中、还是一个人身上,而这环境通常是另一个object。比如物品A和B放在一个人M身上,那么上面的函数就可以给出它们之间的关系 :
<code>
M=environment(A);
M=environment(B);
A=first_inventory(M);
B=next_inventory(M);
A=all_inventory(M)[0];
B=all_inventory(M)[1];
</code>
—-
===== 理解Efun =====
从上面你也许可以看到了,象environment()这样的各种函数,也许会在你编程时时不时地发现,而且用的地方特别地多,最常见的就是this_player()、this_object()、还有strcmp()、implode(),左看右看找不到在哪里定义的,而你就算是找遍你下载下来的单机版的所有文件,都找不到这些函数,其实它们就是efun。efun就是MUD外部定义的函数,也就是externally defined function 的缩写。是由Mud Driver定义好的。它是以计算机直接能理解的二进制的形式存在着的,所以它们执行起来要比一般的Object带有的函数速度快的多。而对于Object内部定义的函数,通常叫作lfun(local function)。一个巫师的主要工作也就是编写一些lfun组成的Object。
那么为什么要创立efun呢?
- 处理一些很常用的、经常会有许多函数会调用的。
- 处理internet socket的输入输出。
- 以及一些Lpc很难处理的事,因为毕竟Lpc是C的很小的子集。
efun是用C写好的,内嵌在Driver里面的。在Mud起来之前,和Driver一起编译好的,它们的调用和你写的函数的调用方法是完全一样的。总的来说,你只需要关心:它需要传入什么参数、将会返回什么的东西就行了。
通常在一个Mud里面,你可以在类似这样的/doc/efun的目录底下找到有关efun的说明和帮助,或者直接用help <efun名>指令就可以得到帮助。因为efun依赖于你所在的Mud的Driver,所以不同的Driver带有的efun区别很大。不要想当然地将别的MUD中的经验带到这里来,也不要在我们这里理解了某个efun,就自以为什么都懂了。对于一个新的巫师不说,你只需要简单地理解,并会使用一些常见的efun就行了
学习真是一件枯燥无味的事件,听得不耐烦了,赶快让我上机操作吧!
好的,这一章,我们就来让你去开始进行一段见习工作了。我们的Mud系统使用的是仿Unix的指令和文件结构。如果有人已经对Unix有所了解的话,那是最好。没用过Unix也没关系,就象LPC虽然是一种很简单其实它与Dos十分地相似,我们只要记住它们最明显的一个区别就是文件路径是用“/“,而不是”\”。下面的许许多多的命令还是让我们会时不时地想起DOS下的种种命令。
<file>
pwd: 显示你目前所在的当前目录
cd: 进入某一目录,..是上一目录,/是到根目录,什么都不加直接回车就是到你自己的的工作目录;
ls: 列出指定目录下的所有文件,就象是Dos的dir一样;
rm: 删除一个文件;
mv: 重新命名一个文件;
cp: 复制一个文件;
md: 创建一个目录;
rd: 删除一个目录(空的目录)。删除这个目录下的所有文件请加 -d参数;
more: 按页显示一个文件在你的当前屏幕;
cat: 显示整个文件;
tail: 显示一个文件的结尾几行,对于太大的文件,一般只能用它;
</file>
对于写程序来说,最好的当然是在专门的可以识别C的编辑器上写,既方便、错误也会少得多,因为它们对于一些固定的函数、变量都会用特殊的颜色突出显示,尤其对于初学编程的巫师来说,多一个字母、少一个符事情的事情可以大大减少了。但是巫师更多的是在线处理一些问题、故障,大多数时候是要通过在线编辑—-也就是edit命令进行程序修改,甚至小程序的在线写作。下面就着重讲一讲MUD所提供的编辑功能的使用。
* 指令格式为:edit <档名>,只加文件名,默认为当前目录,加here,表示编辑你当前所处的房间, 回车后即进入线上编辑系统。
* 如果这是一个已经有的档案,你可以使用 z 或 Z 来看档案。z表示一次显示20行,Z表示一次显示40行。为了编辑方便,最好在开始时用 n 表示每一行在开头处显示它的行数,再用一次n 取消行数显示;
* 还有一种方法 ,就是直接打入行数,则会跳至那行的内容上;
* 如果你开始没打 n ,却想知道现在是第几行请打 = , 想知道内容请打 p ;
* 如果想直接到档案的结尾可输入 $;
* 五种编辑命令 a i c m d :
* a = 从此各行之下插入编辑;
* i = 从此行之上插入编辑;
* c = 修改此行并插入编辑;
* m = 把本行移到特定的行号去;
* d = 删除;这些命令也可以和行数结合使用。如 :
* 7a = 在第7行后插入编辑;
* 6i = 在第6行前插入编辑;
* 4c = 直接编辑第4行;
* 5,8m1 = 将第 5~8 行移至原第 1 行之下。
* 3d = 删去第 3 行 ;
* 2,10d = 删去第 2~10 行;
* 如果这是一个新命名的档案, 这里面当然是没有行数了,一般你应该用 a 来开始编辑第一行;
* 如果你对某一行或某几行编辑完毕后, 请在编辑结尾的次列开头处打 . 即可退出行编辑状态;
* 如果想存档请打 x 表示存档退出。 否则请打 q 或 Q 表示放弃编辑退出。
* 其余功能可打 h 参考。由于有的MUDOS汉化不太好,下面列出 h 的中文注释:
* / 前向查找你后面所跟着的字符,比如/酒袋
* ? 后向查找你后面所跟着的字符
* = 显示当前行是第几行
* a 在当前行后新增加一行
* A 类似'a'命令,但是将翻转自动缩进模式
* c 将当前行内容覆盖掉输入新编辑内容
* d 删除指定范围的行
* e 退出当前档案, 开始编辑另一档(档案改变过未存盘无效)
* E 类似'e'命令,但是文件被修改过也有效
* f 显示或改变文件名
* g 查找匹配行并对其执行相应命令
* h 帮助文件(就是显示你现在看到的这些信息,可能是英文)
* i 在当前行前面插入一行
* I 排版整个代码 (Qixx version 1.0)
* j 合并行,系统默认是将后续行连接到当前行
* k 标记当前行- later referenced as 'a
* l 显示指定行(可显示控制字符)
* m 移动指定行(或几行)到指定位置
* n 行号显示切换开关
* O 同命令'i'
* o 同命令'a'
* p 输出指定范围行
* q 退出编辑器
* Q 退出编辑器,即使文件已经被修改且未存盘
* r 在文件尾或指定行后读进另一文件
* s 查找并替换(只对当前行第一个查找的字符串进行替换)
* set 查询,改变和保存编辑器的设定值
* t 复制指定行到指定位置
* v 搜索并对不匹配行执行指定命令
* x 保存文件并退出
* w 将编辑的内容写到当前文件或指定的文件里
* W 类似'w'命令,但是是将编辑的内容附加到指定或当前文件后
* z 显示20行,可用参数 . + -
* Z 显示40行,可用参数 . + -
前面一章讲过,当你成功地编写了一个程序后,只是意味着这个程序已经在硬盘了,只有在别的使用者调用到它的时候,才会被呼叫出来,进入内存。文件本身是否有错误,将首先会在这时被发现。对于巫师来说,消极地等待别人去调用它时再去发现有没有错是十分不明智的,这时最好的就是先update它。
<code>
update <文档名>
</code>
注意了:如果系统显示:“重新编译 *.c:成功!”的信息,并不就表示你的这个程序就完全正确了,它只是表示你的程序的基本语法没有错误,如果程序里还有一些由其它的条件或文件才能触发呼叫的函数的话,还有有可能存在一些隐患,保险的做法就是按照设计时的条件把它们一一触发,进行尝试,直到完全通过。比如象一些NPC里有是否接受拜师的函数,你则想法不同的条件的人去拜它试试,把每一种可能都试过,看看是否执行正常。有的房间里加了add_action(),你则一一试试这些add_action(),看一下后果。
如果文档中有错误,一般系统会唰地一下子出来一大串的错误信息,对于新巫师来说,只要去看看第一行的出错内容就行了,后面的很多错误都是由前面的带来的。还有许多信息还显示其它的与一些重要的系统文件也出错,也大抵是如此,首先还是找出关于这个文档里的第一个出错的行数,再到这个文档里去查找,仔细查看该行数,以及前后几行,有没有少写多写括号、漏记多添逗号、定义变量类型错误等等,如果显示出错的行数在最后一行,甚至更后的话,那就要看看是不是函数声明出错,或者定义了无效的函数。每改一次,再update一次,直至编译成功。有关于各种出错信息的意思和处理办法,还是要在实践中多多尝试,但是,在此要忠告各位新巫师,如果你所工作实习的MUD是一个正在开放中的MUD,希望对于没有任何把握的文件的编译工作最好先在自己的单机版进行,有些恶性的错误严重时会导致整个游戏宕机。
接下来就是任何一个新巫师一上任就十分感兴趣的命令—-call!call就是直接呼叫(执行)函数的意思。在某种程度上,它就象征着巫师手中的神杖。这个对于玩家来说威力无比的功能,既是一种巫师利器,更是一种危险器械。因此,在大多数的MUDLIB中都对于call的命令的使用进行了记录,以备天神的查看和监督。call的命令格式如下:
<code>
call <物件>→<函数>(<参数>, …… )
</code>
从其理论上来说,它可以呼叫任何没有被protect的函数。具体我们可以看这个程序: /adm/daemons/emoted.c d 在这个程序里面有一个这样的函数:
<code>
string *query_all_emote()
{
return keys(emote);
}
</code>
那么。我们就可使用call命令直接呼叫它:
<code>
call /adm/daemons/emoted.c→query_all_emote()
</code>
由于这个函数本身没有要传参数,就不用再加参数了。那么执行后,程序本身就会返回一个字符串的数组回来。而显示在我们屏幕上面的内容就是所有的emote的英文名字。
在实际工作中,上面的这种用得还是很少的,大部分的修改和查看我们都可以用more或edit去完成,但是对于尤其象玩家档案这些以.o形式储存的文件用edit编辑则有些费劲了,所以这时使用call的命令来得更为方便些。
巫师们常常会call me(或者id)→set(“combat_exp”,10000)
在这里,me就是自己,其实它对应着一个物件程序:/obj/user.c,后面的set()也是系统放在一个文件里最基本的函数。后面的括号里面便是这个set函数的参数。它的意思就是在me这个物件里执行set()函数,通过set()函数将combat_exp这个参数设为10000。如果。要改变别的人,就可以在call后面加上这个人的id。set()这个函数可以执行什么呢?其实很简单,打开一个复杂一点的NPC,它里面所具有的参数,我们一般都能用call命令进行。
call命令可以调用的函数非常多,一般由你call的物件有关。但在一般使用中,我们大多使用三种函数,一是set,也就相当于我们做程序中的set一样,你可以set这个物件任何可以set的参数;第二个就是query,用它可以查看你所call的物件有没有这个参数,这个参数内容是什么?第三个就是delete,顾名思义,它正与set相反,用以删除物件上的这个参数。其它一些固定的函数,例如武功的set_skill,设定姓名的set_name等等就不一一叙述了。
—-
====== 补遗篇 ======
一共四章的《新巫师入门手册》写出去以后,叮当一直有一种诚惶诚恐的感觉。因为我无论在接触MUD之前还是之后,都未接触过任何的编程语言学习,更别提什么C了。象我这样的人写出的教材,是否会误人子弟呢?但叮当也相信,在网上,也一定会有许许多多与当初的叮当一样,对于已有的一些巫师教材看得云里雾里的感觉。不是责怪这些教材写得太深,而是确实自己的基础太差。正是基于这点,叮当才决定依据网上已有的一些教材为基础,从自身的体会与理解出发,编了这册不成样子的《新巫师入门手册》。但是上网后,想不到竟会收到了很多新巫师朋友的感谢、赞扬与鼓励。他们对手册的肯定,也增强了叮当的信心。于是决定在加上一篇补遗篇,补充说明LPC编程中的一些基本概念,完成这册入门教材。并斗胆考虑起中级教材的布局。
同时,叮当也声明,所有的概念都是从我自己的理解出发,请勿与专业教材中的定义相提并论,若有贻笑大方之处,还望各路高手多多指点。
===== 第一节:变量 =====
首先,我发现新巫师们编程结束后,一旦update就呼啦啦地出现一大群的编译错误,其90%以上都是一些逗号,分号,括弧的基本错误。到底这些符号应该怎样使用呢?它们之间有何规律呢?但是在解释它们之前,我们必须来理解LPC中的变量与变量类型。
变量是什么?我觉得你应该把它理解为一种不确定的替代值,有点象现实中的经纪人。其代表的人只要在第一次出来一下:声明某某是我的经纪人后,就可完全由变量来处理了。变量还有局部变量与全局之分,也就是仅仅在一个函数中起作用与在整个系统中起作用的分别。这点还是很好理解的。因此,对于我们来说,编程中之所以用到变量,其目的就是要让程序处理更快、更有效率。举例象这样一段程序:
<code>
if(this_player()→query(“qi”<25))
this_player()→add(qi,-this_player(“qi”/5));
else if(this_player()→query(“qi”>100))
this_player()→add(qi,-this_player(“qi”/2));
else
this_player()→add(qi,-this_player(“qi”/3));
</code>
这段程式中反复调用this_player()→query(“qi”)这个值,每出现一次,程序就要找一次this_player(),将它调出来,再从他的身上取出query(“qi”)这个值进行处理。而使用了变量则会简化了许多。比如,象this_player(),我就定义一个me来代替它,这样,我只要在一开始声明一下,me就是this_player(),这个变量就将this_player()找出,并定义在自己身上,以后每次执行时直接使用me就行了,也就是无须再次调用。其次,我们发现this_player()→query(“qi”)调用也很频繁,我们可以再定义一个变量i,用它来代替它。这样,这段程式可以改写成下面这样:
<code>
object me = this_player();
int i = me→query(“qi”);
if(i<25)
me→add(“qi”,-i/5);
else if(i>100)
me→add(“qi”,-i/2);
else
me→add(“qi”.-i/3);
</code>
发现了吗,两个变量只是在开头定义时分别调用了一次,然后需对这两个变量进行操作便可以了。
接着,细心的你可能会发现,这两个变量,我在定义的时候是用不同的方式的定义的。一个是object,另一个是int。这是因为我想让它们代表的类型不同。总体来说,在LPC里,变量大约有以下几种:
object(对象型)、int(整数数值型)、float(浮点数值型即含小数点的数值)、string(字符串型)、mapping(映射型)、array(数组型)、mixed(混合型)、以及不常用的class(自定义型)。等等。
一、object的意思,是定义一个对象,具体说来一个NPC、一个物品、一个场景、甚至一个运行于内存里的文件。它实际上是一段由后面很多变量按一定运算方式组合在一起的程式。我们经常使用的是将this_object()与this_player()通过object定义成简直的me或ob这样的符号。如果你要想在一个程序里制造成一件新的物品,则必须先定义一个变量,如bject obj;然后再obj = new(*)将这个obj实际上就clone了出来,括弧里的*代表它的文件绝对路径名。
二、int的意思,表明定义的变量是一个整数数字,可以为正负或0,定义出来的数字可以进行各种数字运算,但结果只保留小数点前的数字。比如:
<code>
int i;
i = this_player()→query_skill(“force”,1)/70;
</code>
如果一个玩家的force最高只能到500级,那么这个i的结果只能是从0到7之间的这7个数之一。
三、float相对于int来说可以是有小数的数字。比如i=10/3;如果前面是int i的话,i=3;而如果是float i的话,i=3.3333333。我查了一下外部函数表,对于我们使用的MUDOS来说,大部分的机器支持浮点值变量小数点后7位的精确度;
四、string是说是一个字符串,你可以很简单地把它理解为一串字符号,这些字符不具有任何计算意义。一般来说,字符串的长度在理论上是没有限制的,在LPMUD里,限于网络响应,一般是在编译MUDOS时,在config.h文件里进行设置与限制的。对于字符串型变量的识别,我们有一个很简单的区别标准,就是要看它们有没有用双引号括起来,有则是string的变量,没有则看其是否整数而分辨为整数数值与浮点数值。因此在一些不严谨的语句中,如没有强制定义,也可将int、float与string区分出来。
* A.set(“number”,783);——→int型
* B.set(“number”,78.3);——>float型
* C.set(“number”,“783”;—–>string型
* D.set(“number”,“78.3”);—→string型
string型变量可以相加,但决非数字意义上的运算,而是一种合并,例如上面的C+D就是“78378.3”;
五、映射型变量是LPC独有的一种函数类型,据我的理解,好象是为了让程序更方便地实现一些小的数据库的功能。映射型变量里面有很多的小项,每一个小项都有与自己一一对应的参数。它们就好象是一个个独立的小变量一样,并使用 : 符号进行赋值。而且里面的这些小变量可以用前面的多种类型混用。 举例如下:
<code>
mapping fam = ([“a”:2,“b”:13,“c”:2.333,“d”:“一条小河”,“e”:“158”]);
</code>
这个fam里的a、b子变量是int型的,c是float型的,d、e是string型的。有一些LPC的说明文件里,a、b、c、d被叫做“关键字”,而:后面的象2、13、2.333、一条小河、158被叫做“内容值”。是不是有点类似于数据库的味道?映射型的变量可以用“变量名[“关键字”]”的形式进行调用,并可以用“变量名[“关键字”]=新内容值”的方式进行赋值。例如:
fam[“e”]的值就是“158” ,如果fam[“e”]=“400”,那么再次调用时:fam[“e”]的值就是“400”了。
六、数组型变量实际上是很多的单个变量的集合,它的特征就是在定义变量名的时候,前面加一个*符号,前面可以object、可以int、也可以string,典型的数组型变量如下两种:
<code>
string *num = ({“a”,“b”,“c”,“d”,“e”……});
int *num = ({5,3,4,4,8……});
object *obj = ({ob1,ob2,ob3,ob4});
</code>
相同数型的不同数组型变量之间可以进行加减,加法时,则把两个数组里的项目合在一起,但是并不管里面有没有重复,一律列入。而减法则把被减变量里含有减变量里的项目统统去掉,举例说明:
<code>
string *msg1 =({“a”,“b”,“d”,“d”,“e”});
string *msg2 =({“b”,“b”,“d”,“f”,“g”});
string *msg3 = msg1+msg2;
string *msg4 = msg3-msg2;
</code>
那么msg3 = ({“a”,“b”,“b”,“c”,“d”,“d”,“d”,“e”,“f”,“g”});
而 msg4 = ({“a”,“c”,“e”});
七、混合型变量一般用在一些特殊的地方,因为不能确定变量的类型,或者几个类型都有可能,就会用到它。不过一般的情况下,如果能确定的话还是要固定好。
八、自定义型变量。(略。呵呵,因为我也不大掌握,基本上没用过。)
另外象function (函数指针)用到的地方比较少,就不在入门手册中介绍了。还有一些可加在这些变量定义前面的进一步修饰的类型参数,比如象private、nomask这样的也不一定是新巫师所必须掌握的,还是留待更深一层的教材去讲述吧。
—-
===== 第二节 函数 =====
在LPC中,每一个函数被调用后,有时不需要返回任何值,有时则需要。我们就把不需要返回值的函数称为void(无返回值)型,其它的,则按照返回值的变量类型,区分为与此相互对应的类型。所以,参照上一节,我们就可以很容易地理解:函数也有着象那基本的八个变量、再加一个无返回的void,分为共九个基本类型。它们在函数开头的定义时就要写清楚了。
所以新巫师们看到了这里后,就要使劲地想想,是否自己曾在某一个程序里,开头定义的是int ask_money(),结果在函数里面却是return “客官到底想要些什么?”这样返回是字符串的情况?反正我初写程序时常发生这样的错误。我记得在某些比较老的单机版的MUDOS里,对于函数的返回值检查并不是十分地严格,因此,在单机上测试往往很正常。但是到了LINUX下,尤其是新版本的MUDOS,对于这些检查十分地严谨,甚至在特殊的地方,还会导致宕机。
前面我们讲过,LPC里,一个object就是一个很多变量的集合,那么这么多的变量是谁来控制它们呢,那就是函数了。在具体的编程中,每一个函数的设置都是要有其实际意义的,也就是说,要在运行中能被其它函数或其它object调用到。如果一个永远调用不到的函数,那就是没有任何意义的。在LPC中,有一些基本的函数是由系统,也就是底层的MUDOS自动调用的,我们也就无需去寻找它们的出处的。
<file>
void create()
前面也讲过,这是当一个object被载入内存时,对这个object进行最基本的初始状态设置用的函数。
void init()
当这个object本身进入一个新的object、或者有一个新的object进入了它所处的object、或者进入它自身里时这三种情况下将自动呼叫这一函数。
</file>
然后还有一大堆由系统文件与总的继承文件所定义呼叫的大量函数,这些必须要了解,但是可以留待在实践中慢慢熟悉写与了解。
再下来就是各个文件里自定义的函数了。其实所谓的自定义函数也只是相对的,最终说来,都是一个作者写的。只不过很多函数是由最早的巫师编写,并得到公认或约定俗成固定了下来。那么如何写一个函数呢?
一、首先确定函数返回数据类型,比如是stirng还是int之类的;
二、确定一个函数名,这个名字一般来说,首先你要熟悉你所工作的MUD里的函数命名规则或惯例,一是不要取一些与基本底层函数相同的名。比如die()、init()等等,其二是力求用简洁的英文或拼命取名,让人能够不看内容猜得出其用意;
三、接下来就是一个()、()里放着这个函数执行时所需要的参数,这些参数可不是随便加的,它们的定义实际上是由调用这个函数的那段程序所提供的。
四、写函数内容以一个{ 表示开始,最后当然是以一下 } 表示结束。函数的各种括号十分有意思,它们总是一对一对地出现。只要少了一个或多了一个,程序当然就会出错。
五、函数一开始必须要对它所使用的变量进行声明,比如:
<code>
string m,n;
object ob1,ob2;
</code>
这两句表示,在这个函数将要使用到两个分做m和n的字符串型变量与两个分别叫做ob1与ob2的对象型变量;
六、下面就开始对变量进行赋值,计算指令的各种语句、表达式,也就是我们所看到的if、else、switch等等的语句。当然,就象别的函数调用你一样,你在这个函数里也可以调用别的函数。
七、到了最后,再回到头来看看这个函数到底是什么类型的,只要不是 void,在最后结束的 } 前肯定要有一个 return ,并且返回和这个函数的数据类型一致的一个值。
这里插一个与前面有关的话题,就是函数中所用到的变量问题。函数中的变量来自四个地方,第一个,当然是在函数一开始时声明并在之后直行赋值的;第二个就是在上面所说的第三步里在函数命名后面的()里面的,它是来自于调用这个函数的别的函数所提供的;第三个是来自于这个object()里的全局变量。一般是在整个文件扔程序开头的地方进行总的声明。我称它为小全局变量。这个变量可以在这个文件里所有的函数里进行调用;第四个是来自与整个MUDLIB所提供的全局变量。象我们的LPCMUD里经常会出现一些大写字母的变量名,比如象“USER_OB”“LOG_FILE”等等的变量名,在整个文件里甚至继承文件里也找不到,它一般是定义在/include目录下的全局变量声明文件里的。
——-
===== 第三节 符号 =====
编程要用到很多的符号。下面就要回到这一章开头讲的,到底那么多的符号怎么区别它们的用法。
据我的体会,主要我们频繁使用的符号可以分出包括型与间隔型。
包括型就是各种各样的括号。一共有四种,即()、{}、[]、“” 。这些括号可以掺在一起使用,但是一定要记住,在一个语句中,有几个(就必写会有几个)、同理,有几个[就必写会有几个]。所以在复杂的语句中,最好在检查时仔细数一数括号是否是前后对应的。
一、回过头去看看第二章,就可以看到,()实质大多数是放函数执行时的参数或者是执行运算语句的。一个()前面必定会有一个函数名或者执行语词,当然有很大一部是由MUDOS提供的外部函数。比如象:write()、set()、init()或者是if()、else()、switch()等等。
二、{}有三种用法,第一是用在函数的一开头与结尾,相互呼应。第二是用在一个程序表达式的开头与结尾。比如if(…){};第三便是被()包起来,表示数组,也就是({})。中间可以放入若干个项目;
三、而[]也有三种用法,第一是被()包起来,表示映射函数。也就是([])。第二种是用函数名[关键字]这样的形式来表示映射里的某一关键字的值,比较常见的有在房间文件里的exits[“south”];第三种是直接在一些string型或int型的变量后面跟上一个[],里面有一些参数,根据具体定义的返加值类型,返回不同的值。比如:
<code>
string msg = “tims”;
(string)msg[0]就是t、(string)msg[3]就是s。
</code>
而(int)msg[0]则会返回一组数字。具体数字的含义我也不太清楚,不过据我反复试验,发现这些数字的高低可以判断这个msg是英文字母、英文字符、中文字符或是全角字符。好象是各个字符的区域代码一样。
四、““用在两个地方。一:在函数的具体项目名上要加。比如set(“age”,14);当然,如果这一个项目是一个变量或已经被一个变量所代替了,则不能加。二、在字符串上必须要加,尤其是表示字符串意义的数字。否则若没有定义的话,很容易被当作int型处理。而只要加了”“,则必定被当作字符处理。
间隔型符号主要只有两种:,与;与:
一、逗号:, 逗号一般是表示前后的项目是平等并列的。它常被用在数组的各个数之间的分隔、映射中各个不同关键字的分隔,如:
<code>
string *str = ({“A”,”B”,“C”,“D”})
</code>
或者再如:
<code>
mapping quest = ([“A”:4,“B”:“大河”,“C”:“15”,“D”:31])。
</code>
在一个函数的一变量声明中,它用于分隔同类不同名的变量名,在函数命名后()里的参数也是逗号相隔。当然这里有一处例外,就是在一些mapping型函数里,如果是采用set的方式,在总的映射名与后面的各项关键字之间也是用的是逗号分隔的,比较常用到的如:
<code>
set(“exits”,([……]));
</code>
二、分号:; 分号表示一个完整的语义讲完了、执行完毕。每一个分号前的话都有一定的独立的意思。因此,在某一个独立的变量内部是绝对不会出现分号的。
三、冒号::,冒号一般用在三个地方,一是单独使用时,常常用在映射(mapping)里,表示将冒号右边的值赋给左边。左边的叫关键字,右边的叫做内容值。 二是与?合用,例如:
<code>
A?b:c
</code>
在这里,A是一个条件表达式,如果A成立的话、或者是真的话,就会返回冒号左边的b值,如果不成立,则返回冒号右边的c值。这种写法用在一些简单的判断里,可以省去很长的if else。
第三种情况是在swtich()语句里,放在case <某一项>的后面,表示,如果swtich()里的可能是这某一项时的情况。例:
<code>
swtich(random(10))
{
case 1:
……. ……
</code>
最后再说一下,在程序中,象if() else() switch() 这样的判断语句后面直接跟着{},不需要加间隔符号。而且如果{}里面的内容只有一行的话,这对{}可以省略。例:
<code>
if(me→query(“age”)>45)
{
write(“it is good!\n”);
}
</code>
就可以写成:
<code>
if(me→query(“age”)>45)
write(“it is good!\n”);
</code>
再下来就是一些逻辑符号了,象&&表示并且、||表示或者、=表示赋值。
运算符号,+-*/也就是我们四则运算了。
——
===== 附录:常见编译出错信息 =====
均以/u/llm/npc/test.c文件为例:
一、编译时段错误:/u/llm/npc/test.c line 13: parse error
parse error一般表示错误出在基本的拼写上,多是象逗号、分号写错,或者是各种括号前后多写或漏写的情况,可以在提示的第13行或之前的几句里找一找;
二、编译时段错误:/u/llm/npc/test.c line 13: Undefined variable 'HIY'
Undefined variable表示有一些未曾定义、不知其意义的东西存在。后面跟着的就是这个不明意义的字串。象这句就表示不知道第13行中的'HIY'是何意思。这个错误有三种可能,一是将一些变量的词拼错。比如,本来定义的是“HIT”,结果写成“HIY”。二是因为这个变量未曾定义或者根本就没有声明,第三种情况是这个变量是定义在一些继承文件里,但在这个文件里却忘了继承。象这行就是最后一种情况,是这个文件前没有#include <ansi.h>,因为表示亮黄色的HIY是在/include/ahsi.h文件里定义的。
三、重新编译 /u/llm/npc/test.c:错误讯息被拦截: 执行时段错误:*Bad argument 1 to call_other()
这句在开头,一般是指这个文件里在调用其它文件里的函数或者是对象时发生错误了。这时你可以接着往下看。一些与其它文件相关的错误信息全部跳过去,直接找有关这个 test.c文件相关的错误信息,然后找到比如象这样的信息:
程式:/u/llm/npc/test.c 第 47 行 。那么就仔细查看第47行调用的东西有无问题。
四、重新编译 /u/llm/npc/test.c:错误讯息被拦截: 执行时段错误:F_SKILL: No such skill (froce)
这个错误很明显的,肯定是在设置武功时把force写成了froce,系统当然找不到froce这样的skill了。
五、重新编译 /u/llm/npc/test.c:编译时段错误:/u/llm/npc/test.c line 75: Type of returned value doesn't match function return type ( int vs string ).
这句表示在某一个函数里,返回值的类型与定义的不同,并指出是因为string与int的错误,到75行附近检查吧。
六、重新编译 /u/llm/npc/test.c:编译时段错误:/u/llm/npc/test.c line 72: Warning: Return type doesn't match prototype ( void vs int )
这句也表示错在函数类型上了,只不过是因为函数与前面的声明相冲突,一个是int,一个是void。
七、重新编译 /u/llm/npc/test.c:编译时段错误: /u/llm/npc/test.c line 5: Cannot #include ansii.h
很明显,在第5行处想要继承的文件并不存在。是不是自己写错了?
后记:写完这篇《补遗篇》,这册《新巫师入门手册》就算结束了吧。相信你将这五章都真正看懂,并理解了之后,做一个日常维护的巫师也就可以了,而对于写一些简单的场景、NPC更不在话下了。有什么意见与想法将点击左下角的巫师信箱,给我来信。我们在以后的有关中级教材里再见面吧!
—-
====== 附件 ====
谁能把叮当的教材汇编成chm吗?