vast 发表于 2004-6-19 14:57:09

NPC的结构(zz)

[ NPC的结构 ]


制作复杂的NPC之前,我们回过头来看看NPC的结构,只有了解了结构,
才能自由的操控手里的NPC。

我们首先从NPC入手,
我们会发现,首先需要NPC继承,那么我们找出来看看。

在globals.h中 我们找到了继承的定义文件
#define NPC                     "/std/char/npc"

好的 来看看这个文件的内容:

#include
//角色继承 我们等一下在来看这个继承
inherit F_CLEAN_UP;   //这个是个典型的继承,如果你打算在一段时间后,清除不给访问的npc 那么请加上这个。

object carry_object(string file)
这个函数是用来让npc带上东西。
这个函数返回的是object
所以我们可以这么写:
carry_object(__DIR__"obj/monk_cloth")->wear();
事实上,这个等于:
object ob = carry_object(__DIR__"obj/monk_cloth");
ob->wear();
显然,后一种写法太累赘了。


object add_money(string type, int amount)
这个是为了方便添加money而制作的,
事实上:
add_money("silver",10);
等于:
carry_object("/obj/moeny/silver")->set_amount(10);
这里的type 必须是 "/obj/money"下面存在的钱的文件名。
如果你修改了 "/obj/money"   那么这个add_money()函数也需要做修改。

int accept_fight(object who)
这个函数根据人物设定的性格来对玩家所做出的fight指令响应
这个函数 决定NPC 的 attitude参数
一般有效的是: friendly aggressive killer heroism
当然,你可以在这里定制自己喜欢的性格类型,那么在设计npc的时候
设定   set("attitude", "friendly") 这类参数就要做响应变化了。

int return_home(object home)
这个函数用来让离开房间的npc回到原来的地方。如果有跟随player性质的
npc,你可以在这里来决定什么样子的npc必须回去,比如waiter什么的
有些者不一定需要,比如双儿,我觉得跟着走了,就不要回去了。当然,这
也要和ROOM继承中的reset()配合,因为这个函数会在那里被调用。


int intellgent_behavor()
这个是fy3加的函数,用来对付TASK系统导致的废物问题。
事实上,处理方式不单单如此。

int random_move()
随机移动,很常见的函数,这个函数一般如下设置:
set("chat_msg", ({
(: random_move :)
}));
这个函数一般在非战斗状态被调用。如果这个函数只被用来设置在
chat()中,很多判断条件都是重复的。不过,显然不一定这么设置。



//chat() 这是我想重点说的
chat()是NPC的重要部分,一般NPC的自言自语,常做的动作,战斗
使用的perform等等都会在这里使用。而且,这个函数是被心跳调用的,
所以一不小心,就会导致NPC失去心跳。如果太繁复,就意味着NPC的心
跳会很繁复。这显然是不妥当的。

int chat()
{
string *msg,*emotes;
int chance, rnd;
object *inv;
string action,target;
//为了防止没有空间的npc(只保留在内存中,就是这个现象)
//出现message_vision()错误 必须要判断有没有存在空间
if( !environment() ) return 0;

//显然 昏迷的NPC是不会说话的,如果想说话,哦,
//定制一个chat()来替代标准的吧。
if( query_temp("is_unconcious") ) return 0;

//这个fy3的特点,来捡垃圾的
if(query("intellgent")) intellgent_behavor();


//这个函数用来让npc做emote   是比较繁复动作,可以考虑去掉
if(!random(150) && !is_fighting() && !is_ghost() ){
emotes = EMOTE_D->query_all_emote();
inv = all_inventory(environment());
action = emotes;
target = inv->query("id");
(!random(3) )? command(action) : command(action +" "+target);
}

//来看看 我们是使用chance还是chance_combat
if( !chance = (int)query(is_fighting()? "chat_chance_combat": "chat_chance") )
return 0;

//好的 按照概率来调用设定的参数
if ( arrayp(msg = query(is_fighting()? "chat_msg_combat": "chat_msg"))) {
if( random(100) < chance ) {
rnd = random(sizeof(msg));
if( stringp(msg) )
say(msg);
else if( functionp(msg) )
return evaluate(msg);
}
return 1;
}
}


下面这些函数是npc施展cast exert perform conjure等等等等动作的
如果保险起见,可以直接使用 ccommand("perform XXX")等方式代替
但是需要明白的事,这些动作不一定只用在 chat_msg_combat中
所以,存在或者是否合理可以自己考虑。比如我认为我就只有在战斗中才使
用这些,基本上都让chat()调用,哦,我当然选择去掉。

void cast_spell(string spell)
int exert_function(string func)
int perform_action(string action)


fy3系统的MUD可能要注意武器的问题,因为即使没有武器,NPC也可以按照
这些函数,使出特技。比如用刀施展剑的perform。想想可爱的fy3蝙蝠岛的
东海白衣人,拿着武器施展空手道,问题就在于此,别惊讶。嘻嘻。


所谓NPC继承,也就是说上面这些函数只有NPC设计的时候才可以用。
如果是一个玩家
比如:

object me = this_player();
我是当前玩家
me->carry_object(__DIR__"obj/monk_cloth");
嗯,这是不对的。呵呵


inherit CHARACTER;   现在我们来看看角色继承。
因为玩家的躯体user.c 也继承了角色继承,所以这里面的函数玩家
也是可以使用到的。 要注意的是,如果只有对玩家有效的东西,可以
放在user.c中,而只对npc有效的函数什么的,尽量放在NPC继承。
如果大家都要用到,而且是给活着的角色用的,那就放这里吧。

我们来看看角色继承:
同样的,很容易在globals中找到这个继承的文件定义:
#define CHARACTER               "/std/char"

我们看看内容:
// char.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
这些是预定义,一般都和继承的使用有关。

inherit F_ATTRIBUTE;
inherit F_ACTION;
inherit F_ALIAS;
inherit F_PAWN;
inherit F_APPRENTICE;
inherit F_ATTACK;
inherit F_COMMAND;
inherit F_CONDITION;
inherit F_DAMAGE;
inherit F_DBASE;
inherit F_EDIT;
inherit F_MESSAGE;
inherit F_MORE;
inherit F_MOVE;
inherit F_NAME;
inherit F_SKILL;
inherit F_TEAM;
这些是常用继承 每个MUD不一定相同。我们到时候再来仔细看看

static int tick;   
这个标记是用来对付heal_up的,为了降低系统负担,每次心跳
不一定都要heal_up,所以设置了这个标记。

void create()
{
seteuid(0);
这个是为了uid的设置 因为玩家需要固定的,唯一的uid
}


int is_character() { return 1; }
这个是用来让外界知道这个物件是什么。
这是一个很通用有效的设计方式。比如尸体,我们使用int is_corpse()函数来决定
容器,我们可以使用 int is_container() { return 1; }来决定。
当然,这种设定一定要全面保持一致,如果你在一块石头上设定了这个函数,会导致
石头可以和人fight,哦,那就不妥当了。

void setup()
{
seteuid(getuid(this_object()));
set_heart_beat(1);
tick = 5 + random(10);
    enable_player();
CHAR_D->setup_char( this_object() );
}
setup()是用来生成角色属性的,属性的决定,我们可在这里看到
是用 CHAR_D->setup_char( this_object() ); 来决定的
这个函数设定uid 和启动心跳 同时允许开始使用指令。
enable_player() 是定义在 F_COMMAND继承中的 用来让动作有效

