Lynx Lua scripting

Guide

Getting started

A script is one Lua 5.4 file that Lynx runs inside the game process. It runs in its own sandbox, next to the Lynx menu, and reads game state through a set of globals. It cannot read or write game memory directly, open files, or load code.

The API is modelled on gamesense. There is no lynx table: the globals client, globals, ui, renderer, json, bit, database and vector are always present, along with the game’s own namespace (pool, carrom, soccer, mlbb, …).

Where scripts live

Open the Lynx menu and go to Play → Lua scripts. The tab lists every .lua file in the scripts folder shown at the top of the tab, under the app’s documents directory in Lynx/Scripts. Put a file there, press Refresh, and it appears in the list. A file name is the script name.

Marketplace bundles install into the same tab. See Bundles and the manifest.

The top level is load

A script has no on_load function. Loading compiles the file and runs its top level once; that run is the load step. Set your widgets up and register your callbacks there. The row shows loaded on success, or error: and the message on failure. Unload closes the script’s Lua state (delivering the shutdown event first); Reload does both in order.

-- This whole chunk runs once, at load.
local enabled = ui.new_checkbox("Lua", "Main", "Enabled")
client.log("hello, loaded")

Events

Everything after load is an event. Register a callback with client.set_event_callback(name, fn); remove it with client.unset_event_callback(name, fn). The callback receives one table argument with the event’s fields (absent when the event has no payload).

The base events, on every game:

Event When Payload
paint once per frame; the only place drawing is allowed none
shutdown the script is being unloaded none
menu_open the Lynx menu opened none
menu_close the Lynx menu closed none

Each game adds its own events (match, turn and shot events, for example). See the game’s reference page and the 8 Ball Pool and Mobile Legends examples.

client.set_event_callback("paint", function()
  -- runs on the game thread once per frame; keep it cheap
end)

client.set_event_callback("shutdown", function()
  client.log("bye")
end)

paint runs on the game thread with a small time and instruction budget; see Sandbox limits. If a callback raises an error, the script is closed and the error shows in the tab.

The console

client.log(...) concatenates its arguments and appends a line to the console at the bottom of the Lua scripts tab. client.color_log(r, g, b, ...) does the same in a colour, and client.error_log(...) in red. The ring holds the last lines across every script; each line is prefixed with the script name.

There is no print. Use client.log.

A first script

local shown = ui.new_checkbox("Lua", "Example", "Show ring")
local size = ui.new_slider("Lua", "Example", "Radius", 10, 120, 40)

client.set_event_callback("paint", function()
  if not ui.get(shown) then return end
  local pulse = (math.sin(client.timestamp() / 300.0) + 1) / 2
  local radius = ui.get(size) * (0.6 + pulse * 0.4)
  local w, h = client.screen_size()
  renderer.circle_outline(w / 2, h / 2, 90, 153, 255, 200, radius, 0, 1, 3)
  renderer.text(w / 2, h / 2 + radius + 12, 255, 255, 255, 255, "c", 0, "Lynx")
end)

Save it as ring.lua, press Refresh, then Load. This is the same script the Example button in the tab creates.