第二讲:指令系统
我思考了一下,决定暂时跳过登录部分,先说指令。毕竟有了指令之后,就可以在线更新和重启,不用不停的kill driver进程了。
在开始之前,我们先思考一个问题,指令是干什么用的,以及我们希望如何管理指令系统。
我们常讲一个词叫I/O,也就是所谓的输入输出。
从最基本的意义上说,指令就是玩家敲的东西,他希望通过敲一些东西,告诉mud“我要做什么”。抛开网络层和mudos底层不讲,我们可以把
指令看作是一个字符串(事实上他的确是一个字符串,并且是以\n或者\n\r结尾的字符串,也就是说,很不幸,我们的一telnet为基础的mud不
支持指令或者指令参数里带有回车。。。)
然后,mudlib根据一定的规则,找到一段适当的程序去解释玩家的指令,并且给予适当的操作和反馈。
所以我们第一步可以把指令系统归结为如下的需求:
1.想办法获得玩家的输入
--注意,这个规则一定要是规范的,这个规范我们可以随意制订,比如说用100到999的三位数字表示特定含义的指令;当然了,理论上这虽然
没问题,但是考虑到我们的zmud或者telnet,玩家的指令是手敲的,你让他记这么多数字不太现实。
于是我还是决定继承传统mud的方法,用“指令英文+空格+参数表”的方式来设计这个规范。
2.我们定一个规则,让玩家输入的指令可以在mudlib里找到那段我们希望他来执行的代码去执行之。
如前所述,我们已经假定了指令规则是 “指令英文+空格+参数表”,那么指令的英文名字本身就是一个很好的代号,我们只要通过一定的编码
,赋予不同代号对应的程序段就好了。
======================分割线=================================================
本次讲解的内容分成两个部分,第一部分是简单实现指令,第二部分里我们再进一步细化它。
第一部分代码见附件 newlib.0.2.1.tar.gz
目录结构如下:
.
|-- adm
| |-- daemons
| | `-- cmd_d.c
| `-- obj
| |-- master.c
| `-- simul_efun.c
|-- cmds
| `-- usr
| `-- test.c
|-- include
| `-- globals.h
|-- log
`-- obj
`-- user.c
重点1.我们首先来解决“获得玩家输入”的部分:
请看这个版本的/obj/user.c。大家还记得吗?上次当我们写第一个echo server的时候,留了个尾巴。当时我们是通过process_input()来“处
理”指令。
这一回,我们希望可以把指令系统建立起来,因此我把process_input()的处理暂时注释掉了。
如何获得玩家的输入?
这里我继续使用一般mudlib(es2 类)的做法,通过一个“全局的”add_action()来实现。
请看代码:
void create()
{
setup();
}
int setup()
{
log_file("user",sprintf("%O setup at %s\n",this_object(),ctime(time())));
enable_commands();
add_action("cmd_hook","",1);
return 1;
}
正常来说,我们不应该这么早进入这个部分,不过由于暂时还没有登录认证体系,就先这么凑合。这里只是用来表述流程:
一个user_ob被master.c的connect()创建时(记得吗?这个时候他就是一个真正的连线物件了),apply函数create()被呼叫,于是他调用了
setup()。
我们可以看到,setup()只干了两件事:
a. enable_commands();
b. add_action("cmd_hook","",1);
add_action()大家在制作谜题的时候会经常遇到,就是给某个物件增加一个“临时的”指令,并且指定用于解释这个指令的函数名字。所有“
接触到”这个物体的物体,都可以使用这个指令。
我们这里的用法比较奇怪一些,他大概的意思就是“不管你输入什么,我都认为你是我这个add_action定义的指令”。
我一直觉得这个写法挺古怪的,不过为了兼容和尊重传统,我们继续这么搞吧。
enable_commands()是给物件设置一个标记,好让add_action()有效。
继续看cmd_hook()这个执行函数。
int cmd_hook(string arg)
{
string verb = query_verb();
return CMD_D->do_cmd(this_object(),verb,arg);
}
query_verb()这个efun是用来获取玩家的“最近一个指令”,注意是指令,不包括参数表。参数表是由add_action给定的解释函数(这里就是
cmd_hook以参数形式传进来的)
然后,我们通过一个daemons来设法去执行这个指令(也就是verb)
return CMD_D->do_cmd(this_object(),verb,arg);
CMD_D一会再看。这里我们要注意的有一个要点,请看从cmd_hook被定义为int型函数开始,我们一直在return(CMD_D里也是一路有return的)
。
为什么?这是mudos的一个机制,针对玩家的输入,我们一路处理下来,如果有为true的return。那么系统会认为这个指令被正确的执行了。否
则他就会想办法给出报错。
从我们之前看到的">what?"或者一般mud里定义的“>什么?”,一直到使用notify_fail来自定义的错误返回等等。
重点2.规范的管理和找到指令对应的处理函数:
在前边我们已经获执行指令取到了所有必要的信息,包括指令名字,参数表(还有是“谁”的指令),接下来我们就要开始想办法执行他了。
请看/adm/daemons/cmd_d.c(有些lib里可能叫做commandd.c之类的)
目前我们只有这么一句
int do_cmd(object me,string verb,string arg)
{
return ("/cmds/usr/"+verb)->main(me,arg);
}
这也就是刚才在cmd_hook()里调用的函数体了。
这里不用多解释,我们就是通过"/cmds/usr/"+verb拼出了需要使用的指令的文件名字,然后把“谁”和“什么”传到他的main()函数里了。
其实在lpc里,main()并不是一个特定的函数,指令里用这个函数名作为入口,我猜是当年某个习惯c的人随手定的规则。不过为了讲解的作用
,我们还是继承下来。
我们特意创建了/cmds/usr/test.c这样一个指令,大家可以自己看看。
ok,现在我们的指令流程已经初步创建起来了,如果你现在启动我们0.2.1版的lib,连接上去,敲:
test xxxxx
就会获得如下的反馈:
in cmd test: arg=xxxxx
这说明指令test已经正确的被执行了。
兴奋的我们准备再敲点别的。。比如asdfasdf asdfsdf这样子。。。
嗯。。。。没有反应。
如果你这个时候去看/log,就会发现多了一个error_handler目录。很不幸,一般来说,看到这个就表示我们的lib有“runtime error”(运行
时断错误,也就是说,程序可以被编译,没有语法错误,但是在运行中出了错)。
这说明我们的程序缺乏起码的容错性。
这也就是我们在 lib.0.2.2.tar.gz当中需要解决的内容。
=====================分割线==================================================
有点小忙。。。
稍后我把第二讲的下半部分补全。需要解决三个问题:
1.鲁棒性,无论如何我们不希望总出现error_handler。也就是说,无论玩家怎么乱输入,我们都应该可以处理,而不是任由它报错。。。
2.便于管理,我们希望找到一个方法,让指令的处理文件有序的呆在一个地方,并且可以方便且正确的被程序找到。
3.安全性,即权限,看本文的wiz居多,我们总不希望玩家可以随便执行巫师指令吧。。。那么我们就要想个办法,让适合的人能执行适合的指
令,而不是相反。