//下面的就是心跳 嘻嘻 有两个人对我说玩家的心跳比npc复杂。其实从
//继承上来说,player比npc少了个chat()函数。
void heart_beat()
{
int wimpy_ratio, cnd_flag;
mapping my;
object ob;
string savemyass,last_attack;
//心跳处理 一定要注意算法 这里使用 my 只调用query_entire_dbase()一次
//下面的处理就直接操作了 这个是F_DBASE继承的定义
my = query_entire_dbase();

//心跳的一个重要作用是判断死亡和昏迷的发生
//die() unconcious() 是定义在 F_DAMAGE 继承中的
//remove_all_enemy() 这个函数是定义在 F_ATTACK 继承中的
//这里是处理死亡的
if( my["eff_kee"] < 0 || my["eff_sen"] < 0 || my["eff_gin"] < 0) {
if (stringp(last_attack = my["unconcious_action"]))
SKILL_D(last_attack)->last_attack(this_object());
remove_all_enemy();
die();
return;
}

//这里是处理昏迷的
if( my["kee"] < 0 || my["sen"] < 0 || my["gin"] < 0) {
if (stringp(last_attack = my["unconcious_action"]))
SKILL_D(last_attack)->last_attack(this_object());
remove_all_enemy();
if( my["kee"] < -10 * my["dur"]   || my["sen"] < -10 * my["dur"] || my["gin"] < -10 * my["dur"])
die();
else unconcious();
return;
}

//心跳的一个重要作用,就是来处理fight
//is_busy() continue_action()   定义在 F_ACTION中的
//is_fighting() attack()   定义在F_ATTACK 中
if( is_busy() ) {
continue_action();
return;
} else {
if( is_fighting()
&&intp(wimpy_ratio = (int)my["env"]["wimpy"])
&&wimpy_ratio > 0
&&(my["kee"] * 100 / my["max_kee"] <= wimpy_ratio
||my["sen"] * 100 / my["max_sen"] <= wimpy_ratio
||my["gin"] * 100 / my["max_gin"] <= wimpy_ratio) )
{
if(stringp(savemyass = (string)my["env"]["savemyass"]))
command(savemyass);
else
GO_CMD->do_flee(this_object());
}
//上面的是处理逃跑的
attack();//调用攻击 这是一个fight的起点
}

//让NPC使用chat()函数
if( !userp(this_object()) && !this_object()->query("be_ghost")) {
this_object()->chat();
//chat()可能会摧毁npc 所以加上判断
if( !this_object() ) return;
}

//tick用来决定 多少次心跳来调用一次heal_up
if( tick--   ) return;
else tick = 5 + random(10);

//更新人物的状态 现在很大程度上 condition 被认为是毒 事实上可以做很多事情
//但是 如果真的大量使用了状态 可能会导致心跳的负担加重 这也是不合理的
//所以 不用状态的话 就不要使用状态
cnd_flag = update_condition();

//这里的判断很重要,以免失去心跳 而如果心跳已经无效 比如非战斗的npc什么的
//心跳毫无意思 所以就关闭npc的心跳 用来降低系统负担
// 一般的 常规npc 不包括战斗的 有状态的 等等等等 都要关闭心跳
//help_up   是定义在 F_DAMAGE继承中的

if( ((cnd_flag & CND_NO_HEAL_UP) || !heal_up())
&&!is_fighting()
&&!interactive(this_object())) {
if( environment() ) {
ob = first_inventory(environment());
while(ob && !interactive(ob))
ob = next_inventory(ob);
}
if( !ob ) set_heart_beat(0);
}

//不是互动玩家 就可以结束了 下面将是计算在线时间的 所以断线的要排除
if( !interactive(this_object()) ) return;

//很久以来 一直使用在线时间来计算人的年龄 事实上 你可以做出任何改变

//update_age()和 user_dump()都是 user.c中的定义
//user.c就是常说的玩家物件 USER_OB
this_object()->update_age();

if(query_idle(this_object()) > IDLE_TIMEOUT)
this_object()->user_dump(DUMP_IDLE);
}


//这个当初估计是用来让巫师隐身有效出现的 不过我只喜欢ghost部分
int visible(object ob)

//方便call来执行指令 简直就是force_me()
int ccommand(string com)
{
returncommand(com);
}


上面说到人物的属性是根据
CHAR_D->setup_char( this_object() );
来决定的 那么我们来看看到底是怎么设计的。
这是globals.h中对角色守护的定义
#define CHAR_D                   "/adm/daemons/chard"

来看看这个核心吧
#include
这个include是用来定义文件的 大家看一下就知道了
void create() { seteuid(getuid()); }
void setup_char(object ob)
{
      string race;
      mapping my;
      
      my = ob->query_entire_dbase();
      
      if( undefinedp(my["race"]) || !stringp(my["race"]) ) {
                race = "人类";
                my["race"] = "人类";
      }
      //这里出现了三个函数 用来构架三个种类
      //因为对create构架的不同 所以这里使用了三个不同的文件
      switch(race) {
                case "赛亚人":
                case "人类":
                        HUMAN_RACE->setup_human(ob);
                        break;
                case "妖魔":
                        MONSTER_RACE->setup_monster(ob);
                        break;
                case "野兽":
                        BEAST_RACE->setup_beast(ob);
                        break;
                default:
                HUMAN_RACE->setup_human(ob);
                        break;
      }
      //这里 max_gin 这类必须已经在 setup_human中产生 如果没有 那么会导致错误

      if( undefinedp(my["gin"]) ) my["gin"] = my["max_gin"];
      if( undefinedp(my["kee"]) ) my["kee"] = my["max_kee"];
      if( undefinedp(my["sen"]) ) my["sen"] = my["max_sen"];

      if( undefinedp(my["eff_gin"]) ) my["eff_gin"] = my["max_gin"];
      if( undefinedp(my["eff_kee"]) ) my["eff_kee"] = my["max_kee"];
      if( undefinedp(my["eff_sen"]) ) my["eff_sen"] = my["max_sen"];

      //人物的可载重 这里要注意的对于天赋的设置 类似以前xuejun前辈说的int负
      //我认为不妥 才智负 结果会导致gin负 而导致玩家死亡
      //如果str为零 那么载重就会有问题 一般fy3这里 fight是和负重有关系的
      //直接结果就是导致 score fight出现 除零错
      
      //所以 setup_human这类函数处理的时候 一定要注意数值的判断
      //如果不想在那里面限制 那么在调用的时候 就要加判断 特别是除零这类
      
      if( !ob->query_max_encumbrance() )
                ob->set_max_encumbrance( my["str"] * 20000 );

      //激活人物动作 这个函数 是定义在 F_ATTACK继承中的
      ob->reset_action();
}

//下面这个函数 是用来让角色死亡的时候调用的 系统会在die()里面调用这个函数
varargs object make_corpse(object victim, object killer)


我们来看看human的构架:
// human.c
#define BASE_WEIGHT 40000
//定义重量
inherit F_DBASE;   //dbase继承 这是最基本的保存玩家数据要用到的
mapping *combat_action   //这个定义类似技能制作 用来让query_action调用以便出现随机的动作

