Examples
Example scripts
Six complete scripts - what each one does and what to notice in it.
Each script below is complete. To try one, copy the code into a new file such as watermark.lua in C:\elysium\scripts and press rescan in the Scripts tab.
#watermark.lua
A pill in the corner with the name of the cheat, the frame rate and the map - blurred background, a font made once per size, and four saved settings.
watermark.lua
-- watermark.lua
--
-- A frame handler, a font, a blurred panel and a few saved settings.
-- The controls remember their values between runs on their own; there is
-- nothing to save by hand.
local group = ui.group( "watermark" )
local enabled = group:toggle( "enabled", true )
local show_fps = group:toggle( "show fps", true )
local show_map = group:toggle( "show map", true )
local accent = group:color( "accent", ui.accent( ) )
local text_size = group:slider( "text size", 10, 24, 14, { step = 1 } )
-- One font per size, built the first time it is asked for. A failed load is
-- remembered as false, so a missing file is not retried every frame.
local faces = { }
local function face( size )
if faces[ size ] == nil then
faces[ size ] = render.font( "Segoe UI", size, { weight = 600 } ) or false
end
return faces[ size ]
end
on( "frame", function( canvas )
if not enabled:get( ) then
return
end
local font = face( text_size:get( ) )
if not font then
return
end
local parts = { "elysium" }
if show_fps:get( ) then
parts[ #parts + 1 ] = string.format( "%d fps", math.floor( render.fps( ) + 0.5 ) )
end
local map = game.map( )
if show_map:get( ) and map then
parts[ #parts + 1 ] = map
end
local text = table.concat( parts, " | " )
local width, height = canvas:measure( text, font )
local pad = 10
local x, y = 16, 16
local w, h = width + pad * 2, height + pad
canvas:blur( x, y, w, h, 6 )
canvas:fill( x, y, w, h, color( 14, 12, 20, 160 ), 6 )
canvas:fill( x, y + h - 2, w, 2, accent:get( ) )
canvas:text( x + pad, y + pad / 2, text, color( 238, 232, 248 ), { font = font } )
end )To notice
face( size )makes a font the first time a size is asked for and remembers the result -falsewhen the font could not be loaded, so a missing face is not looked up again every frame.- The controls are created once at the top and read inside the handler with
:get(). Their values are saved without any code. canvas:blurthen a translucentcanvas:fillis the frosted-glass panel. See Drawing.
#hitlog.lua
A toast for every hit you land: who, how much, and where.
hitlog.lua
-- hitlog.lua
--
-- A game event, a few entity reads and toasts. "game:" in front of a name
-- listens to that event of the game itself; the handler gets an object to read
-- the event's fields from.
local group = ui.group( "hit log" )
local enabled = group:toggle( "enabled", true )
local only_mine = group:toggle( "only my hits", true )
local seconds = group:slider( "duration", 1, 10, 4, { step = 1, format = "%d s" } )
-- Hitgroup number -> readable name, from the constants the runtime provides.
local names = { }
for name, id in pairs( ents.hitgroup ) do
names[ id ] = ( name:gsub( "_", " " ) )
end
on( "game:player_hurt", function( ev )
if not enabled:get( ) then
return
end
local attacker = ev:player( "attacker" )
local victim = ev:player( "userid" )
if not victim then
return
end
local mine = attacker ~= nil and attacker == ents.local_player( )
if only_mine:get( ) and not mine then
return
end
local damage = ev:int( "dmg_health" )
local where = names[ ev:int( "hitgroup" ) ] or "body"
ui.notify( string.format( "%s -%d %s", victim:name( ) or "?", damage, where ), {
title = mine and "hit" or "damage",
seconds = seconds:get( ),
color = mine and color( 120, 220, 140 ) or color( 244, 63, 94 ),
} )
end )To notice
"game:player_hurt"listens to the game's own event;ev:player( "attacker" )andev:int( "dmg_health" )read its fields. See game events.- The hitgroup names come from
ents.hitgroup, inverted once at load into a lookup table. - Entities compare with
==, soattacker == ents.local_player()is the test for "that was me".
#velocity.lua
A floating window with a live graph of your horizontal speed.
velocity.lua
-- velocity.lua
--
-- Sampling in one handler, drawing in another, and a floating window with a
-- custom control: a live graph of the player's horizontal speed.
--
-- "move" runs once per command, 64 times a second, on the game's own thread;
-- the graph is drawn by the menu code on the render thread. The two only meet
-- through the plain tables below, which is safe because the runtime runs one
-- callback of a script at a time.
local options = ui.group( "velocity" )
local show = options:toggle( "show graph", true )
local window = ui.window{ title = "velocity", x = 24, y = 320, w = 280, h = 150, persistent = true }
local panel = window:group( "horizontal speed" )
-- The toggle opens and closes the window. Its saved value is applied once at
-- load, and again whenever it changes.
local function apply( )
if show:get( ) then
window:open( )
else
window:close( )
end
end
show:on_change( apply )
apply( )
local capacity = 160
local history = { }
local peak = 260
on( "move", function( cmd )
local pawn = ents.local_pawn( )
local speed = pawn and pawn:speed2d( ) or 0
history[ #history + 1 ] = speed
if #history > capacity then
table.remove( history, 1 )
end
-- The scale follows the fastest recent speed and relaxes slowly.
peak = math.max( 260, speed, peak * 0.998 )
end )
panel:custom{
height = 90,
-- Called every frame the window is on screen. `rect` is the control's own
-- rectangle with the origin at its top left corner, `io` has the mouse.
draw = function( canvas, rect, io )
canvas:fill( rect.x, rect.y, rect.w, rect.h, color( 0, 0, 0, 90 ), 4 )
local count = #history
if count < 2 then
return
end
local points = { }
for i = 1, count do
points[ #points + 1 ] = rect.x + ( i - 1 ) / ( capacity - 1 ) * rect.w
points[ #points + 1 ] = rect.y + rect.h - 4 - history[ i ] / peak * ( rect.h - 8 )
end
canvas:path( points, ui.accent( ), false, 1.5 )
canvas:text( rect.x + 8, rect.y + 6, string.format( "%d u/s", math.floor( history[ count ] + 0.5 ) ), ui.theme( ).text )
end,
}To notice
movesamples, the custom control draws. They sharehistoryandpeakas plain locals - one callback runs at a time, so no locking.- The toggle opens and closes the window:
on_changecallsapply, andapply()is called once at load to honour the saved value. panel:custom{ ... }gives a canvas whose origin is the control's own corner. See Custom controls.
#esp_extras.lua
Adds a distance line, an armour bar and a "scoped" tag to the player overlay.
esp_extras.lua
-- esp_extras.lua
--
-- The "esp" event runs once for every player the overlay draws, right after
-- the built-in elements. Lines and bars added here continue where the overlay
-- stopped instead of drawing over it.
local group = ui.group( "esp extras" )
local enemies_only = group:toggle( "enemies only", true )
local show_distance = group:toggle( "distance", true )
local show_armor = group:toggle( "armor bar", true )
local show_scoped = group:toggle( "scoped", true )
local meters_per_unit = 0.0254
on( "esp", function( player, esp )
if enemies_only:get( ) and not esp:enemy( ) then
return
end
local me = ents.local_pawn( )
if show_distance:get( ) and me then
local meters = ( player:origin( ) - me:origin( ) ):len( ) * meters_per_unit
esp:text( "bottom", string.format( "%dm", math.floor( meters + 0.5 ) ), color( 255, 255, 255, 220 ) )
end
if show_armor:get( ) then
local armor = player:armor( )
if armor > 0 then
esp:bar( "right", armor / 100, color( 90, 160, 255 ) )
end
end
if show_scoped:get( ) and player:scoped( ) then
esp:text( "top", "scoped", color( 250, 204, 21, 230 ) )
end
end )To notice
- The
espevent runs once per drawn player, andesp:text/esp:barstack beside the overlay's own elements. See The player overlay. ( player:origin( ) - me:origin( ) ):len( )is vector arithmetic: subtraction gives a vec3,len()its length.
#fov.lua
A field-of-view slider, in six lines.
fov.lua
-- fov.lua
--
-- The "view" event hands over the camera as it is about to be rendered, after
-- the cheat's own camera work. Reads and writes go straight to the game's view
-- setup, so the object is only valid inside the handler.
local group = ui.group( "camera" )
local enabled = group:toggle( "custom fov", false )
local fov = group:slider( "fov", 60, 140, 100, { step = 1, format = "%d" } )
on( "view", function( view )
if enabled:get( ) then
view:set_fov( fov:get( ) )
end
end )To notice
- The
viewhandler runs inside the game's camera callback, so it only does the one thing it has to. See Camera & projection.
#banner.lua
A bar across the top of the screen drawn with a custom pixel shader.
banner.lua
-- banner.lua
--
-- A custom pixel shader. The script writes `float4 main( PS_IN i ) : SV_TARGET`
-- in HLSL (shader model 4) and may add helper functions before it.
--
-- Available to the shader:
-- i.pos position on screen in pixels i.uv 0..1 across a plain rectangle
-- i.col the colour the shape is drawn in p[0]..p[7] float4s set from Lua
-- info x = seconds, y = frame time, z = screen width, w = screen height
local source = [[
float4 main( PS_IN i ) : SV_TARGET
{
float across = i.pos.x / info.z;
float wave = 0.5 + 0.5 * sin( across * 12.566 + info.x * 1.5 );
float3 tint = lerp( p[0].rgb, p[1].rgb, wave );
return float4( tint, p[0].a ) * i.col;
}
]]
local group = ui.group( "banner" )
local enabled = group:toggle( "enabled", true )
local height = group:slider( "height", 2, 24, 6, { step = 1 } )
local banner = render.shader( source )
banner:set( 0, color( 168, 85, 247 ) )
banner:set( 1, color( 56, 189, 248 ) )
local reported = false
on( "frame", function( canvas )
-- The shader is compiled the first frame it is needed. If that fails, say
-- why once and carry on without it.
if not reported and banner:error( ) then
reported = true
warn( "banner shader: " .. banner:error( ) )
end
if not enabled:get( ) or not banner:ready( ) then
return
end
local width = canvas:screen( )
canvas:shader( banner )
canvas:fill( 0, 0, width, height:get( ), color( 255, 255, 255 ) )
canvas:shader( nil )
end )To notice
- The shader is created once at load and only used in the handler, behind
banner:ready(). warn()writes to the output panel marked as a warning - used once, if the shader did not compile. See Shaders.