evolve
lua 5.4 · 保存即重载

Lua API

这里列出主要的调用,每一个都能在当前版本的 evolve 中使用。

91/ 91 项

start

1

脚本是 scripts 文件夹里的一个 .lua 文件:在游戏菜单中,“设置 › 脚本 › 打开文件夹”会打开这个文件夹。脚本返回一个回调表,保存后会重新读取。Lua 5.4,所有脚本共用一个 VM,回调逐个执行。

基本规则
  1. 1在 on_draw 里绘制,在 on_update 里执行游戏操作。renderer.* 在帧外什么也画不出来,orders.* 在 on_draw 里调用会走错误的路径。
  2. 2从 entities.* 取到的实体只在当前帧有效:先检查 e:valid(),列表本身也要重新从 entities.* 获取。
  3. 3启动后的前 ~10 秒在进行 schema 遍历。位置、队伍和生命值可以立即读取;护甲、技能和 gamerules 在遍历结束前都返回 0。
  4. 4client.local_hero() 为空时,所有按队伍的过滤都会把所有人当作敌人:先拿到英雄,再选目标。
  5. 5坐标使用 Source 世界单位:地图在 X 和 Y 上大约从 -8288 到 8288,一个点就是 vec(x, y, z)。
aim.luareturn { … }hud.luareturn { … }farm.luareturn { … }同一个 VM · Lua 5.4aim.on_updatehud.on_updatefarm.on_update↻ 已保存并重新加载
脚本是返回回调表的 .lua 文件。所有脚本共用一个 VM,回调依次执行。保存文件即重新加载。
client.local_hero()→ Entity|nil

本地玩家的英雄。进入比赛前为 nil。

client.screen_size()→ number, number

窗口的宽度和高度,单位为像素。

client.cursor_world()→ Vector|nil

光标下方的点,位于英雄所在的 z 平面上。

globals.curtime()→ number

游戏时间,单位为秒。所有计时器都用这个时钟。

globals.tickcount()→ number

客户端真实的 tick,30 Hz,单调递增。

注意游戏时间暂停时仍会继续计数。

vec(x, y, z)→ Vector

一个点或一个方向。相关的还有 vec2、angle 和 color(r, g, b, a),分量取值 0..255。

log.info(msg)

向 evolve.log 写一行。脚本没有反应时,首先用它排查。

示例
-- scripts/hello.lua
local s = {}

function s.on_update()                  -- game logic
  local me = client.local_hero()
  if not (me and me:valid()) then return end
end

function s.on_draw()                    -- renderer.* only
  local me = client.local_hero()
  if not (me and me:valid()) then return end
  local x, y, on = renderer.world_to_screen(me:position())
  if on then
    renderer.text(font_default, x, y - 40, color(255, 255, 255, 255), "hello")
  end
end

return s

packs

2

脚本包就是一个文件夹:pack.json、入口、模块、图片和数据。同一个文件夹放进 scripts/ 就能运行,上传到商店时也是把它压缩成 zip(作者后台 › 版本)。

基本规则
  1. 1pack.json:version 必填(格式如 1.2.0),entry 是启动文件(默认 main.lua),name 是脚本列表中的名称,evolve 是它适配的版本。
  2. 2文件夹名就是脚本包名。模块从它开始加载:require("my_pack.lib.util") 读取 lib/util.lua。在商店中,文件夹以作品的 slug 命名。
  3. 3以 ./ 开头的路径指向你的脚本包文件夹:renderer.load_image("./art/icon.png")、菜单图标 "./art/…"、fs.read("./data/x.json")。不需要知道文件夹名。
  4. 4脚本包里可以放 .lua、图片 .png .jpg .bmp .tga .gif(每边最大 4096 px)和数据 .json .txt .md .csv。zip 最大 16 MB,最多 1500 个文件。
  5. 5上传时每个 .lua 都会被编译,每张图片和每个 json 都会被打开。回复会一次列出所有问题,带文件和行号,之后由管理员审阅版本。
my_pack/pack.jsonmain.lualib/util.luaart/icon.pngdata/x.jsonscripts/zip → 商店
包就是一个文件夹:放进 scripts/ 即可运行,同一个文件夹打成 zip 就是上传到商店的内容。pack.json 必须有 version,以 ./ 开头的路径指向包文件夹。
renderer.load_image(path)→ number

