2.简单的房间系统和简单的look指令,物件的move
3.指令权限还是没有。。。。
4.登录过程里依然没有密码验证,但是可以区分新老用户了,并且可以存取数据。
现在已经可以这样了:
欢迎来到测试MUD
请输入您的ID:akuma
读取存盘数据成功,开始进入游戏。
世界的中心 -
这里就是世界的中心。人来人往,车水马龙。
到处都是人,摩肩接踵的。。。。非常热闹。
这里有:
akuma
> test连线进入这个世界。
欢迎来到测试MUD
请输入您的ID:akuma
读取存盘数据成功,开始进入游戏。
世界的中心 -
这里就是世界的中心。人来人往,车水马龙。
到处都是人,摩肩接踵的。。。。非常热闹。
这里有:
akuma
> test连线进入这个世界。
目录结构如下:
.
|-- adm
| |-- daemons
| | |-- cmd_d.c
| | `-- login_d.c
| `-- obj
| |-- master.c
| `-- simul_efun.c
|-- cmds
| `-- usr
| |-- look.c
| `-- test.c
|-- d
| `-- wiz
| `-- center.c
|-- data
| `-- user
| |-- a
| | `-- akuma.o
| `-- t
| |-- test.o
| `-- test2.o
|-- include
| `-- globals.h
|-- log
|-- obj
| `-- user.c
`-- std
|-- body
| |-- move.c
| |-- room_dbase.c
| |-- user_dbase.c
| `-- user_save.c
`-- room.c
.
|-- adm
| |-- daemons
| | |-- cmd_d.c
| | `-- login_d.c
| `-- obj
| |-- master.c
| `-- simul_efun.c
|-- cmds
| `-- usr
| |-- look.c
| `-- test.c
|-- d
| `-- wiz
| `-- center.c
|-- data
| `-- user
| |-- a
| | `-- akuma.o
| `-- t
| |-- test.o
| `-- test2.o
|-- include
| `-- globals.h
|-- log
|-- obj
| `-- user.c
`-- std
|-- body
| |-- move.c
| |-- room_dbase.c
| |-- user_dbase.c
| `-- user_save.c
`-- room.c
0.4.1的目录树:
.
|-- adm
| |-- daemons
| | |-- cmd_d.c
| | `-- login_d.c
| `-- obj
| |-- master.c
| `-- simul_efun.c
|-- cmds
| `-- usr
| |-- look.c
| `-- test.c
|-- d
| `-- wiz
| `-- center.c
|-- data
|-- include
| `-- globals.h
|-- log
|-- obj
| |-- login.c
| `-- user.c
`-- std
|-- body
| |-- move.c
| |-- room_dbase.c
| |-- user_dbase.c
| `-- user_save.c
`-- room.c
.
|-- adm
| |-- daemons
| | |-- cmd_d.c
| | `-- login_d.c
| `-- obj
| |-- master.c
| `-- simul_efun.c
|-- cmds
| `-- usr
| |-- look.c
| `-- test.c
|-- d
| `-- wiz
| `-- center.c
|-- data
|-- include
| `-- globals.h
|-- log
|-- obj
| |-- login.c
| `-- user.c
`-- std
|-- body
| |-- move.c
| |-- room_dbase.c
| |-- user_dbase.c
| `-- user_save.c
`-- room.c
第四讲:较为完整的登录过程
我直接按照0.4.1的版本来讲。
重点1.存盘与读取数据
这个文件是这次新增的,/std/body/user_save.c
它提供了两个函数,save()和restore()。
具体内容可以读文件,这里只说两个关键:
a. mud中我们用的save()和restore()的核心是save_object()与restore_object(),可以参看具体的efun声明。
这两个函数是把object身上的“全局”数据(不包括static或者nosave),以类似sprintf的格式存成文件,或者相反:将符合格式的文件读出来并恢复成数据格式,赋予object。
值得关注的是另外两个efun,save_variable()和restore_variable(),实际上在使用save_object()和restore_object的时候也是在调用这两个函数的内容。
b. 存为什么和读什么文件?
这个是由具体的ob身上的query_save_file()获得的。
注意这里我们使用了query("id"),实际上,在真正的系统当中,如果考虑到安全因素(比如wiz可以call xxx->set("id")之类的),一般是通过euid来解决这个问题。
不过这样我们需要调整的地方太多,就暂时不做处理了,有兴趣的同学可以自行比对其他lib来修改。
重点2.dbase当中增加了tmp一族
如之前所说,我们希望有些时候ob身上的标记是临时的,即不被save()。
因此我们在user_dbase.c当中增加了一个新的mapping
static mapping tmp_dbase = ([]);
并且增加了三个对应的接口函数:query_tmp(),set_tmp(),delete_tmp();
到这里,我们给ob写标记的时候终于比较圆满了(记住:还差一个多重mapping的设计没有实现)
重点3.登录过程
先说明一下,我们这次终于把user_ob和login_ob(/obj/login.c)分开了。。。。
我们稍微整理一下,一次登录,最简单来说,应该处理哪些事情:
a.获取id输入,判断是否有这个用户(有:转到b;没有:转到g)
b.如果有,则载入login数据[密码保存在login_ob的存盘文件里],并获取密码输入,判断是否有存盘的密码相同[注意为了安全,我们应该使用加密存储的密码,范例就不处理这个了](相同:转到c,不同:转到f)
c.寻找是否有连线或者短线的同名用户(有:转到d,没有:转到e)
d.一系列的exec()取代,最终保证新的连线取代老的user_ob,并给予适当的描述【取代登录完成】,进入enter_world()过程
e.创建一个新的user_ob,并restore()玩家数据,进入enter_world()过程
f.(从b过来),密码比对失败,踢出,万事儿。。。
g.(从a过来,没有用户),进入新用户创建过程(获取密码,然后生成user_ob,进入enter_world()过程)
这个过程主要是在login_d.c当中实现。
有一个需要注意的是find_body()。这里之所以不用单纯的find_player(),是因为在某些特殊情况下,find_player()会失效,所以还要尝试一下children()。
重点3. ROOM
room其实就是一个容器(事实上所有的object都是容器)
room本身没有什么特别,我们将一下new(),load_object()和call_other()
a. new()创建出来的object必然是clonep,也即是形如obj/user#xxx这样带井号的object。
事实上,任何在内存中的物品,必然都会有一个不带井号的版本,也就是说,如果我们new()了某个file,那么他至少会存在一个file#xxx和file两个ob。为何呢?这是driver的一个机制。
对应的有clonep(),children()两个函数可以用。
大家看一般的mudlib,在item里经常会有一个set_default_object(__FILE__);
这个玩意儿就是给物品设置一个标记,在query的时候,如果“我”身上没有数据,就去default上去找。这个defalt就是我们说到的new()出来的那个不带#的obj了。
这个做法可以节省内存,但比较浪费时间。我不喜欢它。。。
b. load_object(),如果有物件就返回,否则就创建,这里返回和创建的都是不带#的obj。
c. call_other(),这个是执行某个物件或者文件名身上的函数,如果没有对应的物件存在,就尝试先创建。
所以:
call_other("file",“???”);
find_object("file");
的做法约等于load_object();
call_other()又约等于"fine"->xxx();
象user_ob,npc,item这些,我们往往希望有多个副本,因此我们总是用new()
但是象daemon,room这种,我们通常不希望有副本(比如说,到底是哪个ftpd在起作用?或者我为什么和他进入的不是同一个center?)你看,这种情况,如果不用本体物件,而是new,会产生很多困扰。所以我们这里用load_object()或者call_other。
这是一个大概的解释,有问题我们再继续讨论吧。。。。
我直接按照0.4.1的版本来讲。
重点1.存盘与读取数据
这个文件是这次新增的,/std/body/user_save.c
它提供了两个函数,save()和restore()。
具体内容可以读文件,这里只说两个关键:
a. mud中我们用的save()和restore()的核心是save_object()与restore_object(),可以参看具体的efun声明。
这两个函数是把object身上的“全局”数据(不包括static或者nosave),以类似sprintf的格式存成文件,或者相反:将符合格式的文件读出来并恢复成数据格式,赋予object。
值得关注的是另外两个efun,save_variable()和restore_variable(),实际上在使用save_object()和restore_object的时候也是在调用这两个函数的内容。
b. 存为什么和读什么文件?
这个是由具体的ob身上的query_save_file()获得的。
注意这里我们使用了query("id"),实际上,在真正的系统当中,如果考虑到安全因素(比如wiz可以call xxx->set("id")之类的),一般是通过euid来解决这个问题。
不过这样我们需要调整的地方太多,就暂时不做处理了,有兴趣的同学可以自行比对其他lib来修改。
重点2.dbase当中增加了tmp一族
如之前所说,我们希望有些时候ob身上的标记是临时的,即不被save()。
因此我们在user_dbase.c当中增加了一个新的mapping
static mapping tmp_dbase = ([]);
并且增加了三个对应的接口函数:query_tmp(),set_tmp(),delete_tmp();
到这里,我们给ob写标记的时候终于比较圆满了(记住:还差一个多重mapping的设计没有实现)
重点3.登录过程
先说明一下,我们这次终于把user_ob和login_ob(/obj/login.c)分开了。。。。
我们稍微整理一下,一次登录,最简单来说,应该处理哪些事情:
a.获取id输入,判断是否有这个用户(有:转到b;没有:转到g)
b.如果有,则载入login数据[密码保存在login_ob的存盘文件里],并获取密码输入,判断是否有存盘的密码相同[注意为了安全,我们应该使用加密存储的密码,范例就不处理这个了](相同:转到c,不同:转到f)
c.寻找是否有连线或者短线的同名用户(有:转到d,没有:转到e)
d.一系列的exec()取代,最终保证新的连线取代老的user_ob,并给予适当的描述【取代登录完成】,进入enter_world()过程
e.创建一个新的user_ob,并restore()玩家数据,进入enter_world()过程
f.(从b过来),密码比对失败,踢出,万事儿。。。
g.(从a过来,没有用户),进入新用户创建过程(获取密码,然后生成user_ob,进入enter_world()过程)
这个过程主要是在login_d.c当中实现。
有一个需要注意的是find_body()。这里之所以不用单纯的find_player(),是因为在某些特殊情况下,find_player()会失效,所以还要尝试一下children()。
重点3. ROOM
room其实就是一个容器(事实上所有的object都是容器)
room本身没有什么特别,我们将一下new(),load_object()和call_other()
a. new()创建出来的object必然是clonep,也即是形如obj/user#xxx这样带井号的object。
事实上,任何在内存中的物品,必然都会有一个不带井号的版本,也就是说,如果我们new()了某个file,那么他至少会存在一个file#xxx和file两个ob。为何呢?这是driver的一个机制。
对应的有clonep(),children()两个函数可以用。
大家看一般的mudlib,在item里经常会有一个set_default_object(__FILE__);
这个玩意儿就是给物品设置一个标记,在query的时候,如果“我”身上没有数据,就去default上去找。这个defalt就是我们说到的new()出来的那个不带#的obj了。
这个做法可以节省内存,但比较浪费时间。我不喜欢它。。。
b. load_object(),如果有物件就返回,否则就创建,这里返回和创建的都是不带#的obj。
c. call_other(),这个是执行某个物件或者文件名身上的函数,如果没有对应的物件存在,就尝试先创建。
所以:
call_other("file",“???”);
find_object("file");
的做法约等于load_object();
call_other()又约等于"fine"->xxx();
象user_ob,npc,item这些,我们往往希望有多个副本,因此我们总是用new()
但是象daemon,room这种,我们通常不希望有副本(比如说,到底是哪个ftpd在起作用?或者我为什么和他进入的不是同一个center?)你看,这种情况,如果不用本体物件,而是new,会产生很多困扰。所以我们这里用load_object()或者call_other。
这是一个大概的解释,有问题我们再继续讨论吧。。。。
增加了对房间的出口的粗糙的处理。
/cmds/usr/look.c里支持了对exits的显示。
增加了指令/cmds/usr/go.c,根据房间的exits可以走方向。
另外user_ob的process_input()又回来了,这次我们用它来解决global alias的问题(即用w west来代替go west)
ok,没有太多可以解释的。
依然没有指令权限,但是在执行指令的地方增加了一个对指令是否能正常编译的判定。所以现在可以正常的看到what?而不是报错了。
/adm/daemons/cmd_d.c的:
int do_cmd(object me,string verb,string arg)
{
object cmd;
cmd = load_object("/cmds/usr/"+verb);
if(objectp(cmd))
return cmd->main(me,arg);
else
return 0;
}
至此,我们有了一个可以看起来很像样子的lib了。他都可以走路了~~
接下来搞点啥?大家~
这是0.4.2的目录树:
.
|-- adm
| |-- daemons
| | |-- cmd_d.c
| | `-- login_d.c
| `-- obj
| |-- master.c
| `-- simul_efun.c
|-- cmds
| `-- usr
| |-- go.c
| |-- look.c
| `-- test.c
|-- d
| `-- wiz
| |-- center.c
| `-- westyard.c
|-- data
|-- include
| `-- globals.h
|-- log
|-- obj
| |-- login.c
| `-- user.c
`-- std
|-- body
| |-- move.c
| |-- room_dbase.c
| |-- user_dbase.c
| `-- user_save.c
`-- room.c
/cmds/usr/look.c里支持了对exits的显示。
增加了指令/cmds/usr/go.c,根据房间的exits可以走方向。
另外user_ob的process_input()又回来了,这次我们用它来解决global alias的问题(即用w west来代替go west)
ok,没有太多可以解释的。
依然没有指令权限,但是在执行指令的地方增加了一个对指令是否能正常编译的判定。所以现在可以正常的看到what?而不是报错了。
/adm/daemons/cmd_d.c的:
int do_cmd(object me,string verb,string arg)
{
object cmd;
cmd = load_object("/cmds/usr/"+verb);
if(objectp(cmd))
return cmd->main(me,arg);
else
return 0;
}
至此,我们有了一个可以看起来很像样子的lib了。他都可以走路了~~
接下来搞点啥?大家~
这是0.4.2的目录树:
.
|-- adm
| |-- daemons
| | |-- cmd_d.c
| | `-- login_d.c
| `-- obj
| |-- master.c
| `-- simul_efun.c
|-- cmds
| `-- usr
| |-- go.c
| |-- look.c
| `-- test.c
|-- d
| `-- wiz
| |-- center.c
| `-- westyard.c
|-- data
|-- include
| `-- globals.h
|-- log
|-- obj
| |-- login.c
| `-- user.c
`-- std
|-- body
| |-- move.c
| |-- room_dbase.c
| |-- user_dbase.c
| `-- user_save.c
`-- room.c