SFrames.Player = {} local _A = SFrames.ActiveTheme local CLASS_NAME_ZH = { WARRIOR = "\230\136\152\229\163\171", MAGE = "\230\179\149\229\184\136", ROGUE = "\230\189\156\232\161\140\232\128\133", DRUID = "\229\190\183\233\178\129\228\188\138", HUNTER = "\231\140\142\228\186\186", SHAMAN = "\232\144\168\230\187\161\231\165\173\229\143\184", PRIEST = "\231\137\167\229\184\136", WARLOCK = "\230\156\175\229\163\171", PALADIN = "\229\156\163\233\170\145\229\163\171", } local function GetChineseClassName(classToken, localizedClass) if classToken and CLASS_NAME_ZH[classToken] then return CLASS_NAME_ZH[classToken] end return localizedClass or "" end local function GetIncomingHeals(unit) if not (ShaguTweaks and ShaguTweaks.libpredict and ShaguTweaks.libpredict.UnitGetIncomingHeals) then return 0, 0, 0 end local libpredict = ShaguTweaks.libpredict if libpredict.UnitGetIncomingHealsBreakdown then local ok, total, mine, others = pcall(function() return libpredict:UnitGetIncomingHealsBreakdown(unit, UnitName("player")) end) if ok then total = math.max(0, tonumber(total) or 0) mine = math.max(0, tonumber(mine) or 0) others = math.max(0, tonumber(others) or 0) return total, mine, others end end local ok, amount = pcall(function() return libpredict:UnitGetIncomingHeals(unit) end) if not ok then return 0, 0, 0 end amount = tonumber(amount) or 0 if amount < 0 then amount = 0 end return amount, 0, amount end local function Clamp(value, minValue, maxValue) if value < minValue then return minValue end if value > maxValue then return maxValue end return value end function SFrames.Player:GetConfig() local db = SFramesDB or {} local width = tonumber(db.playerFrameWidth) or SFrames.Config.width or 220 width = Clamp(math.floor(width + 0.5), 170, 420) local portraitWidth = tonumber(db.playerPortraitWidth) or SFrames.Config.portraitWidth or 50 portraitWidth = Clamp(math.floor(portraitWidth + 0.5), 32, 95) if portraitWidth > width - 90 then portraitWidth = width - 90 end local healthHeight = tonumber(db.playerHealthHeight) or 38 healthHeight = Clamp(math.floor(healthHeight + 0.5), 14, 80) local powerHeight = tonumber(db.playerPowerHeight) or 9 powerHeight = Clamp(math.floor(powerHeight + 0.5), 6, 40) local height = healthHeight + powerHeight + 4 height = Clamp(height, 30, 140) local nameFont = tonumber(db.playerNameFontSize) or 10 nameFont = Clamp(math.floor(nameFont + 0.5), 8, 18) local valueFont = tonumber(db.playerValueFontSize) or 10 valueFont = Clamp(math.floor(valueFont + 0.5), 8, 18) local frameScale = tonumber(db.playerFrameScale) or 1 frameScale = Clamp(frameScale, 0.7, 1.8) return { width = width, height = height, portraitWidth = portraitWidth, healthHeight = healthHeight, powerHeight = powerHeight, nameFont = nameFont, valueFont = valueFont, scale = frameScale, } end function SFrames.Player:ApplyConfig() if not self.frame then return end local cfg = self:GetConfig() local f = self.frame f:SetScale(cfg.scale) f:SetWidth(cfg.width) f:SetHeight(cfg.height) if f.portrait then f.portrait:SetWidth(cfg.portraitWidth) f.portrait:SetHeight(cfg.height - 2) end if f.portraitBG then f.portraitBG:ClearAllPoints() f.portraitBG:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0) f.portraitBG:SetPoint("BOTTOMRIGHT", f.portrait, "BOTTOMRIGHT", 1, -1) end if f.health then f.health:ClearAllPoints() f.health:SetPoint("TOPLEFT", f.portrait, "TOPRIGHT", 1, 0) f.health:SetPoint("TOPRIGHT", f, "TOPRIGHT", -1, -1) f.health:SetHeight(cfg.healthHeight) end if f.healthBGFrame then f.healthBGFrame:ClearAllPoints() f.healthBGFrame:SetPoint("TOPLEFT", f.health, "TOPLEFT", -1, 1) f.healthBGFrame:SetPoint("BOTTOMRIGHT", f.health, "BOTTOMRIGHT", 1, -1) end if f.power then f.power:ClearAllPoints() f.power:SetPoint("TOPLEFT", f.health, "BOTTOMLEFT", 0, -1) f.power:SetPoint("TOPRIGHT", f.health, "BOTTOMRIGHT", 0, 0) f.power:SetHeight(cfg.powerHeight) end if f.powerBGFrame then f.powerBGFrame:ClearAllPoints() f.powerBGFrame:SetPoint("TOPLEFT", f.power, "TOPLEFT", -1, 1) f.powerBGFrame:SetPoint("BOTTOMRIGHT", f.power, "BOTTOMRIGHT", 1, -1) end local outline = (SFrames and SFrames.Media and SFrames.Media.fontOutline) or "OUTLINE" local fontPath = SFrames:GetFont() if f.nameText then f.nameText:SetFont(fontPath, cfg.nameFont, outline) end if f.healthText then f.healthText:SetFont(fontPath, cfg.valueFont, outline) end if f.powerText then f.powerText:SetFont(fontPath, cfg.valueFont, outline) end if f.manaText then local manaFont = cfg.valueFont - 1 if manaFont < 8 then manaFont = 8 end f.manaText:SetFont(fontPath, manaFont, outline) end if f.zLetters then for i = 1, 3 do if f.zLetters[i] and f.zLetters[i].text then f.zLetters[i].text:SetFont(fontPath, 8 + (i - 1) * 3, "OUTLINE") end end end self:UpdateAll() end function SFrames.Player:Initialize() local f = CreateFrame("Button", "SFramesPlayerFrame", UIParent) f:SetWidth(SFrames.Config.width) f:SetHeight(SFrames.Config.height) if SFramesDB and SFramesDB.Positions and SFramesDB.Positions["PlayerFrame"] then local pos = SFramesDB.Positions["PlayerFrame"] f:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs, pos.yOfs) else f:SetPoint("CENTER", UIParent, "CENTER", -200, -100) end local frameScale = (SFramesDB and type(SFramesDB.playerFrameScale) == "number") and SFramesDB.playerFrameScale or 1 f:SetScale(frameScale) -- Make it movable f:SetMovable(true) f:EnableMouse(true) f:RegisterForDrag("LeftButton") f:SetScript("OnDragStart", function() if IsAltKeyDown() or SFrames.isUnlocked then f:StartMoving() end end) f:SetScript("OnDragStop", function() f:StopMovingOrSizing() if not SFramesDB then SFramesDB = {} end if not SFramesDB.Positions then SFramesDB.Positions = {} end local point, relativeTo, relativePoint, xOfs, yOfs = f:GetPoint() SFramesDB.Positions["PlayerFrame"] = { point = point, relativePoint = relativePoint, xOfs = xOfs, yOfs = yOfs } end) -- Register clicks for targeting f:RegisterForClicks("LeftButtonUp", "RightButtonUp") f:SetScript("OnClick", function() if arg1 == "LeftButton" then TargetUnit("player") else ToggleDropDownMenu(1, nil, PlayerFrameDropDown, this:GetName(), 106, 27) end end) -- Base Backdrop SFrames:CreateUnitBackdrop(f) -- 3D Portrait local pWidth = SFrames.Config.portraitWidth f.portrait = CreateFrame("PlayerModel", nil, f) f.portrait:SetWidth(pWidth) f.portrait:SetHeight(SFrames.Config.height - 2) f.portrait:SetPoint("LEFT", f, "LEFT", 1, 0) f.portrait:SetUnit("player") f.portrait:SetCamera(0) f.portrait:SetPosition(-1.0, 0, 0) -- We need a backdrop for the portrait to separate it from health bar local pbg = CreateFrame("Frame", nil, f) pbg:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0) pbg:SetPoint("BOTTOMRIGHT", f.portrait, "BOTTOMRIGHT", 1, -1) pbg:SetFrameLevel(f:GetFrameLevel()) SFrames:CreateUnitBackdrop(pbg) f.portraitBG = pbg -- Health Bar f.health = SFrames:CreateStatusBar(f, "SFramesPlayerHealth") f.health:SetPoint("TOPLEFT", f.portrait, "TOPRIGHT", 1, 0) f.health:SetPoint("TOPRIGHT", f, "TOPRIGHT", -1, -1) f.health:SetHeight((SFrames.Config.height - 2) * 0.82 - 1) -- 82% height, minus 1px gap f.health:SetMinMaxValues(0, 100) -- Health Backdrop local hbg = CreateFrame("Frame", nil, f) hbg:SetPoint("TOPLEFT", f.health, "TOPLEFT", -1, 1) hbg:SetPoint("BOTTOMRIGHT", f.health, "BOTTOMRIGHT", 1, -1) hbg:SetFrameLevel(f:GetFrameLevel() - 1) SFrames:CreateUnitBackdrop(hbg) f.healthBGFrame = hbg -- Add a dark backdrop behind the health texture f.health.bg = f.health:CreateTexture(nil, "BACKGROUND") f.health.bg:SetAllPoints() f.health.bg:SetTexture(SFrames:GetTexture()) f.health.bg:SetVertexColor(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 1) -- Heal prediction overlay (incoming heals) f.health.healPredMine = f.health:CreateTexture(nil, "OVERLAY") f.health.healPredMine:SetTexture(SFrames:GetTexture()) f.health.healPredMine:SetVertexColor(0.4, 1.0, 0.55, 0.78) f.health.healPredMine:Hide() f.health.healPredOther = f.health:CreateTexture(nil, "OVERLAY") f.health.healPredOther:SetTexture(SFrames:GetTexture()) f.health.healPredOther:SetVertexColor(0.2, 0.9, 0.35, 0.5) f.health.healPredOther:Hide() -- Power Bar f.power = SFrames:CreateStatusBar(f, "SFramesPlayerPower") f.power:SetPoint("TOPLEFT", f.health, "BOTTOMLEFT", 0, -1) f.power:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -1, 1) f.power:SetMinMaxValues(0, 100) -- Power Backdrop local powerbg = CreateFrame("Frame", nil, f) powerbg:SetPoint("TOPLEFT", f.power, "TOPLEFT", -1, 1) powerbg:SetPoint("BOTTOMRIGHT", f.power, "BOTTOMRIGHT", 1, -1) powerbg:SetFrameLevel(f:GetFrameLevel() - 1) SFrames:CreateUnitBackdrop(powerbg) f.powerBGFrame = powerbg -- Add a dark backdrop behind the power texture f.power.bg = f.power:CreateTexture(nil, "BACKGROUND") f.power.bg:SetAllPoints() f.power.bg:SetTexture(SFrames:GetTexture()) f.power.bg:SetVertexColor(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 1) -- Five-second rule ticker (mana regen delay indicator) f.power.fsrGlow = f.power:CreateTexture(nil, "OVERLAY") f.power.fsrGlow:SetTexture("Interface\\CastingBar\\UI-CastingBar-Spark") f.power.fsrGlow:SetVertexColor(0.62, 0.90, 1.0, 0.52) f.power.fsrGlow:SetBlendMode("ADD") pcall(function() f.power.fsrGlow:SetDrawLayer("OVERLAY", 5) end) f.power.fsrGlow:Hide() -- Texts f.nameText = SFrames:CreateFontString(f.health, 10, "LEFT") f.nameText:SetPoint("LEFT", f.health, "LEFT", 6, 0) f.healthText = SFrames:CreateFontString(f.health, 10, "RIGHT") f.healthText:SetPoint("RIGHT", f.health, "RIGHT", -6, 0) f.powerText = SFrames:CreateFontString(f.power, 10, "RIGHT") f.powerText:SetPoint("RIGHT", f.power, "RIGHT", -6, 0) -- Extra mana text for shapeshift druids (show blue mana while rage/energy is active) f.manaText = SFrames:CreateFontString(f.power, 9, "LEFT") f.manaText:SetPoint("LEFT", f.power, "LEFT", 6, 0) f.manaText:SetTextColor(0.30, 0.65, 1.0) f.manaText:Hide() f.manaBar = CreateFrame("StatusBar", nil, f) f.manaBar:SetHeight(4) f.manaBar:SetPoint("TOPLEFT", f.power, "BOTTOMLEFT", 0, -1) f.manaBar:SetPoint("TOPRIGHT", f.power, "BOTTOMRIGHT", 0, -1) f.manaBar:SetStatusBarTexture(SFrames:GetTexture()) f.manaBar:SetStatusBarColor(0.30, 0.65, 1.0, 0.90) f.manaBar:SetMinMaxValues(0, 100) f.manaBar:SetValue(0) f.manaBar:SetFrameLevel(f:GetFrameLevel() + 1) f.manaBar.bg = f.manaBar:CreateTexture(nil, "BACKGROUND") f.manaBar.bg:SetAllPoints() f.manaBar.bg:SetTexture(SFrames:GetTexture()) f.manaBar.bg:SetVertexColor(0.05, 0.10, 0.20, 0.7) f.manaBar:Hide() -- Outline/shadow setup for text to make it pop f.nameText:SetShadowColor(0, 0, 0, 1) f.nameText:SetShadowOffset(1, -1) f.healthText:SetShadowColor(0, 0, 0, 1) f.healthText:SetShadowOffset(1, -1) f.powerText:SetShadowColor(0, 0, 0, 1) f.powerText:SetShadowOffset(1, -1) f.manaText:SetShadowColor(0, 0, 0, 1) f.manaText:SetShadowOffset(1, -1) -- Resting Indicator (animated zzz on portrait) local restOverlay = CreateFrame("Frame", nil, f) restOverlay:SetFrameLevel((f:GetFrameLevel() or 0) + 6) restOverlay:SetWidth(pWidth) restOverlay:SetHeight(SFrames.Config.height) restOverlay:SetPoint("CENTER", f.portrait, "CENTER", 0, 0) f.restOverlay = restOverlay local zLetters = {} for i = 1, 3 do local zf = CreateFrame("Frame", nil, restOverlay) zf:SetWidth(16) zf:SetHeight(16) local zt = zf:CreateFontString(nil, "OVERLAY") zt:SetFont(SFrames:GetFont(), 8 + (i - 1) * 3, "OUTLINE") zt:SetText("z") zt:SetTextColor(0.85, 0.85, 1.0) zt:SetShadowColor(0, 0, 0, 0.8) zt:SetShadowOffset(1, -1) zt:SetAllPoints(zf) zf.text = zt zf.phase = (i - 1) * 1.2 zf.baseX = (i - 1) * 6 - 2 zf.baseY = (i - 1) * 5 zf:SetPoint("BOTTOMLEFT", restOverlay, "CENTER", zf.baseX, zf.baseY - 4) zLetters[i] = zf end f.zLetters = zLetters local restElapsed = 0 restOverlay:SetScript("OnUpdate", function() restElapsed = restElapsed + arg1 for idx = 1, 3 do local zf = zLetters[idx] local t = math.mod(restElapsed + zf.phase, 3.6) local ratio = t / 3.6 local floatY = ratio * 14 local alpha if ratio < 0.15 then alpha = ratio / 0.15 elseif ratio < 0.7 then alpha = 1.0 else alpha = 1.0 - (ratio - 0.7) / 0.3 end if alpha < 0 then alpha = 0 end if alpha > 1 then alpha = 1 end zf.text:SetAlpha(alpha) zf:ClearAllPoints() zf:SetPoint("BOTTOMLEFT", restOverlay, "CENTER", zf.baseX, zf.baseY - 4 + floatY) end end) restOverlay:Hide() -- Class Icon Badge (overlaid on portrait, top-right corner with 1/3 outside) f.classIcon = SFrames:CreateClassIcon(f, 16) f.classIcon.overlay:SetPoint("CENTER", f.portrait, "TOPRIGHT", 0, 0) -- Party Leader Icon local leaderOvr = CreateFrame("Frame", nil, f) leaderOvr:SetFrameLevel((f:GetFrameLevel() or 0) + 4) leaderOvr:SetAllPoints(f) f.leaderIcon = leaderOvr:CreateTexture(nil, "OVERLAY") f.leaderIcon:SetTexture("Interface\\GroupFrame\\UI-Group-LeaderIcon") f.leaderIcon:SetWidth(16) f.leaderIcon:SetHeight(16) f.leaderIcon:SetPoint("TOPLEFT", f.portrait, "TOPLEFT", -4, 4) f.leaderIcon:Hide() -- Raid Target Icon (top center of health bar, half outside frame) local raidIconSize = 22 local raidIconOvr = CreateFrame("Frame", nil, f) raidIconOvr:SetFrameLevel((f:GetFrameLevel() or 0) + 5) raidIconOvr:SetWidth(raidIconSize) raidIconOvr:SetHeight(raidIconSize) raidIconOvr:SetPoint("CENTER", f.health, "TOP", 0, 0) f.raidIcon = raidIconOvr:CreateTexture(nil, "OVERLAY") f.raidIcon:SetTexture("Interface\\TargetingFrame\\UI-RaidTargetingIcons") f.raidIcon:SetAllPoints(raidIconOvr) f.raidIcon:Hide() f.raidIconOverlay = raidIconOvr self.frame = f self:ApplyConfig() self.frame:Show() -- Ensure it's explicitly shown self:UpdateAll() -- Events SFrames:RegisterEvent("UNIT_HEALTH", function() if arg1 == "player" then self:UpdateHealth() end end) SFrames:RegisterEvent("UNIT_MAXHEALTH", function() if arg1 == "player" then self:UpdateHealth() end end) SFrames:RegisterEvent("UNIT_MANA", function() if arg1 == "player" then self:UpdatePower() end end) SFrames:RegisterEvent("UNIT_MAXMANA", function() if arg1 == "player" then self:UpdatePower() end end) SFrames:RegisterEvent("UNIT_ENERGY", function() if arg1 == "player" then self:UpdatePower() end end) SFrames:RegisterEvent("UNIT_MAXENERGY", function() if arg1 == "player" then self:UpdatePower() end end) SFrames:RegisterEvent("UNIT_RAGE", function() if arg1 == "player" then self:UpdatePower() end end) SFrames:RegisterEvent("UNIT_MAXRAGE", function() if arg1 == "player" then self:UpdatePower() end end) SFrames:RegisterEvent("PLAYER_ENTERING_WORLD", function() self:UpdateAll() end) SFrames:RegisterEvent("PLAYER_LEVEL_UP", function() if arg1 then self.currentLevel = arg1 end self:UpdateAll() if arg1 and mod(arg1, 2) == 0 and (not SFramesDB or SFramesDB.trainerReminder ~= false) then self:ShowTrainerReminder(arg1) end end) SFrames:RegisterEvent("PARTY_MEMBERS_CHANGED", function() self:UpdateLeaderIcon() end) SFrames:RegisterEvent("PARTY_LEADER_CHANGED", function() self:UpdateLeaderIcon() end) SFrames:RegisterEvent("RAID_TARGET_UPDATE", function() self:UpdateRaidIcon() end) SFrames:RegisterEvent("UNIT_PORTRAIT_UPDATE", function() if arg1 == "player" then self.frame.portrait:SetUnit("player") self.frame.portrait:SetCamera(0) self.frame.portrait:SetPosition(-1.0, 0, 0) end end) SFrames:RegisterEvent("UNIT_DISPLAYPOWER", function() if arg1 == "player" then self:UpdatePowerType(); self:UpdatePower() end end) SFrames:RegisterEvent("UPDATE_SHAPESHIFT_FORM", function() self:UpdatePowerType(); self:UpdatePower() end) SFrames:RegisterEvent("PLAYER_UPDATE_RESTING", function() self:UpdateRestingStatus() end) f.unit = "player" f:SetScript("OnEnter", function() GameTooltip_SetDefaultAnchor(GameTooltip, this) GameTooltip:SetUnit(this.unit) GameTooltip:Show() end) f:SetScript("OnLeave", function() GameTooltip:Hide() end) end function SFrames.Player:HasSpellInBook(spellName) local i = 1 while true do local name = GetSpellName(i, BOOKTYPE_SPELL) if not name then return false end if name == spellName then return true end i = i + 1 end end function SFrames.Player:GetSpellIcon(skillDisplayName) local baseName = string.gsub(skillDisplayName, " %d+级$", "") local altName1 = string.gsub(baseName, ":", ":") local altName2 = string.gsub(baseName, ":", ":") local i = 1 while true do local name = GetSpellName(i, BOOKTYPE_SPELL) if not name then break end if name == baseName or name == altName1 or name == altName2 then return GetSpellTexture(i, BOOKTYPE_SPELL) end i = i + 1 end return nil end function SFrames.Player:ShowTrainerReminder(newLevel) local _, classEn = UnitClass("player") local classNames = { WARRIOR = "战士", PALADIN = "圣骑士", HUNTER = "猎人", ROGUE = "盗贼", PRIEST = "牧师", SHAMAN = "萨满祭司", MAGE = "法师", WARLOCK = "术士", DRUID = "德鲁伊", } local className = classNames[classEn] or UnitClass("player") local classFallbackIcons = { WARRIOR = "Interface\\Icons\\Ability_Warrior_OffensiveStance", PALADIN = "Interface\\Icons\\Spell_Holy_HolyBolt", HUNTER = "Interface\\Icons\\Ability_Marksmanship", ROGUE = "Interface\\Icons\\Ability_BackStab", PRIEST = "Interface\\Icons\\Spell_Holy_HolyBolt", SHAMAN = "Interface\\Icons\\Spell_Nature_Lightning", MAGE = "Interface\\Icons\\Spell_Frost_IceStorm", WARLOCK = "Interface\\Icons\\Spell_Shadow_DeathCoil", DRUID = "Interface\\Icons\\Spell_Nature_Regeneration", } local fallbackIcon = classFallbackIcons[classEn] or "Interface\\Icons\\Trade_Engraving" local allSkills = {} local allIcons = {} local baseSkills = SFrames.ClassSkillData and SFrames.ClassSkillData[classEn] and SFrames.ClassSkillData[classEn][newLevel] if baseSkills then for _, s in ipairs(baseSkills) do table.insert(allSkills, s) table.insert(allIcons, self:GetSpellIcon(s) or fallbackIcon) end end local talentData = SFrames.TalentTrainerSkills and SFrames.TalentTrainerSkills[classEn] and SFrames.TalentTrainerSkills[classEn][newLevel] local talentSkills = {} if talentData then for _, entry in ipairs(talentData) do local displayName = entry[1] local requiredSpell = entry[2] if self:HasSpellInBook(requiredSpell) then table.insert(allSkills, displayName) table.insert(allIcons, self:GetSpellIcon(displayName) or fallbackIcon) table.insert(talentSkills, displayName) end end end local mountQuest = SFrames.ClassMountQuests and SFrames.ClassMountQuests[classEn] and SFrames.ClassMountQuests[classEn][newLevel] local skillCount = table.getn(allSkills) local hex = SFrames.Theme and SFrames.Theme:GetAccentHex() or "ffffb3d9" SFrames:Print(string.format("已达到 %d 级!你的%s训练师有新技能可以学习。", newLevel, className)) if skillCount > 0 then DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r 可学习的技能(共 " .. skillCount .. " 项):") local line = " " local lineCount = 0 for i = 1, skillCount do if lineCount > 0 then line = line .. ", " end line = line .. "|cffffd100" .. allSkills[i] .. "|r" lineCount = lineCount + 1 if i == skillCount or lineCount == 4 then DEFAULT_CHAT_FRAME:AddMessage(line) line = " " lineCount = 0 end end end if table.getn(talentSkills) > 0 then DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r |cff00ff00(天赋)|r " .. table.concat(talentSkills, ", ")) end if mountQuest then DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r |cffffff00★|r " .. mountQuest) end local bannerMsg = string.format("Lv.%d - %s训练师有 %d 项新技能可学习", newLevel, className, skillCount) if mountQuest then bannerMsg = bannerMsg .. " + " .. mountQuest end if skillCount == 0 and not mountQuest then bannerMsg = string.format("Lv.%d - 前往%s训练师查看可学技能", newLevel, className) elseif skillCount == 0 and mountQuest then bannerMsg = string.format("Lv.%d - %s", newLevel, mountQuest) end UIErrorsFrame:AddMessage(bannerMsg, 1.0, 0.82, 0.0, 1, 5) PlaySound("LEVELUP") if not self.trainerReminderFrame then local fr = CreateFrame("Frame", "NanamiTrainerReminder", UIParent) fr:SetWidth(440) fr:SetHeight(106) fr:SetPoint("TOP", UIParent, "TOP", 0, -120) fr:SetFrameStrata("DIALOG") local bg = fr:CreateTexture(nil, "BACKGROUND") bg:SetAllPoints(fr) bg:SetTexture(0, 0, 0, 0.78) local border = fr:CreateTexture(nil, "BORDER") border:SetPoint("TOPLEFT", fr, "TOPLEFT", -1, 1) border:SetPoint("BOTTOMRIGHT", fr, "BOTTOMRIGHT", 1, -1) border:SetTexture(1, 0.82, 0, 0.3) local icon = fr:CreateTexture(nil, "ARTWORK") icon:SetWidth(36) icon:SetHeight(36) icon:SetPoint("TOPLEFT", fr, "TOPLEFT", 10, -6) icon:SetTexture("Interface\\Icons\\INV_Misc_Book_11") fr.icon = icon local title = fr:CreateFontString(nil, "OVERLAY", "GameFontNormal") title:SetPoint("TOPLEFT", icon, "TOPRIGHT", 10, -2) title:SetPoint("RIGHT", fr, "RIGHT", -10, 0) title:SetJustifyH("LEFT") title:SetTextColor(1, 0.82, 0) fr.title = title local subtitle = fr:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall") subtitle:SetPoint("TOPLEFT", icon, "TOPRIGHT", 10, -18) subtitle:SetPoint("RIGHT", fr, "RIGHT", -10, 0) subtitle:SetJustifyH("LEFT") subtitle:SetTextColor(0.75, 0.75, 0.75) fr.subtitle = subtitle fr.skillIcons = {} fr.skillBorders = {} local maxIcons = 13 for idx = 1, maxIcons do local bdr = fr:CreateTexture(nil, "BORDER") bdr:SetWidth(30) bdr:SetHeight(30) bdr:SetPoint("TOPLEFT", fr, "TOPLEFT", 9 + (idx - 1) * 32, -45) bdr:SetTexture(1, 0.82, 0, 0.25) bdr:Hide() fr.skillBorders[idx] = bdr local si = fr:CreateTexture(nil, "ARTWORK") si:SetWidth(28) si:SetHeight(28) si:SetPoint("CENTER", bdr, "CENTER", 0, 0) si:SetTexCoord(0.07, 0.93, 0.07, 0.93) si:Hide() fr.skillIcons[idx] = si end local detail = fr:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall") detail:SetPoint("BOTTOMLEFT", fr, "BOTTOMLEFT", 10, 8) detail:SetPoint("RIGHT", fr, "RIGHT", -10, 0) detail:SetJustifyH("LEFT") detail:SetTextColor(0.85, 0.85, 0.85) fr.detail = detail fr:SetAlpha(0) fr:Hide() self.trainerReminderFrame = fr end local fr = self.trainerReminderFrame for idx = 1, 13 do fr.skillIcons[idx]:Hide() fr.skillBorders[idx]:Hide() end local iconCount = 0 for i = 1, skillCount do if iconCount >= 13 then break end iconCount = iconCount + 1 fr.skillIcons[iconCount]:SetTexture(allIcons[i]) fr.skillIcons[iconCount]:Show() fr.skillBorders[iconCount]:Show() end if mountQuest and iconCount < 13 then iconCount = iconCount + 1 fr.skillIcons[iconCount]:SetTexture("Interface\\Icons\\Spell_Nature_Swiftness") fr.skillIcons[iconCount]:Show() fr.skillBorders[iconCount]:Show() end if iconCount > 0 then fr:SetHeight(106) else fr:SetHeight(72) end fr.title:SetText(string.format("已达到 |cffffffff%d|r 级 — %s训练师有新技能", newLevel, className)) if skillCount > 0 or mountQuest then local countText = "" if skillCount > 0 then countText = skillCount .. " 项技能" end if mountQuest then if countText ~= "" then countText = countText .. " + " end countText = countText .. mountQuest end fr.subtitle:SetText(countText) fr.detail:SetText("详见聊天窗口") else fr.subtitle:SetText("") fr.detail:SetText("前往职业训练师查看可学习的技能") end fr:Show() fr:SetAlpha(0) fr.fadeState = "in" fr.fadeTimer = 0 fr.holdTimer = 0 fr:SetScript("OnUpdate", function() local dt = arg1 if fr.fadeState == "in" then fr.fadeTimer = fr.fadeTimer + dt local a = fr.fadeTimer / 0.5 if a >= 1 then a = 1 fr.fadeState = "hold" end fr:SetAlpha(a) elseif fr.fadeState == "hold" then fr.holdTimer = fr.holdTimer + dt if fr.holdTimer >= 8 then fr.fadeState = "out" fr.fadeTimer = 0 end elseif fr.fadeState == "out" then fr.fadeTimer = fr.fadeTimer + dt local a = 1 - fr.fadeTimer / 1.0 if a <= 0 then a = 0 fr:Hide() fr:SetScript("OnUpdate", nil) end fr:SetAlpha(a) end end) end function SFrames.Player:UpdateAll() if not self.frame then return end self:UpdateHealth() self:UpdatePowerType() self:UpdatePower() self:UpdateLeaderIcon() self:UpdateRaidIcon() self:UpdateRestingStatus() local name = UnitName("player") or "" -- Use the stored level from PLAYER_LEVEL_UP if it exists, since API might lag slightly local level = self.currentLevel or UnitLevel("player") local formattedLevel = string.format("|cffffff00%d|r", level) if SFramesDB and SFramesDB.showLevel == false then formattedLevel = "" else formattedLevel = formattedLevel .. " " end self.frame.portrait:SetUnit("player") self.frame.portrait:SetCamera(0) self.frame.portrait:SetPosition(-1.0, 0, 0) -- Class Color for Health local localizedClass, class = UnitClass("player") local className = GetChineseClassName(class, localizedClass) local nameLine = formattedLevel .. name local showClassText = not (SFramesDB and SFramesDB.playerShowClass == false) if showClassText and className and className ~= "" then nameLine = nameLine .. " " .. className end if not (SFramesDB and SFramesDB.playerShowClassIcon == false) then SFrames:SetClassIcon(self.frame.classIcon, class) else self.frame.classIcon:Hide() if self.frame.classIcon.overlay then self.frame.classIcon.overlay:Hide() end end local useClassColor = not (SFramesDB and SFramesDB.classColorHealth == false) if useClassColor and class and SFrames.Config.colors.class[class] then local color = SFrames.Config.colors.class[class] self.frame.health:SetStatusBarColor(color.r, color.g, color.b) -- Apply Class Color to Name self.frame.nameText:SetText(nameLine) self.frame.nameText:SetTextColor(color.r, color.g, color.b) else self.frame.health:SetStatusBarColor(0, 1, 0) self.frame.nameText:SetText(nameLine) self.frame.nameText:SetTextColor(1, 1, 1) end end function SFrames.Player:UpdateRestingStatus() if not self.frame or not self.frame.restOverlay then return end if IsResting() then self.frame.restOverlay:Show() else self.frame.restOverlay:Hide() end end function SFrames.Player:UpdateLeaderIcon() if IsPartyLeader() then self.frame.leaderIcon:Show() else self.frame.leaderIcon:Hide() end end function SFrames.Player:UpdateRaidIcon() if not (self.frame and self.frame.raidIcon) then return end if not GetRaidTargetIndex then self.frame.raidIcon:Hide() return end local index = GetRaidTargetIndex("player") if index and index > 0 and index <= 8 then local col = math.mod(index - 1, 4) local row = math.floor((index - 1) / 4) self.frame.raidIcon:SetTexCoord(col * 0.25, (col + 1) * 0.25, row * 0.25, (row + 1) * 0.25) self.frame.raidIcon:Show() else self.frame.raidIcon:Hide() end end function SFrames.Player:UpdateHealth() local hp = UnitHealth("player") local maxHp = UnitHealthMax("player") self.frame.health:SetMinMaxValues(0, maxHp) self.frame.health:SetValue(hp) if maxHp > 0 then self.frame.healthText:SetText(hp .. " / " .. maxHp) else self.frame.healthText:SetText(hp) end self:UpdateHealPrediction() end function SFrames.Player:UpdateHealPrediction() if not (self.frame and self.frame.health and self.frame.health.healPredMine and self.frame.health.healPredOther) then return end local predMine = self.frame.health.healPredMine local predOther = self.frame.health.healPredOther local function HidePredictions() predMine:Hide() predOther:Hide() end local hp = UnitHealth("player") or 0 local maxHp = UnitHealthMax("player") or 0 if maxHp <= 0 or hp >= maxHp or UnitIsDeadOrGhost("player") then HidePredictions() return end local _, mineIncoming, othersIncoming = GetIncomingHeals("player") local missing = maxHp - hp if missing <= 0 then HidePredictions() return end local mineShown = math.min(math.max(0, mineIncoming), missing) local remaining = missing - mineShown local otherShown = math.min(math.max(0, othersIncoming), remaining) if mineShown <= 0 and otherShown <= 0 then HidePredictions() return end local barWidth = self.frame.health:GetWidth() or 0 if barWidth <= 0 then HidePredictions() return end local currentWidth = math.floor((hp / maxHp) * barWidth + 0.5) if currentWidth < 0 then currentWidth = 0 end if currentWidth > barWidth then currentWidth = barWidth end local availableWidth = barWidth - currentWidth if availableWidth <= 0 then HidePredictions() return end local mineWidth = math.floor((mineShown / maxHp) * barWidth + 0.5) local otherWidth = math.floor((otherShown / maxHp) * barWidth + 0.5) if mineWidth < 0 then mineWidth = 0 end if otherWidth < 0 then otherWidth = 0 end if mineWidth > availableWidth then mineWidth = availableWidth end if otherWidth > (availableWidth - mineWidth) then otherWidth = availableWidth - mineWidth end if mineWidth > 0 then predMine:ClearAllPoints() predMine:SetPoint("TOPLEFT", self.frame.health, "TOPLEFT", currentWidth, 0) predMine:SetPoint("BOTTOMLEFT", self.frame.health, "BOTTOMLEFT", currentWidth, 0) predMine:SetWidth(mineWidth) predMine:Show() else predMine:Hide() end if otherWidth > 0 then predOther:ClearAllPoints() predOther:SetPoint("TOPLEFT", self.frame.health, "TOPLEFT", currentWidth + mineWidth, 0) predOther:SetPoint("BOTTOMLEFT", self.frame.health, "BOTTOMLEFT", currentWidth + mineWidth, 0) predOther:SetWidth(otherWidth) predOther:Show() else predOther:Hide() end end function SFrames.Player:UpdatePowerType() local powerType = UnitPowerType("player") local color = SFrames.Config.colors.power[powerType] if color then self.frame.power:SetStatusBarColor(color.r, color.g, color.b) else self.frame.power:SetStatusBarColor(0, 0, 1) end end function SFrames.Player:GetDruidAltMana(currentPower, currentMaxPower) -- Method 1: DruidManaLib (tracks regen ticks, MP5, talents, shapeshift cost) if AceLibrary and AceLibrary.HasInstance and AceLibrary:HasInstance("DruidManaLib-1.0") then local ok, lib = pcall(function() return AceLibrary("DruidManaLib-1.0") end) if ok and lib and lib.GetMana then local mana, maxMana = lib:GetMana() if type(mana) == "number" and type(maxMana) == "number" and maxMana > 0 then return math.floor(mana + 0.5), math.floor(maxMana + 0.5) end end end -- Method 2: SuperWow returns real mana as second value of UnitMana if CheckSuperWow then local ok, hasSW = pcall(CheckSuperWow) if ok and hasSW then local ok2, _, realMana = pcall(UnitMana, "player") local ok3, _, realMax = pcall(UnitManaMax, "player") if ok2 and ok3 and type(realMana) == "number" and type(realMax) == "number" and realMax > 0 then return realMana, realMax end end end -- Method 3: TBC-style UnitPower API if UnitPower and UnitPowerMax then local okMana, pMana = pcall(function() return UnitPower("player", 0) end) local okMax, pMax = pcall(function() return UnitPowerMax("player", 0) end) if okMana and okMax and type(pMana) == "number" and type(pMax) == "number" and pMax > 0 then if not (pMax == currentMaxPower and pMana == currentPower) and pMax > 100 then return pMana, pMax end end end return nil, nil end function SFrames.Player:UpdateFiveSecondRule() if not (self.frame and self.frame.power and self.frame.power.fsrGlow) then return end local powerBar = self.frame.power local glow = powerBar.fsrGlow local function HideTicker() glow:Hide() end local powerType = UnitPowerType("player") if powerType ~= 0 then self.fiveSecondStart = nil self.fiveSecondPendingStart = nil self.fiveSecondLastMana = nil HideTicker() return end local now = GetTime() -- Continuous monitoring: whenever mana drops, (re)start 5-second rule. -- If mana drops while hard-casting, delay start until cast end. local currentMana = UnitMana("player") or 0 if self.fiveSecondLastMana and currentMana < self.fiveSecondLastMana then local delay = 0 local cb = self.frame and self.frame.castbar if cb and cb.casting and cb.startTime and cb.maxValue then local castEnd = cb.startTime + cb.maxValue if castEnd > now then delay = castEnd - now end end local startAt = now + delay + 0.08 if delay > 0 then self.fiveSecondPendingStart = startAt else self.fiveSecondStart = startAt self.fiveSecondPendingStart = nil end end self.fiveSecondLastMana = currentMana if self.fiveSecondPendingStart then if now >= self.fiveSecondPendingStart then self.fiveSecondStart = self.fiveSecondPendingStart self.fiveSecondPendingStart = nil else HideTicker() return end end local startTime = self.fiveSecondStart if not startTime then HideTicker() return end local elapsed = now - startTime if elapsed < 0 then HideTicker() return end if elapsed >= 5 then self.fiveSecondStart = nil HideTicker() return end local barWidth = powerBar:GetWidth() or 0 if barWidth <= 0 then HideTicker() return end local progress = elapsed / 5 if progress < 0 then progress = 0 end if progress > 1 then progress = 1 end local glowWidth = 40 if glowWidth > barWidth then glowWidth = barWidth end local maxGlowX = barWidth - glowWidth -- Move strictly from left edge to right edge across full 5 seconds. local glowX = math.floor(progress * maxGlowX + 0.5) local pulse = 0.82 + 0.18 * math.sin(GetTime() * 10) glow:ClearAllPoints() glow:SetPoint("TOPLEFT", powerBar, "TOPLEFT", glowX, 0) glow:SetPoint("BOTTOMLEFT", powerBar, "BOTTOMLEFT", glowX, 0) glow:SetWidth(glowWidth) glow:SetAlpha(0.62 * pulse) glow:Show() end function SFrames.Player:UpdatePower() local power = UnitMana("player") local maxPower = UnitManaMax("player") self.frame.power:SetMinMaxValues(0, maxPower) self.frame.power:SetValue(power) self.frame.powerText:SetText(power .. " / " .. maxPower) local _, class = UnitClass("player") local powerType = UnitPowerType("player") self:UpdateFiveSecondRule() if class ~= "DRUID" then if self.frame.manaText then self.frame.manaText:Hide() end if self.frame.manaBar then self.frame.manaBar:Hide() end return end if not self.druidManaCache then self.druidManaCache = { value = 0, max = 0, ts = GetTime() } end if powerType == 0 then self.druidManaCache.value = power or 0 self.druidManaCache.max = maxPower or 0 self.druidManaCache.ts = GetTime() if self.frame.manaText then self.frame.manaText:Hide() end if self.frame.manaBar then self.frame.manaBar:Hide() end return end if not self.frame.manaText then return end local mana, maxMana = self:GetDruidAltMana(power, maxPower) if mana and maxMana and maxMana > 0 then self.druidManaCache.value = mana self.druidManaCache.max = maxMana self.druidManaCache.ts = GetTime() else local cache = self.druidManaCache if cache and cache.max and cache.max > 0 then local now = GetTime() local dt = now - (cache.ts or now) if dt < 0 then dt = 0 end local regen = 0 if GetManaRegen then local base, casting = GetManaRegen() if type(base) == "number" and base > 0 then regen = base elseif type(casting) == "number" and casting > 0 then regen = casting end end if (not regen or regen <= 0) and UnitStat then local ok, _, spi = pcall(UnitStat, "player", 5) if not (ok and type(spi) == "number" and spi > 0) then ok, spi = pcall(function() return UnitStat("player", 5) end) end if ok and type(spi) == "number" and spi > 0 then regen = (math.ceil(spi / 5) + 15) / 2 end end if regen and regen > 0 and dt > 0 then cache.value = math.min(cache.max, (cache.value or 0) + regen * dt) end cache.ts = now mana = math.floor((cache.value or 0) + 0.5) maxMana = cache.max end end if maxMana and maxMana > 0 then local pct = math.floor(mana / maxMana * 100 + 0.5) self.frame.manaText:SetText(pct .. "% " .. mana) self.frame.manaText:Show() if self.frame.manaBar then self.frame.manaBar:SetMinMaxValues(0, maxMana) self.frame.manaBar:SetValue(mana) self.frame.manaBar:Show() end else self.frame.manaText:SetText("--") self.frame.manaText:Show() if self.frame.manaBar then self.frame.manaBar:Hide() end end end -------------------------------------------------------------------------------- -- Player Auras (Buffs / Debuffs) -------------------------------------------------------------------------------- function SFrames.Player:CreateAuras() -- Create 16 Buff Slots self.frame.buffs = {} self.frame.debuffs = {} local size = 24 local spacing = 2 local rowSpacing = 1 local buffsPerRow = 9 for i = 1, 16 do local b = CreateFrame("Button", "SFramesPlayerBuff"..i, self.frame) b:SetWidth(size) b:SetHeight(size) SFrames:CreateUnitBackdrop(b) b.icon = b:CreateTexture(nil, "ARTWORK") b.icon:SetPoint("TOPLEFT", b, "TOPLEFT", 1, -1) b.icon:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", -1, 1) b.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93) b.cdText = SFrames:CreateFontString(b, 9, "CENTER") b.cdText:SetPoint("BOTTOM", b, "BOTTOM", 0, 1) b.cdText:SetTextColor(1, 0.82, 0) b.cdText:SetShadowColor(0, 0, 0, 1) b.cdText:SetShadowOffset(1, -1) -- Tooltip support for precise buff checking b:SetScript("OnEnter", function() GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT") GameTooltip:SetPlayerBuff(this.buffIndex) end) b:SetScript("OnLeave", function() GameTooltip:Hide() end) -- Default row anchor if i == 1 then b:SetPoint("TOPLEFT", self.frame, "BOTTOMLEFT", 0, -1) elseif math.mod(i - 1, buffsPerRow) == 0 then b:SetPoint("TOP", self.frame.buffs[i-buffsPerRow], "BOTTOM", 0, -rowSpacing) else b:SetPoint("LEFT", self.frame.buffs[i-1], "RIGHT", spacing, 0) end b:Hide() self.frame.buffs[i] = b end end function SFrames.Player:UpdateAuras() local timeNow = GetTime() for i = 1, 16 do local b = self.frame.buffs[i] local buffIndex, untilCancelled = GetPlayerBuff(i - 1, "HELPFUL") if buffIndex >= 0 then local texture = GetPlayerBuffTexture(buffIndex) if texture then b.icon:SetTexture(texture) b.buffIndex = buffIndex b:Show() local timeLeft = GetPlayerBuffTimeLeft(buffIndex) if timeLeft and timeLeft > 0 and timeLeft < 9999 then b.cdText:SetText(SFrames:FormatTime(timeLeft)) else b.cdText:SetText("") end else b:Hide() end else b:Hide() end end end -- Initialization Hook for Auras and Castbar local origInit = SFrames.Player.Initialize function SFrames.Player:Initialize() origInit(self) -- Setup Auras self:CreateAuras() self.auraUpdater = CreateFrame("Frame") self.auraUpdater.timer = 0 self.auraUpdater:SetScript("OnUpdate", function() this.timer = this.timer + arg1 SFrames.Player:UpdateFiveSecondRule() if this.timer >= 0.2 then SFrames.Player:UpdateAuras() SFrames.Player:UpdatePower() SFrames.Player:UpdateHealPrediction() this.timer = 0 end end) -- Setup Castbar self:CreateCastbar() -- Hide default castbar if CastingBarFrame then CastingBarFrame:UnregisterAllEvents() CastingBarFrame:Hide() end end -------------------------------------------------------------------------------- -- Player Castbar -------------------------------------------------------------------------------- function SFrames.Player:CreateCastbar() local cb = SFrames:CreateStatusBar(self.frame, "SFramesPlayerCastbar") cb:SetHeight(SFrames.Config.castbarHeight) cb:SetPoint("BOTTOMRIGHT", self.frame, "TOPRIGHT", 0, 6) cb:SetPoint("BOTTOMLEFT", self.frame.portrait, "TOPLEFT", SFrames.Config.castbarHeight + 6, 6) local cbbg = CreateFrame("Frame", nil, self.frame) cbbg:SetPoint("TOPLEFT", cb, "TOPLEFT", -1, 1) cbbg:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT", 1, -1) cbbg:SetFrameLevel(cb:GetFrameLevel() - 1) SFrames:CreateUnitBackdrop(cbbg) cb.bg = cb:CreateTexture(nil, "BACKGROUND") cb.bg:SetAllPoints() cb.bg:SetTexture(SFrames:GetTexture()) cb.bg:SetVertexColor(0.2, 0.2, 0.2, 1) cb:SetStatusBarColor(1, 0.7, 0) cb.text = SFrames:CreateFontString(cb, 10, "LEFT") cb.text:SetPoint("LEFT", cb, "LEFT", 4, 0) cb.time = SFrames:CreateFontString(cb, 10, "RIGHT") cb.time:SetPoint("RIGHT", cb, "RIGHT", -4, 0) cb.icon = cb:CreateTexture(nil, "ARTWORK") cb.icon:SetWidth(SFrames.Config.castbarHeight + 2) cb.icon:SetHeight(SFrames.Config.castbarHeight + 2) cb.icon:SetPoint("RIGHT", cb, "LEFT", -4, 0) cb.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93) -- Icon Backdrop local ibg = CreateFrame("Frame", nil, self.frame) ibg:SetPoint("TOPLEFT", cb.icon, "TOPLEFT", -1, 1) ibg:SetPoint("BOTTOMRIGHT", cb.icon, "BOTTOMRIGHT", 1, -1) ibg:SetFrameLevel(cb:GetFrameLevel() - 1) SFrames:CreateUnitBackdrop(ibg) cb:Hide() cbbg:Hide() cb.icon:Hide() ibg:Hide() self.frame.castbar = cb self.frame.castbar.cbbg = cbbg self.frame.castbar.ibg = ibg cb:SetScript("OnUpdate", function() SFrames.Player:CastbarOnUpdate() end) -- Hook events SFrames:RegisterEvent("SPELLCAST_START", function() self:CastbarStart(arg1, arg2) end) SFrames:RegisterEvent("SPELLCAST_STOP", function() self:CastbarStop() end) SFrames:RegisterEvent("SPELLCAST_FAILED", function() self:CastbarStop() end) SFrames:RegisterEvent("SPELLCAST_INTERRUPTED", function() self:CastbarStop() end) SFrames:RegisterEvent("SPELLCAST_DELAYED", function() self:CastbarDelayed(arg1) end) SFrames:RegisterEvent("SPELLCAST_CHANNEL_START", function() self:CastbarChannelStart(arg1, arg2) end) SFrames:RegisterEvent("SPELLCAST_CHANNEL_UPDATE", function() self:CastbarChannelUpdate(arg1) end) SFrames:RegisterEvent("SPELLCAST_CHANNEL_STOP", function() self:CastbarStop() end) end function SFrames.Player:CastbarStart(spellName, duration) local cb = self.frame.castbar cb.casting = true cb.channeling = nil cb.fadeOut = nil cb.startTime = GetTime() cb.maxValue = duration / 1000 cb:SetMinMaxValues(0, cb.maxValue) cb:SetValue(0) cb.text:SetText(spellName) local texture local _UnitCastingInfo = UnitCastingInfo or (ShaguTweaks and ShaguTweaks.UnitCastingInfo) if _UnitCastingInfo then local _, _, _, tex = _UnitCastingInfo("player") texture = tex end if texture then cb.icon:SetTexture(texture) cb.icon:Show() cb.ibg:Show() else cb.icon:Hide() cb.ibg:Hide() end cb:SetAlpha(1) cb.cbbg:SetAlpha(1) if texture then cb.icon:SetAlpha(1) cb.ibg:SetAlpha(1) end cb:Show() cb.cbbg:Show() end function SFrames.Player:CastbarChannelStart(duration, spellName) local cb = self.frame.castbar cb.casting = nil cb.channeling = true cb.fadeOut = nil cb.startTime = GetTime() cb.maxValue = duration / 1000 cb.endTime = cb.startTime + cb.maxValue cb:SetMinMaxValues(0, cb.maxValue) cb:SetValue(cb.maxValue) cb.text:SetText(spellName) local texture local _UnitChannelInfo = UnitChannelInfo or (ShaguTweaks and ShaguTweaks.UnitChannelInfo) if _UnitChannelInfo then local _, _, _, tex = _UnitChannelInfo("player") texture = tex end if texture then cb.icon:SetTexture(texture) cb.icon:Show() cb.ibg:Show() else cb.icon:Hide() cb.ibg:Hide() end cb:SetAlpha(1) cb.cbbg:SetAlpha(1) if texture then cb.icon:SetAlpha(1) cb.ibg:SetAlpha(1) end cb:Show() cb.cbbg:Show() end function SFrames.Player:CastbarStop() local cb = self.frame.castbar cb.casting = nil cb.channeling = nil cb.fadeOut = true -- keep showing for a short fade out end function SFrames.Player:CastbarDelayed(delay) local cb = self.frame.castbar if cb.casting then cb.maxValue = cb.maxValue + (delay / 1000) cb:SetMinMaxValues(0, cb.maxValue) end end function SFrames.Player:CastbarChannelUpdate(delay) local cb = self.frame.castbar if cb.channeling then local add = delay / 1000 cb.maxValue = cb.maxValue + add cb.endTime = cb.endTime + add cb:SetMinMaxValues(0, cb.maxValue) end end function SFrames.Player:CastbarOnUpdate() local cb = self.frame.castbar if cb.casting then local elapsed = GetTime() - cb.startTime if elapsed >= cb.maxValue then cb.casting = nil cb.fadeOut = true cb:SetValue(cb.maxValue) return end cb:SetValue(elapsed) cb.time:SetText(string.format("%.1f", math.max(cb.maxValue - elapsed, 0))) elseif cb.channeling then local timeRemaining = cb.endTime - GetTime() if timeRemaining <= 0 then cb.channeling = nil cb.fadeOut = true cb:SetValue(0) return end cb:SetValue(timeRemaining) cb.time:SetText(string.format("%.1f", timeRemaining)) elseif cb.fadeOut then local alpha = cb:GetAlpha() - 0.05 if alpha > 0 then cb:SetAlpha(alpha) cb.cbbg:SetAlpha(alpha) cb.icon:SetAlpha(alpha) cb.ibg:SetAlpha(alpha) else cb.fadeOut = nil cb:Hide() cb.cbbg:Hide() cb.icon:Hide() cb.ibg:Hide() end end end