--local DEBUG = true local EHN,ns = ... EventHorizon = ns local class = select(2,UnitClass('player')) local playername = UnitName('player')..' - '..GetRealmName('player') local Cataclysm = select(4,GetBuildInfo()) >= 40000 local Mop = select(4,GetBuildInfo()) >= 50000 ns.defaultDB = { point = {'CENTER', 'UIParent', 'CENTER'}, isActive = true, version = 4, } ns.defaultDBG = { profiles = { default = {}, }, itemInfo = {}, profilePerChar = {}, defaultProfile = 'default', version = 4, } ns.db = { point = {'CENTER', 'UIParent', 'CENTER'}, isActive = true, version = 4, } ns.frames = { config = {}, -- validated barframe config entries - format = ns.frames.config[i] = {barconfig} frames = {}, -- all loaded barframes active = {}, -- refs to barframes currently collecting information (matches talent spec) shown = {}, -- refs to barframes currently visible to the player (matches stance) mouseover = {}, -- refs to barframes requiring mouseover target information } ns.defaultconfig = { showTrinketBars = { default = true, boolean = true, name = 'Show Trinket Bars', desc = 'When enabled, displays trinkets in addition to spells and abilities.', }, castLine = { default = true, boolean = true, number = true, name = 'End-of-Cast Line', desc = 'When enabled, adds a vertical line which marks the end of any spellcast in progress.', }, gcdStyle = { default = 'line', valid = {'line','bar',false}, name = 'Global Cooldown Style', desc = 'When set to Line, a vertical line will mark the end of the current GCD. \n When set to Bar, a textured bar is used instead. \n Can also be disabled to neither track or display the GCD.', }, enableRedshift = { default = false, boolean = true, name = 'Enable Redshift', desc = 'An optional module which hides Axis untless certain conditions, such as combat or targeting, are met.', }, Redshift = { name = 'Redshift States', desc = 'Conditions for the Redshift Module to show Axis.', sub = { showCombat = { default = true, boolean = true, name = 'Show in Combat', desc = 'When enabled, displays Axis when in combat.', }, showHarm = { default = true, boolean = true, name = 'Show Harmful Units', desc = 'When enabled, displays Axis when an attackable unit is targeted.', }, showHelp = { default = false, boolean = true, name = 'Show Helpful Units', desc = 'When enabled, displays Axis when a friendly unit is targeted.', }, showBoss = { default = true, boolean = true, name = 'Show on Boss', desc = 'When enabled, displays Axis when a boss-level unit is targeted.', }, showFocus = { default = false, boolean = true, name = 'Show on Focus', desc = 'When enabled, displays Axis when you have a focus target.' }, hideVehicle = { default = true, boolean = true, name = 'Hide in Vehicle', desc = 'When enabled, HIDES Axis when using a vehicle with its own actionbar.', }, hideVitals = { default = true, boolean = true, name = 'Hide Vitals', desc = 'When enabled, the Vitals display is hidden whenever Axis is hidden.', }, }, }, -- Pulse = 0.5, -- PulseIntensity = 0.5, -- PulseFPS = 30, Lines = { default = false, boolean = true, table = true, name = 'Static Lines', desc = 'When enabled, enables the Lines Module.', }, LinesColor = { default = {1,1,1,0.5}, table = true, name = 'Static Line Colors', desc = 'The color of any static lines being displayed by the Lines Axis Module.' }, anchor = { default = {"TOPRIGHT", "EventHorizonHandle", "BOTTOMRIGHT"}, table = true, name = 'Anchor Position', desc = "Axis' Handle Information", }, width = { default = 150, number = true, name = 'Bar Width', desc = 'Set the width of shown bars. Icons add to the actual width of the window.' }, height = { default = 18, number = true, name = 'Bar Height', desc = 'Set the height of each individual bar. Also sets the width of icons.', }, spacing = { default = 0, number = true, name = 'Bar Spacing', desc = 'Set the spacing between each shown bar.', }, staticheight = { default = false, number = true, boolean = true, name = 'Static Height', desc = 'When set, Axis will resize its bars to fit this height. \n When disabled, Axis will grow or shrink depending on the number of shown bars.' }, hideIcons = { default = false, boolean = true, name = 'Hide Bar Icons', desc = 'When enabled, Icons are not shown, however stack-text is still shown.', }, past = { default = -3, number = true, name = 'Past Time', desc = 'How many seconds in the past for Axis to display.', }, future = { default = 12, number = true, name = 'Future Time', desc = 'How many seconds in the future for Axis to display.' }, texturedbars = { default = true, boolean = true, name = 'Textured Bars', desc = 'When enabled, Axis displays textured bars according to the Bar Texture option. \n When disabled, Axis displays the bars as a solid color.', }, bartexture = { default = "Interface\\Addons\\EventHorizon\\Smooth", string = true, name = 'Bar Texture', desc = 'Set the texture to use for each bar.', }, texturealphamultiplier = { default = 2, number = true, name = 'Texture Alpha-Multiplier', desc = 'This option directly influences the opacity of textured bars to account for varying degrees of visibility.' }, backdrop = { default = true, boolean = true, name = 'Show Backdrop', desc = 'When enabled, Axis displays the backdrop.', }, padding = { default = 3, number = true, name = 'Backdrop Padding', desc = 'Set the padding between the backdrop and bar edges.' }, bg = { default = "Interface\\ChatFrame\\ChatFrameBackground", string = true, name = 'Backdrop Texture', desc = 'Set the texture to use for the backdrop.', }, border = { default = "Interface\\Tooltips\\UI-Tooltip-Border", string = true, name = 'Backdrop Border Texture', desc = 'Set the texture to use for the backdrop border.', }, edgesize = { default = 8, number = true, name = 'Backdrop Edge Size', desc = 'Set the thickness of the backdrop border.', }, inset = { default = {top = 2, bottom = 2, left = 2, right = 2}, table = true, name = 'Backdrop Insets', desc = 'Trim the backdrop texture to account for its border.', }, stackFont = { default = false, boolean = true, string = true, name = 'Stack Text Font', desc = 'Sets the font of the stack text shown on bar icons.', }, stackFontSize = { default = false, boolean = true, number = true, name = 'Stack Text Size', desc = 'Set the size of the stack text shown on bar icons.', }, stackFontOutline = { default = false, valid = {'OUTLINE','THICKOUTLINE','MONOCHROME',false}, name = 'Stack Text Outline', desc = 'Set the outline of the stack text shown on bar icons.', }, stackFontColor = { default = false, table = true, name = 'Stack Text Color', desc = 'Sets the color of the stack text shown on bar icons.', }, stackFontShadow = { default = false, table = true, boolean = true, name = 'Stack Text Shadow', desc = 'Apply a shadow effect to the stack text shown on bar icons. \n This option adjusts the shadow color and can be left at default for black.', }, stackFontShadowOffset = { default = false, table = true, boolean = true, name = 'Stack Text Shadow Offset', desc = 'Set the offset of the stack text shadow.', }, stackOnRight = { default = false, boolean = true, name = 'Stack Text on Right', desc = 'When enabled the stack text is displayed on the right-hand side of the bars. \n When disabled, stack text is shown on the left side, as default.', }, } ns.defaultcolors = { sent = {true,class == 'PRIEST' and 0.7 or 1,1}, tick = {true,class == 'PRIEST' and 0.7 or 1,1}, casting = {0,1,0.2,0.25}, castLine = {0,1,0.2,0.3}, cooldown = {0.6,0.8,1,0.3}, debuffmine = {true,class == 'PRIEST' and 0.7 or 1,0.3}, debuff = {true,0.5,0.3}, playerbuff = {true,class == 'PRIEST' and 0.7 or 1,0.3}, nowLine = {1,1,1,0.3}, bgcolor = {0,0,0,0.6}, bordercolor = {1,1,1,1}, gcdColor = {1,1,1,0.5}, } ns.defaultlayouts = { tick = { top = 0, bottom = 0.2, }, smalldebuff = { top = 0.2, bottom = 0.35, }, cantcast = { top = 0.35, bottom = 1, }, default = { top = 0.2, bottom = 1, }, } ns.config = {Redshift = {}, blendModes = {}} ns.layouts = {} ns.colors = {} ns.glyphs = {} -- currently active glyph storage. format = ns.glyphs[i] = glyphID ns.otherIDs = {} -- combatlog events either not directly tied to bars, or using spells other than bar.spellID ns.modules = {} -- storage for loaded modules - format = module = ns.modules[string.lower(moduleName)] = {namespace} ns.vars = { -- storage for widely used vars/math/etc - format = ns.vars[var] = val config = {}, onepixelwide = 1, visibleFrame = true, numframes = 0, buff = {}, debuff = {}, } local vars = ns.vars local UnitDebuff = UnitDebuff local UnitBuff = UnitBuff local SpellFrame = {} local EventHorizonFrame = CreateFrame('Frame','EventHorizonFrame',UIParent) local mainframe = CreateFrame('Frame',nil,EventHorizonFrame) local frame = CreateFrame('Frame') local frame2 = CreateFrame('Frame') local frame3 = CreateFrame('Frame') ns.mainframe = mainframe -- Frames to be created on demand local handle local function printhelp(...) if select('#',...)>0 then return tostring((select(1,...))), printhelp(select(2,...)) end end local function debug(...) print(...) end local function print(...) ChatFrame1:AddMessage('EventHorizon: '..strjoin(',',printhelp(...))) end local draworder = { default = -8, cooldown = -7, debuff = -6, playerbuff = -5, debuffmine = -4, casting = -3, sent = -2, tick = -1, channeltick = 0, now = 1, gcd = 2, nowI = 7, } local auraids = { tick = true, cantcast = true, debuff = true, playerbuff = true, debuffmine = true, } local customColors = { debuff = true, debuffmine = true, playerbuff = true, } local exemptColors = { default = true, sent = true, tick = true, channeltick = true, castLine = true, nowLine = true, bgcolor = true, bordercolor = true, gcdColor = true, } local equipSlots = { ["ChestSlot"] = 5, ["FeetSlot"] = 8, ["Finger0Slot"] = 11, ["Finger1Slot"] = 12, ["HandsSlot"] = 10, ["HeadSlot"] = 1, ["LegsSlot"] = 7, ["MainHandSlot"] = 16, ["NeckSlot"] = 2, ["RangedSlot"] = 18, ["SecondaryHandSlot"] = 17, ["ShirtSlot"] = 4, ["ShoulderSlot"] = 3, ["TabardSlot"] = 19, ["Trinket0Slot"] = 13, ["Trinket1Slot"] = 14, ["WaistSlot"] = 6, ["WristSlot"] = 9, } local mainframeEvents = { ['COMBAT_LOG_EVENT_UNFILTERED'] = true, ['PLAYER_TALENT_UPDATE'] = true, ['UPDATE_SHAPESHIFT_FORM'] = true, ['UPDATE_SHAPESHIFT_FORMS'] = true, ['SPELL_UPDATE_COOLDOWN'] = true, ['PLAYER_LEVEL_UP'] = true, ['PLAYER_TARGET_CHANGED'] = true, ['UNIT_AURA'] = true, } local reloadEvents = { ['GLYPH_ADDED'] = true, ['GLYPH_ENABLED'] = true, ['GLYPH_REMOVED'] = true, ['GLYPH_UPDATED'] = true, ['GLYPH_DISABLED'] = true, ['PLAYER_REGEN_DISABLED'] = true, ['PLAYER_REGEN_ENABLED'] = true, ['ZONE_CHANGED_NEW_AREA'] = true, ['ZONE_CHANGED_INDOORS'] = true, ['LFG_LOCK_INFO_RECEIVED'] = true, } local tickevents = { ['SPELL_PERIODIC_DAMAGE'] = true, ['SPELL_PERIODIC_HEAL'] = true, ['SPELL_PERIODIC_ENERGIZE'] = true, ['SPELL_PERIODIC_DRAIN'] = true, ['SPELL_PERIODIC_LEACH'] = true, ['SPELL_DAMAGE'] = true, ['SPELL_HEAL'] = true, --['SPELL_AURA_APPLIED'] = true, } -- Dispatch event to method of the event's name. local EventHandler = function (self, event, ...) local f = self[event] if f then --if event ~= 'COMBAT_LOG_EVENT_UNFILTERED' then print(event) end f(self,...) if event ~= 'COMBAT_LOG_EVENT_UNFILTERED' then ns:ModuleEvent(event,...) end end end local Clone = function (t) local new = {} local i, v = next(t, nil) -- i is an index of t, v = t[i] while i do new[i] = v i, v = next(t, i) end return new end -- pairs(t) for metatable usage. Doesn't return numeric index unless value is keyless. function mpairs(t) local visited = {} local f f = function(_, k) if not t then return end while true do local k2, v2 = next(t, k) if k2 == nil then break end if not visited[k2] then visited[k2] = true return k2, v2 end k = k2 end local mt = getmetatable(t) if mt then local indextable = mt.__index if type(indextable) == "table" then t = indextable return f() end end t = nil end return f end -- Since Blizzard doesn't provide the ability to look up a slot name from a slotID... local GetSlotName = function (slot) for k,v in pairs(equipSlots) do if v == slot then return k end end end local mainframe_UNIT_AURA = function (self,unit) if vars.buff[unit] then table.wipe(vars.buff[unit]) for i = 1,50 do local name, _, icon, count, _, duration, expirationTime, source, _, _, spellID = UnitBuff(unit,i) --print(name,icon,count,duration,expirationTime,source,spellID) if not (name and spellID) then break end table.insert(vars.buff[unit],{ name = name, icon = icon, count = count, duration = duration, expirationTime = expirationTime, source = source, spellID = spellID, }) end end if vars.debuff[unit] then table.wipe(vars.debuff[unit]) for i = 1,100 do local name, _, icon, count, _, duration, expirationTime, source, _, _, spellID = UnitDebuff(unit,i) if not (name and spellID) then break end table.insert(vars.debuff[unit], { name = name, icon = icon, count = count, duration = duration, expirationTime = expirationTime, source = source, spellID = spellID, }) end end for i,spellframe in pairs(ns.frames.frames) do if (spellframe.auraunit and spellframe.auraunit == unit) then spellframe:UNIT_AURA(unit) end end end local GetAura = function (self) local s = self.isType == 'playerbuff' and 'buff' or 'debuff' local a = vars[s][self.auraunit] if not a then return end if type(self.auraname) == 'table' then for k,aura in pairs(a) do for i = 1,#self.auraname do if (aura.name == self.auraname[i]) and (aura.source == 'player' or self.unique) and (not(self.uniqueID) or self.uniqueID == aura.spellID) then return aura.name, aura.icon, aura.count, aura.duration, aura.expirationTime, aura.source, aura.spellID end end end else for k,aura in pairs(a) do if (aura.name == self.auraname) and (aura.source == 'player' or self.unique) and (not(self.uniqueID) or self.uniqueID == aura.spellID) then return aura.name, aura.icon, aura.count, aura.duration, aura.expirationTime, aura.source, aura.spellID end end end end ns.GetAura = function (self,auralist,auratype,unit) if not auratype and unit then return error('Invalid arg in EventHorizon:GetAura(self,auralist,auratype,unit)') end local a = vars[auratype][unit] if not a then return end if type(auralist) == 'table' then for k,aura in pairs(a) do for i = 1,#auralist do local t = type(auralist[i]) if (t == 'string' and aura.name or t == 'number' and aura.spellID) == auralist[i] then return aura.name, aura.icon, aura.count, aura.duration, aura.expirationTime, aura.source, aura.spellID end end end else for k,aura in pairs(a) do local t = type(auralist) if (t == 'string' and aura.name or t == 'number' and aura.spellID) == auralist then return aura.name, aura.icon, aura.count, aura.duration, aura.expirationTime, aura.source, aura.spellID end end end end -- SpellFrame - All spell bar frames inherit from this class. --Indicators represent a point or range of time. There are different types. The type determines the color and position. local typeparent = {} local SetStacks = function (self,count) if count>1 then self.stacks:SetFormattedText('%d',count) elseif self.glyphstacks then if self.glyphstacks[guid] and (self.glyphstacks[guid] > 0) then self.stacks:SetText(self.glyphstacks[guid]) else self.stacks:SetText() end else self.stacks:SetText() end end local SpellFrame_NotInteresting = function (self, unitid, spellname) return unitid ~= 'player' or spellname ~= self.spellname end -- FindItemInfo: local SpellFrame_FindItemInfo = function (self,slotID) local itemID = self.itemID or GetInventoryItemID('player',slotID or self.slotID) if itemID then local dbI = EventHorizonDBG.itemInfo[itemID] if dbI and (dbI.name and dbI.tex) then return itemID,dbI.name,dbI.tex else local name,_,_,_,_,_,_,_,_,tex = GetItemInfo(itemID) if (name and tex) then EventHorizonDBG.itemInfo[itemID] = {name = name, tex = tex} return itemID,name,tex end end end end local SpellFrame_AddIndicator = function (self, typeid, layoutid, time, usetexture) local indicator local parent = typeparent[typeid] local ndtex, ndcol if usetexture and self.bartexture then ndtex = self.bartexture end if typeid and customColors[typeid] then if self.barcolorunique and typeid == 'debuff' then ndcol = self.barcolorunique elseif self.barcolor then ndcol = self.barcolor end end if not parent then --print'creating indicator parent' parent = {} parent.unused = {} typeparent[typeid] = parent --if DEBUG and typeid=='tick' then parent.numchildren=0 end--]] end if #parent.unused>0 then indicator = tremove(parent.unused) indicator:ClearAllPoints() indicator.time = nil indicator.start = nil indicator.stop = nil indicator.happened = nil --if DEBUG and typeid=='tick' then debug('reusing indicator',indicator.frameindex) end--]] else indicator = mainframe:CreateTexture(nil, 'ARTWORK', nil, draworder[typeid]) indicator.parent = parent --if DEBUG and typeid=='tick' then parent.numchildren=parent.numchildren+1 indicator.frameindex=parent.numchildren debug('adding indicator',indicator.frameindex) end--]] end -- Layout local layouts = ns.layouts local layout = layouts[layoutid] or layouts.default local color = ndcol or ns.colors[typeid] or ns.colors.default if layoutid == 'frameline' then color = typeid == 'sent' and ns.colors.castLine or ns.colors[typeid] indicator:SetPoint('TOP',ns.mainframe) indicator:SetPoint('BOTTOM',ns.mainframe) else indicator:SetPoint('TOP',self, 'TOP', 0, -layout.top*vars.barheight) indicator:SetPoint('BOTTOM',self, 'TOP', 0, -layout.bottom*vars.barheight) end if usetexture then indicator:SetTexture(ndtex or vars.bartexture) indicator:SetTexCoord(unpack(layout.texcoords)) else indicator:SetTexture(1,1,1,1) end indicator:SetVertexColor(unpack(ndcol or color)) if ns.config.blendModes[typeid] and type(ns.config.blendModes[typeid]) == 'string' then indicator:SetBlendMode(ns.config.blendModes[typeid]) end indicator:Hide() indicator:SetWidth(vars.onepixelwide) indicator.time = time indicator.typeid = typeid indicator.layoutid = layoutid if indicator then tinsert(self.indicators, indicator) end return indicator end local SpellFrame_AddSegment = function (self, typeid, layoutid, start, stop, start2) if stop--------] -- past now time future -- now=795, time=800, past=-3, then time is time-now-past after past. local p = (time-diff)*vars.scale local remove = p<0 or (time<=now and indicator.typeid=='tick' and not indicator.happened) if remove then indicator:Hide() --if DEBUG and indicator.typeid=='tick' then debug('deleting',indicator.frameindex) end--]] tinsert(indicator.parent.unused, tremove(self.indicators,k)) elseif p<=1 then indicator:SetPoint('LEFT', self, 'LEFT', p*vars.barwidth, 0) indicator:Show() end else local start, stop = indicator.start, indicator.stop local p1 = (start-diff)*vars.scale local p2 = (stop-diff)*vars.scale if p2<0 then indicator:Hide() --if DEBUG and indicator.typeid=='tick' then debug('deleting',indicator.frameindex) end--]] tinsert(indicator.parent.unused, tremove(self.indicators,k)) elseif 1 vars.castLine) then self.castLine = self:AddIndicator('sent', 'frameline', endTime) end local numhits = self.cast[spellname].numhits and self.cast[spellname].numhits ~= true and self.cast[spellname].numhits if numhits then local casttime = endTime - startTime local tick = casttime/numhits if numhits and numhits ~= true then for i=1,numhits do self:AddIndicator('channeltick', 'channeltick', startTime + i*tick) end end end end local Cast_Update = function (self, unitid, spellname, spellrank) --debug('UNIT_SPELLCAST_CHANNEL_UPDATE',unitid, spellname, spellrank) if not(self.cast[spellname]) or unitid ~= 'player' then return end local _,_,_,_,startTime,endTime,_ = self.cast[spellname].func(unitid) if not (startTime and endTime) then return end startTime, endTime = startTime/1000, endTime/1000 if self.casting then self.casting.stop = endTime if vars.castLine and self.castLine then self.castLine.time = endTime end end self:RemoveChannelTicksAfter(endTime) end local Cast_Stop = function (self, unitid, spellname, spellrank) if not(self.cast[spellname]) or unitid ~= 'player' then return end local now = GetTime() if self.casting then self.casting.stop = now if vars.castLine and self.castLine then self.castLine.time = now end self.casting = nil end self:RemoveChannelTicksAfter(now) end local SpellFrame_UNIT_AURA = function (self, unitid) if unitid~=self.auraunit then return end if not (self.spellname and self.auraname) then return end local name, icon, count, duration, expirationTime, source, spellID = GetAura(self,unitid) --print(name, icon, count, duration, expirationTime, source, spellID) local afflicted = name and (self.unique or source=='player') and (not self.minstacks or count>=self.minstacks) local addnew local now = GetTime() local start local targ = UnitName(self.auraunit) if self.uniqueID and self.uniqueID ~= spellID then return end if self.aurasegment and expirationTime == 0 and duration == 0 then -- Timeless aura, bar exists (Overkill) for i = #self.indicators,1,-1 do self:Remove(i) end self.aurasegment = nil self.nexttick = nil self.stacks:SetText() return end if expirationTime == 0 then return end if afflicted then start = expirationTime-duration if icon and not(self.cast or self.slotID or self.keepIcon) then self.icon:SetTexture(icon) end if self.aurasegment and (self.aurasegment.lastunit == targ) then -- The aura is currently displayed if expirationTime~=self.aurasegment.stop then if self.alwaysrefresh and not self.cast then -- alwaysrefresh = buff. Cast + buff - HoT = BAD. Buffs with cast time and no HoT component are treated much differently. if self.dot then -- ...check to see if it's a HoT. If so, it's treated as a DoT. self.aurasegment.stop = start-0.2 if self.cast and self.useSmalldebuff then self.cantcast.stop = start-0.2 end self:RemoveTicksAfter(start) addnew = true else -- If it's a buff with no cast time or HoT component, no special handling needed, move along. self.aurasegment.stop = expirationTime end else -- The aura was replaced. self.aurasegment.stop = start-0.2 if self.cast and self.useSmalldebuff then self.cantcast.stop = start-0.2 end self:RemoveTicksAfter(start) addnew = true end if self.internalcooldown and type(self.internalcooldown) == 'number' then local stop = now + self.internalcooldown if start > stop then start = now end self:AddSegment('cooldown', 'cooldown', start, stop) end end else addnew = true if self.internalcooldown and type(self.internalcooldown) == 'number' then local stop = now + self.internalcooldown if start > stop then start = now end self:AddSegment('cooldown', 'cooldown', start, stop) end end SetStacks(self,count) else if self.aurasegment then if math.abs(self.aurasegment.stop - now)>0.3 then self.aurasegment.stop = now if self.cast and self.useSmalldebuff then self.cantcast.stop = now-0.2 end end self:RemoveTicksAfter(now) self.aurasegment = nil self.stacks:SetText() end end self:UpdateDoT(addnew, source, now, start, expirationTime, duration, name) end local mainframe_PLAYER_TARGET_CHANGED = function (self) local exists = UnitExists('target') local dead if exists then dead = UnitIsDead('target') end for i,spellframe in pairs(ns.frames.frames) do if spellframe.auraunit == 'target' then if spellframe.aurasegment then for i = #spellframe.indicators,1,-1 do local ind = spellframe.indicators[i] if auraids[ind.typeid] then spellframe:Remove(i) end end spellframe.aurasegment = nil spellframe.targetdebuff = nil spellframe.nexttick = nil spellframe.recenttick = nil spellframe.stacks:SetText() end if spellframe.refreshable then if exists then if dead then spellframe.debuffs[UnitGUID('target')] = nil else spellframe.targetdebuff = spellframe.debuffs[UnitGUID('target')] end end end end end if UnitExists('target') then self:UNIT_AURA('target') end end local SpellFrame_RemoveTicksAfter = function (self, min) local indicators = self.indicators for i = #indicators,1,-1 do local ind = indicators[i] if (ind.typeid == 'tick') and ind.time>min then self:Remove(i) end end --print('removing ticks after',min) self.nexttick = nil end local SpellFrame_RemoveChannelTicksAfter = function (self, min) local indicators = self.indicators for i = #indicators,1,-1 do local ind = indicators[i] if ind.typeid == 'channeltick' and ind.time>min then self:Remove(i) end end self.nextchanneltick = nil end local mainframe_CLEU_OtherInterestingSpell = function (self, time, event, hideCaster, srcguid, srcname, srcflags, destguid, destname, destflags, spellid, spellname) local now = GetTime() if ns.otherIDs[spellname] then local id = ns.otherIDs[spellname] local bf = ns.frames.frames if event == 'SPELL_DAMAGE' and id.isChannel then for i in pairs(bf) do if bf[i].cast and bf[i].cast[spellname] then local tick = bf[i]:AddIndicator('tick', 'tick', now) tick.happened = true break end end elseif event == 'SPELL_CAST_SUCCESS' or event == 'SPELL_DAMAGE' and id.isGlyph then -- Glyph refresh if id.last and (now < (id.last + 0.9)) then -- Throttle heavily, don't want stacks getting blown --debug("Ignoring "..event.." from "..spellname.." at "..now) return else --debug("Interesting spell potentially not in frame detected! Spell: "..spellname,event) id.last = now for i in pairs(bf) do local gr = bf[i].glyphrefresh or nil local gs = bf[i].glyphstacks or nil if gr and (gr[3] == spellname) then if gs[destguid] then gs[destguid] = gs[destguid] - 1 bf[i].stacks:SetText(gs[destguid] > 0 and gs[destguid] or nil) end --debug("SUCCESS! "..gr[3].." has triggered "..frame[i].auraname) end end end end end end local AddTicks = {} AddTicks.stop = function (self,now,fresh) local nexttick = self.start while nexttick<=self.stop+0.1 do if now+vars.future self.dot and self.dot or self.dotMod self.ticks.last = now end self:RemoveTicksAfter(now) -- Reconstruct ticks from spellframe info local nexttick = now+(self.dotMod or self.dot) self.nexttick = nil self.recenttick = now while nexttick<=(self.stop+0.2) do -- Account for lag if now+vars.future=self.minstacks) local addnew, refresh local now = GetTime() local guid = UnitGUID(self.auraunit or 'target') --print(name,source,self.spellname,self.auraname) -- First find out if the debuff was refreshed. if self.aurasegment and expirationTime == 0 and duration == 0 then -- Timeless aura, bar exists (Overkill) for i = #self.indicators,1,-1 do self:Remove(i) end self.aurasegment = nil self.nexttick = nil self.stacks:SetText() return end if expirationTime == 0 then return end if afflicted then start = expirationTime-duration if icon and not(self.cast or self.slotID or self.keepIcon) then self.icon:SetTexture(icon) end if self.targetdebuff then if self.targetdebuff.stop == expirationTime then start = self.targetdebuff.start else -- Check for refresh. if start < self.targetdebuff.stop then local totalduration = self.targetdebuff.stop - self.targetdebuff.start local lasttick = self.targetdebuff.stop - math.fmod(totalduration, self.dotMod or self.dot) local success = self.castsuccess[guid] local not_recast = true -- Poisons are never actually recast, so we default to true here, because success will always be nil. if success then not_recast = math.abs(success-start)>0.5 end if not_recast and start < lasttick then -- The current debuff was refreshed. start = self.targetdebuff.start refresh = true end end end end if self.aurasegment then if expirationTime~=self.aurasegment.stop and not refresh then -- The current debuff was replaced. self.aurasegment.stop = start-0.2 self:RemoveTicksAfter(start) --debug('replaced') addnew = true end else addnew = true end SetStacks(self,count) else if self.aurasegment then if math.abs(self.aurasegment.stop - now)>0.3 then -- The current debuff ended. self.aurasegment.stop = now if self.cantcast then self.cantcast.stop = now end end self:RemoveTicksAfter(now) self.aurasegment = nil self.cantcast = nil self.targetdebuff = nil self.recenttick = nil self.stacks:SetText() end end self:UpdateDoT(addnew, source, now, start, expirationTime, duration, name, refresh, guid) end local SpellFrame_UpdateDoT = function (self, addnew, source, now, start, expirationTime, duration, name, refresh, guid) local addticks local isHasted local checkDoT = self.auranamePrimary or name local isPrimary = checkDoT == name or nil self.start, self.stop, self.duration = start, expirationTime, duration local targ = UnitName(self.auraunit) if addnew then --debug('addnew', start, expirationTime) local typeid = (source=='player' and self.isType) or (source~='player' and 'debuff') if self.cast and self.useSmalldebuff then self.aurasegment = self:AddSegment(typeid, 'smalldebuff', start, expirationTime) local hastedcasttime = select(7, GetSpellInfo(self.lastcast or self.spellname))/1000 self.cantcast = self:AddSegment(typeid, 'cantcast', start, expirationTime-hastedcasttime) self.aurasegment.lastunit = targ else self.aurasegment = self:AddSegment(typeid, 'default', start, expirationTime) self.aurasegment.lastunit = targ end -- Add visible ticks. if self.dot and isPrimary then addticks = start end if self.debuffs then -- Refreshable only. self.targetdebuff = {start=start, stop=expirationTime} self.debuffs[guid] = self.targetdebuff end self.recenttick = now elseif refresh then -- debug('refresh', start, expirationTime) -- Note: refresh requires afflicted and self.targetdebuff. Also, afflicted and not self.debuff implies addnew. -- So we can get here only if afflicted and self.debuff and self.targetdebuff. self.aurasegment.stop = expirationTime self.targetdebuff.stop = expirationTime if self.cantcast then self.cantcast.start = start self.cantcast.stop = expirationTime-(select(7, GetSpellInfo(self.lastcast or self.spellname))/1000) end if self.latesttick then addticks = self.latesttick end end if addticks then addticks = self.recenttick or addticks local nexttick = addticks+(self.dotMod or self.dot) self.nexttick = nil if self.hasted then if type(self.hasted) == 'number' then for i = 1,6 do if ns.glyphs[i] == self.hasted then isHasted = true end end elseif self.hasted == true then isHasted = true end end if isHasted and self.expectedTicks then -- Using expectedTicks self.dotMod = (expirationTime - start)/self.expectedTicks local magearmor = UnitBuff(self.auraunit, "Mage Armor") if self.dot and magearmor and self.dotMod <= 1.5 then self.dotMod = self.dotMod * 2 end -- Adjust for mage armor rank 5+ 50% duration reduction, use number comparison to make sure we're not changing something that shouldn't be. elseif isHasted then local bct = ns.config.hastedSpellID[2] local hct = select(7, GetSpellInfo(ns.config.hastedSpellID[1]))/1000 self.dotMod = self.dot*(hct/bct) --[[ if ns.config.nonAffectingHaste then for i,nah in ipairs(ns.config.nonAffectingHaste) do local name,_,_,_,_,source = ns:GetAura(nah[1],'buff','player') if name and (source == 'player') then self.dotMod = self.dotMod * nah[2] end end end--]] end self:RemoveTicksAfter(now) --self:AddTicks(now) if self.hasted then if type(self.hasted) == 'number' then for i = 1,6 do if ns.glyphs[i] == self.hasted then isHasted = true end end elseif self.hasted == true then isHasted = true end end if isHasted and self.ticks then -- Tick-process haste handling self.ticks.last = self.ticks.last or now self.dotMod = self.dotMod and self.dotMod or self.dot if (self.dotMod > self.dot) or (self.dotMod < 1.5) then self.dotMod = self.dot end self.isHasted = true elseif isHasted and self.expectedTicks then -- Using expectedTicks self.dotMod = (expirationTime - start)/self.expectedTicks end while nexttick<=(self.stop+0.2) do -- Account for lag if now+vars.future start+duration then -- start = start2 -- duration = duration2 -- enabled = enabled2 -- ready = (enabled==1 and start~=0 and duration) and start+duration -- end --print(start, duration, enabled, ready) -- end --print(start, duration, enabled, ready) -- else start, duration, enabled = self.CooldownFunction(self.cooldownID or self.spellname) ready = enabled==1 and start~=0 and duration and start+duration -- end --print(start, duration, enabled, ready) if ready and duration>1.5 then -- The spell is on cooldown, but not just because of the GCD. if self.cooldownID then end if self.cooldown ~= ready then -- The CD has changed since last check if not(self.coolingdown) then -- No CD bar exists. self.coolingdown = self:AddSegment('cooldown', self.smallCooldown and 'smallCooldown' or 'cooldown', start, ready) elseif self.coolingdown.stop and self.coolingdown.stop ~= ready then -- cd exists but has changed self.coolingdown.start = start self.coolingdown.stop = ready end self.cooldown = ready end else if self.coolingdown then -- Spell is off cooldown or cd expires during GCD window local now = GetTime() -- See when the cooldown is ready. If the spell is currently on GCD, check the GCD end; otherwise check now. if self.cooldown > (ready or now) then -- The cooldown ended earlier. self.coolingdown.stop = now end self.coolingdown = nil end self.cooldown = nil end end local SpellFrame_PLAYER_EQUIPMENT_CHANGED = function (self,slot,equipped) if not (slot or self.slotID) or (self.slotID ~= slot) then return end for i = #self.indicators,1,-1 do self:Remove(i) end self.aurasegment = nil self.nexttick = nil self.stacks:SetText() self.cooldown = nil self.coolingdown = nil self.playerbuff = nil self.spellname = nil self.auraname = nil self.internalcooldown = true local itemID,name,tex = self:FindItemInfo() self.cooldownID = itemID if (itemID and name and tex) and not(ns.trinkets.blacklist[name]) then self.spellname = name self.icon:SetTexture(tex) self.stance = true -- Always show if ns.trinkets[name] then if type(ns.trinkets[name]) == 'number' then self.playerbuff = ns.trinkets[name] elseif type(ns.trinkets[name]) == 'table' then self.playerbuff = ns.trinkets[name][1] self.internalcooldown = ns.trinkets[name][2] end elseif self.slotID == 10 then self.playerbuff = 54758 -- Engy gloves elseif self.slotID == 8 then self.playerbuff = 54861 -- Nitro Boosts end if type(self.playerbuff)=='number' then self.auraname = (GetSpellInfo(self.playerbuff)) elseif type(self.playerbuff)=='table' then self.auraname = {} self.auranamePrimary = (GetSpellInfo(self.playerbuff[1])) for i,id in ipairs(self.playerbuff) do tinsert(self.auraname, (GetSpellInfo(id))) end self.AuraFunction = UnitBuffUnique else self.auraname = self.spellname end self:SPELL_UPDATE_COOLDOWN() else self.stance = 50 -- More efficient than other methods of hiding the bar. self.icon:SetTexture(0,0,0,0) end -- Throttle equipment checks to every 2 seconds. This should decrease overall cpu load while making equipment checks more reliable on beta/ptr. -- vars.EQCframes = vars.EQCframes or {} -- table.insert(vars.EQCframes,self) --print('equipment update - slot '..self.slotID) if not(vars.currentEQcheck) then frame2.elapsed = 0 vars.currentEQcheck = true frame2:SetScript('OnUpdate', function (self,elapsed) self.elapsed = self.elapsed + elapsed if self.elapsed >= 2 then mainframe:UPDATE_SHAPESHIFT_FORM() --[[ for i,v in ipairs(vars.EQCframes) do v:SPELL_UPDATE_COOLDOWN() end ]]-- vars.currentEQcheck = nil -- vars.EQCframes = nil --print('equipment check onupdate complete and cleared') self:SetScript('OnUpdate',nil) end end) end end --[[ Things get ugly again here. UNIT_AURA does not fire for mouseover units, so we need to emulate it. UPDATE_MOUSEOVER_UNIT does not fire when a mouseover unit is cleared, so we need to emulate that as well. The UMU check is unthrottled by necessity. UNIT_AURA doesn't really need to be run more than 10 times per second, so it gets throttled to save cycles. ]]-- local TTL,TSLU = 0.15,0 local UpdateMouseover = function (self,elapsed) TSLU = TSLU+elapsed if not(UnitExists('mouseover')) then ns:CheckMouseover() frame:SetScript('OnUpdate',nil) else if (TSLU >= TTL) then mainframe:UNIT_AURA('mouseover') TSLU = 0 end end end local SpellFrame_UPDATE_MOUSEOVER_UNIT = function (self) --print("UPDATE_MOUSEOVER_UNIT") if UnitExists('mouseover') then vars.isMouseover = true self.auraunit = 'mouseover' else vars.isMouseover = nil self.auraunit = self.baseunit end frame:SetScript('OnUpdate',UpdateMouseover) if self.aurasegment then for i = #self.indicators,1,-1 do local ind = self.indicators[i] if auraids[ind.typeid] then self:Remove(i) end end self.aurasegment = nil self.nexttick = nil self.stacks:SetText() end if self.refreshable then if UnitExists(self.auraunit) then local guid = UnitGUID(self.auraunit) if UnitIsDead(self.auraunit) then self.debuffs[guid] = nil else self.targetdebuff = self.debuffs[guid] --if self.targetdebuff then debug(self.spellname, 'have old') end mainframe:UNIT_AURA(self.auraunit) --if self.aurasegment then debug(self.spellname, 'added new') end if self.glyphstacks and self.aurasegment then local count = self.glyphstacks[guid] if self.glyphstacks[guid] and ( count > 0) then self.stacks:SetText(self.glyphstacks[UnitGUID(self.auraunit)]) end end end end elseif UnitExists(self.auraunit) then mainframe:UNIT_AURA(self.auraunit) end end -- A SpellFrame is active (i.e. listening to events) iff the talent requirements are met. -- The table EventHorizon.frames.active contains all the active frames. -- If the stance requirement is not met, the frame is hidden, but still active. local SpellFrame_Deactivate = function (self) if not self.isActive then return end --debug('unregistering events for', self.spellname) self:UnregisterAllEvents() if self.interestingCLEU then mainframe.framebyspell[self.spellname] = nil end self:Hide() for index=#self.indicators,1,-1 do self:Remove(index) end self.isActive = nil end local SpellFrame_Activate = function (self) if self.isActive then return end --debug('registering events for', self.spellname) for event in pairs(self.interestingEvent) do self:RegisterEvent(event) end if self.interestingCLEU then mainframe.framebyspell[self.spellname] = self end self:Show() self.isActive = true end local timer = 0 local checkInProgress function ns:CheckTalents() if checkInProgress then return end checkInProgress = true frame3:SetScript('OnUpdate', function (f,elapsed) timer = timer + elapsed if timer >= 2 then timer = 0 checkInProgress = nil ns:CheckRequirements() f:SetScript('OnUpdate',nil) end end) end function ns:SetFrameDimensions() local left,right,top,bottom = 0.07, 0.93, 0.07, 0.93 local barheight2 = self.config.height local modHeight = self.config.height local sfn = type(self.config.staticframes) == 'number' and self.config.staticframes or 0 local sfi = self.config.hideIcons == true if (#ns.frames.shown >= sfn) and type(self.config.staticheight) == 'number' then mainframe:SetHeight(self.config.staticheight) vars.barheight = (self.config.staticheight - (vars.barspacing*(vars.numframes - 1)))/vars.numframes modHeight = vars.barheight local ratio = vars.barheight/barheight2 ratio = math.abs( (1-(1/ratio))/2 ) -- Yes, this was a bitch to figure out. if vars.barheight > barheight2 then -- icon is taller than it is wide left = left + ratio right = right - ratio else top = top + ratio bottom = bottom - ratio end else vars.barheight = barheight2 mainframe:SetHeight(vars.numframes * (vars.barheight+vars.barspacing) - vars.barspacing) end vars.nowleft = -vars.past/(vars.future-vars.past)*vars.barwidth-0.5 + (ns.config.hideIcons and 0 or ns.config.height) if ns.frames.nowIndicator then ns.frames.nowIndicator:SetPoint('BOTTOM',mainframe,'BOTTOM') ns.frames.nowIndicator:SetPoint('TOPLEFT',mainframe,'TOPLEFT', vars.nowleft, 0) ns.frames.nowIndicator:SetWidth(vars.onepixelwide) ns.frames.nowIndicator:SetTexture(unpack(self.colors.nowLine)) end local shownframes = #ns.frames.shown > 0 if shownframes then for i = 1,#ns.frames.shown do local spellframe = ns.frames.shown[i] if spellframe then --spellframe:ClearAllPoints() spellframe:SetHeight(vars.barheight) spellframe:SetWidth(vars.barwidth) spellframe.icon:ClearAllPoints() spellframe:SetPoint('RIGHT', mainframe, 'RIGHT') if i == 1 then spellframe:SetPoint('TOPLEFT', mainframe, 'TOPLEFT', sfi and 0 or barheight2, 0) else spellframe:SetPoint('TOPLEFT', ns.frames.shown[i-1], 'BOTTOMLEFT', 0, -vars.barspacing) end if not(sfi) then spellframe.icon:SetPoint('TOPRIGHT',spellframe,'TOPLEFT') spellframe.icon:SetWidth(barheight2) spellframe.icon:SetHeight(modHeight) spellframe.icon:SetTexCoord(left,right,top,bottom) end local indicators = #spellframe.indicators > 0 if indicators then for i=1,#spellframe.indicators do local indicator = spellframe.indicators[i] if indicator then local ndtex, ndcol local typeid = indicator.typeid local layoutid = indicator.layoutid local usetexture if not(exemptColors[typeid]) and self.bartexture then ndtex = self.bartexture end if typeid and customColors[typeid] then usetexture = true if spellframe.barcolorunique and typeid == 'debuff' then ndcol = spellframe.barcolorunique elseif spellframe.barcolor then ndcol = spellframe.barcolor end elseif typeid == 'cooldown' then usetexture = true end usetexture = usetexture and vars.texturedbars -- Layout local layouts = ns.layouts local layout = layouts[layoutid] or layouts.default local color = ndcol or ns.colors[typeid] or ns.colors.default if layoutid == 'frameline' then color = typeid == 'sent' and ns.colors.castLine or ns.colors[typeid] indicator:SetPoint('TOP',ns.mainframe) indicator:SetPoint('BOTTOM',ns.mainframe) else indicator:SetPoint('TOP',spellframe, 'TOP', 0, -layout.top*vars.barheight) indicator:SetPoint('BOTTOM',spellframe, 'TOP', 0, -layout.bottom*vars.barheight) end if usetexture then indicator:SetTexture(ndtex or vars.bartexture) indicator:SetTexCoord(unpack(layout.texcoords)) else indicator:SetTexture(1,1,1,1) end indicator:SetVertexColor(unpack(ndcol or color)) --if typeid == 'casting' then print(unpack(ndcol or color)) end end end end end end end end --[[ function ns:AddCheckedTalent(tab,index) local required = true for k,v in ipairs(self.talents) do if (v[1] == tab) and (v[2] == index) then required = nil end end if required then table.insert(self.talents,{tab,index}) end end ]] function ns:CheckRequirements() --print('CheckTalents') table.wipe(self.frames.active) table.wipe(self.frames.mouseover) --print('checkrequirements') --print(GetTime()) EventHorizonDB.charInfo = EventHorizonDB.charInfo or {} local cc = EventHorizonDB.charInfo vars.activeTree = GetSpecialization() vars.activeTalentGroup = GetActiveSpecGroup('player') vars.currentLevel = UnitLevel('player') self.glyphs = {} for i = 1,6 do local enabled,_,gsID,_ = GetGlyphSocketInfo(i) if enabled and gsID then self.glyphs[i] = gsID else self.glyphs[i] = nil end end for i,config in ipairs(self.frames.config) do local rG = config.requiredGlyph local rS = config.requiredTree local rL = config.requiredLevel or 1 local haveGlyphReq = true local haveSpecReq = true local haveLevelReq = rL <= vars.currentLevel if rG then haveGlyphReq = nil for i,glyph in ipairs(self.glyphs) do if rG == glyph then haveGlyphReq = true end end end if rS then haveSpecReq = nil if type(rS) == 'number' then rS = {rS} end --print(config.spellID,rS) for i,spec in ipairs(rS) do if spec == vars.activeTree then haveSpecReq = true end end end -- Check if there already is a frame local spellframe = self.frames.frames[i] local frameExists = spellframe~=nil if haveGlyphReq and haveSpecReq and haveLevelReq then if frameExists then spellframe:Activate() else spellframe = self:CreateSpellBar(config) self.frames.frames[i] = spellframe end table.insert(self.frames.active, spellframe) if spellframe.usemouseover then table.insert(self.frames.mouseover, spellframe) end if spellframe.cooldownTable then -- We need to update the spellID again spellframe.cooldownID = IsUsableSpell(config.cooldown[2]) and config.cooldown[2] or config.cooldown[1] spellframe.cooldown = true end else if frameExists then spellframe:Deactivate() end end end local activate = #self.frames.active > 0 self:Activate(activate) if activate then mainframe:UPDATE_SHAPESHIFT_FORM() end ns:ModuleEvent('CheckTalents') end local mainframe_UPDATE_SHAPESHIFT_FORM = function (self) local stance = GetShapeshiftForm() -- On PLAYER_LOGIN, GetShapeshiftForm() sometimes returns a bogus value (2 on a priest with 1 form). Ignored for Warlocks and cached information. if not(stance) or (GetNumShapeshiftForms() and class ~= 'WARLOCK' and stance>GetNumShapeshiftForms()) then return end mainframe:SetHeight(1) table.wipe(ns.frames.shown) EventHorizonDB.charInfo.stance = stance vars.numframes = 0 for i,spellframe in ipairs(ns.frames.active) do local shown = spellframe:IsShown() if spellframe.stance then shown = false if type(spellframe.stance) == 'table' then shown = false for i in ipairs(spellframe.stance) do if spellframe.stance[i] == stance then shown = true end end elseif spellframe.stance == true then shown = true elseif spellframe.stance == stance and not shown then shown = true elseif spellframe.stance and spellframe.stance ~= stance and shown then shown = false end end if spellframe.notstance then shown = true if spellframe.notstance and type(spellframe.notstance) == 'table' then for i in ipairs(spellframe.notstance) do if spellframe.notstance[i] == stance then shown = false end end elseif spellframe.notstance == stance then shown = false end end if shown then spellframe:Show() vars.numframes = vars.numframes+1 table.insert(ns.frames.shown,spellframe) else spellframe:Hide() for i,indicator in ipairs(spellframe.indicators) do indicator:Hide() end end end if vars.numframes>0 then ns:SetFrameDimensions() if (EventHorizonDB.redshift) and (ns.modules.redshift.isReady and EventHorizonDB.redshift.isActive == true) then ns.modules.redshift:Check() elseif ns.isActive and vars.visibleFrame then mainframe:Show() end else mainframe:Hide() end return true end function ns:CheckMouseover() for i,spellframe in ipairs(self.frames.mouseover) do spellframe:UPDATE_MOUSEOVER_UNIT() end end -- GCD indicator local mainframe_SPELL_UPDATE_COOLDOWN = function (self) if ns.frames.gcd then local start, duration = GetSpellCooldown(vars.gcdSpellName) local sfi = ns.config.hideIcons --print(start,duration) if start and duration and duration>0 then vars.gcdend = start+duration mainframe:SetScript('OnUpdate', function (self, elapsed) if vars.gcdend then local now = GetTime() if vars.gcdend<=now then vars.gcdend = nil ns.frames.gcd:Hide() else local diff = now+vars.past local p = (vars.gcdend-diff)*vars.scale if p<=1 then ns.frames.gcd:SetPoint('RIGHT', self, 'RIGHT',(p-1)*vars.barwidth+vars.onepixelwide, 0) ns.frames.gcd:Show() end end end end) else vars.gcdend = nil ns.frames.gcd:Hide() mainframe:SetScript('OnUpdate', nil) end end end -- Dispatch the CLEU. local mainframe_COMBAT_LOG_EVENT_UNFILTERED = function (self,time, event, hideCaster, srcguid,srcname,srcflags, destguid,destname,destflags, spellid,spellname) if srcguid~=vars.playerguid or event:sub(1,5)~='SPELL' then return end local spellframe = self.framebyspell[spellname] if ns.otherIDs[spellname] then mainframe:CLEU_OtherInterestingSpell(time, event, hideCaster, srcguid,srcname,srcflags, destguid,destname,destflags, spellid,spellname) end if spellframe then if spellframe.interestingCLEU[event] then spellframe:COMBAT_LOG_EVENT_UNFILTERED(time, event, hideCaster, srcguid,srcname,srcflags, destguid,destname,destflags, spellid,spellname) end end end function ns:LoadClassModule() local class = select(2,UnitClass('player')) class = class:sub(1,1)..class:sub(2):lower() -- 'WARLOCK' -> 'Warlock' local name, _, _, enabled, loadable = GetAddOnInfo('EventHorizon_'..class) DisableAddOn('EventHorizon_Redshift') DisableAddOn('EventHorizon_Lines') if not enabled or not loadable then return end local loaded, reason = LoadAddOn(name) if loaded and self.InitializeClass then return true end end --[[ spellid: number, rank doesn't matter abbrev: string config: table { cast = , channeled = , numhits = , cooldown = , debuff = , dot = , refreshable = , } --]] function ns:NewSpell(config) local spellid = config.spellID or config.itemID or config.slotID if type(spellid)~='number' then return end table.insert(self.frames.config, config) end --Set spellframe attributes separately from bar creation. Helps keep things tidy and all, y'know? local function SetSpellAttributes(spellframe,config) local interestingEvent = {} local interestingCLEU = {} local config = config local otherids = ns.otherIDs local spellname = spellframe.spellname interestingEvent['UNIT_SPELLCAST_SENT'] = true if config.itemID or config.slotID then spellframe.cooldown = true -- Not getting out of this one. It's an item, what else do you watch? spellframe.cooldownID = config.itemID spellframe.CooldownFunction = GetItemCooldown interestingEvent['SPELL_UPDATE_COOLDOWN'] = true interestingEvent['BAG_UPDATE_COOLDOWN'] = true if config.slotID then config.playerbuff = true config.internalcooldown = true -- Failsafe interestingEvent['PLAYER_EQUIPMENT_CHANGED'] = true end end if config.cast or config.channeled then spellframe.cast = {} if config.channeled then -- Register for the CLEU tick event. interestingCLEU.SPELL_DAMAGE = true -- Register event functions interestingEvent['UNIT_SPELLCAST_CHANNEL_START'] = true interestingEvent['UNIT_SPELLCAST_CHANNEL_STOP'] = true interestingEvent['UNIT_SPELLCAST_CHANNEL_UPDATE'] = true local tcc = type(config.channeled) if config.channeled == true then -- defaults spellframe.cast[spellname] = { numhits = config.numhits or true, -- use numhits as an indicator that the cast is channeled and not to change smalldebuff, or something. func = UnitChannelInfo, id = config.spellID, } elseif tcc == 'number' then local sn = GetSpellInfo(config.channeled) spellframe.cast[sn] = { numhits = config.numhits or true, -- use numhits as an indicator that the cast is channeled and not to change smalldebuff, or something. func = UnitChannelInfo, id = config.channeled, } otherids[sn] = {isChannel = true} elseif tcc == 'table' then local channel = type(config.channeled[1]) == 'table' and config.channeled or {config.channeled} for _,v in ipairs(channel) do local sn = GetSpellInfo(v[1]) spellframe.cast[sn] = { numhits = v[2] or config.numhits or true, func = UnitChannelInfo, id = v[1], } otherids[sn] = {isChannel = true} end end end if config.cast then interestingEvent['UNIT_SPELLCAST_START'] = true interestingEvent['UNIT_SPELLCAST_STOP'] = true interestingEvent['UNIT_SPELLCAST_DELAYED'] = true spellframe.useSmalldebuff = config.recast if ((config.debuff or config.playerbuff) and type(config.debuff or config.playerbuff) == 'boolean') then spellframe.useSmalldebuff = true end if config.cast == true then spellframe.cast[spellname] = { func = UnitCastingInfo, id = config.spellID } else config.cast = type(config.cast) == 'table' and config.cast or {config.cast} for _,id in ipairs(config.cast) do spellframe.cast[GetSpellInfo(id)] = { func = UnitCastingInfo, id = id } end end end end if config.cooldown then if type(config.cooldown) == 'number' then spellframe.cooldownID = config.cooldown spellframe.cooldown = true elseif type(config.cooldown) == 'table' then -- If the second spellID entered is actually usable, then use that otherwise use the other spellframe.cooldownID = IsUsableSpell(config.cooldown[2]) and config.cooldown[2] or config.cooldown[1] spellframe.cooldownTable = true spellframe.cooldown = true end spellframe.CooldownFunction = GetSpellCooldown interestingEvent['SPELL_UPDATE_COOLDOWN'] = true end if config.debuff then spellframe.isType = 'debuffmine' spellframe.AuraFunction = UnitDebuff spellframe.auraunit = config.auraunit or 'target' vars.debuff[spellframe.auraunit] = {} if spellframe.auraunit == 'mouseover' then interestingEvent['UPDATE_MOUSEOVER_UNIT'] = true spellframe.usemouseover = true spellframe.baseunit = config.baseunit or 'target' end local tcd = type(config.debuff) if tcd == 'number' then spellframe.auraname = (GetSpellInfo(config.debuff)) elseif tcd == 'table' then spellframe.auranamePrimary = (GetSpellInfo(config.spellID)) spellframe.auraname = {} for i,id in ipairs(config.debuff) do tinsert(spellframe.auraname, (GetSpellInfo(id))) end spellframe.AuraFunction = UnitDebuffUnique else spellframe.auraname = spellname end -- interestingEvent['UNIT_AURA'] = true -- interestingEvent['PLAYER_TARGET_CHANGED'] = true if config.dot then spellframe.dot = config.dot interestingCLEU.SPELL_PERIODIC_DAMAGE = true spellframe.AddTicks = AddTicks.stop if config.refreshable then spellframe.refreshable = true spellframe.UNIT_AURA = SpellFrame.UNIT_AURA_refreshable -- spellframe.PLAYER_TARGET_CHANGED = SpellFrame.PLAYER_TARGET_CHANGED_refreshable interestingEvent['PLAYER_REGEN_ENABLED'] = true interestingCLEU.SPELL_CAST_SUCCESS=true spellframe.debuffs = {} spellframe.castsuccess = {} end end elseif config.playerbuff then spellframe.isType = 'playerbuff' spellframe.AuraFunction = UnitBuff spellframe.auraunit = config.auraunit or 'player' vars.buff[spellframe.auraunit] = {} if config.auraunit then -- interestingEvent['PLAYER_TARGET_CHANGED'] = true end if spellframe.auraunit == 'mouseover' then interestingEvent['UPDATE_MOUSEOVER_UNIT'] = true spellframe.usemouseover = true spellframe.baseunit = config.baseunit or 'target' end spellframe.alwaysrefresh = true -- interestingEvent['UNIT_AURA'] = true local tcp = type(config.playerbuff) if tcp == 'number' then spellframe.auraname = (GetSpellInfo(config.playerbuff)) elseif tcp == 'table' then spellframe.auraname = {} spellframe.auranamePrimary = (GetSpellInfo(config.spellID)) for i,id in ipairs(config.playerbuff) do tinsert(spellframe.auraname, (GetSpellInfo(id))) end spellframe.AuraFunction = UnitBuffUnique else spellframe.auraname = spellname end if config.dot then -- Register for periodic effect intervals. spellframe.dot = config.dot spellframe.AddTicks = AddTicks.stop interestingCLEU.SPELL_PERIODIC_HEAL = true if config.refreshable then spellframe.refreshable = true spellframe.UNIT_AURA = SpellFrame.UNIT_AURA_refreshable -- spellframe.PLAYER_TARGET_CHANGED = SpellFrame.PLAYER_TARGET_CHANGED_refreshable interestingEvent['PLAYER_REGEN_ENABLED'] = true interestingCLEU.SPELL_CAST_SUCCESS=true spellframe.debuffs = {} spellframe.castsuccess = {} end end end if config.cleu or config.event then -- Register custom CLEU events. if config.event then -- Optional alias for the forgetful. config.cleu = config.event end local cleu = type(config.cleu) if cleu == 'string' then -- Single event interestingCLEU[config.cleu] = true elseif cleu == 'table' then for i in pairs(config.cleu) do -- Multiple events interestingCLEU[ config.cleu[i] ] = true end end end if type(config.glyphrefresh) == 'table' then spellframe.glyphrefresh = config.glyphrefresh spellframe.glyphstacks = {} if not otherids[config.glyphrefresh[3]] then otherids[config.glyphrefresh[3]] = {} end otherids[config.glyphrefresh[3]][spellname] = true otherids[config.glyphrefresh[3]].isGlyph = true interestingCLEU.SPELL_AURA_REMOVED = true end spellframe.hasted = config.hasted spellframe.minstacks = config.minstacks spellframe.stance = config.stance spellframe.notstance = config.notstance spellframe.internalcooldown = config.internalcooldown spellframe.bartexture = config.bartexture spellframe.barcolor = config.barcolor spellframe.barcolorunique = config.barcolorunique spellframe.unique = config.unique spellframe.uniqueID = config.uniqueID spellframe.keepIcon = config.keepIcon spellframe.smallCooldown = config.smallCooldown spellframe.interestingCLEU = interestingCLEU return interestingEvent end function ns:CreateSpellBar(config) local spellid = config.spellID local invid = config.itemID local csid = config.slotID local slotname, spellname, tex, _ local spellframe = CreateFrame('Frame', nil, mainframe) mainframe.numframes = mainframe.numframes+1 if spellid then spellname, _, tex = GetSpellInfo(spellid) elseif invid then spellname,_,_,_,_,_,_,_,_,tex,_ = GetItemInfo(invid) elseif csid then slotname = GetSlotName(csid) spellframe.slotID = csid spellframe.slotName = slotname local itemID = GetInventoryItemID('player',csid) if itemID then spellname,_,_,_,_,_,_,_,_,tex,_ = GetItemInfo(itemID) else spellname = slotName tex = nil end end local basename = slotname or spellname --debug('creating frame for ',spellname) spellframe.spellname = spellname -- Create the bar. spellframe.indicators = {} if ns.config.barbg then spellframe:SetBackdrop{bgFile = vars.bartexture} spellframe:SetBackdropColor(unpack(ns.colors.barbgcolor)) end -- Create and set the spell icon. if config.icon then local t = type(config.icon) if t == 'number' then if spellid then _,_,tex = GetSpellInfo(config.icon) elseif invid then tex = select(10,GetItemInfo(config.icon)) end elseif t == 'string' then tex = config.icon end config.keepIcon = true end spellframe.icon = spellframe:CreateTexture(nil, 'BORDER') spellframe.icon:SetTexture(tex) spellframe.stacks = spellframe:CreateFontString(nil, 'OVERLAY') if vars.stackFont then spellframe.stacks:SetFont(vars.stackFont,vars.stackFontSize) if vars.stackFontShadow then spellframe.stacks:SetShadowColor(unpack(vars.stackFontShadow)) spellframe.stacks:SetShadowOffset(unpack(vars.stackFontShadowOffset)) end else spellframe.stacks:SetFontObject('NumberFontNormalSmall') end spellframe.stacks:SetVertexColor(unpack(vars.stackFontColor)) for k,v in pairs(SpellFrame) do if not spellframe[k] then spellframe[k] = v end end spellframe.interestingEvent = SetSpellAttributes(spellframe,config) spellframe:SetScript('OnEvent', EventHandler) spellframe:SetScript('OnUpdate', spellframe.OnUpdate) local sfi = self.config.hideIcons local sor = self.config.stackOnRight -- Layout spellframe.stacks:SetPoint((sfi and not(sor)) and 'BOTTOMLEFT' or 'BOTTOMRIGHT', (sfi or sor) and spellframe or spellframe.icon, (sfi and not(sor)) and 'BOTTOMLEFT' or 'BOTTOMRIGHT') spellframe.stacks:SetJustifyH(sor and 'LEFT' or 'RIGHT') spellframe:Activate() if config.slotID then spellframe:PLAYER_EQUIPMENT_CHANGED(config.slotID) -- Initialize trinkets and such if needed. end return spellframe end function ns:LoadModules() for i,module in pairs(self.modules) do if not EventHorizonDB[i] then EventHorizonDB[i] = { isActive = true } end if (EventHorizonDB[i] and EventHorizonDB[i].isActive == true) or module.alwaysLoad then if not(self.modules[i].Enable) then self.modules[i].Enable = function () end end if not(self.modules[i].Disable) then self.modules[i].Disable = function () end end self.modules[i]:Init() self.modules[i]:Enable() vars.modulesLoaded = true end end end function ns:ActivateModule(module,slash) if EventHorizonDB[module].isActive ~= true then self.modules[module].isActive = true EventHorizonDB[module].isActive = true if not(self.modules[module].isReady == true) then self.modules[module].Init() end self.modules[module].Enable(slash) end end function ns:DeactivateModule(module,slash) if EventHorizonDB[module].isActive == true then self.modules[module].isActive = false EventHorizonDB[module].isActive = false if not(self.modules[module].isReady == true) then self.modules[module].Init() end self.modules[module].Disable(slash) end end function ns:ToggleModule(module,slash) if EventHorizonDB[module].isActive ~= true then self:ActivateModule(module,slash) else self:DeactivateModule(module,slash) end end -- External event handler for modules, same rules as EH's event handler (passes self, extra args, event is presumed known) function ns:ModuleEvent(event, ...) for i,module in pairs(self.modules) do local f = module[event] if f then f(module,...) end end end function ns:Activate(...) local activate = select('#',...) == 0 or ... --debug('Activate',activate, ...) if not activate then return self:Deactivate() end for k,v in pairs(mainframeEvents) do mainframe:RegisterEvent(k) end self.isActive = true vars.visibleFrame = true if (self.modules.redshift.isReady and EventHorizonDB.redshift.isActive == true) then self.modules.redshift:Check() else mainframe:Show() end end function ns:Deactivate() if self.isActive==false then return end mainframe:UnregisterAllEvents() mainframe:RegisterEvent('PLAYER_TALENT_UPDATE') mainframe:Hide() self.isActive = false end function ns:InitDB() if not EventHorizonDB then EventHorizonDB = self.defaultDB end -- print('initdb') local reset -- Upgrade DB. if EventHorizonDB and not EventHorizonDB.version then EventHorizonDB.version = 0 end if EventHorizonDB.version ~= EventHorizon.defaultDB.version then reset = true table.wipe(EventHorizonDB) EventHorizonDB = Clone(ns.defaultDB) end if not EventHorizonDBG then EventHorizonDBG = self.defaultDBG end -- Upgrade DB. if EventHorizonDBG and not EventHorizonDBG.version then EventHorizonDBG.version = 0 end if EventHorizonDBG.version ~= EventHorizon.defaultDBG.version then reset = true table.wipe(EventHorizonDBG) EventHorizonDBG = Clone(ns.defaultDBG) end -- If profile doesn't exist, set it to the default. EventHorizonDBG.profilePerChar[playername] = EventHorizonDBG.profilePerChar[playername] or EventHorizonDBG.defaultProfile local ppc = EventHorizonDBG.profilePerChar[playername] if not EventHorizonDBG.profiles[ppc] then EventHorizonDBG.profiles[ppc] = {} end self.vars.currentProfile = EventHorizonDBG.profiles[ppc] if reset then print('Your savedvariables have been reset due to potential conflicts with older versions.') end self.db = EventHorizonDB self.dbg = EventHorizonDBG end --[[ Should only be called after the DB is loaded and spell and talent information is available. --]] function ns:Initialize() -- Make sure this function is called only once. --self.Initialize = nil --print('initialize') self:InitDB() --debug('GetTalentInfo(1,1)',GetTalentInfo(1,1)) vars.playerguid = UnitGUID('player') -- Create the main and spell frames. mainframe:SetHeight(1) mainframe.numframes = 0 mainframe.framebyspell= {} mainframe:SetScript('OnEvent', EventHandler) mainframe:SetScale(self.config.scale or 1) vars.buff.player = {} if not self:LoadClassModule() then return end self:ApplyConfig() self:InitializeClass() if self.config.showTrinketBars and self.config.showTrinketBars == true then self:NewSpell({slotID = 13}) self:NewSpell({slotID = 14}) end local sfi = self.config.hideIcons mainframe:SetWidth(vars.barwidth + (sfi and 0 or self.config.height)) self:SetupStyleFrame() -- Spawn backdrop frame. -- Create the indicator for the current time. -- Bugfix: When the UI scale is at a very low setting, textures with a width of 1 -- were not visible in some resolutions. local effectiveScale = mainframe:GetEffectiveScale() if effectiveScale then vars.onepixelwide = 1/effectiveScale end --nowI = CreateFrame('Frame',nil,mainframe) --nowI:SetFrameLevel(20) ns.frames.nowIndicator = mainframe:CreateTexture(nil, 'ARTWORK', nil, draworder.nowI) ns.frames.nowIndicator:SetPoint('BOTTOM',mainframe,'BOTTOM') ns.frames.nowIndicator:SetPoint('TOPLEFT',mainframe,'TOPLEFT', vars.nowleft, 0) ns.frames.nowIndicator:SetWidth(vars.onepixelwide) ns.frames.nowIndicator:SetTexture(unpack(self.colors.nowLine)) if self.config.blendModes.nowLine and type(self.config.blendModes.nowLine) == 'string' then ns.frames.nowIndicator:SetBlendMode(self.config.blendModes.nowLine) end local anchor = self.config.anchor or {'TOPRIGHT', 'EventHorizonHandle', 'BOTTOMRIGHT'} if anchor[2]=='EventHorizonHandle' then -- Create the handle to reposition the frame. handle = CreateFrame('Frame', 'EventHorizonHandle', mainframe) handle:SetFrameStrata('HIGH') handle:SetWidth(10) handle:SetHeight(5) handle:EnableMouse(true) handle:SetClampedToScreen(1) handle:RegisterForDrag('LeftButton') handle:SetScript('OnDragStart', function(handle, button) handle:StartMoving() end) handle:SetScript('OnDragStop', function(handle) handle:StopMovingOrSizing() local a,b,c,d,e = handle:GetPoint(1) if type(b)=='frame' then b=b:GetName() end EventHorizonDB.point = {a,b,c,d,e} end) handle:SetMovable(true) mainframe:SetPoint(unpack(anchor)) handle:SetPoint(unpack(EventHorizonDB.point)) handle.tex = handle:CreateTexture(nil, 'ARTWORK', nil, 7) handle.tex:SetAllPoints() handle:SetScript('OnEnter',function(frame) frame.tex:SetTexture(1,1,1,1) end) handle:SetScript('OnLeave',function(frame) frame.tex:SetTexture(1,1,1,0.1) end) handle.tex:SetTexture(1,1,1,0.1) if EventHorizonDB.isLocked then handle:Hide() end end vars.gcdSpellName = self.config.gcdSpellID and (GetSpellInfo(self.config.gcdSpellID)) if vars.gcdSpellName and self.config.gcdStyle then -- Create the GCD indicator, register cooldown event. ns.frames.gcd = mainframe:CreateTexture(nil, 'ARTWORK',nil,draworder.gcd) ns.frames.gcd:SetPoint('BOTTOM',mainframe,'BOTTOM') ns.frames.gcd:SetPoint('TOP',mainframe,'TOP') ns.frames.gcd:Hide() if self.config.gcdStyle == 'line' then ns.frames.gcd:SetWidth(vars.onepixelwide) else ns.frames.gcd:SetPoint('LEFT',mainframe,'LEFT', vars.nowleft, 0) end local gcdColor = self.colors.gcdColor or {.5,.5,.5,.3} ns.frames.gcd:SetTexture(unpack(gcdColor)) if self.config.blendModes.gcdColor and type(self.config.blendModes.gcdColor) == 'string' then ns.frames.gcd:SetBlendMode(self.config.blendModes.gcdColor) end end mainframe:SetPoint(unpack(anchor)) SLASH_EVENTHORIZON1 = '/eventhorizon' SLASH_EVENTHORIZON2 = '/ehz' SlashCmdList['EVENTHORIZON'] = function(msg) local cmd = string.lower(msg) local toggle = not(msg) or cmd == '' if cmd == 'help' then debug'Use "/eventhorizon" or "/ehz" to show or hide EventHorizon.' debug'To enable or disable a module, use "/ehz ModuleName". For example, "/ehz redshift".' debug'To see a list of currently installed modules and visible bars, use "/ehz status".' if anchor[2]=='EventHorizonHandle' then debug(' EventHorizon is currently '..(EventHorizonDB.isLocked and 'locked.' or 'movable.')) debug(' To '..(EventHorizonDB.isLocked and 'unlock ' or 'lock ')..'EventHorizon, use "/ehz lock".') debug' If you are unable see or move EventHorizon, use "/ehz reset".' end elseif cmd == 'status' then print('Installed plugins:') for i in pairs(self.modules) do debug(' '..i) end print('Visible bars:') for i,v in pairs(self.frames.shown) do debug(' '..v.spellname) end elseif cmd == 'reset' then if anchor[2]=='EventHorizonHandle' then debug'Resetting EventHorizon\'s position.' EventHorizonHandle:SetPoint(unpack(self.defaultDB.point)) self:CheckTalents() else print"The frame is otherwise anchored. Adjust config.anchor in [my]config.lua to move EventHorizon." end elseif cmd == 'lock' then if anchor[2]=='EventHorizonHandle' then if EventHorizonHandle:IsShown() then EventHorizonHandle:Hide() EventHorizonDB.isLocked = true else EventHorizonHandle:Show() EventHorizonDB.isLocked = nil end print("The frame is now "..(EventHorizonDB.isLocked and 'locked.' or 'unlocked.')) else print"The frame is otherwise anchored. Adjust config.anchor in [my]config.lua to move EventHorizon." end elseif self.modules[cmd] then self:ToggleModule(string.lower(msg)) print(string.lower(msg)..' has been turned '..((self.modules[string.lower(msg)].isActive == true) and 'ON' or 'OFF')..'.') elseif toggle then if self.isActive then print('Deactivating EventHorizon. Use "/ehz help" to see what else you can do.') self:Deactivate() mainframe:UnregisterEvent('PLAYER_TALENT_UPDATE') else print('Activating EventHorizon. Use "/ehz help" to see what else you can do.') self:Activate() self:CheckTalents() end else print('Invalid command. Use "/ehz" alone to show or hide EventHorizon, or "/ehz help" to see a list of commands.') end EventHorizonDB.isActive = self.isActive end ns.isActive = EventHorizonDB.isActive if not EventHorizonDB.isActive then self:Deactivate() mainframe:UnregisterEvent('PLAYER_TALENT_UPDATE') end self:CheckRequirements() self:LoadModules() if not(ns.config.hastedSpellID and type(ns.config.hastedSpellID) == 'table') then vars.useOldHaste = true end if ns.config.nonAffectingHaste then if type(ns.config.nonAffectingHaste[1]) == 'number' then ns.config.nonAffectingHaste = {ns.config.nonAffectingHaste} end end self.isReady = true end function ns:ApplyConfig() table.wipe(ns.vars.config) local config = {} local ppc = EventHorizonDBG.profilePerChar[playername] -- EventHorizonDBG.profilePerChar[playername] = 'ProfileName' if ppc then config = EventHorizonDBG.profiles[ppc] -- EventHorizon.profiles[ProfileName] = {overriddenConfig} end setmetatable(config,{__index = ns.config}) -- Set non-overridden values to what is in [my]config.lua vars.past = -math.abs(config.past) or -3 -- We really don't want config.past to be positive, so negative absolute values work great here. vars.future = math.abs(config.future) or 9 vars.barheight = config.height or 18 vars.barwidth = config.width or 150 vars.barspacing = config.spacing or 0 vars.scale = 1/(vars.future-vars.past) vars.bartexture = config.bartexture or 'Interface\\Addons\\EventHorizon\\Smooth' vars.texturedbars = config.texturedbars vars.texturealpha = config.texturealphamultiplier or 1 vars.classburn = config.classburn or 0.7 vars.classalpha = config.classalpha or 0.3 vars.castLine = config.castLine and ((type(config.castLine) == 'number' and config.castLine) or config.castLine == true and 0) or nil vars.nowleft = -vars.past/(vars.future-vars.past)*vars.barwidth-0.5 + (config.hideIcons and 0 or config.height) local classcolors = CUSTOM_CLASS_COLORS or RAID_CLASS_COLORS self.classcolor = Clone(classcolors[select(2,UnitClass('player'))]) vars.stackFont = config.stackFont vars.stackFontSize = config.stackFontSize vars.stackFontColor = config.stackFontColor == true and {1,1,1,1} or config.stackFontColor or {1,1,1,1} vars.stackFontShadow = config.stackFontShadow == true and {0,0,0,0.5} or config.stackFontShadow or {0,0,0,0.5} vars.stackFontShadowOffset = config.stackFontShadowOffset == true and {1,-1} or config.stackFontShadowOffset or {1,-1} for colorid,color in pairs(self.colors) do if color[1] == true then if color[2] then self.colors[colorid] = {self.classcolor.r * color[2], self.classcolor.g * color[2], self.classcolor.b * color[2], color[3] or vars.classalpha} -- For really bad reasons, this took a very long time to get right... else self.colors[colorid] = {self.classcolor.r, self.classcolor.g, self.classcolor.b, vars.classalpha} end end end if vars.texturedbars then for colorid,color in pairs(self.colors) do if color[4] and not(exemptColors[colorid]) then color[4] = vars.texturealpha*color[4] end end end local layouts = self.layouts layouts.frameline = { top = 0, bottom = 1, } local default = layouts.default for typeid,layout in pairs(layouts) do if typeid~='default' then for k,v in pairs(default) do if layout[k]==nil then layout[k] = v end end end layout.texcoords = {0, 1, layout.top, layout.bottom} end ns:ModuleEvent('ApplyConfig') end function ns:UpdateConfig() if not(self.isReady) then return end self:ApplyConfig() mainframe:SetScale(self.config.scale or 1) local effectiveScale = mainframe:GetEffectiveScale() if effectiveScale then vars.onepixelwide = 1/effectiveScale end self:SetupStyleFrame() -- Spawn backdrop frame. vars.nowleft = -vars.past/(vars.future-vars.past)*vars.barwidth-0.5 + (ns.config.hideIcons and 0 or ns.config.height) --nowI:SetFrameLevel(20) ns.frames.nowIndicator:SetPoint('BOTTOM',mainframe,'BOTTOM') ns.frames.nowIndicator:SetPoint('TOPLEFT',mainframe,'TOPLEFT', vars.nowleft, 0) ns.frames.nowIndicator:SetTexture(unpack(self.colors.nowLine)) local anchor = self.config.anchor or {'TOPRIGHT', 'EventHorizonHandle', 'BOTTOMRIGHT'} if anchor[2]=='EventHorizonHandle' then -- Create the handle to reposition the frame. handle = handle or CreateFrame('Frame', 'EventHorizonHandle', mainframe) handle:SetFrameStrata('HIGH') handle:SetWidth(10) handle:SetHeight(5) handle:EnableMouse(true) handle:SetClampedToScreen(1) handle:RegisterForDrag('LeftButton') handle:SetScript('OnDragStart', function(handle, button) handle:StartMoving() end) handle:SetScript('OnDragStop', function(handle) handle:StopMovingOrSizing() local a,b,c,d,e = handle:GetPoint(1) if type(b)=='frame' then b=b:GetName() end EventHorizonDB.point = {a,b,c,d,e} end) handle:SetMovable(true) mainframe:SetPoint(unpack(anchor)) handle:SetPoint(unpack(EventHorizonDB.point)) handle.tex = handle:CreateTexture(nil, 'BORDER') handle.tex:SetAllPoints() handle:SetScript('OnEnter',function(frame) frame.tex:SetTexture(1,1,1,1) end) handle:SetScript('OnLeave',function(frame) frame.tex:SetTexture(1,1,1,0.1) end) handle.tex:SetTexture(1,1,1,0.1) end vars.gcdSpellName = self.config.gcdSpellID and (GetSpellInfo(self.config.gcdSpellID)) if vars.gcdSpellName and self.config.gcdStyle then -- Create the GCD indicator, register cooldown event. ns.frames.gcd = mainframe:CreateTexture('EventHorizonns.frames.gcd', 'BORDER') ns.frames.gcd:SetPoint('BOTTOM',mainframe,'BOTTOM') ns.frames.gcd:SetPoint('TOP',mainframe,'TOP') ns.frames.gcd:Hide() if self.config.gcdStyle == 'line' then ns.frames.gcd:SetWidth(vars.onepixelwide) else ns.frames.gcd:SetPoint('LEFT',mainframe,'LEFT', vars.nowleft, 0) end local gcdColor = self.colors.gcdColor or {.5,.5,.5,.3} ns.frames.gcd:SetTexture(unpack(gcdColor)) end mainframe:SetPoint(unpack(anchor)) self:SetFrameDimensions() end local glyphCheck = function () if ns.isActive == true then ns:CheckTalents() for i,spellframe in pairs(ns.frames.shown) do if spellframe.slotID then spellframe:PLAYER_EQUIPMENT_CHANGED(spellframe.slotID) end end end end function ns:SetupStyleFrame() local c = self.config.backdrop if c then if not(self.styleframe) then self.styleframe = CreateFrame('Frame',nil,mainframe) end else if self.styleframe then self.styleframe:Hide() end return end local styleframe = self.styleframe local stylebg = self.config.bg or 'Interface\\ChatFrame\\ChatFrameBackground' local styleborder = self.config.border or 'Interface\\Tooltips\\UI-Tooltip-Border' local stylebgcolor = self.colors.bgcolor or {0,0,0,0.6} local stylebordercolor = self.colors.bordercolor or {1,1,1,1} local styleinset = self.config.inset or {top = 2, bottom = 2, left = 2, right = 2} local stylepadding = self.config.padding or 3 local styleedge = self.config.edgesize or 8 styleframe:SetFrameStrata('BACKGROUND') styleframe:SetPoint('TOPRIGHT', mainframe, 'TOPRIGHT', stylepadding, stylepadding) styleframe:SetPoint('BOTTOMLEFT', mainframe, 'BOTTOMLEFT', -stylepadding, -stylepadding) styleframe:SetBackdrop({ bgFile = stylebg, edgeFile = styleborder, tileSize = 0, edgeSize = styleedge, insets = styleinset, }) styleframe:SetBackdropColor(unpack(stylebgcolor)) styleframe:SetBackdropBorderColor(unpack(stylebordercolor)) end function ns:RegisterModule(module,namespace) if not(module and namespace) then print("Module registration failed. Usage: EventHorizon:RegisterModule(module,namespace)") end local module = string.lower(module) self.modules[module] = namespace end frame:SetScript('OnEvent', EventHandler) frame:RegisterEvent('PLAYER_LOGIN') frame.PLAYER_ALIVE = function (self) self:SetScript('OnUpdate', UpdateMouseover) frame2:SetScript('OnEvent', EventHandler) for k,v in pairs(reloadEvents) do frame2:RegisterEvent(k) frame2[k] = glyphCheck end if not(ns.isReady) then ns:Initialize() else ns:LoadModules() end self:UnregisterEvent('PLAYER_ALIVE') end frame.PLAYER_LOGIN = function (self) local talents = GetTalentInfo(1) if talents then self:UnregisterEvent('PLAYER_LOGIN') self:SetScript('OnUpdate', UpdateMouseover) frame2:SetScript('OnEvent', EventHandler) for k,v in pairs(reloadEvents) do frame2:RegisterEvent(k) frame2[k] = glyphCheck end if not(ns.isReady) then ns:Initialize() else ns:LoadModules() end else self:UnregisterEvent('PLAYER_LOGIN') self:RegisterEvent('PLAYER_ALIVE') end end mainframe.CLEU_OtherInterestingSpell = mainframe_CLEU_OtherInterestingSpell mainframe.UPDATE_SHAPESHIFT_FORM = mainframe_UPDATE_SHAPESHIFT_FORM mainframe.SPELL_UPDATE_COOLDOWN = mainframe_SPELL_UPDATE_COOLDOWN mainframe.COMBAT_LOG_EVENT_UNFILTERED = mainframe_COMBAT_LOG_EVENT_UNFILTERED mainframe.UPDATE_SHAPESHIFT_FORMS = mainframe_UPDATE_SHAPESHIFT_FORM mainframe.PLAYER_TALENT_UPDATE = ns.CheckTalents mainframe.PLAYER_LEVEL_UP = ns.CheckTalents mainframe.PLAYER_TARGET_CHANGED = mainframe_PLAYER_TARGET_CHANGED mainframe.UNIT_AURA = mainframe_UNIT_AURA SpellFrame.NotInteresting = SpellFrame_NotInteresting SpellFrame.AddSegment = SpellFrame_AddSegment SpellFrame.AddIndicator = SpellFrame_AddIndicator SpellFrame.Remove = SpellFrame_Remove SpellFrame.RemoveTicksAfter = SpellFrame_RemoveTicksAfter SpellFrame.RemoveChannelTicksAfter = SpellFrame_RemoveChannelTicksAfter SpellFrame.OnUpdate = SpellFrame_OnUpdate SpellFrame.UpdateDoT = SpellFrame_UpdateDoT SpellFrame.Activate = SpellFrame_Activate SpellFrame.Deactivate = SpellFrame_Deactivate SpellFrame.FindItemInfo = SpellFrame_FindItemInfo SpellFrame.UNIT_AURA = SpellFrame_UNIT_AURA SpellFrame.UNIT_AURA_refreshable = SpellFrame_UNIT_AURA_refreshable SpellFrame.COMBAT_LOG_EVENT_UNFILTERED = SpellFrame_COMBAT_LOG_EVENT_UNFILTERED SpellFrame.UNIT_SPELLCAST_SENT = SpellFrame_UNIT_SPELLCAST_SENT SpellFrame.UNIT_SPELLCAST_CHANNEL_START = Cast_Start SpellFrame.UNIT_SPELLCAST_CHANNEL_UPDATE = Cast_Update SpellFrame.UNIT_SPELLCAST_CHANNEL_STOP = Cast_Stop SpellFrame.UNIT_SPELLCAST_START = Cast_Start SpellFrame.UNIT_SPELLCAST_STOP = Cast_Stop SpellFrame.UNIT_SPELLCAST_DELAYED = Cast_Update --SpellFrame.PLAYER_TARGET_CHANGED = SpellFrame_PLAYER_TARGET_CHANGED --SpellFrame.PLAYER_TARGET_CHANGED_refreshable = SpellFrame_PLAYER_TARGET_CHANGED_refreshable SpellFrame.PLAYER_REGEN_ENABLED = SpellFrame_PLAYER_REGEN_ENABLED SpellFrame.SPELL_UPDATE_COOLDOWN = SpellFrame_SPELL_UPDATE_COOLDOWN SpellFrame.BAG_UPDATE_COOLDOWN = SpellFrame_SPELL_UPDATE_COOLDOWN SpellFrame.PLAYER_EQUIPMENT_CHANGED = SpellFrame_PLAYER_EQUIPMENT_CHANGED SpellFrame.UPDATE_MOUSEOVER_UNIT = SpellFrame_UPDATE_MOUSEOVER_UNIT local Redshift = {} Redshift.Check = function (self) if EventHorizonDB.redshift.isActive ~= true then return Redshift:Disable() end if not(Redshift.frame) then Redshift.frame = CreateFrame("FRAME",nil,UIParent) Redshift.frame:SetScript('OnEvent',EventHandler) for k,v in pairs(Redshift.Events) do if v then Redshift.frame:RegisterEvent(k) Redshift.frame[k] = Redshift.Check end end end local s = Redshift.states showState = nil local attackable = UnitCanAttack("player","target") local targeting = UnitExists("target") local focusing = UnitExists("focus") local classify = UnitClassification("target") local dead = UnitIsDeadOrGhost("target") local vehicle = UnitHasVehicleUI("player") if(s.showCombat and UnitAffectingCombat("player")) then showState = true end if (s.showFocus and UnitExists("focus")) then showState = true end if targeting then if(s.showHelp and not attackable) and not dead then showState = true end if(s.showHarm and attackable) and not dead then showState = true end if(s.showBoss and classify == "worldboss") and not dead then showState = true end end if (s.hideVehicle and UnitHasVehicleUI("player")) then showState = nil end if showState then vars.visibleFrame = true mainframe:Show() if EventHorizon_VitalsFrame and s.hideVitals then EventHorizon_VitalsFrame:Show() end else vars.visibleFrame = false mainframe:Hide() if EventHorizon_VitalsFrame and s.hideVitals then EventHorizon_VitalsFrame:Hide() end end end Redshift.Init = function () local settingsChanged = EventHorizonDB.redshift.lastConfig ~= ns.config.enableRedshift EventHorizonDB.redshift.lastConfig = ns.config.enableRedshift if settingsChanged then local ends = ns.config.enableRedshift and 'enabled' or 'disabled' local settingsString = "Redshift has been "..ends.." via config.lua. Ingame settings have been adjusted to match. Use /ehz redshift to enable or disable Redshift as needed." EventHorizonDB.redshift.isActive = ns.config.enableRedshift end local db = EventHorizonDB.redshift.isActive if not (db) then return end Redshift.states = {} Redshift.Events = { ["PLAYER_REGEN_DISABLED"] = true, ["PLAYER_REGEN_ENABLED"] = true, ["PLAYER_TARGET_CHANGED"] = true, ["PLAYER_GAINS_VEHICLE_DATA"] = true, ["PLAYER_LOSES_VEHICLE_DATA"] = true, ["UNIT_ENTERED_VEHICLE"] = true, ["UNIT_EXITED_VEHICLE"] = true, ["UNIT_ENTERING_VEHICLE"] = true, ["UNIT_EXITING_VEHICLE"] = true, ["VEHICLE_PASSENGERS_CHANGED"] = true, } for k,v in pairs(ns.config.Redshift) do if v then Redshift.states[k] = true end end if (EventHorizonDB.redshift.isActive == true) then Redshift:Check() end Redshift.isReady = true end Redshift.Enable = function (slash) if EventHorizonDB.redshift.isActive and not(Redshift.isReady) then Redshift:Init() Redshift:Check() elseif EventHorizonDB.redshift.isActive then Redshift:Check() end if Redshift.frame then Redshift.frame:SetScript('OnEvent',EventHandler) end end Redshift.Disable = function (slash) vars.visibleFrame = true if Redshift.frame then Redshift.frame:SetScript('OnEvent',nil) end if ns.isActive == true then mainframe:Show() end end local Lines = {} Lines.CreateLines = function () if Lines.frame then return end local c = ns.config.Lines local db = EventHorizonDB.lines.isActive == true if not(c and db) then return elseif type(c) == 'number' then c = {c} -- Turn numbers into delicious tables. elseif type(c) ~= 'table' then return -- Turn away everything else. end Lines.frame = CreateFrame('Frame',nil,UIParent) Lines.line = {} local multicolor local color = ns.config.LinesColor if color and type(color) == 'table' then if type(color[1]) == 'table' then multicolor = true -- trying not to further complicate things for i,v in ipairs(c) do if not(color[i]) then color[i] = color[i-1] -- if we have more lines than colors, we need moar colors end end end else color = {1,1,1,0.5} end local now = -vars.past/(vars.future-vars.past)*vars.barwidth-0.5 + vars.barheight local pps = (vars.barwidth+vars.barheight-now)/vars.future for i = 1, #c do local seconds = c[i] local position = now+(pps*seconds) Lines.line[i] = mainframe:CreateTexture(nil,"OVERLAY") Lines.line[i]:SetPoint('TOPLEFT', mainframe, 'TOPLEFT', position, 0) if multicolor then Lines.line[i]:SetTexture(unpack(color[i])) else Lines.line[i]:SetTexture(unpack(color)) end Lines.line[i]:SetWidth(vars.onepixelwide) Lines.line[i]:SetPoint('BOTTOM', mainframe, 'BOTTOM') end Lines.Enable = function () for i = 1,#Lines.line do Lines.line[i]:Show() end end Lines.Disable = function () for i = 1,#Lines.line do Lines.line[i]:Hide() end end end Lines.Init = function () Lines.CreateLines() Lines.isReady = true end --[[ local Pulse = { cache = {}, -- subframe storage frames = {}, -- framerefs alwaysLoad = true, -- Flag EH to load the module even when db.isActive ~= true } Pulse.Enable = function () local cv = ns.config.Pulse local sv = ns.db.pulse local int = ns.config.PulseIntensity local fps = ns.config.PulseFPS if not(fps) or (type(fps) ~= 'number' or fps == 0) then fps = ns.defaultconfig.PulseFPS end if not Pulse.frame then Pulse.frame = CreateFrame('Frame',nil,mainframe) end Pulse.duration = (cv and sv.isActive) and ((cv == true or type(cv) ~= 'number') and ns.defaultconfig.Pulse or type(cv) == 'number' and cv) Pulse.intensity = int and (type(int) == 'number' and int > 0 and int) or ns.defaultconfig.PulseIntensity Pulse.TTL = (1000/fps)/1000 if Pulse.duration and not(Pulse.framesCreated) then local ehf = EventHorizon.frames.frames -- for k,v in pairs(ns.frames.frames) do print(k,v) end for k,bar in pairs(ns.frames.frames) do -- Use frametable since we want actual bar refs. Don't use ipairs here (will need to fix in other places, likely), as it won't fully iterate. -- local bar = ehf[i] if bar and bar.cooldown then local temp = { spellframe = bar, ['SPELL_UPDATE_COOLDOWN'] = true, } table.insert(Pulse.frames,temp) elseif bar and (bar.slotID or bar.internalcooldown) then -- Check for slotID first, since it doesn't always use internalcooldown local temp = { spellframe = bar, ['UNIT_AURA'] = true, } table.insert(Pulse.frames,temp) end end Pulse.framesCreated = true end Pulse.frame:SetScript('OnEvent',Pulse.OnEvent) Pulse.frame:RegisterEvent('UNIT_AURA') Pulse.frame:RegisterEvent('SPELL_UPDATE_COOLDOWN') end Pulse.Disable = function () Pulse.duration = nil end Pulse.OnFlash = function (self,elapsed) self.TSLU = self.TSLU + elapsed while self.TSLU >= Pulse.TTL do self.current = self.current and (self.current - self.TSLU) or Pulse.duration self.alpha = self.current >= 0 and self.current/(Pulse.duration*Pulse.intensity) or 0 self.tex:SetAlpha(self.alpha) self.TSLU = self.TSLU - Pulse.TTL if self.current <= 0 then self:SetScript('OnUpdate',nil) self.current = nil self.alpha = nil print('clearing onupdates') end end end Pulse.OnAura = function (self,elapsed) self.remaining = self.remaining - elapsed if self.current and self.alpha then -- finish off any remaining pulse self.TSLU = self.TSLU + elapsed while self.TSLU >= Pulse.TTL do self.current = self.current and (self.current - self.TSLU) or Pulse.duration self.alpha = self.current >= 0 and self.current/(Pulse.duration*Pulse.intensity) or 0 self.tex:SetAlpha(self.alpha) self.TSLU = self.TSLU - Pulse.TTL if self.current <= 0 then self.current = nil self.alpha = nil end end end if self.remaining <= 0 and Pulse.duration then self.activeCD = nil self.remaining = nil self.TSLU = 0 self:SetAllPoints(self.parent) self.tex:SetAllPoints(self) print('setting Pulse.OnFlash') self:SetScript('OnUpdate',Pulse.OnFlash) elseif self.remaining <= 0 then self.activeCD = nil self.remaining = nil self:SetScript('OnUpdate',nil) end end Pulse.OnEvent = function (self,event,unit) if event == 'UNIT_AURA' and unit ~= 'player' then return end for i = 1,#Pulse.frames do local frame = Pulse.frames[i] local f = Pulse.frames[i].spellframe if f then local icd = f.internalcooldown local cdTime local apply local now = GetTime() if icd and icd == true then icd = nil end if icd and event == 'UNIT_AURA' then local _,_,_,_,_,_,expirationTime,active = f.AuraFunction('player',f.auraname) local activeCD = frame.activeCD if active and expirationTime and (frame.flash and not(frame.flash.activeCD) or not(frame.flash)) then apply = true frame.activeCD = true cdTime = f.internalcooldown local start = expirationTime local stop = now + f.internalcooldown if start > stop then start = now end f:AddSegment('cooldown', 'cooldown', start, stop) end elseif event == 'SPELL_UPDATE_COOLDOWN' and not(icd) then local start, duration, enabled = f.CooldownFunction(f.cooldownID or f.spellname) local onCooldown = enabled == 1 and start ~= duration and start > 0 and duration > 1.5 -- Check duration against GCD to ensure we're not pulsing every time the char does something cdTime = duration if onCooldown and not(frame.activeCD) then apply = true print(onCooldown,frame.activeCD,start,duration,enabled) elseif (frame.flash and frame.flash.remaining) and not(onCooldown) then -- cooldown expired before pulse went off frame.flash.remaining = 0 -- flash at next opportunity --print'flash' elseif (frame.flash and frame.flash.remaining) and (frame.flash.remaining > (start+duration-now+1)) then -- HOPEFULLY this will catch elemental shaman 2t10. frame.flash.remaining = start+duration-now end frame.activeCD = onCooldown end if apply and cdTime and Pulse.duration then -- check for fresh application, make sure a cooldown time exists, and don't do anything if pulses are disabled if not(frame.flash) then frame.flash = CreateFrame('Frame',nil,f) frame.flash.tex = frame.flash:CreateTexture(nil,'BACKGROUND') frame.flash.tex:SetTexture(vars.texturedbars and vars.bartexture or unpack{1,1,1,1}) frame.flash.tex:SetAlpha(0) frame.flash.TSLU = 0 end frame.flash.parent = f frame.flash.TSLU = (frame.flash.current and frame.flash.alpha) and frame.flash.TSLU or 0 frame.flash.remaining = cdTime frame.flash.activeCD = true frame.flash:SetScript('OnUpdate',Pulse.OnAura) end end end end Pulse.Init = function () if Pulse.frame then return end Pulse:Enable() Pulse.isReady = true end ]]-- ns:RegisterModule('lines',Lines) ns:RegisterModule('redshift',Redshift) --ns:RegisterModule('pulse',Pulse)