按路径加载图片:"./art/x.png" 来自你自己的脚本包,"panorama/images/…" 来自游戏。返回句柄或 -1;按路径缓存。

注意在脚本加载时加载一次,不要每帧加载。脚本重新加载后,修改过的文件会自动生效。

renderer.load_image_bytes(bytes [, key])→ number

用脚本已经拿到的字节(fs.read、http)创建图片。按 key 缓存,没有 key 时按内容缓存。

示例
-- scripts/my_pack/pack.json : { "version": "1.0.0", "name": "My Pack" }
-- scripts/my_pack/main.lua
local util = require("my_pack.lib.util")         -- scripts/my_pack/lib/util.lua
local icon = renderer.load_image("./art/icon.png")
local cfg = json.decode(fs.read("./data/defaults.json") or "{}")

local s = {}
function s.on_draw()
  local me = client.local_hero()
  if not (me and me:valid()) or icon < 0 then return end
  local x, y, on = renderer.world_to_screen(me:position())
  if on then renderer.image(icon, x - 16, y - 80, 32, 32) end
end
return s

events

3

回调。来自引擎线程的一切都会先缓冲,再由帧循环统一取出处理,所以处理函数和 on_update 在同一处运行,可以调用任何 API。

基本规则
  1. 1订阅有两种方式:从文件返回一个 { on_update = fn } 表,或者调用 events.register。两者都一直有效,直到脚本重新加载。
  2. 2事件数据只在回调内部有效:当场读取,不要把这张表留到以后再用。
  3. 3抛出错误的处理函数会被移除,并记录堆栈回溯;diag.errors() 会在客户端里显示它。
on_pre_updateon_updateframe_beginon_drawmenu::renderdrainpoll_keys缓冲
转一圈就是一帧,回调按这个顺序执行。事件在缓冲中等待,在下一次 drain 时到达。
events.register(name, fn)

订阅一个回调。

namestring
回调名,例如 "on_update"。
fnfunction
处理函数。参数取决于事件。
"on_update" fn()

逻辑 tick,每帧一次。orders.* 和所有游戏逻辑都放在这里。

"on_draw" fn()

帧。renderer.* 只能在这里使用。

"on_game_event" fn(name, data)

任何原生事件:不能按名称单独订阅,所有事件都会送达。

namestring
引擎事件名,例如 "dota_player_gained_level"。
datatable
按名称给出事件的全部字段,在触发时截取快照。

注意由帧循环投递,最迟在触发后一帧送达,按触发顺序排列。

"on_key" fn(vk, down)

来自 wndproc 的原始按键。

vknumber
Windows 虚拟键码。
downboolean
按下或松开。

注意返回值会被忽略:脚本无法拦截游戏的按键。

"on_projectile" fn(p)

创建了一个追踪弹道。

ptable
handle, source, target, ability, move_speed, is_attack, dodgeable, expire_time, max_impact_time。

注意其中的实体字段在投递时解析:在同一帧内死亡的单位会以 nil 送达。

"on_modifier_gained" fn(npc, mod)

出现了一个修饰器。与之配对的是 on_modifier_lost(npc, name)。

npcEntity
获得它的单位。
modtable
name、stacks、duration 以及修饰器的其余字段。
"on_prepare_unit_orders" fn(o)

指令即将发往服务器。返回 false 可拦截它;o.synthetic 用来区分脚本发出的指令和玩家的点击。

otable
order, target, position, ability, unit, queue, issuer, synthetic。

注意唯一仍直接在游戏线程上执行的回调。引擎会等待 VM 2 ms,等不到就不经否决直接放行指令;这类情况可以用 events.veto_timeouts() 统计。

timers.after(sec, fn)→ number

一次性计时器,返回它的 id。相关的还有 timers.every(sec, fn) 和 timers.clear(id)。

secnumber
从现在起经过多少秒游戏时间。
fnfunction
要执行的函数。
events.dropped()→ number

因队列已满而丢失的事件数。应保持为 0。

示例
local s = {}

