Core Concepts

General Tools

Essential SYNQ tools for timing, events, visuals, configuration, and navigation

SYNQ provides many useful tools to help clean up code and make complex tasks easier to perform. These tools form the foundation for building sophisticated routines with proper timing, event handling, visual feedback, configuration persistence, and navigation capabilities.

Important Variables

time

Current time in seconds, equal to GetTime(), but only updated when SYNQ framework ticks.

synq.time

This is useful for timing-based logic that needs to account for framework tick timing.

buffer

A combination of dynamic values which is roughly equal to the time it should take for what's displayed on your client to reach the servers, plus 1 tick of buffer for your script to react before it's too late, and 30ms to account for any latency jitter:

buffer = latency + tickRate + jitter

Buffer is used in many places within the framework to dynamically account for latency.

Example Usage:

-- Time an interrupt at the very latest possible moment
-- buffer accounts for latency, tick rate, and jitter
if target.castRemains <= synq.buffer then
    if intimidation.cd == 0 and intimidation:Cast(target) then
        synq.alert("Interrupted " .. target.castName, intimidation.id)
    end
end

latency

Your latency to the game server, averaged out over the last 30 seconds, with HTTP overhead accounted for.

synq.latency

tickRate

The time between each SYNQ framework tick.

synq.tickRate

Generally, if an event will happen before time + tickRate, your script will not 'tick' again to react in time. That's why this is an important variable, it determines how many ticks you want to think ahead.

The value always varies for two reasons:

  1. Routine Actors can set a static tickrate, and the rest of the framework will comply with it, but if framerate is too low, the fastest it can run is once per frame.
  2. If an Actor is running once per frame, the value will still be > 0, roughly equal to the time between each frame 1/GetFramerate(), but the value is actually calculated each tick (current tick time - last tick time) then averaged out between the last 5 ticks.

spellCastBuffer

The Spell Queue Window duration set by SYNQ.

synq.spellCastBuffer

This is also the maximum amount of time remaining on GCD or cast time that it will queue the next spell within.

gcd

The total expected GCD duration per general 1.5s GCD incurring spell cast.

synq.gcd

Most spells on GCD incur a base 1.5s cooldown modified by haste, some incur their own shorter one:

1.5 / (1 + player.haste)

See also: Spell Object Attribute spell.gcd - the gcd that the queried spell incurs.

hasControl

Whether or not the player has control of their character.

synq.hasControl

zone

Current zone text.

synq.zone

mapID

Current mapID.

synq.mapID

powerTypes

A table mapping power type names to their numeric IDs.

synq.powerTypes = {
    ["mana"] = 0,
    ["rage"] = 1,
    ["focus"] = 2,
    ["energy"] = 3,
    ["combopoints"] = 4,
    ["cp"] = 4,
    ["runes"] = 5,
    ["runicpower"] = 6,
    ["soulshards"] = 7,
    ["shards"] = 7,
    ["astralpower"] = 8,
    ["ap"] = 8,
    ["lunarpower"] = 8,
    ["holypower"] = 9,
    ["alternatepower"] = 10,
    ["maelstrom"] = 11,
    ["chi"] = 12,
    ["insanity"] = 13,
    ["arcanecharges"] = 16,
    ["fury"] = 17,
    ["pain"] = 18,
    ["essence"] = 19
}

Framework Functions

onTick

Adds a callback function to the main framework ticker. These are called in the order added, just before the routine actor.

synq.onTick(callback[, enabled])

Examples:

-- Only run while BM Hunter routine is enabled
synq.onTick(function()
    -- Track pet buffs or other maintenance tasks
    if pet.exists and pet.hp < 50 then
        mendPet()
    end
end, true)

-- Run every tick regardless of routine state
synq.onTick(function()
    -- Global tracking or UI updates
    -- This runs even when routine is disabled
end)

onUpdate

Does the same thing as onTick, but runs every frame instead (almost always faster).

synq.onUpdate(callback[, enabled])

onEvent

Assign a callback to a combat log or other event firing.

synq.onEvent(callback, eventType)

Examples:

-- Listen for specialization changes (useful for multi-spec routines)
local function onSpecChange()
    print("Hunter specialization changed to: " .. player.spec)
    -- Reload rotation files or adjust logic based on new spec
