BugMenu Lua 开发者文档

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.moveleftA 左移
buttons.attack2次要攻击buttons.moverightD 右移
buttons.jump跳跃buttons.reload换弹
buttons.duck下蹲buttons.sprint冲刺
buttons.forwardW 前进buttons.zoom开镜
buttons.backS 后退buttons.useE 使用
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. 注意事项