CandyDice = AceLibrary("AceAddon-2.0"):new( "AceEvent-2.0","AceHook-2.0","AceDebug-2.0", "AceDB-2.0","AceConsole-2.0","CandyBar-2.0" ) local cd = CandyDice cd.revision = tonumber(string.sub("$Revision: 129 $", 12, -3)) cd.textures = {} -- they'll get registered in OnInitialize cd.texturenames = {} -- instead of storing this I should just update the options table in RegisterTexture() local compost = AceLibrary:HasInstance("Compost-2.0") and AceLibrary("Compost-2.0") local function GetTable() return compost and compost:Acquire() or {} end local function RecycleTable(t) if compost then compost:Reclaim(t) end end cd:RegisterDB("CandyDiceDBPerChar") cd.cmdtable = {type="group",handler=CandyDice,args = { timers = { type="group", name="Timers", desc="Commands for the ability timers", args = { show = { type="toggle", name="Show Anchor", desc="Show the timer anchor", get=function() return CandyDiceAnchorFrame:IsShown() end, set=function(show) if show then CandyDiceAnchorFrame:Show() else CandyDiceAnchorFrame:Hide() end end }, center = { type="toggle", name="Center Timers", desc="Center the timer bars on the screen rather than anchoring them", get=function() return CandyDice.db.profile.options["center"] end, set="SetCentered" }, enable = { type="toggle", name="Enabled", desc="Enable showing of ability timer bars", get=function() return CandyDice.db.profile.options["buffs"] end, set=function(v) if v then CandyDice:EnableBuffs() else CandyDice:DisableBuffs() end end }, grow = { type="toggle", name="Grow Upward", desc="When enabled, the bars will grow upward from the anchor. Otherwise, they will grow down", get=function() return CandyDice.db.profile.options.grow end, set=function(v) CandyDice:Debug(v) CandyDice.db.profile.options.grow = v CandyDice:UpdateGrowth() end }, scale = { type="range", name="Scale", min=.1, max=10, desc="Scale factor for the timer bars", get=function() return CandyDice.db.profile.options.scale end, set=function(v) CandyDice.db.profile.options.scale = v CandyDice:Setup() end }, icon = { type="toggle", name="Show Icon", desc="Toggle the showing of icons on timer bars", get=function() return CandyDice.db.profile.options.icon end, set=function(v) if v then CandyDice.db.profile.options.icon = true else CandyDice.db.profile.options.icon = false end CandyDice:Setup() end, }, }, }, cooldowns = { type="group", name="Cooldowns", desc="Commands for the cooldown timers", args = { show = { type="toggle", name="Show Anchor", desc="Show the cooldown anchor", get=function() return CandyDiceCooldownAnchorFrame:IsShown() end, set=function(show) if show then CandyDiceCooldownAnchorFrame:Show() else CandyDiceCooldownAnchorFrame:Hide() end end }, enable = { type="toggle", name="Enabled", desc="Enable or disable cooldown scanning", get=function() return CandyDice.db.profile.options.cooldowns end, set=function(v) if v then CandyDice:EnableCooldowns() else CandyDice:DisableCooldowns() end end }, grow = { type="toggle", name="Grow Upward", desc="When enabled, the bars will grow upward from the anchor. Otherwise, they will grow down", get=function() return CandyDice.db.profile.options.growcd end, set=function(v) CandyDice:Debug(v) CandyDice.db.profile.options.growcd = v; CandyDice:UpdateGrowth(); end }, scale = { type="range", name="Scale", min=.1, max=10, desc="Scale factor for the cooldown bars", get=function() return CandyDice.db.profile.options.scalecd end, set=function(v) CandyDice.db.profile.options.scalecd = v CandyDice:Setup() end }, reversed = { type="toggle", name="Reversed", desc="Controls whether the cooldown bars move from empty to filled or filled to empty", get=function() return CandyDice.db.profile.options.reversecd end, set=function(v) if (v) then CandyDice.db.profile.options.reversecd = true else CandyDice.db.profile.options.reversecd = false end CandyDice:Setup() end }, icon = { type="toggle", name="Show Icon", desc="Toggle the showing of icons on cooldown bars", get=function() return CandyDice.db.profile.options.iconcd end, set=function(v) if v then CandyDice.db.profile.options.iconcd = true else CandyDice.db.profile.options.iconcd = false end CandyDice:Setup() end, } }, }, reset = { type="execute", name="Reset Settings", desc="Resets all settings to the default", func=function() CandyDice:ResetDB("profile");CandyDice:Setup() end, order=5000 }, version = { type="execute", name="Report Version", desc="Prints the CandyDice version", func=function() CandyDice:Print(CandyDice.revision) end }, texture = { type="text", name="Bar Texture", desc="The texture used for the Candy Bars", validate=CandyDice.texturenames, set=function(v) CandyDice.db.profile.options.texture = v CandyDice:Setup() end, get=function() return CandyDice.db.profile.options.texture end, } } } cd.defaults = {} cd:RegisterDefaults( 'profile', { tracked = nil, options = { center = true, -- center the timer bars cooldowns = true, -- scan for cooldowns buffs = true, -- scan for buffs grow = true, -- timer bars grow vertically growcd = true, -- cd bars grow vertically scale=1, -- timer bar scale scalecd=1, -- cd bar scale reversecd = true, -- cooldowns count up instead of down iconcd = true, -- show icons in cooldowns icon = true, -- show icons on timers texture= 'Default' } } ) local gratuity = AceLibrary("Gratuity-2.0") local cb = CandyDice local pc = AceLibrary("PaintChips-2.0") cd:RegisterChatCommand({"/cd", "/cdice", "/candydice"}, CandyDice.cmdtable) function CandyDice:OnInitialize() --- erase all the class/race specific defaults --- except the ones in the players race/class local _,class = UnitClass("player") local _,race = UnitRace("Player") for category, buffs in self.defaults do if category ~= race and category ~= class then self.defaults[category] = nil end end self:RegisterTexture('Default','Interface\\Addons\\CandyDice\\Textures\\bar.tga') --~ self:SetDebugging(true) self.debugFrame = ChatFrame3 end function CandyDice:OnEnable() -- CandyBar setup self:RegisterCandyBarGroup("CandyDice") self:RegisterCandyBarGroup("CandyDiceCooldowns") -- Load the class specific defaults into the profile local _,class = UnitClass("player") local _,race = UnitRace("Player") if not self.db.profile.tracked then self.db.profile.tracked = {} self:Print("No tracking data found, loading defaults for %s/%s", race, class) local classbuffs = self.defaults[class] if classbuffs then for buff, info in pairs(classbuffs) do self.db.profile.tracked[buff] = info end end local racebuffs = self.defaults[race] if racebuffs then for buff, info in pairs(self.defaults[race]) do self.db.profile.tracked[buff] = info end end end self:UpdateGrowth() -- Make PaintChip not suck self:RegisterColors() -- Scan the spellbook and create a index/icon cache self:RegisterEvent("SPELLS_CHANGED", "ScanSpellbook") self:RegisterEvent("PLAYER_PET_CHANGED", "ScanSpellbook") self:ScanSpellbook() -- Push the tracked buffs into the options table so they can be manipulated from the command line self:PushTrackedToOptions() if self.db.profile.options.cooldowns then self:EnableCooldowns() -- turn on the cooldown scanning end if self.db.profile.options.buffs then self:EnableBuffs() -- turn on the buff scanning end end function CandyDice:OnDisable() -- I'm sure theres something I should do here. end function CandyDice:GetFuncsForBuff(buff) --- Creates closures that we can feed to the option table to get/set the various buff options local ftable = {} if not self.db.profile.tracked[buff] then return end ftable["GetFGColor"] = function() local c = unpack(CandyDice.db.profile.tracked[buff].colors) local r,g,b=unpack(pc:GetRGB(c)) return r/255,g/255,b/255 end ftable["SetFGColor"]=function(r,g,b) local hex = string.format("%02x%02x%02x", r*255, g*255, b*255) -- Stupid paintchips pc:RegisterHex(hex) CandyDice.db.profile.tracked[buff].colors[1] = hex self:SetCandyBarColor(buff, hex) self:SetCandyBarColor(buff.."CD", hex) end ftable["GetBGColor"]=function() local fg,bg= unpack(CandyDice.db.profile.tracked[buff].colors) local c = bg or fg local r,g,b=unpack(pc:GetRGB(c)) return r/255,g/255,b/255 end ftable["SetBGColor"]=function(r,g,b) CandyDice:Debug("R:"..tostring(r).."G:"..tostring(g).."B:"..tostring(b)) local hex = string.format("%02x%02x%02x", r*255, g*255, b*255) CandyDice:Debug(hex) -- Stupid paintchips pc:RegisterHex(hex) CandyDice.db.profile.tracked[buff].colors[2] = hex cb:SetCandyBarBackgroundColor(buff, hex) cb:SetCandyBarBackgroundColor(buff.."CD", hex) end ftable["GetScanBuff"]= function() return CandyDice.db.profile.tracked[buff]["buff"] end ftable["SetScanBuff"]= function(v) if (v) then CandyDice.db.profile.tracked[buff]["buff"] = true else CandyDice.db.profile.tracked[buff]["buff"]=false end CandyDice:StopCandyBar(buff) end ftable["GetScanCD"]= function() return CandyDice.db.profile.tracked[buff]["cooldown"] end ftable["SetScanCD"]= function(v) if (v) then CandyDice.db.profile.tracked[buff]["cooldown"] = true else CandyDice.db.profile.tracked[buff]["cooldown"]=false end CandyDice:StopCandyBar(buff.."CD") end ftable["delete"] = function() CandyDice.db.profile.tracked[buff] = nil CandyDice:UnregisterCandyBar(buff) CandyDice:UnregisterCandyBar(buff.."CD") CandyDice:PushTrackedToOptions() end return ftable end function CandyDice:PushTrackedToOptions() local header = { name="Abilities", desc="Abilities and buffs scanned", type="group", args = { addnew={ type="text", name="Add New", usage="/cdice abilities addnew ", order=1, desc="Add a new ability to be tracked", get=false, set=function(v) CandyDice:AddNewAbility(v) end, message="[%s]: %s has been added to tracked abilities." }, spacer={ type="header", order=2, }, } } for buff in self.db.profile.tracked do if self.db.profile.tracked[buff] then local ftable = self:GetFuncsForBuff(buff) local btable = { name=buff, type="group", desc=buff, args = { fgcolor= { type="color", name="Foreground color", desc="Foreground color", get=ftable["GetFGColor"], set=ftable["SetFGColor"] }, bgcolor= { type="color", name="Background color", desc="Background color", get=ftable["GetBGColor"], set=ftable["SetBGColor"] }, scanbuff={ type="toggle", name="Scan Buffs", desc="Scan your buffs for this ability", get=ftable.GetScanBuff, set=ftable.SetScanBuff, }, scancd={ type="toggle", name="Scan Cooldowns", desc="Scan for an ability cooldown", get=ftable.GetScanCD, set=ftable.SetScanCD, }, delete={ type="execute", name="Delete", desc="Remove the ability from the list of tracked abilities", func=ftable.delete } } } local key = string.lower(buff) key = string.gsub(key, ' ', '_') header.args[key] = btable end --if self.db.profile.tracked[buff] then end --for buff in self.db.profile.tracked self.cmdtable.args["abilities"] = header end function CandyDice:RegisterColors() for buff, info in pairs(self.db.profile.tracked) do if info then local fgcolor, bgcolor = unpack(info.colors) if fgcolor then pc:RegisterHex(fgcolor) end if bgcolor then pc:RegisterHex(bgcolor) end end end end function CandyDice:GetTrackedIcon(name) --- Returns the icon to use for a tracked buff/cooldown local t = self.db.profile.tracked if t[name] and t.texture then return t.texture end if self.spellcache[name] then return self.spellcache[name].texture end return nil end function CandyDice:ScanSpellbook() -- returns a table formated with -- spellname - {spellIndex, texture} -- Only need to scan the cooldowns local cache = {} self.shoot = nil local p = self.db.profile for ii =1,MAX_SPELLS do local name = GetSpellName(ii, BOOKTYPE_SPELL) if name == BabbleSpell["Shoot"] then self.shoot = ii end if name and p.tracked[name] then local texture = GetSpellTexture(ii, BOOKTYPE_SPELL) cache[name] = {["id"]=ii, ["texture"]=texture, ["type"] = BOOKTYPE_SPELL} end end local pspellcount = HasPetSpells() if pspellcount then for ii = 1, pspellcount do local name = GetSpellName(ii, BOOKTYPE_PET) if name and p.tracked[name] then local texture = GetSpellTexture(ii, BOOKTYPE_PET) cache[name] = {["id"]=ii, ["texture"]=texture, ["type"] = BOOKTYPE_PET} end end end self.spellcache = cache end --~ memtrack = { --~ calls = 0, --~ memory = 0, --~ time = 0, --~ } function CandyDice:ScanBuffs() --~ local time = GetTime() --~ local mem = gcinfo() local tracked = self.db.profile.tracked local cbuffs = GetTable() if self.db.profile.options.buffs then for ii = 0,15 do local bidx = GetPlayerBuff(ii, "HELPFUL") gratuity:SetPlayerBuff(bidx) local buff = gratuity:GetLine(1) if buff and tracked[buff] and tracked[buff].buff then cbuffs[buff] = buff self:UpdateBuff(buff, bidx, GetPlayerBuffTimeLeft(bidx)) end end end for buff, info in pairs(tracked) do if not cbuffs[buff] then local registered,bartime,elapsed,running = cb:CandyBarStatus(buff) if running then cd:StopCandyBar(buff) end end end RecycleTable(cbuffs) --~ memtrack.calls = memtrack.calls + 1 --~ memtrack.time = memtrack.time + (GetTime()-time) --~ local mem2 = gcinfo() --~ memtrack.memory = memtrack.memory + (mem2 - mem) --~ if math.mod(memtrack.calls, 10) == 0 then --~ self:PrintComma(memtrack.calls, memtrack.time, memtrack.memory) --~ end end function CandyDice:ScanCooldowns() local tracked = self.db.profile.tracked self:Debug("Scanning Cooldowns") if self.db.profile.options.cooldowns then for buff, info in pairs(tracked) do if info.cooldown then self:UpdateCooldown(buff, info) end end end end function CandyDice:UpdateBuff(buff, bidx, bufftime) -- Updates a buff bar to match the state of the buff. Be prepared for bidx/bufftime to be null --self:Debug("Updating buff"..tostring(buff)) local info = self.db.profile.tracked[buff] if not info and info.buff then return end local registered,bartime,elapsed,running = cb:CandyBarStatus(buff) if running then if (not (bufftime or bidx)) or bufftime == 0 then cd:StopCandyBar(buff) else cd:SetCandyBarTimeLeft(buff, bufftime) end elseif bufftime and bufftime > 0 then-- bar not running, but with bufftime,so start the bar if not registered then self:Debug("Registering bar for "..buff) local fgcolor, bgcolor = unpack(info.colors) local icon = CandyDice.db.profile.options.icon and (GetPlayerBuffTexture(bidx) or GetPlayerBuffTexture(ii)) or '' self:RegisterTimerBar(buff, fgcolor, bgcolor, icon) end cb:SetCandyBarTime(buff, bufftime) cb:StartCandyBar(buff) else -- This would be a buff with no time, and a bar that isn't running end end function CandyDice:UpdateCooldown(ability, info, doublecheck) local cache = self.spellcache[ability] if not (cache and cache.id and cache.type) then return end --- not in the spellcache if not info and info.cooldown then return end -- not set for cooldown scanning, probably redundant local cbn = ability..'CD' local now = GetTime() local start, duration = GetSpellCooldown(cache.id, cache.type) local registered,bartime,elapsed,running = cb:CandyBarStatus(cbn) if now == start then --- Stealth, natures swiftness, or something else that doesn't actually start its CD until it's buff is gone. --- Remotely possible this could misdetect a regular cooldown. --- To catch any regular CDs we miss, we schedule one (just one) check 1/10th of a second from now --~ self:Print("Skipping CD check for "..ability) if not doublecheck then --~ self:Print("Doublecheck scheduled") self:ScheduleEvent(self.UpdateCooldown, 0.1, self, ability, info, true) end return end if not running then local skipdur = 1.5 if self.shoot then local speed, lowDmg, hiDmg, posBuff, negBuff, percent = UnitRangedDamage("player") if speed > skipdur then skipdur = speed end end if duration <= skipdur then return end -- ignore global CD if not registered then self:Debug("Registering bar for "..cbn) local fgcolor, bgcolor = unpack(info.colors) self:RegisterCooldownBar(ability, fgcolor, bgcolor, cache.texture) end local left = duration - (now - start) self:SetCandyBarTime(cbn, duration) self:StartCandyBar(cbn) cb:SetCandyBarTimeLeft(cbn, left) else -- currently running, stop if CD is gone or update time if not if start == 0 or duration == 0 then self:StopCandyBar(cbn) return end local left = duration - (now - start) cb:SetCandyBarTimeLeft(cbn, left) end end -- command handlers function CandyDice:SetCentered(v) self.db.profile.options["center"] = v self:UpdateGrowth() end function CandyDice:EnableCooldowns() self.db.profile.options.cooldowns = true self:RegisterEvent("SPELL_UPDATE_COOLDOWN", "ScanCooldowns") self:ScanCooldowns() -- pick up any current ones end function CandyDice:DisableCooldowns() self.db.profile.options.cooldowns = false for cd, info in pairs(self.db.profile.tracked) do -- Stop all our bars. if info.cooldown then cb:UnregisterCandyBar(cd.."CD") end end self:UnregisterEvent("SPELL_UPDATE_COOLDOWN") end function CandyDice:EnableBuffs() self.db.profile.options.buffs = true -- PLAYER_ARUAS_CHANGED only fires when you actually gain or lose a debuff, -- not when you re-apply one self:RegisterEvent("PLAYER_AURAS_CHANGED", "ScanBuffs") -- To keep the durations accurate if you re-apply a buff self:ScheduleRepeatingEvent("CandyDice", self.ScanBuffs, 0.3, self) end function CandyDice:DisableBuffs() self.db.profile.options.buffs = false for cd, info in pairs(self.db.profile.tracked) do -- Stop all our bars. if info.buff then cb:UnregisterCandyBar(cd) end end self:UnregisterEvent("PLAYER_AURAS_CHANGED") self:CancelScheduledEvent("CandyDice") end function CandyDice:UpdateGrowth() local o = self.db.profile.options if o.center then cb:SetCandyBarGroupPoint("CandyDice", "CENTER", "UIParent", "CENTER", 0, 0) else if (o.grow) then cb:SetCandyBarGroupPoint("CandyDice", "BOTTOM", "CandyDiceAnchorFrame", "TOP", 0, 0) cb:SetCandyBarGroupGrowth("CandyDice", true) else cb:SetCandyBarGroupPoint("CandyDice", "TOP", "CandyDiceAnchorFrame", "BOTTOM", 0, 0) cb:SetCandyBarGroupGrowth("CandyDice", false) end end if (o.growcd) then cb:SetCandyBarGroupPoint("CandyDiceCooldowns", "BOTTOM", "CandyDiceCooldownAnchorFrame", "TOP", 0, 0) cb:SetCandyBarGroupGrowth("CandyDiceCooldowns", true) else cb:SetCandyBarGroupPoint("CandyDiceCooldowns", "TOP", "CandyDiceCooldownAnchorFrame", "BOTTOM", 0, 0) cb:SetCandyBarGroupGrowth("CandyDiceCooldowns", false) end end function CandyDice:AddNewAbility(name) local info = { colors = {"FFFFFF"}, buff = true, cooldown = true } local p = self.db.profile p.tracked[name] = info self:ScanSpellbook() self:UpdateCooldown(name, info) self:PushTrackedToOptions() end function CandyDice:Setup() -- Cheat for right now & just enable/disable if CandyDice:IsActive() then self:ToggleActive() self:ToggleActive() end end function CandyDice:RegisterTexture(name, file) self.textures[name] = file table.insert(self.texturenames, name) end function CandyDice:RegisterCooldownBar(ability, fgcolor, bgcolor, icon) --- register a CandyBar respecting all the CandyDice settings like show icon, texture, scaling, etc --- Returns the name of the bar local cbn = ability.."CD" local icon = CandyDice.db.profile.options.iconcd and icon or '' local o = self.db.profile.options local texture = self.textures[o.texture] -- register bar cb:RegisterCandyBar(cbn, 1, ability, icon) if fgcolor then cb:SetCandyBarColor(cbn, fgcolor, 0.5) cb:SetCandyBarBackgroundColor(cbn, bgcolor or fgcolor, 0.2) end cb:RegisterCandyBarWithGroup(cbn,"CandyDiceCooldowns") cb:SetCandyBarScale(cbn, o.scalecd) if texture then cb:SetCandyBarTexture(cbn, texture) end if CandyDice.db.profile.options.reversecd then cb:SetCandyBarReversed(cbn, true) end return cbn end function CandyDice:RegisterTimerBar(buff, fgcolor, bgcolor, icon) -- register a CandyBar respecting all the CandyDice settings for timers -- Returns the name of the bar local o = self.db.profile.options local icon = o.icon and icon or '' local texture = self.textures[o.texture] -- register bar cb:RegisterCandyBar(buff, 1, buff, icon) if fgcolor then cb:SetCandyBarColor(buff, fgcolor, 0.5) cb:SetCandyBarBackgroundColor(buff, bgcolor or fgcolor, 0.2) end cb:RegisterCandyBarWithGroup(buff,"CandyDice") cb:SetCandyBarScale(buff, o.scale) if texture then cb:SetCandyBarTexture(buff, texture) end return buff end