end
synq.onEvent(onSpecChange, "PLAYER_SPECIALIZATION_CHANGED")

-- Listen to combat log events for tracking enemy casts
synq.onEvent(function(info, event, source, dest)
    if event == "SPELL_CAST_SUCCESS" then
        -- Combat log event info is passed as a table
        -- SPELL_CAST_SUCCESS's 12th entry is the spellID
        local spellID, spellName = select(12, unpack(info))
        
        -- Track when enemies cast important abilities
        if source.enemy and spellID == 12345 then -- Example: important enemy spell
            synq.alert("Enemy cast " .. spellName, spellID)
        end
    end
end)

Utility Functions

bin

Converts value of given expression / variable into binary.

  • Any value (bool, string, number, float, function) is 1
  • No value (nil, false) is 0
synq.bin(conditions) : 1 | 0

Examples:

local bin = synq.bin

-- Convert boolean conditions to 1 or 0
print("Target is enemy (binary): " .. bin(target.enemy))
-- 1 if enemy, 0 if not

print("Pet exists (binary): " .. bin(pet.exists))
-- 1 if pet exists, 0 if not

-- Use in calculations
local rangeBonus = bin(target.distance < 30) * 10
-- Adds 10 if target is within 30 yards, 0 otherwise

-- Practical use case: adjust timing based on conditions
-- Only cast Intimidation if target has low DR or specific DR timing
if target.incapDR == 1 or target.idrRemains > 14 + bin(target.idr == 0.25) * 3.5 and target.idr >= 0.25 then
    intimidation:Cast(target)
end

-- Without bin, you'd need a ternary-like expression
if target.incapDR == 1 or target.idrRemains > 14 + (target.idr == 0.25 and 3.5 or 0) and target.idr >= 0.25 then
    intimidation:Cast(target)
end

controlMovement

Temporarily disables movement (and optionally facing) input from the player.

synq.controlMovement(duration[, facing])

See also: stopMoving spell :Cast option

Example:

-- In your BM Hunter actor, control movement to ensure important casts complete
-- Control movement for 2 ticks to prevent player from interrupting the cast
if player.castID == cobraShot.id and player.castTarget.los then
    synq.alert("Controlling Movement (Cobra Shot)", cobraShot.id)
    synq.controlMovement(synq.tickRate * 2)
end

controlFacing

Temporarily disables facing input from the player, including right mouse button movement and keyboard turning actions.

synq.controlFacing(duration)

StopMoving

Immediately stops the player from moving, as long as they're not in the air.

synq.StopMoving()

Populate

Creates a reference to each entry within given associative array (Param 1) inside of all other given tables, namespaces, scopes, etc. as well (Params 2 - N).

synq.Populate(aArray, t1[, t2, t3, t4, t5, ...])

This is a useful tool when used correctly, that allows you to 'merge' references to all values (including functions, objects, tables, etc.) between multiple tables - which can be namespaces, lists of objects, etc.

Example Usage:

local Unlocker, synq, example = ...
local bm = example.hunter.bm
local Spell = synq.Spell

-- Create a list of BM Hunter spells as an associative array
local spells = {
    killCommand = Spell(34026, { damage = "physical", targeted = true }),
    barbedShot = Spell(217200, { damage = "physical", ranged = true, targeted = true }),
    cobraShot = Spell(193455, { damage = "physical", ranged = true, targeted = true }),
    bestialWrath = Spell(19574, { damage = "physical" }),
}

-- Make references available to the actor and current file scope
-- First parameter: source list to copy references from
-- Remaining parameters: destinations where references will be copied
synq.Populate(spells, bm, getfenv(1))

-- Now bm actor can access all spells directly
-- And spells are available in this file's scope without local variables
killCommand:Callback(function(spell) spell:Cast(target) end)
barbedShot:Callback(function(spell) spell:Cast(target) end)

Note: The key parent will be written to each table within the associative array. Mainly to allow objects to find their siblings in the list. For example, all spell objects in a list automatically have direct access to each other within their :Callback function environments after Populate is called.

TL;DR: This thing puts a copy of all the stuff in one table into other tables.

pullTimer

