1. Get started
WallbangBros V2 currently exposes wbb.api_version == 3. Scripts are UTF-8 files with a .lua extension.
cmd.game and frame.game), expanded player, weapon, and engine fields, font and size selection for wbb.draw.text, and honors the visible_enemy hitgroup mask. Scripts written for v2 need their version check updated to 3.Script folder
Counter-Strike Global Offensive\game\csgo\WallBangBros\lua
Create the folder if it doesn't exist. Open the WallbangBros V2 menu, select the LUA tab, then enable your file. Script controls appear under LUA SETTINGS while that script is active. Disable and re-enable the file after editing it.
Your first script
Save this as your-first.lua:
assert(wbb.api_version == 3)
local enabled = wbb.ui.checkbox("enabled", "Hello overlay", true)
local paint_token = wbb.events.on("paint", function(frame)
if not wbb.ui.get("enabled") or not frame.local_player.alive then
return
end
local text = string.format("HP: %d", frame.local_player.health)
wbb.draw.text(20, 80, wbb.rgba(38, 255, 61, 255), text)
end)
function __shutdown()
wbb.events.off(paint_token)
end
__shutdown() is optional but recommended. Use it to unregister callbacks and clear script-owned state when the script is disabled or reloaded.
wbb.ui.checkbox call creates this saved setting.2. Events
token = wbb.events.on(name, callback [, priority])
removed = wbb.events.off(token)
| Event | Callback value | What scripts can do |
|---|---|---|
create_move | Command and current game snapshots | Read state, request traces, and change validated command fields. |
paint | Frame and current game snapshots | Submit drawing commands and project world positions. |
game_event | Every supported game event | Read the event name and available numeric fields. |
Event name, such as player_hurt | Only matching events | Handle one event without filtering game_event. |
onreturns an integer token. Keep it if you want to callofflater.offreturnstruewhen it removed the callback, otherwisefalse.- Priority is an integer from
-100to100. Higher priorities run first. - A callback error disables that callback and displays a bounded error in the Lua error overlay.
3. Commands and snapshots
create_move command
| Field | Type | Writable |
|---|---|---|
command_number | integer | No |
view_pitch | number, -89..89 | Yes |
view_yaw | number, -180..180 | Yes |
view_roll | number, -50..50 | Yes |
forward_move, side_move, up_move | number, -1..1 | Yes |
buttons | integer bit mask | Yes, known button bits only |
engine, game, local_player, weapon, players, trace_results | tables | No |
Button constants are available in wbb.buttons: attack, jump, duck, forward, back, use, cancel, left, right, move_left, move_right, attack2, run, reload, left_alt, right_alt, score, speed, walk, zoom, first_weapon, second_weapon, bullrush, first_grenade, second_grenade, middle_attack, and use_or_reload.
cmd.buttons = bit.bor(cmd.buttons, wbb.buttons.jump)
cmd.buttons = bit.band(cmd.buttons, bit.bnot(wbb.buttons.jump))
paint frame
The frame table contains frame_id, delta_time, engine, game, local_player, weapon, and players.
Engine snapshot
connected, in_game, ffa, max_clients, highest_entity_index, screen_width, screen_height, and tick_interval. The tick interval is the smoothed server tick interval in seconds, so math.floor(1 / frame.engine.tick_interval + 0.5) reports the tickrate.
Game state snapshot
cmd.game and frame.game hold round, bomb, and buy state. valid is false while no round is reachable, and every field then uses a safe default.
Booleans: valid, freeze_period, warmup_period, match_waiting_for_resume, bomb_planted, bomb_dropped, match_started, t_cant_buy, and ct_cant_buy.
Values: round_time, round_start_time, game_phase, total_rounds_played, rounds_played_this_phase, overtime_playing, round_win_status, and round_win_reason.
local token = wbb.events.on("paint", function(frame)
if frame.game.valid and frame.game.bomb_planted then
wbb.draw.text(20, 40, wbb.rgba(255, 90, 90, 255), "BOMB PLANTED")
end
end)
Player snapshot
State: valid, local_player, alive, scoped, has_helmet, immune, flashed, on_ground, ducking, defusing, walking, in_buy_zone, has_defuser, and is_bot.
Identity: name, entity_index, controller_index, handle, controller_handle, ground_handle, steam_id, steam_id_low, and steam_id_high. The steam id is a decimal string.
Values: flags, health, max_health, team, armor, money, ping, score, mvps, total_hits_on_server, move_type, crosshair_entity_index, shots_fired, flash_duration, stamina, last_spawn_time, and velocity_modifier.
Vectors: origin, absolute_origin, velocity, eye_position, eye_angles, rotation, collision_mins, collision_maxs, and aim_punch.
Weapon snapshot
valid, name, entity_index, handle, subclass_id, definition_index, clip, max_clip, reserve_ammo, reserve_ammo_secondary, weapon_mode, bullets, damage, range, range_modifier, penetration, armor_ratio, headshot_multiplier, spread, inaccuracy, inaccuracy_stand, inaccuracy_crouch, accuracy_penalty, recoil_index, last_shot_time, burst_mode, paint_kit, seed, wear, stat_trak, quality, account_id, item_id, item_id_low, and item_id_high.
{ x = number, y = number, z = number }. Check valid before using a player or weapon snapshot.4. Drawing
Drawing calls are valid only inside a paint callback.
wbb.draw.line(x1, y1, x2, y2, color [, thickness])
wbb.draw.rect(x1, y1, x2, y2, color [, thickness])
wbb.draw.filled_rect(x1, y1, x2, y2, color)
wbb.draw.circle(x, y, radius, color [, segments [, thickness]])
wbb.draw.filled_circle(x, y, radius, color [, segments])
wbb.draw.text(x, y, color, text [, font [, size]])
screen = wbb.draw.world_to_screen({ x = world_x, y = world_y, z = world_z })
color = wbb.rgba(red, green, blue, alpha)
- Screen coordinates are pixels.
wbb.rgbaaccepts integer channels from0to255and returns a packed color.world_to_screenreturns{ x = number, y = number }, ornilwhen the point can't be projected.- World-space shapes are Lua-owned math: project their points, then draw them with the screen-space functions.
wbb.draw.textaccepts a font name and a pixel size. Fonts aredefault,pixel,inter,tab,section,title, andicon. The size range is0to200, where0keeps the font's native size.
World-space ring example
This complete script draws a lime ring 180 world units ahead. It rejects missing and off-screen projections before calling wbb.draw.line, so near-camera points can't create invalid drawing coordinates.
assert(wbb.api_version == 3)
wbb.ui.checkbox("enabled", "Space ring enabled", true)
local lime = wbb.rgba(38, 255, 61, 255)
local view_yaw = 0
local move_token = wbb.events.on("create_move", function(cmd)
view_yaw = cmd.view_yaw
end)
local function project_visible(frame, point)
local screen = wbb.draw.world_to_screen(point)
if not screen then
return nil
end
local width = frame.engine.screen_width
local height = frame.engine.screen_height
if screen.x < 0 or screen.x > width or screen.y < 0 or screen.y > height then
return nil
end
return screen
end
local function draw_ring(frame, center)
local previous = nil
local segments = 64
local radius = 32
for i = 0, segments do
local angle = i * math.pi * 2 / segments
local current = project_visible(frame, {
x = center.x + math.cos(angle) * radius,
y = center.y + math.sin(angle) * radius,
z = center.z
})
if previous and current then
wbb.draw.line(previous.x, previous.y, current.x, current.y, lime, 2)
end
previous = current
end
end
local paint_token = wbb.events.on("paint", function(frame)
local player = frame.local_player
if not wbb.ui.get("enabled") or not player.valid or not player.alive then
return
end
local yaw = math.rad(view_yaw)
draw_ring(frame, {
x = player.eye_position.x + math.cos(yaw) * 180,
y = player.eye_position.y + math.sin(yaw) * 180,
z = player.eye_position.z - 36
})
end)
function __shutdown()
wbb.events.off(move_token)
wbb.events.off(paint_token)
end
space-ring.lua running in WallbangBros V2.5. UI and configuration
Script-owned controls
value = wbb.ui.checkbox(id, label, default)
value = wbb.ui.slider_int(id, label, default, minimum, maximum)
value = wbb.ui.slider_float(id, label, default, minimum, maximum)
value = wbb.ui.color(id, label, { red, green, blue, alpha })
virtual_key = wbb.ui.keybind(id, label, default_vk)
value = wbb.ui.get(id)
down = wbb.ui.key_down(id)
- Register controls at the top level while the script loads. Callbacks should use
getandkey_down. - Control values persist in WallbangBros V2 configuration profiles.
- UI colors are four-number arrays from
0.0to1.0. - Keybinds use Windows virtual-key values from
1to0xFE.key_downreturns the current physical key state.
Hold or toggle example
wbb.ui.keybind("hotkey", "Overlay key", 0x56)
wbb.ui.checkbox("toggle", "Toggle mode", true)
local latched = false
local was_down = false
local token = wbb.events.on("paint", function()
local down = wbb.ui.key_down("hotkey")
local toggle = wbb.ui.get("toggle")
if toggle and down and not was_down then
latched = not latched
end
local active = toggle and latched or down
was_down = down
if active then
wbb.draw.text(20, 110, wbb.rgba(38, 255, 61, 255), "Active")
end
end)
key_down only reports the physical key state.WallbangBros V2 feature configuration
wbb.features and wbb.config are aliases.
paths = wbb.features.list()
descriptor, err = wbb.features.describe("esp.enabled")
value, err = wbb.features.get("esp.enabled")
ok = wbb.features.set("esp.enabled", true)
list returns an array of current config paths. describe returns type, writable, has_range, optional minimum/maximum, and value. Supported value types are boolean, integer, number, and color.
6. Traces
Trace requests are valid only inside create_move. Their results appear in the next command under cmd.trace_results[id].
wbb.trace.fraction(id, start, delta)
wbb.trace.visible_enemy(id, view_angles [, hitgroup_mask])
wbb.trace.penetrable_enemy(id, view_angles [, minimum_damage [, hitgroup_mask]])
wbb.trace.direction(id, start, direction [, allow_penetration
[, minimum_damage [, hitgroup_mask [, expected_target_index]]]])
Choose a positive integer request ID. A result contains valid, target_entity_index, hit_group, penetrated, fraction, and damage. The optional hitgroup mask accepts an integer from 0 to 0x1FF and is applied to every trace type that lists it, including visible_enemy.
local token = wbb.events.on("create_move", function(cmd)
local previous = cmd.trace_results[1]
if previous and previous.valid then
-- Use previous.fraction, previous.damage, and target fields here.
end
wbb.trace.fraction(1, cmd.local_player.eye_position, {
x = 1000, y = 0, z = 0
})
end)
7. Game events
local token = wbb.events.on("player_hurt", function(event)
print(string.format("Damage: %d", event.dmg_health or 0))
end)
The event table always contains name. Supported integer fields include dmg_health, dmg_armor, health, armor, hitgroup, headshot, penetrated, round, reason, team, objective, userid, attacker, and assister. Numeric position fields are x, y, and z.
8. Unsafe scripts
enabled = wbb.unsafe.enabled()
address, err = wbb.unsafe.module_base(module_name)
address, err = wbb.unsafe.pattern(module_name, signature)
address, err = wbb.unsafe.add(address, signed_offset)
address, err = wbb.unsafe.resolve_relative(address, displacement_offset)
value, err = wbb.unsafe.read_u8(address)
value, err = wbb.unsafe.read_u16(address)
value, err = wbb.unsafe.read_u32(address)
value, err = wbb.unsafe.read_i32(address)
value, err = wbb.unsafe.read_u64(address)
value, err = wbb.unsafe.read_float(address)
value, err = wbb.unsafe.read_double(address)
ok, err = wbb.unsafe.write_u8(address, value)
ok, err = wbb.unsafe.write_u16(address, value)
ok, err = wbb.unsafe.write_u32(address, value)
ok, err = wbb.unsafe.write_i32(address, value)
ok, err = wbb.unsafe.write_u64(address, value)
ok, err = wbb.unsafe.write_float(address, value)
ok, err = wbb.unsafe.write_double(address, value)
Addresses are opaque strings. Unsafe mode doesn't provide FFI, DLL loading, executable allocation, arbitrary native calls, or native hooks.
9. Limits and troubleshooting
| Resource | Limit |
|---|---|
| Active scripts | 64 |
| Memory | 8 MiB per script |
| Source file | 1 MiB |
| Callbacks | 32 per script |
| UI controls | 128 per script, 128 across active scripts |
| Draw commands | 256 per paint |
| World projections | 1,024 per paint across active scripts |
| Config writes | 32 per update |
| Trace requests | 8 per create-move update |
Sandbox
ffi, io, os, package, debug, dynamic loaders, native modules, and LuaJIT bytecode aren't available. Use math, string, table, and bit for normal script logic.
Common problems
- The script isn't listed: confirm the file ends in
.luaand is in the WallbangBros Lua folder shown above. - The script reports an API error: add
assert(wbb.api_version == 3)at the top and use the function names from this page. - Game state reads false:
frame.game.validisfalsewhile no round is running. Join a match and check again. - Drawing doesn't appear: drawing calls must run inside
paint. Check that projected points aren'tnil. - A setting doesn't appear: register UI controls at the script's top level, not inside a callback.
- A trace result is missing: read it on the next
create_movecall using the same request ID. - A callback stopped: check the red Lua error overlay, fix the error, then reload the script.
10. Verify your setup
wbb-verify-all.lua is a self-contained testing script for API v3. It draws one panel with a PASS, FAIL, or WAIT line for every part of the API, so a single screenshot shows whether your installation works.
The script checks the sandbox, event system, UI controls, feature configuration, snapshots including the new game state, command fields, trace round-trips, drawing, fonts, and world projection. It also draws every primitive and font next to the panel so the result is visible in the same screenshot.
- Download the script and place it in the Lua folder from section 1.
- Enable
wbb-verify-allon the LUA tab. - Join a match with bots, spawn, and shoot a bot once.
- Wait three seconds and take a screenshot of the panel.
WAIT lines need an in-game action such as joining a match or firing a shot. Red FAIL lines name the failing check together with a short reason. Send the screenshot to support if a line stays red.