使用MUDCORE框架从零开发LPMUD游戏:基础NPC功能

《使用MUDCORE框架从零开发LPMUD游戏》系列内容,通过教程既能熟悉 MUDCORE 框架的使用,也能学习LPCMUD游戏的开发,版权所有 mud.ren。

上一节我们实现了游戏系统的职业和等级模块,本节我们来开发基础的NPC功能,增加一些天使到天使界,让天使界不再空无一物。

请注意,本节内容一样是游戏功能,非MUDCORE框架提供,所以需要自己独立实现。

NPC是生物,所以要继承我们前面实现的生物公共模块LIVING,但是NPC也会有一些自己特别的公共功能,比如NPC的战斗、行动等,为此我们单独实现一个NPC公共模块NPC,对应代码文件inherit/npc.c,目前内容如下:

/*****************************************************************************
Copyright: 2019, Mud.Ren
File name: npc.c
Description: 非玩家角色公共接口:NPC/魔物等
Author: xuefeng
Version: v1.0
Date: 2019-03-14
History:
*****************************************************************************/
inherit CLEAN_UP;
inherit LIVING;

int is_npc() { return 1; }

/**
 * reset时回到起始环境
 */
int return_home(object home)
{
    if (!environment() || environment() == home)
        return 1;

    if (!living(this_object()) || !mapp(environment()->query("exits")))
        return 0;

    message("vision", this_object()->name() + "急急忙忙地离开了。\n",
            environment(), this_object());

    return move(home);
}

/**
 * 非战斗状态的行为
 */
// 说话
void chat(string msg)
{
    command("say " + msg);
}
// 随机移动
void random_move()
{
    mapping exits;
    string *dirs;

    if (!mapp(exits = environment()->query("exits")) || !sizeof(exits))
        return 0;
    dirs = keys(exits);
    command("go " + dirs[element_of(dirs)]);
}
/**
 * 战斗状态的行为
 */
// 使用咒文
void cast_spell(string spell)
{
    command("cast " + spell);
}
// 使用特技
void exert_skill(string skill)
{
    command("exert " + skill);
}
/**
 * NPC行为控制
 */
void action()
{
    // todo 行为控制,如战斗行为
}

// 触发任务提示
varargs void greeting(object ob, object me)
{
    ob = ob || this_player();
    me = me || this_object();
    if ((environment(me)->is_area() && !area_environment(ob, me)) || environment(ob) != environment(me))
        return;
    msg("info", "$ME对$YOU说到:你好呀,旅行者,我这里需要你的帮助。(提示:ask " + me->query("id") + ")", me, ob);
}

void init()
{
    object ob = this_player(), me = this_object();
    // debug_message(sprintf("init: %O -> %O", ob, me));
    if (interactive(ob) && QUEST_D->hasQuest(ob, me))
    {
        remove_call_out("greeting");
        call_out("greeting", 1, ob, me);
    }
}

注意:这里实现的功能比较多,实际开发中刚建的模块应该没什么能力,只有在开发到需要的功能时再完善,这里只是演示,所以把后面计划开发的功能直接都一次预留了,其中包括了任务和战斗相关的接口。

然后在include/inherits.h中增加NPC模块的宏定义NPC

diff --git a/include/inherits.h b/include/inherits.h
index 232bdf9..1dc4526 100644
--- a/include/inherits.h
+++ b/include/inherits.h
@@ -12,6 +12,7 @@
 #define MESSAGE     "/inherit/message"
 #define MOVE        "/inherit/move"
 #define NAME        "/inherit/name"
+#define NPC         "/inherit/npc"
 #define ROOM        "/inherit/room"
 #define SAVE        "/inherit/save"
 #define USER        "/inherit/user"

注意:这里使用git diff功能生成的补丁,显示改变的代码,方便演示,学习时可以使用git apply直接把diff更新自己的代码,如把以上代码复制到你项目下inherits.diff文件中,然后指令git apply inherits.diff可自动修改代码,后面对项目代码的改变也都用diff的方式显示。

下面我们更新以前实现的标准NPC对象std/living/npc.c,完善代码如下:

diff --git a/std/living/npc.c b/std/living/npc.c
index 667e9c3..34388e4 100644
--- a/std/living/npc.c
+++ b/std/living/npc.c
@@ -1 +1,48 @@
 // 标准NPC对象模板