Creates a reference to a pull timer that has been used by things like DBM or BigWigs. Defaults to 0 if no pull timer.

synq.pullTimer : 0

Alerts & Visuals

These are visual tools that may help to keep the user aware of what's happening, in style.

alert

Displays a toast alert for the user.

Trying to recreate an identical alert while it's still displayed will just extend the duration, so feel free to spam it while it's relevant!

We included a lot of customization options so you can get the tone & delivery of your message across clearly, but we also want to keep the theme consistent with other alerts.

You can opt for as much or as little customization as you want. All that is required is a message, even the texture is optional.

Always returns true so you can include it as part of your conditional expressions for cleaner code.

synq.alert([message / {options}], [texture]) : true

Options:

  • message (string) - The message of your alert
  • texture (integer) - SpellID to use as the texture for this alert
  • duration (float) - Lifespan of the alert (not including fade in and fade out animations)
  • fadeIn (float) - Duration of the fade in animation. Default 0.175 (cubic-bezier easing)
  • fadeOut (float) - Duration of the fade out animation. Default 0.3 (cubic-bezier easing)
  • bgColor (array) - The rgba color value (0-1) of the texture background inside of an array. { r, g, b [,a] }
  • imgX (float) - Number of pixels to move the texture on the X axis behind the circular mask
  • imgY (float) - Number of pixels to move the texture on the Y axis behind the circular mask
  • imgScale (float) - The scale of the texture behind the circular mask

Examples:

-- Simple text alert
synq.alert("Burst window active!")

-- Alert with spell texture (Bestial Wrath)
synq.alert("Bestial Wrath!", 19574)

-- Alert with colored text
synq.alert("Low Focus! " .. synq.colors.red .. "Use Cobra Shot", 193455)

-- Customized alert with background color
synq.alert({
    message = "Burst! " .. synq.colors.yellow .. "All cooldowns active",
    texture = 19574, -- Bestial Wrath icon
    bgColor = synq.rgbColors.yellow
})

-- Custom colors for BM Hunter theme
synq.alert({
    message = "Kill Command! |cFFFFD700Ready",
    texture = 34026, -- Kill Command icon
    bgColor = {255/255, 215/255, 0/255, 0.95} -- Gold color
})

-- Alert for Aspect of the Wild
synq.alert("Aspect of the Wild!", 193530)

-- Fine-tune texture position and scale
synq.alert({
    message = "Bloodshed!",
    texture = 321530, -- Bloodshed icon
    imgX = 1,
    imgY = 0.55,
    imgScale = 0.875
})

textureEscape

Converts spellID or texture ID into a texture escape sequence string for use in alerts, other frames, or prints.

synq.textureEscape(spellID/FileDataID[, size, offsets])

Example:

-- Create texture escape sequence for Kill Command spell
-- Spell ID: 34026, size: 16px, offset: move up 2 pixels (format "x:y")
local killCmdTexture = synq.textureEscape(34026, 16, "0:2")

-- Use the texture in an alert and print statement
synq.alert(killCmdTexture .. " Casting Kill Command!", 34026)
print("Kill Command texture: " .. killCmdTexture .. " displayed in chat!")

-- Use with Barbed Shot
local barbedTexture = synq.textureEscape(217200, 20, "0:1")
synq.alert(barbedTexture .. " Maintaining Barbed Shot", 217200)

Draw

synq.Draw draws various things ingame.

local Draw = synq.Draw

Draw(function(draw)
    draw:SetWidth(number) -- Set the width of the draw, such as the width of a Line
    draw:SetColor(r, g, b, a) -- Set the color of the draw, such as the color of a Line
    draw:Line(x1, y1, z1, x2, y2, z2, maxDistance) -- Draw a line from xyz1 to xyz2
    draw:Circle(x, y, z, radius, steps) -- Draw a circle at xyz with a radius and steps
    draw:Cylinder(x, y, z, radius, height) -- Draw a cylinder at xyz with a radius and height
    draw:Arc(x, y, z, size, arc, rotation) -- Draw an arc at xyz with a size, arc degrees, and rotation
    draw:Rectangle(x, y, z, width, length, rotation) -- Draw a rectangle at xyz
    draw:Outline(x, y, z, radius) -- Draw an outline at xyz with a radius, basically a thick circle
    draw:FilledCircle(x, y, z, radius, steps) -- Draw a filled circle at xyz with a radius and steps
    draw:Triangle(x, y, z, v1, v2, v3, cull, wireframe) -- Draw a triangle at xyz with vertices
    draw:Text(string, font, x, y, z) -- Draw a string of text using the defined font on coordinates
    draw:Texture(config, x, y, z, alphaA) -- Draw a texture at xyz with an alpha value
end)

