【转】MUD教程--巫师入门教程2
简单的人物原则上只要有 set_name<名字> 、 combat_exp <经验>就行了,当然我们总得稍微多添一点了。
inherit NPC;
void create()
{
set_name(<中文名>, ({ <英文id> }) );
set("title", <头衔>);
set("gender",<男性、女性或是无性>);
set("age", <年龄>);
set("long", <人物的长相描述>);
set("combat_exp", <人物的实战经验>);
set("attitude", <好战态度>);
set("neili", <内力值>);
set("max_force", <最大内力>);
你想让这个人物能够时不时说点什么或动点什么,就要
set("chat_chance", <随机动作概率的百分比>);
set("chat_msg", ({
"随机的动作或语言的描述"
........
}) );
这时,如果还想让对玩家ask它时有些特殊的反应的话,就得:
set("inquiry", ([
"ask的关键词": "回答的话",
.......
]) );
物品要比上面两类更要简单一点,看懂了这些,再去调几个物品的文件,自己看看也就明白了。好了,现在你可以自己对自己写一个工作室,也可以在里面放一个傻乎乎的书童了。接下来我们就要深一步,了解文件中更富有变化。更有意思的基本函数之二:
init() 函数
init()函数就是在有玩家或 npc等的活物进入房间时(可以是走进来,扔进来或clone 进来)被触发,从而实现函数功能。也就是说,如果你要对进入房间的玩家做一些动作,比如弄晕他或给他中毒、或者向他问好、显示出一些特殊的信息等等,那么就在init()函数里对那个玩家进行操作。
在 init() 中最常见的的函数莫过於add_action("function", "action")了,它的作用是在进来的生物身上添加上一个指令 (注意, 系统只认指令的第一个单词), 并在玩家下达这个指令时去呼叫那个名称的函数:举例而言, 如果我们写了这样的 init():
init()
{
add_action("do_climb", "climb");
}
就是说,当玩家走进这个房间时, 系统会帮他多出 climb 这个指令,当他下达了climb tree 这个指令时, 系统会去寻找 do_climb() 这个函数, do_climb()这个函数当然是你去写了。同时, 系统会将玩家所输入的 "climb"这个指令后面的所有文字,作为一个检查的变量(是不是有些深了?没关系,看不懂就跳过去,知道是这么个意思就行了)传给 do_climb()。你可以将 do_climb这个函数宣告为
int do_climb(string arg)
这样一来,当玩家下达 climb tree,或是 climb the red wall这种指令时,"tree"或是 "the red wall"就会被存进变量 arg 之中供do_climb进行检查处理。如果判断后面的变量与我们设计的无关,也就是说,比如我们希望玩家应该是climb red tree,那么就应该写:
if(arg!="red tree") return 0;
“!=”就是不等于的意思,“!”代表不、否定的意思。return 传回的是 1,就表示通过;是0,则表示函数处理中止。所以,这句判断名就是当后面跟的那个变量不是“red tree”时,函数的处理就终止。反过来,你就可以设计并设置,条件符合时会发生的事,然后别忘了,在最后加上一句return 1;
在这里,我们给有一些基础的巫师开一个小灶,看不懂的就跳过去看下一段。有时有些新巫师会发现一个有点费些思量的事,就是当我们所加的add_action()的指令同时也是系统提供的cmds指令时,一般add_action的指令将优先于cmds进行执行。比如kill是一个cmds指令,但在你现在写的房间程序中将要进行某种程度的限制。一般的做法就是加add_action("do_kill","kill"),那么,玩家一旦在这里发出kill指令,将首先寻找do_kill()这个函数,进行变量的检查。比如,发现玩家的某些条件不符合就给出类似“这里不允许对杀”的信息后return 1;就表示判断通过了,也就意味着这个指令已被你写的这个函数处理掉了。如果你在某个条件后给出“你心中一动,杀机已起,小小的一个地方立刻激荡出无边的战气”后return 0; 这就是告诉系统,我这个kill指令并没有完,刚才只是增加kill更多的信息,此后系统就会寻找本来的那个kill的cmds指令,也就是正式执行,这被称之为重载。(有点绕人?多想想,想通了后很有用的)
在你的函数检查到玩家输入的变量有问题时 (例如你要他们climb red tree, 但他们却输入了一些错误的指令如 climb three 之类的),你可以象上面一样直接return 0;终止这个函数,系统就会显现出“什么?”的错误信息。而如果你想给他们一些特别的、有些意思或提示性的错误讯息时, 你可以用 notify_fail(".....")来代替0写这个讯息, 比如:
return notify_fail("你想爬什么?\n");
return notify_fail()就是用来取代return 0的东西,这就是想让返回的错误信息更加丰富、或者有效而已。所以我们最常用的写法是:
if (条件不合)
return notify_fail(错误讯息);
if (另一个条件不合)
return notify_fail(另一个错误讯息);
.............................
(所有可能导致错误的输入都过滤光了 开始真正干活的部份.... )
..............................
return 1;
人物的、物品的init()与 ROOM 中的init()函数类似, 但物品中被呼叫的机会多了许多, 主要有下列的几种情况:
1,物品摆在房间中, 有一个玩家走进来,这很好理解;
2,一个物品突然出现在某个玩家所在的房间中,这就是象别人丢下的,机关触发出来的,或者是巫师变出来的;
3,一个物品突然出现在某个玩家的物品栏中,象别人给你的,你买到的东西,通过机关直接触发到身上的;
这些 action 会生效的场合归结起来很简单, 就是: 「玩家用 look 或是 i 指令看得到这个物品的时候」,但是同一个房间中他人或 npc身上的东西时不算,装在袋子的东西不算。
废话少说,下面还是拿出例子进行逐句的解释,这也是新巫师最需要的。首先说明:在文件中“//”后面跟的就是注释语句,凡是这一行“//”后的东东,程序在执行时概不理会。而“/*”也是表示后面是注释名,只不过它表示后面所有行的东东都是程序不必理会执行的语句,一直到“*/” 结束。用在注释行较多的地方。这里为了区别,我们把教材中注释用淡一些的颜色标出。
// Room: /d/wuxi/ximen.c //无锡西城门 程序开头注明一下 文件类型和绝对路径和中文名
// llm by 99/05/21 //继续注释 谁写的和编写时间 有时是修改时间
#include <ansi.h> //表明它继承了定义颜色的文件,后面不要分号
#include <room.h> //表明它继承了定义房间的文件,因为门在这个文件里
string look_pai(object me); //声明一下这个文件里有一个函数的原型定义
inherit ROOM; //表明它是继承 ROOM 类
void create() //开始创建函数void create() 在里面定义各种属性
{
set("short", "梁溪门"); //short是指房间的短描述 系统会自动定义成亮青色
set("long", @LONG //房间的长描述即场景描述
这是无锡城的西城门,缘由城门外的一条梁溪河而得名为梁溪
注意:系统会自动在开头一行空两格,所以你不要多事自己加空格
门,看城官兵比较懈怠,向西出城是一条尘土飞扬的大驿道,往东
便可进入热闹的无锡城了。
LONG
/*在长描述中,前后要加入 @LONG 和 LONG,它们是互相对应的,你可以用任何字接在 @ 後面,但是前后两个单词一定要一样,也就是说你也可以:set("long",@TXTE
*******
TXTX
);
这样系统才能判别,而房间的叙述写完时,一定要换行后再接第二个 LONG ,并且LONG或TXTE这一行不能再有其他任何的字元,不然系统无法判定叙述是否该结束了,会造成编译时的错误。而如果不加 @LONG和LONG时,字符串前后必须要加上" ",中间也必须手动添上“\n”的换行符号。
*/
);
set("valid_startroom", 1); //它的作用是如果在该房间退出游戏的话,就可以成为下一次进来的地方
set("no_fight",1); //表明这是一个不允许战斗的房间,同理还有“no_beg、no_steal”
set("item_desc", ([ //这用在某些可以用look看出机关或特殊描写的地方
"men":"一座高大的城门,用上好的红木拼钉而成,十分威武",
"pai": (: look_pai :),
]) );
/*这里放了两个常用的用法,共同之处,就是“:”的左边是物品名(其实不是物品,也就是一个记号而已)或者叫可以让玩家用look指令看的字符,一旦look men后,就会由右边的来显现信息。第一种情况:右边直接是描述的语句,比如"look men"就会出现“一座高大的城门......”。第二种情况后面是(: 函数名 :),就调用这个函数。而这个函数名必须在文件头声明了,比如我们这里的"pai"就是调用(: look_pai :),在后面要对该个函数进行设置*/
set("exits", ([ //设这个房间的出口
"east" : __DIR__"dajie1", //该方向要连到的房间文件名,注意不需加.c后缀
"west" : __DIR__"yidao1", //__DIR__的意思就是这个文件所在的当前目录
"northeast":__DIR__"qiangdong",
]));
set("objects", ([ //设这个房间里东东,npc和物品
__DIR__"npc/bing":2, //后面的2,也可是1、3 表示数量
]));
create_door("northeast", "小门", "southwest", DOOR_CLOSED);
/*这是定义门,注意它的格式是:
create_door("入口方向","门的名称","出口方向","预设状态")
预设状态有两种,也就是DOOR_CLOSED和DOOR_OPENED,分别表示初始状时是关着或开着。因为这些都是写在/include/room.h文件里,所以我们一定在这个文件头加上inherit <room.h>。*/
set("outdoors", "wuxi"); //指出它是室外,并在wuxi(区域目录名)这一区域
setup(); //设置结束
}
/*注意:关于“replace_program(ROOM);”的用法,由于在房间的标准物件中有定义了如 init() 等其他的函式,而一个简单的没有机关的房间根本没有用到,所以就用replace_program() 来将原本的被继承的标准物件「重置」(或说取代)掉,以便最大限度地节约系统内存的耗用。但是一旦房间中用到了 init() 来编写时,就绝对不可以用 replace_program(),因为如果你写的是一个复杂的房间,就会在很多触发函数的地方,而这个文件加了这行后,又会把那些多于简单房间定义的函数清除掉了。于是一旦这些地方开始触发了,系统到时就会找不到那些触发的函数。一般地情况下,系统就随便呼叫一个记忆体中的位址而随便传进一些乱七八糟的东西,而情况严重时,可以让整个 mud宕机。我们现在这的这个例子中需要用到一些其它的函数,那当然不能用它喽。对于新巫师来说,这一行在不能确定是是否要加的情况下,还是选择不加为好,毕竟,浪费些空间与当机的比较是很明显的。
接下来我们来定义前面提到了look_pai的函数了*/
string look_pai(object me) /*me是一个对象,指作动作人,也就是this_player(),如果在这里不定义,那么就要在函数里用 object me; me = this_player();进行定义*/
{
if( wizardp(me) ) //wizardp(me)是一个efun函数,判断me是否是巫师
return "大木牌写着:无锡城门。正在建设中,叮当留。\n";/*根据上面的条件,这句话只有me是巫师时才能看到*/
else //如果不是巫师
return "大木牌写着:无锡城。\n";
}
这个程序到此结束。
接下来是一个较为复杂的人物例子
// /kungfu/class/baituo/ouyang-feng.c 白驼开山祖师欧阳锋
#include <ansi.h> //表明这个文件要用到颜色
#inherit NPC; //继承NPC的属性
inherit F_MASTER; //继承可收徒的NPC属性
int check_self(); //声明,文件中有战斗行为函数的定义
int learn_message(object ob,string skill);//声明:文件中有learn_message()函数的定义
string ask_zhang(); //声明:文件中有谜题函数的定义
void create()
{
set_name("欧阳锋", ({ "ouyang feng", "ouyang", "feng" }));
//注意,不要用set("name","")直接set_name
set("long", "他是白驼山庄主,号称"HIW"“西毒”"NOR"的欧阳锋。\n"
+"虽然由于习练「九阴真经」走火入魔,变得精神错乱,但\n"
+"是他那额头上的层层紫晕,令人不得不服他是一代高手!\n");
//这就是不用@LONG&LONG的例子,所以就必须在每句尾加上“\n”的换行标志
set("nickname", HIW"西毒"NOR); //外号,用到了颜色,所以开头没有include <ansi.h>这里会出错
set("gender", "男性"); //性别,太监是无性
set("age", 53); //年龄
set("shen_type",-1); //神的正负,如果没有set默认是1,用这个乘以exp/10得到神值
set("attitude", "peaceful"); //指这个人物的好战态度
set("str", 30); //膂力,
set("int", 29); //悟性
set("con", 30); //根骨
set("dex", 28); //身法,这些先天属性,可设可不设,但要符合原著精神
set("qi", 2500); //当前气
set("max_qi", 2500); //最大气,就是恢复满时
set("jing", 900); //当前精
set("max_jing", 900); //最大精
set("neili", 2000); //当前内力
set("max_neili", 2000); //最大内力
set("jiali", 50); //相当于玩家的加力jiali *
set("combat_exp", 1500000); //经验
set_skill("force", 200); //设置武功,这是基本内功
set_skill("unarmed", 170); //反正一项项设,略.......
......
set_skill("nilian-shengong", 200);
......
map_skill("force", "nilian-shengong"); //相当于玩家的jifa
......
create_family("白驼山派",1, "开山祖师");//门派头衔
set("inquiry" ,([
"欧阳克":"欧阳锋嘿嘿一笑:“那是我的乖侄子,你见过他了吗?”\n",
"蛇杖":(:ask_zhang:),
]));
/*这个是设置当玩家ask sb about sth时的信息,“:”前就是sth,后面
则是返回的信息。这有点与房间里的set("desc_item")相似。所以也有关
于 (:ask_zhang:)的函数调用,这个函数我们在文件头已经定义过了,后
面将会有具体的内容。*/
set("chat_chance",2); //设置随机动作的机率,这是指2%
set("chat_msg",({ //设置随机动动作
"欧阳锋自言自语道:“我白驼山派神功一成,定能重霸江湖!!”\n",
"欧阳锋道:“我儿欧阳克聪慧过人,必能够重振白驼山派雄风!”\n",
"欧阳锋道:“江湖险恶,困难重重,我才是天下第一!”\n",
}));
set("chat_chance_combat", 100); //这是指战斗中的随机行为,注意区别。
set("chat_msg_combat", ({
(: command("wield zhang") :),
(: command("wield zhang") :), //装备武器
(: perform_action, "staff.shewu" :),
(: perform_action, "staff.shewu" :),//使用绝招
(: command("unwield zhang") :),
(: check_self :), //这是我们自定义的一个函数,在后面写着
}) );
setup();
carry_object("/d/baituo/obj/shezhang");//身上的东西,加上“->wield”就是装备好了,如果没有这个,就会在身上,但没装备起来
carry_object("/clone/misc/cloth")->wear();//这件衣服就是穿上的,也可不穿
add_money("silver",50); //设置他身上的钱,可以gold,coin
}
void init()
{
::init();
add_action("do_skills","skills");
add_action("do_skills","cha"); //两个动作调用同一个函数do_skills
}
int do_skills(string arg)//定义do_skills函数,并表明其类型
{
object ob ;
ob = this_player () ; //定义ob是指的执行这个动作的人
if( !arg && arg!="ouyang feng"&& arg!="ouyang"&& arg!="feng" )
return 0; //如果对象不是欧阳锋,则返回调用skills的cmds指令
if(wizardp(ob)) //return 0; 是巫师的话返回调用skills的cmds指令
if (ob->query("family/master_name")!="欧阳锋")
return 0; //师父不是欧阳锋的话返回调用skills的cmds指令
if(!ob->query_skill("nilian-shengong",1))//如果没学过逆练神功
{
write("欧阳锋目前所学过的技能:\n"+
" 基本内功 (force) - 深不可测 200/ 0\n"+
"□蛤蟆功 (hamagong) - 深不可测 200/ 0\n"+
......\n"); //略
return 1; //参考前面的懂了这里retrun 0和return 1的意思吗?
}
else //相反则是学过
{
write("欧阳锋目前所学过的技能:\n"+
" 基本内功 (force) - 深不可测 200/ 0\n"+
"□逆练神功 (nilian-shengong) - 深不可测 200/ 0\n"+
.......
\n"); //这时才让他的徒弟能查看欧的逆练神功级别
return 1;
}
}
void attempt_apprentice(object ob)//这个函数的原型就是在前面定义的inherit F_MASTER里
{
if((int)ob->query("combat_exp")<100000) //条件一,经验要大于100000
{
message_vision("欧阳锋冷冷地对$N道:“这点经验就想来拜师?!”\n",ob);
return; //因为这是一个void函数,直接return,面不是return 1之类的
}
if((int)ob->query_skill("hamagong",1)<60) //条件二,蛤蟆功大于60级
{
message_vision("欧阳锋冷冷地对$N道:“武功不错,先跟我的弟子学些的入门武功吧!”\n",ob);
return;
}
message_vision("欧阳锋拍拍$N的头,微微点了点头。\n",ob); //过滤完了就通过
command("recruit " + ob->query("id"));
return;
}
int check_self() //即我们自定义战斗中行为
{
int max_qi,eff_qi,qi; //命名三个变量
object me;
me = this_object(); //不多说了吧
max_qi = me->query("max_qi");
eff_qi = me->query("eff_qi");
qi = me->query("qi"); //对三个变量进行初始定义
if((int)(qi*100/max_qi)<30 || (int)(eff_qi*100/max_qi)<50)//就是说欧受伤到一定程度
{
if((int)me->query_temp("powerup")) //如果已经在提升战斗力状态
{
....... //将干什么什么
return 1;
}
......; //否则就怎么怎么
return 1;
}
}
string ask_zhang()//定义解谜中的关于蛇杖的函数
{ //string与int都可以,区别在于string函数return的是字符串
object me,weapon,obj,obn;
mapping fam;
me = this_player();
if(!(fam = me->query("family"))|| fam["family_name"] != "白驼山派") //不是白驼弟子
return "\n欧阳锋冲你阴阴地一笑:“你是不是想尝尝我西域灵蛇毒的厉害?”\n";
if((int)me->query_skill("xunshe-shu",1)<50) //驯蛇术太低
return "\n欧阳锋拍拍你的头说:“你的驯蛇术还不到家,现在用蛇杖太危险!”\n";
if( !me->query("weapon")||(string)me->query("weapon/type")!= "杖") //没有自铸的杖
return "\n欧阳锋说道:“要想炼蛇杖,必须要有一根自铸的杖,你先找欧冶子铸杖吧!\n";
if( me->query("weapon/she")) //已经有了蛇杖
return "\n欧阳锋怒道:“小子也敢戏弄老夫,明明已有蛇杖,还要问什么?滚!!!\n";
if( me->query_temp("dixi-wan")) //已经要了药丸
return "\n欧阳锋大怒:“贪得无厌的家伙,拿了还想拿,再这样,老夫一杖叫你上西天!\n";
//到此为止,一切不符合解谜的条件都过滤完了,则开始执行
obn=new("/d/baituo/obj/dixi-wan"); //要取出的东西的路径文件名,相当于clone
obn->set("sign",me->query("id")); //东西上设上给的人的记号
obn->move(me); //这个东西放进问的人的身上
me->set_temp("dixi-wan",1); //问的人做记号,以防他再去要,看前面
return "\n欧阳峰仰天哈哈一笑后,缓缓说道:“我灵蛇杖法奇妙无比,配合蛇杖上灵蛇\n"
"的攻击,可使对手防不胜防,我这有一颗通灵地犀丸,你拿去找蛇奴,他会知道的!”\n"
HIC"说完欧阳锋递过来一颗鸽蛋大小的药丸。\n"NOR;
}
int learn_message(object ob,string skill) //定义学武功的条件
{
if((skill=="nilian-shengong")&&(!ob->query_skill("nilian-shengong",1)))
{
message_vision("欧阳锋阴阴地对$N说道:“你如何能从我这学会这种没有一点功基的武功?”\n",ob);
return 0;
}
else return 1;
}
人物也讲完了,其实根据这两个文件,你可只选其中一两点就可以改出很多你所需要的人物来,在初级阶段,把现成的文件改会避免太多的BUG的出现。但是我所要说的是:重在理解。理解了之后,什么都好办了。
以下列出一些定义在人物里的一些附加函数,以供参考。
void defeated_enemy(object victim)
当这名人物打昏一个敌人时会呼叫这个附加函数,victim 即是被打昏的人。
呼叫者: COMBAT_D
有预设定义此一函数的系统物件: none
void killed_enemy(object victim)
当这名人物杀死一个敌人时会呼叫这个附加函数,victim 是将要被杀死的人。
呼叫者: COMBAT_D
有预设定义此一函数的系统物件: none
int accept_fight(object who)
当有其他生物对这个人物下 fight 指令的时候,会呼叫这个附加函数,who是下
fight 指令的生物,只有当这个附加函数传回 1时才会接受挑战,否则显示某某不想
跟你较量的讯息。
呼叫者: "fight" 指令
有预设定义此一函数的系统物件: NPC
int accept_object(object who, object item)
当有人用 give 指令给这个非玩家人物东西时,会呼叫这个附加函数,传回 1
表示愿意接受这个东西,传回 0 表示不接受。
呼叫者: "give" 指令
有预设定义此一函数的系统物件: none
void recruit_apprentice(objct apprentice)
当一个人物收了另一个人物做弟子时会呼叫这个附加函数,你可以在这个函数里
修改弟子的 rank 或其他东西。
呼叫者: "apprentice" 指令
有预设定义此一函数的系统物件: none
有关房间、人物和物品这三种类型只是我们人为的划分,对于系统来说,它们应该都是一回事。为了实现某一种效果,既可以写在房间里进行执行,也可以写在人身上进行执行。看了这一章后,你可以尝试着写一些程序了。我们希望你的感性认识是建立在自己写了超过百个的编译通过的程序以后,下一章,我们就可以学习LPC的概念了。
编程终究是一件技术性的活,我们总不能一直通俗下去吧!在这一章,我们该开始回过头来,对我们工作系统里的一些基本的概念进行一一的理解,当然要接触一些深一些的概念了。
通常我们在大陆所见到的中文MUD大多是LpMUD,一般是一些角色养成型的游戏模式。LpMUD使用Unix的指令和文件结构。而很多版本也是运行在Unix环境下(目前WINNT的版本也不少)。
&&--理解LPC
LPC是什么东东?就是写LPMUD程序的C语言啦!它看起来和一般的C语言区别不大,语法基本一样,只是简单得多啦,可以说,它是我所见到的最简单的一种C语言。他们的根本不同点在于,Lpc程序是编写一个一个的"Object"。 这有什么区别呢?一般的程序是在执行过程中,通常有一个明显的开始和结束。程序从一个地方开始,然后顺序执行下去,到了结束的地方就中断了。而Lpc的Object不是这样的。Lpc的Object可能没有明显的开始和结束的标志,它可能永远在工作。在有些游戏中,整个游戏包括Driver和游戏世界都用C写好,这样处理的速度能快一些,但是游戏的扩充性很差,巫师们不可能在游戏进行中添加任何东西。 LpMud则相反。Driver理论上应该和玩家所接触的世界几乎没有任何直接的联系。游戏世界应该是自己独立的,而且是“即玩即加”的。举个例子,我们在玩三国志系列时,在你没能取得赤壁之战的胜利之前,你是不可能去打六出祁山的,甚至连四川都去不了,但是在MUD游戏中,却可以任由玩家选择,而且巫师也可视其需要,在任何时候、在任何地方再加上一场另外的战役。所以,在你写完一个Lpc的文件时,它就存于主机的硬盘上。在游戏进行中,当需要整个Object时,Driver就从硬盘中读入这个文件,然后放在内存中,一个特殊的函数被调用来初始化这个Object的一些变量。当然这要建立在这个文件没有任何错误的基础上。否则,轻则出错,重则宕机。
在这里,既然我们不断谈到“变量”和“函数”,则有必要谈谈在LPC中它们之间的关系。变量就是一个能够变化的值,函数通常是用来操纵那些变量的一段程序。Lpc 的Object就是一些变量和函数的组合。在前面我们讲过,Object没有开始、也没有结束的地方,更不会有一个特别的地方让Driver去执行它。那当一个Object如果要被内存中的另一个Object调用时将怎么办呢?Driver一般会去找这个Object的那堆变量放在哪里。如果这些变量没有值,那么Driver就会调用一个特定的函数,即create()来初始化这些变量。(看到这里,你可以回去看看第二章的有关部分)。
但是要强调一点,create()并不是 Lpc代码开始执行的地方,只是大多数的Object从这里开始。事实上,create()是可以不存在的。如果这个Object不需要对变量初始化,那么create()可以不存在。那么这样的Object开始执行的地方就完全的不同于一般的Object,它可以从任何地方开始。所以在Lpc的Object中,函数的排列顺序是无所谓的,随便那个排在前面对这个Object的特性没有影响。只不过在各个MUD中的巫师品质要求中稍作规定而已。
&&--理解Object
既然LPC中最不同的就是Object,因此,当你想在程序中设计任何动作时, 都应当要考虑到这个动作是哪一个 object 所做的, 不然很容易导致错误。LPC 的语法并不严谨, 有些场合为了省事可以将函数是由哪个Object所做的省略掉, 例如我们在 create() 函数中最常看到的 set(),事实上严谨的写法应为this_object()->set()。因此我们首先要学习的就是如何利用系统提供的函数,去正确快捷地寻找Object。
MUD系统为我们提供了两个最好用的两个函数this_object()与this_player(),在你写作一个对象(房间、物品......)时,this_object()表示自己这个对象(房间、物品......),也就是你写的这个文件本身啦。在这里,要提及一下object的所谓封闭性。每一个object有自己独立的数据结构,它能与其他object严格区分开来。即使是同一个长剑程序,在同一个房间里你复制了两把时,在内存中就会有两个完全独立的长剑object,你对其中一把无论进行改名、改威力都不会影响到另一把的数据,这就是object的封闭性。而this_player()则比较复杂, 它会传回一个属于玩家类型的对象。这个玩家在init中就是触发init的那个玩家。this_player()会跟著函数呼叫一直传递给所有被init呼叫的函数, 包括add_action中所定义出来的函数, 在这些函数中, 它又表示做动作的那个人。
除此之外,当我们只知道一个对象的名字,而不是它的文件名时,是无法用个 object 类型的变量指向它,这时我们就要用到[present() 函数]
用法:
object=present(string "id",object env)
函数在此时就从名字(id)找到这个object,有了这个object才好对它做操作。就可以派上用场。简单的想, present 函数其实就是在一个房间或者一个人身上(房间与人对于程序其实是一样子的)里找出叫某个名字的物品的函数,它是同类型找物品的函数中最有用的一个, 其余的函数还有:find_player(), find_living() 等等
再看一组很实用的函数:[environment(),first_inventory(),next_inventory(), all_inventory()]。这一组函数跟对象所处在的位置有关:
environment(object ob)传回了对象 ob 所处在的地点。假如 ob 是个玩家或生物,那么这个函数会传回 ob 所在的房间;如果 ob 是个物品,是有人带的就传回携带着 ob 的生物, 没人带的话就传回 ob 所在的房间。
first_inventory(object ob) 所传回的是 ob 中的第一个对象,如果 ob是房间,则传回这个房间中的第一个物品或是生物;如果 ob 是生物, 则传回他身上所带的第一个物品。
next_inventory(object ob) 通常是跟着 first_inventory() 一起使用,它的功用是传回 ob 的下一个物品。
很简单, all_inventory(object ob) 所传回的是包含了所有物品的一整个阵列。一个object(除了房间之外)都要有自己的环境,就是这个object在什么地方放着,是一间房间里、还是一口箱子中、还是一个人身上,而这环境通常是另一个object。比如物品A和B放在一个人M身上,那么上面的函数就可以给出它们之间的关系 :
M=environment(A);
M=environment(B);
A=first_inventory(M);
B=next_inventory(M);
A=all_inventory(M)[0];
B=all_inventory(M)[1];
&&--理解Efun
从上面你也许可以看到了,象environment()这样的各种函数,也许会在你编程时时不时地发现,而且用的地方特别地多,最常见的就是this_player()、this_object()、还有strcmp()、implode(),左看右看找不到在哪里定义的,而你就算是找遍你下载下来的单机版的所有文件,都找不到这些函数,其实它们就是efun。efun就是MUD外部定义的函数,也就是externally defined function 的缩写。是由Mud Driver定义好的。它是以计算机直接能理解的二进制的形式存在着的,所以它们执行起来要比一般的Object带有的函数速度快的多。而对于Object内部定义的函数,通常叫作lfun(local function)。一个巫师的主要工作也就是编写一些lfun组成的Object。
那么为什么要创立efun呢?
1) 处理一些很常用的、经常会有许多函数会调用的。
2) 处理internet socket的输入输出。
3) 以及一些Lpc很难处理的事,因为毕竟Lpc是C的很小的子集。
efun是用C写好的,内嵌在Driver里面的。在Mud起来之前,和Driver一起编译好的,它们的调用和你写的函数的调用方法是完全一样的。总的来说,你只需要关心:它需要传入什么参数、将会返回什么的东西就行了。
通常在一个Mud里面,你可以在类似这样的/doc/efun的目录底下找到有关efun的说明和帮助,或者直接用help <efun名>指令就可以得到帮助。因为efun依赖于你所在的Mud的Driver,所以不同的Driver带有的efun区别很大。不要想当然地将别的MUD中的经验带到这里来,也不要在我们这里理解了某个efun,就自以为什么都懂了。对于一个新的巫师不说,你只需要简单地理解,并会使用一些常见的efun就行了
学习真是一件枯燥无味的事件,听得不耐烦了,赶快让我上机操作吧!
好的,这一章,我们就来让你去开始进行一段见习工作了。我们的Mud系统使用的是仿Unix的指令和文件结构。如果有人已经对Unix有所了解的话,那是最好。没用过Unix也没关系,就象LPC虽然是一种很简单其实它与Dos十分地相似,我们只要记住它们最明显的一个区别就是文件路径是用"/",而不是"\"。下面的许许多多的命令还是让我们会时不时地想起DOS下的种种命令。
pwd: 显示你目前所在的当前目录
cd: 进入某一目录,..是上一目录,/是到根目录,什么都不加直接回车就是到你自己的的工作目录;
ls: 列出指定目录下的所有文件,就象是Dos的dir一样;
rm: 删除一个文件;
mv: 重新命名一个文件;
cp: 复制一个文件;
md: 创建一个目录;
rd: 删除一个目录(空的目录)。删除这个目录下的所有文件请加 -d参数;
more: 按页显示一个文件在你的当前屏幕;
cat: 显示整个文件;
tail: 显示一个文件的结尾几行,对于太大的文件,一般只能用它;
对于写程序来说,最好的当然是在专门的可以识别C的编辑器上写,既方便、错误也会少得多,因为它们对于一些固定的函数、变量都会用特殊的颜色突出显示,尤其对于初学编程的巫师来说,多一个字母、少一个符事情的事情可以大大减少了。但是巫师更多的是在线处理一些问题、故障,大多数时候是要通过在线编辑----也就是edit命令进行程序修改,甚至小程序的在线写作。下面就着重讲一讲MUD所提供的编辑功能的使用。
java新手自学群 626070845
java/springboot/hadoop/JVM 群 4915800
Hadoop/mongodb(搭建/开发/运维)Q群481975850
GOLang Q1群:6848027
GOLang Q2群:450509103
GOLang Q3群:436173132
GOLang Q4群:141984758
GOLang Q5群:215535604
C/C++/QT群 1414577
单片机嵌入式/电子电路入门群群 306312845
MUD/LIB/交流群 391486684
Electron/koa/Nodejs/express 214737701
大前端群vue/js/ts 165150391
操作系统研发群:15375777
汇编/辅助/破解新手群:755783453
大数据 elasticsearch 群 481975850
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。