+
+inherit NPC;
+
+varargs void create(string vocation, int lvl)
+{
+    mixed level_info;
+    if (!stringp(vocation))
+    {
+        vocation = "common";
+    }
+    if (lvl < 1)
+    {
+        lvl = 1;
+    }
+    if (lvl > 99)
+    {
+        lvl = 1 + random(99);
+    }
+
+    level_info = LEVEL_D->level_info(vocation);
+
+    set_name("小女孩", ({"girl"}));
+    set("long", "这是一个小女孩,一直梦想长大后成为托拉佩塔城公主的侍女。");
+    set("gender", "女性");
+    set("vocation", vocation);
+    set("lv", lvl);
+    set_attr("str", level_info[lvl][0]);
+    set_attr("agi", level_info[lvl][1]);
+    set_attr("vit", level_info[lvl][2]);
+    set_attr("luk", level_info[lvl][3]);
+    set_attr("charm", level_info[lvl][4]);
+    set_attr("mend", level_info[lvl][5]);
+    set_attr("int", level_info[lvl][6]);
+    set_attr("max_hp", level_info[lvl][7]);
+    set_attr("max_mp", level_info[lvl][8]);
+    // set("exp", 10);
+    // set("coin", 2);
+    set("no_get", "你想绑架啊?\n");
+    set("no_fight", 1);
+    set("action", ([        // 随机行为
+        "spell" : ({ }),    // 可用咒文列表
+        "skill" : ({ }),    // 可用特技列表
+        "msg" : ({ }),      // 随机说话内容
+        "chance" : 0,       // 行为机率,数值越大机率越小
+    ]));
+    setup();
+}

标准对象是基本功能完善的对象,可直接继承使用并根据需要扩展功能,所有具体对象都应该继承自标准对象。这个STD_NPC会根据职业和等级自动取对应的属性并设置给NPC,同时示例了action属性,将来在NPC模块中实现相关功能。

下面我们实现具体的天使代码,在world目录中新建npc/angel.c,代码如下:

inherit STD_NPC;

private create()
{
    string *vocation_list = ({
        "warrior",
        "priest",
        "mage",
        "martial-artist",
        "thief",
        "minstrel",
        "gladiator",
        "paladin",
        "armament-alist",
        "ranger",
        "sage",
        "luminary",
    });

    ::create(element_of(vocation_list), 100);
    set_name("天使", ({"angel"}));
    set("long", "这是一位守护世界的天使。");
    set("gender", random(2) ? "男性" : "女性");
}

这里代码比较简单,因为具体功能在继承对象中已实现,代码只是列出了可用职业和描述,随机生成不同职业和等级的NPC。

下面修改出生地代码,在游戏中增加NPC:

diff --git a/world/start_room.c b/world/start_room.c
index d73085d..841629b 100644
--- a/world/start_room.c
+++ b/world/start_room.c
@@ -26,3 +26,15 @@ varargs private void create(int x, int y, int z)

     setArea(0, x, y, z);
 }
+
+void virtual_start()
+{
+    if (!random(3))
+    {
+        set("objects", ([
+            WORLD_DIR "npc/angel":random(3),
+        ]));
+    }
+
+    setup();
+}

现在我们进入游戏应该在天使界能随机看到一些天使了,不过因为look指令没完善,看不到细节,现在完善一下指令,可以看NPC的职业和等级,代码更新如下:

diff --git a/cmds/std/look.c b/cmds/std/look.c
index 5917fae..83b6912 100644
--- a/cmds/std/look.c
+++ b/cmds/std/look.c
@@ -1,5 +1,7 @@
 #include <ansi.h>

+#define SC_CMD "/cmds/std/score"
+
 int look_room(object me, object env);
 int look_item(object me, object obj);
 int look_living(object me, object obj);
@@ -72,20 +74,118 @@ int look_item(object me, object obj)
     return 1;
 }

