Elysium scripting Editor API index
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

#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

#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

#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

#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

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

Elysium scripting guide · built 2026-10-03