改造MUD使用数据库存档的基础示例

MUD游戏玩家档案的存取都是使用save_objectrestore_object这二个efun,存档为文本格式,使用非常快捷方便的。

fluffos也支持mysql、sqlite等数据库的,我们也可以把玩家存档使用数据库来保存,不过个人并不推荐用数据库存档。因为文本存档完全满足游戏的需求,而改用MySQL数据库存档并不灵活,还得配置数据库服务,但是如果要和web互交或所有玩家数据的相关批量处理,使用数据库存档是有优势的。

我个人推荐的方式是文本存档和数据库存档结合使用,玩家的游戏数据存档正常使用文本存档,而需要网页互交和批量处理的数据使用数据库存档。而且对这些数据可以直接使用sqlite数据库,使用上即有文本数据库的简便,又有专业数据库的操作优势,毕竟MUD游戏存档的数据量并不大,sqlite完全能满足需求,是个人感觉的最佳解决方案。

本站演示的炎黄玩家英雄榜就是使用sqlite数据库保存玩家的基本资料,这里给基础示例,方便新手开发者。

数据存档守护进程CACHE_D

因为是数据同步存到数据库,我们新建一个守护进程来管理配置,这里我宏定义为CACHE_D,文件/adm/daemons/cached.c,代码如下:

/**
 * 玩家存档缓存接口
 * 缓存玩家数据到db.sqlite,方便网页、全服排行等调用
 */
#ifdef __USE_SQLITE3__
// 初始化数据库
mixed init_db();
// 玩家数据缓存接口
mixed insert(object user, int last_touched);
// 玩家数据更新接口
mixed update(object user);
// 玩家数据删除接口
mixed delete(object user);

nosave object db;

void create()
{
    db = new (DATABASE, "", "/data/db.sqlite", "", __USE_SQLITE3__);
}

mixed init_db()
{
    mixed res;

    db->sql("DROP TABLE IF EXISTS `users`")->exec();
    res = db->sql("CREATE TABLE IF NOT EXISTS `users` (
        `id` VARCHAR(10) PRIMARY KEY NOT NULL,
        `name` VARCHAR(10) NOT NULL,
        `title` VARCHAR(50) DEFAULT NULL,
        `master` VARCHAR(10) DEFAULT NULL,
        `mobile` INTEGER DEFAULT NULL,
        `age` INTEGER DEFAULT NULL,
        `qi` INTEGER DEFAULT NULL,
        `jing` INTEGER DEFAULT NULL,
        `neili` INTEGER DEFAULT NULL,
        `jingli` INTEGER DEFAULT NULL,
        `combat_exp` INTEGER DEFAULT NULL,
        `kill` INTEGER DEFAULT NULL,
        `die` INTEGER DEFAULT NULL,
        `updated_at` INTEGER DEFAULT NULL) ")->exec();

    if (stringp(res))
    {
        env("CACHE_DATA", 0);
    }

    return res;
}

mixed insert(object user, int last_touched)
{
    mixed res;
    mapping my = user->query_entire_dbase();
    string master = mapp(my["family"]) ? my["family"]["master_name"] : "";
    int kill = 0, die = 0;

    if (mapp(my["combat"]))
    {
        kill = my["combat"]["MKS"] + my["combat"]["PKS"];
        die = my["combat"]["dietimes"];
    }

    res = db->table("users")->insert(([
        "id"         : my["id"],
        "name"       : my["name"],
        "title"      : my["title"],
        "mobile"     : my["mobile"],
        "age"        : my["age"],
        "qi"         : my["max_qi"],
        "jing"       : my["max_jing"],
        "neili"      : my["max_neili"],
        "jingli"     : my["max_jingli"],
        "combat_exp" : my["combat_exp"],
        "master"     : master,
        "kill"       : kill,
        "die"        : die,
        "updated_at" : last_touched,
    ]));

    return res;
}

mixed update(object user)
{
    mixed res;
    mapping my = user->query_entire_dbase();
    string master = mapp(my["family"]) ? my["family"]["master_name"] || "" : "";
    int kill = 0, die = 0;

    if (mapp(my["combat"]))
    {
        kill = my["combat"]["MKS"] + my["combat"]["PKS"];
        die = my["combat"]["dietimes"];
    }

    res = db->table("users")->where("id", my["id"])->update(([
        "name"       : my["name"],
        "title"      : my["title"],
        "mobile"     : my["mobile"],
        "age"        : my["age"],
        "qi"         : my["max_qi"],
        "jing"       : my["max_jing"],
        "neili"      : my["max_neili"],
        "jingli"     : my["max_jingli"],
        "combat_exp" : my["combat_exp"],
        "master"     : master,
        "kill"       : kill,
        "die"        : die,
        "updated_at" : time(),
    ]));

    return res;
}

