Skip to content

Writing a rotation ​

A rotation is a priority list. The engine walks it top-down and casts the first entry whose condition holds and whose spell is known, usable, off cooldown and in range. In API 2 it is one responsibility of a plugin, not a separate global registration system.

Template ​

Start with the shipped Template plugin. It is rotation-only and demonstrates the scoped context without declaring a menu:

lua
-- External/Plugins/MyRotation/manifest.lua
return { id = "MyRotation", name = "My rotation", api = 2 }
lua
-- External/Plugins/MyRotation/init.lua
local plugin = ...

local Interrupts = { ROGUE = "Kick", MAGE = "Counterspell" }
local Fillers = { ROGUE = "Sinister Strike", MAGE = "Frostbolt" }

plugin.rotation:Register({
    id = "MyRotation",
    name = "My rotation",
    autoAttack = true,
    entries = {
        { name = "Interrupt", spell = function()
            return Interrupts[Nyx.Class]
        end, when = function()
            return Nyx.Interruptible("target")
        end },
        { name = "Filler", spell = function()
            local spell = Fillers[Nyx.Class]
            return spell and Nyx.Known(spell) and spell
        end },
    },
})

Use plugin.require("Spells") for modules next to the plugin. Load order is undefined, and /nyx reload re-reads the framework and all plugins.

Entry fields ​

FieldMeaning
nameShown in status and error messages.
spellA spell name, or a function returning a name or nil.
whenOptional predicate; omit it for always.
onSelfSelf-cast, and skip the range and line-of-sight checks.
unitWhich unit those checks are about; defaults to "target".
autoAttackStarts melee swinging when a valid target exists.

unit decides what the engine measures, not what the spell is cast at. A pet heal is the case it exists for: the spell lands on the pet because the client resolves a pet-only spell itself, but without unit = "pet" the range and line-of-sight checks would be taken against whatever is being attacked. It is never a cast target — CastSpellByName's second argument is read as a self-cast flag on some 3.3.5a builds and as a unit token on others, so the bot never passes anything but "player".

Range is the client's IsSpellInRange where the spell has range data, and the object manager's own measurement where it does not — both hitboxes, not a flat five yards, so a large mob is reachable from further out than a small one. Line of sight is a real ray through the engine, using terrain and buildings but not doodads, which is the rule the server applies. Both fail open: when the object manager cannot place a unit the cast is allowed, because an unavailable check must not read as a refusal.

State helpers ​

Use Nyx.State and Nyx.Objects for client state and nearby world objects. Aura helpers return seconds remaining, so nil means absent:

lua
local left = Nyx.MyDebuff("Rupture")
return not left or left < 2

Nyx.Objects.Nearby(radius) returns living units sorted nearest first. Every unit has guid, x/y/z, health, healthMax, entry, combatReach and distance. See the API reference for the complete helper list.

Failures ​

Every callback runs inside pcall. An entry that errors is benched for five seconds; three failures drop it for the session. If no working entries remain, the engine stops rather than spinning. /nyx status reports the last error and /nyx reload picks up the fix without a client restart.

For menu pages, settings and assets, continue with writing a plugin.

API reference generated from source.