void create()
{
//这个函数是用来定义到躯体上的
seteuid(getuid());
set("unit", "位");
set("gender", "男性");
set("can_speak", 1);   //可以简单的区别动物和人
set("attitude", "peaceful");
set("limbs", ({//人体部位 需要这么多吗? 而且目前而言 毫无意义
"头部","颈部","胸口","後心","左肩","右肩","左臂",
"右臂","左手","右手","腰间","小腹","左腿","右腿",
"左脚","右脚", "左肋", "右肋", "前胸", "后背", "眉心",
                "后腰", "后颈", "左胯", "右胯", "后脑", "左眼", "右眼",
"左颊", "右颊"
}) );
注意的是 这些定义是在human中的 当玩家进入游戏后,这些属性可以通过query来查询
但是这些属性是不会被save下来的

}

void setup_human(object ob)
{
mapping my;
my = ob->query_entire_dbase();
ob->set("default_actions", (: call_other, __FILE__, "query_action" :));
/*
这个设置是很典型的动作设置方式 目前基本上都采取这种方式
比如:
ob->set("default_attack", (: call_other, __FILE__, "query_action" :));
ob->set("default_parry", (: call_other, __FILE__, "query_parry", ob :));
ob->set("default_dodge", (: call_other, __FILE__, "query_dodge" :));
然后 基本技能就没多大意思了 因为现在的基本技能都是用来为特殊技能做铺垫的
是在没多大意思 如果用来代表这类技能的熟练度 才有用点 那么xo那类MUD的技能类型
不就是多余的了?嗯 真不好说 各有所好 反正现在改的都是面目全非,里面有多少东西
不是重复多余的,那就不知道了。
*/

。 中间内容略

/*
这中间就是MUD对人物属性的处理,如果你想设置人物的属性,那么就看看这里
这里的属性你都可以在设计NPC的时候设定,如果这里没有的,那基本上就没用了。
比如fy3使用的12个属性 在es等类型中 就没有那么多了
所以设置npc的天赋的时候 要按照自身的MUD设置来制作。

*/
//下面这个函数 是定义在dbase继承中的 设置uid和加上no_clean_up标记
ob->set_default_object(__FILE__);

//设置重量
if( !ob->query_weight() ) ob->set_weight(BASE_WEIGHT + (my["str"] - 10)* 200);
}

mapping query_action()
{
return combat_action;
}

到这里,我们来回顾一下:

human-->chard-->继承-->char-->npc/user

所以如果更新了一个继承 就要update char,update npc/user,然后是具体的npc或者玩家

可见 这类更新是很麻烦的。 这也是当初安尼将combatd分离出来的原因。

现在 让我们把目光放到继承上来
在前面 我指出了一些函数是在哪个继承中被调用的 那么现在 我们就来看这些继承

inherit F_ATTRIBUTE;属性
inherit F_ACTION;动作
inherit F_ALIAS;输入
inherit F_APPRENTICE;拜师
inherit F_ATTACK;攻击
inherit F_COMMAND;命令
inherit F_CONDITION;状态
inherit F_DAMAGE;伤害
inherit F_DBASE;数据
inherit F_EDIT;编辑
inherit F_MESSAGE;讯息
inherit F_MORE;显示
inherit F_MOVE;移动
inherit F_NAME;命名
inherit F_SKILL;技能
inherit F_TEAM;组队

一: -------------------------------------------------------------------------
首先是属性 F_ATTRIBUTE 继承
这里面主要是一些后天属性的设置函数 以便使用类似 me->query_str()这样的函数

二: -------------------------------------------------------------------------
F_ACTION是动作的核心
varargs void start_busy(mixed new_busy, mixed new_interrupt)
这个用来产生busy 这个函数太常用了 new_busy可以是数值或函数
当然 没有 start_busy(-1)这种效果

void remove_busy()
用来彻底清除busy

nomask mixed query_busy()
这个是用来查询当前的busy效果 比如还有 4秒的busy 就会返回4

nomask int is_busy()
这是是用来判断是否处于忙的状态

void continue_action()
这个是用来继续的 有了这个 busy才会减少

void interrupt_me(object who, string how)
这个是用来中断busy的

int start_call_out(function fun, int delay)
这个是为了让player能自身call_out,以免以为物件的
消失后者文件的更新导致出错。最常用的地方是状态类pfm


三: -------------------------------------------------------------------------
F_ALIAS继承 这是处理输入的一个函数
alias大家应该明白的吧
这里面最重要的函数就是 process_input()
set_alias()是用来设置mud alias的 不过zmud的alias也有好处
mud alias的指令会被当作输入处理
而zmud的alias会先被zmud处理完以后 输出到server端

一般的 string process_input(string str) 函数里面
都是放些机器人的处理手段,然后是记忆指令,不过到了zmud使用的
时代,基本没用了。

if(this_object()->query_temp("disable_inputs")) {
if(stringp(input_msg = this_object()->query_temp("disable_inputs")))
return input_msg;
else
return "什麽?n";
}
使用这样的函数 可以让玩家在昏迷的时候 指令输入无效 当然放在command继承中处理也是一样的

四: -------------------------------------------------------------------------
F_APPRENTICE 这个继承用来处理拜师的 扩展一下 就是处理人际关系
在这类继承中写代码的时候 如果设计到数据调用的 最好先一次query()出来
然后直接使用main方式调用,以免eval_cost溢出

int is_apprentice_of(object ob)
这个函数用来判断当前角色 是不是ob的学徒
判断的方式很多 简单的可以只使用master id
复杂的可以是 id name 判断 还可以加上userp判断

int is_couple_of(object ob)
这个函数用来判断时候是夫妻

int is_children_of(object ob)
这个函数用来判断时候是家庭关系

void assign_apprentice(string title, int privs)
这个是用来传位的

void create_family(string family_name, int generation, string title)
这个是开帮用的 npc用来设定所属的门派

int recruit_apprentice(object ob)
这个时候徒弟以后的一些处理

int born_from(object mother)
这个是生孩子的一些处理

int born_from(object mother)
{
object father;
mapping family,child,*children;
mapping fa,mo;

if( this_object()->is_children_of( mother ) )return 0;

father = mother->query_temp("husband");

family = allocate_mapping(8);
child = allocate_mapping(4);

fa = father->query_entire_dbase();
mo = mother->query_entire_dbase();

//这里的real_id和real_name是为了对付改名什么的操作以后的问题
//player的判断是为了防止玩家自杀或者与npc重复的问题

family["f_id"] = fa["real_id"];
family["f_name"] = fa["real_name"];
family["f_player"] = userp(father);
family["m_id"] = mo["real_id"];
family["m_name"] = mo["real_name"];
family["m_player"] = userp(mother);
family["gifts"] = mo["marry"]["gifts"];

set("parents", family);

child["player"] = userp(this_object());
child["id"] = this_object()->query("real_id");
child["name"] = this_object()->query("real_name");

children = mo["child"];
if (!arrayp(children))
children = ({ child });
else if( member_array(child,children)==-1)
children += ({ child });
mother->set("child",children);

children = fa["child"];
if (!arrayp(children))
children = ({ child });
else if( member_array(child,children)==-1)
children += ({ child });
father->set("child",children);
//不设计太多的关系 比如类似门派的层次什么的
return 1;
}