mixed delete(object user)
{
    mixed res;

    res = db->table("users")->where("id", user->query("id"))->delete ();

    return res;
}
#endif

代码很简单,主要是使用了我封装的数据库接口DATABASE,这个文件我已经集成在fluffos项目的std代码中,需要的可以直接下载使用并根据自己的需要修改,接口使用教程参考:https://bbs.mud.ren/threads/156

代码中数据存档的位置为/data/db.sqlite,主要保存玩家的姓名、年龄、精、气、内力、经验等基本数据。你可以根据自己的需要修改CACHE_D,比如增加数据查询相关方法。

存档管理指令cache

为方便现有MUD中玩家数据缓存,我们写一个管理指令跑一次,指令/cmds/adm/cache.c,代码如下:

// cache.c
#include <ansi.h>

inherit F_CLEAN_UP;

private void search_dir(object me);
private void examine_player(string name, int last_touched);

void create() { seteuid(getuid()); }

int main(object me, string arg)
{
    if (!SECURITY_D->valid_grant(me, "(admin)"))
        return 0;

    if (arg != "-all")
        return notify_fail("指令格式:cache -all\n");

    message_system("系统进行数据处理中,请耐心等候...\n");
    write(HIG "系统开始缓存所有玩家基本数据...\n" HIG "进度:" + process_bar(0) + "\n");

    search_dir(me);

    return 1;
}

private void search_dir(object me)
{
    string *dir;
    string name;
    mixed *ppls;
    int count;
    int total;
    int i;
    int j;

    if (!is_root(previous_object()))
        return 0;
    // 初始化数据库
    CACHE_D->init_db();
    dir = get_dir(DATA_DIR + "login/");

    count = 0;
    total = 0;
    for (i = 0; i < sizeof(dir); i++)
    {
        ppls = get_dir(DATA_DIR + "login/" + dir[i] + "/", -1);
        for (j = 0; j < sizeof(ppls); j++)
        {
            reset_eval_cost();
            if (sscanf(ppls[j][0], "%s.o", name) == 1)
            {
                examine_player(name, ppls[j][2]);
                count++;
            }
        }
        total += j;
        message("system", ESC + "[1A" + ESC + "[256D" HIG "进度:" + process_bar((i + 1) * 100 / sizeof(dir)) + "\n",
                me ? me : filter_array(all_interactive(), (: wizardp :)));
    }
}

private void examine_player(string name, int last_touched)
{
    object login_ob;
    object user_ob;
    int online;
    mixed *st;

    if (!last_touched)
    {
        st = stat(DATA_DIR + "login/" + name[0..0] + "/" + name + __SAVE_EXTENSION__);

        if (!arrayp(st) || sizeof(st) < 3)
            // 可能没有这个文件
            return;

        last_touched = st[1];
    }

    login_ob = new (LOGIN_OB);
    login_ob->set("id", name);

    if (!login_ob->restore())
    {
        destruct(login_ob);
        return;
    }

    if (login_ob->query("id") != name)
    {
        destruct(login_ob);
        return;
    }

    if (!objectp(user_ob = find_player(name)))
    {
        online = 0;
        user_ob = LOGIN_D->make_body(login_ob);
        if (!user_ob)
        {
            destruct(login_ob);
            return;
        }

        if (!user_ob->restore())
        {
            destruct(login_ob);
            destruct(user_ob);
            return;
        }
    }
    else
        online = 1;

    CACHE_D->insert(user_ob, last_touched);

    if (!online)
    {
        destruct(user_ob);
    }
    destruct(login_ob);
}

int help(object me)
{
    write(@HELP
指令格式:cache -all

缓存玩家数据到·data/db.sqlite·

HELP
    );
    return  1;
}

这个指令只需要跑一次即可缓存游戏中所有玩家的存档到/data/db.sqlite数据库,后期不需要重复执行。后面我们使用玩家存档来自动保存更新到这个数据库中。

新玩家入库

修改/adm/daemons/logind.cinit_new_player方法,在此方法中加入代码CACHE_D->insert(user, time());,这样当有新玩家注册时会自动保存到数据库中。

老玩家存档

修改/clone/user/user.csave方法,在此方法中加入代码CACHE_D->update(me);,这样玩家存档时会自动更新资料到数据库中。

存档删除

修改/adm/daemons/updated.cclear_user_data方法,在此方法中加入代码CACHE_D->delete(ob);,这样当有玩家自杀或被purge时会调用此方法删除数据库中的存档。

至此,玩家存档到数据库的功能实现完成,读取这个数据库我们就很容易的开发网页的功能来实现在线排行榜了,也可以很轻松的在游戏中实现全服玩家排行榜功能,而对玩家数据统计、finger查询等功能也可以优化到从数据库处理,这里不再演示。

京ICP备13031296号-4