关于PlayerBot和NPCBot的一些区别
在魔兽私服游戏中,PlayerBot是召唤出自己账号名下的其他角色(或者说自己的小号),可以一起接任务,一起交任务,一起抢装备,一起升级,实际上就和多开一样的,但是他们能自动和你一起打怪接任务,很好玩。
而NPCBot是召唤出系统预定的一些NPC,这些NPC的等级会自动和你自己的角色的等级一样,这些属于临时雇佣性质,可以给他穿上装备,选择天赋,设定职责。你不需要也可以解雇他,你给他穿的装备会退回到你的背包中。然后别人也就可以雇佣它了。
还有一个很大的区别是,在副本中,很多BOSS不都是有点名嘛,例如ICC老1的骨刺,NPCBot是不会被点名的,而PlayerBot却是会被点名的。所以NPCBot和PlayerBot各有特点,看个人喜欢用哪个,从智能角度来讲,NPCBot更智能一些。我升级的时候喜欢用PlayerBot,毕竟都是自己的账号,都可以一起接任务,交任务,是自己人,而NPCBot只能叫雇佣兵。所以虽然有了NPCBot,我依旧想把PlayerBot集成到服务器中。

关于PlayerBot远程发呆不能使用技能的问题
最开始用TrinityCore源码编译服务器的时候,当时我用的中文客户端地图解压出的dbc,map,mmap,vmap等游戏数据,发现PlayerBot远程发呆不能使用技能,近战也只是平砍。开始时有些懵逼,后来网上一搜说用英文版的客户端导出游戏数据就好了。一实践确实解决这个问题。但是也发现带来了其他一些不好的体验,毕竟中文的客户端上显示一堆的英文的东西也不是很爽,比如对Playerbot使用spells指令查看他的所有可用法术,显示的全都是英文,看起来太费劲了。后来换成AzerothCore源码后依旧有同样的问题。

关于该问题的解决方案
咱的原则是先解决能用,后解决好用的问题。服务器搭好了也能玩了,就开始琢磨解决这个问题了。看了PlayerBot的源码,各种Trigger使用的都是技能的英文名称去施法的,而不是法术ID,用中文客户端导出的dbc数据中法术名称都是中文的,所以肯定就找不到相应的法术了。知道了问题就很好改了,不就是把法术的英文名称在SpellInfo数据结构中正确加载进去的问题么。
1、首先还是使用中文客户端(我用的是3.3.5a 12340版本)导出数据,服务器端使用该份数据,命名为data。
2、然后使用英文版客户端再导出数据,命名为data_en。
3、找了一个WDBXEditor的工具,这个可自行上网下载。.net写的,自己用vs2022编译运行既可以。
4、使用WDBXEditor打开data下的dbc目录下的Spell.dbc,将这份数据导出成一个csv格式的,然后再手动把这个csv文件导入到数据库spell_dbc_zh中。(题外话,数据库中core_world中有个spell_dbc表是额外扩充的一些法术,你可以看到里面只有4000多条记录,而spell_dbc_zh表中有49839条记录。DBCStores.cpp会把data/dbc/Spell.dbc数据加载进去后,再把spell_dbc中的数据合并进去)
5、使用WDBXEditor打开data_en下的dbc目录下的Spell.dbc,将这份数据导出成一个csv格式的,然后再手动把这个csv文件导入到数据库spell_dbc_en中。
6、创建一个spell_locale表:
CREATE TABLE spell_locale (
ID int unsigned NOT NULL DEFAULT '0',
locale varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
Name varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
NameSubtext varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (ID,locale)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
2023-10-04T10:14:22.png
7、把spell_dbc_zh表的Name, NameSubtext插进去,locale设置为zhCN。NameSubtext字段是Rank等级说明,例如法师的法术:寒冰箭(等级2),Name中存储的是“寒冰箭”,NameSubtext存储的是“等级2”。
insert into spell_locale(ID, locale, Name, NameSubtext) select ID, 'zhCN', Name_Lang_deDE, NameSubtext_Lang_deDE from spell_dbc_zh;
咦,这里为啥是Name_Lang_deDE 而不是Name_Lang_zhCN字段呢。这就跟结构体的加载机制有关了,其实在DBCStores中加载xxx_dbc表数据时,表字段的名称是没有意义的,而是根据结构体的结构和表的相应字段顺序依次加载,这里就不展开说了。而zhCN在LocalConstant中的值是4,也就是Name_开头字段的第五个,正好就是Name_Lang_deDE。开始我看的时候也是一脸的懵逼啊,看完代码后才释然的。
8、再把spell_dbc_en表的Name, NameSubtext插进去,locale设置为enUS
insert into spell_locale(ID, locale, Name, NameSubtext) select ID, 'enUS', Name_enUS, NameSubtext_Lang_enUS from spell_dbc_en;
9、当然这只是有数据了,服务器端还需加载进去,这时候就得改代码啦。修改SpellMgr.cpp,在LoadSpellInfoStore()方法中把spell_locale的数据加载进去并修改SpellInfo的SpellName字段:


// 把刚才我们说的表读取并根据相应的locale进行复制
QueryResult result = WorldDatabase.Query("select ID, locale, Name, NameSubtext from spell_locale");
if (result) {
    uint32 localedSpellCount = 0;

    do {
        Field* fields = result->Fetch();

        uint32 spell = fields[0].Get<uint32>();
        LocaleConstant locale = GetLocaleByName(fields[1].Get<std::string>());
        std::string localeName = fields[2].Get<std::string>();
        std::string localeRank = fields[3].Get<std::string>();
        
        SpellInfo* spellInfo = mSpellInfoMap[spell];
        if (spellInfo) {
            if (localeName.size() > 0 && (!spellInfo->SpellName[locale] || strlen(spellInfo->SpellName[locale]) == 0)) {
                char* buf = new char[localeName.size() + 1];
                memcpy(buf, localeName.c_str(), localeName.size() + 1);
                sSpellStore.getStringPool().push_back(buf);

                spellInfo->SpellName[locale] = buf;
                localedSpellCount++;
            }
            if (localeRank.size() > 0 && (!spellInfo->Rank[locale] || strlen(spellInfo->Rank[locale]) == 0)) {
                char* buf = new char[localeRank.size() + 1];
                memcpy(buf, localeRank.c_str(), localeRank.size() + 1);
                sSpellStore.getStringPool().push_back(buf);

                spellInfo->Rank[locale] = buf;
            }
        }
    } while (result->NextRow());
    LOG_INFO("server.loading", "    >> Loaded {} spell locale data in {} ms", localedSpellCount, GetMSTimeDiffToNow(oldMSTime));
}

2023-10-04T10:10:27.png
OK,编译,启动服务,PlayerBot不发呆了,各种技能嗖嗖的发呀。

前面提到一嘴的关于spells指令显示中文的问题是需要修改代码解决的,关于国际化的问题以后可以再写一章,反正这部分动了不少的代码。

标签: none