-- ForeverUI: the player cast bar. Art: interface/castingbar/uicastingbar.blp (ui-castingbar-*). -- Data as in 3.3.5 CastingBarFrame.lua: UnitCastingInfo / UnitChannelInfo (times in ms) and -- the UNIT_SPELLCAST_* events. A cast fills, a channel drains. -- The bar is its own layout system, moved apart from the unit frames, as in camelot's editor. local BAR_WIDTH, BAR_HEIGHT = 214, 16 local FILL_WIDTH = 209 local L = ForeverUI.L local FILL = { standard = "ui-castingbar-filling-standard", channel = "ui-castingbar-filling-channel", uninterruptible = "ui-castingbar-uninterruptable", interrupted = "ui-castingbar-interrupted", } local HOLD_AFTER_END = 1.0 local frame = CreateFrame("Frame", "ForeverUICastBar", UIParent) frame:SetWidth(BAR_WIDTH) frame:SetHeight(BAR_HEIGHT) frame:SetFrameStrata("MEDIUM") frame:Hide() local background = frame:CreateTexture(nil, "BACKGROUND") ForeverUI.SetAtlas(background, "ui-castingbar-background") background:SetPoint("CENTER") local fill = frame:CreateTexture(nil, "BORDER") fill:SetPoint("LEFT", background, "LEFT", 0, 0) local border = frame:CreateTexture(nil, "ARTWORK") ForeverUI.SetAtlas(border, "ui-castingbar-frame") border:SetPoint("CENTER") -- camelot's BorderShield: 29 x 33, TOPLEFT (-27, 4) of its 208 x 11 bar, centred in our frame local SHIELD = { 29, 33, -24, 1.5 } local shield = frame:CreateTexture(nil, "OVERLAY") ForeverUI.SetAtlas(shield, "ui-castingbar-shield", true) shield:SetWidth(SHIELD[1]) shield:SetHeight(SHIELD[2]) shield:SetPoint("TOPLEFT", frame, "TOPLEFT", SHIELD[3], SHIELD[4]) shield:Hide() local spellText = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall") spellText:SetPoint("CENTER", frame, "CENTER", 0, 0) local timeText = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall") timeText:SetPoint("RIGHT", frame, "RIGHT", -6, 0) frame.background = background frame.fill = fill frame.border = border frame.shield = shield frame.spellText = spellText frame.timeText = timeText local function stopBar() frame.casting = false frame.channeling = false frame.holdUntil = nil frame:Hide() end -- Shows the failed bar for HOLD_AFTER_END seconds. -- atlasKey: key in FILL; message: text shown on the bar local function showFailure(atlasKey, message) frame.casting = false frame.channeling = false frame.holdUntil = GetTime() + HOLD_AFTER_END ForeverUI.SetAtlasFill(fill, FILL[atlasKey], 1, FILL_WIDTH) spellText:SetText(message or "") timeText:SetText("") shield:Hide() frame:Show() end -- Starts the bar from the client's current cast. channel: true for a channeled spell local function startCast(channel) local name, _subtext, text, texture, startTime, endTime, _isTrade, castID, notInterruptible if channel then name, _subtext, text, texture, startTime, endTime, _isTrade, notInterruptible = UnitChannelInfo("player") else name, _subtext, text, texture, startTime, endTime, _isTrade, castID, notInterruptible = UnitCastingInfo("player") end if not name then stopBar() return end frame.casting = not channel frame.channeling = channel and true or false frame.castID = castID frame.startTime = startTime / 1000 frame.endTime = endTime / 1000 frame.holdUntil = nil frame.fillKey = notInterruptible and "uninterruptible" or (channel and "channel" or "standard") spellText:SetText(text or name) if notInterruptible then shield:Show() else shield:Hide() end frame:Show() end frame:SetScript("OnEvent", function(_self, event, unit, _spell, _rank, castID) if event == "PLAYER_ENTERING_WORLD" then ForeverUI.Suppress(CastingBarFrame) stopBar() return end if unit ~= "player" then return end if event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_DELAYED" then startCast(false) elseif event == "UNIT_SPELLCAST_CHANNEL_START" or event == "UNIT_SPELLCAST_CHANNEL_UPDATE" then startCast(true) elseif event == "UNIT_SPELLCAST_STOP" then -- As 3.3.5's CastingBarFrame_OnEvent: only the cast on the bar (its castID), and not -- after a failure, whose red bar stays HOLD_AFTER_END if frame.casting and castID == frame.castID then stopBar() end elseif event == "UNIT_SPELLCAST_CHANNEL_STOP" then if frame.channeling then stopBar() end elseif event == "UNIT_SPELLCAST_FAILED" or event == "UNIT_SPELLCAST_INTERRUPTED" then -- A refused attempt (a key pressed again during a cast, an instant spell out of range) -- carries another castID, or comes with no cast on the bar if frame.casting and castID == frame.castID then showFailure("interrupted", event == "UNIT_SPELLCAST_FAILED" and FAILED or INTERRUPTED) end elseif event == "UNIT_SPELLCAST_INTERRUPTIBLE" then shield:Hide() frame.fillKey = frame.channeling and "channel" or "standard" elseif event == "UNIT_SPELLCAST_NOT_INTERRUPTIBLE" then shield:Show() frame.fillKey = "uninterruptible" end end) frame:SetScript("OnUpdate", function(self) if self.holdUntil then if GetTime() > self.holdUntil then stopBar() end return end if not (self.casting or self.channeling) then return end local now = GetTime() local total = self.endTime - self.startTime if total <= 0 then stopBar() return end local elapsed = now - self.startTime if elapsed < 0 then elapsed = 0 end local fraction = elapsed / total if fraction > 1 then fraction = 1 end -- A cast fills, a channel drains. if self.channeling then fraction = 1 - fraction end ForeverUI.SetAtlasFill(fill, FILL[self.fillKey or "standard"], fraction, FILL_WIDTH) timeText:SetText(string.format("%.1f", math.max(0, self.endTime - now))) if now >= self.endTime then stopBar() end end) frame:RegisterEvent("PLAYER_ENTERING_WORLD") frame:RegisterEvent("UNIT_SPELLCAST_START") frame:RegisterEvent("UNIT_SPELLCAST_STOP") frame:RegisterEvent("UNIT_SPELLCAST_DELAYED") frame:RegisterEvent("UNIT_SPELLCAST_FAILED") frame:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED") frame:RegisterEvent("UNIT_SPELLCAST_INTERRUPTIBLE") frame:RegisterEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE") frame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START") frame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE") frame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP") ForeverUI.CastBar = frame ForeverUI.Layout.Register(frame, "castbar", L.CASTBAR_EDIT_LABEL, "BOTTOM", "BOTTOM", 0, 190)