function s.on_projectile(p)
  local me = client.local_hero()
  if not (me and me:valid()) then return end
  if p.target == me and p.dodgeable then
    log.info("incoming, impact in " ..
      string.format("%.2f", p.max_impact_time - globals.curtime()))
  end
end

return s

entities

4

实体列表。快照每秒刷新约 20 次,userdata 经过驻留处理,所以实体可以直接用作表的键。

基本规则
  1. 1过滤器就是一个普通表:{ team = "enemy", alive = true, around = pos, radius = 600, class = "creep" }。class 按类名的子串匹配。
  2. 2team 是字符串 "enemy" 或 "ally",不是数字。
  3. 3地图上存活的野怪中,大约一半是营地预先生成的下一波:它们处于无敌状态,可以用 e:is_waiting_to_spawn() 过滤掉。
过滤条件是普通的表,team 是字符串 "enemy" 或 "ally"。返回的列表是调用时的快照:之后走进来的小兵出现在下一次查询中。
entities.all()→ Entity[]

客户端快照中的所有实体。

entities.heroes(team)→ Entity[]

英雄,可选只取一方。

teamstring?
"enemy"、"ally",不传则返回全部。
entities.npcs(filter)→ Entity[]

按过滤器筛选的单位:小兵、幻象、召唤单位、防御塔。

filtertable?
team / alive / around / radius / class。
entities.in_radius(pos, radius, filter)→ Entity[]

圆内的全部实体。

posVector
圆心。
radiusnumber
半径,单位为世界单位。
filtertable?
与 npcs 相同的过滤器。
entities.closest(pos, filter)→ Entity|nil

最近的单位,不包括你自己。

posVector
测量的起点。
filtertable?
与 npcs 相同的过滤器。
entities.by_class(name)→ Entity[]

精确匹配该类及其所有派生类,而不是按名称猜测。

namestring
C++ 类,例如 "C_DOTA_BaseNPC_Tower"。
entities.trees()→ Entity[]

树木。相关的还有 entities.runes() 和 entities.physical_items()(地上的物品)。

entities.get(index)→ Entity|nil

按索引获取实体;entities.from_handle(h) 按句柄获取。

indexnumber
实体索引。
示例
local s = {}

function s.on_draw()
  local me = client.local_hero()
  if not (me and me:valid()) then return end

  for _, h in ipairs(entities.heroes("enemy")) do
    if h:valid() and h:is_alive() and not h:is_dormant() then
      renderer.circle_3d(h:position(), h:attack_range(), color(255, 80, 80, 120))
    end
  end
end

return s

entity

5

实体对象。英雄、小兵、技能和物品都是同一个类型,哪些方法有意义取决于它具体是什么。

基本规则
  1. 1先调用 e:valid()。已消失的实体会返回默认值,而不是报错。
  2. 2处于战争迷雾中的实体(e:is_dormant())停留在最后一次被看到时的状态:位置、生命值和物品栏都是旧数据。
  3. 3用 e:handle() 作为表的键:索引会被复用,而句柄还带有序列号。
e:valid()→ boolean

实体仍在游戏中。

e:position()→ Vector

世界坐标位置。e:origin() 是同一个调用。

e:health()→ number

当前生命值。相关的还有 max_health()、health_pct()(0..100)、mana() 和 mana_pct()。

e:is_alive()→ boolean

是否存活。相关的还有 is_enemy()、is_ally()、is_illusion()、is_hero() 和 is_dormant()。

e:distance(target)→ number

距离,单位为世界单位。

targetVector|Entity
一个点或另一个实体。
e:ideal_speed()→ number

精确的当前移动速度,已计入所有修饰器:鞋子、极速、减速、光环。

注意e:move_speed() 是网络同步的基础属性,不含修饰器。计算移动要用 ideal_speed。

e:effective_bat()→ number

计入修饰器后的基础攻击间隔,攻击时间线就是据此计算的。相关的还有 e:aps() 和 e:seconds_per_attack()。

e:is_stunned()→ boolean

单位状态位。is_silenced、is_rooted、is_hexed、is_disarmed、is_magic_immune 和 is_invulnerable 的读取方式相同。