五: -------------------------------------------------------------------------
F_ATTACK继承是关系到战斗的主要继承。
这里面主要是关于enemy和killer的问题
处于记仇的缘故 这里对于enemy使用了object类型
而对于killer则使用了string类型 这样的好处就是可以被保留下来
而enemy则可以被有效的清除
存在的问题是 string不足以反映玩家和npc的区别,也很难
反映到具体某个人。

object *query_enemy() { return enemy; }
string *query_killer() { return killer; }
这两个是查询用的 确定现在的敌人或者仇人是哪些。

varargs int is_fighting(object ob)
varargs int is_killing(string id)
这两个是判断状态的 是否在战斗或厮杀 需要注意的是
is_killing()的参数 id是个string类型的
所以不能 is_killing(object target)来判断
时候和target厮杀
应该使用 is_killing(target->query("id"))

void fight_ob(object ob)
void kill_ob(object ob)
这两个是触发战斗和厮杀的 kill_ob可以很快的激活npc
而fight_ob有时候会有延时。

void clean_up_enemy()
这个是重新整理敌人

object select_opponent()
选择一个敌人

int remove_enemy(object ob)
int remove_killer(object ob)
去掉一个敌人或者仇人

void remove_all_enemy()
void remove_all_killer()
去掉所有的敌人或者仇人


下面这个函数 是用来处理战斗的动作的 直接关系到战斗时候的招式描写
这个函数主要是为COMBAT_D服务的 因为COMBAT_D处理战斗的时候
招式是从   me->query("actions") 中取出来的 而且是当作mapping结构
处理的 所以修改reset_action()的时候要保持前后一致。

最简单的方式 就是call_other到一个技能文件 由技能文件中的一个函数
返回一个mapping 这样由于query("actions")是一个函数,所以调用的时候
每次都会取一次,结果就产生了随机的动作。
换个方式 我们把几个动作直接赋值给 actions 然后在COMBAT_D中调用是
再加上随机函数,估计也差不多。比如:
mapping *action   = ({ one ,two, three,   });
set("actions",action);
在COMBAT_D中这样写
actions = query("actions");
action = actions;
设计的时候 主要是考虑调用的效率和方便 前后保持一样的效果很重要

这个reset_action()是一般es模式使用的格式 xkx sj这类有个prepare判断
原理一样的。
void reset_action()
{
object ob;
string type, skill;

if( ob = query_temp("weapon") )
type = ob->query("skill_type");
else
type = "unarmed";

if( stringp(skill = query_skill_mapped(type)) ) {
// If using a mapped skill, call the skill daemon.
set("actions", (: call_other, SKILL_D(skill), "query_action", this_object() , ob :) );
} else {
// Else, let weapon handle it.
if( ob ) set("actions", ob->query("actions",1) );
else set("actions", query("default_actions",1) );
}
}

int attack()
这个简单的函数 是用来产生战斗的 因为里面直接调用了
COMBAT_D->fight(this_object(), opponent);
这个函数 这导致了两个人开始fight

下面这个init就是npc中重要的一个环节,主要来处理很多相遇的情况
所以在设计npc的时候 如果有了定制的init,里面一般都要加上 ::init()
除非你不希望下面函数中的情况出现。
void init()
{
object ob;
string vendetta_mark;

// We check these conditions here prior to handle auto fights. Although
// most of these conditions are checked again in COMBAT_D's auto_fight()
// function, these check reduces lots of possible failure in the call_out
// launched by auto_fight() and saves some overhead.
if(is_fighting()
||!living(this_object())
||!(ob = this_player())
||environment(ob)!=environment()
||!living(ob)
||ob->query("linkdead") )
return;

// Now start check the auto fight cases.
if( userp(ob) && is_killing(ob->query("id")) ){
COMBAT_D->auto_fight(this_object(), ob, "hatred");
return;
} else if( userp(ob) && (string)query("attitude")=="aggressive" ) {
COMBAT_D->auto_fight(this_object(), ob, "aggressive");
return;
} else if( random((int)query("bellicosity") / 40) > (int) this_object()->query_cps() ) {
COMBAT_D->auto_fight(this_object(), ob, "berserk");
return;
}
}


六 -------------------------------------------------------------------------
F_COMMAND 这个继承是深入处理指令的
可以说是最要命的继承
//command.c
将命令目录设置为静态,是为了防止一些安全问题
static string *path;
string find_command(string verb)
{
return (string)COMMAND_D->find_command(verb, path);
}
这个函数通过COMMAND_D核心来搜索命令


下面这个函数是一个加在玩家身上的add_action,用来触动所有的命令
move 动作 聊天等等
Optimization is needed
是原来的就写上的叮嘱 嗯 要很注意呀
nomask int command_hook(string arg)
{
string verb, file;
object me = this_object();
verb = query_verb();
//首先判断时候是move相关动作
//这里要注意if判断以后的分号是空的 其实没有其他事情直接return 1就可以了
if( !arg
&&(environment() && stringp(environment()->query("exits/" + verb)))
&&stringp(file = find_command("go"))
&&call_other(file, "main", this_object(), verb))
;

else if( stringp(file = find_command(verb))
&& call_other(file, "main", this_object(), arg))
;

else if( EMOTE_D->do_emote( this_object(), verb, arg ) )
;

else if( CHANNEL_D->do_channel( this_object(), verb, arg ) )
;
else return 0;

return 1;
}

nomask void set_path(string *p)
string *query_path() { return path; }
mixed *query_commands() { return commands(); }
//上面三个是这是路径和查询的

int force_me(string cmd)
//相比ccommand 多了权限和input判断

//这个是最主要的 前面已经见到过这个函数 就在char继承中
//这个用来激活动作和分配指令目录 相关的东西定义在 /include/command.h 中
//注意 这里的权限要和 安全核心 securityd 给与的权限保持一致
//如果你把(admin)改成了(CEO) 嗯 记得修改所有相应的地方
nomask void enable_player()
{
if(query_temp("disable_inputs"))
delete_temp("disable_inputs");
else
{
if( stringp(query("id")) ) set_living_name(query("id"));
else set_living_name(query("name"));

enable_commands();
add_action("command_hook", "", 1);
if( !userp(this_object()) )
set_path(NPC_PATH);
else
switch( wizhood(this_object()) ) {
case "(admin)":
set_path(ADM_PATH);
enable_wizard();
break;
case "(arch)":
set_path(ARC_PATH);
enable_wizard();
break;
case "(wizard)":
set_path(WIZ_PATH);
enable_wizard();
break;
case "(apprentice)":
set_path(APR_PATH);
enable_wizard();
break;
case "(immortal)":
set_path(IMM_PATH);
break;
default:
set_path(PLR_PATH);
}
}
}

//这个是让玩家失去指令能力 嗯
//自从有了 disable_type这个简单的设置 我已经不大用了
nomask void disable_player(string type)
{
      if( geteuid(previous_object())!=ROOT_UID
      &&       previous_object()!=this_object()) return;
      set("disable_type", type);
      if(!userp(this_object()))
      disable_commands();
      else
      set_temp("disable_inputs",1);
}

回想起玩家的指令,突然想到,好像没有可以单独应用到玩家的add_action
任何的add_action似乎总是要加在玩家可接触的环境中,因为进入游戏后,
就没有这种可设计的动作了,除了这个唯一的 command_hook,这里,可以
尝试在command_hook这个最基础的add_action中加入定制。

比如:
string cmds_file;
string cmds_verb;
string cmds_func;
void add_command(string file,string func,string verb);
{
cmds_file = file;
cmds_verb = verb;
cmds_func = func;
}

