1. 简介
BugMenu 内置 Lua 5.4 脚本引擎。你可以编写 .lua 脚本实现 ESP、自瞄辅助、自动购买、触发、移动辅助、HUD 绘制等任何功能,无需重新编译 DLL,改完脚本点菜单里的「重新加载」即可生效。
每个脚本运行在独立的 Lua 状态中:一个脚本崩溃/报错不影响其他脚本,也不会让游戏崩溃(全部 SEH 保护)。脚本之间不能互相调用。
2. 脚本目录
脚本目录与配置文件同一套创建逻辑:
-- Windows 资源管理器地址栏直接粘贴: %USERPROFILE%\Documents\BugMenu\Scripts -- 例如你的用户名是 Bug: C:\Users\Bug\Documents\BugMenu\Scripts
把任意 .lua 文件放进该目录,在菜单 Lua 标签页点「重新加载」。首次启动会自动生成一个 example.lua 示例脚本。
3. 回调函数
脚本中定义以下全局函数即注册回调:
| 函数 | 触发时机 | 参数 |
|---|---|---|
on_rage() | 每游戏 tick(Rage 暴力处理前) | 无,此时可用 rage.* 配置本帧目标/开火/参数 |
on_create_move(cmd) | 每游戏 tick(创建指令时) | cmd:指令对象,可改按钮/移动/视角 |
on_draw() | 每渲染帧(Present) | 无,此时可调用 draw.* 绘制 |
on_frame() | 每渲染帧(轻量,先于 on_draw) | 无 |
-- 不写这些函数就不会被调用,脚本可以只定义其中一部分。 function on_frame() -- 每帧执行一次的逻辑(如计时器、计数器) end function on_create_move(cmd) -- 每个游戏 tick 执行。cmd 是命令对象,见 §5。 end function on_draw() -- 每帧绘制。此时才能使用 draw.* 绘图。 end
4. cheat 表(全局 API)
| 函数 | 说明 |
|---|---|
cheat.log(...) / cheat.print(...) | 输出日志到控制台(可传多个参数) |
cheat.error(msg) | 输出错误日志 |
cheat.key_down(vk) | 虚拟键码是否按住,如 cheat.key_down(0x46) 判断 F 键(0x46) |
cheat.key_pressed(vk) | 虚拟键码是否刚按下(沿) |
cheat.screen_size() | 返回 宽, 高 |
cheat.is_in_game() | 是否在游戏中(连接 + 进入对局) |
cheat.frame() | 当前渲染帧计数(可用于隔帧执行) |
cheat.get_local_player() | 返回本地玩家实体或 nil |
cheat.get_players() | 返回所有玩家实体数组(表) |
cheat.get_all_entities() | 返回全部实体数组(含武器、炸弹等,用 :is_player() 区分) |
cheat.world_to_screen(x, y, z) | 世界坐标转屏幕坐标;成功返回 屏幕X, 屏幕Y,失败返回 nil |
cheat.console(text) | 写入控制台(预留) |
cheat.exec(cmd) | 执行游戏控制台命令(如 cheat.exec("buy ak47")),返回是否成功。命令不能含引号 |
cheat.add_checkbox(key, label, default) | 注册复选框配置项(default 为 true/false) |
cheat.add_slider_int(key, label, min, max, default) | 注册整数滑块配置项 |
cheat.add_slider_float(key, label, min, max, default, step) | 注册浮点滑块配置项 |
cheat.add_combo(key, label, {选项...}, default_index) | 注册下拉选择配置项 |
cheat.get_config(key) | 读取配置项当前值(复选框返回 boolean,其余返回 number);不存在返回 nil |
cheat.set_config(key, value) | 脚本运行时修改配置项值(会持久化) |
4.5 脚本配置项(可调节参数)
开发者用 cheat.add_* 注册配置项后,用户可以在菜单 Lua 标签页的「脚本配置」区域调节这些参数(复选框 / 滑块 / 下拉框),调节结果自动保存到 scripts_config.json,重启游戏后保持。脚本运行时用 cheat.get_config(key) 读取当前值。
-- 脚本顶层注册配置项(在函数外调用一次) cheat.add_checkbox("esp_enable", "启用自定义 ESP", true) cheat.add_slider_int("esp_dist", "显示距离", 100, 3000, 800) cheat.add_slider_float("esp_alpha", "透明度", 0.1, 1.0, 0.8, 0.05) cheat.add_combo("color_mode", "颜色模式", { "自动", "固定绿", "固定黄" }, 0) function on_draw() -- 运行时读取用户当前调节的值 local enabled = cheat.get_config("esp_enable") local dist = cheat.get_config("esp_dist") local alpha = cheat.get_config("esp_alpha") local mode = cheat.get_config("color_mode") -- ... 用这些值绘制 end
5. 实体方法(ent:)
从 cheat.get_local_player() / cheat.get_players() 获得的实体,支持以下方法(全部内存安全):
| 方法 | 返回 | 说明 |
|---|---|---|
ent:is_valid() | bool | 实体是否仍有效(玩家死亡/离开后失效) |
ent:is_player() | bool | 是否为玩家 |
ent:is_alive() | bool | 存活(血量>0 且生命状态=0) |
ent:is_enemy() | bool | 是否为敌人(与本地玩家不同队) |
ent:is_scoped() | bool | 是否开镜 |
ent:is_dormant() | bool | 是否休眠(不可见对象) |
ent:get_position() | x, y, z | 脚部世界坐标 |
ent:get_origin() | x, y, z | 同 get_position |
ent:get_eye_position() | x, y, z | 眼睛位置(瞄准参考) |
ent:get_velocity() | x, y, z | 速度向量 |
ent:get_health() | int | 当前血量 |
ent:get_max_health() | int | 最大血量 |
ent:get_armor() | int | 护甲 |
ent:get_team() | int | 队伍(2=CT, 3=T) |
ent:get_flags() | int | 实体 flags(FL_ONGROUND=1 表示在地上) |
ent:get_index() | int | 实体索引 |
ent:get_address() | int | 内存地址(高级用户) |
ent:get_class_name() | string | 类名,如 C_CSPlayerPawn |
6. cmd 方法(on_create_move 参数)
cmd 是当前游戏指令对象,修改它会影响本 tick 的移动/按钮/视角:
| 方法 | 说明 |
|---|---|
cmd:get_buttons() | 当前按钮位掩码(整数) |
cmd:set_buttons(bits) | 整体设置按钮位 |
cmd:add_buttons(bits) | 追加按钮(按住) |
cmd:remove_buttons(bits) | 移除按钮 |
cmd:get_forward_move() | 前/后移动量 |
cmd:set_forward_move(v) | 设置前/后移动量(-450 ~ 450) |
cmd:get_side_move() | 左/右移动量 |
cmd:set_side_move(v) | 设置左/右移动量(-450 ~ 450) |
cmd:set_up_move(v) | 设置垂直移动量 |
cmd:get_angles() | 返回 pitch, yaw(发送视角) |
cmd:set_angles(pitch, yaw) | 设置发送视角(可做静默瞄准) |
7. 绘制 API(draw.*,仅 on_draw 内有效)
颜色参数均为 0-255 整数,默认白色不透明。坐标是屏幕像素。
| 函数 | 说明 |
|---|---|
draw.text(x, y, text, r, g, b, a, size) | 绘制文字 |
draw.text_centered(x, y, text, r, g, b, a, size) | 水平居中文字 |
draw.line(x1, y1, x2, y2, r, g, b, a, thickness) | 直线 |
draw.rect(x, y, w, h, r, g, b, a, thickness) | 矩形边框 |
draw.filled_rect(x, y, w, h, r, g, b, a) | 实心矩形 |
draw.circle(x, y, radius, r, g, b, a, thickness) | 圆形边框 |
draw.filled_circle(x, y, radius, r, g, b, a) | 实心圆 |
7.5 Rage API(rage.*,on_rage 回调内使用)
on_rage() 在 Rage 暴力处理之前每 tick 调用。在这里用 rage.* 配置本帧的强制目标、强制开火和参数覆盖(只影响本帧,菜单配置不受影响)。读取类函数 rage.get_* 返回上一帧 Ragebot 的状态。
| 函数 | 说明 |
|---|---|
rage.get_target() | 当前锁定敌人实体或 nil |
rage.get_hitbox() | 当前锁定部位索引(-1 表示无) |
rage.get_aim_point() | 当前瞄准点 x, y, z |
rage.is_firing() | 当前是否开火 |
rage.get_lock_streak() | 锁定确认连击帧数 |
rage.set_target(ent) | 强制只锁定指定敌人(传 nil 取消) |
rage.clear_target() | 取消强制目标 |
rage.force_shot() | 本帧强制开火(无需 2 帧锁定确认,一次性) |
rage.set_autofire(true/false) | 覆盖自动开火 |
rage.set_autostop(true/false) | 覆盖自动急停 |
rage.set_autowall(true/false) | 覆盖自动穿墙 |
rage.set_hitchance(pct) | 覆盖命中率(0-99) |
rage.set_mindamage(v) | 覆盖穿墙最小伤害 |
rage.set_mindamage_vis(v) | 覆盖可见最小伤害 |
rage.set_fov(v) | 覆盖最大 FOV(0-360) |
rage.set_multipoint(v) | 覆盖多点:0 = 关,0.01-0.3 = 开并设外扩缩放 |
rage.set_hitboxes(t) | 覆盖命中部位表,如 {0} 只打头、{0,2,3} 打头胸腹、{head=true, chest=true} |
部位索引对照:0 头 · 1 颈 · 2 胸口 · 3 腹部 · 4 骨盆 · 5 手臂 · 6 腿部 · 7 脚部
8. 按钮常量(buttons.)
| 常量 | 含义 | 常量 | 含义 |
|---|---|---|---|
buttons.attack | 开火 | buttons.moveleft | A 左移 |
buttons.attack2 | 次要攻击 | buttons.moveright | D 右移 |
buttons.jump | 跳跃 | buttons.reload | 换弹 |
buttons.duck | 下蹲 | buttons.sprint | 冲刺 |
buttons.forward | W 前进 | buttons.zoom | 开镜 |
buttons.back | S 后退 | buttons.use | E 使用 |
buttons.left | 视角左转 | buttons.right | 视角右转 |
9. 完整示例
示例 A:自定义 ESP(绘制血量条 + 名字)
function on_draw()
if not cheat.is_in_game() then return end
for _, p in ipairs(cheat.get_players()) do
if not p:is_valid() or not p:is_alive() then goto continue end
local x, y, z = p:get_eye_position()
local sx, sy = cheat.world_to_screen(x, y, z - 20)
if sx then
local hp = p:get_health()
-- 血量颜色:绿 >50 / 黄 20-50 / 红 <20
local r, g = 0, 255
if hp < 50 then r, g = 255, 255 end
if hp < 20 then r, g = 255, 0 end
draw.text_centered(sx, sy, p:get_class_name(), 255, 255, 255, 255, 13)
draw.filled_rect(sx - 20, sy + 16, 40, 4, 0, 0, 0, 160)
draw.filled_rect(sx - 20, sy + 16, 40 * (hp / 100), 4, r, g, 0, 255)
end
::continue::
end
end
示例 B:按住鼠标侧键自动连跳(on_create_move)
function on_create_move(cmd)
-- VK_XBUTTON1 = 0x05(鼠标侧键)
if cheat.key_down(0x05) then
cmd:add_buttons(buttons.jump)
end
end
示例 C:自动购买(on_frame 中按 F1 买枪)
function on_frame()
-- F1 = 0x70,只执行一次(key_pressed 是上升沿)
if cheat.key_pressed(0x70) then
cheat.log("按下了 F1:准备购买")
end
end
示例 D:静默转身瞄准(设置发送视角)
function on_create_move(cmd)
local lp = cheat.get_local_player()
if not lp then return end
local px, py, pz = lp:get_eye_position()
for _, e in ipairs(cheat.get_players()) do
if e:is_valid() and e:is_alive() and e:is_enemy() then
local ex, ey, ez = e:get_eye_position()
local dx, dy, dz = ex - px, ey - py, ez - pz
local len = math.sqrt(dx * dx + dy * dy + dz * dz)
if len > 0.001 then
local pitch = -math.asin(dz / len) * 180 / math.pi
local yaw = math.atan2(dy, dx) * 180 / math.pi
cmd:set_angles(pitch, yaw)
end
break
end
end
end
示例 E:Rage 自动锁定最近敌人并只打头(on_rage)
-- 每 tick 在 Rage 暴力处理前运行:锁定最近的敌人,强制打头 + 高命中率
function on_rage()
local lp = cheat.get_local_player()
if not lp then return end
local best, bestDist = nil, math.huge
for _, e in ipairs(cheat.get_players()) do
if e:is_valid() and e:is_alive() and e:is_enemy() then
local ex, ey, ez = e:get_position()
local px, py, pz = lp:get_position()
local dx, dy = ex - px, ey - py
local d = math.sqrt(dx * dx + dy * dy)
if d < bestDist then bestDist = d; best = e end
end
end
if best then
rage.set_target(best) -- 只锁定这个敌人
rage.set_hitboxes({ 0 }) -- 只打头
rage.set_hitchance(70)
rage.set_mindamage(5)
rage.set_autowall(true)
rage.force_shot() -- 锁定即开火(跳过 2 帧确认)
end
end
示例 F:Rage 状态 HUD(on_draw 显示锁定信息)
function on_draw()
local t = rage.get_target()
if not t then return end
local hb = rage.get_hitbox()
local x, y, z = rage.get_aim_point()
local sx, sy = cheat.world_to_screen(x, y, z)
if sx then
local str = string.format("目标血量 %d | 部位 %d | %s",
t:get_health(), hb, rage.is_firing() and "开火中" or "锁定")
draw.text(sx - 60, sy, str, 255, 255, 0, 255, 13)
end
end
示例 G:可调节参数的自定义 ESP(脚本配置项)
-- 顶层注册配置项,用户可在 菜单 → Lua → 脚本配置 里调节
cheat.add_checkbox("esp_show", "启用 ESP", true)
cheat.add_slider_int("esp_max_dist", "最大显示距离", 100, 3000, 900)
cheat.add_slider_float("esp_box_alpha", "方框透明度", 0.1, 1.0, 0.8, 0.05)
cheat.add_combo("esp_color", "方框颜色", { "绿色", "黄色", "红色" }, 0)
local function box_color()
local mode = cheat.get_config("esp_color")
if mode == 0 then return 0, 255, 100 end
if mode == 1 then return 255, 255, 0 end
return 255, 60, 60
end
function on_draw()
if not cheat.get_config("esp_show") then return end
if not cheat.is_in_game() then return end
local maxDist = cheat.get_config("esp_max_dist")
local alpha = cheat.get_config("esp_box_alpha") * 255
local lp = cheat.get_local_player()
if not lp then return end
local lx, ly, lz = lp:get_position()
local r, g, b = box_color()
for _, p in ipairs(cheat.get_players()) do
if p:is_valid() and p:is_alive() and p:is_enemy() then
local x, y, z = p:get_position()
local d = math.sqrt((x - lx)^2 + (y - ly)^2 + (z - lz)^2)
if d <= maxDist then
local sx, sy = cheat.world_to_screen(x, y, z - 74)
if sx then
draw.rect(sx - 16, sy - 4, 32, 44, r, g, b, alpha, 1.5)
draw.text_centered(sx, sy - 18,
string.format("%d HP", p:get_health()), r, g, b, 255, 12)
end
end
end
end
end
示例 H:自动购买(cheat.exec 执行控制台命令)
软件内置没有自动购买,但通过 cheat.exec 执行游戏命令即可实现。脚本目录已自带 auto_buy.lua 完整示例。
-- 配置项(菜单 → Lua → 脚本配置 里可切换方案) cheat.add_combo("loadout", "购买方案", { "步枪", "经济局", "狙击" }, 0) cheat.add_checkbox("buy_armor", "买防弹衣+头盔", true) cheat.add_checkbox("auto_buy", "出生自动购买", true) local function do_buy() local mode = cheat.get_config("loadout") local list = {} if mode == 0 then list = { "buy ak47", "buy smokegrenade", "buy flashbang", "buy hegrenade", "buy molotov" } elseif mode == 1 then list = { "buy galilar", "buy flashbang", "buy smokegrenade" } else list = { "buy awp", "buy smokegrenade", "buy flashbang" } end if cheat.get_config("buy_armor") then table.insert(list, 2, "buy vesthelm") end for _, c in ipairs(list) do cheat.exec(c) -- 执行控制台命令 end end -- 出生/复活时自动购买 + F1 手动购买 local was_alive = false function on_frame() if not cheat.get_config("auto_buy") then return end local lp = cheat.get_local_player() local alive = lp ~= nil and lp:is_valid() and lp:is_alive() or false if alive and not was_alive then do_buy() end was_alive = alive if cheat.key_pressed(0x70) then do_buy() end end
10. 注意事项
- 安全 脚本错误不会导致游戏崩溃,错误会显示在菜单 Lua 页和日志里。
- 性能 不要在
on_draw里做重活(每帧调用)。实体遍历每次约 0-8000 索引,注意缓存。 - 时机
on_create_move每个 tick 执行一次;on_draw每渲染帧执行一次。两者频率不同。 - Rage
rage.set_*只影响本帧,菜单配置保持不变。rage.force_shot()是"确认即开枪",请配合目标有效性判断使用,避免空枪。 - 锁定
rage.set_target(ent)要求实体有效;目标死亡/消失后 Ragebot 自动跳过,脚本需每帧重新设置目标。 - 命令
cheat.exec依赖 Panorama UI 上下文(游戏中可用)。命令不能含引号;带参数的命令空格分隔即可,如cheat.exec("buy ak47")。 - 注意 内存地址相关的 API 仅供学习;不要在脚本中直接写任意内存(无此 API)。
- 修改脚本后无需重启游戏:菜单 → Lua → 「重新加载」即可。
- 脚本目录每次启动自动创建;首次启动生成示例脚本。