+string look_equiped(object me, object ob)
+{
+    mixed *inv = all_inventory(ob);
+    string str = "", subs = "";
+    string pronoun;
+    mapping equip_info;
+    int i, n = 0;
+
+    for (i = 0; i < sizeof(inv); i++)
+    {
+        if (inv[i]->query_temp("equipped"))
+        {
+            equip_info = inv[i]->query("equip_info");
+            switch (equip_info["type"])
+            {
+            case "accessory":
+                n++;
+                subs += HIC "  [饰品]" NOR + inv[i]->short() + "\n";
+                break;
+            case "armor":
+                n++;
+                subs += HIC "  [上身]" NOR + inv[i]->short() + "\n";
+                break;
+            case "bracer":
+                n++;
+                subs += HIC "  [护腕]" NOR + inv[i]->short() + "\n";
+                break;
+            case "helmet":
+                n++;
+                subs += HIC "  [头部]" NOR + inv[i]->short() + "\n";
+                break;
+            case "shield":
+                n++;
+                subs += HIC "  [盾牌]" NOR + inv[i]->short() + "\n";
+                break;
+            case "shoe":
+                n++;
+                subs += HIC "  [脚部]" NOR + inv[i]->short() + "\n";
+                break;
+            case "trouser":
+                n++;
+                subs += HIC "  [腿部]" NOR + inv[i]->short() + "\n";
+                break;
+            case "weapon":
+                n++;
+                subs += HIC "  [武器]" NOR + inv[i]->short() + "\n";
+                break;
+            default:
+                break;
+            }
+        }
+    }
+    if (me == ob)
+    {
+        pronoun = pronoun(2, me);
+    }
+    else
+    {
+        pronoun = pronoun(3, ob);
+    }
+    str += pronoun + "是一位 " + ob->query("lv") + " 级的" + chinese(ob->query("vocation")) + ",";
+    if (n)
+    {
+        str += pronoun + "装备着:\n" + subs;
+    }
+    else if (ob->is_npc())
+    {
+        str += pronoun + "的装备你看不出来什么属性。";
+    }
+    else
+    {
+        str += pronoun + "没有装备任何物品。\n";
+    }
+    return str;
+}
+
 int look_living(object me, object ob)
 {
-    // 先简单实现,后期扩展功能
-    string msg;
-    if (ob == me)
+    string msg, equip_msg;
+
+    msg = "名称:" + ob->short() + "\n";
+    msg += "说明:" + ob->query("long") + "\n";
+
+    if (ob->is_mob())
     {
+        tell_object(me, sort_string(msg, 72, 6));
         return 1;
     }
+
+    equip_msg = look_equiped(me, ob);
+
+    if (ob->is_npc())
+    {
+        msg += "      " + equip_msg;
+        tell_object(me, sort_string(msg, 72, 6));
+        return 1;
+    }
+
+    if (ob == me)
+    {
+        tell_object(me, equip_msg);
+    }
     else
     {
         msg = "$ME看了看$YOU,好像对$YOU很感兴趣对样子。";
-
+        msg("vision", msg, me, ob);
+        tell_object(me, equip_msg);
+        if (wizardp(me))
+        {
+            tell_object(me, SC_CMD->score(ob));
+        }
     }
-    msg("vision", msg, me, ob);

     return 1;
 }

至此,代码功能完善,提示一下,因为mudcore更新,移除了look指令别名,请自己增加内容为look的别名cmds/std/l.alias

下面,在游戏中看看NPC,是不是显示了职业和等级?

天使界 - /world/start_room/-5,0,0
    这里是位于星空深处的天使界,是守护世界的天使们居住的地方,据说女神就在天使界的
某地沉睡。
    这里明显的出口是 south、north、east、west 和 up。
    2个天使(Angel)
l a
名称:天使(Angel)
说明:这是一位守护世界的天使。
      他是一位 82 级的圣骑士,他的装备你看不出来什么属性。
l a 2
名称:天使(Angel)
说明:这是一位守护世界的天使。
      她是一位 67 级的魔法战士,她的装备你看不出来什么属性。

因为score指令未完善,没有看NPC属性的能力,不过我们可以使用data指令看看不同NPC的属性,如:

data a
Object: /world/npc/angel.c
action          : ([ /* sizeof() == 4 */
  "chance" : 0,
  "spell" : ({ }),
  "msg" : ({ }),
  "skill" : ({ }),
]) 
attr            : ([ /* sizeof() == 11 */
  "mend" : 137,
  "luk" : 8,
  "mp" : 146,
  "str" : 296,
  "max_mp" : 146,
  "agi" : 61,
  "vit" : 301,
  "hp" : 502,
  "max_hp" : 502,
  "int" : 0,
  "charm" : 97,
]) 
gender          : "男性" 
home            : "/world/start_room/-5,0,0" 
id              : "angel" 
long            : "这是一位守护世界的天使。" 
lv              : 82 
name            : "天使" 
no_fight        : 1 
no_get          : "你想绑架啊?
" 
race            : "人类" 
vocation        : "paladin" 

总共有 12 个储存的资料。

TEMP DATA :

living          :  1 

总共有 1 个暂存的资料。

data a 2
Object: /world/npc/angel.c
action          : ([ /* sizeof() == 4 */
  "chance" : 0,
  "spell" : ({ }),
  "msg" : ({ }),
  "skill" : ({ }),
]) 
attr            : ([ /* sizeof() == 11 */
  "mend" : 0,
  "luk" : 76,
  "mp" : 157,
  "str" : 243,
  "max_mp" : 157,
  "agi" : 120,
  "vit" : 168,
  "hp" : 374,
  "max_hp" : 374,
  "int" : 195,
  "charm" : 128,
]) 
gender          : "女性" 
home            : "/world/start_room/-5,0,0" 
id              : "angel" 
long            : "这是一位守护世界的天使。" 
lv              : 67 
name            : "天使" 
no_fight        : 1 
no_get          : "你想绑架啊?
" 
race            : "人类" 
vocation        : "armament-alist" 

总共有 12 个储存的资料。

TEMP DATA :

living          :  1 

总共有 1 个暂存的资料。

最后 commit 我们当前版本:

git add .
git commit -m 'v3.2'

本节代码

附本节版本源码下载地址,如果下载使用,请在项目目录执行 git submodule update --init

京ICP备13031296号-4