nomask int command_hook(string arg)
{
string verb, file;
object me = this_object();
verb = query_verb();

if( !arg
&&(environment() && stringp(environment()->query("exits/" + verb)))
&&stringp(file = find_command("go"))
&&call_other(file, "main", this_object(), verb))
;

else if( stringp(file = find_command(verb))
&& call_other(file, "main", this_object(), arg))
;

else if( EMOTE_D->do_emote( this_object(), verb, arg ) )
;

else if( CHANNEL_D->do_channel( this_object(), verb, arg ) )
;
else if( verb== cmds_verb
&& call_other( cmds_file, cmds_func, arg )
;
else return 0;
return 1;
}

那么在一个文件中可以这样做:

增加一个动作:
object me = this_player();
me->add_command(__FILE__,"do_cmds","pull");

这个动作的函数:
int do_cmds(string arg)
{
//这里设计类似一般的add_action调用函数//
}
嗯,这样子的话,玩家执行 pull 这个动作,在最后都会调用到这个函数
也许,你可以加上call_out延时来清除这些变量,或者就记录在query_entire_temp_dbase()
中,以便退出是能被清除。
有时候真的很不喜欢为了一个临时性的动作而给玩家一个物件。


七:-------------------------------------------------------------------------
F_CONDITION继承 是用来处理状态的 早期的应用就是中毒 现在可能多了很多
这个继承类似于skill继承 是独立与dbase继承的
mapping conditions 状态的结构是mapping

nomask int update_condition()
这个是用来更新状态的 就在char继承中被调用
这个函数先根据玩家的状态 搜索到存在的状态文件
然后利用
call_other(cnd_d, "update_condition", this_object(), conditions);
这个函数 来调用处理这个状态
在写状态的时候 一定要注意算法 因为这是在心跳里面的。能执行到状态文件
的 update_condition这个函数,已经是化了很长的时间了。

nomask void apply_condition(string cnd, mixed info)
这个函数用来增加一个状态,类似dbase的add和skill的skill_imnprove

nomask mixed query_condition(string cnd)
这个函数是用来察看当前状态的属性

mapping query_all_condition()
察看所有的状态

nomask clear_condition()
清除所有的状态

我发现最近将通缉作为一种状态,嗯,如果是这样,就要加个函数,
以便方便的来清除一种状态,而不是盲目的clear_condition()
比如:

int clear_a_condition(string cnd)
{
if( mapp(conditions) ) map_delete(conditions, cnd);
return undefinedp(conditions);
}

八:-------------------------------------------------------------------------
F_DAMAGE 这个继承主要处理伤害 死亡 昏迷 以及恢复等等
是一个相当关键的一个继承 这里有一个定制的属性 是独立于dbase的
就是ghost   一般可以使用既定的函数来变化这个属性

int ghost = 0;
int is_ghost() { return ghost; } //查询是否是ghost
void be_ghost(int flag)//这个是设置ghost的函数 0的话就是不是鬼 1就是鬼
{
ghost = flag;
}

//使用这个函数来处理伤害的好处是 能确定last_damage_from的归宿
//同时能保证恢复(心跳)的产生 尤其对npc更加有效果 注意这里是有三个参数的
// 类型   数值   对象 ,我发现一般而言 对象经常被忽略
//这里可以为force什么的提供一个类型 以便处理负正 溢出等等的问题
varargs int receive_damage(string type, int damage, object who)
{
int val;

if( damage < 0 ) error("F_DAMAGE: 伤害值为负值。n");
if( type!="gin" && type!="kee" && type!="sen" )
error("F_DAMAGE: 伤害种类错误( 只能是 gin, kee, sen 其中之一 )。n");

if( objectp(who) ) set_temp("last_damage_from", who);
val = (int)query(type) - damage;

set(type, val);
set_heart_beat(1);
return damage;
}
//效果同上 不过这个是导致受伤的
varargs int receive_wound(string type, int damage, object who)
{
int val;

if( damage < 0 ) error("F_DAMAGE: 伤害值为负值。n");
if( type!="gin" && type!="kee" && type!="sen" )
error("F_DAMAGE: 伤害种类错误( 只能是 gin, kee, sen 其中之一 )。n");

if( objectp(who) ) set_temp("last_damage_from", who);
val = (int)query("eff_" + type) - damage;

set("eff_" + type, val);
if( (int)query(type) > val ) set(type, val);
set_heart_beat(1);
return damage;
}

//下面两个函数是恢复用的 事实上 这两个函数就是为了方便而设置 未必真的需要利用到
//所以喜欢控制force mana等等属性的 也可以使用这种方式来处理
int receive_heal(string type, int heal)
{
int val;

if( heal < 0 ) error("F_DAMAGE: 恢复值为负值。n");
if( type!="gin" && type!="kee" && type!="sen" )
error("F_DAMAGE: 恢复种类错误( 只能是 gin, kee, sen 其中之一 )。n");

val = (int)query(type) + heal;

if( val > (int)query("eff_" + type) ) set(type, (int)query("eff_" + type));
else set( type, val );

return heal;
}

int receive_curing(string type, int heal)
{
int max, val;

if( heal < 0 ) error("F_DAMAGE: 恢复值为负值。n");
if( type!="gin" && type!="kee" && type!="sen" )
error("F_DAMAGE: 恢复种类错误( 只能是 gin, kee, sen 其中之一 )。n");

val = (int)query("eff_" + type);
max = (int)query("max_" + type);

if( val + heal > max ) {
set("eff_" + type, max);
return max - val;
} else {
set( "eff_" + type, val + heal);
return heal;
}
}


这个是导致昏迷的函数 一般昏迷前 需要处理一些事物
比如胜负 敌人 以及状态
void unconcious()
{
object defeater;

if( !living(this_object()) ) return;
if( wizardp(this_object()) && query("env/immortal") ) return;
if (this_object()->query_temp("is_unconcious"))return;

//这里是处理那些胜负问题 和 去处所有的敌人
if( objectp(defeater = query_temp("last_damage_from")) )
COMBAT_D->winner_reward(defeater, this_object());
COMBAT_D->loser_penalty(defeater, this_object());
if( ! this_object()->query("possessed"))
//remove_all_emeny不会去处所有的仇人 所以killing会继续
this_object()->remove_all_enemy();

message("system", HIR "n你的眼前一黑,接著什麽也不知道了....nn" NOR,
this_object());

//我直接使用 block_msg/all 和 disable_inputs参数来控制
this_object()->set_temp("disable_inputs","你处于昏迷中,什么也做不了!!n");
      this_object()->set_temp("block_msg/all",1);
this_object()->set_temp("is_unconcious",1);
      this_object()->set("disable_type"," <昏迷不醒>");
      
//设置0,0,0是为了防止一些bug
set("gin", 0);
set("kee", 0);
set("sen", 0);
//显示昏迷的描述 和设置醒过来的时间
COMBAT_D->announce(this_object(), "unconcious");
call_out("revive", random(50 - (int) this_object()->query("con")) + 10);
}

苏醒的函数 这里对昏迷的玩家 不显示 你醒过来了 这样的提醒语
我认为这是一种玩家潜在的意识 没必要提醒 这样子更类似真正的属性
因为你一旦醒过来 外界的讯息就会映入你的眼睛
varargs void revive(int quiet)
{
//避免重复调用
remove_call_out("revive");
//醒过来了以后 就要离开活的生物 以免出现待在某人身上这种情况
//类似fy3那种不能get char的MUD 这个可有可无
while( environment()->is_character() )
this_object()->move(environment(environment()));

      //苏醒以后的处理
      if( (int) this_object()->query("gin") < 0 ) this_object()->set("gin",0);
      if( (int) this_object()->query("kee") < 0 ) this_object()->set("kee",0);
      if( (int) this_object()->query("sen") < 0 ) this_object()->set("sen",0);
      
      //周围的人当然能看到你醒过来了
if( !quiet && ! this_object()->is_ghost() )
COMBAT_D->announce(this_object(), "revive");

//去处标记
this_object()->delete_temp("disable_inputs");
this_object()->delete("disable_type");
this_object()->delete_temp("block_msg/all");
this_object()->delete_temp("is_unconcious");
      if( !living(this_object()) ) this_object()->enable_player();
}

这个是sjpl的函数 因为sjpl允许ghost在一定时间内在阳间走动。
void real_death()
{
//不是鬼 那就算了
if (!is_ghost())return;
if (!userp(this_object())){
message_vision("$N渐渐的虚化,消失了...n",this_object());
destruct(this_object());
return;
}
//如果是player 就让他移动到阴间
message_vision("突然一阵烟雾袭来,将$N卷走了...n",this_object());
message("system", HIR "n烟雾中出现牛头马面,二话不说,将枷锁镣铐往你头上一套....n" NOR,this_object());
message("system", HIR "n你的眼前一黑,接著什麽也不知道了....nn" NOR,this_object());
this_object()->move(DEATH_ROOM);
//如果room中有对死亡的人的处理函数 调用他
DEATH_ROOM->start_death(this_object());
}


die()函数是玩家最害怕的函数了吧 事实上 die()不可怕
可怕的是COMBAT_D守护中降低玩家属性的函数。嘻嘻
等一下,介绍一下死亡的过程,抛开数据损失不谈,其实就是一次
地狱观光旅行而已。
我这里使用了deadmsg来显示不同的死亡描述 也可以使用物件
身体上标记来赋值 比如 query_temp("deadmsg") 这类方式
varargs void die(string deadmsg)
{
object corpse, killer, *inv;
int i;

//这是我加的函数 如果昏迷中死亡 那就立刻醒过来吧
revive(1);

if( wizardp(this_object()) && query("env/immortal") ) return;

//清除所有的状态和仇人 嗯 状态可以不再这里清除 因为比武场这类
//死亡 是不应该拿来清除状态的 尤其是中毒

this_object()->clear_condition();
this_object()->remove_all_killer();
//下面是一个典型的阵列调用 LPC没有each 但是下面这个写法可以很方便的
//达到这个目的。
all_inventory(environment())->remove_killer(this_object());

this_object()->dismiss_team();

//如果已经是鬼了,就立刻送往阴间
if( this_object()->is_ghost() ){

inv = all_inventory(this_object());
                inv -= ({ 0 });
//sizeof(inv)是固定的 使用while显然更干净
                i = sizeof(inv);
                while(i--) inv->move(environment());
set("gin", 1);set("eff_gin", 1);
set("kee", 1);set("eff_kee", 1);
set("sen", 1);set("eff_sen", 1);
real_death();
return;
}
//显示死亡的语句 一般都是 某人死了。 这个显示可以在COMBAT_D中修改
COMBAT_D->announce(this_object(), "dead");
//这里就是为什么需要使用 receive_damage()这类标准内含函数的原因之一
//如果都只是简单的加减 会丢失killer
if( objectp(killer = query_temp("last_damage_from")) ) {
set_temp("my_killer", killer->query("id"));
set_temp("my_killer_name", killer->query("name"));
COMBAT_D->killer_reward(killer, this_object());
}

//killer_reward 和 victim_penalty() 是用来处理胜利和死亡的
COMBAT_D->victim_penalty(this_object(), killer, deadmsg);

//人死了当然要有尸体
if( objectp(corpse = CHAR_D->make_corpse(this_object(), killer)) )
corpse->move(environment());

//玩家是允许转世投胎的 NPC因为有room的reset()函数复生,并且es的目的,是不希望
//世界是累积的,就是说npc或者迷题总是在一个水平上的。
//如果 你不想这么做 当然可以让npc也到地狱去一趟,然后和玩家一样,把他复活过来。
if( userp(this_object()) ) {
set("gin", 1);set("eff_gin", 1);
set("kee", 1);set("eff_kee", 1);
set("sen", 1);set("eff_sen", 1);
ghost = 1;
this_object()->save();
remove_call_out("real_death");
//允许ghost在一定时间内在阳间走动 当然了 不能真让ghost到处乱逛
call_out("real_death", 10 + (int) this_object()->query_con());
}else destruct(this_object());
}

//这个是用来复活的 其实这个简单的函数 等同于:
// call me->be_ghost(0)
// full
void reincarnate()
{
ghost = 0;
set("eff_gin", query("max_gin"));
set("eff_kee", query("max_kee"));
set("eff_sen", query("max_sen"));
}

//下面两个是决定人物食物和水的上限的 你可以按照自己的理念来修改 没人规定一定要除以200
int max_food_capacity() { return query_weight() / 200; }

int max_water_capacity() { return query_weight() / 200; }

下面这个是很重要的函数 事实上呢 就是恢复 这个函数只有在心跳存在的时候才会被激活
由于一般的npc没有心跳 所以也就不存在恢复
如果一个npc接受了战斗 或者受伤什么的 必须给与心跳 以便能让他恢复。
下面的算法基本上是按照fy3模式写的 如果真的需要做大的改变 那么就是按需而设
需要注意的是 由于这个是在心跳中被调用的 所以要注意算法的简洁合理。
int heal_up()
{
int update_flag, i;
mapping my;

if( this_object()->is_fighting() ) return -1;

if( userp(this_object()) && this_object()->is_ghost()) return 0;

if (intp(i = this_object()->query_temp("is_sleeping")))i++;
else i = 1;
update_flag = 0;

my = query_entire_dbase();

if( my["water"] > 0 && my["water"] < 5 && userp(this_object()) ) { write(HIB"n你感到渴了!!n"NOR); }

if( my["food"] > 0 && my["food"] < 5 && userp(this_object()) ) { write(HIR"n你感到饿了!!n"NOR); }


if( my["water"] > 0 ) { my["water"] -= 1; update_flag++; }
if( my["food"] > 0 ) { my["food"] -= 1; update_flag++; }

if( my["water"] < 1 && userp(this_object()) )   return update_flag;
      if( my["food"] < 1 && userp(this_object()) )   return update_flag;

my["gin"] += my["spi"] * i   + my["atman"] / 10;
if( my["gin"] >= my["eff_gin"] ) {
my["gin"] = my["eff_gin"];
if( my["eff_gin"] < my["max_gin"] ) { my["eff_gin"] += i ; update_flag++; }
} else update_flag++;

my["kee"] += my["con"] * i + my["force"] / 10;
if( my["kee"] >= my["eff_kee"] ) {
my["kee"] = my["eff_kee"];
if( my["eff_kee"] < my["max_kee"] ){ my["eff_kee"] += i; update_flag++; }
} else update_flag++;

my["sen"] += my["int"] * i + my["mana"] / 10;
if( my["sen"] >= my["eff_sen"] ) {
my["sen"] = my["eff_sen"];
if( my["eff_sen"] < my["max_sen"] ){ my["eff_sen"] += i; update_flag++; }
} else update_flag++;

if( my["max_atman"] && my["atman"] < my["max_atman"] ) {
my["atman"] += (int)this_object()->query_skill("magic", 1)/ 10;
if( my["atman"] > my["max_atman"] ) my["atman"] = my["max_atman"];
update_flag++;
}

if( my["max_force"] && my["force"] < my["max_force"] ) {
my["force"] += (int)this_object()->query_skill("force", 1)/ 10;
if( my["force"] > my["max_force"] ) my["force"] = my["max_force"];
update_flag++;
}

if( my["max_mana"] && my["mana"] < my["max_mana"] ) {
my["mana"] += (int)this_object()->query_skill("spells", 1)/ 10;
if( my["mana"] > my["max_mana"] ) my["mana"] = my["max_mana"];
update_flag++;
}
return update_flag;
}


让我们回顾一下F_DAMAGE 发现死亡的历程是如此的透明。
当你死的时候,其实你什么也没有发生,只不过你身上的一个标记ghost由原来
的 1 变成了 0,于是,is_ghost()返回1,结果就是导致char继承中的visible()
函数对于一般玩家,返回1,反映到look指令中,别人就看不到了。然后系统把你
移动到所谓的阴间,哦,来那么一套,然后把你的ghost标记设置0,你又回来了。
地狱观光团门票 1 kill........


九:-------------------------------------------------------------------------
F_DBASE 这个继承在本人的room设计中已经比较详细的提到了 这里就不想在详细的说了
你可以按照自己的意愿设计出一个格式,只要你愿意。事实上,改革不一定要全盘否定,
那将是毫无疑义的劳动。


十:-------------------------------------------------------------------------
F_EDIT   这个继承也没什么好说的,因为没什么需要大做改动的,基本上,如果你需要输入,
一般都依赖这个继承。为了防止输入的字符溢出,你可以在里面加上一个判断。如果你对
edit的那种格式提示方式不满意,那就仔细的改改吧。


十一:-------------------------------------------------------------------------
F_MESSAGE 这个继承事实上也没什么好说的,原来就已经做的不错了。
// message.c
#define MAX_MSG_BUFFER 500   
这个定义是玩家在输入的时候,所保留的外界讯息,这是一个很好的设计。
这让我们想到sleep的时候、昏迷的时候所该做的东西,保留外界的讯息。
或者只是闲聊的讯息。

下面这个函数,就是处理玩家接受到的讯息
void receive_message(string msgclass, string msg)
{
string subclass, *ch;

//这是为非互动玩家准备的,如果在char继承中加上这个函数,那么断线的人
//就可以自定义自己的讯息,想想OICQ中离开的讯息效果。。。。
if( !interactive(this_object()) ) {
this_object()->relay_message(msgclass, msg);
return;
}

//下面这个if包,里面的内容含义很简单,按照分类显示讯息给不同的环境玩家
if( sscanf(msgclass, "%s:%s", subclass, msgclass)==2 ) {
switch(subclass) {
case "channel":
if( !pointerp(ch = query("channels"))
||member_array(msgclass, ch)==-1 )
return;
break;
case "outdoor":
if( !environment() || !environment()->query("outdoors") )
return;
break;
default:
error("Message: Invalid Subclass " + subclass + ".n");
}
}

//缺省定义就是完全屏蔽掉
if( query_temp("block_msg/all") || query_temp("block_msg/" + msgclass) )
return;

//这里是很有意思的东西,事实上,类似昏迷,睡觉,不一定要disable_player()
//也不一定要block_msg/all
//只要把讯息往这里一保留,就可以了。 事实上,我们不需要做的那么绝情,是吧。
//方法非常简单,在if中加上对sleep这类的判断就可以了
if( in_input(this_object()) || in_edit(this_object()) ) {
if( sizeof(msg_buffer) < MAX_MSG_BUFFER )
msg_buffer += ({ msg });
} else
receive( msg );
}

void write_prompt()
这个函数是玩家输入讯息完毕后的反馈符,一般都是" >"
你可以使用各种方式来做这个,不过如果屏幕前面老出现一行长长的字符
我是会感到繁的。

void receive_snoop(string msg)
这个函数处理snoop讯息,一般没什么用,虽然为了方便巫师管理,
需要这些,但是我正因为玩家的隐私问题,而考虑这些东西存在的必要性。
不知道其他巫师怎么想的,我实在非常讨厌snoop别人,觉得很不道德,
哪怕他是在利用bug,哪怕他是来捣乱的。。。。
这里包括email的输入等等,玩MUD类似浏览网页,但是网页的注册还会强调
一下保护隐私,而对于国内的MUD,似乎正在强调获得玩家的真实资料,而
有从来没有提供保护的手段。这在国外,至少,我认为国外的MUD输入资料
的时候,可以不输入真实的email,真实的name等等等等

虽然这对于很多玩家已经无所谓的态度,但是一想到,处在一个被人看透的世界。
真是毛骨悚然。


十二:-------------------------------------------------------------------------
F_MORE 这个继承也没什么好说的
不过里面start_more有个explode函数 在这之前,最好判断时候存在msg
如:
if(!msg || strlen(msg)<1) // no messages.
return;
其他的,就没什么大不了的了。


十三:-------------------------------------------------------------------------
F_MOVE 这个继承才是真正的移动要点,如果没有了这个继承,你就没法做什么事了。
这个继承涉及到重量的变化,移动的效果等等,比较复杂。
OK,我们来分析他,最后在来看看移动到底是怎么回事。
// move.c
这个继承也是独立于dbase的 所以我们一般又可以找到类似的set query这类表示方法了
重量有两方面,一方面是容器(房间 容器 人物等等)的可负重 表示为encumbrance
一方面是物件的重量weight

#define MAX_CARRY 20//这个定义是用来限制可带物件的。本人不同意room加上限制
static int weight = 0;
static int encumb = 0, max_encumb = 0;
nomask int query_encumbrance() { return encumb; }
nomask int over_encumbranced() { return encumb > max_encumb; }
nomask int query_max_encumbrance() { return max_encumb; }
nomask void set_max_encumbrance(int e) { max_encumb = e; }
这些函数 就是显示当前负量,超重的,最大的负重量,设置最大负重量等等

nomask void add_encumbrance(int w)
{
encumb += w;
if( encumb < 0 )
log_file("move.bug", sprintf("%O encumbrance underflow.n", this_object()));
if( encumb > max_encumb ) this_object()->over_encumbrance();
if( environment() ) environment()->add_encumbrance(w);
}
//add 嗯 一看就知道是增加重量的 当然要注意判断超重问题

void over_encumbrance()
{
if( !interactive(this_object()) ) return;
tell_object(this_object(), "你的负荷过重了!n");
}
//这个是显示超重问题的

nomask int query_weight() { return weight; }
nomask void set_weight(int w)
{
if( !environment() ) {
weight = w;
return;
}
if( w!=weight ) environment()->add_encumbrance( w - weight );
weight = w;
}

nomask int weight() { return weight + encumb; }
这些显示重量问题

varargs int move(mixed dest, int silently)
{
object ob, env;
object *inv;
string str, *dirs;
int i;
mixed brief;
mapping exits;

//移动的时候,如果是装备的东西,就先拿下来
if( query("equipped") && !this_object()->unequip() )
return notify_fail("你没有办法取下这样东西。n");

//寻找对方物件 目的的
if( objectp(dest) )
ob = dest;
else if( stringp(dest) ) {
call_other(dest, "???");
ob = find_object(dest);
if(!ob) return notify_fail("move: destination unavailable.n");
} else
return notify_fail(sprintf("move: invalid destination %O.n", dest));

//考虑移动的时候 要考虑对方时候可以负重 还要考虑负载的东西是否太多
env = this_object();
while(env = environment(env)) if( env==ob ) break;
if( !env && (int)ob->query_encumbrance() + weight()
> (int)ob->query_max_encumbrance() ) {
if( ob==this_player() )
return notify_fail( this_object()->name() + "对你而言太重了。n");
else
return notify_fail( this_object()->name() + "对" + ob->name() + "而言太重了。n");
}
// check if player carrying too much!
if( interactive(ob) && living(ob))
{
inv = all_inventory(ob);
if ( sizeof(inv) >= MAX_CARRY ) {
tell_object(ob,"你身上已经带着太多东西了.n");
return notify_fail(ob->name()+"身上已经带着太多东西了.n");
}
}

if (ob->is_container())
{
inv = all_inventory(ob);
if ( sizeof(inv) >= MAX_CARRY * 10 ) {
return notify_fail(ob->name()+"已经装着太多东西了.n");
}
}

//移动要考虑目标和当前物件重量的转移
if( environment() ) environment()->add_encumbrance( - weight());
move_object(ob);
ob->add_encumbrance(weight());

// If we are players, try look where we are.
if( interactive(this_object())// are we linkdead?
&&living(this_object())// are we still concious?
&&!silently ) {
//下面是人物移动的标记 这里使用brief 0,1,2,3 ...来区分
brief = this_object()->query("env/brief");
if (!intp(brief))brief = 0;
switch(brief){
case 1:
tell_object(this_object(), environment()->query("short") + "n");
break;
case 2:
str = environment()->query("short") + "n";
      inv = all_inventory(environment());
      for(i=0; iquery("no_shown")) continue;
                if( !this_object()->visible(inv) ) continue;
                str += "   " + inv->short() + "n";
      }
tell_object(this_object(), str);
break;
case 3:
                str = environment()->query("short") + "n";
      if( mapp(exits = environment()->query("exits")) ) {
                dirs = keys(exits);
                for(i=0; iquery_door(dirs, "status") & DOOR_CLOSED )
                              dirs = 0;
                dirs -= ({ 0 });
                if( sizeof(dirs)==0 )
                        str += "   这里没有任何明显的出路。n";
                else if( sizeof(dirs)==1 )
                        str += "   这里唯一的出口是 " + BOLD + dirs + NOR + "。n";
                else
                        str += sprintf("   这里明显的出口是 " + BOLD + "%s" + NOR + " 和 " + BOLD + "%s" + NOR + "。n",
                              implode(dirs, "、"), dirs);
      }
elsestr += "   这里没有任何明显的出路。n";

                inv = all_inventory(environment());
                for(i=0; iquery("no_shown")) continue;
                        if( !this_object()->visible(inv) ) continue;
                        str += "   " + inv->short() + "n";
                        }
                        tell_object(this_object(), str);
                      break;
default :
command("look");
break;
}
}
return 1;
}

