LUX API documentation
LUX has an open Lua API, LUX.API, so you can write your own rotations. This is the public surface. It is the same API the LUX rotations use. Writing and running your own scripts needs the Unlocker license. The API is for Retail Midnight and is part of a public beta, so it may change.
Getting started
Every .lua file in the scripts folder is loaded when you type /reload in game. Protected rotations you bought from us are .luxr files and go in the same folder.
C:\Users\Public\LUX\scripts- The launcher has an Open scripts folder link that opens it.
- There is no entry file and nothing to register by name. Put a
.luafile in the folder and type/reload. - To start, copy
example_rotation.luainto the folder, edit it and type/reload. The full example is at the end of this page. - One file per job is a good habit: spell data in one file, a rotation in another.
Take a local copy at the top of each file: local API = LUX.API. Spell arguments are spell ids or names. Unit arguments are standard unit tokens such as "target", "player" or "focus". If you leave the unit out, "target" is used unless stated otherwise.
Reading the game
| Call | Returns / does |
|---|---|
| API.InCombat() | true when the player is in combat |
| API.TimeInCombat() | seconds since combat started |
| API.GCD() | seconds left on the global cooldown |
| API.Exists(unit) | the unit exists |
| API.IsEnemy(unit) | you can attack it |
| API.IsDead(unit) | it is dead or a ghost |
| API.Health(unit) | current, max, percent (default "player") |
| API.Power(Enum.PowerType.X, unit) | resource value (default: your main resource) |
| API.Casting(unit) | name of the spell being cast, or nil (default "player") |
| API.Buff(spell, unit) | active, stacks, seconds left (default "player") |
| API.Debuff(spell, unit) | active, stacks, seconds left (default "target") |
| API.Cooldown(spell) | seconds left, 0 when ready |
| API.Charges(spell) | current charges |
| API.Usable(spell) | known, usable and you have the resource |
| API.Moving(), API.Mounted() | booleans |
| API.Class(), API.Spec() | class token ("ROGUE"), spec index |
Distance and facing
These need LUX's object information. When it is not available they return nil, false or an empty list, so always handle that case.
| Call | Returns / does |
|---|---|
| API.FeedAvailable() | true when positions are available |
| API.Distance(unit) | yards, or nil |
| API.Enemies(range) | list of { token, dist, angle }, nearest first; empty when unavailable |
| API.Angle(unit) | degrees away from where you face (positive = left), or nil |
| API.InFront(unit, arcDegrees) | true / false, nil when unknown (default arc 180) |
| API.InMelee(unit) | melee range check that always works |
| API.SpellInRange(spell, unit) | true / false / nil |
| API.Within(yards, unit) | true only when surely within that range |
Turning the character and choosing targets are options the player can switch on in the General tab. A rotation does not need to do it. If it wants facing while it runs, it calls API.RequestFacing() inside Run().
Actions
| Call | Returns / does |
|---|---|
| API.Cast(spell, unit) | casts the spell, returns true when it was sent. Does not change your target |
| API.RequestFacing() | ask for auto-facing while your rotation runs (call from Run) |
| API.Target(unit) | changes target |
| API.StopCasting() | stops the current cast |
| API.Macro(text) | runs a macro line, for example /startattack |
| API.SinceLastCast() | seconds since the last cast LUX sent |
| API.LastCast() | name and time of the last cast |
Always cast through API.Cast. Calling game functions to cast directly will not work.
Registering a rotation
API.RegisterRotation({
name = "my_rotation", -- unique name
class = "ROGUE", -- class token
spec = 1, -- spec index, or nil for any spec
Run = function() ... end,
})Runis called many times per second while the rotation is on. Keep it light: check, decide, cast once, return.- An exact
specwins overnil. The General tab has a switch button when two rotations match. - An optional
Mounted = function() ... endis called instead ofRunwhile the player is mounted.
Options
Options appear as a tab for the matching class in the /lux window and are saved per character.
API.DefineOptions("my_rotation", "My Rotation", "ROGUE", {
API.Section("Offense"),
API.Check("MYuseCooldowns", "Use cooldowns", true),
API.Slider("MYminEnergy", "Minimum energy", 40, 0, 100, 5),
API.Cycle("MYopener", "Opener", 1, { { v = 1, t = "Ambush" }, { v = 2, t = "Cheap Shot" } }),
API.Input("MYnote", "Note", ""),
API.Text("Anything you want to tell the player."),
})
local energy = API.Option("MYminEnergy") -- read it inside Run(), every time- Builders:
Check,Slider,Cycle,Input,Section,Text. API.Option(key)returns the live value.API.OnOptionChange(fn)is called with(key, value).- Use a short unique prefix for your option keys so they never clash with other rotations.
- Read options inside
Run()instead of storing them once, so changes apply immediately.
Logging and misc
| Call | Returns / does |
|---|---|
| API.Log(level, fmt, ...) | "error", "warn", "info", "debug" or "trace" |
| API.LogOnce(key, seconds, level, fmt, ...) | same message at most once per N seconds |
| API.IsRunning() | the rotation is switched on |
| API.Now() | current time in seconds |
| API.Require(name) | load another script file |
The chat level is a setting on the General tab (quiet by default). /lux log [n] shows the last n log lines.
Complete example
A small combo-point rotation. Save it to script\user\ and list it in _user.lua.
local API = LUX.API
local SINISTER_STRIKE = "Sinister Strike"
local EVISCERATE = "Eviscerate"
API.DefineOptions("example_rogue", "Example", "ROGUE", {
API.Section("Finishers"),
API.Check("EXfinisher", "Use finishers", true),
API.Slider("EXminCP", "Minimum combo points for a finisher", 5, 3, 7, 1),
})
local ptype = Enum.PowerType
local function Run()
if not API.Exists("target") or not API.IsEnemy("target") or API.IsDead("target") then return end
if API.GCD() > 0 or API.Casting() then return end
local cp = API.Power(ptype.ComboPoints) or 0
if API.Option("EXfinisher") and cp >= API.Option("EXminCP") and API.Usable(EVISCERATE) then
API.Cast(EVISCERATE, "target")
return
end
if API.Usable(SINISTER_STRIKE) then
API.Cast(SINISTER_STRIKE, "target")
end
end
API.RegisterRotation({ name = "example_rogue", class = "ROGUE", spec = nil, Run = Run })Tips and troubleshooting
- Check cheap conditions first (target, GCD, casting), then decide, then cast once and return.
- Use
API.GCD(),API.Cooldown()andAPI.Usable()as gates so you do not cast the same spell every tick. - Use
API.LogOnceinsideRun(); a plainAPI.Logrepeats every tick. - Spell ids are safer than names when a spell has several ranks or translations.
- A broken script shows in chat as
[LUX] [LUX-SCRIPT] pack/<file>.lua: syntax error: <message>(a typo in the file) orruntime error: <message>(it failed while running). Fix the file and type/reload. Protected rotations showrotationin place of the file name. - The rotation never casts: it is not switched on, no rotation matches your class and spec, or the target does not pass your guards.
Only the functions on this page are supported. Anything else you find in the game while LUX is running is not part of the API and can change without notice.