game
实时数据(read-only,写入抛 lua error)。访问字段 = 当前帧最新发布的快照。
更新策略
字段表带了一列更新策略 —— 它不是所有字段都每帧从游戏里读,后台按状态自动调频,被跳过的那帧沿用上次的值。理解这列能省掉"为什么屏外敌人血量刷得慢""为什么骨骼在屏外读到零"这类困惑。
- 敌方玩家 / 物资 / 抛射物有屏外降频:离屏、越远、非活体,后台读得越慢。凡标「仅屏内」的字段,屏外期间完全不刷新,读到的是最后一次在屏内看到的旧值。
- 唯一例外:被自瞄当前锁定的目标不受任何降频影响,恒每帧读(保证瞄准判定不依赖 ESP 开关)。
- 本地玩家不降频 —— 你自己永远相关,默认「每帧」。
- 「每次更新时计算」= 该值不直接读内存,而是每轮用最新的其它字段在本机现算,新鲜度跟随它依赖的字段。
- 频率口径的「每帧 / 每 N 帧」指后台读取周期,非固定 60Hz。
game.localplayer
本地玩家没有屏内/距离降频,默认「每帧」。
| 字段 | 类型 | 更新策略 | 说明 |
|---|---|---|---|
index | int | 每帧 | entity_list 索引 |
base / self_base | uint64 | 每帧 | 实体内存基址,配 offsets + mem.read/write 自助读写任意属性。死亡观战时二者分叉:base 跟随被观战者,self_base 永远真实本机——读"我自己"的属性用 self_base |
health | int | 每帧 | 当前血量 |
max_health | int | 低频 | 最大血量(拾甲/金包复活才变) |
shield | int | 每帧 | 当前护盾 |
max_shield | int | 低频 | 最大护盾(换甲才变) |
origin / camera_origin | Vec3 | 每帧 | 实体 / 相机位置(死亡观战时 origin 跟随被观战者) |
view_angle | Vec2 | 每帧 | {pitch, yaw} |
sway_angle | Vec2 | 每帧 | 含呼吸/sway 的子弹发射方向 |
view_offset / punch_angle | Vec3 | 每帧,仅存活未倒地 | 视图偏移 / 后坐力;死亡/倒地保留上次值 |
abs_velocity | Vec3 | 每帧 | |
flags_raw | uint32 | 每帧 | m_fFlags 原始位(FL_ONGROUND 等) |
team_num / squad_id | int | 创建时读一次 | |
platform_uid | string | 创建时读一次 | uint64 转 string |
weapon_id | int | 每帧,仅存活持枪 | 武器 string-table 索引;空手时置空 |
weapon_enum | int | 换武器时解析 | sdk::ItemId 数值,比 weapon 字符串更可靠 |
weapon | string | 换武器时解析 | ESP 显示用武器名 |
weapon_speed / weapon_scale | number | 每帧,仅存活持枪 | 射速 / 蓄力进度 |
target_zoom_fov | number | 每帧,仅存活持枪 | 武器开镜 fov |
weapon_next_ready_time | number | 每帧,仅存活持枪 | |
weap_state / burst_fire_index / ammo_in_clip | int | 每帧,仅存活持枪 | 内部弹药状态 |
is_semi_auto | bool | 每帧,仅存活持枪 | |
is_zooming | bool | 每帧,仅存活未倒地 | |
is_dead / is_down / is_on_ground | bool | 每帧 | |
is_grenade / is_hands | bool | 换武器时解析 | |
is_skydive | bool | 低频(约每 6 帧) | |
time_base | number | 每帧 | 世界时间基准 |
spec_index | int | 每帧 | 观战目标(仅死亡观战有值,活着恒 -1) |
backpack_tier | int | 低频(约每秒) | 0=None / 1=White / 2=Blue / 3=Purple / 4=Gold |
consumables | table | 低频(约每 15 帧) | 16 槽消耗品库存(每槽 {item: uint16, count: uint16},count==0 表示空槽) |
game.entities.players (pairs 可遍历)
每个 PlayerEntity 字段。这张表的更新策略叠了两层:实体本身屏内每帧、屏外降频,字段自己还可能有额外门控(如骨骼要屏内、身份创建时读一次)。实际刷新率取两者更慢的那个。
| 字段 | 类型 | 更新策略 | 说明 |
|---|---|---|---|
index / base / is_npc | int/uint64/bool | 创建时确定 | 不读内存 |
name / platform_uid / xp | string/string/int | 创建时读一次 | platform_uid 读失败会进局后有限重试 |
team_num / squad_id / rank_source | int | 创建时读一次 | |
grade / rank / rank_icon_key / rank_color | int/string | 一局内锁定 | 首帧按分数算,解析一次云端排名后不再变 |
legend / legend_icon_key | string | 换英雄时解析 | 模型名约每 64 帧采样,一局基本只解析一次 |
kills / damage | int | 低频(约每 64 帧) | 屏内屏外都读,受实体降频稀释 |
weapon_id | int | 换枪短滞后 | 武器指针屏内约每 12 帧刷,底层句柄很低频采样 |
weapon | string | 换武器时解析 | |
origin | Vec3 | 屏内每帧,屏外降频 | 死亡后不再读 |
abs_velocity | Vec3 | 仅屏内且有效距离内 | 屏外用位移差分估算兜底 |
head_pos | Vec3 | 仅屏内 | 由头骨每次更新算出(需骨骼已解析) |
yaw | number | <300m 约每 6 帧,更远更低频 | 屏内屏外都读 |
health / shield / max_shield | int | 仅屏内(约每 12 帧) | |
flags | int | 仅屏内(约每 3 帧) | m_fFlags 状态位 |
is_down / cloak_endtime | bool/number | 仅屏内(约每 3 帧) | |
is_dead | bool | 低频(约每 8 帧) | |
spec_index | int | 仅死亡玩家读(约每 8 帧) | 该玩家正在观战的目标;活人恒 -1 |
last_visible_time | number | 仅屏内且有效距离内 | 可见性时间戳 |
vec_min / vec_max | Vec3 | 仅屏内(约每 3 帧),需开动态框/地图识别 | 碰撞盒 AABB;两者都没开则不读 |
is_glow / is_wall | bool | 仅屏内(约每 12 帧),需开辉光 | |
roster | uint8 | 每次更新时查名册 | 名册标记位(pro/star/cheater/friends);名册没改不重查 |
distance | number | 每次更新时计算 | 米 |
is_visible / is_teammate / is_cloak | bool | 每次更新时计算 | 不读内存 |
in_screen / can_draw / is_model_blocked | bool | 每次更新时计算 | 渲染相关状态,直布罗陀气泡盾等静态遮挡 |
has_bones / has_fresh_bones() | bool | 仅屏内,本帧真读满 17 骨才为真 | 屏外恒 false;调用会触发补读全骨(见下) |
#game.entities.players不工作(userdata-backed 表无#)—— 用for _ in pairs(...) do n = n + 1 end计数。
骨骼 / Skeleton
PlayerEntity 暴露 17 根骨骼世界坐标,配 game.BONE(部位常量)与 game.BONE_LINKS(连线表)画骨架 ESP。
| 成员 / 常量 | 说明 |
|---|---|
player:bone(idx) | 部位 idx(0-based body part id)的世界坐标 Vec3;越界或骨骼未就绪返回零向量 |
player:has_fresh_bones() | 本帧 17 根骨是否真读到(false 时 bone() 多半是零,应跳过) |
game.BONE | 部位命名常量表:head=0, upper_chest, chest, waist, hip, l_shoulder, l_elbow, l_hand, r_shoulder, r_elbow, r_hand, l_thigh, l_knee, l_foot, r_thigh, r_knee, r_foot=16 |
game.BONE_LINKS | 连线数组,每项 {a, b} 是一对部位 id(与原生骨架一致,共 16 段) |
更新策略:骨骼只对屏内玩家读,屏外不读(
bone()返零)。后台默认只读头骨,读满 17 根需满足其一:开了骨架 ESP、该玩家被自瞄锁定、或用作 chams 远敌锚点。脚本一旦调用bone()或has_fresh_bones(),会让后台在随后约 0.5 秒内对屏内玩家补读全 17 根骨 —— 所以自己画骨架不必依赖菜单里的骨架 ESP 开关,直接调即可。
math.WorldToScreen(world)返回 3 个值(sx, sy, ok),不是 Vec2。
-- 骨架 ESP:遍历连线,把每根骨投影到屏幕画线
local LINKS = game.BONE_LINKS
event.on("frame_update", function()
for _, p in pairs(game.entities.players) do
if p.is_visible and not p.is_dead and not p.is_teammate and p:has_fresh_bones() then
for _, link in ipairs(LINKS) do
local ax, ay, ok1 = math.WorldToScreen(p:bone(link[1]))
local bx, by, ok2 = math.WorldToScreen(p:bone(link[2]))
if ok1 and ok2 then
draw.line(ax, ay, bx, by, draw.u8(255, 255, 255, 200), 1.5)
end
end
end
end
end)game.entities.loots (pairs 可遍历)
每个 LootEntity 字段。物资属性几乎创建时就定死,只有 origin 持续按状态调频;新落地物资约 0.25 秒被发现(发现延迟,与字段刷新无关)。
| 字段 | 类型 | 更新策略 | 说明 |
|---|---|---|---|
index / base | int/uint64 | 创建时确定 | 实体内存基址,配 offsets + mem.read/write 自助读 |
model_name | string | 创建时读一次 | |
model_hash | uint32 | 创建时读一次 | model_name 的 hash |
quality_level | int | 创建时读一次 | 0=未知, 1..5=COMMON..HEIRLOOM |
weapon_name_index | int | 创建时读一次 | 武器名 string-table 索引 |
custom_script_int / context_id | int | 创建时读一次 | |
classified_id | int | 每次更新时计算 | ItemId 枚举数值 |
classified_name / item_id_str / base_name | string | 每次更新时计算 | 分类后的名字 |
origin | Vec3 | 低频轮询 | 刚移动过高频;静止屏内约每秒 4 次;静止屏外约每秒 1 次;超出物资显示距离约每 4 秒 1 次。永不完全停读 |
distance | number | 每次更新时计算 | 米 |
game.entities.projectiles (pairs 可遍历)
每个 ProjectileEntity 字段。核心是高频画轨迹:近距离本机/敌方每帧读 origin;owner/team/半径/武器类这些生命期恒定的字段只在出现时读一次。
| 字段 | 类型 | 更新策略 | 说明 |
|---|---|---|---|
index / base | int/uint64 | 创建时确定 | 实体内存基址,配 offsets + mem.read/write 自助读 |
owner_handle_raw | uint32 | 首次出现读一次 | m_hOwnerEntity 原始 EHandle 位 |
team_num | int | 首次出现读一次 | |
dmg_radius | number | 首次出现读一次 | m_DmgRadius |
weapon_class_index | uint16 | 首次出现读一次 | m_weaponClassIndex,配合 game.world 反查武器名 |
creation_time | number | 首次出现记一次 | 实体出生时间戳 |
kind | string | 首次解析武器名时定型 | "frag" / "thermite" / "arc_star" / "other" / "unknown" |
origin | Vec3 | 每帧(近距离本机/敌方) | 超出轨迹显示距离约每秒 1 次;队友或地图静态道具确认后不再读 |
is_throwable | bool | 每次更新时计算 | 真玩家手雷(过滤静态 map 道具) |
is_enemy / is_local | bool | 每次更新时计算 |
game.aimbot (read-only)
| 字段 | 类型 | 说明 |
|---|---|---|
target_index | int | 当前 aimbot 目标的实体索引(无目标时为 -1) |
target_distance | number (米) | 当前目标距离 |
predict_pos | Vec3 | nil | aimbot 内部 predict 后的目标位置 |
trigger_busy | bool | 扳机决策当前是否在 busy 状态(自定义扳机 HUD 用) |
trigger | table | 扳机详细状态快照(见下,trigger HUD 用) |
weapon_next_ready_time字段在game.localplayer上;trigger 状态不另立 namespace 防多渠道。
game.aimbot.trigger
所有时间戳是引擎时钟(QPC 秒,base::get_time),不是 time.game()/time.now()。表里带了同源的 now,倒计时直接用它算(如 ready_in = t.weapon_ready_at - t.now)。
| 字段 | 类型 | 说明 |
|---|---|---|
now | number | 当前引擎时钟(QPC 秒),与下列时间戳同源;倒计时用它 |
busy | bool | 持有/释放窗口倒计时中 |
target_index | int | 当前评估目标(0 = 无) |
pressed | bool | 已 mouse_down |
last_hit | bool | 上次评估是否命中 |
snapshot_time | number | 本快照 game 时间 |
release_at / again_at | number | 计划 mouse_up / 下次允许触发时刻 |
visible_at | number | 目标可见起点(react 计时起点) |
weapon_ready_at | number | 武器就绪时刻(含 rdyfudge) |
react_window / again_window / release_window | number | 反应 / 再触发 / 持有时长窗口 |
rdyfudge / radscale | number | 就绪偏移 / 半径缩放 |
aim_offset | Vec3 | 该瞄部位相对 spine_head 的偏移 |
event.on("frame_update", function()
local t = game.aimbot.trigger
if t.target_index ~= 0 then
local ready_in = math.max(0, t.weapon_ready_at - t.now)
draw.text(20, 200, draw.u8(255,255,255,255),
string.format("trigger: tgt=%d hit=%s ready_in=%.2f",
t.target_index, tostring(t.last_hit), ready_in))
end
end)自定义瞄准算法(移动/平滑 + 预测覆写)
让脚本注册自己的移动算法(进 dropdown)和预测算法(全局覆写)。执行模型:回调在 render 线程跑(与 Lua VM 同线程,无锁)。平滑节奏 = aim 发布率 ↔ 渲染帧率取小(render 不限速 → 高频);预测慢变,render 算好瞄点交 aim 线程 500Hz 消费,不掉频。原生「普通/PID」路径完全不受影响。
game.aimbot.solve(shooter, target_pos, target_vel, v0, gravity) → {pitch, yaw, time} | nil
原生弹道解算(无状态,可随便调)。pitch/yaw 为 Source 角(度,pitch 正=向下),time 为飞行时间(秒)。v0 ≤ 1 视为 hitscan(time=0);超出射程返回 nil。坐标 Vec3 世界系,速度 u/s,重力 u/s²。
game.aimbot.register_algorithm(name, fn)
注册一个「移动算法」。name 追加到 aim / 扳机的 移动算法 dropdown;用户选中后由本算法接管鼠标平滑。fn(state) 返回 dx, dy(mickey 位移,引擎自动累加子像素)。脚本卸载时自动注销。
只能在脚本加载主体(top-level)调用 —— deferred 回调(
event.on("frame_update", ...)/Delay()/gui.Button的 callback 等)里调会被拒绝并记限频日志,因为 owner 在 load 完成后已清空,注册没法绑定回正确脚本(避免静默 leak)。需要懒注册请把register_algorithm放主体里、用脚本侧 flag 控制启用。
state 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
err_pitch / err_yaw | number (度) | 当前瞄准误差(已含预测补偿 + 死区清零) |
dt | number (秒) | 距本算法上次被调的时间 |
distance | number (米) | 目标距离 |
zooming | bool | 是否开镜 |
mouse_sens | number | 游戏内灵敏度(要屏幕一致可用 err/0.022/mouse_sens 换 mickey) |
target_index | int | 目标实体索引 |
shooter / sway | Vec3 / Vec2 | 发射点 / 当前参考视角 |
target / velocity | Vec3 | 世界命中点 / 目标速度(u/s) |
符号约定与原生一致:
dx = -err_yaw·k,dy = err_pitch·k。
game.aimbot.set_predictor(fn)
全局复写预测算法。fn(target) 返回世界瞄点 Vec3;aim 线程 500Hz 用它替原生 solve。传 nil 清除(回退原生)。target 字段:index、shooter、aim_point、head、origin、velocity(Vec3)、distance、v0、gravity。
aim_pointvshead:aim_point是当前实际瞄点 —— 上半身/全身档下它会贴在离准星最近的身体部位上、随准星移动,predictor 应当对它算弹道提前量。head是稳定的真头骨世界点,只有头部专属逻辑(如固定瞄头补偿)才用。做 lead 一律用aim_point别用head(否则瞄点被拽回头、抵消瞄点跟随)。
同上
register_algorithm—— 只能在脚本加载主体调用。同时全局只能由一个脚本注册:已被其它脚本占用时拒绝且不会通知 Lua 侧(只记 throttled 日志),建议遵循"一进程一 predictor"约定或卸掉占用脚本后重试。
local tab = gui.tab("AimAlgo")
local g = tab:group("g", "Lua 瞄准算法", 0, 0, 300, 0)
local gain = g:slider_float("gain", "强度 (mickey/度)", 0.55, 0.05, 2.0)
local lead = g:checkbox("lead", "预测覆写", true)
-- 自适应缓动平滑:进 dropdown,选中即生效
game.aimbot.register_algorithm("Lua 自适应", function(s)
local mag = math.sqrt(s.err_pitch^2 + s.err_yaw^2)
local f = gain:get() * (1.0 - 0.4 * math.exp(-mag)) -- 近身柔、远端满
return -s.err_yaw * f, s.err_pitch * f
end)
-- 预测覆写:复用原生 solve 拿飞行时间,再按速度外推落点
game.aimbot.set_predictor(function(t)
if not lead:get() then return t.aim_point end
local sol = game.aimbot.solve(t.shooter, t.aim_point, t.velocity, t.v0, t.gravity)
if not sol then return t.aim_point end
return Vec3(t.aim_point.x + t.velocity.x * sol.time,
t.aim_point.y + t.velocity.y * sol.time,
t.aim_point.z + t.velocity.z * sol.time)
end)game.world
| 字段 | 类型 | 说明 |
|---|---|---|
ring | table | {origin=Vec3, radius_start=N, radius_end=N, time_start=N, time_end=N, is_active=bool};radius / origin 单位为游戏内 units(≈ inches),换米 ÷ 39.37 |
map_name | string | 当前地图字符串 |
game 顶层 method
| 方法 | 返回 | 说明 |
|---|---|---|
game:is_in_game() | bool | 是否在游戏中(signon_state 综合 + 没在等待匹配) |
game:signon_state() | int | client_state.signon_state 原始值 |
示例
local lp = game.localplayer
if lp.is_dead then return end
for _, p in pairs(game.entities.players) do
if (not p.is_teammate) and p.is_visible and p.distance < 50 then
log.info("敌人 " .. (p.name or "?") .. " 在 " .. p.distance .. "m")
end
end
if game.world.map_name == "mp_rr_arena_skygarden" then
-- ...
end