void remove(string euid)
{
object default_ob;

if( !previous_object()
||base_name(previous_object()) != SIMUL_EFUN_OB )
error("move: remove() can only be called by destruct() simul efun.n");

if( userp(this_object()) && euid!=ROOT_UID ) {
log_file("destruct", sprintf("%s attempt to destruct user object %s (%s)n",
euid, this_object()->query("id"), ctime(time())));
error("你(" + euid + ")不能摧毁其他的使用者。n");
} else if( this_object()->query("equipped")) {
if(!this_object()->unequip() )
log_file("destruct", sprintf("Failed to unequip %s when destructed.n",file_name(this_object())));
}

// We only care about our own weight here, since remove() is called once
// on each destruct(), so our inventory (encumbrance) will be counted as
// well.
if( environment() )environment()->add_encumbrance( - weight );
if( default_ob = this_object()->query_default_object() )
default_ob->add("no_clean_up", -1);
}

int move_or_destruct( object dest )
{
if( userp(this_object()) ) {
tell_object(this_object(), "一阵时空的扭曲将你传送到另一个地方....n");
move(VOID_OB);
}
}
上面这两个函数 是用来决定销毁物件的 除了判断安全问题没什么东西
对于这个继承没什么好说的。如果害怕room中物件太多,可以调整物件的定义
和显示。这以后再说吧,其实就是类似三国或者前段时间MUDking上面讨论的
数据流处理问题。不过方法还有其他的。。。。



