if select(6, GetAddOnInfo("PitBull4_" .. (debugstack():match("[o%.][d%.][u%.]les\\(.-)\\") or ""))) ~= "MISSING" then return end -- Upvalues local min = math.min -- CONSTANTS local ALPHA_MODIFIER = 0.6 -- Multiplied to the main CastBar's alpha at any point of time. local DEFAULT_COLOR = {1, 0, 0, 1} local ADJUSTMENT_DIVISOR_FOR_EVENTS = 1e3 -- Events return different timestamps than GetTime. GetTime's is more useful. local ADJUSTMENT_DIVISOR_FOR_QUEUES = 1e3 local MAX_GCD_TIME = 1.5 local DEFAULT_QUEUE_TIME = 300 -- Pseudo global initialization local send_time = 0 local start_time = 0 local end_time = 0 local lag_time = 0 local max_time = 0 local is_channel = nil local queue_time = DEFAULT_QUEUE_TIME local show_queue = true local show_gcd = true local PitBull4 = _G.PitBull4 if not PitBull4 then error("PitBull4_CastBarLatency requires PitBull4") end local GetTime = _G.GetTime local L = PitBull4.L local PitBull4_CastBar = PitBull4:GetModule("CastBar", true) if not PitBull4_CastBar then error(L["PitBull4_CastBarLatency requires the CastBar module"]) end local PitBull4_CastBarLatency = PitBull4:NewModule("CastBarLatency", "AceEvent-3.0") local self = PitBull4_CastBarLatency PitBull4_CastBarLatency:SetModuleType("custom") PitBull4_CastBarLatency:SetName(L["Cast bar latency"]) PitBull4_CastBarLatency:SetDescription(L["Show a guessed safe zone at the end of the player castbar."]) PitBull4_CastBarLatency:SetDefaults({},{ show_queue = true, queue_time = DEFAULT_QUEUE_TIME, show_gcd = true, latency_color = DEFAULT_COLOR }) PitBull4_CastBarLatency:SetLayoutOptionsFunction(function(self) end) -- Create a timer frame with an onupdate to ensure updates of our bar.. local timerFrame = CreateFrame("Frame") timerFrame:Hide() timerFrame:SetScript("OnUpdate", function() -- No lag time so no reason to iterate the frames. if lag_time == 0 then return end -- Loop thru ALL PitBull Frames... for frame in PitBull4:IterateFrames() do local unit = frame.unit if unit and UnitIsUnit(unit,"player") then -- ... but only force updates for frames representing the player PitBull4_CastBarLatency:Update(frame) end end end) function PitBull4_CastBarLatency:UNIT_SPELLCAST_START(event, unit, spell, spellrank) if unit ~= 'player' then return end -- Try to determine GCD local gcd_time = 0 if show_gcd and spell then local _, duration = GetSpellCooldown(spell) if duration and duration > 0 and duration <= MAX_GCD_TIME then gcd_time = duration end end local name, _, _, _, new_start, new_end, _, _ = UnitCastingInfo(unit) if not name then return end end_time = new_end / ADJUSTMENT_DIVISOR_FOR_EVENTS start_time = new_start / ADJUSTMENT_DIVISOR_FOR_EVENTS max_time = end_time - start_time lag_time = start_time - send_time is_channel = nil local queue_val = queue_time / ADJUSTMENT_DIVISOR_FOR_QUEUES if show_queue and (lag_time < queue_val) then lag_time = queue_val end -- GCD handling if show_gcd then local gcd_rest = max_time - gcd_time if gcd_rest < 0 then gcd_rest = 0 end lag_time = math.min(lag_time, gcd_rest) end --print(string.format("DBG-USStart: GCD_time: %s; MAX_time: %s; LAG_time: %s", tostring(gcd_time), tostring(max_time), tostring(lag_time))) end function PitBull4_CastBarLatency:UNIT_SPELLCAST_CHANNEL_START(event, unit, spell, spellrank) if unit ~= 'player' then return end -- Try to determine GCD local gcd_time = 0 if show_gcd and spell then local _, duration = GetSpellCooldown(spell) if duration and duration > 0 and duration <= MAX_GCD_TIME then gcd_time = duration end end local name, _, _, _, new_start, new_end, _, _ = UnitChannelInfo(unit) if not name then return end end_time = new_end / ADJUSTMENT_DIVISOR_FOR_EVENTS start_time = new_start / ADJUSTMENT_DIVISOR_FOR_EVENTS max_time = end_time - start_time lag_time = start_time - send_time is_channel = true local queue_time = queue_time local queue_val = queue_time / ADJUSTMENT_DIVISOR_FOR_QUEUES if show_queue and (lag_time < queue_val) then lag_time = queue_val end -- GCD handling if show_gcd then local gcd_rest = max_time - gcd_time if gcd_rest < 0 then gcd_rest = 0 end lag_time = math.min(lag_time, gcd_rest) end end function PitBull4_CastBarLatency:UNIT_SPELLCAST_SENT(event, unit, spell, spellrank) if unit ~= 'player' then return end send_time = GetTime() end function PitBull4_CastBarLatency:UNIT_SPELLCAST_STOP(event, unit, spell) if unit ~= 'player' then return end -- Ignore SPELLCAST_SUCCEEDED when we're channeling if event == 'UNIT_SPELLCAST_SUCCEEDED' and is_channel then return end -- Clear the lag_time when we're not casting lag_time = 0 for frame in PitBull4:IterateFrames() do self:ClearFrame(frame) end end function PitBull4_CastBarLatency:OnEnable() timerFrame:Show() self:RegisterEvent("UNIT_SPELLCAST_SENT") self:RegisterEvent("UNIT_SPELLCAST_START") self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START") self:RegisterEvent("UNIT_SPELLCAST_STOP") self:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED","UNIT_SPELLCAST_STOP") self:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED","UNIT_SPELLCAST_STOP") self:RegisterEvent("UNIT_SPELLCAST_FAILED","UNIT_SPELLCAST_STOP") self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP","UNIT_SPELLCAST_STOP") queue_time = self.db.profile.global.queue_time show_queue = self.db.profile.global.show_queue show_gcd = self.db.profile.global.show_gcd end function PitBull4_CastBarLatency:OnDisable() timerFrame:Hide() end function PitBull4_CastBarLatency:UpdateFrame(frame) local unit = frame.unit if not unit or not UnitIsUnit(unit,"player") then -- this frame does not represent the player so we remove ourselves return self:ClearFrame(frame) end local bar = frame.CastBar if not bar or lag_time == 0 then -- no cast bar on this frame or no lag so we remove ourselves.. return self:ClearFrame(frame) end local safe_zone = frame.CastBarLatency if not safe_zone then -- Our own Bar doesn't exist yet, create it safe_zone = PitBull4.Controls.MakeBetterStatusBar(frame) -- Populate the new bar with default look attributes.. safe_zone:SetTexture(bar:GetTexture()) -- Might need to be moved down to update properly safe_zone:SetValue(1) safe_zone:SetColor(unpack(self.db.profile.global.latency_color)) safe_zone:SetBackgroundAlpha(0) -- so we can actually see the main bar behind us frame.CastBarLatency = safe_zone end -- Calculate the how much of the entire casttime will be lost to lag local safe_zone_percent = 0 if max_time > 0 then safe_zone_percent = lag_time / max_time end if safe_zone_percent > 1 then safe_zone_percent = 1 end safe_zone:ClearAllPoints() safe_zone:SetAllPoints(bar) -- Find and apply the main castbar's alpha and apply it with a modifier. Must be dynamic for fadouts. local bar_alpha = select(4,PitBull4_CastBar:GetColor(frame, 'player')) if bar_alpha then safe_zone:SetAlpha(bar_alpha*ALPHA_MODIFIER) end -- Find and apply user settings to our bar safe_zone:SetColor(unpack(self.db.profile.global.latency_color)) safe_zone:SetFrameLevel( bar:GetFrameLevel()+1 ) local reverse = not bar:GetReverse() local icon_position = not bar:GetIconPosition() safe_zone:SetOrientation( bar:GetOrientation() ) if bar:GetDeficit() then reverse = not reverse icon_position = not icon_position end if is_channel then -- channelling casts are flipped... again... reverse = not reverse icon_position = not icon_position end safe_zone:SetReverse(reverse) -- Apply our calculated size safe_zone:SetValue(safe_zone_percent) safe_zone:Show() if bar.icon then safe_zone:SetIcon("") safe_zone:SetIconPosition(icon_position) else safe_zone:SetIcon(nil) end return false end function PitBull4_CastBarLatency:ClearFrame(frame) if not frame.CastBarLatency then return false end frame.CastBarLatency = frame.CastBarLatency:Delete() return false end PitBull4_CastBarLatency.OnHide = PitBull4_CastBarLatency.ClearFrame PitBull4_CastBarLatency:SetGlobalOptionsFunction(function(self) return 'show_gcd', { type = 'toggle', width = 'full', name = L['Include GCD lockout'], desc = L['This assumes spells cannot be (locally) triggered before the GCD has run out.'], get = function(info) local id = info[#info] return self.db.profile.global[id] end, set = function(info, value) local id = info[#info] self.db.profile.global[id] = value show_gcd = value end, }, 'show_queue', { type = 'toggle', width = 'full', name = L['Include spell queue time'], desc = L['Always add the spell queue time to the shown latency.'], get = function(info) local id = info[#info] return self.db.profile.global[id] end, set = function(info, value) local id = info[#info] self.db.profile.global[id] = value show_queue = value end, }, 'queue_time', { type = 'range', width = 'double', name = L['Queue time'], desc = string.format(L["Fixed time at then end of a running cast where you are able to cast the next spell. Default is %s\nWARNING: Do not change this unless you know exactly what you're doing!"], tostring(DEFAULT_QUEUE_TIME)), min = 0, max = 1000, step = 1, get = function(info) local id = info[#info] return self.db.profile.global[id] end, set = function(info, value) local id = info[#info] self.db.profile.global[id] = value queue_time = value end, } end) PitBull4_CastBarLatency:SetColorOptionsFunction(function(self) return 'latency_color', { type = 'color', name = L['Latency'], desc = L['Sets which color the latency overlay on the castbar is using.'], get = function(info) return unpack(self.db.profile.global.latency_color) end, set = function(info, r, g, b, a) self.db.profile.global.latency_color = {r, g, b, 1} -- alpha is hardcoded for now because it must be calculated dynamically from the castbar in UpdateFrame() self:UpdateAll() end, }, function(info) self.db.profile.global.latency_color = DEFAULT_COLOR end end)