mirror of
https://github.com/fuffc/WeakestAuras.git
synced 2026-11-10 20:07:35 +00:00
ClassicAPI 1.18 is now the only requirement Requirements - Needs ClassicAPI 1.18.0 or newer. SuperWoW is no longer used, and Nampower is optional. - On an older ClassicAPI the addon stays off, and a popup links the download. - Custom triggers on SuperWoW or Nampower events only fire while that mod is installed. Triggers - New Combat Log trigger, with lists of spell IDs, spell names and NPC IDs. WeakAuras2 auras that use one now import. - Custom triggers can listen to the combat log with CLEU or CLEU:SUBEVENT. - Crowd Control matches a specific type (stun, fear, root, silence, disarm and more) or a school lockout, with the effect's real timer. - Saved Crowd Control auras: Root, Silence and Disarm filters carry over; CC and Snare become "any type", and snares are no longer detected. - Cast bars follow pushback and channel updates. Displays - Icon cooldown swipes are drawn by the client's own cooldown frame. - Sound actions play on a chosen channel, and a new Stop sound type ends a looping sound. Fixes - Cooldown auras no longer sweep on every wand shot. - The Global Cooldown trigger ignores wand and Auto Shot shots.
335 lines
13 KiB
Lua
335 lines
13 KiB
Lua
-- WeakestAuras -- trigger *types*: the options-side registry (defaults + a
|
|
-- config tab + a summary line per trigger.type), via
|
|
-- WeakestAuras.RegisterTriggerType; see Data.lua. The runtime layer that turns
|
|
-- this config into states lives in a trigger *system* (TriggerAura.lua),
|
|
-- registered via WA.RegisterTriggerSystem -- same options/runtime split as
|
|
-- upstream's triggerTypesOptions vs triggerSystems.
|
|
-- Upstream section refs (§N) point at design/architecture/weakauras2-reference.md
|
|
--
|
|
-- Field names track upstream's BuffTrigger2 (§5) so future features and
|
|
-- (eventually) imported WA2 strings
|
|
-- line up: unit, debuffType, useName + auranames, matchesShowOn. `auranames` is
|
|
-- an any-of-these list (LibWidgets add/remove/reorder editor); simplified from
|
|
-- upstream's split auranames/auraspellids arrays -- a numeric entry here is
|
|
-- matched by spellId, a text entry by name, no separate useExactSpellId flag.
|
|
|
|
if WeakestAuras.disabled then return end
|
|
|
|
local WA = WeakestAuras
|
|
|
|
local DEBUFF_TYPE_LABELS = { HELPFUL = "Buff", HARMFUL = "Debuff", BOTH = "Both" }
|
|
local SHOW_ON_LABELS = {
|
|
showOnActive = "Aura(s) Active",
|
|
showOnMissing = "Aura(s) Missing",
|
|
showAlways = "Always",
|
|
}
|
|
local NAME_PATTERN_LABELS = { match = "Contains", nomatch = "Doesn't Contain" }
|
|
-- The dispel classes AuraData.dispelName can carry (from SpellDispelType.dbc).
|
|
-- Already display-ready, so no label table.
|
|
local DISPEL_TYPES = { "Magic", "Curse", "Disease", "Poison" }
|
|
|
|
-- Trigger types grouped for the picker, in menu order. Two dozen types in one
|
|
-- flat list is not browsable, so the editor asks for a category first and then
|
|
-- the type within it (§4.1). The category is *derived* from the selected type
|
|
-- and never stored: a type determines its own category, so keeping both in saved
|
|
-- data would be two fields where one decides the other. `default` is the type a
|
|
-- category switch lands on.
|
|
local CATEGORIES = {
|
|
{ key = "aura", name = "Aura", default = "aura" },
|
|
{ key = "spell", name = "Spell", default = "spellcooldown" },
|
|
{ key = "item", name = "Item", default = "itemcooldown" },
|
|
{ key = "unit", name = "Player & Unit", default = "health" },
|
|
{ key = "event", name = "Events", default = "readycheck" },
|
|
{ key = "custom", name = "Custom", default = "custom" },
|
|
}
|
|
|
|
local function categoryOf(typeName)
|
|
local spec = typeName and WA.triggerTypes[typeName]
|
|
return (spec and spec.category) or "unit"
|
|
end
|
|
|
|
-- Registered trigger types in one category ("aura" plus every GenericTrigger
|
|
-- prototype -- read live at options-build time, so a prototype registered in a
|
|
-- later file still shows up).
|
|
local function triggerTypeList(category)
|
|
local list, labels = {}, {}
|
|
for name, spec in pairs(WA.triggerTypes) do
|
|
if not category or (spec.category or "unit") == category then
|
|
table.insert(list, name)
|
|
labels[name] = spec.displayName or name
|
|
end
|
|
end
|
|
-- Ordered by the label the user reads rather than the internal key, which
|
|
-- would file "In Combat" under `combat` and scatter the cooldown types.
|
|
table.sort(list, function(a, b) return (labels[a] or a) < (labels[b] or b) end)
|
|
return list, labels
|
|
end
|
|
|
|
-- The shared Category + Type controls, reused by every trigger type's options
|
|
-- generator (aura here, each generic prototype in GenericTrigger.lua) so the
|
|
-- user can switch a trigger between kinds from any of them. Returns an array:
|
|
-- the Type dropdown is left out when its category holds a single type, which
|
|
-- would otherwise be a control with nothing to choose. Switching either seeds
|
|
-- the new type's defaults (MergeDefaults is non-destructive) before rebuilding,
|
|
-- so the freshly-selected type's config fields exist for its editor.
|
|
function WA.TriggerTypeFields(data, t)
|
|
local function switchTo(v)
|
|
t.type = v
|
|
WA.MergeDefaults(data)
|
|
WA.Add(data)
|
|
WA.RefreshOptions()
|
|
end
|
|
|
|
local catKeys, catLabels = {}, {}
|
|
for i = 1, #CATEGORIES do
|
|
catKeys[i] = CATEGORIES[i].key
|
|
catLabels[CATEGORIES[i].key] = CATEGORIES[i].name
|
|
end
|
|
|
|
local fields = { {
|
|
type = "select", name = "Category", key = "category",
|
|
values = catKeys, labels = catLabels,
|
|
get = function() return categoryOf(t.type) end,
|
|
set = function(v)
|
|
for i = 1, #CATEGORIES do
|
|
if CATEGORIES[i].key == v then switchTo(CATEGORIES[i].default); return end
|
|
end
|
|
end,
|
|
} }
|
|
|
|
local types, typeLabels = triggerTypeList(categoryOf(t.type))
|
|
if #types > 1 then
|
|
table.insert(fields, {
|
|
type = "select", name = "Type", key = "type",
|
|
values = types, labels = typeLabels,
|
|
get = function() return t.type end,
|
|
set = switchTo,
|
|
})
|
|
end
|
|
return fields
|
|
end
|
|
|
|
WA.RegisterTriggerType("aura", {
|
|
displayName = "Aura",
|
|
category = "aura",
|
|
defaults = {
|
|
unit = "player",
|
|
specificUnit = "",
|
|
debuffType = "HELPFUL",
|
|
useName = true,
|
|
auranames = {},
|
|
auraignorenames = {},
|
|
matchesShowOn = "showOnActive",
|
|
unitExists = false,
|
|
ownOnly = false,
|
|
castByPlayer = false,
|
|
use_debuffClass = false,
|
|
debuffClass = "Magic",
|
|
useStacks = false,
|
|
stacksOperator = ">=",
|
|
stacks = 1,
|
|
useRem = false,
|
|
remOperator = "<=",
|
|
rem = 0,
|
|
useTotal = false,
|
|
totalOperator = ">=",
|
|
total = 0,
|
|
useCasterName = false,
|
|
casterName = "",
|
|
useNamePattern = false,
|
|
namePattern = "",
|
|
namePatternOperator = "match",
|
|
},
|
|
-- In-place migration of an older saved aura trigger to the field names above.
|
|
-- Idempotent (MergeDefaults runs it on every touch).
|
|
migrate = function(t)
|
|
if t.auraType ~= nil then
|
|
if t.debuffType == nil then t.debuffType = t.auraType end
|
|
t.auraType = nil
|
|
end
|
|
if t.spellName ~= nil then
|
|
if t.spellName ~= "" then
|
|
t.auranames = t.auranames or {}
|
|
if not t.auranames[1] then t.auranames[1] = t.spellName end
|
|
t.useName = true
|
|
end
|
|
t.spellName = nil
|
|
end
|
|
end,
|
|
summary = function(data)
|
|
local t = WA.GetTrigger(data, 1)
|
|
if not t then return "" end
|
|
local kind = t.debuffType == "HARMFUL" and "Debuff" or (t.debuffType == "BOTH" and "Aura" or "Buff")
|
|
local n = t.auranames and #t.auranames or 0
|
|
local spell = (n > 0 and t.auranames[1] ~= "") and t.auranames[1] or "?"
|
|
if n > 1 then spell = spell .. " (+" .. (n - 1) .. ")" end
|
|
return kind .. ": " .. spell .. " (" .. WA.TriggerUnit(t, "player") .. ")"
|
|
end,
|
|
options = function(data, triggernum)
|
|
local t = WA.GetTrigger(data, triggernum or 1)
|
|
local fields = WA.TriggerTypeFields(data, t)
|
|
-- The dropdown's "specific" entry swaps in a free-text token field
|
|
-- beside it; the dropdown's stored token survives being hidden and
|
|
-- comes back when the override is switched off.
|
|
table.insert(fields, {
|
|
type = "select", name = "Unit", key = "unit", half = true,
|
|
values = WA.unit_tokens_aura, labels = WA.unit_labels_aura,
|
|
get = function() return t.unit end,
|
|
set = function(v) t.unit = v; WA.Add(data); WA.RefreshOptions() end,
|
|
})
|
|
if t.unit == "specific" then
|
|
table.insert(fields, {
|
|
type = "input", name = "Unit Token", key = "specificUnit", half = true,
|
|
get = function() return t.specificUnit or "" end,
|
|
set = function(v) t.specificUnit = v; WA.Add(data) end,
|
|
})
|
|
end
|
|
local more = {
|
|
{
|
|
type = "select", name = "Aura Type", key = "debuffType", half = true,
|
|
values = { "HELPFUL", "HARMFUL", "BOTH" },
|
|
labels = DEBUFF_TYPE_LABELS,
|
|
get = function() return t.debuffType end,
|
|
set = function(v) t.debuffType = v; WA.Add(data) end,
|
|
},
|
|
|
|
{ type = "header", name = "Spell Selection" },
|
|
{
|
|
-- Any-of-these-match list: a numeric entry matches by spellId
|
|
-- (AuraData.spellId, more precise than name -- handles same-name
|
|
-- rank collisions), a text entry by aura.name. No separate
|
|
-- ID-vs-name toggle is needed since WA.ResolveSpellID's
|
|
-- numeric-trusts-as-ID rule already disambiguates the two per
|
|
-- entry.
|
|
type = "namelist", name = "Aura Names / IDs (any match)", key = "auranames",
|
|
get = function() return t.auranames end,
|
|
-- RefreshList too: the list row's icon and summary line are both
|
|
-- derived from these entries (see resolveRowIcon).
|
|
onChange = function() WA.Add(data); WA.RefreshList(); WA.RefreshOptions() end,
|
|
},
|
|
{
|
|
-- Rejected even when the match list also names them, so a broad
|
|
-- name pattern can be narrowed by exception. No RefreshList: the
|
|
-- row icon and summary come from the match list only.
|
|
type = "namelist", name = "Ignore Names / IDs", key = "auraignorenames",
|
|
get = function() return t.auraignorenames end,
|
|
onChange = function() WA.Add(data) end,
|
|
},
|
|
|
|
{ type = "header", name = "Active Aura Filters" },
|
|
{
|
|
type = "toggle", name = "Own Only", key = "ownOnly",
|
|
get = function() return t.ownOnly end,
|
|
set = function(v) t.ownOnly = v; WA.Add(data) end,
|
|
},
|
|
{
|
|
type = "toggle", name = "Cast By Player", key = "castByPlayer",
|
|
get = function() return t.castByPlayer end,
|
|
set = function(v) t.castByPlayer = v; WA.Add(data) end,
|
|
},
|
|
}
|
|
for i = 1, #more do table.insert(fields, more[i]) end
|
|
table.insert(fields, {
|
|
type = "toggle", name = "Dispel Type", key = "use_debuffClass",
|
|
get = function() return t.use_debuffClass end,
|
|
set = function(v) t.use_debuffClass = v; WA.Add(data); WA.RefreshOptions() end,
|
|
})
|
|
if t.use_debuffClass then
|
|
table.insert(fields, {
|
|
type = "select", name = "Dispel Type", key = "debuffClass",
|
|
values = DISPEL_TYPES,
|
|
get = function() return t.debuffClass end,
|
|
set = function(v) t.debuffClass = v; WA.Add(data) end,
|
|
})
|
|
end
|
|
table.insert(fields, {
|
|
type = "toggle", name = "Stacks", key = "useStacks",
|
|
get = function() return t.useStacks end,
|
|
set = function(v) t.useStacks = v; WA.Add(data); WA.RefreshOptions() end,
|
|
})
|
|
if t.useStacks then
|
|
table.insert(fields, {
|
|
type = "opnumber", name = "Stacks", key = "stacks",
|
|
getOp = function() return t.stacksOperator end,
|
|
setOp = function(v) t.stacksOperator = v; WA.Add(data) end,
|
|
getVal = function() return t.stacks end,
|
|
setVal = function(v) t.stacks = v; WA.Add(data) end,
|
|
})
|
|
end
|
|
table.insert(fields, {
|
|
type = "toggle", name = "Remaining Time", key = "useRem",
|
|
get = function() return t.useRem end,
|
|
set = function(v) t.useRem = v; WA.Add(data); WA.RefreshOptions() end,
|
|
})
|
|
if t.useRem then
|
|
table.insert(fields, {
|
|
type = "opnumber", name = "Remaining Time (s)", key = "rem",
|
|
getOp = function() return t.remOperator end,
|
|
setOp = function(v) t.remOperator = v; WA.Add(data) end,
|
|
getVal = function() return t.rem end,
|
|
setVal = function(v) t.rem = v; WA.Add(data) end,
|
|
})
|
|
end
|
|
table.insert(fields, {
|
|
type = "toggle", name = "Total Duration", key = "useTotal",
|
|
get = function() return t.useTotal end,
|
|
set = function(v) t.useTotal = v; WA.Add(data); WA.RefreshOptions() end,
|
|
})
|
|
if t.useTotal then
|
|
table.insert(fields, {
|
|
type = "opnumber", name = "Total Duration (s)", key = "total",
|
|
getOp = function() return t.totalOperator end,
|
|
setOp = function(v) t.totalOperator = v; WA.Add(data) end,
|
|
getVal = function() return t.total end,
|
|
setVal = function(v) t.total = v; WA.Add(data) end,
|
|
})
|
|
end
|
|
table.insert(fields, {
|
|
type = "toggle", name = "Caster Name", key = "useCasterName",
|
|
get = function() return t.useCasterName end,
|
|
set = function(v) t.useCasterName = v; WA.Add(data); WA.RefreshOptions() end,
|
|
})
|
|
if t.useCasterName then
|
|
table.insert(fields, {
|
|
type = "input", name = "Caster Name", key = "casterName",
|
|
get = function() return t.casterName end,
|
|
set = function(v) t.casterName = v; WA.Add(data) end,
|
|
})
|
|
end
|
|
table.insert(fields, {
|
|
type = "toggle", name = "Name Pattern", key = "useNamePattern",
|
|
get = function() return t.useNamePattern end,
|
|
set = function(v) t.useNamePattern = v; WA.Add(data); WA.RefreshOptions() end,
|
|
})
|
|
if t.useNamePattern then
|
|
table.insert(fields, {
|
|
type = "select", name = "Name Pattern", key = "namePatternOperator", half = true,
|
|
values = { "match", "nomatch" },
|
|
labels = NAME_PATTERN_LABELS,
|
|
get = function() return t.namePatternOperator end,
|
|
set = function(v) t.namePatternOperator = v; WA.Add(data) end,
|
|
})
|
|
table.insert(fields, {
|
|
type = "input", name = "Text", key = "namePattern", half = true,
|
|
get = function() return t.namePattern end,
|
|
set = function(v) t.namePattern = v; WA.Add(data) end,
|
|
})
|
|
end
|
|
|
|
table.insert(fields, { type = "header", name = "Show On" })
|
|
table.insert(fields, {
|
|
type = "select", name = "Show On", key = "matchesShowOn",
|
|
values = { "showOnActive", "showOnMissing", "showAlways" },
|
|
labels = SHOW_ON_LABELS,
|
|
get = function() return t.matchesShowOn end,
|
|
set = function(v) t.matchesShowOn = v; WA.Add(data) end,
|
|
})
|
|
table.insert(fields, {
|
|
type = "toggle", name = "Show If Unit Is Not Available", key = "unitExists",
|
|
get = function() return t.unitExists end,
|
|
set = function(v) t.unitExists = v; WA.Add(data) end,
|
|
})
|
|
return fields
|
|
end,
|
|
})
|