十四:-------------------------------------------------------------------------
F_NAME 这个是命名继承 含关键的一个继承 可以包含很多设计
前面已经提到了使用
void set_random_name(int gender)这种函数来实现random name npc
我们自己来看看这个继承:
// name.c
static string *my_id;
void set_name(string name, string *id)
这个函数被普遍的使用在物件中,只要是item char都依赖这个函数来构架
名字和id

int id(string str)
这个函数是很有用的方式,一般人们喜欢使用更简单的方式来判断
物件,比如 id == query("id") 这类方式
但是更合理的方式,毫无疑问,是使用这个函数。
比如: if( !this_object()->id(arg) ) return 0;
看看是否当前物件和这个arg是否匹配。这个函数会做visable判断。

string *parse_command_id_list()
这个函数返回id阵列

varargs string name(int raw)
很多情况下,query("name") 和 name()返回的是一样的
但是,如果有apply情况,比如面具什么的,那就不一样了。
一般而言,query("name")总是真实的,而name()总是会返回
用到的名字。不过你也可以通过name(1)来确定真实的query("name")
如果没有定义这个名字,那么就会得到文件名。

下面这个函数关系到look指令所返回的描述
varargs string short(int raw)
{
string title, nick, str, *mask;

//如果没有定义short 比如人物。 事实上,可以通过定制这个,来制作面具。
if( !stringp(str = query("short")) )
str = name(raw) + "(" + capitalize(query("id")) + ")";

//如果不是人物,那就直接返回看到的 name + id 模式
if( !this_object()->is_character() ) return str;

//一般而言 很少使用short(1) 而使用short()的效果就是我们打一个look指令
//看到的人物描写:
//比如:   东海无名岛前任岛主『找我call东西的,先警告,然后purge』冰之魂(Lake)
//一般包含了 title nickname name id 还有最后显示还有状态,就是常说的status
if( !raw && sizeof(mask = query_temp("apply/short")) )
str = (string)mask;
else {
if( stringp(nick = query("nickname")) )
str = sprintf("『%s』%s", nick, str);

if( stringp(title = query("title")) )
str = sprintf("%s%s%s", title,(nick?"":" "), str);
}
//上面一个if就是显示nickname and title ,如果有apply效果,那就显示short
if( !raw ) {
if( this_object()->is_ghost() ) str = HIB "(鬼气) " NOR + str;
if( query_tem
页: [1]
查看完整版本: NPC的结构(zz)