diff --git a/.gitignore b/.gitignore index 3d6406e..4b792ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Build Artifacts -Quiver.bundle.lua +# Quiver.bundle.lua # Dependencies node_modules diff --git a/Quiver.bundle.lua b/Quiver.bundle.lua new file mode 100644 index 0000000..b724379 --- /dev/null +++ b/Quiver.bundle.lua @@ -0,0 +1,8794 @@ +local __bundle_require, __bundle_loaded, __bundle_register, __bundle_modules = (function(superRequire) + local loadingPlaceholder = {[{}] = true} + + local register + local modules = {} + + local require + local loaded = {} + + register = function(name, body) + if not modules[name] then + modules[name] = body + end + end + + require = function(name) + local loadedModule = loaded[name] + + if loadedModule then + if loadedModule == loadingPlaceholder then + return nil + end + else + if not modules[name] then + if not superRequire then + local identifier = type(name) == 'string' and '\"' .. name .. '\"' or tostring(name) + error('Tried to require ' .. identifier .. ', but no such module has been registered') + else + return superRequire(name) + end + end + + loaded[name] = loadingPlaceholder + loadedModule = modules[name](require, loaded, register, modules) + loaded[name] = loadedModule + end + + return loadedModule + end + + return require, loaded, register, modules +end)(nil) +__bundle_register("__root", function(require, _LOADED, __bundle_register, __bundle_modules) +local LoadLocale = require("Locale/Lang.lua") +local MainMenu = require("Config/MainMenu.lua") +local Migrations = require("Migrations/Runner.lua") +local AspectTracker = require("Modules/Aspect_Tracker/AspectTracker.lua") +local AutoShotTimer = require("Modules/Auto_Shot_Timer/AutoShotTimer.lua") +local Castbar = require("Modules/Castbar.lua") +local RangeIndicator = require("Modules/RangeIndicator.lua") +local TranqAnnouncer = require("Modules/TranqAnnouncer.lua") +local TrueshotAuraAlarm = require("Modules/TrueshotAuraAlarm.lua") +local UpdateNotifierInit = require("Modules/UpdateNotifier.lua") +local RegisterApiFunctions = require("Api.lua") + +_G = _G or getfenv() +Quiver = Quiver or {} +_G.Quiver_Modules = { + AspectTracker, + AutoShotTimer, + Castbar, + RangeIndicator, + TranqAnnouncer, + TrueshotAuraAlarm, +} + +local savedVariablesRestore = function() + -- If first time running Quiver, then savedVars are nil, so make defaults + Quiver_Store.IsLockedFrames = Quiver_Store.IsLockedFrames == true + Quiver_Store.ModuleEnabled = Quiver_Store.ModuleEnabled or {} + Quiver_Store.ModuleStore = Quiver_Store.ModuleStore or {} + Quiver_Store.DebugLevel = Quiver_Store.DebugLevel or "None" + Quiver_Store.Border_Style = Quiver_Store.Border_Style or "Simple" + for _k, v in _G.Quiver_Modules do + Quiver_Store.ModuleEnabled[v.Id] = Quiver_Store.ModuleEnabled[v.Id] ~= false + Quiver_Store.ModuleStore[v.Id] = Quiver_Store.ModuleStore[v.Id] or {} + -- Loading saved variables into each module gives them a chance to set their own defaults. + v.OnSavedVariablesRestore(Quiver_Store.ModuleStore[v.Id]) + end +end +local savedVariablesPersist = function() + for _k, v in _G.Quiver_Modules do + Quiver_Store.ModuleStore[v.Id] = v.OnSavedVariablesPersist() + end +end + +local initSlashCommandsAndModules = function() + SLASH_QUIVER1 = "/qq" + SLASH_QUIVER2 = "/quiver" + local _, cl = UnitClass("player") + if cl == "HUNTER" then + local frameConfigMenu = MainMenu.Create() + SlashCmdList["QUIVER"] = function(_args, _box) frameConfigMenu:Show() end + for _k, v in _G.Quiver_Modules do + if Quiver_Store.ModuleEnabled[v.Id] then v.OnEnable() end + end + else + SlashCmdList["QUIVER"] = function() DEFAULT_CHAT_FRAME:AddMessage(Quiver.T["Quiver is for hunters."], 1, 0.5, 0) end + end +end + +--[[ +// TODO revisit this now that we don't load any pfUI plugins +https://wowpedia.fandom.com/wiki/AddOn_loading_process +All of these events fire on login and UI reload. We don't need to clutter chat +until the user interacts with Quiver, and we don't pre-cache action bars. That +means it's okay to load before other addons (action bars, chat windows). +pfUI loads before we register plugins for it. Quiver comes alphabetically later, +but it's safer to use a later event in case names change. + +ADDON_LOADED Fires each time any addon loads, but can't yet print to pfUI's chat menu +PLAYER_LOGIN Fires once, but can't yet read talent tree +PLAYER_ENTERING_WORLD fires on every load screen +SPELLS_CHANGED fires every time the spellbook changes +]] +local frame = CreateFrame("Frame", nil) +frame:RegisterEvent("PLAYER_LOGIN") +frame:RegisterEvent("PLAYER_LOGOUT") +frame:RegisterEvent("ADDON_LOADED") +frame:SetScript("OnEvent", function() + if event == "ADDON_LOADED" and arg1 == "Quiver" then + -- TODO set preferred language in saved variables to use here + LoadLocale()-- Must run before everything else + Migrations()-- Modifies saved variables + savedVariablesRestore()-- Passes saved data to modules for init + initSlashCommandsAndModules() + RegisterApiFunctions() + elseif event == "PLAYER_LOGIN" then + UpdateNotifierInit() + elseif event == "PLAYER_LOGOUT" then + savedVariablesPersist() + end +end) + +end) +__bundle_register("Api.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local AutoShotTimer = require("Modules/Auto_Shot_Timer/AutoShotTimer.lua") +local Pet = require("Shiver/API/Pet.lua") +local Spell = require("Shiver/API/Spell.lua") + +---@param spellName string +---@return nil +local CastNoClip = function(spellName) + if not AutoShotTimer.PredMidShot() then + CastSpellByName(spellName) + end +end + +---@param actionName string +---@return nil +local CastPetActionByName = function(actionName) + -- local hasSpells = HasPetUI() + -- local hasUI = HasPetUI() + if GetPetActionsUsable() then + Pet.CastActionByName(actionName) + end +end + +---@param spellNameLocalized string +local predOffCd = function(spellNameLocalized) + local index = Spell.FindSpellIndex(spellNameLocalized) + if index ~= nil then + local timeStartCd, _ = GetSpellCooldown(index, BOOKTYPE_SPELL) + return timeStartCd == 0 + else + return false + end +end + +-- Casts feign death (if needed) and sets pet passive (if needed). +-- Usage: +-- /cast Frost Trap +-- /script Quiver.FdPrepareTrap() +local FdPrepareTrap = function() + -- Requires level 16, which makes it the lowest level trap + local trap = Quiver.L.Spell["Immolation Trap"] + local fd = Quiver.L.Spell["Feign Death"] + if UnitAffectingCombat("player") and predOffCd(trap) and predOffCd(fd) then + if UnitExists("pettarget") and UnitAffectingCombat("pet") then + PetPassiveMode() + PetFollow() + end + CastSpellByName(fd) + end +end + +return function() + Quiver.CastNoClip = CastNoClip + Quiver.CastPetAction = CastPetActionByName + Quiver.FdPrepareTrap = FdPrepareTrap + Quiver.GetSecondsRemainingReload = AutoShotTimer.GetSecondsRemainingReload + Quiver.GetSecondsRemainingShoot = AutoShotTimer.GetSecondsRemainingShoot + Quiver.PredMidShot = AutoShotTimer.PredMidShot +end + +end) +__bundle_register("Shiver/API/Spell.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local DB_SPELL = require("Shiver/Data/Spell.lua") + +---@param spellName string +---@return nil|integer spellIndex +local FindSpellIndex = function(spellName) + local numTabs = GetNumSpellTabs() + local _, _, tabOffset, numEntries = GetSpellTabInfo(numTabs) + local numSpells = tabOffset + numEntries + for spellIndex=1, numSpells do + local name, _rank = GetSpellName(spellIndex, BOOKTYPE_SPELL) + if name == spellName then + return spellIndex + end + end + return nil +end + +--- This assumes the texture uniquely identifies a spell, which may not be true. +---@param texturePath string +---@return nil|string spellName +---@return nil|integer spellIndex +---@nodiscard +local FindSpellByTexture = function(texturePath) + local i = 0 + while true do + i = i + 1 + local t = GetSpellTexture(i, BOOKTYPE_SPELL) + local name, _rank = GetSpellName(i, BOOKTYPE_SPELL) + if not t or not name then + break-- Base Case + elseif t == texturePath then + return name, i + end + end + return nil, nil +end + +--- Returns true if spell is instant cast +--- If meta is nil, we can't run cast time code, so assume instant. +---@param meta nil|{ Time: number; Offset: number } +---@return boolean +---@nodiscard +local PredInstant = function(meta) + if meta == nil then + return true + else + return 0 == meta.Time + meta.Offset + end +end + +---@param name string +---@return boolean +---@nodiscard +local PredInstantShotByName = function(name) + local meta = DB_SPELL[name] + return meta ~= nil and meta.IsAmmo and (meta.Offset + meta.Time == 0) +end + +---@param spellName string +---@return boolean +---@nodiscard +local PredSpellLearned = function(spellName) + local i = 0 + while true do + i = i + 1 + local name, _rank = GetSpellName(i, BOOKTYPE_SPELL) + if not name then return false + elseif name == spellName then return true + end + end +end + +local CheckNewCd = function(cooldown, lastCdStart, spellName) + local spellIndex = FindSpellIndex(spellName) + if spellIndex ~= nil then + local timeStartCD, durationCD = GetSpellCooldown(spellIndex, BOOKTYPE_SPELL) + -- Sometimes spells return a CD of 0 when cast fails. + -- If it's non-zero, we have a valid timeStart to check. + if durationCD == cooldown and timeStartCD ~= lastCdStart then + return true, timeStartCD + end + end + return false, lastCdStart +end + +local CheckNewGCD = function(lastCdStart) + return CheckNewCd(1.5, lastCdStart, Quiver.L.Spell["Serpent Sting"]) +end + +return { + CheckNewCd=CheckNewCd, + CheckNewGCD=CheckNewGCD, + FindSpellByTexture = FindSpellByTexture, + FindSpellIndex = FindSpellIndex, + PredInstant = PredInstant, + PredInstantShotByName = PredInstantShotByName, + PredSpellLearned = PredSpellLearned, +} + +end) +__bundle_register("Shiver/Data/Spell.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +-- Data is fully denormalized since we don't have a database. +-- This will probably cause maintenance problems. +return { + -- Casted Shots + ["Aimed Shot"]={ Class="hunter", Time=3000, Offset=500, Haste="range", Icon="INV_Spear_07", IsAmmo=true }, + ["Multi-Shot"]={ Class="hunter", Time=0, Offset=500, Haste="range", Icon="Ability_UpgradeMoonGlaive", IsAmmo=true }, + ["Trueshot"]={ Class="hunter", Time=1000, Offset=500, Haste="range", Icon="Ability_TrueShot", IsAmmo=true }, + + -- Instant Shots + ["Arcane Shot"]={ Class="hunter", Time=0, Offset=0, Haste="none", Icon="Ability_ImpalingBolt", IsAmmo=true }, + ["Concussive Shot"]={ Class="hunter", Time=0, Offset=0, Haste="none", Icon="Spell_Frost_Stun", IsAmmo=true }, + ["Scatter Shot"]={ Class="hunter", Time=0, Offset=0, Haste="none", Icon="Ability_GolemStormBolt", IsAmmo=true }, + ["Scorpid Sting"]={ Class="hunter", Time=0, Offset=0, Haste="none", Icon="Ability_Hunter_CriticalShot", IsAmmo=true }, + ["Serpent Sting"]={ Class="hunter", Time=0, Offset=0, Haste="none", Icon="Ability_Hunter_Quickshot", IsAmmo=true }, + ["Viper Sting"]={ Class="hunter", Time=0, Offset=0, Haste="none", Icon="Ability_Hunter_AimedShot", IsAmmo=true }, + ["Wyvern Sting"]={ Class="hunter", Time=0, Offset=0, Haste="none", Icon="INV_Spear_02", IsAmmo=true }, +} + +end) +__bundle_register("Shiver/API/Pet.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local _NUM_PET_ACTION_SLOTS = 10 + +---@param actionName string +---@return nil|1|2|3|4|5|6|7|8|9|10 +---@nodiscard +local findActionIndex = function(actionName) + for i=1, _NUM_PET_ACTION_SLOTS, 1 do + local name, subtext, tex, isToken, isActive, isAutoCastAllowed, isAutoCastEnabled = GetPetActionInfo(i) + if (name == actionName) then + return i + end + end + return nil +end + +---@param actionName string +---@return nil +local CastActionByName = function(actionName) + local index = findActionIndex(actionName) + if index ~= nil then CastPetAction(index) end +end + +return { + CastActionByName = CastActionByName, +} + +end) +__bundle_register("Modules/Auto_Shot_Timer/AutoShotTimer.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local FrameLock = require("Events/FrameLock.lua") +local Spellcast = require("Events/Spellcast.lua") +local BorderStyle = require("Modules/BorderStyle.provider.lua") +local Spell = require("Shiver/API/Spell.lua") +local Haste = require("Shiver/Haste.lua") + +local log = function(text) + if Quiver_Store.DebugLevel == "Verbose" then + DEFAULT_CHAT_FRAME:AddMessage(text) + end +end + +-- Auto Shot +local _AIMING_TIME = 0.5-- HSK, rais, and YaHT use 0.65. However, 0.5 seems better. +local MODULE_ID = "AutoShotTimer" +local store = nil---@type StoreAutoShotTimer +local frame = nil +local maxBarWidth = 0 +-- Aimed Shot, Multi-Shot, Trueshot +local castTime = 0 +local isCasting = false +local isFiredInstant = false +local timeStartCastLocal = 0 + +local position = (function() + local x, y = 0, 0 + local updateXY = function() x, y = GetPlayerMapPosition("player") end + return { + UpdateXY = updateXY, + CheckStandingStill = function() + local lastX, lastY = x, y + updateXY() + return x == lastX and y == lastY + end, + } +end)() + +local isReloading = false +local timeReload = (function() + local reloadTime = 0 + local time = GetTime() + local getElapsed = function() return GetTime() - time end + return { + GetPercentCompleted = function() + local elapsed = getElapsed() + if elapsed <= reloadTime then + return elapsed / reloadTime + else + return 1.0 + end + end, + GetRemaining = function() return reloadTime - getElapsed() end, + Reset = function() time = GetTime() end, + ---@param t number + StartAt = function(t) + time = t + local speed, _, _, _, _, _ = UnitRangedDamage("player") + isReloading = true + reloadTime = speed - _AIMING_TIME + log("starting reload") + end, + } +end)() + +local isShooting = false +local timeShoot = (function() + local time = GetTime() + local getElapsed = function() return GetTime() - time end + return { + GetPercentCompleted = function() + local elapsed = getElapsed() + if elapsed <= _AIMING_TIME then + return elapsed / _AIMING_TIME + else + return 1.0 + end + end, + GetRemaining = function() return _AIMING_TIME - getElapsed() end, + Reset = function() time = GetTime() end, + } +end)() +-- May be called after reload while already shooting +local startShooting = function() + if not isReloading then timeShoot.Reset() end + isShooting = true + position.UpdateXY() +end + +local getIsConsumable = function(combatLogMsg) + if combatLogMsg == nil then return false end + for _k, v in Quiver.L.CombatLog.Consumes do + local startPos, _ = string.find(combatLogMsg, v) + if startPos then return true end + end + return false +end +local isConsumable = false + +-- ************ UI ************ +local styleBarAutoShot = function(f) + local sizeInset = BorderStyle.GetInsetSize() + + if BorderStyle.GetStyle() == "Tooltip" then + f:SetBackdrop({ + bgFile = "Interface/BUTTONS/WHITE8X8", + edgeFile = "Interface/Tooltips/UI-Tooltip-Border", + edgeSize = 10, + insets = { left=sizeInset, right=sizeInset, top=sizeInset, bottom=sizeInset }, + }) + f:SetBackdropBorderColor(BorderStyle.GetColor()) + else + f:SetBackdrop({ + bgFile = "Interface/BUTTONS/WHITE8X8", + edgeFile = "Interface/BUTTONS/WHITE8X8", + edgeSize = sizeInset, + }) + f:SetBackdropBorderColor(0.2, 0.2, 0.2, 0.8) + end + f:SetBackdropColor(0, 0, 0, 0.8) + + -- Coerce to boolean because there's nothing sensible to do if we have an invalid value. + if store.BarDirection == "LeftToRight" then + f.BarAutoShot:ClearAllPoints() + f.BarAutoShot:SetPoint("Left", f, "Left", sizeInset, 0) + else + f.BarAutoShot:ClearAllPoints() + f.BarAutoShot:SetPoint("Center", f, "Center", 0, 0) + end + + maxBarWidth = f:GetWidth() - 2 * sizeInset + f.BarAutoShot:SetWidth(1)-- Must be > 0 or UI doesn't resize. + f.BarAutoShot:SetHeight(f:GetHeight() - 2 * sizeInset) +end + +local setFramePosition = function(f, s) + FrameLock.SideEffectRestoreSize(s, { + w=240, h=14, dx=240 * -0.5, dy=-136, + }) + f:SetWidth(s.FrameMeta.W) + f:SetHeight(s.FrameMeta.H) + f:SetPoint("TopLeft", s.FrameMeta.X, s.FrameMeta.Y) +end + +local createUI = function() + local f = CreateFrame("Frame", nil, UIParent) + f:SetFrameStrata("HIGH") + + f.BarAutoShot = CreateFrame("Frame", nil, f) + f.BarAutoShot:SetBackdrop({ + bgFile = "Interface/BUTTONS/WHITE8X8", + tile = false, + }) + + setFramePosition(f, store) + styleBarAutoShot(f) + + FrameLock.SideEffectMakeMoveable(f, store) + FrameLock.SideEffectMakeResizeable(f, store, { + GripMargin=4, + OnResizeDrag=function() styleBarAutoShot(f) end, + OnResizeEnd=function() styleBarAutoShot(f) end, + IsCenterX=true, + }) + return f +end + +-- ************ Frame Update Handlers ************ +local updateBarShooting = function() + frame:SetAlpha(1) + local r, g, b = unpack(store.ColorShoot) + frame.BarAutoShot:SetBackdropColor(r, g, b, 0.8) + if isCasting then + frame.BarAutoShot:SetWidth(1) + else + frame.BarAutoShot:SetWidth(maxBarWidth * timeShoot.GetPercentCompleted()) + end +end + +local tryHideBar = function() + if Quiver_Store.IsLockedFrames then + frame:SetAlpha(0) + else + -- Reset bar if it's locked open + frame.BarAutoShot:SetWidth(1) + timeShoot.Reset() + timeReload.Reset() + end +end + +local updateBarReload = function() + frame:SetAlpha(1) + local r, g, b = unpack(store.ColorReload) + frame.BarAutoShot:SetBackdropColor(r, g, b, 0.8) + local percentCompleted = timeReload.GetPercentCompleted() + if percentCompleted < 1.0 then + frame.BarAutoShot:SetWidth(maxBarWidth - maxBarWidth * percentCompleted) + else + log("End reload") + isReloading = false + if isShooting then + startShooting() + else + tryHideBar() + end + end +end + +local handleUpdate = function() + if isReloading then + updateBarReload() + elseif isShooting and position.CheckStandingStill() then + updateBarShooting() + else + -- We probably moved while shooting + timeShoot.Reset() + tryHideBar() + end +end + +--[[ ************ Event Handlers ************ +1. Instant Shot while either moving or in middle of reload +-> (hook) OnInstant +-> ITEM_LOCK_CHANGED +-> SPELLCAST_STOP +2. Instant Shot as Auto Shot fires +-> (hook) OnInstant +-> ITEM_LOCK_CHANGED +-> ITEM_LOCK_CHANGED +-> SPELLCAST_STOP +3. Casted Shot starts as Auto Shot fires +-> (hook) OnCast +-> ITEM_LOCK_CHANGED +-> ITEM_LOCK_CHANGED +-> SPELLCAST_STOP +4. Auto Shot -> Casted Shot -> Instant Shot -> Auto Shot +-> ITEM_LOCK_CHANGED (auto) +-> (hook) casting +-> (hook) instant (spamming before cast finishes) +-> ITEM_LOCK_CHANGED (casted) +-> SPELLCAST_STOP (casted) +-> (hook) instant (still spamming the instant) +-> ITEM_LOCK_CHANGED (instant) +-> SPELLCAST_STOP (instant) +-> ITEM_LOCK_CHANGED (auto) +]] +--- @type Event[] +local EVENTS = { + -- Fires after SPELLCAST_STOP, but before ITEM_LOCK_CHANGED. + -- Use to ignore whitelisted inventory events corresponding to consumables. + "CHAT_MSG_SPELL_SELF_BUFF", + -- Inventory event, such as using ammo or drinking a potion. + -- This is how we detect auto shots. + "ITEM_LOCK_CHANGED", + "SPELLCAST_DELAYED",-- Pushback + -- Failed / INTERRUPTED / STOP all happen after ITEM_LOCK_CHANGED + "SPELLCAST_FAILED",-- Too close, Spell on CD, already in progress, or success after dropping target + "SPELLCAST_INTERRUPTED",-- Knockback etc. + "SPELLCAST_STOP",-- Finished cast + "START_AUTOREPEAT_SPELL",-- Start shooting (first event fired in chain) + "STOP_AUTOREPEAT_SPELL",-- Stop shooting (last event fired in chain) +} + +---@param event string +---@param arg1 any +local handleEventStateCasting = function(event, arg1) + if event == "SPELLCAST_DELAYED" then + castTime = castTime + arg1 / 1000 + elseif + event == "SPELLCAST_STOP" + or event == "SPELLCAST_FAILED" + or event == "SPELLCAST_INTERRUPTED" + then + -- The instant hook may have set its state variable. That means the user pressed an instant shot + -- before the castbar completed. We can't actually fire an instant shot while casting, + -- so it's a false positive and we need to override it to avoid breaking our state machine. + isFiredInstant = false + isCasting = false -- Exit this handler + if not isReloading then timeShoot.Reset() end + log("Stopped Casting") + elseif event == "ITEM_LOCK_CHANGED" then + -- Failed event means Stop, but we also dropped target before the cast finished. + -- Two possibilities: + -- 1 - Cast Start -> Lock -> Lock -> Cast Stop or Cast Failed + -- - Cause: Auto Shot fired as cast started. + -- - Action: Trigger reload and continue casting. + -- 2 - Cast Start -> Lock -> Cast Stop or Cast Failed + -- - Cause: Cast completed. + -- - Action: End casting. + -- Case 1 happens immediately, while case 2 can't happen faster than the fastest cast. + -- The cast start is latency-adjusted, so we pick a number between 0 and 0.5 (Multi-Shot cast time). + -- Too high a threshold and multi-shot occasionally triggers reload when latency changes. + -- Too low and we get stuck in a shooting state. This is much worse than an extra reload. + -- 0.4 frequently trigger reloads over 100ms latency. + local elapsed = GetTime() - timeStartCastLocal + log(elapsed) + if (elapsed < 0.25) then + -- We must have started the cast exactly as an auto shot fired. + -- This happens when server lag causes the bar the skip. + timeReload.StartAt(GetTime()) + end + end +end + +-- Two cases to handle. +-- Case 1: Auto -> Instant +-- Instant -> Lock -> Lock -> Stop +-- Csae 2: Instant -> Auto +-- Instant -> Lock -> Stop -> Lock +-- There's no way to know whether or not to start the reload at first lock, so we save +-- the reload time and apply it retroactively. This requires its own Mealy machine. +local stateAuto = { IsInitial=true, TimeLock=0 } +---@param event string +local handleEventStateShooting = function(event) + -- Don't have to handle other spellcast events because they only trigger on successful + -- casts without a selected target. However, dropping target cancels Auto Shot. + if event == "SPELLCAST_STOP" then + -- There's a measurable 0.5 second reset to Auto Shot when casting any instant spell (ex. Hunter's Mark). + -- Since auto shot also seems to use 0.5 second shoot time, we can reset it to 0. + timeShoot.Reset() + end + + if stateAuto.IsInitial then + -- Handle first shot in sequence + if event == "ITEM_LOCK_CHANGED" then + if isFiredInstant then + stateAuto.IsInitial = false + stateAuto.TimeLock = GetTime() + isFiredInstant = false + log("State Advance") + elseif timeReload.GetRemaining() > 0 then + -- Sometimes SPELLCAST_STOP triggers before ITEM_LOCK_CHANGED + -- No-op from multi-shot during reload. + log("Edge case -- out-of-order events. Probably mutli-shot: "..timeReload.GetRemaining()) + else + log("Auto Fired") + timeReload.StartAt(GetTime()) + end + end + -- else ignore + else + -- Handle second shot in sequence + if event == "ITEM_LOCK_CHANGED" then + -- Fired another shot, meaning the first one must have been an auto. + -- Retroactively start reload and reset state. + stateAuto.IsInitial = true + isFiredInstant = false + timeReload.StartAt(stateAuto.TimeLock) + log("State Reset: Auto -> Instant") + elseif event == "SPELLCAST_STOP" then + -- Previous shot must have been an instant, so reset state. + stateAuto.IsInitial = true + isFiredInstant = false + log("State Reset: Instant") + end + -- else ignore + end +end + +---@param event string +local handleEventStateIdle = function(event) + if + event == "SPELLCAST_STOP" + or event == "SPELLCAST_FAILED" + or event == "SPELLCAST_INTERRUPTED" + then + if isFiredInstant then log("Instant Shot") end + isFiredInstant = false + end +end + +---@param nameEnglish string +---@param nameLocalized string +local onSpellcast = function(nameEnglish, nameLocalized) + -- User can spam the ability while it's already casting + if isCasting then return end + isCasting = true + local _latAdjusted + castTime, _latAdjusted, timeStartCastLocal = Haste.CalcCastTime(nameEnglish) + log("Start Cast") +end + +local handleEvent = function() + local e = event + if (e ~= "CHAT_MSG_SPELL_SELF_BUFF") then + local t1 = isCasting and "casting" or "false" + local t2 = stateAuto.IsInitial and "initial" or "advanced" + log(t1.." "..t2.." "..e) + end + -- ************ Event logic independant of state ************ + if e == "CHAT_MSG_SPELL_SELF_BUFF" then + isConsumable = getIsConsumable(arg1) + elseif e == "START_AUTOREPEAT_SPELL" then + log("Start shooting") + startShooting() + elseif e == "STOP_AUTOREPEAT_SPELL" then + log("Stop shooting") + isShooting = false + elseif isConsumable and e == "ITEM_LOCK_CHANGED" then + isConsumable = false-- We drank a potion or something, so don't run any handlers + -- ************ Mealy machine states ************ + elseif isCasting then + handleEventStateCasting(e, arg1) + elseif isShooting then + handleEventStateShooting(e) + else + handleEventStateIdle(e) + end +end + +-- ************ Enable macros that avoid clipping shots ************ +---@return boolean +---@nodiscard +local PredMidShot = function() + return isShooting and not isReloading +end + +local GetSecondsRemainingReload = function() + if isReloading then + return true, timeReload.GetRemaining() + else + return false, 0 + end +end + +local GetSecondsRemainingShoot = function() + local t = timeShoot.GetRemaining() + local isFiring = isShooting and not isReloading + if isFiring then + return true, t + else + return false, 0 + end +end + +-- ************ Initialization ************ +local onEnable = function() + if frame == nil then + frame = createUI() + end + frame:SetScript("OnEvent", handleEvent) + frame:SetScript("OnUpdate", handleUpdate) + for _k, e in EVENTS do frame:RegisterEvent(e) end + if Quiver_Store.IsLockedFrames then frame:SetAlpha(0) else frame:SetAlpha(1) end + BorderStyle.Subscribe(MODULE_ID, function(_style) + if frame ~= nil then styleBarAutoShot(frame) end + end) + Spellcast.CastableShot.Subscribe(MODULE_ID, onSpellcast) + Spellcast.Instant.Subscribe(MODULE_ID, function(spellName) + isFiredInstant = Spell.PredInstantShotByName(spellName) + end) + frame:Show() +end + +local onDisable = function() + Spellcast.Instant.Dispose(MODULE_ID) + Spellcast.CastableShot.Dispose(MODULE_ID) + BorderStyle.Dispose(MODULE_ID) + if frame ~= nil then + frame:Hide() + for _k, e in EVENTS do frame:UnregisterEvent(e) end + end +end + +---@type QqModule +return { + Id = MODULE_ID, + GetName = function() return Quiver.T["Auto Shot Timer"] end, + GetTooltipText = function() return nil end, + OnEnable = onEnable, + OnDisable = onDisable, + OnInterfaceLock = function() + if (not isShooting) and (not isReloading) then tryHideBar() end + end, + OnInterfaceUnlock = function() + if frame ~= nil then frame:SetAlpha(1) end + end, + OnResetFrames = function() + store.FrameMeta = nil + if frame then + setFramePosition(frame, store) + styleBarAutoShot(frame) + end + end, + ---@param savedVariables StoreAutoShotTimer + OnSavedVariablesRestore = function(savedVariables) + store = savedVariables + store.BarDirection = savedVariables.BarDirection or "LeftToRight" + store.ColorShoot = savedVariables.ColorShoot or QUIVER.ColorDefault.AutoShotShoot + store.ColorReload = savedVariables.ColorReload or QUIVER.ColorDefault.AutoShotReload + end, + OnSavedVariablesPersist = function() return store end, + UpdateDirection = function() + if frame then styleBarAutoShot(frame) end + end, + -- API exports + GetSecondsRemainingReload = GetSecondsRemainingReload, + GetSecondsRemainingShoot = GetSecondsRemainingShoot, + PredMidShot = PredMidShot, +} + +end) +__bundle_register("Shiver/Haste.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local DB_SPELL = require("Shiver/Data/Spell.lua") +local ScanningTooltip = require("Shiver/ScanningTooltip.lua") +local Enum = require("Shiver/Enum.lua") + +-- GetInventoryItemLink("Player", slot#) returns a link, ex. [name] +-- Weapon name always appears at line TextLeft1 +-- TODo Might be cachable. Experiment which events would clear cache. +local calcRangedWeaponSpeedBase = function() + return ScanningTooltip.Scan(function(tooltip) + tooltip:ClearLines() + local _RANGED = Enum.INVENTORY_SLOT.Ranged + local _, _, _ = tooltip:SetInventoryItem("player", _RANGED) + + for i=1, tooltip:NumLines() do + local text = ScanningTooltip.GetText("TextRight", i) + if text ~= nil then + -- ex. "Speed 3.2" + -- Not matching on the text part since that requires localization + local _, _, speed = string.find(text, "(%d+%.%d+)") + if speed ~= nil then + local parsed = tonumber(speed) + if parsed ~= nil then + tooltip:Hide() + return parsed + end + end + end + end + + -- Something went wrong. Maybe there's no ranged weapon equipped. + return nil + end) +end + +---@param nameEnglish string +---@return number casttime +---@return number startLatAdjusted +---@return number startLocal +---@nodiscard +local CalcCastTime = function(nameEnglish) + local meta = DB_SPELL[nameEnglish] + local baseTime = meta and meta.Time or 0 + local offset = meta and meta.Offset or 0 + + local _,_, msLatency = GetNetStats() + local startLocal = GetTime() + local startLatAdjusted = startLocal + msLatency / 1000 + + if meta.Haste == "range" then + local speedCurrent, _, _ , _, _, _ = UnitRangedDamage("player") + local speedBaseNil = calcRangedWeaponSpeedBase() + local speedBase = speedBaseNil and speedBaseNil or speedCurrent + local speedMultiplier = speedCurrent / speedBase + -- https://www.mmo-champion.com/content/2188-Patch-4-0-6-Feb-22-Hotfixes-Blue-Posts-Artworks-Comic + local casttime = (offset + baseTime * speedMultiplier) / 1000 + return casttime, startLatAdjusted, startLocal + end + + -- LuaLS doesn't support exhaustive checks? TODO investigate + local timeFallback = (meta.Time + meta.Offset) / 1000 + return timeFallback, startLatAdjusted, startLocal +end + +return { + CalcCastTime = CalcCastTime, +} + +end) +__bundle_register("Shiver/Enum.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +--- see API InventorySlot(lua://InventorySlot) +local INVENTORY_SLOT = { + Ammo = 0, + Head = 1, + Neck = 2, + Shoulder = 3, + Shirt = 4, + Chest = 5, + Belt = 6, + Legs = 7, + Feet = 8, + Wrist = 9, + Gloves = 10, + Finger1 = 11, + Finger2 = 12, + Trinket1 = 13, + Trinket2 = 14, + Back = 15, + MainHand = 16, + OffHand = 17, + Ranged = 18, + Tabard = 19, + Bag1 = 20,-- rightmost + Bag2 = 21,-- second from right + Bag3 = 22,-- third from right + Bag4 = 23,-- fourth from right + BankBag1 = 68,-- leftmost + BankBag2 = 69, + BankBag3 = 70, + BankBag4 = 71, + BankBag5 = 72, + BankBag6 = 73, + BankBag7 = 74, +} + +return { + INVENTORY_SLOT = INVENTORY_SLOT, +} + +end) +__bundle_register("Shiver/ScanningTooltip.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +--- Creates a scanning tooltip for later use +---@param name string Name of global tooltip frame +---@return GameTooltip +---@nodiscard +local createTooltip = function(name) + local tt = CreateFrame("GameTooltip", name, nil, "GameTooltipTemplate") + tt:SetScript("OnHide", function() tt:SetOwner(WorldFrame, "Center") end) + tt:Hide() + tt:SetFrameStrata("TOOLTIP") + return tt +end + +local _TOOLTIP_NAME = "QuiverScanningTooltip" +local tooltip = createTooltip(_TOOLTIP_NAME) + +---@param fsName "TextLeft" | "TextRight" +---@param lineNumber integer +---@return nil|string +local GetText = function(fsName, lineNumber) + ---@type nil|FontString + local fs = _G[_TOOLTIP_NAME .. fsName .. lineNumber] + return fs and fs:GetText() +end + +--- Handles setup and teardown when scanning. +---@generic Output +---@param f fun(t: GameTooltip): Output +---@return Output +---@nodiscard +local Scan = function(f) + tooltip:SetOwner(WorldFrame, "Center") + local output = f(tooltip) + tooltip:Hide() + return output +end + +return { + GetText = GetText, + Scan = Scan, +} + +end) +__bundle_register("Modules/BorderStyle.provider.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +---@alias BorderStyle "Simple" | "Tooltip" + +---@type (fun(x: BorderStyle): nil)[] +local callbacks = {} + +---@param moduleId string +---@param callback fun(x: BorderStyle): nil +---@return nil +local Subscribe = function(moduleId, callback) + callbacks[moduleId] = callback +end + +---@param moduleId string +local Dispose = function(moduleId) + callbacks[moduleId] = nil +end + +return { + Dispose = Dispose, + Subscribe = Subscribe, + + ---@param style BorderStyle + ---@return nil + ChangeAndPublish = function(style) + Quiver_Store.Border_Style = style + for _i, v in pairs(callbacks) do + v(style) + end + end, + + GetColor = function() return 0.6, 0.7, 0.7, 1.0 end, + + ---@return integer + ---@nodiscard + GetInsetSize = function() + return Quiver_Store.Border_Style == "Tooltip" and 3 or 1 + end, + + -- TODO Ideally, subscribing would return a provider instance that can access state. + -- However, that's going to require considerable re-architecting to support with type safety. + ---@return BorderStyle + ---@nodiscard + GetStyle = function() return Quiver_Store.Border_Style end, +} + +end) +__bundle_register("Events/Spellcast.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local Action = require("Shiver/API/Action.lua") +local Spell = require("Shiver/API/Spell.lua") +local DB_SPELL = require("Shiver/Data/Spell.lua") +local Print = require("Util/Print.lua") + +local log = function(text) + if Quiver_Store.DebugLevel == "Verbose" then + DEFAULT_CHAT_FRAME:AddMessage(text) + end +end + +-- Hooks get called even if spell didn't fire, but successful cast triggers GCD. +local lastGcdStart = 0 +local checkGCD = function() + local isTriggeredGcd, newStart = Spell.CheckNewGCD(lastGcdStart) + lastGcdStart = newStart + return isTriggeredGcd +end + +-- Castable shot event has 2 triggers: +-- 1. User starts casting Aimed Shot, Multi-Shot, or Trueshot +-- 2. User is already casting, but presses the spell again +-- It's up to the subscriber to differentiate. +---@type (fun(x: string, y: string): nil)[] +local callbacksCastableShot = {} + +---@param nameEnglish string +---@param nameLocalized string +local publishShotCastable = function(nameEnglish, nameLocalized) + for _i, v in pairs(callbacksCastableShot) do + v(nameEnglish, nameLocalized) + end +end + +local CastableShot = { + ---@param moduleId string + ---@param callback fun(x: string, y: string): nil + Subscribe = function(moduleId, callback) + callbacksCastableShot[moduleId] = callback + end, + ---@param moduleId string + Dispose = function(moduleId) + callbacksCastableShot[moduleId] = nil + end, +} + +---@type (fun(x: string, y: string): nil)[] +local callbacksInstant = {} + +---@param nameEnglish string +---@param nameLocalized string +local publishInstant = function(nameEnglish, nameLocalized) + for _i, v in pairs(callbacksInstant) do v(nameEnglish, nameLocalized) end +end +local Instant = { + ---@param moduleId string + ---@param callback fun(x: string, y: string): nil + Subscribe = function(moduleId, callback) + callbacksInstant[moduleId] = callback + end, + ---@param moduleId string + Dispose = function(moduleId) + callbacksInstant[moduleId] = nil + end, +} + +local super = { + CastSpell = CastSpell, + CastSpellByName = CastSpellByName, + UseAction = UseAction, +} + +local println = Print.PrefixedF("spellcast") + +---@param nameLocalized string +---@param isCurrentAction nil|1 +local handleCastByName = function(nameLocalized, isCurrentAction) + local nameEnglish = Quiver.L.SpellReverse[nameLocalized] + if nameEnglish == nil then + log("Localized spellname not found: "..nameLocalized) + -- TODO implement zhCN + nameEnglish = nameLocalized + else + local meta = DB_SPELL[nameEnglish] + local isCastable = not Spell.PredInstant(meta) + + -- We pre-hook the cast, so confirm we actually cast it before triggering callbacks. + -- If it's castable, then check we're casting it, else check that we triggered GCD. + if isCastable then + if isCurrentAction then + publishShotCastable(nameEnglish, nameLocalized) + elseif Action.FindBySpellName(nameLocalized) == nil then + println.Warning(nameLocalized .. " not on action bars, so can't track cast.") + end + elseif checkGCD() then + publishInstant(nameEnglish, nameLocalized) + end + end +end + +---@param spellIndex number +---@param bookType BookType +---@return nil +CastSpell = function(spellIndex, bookType) + super.CastSpell(spellIndex, bookType) + local name, _rank = GetSpellName(spellIndex, bookType) + if name ~= nil then + log("Cast as spell... " .. name) + handleCastByName(name, Action.PredSomeActionBusy()) + end +end + +-- Some spells trigger this one time when spamming, others multiple +---@param name string +---@param isSelf? boolean +---@return nil +CastSpellByName = function(name, isSelf) + super.CastSpellByName(name, isSelf) + log("Cast by name... " .. name) + handleCastByName(name, Action.PredSomeActionBusy()) +end + +-- Triggers multiple times when spamming the cast +---@param slot ActionBarSlot +---@param checkCursor? nil|0|1 +---@param onSelf? nil|0|1 +---@return nil +UseAction = function(slot, checkCursor, onSelf) + super.UseAction(slot, checkCursor, onSelf) + local texturePath = GetActionTexture(slot) + if texturePath ~= nil then + -- If we don't find a name, it means action is a macro with a custom texture. + -- The macro will call CastSpellByName, which triggers a different hook. + -- + -- If the macro uses the same texture, then both these hooks are called! + -- We *could* check macro text etc. to disambiguate, but it's okay + -- to duplicate the spell event since it won't change CD or start time. + local name, index = Spell.FindSpellByTexture(texturePath) + if name ~= nil and index ~= nil then + log("Cast as Action... " .. name) + handleCastByName(name, IsCurrentAction(slot)) + else + log("Skip Action... ") + end + end +end + +return { + CastableShot = CastableShot, + Instant = Instant, +} + +end) +__bundle_register("Util/Print.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local danger = function(text) DEFAULT_CHAT_FRAME:AddMessage(text, 1, 0, 0) end +local neutral = function(text) DEFAULT_CHAT_FRAME:AddMessage(text) end +local success = function(text) DEFAULT_CHAT_FRAME:AddMessage(text, 0, 1, 0) end +local warning = function(text) DEFAULT_CHAT_FRAME:AddMessage(text, 1, 0.6, 0) end + +local PrintLine = { + Danger = function(text) danger("Quiver -- " .. text) end, + Neutral = function(text) neutral("Quiver -- " .. text) end, + Success = function(text) success("Quiver -- " .. text) end, + Warning = function(text) warning("Quiver -- " .. text) end, + -- BigWigs suppresses raid messages unless you guarantee + -- they don't match its spam filter. Adding a space works. + -- https://github.com/CosminPOP/BigWigs/issues/2 + Raid = function(text) SendChatMessage(text.." ", "RAID") end, + Say = function(text) SendChatMessage(text, "SAY") end, +} + +local PrintPrefixedF = function(callerName) + local noNil = function(text) return text or "nil" end + local prefix = "Quiver ["..callerName.."] -- " + return { + Danger = function(text) danger(prefix..noNil(text)) end, + Neutral = function(text) neutral(prefix..noNil(text)) end, + Success = function(text) success(prefix..noNil(text)) end, + Warning = function(text) warning(prefix..noNil(text)) end, + } +end + +return { + Line = PrintLine, + PrefixedF = PrintPrefixedF, +} + +end) +__bundle_register("Shiver/API/Action.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local Spell = require("Shiver/API/Spell.lua") + +local _MAX_NUM_ACTION_SLOTS = 120 + +---@param name string +---@return nil|ActionBarSlot +---@nodiscard +local FindBySpellName = function(name) + local index = Spell.FindSpellIndex(name) + local texture = index ~= nil and GetSpellTexture(index, BOOKTYPE_SPELL) or nil + if texture ~= nil then + for i=0,_MAX_NUM_ACTION_SLOTS do + if HasAction(i) then + local isSpell = ActionHasRange(i) or GetActionText(i) == nil + local isSameTexture = GetActionTexture(i) == texture + if isSpell and isSameTexture then + return i + end + end + end + end + return nil +end + +--- Matches return type of IsCurrentAction +---@return nil|1 isBusy +---@nodiscard +local PredSomeActionBusy = function() + for i=1,_MAX_NUM_ACTION_SLOTS do + if IsCurrentAction(i) then + return 1 + end + end + return nil +end + +return { + FindBySpellName = FindBySpellName, + PredSomeActionBusy = PredSomeActionBusy, +} + +end) +__bundle_register("Events/FrameLock.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local Button = require("Component/Button.lua") + +--[[ +WoW persists positions for frames that have global names. +However, we use custom meta (size+position) logic because +otherwise each login clears all frame data for disabled addons. +TopLeft origin because GetPoint() uses TopLeft + + +Must use entire store as parameter for functions, because we reset by setting FrameMeta to null. +If we only pass FrameMeta, then several event listeners will mutate the wrong object. +]] + +local GRIP_HEIGHT = 12 +local framesMoveable = {} +local framesResizeable = {} +local openWarning + +-- Screensize scales after initializing, but when it does, the UI scale value also changes. +-- Therefore, the result of size * scale never changes, but the result of either size or scale does. +-- Disabling useUIScale doesn't affect the scale value, so we have to conditionally scale saved frame positions. +local getRealScreenWidth = function() + local scale = GetCVar("useUiScale") == 1 and UIParent:GetEffectiveScale() or 1 + return GetScreenWidth() * scale +end +local getRealScreenheight = function() + local scale = GetCVar("useUiScale") == 1 and UIParent:GetEffectiveScale() or 1 + return GetScreenHeight() * scale +end + +local defaultOf = function(val, fallback) + if val == nil then return fallback else return val end +end +local SideEffectRestoreSize = function(store, args) + local sw = getRealScreenWidth() + local sh = getRealScreenheight() + + local m = store.FrameMeta or {} + local w, h, dx, dy = args.w, args.h, args.dx, args.dy + m.W = defaultOf(m.W, w) + m.H = defaultOf(m.H, h) + m.X = defaultOf(m.X, sw / 2 + dx) + m.Y = defaultOf(m.Y, -1 * sh / 2 + dy) + store.FrameMeta = m +end + +-- Tons of users don't read the readme file AT ALL. Not even the first line! +-- We have to guide and strongly encourage them to lock the frames. +local Init = function() + openWarning = CreateFrame("Frame", nil, UIParent) + openWarning:SetFrameStrata("MEDIUM") + openWarning.Text = openWarning:CreateFontString(nil, "ARTWORK", "GameFontNormal") + openWarning.Text:SetAllPoints(openWarning) + openWarning.Text:SetJustifyH("Center") + openWarning.Text:SetJustifyV("Center") + openWarning.Text:SetText(Quiver.T["Quiver Unlocked. Show config dialog with /qq or /quiver.\nClick the lock icon when done."]) + openWarning.Text:SetTextColor(1, 1, 1) + openWarning:SetAllPoints(UIParent) + if Quiver_Store.IsLockedFrames + then openWarning:Hide() + else openWarning:Show() + end +end + +local addFrameMoveable = function(frame) + if not Quiver_Store.IsLockedFrames then + frame:EnableMouse(true) + frame:SetMovable(true) + end + table.insert(framesMoveable, frame) +end +local addFrameResizable = function(frame, handle) + frame.QuiverGripHandle = handle + if Quiver_Store.IsLockedFrames + then frame.QuiverGripHandle.Container:Hide() + else frame:SetResizable(true) + end + table.insert(framesResizeable, frame) +end + +local lockFrames = function() + openWarning:Hide() + for _k, f in framesMoveable do + f:EnableMouse(false) + f:SetMovable(false) + end + for _k, f in framesResizeable do + f.QuiverGripHandle.Container:Hide() + f:SetResizable(false) + end + for _k, v in _G.Quiver_Modules do + if Quiver_Store.ModuleEnabled[v.Id] then v.OnInterfaceLock() end + end +end +local unlockFrames = function() + openWarning:Show() + for _k, f in framesMoveable do + f:EnableMouse(true) + f:SetMovable(true) + end + for _k, f in framesResizeable do + f.QuiverGripHandle.Container:Show() + f:SetResizable(true) + end + for _k, v in _G.Quiver_Modules do + if Quiver_Store.ModuleEnabled[v.Id] then v.OnInterfaceUnlock() end + end +end + +local SetIsLocked = function(isChecked) + Quiver_Store.IsLockedFrames = isChecked + if isChecked then lockFrames() else unlockFrames() end +end + +local absClamp = function(vOpt, vMax) + local fallback = vMax / 2 + if vOpt == nil then return fallback end + + local v = math.abs(vOpt) + if v > 0 and v < vMax + then return v + else return fallback + end +end + + +---@param a number +---@return integer +local round = function(a) + return math.floor(a + 0.5) +end +---@param a number +---@return integer +local round4 = function(a) + return math.floor(a / 4 + 0.5) * 4 +end + +local SideEffectMakeMoveable = function(f, store) + f:SetWidth(store.FrameMeta.W) + f:SetHeight(store.FrameMeta.H) + f:SetMinResize(30, GRIP_HEIGHT) + local sw = getRealScreenWidth() + local sh = getRealScreenheight() + f:SetMaxResize(sw/2, sh/2) + + local xMax = sw - store.FrameMeta.W + local yMax = sh - store.FrameMeta.H + local x = absClamp(store.FrameMeta.X, xMax) + local y = -1 * absClamp(store.FrameMeta.Y, yMax) + f:SetPoint("TopLeft", nil, "TopLeft", x, y) + f:SetScript("OnMouseDown", function() + if not Quiver_Store.IsLockedFrames then f:StartMoving() end + end) + f:SetScript("OnMouseUp", function() + f:StopMovingOrSizing() + local _, _, _, x, y = f:GetPoint() + store.FrameMeta.X = round4(x) + store.FrameMeta.Y = round4(y) + f:SetPoint("TopLeft", nil, "TopLeft", store.FrameMeta.X, store.FrameMeta.Y) + end) + + addFrameMoveable(f) +end + +local SideEffectMakeResizeable = function(frame, store, args) + local margin, isCenterX, onResizeEnd, onResizeDrag = + args.GripMargin, args.IsCenterX, args.OnResizeEnd, args.OnResizeDrag + + if isCenterX then + frame:SetScript("OnSizeChanged", function() + local wOld = store.FrameMeta.W + local delta = round(frame:GetWidth() - wOld) + store.FrameMeta.W = wOld + 2 * delta + store.FrameMeta.X = store.FrameMeta.X - delta + frame:SetWidth(store.FrameMeta.W) + frame:SetPoint("TopLeft", store.FrameMeta.X, store.FrameMeta.Y) + if onResizeDrag ~= nil then onResizeDrag() end + end) + elseif onResizeDrag ~= nil then + frame:SetScript("OnSizeChanged", onResizeDrag) + end + + local handle = Button:Create(frame, QUIVER.Icon.GripHandle, nil, 0.5) + addFrameResizable(frame, handle) + handle.Container:SetFrameLevel(100)-- Should be top element + handle.Container:SetPoint("BottomRight", frame, "BottomRight", -margin, margin) + + handle.HookMouseDown = function() + if frame:IsResizable() then frame:StartSizing("BottomRight") end + end + handle.HookMouseUp = function() + frame:StopMovingOrSizing() + store.FrameMeta.W = math.floor(frame:GetWidth() + 0.5) + store.FrameMeta.H = math.floor(frame:GetHeight() + 0.5) + frame:SetWidth(store.FrameMeta.W) + frame:SetHeight(store.FrameMeta.H) + if onResizeEnd ~= nil then onResizeEnd() end + end +end + +return { + Init = Init, + SetIsLocked = SetIsLocked, + SideEffectMakeMoveable = SideEffectMakeMoveable, + SideEffectMakeResizeable = SideEffectMakeResizeable, + SideEffectRestoreSize = SideEffectRestoreSize, +} + +end) +__bundle_register("Component/Button.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local Util = require("Component/_Util.lua") +local L = require("Shiver/Lib/All.lua") +local Sugar = require("Shiver/Sugar.lua") + +local _GAP = 6 +local _SIZE = 16 + +-- see [CheckButton](lua://QqCheckButton) +-- see [Switch](lua://QqSwitch) +---@class (exact) QqButton : IMouseInteract +---@field private __index? QqButton +---@field Container Frame +---@field HookClick nil|(fun(): nil) +---@field HookMouseDown nil|(fun(): nil) +---@field HookMouseUp nil|(fun(): nil) +---@field Icon Frame +---@field Label? FontString +---@field TooltipText? string +---@field Texture Texture +---@field private isEnabled boolean +---@field private isHover boolean +---@field private isMouseDown boolean +local QqButton = {} + +function QqButton:resetTexture() + local r, g, b = Util.SelectColor(self) + self.Texture:SetVertexColor(r, g, b) + if self.Label ~= nil then + self.Label:SetTextColor(r, g, b) + end +end + +function QqButton:OnHoverStart() + self.isHover = true + self:resetTexture() + Util.ToggleTooltip(self, self.Container, self.TooltipText) +end + +function QqButton:OnHoverEnd() + self.isHover = false + self:resetTexture() + Util.ToggleTooltip(self, self.Container, self.TooltipText) +end + +function QqButton:OnMouseDown() + self.isMouseDown = true + if self.HookMouseDown ~= nil then self.HookMouseDown() end + self:resetTexture() +end + +function QqButton:OnMouseUp() + self.isMouseDown = false + if self.HookMouseUp ~= nil then self.HookMouseUp() end + if MouseIsOver(self.Container) == 1 and self.HookClick ~= nil then + self.HookClick() + end + self:resetTexture() +end + +---@param isEnabled boolean +function QqButton:ToggleEnabled(isEnabled) + self.isEnabled = isEnabled + self:resetTexture() +end + +---@param isHover boolean +function QqButton:ToggleHover(isHover) + self.isHover = isHover + self:resetTexture() +end + + +---@param parent Frame +---@param texPath string +---@param labelText? string +---@param scale? number +---@return QqButton +function QqButton:Create(parent, texPath, labelText, scale) + local container = CreateFrame("Frame", nil, parent, nil) + local icon = CreateFrame("Frame", nil, container, nil) + + ---@type QqButton + local r = { + Container = container, + Icon = icon, + Texture = icon:CreateTexture(nil, "OVERLAY"), + isEnabled = true, + isHover = false, + isMouseDown = false, + } + setmetatable(r, self) + self.__index = self + + container:SetScript("OnEnter", function() r:OnHoverStart() end) + container:SetScript("OnLeave", function() r:OnHoverEnd() end) + container:SetScript("OnMouseDown", function() r:OnMouseDown() end) + container:SetScript("OnMouseUp", function() r:OnMouseUp() end) + container:EnableMouse(true) + + r.Texture:SetAllPoints(r.Icon) + local scaleOr = scale and scale or 1.0 + r.Icon:SetWidth(_SIZE * scaleOr) + r.Icon:SetHeight(_SIZE * scaleOr) + r.Texture:SetTexture(texPath) + + r.Icon:SetPoint("Left", container, "Left", 0, 0) + local h, w = 0, 0 + if labelText then + r.Label = container:CreateFontString(nil, "BACKGROUND", "GameFontNormal") + r.Label:SetText(labelText) + r.Label:SetPoint("Right", container, "Right", 0, 0) + h = L.Psi(L.Max, Sugar.Region._GetHeight, r.Icon, r.Label) + w = L.Psi(L.Add, Sugar.Region._GetWidth, r.Icon, r.Label) + _GAP + else + h = r.Icon:GetHeight() + w = r.Icon:GetWidth() + end + container:SetHeight(h) + container:SetWidth(w) + + r:resetTexture() + return r +end + +return QqButton + +end) +__bundle_register("Shiver/Sugar.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local Region = { + --- @type fun(r: Region): number + _GetHeight = function(r) return r:GetHeight() end, + + --- @type fun(r: Region): number + _GetWidth = function(r) return r:GetWidth() end, +} + +return { + Region = Region, +} + +end) +__bundle_register("Shiver/Lib/All.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +-- Reference library: +-- https://github.com/codereport/blackbird/blob/main/combinators.hpp +local Array = require("Shiver/Lib/Array.lua") +local Op = require("Shiver/Lib/Operator.lua") + +-- ************ Combinators ************ +--- (>>), forward function composition +---@generic A +---@generic B +---@generic C +---@param f fun(a: A): B +---@param g fun(y: B): C +---@return fun(x: A): C +local Forward = function(f, g) + return function(a) + return g(f(a)) + end +end + +-- No support yet for generic overloads +-- https://github.com/LuaLS/lua-language-server/issues/723 +---@generic A +---@generic B +---@generic C +--@generic D +--@generic E +---@type fun(a: A, f: (fun(a: A): B), g: (fun(b: B): C)): C +--@overload fun(a: A, f: (fun(a: A): B), g: (fun(b: B): C), h: (fun(c: C): D)): D +--@overload fun(a: A, f: (fun(a: A): B), g: (fun(b: B): C), h: (fun(c: C): D), i: (fun(d: D): E)): D +local Pipe = function(a, ...) + local out = a + for _, fn in ipairs(arg) do + out = fn(out) + end + return out +end + +-- No support yet for generic overloads +-- https://github.com/LuaLS/lua-language-server/issues/723 +---@generic A +---@generic B +---@generic C +---@generic D +---@type fun(a: A, f: (fun(a: A): B), g: (fun(b: B): C), h: (fun(c: C): D)): D +local Pipe3 = Pipe + +-- No support yet for generic overloads +-- https://github.com/LuaLS/lua-language-server/issues/723 +---@generic A +---@generic B +---@generic C +---@generic D +---@generic E +---@type fun(a: A, f: (fun(a: A): B), g: (fun(b: B): C), h: (fun(c: C): D), i: (fun(d: D): E)): D +local Pipe4 = Pipe + +--- f(g(x), (y)) +---@generic A +---@generic B +---@generic C +---@type fun(f: (fun(x: B, y: B): C), g: (fun(x: A): B), x: A, y: A): C +local Psi = function(f, g, x, y) + return f(g(x), g(y)) +end + +return { + Array = Array, + -- Combinators + Fw = Forward, + Pipe = Pipe, + Pipe3 = Pipe3, + Pipe4 = Pipe4, + Psi = Psi, + -- Binary / Unary + Add = Op.Add, + Max = Op.Max, + -- Comparison + Lt = Op.Lt, + Le = Op.Le, + Eq = Op.Eq, + Ne = Op.Ne, + Ge = Op.Ge, + Gt = Op.Gt, + -- Logic + And = Op.And, + Or = Op.Or, +} + +end) +__bundle_register("Shiver/Lib/Operator.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +return { + -- ************ Binary / Unary ************ + ---@type fun(a: number, b: number): number + Add = function(a, b) return a + b end, + + ---@type fun(a: number, b: number): number + Max = function(a, b) return math.max(a, b) end, + + -- ************ Comparison ************ + ---@generic A + ---@type fun(a: A, b: A): boolean + Lt = function(a, b) return a < b end, + ---@generic A + ---@type fun(a: A, b: A): boolean + Le = function(a, b) return a <= b end, + ---@generic A + ---@type fun(a: A, b: A): boolean + Eq = function(a, b) return a == b end, + ---@generic A + ---@type fun(a: A, b: A): boolean + Ne = function(a, b) return a ~= b end, + ---@generic A + ---@type fun(a: A, b: A): boolean + Ge = function(a, b) return a >= b end, + ---@generic A + ---@type fun(a: A, b: A): boolean + Gt = function(a, b) return a > b end, + + -- ************ Logic ************ + ---@type fun(a: boolean, b: boolean): boolean + And = function(a, b) return a and b end, + + ---@type fun(a: boolean, b: boolean): boolean + Or = function(a, b) return a or b end, +} + +end) +__bundle_register("Shiver/Lib/Array.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +---@generic A +---@param xs A[] +---@param f fun(x: A): boolean +---@return boolean +local Every = function(xs, f) + for _k, v in ipairs(xs) do + if not f(v) then return false end + end + return true +end + +---@generic A +---@param xs A[] +---@param f fun(x: A): boolean +---@return nil|A +local Find = function(xs, f) + for _k, v in ipairs(xs) do + if f(v) then + return v + end + end + return nil +end + +---ϴ(N) +---@generic A +---@param xs A[] +---@return integer +local Length = function(xs) + local l = 0 + for _k, _v in ipairs(xs) do l = l + 1 end + return l +end + +---@generic A +---@generic B +---@param xs A[] +---@param f fun(x: A): B +---@return B[] +local Map = function(xs, f) + local ys = {} + for _k, v in ipairs(xs) do + table.insert(ys, f(v)) + end + return ys +end + +---@generic A +---@generic B +---@param xs A[] +---@param f fun(x: A, i: integer): B +---@return B[] +local Mapi = function(xs, f) + local ys = {} + local i = 0 + for _k, v in ipairs(xs) do + table.insert(ys, f(v, i)) + i = i + 1 + end + return ys +end + +--- ϴ(1) memory allocation
+--- ϴ(N) runtime complexity +---@generic A +---@generic B +---@param xs A[] +---@param f fun(a: A): B +---@param reducer fun(b1: B, b2: B): B +---@param identity B +---@return B +local MapReduce = function(xs, f, reducer, identity) + local zRef = identity + for _k, x in ipairs(xs) do + zRef = reducer(f(x), zRef) + end + return zRef +end + +---@generic A +---@param xs A[] +---@param f fun(x: A): boolean +---@return boolean +local Some = function(xs, f) + for _k, v in ipairs(xs) do + if f(v) then return true end + end + return false +end + +---@param xs number[] +---@return number +local Sum = function(xs) + local total = 0 + for _k, v in ipairs(xs) do + total = total + v + end + return total +end + +--- ϴ(1) memory allocation
+--- ϴ(N) runtime complexity +---@generic A +---@generic B +---@param xs A[] +---@param reducer fun(b1: B, b2: B): B +---@param identity B +---@return B +local Reduce = function(xs, reducer, identity) + local zRef = identity + for _k, x in ipairs(xs) do + zRef = reducer(x, zRef) + end + return zRef +end + +---@generic A +---@generic B +---@param as A[] +---@param bs B[] +---@return [A,B][] +local Zip2 = function(as, bs) + local zipped = {} + local l1, l2 = Length(as), Length(bs) + if l1 ~= l2 then + DEFAULT_CHAT_FRAME:AddMessage("Warning -- Called Zip2 on arrays of unequal length.", 1.0, 0.5, 0) + DEFAULT_CHAT_FRAME:AddMessage(l1 .. " <> " .. l2, 1.0, 0, 0) + end + local length = math.min(l1, l2) + for i=1, length do + zipped[i] = { as[i], bs[i] } + end + return zipped +end + +return { + Every=Every, + Find=Find, + Length=Length, + Map=Map, + Mapi=Mapi, + MapReduce=MapReduce, + Some=Some, + Sum=Sum, + Reduce=Reduce, + Zip2=Zip2, +} + +end) +__bundle_register("Component/_Util.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local Color = require("Shiver/Color.lua") +local Widget = require("Shiver/Widget.lua") + +---@class IMouseInteract +---@field isEnabled boolean +---@field isHover boolean +---@field isMouseDown boolean + +local _COLOR_NORMAL = Color:Lift({ 1.0, 0.82, 0.0 }) +local _COLOR_HOVER = Color:Lift({ 1.0, 0.6, 0.0 }) +local _COLOR_MOUSEDOWN = Color:Lift({ 1.0, 0.3, 0.0 }) +local _COLOR_DISABLE = Color:Lift({ 0.3, 0.3, 0.3 }) + +---@param self IMouseInteract +---@return number, number, number +local SelectColor = function(self) + if not self.isEnabled then + return _COLOR_DISABLE:Rgb() + elseif self.isMouseDown then + return _COLOR_MOUSEDOWN:Rgb() + elseif self.isHover then + return _COLOR_HOVER:Rgb() + else + return _COLOR_NORMAL:Rgb() + end +end + +---@param self IMouseInteract +---@param frame Frame +---@param text nil|string +local ToggleTooltip = function(self, frame, text) + if text ~= nil then + if self.isHover then + Widget.PositionTooltip(frame) + GameTooltip:AddLine(text, nil, nil, nil, 1) + GameTooltip:Show() + else + GameTooltip:Hide() + GameTooltip:ClearLines() + end + end +end + +return { + SelectColor = SelectColor, + ToggleTooltip = ToggleTooltip, +} + +end) +__bundle_register("Shiver/Widget.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +---@param anchor Frame +---@return FrameAnchor +---@nodiscard +local calcBestAnchorSide = function(anchor) + local screenW = GetScreenWidth() + local center = screenW / 2.0 + + -- TODO library coalesce + local leftNil = anchor:GetLeft() + local rightNil = anchor:GetRight() + local left = leftNil and leftNil or 0 + local right = rightNil and rightNil or screenW + + -- TODO library psi combinator + local dLeft = math.abs(center - left) + local dRight = math.abs(center - right) + return dLeft < dRight and "ANCHOR_BOTTOMRIGHT" or "ANCHOR_BOTTOMLEFT" +end + +-- TODO figure out how to let caller specify preferred side, then +-- flip if there isn't enough room for tooltip. This is hard because +-- we don't know how big the tooltip is until after rendering it. +---@param anchor Frame +---@param x? number +---@param y? number +---@return nil +local PositionTooltip = function(anchor, x, y) + local anchorSide = calcBestAnchorSide(anchor) + -- TODO library coalesce + local xx = (x and x or 0) + local yy = (y and y or 0) + anchor:GetHeight() + GameTooltip:SetOwner(anchor, anchorSide, xx, yy) +end + +return { + PositionTooltip = PositionTooltip, +} + +end) +__bundle_register("Shiver/Color.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +---@alias Rgb [number, number, number] + +---@class Color +---@field private __index? Color +---@field private cache Rgb +---@field private default Rgb +local Color = {} + +---@param store Rgb +---@return Color +function Color:Lift(store) + local default = { store[1], store[2], store[3] } + ---@type Color + local o = { cache=store, default=default } + setmetatable(o, self) + self.__index = self + return o +end + +---@param store Rgb +---@param default Rgb +---@return Color +function Color:LiftReset(store, default) + ---@type Color + local o = { cache=store, default=default } + setmetatable(o, self) + self.__index = self + return o +end + +function Color:Reset() + self.cache[1] = self.default[1] + self.cache[2] = self.default[2] + self.cache[3] = self.default[3] +end + +---@return number, number, number +---@nodiscard +function Color:Rgb() + local c = self.cache + return c[1], c[2], c[3] +end + +---@return [number, number, number] +---@nodiscard +function Color:RgbArray() + local c = self.cache + return { c[1], c[2], c[3] } +end + +function Color:R() return self.cache[1] end +function Color:G() return self.cache[2] end +function Color:B() return self.cache[3] end + +---@param r number 0 to 1 +---@param g number 0 to 1 +---@param b number 0 to 1 +function Color:SetRgb(r, g, b) + self.cache[1] = r + self.cache[2] = g + self.cache[3] = b +end + +return Color + +end) +__bundle_register("Modules/UpdateNotifier.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local Version = require("Util/Version.lua") + +-- This file based on pfUI's updatenotify.lua +-- Copyright (c) 2016-2021 Eric Mauser (Shagu) +-- Copyright (c) 2022 SabineWren +local hasNotified = false +local CURRENT = Version:ParseThrows(GetAddOnMetadata("Quiver", "Version")) + +local broadcast = (function() + local channelsLogin = { "Battleground", "Raid", "guild" } + local channelsPlayerGroup = { "Battleground", "Raid" } + local send = function(channels) + for _k, chan in channels do + SendAddonMessage("Quiver", "VERSION:"..CURRENT.Text, chan) + end + end + return { + Group = function() send(channelsPlayerGroup) end, + Login = function() send(channelsLogin) end, + } +end)() + +local checkGroupGrew = (function() + local lastSize = 0 + return function() + local sizeRaid = GetNumRaidMembers() + local sizeParty = GetNumPartyMembers() + local sizeGroup = sizeRaid > 0 and sizeRaid + or sizeParty > 0 and sizeParty + or 0 + local isLarger = sizeGroup > lastSize + lastSize = sizeGroup + return isLarger + end +end)() + +--- @type Event[] +local EVENTS = { + "CHAT_MSG_ADDON", + "PARTY_MEMBERS_CHANGED", + "PLAYER_ENTERING_WORLD", +} +local handleEvent = function() + if event == "CHAT_MSG_ADDON" and arg1 == "Quiver" then + local _, _, versionText = string.find(arg2, "VERSION:(.*)") + if versionText ~= nil + and CURRENT:PredNewer(versionText) + and not hasNotified + then + local URL = "https://github.com/SabineWren/Quiver" + local m1 = Quiver.T["New version %s available at %s"] + local m2 = Quiver.T["It's always safe to upgrade Quiver. You won't lose any of your configuration."] + local text = string.format(m1, versionText, URL) + DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00Quiver|r - "..text) + DEFAULT_CHAT_FRAME:AddMessage("|cffdddddd"..m2) + hasNotified = true + end + elseif event == "PARTY_MEMBERS_CHANGED" then + if checkGroupGrew() then broadcast.Group() end + elseif event == "PLAYER_ENTERING_WORLD" then + broadcast.Login() + end +end + +-- ************ Initialization ************ +return function() + local frame = CreateFrame("Frame", nil) + frame:SetScript("OnEvent", handleEvent) + -- We don't need to unsubscribe, as we never disable the update notifier + for _k, e in EVENTS do frame:RegisterEvent(e) end +end + +end) +__bundle_register("Util/Version.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +---@class Version +---@field private __index? Version +---@field private breaking integer +---@field private feature integer +---@field private fix integer +---@field Text string +local Version = {} + +---@param text string +---@return Version +---@nodiscard +function Version:ParseThrows(text) + if text == nil then + error("Nil version string") + elseif string.len(text) == 0 then + error("Empty version string") + else + local _, _, a, b, c = string.find(text, "(%d+)%.(%d+)%.(%d+)") + local x, y, z = tonumber(a), tonumber(b), tonumber(c) + if x == nil or y == nil or z == nil then + error("Invalid version string: "..text) + else + ---@type Version + local r = { + breaking = x, + feature = y, + fix = z, + Text = text, + } + setmetatable(r, self) + self.__index = self + return r + end + end +end + +---@param text string +---@return boolean +---@nodiscard +function Version:PredNewer(text) + local a = self + local b = Version:ParseThrows(text) + return b.breaking > a.breaking + or b.breaking == a.breaking and b.feature > a.feature + or b.breaking == a.breaking and b.feature == a.feature and b.fix > a.fix +end + +return Version + +end) +__bundle_register("Modules/TrueshotAuraAlarm.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local FrameLock = require("Events/FrameLock.lua") +local Spellcast = require("Events/Spellcast.lua") +local Spell = require("Shiver/API/Spell.lua") +local Aura = require("Util/Aura.lua") + +local MODULE_ID = "TrueshotAuraAlarm" +local store = nil +local frame = nil + +local UPDATE_DELAY_SLOW = 5 +local UPDATE_DELAY_FAST = 0.1 +local updateDelay = UPDATE_DELAY_SLOW + +local INSET = 4 +local DEFAULT_ICON_SIZE = 40 +local MINUTES_LEFT_WARNING = 5 + +-- ************ State ************ +local aura = (function() + local knowsAura, isActive, lastUpdate, timeLeft = false, false, 1800, 0 + return { + ShouldUpdate = function(elapsed) + lastUpdate = lastUpdate + elapsed + return knowsAura and lastUpdate > updateDelay + end, + UpdateUI = function() + knowsAura = Spell.PredSpellLearned(Quiver.L.Spell["Trueshot Aura"]) + or not Quiver_Store.IsLockedFrames + isActive, timeLeft = Aura.GetIsActiveAndTimeLeftByTexture(QUIVER.Icon.Trueshot) + lastUpdate = 0 + + if not Quiver_Store.IsLockedFrames or knowsAura and not isActive then + frame.Icon:SetAlpha(0.75) + frame:SetBackdropBorderColor(1, 0, 0, 0.8) + elseif knowsAura and isActive and timeLeft > 0 and timeLeft < MINUTES_LEFT_WARNING * 60 then + frame.Icon:SetAlpha(0.4) + frame:SetBackdropBorderColor(0, 0, 0, 0.1) + else + updateDelay = UPDATE_DELAY_SLOW + frame.Icon:SetAlpha(0.0) + frame:SetBackdropBorderColor(0, 0, 0, 0) + end + end, + } +end)() + +-- ************ UI ************ +local setFramePosition = function(f, s) + FrameLock.SideEffectRestoreSize(s, { + w=DEFAULT_ICON_SIZE, h=DEFAULT_ICON_SIZE, dx=150, dy=40, + }) + f:SetWidth(s.FrameMeta.W) + f:SetHeight(s.FrameMeta.H) + f:SetPoint("TopLeft", s.FrameMeta.X, s.FrameMeta.Y) +end + +local createUI = function() + local f = CreateFrame("Frame", nil, UIParent) + f:SetFrameStrata("LOW") + f:SetBackdrop({ + edgeFile = "Interface/Tooltips/UI-Tooltip-Border", + edgeSize = 16, + insets = { left=INSET, right=INSET, top=INSET, bottom=INSET }, + }) + setFramePosition(f, store) + + f.Icon = CreateFrame("Frame", nil, f) + f.Icon:SetBackdrop({ bgFile = QUIVER.Icon.Trueshot, tile = false }) + f.Icon:SetPoint("Left", f, "Left", INSET, 0) + f.Icon:SetPoint("Right", f, "Right", -INSET, 0) + f.Icon:SetPoint("Top", f, "Top", 0, -INSET) + f.Icon:SetPoint("Bottom", f, "Bottom", 0, INSET) + + FrameLock.SideEffectMakeMoveable(f, store) + FrameLock.SideEffectMakeResizeable(f, store, { GripMargin=0 }) + return f +end + +-- ************ Event Handlers ************ +--- @type Event[] +local EVENTS = { + "PLAYER_AURAS_CHANGED", + "SPELLS_CHANGED",-- Open or click thru spellbook, learn/unlearn spell +} +local handleEvent = function() + if event == "SPELLS_CHANGED" and arg1 ~= "LeftButton" + or event == "PLAYER_AURAS_CHANGED" + then + aura.UpdateUI() + end +end + +-- ************ Initialization ************ +local onEnable = function() + if frame == nil then frame = createUI() end + frame:SetScript("OnEvent", handleEvent) + frame:SetScript("OnUpdate", function() + if aura.ShouldUpdate(arg1) then aura.UpdateUI() end + end) + for _k, e in EVENTS do frame:RegisterEvent(e) end + frame:Show() + aura.UpdateUI() + Spellcast.Instant.Subscribe(MODULE_ID, function(spellName) + if spellName == Quiver.L.Spell["Trueshot Aura"] then + -- Buffs don't update right away, but we want fast user feedback + updateDelay = UPDATE_DELAY_FAST + end + end) +end +local onDisable = function() + Spellcast.Instant.Dispose(MODULE_ID) + frame:Hide() + for _k, e in EVENTS do frame:UnregisterEvent(e) end +end + +---@type QqModule +return { + Id = MODULE_ID, + GetName = function() return Quiver.T["Trueshot Aura Alarm"] end, + GetTooltipText = function() return nil end, + OnEnable = onEnable, + OnDisable = onDisable, + OnInterfaceLock = function() aura.UpdateUI() end, + OnInterfaceUnlock = function() aura.UpdateUI() end, + OnResetFrames = function() + store.FrameMeta = nil + if frame then setFramePosition(frame, store) end + end, + OnSavedVariablesRestore = function(savedVariables) + store = savedVariables + end, + OnSavedVariablesPersist = function() return store end, +} + +end) +__bundle_register("Util/Aura.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local ScanningTooltip = require("Shiver/ScanningTooltip.lua") + +-- This doesn't work for duplicate textures (ex. cheetah + zg mount). +-- For those you have to scan by name using the GameTooltip. +local GetIsActiveAndTimeLeftByTexture = function(targetTexture) + -- This seems to check debuffs as well (tested with deserter) + local maxIndex = QUIVER.Aura_Cap - 1 + for i=0, maxIndex do + local texture = GetPlayerBuffTexture(i) + if texture == targetTexture then + local timeLeft = GetPlayerBuffTimeLeft(i) + return true, timeLeft + end + end + return false, 0 +end + +---@param buffname string +---@nodiscard +local PredBuffActive = function(buffname) + return ScanningTooltip.Scan(function(tooltip) + for i=0, QUIVER.Buff_Cap do + local buffIndex, _untilCancelled = GetPlayerBuff(i, "HELPFUL|PASSIVE") + if buffIndex >= 0 then + tooltip:ClearLines() + tooltip:SetPlayerBuff(buffIndex) + if ScanningTooltip.GetText("TextLeft", 1) == buffname then + return true + end + end + end + return false + end) +end + + +-- This works great. Don't delete because I'm sure it will be useful in the future. +--[[ +local PredIsBuffActiveTimeLeftByName = function(buffname) + local tooltip = resetTooltip() + for i=0,QUIVER.Buff_Cap do + local buffIndex, _untilCancelled = GetPlayerBuff(i, "HELPFUL|PASSIVE") + if buffIndex >= 0 then + tooltip:ClearLines() + tooltip:SetPlayerBuff(buffIndex) + local fs1 = _G["QuiverAuraScanningTooltipTextLeft1"] + local fs3 = _G["QuiverAuraScanningTooltipTextLeft3"] + + local auraTimeLeft = fs3 and fs3:GetText() or "" + local _, _, strHours = string.find(auraTimeLeft, "(.*) hours remaining") + local _, _, strMinutes = string.find(auraTimeLeft, "(.*) minutes remaining") + local _, _, strSeconds = string.find(auraTimeLeft, "(.*) seconds remaining") + local hours = tonumber(strHours) or 0 + local minutes = tonumber(strMinutes) or 0 + local seconds = tonumber(strSeconds) or 0 + local secondsLeft = seconds + 60 * (minutes + 60 * hours) + + if fs1 and fs1:GetText() == buffname then + return true, secondsLeft + end + end + end + return false, 0 +end +]] + +return { + GetIsActiveAndTimeLeftByTexture = GetIsActiveAndTimeLeftByTexture, + PredBuffActive = PredBuffActive, +} + +end) +__bundle_register("Modules/TranqAnnouncer.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local FrameLock = require("Events/FrameLock.lua") +local BorderStyle = require("Modules/BorderStyle.provider.lua") +local Spell = require("Shiver/API/Spell.lua") +local L = require("Shiver/Lib/All.lua") +local Print = require("Util/Print.lua") + +local MODULE_ID = "TranqAnnouncer" +local store = nil +local frame = nil +local TRANQ_CD_SEC = 20 +local INSET = 4 +local BORDER_BAR = 1 +local HEIGHT_BAR = 17 +local WIDTH_FRAME_DEFAULT = 120 + +local message = (function() + local ADDON_MESSAGE_CAST = "Quiver_Tranq_Shot" + local MATCH = ADDON_MESSAGE_CAST..":(.*):(.*)" + return { + Broadcast = function() + local playerName = UnitName("player") + local _,_, msLatency = GetNetStats() + local serialized = ADDON_MESSAGE_CAST..":"..playerName..":"..msLatency + SendAddonMessage("Quiver", serialized, "Raid") + end, + Deserialize = function(msg) + local _, _, nameCaster, latencyOrZero = string.find(msg, MATCH) + local msLatencyCaster = latencyOrZero and latencyOrZero or 0 + -- Game client updates latency every 30 seconds, so it's unlikely + -- to break deterministic ordering, but could happen in rare cases. + -- Might consider a logical clock or something in the future. + local _,_, msLatency = GetNetStats() + local timeCastSec = GetTime() - (msLatency + msLatencyCaster) / 1000 + return nameCaster, timeCastSec + end, + } +end)() + +local getColorForeground = (function() + -- It would be expensive to compute non-rgb gradients in Lua during the update loop, + -- so we design stop points using an online gradient generator and convert them to RGB. + -- lch(52% 100 40) to lch(52% 100 141) + -- https://non-boring-gradients.netlify.app/ + local NUM_COLORS = 17 + local COLOR_FG = { + { 0.95, 0.05, 0.05 }, + { 0.91, 0.19, 0.0 }, + { 0.79, 0.35, 0.08 }, + { 0.75, 0.38, 0.02 }, + { 0.72, 0.40, 0.0 }, + { 0.68, 0.43, 0.0 }, + { 0.64, 0.45, 0.0 }, + { 0.60, 0.46, 0.0 }, + { 0.56, 0.48, 0.0 }, + { 0.52, 0.49, 0.0 }, + { 0.48, 0.51, 0.0 }, + { 0.44, 0.52, 0.04 }, + { 0.40, 0.53, 0.10 }, + { 0.29, 0.55, 0.0 }, + { 0.23, 0.55, 0.0 }, + { 0.15, 0.56, 0.11 }, + { 0.00, 0.56, 0.18 }, + } + return function(progress) + -- Fixes floating point bugs + local p = progress <= 0.0 and 0.001 + or progress >= 1.0 and 0.999 + or progress + local i = math.ceil(p * NUM_COLORS) + return unpack(COLOR_FG[i]) + end +end)() + +local createProgressBar = function() + local MARGIN_TEXT = 4 + local bar = CreateFrame("Frame") + bar:SetBackdrop({ + bgFile = "Interface/BUTTONS/WHITE8X8", + edgeFile = "Interface/BUTTONS/WHITE8X8", + edgeSize = 1, + tile = false, + }) + bar:SetBackdropBorderColor(0, 0, 0, 0.6) + + local centerVertically = function(ele) + ele:SetPoint("Top", bar, "Top", 0, -BORDER_BAR) + ele:SetPoint("Bottom", bar, "Bottom", 0, BORDER_BAR) + end + + bar.ProgressFrame = CreateFrame("Frame", nil, bar) + centerVertically(bar.ProgressFrame) + bar.ProgressFrame:SetPoint("Left", bar, "Left", BORDER_BAR, 0) + bar.ProgressFrame:SetBackdrop({ + bgFile = "Interface/BUTTONS/WHITE8X8", tile = false, + }) + + bar.FsPlayerName = bar.ProgressFrame:CreateFontString(nil, "ARTWORK", "GameFontNormal") + centerVertically(bar.FsPlayerName) + bar.FsPlayerName:SetPoint("Left", bar, "Left", MARGIN_TEXT, 0) + bar.FsPlayerName:SetJustifyH("Left") + bar.FsPlayerName:SetJustifyV("Center") + bar.FsPlayerName:SetTextColor(1, 1, 1) + + bar.FsCdTimer = bar.ProgressFrame:CreateFontString(nil, "ARTWORK", "GameFontNormal") + centerVertically(bar.FsCdTimer) + bar.FsCdTimer:SetPoint("Right", bar, "Right", -MARGIN_TEXT, 0) + bar.FsCdTimer:SetJustifyH("Right") + bar.FsPlayerName:SetJustifyV("Center") + bar.FsCdTimer:SetTextColor(1, 1, 1) + + return bar +end + +local poolProgressBar = (function() + local fs = {} + return { + Acquire = function(parent) + local bar = table.remove(fs) or createProgressBar() + bar:SetParent(parent) + -- Clearing parent on release has side effects: hides frame and change stratas + bar:SetFrameStrata("LOW") + bar.ProgressFrame:SetFrameStrata("Medium") + bar:Show() + return bar + end, + Release = function(bar) + bar:SetParent(nil) + bar:ClearAllPoints() + table.insert(fs, bar) + end, + } +end)() + +local getIdealFrameHeight = function() + local height = 0 + for _i, bar in frame.Bars do + height = height + bar:GetHeight() + end + -- Make space for at least 1 bar when UI unlocked + if height == 0 then height = HEIGHT_BAR end + return height + 2 * INSET +end + +local adjustBarYOffsets = function() + local height = 0 + for _i, bar in frame.Bars do + bar:SetPoint("Left", frame, "Left", INSET, 0) + bar:SetPoint("Right", frame, "Right", -INSET, 0) + bar:SetPoint("Top", frame, "Top", 0, -height - INSET) + height = height + bar:GetHeight() + end +end + +local setFramePosition = function(f, s) + local height = getIdealFrameHeight() + FrameLock.SideEffectRestoreSize(s, { + w=WIDTH_FRAME_DEFAULT, h=height, dx=110, dy=150, + }) + f:SetWidth(s.FrameMeta.W) + f:SetHeight(s.FrameMeta.H) + f:SetPoint("TopLeft", s.FrameMeta.X, s.FrameMeta.Y) +end + +local createUI = function() + frame = CreateFrame("Frame", nil, UIParent) + frame.Bars = {} + + frame:SetFrameStrata("LOW") + frame:SetBackdrop({ + edgeFile = "Interface/Tooltips/UI-Tooltip-Border", + edgeSize = 16, + insets = { left=INSET, right=INSET, top=INSET, bottom=INSET }, + }) + frame:SetBackdropBorderColor(BorderStyle.GetColor()) + + setFramePosition(frame, store) + FrameLock.SideEffectMakeMoveable(frame, store) + FrameLock.SideEffectMakeResizeable(frame, store, { GripMargin=4 }) + return frame +end + +-- ************ Frame Update Handlers ************ +local getCanHide = function() + local now = GetTime() + local getIsFinished = function(v) + local secElapsed = now - v.TimeCastSec + return secElapsed >= TRANQ_CD_SEC + end + return not UnitAffectingCombat('player') + and L.Array.Every(frame.Bars, getIsFinished) + and Quiver_Store.IsLockedFrames +end + +local hideFrameDeleteBars = function() + frame:Hide() + for _k, bar in frame.Bars do + poolProgressBar.Release(bar) + end + frame.Bars = {} +end + +local handleUpdate = function() + if getCanHide() then hideFrameDeleteBars() end + -- Animate Progress Bars + local now = GetTime() + for _k, bar in frame.Bars do + local secElapsed = now - bar.TimeCastSec + local secProgress = secElapsed > TRANQ_CD_SEC and TRANQ_CD_SEC or secElapsed + local percentProgress = secProgress / TRANQ_CD_SEC + local width = (bar:GetWidth() - 2 * BORDER_BAR) * percentProgress + bar.ProgressFrame:SetWidth(width > 1 and width or 1) + bar.FsCdTimer:SetText(string.format("%.1f / %.0f", secProgress, TRANQ_CD_SEC)) + + local r, g, b = getColorForeground(percentProgress) + -- RGB scaling doesn't change brightness equally for all colors, + -- so we may need to make a separate gradient for bg + local s = 0.7 + bar:SetBackdropColor(r*s, g*s, b*s, 0.8) + bar.ProgressFrame:SetBackdropColor(r, g, b, 0.9) + end +end + +-- ************ Event Handlers ************ +local handleMsg = function(_source, msg) + -- For compatibility with other tranq addons, ignore the message source. + local nameCaster, timeCastSec = message.Deserialize(msg) + if nameCaster ~= nil then + local barVisible = L.Array.Find(frame.Bars, function(bar) + return bar.FsPlayerName:GetText() == nameCaster + end) + + if barVisible then + barVisible.TimeCastSec = timeCastSec + else + local barNew = poolProgressBar.Acquire(frame) + barNew.TimeCastSec = timeCastSec + barNew:SetHeight(HEIGHT_BAR) + barNew.FsPlayerName:SetText(nameCaster) + table.insert(frame.Bars, barNew) + end + + table.sort(frame.Bars, function(a,b) return a.TimeCastSec < b.TimeCastSec end) + adjustBarYOffsets() + frame:SetHeight(getIdealFrameHeight()) + frame:Show() + end +end + +--- @type Event[] +local EVENTS = { + "CHAT_MSG_ADDON",-- Also works with macros + "CHAT_MSG_SPELL_SELF_DAMAGE",-- Detect misses + "SPELL_UPDATE_COOLDOWN", +} +local lastCastStart = 0 +local getHasFiredTranq = function() + local isCast, cdStart = Spell.CheckNewCd( + TRANQ_CD_SEC, lastCastStart, Quiver.L.Spell["Tranquilizing Shot"]) + lastCastStart = cdStart + return isCast +end +local handleEvent = function() + if event == "CHAT_MSG_ADDON" then + handleMsg(arg1, arg2) + elseif event == "CHAT_MSG_SPELL_SELF_DAMAGE" then + if string.find(arg1, Quiver.L.CombatLog.Tranq.Miss) + or string.find(arg1, Quiver.L.CombatLog.Tranq.Resist) + or string.find(arg1, Quiver.L.CombatLog.Tranq.Fail) + then + Print.Line.Say(store.MsgTranqMiss) + Print.Line.Raid(store.MsgTranqMiss) + end + elseif event == "SPELL_UPDATE_COOLDOWN" then + if getHasFiredTranq() then + message.Broadcast() + if store.TranqChannel == "/Say" then + Print.Line.Say(store.MsgTranqCast) + elseif store.TranqChannel == "/Raid" then + Print.Line.Raid(store.MsgTranqCast) + -- else don't announce + end + end + end +end + +local onEnable = function() + if frame == nil then frame = createUI() end + frame:SetScript("OnEvent", handleEvent) + frame:SetScript("OnUpdate", handleUpdate) + for _k, e in EVENTS do frame:RegisterEvent(e) end + if getCanHide() then hideFrameDeleteBars() else frame:Show() end +end +local onDisable = function() + frame:Hide() + for _k, e in EVENTS do frame:UnregisterEvent(e) end +end + +---@type QqModule +return { + Id = MODULE_ID, + GetName = function() return Quiver.T["Tranq Shot Announcer"] end, + GetTooltipText = function() return Quiver.T["Announces in chat when your tranquilizing shot hits or misses a target."] end, + OnEnable = onEnable, + OnDisable = onDisable, + OnInterfaceLock = function() + if getCanHide() then hideFrameDeleteBars() end + end, + OnInterfaceUnlock = function() frame:Show() end, + OnResetFrames = function() + store.FrameMeta = nil + if frame then setFramePosition(frame, store) end + end, + OnSavedVariablesRestore = function(savedVariables) + store = savedVariables + store.MsgTranqMiss = savedVariables.MsgTranqMiss or Quiver.T["*** MISSED Tranq Shot ***"] + store.MsgTranqCast = store.MsgTranqCast or Quiver.T["Casting Tranq Shot"] + -- TODO DRY violation -- dropdown must match the module store init + store.TranqChannel = store.TranqChannel or "/Say" + end, + OnSavedVariablesPersist = function() return store end, +} + +end) +__bundle_register("Modules/RangeIndicator.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local FrameLock = require("Events/FrameLock.lua") +local Action = require("Shiver/API/Action.lua") + +local MODULE_ID = "RangeIndicator" +local store = nil +local frame = nil +local fontString = nil + +local setFramePosition = function(f, s) + FrameLock.SideEffectRestoreSize(s, { + w=190, h=35, dx=190 * -0.5, dy=-183, + }) + f:SetWidth(s.FrameMeta.W) + f:SetHeight(s.FrameMeta.H) + f:SetPoint("TopLeft", s.FrameMeta.X, s.FrameMeta.Y) +end + +local createUI = function() + local f = CreateFrame("Frame", nil, UIParent) + setFramePosition(f, store) + FrameLock.SideEffectMakeMoveable(f, store) + FrameLock.SideEffectMakeResizeable(f, store, { GripMargin=4 }) + + f:SetFrameStrata("LOW") + f:SetBackdrop({ + bgFile = "Interface/Tooltips/UI-Tooltip-Background", + edgeFile = "Interface/Tooltips/UI-Tooltip-Border", + tile = true, + tileSize = 8, + edgeSize = 16, + insets = { left=4, right=4, top=4, bottom=4 }, + }) + f:SetBackdropColor(0, 0, 0, 0.6) + f:SetBackdropBorderColor(0.25, 0.25, 0.25, 1) + + local fs = f:CreateFontString(nil, "ARTWORK", "GameFontNormal") + fs:SetAllPoints(f) + fs:SetJustifyH("Center") + fs:SetJustifyV("Middle") + fs:SetText("Range Indicator") + fs:SetTextColor(1, 1, 1) + + return f, fs +end + +---@param name string +---@return boolean +---@nodiscard +local predSpellInRange = function(name) + local slot = Action.FindBySpellName(name) + if slot == nil then + return false + else + return IsActionInRange(slot) == 1 + end +end + +local checkDistance = { + -- https://wowwiki-archive.fandom.com/wiki/API_CheckInteractDistance + Inspect=function() return CheckInteractDistance("target", 1) end,-- 11.11 yards + Trade=function() return CheckInteractDistance("target", 2) end,-- 11.11 yards + Duel=function() return CheckInteractDistance("target", 3) end,-- 9.9 yards (or 10?) + Follow=function() return CheckInteractDistance("target", 4) end,-- 28 yards + -- Using Action Bars + Melee=function() return predSpellInRange(Quiver.L.Spell["Wing Clip"]) end,-- 5 yards + Mark=function() return predSpellInRange(Quiver.L.Spell["Hunter's Mark"]) end,-- 100 yards + Ranged=function() return predSpellInRange(Quiver.L.Spell["Auto Shot"]) end,-- 35-41 yards (talents) + Scare=function() return predSpellInRange(Quiver.L.Spell["Scare Beast"]) end,-- 10 yards + Scatter=function() return predSpellInRange(Quiver.L.Spell["Scatter Shot"]) end,-- 15-21 yards (talents) +} + +local render = function(color, text) + fontString:SetText(text) + local r, g, b, a = unpack(color) + frame:SetBackdropColor(r, g, b, a) + frame:SetBackdropBorderColor(r, g, b, a) + -- if not Quiver_Store.IsLockedFrames then + -- TODO do we care about grip handle color here? + -- frame.QuiverGripHandle:GetNormalTexture():SetVertexColor(r, g, b) + -- frame.QuiverGripHandle:GetHighlightTexture():SetVertexColor(r+0.3, g-0.1, b+0.3) + -- end +end + +-- ************ Event Handlers ************ +local handleUpdate = function() + if checkDistance.Melee() then + render(store.ColorMelee, Quiver.T["Melee Range"]) + elseif checkDistance.Ranged() then + if UnitCreatureType("target") == "Beast" and checkDistance.Scare() then + render(store.ColorScareBeast, Quiver.T["Scare Beast"]) + elseif checkDistance.Scatter() then + render(store.ColorScatterShot, Quiver.T["Scatter Shot"]) + elseif checkDistance.Follow() then + render(store.ColorShort, Quiver.T["Short Range"]) + else + render(store.ColorLong, Quiver.T["Long Range"]) + end + elseif checkDistance.Follow() then + render(store.ColorDeadZone, Quiver.T["Dead Zone"]) + elseif checkDistance.Mark() then + render(store.ColorMark, Quiver.T["Hunter's Mark"]) + else + render(store.ColorTooFar, Quiver.T["Out of Range"]) + end +end + +local handleEvent = function() + if UnitExists("target") + and (not UnitIsDead("target")) + and UnitCanAttack("player", "target") + then + frame:Show() + elseif Quiver_Store.IsLockedFrames + then frame:Hide() + end +end + +-- ************ Initialization ************ +--- @type Event[] +local EVENTS = { + "PLAYER_TARGET_CHANGED", + "UNIT_FACTION", +} +local onEnable = function() + if frame == nil then frame, fontString = createUI() end + frame:SetScript("OnEvent", handleEvent) + frame:SetScript("OnUpdate", handleUpdate) + for _k, e in EVENTS do frame:RegisterEvent(e) end + if Quiver_Store.IsLockedFrames then handleEvent() else frame:Show() end +end + +local onDisable = function() + frame:Hide() + for _k, e in EVENTS do frame:UnregisterEvent(e) end +end + +---@type QqModule +return { + Id = MODULE_ID, + GetName = function() return Quiver.T["Range Indicator"] end, + GetTooltipText = function() return Quiver.T["Shows when abilities are in range. Requires spellbook abilities placed somewhere on your action bars."] end, + OnEnable = onEnable, + OnDisable = onDisable, + OnInterfaceLock = function() handleEvent() end, + OnInterfaceUnlock = function() frame:Show() end, + OnResetFrames = function() + store.FrameMeta = nil + if frame then setFramePosition(frame, store) end + end, + OnSavedVariablesRestore = function(savedVariables) + store = savedVariables + store.ColorMelee = store.ColorMelee or QUIVER.ColorDefault.Range.Melee + store.ColorDeadZone = store.ColorDeadZone or QUIVER.ColorDefault.Range.DeadZone + store.ColorScareBeast = store.ColorScareBeast or QUIVER.ColorDefault.Range.ScareBeast + store.ColorScatterShot = store.ColorScatterShot or QUIVER.ColorDefault.Range.ScatterShot + store.ColorShort = store.ColorShort or QUIVER.ColorDefault.Range.Short + store.ColorLong = store.ColorLong or QUIVER.ColorDefault.Range.Long + store.ColorMark = store.ColorMark or QUIVER.ColorDefault.Range.Mark + store.ColorTooFar = store.ColorTooFar or QUIVER.ColorDefault.Range.TooFar + end, + OnSavedVariablesPersist = function() return store end, +} + +end) +__bundle_register("Modules/Castbar.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local FrameLock = require("Events/FrameLock.lua") +local Spellcast = require("Events/Spellcast.lua") +local BorderStyle = require("Modules/BorderStyle.provider.lua") +local Haste = require("Shiver/Haste.lua") + +local MODULE_ID = "Castbar" +local store = nil +local frame = nil + +local maxBarWidth = 0 +local castTime = 0 +local isCasting = false +local timeStartCasting = 0 + +-- ************ UI ************ +local styleCastbar = function(f) + local sizeInset = BorderStyle.GetInsetSize() + + if BorderStyle.GetStyle() == "Tooltip" then + f:SetBackdrop({ + bgFile = "Interface/BUTTONS/WHITE8X8", + edgeFile = "Interface/Tooltips/UI-Tooltip-Border", + edgeSize = 10, + insets = { left=sizeInset, right=sizeInset, top=sizeInset, bottom=sizeInset }, + }) + f:SetBackdropBorderColor(BorderStyle.GetColor()) + else + f:SetBackdrop({ + bgFile = "Interface/BUTTONS/WHITE8X8", + edgeFile = "Interface/BUTTONS/WHITE8X8", + edgeSize = sizeInset, + }) + f:SetBackdropBorderColor(0.2, 0.2, 0.2, 0.8) + end + f:SetBackdropColor(0, 0, 0, 0.8) + + maxBarWidth = f:GetWidth() - 2 * sizeInset + f.Castbar:SetPoint("Left", f, "Left", sizeInset, 0) + f.Castbar:SetWidth(1) + f.Castbar:SetHeight(f:GetHeight() - 2 * sizeInset) + + f.SpellName:SetWidth(maxBarWidth) + f.SpellTime:SetWidth(maxBarWidth) + + local path, _size, flags = f.SpellName:GetFont() + local textMargin = 5 + local calcFontSize = f:GetHeight() - sizeInset - textMargin + local fontSize = calcFontSize > 18 and 18 + or calcFontSize < 10 and 10 + or calcFontSize + + f.SpellName:SetPoint("Left", f, "Left", textMargin, 0) + f.SpellTime:SetPoint("Right", f, "Right", -textMargin, 0) + + f.SpellName:SetFont(path, fontSize, flags) + f.SpellTime:SetFont(path, fontSize, flags) +end + +local setFramePosition = function(f, s) + FrameLock.SideEffectRestoreSize(s, { + w=240, h=20, dx=240 * -0.5, dy=-116, + }) + f:SetWidth(s.FrameMeta.W) + f:SetHeight(s.FrameMeta.H) + f:SetPoint("TopLeft", s.FrameMeta.X, s.FrameMeta.Y, 0, 0) +end + +local createUI = function() + local f = CreateFrame("Frame", nil, UIParent) + f:SetFrameStrata("HIGH") + + f.Castbar = CreateFrame("Frame", nil, f) + f.Castbar:SetBackdrop({ + bgFile = "Interface/BUTTONS/WHITE8X8", + }) + + f.SpellName = f.Castbar:CreateFontString(nil, "OVERLAY", "GameFontNormal") + f.SpellName:SetJustifyH("Left") + f.SpellName:SetTextColor(1, 1, 1) + + f.SpellTime = f.Castbar:CreateFontString(nil, "OVERLAY", "GameFontNormal") + f.SpellTime:SetJustifyH("Right") + f.SpellTime:SetTextColor(1, 1, 1) + + setFramePosition(f, store) + styleCastbar(f) + + FrameLock.SideEffectMakeMoveable(f, store) + FrameLock.SideEffectMakeResizeable(f, store, { + GripMargin=4, + OnResizeDrag=function() styleCastbar(f) end, + OnResizeEnd=function() styleCastbar(f) end, + IsCenterX=true, + }) + return f +end + +-- ************ Custom Event Handlers ************ +local displayTime = function(current) + if current < 0 then current = 0 end + frame.SpellTime:SetText(string.format("%.1f / %.2f", current, castTime)) +end + +---@param nameEnglish string +---@param nameLocalized string +local onSpellcast = function(nameEnglish, nameLocalized) + if isCasting then return end + isCasting = true + local _timeStartLocal + castTime, timeStartCasting, _timeStartLocal = Haste.CalcCastTime(nameEnglish) + frame.SpellName:SetText(nameLocalized) + frame.Castbar:SetWidth(1) + displayTime(0) + + local r, g, b = unpack(store.ColorCastbar) + frame.Castbar:SetBackdropColor(r, g, b, 1) + frame:Show() +end + +-- ************ Frame Update Handlers ************ +local handleUpdate = function() + local timePassed = GetTime() - timeStartCasting + if not isCasting then + frame.Castbar:SetWidth(1) + elseif timePassed <= castTime then + displayTime(timePassed) + frame.Castbar:SetWidth(maxBarWidth * timePassed / castTime) + else + displayTime(castTime) + frame.Castbar:SetWidth(maxBarWidth) + end +end + +-- ************ Event Handlers ************ +local handleEvent = function() + if event == "SPELLCAST_DELAYED" then + castTime = castTime + arg1 / 1000 + else + isCasting = false + if Quiver_Store.IsLockedFrames then frame:Hide() end + end +end + +-- ************ Initialization ************ +--- @type Event[] +local EVENTS = { + "SPELLCAST_DELAYED", + "SPELLCAST_FAILED", + "SPELLCAST_INTERRUPTED", + "SPELLCAST_STOP", +} +local onEnable = function() + if frame == nil then frame = createUI() end + if Quiver_Store.IsLockedFrames then frame:Hide() else frame:Show() end + frame:SetScript("OnEvent", handleEvent) + frame:SetScript("OnUpdate", handleUpdate) + for _k, e in EVENTS do frame:RegisterEvent(e) end + BorderStyle.Subscribe(MODULE_ID, function(_style) + if frame ~= nil then styleCastbar(frame) end + end) + Spellcast.CastableShot.Subscribe(MODULE_ID, onSpellcast) +end +local onDisable = function() + Spellcast.CastableShot.Dispose(MODULE_ID) + BorderStyle.Dispose(MODULE_ID) + if frame ~= nil then + frame:Hide() + for _k, e in EVENTS do frame:UnregisterEvent(e) end + end +end + +---@type QqModule +return { + Id = MODULE_ID, + GetName = function() return Quiver.T["Castbar"] end, + GetTooltipText = function() return Quiver.T["Shows Aimed Shot, Multi-Shot, and Trueshot."] end, + OnEnable = onEnable, + OnDisable = onDisable, + OnInterfaceLock = function() if not isCasting then frame:Hide() end end, + OnInterfaceUnlock = function() frame:Show() end, + OnResetFrames = function() + store.FrameMeta = nil + if frame then + setFramePosition(frame, store) + styleCastbar(frame) + end + end, + OnSavedVariablesRestore = function(savedVariables) + store = savedVariables + store.ColorCastbar = store.ColorCastbar or QUIVER.ColorDefault.Castbar + end, + OnSavedVariablesPersist = function() return store end, +} + +end) +__bundle_register("Modules/Aspect_Tracker/AspectTracker.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local FrameLock = require("Events/FrameLock.lua") +local Spell = require("Shiver/API/Spell.lua") +local Aura = require("Util/Aura.lua") + +local MODULE_ID = "AspectTracker" +local store = nil---@type StoreAspectTracker +local frame = nil + +local DEFAULT_ICON_SIZE = 40 +local INSET = 5 +local TRANSPARENCY = 0.5 + +local chooseIconTexture = function() + if Aura.PredBuffActive(Quiver.L.Spell["Aspect of the Beast"]) then + return QUIVER.Icon.Aspect_Beast + elseif Aura.PredBuffActive(Quiver.L.Spell["Aspect of the Cheetah"]) then + return QUIVER.Icon.Aspect_Cheetah + elseif Aura.PredBuffActive(Quiver.L.Spell["Aspect of the Monkey"]) then + return QUIVER.Icon.Aspect_Monkey + elseif Aura.PredBuffActive(Quiver.L.Spell["Aspect of the Wild"]) then + return QUIVER.Icon.Aspect_Wild + elseif Aura.PredBuffActive(Quiver.L.Spell["Aspect of the Wolf"]) then + return QUIVER.Icon.Aspect_Wolf + elseif Spell.PredSpellLearned(Quiver.L.Spell["Aspect of the Hawk"]) + and not Aura.PredBuffActive(Quiver.L.Spell["Aspect of the Hawk"]) + or not Quiver_Store.IsLockedFrames + then + return QUIVER.Icon.Aspect_Hawk + else + return nil + end +end + +-- ************ UI ************ +local updateUI = function() + local activeTexture = chooseIconTexture() + if activeTexture then + frame.Icon:SetBackdrop({ bgFile = activeTexture, tile = false }) + frame.Icon:SetAlpha(TRANSPARENCY) + else + frame.Icon:SetAlpha(0.0) + end + + -- Exclude Pack from main texture, since party members can apply it. + -- I don't have a simple way of detecting who cast it, because + -- the cancellable bit is 1 even if a party member cast it. + if Aura.PredBuffActive(Quiver.L.Spell["Aspect of the Pack"]) then + frame:SetBackdrop({ + bgFile = "Interface/Tooltips/UI-Tooltip-Background", + edgeFile = "Interface/Tooltips/UI-Tooltip-Border", + edgeSize = 20, + insets = { left=INSET, right=INSET, top=INSET, bottom=INSET }, + tile = false, + }) + frame:SetBackdropBorderColor(0.7, 0.8, 0.9, 1.0) + else + frame:SetBackdrop({ bgFile = "Interface/BUTTONS/WHITE8X8", tile = false }) + end + frame:SetBackdropColor(0, 0, 0, 0) +end + +local setFramePosition = function(f, s) + FrameLock.SideEffectRestoreSize(s, { + w=DEFAULT_ICON_SIZE, h=DEFAULT_ICON_SIZE, dx=110, dy=40, + }) + f:SetWidth(s.FrameMeta.W) + f:SetHeight(s.FrameMeta.H) + f:SetPoint("TopLeft", s.FrameMeta.X, s.FrameMeta.Y) +end + +local createUI = function() + local f = CreateFrame("Frame", nil, UIParent) + f:SetFrameStrata("LOW") + setFramePosition(f, store) + + f.Icon = CreateFrame("Frame", nil, f) + f.Icon:SetPoint("Left", f, "Left", INSET, 0) + f.Icon:SetPoint("Right", f, "Right", -INSET, 0) + f.Icon:SetPoint("Top", f, "Top", 0, -INSET) + f.Icon:SetPoint("Bottom", f, "Bottom", 0, INSET) + + FrameLock.SideEffectMakeMoveable(f, store) + FrameLock.SideEffectMakeResizeable(f, store, { GripMargin=0 }) + return f +end + +-- ************ Event Handlers ************ +--- @type Event[] +local EVENTS = { + "PLAYER_AURAS_CHANGED", + "SPELLS_CHANGED",-- Open or click thru spellbook, learn/unlearn spell +} +local handleEvent = function() + if event == "SPELLS_CHANGED" and arg1 ~= "LeftButton" + or event == "PLAYER_AURAS_CHANGED" + then + updateUI() + end +end + +-- ************ Initialization ************ +local onEnable = function() + if frame == nil then frame = createUI() end + updateUI() + frame:SetScript("OnEvent", handleEvent) + for _k, e in EVENTS do frame:RegisterEvent(e) end + frame:Show() +end +local onDisable = function() + frame:Hide() + for _k, e in EVENTS do frame:UnregisterEvent(e) end +end + +---@type QqModule +return { + Id = MODULE_ID, + GetName = function() return Quiver.T["Aspect Tracker"] end, + GetTooltipText = function() return nil end, + OnEnable = onEnable, + OnDisable = onDisable, + OnInterfaceLock = function() updateUI() end, + OnInterfaceUnlock = function() updateUI() end, + OnResetFrames = function() + store.FrameMeta = nil + if frame then setFramePosition(frame, store) end + end, + ---@param savedVariables StoreAspectTracker + OnSavedVariablesRestore = function(savedVariables) + store = savedVariables + end, + OnSavedVariablesPersist = function() return store end, +} + +end) +__bundle_register("Migrations/Runner.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local M001 = require("Migrations/M001.lua") +local M002 = require("Migrations/M002.lua") +local M003 = require("Migrations/M003.lua") +local Version = require("Util/Version.lua") + +return function() + -- toc version (after 1.0.0) persists to saved variables. A clean + -- install has no saved variables, which distinguishes a 1.0.0 install. + if Quiver_Store == nil then + Quiver_Store = {} + else + local vOld = Version:ParseThrows(Quiver_Store.Version or "1.0.0") + if vOld:PredNewer("2.0.0") then M001() end + if vOld:PredNewer("2.3.1") then M002() end + if vOld:PredNewer("2.5.0") then M003() end + end + Quiver_Store.Version = GetAddOnMetadata("Quiver", "Version") +end + +end) +__bundle_register("Migrations/M003.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local TranqAnnouncer = require("Modules/TranqAnnouncer.lua") + +return function() + local mstore = Quiver_Store.ModuleStore or {} + local s = mstore[TranqAnnouncer.Id] or {} + + if s.MsgTranqHit then + -- We notify on tranq cast instead of hit. To prevent a breaking + -- release version, attempt changing contradictory text. + local startPos, _ = string.find(string.lower(s.MsgTranqHit), "hit") + if startPos then + s.MsgTranqHit = Quiver.T["Casting Tranq Shot"] + end + + -- Change name to account for new behaviour + s.MsgTranqCast = s.MsgTranqHit + s.MsgTranqHit = nil + end +end + +end) +__bundle_register("Migrations/M002.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local AutoShotTimer = require("Modules/Auto_Shot_Timer/AutoShotTimer.lua") + +return function() + local mstore = Quiver_Store.ModuleStore or {} + local s = mstore[AutoShotTimer.Id] or {} + + -- Change colour to color + if s.ColourShoot then s.ColorShoot = s.ColourShoot end + if s.ColorReload then s.ColorReload = s.ColourReload end + s.ColourShoot = nil + s.ColourReload = nil +end + +end) +__bundle_register("Migrations/M001.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local AutoShotTimer = require("Modules/Auto_Shot_Timer/AutoShotTimer.lua") + +return function() + local mstore = Quiver_Store.ModuleStore + if mstore == nil or Quiver_Store.FrameMeta == nil then return end + + -- Rename Auto Shot timer module + Quiver_Store.ModuleEnabled[AutoShotTimer.Id] = Quiver_Store.ModuleEnabled["AutoShotCastbar"] + Quiver_Store.ModuleEnabled["AutoShotCastbar"] = nil + + mstore[AutoShotTimer.Id] = mstore["AutoShotCastbar"] + mstore["AutoShotCastbar"] = nil + + Quiver_Store.FrameMeta[AutoShotTimer.Id] = Quiver_Store.FrameMeta["AutoShotCastbar"] + Quiver_Store.FrameMeta["AutoShotCastbar"] = nil + + -- Move all module-specific frame data into module stores + for _k, v in _G.Quiver_Modules do + if mstore[v.Id] and Quiver_Store.FrameMeta[v.Id] then + mstore[v.Id].FrameMeta = Quiver_Store.FrameMeta[v.Id] + end + end + Quiver_Store.FrameMeta = nil +end + +end) +__bundle_register("Config/MainMenu.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local Button = require("Component/Button.lua") +local CheckButton = require("Component/CheckButton.lua") +local Dialog = require("Component/Dialog.lua") +local Select = require("Component/Select.lua") +local Switch = require("Component/Switch.lua") +local TitleBox = require("Component/TitleBox.lua") +local Color = require("Config/Color.lua") +local InputText = require("Config/InputText.lua") +local FrameLock = require("Events/FrameLock.lua") +local AutoShotTimer = require("Modules/Auto_Shot_Timer/AutoShotTimer.lua") +local BorderStyle = require("Modules/BorderStyle.provider.lua") +local TranqAnnouncer = require("Modules/TranqAnnouncer.lua") +local L = require("Shiver/Lib/All.lua") + +local createModuleControls = function(parent, m) + local f = CreateFrame("Frame", nil, parent) + + local btnReset = Button:Create(f, QUIVER.Icon.Reset) + btnReset.TooltipText = Quiver.T["Reset Frame Size and Position"] + btnReset.HookClick = function() m.OnResetFrames() end + if not Quiver_Store.ModuleEnabled[m.Id] then + btnReset:ToggleEnabled(false) + end + + local switch = Switch:Create(f, { + IsChecked = Quiver_Store.ModuleEnabled[m.Id], + LabelText = m.GetName(), + TooltipText = m.GetTooltipText(), + OnChange = function (isChecked) + Quiver_Store.ModuleEnabled[m.Id] = isChecked + if isChecked then + m.OnEnable() + else + m.OnDisable() + end + btnReset:ToggleEnabled(isChecked) + end, + }) + + local x = 0 + local gap = 8 + btnReset.Container:SetPoint("Left", f, "Left", x, 0) + x = x + btnReset.Container:GetWidth() + gap + switch.Container:SetPoint("Left", f, "Left", x, 0) + x = x + switch.Container:GetWidth() + + f:SetHeight(switch.Container:GetHeight()) + f:SetWidth(x) + return f +end + +local createAllModuleControls = function(parent, gap) + local f = CreateFrame("Frame", nil, parent) + local frames = L.Array.Mapi(_G.Quiver_Modules, function(m, i) + local frame = createModuleControls(f, m) + local yOffset = i * (frame:GetHeight() + gap) + frame:SetPoint("Left", f, "Left", 0, 0) + frame:SetPoint("Top", f, "Top", 0, -yOffset) + return frame + end) + + local maxWidths = + L.Array.MapReduce(frames, function(x) return x:GetWidth() end, math.max, 0) + local totalHeight = + L.Array.MapReduce(frames, function(x) return x:GetHeight() + gap end, L.Add, 0) + - gap + f:SetHeight(totalHeight) + f:SetWidth(maxWidths) + + return f +end + +local makeSelectAutoShotTimerDirection = function(parent) + -- Factored out text until we can re-render options upon locale change. + -- Otherwise, the change handler with compare wrong locale. + local both = Quiver.T["Both Directions"] + local selected = Quiver_Store.ModuleStore[AutoShotTimer.Id].BarDirection + local options = { Quiver.T["Left to Right"], both } + return Select:Create(parent, + Quiver.T["Auto Shot Timer"], + options, + Quiver.T[selected], + function(text) + -- Reverse map from localized text to saved value + local direction = text == both and "BothDirections" or "LeftToRight" + Quiver_Store.ModuleStore[AutoShotTimer.Id].BarDirection = direction + AutoShotTimer.UpdateDirection() + end + ) +end + +local makeSelectBorderStyle = function(parent) + local tooltip = Quiver.T["Tooltip"] + local selected = Quiver_Store.Border_Style + local options = { Quiver.T["Simple"], tooltip } + return Select:Create(parent, + Quiver.T["Border Style"], + options, + Quiver.T[selected], + function(text) + -- Reverse map from localized text to saved value + local style = text == tooltip and "Tooltip" or "Simple" + BorderStyle.ChangeAndPublish(style) + end + ) +end + +local makeSelectChannelHit = function(parent) + local defaultTranqText = (function() + local store = Quiver_Store.ModuleStore[TranqAnnouncer.Id] + -- TODO DRY violation -- dropdown must match the module store init + return store and store.TranqChannel or "/Say" + end)() + return Select:Create(parent, + Quiver.T["Tranq Speech"], + { Quiver.T["None"], "/Say", "/Raid" }, + defaultTranqText, + function(text) + local val = (function() + if text == Quiver.T["None"] then + return "None" + else + return text or "/Say" + end + end)() + Quiver_Store.ModuleStore[TranqAnnouncer.Id].TranqChannel = val + end + ) +end + +local makeSelectDebugLevel = function(parent) + return Select:Create(parent, + Quiver.T["Debug Level"], + { Quiver.T["None"], Quiver.T["Verbose"] }, + Quiver_Store.DebugLevel == "Verbose" and Quiver.T["Verbose"] or Quiver.T["None"], + function(text) + local level = text == Quiver.T["Verbose"] and "Verbose" or "None" + Quiver_Store.DebugLevel = level + end + ) +end + +local Create = function() + -- WoW uses border-box content sizing + local _PADDING_CLOSE = QUIVER.Size.Border + 6 + local _PADDING_FAR = QUIVER.Size.Border + QUIVER.Size.Gap + local dialog = Dialog.Create(_PADDING_CLOSE) + + local titleText = "Quiver " .. GetAddOnMetadata("Quiver", "Version") + local titleBox = TitleBox.Create(dialog, titleText) + titleBox:SetPoint("Center", dialog, "Top", 0, -10) + + local btnCloseTop = Button:Create(dialog, QUIVER.Icon.XMark) + btnCloseTop.TooltipText = Quiver.T["Close Window"] + btnCloseTop.HookClick = function() dialog:Hide() end + btnCloseTop.Container:SetPoint("TopRight", dialog, "TopRight", -_PADDING_CLOSE, -_PADDING_CLOSE) + + local btnToggleLock = CheckButton:Create(dialog, { + IsChecked = Quiver_Store.IsLockedFrames, + OnChange = function(isLocked) FrameLock.SetIsLocked(isLocked) end, + TexPathOff = QUIVER.Icon.LockOpen, + TexPathOn = QUIVER.Icon.LockClosed, + TooltipText=Quiver.T["Lock/Unlock Frames"], + }) + FrameLock.Init() + + local lockOffsetX = _PADDING_CLOSE + QUIVER.Size.Icon + QUIVER.Size.Gap/2 + btnToggleLock.Icon:SetPoint("TopRight", dialog, "TopRight", -lockOffsetX, -_PADDING_CLOSE) + + local btnResetFrames = Button:Create(dialog, QUIVER.Icon.Reset) + btnResetFrames.TooltipText = Quiver.T["Reset All Frame Sizes and Positions"] + btnResetFrames.HookClick = function() + for _k, v in _G.Quiver_Modules do v.OnResetFrames() end + end + local resetOffsetX = lockOffsetX + btnResetFrames.Container:GetWidth() + QUIVER.Size.Gap/2 + btnResetFrames.Container:SetPoint("TopRight", dialog, "TopRight", -resetOffsetX, -_PADDING_CLOSE) + + local controls = createAllModuleControls(dialog, QUIVER.Size.Gap) + local colorPickers = Color.Create(dialog, QUIVER.Size.Gap) + + local yOffset = -_PADDING_CLOSE - QUIVER.Size.Icon - QUIVER.Size.Gap + controls:SetPoint("Top", dialog, "Top", 0, yOffset) + controls:SetPoint("Left", dialog, "Left", _PADDING_FAR, 0) + colorPickers:SetPoint("Top", dialog, "Top", 0, yOffset) + colorPickers:SetPoint("Right", dialog, "Right", -_PADDING_FAR, 0) + dialog:SetWidth(_PADDING_FAR + controls:GetWidth() + _PADDING_FAR + colorPickers:GetWidth() + _PADDING_FAR) + + local ddContainer = CreateFrame("Frame", nil, dialog) + local selectChannelHit = makeSelectChannelHit(ddContainer) + local selectAutoShotTimerDirection = makeSelectAutoShotTimerDirection(ddContainer) + local selectBorderStyle = makeSelectBorderStyle(ddContainer) + local selectDebugLevel = makeSelectDebugLevel(ddContainer) + + selectChannelHit.Container:SetPoint("Right", ddContainer, "Right") + selectAutoShotTimerDirection.Container:SetPoint("Right", ddContainer, "Right") + selectBorderStyle.Container:SetPoint("Right", ddContainer, "Right") + selectDebugLevel.Container:SetPoint("Right", ddContainer, "Right") + + local dropdownY = 0 + selectChannelHit.Container:SetPoint("Top", ddContainer, "Top", 0, dropdownY) + dropdownY = dropdownY - QUIVER.Size.Gap - selectChannelHit.Container:GetHeight() + + selectAutoShotTimerDirection.Container:SetPoint("Top", ddContainer, "Top", 0, dropdownY) + dropdownY = dropdownY - QUIVER.Size.Gap - selectAutoShotTimerDirection.Container:GetHeight() + + selectBorderStyle.Container:SetPoint("Top", ddContainer, "Top", 0, dropdownY) + dropdownY = dropdownY - QUIVER.Size.Gap - selectBorderStyle.Container:GetHeight() + + selectDebugLevel.Container:SetPoint("Top", ddContainer, "Top", 0, dropdownY) + dropdownY = dropdownY - selectDebugLevel.Container:GetHeight() + + ddContainer:SetPoint("Top", dialog, "Top", 0, yOffset - controls:GetHeight() - _PADDING_FAR) + ddContainer:SetPoint("Right", dialog, "Right", -(_PADDING_FAR + colorPickers:GetWidth() + _PADDING_FAR), 0) + ddContainer:SetHeight(-dropdownY) + + local dropdowns = { selectChannelHit, selectAutoShotTimerDirection, selectBorderStyle, selectDebugLevel } + local maxWidth = L.Array.MapReduce(dropdowns, function(x) return x.Container:GetWidth() end, math.max, 0) + ddContainer:SetHeight(-dropdownY) + ddContainer:SetWidth(maxWidth) + + local hLeft = controls:GetHeight() + _PADDING_FAR + ddContainer:GetHeight() + local hRight = colorPickers:GetHeight() + local hMax = hRight > hLeft and hRight or hLeft + yOffset = yOffset - hMax - QUIVER.Size.Gap + + local tranqOptions = InputText.Create(dialog, QUIVER.Size.Gap) + tranqOptions:SetPoint("TopLeft", dialog, "TopLeft", 0, yOffset) + yOffset = yOffset - tranqOptions:GetHeight() + yOffset = yOffset - QUIVER.Size.Gap + + dialog:SetHeight(-1 * yOffset + _PADDING_CLOSE + QUIVER.Size.Button) + return dialog +end + +return { + Create = Create, +} + +end) +__bundle_register("Config/InputText.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local EditBox = require("Component/EditBox.lua") +local TranqAnnouncer = require("Modules/TranqAnnouncer.lua") + +-- TODO this is tightly coupled to tranq announcer, +-- which doesn't make sense for a separate component. +local Create = function(parent, gap) + local store = Quiver_Store.ModuleStore[TranqAnnouncer.Id] + local f = CreateFrame("Frame", nil, parent) + + local editCast = EditBox:Create(f, Quiver.T["Reset Tranq Message to Default"]) + editCast.Box:SetText(store.MsgTranqCast) + editCast.Box:SetScript("OnTextChanged", function() + store.MsgTranqCast = editCast.Box:GetText() + end) + editCast.Reset.HookClick = function() + editCast.Box:SetText(Quiver.T["Casting Tranq Shot"]) + end + + local editMiss = EditBox:Create(f, Quiver.T["Reset Miss Message to Default"]) + editMiss.Box:SetText(store.MsgTranqMiss) + editMiss.Box:SetScript("OnTextChanged", function() + store.MsgTranqMiss = editMiss.Box:GetText() + end) + editMiss.Reset.HookClick = function() + editMiss.Box:SetText(Quiver.T["*** MISSED Tranq Shot ***"]) + end + + local height1 = editCast.Box:GetHeight() + editCast.Box:SetPoint("Top", f, "Top", 0, 0) + editMiss.Box:SetPoint("Top", f, "Top", 0, -1 * (height1 + gap)) + + f:SetWidth(parent:GetWidth()) + f:SetHeight(height1 + gap + editMiss.Box:GetHeight()) + return f +end + +return { + Create = Create, +} + +end) +__bundle_register("Component/EditBox.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local Button = require("Component/Button.lua") + +local _GAP = QUIVER.Size.Gap +local _GAP_RESET = 4 + +---@class QqEditBox +---@field private __index? QqEditBox +---@field Box EditBox +---@field Reset QqButton +local QqEditBox = {} + +---@param parent Frame +---@param tooltipText string +---@return QqEditBox +function QqEditBox:Create(parent, tooltipText) + local box = CreateFrame("EditBox", nil, parent) + box:SetWidth(300) + box:SetHeight(25) + + ---@type QqEditBox + local r = { + Box = box, + Reset = Button:Create(box, QUIVER.Icon.Reset), + } + r.Reset.TooltipText = tooltipText + setmetatable(r, self) + self.__index = self + + local fMarginLeft = QUIVER.Size.Border + _GAP + local fMarginRight = QUIVER.Size.Border + _GAP + QUIVER.Size.Icon + _GAP_RESET + + local xr = r.Reset.Container:GetWidth() + _GAP_RESET + r.Reset.Container:SetPoint("Right", box, "Right", xr, 0) + + box:SetPoint("Left", parent, "Left", fMarginLeft, 0) + box:SetPoint("Right", parent, "Right", -fMarginRight, 0) + box:SetTextColor(.5, 1, .8, 1) + box:SetJustifyH("Left") + box:SetMaxLetters(50) + + box:SetFontObject(GameFontNormalSmall) + + box:SetBackdrop({ + bgFile = "Interface/BUTTONS/WHITE8X8", + edgeFile = "Interface/Tooltips/UI-Tooltip-Border", + tile = true, + tileSize = 32, + edgeSize = 10, + insets = { left=3, right=3, top=3, bottom=3 }, + }) + box:SetBackdropColor(0, 0, 0, 1) + box:SetBackdropBorderColor(0.25, 0.25, 0.25, 1) + box:SetTextInsets(6,6,0,0) + + box:SetAutoFocus(false) + box:SetScript("OnEscapePressed", function() box:ClearFocus() end) + box:SetScript("OnEnterPressed", function() box:ClearFocus() end) + return r +end + +return QqEditBox + +end) +__bundle_register("Config/Color.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local Button = require("Component/Button.lua") +local ColorSwatch = require("Component/ColorSwatch.lua") +local AutoShotTimer = require("Modules/Auto_Shot_Timer/AutoShotTimer.lua") +local Castbar = require("Modules/Castbar.lua") +local RangeIndicator = require("Modules/RangeIndicator.lua") +local Color = require("Shiver/Color.lua") +local L = require("Shiver/Lib/All.lua") + +---@param c1 Color +---@param c2 Color +local createBtnColorSwap = function(parent, f1, f2, c1, c2) + local f = Button:Create(parent, QUIVER.Icon.ArrowsSwap, Quiver.T["Shoot / Reload"]) + f.TooltipText = Quiver.T["Swap Shoot and Reload Colours"] + f.HookClick = function() + -- Swap colors + local r, g, b = c1:Rgb() + c1:SetRgb(c2:Rgb()) + c2:SetRgb(r, g, b) + + -- Update preview button + f1.Button:SetBackdropColor(c1:Rgb()) + f2.Button:SetBackdropColor(c2:Rgb()) + end + + return f +end + +---@param f Frame +---@param label string +---@param store Rgb +---@param default Rgb +local swatch = function(f, label, store, default) + local color = Color:LiftReset(store, default) + return ColorSwatch:Create(f, label, color) +end + +local Create = function(parent, gap) + local storeAutoShotTimer = Quiver_Store.ModuleStore[AutoShotTimer.Id] + local storeCastbar = Quiver_Store.ModuleStore[Castbar.Id] + local storeRange = Quiver_Store.ModuleStore[RangeIndicator.Id] + local f = CreateFrame("Frame", nil, parent) + + local colorShoot = Color:LiftReset(storeAutoShotTimer.ColorShoot, QUIVER.ColorDefault.AutoShotShoot) + local colorReload = Color:LiftReset(storeAutoShotTimer.ColorReload, QUIVER.ColorDefault.AutoShotReload) + local optionShoot = ColorSwatch:Create(f, Quiver.T["Shooting"], colorShoot) + local optionReload = ColorSwatch:Create(f, Quiver.T["Reloading"], colorReload) + + local elements = { + swatch(f, Quiver.T["Casting"], storeCastbar.ColorCastbar, QUIVER.ColorDefault.Castbar), + createBtnColorSwap(f, optionShoot, optionReload, colorShoot, colorReload), + optionShoot, + optionReload, + swatch(f, Quiver.T["Melee Range"], storeRange.ColorMelee, QUIVER.ColorDefault.Range.Melee), + swatch(f, Quiver.T["Dead Zone"], storeRange.ColorDeadZone, QUIVER.ColorDefault.Range.DeadZone), + swatch(f, Quiver.T["Scare Beast"], storeRange.ColorScareBeast, QUIVER.ColorDefault.Range.ScareBeast), + swatch(f, Quiver.T["Scatter Shot"], storeRange.ColorScatterShot, QUIVER.ColorDefault.Range.ScatterShot), + swatch(f, Quiver.T["Short Range"], storeRange.ColorShort, QUIVER.ColorDefault.Range.Short), + swatch(f, Quiver.T["Long Range"], storeRange.ColorLong, QUIVER.ColorDefault.Range.Long), + swatch(f, Quiver.T["Hunter's Mark"], storeRange.ColorMark, QUIVER.ColorDefault.Range.Mark), + swatch(f, Quiver.T["Out of Range"], storeRange.ColorTooFar, QUIVER.ColorDefault.Range.TooFar), + } + -- Right align buttons using minimum amount of space + local labelMaxWidth = L.Array.MapReduce( + elements, + function(x) return x.Label and x.Label:GetWidth() or 0 end, + L.Max, + 0 + ) + + local y = 0 + for _,ele in elements do + if ele.WidthMinusLabel ~= nil then + ele.Container:SetWidth(ele.WidthMinusLabel + labelMaxWidth) + end + ele.Container:SetPoint("Left", f, "Left", 0, 0) + ele.Container:SetPoint("Top", f, "Top", 0, -y) + y = y + ele.Container:GetHeight() + gap + end + + f:SetWidth(L.Array.MapReduce(elements, function(x) return x.Container:GetWidth() end, math.max, 0)) + f:SetHeight(y) + return f +end + +return { + Create = Create, +} + +end) +__bundle_register("Component/ColorSwatch.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local Button = require("Component/Button.lua") + +---@param color Color +---@param button Frame +local openColorPicker = function(color, button) + -- colors at time of opening picker + local ri, gi, bi = color:Rgb() + + -- Must replace existing callback before changing anything else, + -- or edits can fire previous callback, contaminating other values. + ColorPickerFrame.func = function() + local r, g, b = ColorPickerFrame:GetColorRGB() + color:SetRgb(r, g, b) + button:SetBackdropColor(r, g, b, 1) + end + + ColorPickerFrame.cancelFunc = function() + color:SetRgb(ri, gi, bi) + button:SetBackdropColor(ri, gi, bi, 1) + -- Reset native picker + ColorPickerFrame:SetFrameStrata("MEDIUM") + end + + ColorPickerFrame.hasOpacity = false + ColorPickerFrame:SetColorRGB(ri, gi, bi) + ColorPickerFrame:SetFrameStrata("FULLSCREEN_DIALOG") + ColorPickerFrame:Show() +end + +---@param parent Frame +---@param color Color +---@return Frame +local createButton = function(parent, color) + local f = CreateFrame("Button", nil, parent) + f:SetWidth(40) + f:SetHeight(20) + f:SetBackdrop({ + bgFile = "Interface/BUTTONS/WHITE8X8", + edgeFile = "Interface/Tooltips/UI-Tooltip-Border", + tile = true, + tileSize = 8, + edgeSize = 8, + insets = { left=2, right=2, top=2, bottom=2 }, + }) + f:SetBackdropColor(color:Rgb()) + f:SetScript("OnClick", function() openColorPicker(color, f) end) + return f +end + +---@class ButtonColorPicker +---@field Button Frame +---@field Container Frame +---@field Label FontString +---@field WidthMinusLabel number +local ColorSwatch = {} + +---@param parent Frame +---@param labelText string +---@param color Color +---@return ButtonColorPicker +function ColorSwatch:Create(parent, labelText, color) + local container = CreateFrame("Frame", nil, parent) + + ---@type ButtonColorPicker + local r = { + Button = createButton(container, color), + Container = container, + Label = container:CreateFontString(nil, "BACKGROUND", "GameFontNormal"), + WidthMinusLabel = 0, + } + + r.Label:SetPoint("Left", container, "Left", 0, 0) + r.Label:SetText(labelText) + + local reset = Button:Create(container, QUIVER.Icon.Reset) + reset.TooltipText = Quiver.T["Reset Color"] + reset.HookClick = function() + color:Reset() + r.Button:SetBackdropColor(color:Rgb()) + end + reset.Container:SetPoint("Right", container, "Right", 0, 0) + + local x = 4 + reset.Container:GetWidth() + r.Button:SetPoint("Right", container, "Right", -x, 0) + + r.Container:SetHeight(r.Button:GetHeight()) + r.WidthMinusLabel = 6 + x + r.Button:GetWidth() + + return r +end + +return ColorSwatch + +end) +__bundle_register("Component/TitleBox.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +---@param parent Frame +---@param text string +---@return Frame +local Create = function(parent, text) + local f = CreateFrame("Frame", nil, parent) + local fs = f:CreateFontString(nil, "ARTWORK", "GameFontNormal") + fs:SetAllPoints(f) + fs:SetJustifyH("Center") + fs:SetJustifyV("Middle") + fs:SetText(text) + + f:SetWidth(fs:GetStringWidth() + 30) + f:SetHeight(35) + f:SetBackdrop({ + bgFile = "Interface/BUTTONS/WHITE8X8", + edgeFile = "Interface/DialogFrame/UI-DialogBox-Border", + tile = true, + tileSize = 24, + edgeSize = 24, + insets = { left=8, right=8, top=8, bottom=8 }, + }) + -- TODO figure out how to clip parent frame instead of 100% opacity. + f:SetBackdropColor(0, 0, 0, 1) + f:SetBackdropBorderColor(0.25, 0.25, 0.25, 1) + return f +end + +return { + Create = Create, +} + +end) +__bundle_register("Component/Switch.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local Util = require("Component/_Util.lua") +local L = require("Shiver/Lib/All.lua") +local Sugar = require("Shiver/Sugar.lua") + +local _GAP = 6 +local _SIZE = 18 + +-- Three frame types exist for implementing a Switch: CheckButton, Button, Frame +-- For custom functionality with minimal code, Frame is the easiest starting point. + +-- - CheckButton +-- The built-in texture slots don't allow different highlight/pushed effects for checked/unchecked. +-- Also inherits all problems from Button. + +-- - Button +-- 1. Requires a pushed texture, otherwise icon disappears when user drags mouse. +-- That's twice the code of putting a single texture on a frame. +-- 2. Requires re-creating textures every time the button state changes, or +-- the next click causes a nil reference. +-- 3. If we use the built-in hover slot, the hover MUST stack with normal texture. +-- i.e. can't darken on hover. +-- 4. The built-in pushed effect doesn't take effect until MouseUp. + +-- - Frame +-- 1. Mouse disabled by default. +-- 2. Click event not implemented. +-- 3. Disabled not implemented. + +-- see [Button](lua://QqButton) +-- see [CheckButton](lua://QqCheckButton) +---@class (exact) QqSwitch : IMouseInteract +---@field private __index? QqSwitch +---@field Container Frame +---@field Icon Frame +---@field Label FontString +---@field Texture Texture +---@field isChecked boolean +---@field isEnabled boolean +---@field isHover boolean +---@field isMouseDown boolean +local QqSwitch = {} + +---@class (exact) paramsSwitch +---@field IsChecked boolean +---@field LabelText string +---@field OnChange fun(b: boolean): nil +---@field TooltipText? string + +---@param self QqSwitch +local resetTexture = function(self) + local path = self.isChecked and QUIVER.Icon.ToggleOn or QUIVER.Icon.ToggleOff + self.Texture:SetTexture(path) + + local r, g, b = Util.SelectColor(self) + local a = self.isChecked and 1.0 or 0.7 + self.Texture:SetVertexColor(r, g, b, a) + self.Label:SetTextColor(r, g, b, a) +end + +---@param parent Frame +---@param bag paramsSwitch +---@return QqSwitch +---@nodiscard +function QqSwitch:Create(parent, bag) + local container = CreateFrame("Frame", nil, parent, nil) + local icon = CreateFrame("Frame", nil, container, nil) + + ---@type QqSwitch + local r = { + Container = container, + Icon = icon, + Label = container:CreateFontString(nil, "BACKGROUND", "GameFontNormal"), + Texture = icon:CreateTexture(nil, "OVERLAY"), + isChecked = bag.IsChecked, + isEnabled = true, + isHover = false, + isMouseDown = false, + } + setmetatable(r, self) + self.__index = self + + local onEnter = function() + r.isHover = true + resetTexture(r) + Util.ToggleTooltip(r, r.Container, bag.TooltipText) + end + local onLeave = function() + r.isHover = false + resetTexture(r) + Util.ToggleTooltip(r, r.Container, bag.TooltipText) + end + + local onMouseDown = function() + r.isMouseDown = true + resetTexture(r) + end + local onMouseUp = function() + r.isMouseDown = false + if MouseIsOver(r.Container) == 1 then + r.isChecked = not r.isChecked + bag.OnChange(r.isChecked) + end + resetTexture(r) + end + + container:SetScript("OnEnter", onEnter) + container:SetScript("OnLeave", onLeave) + container:SetScript("OnMouseDown", onMouseDown) + container:SetScript("OnMouseUp", onMouseUp) + container:EnableMouse(true) + + r.Texture:SetAllPoints(r.Icon) + r.Icon:SetWidth(_SIZE * 1.2) + r.Icon:SetHeight(_SIZE) + r.Label:SetText(bag.LabelText) + + r.Icon:SetPoint("Left", container, "Left", 0, 0) + r.Label:SetPoint("Right", container, "Right", 0, 0) + local h = L.Psi(L.Max, Sugar.Region._GetHeight, r.Icon, r.Label) + local w = L.Psi(L.Add, Sugar.Region._GetWidth, r.Icon, r.Label) + _GAP + container:SetHeight(h) + container:SetWidth(w) + + resetTexture(r) + return r +end + +return QqSwitch + +end) +__bundle_register("Component/Select.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local Util = require("Component/_Util.lua") +local L = require("Shiver/Lib/All.lua") + +local _BORDER, _INSET, _SPACING = 1, 4, 4 +local _OPTION_PAD_H, _OPTION_PAD_V = 8, 4 +local _MENU_PAD_TOP = 6 + +---@type QqSelect[] +local allSelects = {} + +---@class Icon +---@field Frame Frame +---@field Texture Texture + +---@param container Frame +---@return Icon +---@nodiscard +local createIcon = function(container) + local f = CreateFrame("Frame", nil, container) + f:SetPoint("Right", container, "Right", -_INSET, 0) + f:SetWidth(16) + f:SetHeight(16) + + local t = f:CreateTexture(nil, "OVERLAY") + t:SetAllPoints(f) + t:SetTexture(QUIVER.Icon.CaretDown) + return { Frame=f, Texture=t } +end + +---@class (exact) QqSelect : IMouseInteract +---@field private __index? QqSelect +---@field Container Frame +---@field private icon Icon +---@field private label FontString +---@field private isEnabled boolean +---@field private isHover boolean +---@field private isMouseDown boolean +---@field Menu Frame +---@field Selected FontString +local QqSelect = {} + +function QqSelect:resetTexture() + local r, g, b = Util.SelectColor(self) + + local borderAlpha = self.isHover and 0.6 or 0.0 + self.Container:SetBackdropBorderColor(r, g, b, borderAlpha) + + self.label:SetTextColor(r, g, b) + self.Selected:SetTextColor(r, g, b) + + self.icon.Texture:SetVertexColor(r, g, b) + + -- Vertically flip caret + if self.Menu:IsVisible() then + self.icon.Texture:SetTexCoord(0, 1, 1, 0) + else + self.icon.Texture:SetTexCoord(0, 1, 0, 1) + end +end + +function QqSelect:OnHoverStart() + self.isHover = true + self:resetTexture() +end + +function QqSelect:OnHoverEnd() + self.isHover = false + self:resetTexture() +end + +function QqSelect:OnMouseDown() + self.isMouseDown = true + self:resetTexture() +end + +function QqSelect:OnMouseUp() + self.isMouseDown = false + if self:predMouseOver() then + local isVisible = self.Menu:IsVisible() + for _k, m in allSelects do + m.Menu:Hide() + m:resetTexture() + end + if not isVisible then self.Menu:Show() end + end + self:resetTexture() +end + +---@private +---@return boolean +---@nodiscard +function QqSelect:predMouseOver() + local xs = { self.Container, self.icon.Frame } + return L.Array.MapReduce(xs, function(x) return MouseIsOver(x) == 1 end, L.Or, false) +end + +---@param parent Frame +---@param labelText string +---@param optionsText string[] +---@param selectedText nil|string +---@param onSet fun(text: string): nil +---@return QqSelect +function QqSelect:Create(parent, labelText, optionsText, selectedText, onSet) + local select = CreateFrame("Frame", nil, parent) + + ---@type QqSelect + local r = { + Container = select, + icon = createIcon(select), + label = select:CreateFontString(nil, "BACKGROUND", "GameFontNormal"), + isEnabled = true, + isHover = false, + isMouseDown = false, + Menu = CreateFrame("Frame", nil, parent), + Selected = select:CreateFontString(nil, "BACKGROUND", "GameFontNormal") + } + setmetatable(r, self) + self.__index = self + table.insert(allSelects, r) + + r.Container:SetBackdrop({ + edgeFile="Interface/BUTTONS/WHITE8X8", + edgeSize=_BORDER, + }) + + r.Menu:SetFrameStrata("TOOLTIP") + r.Menu:SetBackdrop({ + bgFile = "Interface/BUTTONS/WHITE8X8", + edgeFile="Interface/BUTTONS/WHITE8X8", + edgeSize=1, + }) + r.Menu:SetBackdropColor(0, 0, 0, 1) + r.Menu:SetBackdropBorderColor(0.3, 0.3, 0.3, 1) + + r.label:SetPoint("Left", select, "Left", _INSET, 0) + r.label:SetPoint("Top", select, "Top", 0, -_INSET) + r.label:SetText(labelText) + + r.Selected:SetPoint("Bottom", select, "Bottom", 0, _INSET) + r.Selected:SetPoint("Left", select, "Left", _INSET, 0) + r.Selected:SetPoint("Right", select, "Right", -_INSET - r.icon.Frame:GetWidth(), 0) + r.Selected:SetText(selectedText or optionsText[1]) + + local options = L.Array.Mapi(optionsText, function(t, i) + local option = CreateFrame("Button", nil, r.Menu) + local optionFs = option:CreateFontString(nil, "OVERLAY", "GameFontNormal") + + option:SetFontString(optionFs) + optionFs:SetPoint("TopLeft", option, "TopLeft", _OPTION_PAD_H, -_OPTION_PAD_V) + optionFs:SetText(t) + + option:SetHeight(optionFs:GetHeight() + 2 * _OPTION_PAD_V) + option:SetPoint("Left", r.Menu, "Left", _BORDER, 0) + option:SetPoint("Right", r.Menu, "Right", -_BORDER, 0) + option:SetPoint("Top", r.Menu, "Top", 0, -i * option:GetHeight() - _BORDER - _MENU_PAD_TOP) + + local texHighlight = option:CreateTexture(nil, "OVERLAY") + -- It would probably look better to set a fancy texture and adjust vertex color. + texHighlight:SetTexture(0.22, 0.1, 0) + texHighlight:SetAllPoints(option) + option:SetHighlightTexture(texHighlight) + + return option + end) + + for _k, oLoop in options do + local option = oLoop---@type Button + option:SetScript("OnClick", function() + local text = option:GetFontString():GetText() or "" + onSet(text) + r.Selected:SetText(text) + r.Menu:Hide() + end) + end + + local sumOptionHeights = + L.Array.MapReduce(options, function(o) return o:GetHeight() end, L.Add, 0) + local maxOptionWidth = + L.Array.MapReduce(options, function(o) return o:GetFontString():GetWidth() end, math.max, 0) + + select:SetScript("OnEnter", function() r:OnHoverStart() end) + select:SetScript("OnLeave", function() r:OnHoverEnd() end) + select:SetScript("OnMouseDown", function() r:OnMouseDown() end) + select:SetScript("OnMouseUp", function() r:OnMouseUp() end) + select:EnableMouse(true) + + select:SetHeight( + r.Selected:GetHeight() + + _SPACING + + r.label:GetHeight() + + 2 * _INSET + ) + select:SetWidth( + math.max(r.label:GetWidth(), maxOptionWidth) + + r.icon.Frame:GetWidth() + + _SPACING + + _INSET * 2 + ) + + r.Menu:SetHeight(sumOptionHeights + _MENU_PAD_TOP + 2 * _BORDER) + r.Menu:SetWidth(maxOptionWidth + 2 * (_OPTION_PAD_H + _BORDER)) + r.Menu:SetPoint("Right", select, "Right", 0, 0) + r.Menu:SetPoint("Top", select, "Top", 0, -select:GetHeight()) + r.Menu:Hide() + + r:resetTexture() + return r +end + +return QqSelect + +end) +__bundle_register("Component/Dialog.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local Create = function(padding) + local f = CreateFrame("Frame", nil, UIParent) + f:Hide() + f:SetFrameStrata("DIALOG") + f:SetPoint("Center", nil, "Center", 0, 0) + f:SetBackdrop({ + bgFile = "Interface/Tooltips/UI-Tooltip-Background", + edgeFile = "Interface/DialogFrame/UI-DialogBox-Border", + tile = true, + tileSize = 32, + edgeSize = 32, + insets = { left=8, right=8, top=8, bottom=8 }, + }) + f:SetBackdropColor(0, 0, 0, 0.6) + f:SetBackdropBorderColor(0.25, 0.25, 0.25, 1) + f:SetMovable(true) + f:EnableMouse(true) + f:SetScript("OnMouseDown", function() f:StartMoving() end) + f:SetScript("OnMouseUp", function() f:StopMovingOrSizing() end) + + local btnCloseBottom = CreateFrame("Button", nil, f, "UIPanelButtonTemplate") + btnCloseBottom:SetWidth(70) + btnCloseBottom:SetHeight(QUIVER.Size.Button) + btnCloseBottom:SetPoint("BottomRight", f, "BottomRight", -padding, padding) + btnCloseBottom:SetText(Quiver.T["Close"]) + btnCloseBottom:SetScript("OnClick", function() f:Hide() end) + + return f +end + +return { + Create = Create, +} + +end) +__bundle_register("Component/CheckButton.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local Util = require("Component/_Util.lua") + +local _SIZE = 16 + +-- see [Button](lua://QqButton) +-- see [Switch](lua://QqSwitch) +---@class (exact) QqCheckButton : IMouseInteract +---@field private __index? QqCheckButton +---@field Icon Frame +---@field IsChecked boolean +---@field TexPathOff string +---@field TexPathOn string +---@field Texture Texture +---@field isEnabled boolean +---@field isHover boolean +---@field isMouseDown boolean +local QqCheckButton = {} + +---@class (exact) paramsCheckButton +---@field IsChecked boolean +---@field OnChange fun(b: boolean): nil +---@field TexPathOff string +---@field TexPathOn string +---@field TooltipText? string + +---@param self QqCheckButton +local resetTexture = function(self) + local path = self.IsChecked and self.TexPathOn or self.TexPathOff + self.Texture:SetTexture(path) + + local r, g, b = Util.SelectColor(self) + self.Texture:SetVertexColor(r, g, b) +end + +---@param parent Frame +---@param bag paramsCheckButton +---@return QqCheckButton +---@nodiscard +function QqCheckButton:Create(parent, bag) + local icon = CreateFrame("Frame", nil, parent, nil) + + ---@type QqCheckButton + local r = { + Icon = icon, + IsChecked = bag.IsChecked, + TexPathOff = bag.TexPathOff, + TexPathOn = bag.TexPathOn, + Texture = icon:CreateTexture(nil, "OVERLAY"), + isEnabled = true, + isHover = false, + isMouseDown = false, + } + setmetatable(r, self) + self.__index = self + + r.Texture:SetAllPoints(r.Icon) + + local onEnter = function() + r.isHover = true + resetTexture(r) + Util.ToggleTooltip(r, r.Icon, bag.TooltipText) + end + local onLeave = function() + r.isHover = false + resetTexture(r) + Util.ToggleTooltip(r, r.Icon, bag.TooltipText) + end + + local onMouseDown = function() + r.isMouseDown = true + resetTexture(r) + end + local onMouseUp = function() + r.isMouseDown = false + if MouseIsOver(r.Icon) == 1 then + r.IsChecked = not r.IsChecked + bag.OnChange(r.IsChecked) + end + resetTexture(r) + end + + r.Icon:SetScript("OnEnter", onEnter) + r.Icon:SetScript("OnLeave", onLeave) + r.Icon:SetScript("OnMouseDown", onMouseDown) + r.Icon:SetScript("OnMouseUp", onMouseUp) + + r.Icon:EnableMouse(true) + r.Icon:SetWidth(_SIZE) + r.Icon:SetHeight(_SIZE) + + resetTexture(r) + return r +end + +return QqCheckButton + +end) +__bundle_register("Locale/Lang.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local enUS_C = require("Locale/enUS/Client.enUS.lua") +local enUS_T = require("Locale/enUS/Translations.enUS.lua") +local zhCN_C = require("Locale/zhCN/Client.zhCN.lua") +local zhCN_T = require("Locale/zhCN/Translations.zhCN.lua") + +return function() + local translation = { + ["enUS"] = enUS_T, + ["zhCN"] = zhCN_T, + } + local client = { + ["enUS"] = enUS_C, + ["zhCN"] = zhCN_C, + } + local currentLang = GetLocale() + Quiver.T = translation[currentLang] or translation["enUS"] + Quiver.L = client[currentLang] or client["enUS"] +end + +end) +__bundle_register("Locale/zhCN/Translations.zhCN.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +return { + ["Announces in chat when your tranquilizing shot hits or misses a target."] = "在“/团队”聊天中通告你的宁神射击是否命中目标。", + ["Aspect Tracker"] = "守护追踪器", + ["Auto Shot Timer"] = "自动射击计时器", + ["Border Style"] = "边框样式", + ["Both Directions"] = "双向", + ["Castbar"] = "施法条", + ["Casting"] = "正在施法", + ["Casting Tranq Shot"] = "施放宁神射击", + ["Close"] = "关闭", + ["Close Window"] = "关闭窗口", + ["Dead Zone"] = "死区", + ["Debug Level"] = "调试级别", + ["Hunter's Mark"] = "猎人印记", + ["It's always safe to upgrade Quiver. You won't lose any of your configuration."] = "升级Quiver是安全的,你不会丢失任何配置。", + ["Left to Right"] = "从左到右", + ["Lock/Unlock Frames"] = "锁定/解锁框架", + ["Long Range"] = "远距离", + ["Melee Range"] = "近战范围", + ["*** MISSED Tranq Shot ***"] = "*** 宁神射击未命中 ***", + ["New version %s available at %s"] = "新版本%s可在%s下载", + ["None"] = "无", + ["Out of Range"] = "超出范围", + ["Quiver is for hunters."] = "Quiver仅适用于猎人。", + ["Quiver Unlocked. Show config dialog with /qq or /quiver.\nClick the lock icon when done."] = "Quiver已解锁。使用/qq或/quiver显示配置对话框。\n完成后点击锁定图标。", + ["Range Indicator"] = "距离指示器", + ["Reloading"] = "正在装填", + ["Reset All Frame Sizes and Positions"] = "重置所有框架大小和位置", + ["Reset Color"] = "重置颜色", + ["Reset Frame Size and Position"] = "重置框架大小和位置", + ["Reset Miss Message to Default"] = "重置未命中消息为默认", + ["Reset Tranq Message to Default"] = "重置宁神射击消息为默认", + ["Scare Beast"] = "恐吓野兽", + ["Scatter Shot"] = "驱散射击", + ["Shoot / Reload"] = "射击/装填", + ["Shooting"] = "正在射击", + ["Short Range"] = "近距离", + ["Shows Aimed Shot, Multi-Shot, and Trueshot."] = "显示瞄准射击、多重射击和稳固射击的施法条。", + ["Shows when abilities are in range. Requires spellbook abilities placed somewhere on your action bars."] = "显示技能是否在范围内。需要将技能书中的技能放在动作条上。", + ["Simple"] = "简单的", + ["Swap Shoot and Reload Colours"] = "交换射击和装填颜色", + ["Tooltip"] = "工具提示", + ["Tranq Shot Announcer"] = "宁神射击通告器", + ["Tranq Speech"] = "宁神通知", + ["Trueshot Aura Alarm"] = "强击光环警报", + ["Verbose"] = "详细信息", +} + +end) +__bundle_register("Locale/zhCN/Client.zhCN.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local Spell = require("Locale/zhCN/Spell.zhCN.lua") +local SpellReverse = require("Locale/zhCN/Spell.reverse.zhCN.lua") +-- local Zone = require "Locale/zhCN/Zone.zhCN.lua" + +return { + CombatLog = { + Consumes = { + ManaPotion = "你从恢复法力中获得(.*)点法力值。", + HealthPotion = "你的治疗药水为你恢复了(.*)点生命值。", + Healthstone = "你的(.*)治疗石为你恢复了(.*)点生命值。", + Tea = "你的糖水茶为你恢复了(.*)点生命值。", + }, + Tranq = { + Fail = "你未能驱散", + Miss = "你的宁神射击未命中", + Resist = "你的宁神射击被抵抗了", + }, + }, + Spell = Spell, + -- TODO it turns out spellnames aren't unique in Chinese. + -- This approach isn't going to work in the general case. + SpellReverse = SpellReverse, +} + +end) +__bundle_register("Locale/zhCN/Spell.reverse.zhCN.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +return { + ["孤狼守护"] = "Aspect of the Wolf", + ["稳固射击"] = "Trueshot", + ["驱除疾病"] = "Abolish Disease", + ["驱毒术"] = "Abolish Poison", + ["驱毒术效果"] = "Abolish Poison Effect", + ["Acid Breath"] = "Acid Breath", + ["Acid of Hakkar"] = "Acid of Hakkar", + ["Acid Spit"] = "Acid Spit", + ["Acid Splash"] = "Acid Splash", + ["速射炮台"] = "Activate MG Turret", + ["冲动"] = "Adrenaline Rush", + ["清算"] = "Reckoning", + ["侵略"] = "Aggression", + ["瞄准射击"] = "Aimed Shot", + ["炼金术"] = "Alchemy", + ["伏击"] = "Ambush", + ["诅咒增幅"] = "Amplify Curse", + ["Amplify Damage"] = "Amplify Damage", + ["Amplify Flames"] = "Amplify Flames", + ["魔法增效"] = "Amplify Magic", + ["先祖坚韧"] = "Ancestral Fortitude", + ["先祖治疗"] = "Ancestral Healing", + ["先祖知识"] = "Ancestral Knowledge", + ["先祖之魂"] = "Ancestral Spirit", + ["Anesthetic Poison"] = "Anesthetic Poison", + ["愤怒掌控"] = "Anger Management", + ["Anguish"] = "Anguish", + ["预知"] = "Anticipation", + ["Aqua Jet"] = "Aqua Jet", + ["水栖形态"] = "Aquatic Form", + ["Arcane Blast"] = "Arcane Blast", + ["Arcane Bolt"] = "Arcane Bolt", + ["奥术光辉"] = "Arcane Brilliance", + ["奥术专注"] = "Arcane Concentration", + ["魔爆术"] = "Arcane Explosion", + ["奥术集中"] = "Arcane Focus", + ["奥术增效"] = "Arcane Instability", + ["奥术智慧"] = "Arcane Intellect", + ["奥术冥想"] = "Arcane Meditation", + ["奥术心智"] = "Arcane Mind", + ["奥术飞弹"] = "Arcane Missiles", + ["Arcane Potency"] = "Arcane Potency", + ["奥术强化"] = "Arcane Power", + ["奥术抗性"] = "Arcane Resistance", + ["奥术射击"] = "Arcane Shot", + ["奥术精妙"] = "Arcane Subtlety", + ["Arcane Weakness"] = "Arcane Weakness", + ["Arcing Smash"] = "Arcing Smash", + ["极寒延伸"] = "Arctic Reach", + ["护甲锻造师"] = "Armorsmith", + ["Arugal's Curse"] = "Arugal's Curse", + ["Arugal's Gift"] = "Arugal's Gift", + ["Ascendance"] = "Ascendance", + ["Aspect of Arlokk"] = "Aspect of Arlokk", + ["Aspect of Jeklik"] = "Aspect of Jeklik", + ["Aspect of Mar'li"] = "Aspect of Mar'li", + ["野兽守护"] = "Aspect of the Beast", + ["猎豹守护"] = "Aspect of the Cheetah", + ["雄鹰守护"] = "Aspect of the Hawk", + ["灵猴守护"] = "Aspect of the Monkey", + ["豹群守护"] = "Aspect of the Pack", + ["Aspect of the Viper"] = "Aspect of the Viper", + ["野性守护"] = "Aspect of the Wild", + ["Aspect of Venoxis"] = "Aspect of Venoxis", + ["星界传送"] = "Astral Recall", + ["攻击"] = "Attacking", + ["Aura of Command"] = "Aura of Command", + ["Aural Shock"] = "Aural Shock", + ["自动射击"] = "Auto Shot", + ["Avenger's Shield"] = "Avenger's Shield", + ["Avenging Wrath"] = "Avenging Wrath", + ["Avoidance"] = "Avoidance", + ["Axe Flurry"] = "Axe Flurry", + ["斧专精"] = "Axe Specialization", + ["Axe Toss"] = "Axe Toss", + ["Backhand"] = "Backhand", + ["Backlash"] = "Backlash", + ["背刺"] = "Backstab", + ["灾祸"] = "Bane", + ["Baneful Poison"] = "Baneful Poison", + ["放逐术"] = "Banish", + ["Banshee Curse"] = "Banshee Curse", + ["Banshee Shriek"] = "Banshee Shriek", + ["Barbed Sting"] = "Barbed Sting", + ["树皮术"] = "Barkskin", + ["树皮术效果"] = "Barkskin Effect", + ["弹幕"] = "Barrage", + ["重击"] = "Bash", + ["基础营火"] = "Basic Campfire", + ["战斗怒吼"] = "Battle Shout", + ["战斗姿态"] = "Battle Stance", + ["战斗姿态(被动)"] = "Battle Stance Passive", + ["熊形态"] = "Bear Form", + ["野兽知识"] = "Beast Lore", + ["野兽杀手"] = "Beast Slaying", + ["训练野兽"] = "Beast Training", + ["The Beast Within"] = "The Beast Within", + ["Befuddlement"] = "Befuddlement", + ["祈福"] = "Benediction", + ["Berserker Charge"] = "Berserker Charge", + ["狂暴之怒"] = "Berserker Rage", + ["狂暴姿态"] = "Berserker Stance", + ["狂暴姿态(被动)"] = "Berserker Stance Passive", + ["狂暴"] = "Berserking", + ["野兽戒律"] = "Bestial Discipline", + ["野兽迅捷"] = "Bestial Swiftness", + ["狂野怒火"] = "Bestial Wrath", + ["Biletoad Infection"] = "Biletoad Infection", + ["Binding Heal"] = "Binding Heal", + ["撕咬"] = "Bite", + ["黑箭"] = "Black Arrow", + ["昏阙"] = "Blackout", + ["锻造"] = "Blacksmithing", + ["剑刃乱舞"] = "Blade Flurry", + ["冲击波"] = "Blast Wave", + ["Blaze"] = "Blaze", + ["Blazing Speed"] = "Blazing Speed", + ["神恩回复"] = "Blessed Recovery", + ["Blessing of Blackfathom"] = "Blessing of Blackfathom", + ["自由祝福"] = "Blessing of Freedom", + ["王者祝福"] = "Blessing of Kings", + ["光明祝福"] = "Blessing of Light", + ["力量祝福"] = "Blessing of Might", + ["保护祝福"] = "Blessing of Protection", + ["牺牲祝福"] = "Blessing of Sacrifice", + ["拯救祝福"] = "Blessing of Salvation", + ["庇护祝福"] = "Blessing of Sanctuary", + ["Blessing of Shahram"] = "Blessing of Shahram", + ["智慧祝福"] = "Blessing of Wisdom", + ["致盲"] = "Blind", + ["致盲粉"] = "Blinding Powder", + ["闪现术"] = "Blink", + ["暴风雪"] = "Blizzard", + ["格挡"] = "Block", + ["血之狂热"] = "Blood Craze", + ["血之狂暴"] = "Blood Frenzy", + ["Blood Funnel"] = "Blood Funnel", + ["血性狂暴"] = "Bloodrage", + ["Blood Leech"] = "Blood Leech", + ["血之契印"] = "Blood Pact", + ["Blood Siphon"] = "Blood Siphon", + ["Blood Tap"] = "Blood Tap", + ["Bloodlust"] = "Bloodlust", + ["残忍"] = "Cruelty", + ["Bomb"] = "Bomb", + ["震耳嗓音"] = "Booming Voice", + ["Boulder"] = "Boulder", + ["弓专精"] = "Bow Specialization", + ["弓"] = "Bows", + ["Brain Wash"] = "Brain Wash", + ["明亮篝火"] = "Bright Campfire", + ["野蛮冲撞"] = "Brutal Impact", + ["Burning Adrenaline"] = "Burning Adrenaline", + ["燃烧之魂"] = "Burning Soul", + ["Burning Wish"] = "Burning Wish", + ["Butcher Drain"] = "Butcher Drain", + ["烈焰召唤"] = "Call of Flame", + ["Call of the Grave"] = "Call of the Grave", + ["雷霆召唤"] = "Call of Thunder", + ["召唤宠物"] = "Call Pet", + ["伪装"] = "Camouflage", + ["食尸"] = "Cannibalize", + ["猎豹形态"] = "Cat Form", + ["灾变"] = "Cataclysm", + ["Cause Insanity"] = "Cause Insanity", + ["Chain Bolt"] = "Chain Bolt", + ["Chain Burn"] = "Chain Burn", + ["治疗链"] = "Chain Heal", + ["闪电链"] = "Chain Lightning", + ["Chained Bolt"] = "Chained Bolt", + ["Chains of Ice"] = "Chains of Ice", + ["挑战咆哮"] = "Challenging Roar", + ["挑战怒吼"] = "Challenging Shout", + ["冲锋"] = "Charge", + ["冲锋额外怒气效果"] = "Charge Rage Bonus Effect", + ["冲锋击昏"] = "Charge Stun", + ["偷袭"] = "Cheap Shot", + ["冰冻"] = "Chilled", + ["Chilling Touch"] = "Chilling Touch", + ["Chromatic Infusion"] = "Chromatic Infusion", + ["Circle of Healing"] = "Circle of Healing", + ["爪击"] = "Claw", + ["清洁术"] = "Cleanse", + ["Cleanse Nova"] = "Cleanse Nova", + ["节能施法"] = "Clearcasting", + ["顺劈斩"] = "Cleave", + ["灵巧陷阱"] = "Clever Traps", + ["Cloak of Shadows"] = "Cloak of Shadows", + ["关闭"] = "Closing", + ["布甲"] = "Cloth", + ["粗制磨刀石"] = "Coarse Sharpening Stone", + ["毒蛇反射"] = "Cobra Reflexes", + ["冷血"] = "Cold Blood", + ["急速冷却"] = "Cold Snap", + ["作战持久"] = "Combat Endurance", + ["燃烧"] = "Conflagrate", + ["命令"] = "Command", + ["Commanding Shout"] = "Commanding Shout", + ["专注光环"] = "Concentration Aura", + ["震荡"] = "Concussion", + ["震荡猛击"] = "Concussion Blow", + ["震荡射击"] = "Concussive Shot", + ["冰锥术"] = "Cone of Cold", + ["造食术"] = "Conjure Food", + ["制造魔法玛瑙"] = "Conjure Mana Agate", + ["制造魔法黄水晶"] = "Conjure Mana Citrine", + ["制造魔法翡翠"] = "Conjure Mana Jade", + ["制造魔法红宝石"] = "Conjure Mana Ruby", + ["造水术"] = "Conjure Water", + ["Consecrated Sharpening Stone"] = "Consecrated Sharpening Stone", + ["奉献"] = "Consecration", + ["Consume Magic"] = "Consume Magic", + ["吞噬暗影"] = "Consume Shadows", + ["Consuming Shadows"] = "Consuming Shadows", + ["传导"] = "Convection", + ["定罪"] = "Conviction", + ["烹饪"] = "Cooking", + ["Corrosive Acid Breath"] = "Corrosive Acid Breath", + ["Corrosive Ooze"] = "Corrosive Ooze", + ["Corrosive Poison"] = "Corrosive Poison", + ["Corrupted Blood"] = "Corrupted Blood", + ["腐蚀"] = "Corruption", + ["反击"] = "Counterattack", + ["法术反制"] = "Counterspell", + ["法术反制 - 沉默"] = "Counterspell - Silenced", + ["畏缩"] = "Cower", + ["制造火焰石"] = "Create Firestone", + ["制造强效火焰石"] = "Create Firestone (Greater)", + ["制造次级火焰石"] = "Create Firestone (Lesser)", + ["制造极效火焰石"] = "Create Firestone (Major)", + ["制造治疗石"] = "Create Healthstone", + ["制造强效治疗石"] = "Create Healthstone (Greater)", + ["制造次级治疗石"] = "Create Healthstone (Lesser)", + ["制造极效治疗石"] = "Create Healthstone (Major)", + ["制造初级治疗石"] = "Create Healthstone (Minor)", + ["制造灵魂石"] = "Create Soulstone", + ["制造强效灵魂石"] = "Create Soulstone (Greater)", + ["制造次级灵魂石"] = "Create Soulstone (Lesser)", + ["制造极效灵魂石"] = "Create Soulstone (Major)", + ["制造初级灵魂石"] = "Create Soulstone (Minor)", + ["制造法术石"] = "Create Spellstone", + ["制造强效法术石"] = "Create Spellstone (Greater)", + ["制造极效法术石"] = "Create Spellstone (Major)", + ["Create Spellstone (Master)"] = "Create Spellstone (Master)", + ["Creeper Venom"] = "Creeper Venom", + ["Cripple"] = "Cripple", + ["致残毒药"] = "Crippling Poison", + ["致残毒药 II"] = "Crippling Poison II", + ["火焰重击"] = "Critical Mass", + ["弩"] = "Crossbows", + ["Crowd Pummel"] = "Crowd Pummel", + ["Crusader Aura"] = "Crusader Aura", + ["Crusader Strike"] = "Crusader Strike", + ["Crusader's Wrath"] = "Crusader's Wrath", + ["Crystal Charge"] = "Crystal Charge", + ["Crystal Force"] = "Crystal Force", + ["Crystal Restore"] = "Crystal Restore", + ["Crystal Spire"] = "Crystal Spire", + ["Crystal Ward"] = "Crystal Ward", + ["Crystal Yield"] = "Crystal Yield", + ["Crystalline Slumber"] = "Crystalline Slumber", + ["栽培"] = "Cultivation", + ["祛病术"] = "Cure Disease", + ["消毒术"] = "Cure Poison", + ["痛苦诅咒"] = "Curse of Agony", + ["Curse of Blood"] = "Curse of Blood", + ["厄运诅咒"] = "Curse of Doom", + ["厄运诅咒效果"] = "Curse of Doom Effect", + ["疲劳诅咒"] = "Curse of Exhaustion", + ["痴呆诅咒"] = "Curse of Idiocy", + ["鲁莽诅咒"] = "Curse of Recklessness", + ["暗影诅咒"] = "Curse of Shadow", + ["Curse of the Deadwood"] = "Curse of the Deadwood", + ["Curse of the Elemental Lord"] = "Curse of the Elemental Lord", + ["元素诅咒"] = "Curse of the Elements", + ["语言诅咒"] = "Curse of Tongues", + ["Curse of Tuten'kash"] = "Curse of Tuten'kash", + ["虚弱诅咒"] = "Curse of Weakness", + ["Cursed Blood"] = "Cursed Blood", + ["Cyclone"] = "Cyclone", + ["匕首专精"] = "Dagger Specialization", + ["匕首"] = "Daggers", + ["魔法抑制"] = "Dampen Magic", + ["Dark Iron Bomb"] = "Dark Iron Bomb", + ["Dark Offering"] = "Dark Offering", + ["黑暗契约"] = "Dark Pact", + ["黑暗"] = "Darkness", + ["急奔"] = "Dash", + ["Dazed"] = "Dazed", + ["致命毒药"] = "Deadly Poison", + ["致命毒药 II"] = "Deadly Poison II", + ["致命毒药 III"] = "Deadly Poison III", + ["致命毒药 IV"] = "Deadly Poison IV", + ["致命毒药 V"] = "Deadly Poison V", + ["Deadly Throw"] = "Deadly Throw", + ["死亡缠绕"] = "Death Coil", + ["死亡之愿"] = "Death Wish", + ["Deep Sleep"] = "Deep Sleep", + ["Deep Slumber"] = "Deep Slumber", + ["重度伤口"] = "Deep Wounds", + ["防御"] = "Defense", + ["防御姿态"] = "Defensive Stance", + ["防御姿态(被动)"] = "Defensive Stance Passive", + ["防御状态"] = "Defensive State", + ["防御状态 2"] = "Defensive State 2", + ["挑衅"] = "Defiance", + ["偏斜"] = "Deflection", + ["Delusions of Jin'do"] = "Delusions of Jin'do", + ["魔甲术"] = "Mage Armor", + ["恶魔皮肤"] = "Demon Skin", + ["恶魔之拥"] = "Demonic Embrace", + ["Demonic Frenzy"] = "Demonic Frenzy", + ["恶魔牺牲"] = "Demonic Sacrifice", + ["挫志咆哮"] = "Demoralizing Roar", + ["挫志怒吼"] = "Demoralizing Shout", + ["致密磨刀石"] = "Dense Sharpening Stone", + ["绝望祷言"] = "Desperate Prayer", + ["毁灭延伸"] = "Destructive Reach", + ["侦测"] = "Detect", + ["侦测强效隐形"] = "Detect Greater Invisibility", + ["侦测隐形"] = "Detect Invisibility", + ["侦测次级隐形"] = "Detect Lesser Invisibility", + ["侦测魔法"] = "Detect Magic", + ["侦测陷阱"] = "Detect Traps", + ["威慑"] = "Deterrence", + ["Detonation"] = "Detonation", + ["Devastate"] = "Devastate", + ["毁灭"] = "Ruin", + ["虔诚光环"] = "Devotion Aura", + ["吞噬魔法"] = "Devour Magic", + ["吞噬魔法效果"] = "Devour Magic Effect", + ["噬灵瘟疫"] = "Devouring Plague", + ["Diamond Flask"] = "Diamond Flask", + ["外交"] = "Diplomacy", + ["巨熊形态"] = "Dire Bear Form", + ["Dire Growl"] = "Dire Growl", + ["缴械"] = "Disarm", + ["解除陷阱"] = "Disarm Trap", + ["祛病图腾"] = "Poison Cleansing Totem", + ["Disease Cloud"] = "Disease Cloud", + ["Diseased Shot"] = "Diseased Shot", + ["Diseased Spit"] = "Diseased Spit", + ["分解"] = "Disenchant", + ["逃脱"] = "Disengage", + ["Disjunction"] = "Disjunction", + ["解散野兽"] = "Dismiss Pet", + ["驱散魔法"] = "Dispel Magic", + ["扰乱"] = "Distract", + ["Distracting Pain"] = "Distracting Pain", + ["扰乱射击"] = "Distracting Shot", + ["俯冲"] = "Dive", + ["神恩术"] = "Divine Favor", + ["神圣之怒"] = "Divine Fury", + ["Divine Illumination"] = "Divine Illumination", + ["神圣智慧"] = "Divine Intellect", + ["神圣干涉"] = "Divine Intervention", + ["圣佑术"] = "Divine Protection", + ["圣盾术"] = "Divine Shield", + ["神圣之灵"] = "Divine Spirit", + ["神圣之力"] = "Divine Strength", + ["Diving Sweep"] = "Diving Sweep", + ["躲闪"] = "Dodge", + ["Dominate Mind"] = "Dominate Mind", + ["Dragon's Breath"] = "Dragon's Breath", + ["龙鳞制皮"] = "Dragonscale Leatherworking", + ["吸取生命"] = "Drain Life", + ["吸取法力"] = "Drain Mana", + ["吸取灵魂"] = "Drain Soul", + ["Dredge Sickness"] = "Dredge Sickness", + ["喝水"] = "Drink", + ["Druid's Slumber"] = "Druid's Slumber", + ["双武器"] = "Dual Wield", + ["双武器专精"] = "Dual Wield Specialization", + ["决斗"] = "Duel", + ["Dust Field"] = "Dust Field", + ["鹰眼术"] = "Eagle Eye", + ["Earth Elemental Totem"] = "Earth Elemental Totem", + ["Earth Shield"] = "Earth Shield", + ["大地震击"] = "Earth Shock", + ["地缚图腾"] = "Earthbind Totem", + ["Earthborer Acid"] = "Earthborer Acid", + ["Earthgrab"] = "Earthgrab", + ["效率"] = "Efficiency", + ["Electric Discharge"] = "Electric Discharge", + ["Electrified Net"] = "Electrified Net", + ["元素集中"] = "Elemental Focus", + ["元素之怒"] = "Elemental Fury", + ["元素制皮"] = "Elemental Leatherworking", + ["元素掌握"] = "Elemental Mastery", + ["Elemental Precision"] = "Elemental Precision", + ["元素磨刀石"] = "Elemental Sharpening Stone", + ["艾露恩的赐福"] = "Elune's Grace", + ["飘忽不定"] = "Elusiveness", + ["琥珀风暴"] = "Emberstorm", + ["Enamored Water Spirit"] = "Enamored Water Spirit", + ["附魔"] = "Enchanting", + ["耐久"] = "Endurance", + ["耐久训练"] = "Endurance Training", + ["工程学"] = "Engineering", + ["工程学专精"] = "Engineering Specialization", + ["狂怒"] = "Enrage", + ["可口的魔法点心"] = "Enriched Manna Biscuit", + ["奴役恶魔"] = "Enslave Demon", + ["纠缠根须"] = "Entangling Roots", + ["诱捕"] = "Entrapment", + ["Enveloping Web"] = "Enveloping Web", + ["Enveloping Webs"] = "Enveloping Webs", + ["Enveloping Winds"] = "Enveloping Winds", + ["Envenom"] = "Envenom", + ["Ephemeral Power"] = "Ephemeral Power", + ["逃命专家"] = "Escape Artist", + ["Essence of Sapphiron"] = "Essence of Sapphiron", + ["闪避"] = "Evasion", + ["Eventide"] = "Eventide", + ["剔骨"] = "Eviscerate", + ["唤醒"] = "Evocation", + ["斩杀"] = "Execute", + ["驱邪术"] = "Exorcism", + ["开阔思维"] = "Expansive Mind", + ["Exploding Shot"] = "Exploding Shot", + ["Exploit Weakness"] = "Exploit Weakness", + ["Explosive Shot"] = "Explosive Shot", + ["爆炸陷阱"] = "Explosive Trap", + ["爆炸陷阱效果"] = "Explosive Trap Effect", + ["破甲"] = "Sunder Armor", + ["Expose Weakness"] = "Expose Weakness", + ["以眼还眼"] = "Eye for an Eye", + ["基尔罗格之眼"] = "Eye of Kilrogg", + ["The Eye of the Dead"] = "The Eye of the Dead", + ["野兽之眼"] = "Eyes of the Beast", + ["渐隐术"] = "Fade", + ["精灵之火"] = "Faerie Fire", + ["精灵之火(野性)"] = "Faerie Fire (Feral)", + ["视界术"] = "Far Sight", + ["Fatal Bite"] = "Fatal Bite", + ["恐惧术"] = "Fear", + ["防护恐惧结界"] = "Fear Ward", + ["喂养宠物"] = "Feed Pet", + ["回馈"] = "Feedback", + ["假死"] = "Feign Death", + ["佯攻"] = "Feint", + ["Fel Armor"] = "Fel Armor", + ["恶魔专注"] = "Fel Concentration", + ["恶魔支配"] = "Fel Domination", + ["恶魔智力"] = "Fel Intellect", + ["恶魔耐力"] = "Fel Stamina", + ["Fel Stomp"] = "Fel Stomp", + ["魔火"] = "Felfire", + ["豹之优雅"] = "Feline Grace", + ["豹之迅捷"] = "Feline Swiftness", + ["野性侵略"] = "Feral Aggression", + ["野性冲锋"] = "Feral Charge", + ["野性本能"] = "Feral Instinct", + ["凶猛撕咬"] = "Ferocious Bite", + ["凶暴"] = "Ferocity", + ["神像"] = "Fetish", + ["Fevered Plague"] = "Fevered Plague", + ["Fiery Burst"] = "Fiery Burst", + ["寻找草药"] = "Find Herbs", + ["寻找矿物"] = "Find Minerals", + ["寻找财宝"] = "Find Treasure", + ["火焰冲击"] = "Fire Blast", + ["Fire Elemental Totem"] = "Fire Elemental Totem", + ["Fire Nova"] = "Fire Nova", + ["火焰新星图腾"] = "Fire Nova Totem", + ["火焰强化"] = "Fire Power", + ["火焰抗性"] = "Fire Resistance", + ["火焰抗性光环"] = "Fire Resistance Aura", + ["抗火图腾"] = "Fire Resistance Totem", + ["火焰之盾"] = "Fire Shield", + ["Fire Shield Effect"] = "Fire Shield Effect", + ["Fire Shield Effect II"] = "Fire Shield Effect II", + ["Fire Shield Effect III"] = "Fire Shield Effect III", + ["Fire Shield Effect IV"] = "Fire Shield Effect IV", + ["Fire Storm"] = "Fire Storm", + ["火焰易伤"] = "Fire Vulnerability", + ["防护火焰结界"] = "Fire Ward", + ["Fire Weakness"] = "Fire Weakness", + ["火球术"] = "Fireball", + ["Fireball Volley"] = "Fireball Volley", + ["火焰箭"] = "Firebolt", + ["急救"] = "First Aid", + ["钓鱼"] = "Fishing", + ["鱼竿"] = "Fishing Poles", + ["Fist of Ragnaros"] = "Fist of Ragnaros", + ["拳套专精"] = "Fist Weapon Specialization", + ["拳套"] = "Fist Weapons", + ["Flame Buffet"] = "Flame Buffet", + ["Flame Cannon"] = "Flame Cannon", + ["Flame Lash"] = "Flame Lash", + ["烈焰震击"] = "Flame Shock", + ["Flame Spike"] = "Flame Spike", + ["Flame Spray"] = "Flame Spray", + ["烈焰投掷"] = "Flame Throwing", + ["Flames of Shahram"] = "Flames of Shahram", + ["烈焰冲击"] = "Flamestrike", + ["火焰喷射器"] = "Flamethrower", + ["火舌图腾"] = "Flametongue Totem", + ["火舌武器"] = "Flametongue Weapon", + ["照明弹"] = "Flare", + ["Flash Bomb"] = "Flash Bomb", + ["快速治疗"] = "Flash Heal", + ["圣光闪现"] = "Flash of Light", + ["Flight Form"] = "Flight Form", + ["乱舞"] = "Flurry", + ["专注施法"] = "Focused Casting", + ["Focused Mind"] = "Focused Mind", + ["进食"] = "Food", + ["自律"] = "Forbearance", + ["Force of Nature"] = "Force of Nature", + ["意志之力"] = "Force of Will", + ["Force Punch"] = "Force Punch", + ["Force Reactive Disk"] = "Force Reactive Disk", + ["Forked Lightning"] = "Forked Lightning", + ["Forsaken Skills"] = "Forsaken Skills", + ["Frailty"] = "Frailty", + ["Freeze Solid"] = "Freeze Solid", + ["冰冻陷阱"] = "Freezing Trap", + ["冰冻陷阱效果"] = "Freezing Trap Effect", + ["狂暴回复"] = "Frenzied Regeneration", + ["疯狂"] = "Frenzy", + ["霜甲术"] = "Frost Armor", + ["Frost Breath"] = "Frost Breath", + ["冰霜导能"] = "Frost Channeling", + ["冰霜新星"] = "Frost Nova", + ["冰霜抗性"] = "Frost Resistance", + ["冰霜抗性光环"] = "Frost Resistance Aura", + ["抗寒图腾"] = "Frost Resistance Totem", + ["冰霜震击"] = "Frost Shock", + ["Frost Shot"] = "Frost Shot", + ["冰霜陷阱"] = "Frost Trap", + ["冰霜陷阱光环"] = "Frost Trap Aura", + ["防护冰霜结界"] = "Frost Ward", + ["Frost Warding"] = "Frost Warding", + ["Frost Weakness"] = "Frost Weakness", + ["霜寒刺骨"] = "Frostbite", + ["寒冰箭"] = "Frostbolt", + ["Frostbolt Volley"] = "Frostbolt Volley", + ["冰封武器"] = "Frostbrand Weapon", + ["狂怒之嚎"] = "Furious Howl", + ["The Furious Storm"] = "The Furious Storm", + ["激怒"] = "Furor", + ["Fury of Ragnaros"] = "Fury of Ragnaros", + ["Gahz'ranka Slam"] = "Gahz'ranka Slam", + ["Gahz'rilla Slam"] = "Gahz'rilla Slam", + ["绞喉"] = "Garrote", + ["Gehennas' Curse"] = "Gehennas' Curse", + ["基本"] = "Generic", + ["幽魂之狼"] = "Ghost Wolf", + ["鬼魅攻击"] = "Ghostly Strike", + ["Gift of Life"] = "Gift of Life", + ["自然赐福"] = "Gift of Nature", + ["野性赐福"] = "Gift of the Wild", + ["Goblin Dragon Gun"] = "Goblin Dragon Gun", + ["Goblin Sapper Charge"] = "Goblin Sapper Charge", + ["凿击"] = "Gouge", + ["风之优雅图腾"] = "Grace of Air Totem", + ["Grace of the Sunwell"] = "Grace of the Sunwell", + ["Grasping Vines"] = "Grasping Vines", + ["持久耐力"] = "Great Stamina", + ["强效王者祝福"] = "Greater Blessing of Kings", + ["强效光明祝福"] = "Greater Blessing of Light", + ["强效力量祝福"] = "Greater Blessing of Might", + ["强效拯救祝福"] = "Greater Blessing of Salvation", + ["强效庇护祝福"] = "Greater Blessing of Sanctuary", + ["强效智慧祝福"] = "Greater Blessing of Wisdom", + ["强效治疗术"] = "Greater Heal", + ["无情延伸"] = "Grim Reach", + ["Ground Tremor"] = "Ground Tremor", + ["根基图腾"] = "Grounding Totem", + ["匍匐"] = "Grovel", + ["低吼"] = "Growl", + ["守护者的宠爱"] = "Guardian's Favor", + ["Guillotine"] = "Guillotine", + ["枪械专精"] = "Gun Specialization", + ["枪械"] = "Guns", + ["Hail Storm"] = "Hail Storm", + ["制裁之锤"] = "Hammer of Justice", + ["愤怒之锤"] = "Hammer of Wrath", + ["断筋"] = "Hamstring", + ["侵扰"] = "Harass", + ["坚韧"] = "Toughness", + ["Haunting Spirits"] = "Haunting Spirits", + ["鹰眼"] = "Hawk Eye", + ["Head Crack"] = "Head Crack", + ["治疗术"] = "Heal", + ["Healing Circle"] = "Healing Circle", + ["治疗专注"] = "Healing Focus", + ["治疗之光"] = "Healing Light", + ["Healing of the Ages"] = "Healing of the Ages", + ["治疗之泉图腾"] = "Healing Stream Totem", + ["治疗之触"] = "Healing Touch", + ["治疗波"] = "Healing Wave", + ["治疗之道"] = "Healing Way", + ["生命通道"] = "Health Funnel", + ["野性之心"] = "Heart of the Wild", + ["重磨刀石"] = "Heavy Sharpening Stone", + ["地狱烈焰"] = "Hellfire", + ["地狱烈焰效果"] = "Hellfire Effect", + ["出血"] = "Hemorrhage", + ["采集草药"] = "Herb Gathering", + ["草药学"] = "Herbalism", + ["英勇打击"] = "Heroic Strike", + ["Heroism"] = "Heroism", + ["Hex"] = "Hex", + ["Hex of Jammal'an"] = "Hex of Jammal'an", + ["虚弱妖术"] = "Hex of Weakness", + ["休眠"] = "Hibernate", + ["神圣之火"] = "Holy Fire", + ["圣光术"] = "Holy Light", + ["神圣新星"] = "Holy Nova", + ["神圣强化"] = "Holy Power", + ["神圣延伸"] = "Holy Reach", + ["神圣之盾"] = "Holy Shield", + ["神圣震击"] = "Holy Shock", + ["Holy Smite"] = "Holy Smite", + ["神圣专精"] = "Holy Specialization", + ["Holy Strength"] = "Holy Strength", + ["Holy Strike"] = "Holy Strike", + ["神圣愤怒"] = "Holy Wrath", + ["无荣誉目标"] = "Honorless Target", + ["Hooked Net"] = "Hooked Net", + ["骑术:马"] = "Horse Riding", + ["恐惧嚎叫"] = "Howl of Terror", + ["人类精魂"] = "The Human Spirit", + ["人型生物杀手"] = "Humanoid Slaying", + ["猎人印记"] = "Hunter's Mark", + ["飓风"] = "Hurricane", + ["冰甲术"] = "Ice Armor", + ["寒冰护体"] = "Ice Barrier", + ["Ice Blast"] = "Ice Blast", + ["寒冰屏障"] = "Ice Block", + ["Ice Lance"] = "Ice Lance", + ["Ice Nova"] = "Ice Nova", + ["寒冰碎片"] = "Ice Shards", + ["Icicle"] = "Icicle", + ["点燃"] = "Ignite", + ["启发"] = "Illumination", + ["献祭"] = "Immolate", + ["献祭陷阱"] = "Immolation Trap", + ["献祭陷阱效果"] = "Immolation Trap Effect", + ["冲击"] = "Impact", + ["穿刺"] = "Impale", + ["强化伏击"] = "Improved Ambush", + ["强化魔爆术"] = "Improved Arcane Explosion", + ["强化奥术飞弹"] = "Improved Arcane Missiles", + ["强化奥术射击"] = "Improved Arcane Shot", + ["强化雄鹰守护"] = "Improved Aspect of the Hawk", + ["强化灵猴守护"] = "Improved Aspect of the Monkey", + ["强化背刺"] = "Improved Backstab", + ["强化战斗怒吼"] = "Improved Battle Shout", + ["强化狂暴之怒"] = "Improved Berserker Rage", + ["强化力量祝福"] = "Improved Blessing of Might", + ["强化智慧祝福"] = "Improved Blessing of Wisdom", + ["强化暴风雪"] = "Improved Blizzard", + ["强化血性狂暴"] = "Improved Bloodrage", + ["强化治疗链"] = "Improved Chain Heal", + ["强化闪电链"] = "Improved Chain Lightning", + ["强化挑战怒吼"] = "Improved Challenging Shout", + ["强化冲锋"] = "Improved Charge", + ["强化偷袭"] = "Improved Cheap Shot", + ["强化顺劈斩"] = "Improved Cleave", + ["强化专注光环"] = "Improved Concentration Aura", + ["强化震荡射击"] = "Improved Concussive Shot", + ["强化冰锥术"] = "Improved Cone of Cold", + ["强化腐蚀术"] = "Improved Corruption", + ["强化法术反制"] = "Improved Counterspell", + ["强化痛苦诅咒"] = "Improved Curse of Agony", + ["强化疲劳诅咒"] = "Improved Curse of Exhaustion", + ["强化虚弱诅咒"] = "Improved Curse of Weakness", + ["强化魔法抑制"] = "Improved Dampen Magic", + ["强化致命毒药"] = "Improved Deadly Poison", + ["强化挫志怒吼"] = "Improved Demoralizing Shout", + ["强化虔诚光环"] = "Improved Devotion Aura", + ["强化缴械"] = "Improved Disarm", + ["强化扰乱"] = "Improved Distract", + ["强化吸取生命"] = "Improved Drain Life", + ["强化吸取法力"] = "Improved Drain Mana", + ["强化吸取灵魂"] = "Improved Drain Soul", + ["强化狂怒"] = "Improved Enrage", + ["强化奴役恶魔"] = "Improved Enslave Demon", + ["强化纠缠根须"] = "Improved Entangling Roots", + ["强化闪避"] = "Improved Evasion", + ["强化剔骨"] = "Improved Eviscerate", + ["强化斩杀"] = "Improved Execute", + ["强化破甲"] = "Improved Expose Armor", + ["强化野兽之眼"] = "Improved Eyes of the Beast", + ["强化渐隐术"] = "Improved Fade", + ["强化假死"] = "Improved Feign Death", + ["强化火焰冲击"] = "Improved Fire Blast", + ["强化火焰图腾"] = "Improved Fire Nova Totem", + ["强化防护火焰结界"] = "Improved Fire Ward", + ["强化火球术"] = "Improved Fireball", + ["强化火焰箭"] = "Improved Firebolt", + ["强化火焰石"] = "Improved Firestone", + ["强化烈焰冲击"] = "Improved Flamestrike", + ["强化火舌武器"] = "Improved Flametongue Weapon", + ["强化圣光闪现"] = "Improved Flash of Light", + ["强化冰霜新星"] = "Improved Frost Nova", + ["强化防护冰霜结界"] = "Improved Frost Ward", + ["强化寒冰箭"] = "Improved Frostbolt", + ["强化冰封武器"] = "Improved Frostbrand Weapon", + ["强化绞喉"] = "Improved Garrote", + ["强化幽魂之狼"] = "Improved Ghost Wolf", + ["强化凿击"] = "Improved Gouge", + ["强化风之优雅图腾"] = "Improved Grace of Air Totem", + ["强化根基图腾"] = "Improved Grounding Totem", + ["强化制裁之锤"] = "Improved Hammer of Justice", + ["强化断筋"] = "Improved Hamstring", + ["强化治疗术"] = "Improved Healing", + ["强化治疗之泉图腾"] = "Improved Healing Stream Totem", + ["强化治疗之触"] = "Improved Healing Touch", + ["强化治疗波"] = "Improved Healing Wave", + ["强化生命通道"] = "Improved Health Funnel", + ["强化治疗石"] = "Improved Healthstone", + ["强化英勇打击"] = "Improved Heroic Strike", + ["强化猎人印记"] = "Improved Hunter's Mark", + ["强化献祭"] = "Improved Immolate", + ["强化小鬼"] = "Improved Imp", + ["强化心灵之火"] = "Improved Inner Fire", + ["强化速效毒药"] = "Improved Instant Poison", + ["强化拦截"] = "Improved Intercept", + ["强化破胆怒吼"] = "Improved Intimidating Shout", + ["强化审判"] = "Improved Judgement", + ["强化脚踢"] = "Improved Kick", + ["强化肾击"] = "Improved Kidney Shot", + ["强化剧痛鞭笞"] = "Improved Lash of Pain", + ["强化圣疗术"] = "Improved Lay on Hands", + ["强化次级治疗波"] = "Improved Lesser Healing Wave", + ["强化生命分流"] = "Improved Life Tap", + ["强化闪电箭"] = "Improved Lightning Bolt", + ["强化闪电护盾"] = "Improved Lightning Shield", + ["强化熔岩图腾"] = "Improved Magma Totem", + ["强化法力燃烧"] = "Improved Mana Burn", + ["强化法力护盾"] = "Improved Mana Shield", + ["强化法力之泉图腾"] = "Improved Mana Spring Totem", + ["强化野性印记"] = "Improved Mark of the Wild", + ["强化治疗宠物"] = "Improved Mend Pet", + ["强化心灵震爆"] = "Improved Mind Blast", + ["强化月火术"] = "Improved Moonfire", + ["强化自然之握"] = "Improved Nature's Grasp", + ["强化压制"] = "Improved Overpower", + ["强化真言术:韧"] = "Improved Power Word: Fortitude", + ["强化圣言术:盾"] = "Improved Power Word: Shield", + ["强化治疗祷言"] = "Improved Prayer of Healing", + ["强化心灵尖啸"] = "Improved Psychic Scream", + ["强化拳击"] = "Improved Pummel", + ["强化愈合"] = "Improved Regrowth", + ["强化复生"] = "Improved Reincarnation", + ["强化回春"] = "Improved Rejuvenation", + ["强化撕裂"] = "Improved Rend", + ["强化恢复"] = "Improved Renew", + ["强化惩罚光环"] = "Improved Retribution Aura", + ["强化复仇"] = "Improved Revenge", + ["强化复活宠物"] = "Improved Revive Pet", + ["强化正义之怒"] = "Improved Righteous Fury", + ["强化石化武器"] = "Improved Rockbiter Weapon", + ["强化割裂"] = "Improved Rupture", + ["强化闷棍"] = "Improved Sap", + ["强化灼烧"] = "Improved Scorch", + ["强化毒蝎钉刺"] = "Improved Scorpid Sting", + ["强化正义圣印"] = "Improved Seal of Righteousness", + ["强化十字军圣印"] = "Improved Seal of the Crusader", + ["强化灼热之痛"] = "Improved Searing Pain", + ["强化灼热图腾"] = "Improved Searing Totem", + ["强化毒蛇钉刺"] = "Improved Serpent Sting", + ["强化暗影箭"] = "Improved Shadow Bolt", + ["强化暗言术:痛"] = "Improved Shadow Word: Pain", + ["强化盾击"] = "Improved Shield Bash", + ["强化盾牌格挡"] = "Improved Shield Block", + ["强化盾墙"] = "Improved Shield Wall", + ["强化撕碎"] = "Improved Shred", + ["强化邪恶攻击"] = "Improved Sinister Strike", + ["强化猛击"] = "Improved Slam", + ["强化切割"] = "Improved Slice and Dice", + ["强化法术石"] = "Improved Spellstone", + ["强化疾跑"] = "Improved Sprint", + ["强化星火术"] = "Improved Starfire", + ["强化石爪图腾"] = "Improved Stoneclaw Totem", + ["强化石肤图腾"] = "Improved Stoneskin Totem", + ["强化大地之力图腾"] = "Improved Strength of Earth Totem", + ["强化魅魔"] = "Improved Succubus", + ["强化破甲攻击"] = "Improved Sunder Armor", + ["强化嘲讽"] = "Improved Taunt", + ["强化荆棘术"] = "Improved Thorns", + ["强化雷霆一击"] = "Improved Thunder Clap", + ["强化宁静"] = "Improved Tranquility", + ["强化吸血鬼的拥抱"] = "Improved Vampiric Embrace", + ["强化消失"] = "Improved Vanish", + ["强化虚空行者"] = "Improved Voidwalker", + ["强化风怒武器"] = "Improved Windfury Weapon", + ["强化摔绊"] = "Improved Wing Clip", + ["强化愤怒"] = "Improved Wrath", + ["焚烧"] = "Incinerate", + ["Infected Bite"] = "Infected Bite", + ["Infected Wound"] = "Infected Wound", + ["地狱火"] = "Inferno", + ["Inferno Shell"] = "Inferno Shell", + ["先发制人"] = "Initiative", + ["心灵之火"] = "Inner Fire", + ["心灵专注"] = "Inner Focus", + ["激活"] = "Innervate", + ["虫群"] = "Insect Swarm", + ["灵感"] = "Inspiration", + ["速效毒药"] = "Instant Poison", + ["速效毒药 II"] = "Instant Poison II", + ["速效毒药 III"] = "Instant Poison III", + ["速效毒药 IV"] = "Instant Poison IV", + ["速效毒药 V"] = "Instant Poison V", + ["速效毒药 VI"] = "Instant Poison VI", + ["强烈"] = "Intensity", + ["拦截"] = "Intercept", + ["拦截昏迷"] = "Intercept Stun", + ["Intervene"] = "Intervene", + ["Intimidating Roar"] = "Intimidating Roar", + ["破胆怒吼"] = "Intimidating Shout", + ["胁迫"] = "Intimidation", + ["Intoxicating Venom"] = "Intoxicating Venom", + ["Invisibility"] = "Invisibility", + ["Iron Will"] = "Iron Will", + ["Jewelcrafting"] = "Jewelcrafting", + ["审判"] = "Judgement", + ["命令审判"] = "Judgement of Command", + ["公正审判"] = "Judgement of Justice", + ["光明审判"] = "Judgement of Light", + ["正义审判"] = "Judgement of Righteousness", + ["十字军审判"] = "Judgement of the Crusader", + ["智慧审判"] = "Judgement of Wisdom", + ["脚踢"] = "Kick", + ["脚踢 - 沉默"] = "Kick - Silenced", + ["肾击"] = "Kidney Shot", + ["Kill Command"] = "Kill Command", + ["杀戮本能"] = "Killer Instinct", + ["Knock Away"] = "Knock Away", + ["Knockdown"] = "Knockdown", + ["骑术:科多兽"] = "Kodo Riding", + ["Lacerate"] = "Lacerate", + ["Larva Goo"] = "Larva Goo", + ["Lash"] = "Lash", + ["剧痛鞭笞"] = "Lash of Pain", + ["破釜沉舟"] = "Last Stand", + ["持久审判"] = "Lasting Judgement", + ["Lava Spout Totem"] = "Lava Spout Totem", + ["圣疗术"] = "Lay on Hands", + ["兽群领袖"] = "Leader of the Pack", + ["皮甲"] = "Leather", + ["制皮"] = "Leatherworking", + ["Leech Poison"] = "Leech Poison", + ["次级治疗术"] = "Lesser Heal", + ["次级治疗波"] = "Lesser Healing Wave", + ["次级隐形术"] = "Lesser Invisibility", + ["夺命射击"] = "Lethal Shots", + ["致命偷袭"] = "Lethality", + ["漂浮"] = "Levitate", + ["圣物"] = "Libram", + ["Lich Slap"] = "Lich Slap", + ["生命分流"] = "Life Tap", + ["Lifebloom"] = "Lifebloom", + ["Lifegiving Gem"] = "Lifegiving Gem", + ["Lightning Blast"] = "Lightning Blast", + ["闪电箭"] = "Lightning Bolt", + ["闪电吐息"] = "Lightning Breath", + ["Lightning Cloud"] = "Lightning Cloud", + ["闪电掌握"] = "Lightning Mastery", + ["闪电反射"] = "Lightning Reflexes", + ["闪电护盾"] = "Lightning Shield", + ["Lightning Wave"] = "Lightning Wave", + ["光明之泉"] = "Lightwell", + ["光明之泉回复"] = "Lightwell Renew", + ["Lizard Bolt"] = "Lizard Bolt", + ["Localized Toxin"] = "Localized Toxin", + ["开锁"] = "Pick Lock", + ["长时间眩晕"] = "Long Daze", + ["锤类武器专精"] = "Mace Specialization", + ["锤击昏迷效果"] = "Mace Stun Effect", + ["Machine Gun"] = "Machine Gun", + ["Magic Attunement"] = "Magic Attunement", + ["Magma Splash"] = "Magma Splash", + ["熔岩图腾"] = "Magma Totem", + ["锁甲"] = "Mail", + ["Maim"] = "Maim", + ["恶意"] = "Malice", + ["法力燃烧"] = "Mana Burn", + ["Mana Feed"] = "Mana Feed", + ["法力护盾"] = "Mana Shield", + ["法力之泉图腾"] = "Mana Spring Totem", + ["法力之潮图腾"] = "Mana Tide Totem", + ["割碎"] = "Mangle", + ["Mangle (Bear)"] = "Mangle (Bear)", + ["Mangle (Cat)"] = "Mangle (Cat)", + ["Mark of Arlokk"] = "Mark of Arlokk", + ["野性印记"] = "Mark of the Wild", + ["殉难"] = "Martyrdom", + ["Mass Dispel"] = "Mass Dispel", + ["恶魔学识大师"] = "Master Demonologist", + ["欺诈高手"] = "Master of Deception", + ["Master of Elements"] = "Master of Elements", + ["召唤大师"] = "Master Summoner", + ["槌击"] = "Maul", + ["骑术:机械陆行鸟"] = "Mechanostrider Piloting", + ["冥想"] = "Meditation", + ["Megavolt"] = "Megavolt", + ["近战专精"] = "Melee Specialization", + ["Melt Ore"] = "Melt Ore", + ["治疗宠物"] = "Mend Pet", + ["精神敏锐"] = "Mental Agility", + ["心灵之力"] = "Mental Strength", + ["Mighty Blow"] = "Mighty Blow", + ["心灵震爆"] = "Mind Blast", + ["精神控制"] = "Mind Control", + ["精神鞭笞"] = "Mind Flay", + ["安抚心灵"] = "Mind Soothe", + ["Mind Tremor"] = "Mind Tremor", + ["心灵视界"] = "Mind Vision", + ["麻痹毒药"] = "Mind-numbing Poison", + ["麻痹毒药 II"] = "Mind-numbing Poison II", + ["麻痹毒药 III"] = "Mind-numbing Poison III", + ["采矿"] = "Mining", + ["Misdirection"] = "Misdirection", + ["惩戒痛击"] = "Mocking Blow", + ["Molten Armor"] = "Molten Armor", + ["Molten Blast"] = "Molten Blast", + ["Molten Metal"] = "Molten Metal", + ["猫鼬撕咬"] = "Mongoose Bite", + ["怪物杀手"] = "Monster Slaying", + ["月火术"] = "Moonfire", + ["月怒"] = "Moonfury", + ["月光"] = "Moonglow", + ["枭兽光环"] = "Moonkin Aura", + ["枭兽形态"] = "Moonkin Form", + ["Mortal Cleave"] = "Mortal Cleave", + ["致死射击"] = "Mortal Shots", + ["致死打击"] = "Mortal Strike", + ["Mortal Wound"] = "Mortal Wound", + ["多重射击"] = "Multi-Shot", + ["谋杀"] = "Murder", + ["Mutilate"] = "Mutilate", + ["Naralex's Nightmare"] = "Naralex's Nightmare", + ["自然护甲"] = "Natural Armor", + ["自然变形"] = "Natural Shapeshifter", + ["武器平衡"] = "Natural Weapons", + ["Nature Aligned"] = "Nature Aligned", + ["自然抗性"] = "Nature Resistance", + ["自然抗性图腾"] = "Nature Resistance Totem", + ["Nature Weakness"] = "Nature Weakness", + ["自然集中"] = "Nature's Focus", + ["自然之赐"] = "Nature's Grace", + ["自然之握"] = "Nature's Grasp", + ["自然延伸"] = "Nature's Reach", + ["自然迅捷"] = "Nature's Swiftness", + ["Necrotic Poison"] = "Necrotic Poison", + ["Negative Charge"] = "Negative Charge", + ["Net"] = "Net", + ["夜幕"] = "Nightfall", + ["Noxious Catalyst"] = "Noxious Catalyst", + ["Noxious Cloud"] = "Noxious Cloud", + ["清晰预兆"] = "Omen of Clarity", + ["单手斧"] = "One-Handed Axes", + ["单手锤"] = "One-Handed Maces", + ["单手剑"] = "One-Handed Swords", + ["单手武器专精"] = "One-Handed Weapon Specialization", + ["打开"] = "Opening", + ["打开 - No Text"] = "Opening - No Text", + ["伺机而动"] = "Opportunity", + ["压制"] = "Suppression", + ["Pacify"] = "Pacify", + ["Pain Suppression"] = "Pain Suppression", + ["Paralyzing Poison"] = "Paralyzing Poison", + ["多疑"] = "Paranoia", + ["Parasitic Serpent"] = "Parasitic Serpent", + ["招架"] = "Parry", + ["寻路"] = "Pathfinding", + ["感知"] = "Perception", + ["极寒冰霜"] = "Permafrost", + ["宠物好斗"] = "Pet Aggression", + ["宠物耐久"] = "Pet Hardiness", + ["宠物恢复"] = "Pet Recovery", + ["宠物抗魔"] = "Pet Resistance", + ["Petrify"] = "Petrify", + ["相位变换"] = "Phase Shift", + ["偷窃"] = "Pick Pocket", + ["Pierce Armor"] = "Pierce Armor", + ["刺耳怒吼"] = "Piercing Howl", + ["刺骨寒冰"] = "Piercing Ice", + ["Piercing Shadow"] = "Piercing Shadow", + ["Piercing Shot"] = "Piercing Shot", + ["Plague Cloud"] = "Plague Cloud", + ["板甲"] = "Plate Mail", + ["Poison"] = "Poison", + ["Poison Bolt"] = "Poison Bolt", + ["Poison Bolt Volley"] = "Poison Bolt Volley", + ["Poison Cloud"] = "Poison Cloud", + ["Poison Shock"] = "Poison Shock", + ["Poisoned Harpoon"] = "Poisoned Harpoon", + ["Poisoned Shot"] = "Poisoned Shot", + ["Poisonous Blood"] = "Poisonous Blood", + ["毒药"] = "Poisons", + ["长柄武器专精"] = "Polearm Specialization", + ["长柄武器"] = "Polearms", + ["变形术"] = "Polymorph", + ["变形术:猪"] = "Polymorph: Pig", + ["变形术:龟"] = "Polymorph: Turtle", + ["传送门:达纳苏斯"] = "Portal: Darnassus", + [" 传送门:铁炉堡"] = "Portal: Ironforge", + ["传送门:奥格瑞玛"] = "Portal: Orgrimmar", + ["传送门:暴风城"] = "Portal: Stormwind", + ["传送门:雷霆崖"] = "Portal: Thunder Bluff", + ["传送门:幽暗城"] = "Portal: Undercity", + ["Positive Charge"] = "Positive Charge", + ["突袭"] = "Pounce Bleed", + ["能量灌注"] = "Power Infusion", + ["真言术:韧"] = "Power Word: Fortitude", + ["真言术:盾"] = "Power Word: Shield", + ["Prayer Beads Blessing"] = "Prayer Beads Blessing", + ["坚韧祷言"] = "Prayer of Fortitude", + ["治疗祷言"] = "Prayer of Healing", + ["Prayer of Mending"] = "Prayer of Mending", + ["暗影防护祷言"] = "Prayer of Shadow Protection", + ["精神祷言"] = "Prayer of Spirit", + ["精确"] = "Precision", + ["猛兽攻击"] = "Predatory Strikes", + ["预谋"] = "Premeditation", + ["伺机待发"] = "Preparation", + ["气定神闲"] = "Presence of Mind", + ["原始狂怒"] = "Primal Fury", + ["潜伏"] = "Prowl", + ["心灵尖啸"] = "Psychic Scream", + ["拳击"] = "Pummel", + ["Puncture"] = "Puncture", + ["净化术"] = "Purge", + ["净化"] = "Purification", + ["纯净术"] = "Purify", + ["正义追击"] = "Pursuit of Justice", + ["Putrid Breath"] = "Putrid Breath", + ["Putrid Enzyme"] = "Putrid Enzyme", + ["炎爆术"] = "Pyroblast", + ["火焰冲撞"] = "Pyroclasm", + ["快速射击"] = "Quick Shots", + ["迅捷"] = "Quickness", + ["Radiation"] = "Radiation", + ["Radiation Bolt"] = "Radiation Bolt", + ["Radiation Cloud"] = "Radiation Cloud", + ["Radiation Poisoning"] = "Radiation Poisoning", + ["火焰之雨"] = "Rain of Fire", + ["扫击"] = "Rake", + ["骑术:羊"] = "Ram Riding", + ["Rampage"] = "Rampage", + ["远程武器专精"] = "Ranged Weapon Specialization", + ["迅速隐蔽"] = "Rapid Concealment", + ["急速射击"] = "Rapid Fire", + ["骑术:迅猛龙"] = "Raptor Riding", + ["猛禽一击"] = "Raptor Strike", + ["Ravenous Claw"] = "Ravenous Claw", + ["准备就绪"] = "Readiness", + ["复生"] = "Reincarnation", + ["Rebuild"] = "Rebuild", + ["Recently Bandaged"] = "Recently Bandaged", + ["无畏冲锋"] = "Reckless Charge", + ["鲁莽"] = "Recklessness", + ["Recombobulate"] = "Recombobulate", + ["救赎"] = "Redemption", + ["盾牌壁垒"] = "Redoubt", + ["反射"] = "Reflection", + ["回复"] = "Regeneration", + ["愈合"] = "Regrowth", + ["回春术"] = "Rejuvenation", + ["无情打击"] = "Relentless Strikes", + ["冷酷"] = "Remorseless", + ["冷酷攻击"] = "Remorseless Attacks", + ["解除诅咒"] = "Remove Curse", + ["解除徽记"] = "Remove Insignia", + ["解除次级诅咒"] = "Remove Lesser Curse", + ["撕裂"] = "Rend", + ["恢复"] = "Renew", + ["忏悔"] = "Repentance", + ["Repulsive Gaze"] = "Repulsive Gaze", + ["Restorative Totems"] = "Restorative Totems", + ["复活"] = "Resurrection", + ["反击风暴"] = "Retaliation", + ["惩罚光环"] = "Retribution Aura", + ["复仇"] = "Vengeance", + ["复仇昏迷"] = "Revenge Stun", + ["回响"] = "Reverberation", + ["复活宠物"] = "Revive Pet", + ["Rhahk'Zor Slam"] = "Rhahk'Zor Slam", + ["Ribbon of Souls"] = "Ribbon of Souls", + ["Righteous Defense"] = "Righteous Defense", + ["正义之怒"] = "Righteous Fury", + ["撕扯"] = "Rip", + ["还击"] = "Riposte", + ["末日仪式"] = "Ritual of Doom", + ["末日仪式效果"] = "Ritual of Doom Effect", + ["Ritual of Souls"] = "Ritual of Souls", + ["召唤仪式"] = "Ritual of Summoning", + ["石化武器"] = "Rockbiter Weapon", + ["盗贼被动效果"] = "Rogue Passive", + ["劣质磨刀石"] = "Rough Sharpening Stone", + ["割裂"] = "Rupture", + ["无情"] = "Ruthlessness", + ["牺牲"] = "Sacrifice", + ["安全降落"] = "Safe Fall", + ["圣洁光环"] = "Sanctity Aura", + ["闷棍"] = "Sap", + ["野蛮暴怒"] = "Savage Fury", + ["野蛮打击"] = "Savage Strikes", + ["恐吓野兽"] = "Scare Beast", + ["驱散射击"] = "Scatter Shot", + ["灼烧"] = "Scorch", + ["蝎毒"] = "Scorpid Poison", + ["毒蝎钉刺"] = "Scorpid Sting", + ["Screams of the Past"] = "Screams of the Past", + ["尖啸"] = "Screech", + ["封印命运"] = "Seal Fate", + ["Seal of Blood"] = "Seal of Blood", + ["命令圣印"] = "Seal of Command", + ["公正圣印"] = "Seal of Justice", + ["光明圣印"] = "Seal of Light", + ["Seal of Reckoning"] = "Seal of Reckoning", + ["正义圣印"] = "Seal of Righteousness", + ["十字军圣印"] = "Seal of the Crusader", + ["Seal of Vengeance"] = "Seal of Vengeance", + ["智慧圣印"] = "Seal of Wisdom", + ["灼热之光"] = "Searing Light", + ["灼热之痛"] = "Searing Pain", + ["灼热图腾"] = "Searing Totem", + ["Second Wind"] = "Second Wind", + ["诱惑"] = "Seduction", + ["Seed of Corruption"] = "Seed of Corruption", + ["感知恶魔"] = "Sense Demons", + ["感知亡灵"] = "Sense Undead", + ["岗哨图腾"] = "Sentry Totem", + ["毒蛇钉刺"] = "Serpent Sting", + ["调整"] = "Setup", + ["束缚亡灵"] = "Shackle Undead", + ["暗影亲和"] = "Shadow Affinity", + ["暗影箭"] = "Shadow Bolt", + ["Shadow Bolt Volley"] = "Shadow Bolt Volley", + ["暗影集中"] = "Shadow Focus", + ["暗影掌握"] = "Shadow Mastery", + ["暗影防护"] = "Shadow Protection", + ["暗影延伸"] = "Shadow Reach", + ["暗影抗性"] = "Shadow Resistance", + ["暗影抗性光环"] = "Shadow Resistance Aura", + ["Shadow Shock"] = "Shadow Shock", + ["暗影冥思"] = "Shadow Trance", + ["暗影易伤"] = "Shadow Vulnerability", + ["防护暗影结界"] = "Shadow Ward", + ["Shadow Weakness"] = "Shadow Weakness", + ["暗影之波"] = "Shadow Weaving", + ["Shadow Word: Death"] = "Shadow Word: Death", + ["暗言术:痛"] = "Shadow Word: Pain", + ["暗影灼烧"] = "Shadowburn", + ["Shadowfiend"] = "Shadowfiend", + ["暗影形态"] = "Shadowform", + ["Shadowfury"] = "Shadowfury", + ["暗影守卫"] = "Shadowguard", + ["影遁"] = "Shadowmeld Passive", + ["Shadowstep"] = "Shadowstep", + ["Shamanistic Rage"] = "Shamanistic Rage", + ["锋利兽爪"] = "Sharpened Claws", + ["碎冰"] = "Shatter", + ["Sheep"] = "Sheep", + ["甲壳护盾"] = "Shell Shield", + ["盾牌"] = "Shield", + ["盾击"] = "Shield Bash", + ["盾击 - 沉默"] = "Shield Bash - Silenced", + ["盾牌格挡"] = "Shield Block", + ["盾牌猛击"] = "Shield Slam", + ["盾牌专精"] = "Shield Specialization", + ["盾墙"] = "Shield Wall", + ["Shiv"] = "Shiv", + ["Shock"] = "Shock", + ["射击"] = "Shoot", + ["弓射击"] = "Shoot Bow", + ["弩射击"] = "Shoot Crossbow", + ["枪械射击"] = "Shoot Gun", + ["撕碎"] = "Shred", + ["Shrink"] = "Shrink", + ["沉默"] = "Silence", + ["Silencing Shot"] = "Silencing Shot", + ["无声消退"] = "Silent Resolve", + ["邪恶攻击"] = "Sinister Strike", + ["生命虹吸"] = "Siphon Life", + ["剥皮"] = "Skinning", + ["Skull Crack"] = "Skull Crack", + ["猛击"] = "Slam", + ["沉睡"] = "Sleep", + ["切割"] = "Slice and Dice", + ["Slow"] = "Slow", + ["缓落术"] = "Slow Fall", + ["Slowing Poison"] = "Slowing Poison", + ["熔炼"] = "Smelting", + ["惩击"] = "Smite", + ["Smite Slam"] = "Smite Slam", + ["Smite Stomp"] = "Smite Stomp", + ["Smoke Bomb"] = "Smoke Bomb", + ["Snake Trap"] = "Snake Trap", + ["Snap Kick"] = "Snap Kick", + ["坚固的磨刀石"] = "Solid Sharpening Stone", + ["Sonic Burst"] = "Sonic Burst", + ["安抚动物"] = "Soothe Animal", + ["安抚之吻"] = "Soothing Kiss", + ["Soul Bite"] = "Soul Bite", + ["Soul Drain"] = "Soul Drain", + ["灵魂之火"] = "Soul Fire", + ["灵魂链接"] = "Soul Link", + ["灵魂虹吸"] = "Soul Siphon", + ["Soul Tap"] = "Soul Tap", + ["Soulshatter"] = "Soulshatter", + ["灵魂石复活"] = "Soulstone Resurrection", + ["法术封锁"] = "Spell Lock", + ["Spell Reflection"] = "Spell Reflection", + ["法术屏障"] = "Spell Warding", + ["Spellsteal"] = "Spellsteal", + ["灵魂连接"] = "Spirit Bond", + ["Spirit Burst"] = "Spirit Burst", + ["救赎之魂"] = "Spirit of Redemption", + ["精神分流"] = "Spirit Tap", + ["Spiritual Attunement"] = "Spiritual Attunement", + ["精神集中"] = "Spiritual Focus", + ["精神指引"] = "Spiritual Guidance", + ["精神治疗"] = "Spiritual Healing", + ["Spit"] = "Spit", + ["Spore Cloud"] = "Spore Cloud", + ["疾跑"] = "Sprint", + ["Stance Mastery"] = "Stance Mastery", + ["星火术"] = "Starfire", + ["星火昏迷"] = "Starfire Stun", + ["星辰碎片"] = "Starshards", + ["法杖"] = "Staves", + ["Steady Shot"] = "Steady Shot", + ["潜行"] = "Stealth", + ["石爪图腾"] = "Stoneclaw Totem", + ["石像形态"] = "Stoneform", + ["石肤图腾"] = "Stoneskin Totem", + ["风暴打击"] = "Stormstrike", + ["大地之力图腾"] = "Strength of Earth Totem", + ["Strike"] = "Strike", + ["卡死"] = "Stuck", + ["Stun"] = "Stun", + ["微妙"] = "Subtlety", + ["受难"] = "Suffering", + ["召唤战马"] = "Summon Charger", + ["召唤恐惧战马"] = "Summon Dreadsteed", + ["Summon Felguard"] = "Summon Felguard", + ["召唤地狱猎犬"] = "Summon Felhunter", + ["召唤地狱战马"] = "Summon Felsteed", + ["召唤小鬼"] = "Summon Imp", + ["Summon Spawn of Bael'Gar"] = "Summon Spawn of Bael'Gar", + ["召唤魅魔"] = "Summon Succubus", + ["召唤虚空行者"] = "Summon Voidwalker", + ["召唤军马"] = "Summon Warhorse", + ["Summon Water Elemental"] = "Summon Water Elemental", + ["稳固"] = "Surefooted", + ["生存专家"] = "Survivalist", + ["Sweeping Slam"] = "Sweeping Slam", + ["横扫攻击"] = "Sweeping Strikes", + ["迅捷治愈"] = "Swiftmend", + ["挥击"] = "Swipe", + ["Swoop"] = "Swoop", + ["剑类武器专精"] = "Sword Specialization", + ["战术掌握"] = "Tactical Mastery", + ["裁缝"] = "Tailoring", + ["腐坏之血"] = "Tainted Blood", + ["驯服野兽"] = "Tame Beast", + ["驯服宠物(被动)"] = "Tamed Pet Passive", + ["嘲讽"] = "Taunt", + ["传送:达纳苏斯"] = "Teleport: Darnassus", + ["传送:铁炉堡"] = "Teleport: Ironforge", + ["传送:月光林地"] = "Teleport: Moonglade", + ["传送:奥格瑞玛"] = "Teleport: Orgrimmar", + ["传送:暴风城"] = "Teleport: Stormwind", + ["传送:雷霆崖"] = "Teleport: Thunder Bluff", + ["传送:幽暗城"] = "Teleport: Undercity", + ["Tendon Rip"] = "Tendon Rip", + ["Tendon Slice"] = "Tendon Slice", + ["Terrify"] = "Terrify", + ["Terrifying Screech"] = "Terrifying Screech", + ["厚皮"] = "Thick Hide", + ["Thorn Volley"] = "Thorn Volley", + ["荆棘术"] = "Thorns", + ["Thousand Blades"] = "Thousand Blades", + ["Threatening Gaze"] = "Threatening Gaze", + ["投掷"] = "Thrown", + ["Throw Axe"] = "Throw Axe", + ["Throw Dynamite"] = "Throw Dynamite", + ["Throw Liquid Fire"] = "Throw Liquid Fire", + ["Throw Wrench"] = "Throw Wrench", + ["投掷专精"] = "Throwing Specialization", + ["投掷武器专精"] = "Throwing Weapon Specialization", + ["雷霆一击"] = "Thunder Clap", + ["Thunderclap"] = "Thunderclap", + ["Thunderfury"] = "Thunderfury", + ["雷鸣猛击"] = "Thundering Strikes", + ["Thundershock"] = "Thundershock", + ["雷霆践踏"] = "Thunderstomp", + ["潮汐集中"] = "Tidal Focus", + ["潮汐掌握"] = "Tidal Mastery", + ["骑术:豹"] = "Tiger Riding", + ["猛虎之怒"] = "Tiger's Fury", + ["折磨"] = "Torment", + ["图腾"] = "Totem", + ["Totem of Wrath"] = "Totem of Wrath", + ["图腾集中"] = "Totemic Focus", + ["虚弱之触"] = "Touch of Weakness", + ["Toxic Saliva"] = "Toxic Saliva", + ["Toxic Spit"] = "Toxic Spit", + ["Toxic Volley"] = "Toxic Volley", + ["Traces of Silithyst"] = "Traces of Silithyst", + ["追踪野兽"] = "Track Beasts", + ["追踪恶魔"] = "Track Demons", + ["追踪龙类"] = "Track Dragonkin", + ["追踪元素生物"] = "Track Elementals", + ["追踪巨人"] = "Track Giants", + ["追踪隐藏生物"] = "Track Hidden", + ["追踪人型生物"] = "Track Humanoids", + ["追踪亡灵"] = "Track Undead", + ["Trample"] = "Trample", + ["宁静之风图腾"] = "Tranquil Air Totem", + ["宁静之魂"] = "Tranquil Spirit", + ["宁静"] = "Tranquility", + ["Tranquilizing Poison"] = "Tranquilizing Poison", + ["宁神射击"] = "Tranquilizing Shot", + ["陷阱掌握"] = "Trap Mastery", + ["旅行形态"] = "Travel Form", + ["Tree of Life"] = "Tree of Life", + ["战栗图腾"] = "Tremor Totem", + ["部族制皮"] = "Tribal Leatherworking", + ["强击光环"] = "Trueshot Aura", + ["超度亡灵"] = "Turn Undead", + ["Twisted Tranquility"] = "Twisted Tranquility", + ["双手斧"] = "Two-Handed Axes", + ["双手斧和锤"] = "Two-Handed Axes and Maces", + ["双手锤"] = "Two-Handed Maces", + ["无光泽的双刃刀"] = "Two-Handed Swords", + ["双手武器专精"] = "Two-Handed Weapon Specialization", + ["徒手"] = "Unarmed", + ["坚定意志"] = "Unbreakable Will", + ["怒不可遏"] = "Unbridled Wrath", + ["Unbridled Wrath Effect"] = "Unbridled Wrath Effect", + ["骑术:骸骨战马"] = "Undead Horsemanship", + ["水下呼吸"] = "Water Breathing", + ["魔息术"] = "Unending Breath", + ["Unholy Frenzy"] = "Unholy Frenzy", + ["邪恶强化"] = "Unholy Power", + ["狂怒释放"] = "Unleashed Fury", + ["Unleashed Rage"] = "Unleashed Rage", + ["Unstable Affliction"] = "Unstable Affliction", + ["Unstable Concoction"] = "Unstable Concoction", + ["Unstable Power"] = "Unstable Power", + ["不灭信仰"] = "Unyielding Faith", + ["Uppercut"] = "Uppercut", + ["吸血鬼的拥抱"] = "Vampiric Embrace", + ["Vampiric Touch"] = "Vampiric Touch", + ["消失"] = "Vanished", + ["Veil of Shadow"] = "Veil of Shadow", + ["Venom Spit"] = "Venom Spit", + ["Venom Sting"] = "Venom Sting", + ["Venomhide Poison"] = "Venomhide Poison", + ["Vicious Rend"] = "Vicious Rend", + ["Victory Rush"] = "Victory Rush", + ["精力"] = "Vigor", + ["恶性毒药"] = "Vile Poisons", + ["辩护"] = "Vindication", + ["蝰蛇钉刺"] = "Viper Sting", + ["Virulent Poison"] = "Virulent Poison", + ["Void Bolt"] = "Void Bolt", + ["乱射"] = "Volley", + ["Walking Bomb Effect"] = "Walking Bomb Effect", + ["魔杖掌握"] = "Wand Specialization", + ["Wandering Plague"] = "Wandering Plague", + ["魔杖"] = "Wands", + ["战争践踏"] = "War Stomp", + ["Water"] = "Water", + ["Water Shield"] = "Water Shield", + ["水上行走"] = "Water Walking", + ["Waterbolt"] = "Waterbolt", + ["Wavering Will"] = "Wavering Will", + ["虚弱灵魂"] = "Weakened Soul", + ["武器锻造师"] = "Weaponsmith", + ["Web"] = "Web", + ["Web Explosion"] = "Web Explosion", + ["Web Spin"] = "Web Spin", + ["Web Spray"] = "Web Spray", + ["Whirling Barrage"] = "Whirling Barrage", + ["Whirling Trip"] = "Whirling Trip", + ["旋风斩"] = "Whirlwind", + ["Wide Slash"] = "Wide Slash", + ["Will of Hakkar"] = "Will of Hakkar", + ["亡灵意志"] = "Will of the Forsaken", + ["风怒图腾"] = "Windfury Totem", + ["风怒武器"] = "Windfury Weapon", + ["Windsor's Frenzy"] = "Windsor's Frenzy", + ["风墙图腾"] = "Windwall Totem", + ["摔绊"] = "Wing Clip", + ["Wing Flap"] = "Wing Flap", + ["深冬之寒"] = "Winter's Chill", + ["精灵之魂"] = "Wisp Spirit", + ["骑术:狼"] = "Wolf Riding", + ["致伤毒药"] = "Wound Poison", + ["致伤毒药 II"] = "Wound Poison II", + ["致伤毒药 III"] = "Wound Poison III", + ["致伤毒药 IV"] = "Wound Poison IV", + ["愤怒"] = "Wrath", + ["Wrath of Air Totem"] = "Wrath of Air Totem", + ["翼龙钉刺"] = "Wyvern Sting", +} + +end) +__bundle_register("Locale/zhCN/Spell.zhCN.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +return { + -- Turtle 1.17 + ["Aspect of the Wolf"] = "孤狼守护", + ["Trueshot"] = "稳固射击", + + -- Vanilla 1.12 + ["Abolish Disease"] = "驱除疾病", + ["Abolish Poison"] = "驱毒术", + ["Abolish Poison Effect"] = "驱毒术效果", + ["Acid Breath"] = "Acid Breath",-- TODO translate + ["Acid of Hakkar"] = "Acid of Hakkar",-- TODO translate + ["Acid Spit"] = "Acid Spit",-- TODO translate + ["Acid Splash"] = "Acid Splash",-- TODO translate + ["Activate MG Turret"] = "速射炮台", + ["Adrenaline Rush"] = "冲动", + ["Aftermath"] = "清算", + ["Aggression"] = "侵略", + ["Aimed Shot"] = "瞄准射击", + ["Alchemy"] = "炼金术", + ["Ambush"] = "伏击", + ["Amplify Curse"] = "诅咒增幅", + ["Amplify Damage"] = "Amplify Damage",-- TODO translate + ["Amplify Flames"] = "Amplify Flames",-- TODO translate + ["Amplify Magic"] = "魔法增效", + ["Ancestral Fortitude"] = "先祖坚韧", + ["Ancestral Healing"] = "先祖治疗", + ["Ancestral Knowledge"] = "先祖知识", + ["Ancestral Spirit"] = "先祖之魂", + ["Anesthetic Poison"] = "Anesthetic Poison",-- TODO translate + ["Anger Management"] = "愤怒掌控", + ["Anguish"] = "Anguish",-- TODO translate + ["Anticipation"] = "预知", + ["Aqua Jet"] = "Aqua Jet",-- TODO translate + ["Aquatic Form"] = "水栖形态", + ["Arcane Blast"] = "Arcane Blast",-- TODO translate + ["Arcane Bolt"] = "Arcane Bolt",-- TODO translate + ["Arcane Brilliance"] = "奥术光辉", + ["Arcane Concentration"] = "奥术专注", + ["Arcane Explosion"] = "魔爆术", + ["Arcane Focus"] = "奥术集中", + ["Arcane Instability"] = "奥术增效", + ["Arcane Intellect"] = "奥术智慧", + ["Arcane Meditation"] = "奥术冥想", + ["Arcane Mind"] = "奥术心智", + ["Arcane Missiles"] = "奥术飞弹", + ["Arcane Potency"] = "Arcane Potency", + ["Arcane Power"] = "奥术强化", + ["Arcane Resistance"] = "奥术抗性", + ["Arcane Shot"] = "奥术射击", + ["Arcane Subtlety"] = "奥术精妙", + ["Arcane Weakness"] = "Arcane Weakness", + ["Arcing Smash"] = "Arcing Smash",-- TODO translate + ["Arctic Reach"] = "极寒延伸", + ["Armorsmith"] = "护甲锻造师", + ["Arugal's Curse"] = "Arugal's Curse",-- TODO translate + ["Arugal's Gift"] = "Arugal's Gift",-- TODO translate + ["Ascendance"] = "Ascendance",-- TODO translate + ["Aspect of Arlokk"] = "Aspect of Arlokk",-- TODO translate + ["Aspect of Jeklik"] = "Aspect of Jeklik",-- TODO translate + ["Aspect of Mar'li"] = "Aspect of Mar'li",-- TODO translate + ["Aspect of the Beast"] = "野兽守护", + ["Aspect of the Cheetah"] = "猎豹守护", + ["Aspect of the Hawk"] = "雄鹰守护", + ["Aspect of the Monkey"] = "灵猴守护", + ["Aspect of the Pack"] = "豹群守护", + ["Aspect of the Viper"] = "Aspect of the Viper",-- TODO translate + ["Aspect of the Wild"] = "野性守护", + ["Aspect of Venoxis"] = "Aspect of Venoxis",-- TODO translate + ["Astral Recall"] = "星界传送", + ["Attack"] = "攻击", + ["Attacking"] = "攻击", + ["Aura of Command"] = "Aura of Command",-- TODO translate + ["Aural Shock"] = "Aural Shock",-- TODO translate + ["Auto Shot"] = "自动射击", + ["Avenger's Shield"] = "Avenger's Shield",-- TODO translate + ["Avenging Wrath"] = "Avenging Wrath",-- TODO translate + ["Avoidance"] = "Avoidance", + ["Axe Flurry"] = "Axe Flurry",-- TODO translate + ["Axe Specialization"] = "斧专精", + ["Axe Toss"] = "Axe Toss",-- TODO translate + ["Backhand"] = "Backhand",-- TODO translate + ["Backlash"] = "Backlash",-- TODO translate + ["Backstab"] = "背刺", + ["Bane"] = "灾祸", + ["Baneful Poison"] = "Baneful Poison",-- TODO translate + ["Banish"] = "放逐术", + ["Banshee Curse"] = "Banshee Curse",-- TODO translate + ["Banshee Shriek"] = "Banshee Shriek",-- TODO translate + ["Barbed Sting"] = "Barbed Sting",-- TODO translate + ["Barkskin"] = "树皮术", + ["Barkskin Effect"] = "树皮术效果", + ["Barrage"] = "弹幕", + ["Bash"] = "重击", + ["Basic Campfire"] = "基础营火", + ["Battle Shout"] = "战斗怒吼", + ["Battle Stance"] = "战斗姿态", + ["Battle Stance Passive"] = "战斗姿态(被动)", + ["Bear Form"] = "熊形态", + ["Beast Lore"] = "野兽知识", + ["Beast Slaying"] = "野兽杀手", + ["Beast Training"] = "训练野兽", + ["The Beast Within"] = "The Beast Within",-- TODO translate + ["Befuddlement"] = "Befuddlement",-- TODO translate + ["Benediction"] = "祈福", + ["Berserker Charge"] = "Berserker Charge",-- TODO translate + ["Berserker Rage"] = "狂暴之怒", + ["Berserker Stance"] = "狂暴姿态", + ["Berserker Stance Passive"] = "狂暴姿态(被动)", + ["Berserking"] = "狂暴", + ["Bestial Discipline"] = "野兽戒律", + ["Bestial Swiftness"] = "野兽迅捷", + ["Bestial Wrath"] = "狂野怒火", + ["Biletoad Infection"] = "Biletoad Infection",-- TODO translate + ["Binding Heal"] = "Binding Heal",-- TODO translate + ["Bite"] = "撕咬", + ["Black Arrow"] = "黑箭", + ["Blackout"] = "昏阙", + ["Blacksmithing"] = "锻造", + ["Blade Flurry"] = "剑刃乱舞", + ["Blast Wave"] = "冲击波", + ["Blaze"] = "Blaze",-- TODO translate + ["Blazing Speed"] = "Blazing Speed",-- TODO translate + ["Blessed Recovery"] = "神恩回复", + ["Blessing of Blackfathom"] = "Blessing of Blackfathom",-- TODO translate + ["Blessing of Freedom"] = "自由祝福", + ["Blessing of Kings"] = "王者祝福", + ["Blessing of Light"] = "光明祝福", + ["Blessing of Might"] = "力量祝福", + ["Blessing of Protection"] = "保护祝福", + ["Blessing of Sacrifice"] = "牺牲祝福", + ["Blessing of Salvation"] = "拯救祝福", + ["Blessing of Sanctuary"] = "庇护祝福", + ["Blessing of Shahram"] = "Blessing of Shahram",-- TODO translate + ["Blessing of Wisdom"] = "智慧祝福", + ["Blind"] = "致盲", + ["Blinding Powder"] = "致盲粉", + ["Blink"] = "闪现术", + ["Blizzard"] = "暴风雪", + ["Block"] = "格挡", + ["Blood Craze"] = "血之狂热", + ["Blood Frenzy"] = "血之狂暴", + ["Blood Funnel"] = "Blood Funnel",-- TODO translate + ["Blood Fury"] = "血性狂暴", + ["Blood Leech"] = "Blood Leech",-- TODO translate + ["Blood Pact"] = "血之契印", + ["Blood Siphon"] = "Blood Siphon",-- TODO translate + ["Blood Tap"] = "Blood Tap",-- TODO translate + ["Bloodlust"] = "Bloodlust", + ["Bloodrage"] = "血性狂暴", + ["Bloodthirst"] = "残忍", + ["Bomb"] = "Bomb",-- TODO translate + ["Booming Voice"] = "震耳嗓音", + ["Boulder"] = "Boulder",-- TODO translate + ["Bow Specialization"] = "弓专精", + ["Bows"] = "弓", + ["Brain Wash"] = "Brain Wash",-- TODO translate + ["Bright Campfire"] = "明亮篝火", + ["Brutal Impact"] = "野蛮冲撞", + ["Burning Adrenaline"] = "Burning Adrenaline", + ["Burning Soul"] = "燃烧之魂", + ["Burning Wish"] = "Burning Wish", + ["Butcher Drain"] = "Butcher Drain",-- TODO translate + ["Call of Flame"] = "烈焰召唤", + ["Call of the Grave"] = "Call of the Grave",-- TODO translate + ["Call of Thunder"] = "雷霆召唤", + ["Call Pet"] = "召唤宠物", + ["Camouflage"] = "伪装", + ["Cannibalize"] = "食尸", + ["Cat Form"] = "猎豹形态", + ["Cataclysm"] = "灾变", + ["Cause Insanity"] = "Cause Insanity",-- TODO translate + ["Chain Bolt"] = "Chain Bolt",-- TODO translate + ["Chain Burn"] = "Chain Burn",-- TODO translate + ["Chain Heal"] = "治疗链", + ["Chain Lightning"] = "闪电链", + ["Chained Bolt"] = "Chained Bolt",-- TODO translate + ["Chains of Ice"] = "Chains of Ice",-- TODO translate + ["Challenging Roar"] = "挑战咆哮", + ["Challenging Shout"] = "挑战怒吼", + ["Charge"] = "冲锋", + ["Charge Rage Bonus Effect"] = "冲锋额外怒气效果",-- [TODO Translate not sure about this one] + ["Charge Stun"] = "冲锋击昏", + ["Cheap Shot"] = "偷袭", + ["Chilled"] = "冰冻", + ["Chilling Touch"] = "Chilling Touch",-- TODO translate + ["Chromatic Infusion"] = "Chromatic Infusion",-- TODO translate + ["Circle of Healing"] = "Circle of Healing",-- TODO translate + ["Claw"] = "爪击", + ["Cleanse"] = "清洁术", + ["Cleanse Nova"] = "Cleanse Nova",-- TODO translate + ["Clearcasting"] = "节能施法", + ["Cleave"] = "顺劈斩", + ["Clever Traps"] = "灵巧陷阱", + ["Cloak of Shadows"] = "Cloak of Shadows",-- TODO translate + ["Closing"] = "关闭", + ["Cloth"] = "布甲", + ["Coarse Sharpening Stone"] = "粗制磨刀石", + ["Cobra Reflexes"] = "毒蛇反射", + ["Cold Blood"] = "冷血", + ["Cold Snap"] = "急速冷却", + ["Combat Endurance"] = "作战持久", + ["Combustion"] = "燃烧", + ["Command"] = "命令", + ["Commanding Shout"] = "Commanding Shout", + ["Concentration Aura"] = "专注光环", + ["Concussion"] = "震荡", + ["Concussion Blow"] = "震荡猛击", + ["Concussive Shot"] = "震荡射击", + ["Cone of Cold"] = "冰锥术", + ["Conflagrate"] = "燃烧", + ["Conjure Food"] = "造食术", + ["Conjure Mana Agate"] = "制造魔法玛瑙", + ["Conjure Mana Citrine"] = "制造魔法黄水晶", + ["Conjure Mana Jade"] = "制造魔法翡翠", + ["Conjure Mana Ruby"] = "制造魔法红宝石", + ["Conjure Water"] = "造水术", + ["Consecrated Sharpening Stone"] = "Consecrated Sharpening Stone",-- TODO translate + ["Consecration"] = "奉献", + ["Consume Magic"] = "Consume Magic",-- TODO translate + ["Consume Shadows"] = "吞噬暗影", + ["Consuming Shadows"] = "Consuming Shadows",-- TODO translate + ["Convection"] = "传导", + ["Conviction"] = "定罪", + ["Cooking"] = "烹饪", + ["Corrosive Acid Breath"] = "Corrosive Acid Breath",-- TODO translate + ["Corrosive Ooze"] = "Corrosive Ooze",-- TODO translate + ["Corrosive Poison"] = "Corrosive Poison",-- TODO translate + ["Corrupted Blood"] = "Corrupted Blood",-- TODO translate + ["Corruption"] = "腐蚀", + ["Counterattack"] = "反击", + ["Counterspell"] = "法术反制", + ["Counterspell - Silenced"] = "法术反制 - 沉默", + ["Cower"] = "畏缩", + ["Create Firestone"] = "制造火焰石", + ["Create Firestone (Greater)"] = "制造强效火焰石", + ["Create Firestone (Lesser)"] = "制造次级火焰石", + ["Create Firestone (Major)"] = "制造极效火焰石", + ["Create Healthstone"] = "制造治疗石", + ["Create Healthstone (Greater)"] = "制造强效治疗石", + ["Create Healthstone (Lesser)"] = "制造次级治疗石", + ["Create Healthstone (Major)"] = "制造极效治疗石", + ["Create Healthstone (Minor)"] = "制造初级治疗石", + ["Create Soulstone"] = "制造灵魂石", + ["Create Soulstone (Greater)"] = "制造强效灵魂石", + ["Create Soulstone (Lesser)"] = "制造次级灵魂石", + ["Create Soulstone (Major)"] = "制造极效灵魂石", + ["Create Soulstone (Minor)"] = "制造初级灵魂石", + ["Create Spellstone"] = "制造法术石", + ["Create Spellstone (Greater)"] = "制造强效法术石", + ["Create Spellstone (Major)"] = "制造极效法术石", + ["Create Spellstone (Master)"] = "Create Spellstone (Master)",-- TODO translate + ["Creeper Venom"] = "Creeper Venom",-- TODO translate + ["Cripple"] = "Cripple",-- TODO translate + ["Crippling Poison"] = "致残毒药", + ["Crippling Poison II"] = "致残毒药 II", + ["Critical Mass"] = "火焰重击", + ["Crossbows"] = "弩", + ["Crowd Pummel"] = "Crowd Pummel",-- TODO translate + ["Cruelty"] = "残忍", + ["Crusader Aura"] = "Crusader Aura",-- TODO translate + ["Crusader Strike"] = "Crusader Strike", + ["Crusader's Wrath"] = "Crusader's Wrath",-- TODO translate + ["Crystal Charge"] = "Crystal Charge",-- TODO translate + ["Crystal Force"] = "Crystal Force",-- TODO translate + ["Crystal Restore"] = "Crystal Restore",-- TODO translate + ["Crystal Spire"] = "Crystal Spire",-- TODO translate + ["Crystal Ward"] = "Crystal Ward",-- TODO translate + ["Crystal Yield"] = "Crystal Yield",-- TODO translate + ["Crystalline Slumber"] = "Crystalline Slumber",-- TODO translate + ["Cultivation"] = "栽培", + ["Cure Disease"] = "祛病术", + ["Cure Poison"] = "消毒术", + ["Curse of Agony"] = "痛苦诅咒", + ["Curse of Blood"] = "Curse of Blood",-- TODO translate + ["Curse of Doom"] = "厄运诅咒", + ["Curse of Doom Effect"] = "厄运诅咒效果", + ["Curse of Exhaustion"] = "疲劳诅咒", + ["Curse of Idiocy"] = "痴呆诅咒", + ["Curse of Recklessness"] = "鲁莽诅咒", + ["Curse of Shadow"] = "暗影诅咒", + ["Curse of the Deadwood"] = "Curse of the Deadwood",-- TODO translate + ["Curse of the Elemental Lord"] = "Curse of the Elemental Lord",-- TODO translate + ["Curse of the Elements"] = "元素诅咒", + ["Curse of Tongues"] = "语言诅咒", + ["Curse of Tuten'kash"] = "Curse of Tuten'kash",-- TODO translate + ["Curse of Weakness"] = "虚弱诅咒", + ["Cursed Blood"] = "Cursed Blood",-- TODO translate + ["Cyclone"] = "Cyclone", + ["Dagger Specialization"] = "匕首专精", + ["Daggers"] = "匕首", + ["Dampen Magic"] = "魔法抑制", + ["Dark Iron Bomb"] = "Dark Iron Bomb",-- TODO translate + ["Dark Offering"] = "Dark Offering",-- TODO translate + ["Dark Pact"] = "黑暗契约", + ["Darkness"] = "黑暗", + ["Dash"] = "急奔", + ["Dazed"] = "Dazed", + ["Deadly Poison"] = "致命毒药", + ["Deadly Poison II"] = "致命毒药 II", + ["Deadly Poison III"] = "致命毒药 III", + ["Deadly Poison IV"] = "致命毒药 IV", + ["Deadly Poison V"] = "致命毒药 V", + ["Deadly Throw"] = "Deadly Throw",-- TODO translate + ["Death Coil"] = "死亡缠绕", + ["Death Wish"] = "死亡之愿", + ["Deep Sleep"] = "Deep Sleep",-- TODO translate + ["Deep Slumber"] = "Deep Slumber",-- TODO translate + ["Deep Wounds"] = "重度伤口", + ["Defense"] = "防御", + ["Defensive Stance"] = "防御姿态", + ["Defensive Stance Passive"] = "防御姿态(被动)", + ["Defensive State"] = "防御状态",-- [TODO Translate not sure about this one] + ["Defensive State 2"] = "防御状态 2",-- [TODO Translate not sure about this one] + ["Defiance"] = "挑衅", + ["Deflection"] = "偏斜", + ["Delusions of Jin'do"] = "Delusions of Jin'do",-- TODO translate + ["Demon Armor"] = "魔甲术", + ["Demon Skin"] = "恶魔皮肤", + ["Demonic Embrace"] = "恶魔之拥", + ["Demonic Frenzy"] = "Demonic Frenzy", + ["Demonic Sacrifice"] = "恶魔牺牲", + ["Demoralizing Roar"] = "挫志咆哮", + ["Demoralizing Shout"] = "挫志怒吼", + ["Dense Sharpening Stone"] = "致密磨刀石", + ["Desperate Prayer"] = "绝望祷言", + ["Destructive Reach"] = "毁灭延伸", + ["Detect"] = "侦测", + ["Detect Greater Invisibility"] = "侦测强效隐形", + ["Detect Invisibility"] = "侦测隐形", + ["Detect Lesser Invisibility"] = "侦测次级隐形", + ["Detect Magic"] = "侦测魔法", + ["Detect Traps"] = "侦测陷阱", + ["Deterrence"] = "威慑", + ["Detonation"] = "Detonation",-- TODO translate + ["Devastate"] = "Devastate", + ["Devastation"] = "毁灭", + ["Devotion Aura"] = "虔诚光环", + ["Devour Magic"] = "吞噬魔法", + ["Devour Magic Effect"] = "吞噬魔法效果", + ["Devouring Plague"] = "噬灵瘟疫", + ["Diamond Flask"] = "Diamond Flask",-- TODO translate + ["Diplomacy"] = "外交", + ["Dire Bear Form"] = "巨熊形态", + ["Dire Growl"] = "Dire Growl",-- TODO translate + ["Disarm"] = "缴械", + ["Disarm Trap"] = "解除陷阱", + ["Disease Cleansing Totem"] = "祛病图腾", + ["Disease Cloud"] = "Disease Cloud",-- TODO translate + ["Diseased Shot"] = "Diseased Shot",-- TODO translate + ["Diseased Spit"] = "Diseased Spit",-- TODO translate + ["Disenchant"] = "分解", + ["Disengage"] = "逃脱", + ["Disjunction"] = "Disjunction",-- TODO translate + ["Dismiss Pet"] = "解散野兽", + ["Dispel Magic"] = "驱散魔法", + ["Distract"] = "扰乱", + ["Distracting Pain"] = "Distracting Pain",-- TODO translate + ["Distracting Shot"] = "扰乱射击", + ["Dive"] = "俯冲", + ["Divine Favor"] = "神恩术", + ["Divine Fury"] = "神圣之怒", + ["Divine Illumination"] = "Divine Illumination",-- TODO translate + ["Divine Intellect"] = "神圣智慧", + ["Divine Intervention"] = "神圣干涉", + ["Divine Protection"] = "圣佑术", + ["Divine Shield"] = "圣盾术", + ["Divine Spirit"] = "神圣之灵", + ["Divine Strength"] = "神圣之力", + ["Diving Sweep"] = "Diving Sweep",-- TODO translate + ["Dodge"] = "躲闪", + ["Dominate Mind"] = "Dominate Mind",-- TODO translate + ["Dragon's Breath"] = "Dragon's Breath",-- TODO translate + ["Dragonscale Leatherworking"] = "龙鳞制皮", + ["Drain Life"] = "吸取生命", + ["Drain Mana"] = "吸取法力", + ["Drain Soul"] = "吸取灵魂", + ["Dredge Sickness"] = "Dredge Sickness",-- TODO translate + ["Drink"] = "喝水", + ["Druid's Slumber"] = "Druid's Slumber",-- TODO translate + ["Dual Wield"] = "双武器", + ["Dual Wield Specialization"] = "双武器专精", + ["Duel"] = "决斗", + ["Dust Field"] = "Dust Field",-- TODO translate + ["Eagle Eye"] = "鹰眼术", + ["Earth Elemental Totem"] = "Earth Elemental Totem",-- TODO translate + ["Earth Shield"] = "Earth Shield",-- TODO translate + ["Earth Shock"] = "大地震击", + ["Earthbind Totem"] = "地缚图腾", + ["Earthborer Acid"] = "Earthborer Acid",-- TODO translate + ["Earthgrab"] = "Earthgrab",-- TODO translate + ["Efficiency"] = "效率", + ["Electric Discharge"] = "Electric Discharge",-- TODO translate + ["Electrified Net"] = "Electrified Net",-- TODO translate + ["Elemental Focus"] = "元素集中", + ["Elemental Fury"] = "元素之怒", + ["Elemental Leatherworking"] = "元素制皮", + ["Elemental Mastery"] = "元素掌握", + ["Elemental Precision"] = "Elemental Precision", + ["Elemental Sharpening Stone"] = "元素磨刀石", + ["Elune's Grace"] = "艾露恩的赐福", + ["Elusiveness"] = "飘忽不定", + ["Emberstorm"] = "琥珀风暴", + ["Enamored Water Spirit"] = "Enamored Water Spirit",-- TODO translate + ["Enchanting"] = "附魔", + ["Endurance"] = "耐久", + ["Endurance Training"] = "耐久训练", + ["Engineering"] = "工程学", + ["Engineering Specialization"] = "工程学专精", + ["Enrage"] = "狂怒", + ["Enriched Manna Biscuit"] = "可口的魔法点心", + ["Enslave Demon"] = "奴役恶魔", + ["Entangling Roots"] = "纠缠根须", + ["Entrapment"] = "诱捕", + ["Enveloping Web"] = "Enveloping Web",-- TODO translate + ["Enveloping Webs"] = "Enveloping Webs",-- TODO translate + ["Enveloping Winds"] = "Enveloping Winds",-- TODO translate + ["Envenom"] = "Envenom",-- TODO translate + ["Ephemeral Power"] = "Ephemeral Power",-- TODO translate + ["Escape Artist"] = "逃命专家", + ["Essence of Sapphiron"] = "Essence of Sapphiron",-- TODO translate + ["Evasion"] = "闪避", + ["Eventide"] = "Eventide",-- TODO translate + ["Eviscerate"] = "剔骨", + ["Evocation"] = "唤醒", + ["Execute"] = "斩杀", + ["Exorcism"] = "驱邪术", + ["Expansive Mind"] = "开阔思维", + ["Exploding Shot"] = "Exploding Shot",-- TODO translate + ["Exploit Weakness"] = "Exploit Weakness",-- TODO translate + ["Explosive Shot"] = "Explosive Shot",-- TODO translate + ["Explosive Trap"] = "爆炸陷阱", + ["Explosive Trap Effect"] = "爆炸陷阱效果", + ["Expose Armor"] = "破甲", + ["Expose Weakness"] = "Expose Weakness", + ["Eye for an Eye"] = "以眼还眼", + ["Eye of Kilrogg"] = "基尔罗格之眼", + ["The Eye of the Dead"] = "The Eye of the Dead",-- TODO translate + ["Eyes of the Beast"] = "野兽之眼", + ["Fade"] = "渐隐术", + ["Faerie Fire"] = "精灵之火", + ["Faerie Fire (Feral)"] = "精灵之火(野性)", + ["Far Sight"] = "视界术", + ["Fatal Bite"] = "Fatal Bite",-- TODO translate + ["Fear"] = "恐惧术", + ["Fear Ward"] = "防护恐惧结界", + ["Feed Pet"] = "喂养宠物", + ["Feedback"] = "回馈", + ["Feign Death"] = "假死", + ["Feint"] = "佯攻", + ["Fel Armor"] = "Fel Armor",-- TODO translate + ["Fel Concentration"] = "恶魔专注", + ["Fel Domination"] = "恶魔支配", + ["Fel Intellect"] = "恶魔智力", + ["Fel Stamina"] = "恶魔耐力", + ["Fel Stomp"] = "Fel Stomp",-- TODO translate + ["Felfire"] = "魔火", + ["Feline Grace"] = "豹之优雅", + ["Feline Swiftness"] = "豹之迅捷", + ["Feral Aggression"] = "野性侵略", + ["Feral Charge"] = "野性冲锋", + ["Feral Instinct"] = "野性本能", + ["Ferocious Bite"] = "凶猛撕咬", + ["Ferocity"] = "凶暴", + ["Fetish"] = "神像", + ["Fevered Plague"] = "Fevered Plague",-- TODO translate + ["Fiery Burst"] = "Fiery Burst",-- TODO translate + ["Find Herbs"] = "寻找草药", + ["Find Minerals"] = "寻找矿物", + ["Find Treasure"] = "寻找财宝", + ["Fire Blast"] = "火焰冲击", + ["Fire Elemental Totem"] = "Fire Elemental Totem",-- TODO translate + ["Fire Nova"] = "Fire Nova",-- TODO translate + ["Fire Nova Totem"] = "火焰新星图腾", + ["Fire Power"] = "火焰强化", + ["Fire Resistance"] = "火焰抗性", + ["Fire Resistance Aura"] = "火焰抗性光环", + ["Fire Resistance Totem"] = "抗火图腾", + ["Fire Shield"] = "火焰之盾", + ["Fire Shield Effect"] = "Fire Shield Effect",-- TODO translate + ["Fire Shield Effect II"] = "Fire Shield Effect II",-- TODO translate + ["Fire Shield Effect III"] = "Fire Shield Effect III",-- TODO translate + ["Fire Shield Effect IV"] = "Fire Shield Effect IV",-- TODO translate + ["Fire Storm"] = "Fire Storm",-- TODO translate + ["Fire Vulnerability"] = "火焰易伤", + ["Fire Ward"] = "防护火焰结界", + ["Fire Weakness"] = "Fire Weakness", + ["Fireball"] = "火球术", + ["Fireball Volley"] = "Fireball Volley",-- TODO translate + ["Firebolt"] = "火焰箭", + ["First Aid"] = "急救", + ["Fishing"] = "钓鱼", + ["Fishing Poles"] = "鱼竿", + ["Fist of Ragnaros"] = "Fist of Ragnaros",-- TODO translate + ["Fist Weapon Specialization"] = "拳套专精", + ["Fist Weapons"] = "拳套", + ["Flame Buffet"] = "Flame Buffet",-- TODO translate + ["Flame Cannon"] = "Flame Cannon",-- TODO translate + ["Flame Lash"] = "Flame Lash",-- TODO translate + ["Flame Shock"] = "烈焰震击", + ["Flame Spike"] = "Flame Spike",-- TODO translate + ["Flame Spray"] = "Flame Spray",-- TODO translate + ["Flame Throwing"] = "烈焰投掷", + ["Flames of Shahram"] = "Flames of Shahram",-- TODO translate + ["Flamestrike"] = "烈焰冲击", + ["Flamethrower"] = "火焰喷射器", + ["Flametongue Totem"] = "火舌图腾", + ["Flametongue Weapon"] = "火舌武器", + ["Flare"] = "照明弹", + ["Flash Bomb"] = "Flash Bomb",-- TODO translate + ["Flash Heal"] = "快速治疗", + ["Flash of Light"] = "圣光闪现", + ["Flight Form"] = "Flight Form",-- TODO translate + ["Flurry"] = "乱舞", + ["Focused Casting"] = "专注施法", + ["Focused Mind"] = "Focused Mind", + ["Food"] = "进食", + ["Forbearance"] = "自律", + ["Force of Nature"] = "Force of Nature",-- TODO translate + ["Force of Will"] = "意志之力", + ["Force Punch"] = "Force Punch",-- TODO translate + ["Force Reactive Disk"] = "Force Reactive Disk",-- TODO translate + ["Forked Lightning"] = "Forked Lightning",-- TODO translate + ["Forsaken Skills"] = "Forsaken Skills",-- TODO translate + ["Frailty"] = "Frailty",-- TODO translate + ["Freeze Solid"] = "Freeze Solid",-- TODO translate + ["Freezing Trap"] = "冰冻陷阱", + ["Freezing Trap Effect"] = "冰冻陷阱效果", + ["Frenzied Regeneration"] = "狂暴回复", + ["Frenzy"] = "疯狂", + ["Frost Armor"] = "霜甲术", + ["Frost Breath"] = "Frost Breath",-- TODO translate + ["Frost Channeling"] = "冰霜导能", + ["Frost Nova"] = "冰霜新星", + ["Frost Resistance"] = "冰霜抗性", + ["Frost Resistance Aura"] = "冰霜抗性光环", + ["Frost Resistance Totem"] = "抗寒图腾", + ["Frost Shock"] = "冰霜震击", + ["Frost Shot"] = "Frost Shot",-- TODO translate + ["Frost Trap"] = "冰霜陷阱", + ["Frost Trap Aura"] = "冰霜陷阱光环", + ["Frost Ward"] = "防护冰霜结界", + ["Frost Warding"] = "Frost Warding", + ["Frost Weakness"] = "Frost Weakness", + ["Frostbite"] = "霜寒刺骨", + ["Frostbolt"] = "寒冰箭", + ["Frostbolt Volley"] = "Frostbolt Volley",-- TODO translate + ["Frostbrand Weapon"] = "冰封武器", + ["Furious Howl"] = "狂怒之嚎", + ["The Furious Storm"] = "The Furious Storm",-- TODO translate + ["Furor"] = "激怒", + ["Fury of Ragnaros"] = "Fury of Ragnaros",-- TODO translate + ["Gahz'ranka Slam"] = "Gahz'ranka Slam",-- TODO translate + ["Gahz'rilla Slam"] = "Gahz'rilla Slam",-- TODO translate + ["Garrote"] = "绞喉", + ["Gehennas' Curse"] = "Gehennas' Curse",-- TODO translate + ["Generic"] = "基本",-- [TODO Translate not sure about this one] + ["Ghost Wolf"] = "幽魂之狼", + ["Ghostly Strike"] = "鬼魅攻击", + ["Gift of Life"] = "Gift of Life", + ["Gift of Nature"] = "自然赐福", + ["Gift of the Wild"] = "野性赐福", + ["Goblin Dragon Gun"] = "Goblin Dragon Gun",-- TODO translate + ["Goblin Sapper Charge"] = "Goblin Sapper Charge",-- TODO translate + ["Gouge"] = "凿击", + ["Grace of Air Totem"] = "风之优雅图腾", + ["Grace of the Sunwell"] = "Grace of the Sunwell",-- TODO translate + ["Grasping Vines"] = "Grasping Vines",-- TODO translate + ["Great Stamina"] = "持久耐力", + ["Greater Blessing of Kings"] = "强效王者祝福", + ["Greater Blessing of Light"] = "强效光明祝福", + ["Greater Blessing of Might"] = "强效力量祝福", + ["Greater Blessing of Salvation"] = "强效拯救祝福", + ["Greater Blessing of Sanctuary"] = "强效庇护祝福", + ["Greater Blessing of Wisdom"] = "强效智慧祝福", + ["Greater Heal"] = "强效治疗术", + ["Grim Reach"] = "无情延伸", + ["Ground Tremor"] = "Ground Tremor",-- TODO translate + ["Grounding Totem"] = "根基图腾", + ["Grovel"] = "匍匐", + ["Growl"] = "低吼", + ["Guardian's Favor"] = "守护者的宠爱", + ["Guillotine"] = "Guillotine",-- TODO translate + ["Gun Specialization"] = "枪械专精", + ["Guns"] = "枪械", + ["Hail Storm"] = "Hail Storm",-- TODO translate + ["Hammer of Justice"] = "制裁之锤", + ["Hammer of Wrath"] = "愤怒之锤", + ["Hamstring"] = "断筋", + ["Harass"] = "侵扰", + ["Hardiness"] = "坚韧", + ["Haunting Spirits"] = "Haunting Spirits",-- TODO translate + ["Hawk Eye"] = "鹰眼", + ["Head Crack"] = "Head Crack",-- TODO translate + ["Heal"] = "治疗术", + ["Healing Circle"] = "Healing Circle",-- TODO translate + ["Healing Focus"] = "治疗专注", + ["Healing Light"] = "治疗之光", + ["Healing of the Ages"] = "Healing of the Ages",-- TODO translate + ["Healing Stream Totem"] = "治疗之泉图腾", + ["Healing Touch"] = "治疗之触", + ["Healing Wave"] = "治疗波", + ["Healing Way"] = "治疗之道", + ["Health Funnel"] = "生命通道", + ["Heart of the Wild"] = "野性之心", + ["Heavy Sharpening Stone"] = "重磨刀石", + ["Hellfire"] = "地狱烈焰", + ["Hellfire Effect"] = "地狱烈焰效果", + ["Hemorrhage"] = "出血", + ["Herb Gathering"] = "采集草药", + ["Herbalism"] = "草药学", + ["Heroic Strike"] = "英勇打击", + ["Heroism"] = "Heroism", + ["Hex"] = "Hex",-- TODO translate + ["Hex of Jammal'an"] = "Hex of Jammal'an",-- TODO translate + ["Hex of Weakness"] = "虚弱妖术", + ["Hibernate"] = "休眠", + ["Holy Fire"] = "神圣之火", + ["Holy Light"] = "圣光术", + ["Holy Nova"] = "神圣新星", + ["Holy Power"] = "神圣强化", + ["Holy Reach"] = "神圣延伸", + ["Holy Shield"] = "神圣之盾", + ["Holy Shock"] = "神圣震击", + ["Holy Smite"] = "Holy Smite",-- TODO translate + ["Holy Specialization"] = "神圣专精", + ["Holy Strength"] = "Holy Strength",-- TODO translate + ["Holy Strike"] = "Holy Strike",-- TODO translate + ["Holy Wrath"] = "神圣愤怒", + ["Honorless Target"] = "无荣誉目标", + ["Hooked Net"] = "Hooked Net",-- TODO translate + ["Horse Riding"] = "骑术:马", + ["Howl of Terror"] = "恐惧嚎叫", + ["The Human Spirit"] = "人类精魂", + ["Humanoid Slaying"] = "人型生物杀手", + ["Hunter's Mark"] = "猎人印记", + ["Hurricane"] = "飓风", + ["Ice Armor"] = "冰甲术", + ["Ice Barrier"] = "寒冰护体", + ["Ice Blast"] = "Ice Blast",-- TODO translate + ["Ice Block"] = "寒冰屏障", + ["Ice Lance"] = "Ice Lance",-- TODO translate + ["Ice Nova"] = "Ice Nova",-- TODO translate + ["Ice Shards"] = "寒冰碎片", + ["Icicle"] = "Icicle",-- TODO translate + ["Ignite"] = "点燃", + ["Illumination"] = "启发", + ["Immolate"] = "献祭", + ["Immolation Trap"] = "献祭陷阱", + ["Immolation Trap Effect"] = "献祭陷阱效果", + ["Impact"] = "冲击", + ["Impale"] = "穿刺", + ["Improved Ambush"] = "强化伏击", + ["Improved Arcane Explosion"] = "强化魔爆术", + ["Improved Arcane Missiles"] = "强化奥术飞弹", + ["Improved Arcane Shot"] = "强化奥术射击", + ["Improved Aspect of the Hawk"] = "强化雄鹰守护", + ["Improved Aspect of the Monkey"] = "强化灵猴守护", + ["Improved Backstab"] = "强化背刺", + ["Improved Battle Shout"] = "强化战斗怒吼", + ["Improved Berserker Rage"] = "强化狂暴之怒", + ["Improved Blessing of Might"] = "强化力量祝福", + ["Improved Blessing of Wisdom"] = "强化智慧祝福", + ["Improved Blizzard"] = "强化暴风雪", + ["Improved Bloodrage"] = "强化血性狂暴", + ["Improved Chain Heal"] = "强化治疗链", + ["Improved Chain Lightning"] = "强化闪电链", + ["Improved Challenging Shout"] = "强化挑战怒吼", + ["Improved Charge"] = "强化冲锋", + ["Improved Cheap Shot"] = "强化偷袭", + ["Improved Cleave"] = "强化顺劈斩", + ["Improved Concentration Aura"] = "强化专注光环", + ["Improved Concussive Shot"] = "强化震荡射击", + ["Improved Cone of Cold"] = "强化冰锥术", + ["Improved Corruption"] = "强化腐蚀术", + ["Improved Counterspell"] = "强化法术反制", + ["Improved Curse of Agony"] = "强化痛苦诅咒", + ["Improved Curse of Exhaustion"] = "强化疲劳诅咒", + ["Improved Curse of Weakness"] = "强化虚弱诅咒", + ["Improved Dampen Magic"] = "强化魔法抑制", + ["Improved Deadly Poison"] = "强化致命毒药", + ["Improved Demoralizing Shout"] = "强化挫志怒吼", + ["Improved Devotion Aura"] = "强化虔诚光环", + ["Improved Disarm"] = "强化缴械", + ["Improved Distract"] = "强化扰乱", + ["Improved Drain Life"] = "强化吸取生命", + ["Improved Drain Mana"] = "强化吸取法力", + ["Improved Drain Soul"] = "强化吸取灵魂", + ["Improved Enrage"] = "强化狂怒", + ["Improved Enslave Demon"] = "强化奴役恶魔", + ["Improved Entangling Roots"] = "强化纠缠根须", + ["Improved Evasion"] = "强化闪避", + ["Improved Eviscerate"] = "强化剔骨", + ["Improved Execute"] = "强化斩杀", + ["Improved Expose Armor"] = "强化破甲", + ["Improved Eyes of the Beast"] = "强化野兽之眼", + ["Improved Fade"] = "强化渐隐术", + ["Improved Feign Death"] = "强化假死", + ["Improved Fire Blast"] = "强化火焰冲击", + ["Improved Fire Nova Totem"] = "强化火焰图腾", + ["Improved Fire Ward"] = "强化防护火焰结界", + ["Improved Fireball"] = "强化火球术", + ["Improved Firebolt"] = "强化火焰箭", + ["Improved Firestone"] = "强化火焰石", + ["Improved Flamestrike"] = "强化烈焰冲击", + ["Improved Flametongue Weapon"] = "强化火舌武器", + ["Improved Flash of Light"] = "强化圣光闪现", + ["Improved Frost Nova"] = "强化冰霜新星", + ["Improved Frost Ward"] = "强化防护冰霜结界", + ["Improved Frostbolt"] = "强化寒冰箭", + ["Improved Frostbrand Weapon"] = "强化冰封武器", + ["Improved Garrote"] = "强化绞喉", + ["Improved Ghost Wolf"] = "强化幽魂之狼", + ["Improved Gouge"] = "强化凿击", + ["Improved Grace of Air Totem"] = "强化风之优雅图腾", + ["Improved Grounding Totem"] = "强化根基图腾", + ["Improved Hammer of Justice"] = "强化制裁之锤", + ["Improved Hamstring"] = "强化断筋", + ["Improved Healing"] = "强化治疗术", + ["Improved Healing Stream Totem"] = "强化治疗之泉图腾", + ["Improved Healing Touch"] = "强化治疗之触", + ["Improved Healing Wave"] = "强化治疗波", + ["Improved Health Funnel"] = "强化生命通道", + ["Improved Healthstone"] = "强化治疗石", + ["Improved Heroic Strike"] = "强化英勇打击", + ["Improved Hunter's Mark"] = "强化猎人印记", + ["Improved Immolate"] = "强化献祭", + ["Improved Imp"] = "强化小鬼", + ["Improved Inner Fire"] = "强化心灵之火", + ["Improved Instant Poison"] = "强化速效毒药", + ["Improved Intercept"] = "强化拦截", + ["Improved Intimidating Shout"] = "强化破胆怒吼", + ["Improved Judgement"] = "强化审判", + ["Improved Kick"] = "强化脚踢", + ["Improved Kidney Shot"] = "强化肾击", + ["Improved Lash of Pain"] = "强化剧痛鞭笞", + ["Improved Lay on Hands"] = "强化圣疗术", + ["Improved Lesser Healing Wave"] = "强化次级治疗波", + ["Improved Life Tap"] = "强化生命分流", + ["Improved Lightning Bolt"] = "强化闪电箭", + ["Improved Lightning Shield"] = "强化闪电护盾", + ["Improved Magma Totem"] = "强化熔岩图腾", + ["Improved Mana Burn"] = "强化法力燃烧", + ["Improved Mana Shield"] = "强化法力护盾", + ["Improved Mana Spring Totem"] = "强化法力之泉图腾", + ["Improved Mark of the Wild"] = "强化野性印记", + ["Improved Mend Pet"] = "强化治疗宠物", + ["Improved Mind Blast"] = "强化心灵震爆", + ["Improved Moonfire"] = "强化月火术", + ["Improved Nature's Grasp"] = "强化自然之握", + ["Improved Overpower"] = "强化压制", + ["Improved Power Word: Fortitude"] = "强化真言术:韧", + ["Improved Power Word: Shield"] = "强化圣言术:盾", + ["Improved Prayer of Healing"] = "强化治疗祷言", + ["Improved Psychic Scream"] = "强化心灵尖啸", + ["Improved Pummel"] = "强化拳击", + ["Improved Regrowth"] = "强化愈合", + ["Improved Reincarnation"] = "强化复生", + ["Improved Rejuvenation"] = "强化回春", + ["Improved Rend"] = "强化撕裂", + ["Improved Renew"] = "强化恢复", + ["Improved Retribution Aura"] = "强化惩罚光环", + ["Improved Revenge"] = "强化复仇", + ["Improved Revive Pet"] = "强化复活宠物", + ["Improved Righteous Fury"] = "强化正义之怒", + ["Improved Rockbiter Weapon"] = "强化石化武器", + ["Improved Rupture"] = "强化割裂", + ["Improved Sap"] = "强化闷棍", + ["Improved Scorch"] = "强化灼烧", + ["Improved Scorpid Sting"] = "强化毒蝎钉刺", + ["Improved Seal of Righteousness"] = "强化正义圣印", + ["Improved Seal of the Crusader"] = "强化十字军圣印", + ["Improved Searing Pain"] = "强化灼热之痛", + ["Improved Searing Totem"] = "强化灼热图腾", + ["Improved Serpent Sting"] = "强化毒蛇钉刺", + ["Improved Shadow Bolt"] = "强化暗影箭", + ["Improved Shadow Word: Pain"] = "强化暗言术:痛", + ["Improved Shield Bash"] = "强化盾击", + ["Improved Shield Block"] = "强化盾牌格挡", + ["Improved Shield Wall"] = "强化盾墙", + ["Improved Shred"] = "强化撕碎", + ["Improved Sinister Strike"] = "强化邪恶攻击", + ["Improved Slam"] = "强化猛击", + ["Improved Slice and Dice"] = "强化切割", + ["Improved Spellstone"] = "强化法术石", + ["Improved Sprint"] = "强化疾跑", + ["Improved Starfire"] = "强化星火术", + ["Improved Stoneclaw Totem"] = "强化石爪图腾", + ["Improved Stoneskin Totem"] = "强化石肤图腾", + ["Improved Strength of Earth Totem"] = "强化大地之力图腾", + ["Improved Succubus"] = "强化魅魔", + ["Improved Sunder Armor"] = "强化破甲攻击", + ["Improved Taunt"] = "强化嘲讽", + ["Improved Thorns"] = "强化荆棘术", + ["Improved Thunder Clap"] = "强化雷霆一击", + ["Improved Tranquility"] = "强化宁静", + ["Improved Vampiric Embrace"] = "强化吸血鬼的拥抱", + ["Improved Vanish"] = "强化消失", + ["Improved Voidwalker"] = "强化虚空行者", + ["Improved Windfury Weapon"] = "强化风怒武器", + ["Improved Wing Clip"] = "强化摔绊", + ["Improved Wrath"] = "强化愤怒", + ["Incinerate"] = "焚烧", + ["Infected Bite"] = "Infected Bite",-- TODO translate + ["Infected Wound"] = "Infected Wound",-- TODO translate + ["Inferno"] = "地狱火", + ["Inferno Shell"] = "Inferno Shell",-- TODO translate + ["Initiative"] = "先发制人", + ["Inner Fire"] = "心灵之火", + ["Inner Focus"] = "心灵专注", + ["Innervate"] = "激活", + ["Insect Swarm"] = "虫群", + ["Inspiration"] = "灵感", + ["Instant Poison"] = "速效毒药", + ["Instant Poison II"] = "速效毒药 II", + ["Instant Poison III"] = "速效毒药 III", + ["Instant Poison IV"] = "速效毒药 IV", + ["Instant Poison V"] = "速效毒药 V", + ["Instant Poison VI"] = "速效毒药 VI", + ["Intensity"] = "强烈", + ["Intercept"] = "拦截", + ["Intercept Stun"] = "拦截昏迷", + ["Intervene"] = "Intervene",-- TODO translate + ["Intimidating Roar"] = "Intimidating Roar",-- TODO translate + ["Intimidating Shout"] = "破胆怒吼", + ["Intimidation"] = "胁迫", + ["Intoxicating Venom"] = "Intoxicating Venom",-- TODO translate + ["Invisibility"] = "Invisibility",-- TODO translate + ["Iron Will"] = "Iron Will",-- TODO translate + ["Jewelcrafting"] = "Jewelcrafting", + ["Judgement"] = "审判", + ["Judgement of Command"] = "命令审判", + ["Judgement of Justice"] = "公正审判", + ["Judgement of Light"] = "光明审判", + ["Judgement of Righteousness"] = "正义审判", + ["Judgement of the Crusader"] = "十字军审判", + ["Judgement of Wisdom"] = "智慧审判", + ["Kick"] = "脚踢", + ["Kick - Silenced"] = "脚踢 - 沉默", + ["Kidney Shot"] = "肾击", + ["Kill Command"] = "Kill Command",-- TODO translate + ["Killer Instinct"] = "杀戮本能", + ["Knock Away"] = "Knock Away",-- TODO translate + ["Knockdown"] = "Knockdown",-- TODO translate + ["Kodo Riding"] = "骑术:科多兽", + ["Lacerate"] = "Lacerate", + ["Larva Goo"] = "Larva Goo",-- TODO translate + ["Lash"] = "Lash",-- TODO translate + ["Lash of Pain"] = "剧痛鞭笞", + ["Last Stand"] = "破釜沉舟", + ["Lasting Judgement"] = "持久审判", + ["Lava Spout Totem"] = "Lava Spout Totem",-- TODO translate + ["Lay on Hands"] = "圣疗术", + ["Leader of the Pack"] = "兽群领袖", + ["Leather"] = "皮甲", + ["Leatherworking"] = "制皮", + ["Leech Poison"] = "Leech Poison",-- TODO translate + ["Lesser Heal"] = "次级治疗术", + ["Lesser Healing Wave"] = "次级治疗波", + ["Lesser Invisibility"] = "次级隐形术", + ["Lethal Shots"] = "夺命射击", + ["Lethality"] = "致命偷袭", + ["Levitate"] = "漂浮", + ["Libram"] = "圣物", + ["Lich Slap"] = "Lich Slap",-- TODO translate + ["Life Tap"] = "生命分流", + ["Lifebloom"] = "Lifebloom",-- TODO translate + ["Lifegiving Gem"] = "Lifegiving Gem", + ["Lightning Blast"] = "Lightning Blast",-- TODO translate + ["Lightning Bolt"] = "闪电箭", + ["Lightning Breath"] = "闪电吐息", + ["Lightning Cloud"] = "Lightning Cloud",-- TODO translate + ["Lightning Mastery"] = "闪电掌握", + ["Lightning Reflexes"] = "闪电反射", + ["Lightning Shield"] = "闪电护盾", + ["Lightning Wave"] = "Lightning Wave",-- TODO translate + ["Lightwell"] = "光明之泉", + ["Lightwell Renew"] = "光明之泉回复", + ["Lizard Bolt"] = "Lizard Bolt",-- TODO translate + ["Localized Toxin"] = "Localized Toxin",-- TODO translate + ["Lockpicking"] = "开锁", + ["Long Daze"] = "长时间眩晕", + ["Mace Specialization"] = "锤类武器专精", + ["Mace Stun Effect"] = "锤击昏迷效果", + ["Machine Gun"] = "Machine Gun",-- TODO translate + ["Mage Armor"] = "魔甲术", + ["Magic Attunement"] = "Magic Attunement", + ["Magma Splash"] = "Magma Splash",-- TODO translate + ["Magma Totem"] = "熔岩图腾", + ["Mail"] = "锁甲", + ["Maim"] = "Maim",-- TODO translate + ["Malice"] = "恶意", + ["Mana Burn"] = "法力燃烧", + ["Mana Feed"] = "Mana Feed", + ["Mana Shield"] = "法力护盾", + ["Mana Spring Totem"] = "法力之泉图腾", + ["Mana Tide Totem"] = "法力之潮图腾", + ["Mangle"] = "割碎", + ["Mangle (Bear)"] = "Mangle (Bear)",-- TODO translate + ["Mangle (Cat)"] = "Mangle (Cat)",-- TODO translate + ["Mark of Arlokk"] = "Mark of Arlokk",-- TODO translate + ["Mark of the Wild"] = "野性印记", + ["Martyrdom"] = "殉难", + ["Mass Dispel"] = "Mass Dispel", + ["Master Demonologist"] = "恶魔学识大师", + ["Master of Deception"] = "欺诈高手", + ["Master of Elements"] = "Master of Elements", + ["Master Summoner"] = "召唤大师", + ["Maul"] = "槌击", + ["Mechanostrider Piloting"] = "骑术:机械陆行鸟", + ["Meditation"] = "冥想", + ["Megavolt"] = "Megavolt",-- TODO translate + ["Melee Specialization"] = "近战专精", + ["Melt Ore"] = "Melt Ore",-- TODO translate + ["Mend Pet"] = "治疗宠物", + ["Mental Agility"] = "精神敏锐", + ["Mental Strength"] = "心灵之力", + ["Mighty Blow"] = "Mighty Blow",-- TODO translate + ["Mind Blast"] = "心灵震爆", + ["Mind Control"] = "精神控制", + ["Mind Flay"] = "精神鞭笞", + ["Mind Soothe"] = "安抚心灵", + ["Mind Tremor"] = "Mind Tremor",-- TODO translate + ["Mind Vision"] = "心灵视界", + ["Mind-numbing Poison"] = "麻痹毒药", + ["Mind-numbing Poison II"] = "麻痹毒药 II", + ["Mind-numbing Poison III"] = "麻痹毒药 III", + ["Mining"] = "采矿", + ["Misdirection"] = "Misdirection",-- TODO translate + ["Mocking Blow"] = "惩戒痛击", + ["Molten Armor"] = "Molten Armor",-- TODO translate + ["Molten Blast"] = "Molten Blast",-- TODO translate + ["Molten Metal"] = "Molten Metal",-- TODO translate + ["Mongoose Bite"] = "猫鼬撕咬", + ["Monster Slaying"] = "怪物杀手", + ["Moonfire"] = "月火术", + ["Moonfury"] = "月怒", + ["Moonglow"] = "月光", + ["Moonkin Aura"] = "枭兽光环", + ["Moonkin Form"] = "枭兽形态", + ["Mortal Cleave"] = "Mortal Cleave",-- TODO translate + ["Mortal Shots"] = "致死射击", + ["Mortal Strike"] = "致死打击", + ["Mortal Wound"] = "Mortal Wound",-- TODO translate + ["Multi-Shot"] = "多重射击", + ["Murder"] = "谋杀", + ["Mutilate"] = "Mutilate",-- TODO translate + ["Naralex's Nightmare"] = "Naralex's Nightmare",-- TODO translate + ["Natural Armor"] = "自然护甲", + ["Natural Shapeshifter"] = "自然变形", + ["Natural Weapons"] = "武器平衡", + ["Nature Aligned"] = "Nature Aligned",-- TODO translate + ["Nature Resistance"] = "自然抗性", + ["Nature Resistance Totem"] = "自然抗性图腾", + ["Nature Weakness"] = "Nature Weakness",-- TODO translate + ["Nature's Focus"] = "自然集中", + ["Nature's Grace"] = "自然之赐", + ["Nature's Grasp"] = "自然之握", + ["Nature's Reach"] = "自然延伸", + ["Nature's Swiftness"] = "自然迅捷", + ["Necrotic Poison"] = "Necrotic Poison",-- TODO translate + ["Negative Charge"] = "Negative Charge",-- TODO translate + ["Net"] = "Net",-- TODO translate + ["Nightfall"] = "夜幕", + ["Noxious Catalyst"] = "Noxious Catalyst",-- TODO translate + ["Noxious Cloud"] = "Noxious Cloud",-- TODO translate + ["Omen of Clarity"] = "清晰预兆", + ["One-Handed Axes"] = "单手斧", + ["One-Handed Maces"] = "单手锤", + ["One-Handed Swords"] = "单手剑", + ["One-Handed Weapon Specialization"] = "单手武器专精", + ["Opening"] = "打开", + ["Opening - No Text"] = "打开 - No Text",-- [TODO Translate not sure what this is] + ["Opportunity"] = "伺机而动", + ["Overpower"] = "压制", + ["Pacify"] = "Pacify",-- TODO translate + ["Pain Suppression"] = "Pain Suppression",-- TODO translate + ["Paralyzing Poison"] = "Paralyzing Poison",-- TODO translate + ["Paranoia"] = "多疑", + ["Parasitic Serpent"] = "Parasitic Serpent",-- TODO translate + ["Parry"] = "招架", + ["Pathfinding"] = "寻路", + ["Perception"] = "感知", + ["Permafrost"] = "极寒冰霜", + ["Pet Aggression"] = "宠物好斗", + ["Pet Hardiness"] = "宠物耐久", + ["Pet Recovery"] = "宠物恢复", + ["Pet Resistance"] = "宠物抗魔", + ["Petrify"] = "Petrify",-- TODO translate + ["Phase Shift"] = "相位变换", + ["Pick Lock"] = "开锁", + ["Pick Pocket"] = "偷窃", + ["Pierce Armor"] = "Pierce Armor",-- TODO translate + ["Piercing Howl"] = "刺耳怒吼", + ["Piercing Ice"] = "刺骨寒冰", + ["Piercing Shadow"] = "Piercing Shadow",-- TODO translate + ["Piercing Shot"] = "Piercing Shot",-- TODO translate + ["Plague Cloud"] = "Plague Cloud",-- TODO translate + ["Plate Mail"] = "板甲", + ["Poison"] = "Poison",-- TODO translate + ["Poison Bolt"] = "Poison Bolt",-- TODO translate + ["Poison Bolt Volley"] = "Poison Bolt Volley",-- TODO translate + ["Poison Cleansing Totem"] = "祛病图腾", + ["Poison Cloud"] = "Poison Cloud",-- TODO translate + ["Poison Shock"] = "Poison Shock",-- TODO translate + ["Poisoned Harpoon"] = "Poisoned Harpoon",-- TODO translate + ["Poisoned Shot"] = "Poisoned Shot",-- TODO translate + ["Poisonous Blood"] = "Poisonous Blood",-- TODO translate + ["Poisons"] = "毒药", + ["Polearm Specialization"] = "长柄武器专精", + ["Polearms"] = "长柄武器", + ["Polymorph"] = "变形术", + ["Polymorph: Pig"] = "变形术:猪", + ["Polymorph: Turtle"] = "变形术:龟", + ["Portal: Darnassus"] = "传送门:达纳苏斯", + ["Portal: Ironforge"] = " 传送门:铁炉堡", + ["Portal: Orgrimmar"] = "传送门:奥格瑞玛", + ["Portal: Stormwind"] = "传送门:暴风城", + ["Portal: Thunder Bluff"] = "传送门:雷霆崖", + ["Portal: Undercity"] = "传送门:幽暗城", + ["Positive Charge"] = "Positive Charge", + ["Pounce"] = "突袭", + ["Pounce Bleed"] = "突袭", + ["Power Infusion"] = "能量灌注", + ["Power Word: Fortitude"] = "真言术:韧", + ["Power Word: Shield"] = "真言术:盾", + ["Prayer Beads Blessing"] = "Prayer Beads Blessing",-- TODO translate + ["Prayer of Fortitude"] = "坚韧祷言", + ["Prayer of Healing"] = "治疗祷言", + ["Prayer of Mending"] = "Prayer of Mending",-- TODO translate + ["Prayer of Shadow Protection"] = "暗影防护祷言", + ["Prayer of Spirit"] = "精神祷言", + ["Precision"] = "精确", + ["Predatory Strikes"] = "猛兽攻击", + ["Premeditation"] = "预谋", + ["Preparation"] = "伺机待发", + ["Presence of Mind"] = "气定神闲", + ["Primal Fury"] = "原始狂怒", + ["Prowl"] = "潜伏", + ["Psychic Scream"] = "心灵尖啸", + ["Pummel"] = "拳击", + ["Puncture"] = "Puncture",-- TODO translate + ["Purge"] = "净化术", + ["Purification"] = "净化", + ["Purify"] = "纯净术", + ["Pursuit of Justice"] = "正义追击", + ["Putrid Breath"] = "Putrid Breath",-- TODO translate + ["Putrid Enzyme"] = "Putrid Enzyme",-- TODO translate + ["Pyroblast"] = "炎爆术", + ["Pyroclasm"] = "火焰冲撞", + ["Quick Shots"] = "快速射击", + ["Quickness"] = "迅捷", + ["Radiation"] = "Radiation",-- TODO translate + ["Radiation Bolt"] = "Radiation Bolt",-- TODO translate + ["Radiation Cloud"] = "Radiation Cloud",-- TODO translate + ["Radiation Poisoning"] = "Radiation Poisoning",-- TODO translate + ["Rain of Fire"] = "火焰之雨", + ["Rake"] = "扫击", + ["Ram Riding"] = "骑术:羊", + ["Rampage"] = "Rampage",-- TODO translate + ["Ranged Weapon Specialization"] = "远程武器专精", + ["Rapid Concealment"] = "迅速隐蔽", + ["Rapid Fire"] = "急速射击", + ["Raptor Riding"] = "骑术:迅猛龙", + ["Raptor Strike"] = "猛禽一击", + ["Ravage"] = "毁灭", + ["Ravenous Claw"] = "Ravenous Claw",-- TODO translate + ["Readiness"] = "准备就绪", + ["Rebirth"] = "复生", + ["Rebuild"] = "Rebuild",-- TODO translate + ["Recently Bandaged"] = "Recently Bandaged",-- TODO translate + ["Reckless Charge"] = "无畏冲锋", + ["Recklessness"] = "鲁莽", + ["Reckoning"] = "清算", + ["Recombobulate"] = "Recombobulate",-- TODO translate + ["Redemption"] = "救赎", + ["Redoubt"] = "盾牌壁垒", + ["Reflection"] = "反射", + ["Regeneration"] = "回复", + ["Regrowth"] = "愈合", + ["Reincarnation"] = "复生", + ["Rejuvenation"] = "回春术", + ["Relentless Strikes"] = "无情打击", + ["Remorseless"] = "冷酷", + ["Remorseless Attacks"] = "冷酷攻击", + ["Remove Curse"] = "解除诅咒", + ["Remove Insignia"] = "解除徽记", + ["Remove Lesser Curse"] = "解除次级诅咒", + ["Rend"] = "撕裂", + ["Renew"] = "恢复", + ["Repentance"] = "忏悔", + ["Repulsive Gaze"] = "Repulsive Gaze",-- TODO translate + ["Restorative Totems"] = "Restorative Totems", + ["Resurrection"] = "复活", + ["Retaliation"] = "反击风暴", + ["Retribution Aura"] = "惩罚光环", + ["Revenge"] = "复仇", + ["Revenge Stun"] = "复仇昏迷", + ["Reverberation"] = "回响", + ["Revive Pet"] = "复活宠物", + ["Rhahk'Zor Slam"] = "Rhahk'Zor Slam",-- TODO translate + ["Ribbon of Souls"] = "Ribbon of Souls",-- TODO translate + ["Righteous Defense"] = "Righteous Defense",-- TODO translate + ["Righteous Fury"] = "正义之怒", + ["Rip"] = "撕扯", + ["Riposte"] = "还击", + ["Ritual of Doom"] = "末日仪式", + ["Ritual of Doom Effect"] = "末日仪式效果", + ["Ritual of Souls"] = "Ritual of Souls",-- TODO translate + ["Ritual of Summoning"] = "召唤仪式", + ["Rockbiter Weapon"] = "石化武器", + ["Rogue Passive"] = "盗贼被动效果",-- [TODO Translate not sure] + ["Rough Sharpening Stone"] = "劣质磨刀石", + ["Ruin"] = "毁灭", + ["Rupture"] = "割裂", + ["Ruthlessness"] = "无情", + ["Sacrifice"] = "牺牲", + ["Safe Fall"] = "安全降落", + ["Sanctity Aura"] = "圣洁光环", + ["Sap"] = "闷棍", + ["Savage Fury"] = "野蛮暴怒", + ["Savage Strikes"] = "野蛮打击", + ["Scare Beast"] = "恐吓野兽", + ["Scatter Shot"] = "驱散射击", + ["Scorch"] = "灼烧", + ["Scorpid Poison"] = "蝎毒", + ["Scorpid Sting"] = "毒蝎钉刺", + ["Screams of the Past"] = "Screams of the Past",-- TODO translate + ["Screech"] = "尖啸", + ["Seal Fate"] = "封印命运", + ["Seal of Blood"] = "Seal of Blood",-- TODO translate + ["Seal of Command"] = "命令圣印", + ["Seal of Justice"] = "公正圣印", + ["Seal of Light"] = "光明圣印", + ["Seal of Reckoning"] = "Seal of Reckoning",-- TODO translate + ["Seal of Righteousness"] = "正义圣印", + ["Seal of the Crusader"] = "十字军圣印", + ["Seal of Vengeance"] = "Seal of Vengeance",-- TODO translate + ["Seal of Wisdom"] = "智慧圣印", + ["Searing Light"] = "灼热之光", + ["Searing Pain"] = "灼热之痛", + ["Searing Totem"] = "灼热图腾", + ["Second Wind"] = "Second Wind", + ["Seduction"] = "诱惑", + ["Seed of Corruption"] = "Seed of Corruption",-- TODO translate + ["Sense Demons"] = "感知恶魔", + ["Sense Undead"] = "感知亡灵", + ["Sentry Totem"] = "岗哨图腾", + ["Serpent Sting"] = "毒蛇钉刺", + ["Setup"] = "调整", + ["Shackle Undead"] = "束缚亡灵", + ["Shadow Affinity"] = "暗影亲和", + ["Shadow Bolt"] = "暗影箭", + ["Shadow Bolt Volley"] = "Shadow Bolt Volley",-- TODO translate + ["Shadow Focus"] = "暗影集中", + ["Shadow Mastery"] = "暗影掌握", + ["Shadow Protection"] = "暗影防护", + ["Shadow Reach"] = "暗影延伸", + ["Shadow Resistance"] = "暗影抗性", + ["Shadow Resistance Aura"] = "暗影抗性光环", + ["Shadow Shock"] = "Shadow Shock",-- TODO translate + ["Shadow Trance"] = "暗影冥思", + ["Shadow Vulnerability"] = "暗影易伤", + ["Shadow Ward"] = "防护暗影结界", + ["Shadow Weakness"] = "Shadow Weakness", + ["Shadow Weaving"] = "暗影之波", + ["Shadow Word: Death"] = "Shadow Word: Death",-- TODO translate + ["Shadow Word: Pain"] = "暗言术:痛", + ["Shadowburn"] = "暗影灼烧", + ["Shadowfiend"] = "Shadowfiend",-- TODO translate + ["Shadowform"] = "暗影形态", + ["Shadowfury"] = "Shadowfury",-- TODO translate + ["Shadowguard"] = "暗影守卫", + ["Shadowmeld"] = "影遁", + ["Shadowmeld Passive"] = "影遁", + ["Shadowstep"] = "Shadowstep",-- TODO translate + ["Shamanistic Rage"] = "Shamanistic Rage",-- TODO translate + ["Sharpened Claws"] = "锋利兽爪", + ["Shatter"] = "碎冰", + ["Sheep"] = "Sheep",-- TODO translate + ["Shell Shield"] = "甲壳护盾", + ["Shield"] = "盾牌", + ["Shield Bash"] = "盾击", + ["Shield Bash - Silenced"] = "盾击 - 沉默", + ["Shield Block"] = "盾牌格挡", + ["Shield Slam"] = "盾牌猛击", + ["Shield Specialization"] = "盾牌专精", + ["Shield Wall"] = "盾墙", + ["Shiv"] = "Shiv",-- TODO translate + ["Shock"] = "Shock",-- TODO translate + ["Shoot"] = "射击", + ["Shoot Bow"] = "弓射击", + ["Shoot Crossbow"] = "弩射击", + ["Shoot Gun"] = "枪械射击", + ["Shred"] = "撕碎", + ["Shrink"] = "Shrink",-- TODO translate + ["Silence"] = "沉默", + ["Silencing Shot"] = "Silencing Shot", + ["Silent Resolve"] = "无声消退", + ["Sinister Strike"] = "邪恶攻击", + ["Siphon Life"] = "生命虹吸", + ["Skinning"] = "剥皮", + ["Skull Crack"] = "Skull Crack",-- TODO translate + ["Slam"] = "猛击", + ["Sleep"] = "沉睡",-- [TODO Translate not so sure] + ["Slice and Dice"] = "切割", + ["Slow"] = "Slow", + ["Slow Fall"] = "缓落术", + ["Slowing Poison"] = "Slowing Poison",-- TODO translate + ["Smelting"] = "熔炼", + ["Smite"] = "惩击", + ["Smite Slam"] = "Smite Slam",-- TODO translate + ["Smite Stomp"] = "Smite Stomp",-- TODO translate + ["Smoke Bomb"] = "Smoke Bomb",-- TODO translate + ["Snake Trap"] = "Snake Trap",-- TODO translate + ["Snap Kick"] = "Snap Kick",-- TODO translate + ["Solid Sharpening Stone"] = "坚固的磨刀石", + ["Sonic Burst"] = "Sonic Burst",-- TODO translate + ["Soothe Animal"] = "安抚动物", + ["Soothing Kiss"] = "安抚之吻", + ["Soul Bite"] = "Soul Bite",-- TODO translate + ["Soul Drain"] = "Soul Drain",-- TODO translate + ["Soul Fire"] = "灵魂之火", + ["Soul Link"] = "灵魂链接", + ["Soul Siphon"] = "灵魂虹吸", + ["Soul Tap"] = "Soul Tap",-- TODO translate + ["Soulshatter"] = "Soulshatter",-- TODO translate + ["Soulstone Resurrection"] = "灵魂石复活", + ["Spell Lock"] = "法术封锁", + ["Spell Reflection"] = "Spell Reflection", + ["Spell Warding"] = "法术屏障", + ["Spellsteal"] = "Spellsteal",-- TODO translate + ["Spirit Bond"] = "灵魂连接", + ["Spirit Burst"] = "Spirit Burst",-- TODO translate + ["Spirit of Redemption"] = "救赎之魂", + ["Spirit Tap"] = "精神分流", + ["Spiritual Attunement"] = "Spiritual Attunement",-- TODO translate + ["Spiritual Focus"] = "精神集中", + ["Spiritual Guidance"] = "精神指引", + ["Spiritual Healing"] = "精神治疗", + ["Spit"] = "Spit",-- TODO translate + ["Spore Cloud"] = "Spore Cloud",-- TODO translate + ["Sprint"] = "疾跑", + ["Stance Mastery"] = "Stance Mastery",-- TODO translate + ["Starfire"] = "星火术", + ["Starfire Stun"] = "星火昏迷", + ["Starshards"] = "星辰碎片", + ["Staves"] = "法杖", + ["Steady Shot"] = "Steady Shot",-- TODO translate + ["Stealth"] = "潜行", + ["Stoneclaw Totem"] = "石爪图腾", + ["Stoneform"] = "石像形态", + ["Stoneskin Totem"] = "石肤图腾", + ["Stormstrike"] = "风暴打击", + ["Strength of Earth Totem"] = "大地之力图腾", + ["Strike"] = "Strike",-- TODO translate + ["Stuck"] = "卡死", + ["Stun"] = "Stun",-- TODO translate + ["Subtlety"] = "微妙", + ["Suffering"] = "受难", + ["Summon Charger"] = "召唤战马", + ["Summon Dreadsteed"] = "召唤恐惧战马", + ["Summon Felguard"] = "Summon Felguard",-- TODO translate + ["Summon Felhunter"] = "召唤地狱猎犬", + ["Summon Felsteed"] = "召唤地狱战马", + ["Summon Imp"] = "召唤小鬼", + ["Summon Spawn of Bael'Gar"] = "Summon Spawn of Bael'Gar",-- TODO translate + ["Summon Succubus"] = "召唤魅魔", + ["Summon Voidwalker"] = "召唤虚空行者", + ["Summon Warhorse"] = "召唤军马", + ["Summon Water Elemental"] = "Summon Water Elemental", + ["Sunder Armor"] = "破甲", + ["Suppression"] = "压制", + ["Surefooted"] = "稳固", + ["Survivalist"] = "生存专家", + ["Sweeping Slam"] = "Sweeping Slam",-- TODO translate + ["Sweeping Strikes"] = "横扫攻击", + ["Swiftmend"] = "迅捷治愈", + ["Swipe"] = "挥击", + ["Swoop"] = "Swoop",-- TODO translate + ["Sword Specialization"] = "剑类武器专精", + ["Tactical Mastery"] = "战术掌握", + ["Tailoring"] = "裁缝", + ["Tainted Blood"] = "腐坏之血", + ["Tame Beast"] = "驯服野兽", + ["Tamed Pet Passive"] = "驯服宠物(被动)", + ["Taunt"] = "嘲讽", + ["Teleport: Darnassus"] = "传送:达纳苏斯", + ["Teleport: Ironforge"] = "传送:铁炉堡", + ["Teleport: Moonglade"] = "传送:月光林地", + ["Teleport: Orgrimmar"] = "传送:奥格瑞玛", + ["Teleport: Stormwind"] = "传送:暴风城", + ["Teleport: Thunder Bluff"] = "传送:雷霆崖", + ["Teleport: Undercity"] = "传送:幽暗城", + ["Tendon Rip"] = "Tendon Rip",-- TODO translate + ["Tendon Slice"] = "Tendon Slice",-- TODO translate + ["Terrify"] = "Terrify",-- TODO translate + ["Terrifying Screech"] = "Terrifying Screech",-- TODO translate + ["Thick Hide"] = "厚皮", + ["Thorn Volley"] = "Thorn Volley",-- TODO translate + ["Thorns"] = "荆棘术", + ["Thousand Blades"] = "Thousand Blades",-- TODO translate + ["Threatening Gaze"] = "Threatening Gaze",-- TODO translate + ["Throw"] = "投掷", + ["Throw Axe"] = "Throw Axe",-- TODO translate + ["Throw Dynamite"] = "Throw Dynamite",-- TODO translate + ["Throw Liquid Fire"] = "Throw Liquid Fire",-- TODO translate + ["Throw Wrench"] = "Throw Wrench",-- TODO translate + ["Throwing Specialization"] = "投掷专精", + ["Throwing Weapon Specialization"] = "投掷武器专精", + ["Thrown"] = "投掷", + ["Thunder Clap"] = "雷霆一击", + ["Thunderclap"] = "Thunderclap",-- TODO translate + ["Thunderfury"] = "Thunderfury",-- TODO translate + ["Thundering Strikes"] = "雷鸣猛击", + ["Thundershock"] = "Thundershock",-- TODO translate + ["Thunderstomp"] = "雷霆践踏", + ["Tidal Focus"] = "潮汐集中", + ["Tidal Mastery"] = "潮汐掌握", + ["Tiger Riding"] = "骑术:豹", + ["Tiger's Fury"] = "猛虎之怒", + ["Torment"] = "折磨", + ["Totem"] = "图腾", + ["Totem of Wrath"] = "Totem of Wrath",-- TODO translate + ["Totemic Focus"] = "图腾集中", + ["Touch of Weakness"] = "虚弱之触", + ["Toughness"] = "坚韧", + ["Toxic Saliva"] = "Toxic Saliva",-- TODO translate + ["Toxic Spit"] = "Toxic Spit",-- TODO translate + ["Toxic Volley"] = "Toxic Volley",-- TODO translate + ["Traces of Silithyst"] = "Traces of Silithyst", + ["Track Beasts"] = "追踪野兽", + ["Track Demons"] = "追踪恶魔", + ["Track Dragonkin"] = "追踪龙类", + ["Track Elementals"] = "追踪元素生物", + ["Track Giants"] = "追踪巨人", + ["Track Hidden"] = "追踪隐藏生物", + ["Track Humanoids"] = "追踪人型生物", + ["Track Undead"] = "追踪亡灵", + ["Trample"] = "Trample",-- TODO translate + ["Tranquil Air Totem"] = "宁静之风图腾", + ["Tranquil Spirit"] = "宁静之魂", + ["Tranquility"] = "宁静", + ["Tranquilizing Poison"] = "Tranquilizing Poison",-- TODO translate + ["Tranquilizing Shot"] = "宁神射击", + ["Trap Mastery"] = "陷阱掌握", + ["Travel Form"] = "旅行形态", + ["Tree of Life"] = "Tree of Life",-- TODO translate + ["Tremor Totem"] = "战栗图腾", + ["Tribal Leatherworking"] = "部族制皮", + ["Trueshot Aura"] = "强击光环", + ["Turn Undead"] = "超度亡灵", + ["Twisted Tranquility"] = "Twisted Tranquility",-- TODO translate + ["Two-Handed Axes"] = "双手斧", + ["Two-Handed Axes and Maces"] = "双手斧和锤", + ["Two-Handed Maces"] = "双手锤", + ["Two-Handed Swords"] = "无光泽的双刃刀", + ["Two-Handed Weapon Specialization"] = "双手武器专精", + ["Unarmed"] = "徒手", + ["Unbreakable Will"] = "坚定意志", + ["Unbridled Wrath"] = "怒不可遏", + ["Unbridled Wrath Effect"] = "Unbridled Wrath Effect",-- TODO translate + ["Undead Horsemanship"] = "骑术:骸骨战马", + ["Underwater Breathing"] = "水下呼吸", + ["Unending Breath"] = "魔息术", + ["Unholy Frenzy"] = "Unholy Frenzy",-- TODO translate + ["Unholy Power"] = "邪恶强化", + ["Unleashed Fury"] = "狂怒释放", + ["Unleashed Rage"] = "Unleashed Rage", + ["Unstable Affliction"] = "Unstable Affliction",-- TODO translate + ["Unstable Concoction"] = "Unstable Concoction",-- TODO translate + ["Unstable Power"] = "Unstable Power",-- TODO translate + ["Unyielding Faith"] = "不灭信仰", + ["Uppercut"] = "Uppercut",-- TODO translate + ["Vampiric Embrace"] = "吸血鬼的拥抱", + ["Vampiric Touch"] = "Vampiric Touch",-- TODO translate + ["Vanish"] = "消失", + ["Vanished"] = "消失", + ["Veil of Shadow"] = "Veil of Shadow",-- TODO translate + ["Vengeance"] = "复仇", + ["Venom Spit"] = "Venom Spit",-- TODO translate + ["Venom Sting"] = "Venom Sting",-- TODO translate + ["Venomhide Poison"] = "Venomhide Poison",-- TODO translate + ["Vicious Rend"] = "Vicious Rend",-- TODO translate + ["Victory Rush"] = "Victory Rush",-- TODO translate + ["Vigor"] = "精力", + ["Vile Poisons"] = "恶性毒药", + ["Vindication"] = "辩护", + ["Viper Sting"] = "蝰蛇钉刺", + ["Virulent Poison"] = "Virulent Poison",-- TODO translate + ["Void Bolt"] = "Void Bolt",-- TODO translate + ["Volley"] = "乱射", + ["Walking Bomb Effect"] = "Walking Bomb Effect",-- TODO translate + ["Wand Specialization"] = "魔杖掌握", + ["Wandering Plague"] = "Wandering Plague",-- TODO translate + ["Wands"] = "魔杖", + ["War Stomp"] = "战争践踏", + ["Water"] = "Water",-- TODO translate + ["Water Breathing"] = "水下呼吸", + ["Water Shield"] = "Water Shield",-- TODO translate + ["Water Walking"] = "水上行走", + ["Waterbolt"] = "Waterbolt",-- TODO translate + ["Wavering Will"] = "Wavering Will",-- TODO translate + ["Weakened Soul"] = "虚弱灵魂", + ["Weaponsmith"] = "武器锻造师", + ["Web"] = "Web",-- TODO translate + ["Web Explosion"] = "Web Explosion",-- TODO translate + ["Web Spin"] = "Web Spin",-- TODO translate + ["Web Spray"] = "Web Spray",-- TODO translate + ["Whirling Barrage"] = "Whirling Barrage",-- TODO translate + ["Whirling Trip"] = "Whirling Trip",-- TODO translate + ["Whirlwind"] = "旋风斩", + ["Wide Slash"] = "Wide Slash",-- TODO translate + ["Will of Hakkar"] = "Will of Hakkar",-- TODO translate + ["Will of the Forsaken"] = "亡灵意志", + ["Windfury Totem"] = "风怒图腾", + ["Windfury Weapon"] = "风怒武器", + ["Windsor's Frenzy"] = "Windsor's Frenzy",-- TODO translate + ["Windwall Totem"] = "风墙图腾", + ["Wing Clip"] = "摔绊", + ["Wing Flap"] = "Wing Flap",-- TODO translate + ["Winter's Chill"] = "深冬之寒", + ["Wisp Spirit"] = "精灵之魂", + ["Wolf Riding"] = "骑术:狼", + ["Wound Poison"] = "致伤毒药", + ["Wound Poison II"] = "致伤毒药 II", + ["Wound Poison III"] = "致伤毒药 III", + ["Wound Poison IV"] = "致伤毒药 IV", + ["Wrath"] = "愤怒", + ["Wrath of Air Totem"] = "Wrath of Air Totem",-- TODO translate + ["Wyvern Sting"] = "翼龙钉刺", +} + +end) +__bundle_register("Locale/enUS/Translations.enUS.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +return { + ["Announces in chat when your tranquilizing shot hits or misses a target."] = "Announces in chat when your tranquilizing shot hits or misses a target.", + ["Aspect Tracker"] = "Aspect Tracker", + ["Auto Shot Timer"] = "Auto Shot Timer", + ["Border Style"] = "Border Style", + ["Both Directions"] = "Both Directions", + ["Castbar"] = "Castbar", + ["Casting"] = "Casting", + ["Casting Tranq Shot"] = "Casting Tranq Shot", + ["Close"] = "Close", + ["Close Window"] = "Close Window", + ["Dead Zone"] = "Dead Zone", + ["Debug Level"] = "Debug Level", + ["Hunter's Mark"] = "Hunter's Mark", + ["It's always safe to upgrade Quiver. You won't lose any of your configuration."] = "It's always safe to upgrade Quiver. You won't lose any of your configuration.", + ["Left to Right"] = "Left to Right", + ["Lock/Unlock Frames"] = "Lock/Unlock Frames", + ["Long Range"] = "Long Range", + ["Melee Range"] = "Melee Range", + ["*** MISSED Tranq Shot ***"] = "*** MISSED Tranq Shot ***", + ["New version %s available at %s"] = "New version %s available at %s", + ["None"] = "None", + ["Out of Range"] = "Out of Range", + ["Quiver is for hunters."] = "Quiver is for hunters.", + ["Quiver Unlocked. Show config dialog with /qq or /quiver.\nClick the lock icon when done."] = "Quiver Unlocked. Show config dialog with /qq or /quiver.\nClick the lock icon when done.", + ["Range Indicator"] = "Range Indicator", + ["Reloading"] = "Reloading", + ["Reset All Frame Sizes and Positions"] = "Reset All Frame Sizes and Positions", + ["Reset Color"] = "Reset Color", + ["Reset Frame Size and Position"] = "Reset Frame Size and Position", + ["Reset Miss Message to Default"] = "Reset Miss Message to Default", + ["Reset Tranq Message to Default"] = "Reset Tranq Message to Default", + ["Scare Beast"] = "Scare Beast", + ["Scatter Shot"] = "Scatter Shot", + ["Shoot / Reload"] = "Shoot / Reload", + ["Shooting"] = "Shooting", + ["Short Range"] = "Short Range", + ["Shows Aimed Shot, Multi-Shot, and Trueshot."] = "Shows Aimed Shot, Multi-Shot, and Trueshot.", + ["Shows when abilities are in range. Requires spellbook abilities placed somewhere on your action bars."] = "Shows when abilities are in range. Requires spellbook abilities placed somewhere on your action bars.", + ["Simple"] = "Simple", + ["Swap Shoot and Reload Colours"] = "Swap Shoot and Reload Colours", + ["Tooltip"] = "Tooltip", + ["Tranq Shot Announcer"] = "Tranq Shot Announcer", + ["Tranq Speech"] = "Tranq Speech", + ["Trueshot Aura Alarm"] = "Trueshot Aura Alarm", + ["Verbose"] = "Verbose", +} + +end) +__bundle_register("Locale/enUS/Client.enUS.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +local Spell = require("Locale/enUS/Spell.enUS.lua") +-- local Zone = require "Locale/enUS/Zone.enUS.lua" + +return { + CombatLog = { + Consumes = { + ManaPotion = "You gain (.*) Mana from Restore Mana.", + HealthPotion = "Your Healing Potion heals you for (.*).", + Healthstone = "Your (.*) Healthstone heals you for (.*).", + Tea = "Your Tea with Sugar heals you for (.*).", + }, + Tranq = { + Fail = "You fail to dispel", + Miss = "Your Tranquilizing Shot miss", + Resist = "Your Tranquilizing Shot was resisted", + }, + }, + Spell = Spell, + SpellReverse = Spell, +} + +end) +__bundle_register("Locale/enUS/Spell.enUS.lua", function(require, _LOADED, __bundle_register, __bundle_modules) +return { + ["Aspect of the Wolf"] = "Aspect of the Wolf", + ["Trueshot"] = "Trueshot", + ["Abolish Disease"] = "Abolish Disease", + ["Abolish Poison"] = "Abolish Poison", + ["Abolish Poison Effect"] = "Abolish Poison Effect", + ["Acid Breath"] = "Acid Breath", + ["Acid of Hakkar"] = "Acid of Hakkar", + ["Acid Spit"] = "Acid Spit", + ["Acid Splash"] = "Acid Splash", + ["Activate MG Turret"] = "Activate MG Turret", + ["Adrenaline Rush"] = "Adrenaline Rush", + ["Aftermath"] = "Aftermath", + ["Aggression"] = "Aggression", + ["Aimed Shot"] = "Aimed Shot", + ["Alchemy"] = "Alchemy", + ["Ambush"] = "Ambush", + ["Amplify Curse"] = "Amplify Curse", + ["Amplify Damage"] = "Amplify Damage", + ["Amplify Flames"] = "Amplify Flames", + ["Amplify Magic"] = "Amplify Magic", + ["Ancestral Fortitude"] = "Ancestral Fortitude", + ["Ancestral Healing"] = "Ancestral Healing", + ["Ancestral Knowledge"] = "Ancestral Knowledge", + ["Ancestral Spirit"] = "Ancestral Spirit", + ["Anesthetic Poison"] = "Anesthetic Poison", + ["Anger Management"] = "Anger Management", + ["Anguish"] = "Anguish", + ["Anticipation"] = "Anticipation", + ["Aqua Jet"] = "Aqua Jet", + ["Aquatic Form"] = "Aquatic Form", + ["Arcane Blast"] = "Arcane Blast", + ["Arcane Bolt"] = "Arcane Bolt", + ["Arcane Brilliance"] = "Arcane Brilliance", + ["Arcane Concentration"] = "Arcane Concentration", + ["Arcane Explosion"] = "Arcane Explosion", + ["Arcane Focus"] = "Arcane Focus", + ["Arcane Instability"] = "Arcane Instability", + ["Arcane Intellect"] = "Arcane Intellect", + ["Arcane Meditation"] = "Arcane Meditation", + ["Arcane Mind"] = "Arcane Mind", + ["Arcane Missiles"] = "Arcane Missiles", + ["Arcane Potency"] = "Arcane Potency", + ["Arcane Power"] = "Arcane Power", + ["Arcane Resistance"] = "Arcane Resistance", + ["Arcane Shot"] = "Arcane Shot", + ["Arcane Subtlety"] = "Arcane Subtlety", + ["Arcane Weakness"] = "Arcane Weakness", + ["Arcing Smash"] = "Arcing Smash", + ["Arctic Reach"] = "Arctic Reach", + ["Armorsmith"] = "Armorsmith", + ["Arugal's Curse"] = "Arugal's Curse", + ["Arugal's Gift"] = "Arugal's Gift", + ["Ascendance"] = "Ascendance", + ["Aspect of Arlokk"] = "Aspect of Arlokk", + ["Aspect of Jeklik"] = "Aspect of Jeklik", + ["Aspect of Mar'li"] = "Aspect of Mar'li", + ["Aspect of the Beast"] = "Aspect of the Beast", + ["Aspect of the Cheetah"] = "Aspect of the Cheetah", + ["Aspect of the Hawk"] = "Aspect of the Hawk", + ["Aspect of the Monkey"] = "Aspect of the Monkey", + ["Aspect of the Pack"] = "Aspect of the Pack", + ["Aspect of the Viper"] = "Aspect of the Viper", + ["Aspect of the Wild"] = "Aspect of the Wild", + ["Aspect of Venoxis"] = "Aspect of Venoxis", + ["Astral Recall"] = "Astral Recall", + ["Attack"] = "Attack", + ["Attacking"] = "Attacking", + ["Aura of Command"] = "Aura of Command", + ["Aural Shock"] = "Aural Shock", + ["Auto Shot"] = "Auto Shot", + ["Avenger's Shield"] = "Avenger's Shield", + ["Avenging Wrath"] = "Avenging Wrath", + ["Avoidance"] = "Avoidance", + ["Axe Flurry"] = "Axe Flurry", + ["Axe Specialization"] = "Axe Specialization", + ["Axe Toss"] = "Axe Toss", + ["Backhand"] = "Backhand", + ["Backlash"] = "Backlash", + ["Backstab"] = "Backstab", + ["Bane"] = "Bane", + ["Baneful Poison"] = "Baneful Poison", + ["Banish"] = "Banish", + ["Banshee Curse"] = "Banshee Curse", + ["Banshee Shriek"] = "Banshee Shriek", + ["Barbed Sting"] = "Barbed Sting", + ["Barkskin"] = "Barkskin", + ["Barkskin Effect"] = "Barkskin Effect", + ["Barrage"] = "Barrage", + ["Bash"] = "Bash", + ["Basic Campfire"] = "Basic Campfire", + ["Battle Shout"] = "Battle Shout", + ["Battle Stance"] = "Battle Stance", + ["Battle Stance Passive"] = "Battle Stance Passive", + ["Bear Form"] = "Bear Form", + ["Beast Lore"] = "Beast Lore", + ["Beast Slaying"] = "Beast Slaying", + ["Beast Training"] = "Beast Training", + ["The Beast Within"] = "The Beast Within", + ["Befuddlement"] = "Befuddlement", + ["Benediction"] = "Benediction", + ["Berserker Charge"] = "Berserker Charge", + ["Berserker Rage"] = "Berserker Rage", + ["Berserker Stance"] = "Berserker Stance", + ["Berserker Stance Passive"] = "Berserker Stance Passive", + ["Berserking"] = "Berserking", + ["Bestial Discipline"] = "Bestial Discipline", + ["Bestial Swiftness"] = "Bestial Swiftness", + ["Bestial Wrath"] = "Bestial Wrath", + ["Biletoad Infection"] = "Biletoad Infection", + ["Binding Heal"] = "Binding Heal", + ["Bite"] = "Bite", + ["Black Arrow"] = "Black Arrow", + ["Blackout"] = "Blackout", + ["Blacksmithing"] = "Blacksmithing", + ["Blade Flurry"] = "Blade Flurry", + ["Blast Wave"] = "Blast Wave", + ["Blaze"] = "Blaze", + ["Blazing Speed"] = "Blazing Speed", + ["Blessed Recovery"] = "Blessed Recovery", + ["Blessing of Blackfathom"] = "Blessing of Blackfathom", + ["Blessing of Freedom"] = "Blessing of Freedom", + ["Blessing of Kings"] = "Blessing of Kings", + ["Blessing of Light"] = "Blessing of Light", + ["Blessing of Might"] = "Blessing of Might", + ["Blessing of Protection"] = "Blessing of Protection", + ["Blessing of Sacrifice"] = "Blessing of Sacrifice", + ["Blessing of Salvation"] = "Blessing of Salvation", + ["Blessing of Sanctuary"] = "Blessing of Sanctuary", + ["Blessing of Shahram"] = "Blessing of Shahram", + ["Blessing of Wisdom"] = "Blessing of Wisdom", + ["Blind"] = "Blind", + ["Blinding Powder"] = "Blinding Powder", + ["Blink"] = "Blink", + ["Blizzard"] = "Blizzard", + ["Block"] = "Block", + ["Blood Craze"] = "Blood Craze", + ["Blood Frenzy"] = "Blood Frenzy", + ["Blood Funnel"] = "Blood Funnel", + ["Blood Fury"] = "Blood Fury", + ["Blood Leech"] = "Blood Leech", + ["Blood Pact"] = "Blood Pact", + ["Blood Siphon"] = "Blood Siphon", + ["Blood Tap"] = "Blood Tap", + ["Bloodlust"] = "Bloodlust", + ["Bloodrage"] = "Bloodrage", + ["Bloodthirst"] = "Bloodthirst", + ["Bomb"] = "Bomb", + ["Booming Voice"] = "Booming Voice", + ["Boulder"] = "Boulder", + ["Bow Specialization"] = "Bow Specialization", + ["Bows"] = "Bows", + ["Brain Wash"] = "Brain Wash", + ["Bright Campfire"] = "Bright Campfire", + ["Brutal Impact"] = "Brutal Impact", + ["Burning Adrenaline"] = "Burning Adrenaline", + ["Burning Soul"] = "Burning Soul", + ["Burning Wish"] = "Burning Wish", + ["Butcher Drain"] = "Butcher Drain", + ["Call of Flame"] = "Call of Flame", + ["Call of the Grave"] = "Call of the Grave", + ["Call of Thunder"] = "Call of Thunder", + ["Call Pet"] = "Call Pet", + ["Camouflage"] = "Camouflage", + ["Cannibalize"] = "Cannibalize", + ["Cat Form"] = "Cat Form", + ["Cataclysm"] = "Cataclysm", + ["Cause Insanity"] = "Cause Insanity", + ["Chain Bolt"] = "Chain Bolt", + ["Chain Burn"] = "Chain Burn", + ["Chain Heal"] = "Chain Heal", + ["Chain Lightning"] = "Chain Lightning", + ["Chained Bolt"] = "Chained Bolt", + ["Chains of Ice"] = "Chains of Ice", + ["Challenging Roar"] = "Challenging Roar", + ["Challenging Shout"] = "Challenging Shout", + ["Charge"] = "Charge", + ["Charge Rage Bonus Effect"] = "Charge Rage Bonus Effect", + ["Charge Stun"] = "Charge Stun", + ["Cheap Shot"] = "Cheap Shot", + ["Chilled"] = "Chilled", + ["Chilling Touch"] = "Chilling Touch", + ["Chromatic Infusion"] = "Chromatic Infusion", + ["Circle of Healing"] = "Circle of Healing", + ["Claw"] = "Claw", + ["Cleanse"] = "Cleanse", + ["Cleanse Nova"] = "Cleanse Nova", + ["Clearcasting"] = "Clearcasting", + ["Cleave"] = "Cleave", + ["Clever Traps"] = "Clever Traps", + ["Cloak of Shadows"] = "Cloak of Shadows", + ["Closing"] = "Closing", + ["Cloth"] = "Cloth", + ["Coarse Sharpening Stone"] = "Coarse Sharpening Stone", + ["Cobra Reflexes"] = "Cobra Reflexes", + ["Cold Blood"] = "Cold Blood", + ["Cold Snap"] = "Cold Snap", + ["Combat Endurance"] = "Combat Endurance", + ["Combustion"] = "Combustion", + ["Command"] = "Command", + ["Commanding Shout"] = "Commanding Shout", + ["Concentration Aura"] = "Concentration Aura", + ["Concussion"] = "Concussion", + ["Concussion Blow"] = "Concussion Blow", + ["Concussive Shot"] = "Concussive Shot", + ["Cone of Cold"] = "Cone of Cold", + ["Conflagrate"] = "Conflagrate", + ["Conjure Food"] = "Conjure Food", + ["Conjure Mana Agate"] = "Conjure Mana Agate", + ["Conjure Mana Citrine"] = "Conjure Mana Citrine", + ["Conjure Mana Jade"] = "Conjure Mana Jade", + ["Conjure Mana Ruby"] = "Conjure Mana Ruby", + ["Conjure Water"] = "Conjure Water", + ["Consecrated Sharpening Stone"] = "Consecrated Sharpening Stone", + ["Consecration"] = "Consecration", + ["Consume Magic"] = "Consume Magic", + ["Consume Shadows"] = "Consume Shadows", + ["Consuming Shadows"] = "Consuming Shadows", + ["Convection"] = "Convection", + ["Conviction"] = "Conviction", + ["Cooking"] = "Cooking", + ["Corrosive Acid Breath"] = "Corrosive Acid Breath", + ["Corrosive Ooze"] = "Corrosive Ooze", + ["Corrosive Poison"] = "Corrosive Poison", + ["Corrupted Blood"] = "Corrupted Blood", + ["Corruption"] = "Corruption", + ["Counterattack"] = "Counterattack", + ["Counterspell"] = "Counterspell", + ["Counterspell - Silenced"] = "Counterspell - Silenced", + ["Cower"] = "Cower", + ["Create Firestone"] = "Create Firestone", + ["Create Firestone (Greater)"] = "Create Firestone (Greater)", + ["Create Firestone (Lesser)"] = "Create Firestone (Lesser)", + ["Create Firestone (Major)"] = "Create Firestone (Major)", + ["Create Healthstone"] = "Create Healthstone", + ["Create Healthstone (Greater)"] = "Create Healthstone (Greater)", + ["Create Healthstone (Lesser)"] = "Create Healthstone (Lesser)", + ["Create Healthstone (Major)"] = "Create Healthstone (Major)", + ["Create Healthstone (Minor)"] = "Create Healthstone (Minor)", + ["Create Soulstone"] = "Create Soulstone", + ["Create Soulstone (Greater)"] = "Create Soulstone (Greater)", + ["Create Soulstone (Lesser)"] = "Create Soulstone (Lesser)", + ["Create Soulstone (Major)"] = "Create Soulstone (Major)", + ["Create Soulstone (Minor)"] = "Create Soulstone (Minor)", + ["Create Spellstone"] = "Create Spellstone", + ["Create Spellstone (Greater)"] = "Create Spellstone (Greater)", + ["Create Spellstone (Major)"] = "Create Spellstone (Major)", + ["Create Spellstone (Master)"] = "Create Spellstone (Master)", + ["Creeper Venom"] = "Creeper Venom", + ["Cripple"] = "Cripple", + ["Crippling Poison"] = "Crippling Poison", + ["Crippling Poison II"] = "Crippling Poison II", + ["Critical Mass"] = "Critical Mass", + ["Crossbows"] = "Crossbows", + ["Crowd Pummel"] = "Crowd Pummel", + ["Cruelty"] = "Cruelty", + ["Crusader Aura"] = "Crusader Aura", + ["Crusader Strike"] = "Crusader Strike", + ["Crusader's Wrath"] = "Crusader's Wrath", + ["Crystal Charge"] = "Crystal Charge", + ["Crystal Force"] = "Crystal Force", + ["Crystal Restore"] = "Crystal Restore", + ["Crystal Spire"] = "Crystal Spire", + ["Crystal Ward"] = "Crystal Ward", + ["Crystal Yield"] = "Crystal Yield", + ["Crystalline Slumber"] = "Crystalline Slumber", + ["Cultivation"] = "Cultivation", + ["Cure Disease"] = "Cure Disease", + ["Cure Poison"] = "Cure Poison", + ["Curse of Agony"] = "Curse of Agony", + ["Curse of Blood"] = "Curse of Blood", + ["Curse of Doom"] = "Curse of Doom", + ["Curse of Doom Effect"] = "Curse of Doom Effect", + ["Curse of Exhaustion"] = "Curse of Exhaustion", + ["Curse of Idiocy"] = "Curse of Idiocy", + ["Curse of Recklessness"] = "Curse of Recklessness", + ["Curse of Shadow"] = "Curse of Shadow", + ["Curse of the Deadwood"] = "Curse of the Deadwood", + ["Curse of the Elemental Lord"] = "Curse of the Elemental Lord", + ["Curse of the Elements"] = "Curse of the Elements", + ["Curse of Tongues"] = "Curse of Tongues", + ["Curse of Tuten'kash"] = "Curse of Tuten'kash", + ["Curse of Weakness"] = "Curse of Weakness", + ["Cursed Blood"] = "Cursed Blood", + ["Cyclone"] = "Cyclone", + ["Dagger Specialization"] = "Dagger Specialization", + ["Daggers"] = "Daggers", + ["Dampen Magic"] = "Dampen Magic", + ["Dark Iron Bomb"] = "Dark Iron Bomb", + ["Dark Offering"] = "Dark Offering", + ["Dark Pact"] = "Dark Pact", + ["Darkness"] = "Darkness", + ["Dash"] = "Dash", + ["Dazed"] = "Dazed", + ["Deadly Poison"] = "Deadly Poison", + ["Deadly Poison II"] = "Deadly Poison II", + ["Deadly Poison III"] = "Deadly Poison III", + ["Deadly Poison IV"] = "Deadly Poison IV", + ["Deadly Poison V"] = "Deadly Poison V", + ["Deadly Throw"] = "Deadly Throw", + ["Death Coil"] = "Death Coil", + ["Death Wish"] = "Death Wish", + ["Deep Sleep"] = "Deep Sleep", + ["Deep Slumber"] = "Deep Slumber", + ["Deep Wounds"] = "Deep Wounds", + ["Defense"] = "Defense", + ["Defensive Stance"] = "Defensive Stance", + ["Defensive Stance Passive"] = "Defensive Stance Passive", + ["Defensive State"] = "Defensive State", + ["Defensive State 2"] = "Defensive State 2", + ["Defiance"] = "Defiance", + ["Deflection"] = "Deflection", + ["Delusions of Jin'do"] = "Delusions of Jin'do", + ["Demon Armor"] = "Demon Armor", + ["Demon Skin"] = "Demon Skin", + ["Demonic Embrace"] = "Demonic Embrace", + ["Demonic Frenzy"] = "Demonic Frenzy", + ["Demonic Sacrifice"] = "Demonic Sacrifice", + ["Demoralizing Roar"] = "Demoralizing Roar", + ["Demoralizing Shout"] = "Demoralizing Shout", + ["Dense Sharpening Stone"] = "Dense Sharpening Stone", + ["Desperate Prayer"] = "Desperate Prayer", + ["Destructive Reach"] = "Destructive Reach", + ["Detect"] = "Detect", + ["Detect Greater Invisibility"] = "Detect Greater Invisibility", + ["Detect Invisibility"] = "Detect Invisibility", + ["Detect Lesser Invisibility"] = "Detect Lesser Invisibility", + ["Detect Magic"] = "Detect Magic", + ["Detect Traps"] = "Detect Traps", + ["Deterrence"] = "Deterrence", + ["Detonation"] = "Detonation", + ["Devastate"] = "Devastate", + ["Devastation"] = "Devastation", + ["Devotion Aura"] = "Devotion Aura", + ["Devour Magic"] = "Devour Magic", + ["Devour Magic Effect"] = "Devour Magic Effect", + ["Devouring Plague"] = "Devouring Plague", + ["Diamond Flask"] = "Diamond Flask", + ["Diplomacy"] = "Diplomacy", + ["Dire Bear Form"] = "Dire Bear Form", + ["Dire Growl"] = "Dire Growl", + ["Disarm"] = "Disarm", + ["Disarm Trap"] = "Disarm Trap", + ["Disease Cleansing Totem"] = "Disease Cleansing Totem", + ["Disease Cloud"] = "Disease Cloud", + ["Diseased Shot"] = "Diseased Shot", + ["Diseased Spit"] = "Diseased Spit", + ["Disenchant"] = "Disenchant", + ["Disengage"] = "Disengage", + ["Disjunction"] = "Disjunction", + ["Dismiss Pet"] = "Dismiss Pet", + ["Dispel Magic"] = "Dispel Magic", + ["Distract"] = "Distract", + ["Distracting Pain"] = "Distracting Pain", + ["Distracting Shot"] = "Distracting Shot", + ["Dive"] = "Dive", + ["Divine Favor"] = "Divine Favor", + ["Divine Fury"] = "Divine Fury", + ["Divine Illumination"] = "Divine Illumination", + ["Divine Intellect"] = "Divine Intellect", + ["Divine Intervention"] = "Divine Intervention", + ["Divine Protection"] = "Divine Protection", + ["Divine Shield"] = "Divine Shield", + ["Divine Spirit"] = "Divine Spirit", + ["Divine Strength"] = "Divine Strength", + ["Diving Sweep"] = "Diving Sweep", + ["Dodge"] = "Dodge", + ["Dominate Mind"] = "Dominate Mind", + ["Dragon's Breath"] = "Dragon's Breath", + ["Dragonscale Leatherworking"] = "Dragonscale Leatherworking", + ["Drain Life"] = "Drain Life", + ["Drain Mana"] = "Drain Mana", + ["Drain Soul"] = "Drain Soul", + ["Dredge Sickness"] = "Dredge Sickness", + ["Drink"] = "Drink", + ["Druid's Slumber"] = "Druid's Slumber", + ["Dual Wield"] = "Dual Wield", + ["Dual Wield Specialization"] = "Dual Wield Specialization", + ["Duel"] = "Duel", + ["Dust Field"] = "Dust Field", + ["Eagle Eye"] = "Eagle Eye", + ["Earth Elemental Totem"] = "Earth Elemental Totem", + ["Earth Shield"] = "Earth Shield", + ["Earth Shock"] = "Earth Shock", + ["Earthbind Totem"] = "Earthbind Totem", + ["Earthborer Acid"] = "Earthborer Acid", + ["Earthgrab"] = "Earthgrab", + ["Efficiency"] = "Efficiency", + ["Electric Discharge"] = "Electric Discharge", + ["Electrified Net"] = "Electrified Net", + ["Elemental Focus"] = "Elemental Focus", + ["Elemental Fury"] = "Elemental Fury", + ["Elemental Leatherworking"] = "Elemental Leatherworking", + ["Elemental Mastery"] = "Elemental Mastery", + ["Elemental Precision"] = "Elemental Precision", + ["Elemental Sharpening Stone"] = "Elemental Sharpening Stone", + ["Elune's Grace"] = "Elune's Grace", + ["Elusiveness"] = "Elusiveness", + ["Emberstorm"] = "Emberstorm", + ["Enamored Water Spirit"] = "Enamored Water Spirit", + ["Enchanting"] = "Enchanting", + ["Endurance"] = "Endurance", + ["Endurance Training"] = "Endurance Training", + ["Engineering"] = "Engineering", + ["Engineering Specialization"] = "Engineering Specialization", + ["Enrage"] = "Enrage", + ["Enriched Manna Biscuit"] = "Enriched Manna Biscuit", + ["Enslave Demon"] = "Enslave Demon", + ["Entangling Roots"] = "Entangling Roots", + ["Entrapment"] = "Entrapment", + ["Enveloping Web"] = "Enveloping Web", + ["Enveloping Webs"] = "Enveloping Webs", + ["Enveloping Winds"] = "Enveloping Winds", + ["Envenom"] = "Envenom", + ["Ephemeral Power"] = "Ephemeral Power", + ["Escape Artist"] = "Escape Artist", + ["Essence of Sapphiron"] = "Essence of Sapphiron", + ["Evasion"] = "Evasion", + ["Eventide"] = "Eventide", + ["Eviscerate"] = "Eviscerate", + ["Evocation"] = "Evocation", + ["Execute"] = "Execute", + ["Exorcism"] = "Exorcism", + ["Expansive Mind"] = "Expansive Mind", + ["Exploding Shot"] = "Exploding Shot", + ["Exploit Weakness"] = "Exploit Weakness", + ["Explosive Shot"] = "Explosive Shot", + ["Explosive Trap"] = "Explosive Trap", + ["Explosive Trap Effect"] = "Explosive Trap Effect", + ["Expose Armor"] = "Expose Armor", + ["Expose Weakness"] = "Expose Weakness", + ["Eye for an Eye"] = "Eye for an Eye", + ["Eye of Kilrogg"] = "Eye of Kilrogg", + ["The Eye of the Dead"] = "The Eye of the Dead", + ["Eyes of the Beast"] = "Eyes of the Beast", + ["Fade"] = "Fade", + ["Faerie Fire"] = "Faerie Fire", + ["Faerie Fire (Feral)"] = "Faerie Fire (Feral)", + ["Far Sight"] = "Far Sight", + ["Fatal Bite"] = "Fatal Bite", + ["Fear"] = "Fear", + ["Fear Ward"] = "Fear Ward", + ["Feed Pet"] = "Feed Pet", + ["Feedback"] = "Feedback", + ["Feign Death"] = "Feign Death", + ["Feint"] = "Feint", + ["Fel Armor"] = "Fel Armor", + ["Fel Concentration"] = "Fel Concentration", + ["Fel Domination"] = "Fel Domination", + ["Fel Intellect"] = "Fel Intellect", + ["Fel Stamina"] = "Fel Stamina", + ["Fel Stomp"] = "Fel Stomp", + ["Felfire"] = "Felfire", + ["Feline Grace"] = "Feline Grace", + ["Feline Swiftness"] = "Feline Swiftness", + ["Feral Aggression"] = "Feral Aggression", + ["Feral Charge"] = "Feral Charge", + ["Feral Instinct"] = "Feral Instinct", + ["Ferocious Bite"] = "Ferocious Bite", + ["Ferocity"] = "Ferocity", + ["Fetish"] = "Fetish", + ["Fevered Plague"] = "Fevered Plague", + ["Fiery Burst"] = "Fiery Burst", + ["Find Herbs"] = "Find Herbs", + ["Find Minerals"] = "Find Minerals", + ["Find Treasure"] = "Find Treasure", + ["Fire Blast"] = "Fire Blast", + ["Fire Elemental Totem"] = "Fire Elemental Totem", + ["Fire Nova"] = "Fire Nova", + ["Fire Nova Totem"] = "Fire Nova Totem", + ["Fire Power"] = "Fire Power", + ["Fire Resistance"] = "Fire Resistance", + ["Fire Resistance Aura"] = "Fire Resistance Aura", + ["Fire Resistance Totem"] = "Fire Resistance Totem", + ["Fire Shield"] = "Fire Shield", + ["Fire Shield Effect"] = "Fire Shield Effect", + ["Fire Shield Effect II"] = "Fire Shield Effect II", + ["Fire Shield Effect III"] = "Fire Shield Effect III", + ["Fire Shield Effect IV"] = "Fire Shield Effect IV", + ["Fire Storm"] = "Fire Storm", + ["Fire Vulnerability"] = "Fire Vulnerability", + ["Fire Ward"] = "Fire Ward", + ["Fire Weakness"] = "Fire Weakness", + ["Fireball"] = "Fireball", + ["Fireball Volley"] = "Fireball Volley", + ["Firebolt"] = "Firebolt", + ["First Aid"] = "First Aid", + ["Fishing"] = "Fishing", + ["Fishing Poles"] = "Fishing Poles", + ["Fist of Ragnaros"] = "Fist of Ragnaros", + ["Fist Weapon Specialization"] = "Fist Weapon Specialization", + ["Fist Weapons"] = "Fist Weapons", + ["Flame Buffet"] = "Flame Buffet", + ["Flame Cannon"] = "Flame Cannon", + ["Flame Lash"] = "Flame Lash", + ["Flame Shock"] = "Flame Shock", + ["Flame Spike"] = "Flame Spike", + ["Flame Spray"] = "Flame Spray", + ["Flame Throwing"] = "Flame Throwing", + ["Flames of Shahram"] = "Flames of Shahram", + ["Flamestrike"] = "Flamestrike", + ["Flamethrower"] = "Flamethrower", + ["Flametongue Totem"] = "Flametongue Totem", + ["Flametongue Weapon"] = "Flametongue Weapon", + ["Flare"] = "Flare", + ["Flash Bomb"] = "Flash Bomb", + ["Flash Heal"] = "Flash Heal", + ["Flash of Light"] = "Flash of Light", + ["Flight Form"] = "Flight Form", + ["Flurry"] = "Flurry", + ["Focused Casting"] = "Focused Casting", + ["Focused Mind"] = "Focused Mind", + ["Food"] = "Food", + ["Forbearance"] = "Forbearance", + ["Force of Nature"] = "Force of Nature", + ["Force of Will"] = "Force of Will", + ["Force Punch"] = "Force Punch", + ["Force Reactive Disk"] = "Force Reactive Disk", + ["Forked Lightning"] = "Forked Lightning", + ["Forsaken Skills"] = "Forsaken Skills", + ["Frailty"] = "Frailty", + ["Freeze Solid"] = "Freeze Solid", + ["Freezing Trap"] = "Freezing Trap", + ["Freezing Trap Effect"] = "Freezing Trap Effect", + ["Frenzied Regeneration"] = "Frenzied Regeneration", + ["Frenzy"] = "Frenzy", + ["Frost Armor"] = "Frost Armor", + ["Frost Breath"] = "Frost Breath", + ["Frost Channeling"] = "Frost Channeling", + ["Frost Nova"] = "Frost Nova", + ["Frost Resistance"] = "Frost Resistance", + ["Frost Resistance Aura"] = "Frost Resistance Aura", + ["Frost Resistance Totem"] = "Frost Resistance Totem", + ["Frost Shock"] = "Frost Shock", + ["Frost Shot"] = "Frost Shot", + ["Frost Trap"] = "Frost Trap", + ["Frost Trap Aura"] = "Frost Trap Aura", + ["Frost Ward"] = "Frost Ward", + ["Frost Warding"] = "Frost Warding", + ["Frost Weakness"] = "Frost Weakness", + ["Frostbite"] = "Frostbite", + ["Frostbolt"] = "Frostbolt", + ["Frostbolt Volley"] = "Frostbolt Volley", + ["Frostbrand Weapon"] = "Frostbrand Weapon", + ["Furious Howl"] = "Furious Howl", + ["The Furious Storm"] = "The Furious Storm", + ["Furor"] = "Furor", + ["Fury of Ragnaros"] = "Fury of Ragnaros", + ["Gahz'ranka Slam"] = "Gahz'ranka Slam", + ["Gahz'rilla Slam"] = "Gahz'rilla Slam", + ["Garrote"] = "Garrote", + ["Gehennas' Curse"] = "Gehennas' Curse", + ["Generic"] = "Generic", + ["Ghost Wolf"] = "Ghost Wolf", + ["Ghostly Strike"] = "Ghostly Strike", + ["Gift of Life"] = "Gift of Life", + ["Gift of Nature"] = "Gift of Nature", + ["Gift of the Wild"] = "Gift of the Wild", + ["Goblin Dragon Gun"] = "Goblin Dragon Gun", + ["Goblin Sapper Charge"] = "Goblin Sapper Charge", + ["Gouge"] = "Gouge", + ["Grace of Air Totem"] = "Grace of Air Totem", + ["Grace of the Sunwell"] = "Grace of the Sunwell", + ["Grasping Vines"] = "Grasping Vines", + ["Great Stamina"] = "Great Stamina", + ["Greater Blessing of Kings"] = "Greater Blessing of Kings", + ["Greater Blessing of Light"] = "Greater Blessing of Light", + ["Greater Blessing of Might"] = "Greater Blessing of Might", + ["Greater Blessing of Salvation"] = "Greater Blessing of Salvation", + ["Greater Blessing of Sanctuary"] = "Greater Blessing of Sanctuary", + ["Greater Blessing of Wisdom"] = "Greater Blessing of Wisdom", + ["Greater Heal"] = "Greater Heal", + ["Grim Reach"] = "Grim Reach", + ["Ground Tremor"] = "Ground Tremor", + ["Grounding Totem"] = "Grounding Totem", + ["Grovel"] = "Grovel", + ["Growl"] = "Growl", + ["Guardian's Favor"] = "Guardian's Favor", + ["Guillotine"] = "Guillotine", + ["Gun Specialization"] = "Gun Specialization", + ["Guns"] = "Guns", + ["Hail Storm"] = "Hail Storm", + ["Hammer of Justice"] = "Hammer of Justice", + ["Hammer of Wrath"] = "Hammer of Wrath", + ["Hamstring"] = "Hamstring", + ["Harass"] = "Harass", + ["Hardiness"] = "Hardiness", + ["Haunting Spirits"] = "Haunting Spirits", + ["Hawk Eye"] = "Hawk Eye", + ["Head Crack"] = "Head Crack", + ["Heal"] = "Heal", + ["Healing Circle"] = "Healing Circle", + ["Healing Focus"] = "Healing Focus", + ["Healing Light"] = "Healing Light", + ["Healing of the Ages"] = "Healing of the Ages", + ["Healing Stream Totem"] = "Healing Stream Totem", + ["Healing Touch"] = "Healing Touch", + ["Healing Wave"] = "Healing Wave", + ["Healing Way"] = "Healing Way", + ["Health Funnel"] = "Health Funnel", + ["Heart of the Wild"] = "Heart of the Wild", + ["Heavy Sharpening Stone"] = "Heavy Sharpening Stone", + ["Hellfire"] = "Hellfire", + ["Hellfire Effect"] = "Hellfire Effect", + ["Hemorrhage"] = "Hemorrhage", + ["Herb Gathering"] = "Herb Gathering", + ["Herbalism"] = "Herbalism", + ["Heroic Strike"] = "Heroic Strike", + ["Heroism"] = "Heroism", + ["Hex"] = "Hex", + ["Hex of Jammal'an"] = "Hex of Jammal'an", + ["Hex of Weakness"] = "Hex of Weakness", + ["Hibernate"] = "Hibernate", + ["Holy Fire"] = "Holy Fire", + ["Holy Light"] = "Holy Light", + ["Holy Nova"] = "Holy Nova", + ["Holy Power"] = "Holy Power", + ["Holy Reach"] = "Holy Reach", + ["Holy Shield"] = "Holy Shield", + ["Holy Shock"] = "Holy Shock", + ["Holy Smite"] = "Holy Smite", + ["Holy Specialization"] = "Holy Specialization", + ["Holy Strength"] = "Holy Strength", + ["Holy Strike"] = "Holy Strike", + ["Holy Wrath"] = "Holy Wrath", + ["Honorless Target"] = "Honorless Target", + ["Hooked Net"] = "Hooked Net", + ["Horse Riding"] = "Horse Riding", + ["Howl of Terror"] = "Howl of Terror", + ["The Human Spirit"] = "The Human Spirit", + ["Humanoid Slaying"] = "Humanoid Slaying", + ["Hunter's Mark"] = "Hunter's Mark", + ["Hurricane"] = "Hurricane", + ["Ice Armor"] = "Ice Armor", + ["Ice Barrier"] = "Ice Barrier", + ["Ice Blast"] = "Ice Blast", + ["Ice Block"] = "Ice Block", + ["Ice Lance"] = "Ice Lance", + ["Ice Nova"] = "Ice Nova", + ["Ice Shards"] = "Ice Shards", + ["Icicle"] = "Icicle", + ["Ignite"] = "Ignite", + ["Illumination"] = "Illumination", + ["Immolate"] = "Immolate", + ["Immolation Trap"] = "Immolation Trap", + ["Immolation Trap Effect"] = "Immolation Trap Effect", + ["Impact"] = "Impact", + ["Impale"] = "Impale", + ["Improved Ambush"] = "Improved Ambush", + ["Improved Arcane Explosion"] = "Improved Arcane Explosion", + ["Improved Arcane Missiles"] = "Improved Arcane Missiles", + ["Improved Arcane Shot"] = "Improved Arcane Shot", + ["Improved Aspect of the Hawk"] = "Improved Aspect of the Hawk", + ["Improved Aspect of the Monkey"] = "Improved Aspect of the Monkey", + ["Improved Backstab"] = "Improved Backstab", + ["Improved Battle Shout"] = "Improved Battle Shout", + ["Improved Berserker Rage"] = "Improved Berserker Rage", + ["Improved Blessing of Might"] = "Improved Blessing of Might", + ["Improved Blessing of Wisdom"] = "Improved Blessing of Wisdom", + ["Improved Blizzard"] = "Improved Blizzard", + ["Improved Bloodrage"] = "Improved Bloodrage", + ["Improved Chain Heal"] = "Improved Chain Heal", + ["Improved Chain Lightning"] = "Improved Chain Lightning", + ["Improved Challenging Shout"] = "Improved Challenging Shout", + ["Improved Charge"] = "Improved Charge", + ["Improved Cheap Shot"] = "Improved Cheap Shot", + ["Improved Cleave"] = "Improved Cleave", + ["Improved Concentration Aura"] = "Improved Concentration Aura", + ["Improved Concussive Shot"] = "Improved Concussive Shot", + ["Improved Cone of Cold"] = "Improved Cone of Cold", + ["Improved Corruption"] = "Improved Corruption", + ["Improved Counterspell"] = "Improved Counterspell", + ["Improved Curse of Agony"] = "Improved Curse of Agony", + ["Improved Curse of Exhaustion"] = "Improved Curse of Exhaustion", + ["Improved Curse of Weakness"] = "Improved Curse of Weakness", + ["Improved Dampen Magic"] = "Improved Dampen Magic", + ["Improved Deadly Poison"] = "Improved Deadly Poison", + ["Improved Demoralizing Shout"] = "Improved Demoralizing Shout", + ["Improved Devotion Aura"] = "Improved Devotion Aura", + ["Improved Disarm"] = "Improved Disarm", + ["Improved Distract"] = "Improved Distract", + ["Improved Drain Life"] = "Improved Drain Life", + ["Improved Drain Mana"] = "Improved Drain Mana", + ["Improved Drain Soul"] = "Improved Drain Soul", + ["Improved Enrage"] = "Improved Enrage", + ["Improved Enslave Demon"] = "Improved Enslave Demon", + ["Improved Entangling Roots"] = "Improved Entangling Roots", + ["Improved Evasion"] = "Improved Evasion", + ["Improved Eviscerate"] = "Improved Eviscerate", + ["Improved Execute"] = "Improved Execute", + ["Improved Expose Armor"] = "Improved Expose Armor", + ["Improved Eyes of the Beast"] = "Improved Eyes of the Beast", + ["Improved Fade"] = "Improved Fade", + ["Improved Feign Death"] = "Improved Feign Death", + ["Improved Fire Blast"] = "Improved Fire Blast", + ["Improved Fire Nova Totem"] = "Improved Fire Nova Totem", + ["Improved Fire Ward"] = "Improved Fire Ward", + ["Improved Fireball"] = "Improved Fireball", + ["Improved Firebolt"] = "Improved Firebolt", + ["Improved Firestone"] = "Improved Firestone", + ["Improved Flamestrike"] = "Improved Flamestrike", + ["Improved Flametongue Weapon"] = "Improved Flametongue Weapon", + ["Improved Flash of Light"] = "Improved Flash of Light", + ["Improved Frost Nova"] = "Improved Frost Nova", + ["Improved Frost Ward"] = "Improved Frost Ward", + ["Improved Frostbolt"] = "Improved Frostbolt", + ["Improved Frostbrand Weapon"] = "Improved Frostbrand Weapon", + ["Improved Garrote"] = "Improved Garrote", + ["Improved Ghost Wolf"] = "Improved Ghost Wolf", + ["Improved Gouge"] = "Improved Gouge", + ["Improved Grace of Air Totem"] = "Improved Grace of Air Totem", + ["Improved Grounding Totem"] = "Improved Grounding Totem", + ["Improved Hammer of Justice"] = "Improved Hammer of Justice", + ["Improved Hamstring"] = "Improved Hamstring", + ["Improved Healing"] = "Improved Healing", + ["Improved Healing Stream Totem"] = "Improved Healing Stream Totem", + ["Improved Healing Touch"] = "Improved Healing Touch", + ["Improved Healing Wave"] = "Improved Healing Wave", + ["Improved Health Funnel"] = "Improved Health Funnel", + ["Improved Healthstone"] = "Improved Healthstone", + ["Improved Heroic Strike"] = "Improved Heroic Strike", + ["Improved Hunter's Mark"] = "Improved Hunter's Mark", + ["Improved Immolate"] = "Improved Immolate", + ["Improved Imp"] = "Improved Imp", + ["Improved Inner Fire"] = "Improved Inner Fire", + ["Improved Instant Poison"] = "Improved Instant Poison", + ["Improved Intercept"] = "Improved Intercept", + ["Improved Intimidating Shout"] = "Improved Intimidating Shout", + ["Improved Judgement"] = "Improved Judgement", + ["Improved Kick"] = "Improved Kick", + ["Improved Kidney Shot"] = "Improved Kidney Shot", + ["Improved Lash of Pain"] = "Improved Lash of Pain", + ["Improved Lay on Hands"] = "Improved Lay on Hands", + ["Improved Lesser Healing Wave"] = "Improved Lesser Healing Wave", + ["Improved Life Tap"] = "Improved Life Tap", + ["Improved Lightning Bolt"] = "Improved Lightning Bolt", + ["Improved Lightning Shield"] = "Improved Lightning Shield", + ["Improved Magma Totem"] = "Improved Magma Totem", + ["Improved Mana Burn"] = "Improved Mana Burn", + ["Improved Mana Shield"] = "Improved Mana Shield", + ["Improved Mana Spring Totem"] = "Improved Mana Spring Totem", + ["Improved Mark of the Wild"] = "Improved Mark of the Wild", + ["Improved Mend Pet"] = "Improved Mend Pet", + ["Improved Mind Blast"] = "Improved Mind Blast", + ["Improved Moonfire"] = "Improved Moonfire", + ["Improved Nature's Grasp"] = "Improved Nature's Grasp", + ["Improved Overpower"] = "Improved Overpower", + ["Improved Power Word: Fortitude"] = "Improved Power Word: Fortitude", + ["Improved Power Word: Shield"] = "Improved Power Word: Shield", + ["Improved Prayer of Healing"] = "Improved Prayer of Healing", + ["Improved Psychic Scream"] = "Improved Psychic Scream", + ["Improved Pummel"] = "Improved Pummel", + ["Improved Regrowth"] = "Improved Regrowth", + ["Improved Reincarnation"] = "Improved Reincarnation", + ["Improved Rejuvenation"] = "Improved Rejuvenation", + ["Improved Rend"] = "Improved Rend", + ["Improved Renew"] = "Improved Renew", + ["Improved Retribution Aura"] = "Improved Retribution Aura", + ["Improved Revenge"] = "Improved Revenge", + ["Improved Revive Pet"] = "Improved Revive Pet", + ["Improved Righteous Fury"] = "Improved Righteous Fury", + ["Improved Rockbiter Weapon"] = "Improved Rockbiter Weapon", + ["Improved Rupture"] = "Improved Rupture", + ["Improved Sap"] = "Improved Sap", + ["Improved Scorch"] = "Improved Scorch", + ["Improved Scorpid Sting"] = "Improved Scorpid Sting", + ["Improved Seal of Righteousness"] = "Improved Seal of Righteousness", + ["Improved Seal of the Crusader"] = "Improved Seal of the Crusader", + ["Improved Searing Pain"] = "Improved Searing Pain", + ["Improved Searing Totem"] = "Improved Searing Totem", + ["Improved Serpent Sting"] = "Improved Serpent Sting", + ["Improved Shadow Bolt"] = "Improved Shadow Bolt", + ["Improved Shadow Word: Pain"] = "Improved Shadow Word: Pain", + ["Improved Shield Bash"] = "Improved Shield Bash", + ["Improved Shield Block"] = "Improved Shield Block", + ["Improved Shield Wall"] = "Improved Shield Wall", + ["Improved Shred"] = "Improved Shred", + ["Improved Sinister Strike"] = "Improved Sinister Strike", + ["Improved Slam"] = "Improved Slam", + ["Improved Slice and Dice"] = "Improved Slice and Dice", + ["Improved Spellstone"] = "Improved Spellstone", + ["Improved Sprint"] = "Improved Sprint", + ["Improved Starfire"] = "Improved Starfire", + ["Improved Stoneclaw Totem"] = "Improved Stoneclaw Totem", + ["Improved Stoneskin Totem"] = "Improved Stoneskin Totem", + ["Improved Strength of Earth Totem"] = "Improved Strength of Earth Totem", + ["Improved Succubus"] = "Improved Succubus", + ["Improved Sunder Armor"] = "Improved Sunder Armor", + ["Improved Taunt"] = "Improved Taunt", + ["Improved Thorns"] = "Improved Thorns", + ["Improved Thunder Clap"] = "Improved Thunder Clap", + ["Improved Tranquility"] = "Improved Tranquility", + ["Improved Vampiric Embrace"] = "Improved Vampiric Embrace", + ["Improved Vanish"] = "Improved Vanish", + ["Improved Voidwalker"] = "Improved Voidwalker", + ["Improved Windfury Weapon"] = "Improved Windfury Weapon", + ["Improved Wing Clip"] = "Improved Wing Clip", + ["Improved Wrath"] = "Improved Wrath", + ["Incinerate"] = "Incinerate", + ["Infected Bite"] = "Infected Bite", + ["Infected Wound"] = "Infected Wound", + ["Inferno"] = "Inferno", + ["Inferno Shell"] = "Inferno Shell", + ["Initiative"] = "Initiative", + ["Inner Fire"] = "Inner Fire", + ["Inner Focus"] = "Inner Focus", + ["Innervate"] = "Innervate", + ["Insect Swarm"] = "Insect Swarm", + ["Inspiration"] = "Inspiration", + ["Instant Poison"] = "Instant Poison", + ["Instant Poison II"] = "Instant Poison II", + ["Instant Poison III"] = "Instant Poison III", + ["Instant Poison IV"] = "Instant Poison IV", + ["Instant Poison V"] = "Instant Poison V", + ["Instant Poison VI"] = "Instant Poison VI", + ["Intensity"] = "Intensity", + ["Intercept"] = "Intercept", + ["Intercept Stun"] = "Intercept Stun", + ["Intervene"] = "Intervene", + ["Intimidating Roar"] = "Intimidating Roar", + ["Intimidating Shout"] = "Intimidating Shout", + ["Intimidation"] = "Intimidation", + ["Intoxicating Venom"] = "Intoxicating Venom", + ["Invisibility"] = "Invisibility", + ["Iron Will"] = "Iron Will", + ["Jewelcrafting"] = "Jewelcrafting", + ["Judgement"] = "Judgement", + ["Judgement of Command"] = "Judgement of Command", + ["Judgement of Justice"] = "Judgement of Justice", + ["Judgement of Light"] = "Judgement of Light", + ["Judgement of Righteousness"] = "Judgement of Righteousness", + ["Judgement of the Crusader"] = "Judgement of the Crusader", + ["Judgement of Wisdom"] = "Judgement of Wisdom", + ["Kick"] = "Kick", + ["Kick - Silenced"] = "Kick - Silenced", + ["Kidney Shot"] = "Kidney Shot", + ["Kill Command"] = "Kill Command", + ["Killer Instinct"] = "Killer Instinct", + ["Knock Away"] = "Knock Away", + ["Knockdown"] = "Knockdown", + ["Kodo Riding"] = "Kodo Riding", + ["Lacerate"] = "Lacerate", + ["Larva Goo"] = "Larva Goo", + ["Lash"] = "Lash", + ["Lash of Pain"] = "Lash of Pain", + ["Last Stand"] = "Last Stand", + ["Lasting Judgement"] = "Lasting Judgement", + ["Lava Spout Totem"] = "Lava Spout Totem", + ["Lay on Hands"] = "Lay on Hands", + ["Leader of the Pack"] = "Leader of the Pack", + ["Leather"] = "Leather", + ["Leatherworking"] = "Leatherworking", + ["Leech Poison"] = "Leech Poison", + ["Lesser Heal"] = "Lesser Heal", + ["Lesser Healing Wave"] = "Lesser Healing Wave", + ["Lesser Invisibility"] = "Lesser Invisibility", + ["Lethal Shots"] = "Lethal Shots", + ["Lethality"] = "Lethality", + ["Levitate"] = "Levitate", + ["Libram"] = "Libram", + ["Lich Slap"] = "Lich Slap", + ["Life Tap"] = "Life Tap", + ["Lifebloom"] = "Lifebloom", + ["Lifegiving Gem"] = "Lifegiving Gem", + ["Lightning Blast"] = "Lightning Blast", + ["Lightning Bolt"] = "Lightning Bolt", + ["Lightning Breath"] = "Lightning Breath", + ["Lightning Cloud"] = "Lightning Cloud", + ["Lightning Mastery"] = "Lightning Mastery", + ["Lightning Reflexes"] = "Lightning Reflexes", + ["Lightning Shield"] = "Lightning Shield", + ["Lightning Wave"] = "Lightning Wave", + ["Lightwell"] = "Lightwell", + ["Lightwell Renew"] = "Lightwell Renew", + ["Lizard Bolt"] = "Lizard Bolt", + ["Localized Toxin"] = "Localized Toxin", + ["Lockpicking"] = "Lockpicking", + ["Long Daze"] = "Long Daze", + ["Mace Specialization"] = "Mace Specialization", + ["Mace Stun Effect"] = "Mace Stun Effect", + ["Machine Gun"] = "Machine Gun", + ["Mage Armor"] = "Mage Armor", + ["Magic Attunement"] = "Magic Attunement", + ["Magma Splash"] = "Magma Splash", + ["Magma Totem"] = "Magma Totem", + ["Mail"] = "Mail", + ["Maim"] = "Maim", + ["Malice"] = "Malice", + ["Mana Burn"] = "Mana Burn", + ["Mana Feed"] = "Mana Feed", + ["Mana Shield"] = "Mana Shield", + ["Mana Spring Totem"] = "Mana Spring Totem", + ["Mana Tide Totem"] = "Mana Tide Totem", + ["Mangle"] = "Mangle", + ["Mangle (Bear)"] = "Mangle (Bear)", + ["Mangle (Cat)"] = "Mangle (Cat)", + ["Mark of Arlokk"] = "Mark of Arlokk", + ["Mark of the Wild"] = "Mark of the Wild", + ["Martyrdom"] = "Martyrdom", + ["Mass Dispel"] = "Mass Dispel", + ["Master Demonologist"] = "Master Demonologist", + ["Master of Deception"] = "Master of Deception", + ["Master of Elements"] = "Master of Elements", + ["Master Summoner"] = "Master Summoner", + ["Maul"] = "Maul", + ["Mechanostrider Piloting"] = "Mechanostrider Piloting", + ["Meditation"] = "Meditation", + ["Megavolt"] = "Megavolt", + ["Melee Specialization"] = "Melee Specialization", + ["Melt Ore"] = "Melt Ore", + ["Mend Pet"] = "Mend Pet", + ["Mental Agility"] = "Mental Agility", + ["Mental Strength"] = "Mental Strength", + ["Mighty Blow"] = "Mighty Blow", + ["Mind Blast"] = "Mind Blast", + ["Mind Control"] = "Mind Control", + ["Mind Flay"] = "Mind Flay", + ["Mind Soothe"] = "Mind Soothe", + ["Mind Tremor"] = "Mind Tremor", + ["Mind Vision"] = "Mind Vision", + ["Mind-numbing Poison"] = "Mind-numbing Poison", + ["Mind-numbing Poison II"] = "Mind-numbing Poison II", + ["Mind-numbing Poison III"] = "Mind-numbing Poison III", + ["Mining"] = "Mining", + ["Misdirection"] = "Misdirection", + ["Mocking Blow"] = "Mocking Blow", + ["Molten Armor"] = "Molten Armor", + ["Molten Blast"] = "Molten Blast", + ["Molten Metal"] = "Molten Metal", + ["Mongoose Bite"] = "Mongoose Bite", + ["Monster Slaying"] = "Monster Slaying", + ["Moonfire"] = "Moonfire", + ["Moonfury"] = "Moonfury", + ["Moonglow"] = "Moonglow", + ["Moonkin Aura"] = "Moonkin Aura", + ["Moonkin Form"] = "Moonkin Form", + ["Mortal Cleave"] = "Mortal Cleave", + ["Mortal Shots"] = "Mortal Shots", + ["Mortal Strike"] = "Mortal Strike", + ["Mortal Wound"] = "Mortal Wound", + ["Multi-Shot"] = "Multi-Shot", + ["Murder"] = "Murder", + ["Mutilate"] = "Mutilate", + ["Naralex's Nightmare"] = "Naralex's Nightmare", + ["Natural Armor"] = "Natural Armor", + ["Natural Shapeshifter"] = "Natural Shapeshifter", + ["Natural Weapons"] = "Natural Weapons", + ["Nature Aligned"] = "Nature Aligned", + ["Nature Resistance"] = "Nature Resistance", + ["Nature Resistance Totem"] = "Nature Resistance Totem", + ["Nature Weakness"] = "Nature Weakness", + ["Nature's Focus"] = "Nature's Focus", + ["Nature's Grace"] = "Nature's Grace", + ["Nature's Grasp"] = "Nature's Grasp", + ["Nature's Reach"] = "Nature's Reach", + ["Nature's Swiftness"] = "Nature's Swiftness", + ["Necrotic Poison"] = "Necrotic Poison", + ["Negative Charge"] = "Negative Charge", + ["Net"] = "Net", + ["Nightfall"] = "Nightfall", + ["Noxious Catalyst"] = "Noxious Catalyst", + ["Noxious Cloud"] = "Noxious Cloud", + ["Omen of Clarity"] = "Omen of Clarity", + ["One-Handed Axes"] = "One-Handed Axes", + ["One-Handed Maces"] = "One-Handed Maces", + ["One-Handed Swords"] = "One-Handed Swords", + ["One-Handed Weapon Specialization"] = "One-Handed Weapon Specialization", + ["Opening"] = "Opening", + ["Opening - No Text"] = "Opening - No Text", + ["Opportunity"] = "Opportunity", + ["Overpower"] = "Overpower", + ["Pacify"] = "Pacify", + ["Pain Suppression"] = "Pain Suppression", + ["Paralyzing Poison"] = "Paralyzing Poison", + ["Paranoia"] = "Paranoia", + ["Parasitic Serpent"] = "Parasitic Serpent", + ["Parry"] = "Parry", + ["Pathfinding"] = "Pathfinding", + ["Perception"] = "Perception", + ["Permafrost"] = "Permafrost", + ["Pet Aggression"] = "Pet Aggression", + ["Pet Hardiness"] = "Pet Hardiness", + ["Pet Recovery"] = "Pet Recovery", + ["Pet Resistance"] = "Pet Resistance", + ["Petrify"] = "Petrify", + ["Phase Shift"] = "Phase Shift", + ["Pick Lock"] = "Pick Lock", + ["Pick Pocket"] = "Pick Pocket", + ["Pierce Armor"] = "Pierce Armor", + ["Piercing Howl"] = "Piercing Howl", + ["Piercing Ice"] = "Piercing Ice", + ["Piercing Shadow"] = "Piercing Shadow", + ["Piercing Shot"] = "Piercing Shot", + ["Plague Cloud"] = "Plague Cloud", + ["Plate Mail"] = "Plate Mail", + ["Poison"] = "Poison", + ["Poison Bolt"] = "Poison Bolt", + ["Poison Bolt Volley"] = "Poison Bolt Volley", + ["Poison Cleansing Totem"] = "Poison Cleansing Totem", + ["Poison Cloud"] = "Poison Cloud", + ["Poison Shock"] = "Poison Shock", + ["Poisoned Harpoon"] = "Poisoned Harpoon", + ["Poisoned Shot"] = "Poisoned Shot", + ["Poisonous Blood"] = "Poisonous Blood", + ["Poisons"] = "Poisons", + ["Polearm Specialization"] = "Polearm Specialization", + ["Polearms"] = "Polearms", + ["Polymorph"] = "Polymorph", + ["Polymorph: Pig"] = "Polymorph: Pig", + ["Polymorph: Turtle"] = "Polymorph: Turtle", + ["Portal: Darnassus"] = "Portal: Darnassus", + ["Portal: Ironforge"] = "Portal: Ironforge", + ["Portal: Orgrimmar"] = "Portal: Orgrimmar", + ["Portal: Stormwind"] = "Portal: Stormwind", + ["Portal: Thunder Bluff"] = "Portal: Thunder Bluff", + ["Portal: Undercity"] = "Portal: Undercity", + ["Positive Charge"] = "Positive Charge", + ["Pounce"] = "Pounce", + ["Pounce Bleed"] = "Pounce Bleed", + ["Power Infusion"] = "Power Infusion", + ["Power Word: Fortitude"] = "Power Word: Fortitude", + ["Power Word: Shield"] = "Power Word: Shield", + ["Prayer Beads Blessing"] = "Prayer Beads Blessing", + ["Prayer of Fortitude"] = "Prayer of Fortitude", + ["Prayer of Healing"] = "Prayer of Healing", + ["Prayer of Mending"] = "Prayer of Mending", + ["Prayer of Shadow Protection"] = "Prayer of Shadow Protection", + ["Prayer of Spirit"] = "Prayer of Spirit", + ["Precision"] = "Precision", + ["Predatory Strikes"] = "Predatory Strikes", + ["Premeditation"] = "Premeditation", + ["Preparation"] = "Preparation", + ["Presence of Mind"] = "Presence of Mind", + ["Primal Fury"] = "Primal Fury", + ["Prowl"] = "Prowl", + ["Psychic Scream"] = "Psychic Scream", + ["Pummel"] = "Pummel", + ["Puncture"] = "Puncture", + ["Purge"] = "Purge", + ["Purification"] = "Purification", + ["Purify"] = "Purify", + ["Pursuit of Justice"] = "Pursuit of Justice", + ["Putrid Breath"] = "Putrid Breath", + ["Putrid Enzyme"] = "Putrid Enzyme", + ["Pyroblast"] = "Pyroblast", + ["Pyroclasm"] = "Pyroclasm", + ["Quick Shots"] = "Quick Shots", + ["Quickness"] = "Quickness", + ["Radiation"] = "Radiation", + ["Radiation Bolt"] = "Radiation Bolt", + ["Radiation Cloud"] = "Radiation Cloud", + ["Radiation Poisoning"] = "Radiation Poisoning", + ["Rain of Fire"] = "Rain of Fire", + ["Rake"] = "Rake", + ["Ram Riding"] = "Ram Riding", + ["Rampage"] = "Rampage", + ["Ranged Weapon Specialization"] = "Ranged Weapon Specialization", + ["Rapid Concealment"] = "Rapid Concealment", + ["Rapid Fire"] = "Rapid Fire", + ["Raptor Riding"] = "Raptor Riding", + ["Raptor Strike"] = "Raptor Strike", + ["Ravage"] = "Ravage", + ["Ravenous Claw"] = "Ravenous Claw", + ["Readiness"] = "Readiness", + ["Rebirth"] = "Rebirth", + ["Rebuild"] = "Rebuild", + ["Recently Bandaged"] = "Recently Bandaged", + ["Reckless Charge"] = "Reckless Charge", + ["Recklessness"] = "Recklessness", + ["Reckoning"] = "Reckoning", + ["Recombobulate"] = "Recombobulate", + ["Redemption"] = "Redemption", + ["Redoubt"] = "Redoubt", + ["Reflection"] = "Reflection", + ["Regeneration"] = "Regeneration", + ["Regrowth"] = "Regrowth", + ["Reincarnation"] = "Reincarnation", + ["Rejuvenation"] = "Rejuvenation", + ["Relentless Strikes"] = "Relentless Strikes", + ["Remorseless"] = "Remorseless", + ["Remorseless Attacks"] = "Remorseless Attacks", + ["Remove Curse"] = "Remove Curse", + ["Remove Insignia"] = "Remove Insignia", + ["Remove Lesser Curse"] = "Remove Lesser Curse", + ["Rend"] = "Rend", + ["Renew"] = "Renew", + ["Repentance"] = "Repentance", + ["Repulsive Gaze"] = "Repulsive Gaze", + ["Restorative Totems"] = "Restorative Totems", + ["Resurrection"] = "Resurrection", + ["Retaliation"] = "Retaliation", + ["Retribution Aura"] = "Retribution Aura", + ["Revenge"] = "Revenge", + ["Revenge Stun"] = "Revenge Stun", + ["Reverberation"] = "Reverberation", + ["Revive Pet"] = "Revive Pet", + ["Rhahk'Zor Slam"] = "Rhahk'Zor Slam", + ["Ribbon of Souls"] = "Ribbon of Souls", + ["Righteous Defense"] = "Righteous Defense", + ["Righteous Fury"] = "Righteous Fury", + ["Rip"] = "Rip", + ["Riposte"] = "Riposte", + ["Ritual of Doom"] = "Ritual of Doom", + ["Ritual of Doom Effect"] = "Ritual of Doom Effect", + ["Ritual of Souls"] = "Ritual of Souls", + ["Ritual of Summoning"] = "Ritual of Summoning", + ["Rockbiter Weapon"] = "Rockbiter Weapon", + ["Rogue Passive"] = "Rogue Passive", + ["Rough Sharpening Stone"] = "Rough Sharpening Stone", + ["Ruin"] = "Ruin", + ["Rupture"] = "Rupture", + ["Ruthlessness"] = "Ruthlessness", + ["Sacrifice"] = "Sacrifice", + ["Safe Fall"] = "Safe Fall", + ["Sanctity Aura"] = "Sanctity Aura", + ["Sap"] = "Sap", + ["Savage Fury"] = "Savage Fury", + ["Savage Strikes"] = "Savage Strikes", + ["Scare Beast"] = "Scare Beast", + ["Scatter Shot"] = "Scatter Shot", + ["Scorch"] = "Scorch", + ["Scorpid Poison"] = "Scorpid Poison", + ["Scorpid Sting"] = "Scorpid Sting", + ["Screams of the Past"] = "Screams of the Past", + ["Screech"] = "Screech", + ["Seal Fate"] = "Seal Fate", + ["Seal of Blood"] = "Seal of Blood", + ["Seal of Command"] = "Seal of Command", + ["Seal of Justice"] = "Seal of Justice", + ["Seal of Light"] = "Seal of Light", + ["Seal of Reckoning"] = "Seal of Reckoning", + ["Seal of Righteousness"] = "Seal of Righteousness", + ["Seal of the Crusader"] = "Seal of the Crusader", + ["Seal of Vengeance"] = "Seal of Vengeance", + ["Seal of Wisdom"] = "Seal of Wisdom", + ["Searing Light"] = "Searing Light", + ["Searing Pain"] = "Searing Pain", + ["Searing Totem"] = "Searing Totem", + ["Second Wind"] = "Second Wind", + ["Seduction"] = "Seduction", + ["Seed of Corruption"] = "Seed of Corruption", + ["Sense Demons"] = "Sense Demons", + ["Sense Undead"] = "Sense Undead", + ["Sentry Totem"] = "Sentry Totem", + ["Serpent Sting"] = "Serpent Sting", + ["Setup"] = "Setup", + ["Shackle Undead"] = "Shackle Undead", + ["Shadow Affinity"] = "Shadow Affinity", + ["Shadow Bolt"] = "Shadow Bolt", + ["Shadow Bolt Volley"] = "Shadow Bolt Volley", + ["Shadow Focus"] = "Shadow Focus", + ["Shadow Mastery"] = "Shadow Mastery", + ["Shadow Protection"] = "Shadow Protection", + ["Shadow Reach"] = "Shadow Reach", + ["Shadow Resistance"] = "Shadow Resistance", + ["Shadow Resistance Aura"] = "Shadow Resistance Aura", + ["Shadow Shock"] = "Shadow Shock", + ["Shadow Trance"] = "Shadow Trance", + ["Shadow Vulnerability"] = "Shadow Vulnerability", + ["Shadow Ward"] = "Shadow Ward", + ["Shadow Weakness"] = "Shadow Weakness", + ["Shadow Weaving"] = "Shadow Weaving", + ["Shadow Word: Death"] = "Shadow Word: Death", + ["Shadow Word: Pain"] = "Shadow Word: Pain", + ["Shadowburn"] = "Shadowburn", + ["Shadowfiend"] = "Shadowfiend", + ["Shadowform"] = "Shadowform", + ["Shadowfury"] = "Shadowfury", + ["Shadowguard"] = "Shadowguard", + ["Shadowmeld"] = "Shadowmeld", + ["Shadowmeld Passive"] = "Shadowmeld Passive", + ["Shadowstep"] = "Shadowstep", + ["Shamanistic Rage"] = "Shamanistic Rage", + ["Sharpened Claws"] = "Sharpened Claws", + ["Shatter"] = "Shatter", + ["Sheep"] = "Sheep", + ["Shell Shield"] = "Shell Shield", + ["Shield"] = "Shield", + ["Shield Bash"] = "Shield Bash", + ["Shield Bash - Silenced"] = "Shield Bash - Silenced", + ["Shield Block"] = "Shield Block", + ["Shield Slam"] = "Shield Slam", + ["Shield Specialization"] = "Shield Specialization", + ["Shield Wall"] = "Shield Wall", + ["Shiv"] = "Shiv", + ["Shock"] = "Shock", + ["Shoot"] = "Shoot", + ["Shoot Bow"] = "Shoot Bow", + ["Shoot Crossbow"] = "Shoot Crossbow", + ["Shoot Gun"] = "Shoot Gun", + ["Shred"] = "Shred", + ["Shrink"] = "Shrink", + ["Silence"] = "Silence", + ["Silencing Shot"] = "Silencing Shot", + ["Silent Resolve"] = "Silent Resolve", + ["Sinister Strike"] = "Sinister Strike", + ["Siphon Life"] = "Siphon Life", + ["Skinning"] = "Skinning", + ["Skull Crack"] = "Skull Crack", + ["Slam"] = "Slam", + ["Sleep"] = "Sleep", + ["Slice and Dice"] = "Slice and Dice", + ["Slow"] = "Slow", + ["Slow Fall"] = "Slow Fall", + ["Slowing Poison"] = "Slowing Poison", + ["Smelting"] = "Smelting", + ["Smite"] = "Smite", + ["Smite Slam"] = "Smite Slam", + ["Smite Stomp"] = "Smite Stomp", + ["Smoke Bomb"] = "Smoke Bomb", + ["Snake Trap"] = "Snake Trap", + ["Snap Kick"] = "Snap Kick", + ["Solid Sharpening Stone"] = "Solid Sharpening Stone", + ["Sonic Burst"] = "Sonic Burst", + ["Soothe Animal"] = "Soothe Animal", + ["Soothing Kiss"] = "Soothing Kiss", + ["Soul Bite"] = "Soul Bite", + ["Soul Drain"] = "Soul Drain", + ["Soul Fire"] = "Soul Fire", + ["Soul Link"] = "Soul Link", + ["Soul Siphon"] = "Soul Siphon", + ["Soul Tap"] = "Soul Tap", + ["Soulshatter"] = "Soulshatter", + ["Soulstone Resurrection"] = "Soulstone Resurrection", + ["Spell Lock"] = "Spell Lock", + ["Spell Reflection"] = "Spell Reflection", + ["Spell Warding"] = "Spell Warding", + ["Spellsteal"] = "Spellsteal", + ["Spirit Bond"] = "Spirit Bond", + ["Spirit Burst"] = "Spirit Burst", + ["Spirit of Redemption"] = "Spirit of Redemption", + ["Spirit Tap"] = "Spirit Tap", + ["Spiritual Attunement"] = "Spiritual Attunement", + ["Spiritual Focus"] = "Spiritual Focus", + ["Spiritual Guidance"] = "Spiritual Guidance", + ["Spiritual Healing"] = "Spiritual Healing", + ["Spit"] = "Spit", + ["Spore Cloud"] = "Spore Cloud", + ["Sprint"] = "Sprint", + ["Stance Mastery"] = "Stance Mastery", + ["Starfire"] = "Starfire", + ["Starfire Stun"] = "Starfire Stun", + ["Starshards"] = "Starshards", + ["Staves"] = "Staves", + ["Steady Shot"] = "Steady Shot", + ["Stealth"] = "Stealth", + ["Stoneclaw Totem"] = "Stoneclaw Totem", + ["Stoneform"] = "Stoneform", + ["Stoneskin Totem"] = "Stoneskin Totem", + ["Stormstrike"] = "Stormstrike", + ["Strength of Earth Totem"] = "Strength of Earth Totem", + ["Strike"] = "Strike", + ["Stuck"] = "Stuck", + ["Stun"] = "Stun", + ["Subtlety"] = "Subtlety", + ["Suffering"] = "Suffering", + ["Summon Charger"] = "Summon Charger", + ["Summon Dreadsteed"] = "Summon Dreadsteed", + ["Summon Felguard"] = "Summon Felguard", + ["Summon Felhunter"] = "Summon Felhunter", + ["Summon Felsteed"] = "Summon Felsteed", + ["Summon Imp"] = "Summon Imp", + ["Summon Spawn of Bael'Gar"] = "Summon Spawn of Bael'Gar", + ["Summon Succubus"] = "Summon Succubus", + ["Summon Voidwalker"] = "Summon Voidwalker", + ["Summon Warhorse"] = "Summon Warhorse", + ["Summon Water Elemental"] = "Summon Water Elemental", + ["Sunder Armor"] = "Sunder Armor", + ["Suppression"] = "Suppression", + ["Surefooted"] = "Surefooted", + ["Survivalist"] = "Survivalist", + ["Sweeping Slam"] = "Sweeping Slam", + ["Sweeping Strikes"] = "Sweeping Strikes", + ["Swiftmend"] = "Swiftmend", + ["Swipe"] = "Swipe", + ["Swoop"] = "Swoop", + ["Sword Specialization"] = "Sword Specialization", + ["Tactical Mastery"] = "Tactical Mastery", + ["Tailoring"] = "Tailoring", + ["Tainted Blood"] = "Tainted Blood", + ["Tame Beast"] = "Tame Beast", + ["Tamed Pet Passive"] = "Tamed Pet Passive", + ["Taunt"] = "Taunt", + ["Teleport: Darnassus"] = "Teleport: Darnassus", + ["Teleport: Ironforge"] = "Teleport: Ironforge", + ["Teleport: Moonglade"] = "Teleport: Moonglade", + ["Teleport: Orgrimmar"] = "Teleport: Orgrimmar", + ["Teleport: Stormwind"] = "Teleport: Stormwind", + ["Teleport: Thunder Bluff"] = "Teleport: Thunder Bluff", + ["Teleport: Undercity"] = "Teleport: Undercity", + ["Tendon Rip"] = "Tendon Rip", + ["Tendon Slice"] = "Tendon Slice", + ["Terrify"] = "Terrify", + ["Terrifying Screech"] = "Terrifying Screech", + ["Thick Hide"] = "Thick Hide", + ["Thorn Volley"] = "Thorn Volley", + ["Thorns"] = "Thorns", + ["Thousand Blades"] = "Thousand Blades", + ["Threatening Gaze"] = "Threatening Gaze", + ["Throw"] = "Throw", + ["Throw Axe"] = "Throw Axe", + ["Throw Dynamite"] = "Throw Dynamite", + ["Throw Liquid Fire"] = "Throw Liquid Fire", + ["Throw Wrench"] = "Throw Wrench", + ["Throwing Specialization"] = "Throwing Specialization", + ["Throwing Weapon Specialization"] = "Throwing Weapon Specialization", + ["Thrown"] = "Thrown", + ["Thunder Clap"] = "Thunder Clap", + ["Thunderclap"] = "Thunderclap", + ["Thunderfury"] = "Thunderfury", + ["Thundering Strikes"] = "Thundering Strikes", + ["Thundershock"] = "Thundershock", + ["Thunderstomp"] = "Thunderstomp", + ["Tidal Focus"] = "Tidal Focus", + ["Tidal Mastery"] = "Tidal Mastery", + ["Tiger Riding"] = "Tiger Riding", + ["Tiger's Fury"] = "Tiger's Fury", + ["Torment"] = "Torment", + ["Totem"] = "Totem", + ["Totem of Wrath"] = "Totem of Wrath", + ["Totemic Focus"] = "Totemic Focus", + ["Touch of Weakness"] = "Touch of Weakness", + ["Toughness"] = "Toughness", + ["Toxic Saliva"] = "Toxic Saliva", + ["Toxic Spit"] = "Toxic Spit", + ["Toxic Volley"] = "Toxic Volley", + ["Traces of Silithyst"] = "Traces of Silithyst", + ["Track Beasts"] = "Track Beasts", + ["Track Demons"] = "Track Demons", + ["Track Dragonkin"] = "Track Dragonkin", + ["Track Elementals"] = "Track Elementals", + ["Track Giants"] = "Track Giants", + ["Track Hidden"] = "Track Hidden", + ["Track Humanoids"] = "Track Humanoids", + ["Track Undead"] = "Track Undead", + ["Trample"] = "Trample", + ["Tranquil Air Totem"] = "Tranquil Air Totem", + ["Tranquil Spirit"] = "Tranquil Spirit", + ["Tranquility"] = "Tranquility", + ["Tranquilizing Poison"] = "Tranquilizing Poison", + ["Tranquilizing Shot"] = "Tranquilizing Shot", + ["Trap Mastery"] = "Trap Mastery", + ["Travel Form"] = "Travel Form", + ["Tree of Life"] = "Tree of Life", + ["Tremor Totem"] = "Tremor Totem", + ["Tribal Leatherworking"] = "Tribal Leatherworking", + ["Trueshot Aura"] = "Trueshot Aura", + ["Turn Undead"] = "Turn Undead", + ["Twisted Tranquility"] = "Twisted Tranquility", + ["Two-Handed Axes"] = "Two-Handed Axes", + ["Two-Handed Axes and Maces"] = "Two-Handed Axes and Maces", + ["Two-Handed Maces"] = "Two-Handed Maces", + ["Two-Handed Swords"] = "Two-Handed Swords", + ["Two-Handed Weapon Specialization"] = "Two-Handed Weapon Specialization", + ["Unarmed"] = "Unarmed", + ["Unbreakable Will"] = "Unbreakable Will", + ["Unbridled Wrath"] = "Unbridled Wrath", + ["Unbridled Wrath Effect"] = "Unbridled Wrath Effect", + ["Undead Horsemanship"] = "Undead Horsemanship", + ["Underwater Breathing"] = "Underwater Breathing", + ["Unending Breath"] = "Unending Breath", + ["Unholy Frenzy"] = "Unholy Frenzy", + ["Unholy Power"] = "Unholy Power", + ["Unleashed Fury"] = "Unleashed Fury", + ["Unleashed Rage"] = "Unleashed Rage", + ["Unstable Affliction"] = "Unstable Affliction", + ["Unstable Concoction"] = "Unstable Concoction", + ["Unstable Power"] = "Unstable Power", + ["Unyielding Faith"] = "Unyielding Faith", + ["Uppercut"] = "Uppercut", + ["Vampiric Embrace"] = "Vampiric Embrace", + ["Vampiric Touch"] = "Vampiric Touch", + ["Vanish"] = "Vanish", + ["Vanished"] = "Vanished", + ["Veil of Shadow"] = "Veil of Shadow", + ["Vengeance"] = "Vengeance", + ["Venom Spit"] = "Venom Spit", + ["Venom Sting"] = "Venom Sting", + ["Venomhide Poison"] = "Venomhide Poison", + ["Vicious Rend"] = "Vicious Rend", + ["Victory Rush"] = "Victory Rush", + ["Vigor"] = "Vigor", + ["Vile Poisons"] = "Vile Poisons", + ["Vindication"] = "Vindication", + ["Viper Sting"] = "Viper Sting", + ["Virulent Poison"] = "Virulent Poison", + ["Void Bolt"] = "Void Bolt", + ["Volley"] = "Volley", + ["Walking Bomb Effect"] = "Walking Bomb Effect", + ["Wand Specialization"] = "Wand Specialization", + ["Wandering Plague"] = "Wandering Plague", + ["Wands"] = "Wands", + ["War Stomp"] = "War Stomp", + ["Water"] = "Water", + ["Water Breathing"] = "Water Breathing", + ["Water Shield"] = "Water Shield", + ["Water Walking"] = "Water Walking", + ["Waterbolt"] = "Waterbolt", + ["Wavering Will"] = "Wavering Will", + ["Weakened Soul"] = "Weakened Soul", + ["Weaponsmith"] = "Weaponsmith", + ["Web"] = "Web", + ["Web Explosion"] = "Web Explosion", + ["Web Spin"] = "Web Spin", + ["Web Spray"] = "Web Spray", + ["Whirling Barrage"] = "Whirling Barrage", + ["Whirling Trip"] = "Whirling Trip", + ["Whirlwind"] = "Whirlwind", + ["Wide Slash"] = "Wide Slash", + ["Will of Hakkar"] = "Will of Hakkar", + ["Will of the Forsaken"] = "Will of the Forsaken", + ["Windfury Totem"] = "Windfury Totem", + ["Windfury Weapon"] = "Windfury Weapon", + ["Windsor's Frenzy"] = "Windsor's Frenzy", + ["Windwall Totem"] = "Windwall Totem", + ["Wing Clip"] = "Wing Clip", + ["Wing Flap"] = "Wing Flap", + ["Winter's Chill"] = "Winter's Chill", + ["Wisp Spirit"] = "Wisp Spirit", + ["Wolf Riding"] = "Wolf Riding", + ["Wound Poison"] = "Wound Poison", + ["Wound Poison II"] = "Wound Poison II", + ["Wound Poison III"] = "Wound Poison III", + ["Wound Poison IV"] = "Wound Poison IV", + ["Wrath"] = "Wrath", + ["Wrath of Air Totem"] = "Wrath of Air Totem", + ["Wyvern Sting"] = "Wyvern Sting", +} + +end) +return __bundle_require("__root") \ No newline at end of file