condition是利用系统的心跳来解决关于在一个不算太长的时间里,定时触发种现象的解决方法。在MUD中
,为了解决定时触发某种现象,一般有两种方法,一种是通过call_out()延时呼叫,另一种就是通过心跳
。在spock翻译的LPC及教材中曾对这两种方法进行了比较,好象是说:如果时间较长的话采用心跳比较好
,时间短的话采用call_out()。紧接着又说了句,其个人看不出这两者有什么必要的区别,反正稀里糊涂
的。不过,在实际运用中,两者都有不同的效果,大家看明白了本文之后,可以有自己的理解,并进行正
确的选择。
call_out(other_fun,t)这个外部函数就是起一种延时呼叫的作用,后面必须要加第一个参数,指定
延时呼叫的函数名,第二个参数表示延时几秒,再之后加上的参数可以作为呼叫的那个函数的参数。象这
一个也就是设定在t秒钟后,呼叫other_fun这个事先指定的函数,你可以在这个函数里设定好应该进行的
事情。比如,你在一个玩家进入监狱后,要做牢五分钟,如果一定要用call_out()进行的话,你可以设定
成:
call_out("out_jianyu",300,ob);
意思就是在300秒后呼叫out_jianyu()这个函数,ob作为这个玩家的变量传递过去,然后在这个函数
里进行将玩家从牢中放出来,告诉他做牢结束等等处理。但是,有的巫师看到这里,就会问了,如果五分
钟没到,这个玩家退出游戏了怎么办?对,如果这个玩家退出游戏,这个call_out()一到时间就会找不到
这个操作对象,如果程序写得不好就容易出错,即使是不会出错,那个玩家再次进入游戏后也就无法继续
刚才的延时过程,也就不能够出牢。于是,对于要跨起离线前后的象做牢这类的事,大多都是采用
condition。
condition的处理方法,就是在开始的时候在玩家身上设定一定的点数的记号,这些记号会通过save
()保存进玩家的档案中。然后通过系统的心跳,每一次心跳就执行一次这个固定的condition的函数,函
数每被执行一次就减一点记号,直至这个记号为0后,就可以触发某种效果。比如做牢,就在进牢时设定
一定点数,每一次心跳减一点,减为0时,将玩家放出。由于这个记号在玩家身上,因此,在玩家离线时
不会发生到有关函数对这个玩家的操作。
在ES系列MUD中。一个完整的condition系统包括三个部分:
一、首先就是调用或者说是触发它的程序,也就是/inherit/char/char.c这个
文件,这是所有玩家与NPC都共同继承的文件,在里面的heart_beat()函数,就是每一次心跳时调用执行
的函数。里面有这么几句:
if( tick-- ) return;
else tick = 5 + random(10);
cnd_flag = update_condition();
解释:tick是这个文件里的一个全局变量,假设它只要>1,那么tick--就不会等于0,那么tick值就会减
1,并且立即return;中止这个函数向下执行。假想如此这样经过几次减1之后,终有一次tick--就会为0,
那么这时,程序就不会中止而是执行下一句的else。这时,tick被重新赋值为5+random(10)。并执行
update_condition()函数,update_condition()是一个什么函数呢?在char.c里怎么也找不到。
那么我们再回头看看char.c这个文件的开头,就会看到,这个文件已经继承了/feature/condition.c
文件,update_condition()这个函数正是在这个文件里面。所以下面我们就开看这个文件。
附:由于大多数MUD里的心跳是每两秒调一次,5+random(10)是5至14次,因此可以看出每一个
condition被调用的时间是平均19秒。知道了这此,你才能更加有数地设定一些毒的发作时间,一些做牢
的时间长短了。
二、下面就是主程序,feature目录下的condition.c文件。
程序详解:
#include "condition.h"//继承一些宏定义
mapping conditions;//定义一个映射集
/*更新函数 update_condition()
这个函数首先要检查玩家身上的每一个condition是否有效,如果出现了无效的condition,它会记录
进/log/condition.err这个文件里,经常性地检查一下这个文件,可以发现并排除相当多的错误,因为一
旦有一个错误,会在每一次的心跳中经常性地发生。然后它会按照condition的名字,也就是str这个变量
去/kungfu/condition(某些MUDLIB下的路径是/daemon/condition)目录下去 寻找同名的.c文件进行执行
。*/
nomask int update_condition()
{
mixed *cnd, err;
int i, flag, update_flag;
object cnd_d;
if( !mapp(conditions) || !(i=sizeof(conditions)) ) return 0;
//判断玩家有无condition的映射,没有就中止,毕竟不会每人都会有
cnd = keys(conditions);
//从这个映射中把关键词取出组成一个数组,实际上就是不同condition的名字
update_flag = 0;//初始化这个变量
while(i--)
//如果有1个以上的condition,就会一个个地循环执行
{
cnd_d = find_object(CONDITION_D(cnd[i]));
//到放condition的目录下寻找这个文件名,这个目录路径有宏定义的文件指定,一般在XKX风格里大多是
kungfu/condition/下,xyj等放在/daemons/condition/下
if( !cnd_d )//如果没有的话,再尝试
{
err = catch(call_other(CONDITION_D(cnd[i]), "???"));//强制检验
cnd_d = find_object(CONDITION_D(cnd[i]));//再次寻找
if( err || !cnd_d )//如果强制检验与再次寻找中仍找不到,表示不存在这个condition
{
log_file("condition.err",sprintf("Failed to load condition daemon %s,
removed from %O\nError: %s\n",CONDITION_D(cnd[i]), this_object(), err) );
//记录下来
map_delete(conditions, cnd[i]);
//删除玩家身上的这个condition
continue;//继续循环下一个
}
}
flag = call_other(cnd_d, "update_condition", this_object(), conditions[cnd[i]]);
//这个就开始执行这个conditon的设定文件里的update_condition()函数了,flag就是返回值,这个要看
下面的具体设定文件的详解
if( !( flag & CND_CONTINUE ) )
map_delete(conditions, cnd[i]);
//返回值为0就表示这个conditon完毕了,就删掉
update_flag |= flag;
}
if( !sizeof(conditions) ) conditions = 0;
//检查一个也没有了,就清零
return update_flag;
}
/*改变大小函数 apply_codition(cnd,info),通过这个函数将玩家身上名叫的cnd的condition的值设为
info,info可以为0,所以可以得用这个函数对玩家身上的condition进行增减。 */
nomask void apply_condition(string cnd, mixed info)
{
if( !mapp(conditions) )
conditions = ([ cnd : info ]);
//如果没有的话,就添加上,以cnd为键名,info为内容值
else
conditions[cnd] = info;
//有的话,直接改变内容值
}
/*取值函数 query_codition(cnd)通过这个函数,可以调出某个玩家身上名叫cnd的这种condition的值有
多少。*/
nomask mixed query_condition(string cnd)
{
if( !mapp(conditions)||undefinedp(conditions[cnd]) )
return 0;
//如果没有conditions或者没有这个cnd的condition,就返回为0
return conditions[cnd];//否则返回具体值
}
/*清除函数 clear_condition()这个主要是在/feature/damage.c里的die()函数里调用,意思是一旦死
亡,死者就会被清除所有的comdition。*/
nomask void clear_condition()
{
conditions = 0;
}
//END
三、下面就是上面说的与cnd同名.c文件--具体设定文件。一般每一种condition种类都要对应一个文件
。里面只有一个update_condition()函数,通过/feature/condition.c这个程序来调用它的这个函数,可
以添加一些效果或信息,比如,中毒的就会减精减气。但最主要是就每调用一次,将键名为这个cnd的内
容值减少1点或多点。然后到了设定的点数,一般是到了0之后,会出现一些特殊的现象。象做牢的到了0
就会被放出来,等等。
下面看一个例子:少林的做牢的condition的详解:
//kungfu/condition/bonze_jial.c
#include <ansi.h>
#include <login.h>
/*参照前面调用这个函数的/feature/condition.c文件,就可以发现,调用它的时候会传递一个玩家与他
的名为bonze_jia1的这个condition的值*/
int update_condition(object me, int duration)
{
if (duration < 1)
//如果这个点数参数小于1,就表示做牢时间够了
{
me->move("/d/shaolin/guangchang1");
//从牢房里直接移到少林大门口
message("vision","只听乒地一声,你吓了一跳,定睛一看,\n"
"原来是一个昏昏沉沉的家伙从大门里被扔了出来!\n",environment(me), me);
tell_object(me, HIY "只觉一阵腾云驾雾般,你昏昏沉沉地被扔出了少林寺!\n" NOR);
//出一些信息
me->set("startroom", START_ROOM);
return 0;
}
//如果不小于1,则设定减去一点,返回1,下次还会再执行
me->apply_condition("bonze_jail", duration - 1);
return 1;
}
到这里,condition的三大部分介绍完了,我们就以这少林寺的做牢为例,看一看condition执行调用
的流程。
程序大约是在松林里被僧兵抓住后,进了戒律院,这个房间文件会通过apply_condition()这个函数
在玩家身上被加上了名为bonze_jail的condition。于是玩家身上会多了这样的一个映射:
condition([({"bonze_jial":35},.....)])
那么通过前面的文件,就会看到,由于我们玩家是继承了char.c文件,每一个心跳中,就会检查tick
,如果tick为1时,那么就会开始调用update_conditon()函数了。
这个函数首先取出玩家身上conditions映射中的关键词组成一个数组cnd:
cnd = ({"bonze_jial",......});
首先发现了bonze_jia1,于是开始到/kungfu/condition/目录下寻找名为bonze_jial.c的文件,也就
是上面帖出的第三部分的文件,如果没有找到这个文件就会被记录进/log/condition.err文件里,有的话
,开始传递时这个玩家与这个conditon的值,开始执行bonze_jial.c里的update_condition()函数 。第
一次执行时duration也就是35,只要它大于或等1,就会被减出一点,返回值是1。会在下次tick为1的心
跳是再次呼叫。这样经过若干次调用后,duration已经等于0了,那么也就是durtion < 1,于是me被move
到少林的广场,出现信息:只听乒地一声,你......me又被覆盖了startroom然后返回值为0。玩家身上就
会被删除这个condition。由于返回是0,那么/feature/condition.c文件里的update_condition()函数就
会删除玩家身上的这个bonze_jia1的内容。
除了做牢之外,通过这样的随机调用,可以实现象毒这样不定时发作的特殊效果,每调用一次就让玩
家减一些精呀气之类的,却出现一些恐怖的中毒信息症状之类的。其实,你还以设计一些特殊的毒,不但
在发作的过程中伤害人体。而且要求必须在这段时间找到解毒方法或解药,否则,一旦到了最后一两点还
没有彻底解毒,这个人就OVER死翘翘。呵呵,看起来有点恐怖呀,实际上我觉得这样才是最符合实际情况
的呢!
采用condition处理的好处就在于,这个属性是加在玩家身上的,通过在游戏中的心跳进行调用。如
果玩家离了游戏就不会调用得到。一旦玩家进入游戏中又会开始继续执行。这比call_out()一旦主呼叫者
或呼叫中的参数失也就无法继续执行的缺点要灵活得多。此外,它由心跳调用,不管玩家在做什么事情,
它都可以即时地主动反馈情况。
而如果你无需即时反映变化,比如说去领工资,只需要规定玩家在一定时间的间隔之内不能领两次工
资的话。可以在第一次领的时候,在玩家身上记下领的时间,第二次再去领时,将当前时间与记录时间进
行上减,看间隔够不够就行。象这样子就很简单,也几乎没有任何系统负责,比采用condition林节约得
多。而如果你需要有时间一到就立即通知玩家去领下一次工资的话。那就必须要采用conditon。
它的缺点也显而易见,放在每一次的心跳里呼叫。如果一个MUD系统里的生物与玩家身上的condition
过多过长的话,系统的负担也是不小的。
最后谈一谈有关的BUG。许多新巫师没有正确或完全理解condition的用法,就开始大范围地使用。在
一些不应该用的地方也使用condition。举例,在某一个可以使玩家得到金钱等东西地方,设定任何玩家
都必须间隔在线一定的时间才能去拿一次。如果这个通过condition来实现。就有可能利用死亡后清除所
有condition的特点,让一个玩家反复去死亡再立即重新去拿钱拿东西。象这种情况,你或者要修改死亡
后会清除所有condition,或者就不能采用它来做那些拿钱等东西的效果。
其二,象上面所讲的少林监狱的condition文件中,就隐藏着一个BUG。因为在XKX的文件里,少林的
监狱并非是一定要等时间到了才会被放出来,可以通过贿赂狱卒,走五行洞先出来。而这样的话,一旦这
个conditon到了最后的时候,不论这个玩家在哪里,都会被一下子Move到少林广场,然后出现你被扔出监
狱等的字样。仔细想一想:如果MUD有不让你随便带东西或正常出来的地方,那么你就可以通过这个BUG跑
到这种地方,把那些不能带出的东西放在身上,专心等时间一到,就会象乘飞机一样,一下子从那个地方
飞到少林的广场。(例如谁与争锋里演武场)
解决方法很简单,只要在if(duration < 1)下面再加一个条件:
if(environment(me)->query("short")=="监狱")
才会move玩家出现信息。否则直接reuturn 0;就解决了。
最后,举一个毒的例子进行详解作为结束吧:
// /kungfu/condition/ice_poison.c
#include <ansi.h>
#include <condition.h>
inherit F_CLEAN_UP;
int update_condition(object me, int duration)
{
if( duration < 1 ) return 0;
if( !living(me) )
//如果对象不是清醒着,出的信息
{
message("vision", me->name() + "浑身颤抖,痛苦地哼了一声。\n", environment(me), me);
}
else//否则就清醒着
{
tell_object(me, HIB "忽然一阵奇寒从丹田升起,沁入四肢百骸,你中的寒冰绵掌发作了!\n"
NOR );
message("vision", me->name() + "的身子突然晃了两晃,牙关格格地响了起来。
\n",environment(me), me);
}
me->receive_wound("qi",15 + random(10));
me->receive_wound("jing", 10);
//伤一定的精与气
me->apply_condition("ice_poison", duration - 1);
//这个值减一点
if ( (int)me->query_temp("powerup") )
{
me->add_temp("apply/attack", -(int)(me->query_skill("force")/3));
me->add_temp("apply/dodge", -(int)(me->query_skill("force")/3));
//降低他的攻击力与躲避力
me->delete_temp("powerup");
//结束他的powerup
}
if( duration < 1 ) return 0;
//如果到了0,就返回,什么也不做,表示毒发结束,好了
return CND_CONTINUE;
}
尊重作者 转载请注明出处52mud.com