注意状态位只回答是或否,不带时间。还剩多久要从修饰器得出,而不是从状态位。

e:modifiers()→ table[]

所有修饰器:name、class、stacks、duration、creation_time、die_time、ability、caster、parent、is_aura。

注意parent 是身上带着这个状态的单位;对减益状态来说,它不是施法者。

e:has_modifier(name)→ boolean

快速检查单个修饰器;e:get_modifier(name) 返回它的表。

namestring
内部名称,例如 "modifier_bloodseeker_thirst"。
e:handle()→ number

完整的 32 位句柄:索引加序列号。可以安全地跨帧用作键。

e:visible_to_enemy()→ boolean

敌方是否能看到你。对你自己的单位是可靠的。

abilities

6

技能和物品也是实体,有各自的方法:冷却、魔法消耗、施法距离、施法前摇,以及背后的 KV 数据。

基本规则
  1. 1冷却、魔法值和施法阶段只同步给拥有者。在别人的英雄上,技能看起来总是就绪:这表示“没人告诉我们”,而不是“冷却已结束”。
  2. 2e:get_ability(name) 找不到时返回无效实体而不是 nil,并且每次调用都会扫描快照。只解析一次,存进局部变量。
  3. 3技能能否穿透黑皇杖,看 ab:immunity_type() == 1,而不是按名称猜。
冷却、魔法和施法阶段只同步给拥有者:敌方英雄的 ab:is_ready() 永远为 true。用 get_ability 解析一次技能,存进局部变量。
hero:get_ability(name)→ Entity

按名称获取技能。

namestring
内部名称,例如 "lion_impale"。
hero:abilities()→ Entity[]

英雄的所有技能,按槽位顺序排列。

注意每次调用都会扫描快照:请缓存结果,不要在绘制循环里调用。

hero:get_item(slot)→ Entity

槽位中的物品;hero:items() 返回整个物品栏。

slotnumber
物品栏槽位,从零开始计数:0..5 是英雄身上的物品,之后是背包和储藏处。
ab:cooldown()→ number

剩余冷却时间,单位为秒,0 表示就绪。

ab:is_ready()→ boolean

等级大于零且冷却已结束。

ab:mana_cost()→ number

当前等级的魔法消耗;ab:ability_level() 返回等级本身。

ab:cast_range()→ number

施法距离,已计入所有加成。

ab:cast_point()→ number

施法前摇,单位为秒,即从下达指令到生效之间的延迟。

ab:special(key, level)→ number

技能数据中的任意数值,已计入天赋和阿哈利姆神杖。

keystring
AbilityValues 中的键,例如 lion_impale 的 "duration"。键名以技能数据为准,不要靠猜。
levelnumber?
等级;-1 或不传表示当前等级。

注意未学习的技能按当前等级会返回 0,请显式传入等级:ab:special("duration", 0)。

ab:immunity_type()→ number|nil

0 无,1 对敌方穿透,2 不穿透,3 和 4 对友方同理。

注意nil 表示“没能读取”,这和 0 不是一回事。

ab:dispellable_type()→ number|nil

0 无,1 强驱散,2 基础驱散,3 否。

it:item_charges()→ number

物品充能:魔瓶、魔杖、影之灵龛。技能的充能则用 ab:charges()。

示例
local s = {}
local hex

function s.on_update()
  local me = client.local_hero()
  if not (me and me:valid()) then return end

  hex = hex or me:get_ability("lion_voodoo")
  if not (hex and hex:valid() and hex:is_ready()) then return end
  if me:mana() < hex:mana_cost() then return end

  for _, h in ipairs(entities.heroes("enemy")) do
    if h:is_alive() and not h:is_magic_immune()
       and me:distance(h) < hex:cast_range() then
      orders.cast_target(hex, h)
      return
    end
  end
end

return s

orders

7

真正的引擎指令,走的是和鼠标点击相同的路径。在帧内发送,放在 on_update 里。

基本规则
  1. 1每个指令都有一个可选的末尾参数 queue:orders.move_to(pos, true) 会像按住 Shift 点击一样加入队列。
  2. 2来自其他线程的指令(GC 回调、网络钩子、你自己的线程)必须先缓冲,再从 on_update 发出。在其他线程调用引擎会让客户端在互斥锁上死锁。
  3. 3每帧发一次指令并不会更快,只是在刷指令:服务器只执行最后一条,而且指令流在录像里看得见。
