local L = LibStub("AceLocale-3.0"):GetLocale("IceHUD", false) local RollTheBones = IceCore_CreateClass(IceUnitBar) local IceHUD = _G.IceHUD local baseTime = 12 local gapPerComboPoint = 6 local maxComboPoints = 5 local rtbEndTime = 0 local rtbDuration = 0 local rtbCount = 0 local sixComboPointsTalentID = 19240 local behaviorDependsOnComboPoints = IceHUD.WowVer < 90000 local CurrMaxRtBDuration = 0 local PotentialRtBDuration = 0 local RtBBuffs = {199603, 193358, 193357, 193359, 199600, 193356} local RtBSet = {} for _, v in ipairs(RtBBuffs) do RtBSet[v] = true end local SPELL_POWER_COMBO_POINTS = SPELL_POWER_COMBO_POINTS if Enum and Enum.PowerType then SPELL_POWER_COMBO_POINTS = Enum.PowerType.ComboPoints end local GetSpellInfo = GetSpellInfo if not GetSpellInfo and C_Spell and C_Spell.GetSpellInfo then GetSpellInfo = function(spellID) if not spellID then return nil end local spellInfo = C_Spell.GetSpellInfo(spellID) if spellInfo then return spellInfo.name, nil, spellInfo.iconID, spellInfo.castTime, spellInfo.minRange, spellInfo.maxRange, spellInfo.spellID, spellInfo.originalIconID end end end local GetSpellName = GetSpellInfo if C_Spell and C_Spell.GetSpellName then GetSpellName = C_Spell.GetSpellName end -- Constructor -- function RollTheBones.prototype:init() RollTheBones.super.prototype.init(self, "RollTheBones", "player") self.moduleSettings = {} self.moduleSettings.desiredLerpTime = 0 self.moduleSettings.shouldAnimate = false self:SetDefaultColor("RollTheBones", 1, 0.6, 0.2) if behaviorDependsOnComboPoints then self:SetDefaultColor("RollTheBones2", 0.75, 1, 0.2) self:SetDefaultColor("RollTheBones3", 0.4, 1, 0.2) self:SetDefaultColor("RollTheBones4", 0.4, 1, 0.2) self:SetDefaultColor("RollTheBones5", 0.1, 1, 0.7) self:SetDefaultColor("RollTheBones6", 0.1, 1, 0.7) self:SetDefaultColor("RollTheBonesPotential", 1, 1, 1) end self.bTreatEmptyAsFull = true end -- 'Public' methods ----------------------------------------------------------- -- OVERRIDE function RollTheBones.prototype:Enable(core) RollTheBones.super.prototype.Enable(self, core) self:RegisterEvent("UNIT_AURA", "UpdateRollTheBones") if behaviorDependsOnComboPoints then self:RegisterEvent(IceHUD.UnitPowerEvent, "ComboPointsChanged") end if not self.moduleSettings.alwaysFullAlpha then self:Show(false) else self:UpdateRollTheBones() end self:SetBottomText1("") end function RollTheBones.prototype:Disable(core) RollTheBones.super.prototype.Disable(self, core) end function RollTheBones.prototype:ComboPointsChanged(...) if select('#', ...) >= 3 and select(1, ...) == IceHUD.UnitPowerEvent and select(3, ...) ~= "COMBO_POINTS" then return end self:TargetChanged() self:UpdateDurationBar() end -- OVERRIDE function RollTheBones.prototype:GetDefaultSettings() local settings = RollTheBones.super.prototype.GetDefaultSettings(self) settings["enabled"] = false settings["shouldAnimate"] = false settings["desiredLerpTime"] = nil settings["lowThreshold"] = 0 settings["side"] = IceCore.Side.Right settings["offset"] = 6 settings["upperText"]="RtB:" settings["showAsPercentOfMax"] = true settings["durationAlpha"] = 0.6 settings["usesDogTagStrings"] = false settings["lockLowerFontAlpha"] = false settings["lowerTextString"] = "" settings["lowerTextVisible"] = false settings["hideAnimationSettings"] = true settings["bAllowExpand"] = true settings["bShowWithNoTarget"] = true settings["bUseMultipleBuffColors"] = true return settings end -- OVERRIDE function RollTheBones.prototype:GetOptions() local opts = RollTheBones.super.prototype.GetOptions(self) opts["textSettings"].args["upperTextString"]["desc"] = "The text to display under this bar. # will be replaced with the number of Roll the Bones seconds remaining." opts["textSettings"].args["upperTextString"].hidden = false if behaviorDependsOnComboPoints then opts["showAsPercentOfMax"] = { type = 'toggle', name = L["Show bar as % of maximum"], desc = L["If this is checked, then the RtB buff time shows as a percent of the maximum attainable (taking set bonuses and talents into account). Otherwise, the bar always goes from full to empty when applying RtB no matter the duration."], get = function() return self.moduleSettings.showAsPercentOfMax end, set = function(info, v) self.moduleSettings.showAsPercentOfMax = v end, disabled = function() return not self.moduleSettings.enabled end } opts["durationAlpha"] = { type = "range", name = L["Potential RtB time bar alpha"], desc = L["What alpha value to use for the bar that displays how long your RtB will last if you activate it. (This gets multiplied by the bar's current alpha to stay in line with the bar on top of it)"], min = 0, max = 100, step = 5, get = function() return self.moduleSettings.durationAlpha * 100 end, set = function(info, v) self.moduleSettings.durationAlpha = v / 100.0 self:Redraw() end, disabled = function() return not self.moduleSettings.enabled end } opts["bUseMultipleBuffColors"] = { type = 'toggle', name = L["Use multiple buff colors"], desc = L["If this is checked, then the bar uses different colors depending on how many RtB buffs you have"], get = function() return self.moduleSettings.bUseMultipleBuffColors end, set = function(info, v) self.moduleSettings.bUseMultipleBuffColors = v self:Redraw() end, disabled = function() return not self.moduleSettings.enabled end, } end opts["bShowWithNoTarget"] = { type = 'toggle', name = L["Show with no target"], desc = L["Whether or not to display when you have no target selected but have combo points available"], get = function() return self.moduleSettings.bShowWithNoTarget end, set = function(info, v) self.moduleSettings.bShowWithNoTarget = v self:ComboPointsChanged() end, disabled = function() return not self.moduleSettings.enabled end, } return opts end function RollTheBones.prototype:CreateFrame() RollTheBones.super.prototype.CreateFrame(self) self:CreateDurationBar() end function RollTheBones.prototype:CreateDurationBar() if not behaviorDependsOnComboPoints then return end self.durationFrame = self:BarFactory(self.durationFrame, "BACKGROUND","ARTWORK", "Duration") -- Rokiyo: Do we need to call this here? self.CurrScale = 0 self.durationFrame.bar:SetVertexColor(self:GetColor("RollTheBonesPotential", self.moduleSettings.durationAlpha)) self.durationFrame.bar:SetHeight(0) self:UpdateBar(1, "undef") -- force update the bar...if we're in here, then either the UI was just loaded or the player is jacking with the options. -- either way, make sure the duration bar matches accordingly self:UpdateDurationBar() end function RollTheBones.prototype:RotateHorizontal() RollTheBones.super.prototype.RotateHorizontal(self) if self.durationFrame then self:RotateFrame(self.durationFrame) end end function RollTheBones.prototype:ResetRotation() RollTheBones.super.prototype.ResetRotation(self) if self.durationFrame and self.durationFrame.anim then self.durationFrame.anim:Stop() end end -- 'Protected' methods -------------------------------------------------------- function RollTheBones.prototype:GetBuffDuration(unitName, ids) local i = 1 local buff, _, type, duration, endTime, spellId if IceHUD.SpellFunctionsReturnRank then buff, _, _, _, type, duration, endTime, _, _, _, spellId = UnitBuff(unitName, i) else buff, _, _, type, duration, endTime, _, _, _, spellId = UnitBuff(unitName, i) end local realDuration, remaining, count local now = GetTime() count = 0 while buff do if (spellId and ids[spellId]) then if endTime then realDuration = duration remaining = endTime - now count = count + 1 end end i = i + 1; if IceHUD.SpellFunctionsReturnRank then buff, _, _, _, type, duration, endTime, _, _, _, spellId = UnitBuff(unitName, i) else buff, _, _, type, duration, endTime, _, _, _, spellId = UnitBuff(unitName, i) end end if count > 0 then return realDuration, remaining, count else return nil, nil, 0 end end function RollTheBones.prototype:MyOnUpdate() RollTheBones.super.prototype.MyOnUpdate(self) if self.bUpdateRtb then self:UpdateRollTheBones("internal", self.unit) end if self.target or self.moduleSettings.bShowWithNoTarget then self:UpdateDurationBar() end end local function RTBGetComboPoints(unit) return UnitPower(unit, SPELL_POWER_COMBO_POINTS) end -- use this to figure out if Roll the Bones is available or not. neither IsSpellKnown nor IsPlayerSpell are correct for it -- when SnD is known, but this is. local function HasSpell(id) local spell = GetSpellName(id) return spell == GetSpellName(spell) end local function ShouldHide() return not HasSpell(193316) end function RollTheBones.prototype:UpdateRollTheBones(event, unit) if unit and unit ~= self.unit then return end local now = GetTime() local remaining = nil local fromUpdate = event == "internal" if not fromUpdate then rtbDuration, remaining, rtbCount = self:GetBuffDuration(self.unit, RtBSet) if not remaining then rtbEndTime = 0 else rtbEndTime = remaining + now end end if rtbEndTime and rtbEndTime >= now then if not fromUpdate then self.bUpdateRtb = true end self:Show(true) if not remaining then remaining = rtbEndTime - now end local denominator = ((self.moduleSettings.showAsPercentOfMax and behaviorDependsOnComboPoints) and CurrMaxRtBDuration or rtbDuration) self:UpdateBar(denominator ~= 0 and remaining / denominator or 0, self:GetColorName(rtbCount)) else self:UpdateBar(0, "RollTheBones") if RTBGetComboPoints(self.unit) == 0 or (not UnitExists("target") and not self.moduleSettings.bShowWithNoTarget) or ShouldHide() then if self.bIsVisible then self.bUpdateRtb = nil end if not self.moduleSettings.alwaysFullAlpha or ShouldHide() then self:Show(false) end end end -- somewhat redundant, but we also need to check potential remaining time if (remaining ~= nil) or PotentialRtBDuration > 0 then local potText = behaviorDependsOnComboPoints and " (" .. PotentialRtBDuration .. ")" or "" self:SetBottomText1(self.moduleSettings.upperText .. tostring(floor(remaining or 0)) .. (self.moduleSettings.durationAlpha ~= 0 and potText or "")) end end function RollTheBones.prototype:GetColorName(count) if behaviorDependsOnComboPoints and self.moduleSettings.bUseMultipleBuffColors and count >= 2 then return "RollTheBones"..count else return "RollTheBones" end end function RollTheBones.prototype:TargetChanged() if self.moduleSettings.bShowWithNoTarget and RTBGetComboPoints(self.unit) > 0 then self.target = true else self.target = UnitExists("target") end self:Update(self.unit) self:UpdateDurationBar() self:UpdateRollTheBones() end function RollTheBones.prototype:UpdateDurationBar(event, unit) if not self.durationFrame then return end if unit and unit ~= self.unit then return end local points = RTBGetComboPoints(self.unit) if UnitPowerMax then CurrMaxRtBDuration = self:GetMaxBuffTime(UnitPowerMax(self.unit, SPELL_POWER_COMBO_POINTS)) else -- check for Deeper Stratagem local _, _, _, DeeperStratagem = GetTalentInfoByID(sixComboPointsTalentID, GetActiveSpecGroup()) if DeeperStratagem then -- first, set the cached upper limit of RtB duration CurrMaxRtBDuration = self:GetMaxBuffTime(maxComboPoints + 1) else CurrMaxRtBDuration = self:GetMaxBuffTime(maxComboPoints) end end if event then self:UpdateRollTheBones() end -- player doesn't want to show the percent of max or the alpha is zeroed out, so don't bother with the duration bar if not self.moduleSettings.showAsPercentOfMax or self.moduleSettings.durationAlpha == 0 or (points == 0 and not self:IsVisible()) or ShouldHide() then self.durationFrame:Hide() return end self.durationFrame:Show() -- if we have combo points and a target selected, go ahead and show the bar so the duration bar can be seen if points > 0 and (UnitExists("target") or self.moduleSettings.bShowWithNoTarget) then self:Show(true) end if self.moduleSettings.durationAlpha > 0 then PotentialRtBDuration = self:GetMaxBuffTime(points) -- compute the scale from the current number of combo points local scale = IceHUD:Clamp(PotentialRtBDuration / CurrMaxRtBDuration, 0, 1) -- sadly, animation uses bar-local variables so we can't use the animation for 2 bar textures on the same bar element if (self.moduleSettings.reverse) then scale = 1 - scale end self.durationFrame.bar:SetVertexColor(self:GetColor("RollTheBonesPotential", self.moduleSettings.durationAlpha)) self:SetBarCoord(self.durationFrame, scale) end if rtbEndTime < GetTime() then local potText = " (" .. PotentialRtBDuration .. ")" self:SetBottomText1(self.moduleSettings.upperText .. "0" .. (self.moduleSettings.durationAlpha > 0 and potText or "")) end end function RollTheBones.prototype:GetMaxBuffTime(numComboPoints) local maxduration if numComboPoints == 0 then return 0 end maxduration = baseTime + ((numComboPoints - 1) * gapPerComboPoint) return maxduration end function RollTheBones.prototype:GetItemIdFromItemLink(linkStr) local itemId local _ if linkStr then _, itemId, _, _, _, _, _, _, _ = strsplit(":", linkStr) end return itemId or 0 end function RollTheBones.prototype:IsItemIdInList(itemId, list) for i=1,#list do if string.match(itemId, list[i]) then return true end end return false end function RollTheBones.prototype:OutCombat() RollTheBones.super.prototype.OutCombat(self) self:UpdateRollTheBones() end local _, unitClass = UnitClass("player") -- Load us up if unitClass == "ROGUE" and IceHUD.WowVer >= 70000 then IceHUD.RollTheBones = RollTheBones:new() end