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. That is the whole model.
lua
Nyx.Rotations.MySpec = {
name = "MySpec",
autoAttack = true,
entries = {
{ name = "Interrupt",
spell = "Kick",
when = function() return Nyx.Interruptible("target") end },
{ name = "Slice and Dice",
spell = "Slice and Dice",
onSelf = true, -- self-cast, and skips the range check
when = function()
local left = Nyx.MyBuff("Slice and Dice")
return Nyx.ComboPoints() >= 1 and (not left or left < 2)
end },
{ name = "Finisher",
spell = "Eviscerate",
when = function() return Nyx.ComboPoints() >= 5 end },
{ name = "Filler", spell = "Sinister Strike" }, -- no when() = always
},
}
-- Optional: bind it to a talent tab so /nyx auto picks it up.
Nyx.SpecRotations.ROGUE = { [2] = "MySpec" }Save that as External/Rotations/MySpec.lua, then /nyx reload and /nyx use MySpec.
Entry fields
| Field | Meaning |
|---|---|
name | Shown in the status frame and in error messages. Worth setting. |
spell | A spell name, or a function returning a name (or nil to skip). |
when | Optional predicate. Omit it for "always". |
onSelf | Self-cast, and skip the range check. What self-buffs need. |
autoAttack on the rotation starts melee swinging when there is a valid target.
Choosing a spell at runtime
spell can be a function, which is how you pick between ranks, talents or situational abilities:
lua
{ name = "Builder",
spell = function()
if Nyx.Known("Hemorrhage") then return "Hemorrhage" end
return "Backstab"
end,
when = function() return Nyx.Power() >= 35 end },Nyx.Known reads the spellbook, so an ability you have not learned simply falls through to the next entry.
Using nearby units
Anything beyond your current target comes from Nyx.Objects:
lua
{ name = "Fan of Knives",
spell = "Fan of Knives",
onSelf = true,
when = function()
local count = 0
for _, unit in ipairs(Nyx.Objects.Nearby(8)) do
if Nyx.Objects.LineOfSight(unit) then count = count + 1 end
end
return count >= 3
end },Nearby(radius) returns living units sorted nearest first, each a table with guid, x/y/z, health, healthMax, entry, combatReach and distance.
Interrupts
Nyx.Interruptible(unit) is true only for a cast you are actually allowed to interrupt — firing Kick at an immune cast just wastes the cooldown:
lua
{ name = "Kick",
spell = "Kick",
when = function() return Nyx.Interruptible("target") end },For units you have not targeted, channelSpell on a unit table carries the channelled spell ID, which the client's own UnitChannelInfo cannot give you.
Keeping buffs and debuffs up
Aura helpers return seconds remaining, not a boolean:
lua
local left = Nyx.MyDebuff("Rupture")
if not left or left < 2 then ... endNyx.MyDebuff only counts auras you cast, so another rogue's Rupture will not stop you refreshing your own. See things that bite.
When something goes wrong
Every callback runs inside pcall. An entry that errors is benched for five seconds and named in chat; three failures drop it for the session; a rotation with no working entries left stops the bot rather than spinning.
/nyx status shows the last error, and /nyx reload picks up your fix without a restart.