Cookbook
Short, complete solutions to the jobs scripts are most often written for. Paste, change the numbers, done.
Each recipe is a whole script. They use only what the earlier pages describe, so every call is explained somewhere in the guide.
#Share data between handlers
move runs on the game's thread, once per tick; frame draws once per frame. They can use the same local variables, because only one callback runs at a time. Measure in move, show in frame:
local speed = 0
on( "move", function( cmd )
local me = ents.local_pawn( )
speed = me and me:speed2d( ) or 0
end )
on( "frame", function( canvas )
canvas:text( 20, 20, string.format( "%d u/s", math.floor( speed + 0.5 ) ), color( 255, 255, 255 ), { shadow = true } )
end )#Do something every few seconds
local count = 0
every( 5, function( )
count = count + 1
print( "still running", count )
end )#Fade something in and out
Keep a value that chases a target, and draw with it. math.approach moves it a bit each frame without overshooting:
local shown = ui.group( "fade demo" ):toggle( "show", true )
local opacity = 0
on( "frame", function( canvas )
opacity = math.approach( opacity, shown:get( ) and 1 or 0, render.dt( ) * 4 )
if opacity > 0 then
canvas:fill( 20, 20, 160, 40, color( 168, 85, 247 ):fade( opacity ), 8 )
end
end )#A key that switches something on
A key control lets the user choose the key and saves it:
local aim_key = ui.group( "assist" ):key( "hold to assist", input.vk.mouse4, "hold" )
on( "frame", function( canvas )
if aim_key:get( ) then
canvas:text( 20, 60, "ASSIST", color( 120, 220, 140 ) )
end
end )#List the cheat's active hotkeys
on( "frame", function( canvas )
local y = 240
for _, bind in ipairs( input.hotkeys( ) ) do
if bind.active then
canvas:text( 20, y, bind.name, color( 255, 255, 255 ), { shadow = true } )
y = y + 16
end
end
end )#Remember something between sessions
local best = store.get( "best_speed", 0 )
on( "move", function( cmd )
local me = ents.local_pawn( )
local speed = me and me:speed2d( ) or 0
if speed > best then
best = speed
store.set( "best_speed", best )
end
end )
on( "frame", function( canvas )
canvas:text( 20, 20, string.format( "best %d u/s", math.floor( best + 0.5 ) ), color( 255, 255, 255 ) )
end )#Name tags that follow the players
Project a point above each head with view.project:
on( "frame", function( canvas )
for _, player in ipairs( ents.players( { enemies = true, alive = true } ) ) do
local head = player:bone( ents.bone.head )
if head then
local x, y, on_screen = view.project( head + vec3( 0, 0, 14 ) )
if on_screen then
canvas:text( x, y, player:name( ), color( 255, 255, 255 ), { align = "center", valign = "bottom", shadow = true } )
end
end
end
end )#A damage log in a window
local window = ui.window{ title = "damage", x = 24, y = 420, w = 260, h = 160, persistent = true, open = true }
local panel = window:group( "last hits" )
local lines = { }
on( "game:player_hurt", function( ev )
local victim, attacker = ev:player( "userid" ), ev:player( "attacker" )
if attacker and attacker == ents.local_player( ) and victim then
table.insert( lines, 1, string.format( "%s -%d", victim:name( ) or "?", ev:int( "dmg_health" ) ) )
lines[ 7 ] = nil
end
end )
panel:custom{
height = 110,
draw = function( canvas, rect, io )
for i, line in ipairs( lines ) do
canvas:text( 4, 2 + ( i - 1 ) * 15, line, color( 255, 255, 255, 255 - i * 25 ) )
end
end,
}#The planted bomb's timer
on( "frame", function( canvas )
local entity = ents.bomb( )
local bomb = entity and entity:bomb( )
if bomb and not bomb.defused and not bomb.exploded then
local left = math.max( 0, bomb.blow_time - game.curtime( ) )
local text = string.format( "%.1f", left )
canvas:text( 20, 100, "bomb " .. text, left < 10 and color( 244, 63, 94 ) or color( 255, 255, 255 ), { shadow = true } )
end
end )#Wallbang damage at the crosshair
Ask the engine what is left of a bullet fired along your view, and print it next to the crosshair:
on( "frame", function( canvas )
local me = ents.local_pawn( )
if not me or not me:weapon( ) or not me:weapon( ):is_gun( ) then return end
local hit = world.bullet( me:eye( ), me:eye_angles( ):forward( ) )
if hit and hit.surfaces > 0 then
local width, height = canvas:screen( )
canvas:text( width / 2 + 14, height / 2 - 6, string.format( "%d (%d)", math.floor( hit.damage + 0.5 ), hit.surfaces ), color( 250, 204, 21 ), { shadow = true } )
end
end )#Change a cheat setting for a while
Use a different value while the situation lasts, and put the old one back afterwards - and when the script unloads:
local setting = cheat.find( "ragebot", "doubletap" )
local saved
on( "move", function( cmd )
local me = ents.local_pawn( )
if not setting or not me then return end
if me:on_ground( ) then
if saved ~= nil then
setting:set( saved )
saved = nil
end
elseif saved == nil then
saved = setting:get( )
setting:set( false )
end
end )
on( "unload", function( )
if setting and saved ~= nil then
setting:set( saved )
end
end )#Scroll a long log inside a panel
Clip the text to the panel and move it with the wheel, in a custom control:
local group = ui.group( "log" )
local lines = { }
local scroll = 0
for i = 1, 40 do lines[ i ] = "line " .. i end
group:custom{
height = 140,
draw = function( canvas, rect, io )
scroll = math.clamp( scroll - io.scroll * 24, 0, math.max( 0, #lines * 16 - rect.h ) )
canvas:fill( 0, 0, rect.w, rect.h, color( 0, 0, 0, 90 ), 6 )
canvas:clip( 0, 0, rect.w, rect.h )
for i, line in ipairs( lines ) do
canvas:text( 6, 4 + ( i - 1 ) * 16 - scroll, line, color( 255, 255, 255 ) )
end
canvas:unclip( )
end,
}