指令像玩家点击一样发给游戏:move_to 让英雄走到某点,attack 让英雄攻击目标。在 on_update 中发出指令。
orders.move_to(pos, queue)

移动指令。相关的还有 attack_move(pos) 和 move_to_target(entity)。

posVector
要走到的位置。
queueboolean?
排入队列,而不是替换。
orders.attack(target, queue)

攻击一个目标。

targetEntity
要攻击的目标。
queueboolean?
排入队列。
orders.cast_target(ability, target, queue)

对单位施法。

abilityEntity
技能或物品。
targetEntity
施法目标。
queueboolean?
排入队列。
orders.cast_position(ability, pos, queue)

对地点施法。

abilityEntity
技能或物品。
posVector
施法的目标点。
queueboolean?
排入队列。
orders.cast_no_target(ability, queue)

无目标施法;cast_toggle(ability) 切换开关类技能。

abilityEntity
技能或物品。
queueboolean?
排入队列。
orders.use_item(item, target)

使用物品。

itemEntity
物品栏中的物品。
targetEntity|Vector?
目标或地点,如果该物品需要的话。
orders.stop()

停止;orders.hold() 为原地不动。

orders.unit_move_to(unit, pos, queue)

给受控单位而不是你的英雄下达的指令;unit_attack 用法相同。

unitEntity
执行指令的单位:召唤单位、信使、第二个米波。
posVector
要走到的位置。
queueboolean?
排入队列。
orders.raw(type, opts)

完整的 dotaUnitOrder_t 指令集,用于具名调用覆盖不到的情况。

typenumber
orders.types 中的指令代码。
optstable
target, position, ability, unit, queue, issuer, show_effects。

renderer

8

基于 d3d11 的即时模式渲染器。在 on_draw 中绘制的内容只存在一帧;世界图元也可以改为只注册一次,由 C++ 负责重新投影。

基本规则
  1. 1颜色为 color(r, g, b, a),取值 0..255。文本绘制和测量的第一个参数是字体;全局的 font_default 已经准备好。
  2. 2world_to_screen 返回三个值,第三个表示“这个点到底在不在屏幕上”。不检查它,你身后的东西会被镜像画出来。
  3. 3字体要提前预热:图集重建前取到的度量是 47px 的后备字体,据此计算的排版会出错。
游戏画面on_draw · renderer.*menu::render
一帧按层组成:游戏画面,其上是脚本在 on_draw 中绘制的一切,最上面是菜单。renderer.* 只能在 on_draw 中绘制。
renderer.world_to_screen(pos)→ number, number, boolean

投影结果:x、y 以及是否在屏幕上。

posVector
世界坐标中的点。
renderer.text(font, x, y, col, str, align, shadow)

在屏幕上绘制文本;renderer.measure_text(font, str) 返回它的宽度和高度。

fontFont
字体;用全局的 font_default 即可。
x, ynumber
屏幕坐标。
colColor
颜色。
strstring
文本。
alignstring?
由 "c"(居中)、"r"(靠右)、"v"(垂直)组合而成。
shadowboolean?
在文本下方加阴影。
renderer.load_font(name, size, weight, flags)→ Font

按参数缓存的字体,重复调用是安全的。

namestring
系统字体名称。
sizenumber
字号,单位为像素。
weightnumber?
字重。
flagsnumber?
样式标志。
renderer.line(x1, y1, x2, y2, col, thick)

线段。相关的还有 rect、rect_outline、rounded_rect、triangle、poly 和 gradient。

renderer.circle_filled(x, y, r, col, segs)

屏幕上的实心圆;renderer.circle 绘制轮廓。

renderer.circle_3d(center, radius, col, segs, thick)

地面上的圆环,和游戏自带的范围指示器一样。

centerVector
世界坐标中的圆心。
radiusnumber
半径,单位为世界单位。
renderer.load_image(path)→ Image

图片;用 renderer.image(handle, x, y, w, h, tint) 绘制。

