1、自定义模块简介
除了开源代码集成了优秀的自定义幻化、嵌入式Lua引擎模块之外,用户也可以根据自身需求自定义功能模块。
二、嵌入式 Lua 脚本引擎简介
项目源码git地址:https://github.com/azerothcore/mod-eluna,Eluna支持MaNGOS,CMaNGOS,TrinityCore和AzerothCore,开源社区还提供了入门指南、API接口、Lua参考手册、示例脚本等等各种文档,极大的方便用户进行开发。
三、开发工作准备
首先,我们需要克隆一份Lua功能模块源码。
1、克隆Lua引擎模块源码
进入克隆下来的AzerothCore源码目录下modules文件夹,空白处右键选择Git Bash Here。
在弹出的git命令行中输入:git clone https://github.com/azerothcore/mod-eluna.git mod-eluna,等待下载完成。
2、使用CMake重新编译
请参考兄弟篇文章《魔兽世界开源 Azeroth Core 服务端编译手册》第九、十章。在编译前,先把build目录下所有文件删掉,注意备份源码编译输出目录下的bin\Debug\data文件夹内容、那3个拷贝过来的动态链接库dll以及修改过的configs目录下的两个配置文件,后续使用VS编译源码完成后,可以直接覆盖过去。
生成的新工程会多出lua_scripts文件夹以及configs目录下的modules文件夹,我们需要进入configs\modules文件夹,复制mod_LuaEngine.conf.dist并重命名为mod_LuaEngine.conf,不做修改,让它默认配置就行了。后续编写完成的lua脚本,直接放到lua_scripts根目录下。
3、开发工具的选择
开发工具可选以下其中一个,但我还是墙裂推荐使用Visual Studio Code,宇宙最强开发工具(代码编辑器)没有之一,和我们编译服务端的Visual Studio 2022开发工具都是同一家公司出品,那就是盖茨公司。虽然没有VS功能强大和全面,但它免费啊,插件生态圈那么优秀、突出不是吗?2022年了都,相信大多写前端的工程师码农们,用的都是VS Code吧?
四、编写 Lua 脚本前准备工作
1、根据个人癖好,在指定的目录下新建一个文件夹,我这里指定的是和AzerothCore源码目录平级的文件夹,也就是D:\games\wow\azeroth-core\lua-scripts。
2、使用VS Code打开这个文件夹
3、打开后,咱先装一个南山必胜客开源的Lua插件LuaHelper,让代码看起来舒服点不是吗?当然,打开前也可以安装,怎么高兴怎么来。
4、新建一个名为teleport-stone.lua文件,看名字就知道,传送石,不错!今天我们将编写一个具有传送功能的对话菜单lua脚本,并将它绑定到物品【炉石】上。
5、我们看下社区针对lua引擎模块的其他介绍。
6、先看API接口文档,看都有些什么接口。
7、再看看示例脚本,其中example_gossip.lua就是我们要做的对话菜单Demo,盘它。
8、进去看看Demo怎么写的,我们今天要做的是绑定对话菜单到物品【炉石】上,所以只用关心其中的RegisterItemGossipEvent函数,其他暂时用不到。比如RegisterCreatureGossipEvent函数针对非玩家控制的目标(也就是NPC)事件,RegisterPlayerEvent函数是监听玩家事件。
9、AzerothCore代码太多,一开始也不可能一行行去读,上来直接放大招,在源码工程里ctrl + shift + F全局搜索,就搜RegisterItemGossipEvent这个函数。
10、可以看到,函数RegisterItemGossipEvent需要三个参数(实际上是4个,只不过最后个参数默认为0,咱暂时用不到),调用方式是这样婶的:
RegisterItemGossipEvent(itemId, eventType, callback, [cout]),其中参数itemId为绑定的物品id;eventType为注册的事件类型(1为绑定物品对象事件,2为对话菜单选中事件);callback为事件回调函数,[count]为调用次数,可选参数,默认为0,因暂时用不到,我们在调用时,只需要传前三个参数就行了。
11、第一个参数就是物品的唯一标识,也就是物品id,接下来我们需要找到【炉石】在数据库里的唯一标识,需要使用到以下几个工具:
a.巫妖王之怒中文数据库站点:https://80.wowfan.net/
b.Keira3,详情请看:https://www.azerothcore.org/catalogue.html#/
c.Navicat数据库管理工具
12、咱先到巫妖王之怒中文数据库站点:https://80.wowfan.net/去搜一下【炉石】这个物品,找下这个物品的id。
13、我们在中文数据库站点找到【炉石】的id为6948,觉得不靠谱?那就使用工具Keira3再查一遍,这个工具链接的就是本地数据库acore_world。
14、不过从Keira3工具查询出来的结果,那也不能够确定啊,谁知道【炉石】的专业术语对应的英文是啥对不对?还是不太靠谱,Ok,直接放大招,用Navicat打开acore_world库,找到物品表item_template,在此之前让我们大胆猜测,数据库里肯定有国际化的表,这张表记录就是对应这些物品的各种语言的描述。
15、现在我们已经找到【炉石】这件物品的id了,下一步就是分析下示例脚本example_gossip.lua的RegisterItemGossipEvent注册绑定物品对话菜单事件和事件回调函数OnGossipHello。
看下源码的GossipClearMenu、GossipMenuAddItem函数说明。
从上图中可以看出,调用的GossipMenuAddItem函数所需传递的各项参数,故调用方式应为:player:GossipMenuAddItem(icon,sender,intid,code,popup,money),其中参数icon为菜单图标;sender为菜单绑定的句柄,暂理解为附加数据;intid为菜单项唯一标识,也就是菜单id;code为是否弹出输入框,默认为false;popup为弹出确认对话框的内容,字符串类型,为空则不弹出;money为扣除玩家身上的游戏币金额。
16、接着看选中对话菜单项回调函数OnGossipSelect。
综上所述,选中菜单项回调函数会传递7个参数,分别是event(事件)、player(当前玩家)、object(绑定对象)、sender(绑定句柄)、intid(菜单id)、code(弹出的输入框输入内容)、menu_id(菜单id),现在我们就可以来看看回调函数OnGossipSelect的函数体具体信息了。
五、编写对话菜单 Lua
该了解的大致都了解了,接下来开始动手写脚本代码咯!咱也照葫芦画瓢,一步一步来,让一颗普通的【炉石】变成一颗【超级炉石】。
1、注册对话菜单绑定到指定物品【炉石】。
--[[
制作炉石对话功能
]] --
-- 已知【炉石】id为6948,声明它为绑定对象
local BIND_ITEM = 6948
--[[
添加菜单项
player:当前玩家
object:绑定对象
menuId:菜单id
]] --
local function AddMenuItem(player, object, menuId)
player:GossipClearMenu() -- 清除当前玩家对话菜单列表
--[[
发送菜单到绑定物品
GossipSendMenu(npc_text, sender)
npc_text:world数据库表npc_text对应的id,默认使用id为100的数据记录
sender:绑定对象
]] --
player:GossipSendMenu(1, object, menuId)
end
--[[
指定物品绑定对话菜单回调函数,这里我们先让它显示主菜单界面
参数:
参阅源码RegisterItemGossipEvent函数注释
]] --
local function OnShowMenu(event, player, object)
--[[
添加菜单项
AddMenuItem(player, object, menuId)
player:当前玩家
object:绑定对象
menuId:菜单id
]] --
AddMenuItem(player, object, 1)
end
--[[
注册物品对话菜单绑定事件
BIND_ITEM:绑定的物品
1:注册绑定事件类型,对指定物品绑定对话菜单
ShowMenu:当使用绑定的物品时回调的函数
]] --
RegisterItemGossipEvent(BIND_ITEM, 1, OnShowMenu)
编写好的脚本保存后复制到lua_scripts根目录下,需要重启authserver和worldserver。
相信细心的同学已经发现了,在使用炉石弹出对话菜单的时候,居然还在读条,那么我们怎么取消这个动作呢?其实在前面的第四章第9节有说过,RegisterItemGossipEvent这个事件回调的lua脚本方法,方法返回false(return false)的话就会停止施法,我们只需要让lua脚本的OnShowMenu方法返回false就可以了,稍加改动下脚本。
local function OnShowMenu(event, player, object)
--[[
添加菜单项
AddMenuItem(player, object, menuId)
player:当前玩家
object:绑定对象
menuId:菜单id
]] --
AddMenuItem(player, object, 1)
return false --停止施法
end
2、为弹出的对话菜单添加菜单项。
我们只需要修改AddMenuItem方法,在调用GossipClearMenu函数和调用GossipSendMenu函数之间处理添加菜单项逻辑就行了。
local function AddMenuItem(player, object, menuId)
player:GossipClearMenu() -- 清除当前玩家对话菜单列表
--[[
添加菜单项
GossipMenuAddItem(icon, text, sender, intid, code = false, popup, money)
icon:图标类型可以搜源码查看枚举GossipOptionIcon,下图中的函数。
text:菜单名称
sender:绑定句柄
intid:唯一标识,菜单id
code:是否弹出输入框
popup:字符串类型,有值的话就弹出对话确认
money:扣除玩家费用
]] --
player:GossipMenuAddItem(0, "菜单一", 0, 2)
--[[
发送菜单到绑定物品
GossipSendMenu(npc_text, sender)
npc_text:world数据库表npc_text对应的id,默认使用id为100的数据记录
sender:绑定对象
]] --
player:GossipSendMenu(1, object, menuId)
end
GossipMenuAddItem函数用到的icon图标参数,可以在源码库game\Entities\Creature\GossipDef.h头文件中枚举GossipOptionIcon查看。
3、处理点击(选中)菜单项。
前面说过,RegisterItemGossipEvent的第二个参数,事件类型为2就是选中菜单项响应事件。我们暂时以发送提示消息来实现这个功能,发送提示消息的接口是SendBroadcastMessage("提示消息内容"),在Lua模块下PlayerMethods.h头文件里。
首先,需要注册选中菜单项事件。
--[[
注册对话菜单选中事件
BIND_ITEM:绑定的物品
2:注册绑定事件类型,对话菜单项被选中
OnSelectMenu:选中对话菜单项时回调的函数
]] --
RegisterItemGossipEvent(BIND_ITEM, 2, OnSelectMenuItem)
然后就是声明回调方法OnSelectMenuItem。
--[[
选中菜单回调函数
event:事件
player:玩家
object:绑定对象
sender:绑定句柄
intid:唯一标识,菜单id
code:弹出的输入框输入内容
menuid:菜单id
]] --
local function OnSelectMenuItem(event, player, object, sender, intid, code, menuid)
player:SendBroadcastMessage("响应菜单点击事件,菜单id:" .. intid)
end
到这里,绑定对话菜单到【炉石】的基本功能就完成了。接下来我们就要开始编写具有功能性的菜单咯!
六、编写功能性菜单 Lua
功能性菜单,顾名思义,就是让这个菜单发挥我们想要的功能作用。比如传送、修理装备、召唤玩家/NPC、刷钱/刷装备、玩家状态设置等等很多很多,具体需要看看API文档。
1、获取传送参数。
传送,这应该是最常用的功能了吧?就那地图之大,步行或骑马都要整半天,时间就是金钱我的朋友。要想能进行传送,得先找到传送API接口,看下这个接口需要传递什么参数。
现在我们知道了,传送API接口,需要传递的参数有地图id(mapId)和x、y、z、o这几个空间坐标。Ok,再来找找如何获取这些参数的API接口。
万事俱备,整起,燥起来。我们将获取到的这些传送所需参数打印出来,然后记录下这些参数(空间坐标小数位取后3位即可)。
local function OnSelectMenuItem(event, player, object, sender, intid, code, menuid)
local mapId = player:GetMapId() --获取当前玩家所在地图id
local x = player:GetX() --获取当前玩家所在地图X坐标
local y = player:GetY() --获取当前玩家所在地图Y坐标
local z = player:GetZ() --获取当前玩家所在地图Z坐标
local o = player:GetO() --获取当前玩家所在地图O坐标
player:SendBroadcastMessage("响应菜单点击事件,菜单id:" .. intid .. ",当前地图id:" .. mapId ..
",X坐标:" .. x .. ",Y坐标:" .. y .. ",Z坐标:" .. z .. ",O坐标:" .. o)
end
2、穿梭艾泽拉斯
现在,传送的几个参数我们都获取到了,可以开始传送咯!等会儿我稍微跑远点,然后点击菜单再给人物传送回来,嘿嘿嘿......
local function OnSelectMenuItem(event, player, object, sender, intid, code, menuid)
--[[
Teleport(mapId, x, y, z, o)
mapId:地图id
x:X坐标
y:Y坐标
z:Z坐标
o:O坐标
刚才获取到的各项传递参数,小数位精确到后3位就够了
mapId:1
x:-2920.7922363281
y:-269.55258178711
z:53.859161376953
o:6.1865758895874
]]--
player:Teleport(1, -2920.792, -269.552, 53.859, 6.186)
end
是不是很简单?哈哈,到这也就完成我们编写的第一个Lua脚本了,更多的功能接口功能,需要自己去慢慢摸索哦!下篇章,我们来说说修改源码那些事儿,比如:调整宠物血量、宠物物理攻击、玩家拉怪漏背眩晕(坦克天赋下,拉怪不应该被眩晕)等等。
文章评论