My Rotations
My Rotations
| Name | Class / Spec | Hero Tree | Version | Status | |
|---|---|---|---|---|---|
| Loading… | |||||
Edit Rotation
Manifest
Pricing
Empty price = no SKU. Saving creates or updates the SKU for that tier.
Rotation Script (Lua)
Reference the Custom Rotation API: player, target,
spell("foo"), buff("foo"), debuff("foo"),
cooldown("foo"), resource("foo"), toggle("x"),
talent("x"), should_defensive("x"),
cast("x" [, "reason"]), defensive(...),
interrupt(...). First cast() wins.
Versions
Simulate
Run the current saved version against a synthetic combat state for N seconds and inspect the per-tick action timeline. The simulator reuses the same engine the desktop app runs, so a rotation that fires here will fire in-game.
Custom Rotation API
Lua scripting surface for custom rotations. The same engine runs in the desktop app and in the simulator, so anything below that compiles + runs here will run identically in-game.
Execution model: your script runs once per rotation tick
(~100 ms). First successful cast() wins — write
spells in priority order with if cast("x") then return end.
Errors are captured into the trace, never crash the runtime.
Globals
Top-level scalars set on every tick.
in_combat | bool — player is in combat |
in_group | bool — player is in a party/raid |
stealthed | bool |
mounted / in_vehicle | bool |
moving | bool |
gcd / gcd_max / gcd_remains | number — seconds |
haste | number — spell haste multiplier |
time / wow_time | number — engine clock + WoW GetTime() |
fight_remains | number — TTD on current target |
enemies / active_enemies | integer — nearby hostile count |
aoe_mode | integer — 0=ST, 1=auto, 2=AoE |
interrupt_mode | integer — 1=OFF, 2=ALL, 3=Whitelist, 4=Dangerous |
player
Everything about the local player. All fields read-only; mutate the world via the action functions instead.
player.hp / player.hp_max / player.hp_pct | number |
player.class_id / player.spec_id | integer — Blizzard ids |
player.level | integer |
player.hero_tree | string — empty if unknown |
player.in_combat / moving / stealthed / mounted / in_vehicle | bool |
player.gcd / gcd_max / gcd_remains / haste | number |
player.casting / channeling | bool |
player.cast_spell_id / channel_spell_id | integer — 0 when idle |
player.cast_end_time / channel_end_time | number — WoW time |
player.has_curse | bool — at least one curse debuff on player |
player.assisted_combat_keybind | string |
player.assisted_combat_spell_id | integer |
player.previous_cast | string — last cast spell name |
player.previous_gcd(n) | string — nth previous GCD (1 = most recent) |
if player.hp_pct < 35 and not player.in_vehicle then
if defensive("ice_block", "low hp") then return end
end
target
Current target. Inherits the same fields as any unit, plus stealable + counter-cast info.
target.exists / alive / dead | bool |
target.enemy / friend / in_combat | bool |
target.casting / channeling | bool |
target.cast_spell_id / channel_spell_id | integer |
target.should_interrupt | bool — engine-side gate; honor it before interrupt() |
target.health_pct / hp_pct | number — 0..100 |
target.power / power_max | number — target's primary resource |
target.range | number — yards |
target.time_to_die / ttd | number — seconds |
target.has_stealable_buff | bool — triggers Spellsteal |
target.counter — set when the cast is in the counter database:
.known | bool — counter DB has this cast |
.is_interrupt / .is_stop / .is_important | bool — cast type |
.is_tank_buster / .is_party_damage / .is_debuff | bool |
.is_physical / .is_magic / .is_bleed | bool — damage school |
.util_mask / .type_mask / .extra_mask | integer — raw bits if you want to roll your own |
spell(name) / cooldown(name)
Look up a spell by SimC name (lowercase, underscores). Returns a table even if unknown so .ready is always safe to read.
spell(n).id | integer — Blizzard spell id, 0 when unknown |
spell(n).name / keybind | string |
spell(n).known | bool — true when the spell book has it AND a keybind is set |
spell(n).usable | bool — current power + reagent allow casting |
spell(n).cooldown / cooldown_remains / cooldown_duration | number — seconds |
spell(n).charges | integer |
spell(n).pmultiplier | number — persistent DoT multiplier |
spell(n).ready | bool — usable AND (off-cd OR has charges) AND keybind set |
cooldown(name) returns the cooldown view directly:
cooldown(n).remains / duration | number |
cooldown(n).charges | integer |
cooldown(n).ready | bool |
cooldown(n).full_recharge_time | number — secs to all charges |
buff(name) / debuff(name) / aura(name)
buff reads the player's buff table. debuff
reads debuffs on the current target. aura searches buffs
first, then debuffs — useful for class-agnostic checks like
aura("bloodlust").up.
.up / .active | bool — present (alias) |
.down | bool — absent |
.remains / .duration | number — seconds |
.stack / .stacks / .max_stack | integer |
.pmultiplier | number |
.source_spell_id | integer |
Resources
Lowercase resource names: mana, rage, focus, energy, combo_points, etc.
resource(n) | number — current |
resource_max(n) | number |
resource_pct(n) | number — 0..100 |
resource_regen(n) | number — per second |
Toggles, Talents, Tuning
User-facing settings from the desktop app's panels.
toggle(name) | bool — names: cooldowns, interrupts, defensive, potion, movement, dispel, cc, auto-target, ooc-attack, … |
talent(name) | bool — SimC talent slug |
hero_tree() | string — active hero talent tree |
tuning(name) | integer — per-spell mode (0=Default, 1=Disabled, 2=Bypass) |
defensive_tuning(name).enabled | bool |
defensive_tuning(name).health_pct | number — threshold |
Defensives + Interrupts (helpers)
Convenience gates that wrap the user's tuning. Use these instead of rolling your own thresholds — they respect the desktop app's defensive panel + interrupt selector.
should_defensive(name) | bool — user enabled it AND hp_pct ≤ threshold AND spell ready |
interrupt_allowed() | bool — top-level "may I press an interrupt right now?" — combines the toggle, selector mode, and target's counter info |
counter_spell_allow(name) | bool — per-spell user override (default true) |
counter_rule(spell_id) | table — direct counter-DB lookup (.known, .type_mask, etc.) |
Actions — cast / defensive / interrupt / skip
All four return true if the action was accepted.
First-cast-wins: once cast() succeeds, subsequent
calls return false without firing. Use the pattern
if cast("x") then return end to chain priorities.
cast(name [, reason]) | bool — primary rotation press; requires usable + ready + keybind set |
defensive(name [, reason]) | bool — suggests a defensive press (runtime re-checks tuning) |
interrupt(name [, reason]) | bool — suggests an interrupt press (runtime re-checks whitelist) |
skip(reason) | true — explicit no-op marker recorded in the trace |
-- Priority order — first match wins.
if interrupt_allowed() and target.casting then
if interrupt("counterspell") then return end
end
if should_defensive("ice_block") then
if defensive("ice_block", "low hp") then return end
end
if buff("brain_freeze").up and cast("flurry") then return end
if cast("frostbolt") then return end
Logging + helpers
print(...) | append a line to the tick trace + lumen.log |
trace(...) | append only to the tick trace (no log file) |
any{a, b, c} | bool — true if any element is truthy |
all{a, b, c} | bool — true if every element is truthy (false if empty) |
none{a, b, c} | bool — true if every element is falsy |
if all{buff("a").up, buff("b").up, not moving} then
if cast("burst") then return end
end
Full example — Frost Mage opener
-- 1. Interrupts gated by the user's selector + whitelist.
if interrupt_allowed() and target.casting then
if interrupt("counterspell") then return end
end
-- 2. Defensives gated by the user's defensive panel.
if should_defensive("ice_block") then
if defensive("ice_block", "panic") then return end
end
-- 3. Burst window when cooldowns toggle is on.
if buff("icy_veins").down and toggle("cooldowns") then
if cast("icy_veins") then return end
end
-- 4. Proc consumption (no GCD spent).
if buff("brain_freeze").up then
if cast("flurry") then return end
end
-- 5. Spend Fingers of Frost charges.
if resource("fingers_of_frost") > 0 then
if cast("ice_lance") then return end
end
-- 6. Default filler.
if cast("frostbolt") then return end