pathstring
磁盘上的文件,或游戏自带的 panorama 资源。
renderer.world_circle(id, center, radius, col, opts)

注册后由 C++ 接管的世界图形:在 tick 中注册,之后每帧重新投影。相关的还有 world_line、world_poly、world_text、world_image 和 world_clear。

idstring
图形的 id:相同的字符串会替换之前的图形。
centerVector
中心。
radiusnumber
半径,单位为世界单位。
colColor
颜色。
optstable?
ttl(默认 0.25 s)、thickness、segments、filled、layer。

注意注册不等于绘制:在 on_update 中调用即可,它会显示在 on_draw 所画内容的下层。

示例
local s = {}
local font = renderer.load_font("Verdana", 13)

function s.on_draw()
  local me = client.local_hero()
  if not (me and me:valid()) then return end

  for _, h in ipairs(entities.heroes("enemy")) do
    if h:is_alive() then
      local x, y, on = renderer.world_to_screen(h:position())
      if on then
        local w = renderer.measure_text(font, h:health() .. "")
        renderer.text(font, x - w / 2, y - 60, color(255, 90, 90, 255), h:health() .. "")
      end
    end
  end
end

return s

ui

9

菜单。控件在文件顶层声明一次,热重载后依然保留,通过 :get() 读取值。

基本规则
  1. 1第一个参数是 "选项卡.分组",按第一个点拆分;没有点时,控件会放进名为 general 的分组。
  2. 2设置的键是 "选项卡.分组.标签"。改了标签,保存在它下面的值就全部丢失;只改显示文字请用 c:caption()。
  3. 3对于 combo 和 listbox,:get() 返回从零开始的索引:options[c:get() + 1]。
scripts.demoenabledrangemode▾lowmidhighon:get()→falsetruetruerange:get()→300600900mode:get()→012modes[mode:get() + 1]→"low""mid""high"
控件在文件顶层声明一次,用 :get() 读取值。combo 的索引从零开始,所以写 options[c:get() + 1]。
ui.checkbox(group, label, default)→ Control

复选框。

groupstring
"选项卡.分组"。
labelstring
标签,同时也是设置键的一部分。
defaultboolean
首次运行时的值。
ui.slider_int(group, label, min, max, default, opts)→ Control

整数滑块;ui.slider_float 多一个小数位数参数。

min, maxnumber
上下限。
optstable?
{ unit = " ms", min_text = "Off", max_text = "Max" }。
ui.combo(group, label, items, default_index)→ Control

下拉列表;ui.multiselect 和 ui.listbox 用法相同。

itemsstring[]
选项。
default_indexnumber
从零开始的索引。
ui.hotkey(group, label, key, mode)→ Control

按键绑定。:get() 表示“此刻是否按住”,:key() 返回绑定的键码。

keynumber
默认的虚拟键码。
modestring?
"hold"、"toggle"、"always" 或 "off"。
ui.button(group, label, fn)→ Control

按钮。相关的还有 ui.color_picker、ui.textbox、ui.label 和 ui.separator。

c:get()

当前值;c:set(v) 用于写入。

c:on_change(fn)→ Control

响应值的变化。返回控件本身,因此可以链式调用。

fnfunction
调用时不带参数,值请用 :get() 读取。
c:depend(other, expected, mode)→ Control

让一个控件依附于另一个控件。

otherControl
父控件。
expectednumber?
使当前控件保持可用的值,默认为 1。
modestring?
"grey" 显示为灰色,"hide" 从页面上移除。
ui.menu_open()→ boolean

菜单是否打开,可以据此隐藏你自己叠加绘制的内容。

示例
local enabled = ui.checkbox("Draw.ESP", "Enemy circles", true)
local radius = ui.slider_int("Draw.ESP", "Radius", 100, 900, 400, { unit = " u" })

radius:depend(enabled)

local s = {}

function s.on_draw()
  if not enabled:get() then return end
  for _, h in ipairs(entities.heroes("enemy")) do
    if h:is_alive() then
      renderer.circle_3d(h:position(), radius:get(), color(255, 80, 80, 120))
    end
  end
end

return s

ml

10

在游戏进程内运行 ONNX,可同步执行,也可在工作线程上执行。模型就是脚本旁边的一个普通文件,核心对它一无所知。

