local function tern(q,x,y) if q then return x; end return y; end --so i don't have to do this everytime i use the x or y for assigning default booleans local function dor(x,d) if x ~= nil then return x; end return d; end local function deepcopy(t) local d = {}; for k,v in pairs(t) do if type(v) == 'table' then d[k] = deepcopy(v); else d[k] = v; end end return d; end --bummed from ckknight's pitbull, with his permission: local new, del, newHash; do local list = setmetatable({}, {__mode='k'}) function new(...) local t = next(list) if t then list[t] = nil for i = 1, select('#', ...) do t[i] = select(i, ...) end return t else return { ... } end end function del(t) for k in pairs(t) do t[k] = nil end list[t] = true return nil end end --end sleazy code borrowing local tables = {}; local db; local unitbuff = UnitBuff; local unitdebuff = UnitDebuff; local select = select; local L = AceLibrary("AceLocale-2.2"):new("SorrenTimers"); local dd = AceLibrary("Dewdrop-2.0"); local sm = LibStub("LibSharedMedia-3.0"); ---local BS = AceLibrary("Babble-Spell-2.2"); local wf = AceLibrary("Waterfall-1.0"); local pc = AceLibrary("PaintChips-2.0"); local empty = {}; --useful local options = { type = 'group', args = { config = { type = 'execute', name = L["Config"], desc = L["Bring up the config window"], func = "OpenOptions", }, }, }; SorrenTimers = AceLibrary("AceAddon-2.0"):new("AceConsole-2.0", "AceEvent-2.0", "AceDB-2.0", "FuBarPlugin-2.0", "CandyBar-2.0", "AceDebug-2.0", "AceComm-2.0"); SorrenTimers:RegisterChatCommand({"/sorrentimers", "/st"}, options); SorrenTimers:RegisterDB( "SorrenTimersDB", "SorrenTimersDBPC" ); SorrenTimers:RegisterDefaults( "char", { targetonly = true, resettime = 10, defaults = { fade = 0, scale = 1, alpha = 1, colors = { "blue", "cyan", "green" }, buffcolors = { "blue", "cyan", "green" }, debuffcolors = { "red", "orange", "yellow" }, diminishcolors = { "red", "red", "red" }, width = 200, space = 0, height = 16, texture = "BantoBar", up = false, targetstring = "$name $s - $target", skillstring = "$name $s", diminishstring = "($n) $name - $target", }, groups = { defaultgroup = {}, groupskills = { disabled = true; }, }, custom = {}, customenemy = {}, custombuffs = {}, checknpc = true, defaultskillgroup = "defaultgroup", defaultenemyskillgroup = "defaultgroup", defaultbuffgroup = "defaultgroup", defaultgroupskillsgroup = "groupskills", groupskills = {}, customcolors = {}, } ); SorrenTimers.title = "Sorren's Timers"; SorrenTimers.hasNoColor = true; SorrenTimers.defaultMinimapPosition = 300; SorrenTimers.independantProfile = true; SorrenTimers.cannotDetachTooltip = true; SorrenTimers.hideWithoutStandby = true; SorrenTimers.hasIcon = "Interface\\Icons\\Spell_Shadow_SoulLeech_2"; local my_units = { player = true, pet = true, vehicle = true, } function SorrenTimers:OnInitialize() db = self.db.char; sm.RegisterCallback(SorrenTimers, "LibSharedMedia_Registered", "UpdateSharedMedia"); sm.RegisterCallback(SorrenTimers, "LibSharedMedia_SetGlobal", "UpdateSharedMedia"); sm:Register("statusbar", "BantoBar", "Interface\\AddOns\\SorrenTimers\\Textures\\bar.tga"); sm:Register("statusbar", "Blizzard Default", "Interface\\TargetingFrame\\UI-StatusBar"); sm:Register("statusbar", "Smooth", "Interface\\AddOns\\SorrenTimers\\Textures\\smooth.tga"); sm:Register("statusbar", "Cilo", "Interface\\AddOns\\SorrenTimers\\Textures\\cilo.tga"); sm:Register("statusbar", "Steel", "Interface\\AddOns\\SorrenTimers\\Textures\\steel.tga"); end function SorrenTimers:UpdateSharedMedia() if not self.bars then return end local defaults = db.defaults; local group; for k, v in pairs(self.bars) do group = v[5] or ""; self:SetCandyBarTexture(k, sm:Fetch(sm.MediaType.STATUSBAR, group.texture or defaults.texture)); end end function SorrenTimers:OnEnable(first) --self:SetDebugging(true); --should be off self:Debug("ST Enabled"); self:RegisterEvent("UNIT_AURA"); self:RegisterEvent("UNIT_PET"); self:RegisterEvent("PLAYER_TARGET_CHANGED"); self:RegisterEvent("PLAYER_ALIVE", "InitializeSkills", true); --poll talents self:RegisterEvent("SKILL_LINES_CHANGED", "InitializeSkills", true); self:RegisterEvent("MODIFIER_STATE_CHANGED"); self:RegisterEvent("UPDATE_MOUSEOVER_UNIT"); self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED"); self:RegisterEvent("PLAYER_ENTERING_WORLD", "EnterWorld"); self:RegisterEvent("PLAYER_LEAVING_WORLD"); self:RegisterEvent("PARTY_MEMBERS_CHANGED", "Party"); if db.abilities then self:ConvertToNewOptions(); end self:SetUp(); self.units = {}; self.bars = {}; self.buffs = {}; self.offsets = {}; self:SetCommPrefix("SorrenTimers"); if not self.db.char.groups.groupskills.disabled then self:RegisterComm("SorrenTimers", "GROUP"); end self:SetDefaultCommPriority("ALERT"); self:RegisterMemoizations( { "offset", "new", "kill", "reqoffsets" } ); self.lastsent = 0; for k,v in pairs(db.customcolors) do pc:RegisterColor(k,unpack(v)); end end function SorrenTimers:EnterWorld() if GetNumPartyMembers() > 0 then self:Party(); self:RequestOffsets(); end end function SorrenTimers:PLAYER_LEAVING_WORLD() if not self.bars then return end for k,v in pairs(self.bars) do self:StopBar(k); self.units[k] = nil; end end function SorrenTimers:InitializeSkills() local gcdspell; local _, class = UnitClass("player"); local name = UnitName("player"); local level = UnitLevel("player"); if( class == "HUNTER" ) then self.skills, gcdspell = self:SetHunter(); self:RegisterEvent("START_AUTOREPEAT_SPELL", "AutoStart"); self:RegisterEvent("STOP_AUTOREPEAT_SPELL", "AutoStop"); elseif( class == "SHAMAN" ) then self.skills, gcdspell = self:SetShaman(); self.totemAuras = {}; --self.totems = {}; self:RegisterEvent("PLAYER_AURAS_CHANGED", "CheckTotems"); if not self.spellCast then self.spellCast = self.UNIT_SPELLCAST_SUCCEEDED; self.UNIT_SPELLCAST_SUCCEEDED = self.ShamanCast; end self.skills.totemAuras = {}; for k,v in pairs(self.skills.totems) do if type(v) ~= 'boolean' then self.skills.totemAuras[v] = k; end end elseif( class == "DRUID" ) then self.skills, gcdspell = self:SetDruid(); elseif( class == "WARRIOR" ) then self.skills, gcdspell = self:SetWarrior(); elseif( class == "ROGUE" ) then self.skills, gcdspell = self:SetRogue(); gcdtime = 1; elseif( class == "PRIEST" ) then self.skills, gcdspell = self:SetPriest(); elseif( class == "WARLOCK" ) then self.skills, gcdspell = self:SetWarlock(); elseif( class == "MAGE" ) then self.skills, gcdspell = self:SetMage(); elseif( class == "PALADIN" ) then self.skills, gcdspell = self:SetPaladin(); elseif( class == "DEATHKNIGHT" ) then self.skills, gcdspell = self:SetDeathknight(); end local s = self.skills; if gcdspell then self:GetGlobalCooldownSpell(gcdspell); self:RegisterEvent("SPELL_UPDATE_COOLDOWN", "GetGlobalCooldown"); end for k,v in pairs(s) do --ondodge, useactionskills etc. for x,y in pairs(v) do --actual skills inside, if we can't use it just remove it if type(y) == 'table' then if y.talent and select(5, GetTalentInfo(y.talent.tab, y.talent.num)) == 0 then self:Debug(GetTalentInfo(y.talent.tab, y.talent.num)); del(v[x]); v[x] = nil; end if y.level and not (level >= y.level) then del(v[x]); v[x] = nil; end end end if not next(s[k]) then del(s[k]); s[k] = nil; end end if s.useActionSkills or s.interrupts then self:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED"); end end function SorrenTimers:ShamanCast(unit, spell) if unit ~= "player" then return; end if self.skills.totems[spell] then if type(self.skills.totems[spell]) ~= 'boolean' then self.totemAuras[self.skills.totems[spell] ] = true; else --self.totems[spell] = true; --for detecting death of non-aura granting totems, but unfinished. end end self:spellCast(unit, spell); --hook of UNIT_SPELLCAST_SUCCEEDED end function SorrenTimers:CheckTotems() local i = 1; local aura; for k in pairs(self.totemAuras) do self.totemAuras[k] = false; end while unitbuff("player", i) do aura = unitbuff("player",i); if self.totemAuras[aura] ~= nil then self.totemAuras[aura] = true; end i = i + 1; end for k,v in pairs(self.totemAuras) do if not v then self:StopBar("st"..self.skills.totemAuras[k]); self.totemAuras[k] = nil; end end end function SorrenTimers:AutoStart() local icon = GetInventoryItemTexture("player", 18); self:UpdateBar(nil, select(1, GetSpellInfo(75)), icon, nil, 0.5, 0.5); --- Driizt end function SorrenTimers:AutoStop() end function SorrenTimers:Party() if GetNumPartyMembers() > 0 and abs(GetTime() - self.lastsent) > 3 then self.lastsent = GetTime(); self:SendCommMessage("GROUP", "offset", GetTime() ); else for k,v in pairs(self.offsets) do self.offsets[k] = del(v); end end end function SorrenTimers:OnCommReceive(pre, sender, dis, cmd, ...) --self:Print(pre,sender,dis,cmd, ...); if cmd == "offset" then local offset = ...; if not self.offsets[sender] then self.offsets[sender] = new(); self.offsets[sender].offset = 0; self.offsets[sender].x = 0; end offset = offset - GetTime() + select(3, GetNetStats())/1000; if abs(self.offsets[sender].offset - offset) > 3* select(3, GetNetStats())/1000 then self.offsets[sender].x = 1; self.offsets[sender].offset = offset; else self.offsets[sender].offset = (self.offsets[sender].offset * self.offsets[sender].x + offset)/(self.offsets[sender].x+1); self.offsets[sender].x = self.offsets[sender].x + 1; end if self.offsets[sender].x < 5 and not self:IsEventScheduled("st-req-"..sender) then self:ScheduleRepeatingEvent( "st-req-"..sender, function() self:RequestOffsets(sender); end, 5); elseif self.offsets[sender].x >= 5 and self:IsEventScheduled("st-req-"..sender) then self:CancelScheduledEvent("st-req-"..sender); end elseif cmd == "reqoffsets" then local who = ...; if not who or who == UnitName('player') then self:Party(); end elseif cmd == "new" and not self.db.char.groups.groupskills.disabled then if not self.offsets[sender] then return; end local name, target, icon, start, duration = ...; if not db.groupskills[name] then db.groupskills[name] = new(); end if not db.groupskills[name].disabled then local timeleft = duration - (GetTime() - (start - self.offsets[sender].offset)); local t, display = sender, nil; if target then t = target.." ("..sender..")"; display = strsplit(":",target).." ("..sender..")".." - "..name; else display = t.." - "..name; end self:CreateOrUpdateBar(name, t, false, "st-"..t..name, display--[[t.." - "..name]], duration, timeleft, icon, db.groupskills[name], db.groupskills[name].group and db.groups[db.groupskills[name].group] or db.groups[db.defaultgroupskillsgroup], "group", false ); end elseif cmd == "kill" then local name, target = ...; local bar = "st-"..sender..name; if target then bar = "st-"..target.." ("..sender..")"..name; end self:StopBar(bar); end end function SorrenTimers:UNIT_SPELLCAST_SUCCEEDED(unit, spell) if unit ~= "player" then return; end if self.skills.useActionSkills and self.skills.useActionSkills[spell] then local s = self.skills.useActionSkills[spell]; if spell == select(1, GetSpellInfo(75)) then s.duration = UnitRangedDamage("player"); s.icon = GetInventoryItemTexture("player", 18); if self:IsCandyBarRegistered("st"..select(1, GetSpellInfo(75))) then self:SetCandyBarTime("st"..select(1, GetSpellInfo(75)), s.duration); end end if s.start then self:UpdateBar(nil, s.start, self.skills.special[s.start].icon or select(3, GetSpellInfo(spell)), nil, self.skills.special[s.start].duration, self.skills.special[s.start].duration ); --- Driizt end if s.stop then self:StopBars(s.stop); end if s.duration then self:UpdateBar(nil, spell, s.icon or select(3, GetSpellInfo(spell)), nil, s.duration, s.duration); --- Driizt end end if self.skills.interrupts and self.skills.interrupts[spell] then self.interrupt = spell; end end function SorrenTimers:OnDisable() end --sets up all the anchor frames, skill settings, etc. function SorrenTimers:SetUp() --make anchors and set up candybar groups for group,k in pairs(db.groups) do self:CreateAnchor(group); self:RegisterCandyBarGroup(group); self:SetCandyBarGroupPoint(group, "TOP", self.anchors[group]:GetName(), "BOTTOM", 0, 0) self:SetCandyBarGroupGrowth(group, dor(k.up,db.defaults.up)); end for skill in pairs(self.enemySkills) do if not db.customenemy[skill] then db.customenemy[skill] = {}; end end wf:Register('SorrenTimers', 'aceOptions', self.wfoptions, 'title',"Sorren's Timers Options", 'colorR', 0.3, 'colorG', 0.7, 'colorB', 0.5 ); end function SorrenTimers:PLAYER_TARGET_CHANGED() if UnitExists("target") then self:UNIT_AURA("target"); end end function SorrenTimers:UPDATE_MOUSEOVER_UNIT() if UnitExists("mouseover") then self:UNIT_AURA("mouseover"); end end function SorrenTimers:UNIT_PET() if not self.units then return end --- if we don't have bars visible return immediately local petowner = arg1; local petunit = petowner.."pet"; if UnitExists(petunit) then local petname = UnitName(petunit); if petname then local guidstr = strsub(tostring(UnitGUID(petunit)),3); local barname = ""; if petname ~= L["Unknown"] then barname = petname..":"..guidstr; else for i,j in pairs(self.units) do if string.find(i,guidstr,1,true) then barname = i; end end end if self.units[barname] then for k,v in pairs(self.units[barname]) do self:StopBar("st"..k..barname); end end end end end --Checks for new skills placed on the unit, updates any bars that may be off for that unit function SorrenTimers:UNIT_AURA(unit) if UnitName(unit) == L["Unknown"] then return; end -- dont get info for unknown units if not UnitIsUnit("player", unit) then --player is handled slightly differently, rather than blindly adding anything with a duration it checks for things on a list local npc = not UnitPlayerControlled(unit); local name = UnitName(unit); local guidstr = strsub(tostring(UnitGUID(unit)),3); if name and guidstr then name = name..":"..guidstr; end local checknpc = db.checknpc; if (not npc or checknpc) and self.units[name] then for k,v in pairs(self.units[name]) do if type(v) == "boolean" then self.units[name][k] = false; end end else self.units[name] = new(); end local i = 1; local skill, rank, icon, count, duration, timeleft, caster, exists, bartime; --check debuffs while unitdebuff(unit,i) do skill, _, icon, count, _, duration, timeleft, caster = unitdebuff(unit, i); local ismine = my_units[caster] if timeleft then timeleft = timeleft - GetTime() end; if count == 0 then count = nil end if duration and timeleft and ismine and duration > 0 and timeleft > 0 then if not npc or checknpc then self.units[name][skill] = true; end exists, bartime = self:CandyBarStatus("st"..skill..name); if self.skills.diminish and self.skills.diminish[skill] and (not exists or duration ~= bartime) and not npc then self:StartOrUpdateDiminishBar(skill, name, self.skills.diminish[skill]); end self:UpdateBar(name, skill, icon, count, duration, timeleft, npc); --- Driizt if self.skills.diminish and self.skills.diminish[skill] and self.units[name][L["Diminish "]..self.skills.diminish[skill]] then self:PauseCandyBar("st"..L["Diminish "]..self.skills.diminish[skill]..name); end end i = i + 1; end i = 1; --check buffs while unitbuff(unit,i) do skill, _, icon, count, _, duration, timeleft, caster = unitbuff(unit, i); local ismine = my_units[caster] if timeleft then timeleft = timeleft - GetTime() end; if count == 0 then count = nil end if duration and timeleft and ismine and duration > 0 and timeleft > 0 then self:UpdateBar(name, skill, icon, count, duration, timeleft, npc); --- Driizt if not npc or checknpc then self.units[name][skill] = true; end end i = i + 1; end for k,v in pairs(self.units[name]) do if not v then self:StopBar("st"..k..name); end end else --unit == player local i = 1; for k,v in pairs(self.buffs) do self.buffs[k] = false; end local skill, count, icon, duration, timeleft, caster; while unitbuff("player",i) do skill, _, icon, count, _, duration, timeleft, caster = unitbuff(unit, i); local ismine = my_units[caster] if timeleft then timeleft = timeleft - GetTime() end; if count == 0 then count = nil end if duration and timeleft and ismine and duration > 0 and timeleft > 0 then if not db.custombuffs[skill] then db.custombuffs[skill] = { disabled = true, }; end if not db.custombuffs[skill].disabled then local display = string.gsub(db.defaults.skillstring, "$name", skill); local textupdate = false; if count then count = "("..count..")"; textupdate = true; else count = ""; end display = string.gsub(display, "$s", count); self:CreateOrUpdateBar(skill, nil, nil, "st"..skill, display, duration, timeleft, icon, db.custombuffs[skill], db.custombuffs[skill].group and db.groups[db.custombuffs[skill].group] or db.groups[db.defaultbuffgroup], "buff", textupdate); self.buffs[skill] = i; if not self:IsEventScheduled("stTrackBuffs") then self:ScheduleRepeatingEvent("stTrackBuffs", self.TrackBuffs, 0.1, self); end end end i = i + 1; end for k,v in pairs(self.buffs) do if not v then self:StopBar("st"..k); self.buffs[k] = nil; end end end end function SorrenTimers:RequestOffsets(...) self:SendPrioritizedCommMessage("NORMAL", "GROUP", "reqoffsets", ...); end function SorrenTimers:UpdateBar(name, skill, icon, count, duration, timeleft, npc, enemySkill) --- Driizt self:Debug("UpdateBar called: ", name, skill, icon, count, duration, timeleft, npc); --- Driizt --check to see that this isn't a banned skill if not enemySkill then if db.custom[skill] and db.custom[skill].disabled then return; end elseif db.customenemy[skill] and db.customenemy[skill].disabled then return; end --add it to the options if its not there if not tern(not enemySkill, db.custom[skill], db.customenemy[skill]) then local a = tern(not enemySkill, db.custom, db.customenemy); a[skill] = {}; end --special case, stops one or more other bars if not enemySkill and self.skills.stops and self.skills.stops[skill] then self:Debug("stops", skill, self.skills.stops[skill]); self:StopBars(self.skills.stops[skill]); end --set the bar name, prepending "st" to prevent collisions with other mods using candybar local barname; if name then if not npc or db.checknpc then barname = "st"..skill..name; else barname = "st"..skill..math.floor(GetTime())..name; end else barname = "st"..skill; end --check for custom settings local bar = tern(not enemySkill, db.custom[skill], db.customenemy[skill]); local group; if bar.group then group = db.groups[bar.group]; else group = db.groups[db[tern(not enemySkill, "defaultskillgroup", "defaultenemyskillgroup")] ]; end --set up the display string local display, textupdate = "", false; if name then local simplename,_ = strsplit(":",name); --- for display purposes only display = string.gsub(group.targetstring or db.defaults.targetstring, "$target", simplename); display = string.gsub(display, "$name", skill); if count then count = "("..count..")"; textupdate = true; else count = ""; end display = string.gsub(display, "$s", count); else display = string.gsub(group.skillstring or db.defaults.skillstring, "$name", skill); if count then count = "("..count..")"; textupdate = true; else count = ""; end display = string.gsub(display, "$s", count); end --make the bar if its not made self:CreateOrUpdateBar(skill, name, npc, barname, display, duration, timeleft, icon, bar, group, enemySkill, textupdate); end function SorrenTimers:CreateOrUpdateBar(skill, name, npc, barname, display, duration, timeleft, icon, bar, group, enemySkill, textupdate) self:Debug(skill, name, npc, barname, display, duration, timeleft, icon, bar, group); local dgroup = db.defaultskillgroup; if enemySkill == "enem" then dgroup = db.defaultenemyskillgroup; elseif enemySkill == "buff" then dgroup = db.defaultbuffgroup; elseif enemySkill == "group" then dgroup = db.defaultgroupskillsgroup; end local defaults = db.defaults; --self:PrintLiteral(group.colors); if not self:IsCandyBarRegistered(barname) then self:RegisterCandyBar(barname, duration, display, icon); ---, unpack(bar.colors or group.colors or tern(enemySkill ~= "dimin", defaults.colors, defaults.diminishcolors))); self:SetCandyBarColor(barname, unpack(bar.colors or group.colors or tern(enemySkill ~= "dimin", defaults.colors, defaults.diminishcolors)), group.alpha or defaults.alpha); --- hardcoded alpha for test, TODO: provide option for it self:RegisterCandyBarWithGroup(barname, bar.group or dgroup); self:SetCandyBarTexture(barname, sm:Fetch(sm.MediaType.STATUSBAR, group.texture or defaults.texture)); self:SetCandyBarHeight(barname, group.height or defaults.height); self:SetCandyBarWidth(barname, group.width or defaults.width); self:SetCandyBarScale(barname, group.scale or defaults.scale); self:SetCandyBarFade(barname, bar.fade or group.fade or defaults.fade); self.bars[barname] = new(skill, name, npc, enemySkill, bar.group or dgroup); self:SetCandyBarCompletion(barname, function() self:Clean(barname) end); end --self:PrintLiteral(enemySkill, self.bars[barname]); self:StartCandyBar(barname, true); self:SetCandyBarTime(barname, duration); self:SetCandyBarTimeLeft(barname, timeleft); if textupdate then self:SetCandyBarText(barname, display); end if not enemySkill and not db.groups.groupskills.disabled and bar.share and next(self.offsets) then --- if not db.groups.groupskills.disabled and bar.share and next(self.offsets) and dgroup == db.defaultskillgroup then --- BUG: If the user hasn't customized buff and enemy skill groups they are the same as db.defaultskillgroup so those abilities are send also local offset = GetTime() - (duration - timeleft); self:SendCommMessage("GROUP", "new", skill, name, icon, offset, duration); end end --cleanup, set up diminishing returns bars if necessary function SorrenTimers:Clean(barname) self:Debug(barname); if not self.bars[barname] then return; end if not self.bars[barname][3] and self.skills.diminish and self.skills.diminish[self.bars[barname][1] ] then self:SetCandyBarTimeLeft("st"..L["Diminish "]..self.skills.diminish[self.bars[barname][1] ]..self.bars[barname][2], 15); self:StartCandyBar("st"..L["Diminish "]..self.skills.diminish[self.bars[barname][1] ]..self.bars[barname][2], true); end --self:PrintLiteral("Clean:", self.bars[barname]); if self.bars[barname][2] and self.bars[barname][1] then if self.units[self.bars[barname][2]] and self.units[self.bars[barname][2]][self.bars[barname][1] ] then self.units[self.bars[barname][2] ][self.bars[barname][1] ] = nil; end if self.units[self.bars[barname][2] ] and not next(self.units[self.bars[barname][2] ]) then del(self.units[self.bars[barname][2] ]); self.units[self.bars[barname][2] ] = nil; end end del(self.bars[barname]); self.bars[barname] = nil; end function SorrenTimers:StartOrUpdateDiminishBar(name, unit, s) if db.custom[L["Diminish "]..s] and db.custom[L["Diminish "]..s].disabled then return; end if not self.units[unit] then self.units[unit] = new(); end if not self.units[unit][L["Diminish "]..s] then self.units[unit][L["Diminish "]..s] = 1; else self.units[unit][L["Diminish "]..s] = self.units[unit][L["Diminish "]..s] + 1; end local display = string.gsub(db.defaults.diminishstring, "$name", s); local simpleunit,_ = strsplit(":",unit); display = string.gsub(display, "$n", self.units[unit][L["Diminish "]..s]); display = string.gsub(display, "$target", simpleunit); local barname = "st"..L["Diminish "]..s..unit; --copied over from update bar if not db.custom[L["Diminish "]..s] then db.custom[L["Diminish "]..s] = {}; end local bar = db.custom[L["Diminish "]..s]; local group; if bar.group then group = db.groups[bar.group]; else group = db.groups.defaultgroup; end self:CreateOrUpdateBar(L["Diminish "]..s, unit, false, barname, display, 15, 15, select(3, GetSpellInfo(name)), bar, group, "dimin", false); if self:IsCandyBarRegistered(barname) then --otherwise the text won't change. self:SetCandyBarText(barname, display); end end --anchor creation function SorrenTimers:CreateAnchor(group) local groups = db.groups; --default position is smack dab in the middle of the screen. if not groups[group].pos then groups[group].pos = { point = "CENTER", relPoint = "CENTER", x = 0, y = 0, }; end --table to keep track of the anchors, not saved since it holds frames which are destroyed anyway if not self.anchors then self.anchors = { }; end --less typing. local anchors = self.anchors; anchors[group] = CreateFrame("Frame",group..tostring(math.floor(GetTime())),UIParent) --I forget why I have the randomnumbers, but I'm pretty sure its there for a reason. anchors[group]:ClearAllPoints(); anchors[group]:SetBackdrop( { bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", edgeFile="Interface\\Tooltips\\UI-Tooltip-Border", tile = true, tileSize = 16, edgeSize = 16, insets = { left = 5, right = 5, top = 5, bottom = 5 }, } ); --default tooltip backdrop anchors[group]:SetWidth(128) anchors[group]:SetHeight(30) anchors[group]:SetBackdropColor(0,0,0); anchors[group]:EnableMouse(true); anchors[group]:SetPoint(groups[group].pos.point, "UIParent", groups[group].pos.relPoint, groups[group].pos.x, groups[group].pos.y ); --text local msg = anchors[group]:CreateFontString(nil,"overlay","GameFontNormal"); msg:ClearAllPoints(); msg:SetPoint("center", anchors[group], "center"); msg:SetText(group); --scripts and whatnot anchors[group]:SetMovable(true); anchors[group]:SetScript("OnDragStart", function() anchors[group]:StartMoving(); GameTooltip:Hide() end); anchors[group]:SetScript("OnDragStop", function() anchors[group]:StopMovingOrSizing(); SorrenTimers:SavePosition(group) end); anchors[group]:SetScript("OnMouseUp", function() self:OnAnchorClick(group) end); anchors[group]:SetScript("OnEnter", function() self:ShowAnchorTooltip() end); anchors[group]:SetScript("OnLeave", function() GameTooltip:Hide() end); anchors[group]:RegisterForDrag("LeftButton"); --show or hide for lockage. if groups[group].locked or groups[group].disabled then anchors[group]:Hide(); else anchors[group]:Show(); end end --excessively long tooltip because people keep asking these questions. function SorrenTimers:ShowAnchorTooltip() GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT"); GameTooltip:AddLine(L["Sorren Timers Group Anchor"]); GameTooltip:AddLine(L["Drag this to reposition"]); GameTooltip:AddLine(L["Shift+Left Click to hide"]); GameTooltip:AddLine(L["Alt+Left Click to show a test bar"]); GameTooltip:AddLine(L["Alt+Left Click again to hide the test bar"]); GameTooltip:AddLine(L["If you hide this, you can show it again by going to Group Options -> groupname -> Uncheck Lock"]); GameTooltip:Show(); end function SorrenTimers:OnAnchorClick(group, altkey) if IsAltKeyDown() or altkey then if self:IsCandyBarRegistered(group.."testbar") then self:StopCandyBar(group.."testbar"); return; end local defaults = db.defaults; local gsettings = db.groups[group]; local bar = group.."testbar"; self:RegisterCandyBar(bar, 300, L["Test bar"], "Interface\\Icons\\INV_Misc_QuestionMark", unpack(gsettings.colors or defaults.colors) ); self:RegisterCandyBarWithGroup(bar, group); self:SetCandyBarTexture(bar, sm:Fetch(sm.MediaType.STATUSBAR, gsettings.texture or defaults.texture)); self:SetCandyBarHeight(bar, gsettings.height or defaults.height); self:SetCandyBarWidth(bar, gsettings.width or defaults.width); self:SetCandyBarScale(bar, gsettings.scale or defaults.scale); self:StartCandyBar(bar, true); end if IsShiftKeyDown() then self:LockAnchor(group); return; end end function SorrenTimers:SavePosition(group) local ginfo = db.groups[group].pos; ginfo.point, _, ginfo.relPoint, ginfo.x, ginfo.y = self.anchors[group]:GetPoint(); self:Debug(self.anchors[group]:GetPoint()); end function SorrenTimers:LockAnchor(group) if not db.groups[group] then self:Print(string.format(L["Group \"%s\" not found!"], group) ); return end self.anchors[group]:Hide(); db.groups[group].locked = true; end function SorrenTimers:StopBars(bars) --self:Print("StopBars"); if type(bars) == 'table' then for k,v in pairs(self.bars) do if bars[v[1] ] then self:StopBar(k); end end else for k,v in pairs(self.bars) do if v[1] == bars then self:StopBar(k); end end end end function SorrenTimers:StopBar(bar) if self.bars[bar] then if not self.bars[bar][4] or self.bars[bar][4] == "dimin" then if db.custom[self.bars[bar][1] ] and db.custom[self.bars[bar][1] ].share then self:SendCommMessage("GROUP", "kill", self.bars[bar][1], self.bars[bar][2]); end end self:StopCandyBar(bar); self:Clean(bar); end end --enable clicking on the bars through the alt key, function SorrenTimers:MODIFIER_STATE_CHANGED(key, pressed) if key == "LALT" or key == "RALT" then if pressed == 1 then for k,v in pairs(self.bars) do self:SetCandyBarOnClick(k, function() dd:Open(UIParent, 'point', "CENTER", 'relativePoint', "CENTER", 'cursorX', true, 'cursorY', true, 'children', function() SorrenTimers:OnBarClick(k); end ); end); end else for k,v in pairs(self.bars) do self:SetCandyBarOnClick(k); end end end end function SorrenTimers:OnBarClick(bar) --opens config stuff for that bar dd:AddLine( 'text', L["Kill Bar"], 'func', function() self:StopBar(bar); end ); dd:AddLine( 'text', L["Disable Bar"], 'func', function() local baroptions; if not self.bars[bar][4] or self.bars[bar][4] == "dimin" then baroptions = db.custom; elseif self.bars[bar][4] == "enem" then baroptions = db.customenemy; elseif self.bars[bar][4] == "buff" then baroptions = db.custombuffs; elseif self.bars[bar][4] == "group" then baroptions = db.groupskills; end --self:PrintLiteral(self.bars[bar]); baroptions[self.bars[bar][1] ].disabled = true; local bartype = self.bars[bar][1]; for k,v in pairs(self.bars) do if v[1] == bartype then self:StopBar(k); end end dd:Close(); end ); end function SorrenTimers:ConvertToNewOptions() self:Print(L["Old options detected. Converted to new options, backed up old options. /st nukeoldoptions to get rid of backup."]); db.oldsettings = deepcopy(db); --defaults del(db.defaults.buffcolors); del(db.defaults.debuffcolors); del(db.defaults.diminishcolors); del(db.defaults); db.defaults = { fade = db.barfade or 0, scale = db.barscale or 1, colors = db.defaultcolors or {"blue", "cyan", "green" }; buffcolors = { "blue", "cyan", "green" }, debuffcolors = { "red", "orange", "yellow" }, diminishcolors = { "red", "red", "red" }, width = db.barwidth or 200, space = 0, height = db.height or 16, texture = db.bartexture or "BantoBar", up = false, targetstring = db.enemyskillstring or "$name $s - $target", skillstring = db.skillstring or "$name $s", diminishstring = "($n) $name - $target", }; db.barfade = nil; db.barscale = nil; db.defaultcolors = nil; db.barwidth = nil; db.height = nil; db.bartexture = nil; db.enemyskillstring = nil; db.skillstring = nil; --group settings for k,v in pairs(db.groups) do --location info db.groups[k].pos = db.anchors["STAnchor"..k]; db.groups[k].locked = not db.anchors["STAnchor"..k].unlocked; v.pos.unlocked = nil; db.anchors["STAnchor"..k] = nil; --bar settings v.width = v.barwidth; v.fade = v.barfade; v.scale = v.barscale; v.height = v.barheight; v.colors = v.color; v.texture = v.bartexture; v.barwidth = nil; v.barfade = nil; v.barscale = nil; v.barheight = nil; v.color = nil; v.bartexture = nil; end --skill settings for k,v in pairs(db.abilities) do if v.color or v.group ~= "defaultgroup" or not v.enable or v.fade then db.custom[k] = { group = v.group, colors = v.color, fade = v.fade, }; if not v.enable then db.custom[k].disabled = true; end end end db.abilities = nil; end function SorrenTimers:GetGlobalCooldownSpell(gcdspell) local i = 1; while GetSpellName(i, BOOKTYPE_SPELL) do if GetSpellName(i, BOOKTYPE_SPELL) == gcdspell then self.gcdspell = i; return; end i = i + 1; end end function SorrenTimers:GetGlobalCooldown() if not self.gcdspell then return end local starttime, duration = GetSpellCooldown(self.gcdspell, BOOKTYPE_SPELL); if starttime ~= 0 and starttime ~= self.gcdstart and duration <= 1.5 then self.gcdstart = starttime; self:UpdateBar(nil, L["Global Cooldown"], "Interface\\Icons\\Ability_Whirlwind", nil, duration, duration); --- Driizt end if starttime == 0 then self:StopBar("st"..L["Global Cooldown"]); end end function SorrenTimers:COMBAT_LOG_EVENT_UNFILTERED(tstamp,event,srcGUID,srcName,srcFlags,destGUID,destName,destFlags,...) local fromPlayer, toPlayer = false, false; local me = UnitName("player"); if srcName and srcName == me then fromPlayer = true; end if destName and destName == me then toPlayer = true; end local isSourceEnemy = (bit.band(srcFlags, COMBATLOG_OBJECT_REACTION_HOSTILE) == COMBATLOG_OBJECT_REACTION_HOSTILE); local isDestEnemy = (bit.band(destFlags, COMBATLOG_OBJECT_REACTION_HOSTILE) == COMBATLOG_OBJECT_REACTION_HOSTILE); local isDestTarget = (bit.band(destFlags, COMBATLOG_OBJECT_TARGET) == COMBATLOG_OBJECT_TARGET); if event == "SPELL_INTERRUPT" then if self.skills.interrupts then if fromPlayer then local enemy = destName; if enemy and self.interrupt then local guidstr = strsub(tostring(destGUID),3); if guidstr then enemy = enemy..":"..guidstr; end self:UpdateBar(enemy, self.interrupt, select(3, GetSpellInfo(self.interrupt)), nil, self.skills.interrupts[self.interrupt].duration, self.skills.interrupts[self.interrupt].duration); --- Driizt self.interrupt = nil; end end end elseif event == "SPELL_AURA_APPLIED" then if isDestEnemy then local unit = destName; local aura = select(4,...); if unit and aura == "BUFF" then local guidstr = strsub(tostring(destGUID),3); if guidstr then unit = unit..":"..guidstr; end local buff = select(2,...); if buff and self.enemySkills[buff] then if not self.units[unit] then self.units[unit] = new(); end self.units[unit][buff] = "enem"; local buffid = select(1,...); local bufficon = nil; if buffid and type(buffid) == "number" then bufficon = select(3, GetSpellInfo(buffid)); end self:UpdateBar(unit, buff, bufficon, nil, self.enemySkills[buff].duration, self.enemySkills[buff].duration, false, "enem"); --- Driizt end end end elseif event == "SPELL_AURA_REMOVED" then if isDestEnemy then local unit = destName; local aura = select(4,...); if unit and aura == "BUFF" then local guidstr = strsub(tostring(destGUID),3); if guidstr then unit = unit..":"..guidstr; end local buff = select(2,...); if self.units[unit] and self.units[unit][buff] then self:StopBar("st"..buff..unit); end end end elseif event == "SWING_MISSED" or event == "SPELL_MISSED" or event == "RANGE_MISSED" then local action, missType; if event == "SWING_MISSED" then missType = select(1,...); else missType = select(4,...); end if isSourceEnemy and toPlayer then if missType == "DODGE" then action = "onDodge"; elseif missType == "PARRY" then action = "onParry"; elseif missType == "BLOCK" then action = "onBlock"; end elseif fromPlayer then if missType == "DODGE" then action = "onEnemyDodge"; end end if not action or not self.skills[action] then return end for k,v in pairs(self.skills[action]) do self:UpdateBar(nil, k, select(3, GetSpellInfo(k)), nil, v.duration, v.duration); --- Driizt end elseif event == "SWING_DAMAGE" or event == "RANGE_DAMAGE" or event == "SPELL_DAMAGE" then if fromPlayer then local crit = select(9,...); if crit then local action = "onCrit"; if not action or not self.skills[action] then return end for k,v in pairs(self.skills[action]) do self:UpdateBar(nil, k, select(3, GetSpellInfo(k)), nil, v.duration, v.duration); --- Driizt end end if self.skills.cast then if event == "SPELL_DAMAGE" then local spell = select(2,...); if spell and self.skills.cast and self.skills.cast[spell] then local s = self.skills.cast[spell]; local spellid = select(1,...); local spellicon = nil; if spellid and type(spellid) == "number" then spellicon = select(3, GetSpellInfo(spellid)); end if s.start and self.skills.special[s.start] then self:UpdateBar(nil, s.start, spellicon, nil, self.skills.special[s.start].duration, self.skills.special[s.start].duration); --- Driizt end if s.duration then self:UpdateBar(nil, spell, spellicon, nil, s.duration, s.duration); --- Driizt end return; end end end end elseif event == "UNIT_DIED" then if destName then local whodied = destName local guidstr = strsub(tostring(destGUID),3); if guidstr then whodied = whodied..":"..guidstr; end if self.units[whodied] then for k,v in pairs(self.units[whodied]) do self:StopBar("st"..k..whodied); end end end elseif event == "PARTY_KILL" then if fromPlayer then local action = "onKill"; if not action or not self.skills[action] then return end for k,v in pairs(self.skills[action]) do self:UpdateBar(nil, k, select(3, GetSpellInfo(k)), nil, v.duration, v.duration); --- Driizt end if destName then --- partial work-around for the inconsistencies of UNIT_DIED, check to remove after 2.4.2 -- Driizt local whodied = destName local guidstr = strsub(tostring(destGUID),3); if guidstr then whodied = whodied..":"..guidstr; end if self.units[whodied] then for k,v in pairs(self.units[whodied]) do self:StopBar("st"..k..whodied); end end end end end end function SorrenTimers:TrackBuffs() local skill, icon, duration, timeleft; for k,v in pairs(self.buffs) do skill, _, icon, _, _, duration, timeleft = unitbuff("player", v); if skill ~= k then v = self:FindBuff(k); if not v then self:StopBar("st"..k); --self.buffs[k] = nil; else self.buffs[k] = v; timeleft = select(7, unitbuff("player",v) ); if timeleft then timeleft = timeleft - GetTime(); self:SetCandyBarTimeLeft("st"..k, timeleft ); end end elseif timeleft then timeleft = timeleft - GetTime(); self:SetCandyBarTimeLeft("st"..k, timeleft); end end if not next(self.buffs) then self:CancelScheduledEvent("stTrackBuffs"); end end function SorrenTimers:FindBuff(buff) local i = 1; while unitbuff("player",i) do if select(1, unitbuff("player",i) ) == buff then return i; end i = i + 1; end return nil; end local pc = AceLibrary("PaintChips-2.0"); function SorrenTimers:TestStuff() self:PrintLiteral(pc.vars); end