# ydwe lua 引擎使用说明
# 简介
ydwe lua 引擎 (以下简称 lua 引擎) 是一个嵌入到《魔兽争霸 III》(以下简称魔兽) 中的一个插件,它可以让魔兽可以执行 lua 并且调用魔兽的导出函数 (在 common.j 内定义的函数),就像使用 jass 那样。本说明假定你已经掌握了 jass 和 lua 的相关语法,有关语法的问题不再另行解释。
# 入口
在 jass 内调用 call Cheat("exec-lua: hello")
,这等价于在 lua 里调用了 require 'hello'
。lua 引擎已经把地图内的文件加载到搜索路径,所以地图内的 hello.lua 将会得到执行。
# lua 引擎对标准 lua 的修改
为了适合在魔兽内使用 lua 引擎对 lua 略有修改。
- math.randomseed 改为使用 jass 函数 SetRandomSeed 实现。
- math.random 改为使用 jass 函数 GetRandomReal 实现。
- table 元素随机化种子依赖于魔兽内部的随机种子。
- 屏蔽了部分被认为不安全的函数
# 内置库
lua 引擎一共有 12 个内置库,可以通过 "require ' 库名 '" 调用。
- jass.common
- jass.ai
- jass.globals
- jass.japi
- jass.hook
- jass.runtime
- jass.slk
- jass.console
- jass.debug
- jass.log
- jass.message
- jass.bignum
# jass.common
jass.common 库包含 common.j 内注册的所有函数。
local jass = require 'jass.common'
print(jass.GetHandleId(jass.Player(0)))
2
# jass.ai
jass.ai 库包含 common.ai 内注册的所有函数。
local jass = require 'jass.common'
local ai = require 'jass.ai'
print(ai.UnitAlive(jass.GetTriggerUnit()))
2
3
# jass.globals
jass.globals 库可以让你访问到 jass 内的全局变量。
local g = require 'jass.globals'
g.udg_code = function() --将一个jass中定义的code变量赋值为一个lua函数
print(jass.udg_strings[2]) --获取jass中定义的string数组
end
2
3
4
你可以通过这个方式来实现和 jass 脚本之间的数据交互
注意,你无法通过这个方式来访问 jass 中的自定义函数。也就是说,你无法带着参数来调用对方的函数,但通过 code 变量可以不带参数的来调用
在上面的例子中,你可以在 jass 中执行:
call TimerStart(CreateTimer(), 1, true, udg_code)
这个计时器将在每次到期时正确的回调你在 lua 中定义的函数
# jass.japi
jass.japi 库当前已经注册的所有 japi 函数。
local jass = require 'jass.common'
local japi = require 'jass.japi'
japi.EXDisplayChat(jass.Player(0), 0, "Hello!")
2
3
japi 函数不同环境下可能会略有不同,你可以通过 pairs 遍历当前的所有 japi 函数
for k, v in pairs(require 'jass.japi') do
print(k, v)
end
2
3
# jass.hook
jass.hook 库可以对 common.j 内注册的函数下钩子。注:jass.common 库不会受到影响。
同时,为了避免 jass 和 lua 之间传递浮点数时产生误差,通过 jass.hook 传递到 lua 中的浮点数,并不是 number 类型,而是 userdata。当你需要精确地操纵浮点数时,也请注意这点。
local hook = require 'jass.hook'
function hook.CreateUnit(pid, uid, x, y, face, realCreateUnit)
-- 当jass内调用CreateUnit时,就会被执行
print('CreateUnit')
print(type(x))
return realCreateUnit(pid, uid, x, y, face)
end
2
3
4
5
6
7
# jass.slk
jass.slk 库可以在地图运行时读取地图内的 slk/w3 * 文件。
local slk = require 'jass.slk'
print(slk.ability.AHbz.Name)
2
你也可以遍历一个表或者一个物体
local slk = require 'jass.slk'
for k, v in pairs(slk.ability) do
print(k .. ' ' .. v)
end
for k, v in pairs(slk.ability.AHbz) do
print(k .. ' ' .. v)
end
2
3
4
5
6
7
slk 包含
- unit
- item
- destructable
- doodad
- ability
- buff
- upgrade
- misc
与你物体编辑器中的项目一一对应。
获取数据时使用的索引你可以在物体编辑器中通过 Ctrl+D 来查询到
注意,当访问正确时返回值永远是字符串。如果你获取的是某个单位的生命值,你可能需要使用 tonumber 来进行转换。当访问不正确时将返回 nil。
# jass.runtime
# jass.runtime 库可以在地图运行时获取 lua 引擎的信息或修改 lua 引擎的部分配置。
local runtime = require 'jass.runtime'
# runtime.console (默认为 false)
赋值为 true 后会打开一个 cmd 窗口,print 与 console.write 函数可以输出到这里
runtime.console = true
# runtime.version
返回当前 lua 引擎的版本号
print(runtime.version)
# runtime.error_handle
当你的 lua 脚本出现错误时将会调用此函数。
runtime.error_handle 有一个默认值,等价于以下函数
runtime.error_handle = function(msg)
print("Error: ", msg, "\n")
end
2
3
你也可以让它输出更多的信息,比如输出错误时的调用栈
runtime.error_handle = function(msg)
print("---------------------------------------")
print(" LUA ERROR!! ")
print("---------------------------------------")
print(tostring(msg) .. "\n")
print(debug.traceback())
print("---------------------------------------")
end
2
3
4
5
6
7
8
注意,注册此函数后 lua 脚本的效率会降低 (即使并没有发生错误)。
# runtime.handle_level (默认为 0)
lua 引擎处理的 handle 的安全等级,有效值为 0~2,注,等级越高,效率越低,安全性越高、
# 0: handle 直接使用 number,jass 无法了解你在 lua 中对这个 handle 的引用情况,也不会通过增加引用计数来保护这个 handle
local t = jass.CreateTimer()
print(t) -- 1048000
type(t) -- "number"
2
3
# 1: handle 封装在 lightuserdata 中,保证 handle 不能和整数相互转换,同样不支持引用计数
local t = jass.CreateTimer()
print(t) -- "handle: 0x10005D"
type(t) -- "userdata"
jass.TimerStart(t, 1, false, 0) -- ok
2
3
4
local t = jass.CreateTimer()
local h1 = jass.CreateTimer()
jass.DestroyTimer(h1)
jass.TimerStart(t, 1, false,
function()
local h2 = jass.CreateTimer()
print(h1) -- "handle: 0x10005E"
print(h2) -- "handle: 0x10005E"
end
)
2
3
4
5
6
7
8
9
10
# 2: handle 封装在 userdata 中,lua 持有该 handle 时将增加 handle 的引用计数。lua 释放 handle 时会释放 handle 的引用计数。
local t = jass.CreateTimer()
local h1 = jass.CreateTimer()
jass.DestroyTimer(h1)
jass.TimerStart(t, 1, false,
function()
local h2 = jass.CreateTimer()
print(h1) -- "handle: 0x10005E"
print(h2) -- "handle: 0x10005F"
end
)
2
3
4
5
6
7
8
9
10
# runtime.sleep (默认为 false)
common.j 中包含 sleep 操作的函数有 4 个,TriggerSleepAction/TriggerSyncReady/TriggerWaitForSound/SyncSelections。当此项为 false 时,lua 引擎会忽略这 4 个函数的调用,并给予运行时警告。当此项为 true 时,这 4 个函数将会得到正确的执行。
但请注意此项为 true 时将降低 lua 引擎的运行效率,即使你没有使用这 4 个函数。
local trg = jass.CreateTrigger()
local a = 1
jass.TriggerAddAction(trg, function()
jass.TriggerSleepAction(0.2)
print(a) -- 2
end)
jass.TriggerExecute(trg)
a = 2
2
3
4
5
6
7
8
# runtime.catch_crash (默认为 true)
调用 jass.xxx/japi.xxx 发生崩溃时,会生产一个 lua 错误,并忽略这个崩溃。你可以注册 jass.runtime.error_handle 来获得这个错误。注:开启此项会略微增加运行时消耗(即使没有发生错误)。
# runtime.debugger
启动调试器并监听指定端口。需要使用 VSCode
并安装 Lua Debug (opens new window)。
runtime.debugger = 4279
# jass.console
# jass.console 与控制台相关
# console.enable (默认为 false)
赋值为 true 后会打开一个 cmd 窗口,print 与 console.write 函数可以输出到这里
console.enable = true
# console.write
将 utf8 编码的字符串转化为 ansi 编码后输出到 cmd 窗口中,如果你需要输出魔兽中的中文,请使用该函数而不是 print
# console.read
将控制台中的输入传入魔兽中 (会自动转换编码)
首次调用 console.read 后将允许用户在控制台输入,输入完成后按回车键提交输入。
用户提交完成后,传入一个函数 f 来调用 console.read,将会调用函数 f,并将用户的输入作为参数传入 (已转换为 utf8 编码)。
推荐的做法是每 0.1 秒运行一次 console.read,见下面的例子:
local jass = require 'jass.common'
local console = require 'jass.console'
console.write('测试开始...')
--开启计时器,每0.1秒检查输入
jass.TimerStart(jass.CreateTimer(), 0.1, true,
function()
--检查CMD窗口中的用户输入,如果用户有提交了的输入,则回调函数(按回车键提交输入).否则不做任何动作
console.read(
function(str)
--参数即为用户的输入.需要注意的是这个函数调用是不同步的(毕竟其他玩家不知道你输入了什么)
jass.DisplayTimedTextToPlayer(jass.Player(0), 0, 0, 60, '你在控制台中输入了:' .. str)
end
)
end
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
需要注意的是控制台输入是不同步的。
# jass.debug
jass.debug 库能帮助你更深入地剖析 lua 引擎的内部机制。
- functiondef jass.common 或者 jass.japi 函数的定义
local jass = require 'jass.common'
local dbg = require 'jass.debug'
print(table.unpack(dbg.functiondef(jass.GetUnitX)))
2
3
globaldef jass.globals 内值的定义
handledef handle 对应对象的内部定义
currentpos 当前 jass 执行到的位置
handlemax jass 虚拟机当前最大的 handle
handlecount jass 虚拟机当前的 handle 数
h2i/i2h handle 和 integer 的转换,当你 runtime.handle_level 不是 0 时,你可能会需要它
handle_ref 增加 handle 的引用
handle_unref 减少 handle 的引用
gchash 指定一张 table 的 gchash,gchash 会决定了在其他 table 中这个 table 的排序次序 在默认的情况下,lua 对 table 的排序次序是由随机数决定的,不同玩家的 lua 生成的随机数不一致,所以下面的代码在不同的玩家上执行的次序是不一致的,这可能会引起不同步掉线
local tbl = {}
for i = 1, 10 do
local k = { id = i }
tbl[k] = i
end
for k, v in pairs(tbl) do
print(k.id)
end
2
3
4
5
6
7
8
加上如果指定了 gchash,那么它的次序就可以固定了
local dbg = require 'jass.debug'
local tbl = {}
for i = 1, 10 do
local k = { id = i }
dbg.gchash(k, i)
tbl[k] = i
end
for k, v in pairs(tbl) do
print(k.id)
end
2
3
4
5
6
7
8
9
10
# jass.log
日志库
- path 日志的输出路径
- level 日志的等级,指定等级以上的日志才会输出
- 日志有 6 个等级 trace、debug、info、warn、error、fatal
local log = require 'jass.log'
log.info('这是一行日志')
log.error('这是一行', '日志')
2
3
# jass.message
- keyboard 一张表,魔兽的键盘码
- mouse 本地玩家的鼠标坐标 (游戏坐标)
- button 本地玩家技能按钮的状态
- hook 魔兽的消息回调,可以获得部分鼠标和键盘消息
- selection 获得本地玩家当前选中单位
- order_immediate 发布本地命令,无目标
- order_point 发布本地命令,点目标
- order_target 发布本地命令,单位目标
- order_enable_debug 开启后,会在控制台打印当前的本地命令,调试用
# jass.bignum
大数库