|  | 
 
| 转自mudbuilder.com,akuma原创,akuma是北大侠客行老牌巫师柳残阳 
 第二讲:指令系统
 我思考了一下,决定暂时跳过登录部分,先说指令。毕竟有了指令之后,就可以在线更新和重启,不用不停的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居多,我们总不希望玩家可以随便执行巫师指令吧。。。那么我们就要想个办法,让适合的人能执行适合的指令,而不是相反。
 请等待第二讲·下~~谢谢。
 | 
 
x本帖子中包含更多资源您需要 登录 才可以下载或查看,没有帐号?注册  |