LUX

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 .lua file in the folder and type /reload.
  • To start, copy example_rotation.lua into 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

State accessors
CallReturns / 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.

Distance and facing
CallReturns / 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

Actions
CallReturns / 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,
})
  • Run is called many times per second while the rotation is on. Keep it light: check, decide, cast once, return.
  • An exact spec wins over nil. The General tab has a switch button when two rotations match.
  • An optional Mounted = function() ... end is called instead of Run while 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

Logging and misc
CallReturns / 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() and API.Usable() as gates so you do not cast the same spell every tick.
  • Use API.LogOnce inside Run(); a plain API.Log repeats 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) or runtime error: <message> (it failed while running). Fix the file and type /reload. Protected rotations show rotation in 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.