巫师编程 1) 最简单的房间的撰写 最简单的房间是什么?就是里面什么都没有,机关,人物(npc),物品,什么都没有 比如说就是一条路上的一 程序举例如下:(//表示注释) // Room: /d/snow/eroad1.c //程序开头注一下,说明是什么文件,在什么地方 inherit ROOM; //继承 ROOM 类 void create() // 创建函数;在里面定义各种属性 set("short", "黄土小径"); // 房间的 短描述 set("long", @LONG // 房间的长描述 这是一条普通的黄土小径,弯弯曲曲往东北一路盘旋上山,北边有 一间城隍庙,往西则是雪亭镇的街道。 //@LONG&LONG是一对宏,表示里面是字符串 LONG ); set("exits", ([ /* sizeof() == 3 */ // 设房间出口 "northeast" : "/d/snow/eroad2", // 不同的方向指令,跳转到 "north" : "/d/snow/temple", // 不同的房间 "west" : "/d/snow/sroad1", // 注意,不加 .c 后缀 ])); set("outdoors", "snow"); // 是雪淳镇的室外 setup(); //不用管它,照抄就是 replace_program(ROOM); 2) 房间的物品和门 最简单的房间说完了,就再复杂一点儿加上 物品定义,和物品拿枋?还有建个门吧 还是举例说明 // inn.c #include inherit ROOM; string look_sign(object me); // 函数原型定义 void create() set("short", "饮风客栈"); set("long", @LONG 你现在正站在雪亭镇南边的一家小客栈里,这家客栈虽小,却是方圆 五百里内最负盛名的一家,客栈的主人据说是一位云游四海的仙人,如果 你的福缘深厚的话,也许可以在这里遇到他。除此之外,来自各地的旅人 都喜欢聚集在这里交换旅途上的见闻,你也可以在这里打听到许多有趣的 消息。靠近门口的地方有一块乌木雕成的招牌(sign)。 西北边有一个红木门(door),门上泛著一层白色的光晕,一种令人感 觉很有力量,又很柔和的光芒。 LONG ); // This enables players to save this room as their startroom. set("valid_startroom", 1); //使该房间可以成为下一次进来的地方 set("item_desc", ([ "sign": (: look_sign :), // 物品(其实不是物品)描述 // 只是一个记号而已,玩家可以用 look sign 来看你的描述 , 不是实际存在的物品 // 冒号左边的是名称,右边的是描述 如 : // "pillar" : "石柱上刻著:剑气指天、剑心内敛、剑芒不显、剑神如电。" // 玩家 look pillar 是,就会出来 后面跟的话 // 如果你想叫玩家look 时,去调一个函数,就后面跟一个 (: 函数名 :) "door": (: look_door, "northwest" :), // 这里调的就是look_door函数,参数是northwest ]) ); set("exits", ([ "east" : "/d/snow/square", "up" : "/d/snow/inn_2f", "northwest" : "/d/wiz/entrance" ]) ); // 下面是定义房间里的NPC, 这就是一个店小二啦 set("objects", ([ "/d/snow/npc/waiter" : 1 ]) ); // 后面的1, 就是一个的意思 // 创建房间的门, 平时是关闭的 create_door("northwest", "木门", "southeast", DOOR_CLOSED); setup(); // 建一个公告板,是另一个地方定义的函数 // To "load" the board, don't ever "clone" a bulletin board. call_other( "/obj/board/common_b", "???" ); // look sign时调用,根据玩家的级别返回描述 string look_sign(object me) // me是一个对象,指向动作的主人 // 这里的me是参数,如果没有这个参数, // 可以在函数里用 // object me; me = this_player();定 if( wizardp(me) ) // wizardp(me) 是一个efun, 是mudos 定义的 // 判断me这个对象是否是巫师 return "招牌写著:饮风客栈。旁边一排小字:庄思哑题。\n"; else return "招牌写著:饮风客栈。\n"; 3) 人物创作入门 每一个人物都需要单独的一个*.c文件,由其所在房间的源码调用.前面已经说过 一般来说,NPC放在npc各个地方的npc目录下,如 /d/snow/npc下是雪淳镇的npc 记住一点就是:NPC其实和玩家是一样的,只不过是电脑控制而已, 玩家有的各项值它都有,而且比如说,fight的时候,它也会涨经验值的 也会慢慢恢复. 所以它可以用command来执行命令. 举例说明: :::::::::::::: fist_trainer.c :::::::::::::: // trainer.c inherit NPC; // 继承 NPC类 void create() set_name("李火狮", ({ "trainer", "lee" }) ); //设NPC名字和中文名字 ^^^^^^^^^|^^^^^ +-------就是有两个名字,哪个都可以 set("title", "拳法教练"); // 头 set("gender", "男性" ); // 性别 set("age", 28); // 年龄 set("str", 26); // str, int 都是天赋值 set("int", 14); set("long", // 人物的长描述,当玩家look trainer时 // 显示该描述 "李火狮是个孔武有力的大块头,他正在训练他的弟子们习练「柳家拳\n" "法」(liuh-ken)。\n" ); set("combat_exp", 3000); // 人物的实战经验 set("attitude", "heroism"); // 好战态度 set("force", 120); // 内力值 set("max_force", 120); // 最大内力 set("force_factor", 1); // 一次发出多少点内力 set("inquiry", ([ // 玩家ask 时的反应 "here": "这里当然是淳风武馆,不然还是哪里?\n", // 如上句, 玩家ask trainer about here 就会出后面的话 "name": "在下姓李,名字就叫火狮,人称李教头的便是我。\n", "柳家拳法": "哦....说来惭愧,小弟这套拳法还没学得到家, 柳馆主就教我在这里传艺。\n" ]) ); set_skill("unarmed", 30); // 设置各项武功技能 set_skill("liuh-ken", 20); set_skill("dodge", 30); map_skill("unarmed", "liuh-ken"); // enable 柳家拳法 setup(); int recognize_apprentice(object ob) // 接受玩家拜师时的反应 if( (string)ob->query("family/family_name")=="封山剑派" ) return 1; ^^^^^^^^^^^^|^^^^^^^^^^^^ +---------------查询玩家的派别 command("say 对不起,这位" + RANK_D->query_respect(ob) + ^^^^^^^^^^^|^^^^^^^^^^^^ +---对象ob, 就是玩家,的头衔 ",您不是我们武馆的弟子。"); return notify_fail( "李火狮不愿意教你拳法。\n"); ^^^^^|^^^^^ +-------这是一个很有用的函数,就是给出错误信息 int accept_fight(object me) // 接受玩家fight时调用 if( (string)me->query("family/family_name")=="封山剑派" ) { command("say 进招吧。"); return 1; } command("say 馆主吩咐过,不许和来这里的客人过招。"); return 0; 4) 几个零散的问题 ******************************************* 1. NPC怎么有货物? ******************************************* 如店小二,在create()函数里定义 set("vendor_goods", ([ "匕首": "/obj/example/dagger", "酒": "/obj/example/wineskin", "包子": "/obj/example/dumpling", "鸡腿": "/obj/example/chicken_leg", ]) ); 增加一个list功能 在init()里定义 void init() // 当一个玩家进入同一环境时,调用 object ob; ::init(); if( interactive(ob = this_player()) && !is_fighting() ) { ^^^^^^^^^^^^^^|^^^^^^^^^^^^^^^ ^^^^^^|^^^^^^^ +---判断玩家是否存在 +---相互是否处于战斗状态 remove_call_out("greeting"); call_out("greeting", 1, ob); } add_action("do_vendor_list", "list"); //增加一个list的动作, // list时自动调do_vendor_list,是系统函数 ************************************************* 2. 进门时,自动问好 ************************************************* init()中函数设置如上 另外写一个greeting函数 void greeting(object ob) if( !ob || environment(ob) != environment() ) return; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^判断是否是同一个房间 switch( random(3) ) { // 随机问好 case 0: say( "店小二笑咪咪地说道:这位" + RANK_D->query_respect(ob) + ",进来喝杯茶,歇歇腿吧。\n"); break; case 1: say( "店小二用脖子上的毛巾抹了抹手,说道:这位" + RANK_D->query_respect(ob) + ",请进请进。\n"); break; case 2: say( "店小二说道:这位" + RANK_D->query_respect(ob) +",进来喝几盅小店的红酒吧,这几天\ 才从窖子里开封的哟。\n"); break; } ******************************************** 3. 平时随机动作和战斗中动作 ******************************************** 在create()函数里 set("chat_chance", 25); // 随机动作概率 25% set("chat_msg", ({ "僵尸护法说道:我们茅山派实力最强!谁敢欺负我们?\n" "僵尸护法对你说道:小心我把你变成僵尸! \n", "僵尸护法对你念了一会咒,你不禁毛骨悚然起来...\n", (:random_move :) // 随机移动 }) ); set("chat_chance_combat", 70); // 战斗中随机动作概率 set("chat_msg_combat", ({ (: command("你敢惹我,追到天涯海角我都要杀了你!") :), (: cast_spell, "drainerbolt" :), (: cast_spell, "netherbolt" :), //使用各种法术 (: cast_spell, "feeblebolt" :), (: cast_spell, "invocation" :), }) ); ******************************************** 4. 根据玩家询问的信息作相应的动作 ******************************************** 杨掌柜为例: 在create()函数里: set("inquiry", ([ "治伤": (: heal_me :), "疗伤": (: heal_me :), "开药": (: heal_me :), ]) ); 再定义一个heal_me函数 string heal_me(object me) int ratio; ratio = (int)me->query("eff_kee") * 100 / (int)me->query("max_kee"); if( ratio >= 100 ) return "这位" + RANK_D->query_respect(me) + ",您看起来气色很好啊,不像有受伤的样子。"; if( ratio >= 95 ) return "哦....我看看....只是些皮肉小伤,您买包金创药回去敷敷就没事了。"; 作相应的动作 ***************************************** 5. 人物身上的物品和钱 ***************************************** 在create()函数里 __DIR__是一个宏,表示当前目录,提高程序可移植性 carry_object(__DIR__"obj/magic_book"); // 一般的物品 carry_object(__DIR__"obj/spells_book"); carry_object(__DIR__"obj/thin_sword")->wield(); // 武器可以装备 carry_object(__DIR__"obj/pink_cloth")->wear(); // 衣服等可以穿上 carry_object(__DIR__"obj/shoe")->wear(); add_money("gold", 3); // 钱用add_money函数 -- ************************************************************************************** *********************************************************************************** 巫师编程 (2) 5) 人物的属性 (scratch) "id" (string) 人物的识别字,这个字可以用来识别玩家,通常 "id" 跟 "name" 都不直接用 set() 设定,而是用 F_NAME 中的 set_name()。 "title", "nickname", "name" (string) 人物的称号、绰号、与中文姓名。 "age" (int) 人物的年龄,玩家的年龄是由定义在 USER_OB 中的 update_age() 定期更新。 "age_modify" (int) 这个数值会在 update_age() 中被加在人物的年龄上,可以是负数。 "gin", "eff_gin", "max_gin" (int) "kee", "eff_kee", "max_kee" "sen", "eff_sen", "max_sen" 人物的精气神数值及其上限,其中 "gin", "kee", "sen" 请透过定义在 F_DAMAGE 中的 receive_damage() 与 receive_heal() 增减,"eff_gin", "eff_kee", "eff_sen" 则用 receive_wound() 与 receive_curing()。 "str", "cor", "int", "spi", "cps", "per", "kar" (int) 人物的天赋,依序分别为膂力(strength)、胆识(courage)、悟性(intelligence) 、灵性(spirituality)、定力(composure)、容貌(personality)、福缘(karma)。 根据天赋的原意,请勿修改这几个值,也不要依赖这些值做一些相关重大的检查。 "atman", "max_atman" (int) "force", "max_force" "mana", "max_mana" 人物的灵力(atman, AP)、内力(force, FP)、法力(mana, MP) 及其上限,可 以用 set(), add() 直接修改,但是必须自行检查上下限。 "MKS", "PKS" (int) 人物杀死其他生物的统计(MKS = Monster Killing Statistic, PKS = Player Killing Stastics)。 "combat_exp" (int) 人物的实战经验? "score" (int) 人物的综合评价。 "force_factor" (int) 人物 enforce 指定的强度,表示用内力伤人所耗的内力。 "mana_factor" (int) 人物 enchant 指定的强度,表示引出武器魔法效果所耗的法力。 "family" (mapping) 人物的师承门派等记录,当人物拜师或自创门派时就会设定这个属性,其内容 及定义如下: "family_name": 门派名称 "master_name": 师父的中文名字 "master_id": 师父的英文代? "generation": 人物在这个门派中的辈分,1 表示开山始祖,2 表示 第二代弟子....等等。 "title": 人物在这个门派中的地位,如"掌门人"、"左护法"、等 等,刚加入一个新门派的预设值是"弟子"。 "priv": 人物在门派中的权限旗号,一般弟子是 0,掌门人是 -1 "enter_time": 人物加入门派时 time() 所传回的值。 "guilds" (mapping) 人物所参加的各种组织,每个组织有一个 identity 当 key,value 的内容由该组织的相关物件自行定义。 "startroom" (string) 人物 login 时出现的房间,一般都设为公会。 6) 如何建造一个房间 房间是构成这整个世界的要素之一,在此我们提供了一个房间的标准物件来让 所有的房间继承。而如同其他的物件一般,你需要写一个 create() 来设定房间中 的叙述、出口、物品、生物等等。这里,我喜欢说你用 create() 这个函式来赋予 这个房间的属性。一般来说,要建造一个简单的房间,你只要赋予它基本的属性即 可。当然,我们不认为一个区域中几十个房间没有任何的机关或秘密,是个会吸引 玩家一游的好地方。 下面,提到了一些建造房间所需要留意的事项,也会配合一些例子来说明。 一、基本篇 一个基本的房间,要有 short <短叙述> 、 long <长叙述>、 exits <出口> □ 当你在写一个房间的 long <长叙述>时,其格式为: set("long", @LONG 房间的叙述....... LONG "west" : __DIR__"path3", 和 "west" : "/u/d/davidoff/goathill/path3", 是完全一样的。但前者显然在以後目录的搬移上方便的多。而在下面会提到设定房 间中的物品或生物时,也建议采用这种方式写作。 □ item_desc 这是用来设定个别景物的描述,当玩家用 look 这个指令时就会作 用。其格式为: set("item_desc", ([ "景物名称" : "景物叙述", ........... ]); 其中景物叙述可以是字串或是一个 function ,所以你可以利用这个功能加以 变化,当玩家 look 一个景物时,可能看到叙述,也可能发生一些特殊的事件,而 你就可以在被呼叫的函式中写下这些事件。 □ objects 可以让这个房间在每次 reset时载入某些生物或某些物品: set("objects", ([ "物品或生物的档名" : 数量, ........... ]); 如同前面所提到的,建议采用 __DIR__来编写你的路径,而数量则要用整数。 □ 要为这个房间添上门户时,记得前面必须先 #include 。而格式为: create_door("出口方向", "门的名称", "进入方向", 预设状态); 比如说,这里明显的出口有 west、east 和 up。 而你要让西边有一个关上的 红木门,你可以这样写: create_door("west", "红木门", "east", DOOR_CLOSED); 当玩家进入这个房间时,他会看到: 这里明显的出口有 east 和 up。 而当他 look west 时,会看到:这个红木门是关上的。 其他的一些属性,你可以参考 /doc/build/room_prop 或是读一下标准物件的 room.c。也建议你可以多用 more here来观看一间特殊的 room。 二、进阶篇 要让你的区域中富有变化,生动有趣,除了文字叙述的丰富度以外,你更可以 利用 init() 这个函式为你的房间增加一些「机关」或「秘密」。 这里,先让我们了解一下 init() 的用途为何,和为什麽要用到它。每一个房 间的 create() 只有当 reset时才会被呼叫到,而 init() 则是在 B物件进入到 A 物件时都会呼叫到 A物件的 init() 。看到这,你应该可以看出差别了,我们希望 当一个物件(此处较多是玩家)进到一个房间时,能够经由某个动作启动这个房间 的机关的话,自然是利用 init() 来编写。 一般的使用方式,是在 init() 中利用 add_action() 来呼叫你写的函式,? 格式为: add_action("function type", "action"); function type 即是被呼叫的函式名 action 是启动的动作 而你就可以将被 action 启动後要发生的事,都写在被呼叫的函式里面。理论上来说,利用这个方式我们可以做到 任何事,当然,能不能达成就看写程式的功力 了。下面举个简单的例子: void init() add_action("do_pick", "pick"); int do_pick(string arg) object me; me = this_player(); if ( !arg || ( arg != "flower" ) ) return notify_fail("你要摘什麽?\n"); else if ( random((int)me->query("kar")) < 7 ) message_vision("$N将花摘了下来,但一不小心被刺了一下。\n", me); else message_vision("$N摘下一朵美丽的血红色鲜花。\n", me); return 1; 当玩家利用 pick 这个指令时就会呼叫到 do_pick() 这个 function,而启动了 这个房间的机关。 这里特别提到一点,一个简单的 room 我们为了使记忆体的使用量降到最低,会 在 create() 最後加上一行 replace_program(ROOM); 。这是因为在房间的标准物件 中有定义了如 init() 等其他的函式,而一个简单的房间根本没有用到,所以我们用 replace_program() 来将原本的被继承的标准物件「重置」(或说取代)掉,但是一 旦房间中用到了 init() 来编写时,就绝对不可以用 replace_program(),因为系统 届时找不到 init() 便会随便呼叫一个记忆体中的位址而随便传进一些乱七八糟的东 西,情况严重时,甚至可以让整个 mud crash。但是,我们自不可因噎废食,该用的 时候还是要用,这些应该是一个好的程式写作人员自己必须留意的,发生状况要自己 负责。 7) 定义在人物物件中的附加函数(apply functions) 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 指令的生物,如果是对 player 下,"fight" 指令会直接显示讯息徵 求被挑战一方的同意,如果是对 NPC 下这个指令,则当这个附加函数传回 1 时才会接受挑战,否则显示某某不想跟你较量的讯息。 呼叫者: "fight" 指令 有预设定义此一函数的系统物件: NPC int accept_object(object who, object item) 当有人用 give 指令给这个非玩家人物东西时,会呼叫这个附加函数,传回 1 表示愿意接受这个东西,传回 0 表示不接受。 呼叫者: "give" 指令 有预设定义此一函数的系统物件: none void recruit_apprentice(objct apprentice) 当一个人物收了另一个人物做弟子时会呼叫这个附加函数,你可以在这个函数里 修改弟子的 rank 或其他东西。 呼叫者: "apprentice" 指令 有预设定义此一函数的系统物件: none 8) 房间属性 "short" (string) 房间的短叙述。 "long" (string) 房间的长叙述。 "item_desc" (mapping) 房间中个别景物的叙述,格式为:([ <景物名称>:<景物叙述>, .... ])。 其中<景物叙述>可以是字串或 function type。 "exits" (mapping) 房间的出口,包括有门的方向,格式为:([ <出口>:<房间档名>, .... ])。 "objects" (mapping) 房间中的物品、生物,格式:([ <物品或生物档名>:<数量>, .... ])。 "outdoors" (string) 房间是否为「户外」,户外房间可以看到天色变化与气候影响。字串的意义 表示房间的气候区,通常和该区域的 domain (即 /d 下的目录名称) 同。 "no_fight" (int) 房间是为禁止作战区域。 "no_magic" (int) 房间是为禁止施法区域。 9) 物品属性 "name" (string) 物品的中文名称。 "id" (string) 物品的英文名称。 "long" (string) 物品的详细描述。 "value" (int) 物品的价值,单位是「钱」(coin)。 "unit" (string) 物品的单位,如:个、把、枝..... "no_get" (int) 表示物品是否可被捡起来。 "no_drop" (int string) 表示物品是否可被丢弃,如果型态是 string, 则当有人企图丢弃这个物品时 会将该字串用 notify_fail 传回去。 □ 武器属性 "skill_type" (string) 这个武器所属的技能种类,如 "sword"、"blade" 等,要注意在 /d/skill 下必须有一个定义该武器技能的物件,否则装备这个武器战斗时会有错误 讯息。 "rigidity" (int) 武器的硬度,当使用武器相斗时,硬度、武器的重量、持用者的力量将会影 响武器受损的机率。 武器受损机会所用的强度值计算公式如下: 重量/500 + 硬度 + 力量 "weapon_prop" (mapping) 武器对持用者的状态影响,通常武器只影响 "damage",这些状态影响会在 武器被装备时用 add_temp() 加到持用者的 apply 上,并於卸除或 dest 时减回来。 "flag" (int) 武器的一些型态旗标,详见 的定义。 ************************************************************************************** *********************************************************************************** 编程中要注意的小问题 1.一些重要的单词可别写错喔:象create,environment,gender,...... 2.LONG 描述的要点: 这是很重要的,一旦出错就很惨了..... 请注意:long <长叙述>,其格式为: set("long", @LONG room or npc 的叙述....... LONG ); 其中 @LONG 和 LONG 是互相对应的,你可以用任何字接在 @ 後面,但是前 後两个字一定要一样,这样系统才能判别,而房间的叙述写完时,一定要换行後再 ^^^^^^^^^^^^^^^^ 接第二个 LONG ,且同一行不能再有其他任何的字元,不然系统无法判定叙述是否 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 该结束了,会造成编译时的错误。 3.一些结构要有始有终,象函数的{ } 啦,还有mapping([ ]) ,array({ }) 啦, 要是忘记写后面的那个就....... 4.__DIR__ 的用法,一般来说__DIR__ 是指该NPC OR ROOM 的当前目录。所以要弄 什么物件的话就用这个__DIR__的相对目录做好了。 嗯,就想起这么多了,以后再写别的吧。 - ************************************************************************************** *********************************************************************************** wiz 编程杂谈 (1) 关于notifal_fail usage: notofy_fail(string | function str); 请注意:notify_fail() always returns 0. nofify_fail() 的作用是设置本次命令的失败信息。嗯,听起来好像很复杂 :P 其实就是取代“什么?”这句话的东东啦。比如我们打一个命令(包括动作什么的)。 要是此命令的程序return 0 的话,系统就会给个错误信息 “什么?”但假如我们 在命令的程序里面有notify_fail() ,那么假如命令最后返回 0 。那系统就给出 本命令执行的最后一个notify_fail() 所设定的错误信息,否则就是“什么?”。 其中notify 的参数可以是一个str 的function 。就是把这个func 所返回的 string 做错误信息了。 例如:help 命令里的一段: if( stringp(file = me->find_command(arg)) ) { notify_fail("有这个指令存在,但是并没有详细的说明文件。\n"); //在执行notify_fail 的时候并没显示,只是在最后返回0 值时才显示此信息。 return file->help(me); } //要是return file->help(me) == 0 的话。 //就会给出错误信息"有这个指令存在,但是并没有详细的说明文件。\n" //假如没notify_fail 这句,错误信息就是 “什么?” 标 题: room的制作 /doc/build/room □ 如何建造一个房间 房间是构成这整个世界的要素之一,在此我们提供了一个房间的标准物件来让 所有的房间继承。而如同其他的物件一般,你需要写一个 create() 来设定房间中 的叙述、出口、物品、生物等等。这里,我喜欢说你用 create() 这个函式来赋予 这个房间的属性。一般来说,要建造一个简单的房间,你只要赋予它基本的属性即 可。当然,我们不认为一个区域中几十个房间没有任何的机关或秘密,是个会吸引 玩家一游的好地方。 下面,提到了一些建造房间所需要留意的事项,也会配合一些例子来说明。 一、基本篇 一个基本的房间,要有 short <短叙述> 、 long <长叙述>、 exits <出口> □ 当你在写一个房间的 long <长叙述>时,其格式为: set("long", @LONG 房间的叙述....... LONG ); 其中 @LONG 和 LONG 是互相对应的,你可以用任何字接在 @ 後面,但是前 後两个字一定要一样,这样系统才能判别,而房间的叙述写完时,一定要换行後再 接第二个 LONG ,且同一行不能再有其他任何的字元,不然系统无法判定叙述是否 该结束了,会造成编译时的错误。 而为求区域看起来外观上整齐、统一,房间的长叙述中每一行的长度必须一样 ,而一行的长度建议为 29 到 32 个中文字,约占萤幕的三分之二。并且一个房间 的叙述最好不要低於三行,区域各个房间的叙述重复性降到越低越好,这样你的区 域看起来才不会太过阳春。当然,有时候为了某些目的,比如一个迷宫,你可能会 相邻的几个房间都用到一样的叙述,那自然不在此限。 □ 一个房间的出口则以下列格式赋予: set("exits", ([ "方向" : "连接到的房间之档名", ........... ]); 在这里,为了一个以後区域开放後搬移目录的便利性,建议采用__DIR__ 这个 由系统提供的巨集来写路径,比如说: "west" : __DIR__"path3", 和 "west" : "/u/d/davidoff/goathill/path3", 是完全一样的。但前者显然在以後目录的搬移上方便的多。而在下面会提到设定房 间中的物品或生物时,也建议采用这种方式写作。 □ item_desc 这是用来设定个别景物的描述,当玩家用 look 这个指令时就会作 用。其格式为: set("item_desc", ([ "景物名称" : "景物叙述", ........... ]); 其中景物叙述可以是字串或是一个 function ,所以你可以利用这个功能加以 变化,当玩家 look 一个景物时,可能看到叙述,也可能发生一些特殊的事件,而 你就可以在被呼叫的函式中写下这些事件。 □ objects 可以让这个房间在每次 reset时载入某些生物或某些物品: set("objects", ([ "物品或生物的档名" : 数量, ........... ]); 如同前面所提到的,建议采用 __DIR__来编写你的路径,而数量则要用整数。 □ 要为这个房间添上门户时,记得前面必须先 #include 。而格式为: create_door("出口方向", "门的名称", "进入方向", 预设状态); 比如说,这里明显的出口有 west、east 和 up。 而你要让西边有一个关上的 红木门,你可以这样写: create_door("west", "红木门", "east", DOOR_CLOSED); 当玩家进入这个房间时,他会看到: 这里明显的出口有 east 和 up。 而当他 look west 时,会看到:这个红木门是关上的。 其他的一些属性,你可以参考 /doc/build/room_prop 或是读一下标准物件的 room.c。也建议你可以多用 more here来观看一间特殊的 room。 二、进阶篇 要让你的区域中富有变化,生动有趣,除了文字叙述的丰富度以外,你更可以 利用 init() 这个函式为你的房间增加一些「机关」或「秘密」。 这里,先让我们了解一下 init() 的用途为何,和为什麽要用到它。每一个房 间的 create() 只有当 reset时才会被呼叫到,而 init() 则是在 B物件进入到 A 物件时都会呼叫到 A物件的 init() 。看到这,你应该可以看出差别了,我们希望 当一个物件(此处较多是玩家)进到一个房间时,能够经由某个动作启动这个房间 的机关的话,自然是利用 init() 来编写。 一般的使用方式,是在 init() 中利用 add_action() 来呼叫你写的函式,其 格式为: add_action("function type", "action"); function type 即是被呼叫的函式名 action 是启动的动作 而你就可以将被 action 启动後要发生的事,都写在被呼叫的函式里面。理论 上来说,利用这个方式我们可以做到任何事,当然,能不能达成就看写程式的功力 了。下面举个简单的例子: void init() { add_action("do_pick", "pick"); } int do_pick(string arg) { object me; me = this_player(); if ( !arg || ( arg != "flower" ) ) return notify_fail("你要摘什麽?\n"); else if ( random((int)me->query("kar")) < 7 ) message_vision("$N将花摘了下来,但一不小心被刺了一下。\n", me); else message_vision("$N摘下一朵美丽的血红色鲜花。\n", me); return 1; } 当玩家利用 pick 这个指令时就会呼叫到 do_pick() 这个 function,而启动了 这个房间的机关。 这里特别提到一点,一个简单的 room 我们为了使记忆体的使用量降到最低,会 在 create() 最後加上一行 replace_program(ROOM); 。这是因为在房间的标准物件 中有定义了如 init() 等其他的函式,而一个简单的房间根本没有用到,所以我们用 replace_program() 来将原本的被继承的标准物件「重置」(或说取代)掉,但是一 旦房间中用到了 init() 来编写时,就绝对不可以用 replace_program(),因为系统 届时找不到 init() 便会随便呼叫一个记忆体中的位址而随便传进一些乱七八糟的东 西,情况严重时,甚至可以让整个 mud crash。但是,我们自不可因噎废食,该用的 时候还是要用,这些应该是一个好的程式写作人员自己必须留意的,发生状况要自己 负责。 三、建议 这里我们提供了一个工具来让巫师们可以方便的编写一个房间,那就是房间编辑 器(Roommaker) ,你可以 clone /obj/roommaker 来使用它。一般的步骤是,先利用 mkroom来造一个空房间,然後利用 goto 这个指令到房间里去,再用 rset short 和 connect 来设定这个房间的短叙述及出口,而像其他的一些属性例如 outdoors 等等 也都可以利用他来做到,接著用 to rset long 来设定这个房间的长叙述,最後再用 saveroom将这个房间存档。要是这只是一个基本的房间,那到这里就大功告成了,要 是□想让这个房间富有变化,那就再用线上编辑器 edit 或将这个房间的档案 ftp 回去继续修改。 Subject: room的属性 (fwd) /doc/build/room_pro □ 房间属性 "short" (string) 房间的短叙述。 "long" (string) 房间的长叙述。 "item_desc" (mapping) 房间中个别景物的叙述,格式为:([ <景物名称>:<景物叙述>, .... ])。 其中<景物叙述>可以是字串或 function type。 "exits" (mapping) 房间的出口,包括有门的方向,格式为:([ <出口>:<房间档名>, .... ])。 "objects" (mapping) 房间中的物品、生物,格式:([ <物品或生物档名>:<数量>, .... ])。 "outdoors" (string) 房间是否为「户外」,户外房间可以看到天色变化与气候影响。字串的意义 表示房间的气候区,通常和该区域的 domain (即 /d 下的目录名称) 同。 "no_fight" (int) 房间是为禁止作战区域。 "no_magic" (int) 房间是为禁止施法区域。 ************************************************************************************** *********************************************************************************** 1。LPC入门(上) LPC就是我们用来写MUD的语言啦,它的语法和C 基本一样。它独特之处在于有简单的 OOP特性(简单但很有用:PP),还有一个C里面没有的HASH表的类型:MAPPING LPC和C还有一个不同是其主函数是CREATE()而不是MAIN()。create()别写错哦:PP LPC里面主要的(也是写MUD所足够的)数据类型有int,string,mapping,object, mixed。下面主要讲一下这些类型了。 因为整数类型对于写MUD已经足够,所以不用FLOAT了。同样的,因为我们的 汉字是双字节的,所以CHAR类型其实也没用,只需要STRING 类型就可以了。 STRING的定义,这里要提一下:string常量的赋值,假如是常量的话可以只用 “连接”的办法代替string,例如: string str = "我" "们" ;那么结果是str == "我们" 同样的str = "我" "们";也是一样,空格与换行在LPC编译时是被忽略的,所以我们 写MUD的时侯不仿多些TAB和换行,这样程序容易看些。 当然,除了直接连接之外还可以用 + 连接。在有变量的时侯就要用了。例如: string str0 = "我们"; string str1 = "和"+str0+"大家"; 结果是str1 == "和我们大家" mixed是一个比较特别的类型。mixed 类型的变量可以赋任何其他类型的值。 这在未知变量类型的时侯非常有用。不过一般情况下很少会用到mixed。 LPC里面没有“指针”的概念。在变量名前面加 * 的定义表示数组。如int *a 表示a 是整数数组。一般来说我们定义数组时是未知其大小的。所以定义时不用象 C 那样给定大小。数组和MAPPING有些类似,所以将在下面和MAPPING一起讲它们 的操作。 ************************************************************************************** ********************************************************************************* LPC入门 (中) LPC里面没有“指针”的概念。在变量名前面加 * 的定义表示数组。如int *a 表示a 是整数数组。一般来说我们定义数组时是未知其大小的。所以定义时不用象 C 那样给定大小。数组和MAPPING有些类似,所以将在下面和MAPPING一起讲它们 的操作。 object, 是OOP概念了,在LPC里面好象有CREATE()的都可以做object 类型变量 了。object 我们称为“对象”,在MUD里就是一件物品,一个房间或任何一个“具 体”的东西,都是OBJECT。“对象”,在OOP中是一些数据与基于这些数据的函数的 集合(好象文诌诌的? :PP)嗯,object 中的数据一般不能直接操作(至少在LPC 里面不能的 :PP),所以对object的操作只有赋值(对object类型的赋直 )和执行 object的函数( private函数不能被外部调用 )。 函数调用格式: eg. object ob; ob->test(1,2); 基本格式: object类型变量名->函数名(函数参数列); 其中若该object 中未定义该函数名的函数则返回 0 值(呵呵,不会有出错信息的哦, 所以千万别写错名字了)。 写了这么多终于写到LPC 最有特色的两个类型了,mapping和数组。 mapping和数组在“外观”上有些类似,所以在一起写了。前面提到过:mapping是 散列表,具体如何这里不详述了,只希望大家一定要记住mapping的格式!!( 实际 上这格式只在给变量赋初值时用到 )mapping 格式如下: eg. mapping a = ([ "ab" : 1 , "cd": 2 , ]) 标准格式: mapping 变量名 = ([ key1 : value1 , key2 : value2 , .......... .......... ]); 其中key可以是除了int以外的任何值!! (好象char也不行吧 :P ),包括任何数组 甚至mapping也可以做key ,而value则可以是任意所有值。mapping的应用在于: 可以用key来index得到相应的value值。如上面eg的,我们有:a["cd"] == 2,.... 因为要用key来index的关系,在mapping中key值不能相等,若相等则覆盖掉原来该 key对应的value。写的有些乱了,SORRY :P 那么一个mapping怎么改变它的值呢?有两个方法(下面设mapping map )。 1. map[key] = value; 2. map += ([key : value ]); 请大家留意第二种赋值方法,它跟我们将要提到的数组的方法一样的 。 在mapping中“删除”一个key (其相应value当然也没了)方法是: map_delete(map,key); lpc里面的系统函数(efun)的命名一般按参数顺序来取名( map_delete的参数就是 map在前,被delete的key在后的 ),希望大家注意。 另外mapping的efun还有values,keys,undefinedp 大家可以直接在MUD里 help efunname 来看帮助,也可以参阅 /doc/lpc/types/mapping 这一文件