在正式教程之前,先对上面提到的efun,lfun,simul_efun做一下解释:efun是MUD外部定义的函数,也就是externally defined function 的缩写,这类函数不是游戏代码中定义的,而是游戏驱动MUDOS中定义的,可以直接使用,其执行速度要比自己定义的函数快,也有教程把efun翻译成“超越函数”;simul_efun就是模拟efun,是游戏LIB中自己写的一套可以在整个lib里使用的函数,对一般的ob来说,和efun没有什么区别,一般重载efun也是写在simul_efun中;而对于游戏内对象(Object)内部定义的函数,通常叫作lfun(local function)。
此教程主要讲的就是EFUN,了解这些MUDOS定义好的函数的作用和用法。MUDOS的源代码可以到官方网站上下载:http://www.mudos.org/beta.tar.gz 其版本是:v22.2b14 .因为有两三年没有更新了,基本上各MUD游戏的驱动都是自己修改源程序后重新编译的,像支持中文,支持数据库……这里直接使用一个已经编译好的OS做教程,下载地址:http://gameivy.com/down/3344520.asp?id=150&url=1 解压后会得到二个重要的文件:mudos.exe就是游戏驱动,Config.cfg则是游戏运行配置文件,这里要根据需要修改了。
假如MUDOS和config文件所在文件夹是:C:/mud,游戏LIB是:C:/mud/lib。在运行MUDOS前先修改config.cfg文件:其中:name是游戏的名称,可以修改成自己喜欢的名字;mudlib directory是游戏LIB的路径(一般用绝对路径),在这里应该是:C:/mud/lib;binary directory是游戏驱动和配置文件的路径(一般也用绝对路径),在这里是:C:/mud。其它则是相对LIB的相对路径,其中的文件和文件夹都在LIB下,这里全用默认设置,不做修改。根据config.cfg的内容在C:/mud/lib下建立以下文件夹:log(游戏日志记录),include(游戏代码中#include包含的文件所在文件夹),binaries(游戏编译的二进制文件保存文件夹),single(master和simul_efun所在文件夹)。
下面我们来运行MUDOS,当然,肯定不能运行起来,因为我们的MUDLIB中就几个可能需要的文件夹,没有任何游戏代码。不过我们还是先来运行MUDOS,看看游戏加载顺序以及出了哪些错误。
第一次运行MUDOS会弹出如下的窗口:
点确定,然后浏览选择和MUDOS在一起的配置文件config.cfg:
这里,如果你点注册服务则会把MUDOS注册成系统服务,每次电脑开机会自动启动MUD,如果直接点确定则是只运行MUD一次,不会在下次开机时自动启动,确定以后,等待MUDOS启动,这可能需要几秒,然后……
出现如下错误:
意思是没有加载/single/simul_efun.c,MUDOS启动首先加载simul_efun.c这个文件,其位置是自己在config.cfg中指定的,一般我们在这个文件中#include自己定义的其它simul_efun文件。我们的这个LIB中是空的,不存在这个文件,自然出错,在/single/下新建一个文本文件,改名为simul_efun.c吧,这个文件中暂时什么内容也没有,不去管它,重新运行MUDOS,然后……
出现如下错误:
错误显示的比上次要多了,其实关键只是一个问题,没有globals.h这个文件,这个文件是会自动包含在所有的游戏程序(对象)中的,simul_efun.c自然也会自动#include这个文件,但LIB中没有这个文件,自然出错,我们在/include/下新建一个文本文档并改名为globals.h吧,还是没有内容的,再次运行MUDOS……
出现如下错误:
这次是/single/master没有加载,现在在/single/下新建一个master.c文件吧,还是空文件,现在再次运行MUDOS看看。
这次的错误是:
不再是因为缺少必要的文件而报错,这次是master.c中没有get_root_uid()这个函数的原因,这个函数是什么意思?起什么作用?怎么使用?
master.c是游戏最重要的文件之一,MUDOS启动后会加载master.c这个对象并自动调用一些函数,这些函数被统称为applies,这类函数是在一定的情况下由MUDOS自动调用的,有一些是必定会调用,但也有一些是根据MUDOS编译时的配置文件options.h中的设置来决定是否调用,你可以在MUDOS的源程序中找到这个文件,这个文件修改后必须重新编译一个新的MUDOS才起作用。
在options.h中#define PACKAGE_UIDS后,每次master.c这个对象被加载时都会呼叫get_root_uid()和get_bb_uid()这二个函数,这二个函数的返回值都是string类型,get_root_uid()函数的作用是取得根UID,get_bb_uid()是取得骨干UID。uid就是user id,意思是使用者识别名称,还有一个euid是effective user id,意思是有效使用者识别名称,uid/euid机制主要用于系统安全方面,可以设定某些uid or euid的object不可以或者可以做某种操作。
函数get_root_uid()和get_bb_uid()的说明文档如下:
get_root_uid - get the root uid
string get_root_uid();
This function is only used if PACKAGE_UIDS is used.
This master apply is called by the driver each time
it loads the master object, to verify that the master
object has loaded, and to get the root uid defined by
the mud. The function should return a string, eg "ROOT"
See also:
get_bb_uid
get_bb_uid - get the backbone uid
string get_bb_uid();
This routine is only used if PACKAGE_UIDS is used.
This master apply is called by the driver on startup,
after it has loaded the master object, to get the
backbone uid defined by the mud. The function should
return a string, eg "BACKBONE"
See also:
get_root_uid
Tim Hollebeek Beek@ZorkMUD, Lima Bean, IdeaExchange, and elsewhere
根UID和骨干UID是我们自己设定的,一般可以把根UID设成“ROOT”,把骨干UID设成“BACKBONE”,现在我们在master.c文件中写入如下代码:
string get_root_uid()
{
return "Root";
}
string get_bb_uid()
{
return "Backbone";
}
保存文件,不过,因为根UID和骨干UID在MUDLIB的其它代码可能再次用到,为了方便调用和修改,我们最好把它们定义成宏。在/include/globals.h中写入如下代码:
//User IDs
#define ROOT_UID "Root"
#define BACKBONE_UID "Backbone"
请注意:在.h头文件中尾行必须多一行空行,也就是程序尾行有一回车换行,否则MUDOS运行会报错:
当globals.h文件尾加一空行后就正常了,其它的.h文件也都得如此。定义好宏后,把master.c中的代码改成如下:
//master.c
string get_root_uid()
{
return ROOT_UID;
}
string get_bb_uid()
{
return BACKBONE_UID;
}
这次运行MUDOS吧,正常启动:
Initializations complete.
Accepting connections on port 4000.
Accepting connections on port 4001.
因为config.cfg默认配置的连线端口是4000和4001,所以我们telnet 127.0.0.1 4000或telnet 127.0.0.1 4001吧,最好用zMUD来连接游戏,连线成功但马上就断了,这是因为缺了必要的函数,下面我们来写连线相关代码吧。
连线时MUDOS自动调用master对象中的object connect(int port)这个函数,每当有一个新的玩家连接游戏都会调用这个函数,并返回新玩家对象,函数的参数port就是玩家连线的端口。
我们先在LIB根目录中再建一个文件夹object用来放我们的连线玩家对象,在object文件夹中新建一个login.c的空文件,这个将用来连线。为了方便调用和修改,再在globals.h中定义如下宏:“#define LOGIN_OB "/object/login"”,这样我们可以在程序中用LOGIN_OB来代替"/object/login.c"这个文件。做好以上工作后在我们的master对象(/single/master.c)中加入如下代码:
object connect(int port)
{
object login_ob;
login_ob = new (LOGIN_OB);
return login_ob;
}
对上面的代码做一下解释:
“object login_ob”是定义一个对象(object)类型的变量login_ob,“new(LOGIN_OB)”则是把LOGIN_OB对象载入游戏(如果此对象已经被载入则不再执行此操作)并复制这个对象产生一个对象的复件,“login_ob = new(LOGIN_OB)”是把LOGIN_OB这个对象的复件赋值给变量login_ob,“return login_ob”则是返回这个对象,对于函数、变量、返回值这类基础知识不做讨论,你应该懂得这些。只补充说明一下“对象”,LPMUD是一个纯对象的世界,房间、NPC、玩家、兵器……都是对象,一个对象应该是一个实际存在的.c文件,当我们希望在游戏中载入或复制这个对象时,如果这个对象的原始对象并不在内存中,这个文件会被编译并载入游戏内存,new()函数就是载入原始对象并复制这个对象,还有一个efun load_object()则是只载入原始对象。原始对象的名称通常是对象文件名(包括路径)去掉扩展名.c的后的部分,如master.c就是一个对象,是游戏的主控对象这个对象的名称就是OBJ(/single/master),复制对象的名称后都有一个#号,其后面跟着一个数字编号(从0开始),一个对象的所有复制对象的编号都不相同,一般后一个复制对象的编号就是前一个复制对象加1,关于这些内容以后会具体的讨论。
保存后重启MUDOS,连接游戏,连线成功,不会自动断线,因为我们没有写必要的游戏代码,所以什么都不能做,游戏中了是什么都没显示,不过如果看看MUDOS所在目录下的日志文件mudos.log会发现其实代码还是有错:
Initializations complete.
Accepting connections on port 4000.
Accepting connections on port 4001.
No error handler for error: *master object: No function creator_file() defined!
program: /single/master.c, object: /single/master, file: /single/master.c:17
' connect' in ' single/master.c' (' single/master') /single/master.c:17
arguments were (4000)
locals were: 0
Initializing internal tables....
错误显示在MASTER对象中没有定义creator_file()函数,creator_file()函数和get_root_uid()函数一样,只在定义了PACKAGE_UIDS时才用到,这个函数的作用是定义一个新生成的对象的UID。函数原型是string creator_file(string filename);每次产生一个新的对象时这个函数都会在MASTER对象中被调用,新生产的对象的文件名就是这个函数的参数,如果在options.h中定义了AUTO_SETEUID,这个函数也会设置新对象的EUID。因为我们用new()函数生成了一个新的对象所以MASTER对象会呼叫creator_file()函数,而我们的master.c中没有定义这个函数,所以出错。
现在在master.c中加入如下代码:
string creator_file(string filename)
{
return ROOT_UID;
}
保存后重启MUDOS,连线吧,这次正常,而且日志中也没有任何错误了。
MASTER对象的connect()函数复制并返回了新的连线对象,事实上,connect()函数会自动调用另一个apply函数:logon(),这个函数的原型是:object logon();它的作用是初始化连接,这个函数可以不必存在,也就是我们可以不在程序中写这个函数,不过这个函数还是很有必要的。
我们在LOGIN_OB(/object/login.c)中写入如下代码:
object logon()
{
write("连线成功了。/n");
return this_object();
}
保存后,再次重启MUDOS,进入游戏,这次游戏中会显示一行字:连线成功了。write()函数的作用就对目前的玩家送出讯息.
以上的代码中只有write()和new()二个函数是efun,其它的都是applies,其中logon()是最后调用的,在游戏中,我们一般会写一个游戏登录程序,然后在logon()函数中调用这个程序让玩家进入游戏。
现在先不写自己的游戏代码,而是补充一个applies:
void create(),这个apply的作用是对象初始化,每当一个对象被成功载入时都会首先调用这个函数来初始化这个对象,这个函数可以不存在,但严格来说,所有对象都应该定义这个函数来进行初始化。
猜测:create()和java中的构造方法很像呀,如果一个对象中没有定义create()这个函数,那是不是有一个隐式的void create(){}存在呢?
现在我们的LIB中只有四个文件:/include/globals.h、/single/simul_efun.c、/single/master.c、/object/login.c。
我们在后三个对象代码中都加入create()函数吧:
//simul_efun.c
void create()
{
write("SIMUL_EFUN_OB loaded!/n");
}
//master.c
void create()
{
write("MASTER_OB loaded!/n");
}
//login.c
void create()
{
write("连线初始化……/n");
}
保存后重启MUDOS,连接游戏,哇——真奇怪!怎么会有这种事?游戏中只显示了“连线初始化……”,而且还显示了两次,而其它二个对象中的create函数中的内容却没显示出来,怎么搞的?如果断线重连,或新建一个连接,在游戏中“连线初始化……”又只显示一次了,这又是为什么?
打开mudos.log这个文件,你会看到以下内容:
]SIMUL_EFUN_OB loaded!
]MASTER_OB loaded!
Initializations complete.
游戏驱动程序MUDOS启动时首先载入的是simul_efun.c这个对象,所有对象都会自动包括/include/globals.h这个头文件,所以MUDOS载入simul_efun对象时也会首先加载globals.h这个文件,如果simul_efun.c和globals.h这二个文件都没有编辑时段错误,MUDOS会自动执行simul_efun对象的create()函数进行初始化,这时并没有玩家连线,write()的内容写在mudos.log文件中;然后MUDOS载入master.c对象,同时也会自动加载globals.h这个头文件,如果没有语法错误,MUDOS会自动执行master.c对象的create()函数进入初始化,同样write()仍然写在mudos.log文件中,然后MUDOS自动调用get_root_uid()和get_bb_uid()以取得根UID和骨干UID,至此MUDOS完成启动。当有玩家尝试连线时MUDOS会自动调用connect()函数取得新玩家初始对象,同时connect()函数会自动调用logon()函数完成连线初始化。connect()函数在这里是通过new()函数来复制logon()函数所在的连线对象(这时master对象会自动调用creator_file()函数为新的对象设定UID),因为new()函数是先载入原始对象,再复制对象,所以这里显示了二次“连线初始化……”,原始对象只载入一次,再次连线时只用复制对象,所以当你再次连线时“连线初始化……”就不会显示二次了。
如果我们把master对象中“login_ob = new (LOGIN_OB);”改成“login_ob = load_object(LOGIN_OB);”“连线初始化……”仅在第一次连接游戏时显示一次,再次连接时却一次也不显示了,这是因为load_object()函数和clone_object函数(new()函数)不同,这个函数仅仅载入原始对象而不复制对象,所以在第一次连线时会载入对象并调用create()函数初始化,以后再次调用load_object()函数时因为对象已被载入是不会再次初始化的,所以不会再调用create()函数了。不过把master对象中的new()函数改成load_object()函数是一个致命的错误,要知道不同的玩家是不同的对象,使用new()函数时每次都是复制一个对象,这样每个连接是不同的对象,而使用load_object()函数的结果是所有的连接都是同一个对象,这是不行滴。
另外:在master对象中除了上面几个必要的applies外,还有一些函数可能会用到。
string author_file (string file);
object compile_object( string pathname );
void crash( string crash_message, object command_giver, object current_object );
string domain_file( string file );
int valid_save_binary( string file );
int valid_seteuid( object obj, string euid );
…………
这些函数会在一定的情况要被MASTER_OB自动调用,如void crash()函数会在MUDOS崩溃时自动调用,int valid_save_binary()则是在需要保存二进制文件时被调用以判断是否保存。