基本规则
  1. 1onnxruntime.dll 必须放在 evolve.dll 旁边。ml.available() 告诉你它是否存在,ml.gpu_available() 告诉你 DirectML 是否可用。
  2. 2net:run 是同步的,耗时 0.2-0.5 ms。更重的模型交给 net:submit,下一帧再通过 net:poll 取回结果。
  3. 3history 返回原始的世界数值。归一化由脚本负责,而不是缓冲区。
history.tensor(e, 6)[6, 11] · 20 Hznet:run(t)0.2–0.5 毫秒net:submit(t)poll · +1 帧
net:run 是同步的,耗时 0.2–0.5 毫秒;较重的模型交给 net:submit,下一帧用 net:poll 取回。history 返回原始世界坐标值,归一化由脚本完成。
ml.load(path, provider)→ Model|nil

按路径和 provider 缓存的模型。失败时返回 nil,原因见 ml.last_error()。

pathstring
.onnx 文件。
providerstring?
默认 "cpu",也可以是 "gpu" 或 "auto"。
net:run(input)→ Tensor|nil

同步运行。

inputTensor|Tensor[]
一个张量,或与输入数量一致的列表。
net:submit(input)

在工作线程上异步运行;net:poll() 取回结果,net:busy() 表示仍在计算。

ml.tensor(rows, cols)→ Tensor

全零张量。索引从 1 开始:t:set(r, c, v)、t:get(r, c)、t:table()。

rowsnumber
行数。
colsnumber?
列数;不传则得到一个向量。
history.tensor(entity, frames)→ Tensor|nil

[frames, 11] 的运动缓冲区,前端用零填充。各通道列在 history.CH 中。

entityEntity
要读取的单位。
framesnumber
历史帧数,从旧到新排列。
history.set_rate(hz)

共享缓冲区的采样频率。history.track_all(true) 会把范围从英雄扩大到所有单位,开销明显更高。

hznumber
采样频率,默认 20。

data

11

文件、设置、日志和 JSON。脚本在客户端重启后延续状态所需的一切。

基本规则
  1. 1你自己的文件放在 fs.datadir(),同一个包里的其他文件从 fs.scriptdir() 查找。不要从报错里的 chunk 名还原路径:Lua 会把它截断到 60 个字符。
  2. 2整局都在写入的内容请用 fs.append。通过 fs.write 做“读取-修改-写入”循环,耗时随文件大小呈平方增长,写到一半出错还会丢失全部内容。
客户端fs.append(path, s)fs.read(path)→ 3 行↻ 重启data/run.log{"t":72,"lh":1}{"t":95,"lh":2}{"t":131,"lh":3}
整局持续写入的内容用 fs.append 写进 fs.datadir():这些行在客户端重启后仍然保留。用 fs.write 做读-改-写循环,开销随文件大小平方增长,中途崩溃会丢失全部内容。
log.info(msg)

向 evolve.log 写一行;相关的还有 log.warn 和 log.error。

fs.read(name)→ string|nil

读取整个文件;相关的还有 fs.exists 和 fs.listdir。

namestring
文件路径。
fs.append(name, str)

追加写入,会自动创建父目录。在比赛过程中持续记录日志就用它。

fs.datadir()→ string

客户端的 data/ 目录,已经创建好。fs.scriptdir() 返回 scripts/ 的绝对路径。

config.write_int(key, value)

脚本的类型化存储:read_int、read_float、read_bool、read_string 及对应的 write 方法,另有 config.has 和 config.delete。

keystring
你自己的键,与菜单无关。
valuenumber
值。
config.get(path)

按路径读取菜单控件的值;config.set(path, v) 用于写入。

pathstring
"选项卡.分组.标签",与 config.export() 使用的键相同。

注意路径不存在时会抛出错误,这是有意为之:否则路径里的拼写错误会悄无声息地表现为一个关闭的设置。

json.encode(value)→ string

把表转成字符串;json.decode(str) 做反向转换。

diag.errors()→ table[]

脚本错误,每种不同的故障合并为一行:event、script、msg、traceback、count。相关的还有 diag.profile(),它给出每个处理函数的耗时。