# 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 略有修改。

  1. math.randomseed 改为使用 jass 函数 SetRandomSeed 实现。
  2. math.random 改为使用 jass 函数 GetRandomReal 实现。
  3. table 元素随机化种子依赖于魔兽内部的随机种子。
  4. 屏蔽了部分被认为不安全的函数

# 内置库

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)))
1
2

# jass.ai

jass.ai 库包含 common.ai 内注册的所有函数。

	local jass = require 'jass.common'
	local ai = require 'jass.ai'
	print(ai.UnitAlive(jass.GetTriggerUnit()))
1
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
1
2
3
4

你可以通过这个方式来实现和 jass 脚本之间的数据交互

注意,你无法通过这个方式来访问 jass 中的自定义函数。也就是说,你无法带着参数来调用对方的函数,但通过 code 变量可以不带参数的来调用

在上面的例子中,你可以在 jass 中执行:

	call TimerStart(CreateTimer(), 1, true, udg_code)
1

这个计时器将在每次到期时正确的回调你在 lua 中定义的函数

# jass.japi

jass.japi 库当前已经注册的所有 japi 函数。

	local jass = require 'jass.common'
	local japi = require 'jass.japi'
	japi.EXDisplayChat(jass.Player(0), 0, "Hello!")
1
2
3

japi 函数不同环境下可能会略有不同,你可以通过 pairs 遍历当前的所有 japi 函数

	for k, v in pairs(require 'jass.japi') do
		print(k, v)
	end
1
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
1
2
3
4
5
6
7

# jass.slk

jass.slk 库可以在地图运行时读取地图内的 slk/w3 * 文件。

	local slk = require 'jass.slk'
	print(slk.ability.AHbz.Name)
1
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
1
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'
1

# runtime.console (默认为 false)

赋值为 true 后会打开一个 cmd 窗口,print 与 console.write 函数可以输出到这里

	runtime.console = true
1

# runtime.version

返回当前 lua 引擎的版本号

	print(runtime.version)
1

# runtime.error_handle

当你的 lua 脚本出现错误时将会调用此函数。

runtime.error_handle 有一个默认值,等价于以下函数

	runtime.error_handle = function(msg)
		print("Error: ", msg, "\n")
	end
1
2
3

你也可以让它输出更多的信息,比如输出错误时的调用栈

	runtime.error_handle = function(msg)
		print("---------------------------------------")
		print("              LUA ERROR!!              ")
		print("---------------------------------------")
		print(tostring(msg) .. "\n")
		print(debug.traceback())
		print("---------------------------------------")
	end
1
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"
1
2
3
# 1: handle 封装在 lightuserdata 中,保证 handle 不能和整数相互转换,同样不支持引用计数
	local t = jass.CreateTimer()
	print(t) -- "handle: 0x10005D"
	type(t) -- "userdata"
	jass.TimerStart(t, 1, false, 0) -- ok
1
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
	)
1
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
	)
1
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
1
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
1

# jass.console

# jass.console 与控制台相关

# console.enable (默认为 false)

赋值为 true 后会打开一个 cmd 窗口,print 与 console.write 函数可以输出到这里

	console.enable = true
1

# 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
	)
1
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)))
1
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
1
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
1
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('这是一行', '日志')
1
2
3

# jass.message

  • keyboard 一张表,魔兽的键盘码
  • mouse 本地玩家的鼠标坐标 (游戏坐标)
  • button 本地玩家技能按钮的状态
  • hook 魔兽的消息回调,可以获得部分鼠标和键盘消息
  • selection 获得本地玩家当前选中单位
  • order_immediate 发布本地命令,无目标
  • order_point 发布本地命令,点目标
  • order_target 发布本地命令,单位目标
  • order_enable_debug 开启后,会在控制台打印当前的本地命令,调试用

# jass.bignum

大数库