Configuration

Some users may wish to store certain things in between reloads or per character. This can be accomplished by using the synq.config method.

Basic Example

local Unlocker, synq, example = ...

-- Create a new config file within SYNQ for this project
local config = synq.NewConfig("example")

-- Store the config globally so it can be accessed from other files
example.config = config

-- In another file (e.g., hunter/bm-actor.lua), retrieve the config
local Unlocker, synq, example = ...
local config = example.config

-- Store BM Hunter rotation settings
config.burstMode = true
config.autoPetHeal = true
config.savedPos = {
    point = "CENTER",
    x = 0,
    y = 0,
}

Per-Character Config

local Unlocker, synq, example = ...
local name, realm = UnitFullName("player")
-- Create per-character config using player name and realm
local config = synq.NewConfig('example' .. name .. realm)

-- Store per-character BM Hunter preferences
config.burstThreshold = 80 -- Use burst when target HP is above this
config.focusThreshold = 50 -- Maintain focus above this value
config.petHealThreshold = 40 -- Heal pet when HP drops below this

-- Store rotation settings
config.rotationSettings = {
    useBloodshed = true,
    maintainSerpentSting = true,
    autoDisengage = false
}

Triggering a Save

Any shallow write to the config will trigger a config save:

-- config.savedVar value is updated and saved to persist through reloads and restarts
config.savedVar = true

-- config.newSavedTable is updated and saved to persist
config.newSavedTable = { 'a', 'b' }

-- config.newSavedTable is updated for the current session, but is NOT saved and will not persist
table.insert(config.newSavedTable, 'c')

-- you must do a shallow write to the config table to save and persist
local function insertSubConfigValue(key, value)
    -- nested write
    tinsert(config[key], value)
    
    -- shallow write saves and persists
    config[key] = config[key]
end

-- saves and persists
insertSubConfigValue("newSavedTable", 'c')

synq.path

Returns a SYNQ path object for navigation.

Path between player and coordinates:

local Unlocker, synq, example = ...
local bm = example.hunter.bm

bm:Init(function()
    -- Create path to optimal ranged position (e.g., 30 yards from target)
    if target.enemy and target.distance < 25 then
        local optimalX, optimalY, optimalZ = target.x, target.y, target.z
        -- Calculate position 30 yards away
        -- ... position calculation logic ...
        
        local path = synq.path(player, optimalX, optimalY, optimalZ)
        path = path.simplify(1, 1)
        
        -- Draw path for visualization (useful for debugging)
        path.draw()
        
        -- Follow the path to optimal range
        path.follow()
    end
end)

path object

Path object is first return of synq.path - an array of nodes { x = 1093.3312, y = 1996.940, z = 92.01355 }

Contains some built in methods for manipulating and interacting with the path. Unlocker agnostic, should work nearly the same with any unlocker supported.

-- returns the path simplified with Douglas Peucker path simplification algorithm
path.simplify(tolerance, highestQuality)

-- draws the path for this tick
path.draw()

-- follow the path, moving your character along it each time called
path.follow()

Summary

General Tools provide the foundation for building sophisticated SYNQ routines with proper timing, event handling, visual feedback, configuration persistence, and navigation capabilities.

Key Takeaways:

  • Timing variables – Use buffer, tickRate, and latency for precise timing
  • Event handlingonTick, onUpdate, and onEvent for reactive code
  • Visual feedbackalert and Draw for user awareness
  • Configuration – Persistent storage with NewConfig
  • Navigation – Path finding and following with synq.path

These tools work together to create robust, responsive routines that adapt to game conditions and provide clear feedback to users.


Next: Explore Advanced Routine Features to see these tools in action, or check out the SYNQ UI documentation.