SFrames.Pet = {} local _A = SFrames.ActiveTheme 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.Pet:ShowContextMenu() if not self.contextMenu then self.contextMenu = CreateFrame("Frame", "SFramesPetContextDD", UIParent, "UIDropDownMenuTemplate") end UIDropDownMenu_Initialize(self.contextMenu, function() local info info = {} info.text = "查看属性" info.notCheckable = 1 info.func = function() ToggleCharacter("PetPaperDollFrame") end UIDropDownMenu_AddButton(info) local hasPetUI, isHunterPet = HasPetUI() if isHunterPet then info = {} info.text = "重命名" info.notCheckable = 1 info.func = function() SFrames.Pet:ShowRenameDialog() end UIDropDownMenu_AddButton(info) info = {} info.text = "解散宠物" info.notCheckable = 1 info.func = function() if PetDismiss then PetDismiss() end end UIDropDownMenu_AddButton(info) info = {} info.text = "放弃宠物" info.notCheckable = 1 info.textR = 1; info.textG = 0.3; info.textB = 0.3 info.func = function() if PetAbandon then PetAbandon() end end UIDropDownMenu_AddButton(info) else info = {} info.text = "解散宠物" info.notCheckable = 1 info.func = function() if PetDismiss then PetDismiss() end end UIDropDownMenu_AddButton(info) end info = {} info.text = CANCEL or "取消" info.notCheckable = 1 info.func = function() CloseDropDownMenus() end UIDropDownMenu_AddButton(info) end, "MENU") ToggleDropDownMenu(1, nil, self.contextMenu, "SFramesPetFrame", 106, 27) end function SFrames.Pet:CreateRenameFrame() local T = SFrames.ActiveTheme local font = SFrames:GetFont() local outline = (SFrames.Media and SFrames.Media.fontOutline) or "OUTLINE" local f = CreateFrame("Frame", "SFramesPetRenameDialog", UIParent) f:SetWidth(300) f:SetHeight(120) f:SetPoint("CENTER", UIParent, "CENTER", 0, 80) f:SetFrameStrata("DIALOG") f:SetToplevel(true) f:EnableMouse(true) f:SetMovable(true) f:RegisterForDrag("LeftButton") f:SetScript("OnDragStart", function() this:StartMoving() end) f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end) f:SetBackdrop({ bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", tile = true, tileSize = 16, edgeSize = 14, insets = { left = 3, right = 3, top = 3, bottom = 3 }, }) f:SetBackdropColor(T.panelBg[1], T.panelBg[2], T.panelBg[3], T.panelBg[4]) f:SetBackdropBorderColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3], T.panelBorder[4]) local shadow = CreateFrame("Frame", nil, f) shadow:SetPoint("TOPLEFT", f, "TOPLEFT", -4, 4) shadow:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 4, -4) shadow:SetBackdrop({ bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", tile = true, tileSize = 16, edgeSize = 16, insets = { left = 4, right = 4, top = 4, bottom = 4 }, }) shadow:SetBackdropColor(0, 0, 0, 0.45) shadow:SetBackdropBorderColor(0, 0, 0, 0.6) shadow:SetFrameLevel(math.max(0, f:GetFrameLevel() - 1)) local header = CreateFrame("Frame", nil, f) header:SetPoint("TOPLEFT", f, "TOPLEFT", 3, -3) header:SetPoint("TOPRIGHT", f, "TOPRIGHT", -3, -3) header:SetHeight(26) header:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8" }) header:SetBackdropColor(T.headerBg[1], T.headerBg[2], T.headerBg[3], T.headerBg[4]) local titleFS = header:CreateFontString(nil, "OVERLAY") titleFS:SetFont(font, 12, outline) titleFS:SetPoint("CENTER", header, "CENTER", 0, 0) titleFS:SetText("宠物重命名") titleFS:SetTextColor(T.gold[1], T.gold[2], T.gold[3]) local hsep = f:CreateTexture(nil, "ARTWORK") hsep:SetTexture("Interface\\Buttons\\WHITE8X8") hsep:SetHeight(1) hsep:SetPoint("TOPLEFT", f, "TOPLEFT", 4, -29) hsep:SetPoint("TOPRIGHT", f, "TOPRIGHT", -4, -29) hsep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4]) local eb = CreateFrame("EditBox", "SFramesPetRenameEditBox", f) eb:SetWidth(260) eb:SetHeight(24) eb:SetPoint("TOP", f, "TOP", 0, -42) eb:SetFont(font, 12, outline) eb:SetAutoFocus(false) eb:SetMaxLetters(24) eb:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", tile = false, tileSize = 0, edgeSize = 1, insets = { left = 1, right = 1, top = 1, bottom = 1 }, }) eb:SetBackdropColor(T.inputBg[1], T.inputBg[2], T.inputBg[3], T.inputBg[4]) eb:SetBackdropBorderColor(T.inputBorder[1], T.inputBorder[2], T.inputBorder[3], T.inputBorder[4]) eb:SetTextInsets(8, 8, 0, 0) eb:SetTextColor(1, 1, 1) eb:SetScript("OnEnterPressed", function() SFrames.Pet:DoRename(this:GetText()) end) eb:SetScript("OnEscapePressed", function() SFrames.Pet.renameFrame:Hide() end) eb:SetScript("OnEditFocusGained", function() this:SetBackdropBorderColor(T.accent[1], T.accent[2], T.accent[3], 1) end) eb:SetScript("OnEditFocusLost", function() this:SetBackdropBorderColor(T.inputBorder[1], T.inputBorder[2], T.inputBorder[3], T.inputBorder[4]) end) f.editBox = eb local function CreateBtn(text, parent) local btn = CreateFrame("Button", nil, parent) btn:SetWidth(120) btn:SetHeight(26) btn:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", tile = false, tileSize = 0, edgeSize = 1, insets = { left = 1, right = 1, top = 1, bottom = 1 }, }) btn:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4]) btn:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], T.btnBorder[4]) local fs = btn:CreateFontString(nil, "OVERLAY") fs:SetFont(font, 11, outline) fs:SetPoint("CENTER", 0, 0) fs:SetText(text) fs:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3]) btn.label = fs btn:SetScript("OnEnter", function() this:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4]) this:SetBackdropBorderColor(T.btnHoverBd[1], T.btnHoverBd[2], T.btnHoverBd[3], T.btnHoverBd[4]) this.label:SetTextColor(T.btnActiveText[1], T.btnActiveText[2], T.btnActiveText[3]) end) btn:SetScript("OnLeave", function() this:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4]) this:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], T.btnBorder[4]) this.label:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3]) end) return btn end local confirmBtn = CreateBtn("确定", f) confirmBtn:SetPoint("BOTTOMRIGHT", f, "BOTTOM", -4, 10) confirmBtn:SetScript("OnClick", function() SFrames.Pet:DoRename(f.editBox:GetText()) end) local cancelBtn = CreateBtn("取消", f) cancelBtn:SetPoint("BOTTOMLEFT", f, "BOTTOM", 4, 10) cancelBtn:SetScript("OnClick", function() f:Hide() end) f:Hide() table.insert(UISpecialFrames, "SFramesPetRenameDialog") self.renameFrame = f end function SFrames.Pet:ShowRenameDialog() if not UnitExists("pet") then return end if not self.renameFrame then self:CreateRenameFrame() end local currentName = UnitName("pet") or "" self.renameFrame.editBox:SetText(currentName) self.renameFrame:Show() self.renameFrame.editBox:SetFocus() self.renameFrame.editBox:HighlightText() end function SFrames.Pet:DoRename(name) if not name or name == "" then return end if PetRename then PetRename(name) end if self.renameFrame then self.renameFrame:Hide() end end function SFrames.Pet:Initialize() local f = CreateFrame("Button", "SFramesPetFrame", UIParent) f:SetWidth(150) f:SetHeight(30) if SFramesDB and SFramesDB.Positions and SFramesDB.Positions["PetFrame"] then local pos = SFramesDB.Positions["PetFrame"] f:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs, pos.yOfs) else f:SetPoint("TOPLEFT", SFramesPlayerFrame, "BOTTOMLEFT", 10, -55) end local frameScale = (SFramesDB and type(SFramesDB.petFrameScale) == "number") and SFramesDB.petFrameScale or 1 f:SetScale(Clamp(frameScale, 0.7, 1.8)) 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["PetFrame"] = { point = point, relativePoint = relativePoint, xOfs = xOfs, yOfs = yOfs } end) f:RegisterForClicks("LeftButtonUp", "RightButtonUp") f:SetScript("OnClick", function() if arg1 == "LeftButton" then if SpellIsTargeting() then SpellTargetUnit("pet") elseif CursorHasItem() then DropItemOnUnit("pet") else TargetUnit("pet") end else SFrames.Pet:ShowContextMenu() end end) f:SetScript("OnReceiveDrag", function() if CursorHasItem() then DropItemOnUnit("pet") end end) f:SetScript("OnEnter", function() if SetMouseoverUnit then SetMouseoverUnit("pet") end GameTooltip_SetDefaultAnchor(GameTooltip, this) GameTooltip:SetUnit("pet") GameTooltip:Show() end) f:SetScript("OnLeave", function() if SetMouseoverUnit then SetMouseoverUnit() end GameTooltip:Hide() end) SFrames:CreateUnitBackdrop(f) -- Health Bar f.health = SFrames:CreateStatusBar(f, "SFramesPetHealth") f.health:SetPoint("TOPLEFT", f, "TOPLEFT", 1, -1) f.health:SetPoint("TOPRIGHT", f, "TOPRIGHT", -1, -1) f.health:SetHeight(18) 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.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, "ARTWORK") f.health.healPredMine:SetTexture(SFrames:GetTexture()) f.health.healPredMine:SetVertexColor(0.4, 1.0, 0.55, 0.78) f.health.healPredMine:SetDrawLayer("ARTWORK", 2) f.health.healPredMine:Hide() f.health.healPredOther = f.health:CreateTexture(nil, "ARTWORK") f.health.healPredOther:SetTexture(SFrames:GetTexture()) f.health.healPredOther:SetVertexColor(0.2, 0.9, 0.35, 0.5) f.health.healPredOther:SetDrawLayer("ARTWORK", 2) f.health.healPredOther:Hide() f.health.healPredOver = f.health:CreateTexture(nil, "OVERLAY") f.health.healPredOver:SetTexture(SFrames:GetTexture()) f.health.healPredOver:SetVertexColor(1.0, 0.3, 0.3, 0.6) f.health.healPredOver:SetDrawLayer("OVERLAY", 7) f.health.healPredOver:Hide() -- Power Bar f.power = SFrames:CreateStatusBar(f, "SFramesPetPower") f.power:SetPoint("TOPLEFT", f.health, "BOTTOMLEFT", 0, -1) f.power:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -1, 1) local pbg = CreateFrame("Frame", nil, f) pbg:SetPoint("TOPLEFT", f.power, "TOPLEFT", -1, 1) pbg:SetPoint("BOTTOMRIGHT", f.power, "BOTTOMRIGHT", 1, -1) pbg:SetFrameLevel(f:GetFrameLevel() - 1) SFrames:CreateUnitBackdrop(pbg) 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) -- Texts local fontPath = SFrames:GetFont() local outline = (SFrames and SFrames.Media and SFrames.Media.fontOutline) or "OUTLINE" f.nameText = SFrames:CreateFontString(f.health, 10, "LEFT") f.nameText:SetPoint("LEFT", f.health, "LEFT", 4, 0) f.nameText:SetWidth(75) f.nameText:SetHeight(12) f.nameText:SetJustifyH("LEFT") f.nameText:SetFont(fontPath, 10, outline) f.nameText:SetShadowColor(0, 0, 0, 1) f.nameText:SetShadowOffset(1, -1) f.healthText = SFrames:CreateFontString(f.health, 10, "RIGHT") f.healthText:SetPoint("RIGHT", f.health, "RIGHT", -4, 0) f.healthText:SetFont(fontPath, 10, outline) f.healthText:SetShadowColor(0, 0, 0, 1) f.healthText:SetShadowOffset(1, -1) -- Happiness Icon (for hunters) local hBG = CreateFrame("Frame", nil, f) hBG:SetWidth(20) hBG:SetHeight(20) hBG:SetPoint("RIGHT", f, "LEFT", -2, 0) SFrames:CreateUnitBackdrop(hBG) f.happiness = hBG:CreateTexture(nil, "OVERLAY") f.happiness:SetPoint("TOPLEFT", hBG, "TOPLEFT", 1, -1) f.happiness:SetPoint("BOTTOMRIGHT", hBG, "BOTTOMRIGHT", -1, 1) f.happiness:SetTexture("Interface\\PetPaperDollFrame\\UI-PetHappiness") f.happinessBG = hBG f.happinessBG:Hide() self.frame = f self.frame.unit = "pet" f:Hide() self:CreateAuras() self:CreateHappinessWarning() self:CreateCastbar() SFrames:RegisterEvent("UNIT_PET", function() if arg1 == "player" then self:UpdateAll() end end) SFrames:RegisterEvent("PET_BAR_UPDATE", function() self:UpdateAll() end) SFrames:RegisterEvent("UNIT_HEALTH", function() if arg1 == "pet" then self:UpdateHealth() end end) SFrames:RegisterEvent("UNIT_MAXHEALTH", function() if arg1 == "pet" then self:UpdateHealth() end end) SFrames:RegisterEvent("UNIT_MANA", function() if arg1 == "pet" then self:UpdatePower() end end) SFrames:RegisterEvent("UNIT_MAXMANA", function() if arg1 == "pet" then self:UpdatePower() end end) SFrames:RegisterEvent("UNIT_ENERGY", function() if arg1 == "pet" then self:UpdatePower() end end) SFrames:RegisterEvent("UNIT_MAXENERGY", function() if arg1 == "pet" then self:UpdatePower() end end) SFrames:RegisterEvent("UNIT_FOCUS", function() if arg1 == "pet" then self:UpdatePower() end end) SFrames:RegisterEvent("UNIT_MAXFOCUS", function() if arg1 == "pet" then self:UpdatePower() end end) SFrames:RegisterEvent("UNIT_RAGE", function() if arg1 == "pet" then self:UpdatePower() end end) SFrames:RegisterEvent("UNIT_MAXRAGE", function() if arg1 == "pet" then self:UpdatePower() end end) SFrames:RegisterEvent("UNIT_DISPLAYPOWER", function() if arg1 == "pet" then self:UpdatePowerType() end end) SFrames:RegisterEvent("UNIT_HAPPINESS", function() if arg1 == "pet" then self:UpdateHappiness() end end) SFrames:RegisterEvent("UNIT_NAME_UPDATE", function() if arg1 == "pet" then self:UpdateAll() end end) SFrames:RegisterEvent("PLAYER_ENTERING_WORLD", function() self:UpdateAll() end) self:InitFoodFeature() self:UpdateAll() if SFrames.Movers and SFrames.Movers.RegisterMover and self.frame then SFrames.Movers:RegisterMover("PetFrame", self.frame, "宠物", "TOPLEFT", "SFramesPlayerFrame", "BOTTOMLEFT", 10, -55) end if StaticPopup_Show then local origStaticPopupShow = StaticPopup_Show StaticPopup_Show = function(which, a1, a2, a3) if which == "RENAME_PET" then SFrames.Pet:ShowRenameDialog() return end return origStaticPopupShow(which, a1, a2, a3) end end end function SFrames.Pet:UpdateAll() if UnitExists("pet") then if SFramesDB and SFramesDB.showPetFrame == false then self.frame:Hide() if self.foodPanel then self.foodPanel:Hide() end self:HideAuras() return end self.frame:Show() self:UpdateHealth() self:UpdatePowerType() self:UpdatePower() self:UpdateHappiness() self:UpdateAuras() local name = UnitName("pet") if name == UNKNOWNOBJECT or name == "未知目标" or name == "Unknown" then name = "宠物" end self.frame.nameText:SetText(name) local r, g, b = 0.33, 0.59, 0.33 self.frame.health:SetStatusBarColor(r, g, b) else self.frame:Hide() if self.foodPanel then self.foodPanel:Hide() end self:HideAuras() end end function SFrames.Pet:UpdateHealth() local hp = UnitHealth("pet") local maxHp = UnitHealthMax("pet") 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("") end self:UpdateHealPrediction() end function SFrames.Pet:UpdateHealPrediction() if not (self.frame and self.frame.health and self.frame.health.healPredMine and self.frame.health.healPredOther and self.frame.health.healPredOver) then return end local predMine = self.frame.health.healPredMine local predOther = self.frame.health.healPredOther local predOver = self.frame.health.healPredOver local function HidePredictions() predMine:Hide() predOther:Hide() predOver:Hide() end local hp = UnitHealth("pet") or 0 local maxHp = UnitHealthMax("pet") or 0 if CheckSuperWow then local ok, hasSW = pcall(CheckSuperWow) if ok and hasSW then local ok2, realHp = pcall(UnitHealth, "pet") if ok2 then hp = realHp or hp end local ok3, realMaxHp = pcall(UnitHealthMax, "pet") if ok3 then maxHp = realMaxHp or maxHp end end end if maxHp <= 0 or UnitIsDeadOrGhost("pet") then HidePredictions() return end local totalIncoming, mineIncoming, othersIncoming = 0, 0, 0 local ok, t, m, o = pcall(function() return SFrames:GetIncomingHeals("pet") end) if ok then totalIncoming, mineIncoming, othersIncoming = t or 0, m or 0, o or 0 end local missing = maxHp - hp if missing <= 0 and (mineIncoming <= 0 and othersIncoming <= 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 and (mineIncoming <= 0 and othersIncoming <= 0) then HidePredictions() return end local barWidth = self.frame.health:GetWidth() if barWidth <= 0 then HidePredictions() return end local currentWidth = (hp / maxHp) * barWidth if currentWidth < 0 then currentWidth = 0 end if currentWidth > barWidth then currentWidth = barWidth end local availableWidth = barWidth - currentWidth if availableWidth <= 0 and (mineIncoming <= 0 and othersIncoming <= 0) then HidePredictions() return end local mineWidth = 0 local otherWidth = 0 if missing > 0 then mineWidth = (mineShown / missing) * availableWidth otherWidth = (otherShown / missing) * availableWidth 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 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:SetHeight(self.frame.health:GetHeight()) 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:SetHeight(self.frame.health:GetHeight()) predOther:Show() else predOther:Hide() end local totalIncomingValue = mineIncoming + othersIncoming local overHeal = totalIncomingValue - missing if overHeal > 0 then local overWidth = math.floor((overHeal / maxHp) * barWidth + 0.5) if overWidth > 0 then predOver:ClearAllPoints() predOver:SetPoint("TOPLEFT", self.frame.health, "TOPRIGHT", 0, 0) predOver:SetPoint("BOTTOMLEFT", self.frame.health, "BOTTOMRIGHT", 0, 0) predOver:SetWidth(overWidth) predOver:SetHeight(self.frame.health:GetHeight()) predOver:Show() else predOver:Hide() end else predOver:Hide() end end function SFrames.Pet:UpdatePowerType() local powerType = UnitPowerType("pet") 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.Pet:UpdatePower() local power = UnitMana("pet") local maxPower = UnitManaMax("pet") self.frame.power:SetMinMaxValues(0, maxPower) self.frame.power:SetValue(power) end function SFrames.Pet:UpdateHappiness() local happiness = GetPetHappiness() if not happiness then self.frame.happinessBG:Hide() self:HideHappinessWarning() self:UpdateFoodButton() return end local isHunter = false local _, class = UnitClass("player") if class == "HUNTER" then isHunter = true end if isHunter then if happiness == 1 then self.frame.happiness:SetTexCoord(0.375, 0.5625, 0, 0.359375) self.frame.happinessBG:Show() elseif happiness == 2 then self.frame.happiness:SetTexCoord(0.1875, 0.375, 0, 0.359375) self.frame.happinessBG:Show() elseif happiness == 3 then self.frame.happiness:SetTexCoord(0, 0.1875, 0, 0.359375) self.frame.happinessBG:Show() end self:ShowHappinessWarning(happiness) else self.frame.happinessBG:Hide() self:HideHappinessWarning() end self:UpdateFoodButton() end -------------------------------------------------------------------------------- -- Pet Food Feature (Hunter only) -- Food button on pet frame, food selection panel, quick-feed via right-click -------------------------------------------------------------------------------- local petFoodScanTip local cachedFeedSpell local function EnsureFoodScanTooltip() if not petFoodScanTip then petFoodScanTip = CreateFrame("GameTooltip", "NanamiPetFoodScanTip", UIParent, "GameTooltipTemplate") petFoodScanTip:SetOwner(UIParent, "ANCHOR_NONE") end return petFoodScanTip end local function GetFeedPetSpell() if cachedFeedSpell then return cachedFeedSpell end for tab = 1, GetNumSpellTabs() do local _, _, offset, numSpells = GetSpellTabInfo(tab) for i = offset + 1, offset + numSpells do local spellName = GetSpellName(i, BOOKTYPE_SPELL) if spellName and (spellName == "Feed Pet" or spellName == "喂养宠物") then cachedFeedSpell = spellName return cachedFeedSpell end end end cachedFeedSpell = "Feed Pet" return cachedFeedSpell end local REJECT_NAME_PATTERNS = { "Potion", "potion", "药水", "Elixir", "elixir", "药剂", "Flask", "flask", "合剂", "Bandage", "bandage", "绷带", "Scroll", "scroll", "卷轴", "Healthstone", "healthstone", "治疗石", "Mana Gem", "法力宝石", "Thistle Tea", "蓟花茶", "Firewater", "火焰花水", "Juju", "符咒", } local function NameIsRejected(itemName) for i = 1, table.getn(REJECT_NAME_PATTERNS) do if string.find(itemName, REJECT_NAME_PATTERNS[i], 1, true) then return true end end return false end local function IsItemPetFood(bag, slot) local link = GetContainerItemLink(bag, slot) if not link then return false end local texture = GetContainerItemInfo(bag, slot) local _, _, itemIdStr = string.find(link, "item:(%d+)") local name, itemType, subType if itemIdStr then local n, _, _, _, _, t, st = GetItemInfo("item:" .. itemIdStr) name = n itemType = t subType = st end if not name then local _, _, parsed = string.find(link, "%[(.+)%]") name = parsed end if not name then return false end if NameIsRejected(name) then return false end if itemType then if itemType ~= "Consumable" and itemType ~= "消耗品" then return false end if subType then if string.find(subType, "Potion", 1, true) or string.find(subType, "药水", 1, true) or string.find(subType, "Elixir", 1, true) or string.find(subType, "药剂", 1, true) or string.find(subType, "Flask", 1, true) or string.find(subType, "合剂", 1, true) or string.find(subType, "Bandage", 1, true) or string.find(subType, "绷带", 1, true) or string.find(subType, "Scroll", 1, true) or string.find(subType, "卷轴", 1, true) then return false end if string.find(subType, "Food", 1, true) or string.find(subType, "食物", 1, true) then return true, name, texture end end end local tip = EnsureFoodScanTooltip() tip:SetOwner(UIParent, "ANCHOR_NONE") tip:SetBagItem(bag, slot) local found = false local rejected = false for i = 1, tip:NumLines() do local leftObj = _G["NanamiPetFoodScanTipTextLeft" .. i] if leftObj then local text = leftObj:GetText() if text then if string.find(text, "进食", 1, true) or string.find(text, "eating", 1, true) then found = true end if string.find(text, "Well Fed", 1, true) or string.find(text, "充分进食", 1, true) then found = true end if string.find(text, "Restores", 1, true) and string.find(text, "health", 1, true) and string.find(text, "over", 1, true) then found = true end if string.find(text, "恢复", 1, true) and string.find(text, "生命", 1, true) and string.find(text, "秒", 1, true) then found = true end if string.find(text, "Potion", 1, true) or string.find(text, "药水", 1, true) or string.find(text, "Elixir", 1, true) or string.find(text, "药剂", 1, true) or string.find(text, "Bandage", 1, true) or string.find(text, "绷带", 1, true) then rejected = true end end end end tip:Hide() if found and not rejected then return true, name, texture end if itemType and subType then if (itemType == "Consumable" or itemType == "消耗品") and (subType == "Food & Drink" or subType == "食物和饮料") then return true, name, texture end end return false end function SFrames.Pet:ScanBagsForFood() local foods = {} for bag = 0, 4 do for slot = 1, GetContainerNumSlots(bag) do local isFood, name, itemTex = IsItemPetFood(bag, slot) if isFood then local texture, itemCount = GetContainerItemInfo(bag, slot) local link = GetContainerItemLink(bag, slot) table.insert(foods, { bag = bag, slot = slot, name = name, link = link, texture = texture or itemTex, count = itemCount or 1, }) end end end return foods end function SFrames.Pet:FeedPet(bag, slot) if not UnitExists("pet") then return end local link = GetContainerItemLink(bag, slot) local tex = GetContainerItemInfo(bag, slot) local spell = GetFeedPetSpell() CastSpellByName(spell) PickupContainerItem(bag, slot) if link then local _, _, itemId = string.find(link, "item:(%d+)") if itemId then if not SFramesDB then SFramesDB = {} end SFramesDB.lastPetFoodId = tonumber(itemId) end end if tex and self.foodButton then self.foodButton.icon:SetTexture(tex) if not SFramesDB then SFramesDB = {} end SFramesDB.lastPetFoodIcon = tex end end function SFrames.Pet:QuickFeed() if not UnitExists("pet") then return end local foods = self:ScanBagsForFood() if table.getn(foods) == 0 then DEFAULT_CHAT_FRAME:AddMessage("|cffff9900[Nanami]|r 背包中没有可喂食的食物") return end local preferred = SFramesDB and SFramesDB.lastPetFoodId if preferred then for i = 1, table.getn(foods) do local f = foods[i] if f.link then local _, _, itemId = string.find(f.link, "item:(%d+)") if itemId and tonumber(itemId) == preferred then self:FeedPet(f.bag, f.slot) return end end end end self:FeedPet(foods[1].bag, foods[1].slot) end -------------------------------------------------------------------------------- -- Food Button & Panel UI -------------------------------------------------------------------------------- local FOOD_COLS = 6 local FOOD_SLOT_SIZE = 30 local FOOD_SLOT_GAP = 2 local FOOD_PAD = 8 function SFrames.Pet:CreateFoodButton() local f = self.frame local A = SFrames.ActiveTheme local btn = CreateFrame("Button", "SFramesPetFoodBtn", f) btn:SetWidth(20) btn:SetHeight(20) btn:SetPoint("TOP", f.happinessBG, "BOTTOM", 0, -2) SFrames:CreateUnitBackdrop(btn) local icon = btn:CreateTexture(nil, "ARTWORK") icon:SetPoint("TOPLEFT", btn, "TOPLEFT", 1, -1) icon:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -1, 1) icon:SetTexture("Interface\\Icons\\INV_Misc_Food_14") btn.icon = icon btn:RegisterForClicks("LeftButtonUp", "RightButtonUp") local pet = self btn:SetScript("OnClick", function() if CursorHasItem() then local curTex = pet:GetCursorItemTexture() DropItemOnUnit("pet") if curTex then pet:SetFoodIcon(curTex) end return end if arg1 == "RightButton" then pet:QuickFeed() else if pet.foodPanel and pet.foodPanel:IsShown() then pet.foodPanel:Hide() else pet:ShowFoodPanel() end end end) btn:SetScript("OnEnter", function() GameTooltip:SetOwner(this, "ANCHOR_RIGHT") GameTooltip:AddLine("喂养宠物", 1, 1, 1) GameTooltip:AddLine("左键: 选择食物", 0.7, 0.7, 0.7) GameTooltip:AddLine("右键: 快速喂食", 0.7, 0.7, 0.7) GameTooltip:AddLine("可拖拽背包食物到此按钮", 0.7, 0.7, 0.7) GameTooltip:Show() end) btn:SetScript("OnLeave", function() GameTooltip:Hide() end) btn:SetScript("OnReceiveDrag", function() if CursorHasItem() then DropItemOnUnit("pet") end end) self.foodButton = btn btn:Hide() end function SFrames.Pet:CreateFoodPanel() if self.foodPanel then return self.foodPanel end local A = SFrames.ActiveTheme local panel = CreateFrame("Frame", "SFramesPetFoodPanel", UIParent) panel:SetFrameStrata("DIALOG") panel:SetFrameLevel(20) panel:SetWidth(FOOD_COLS * (FOOD_SLOT_SIZE + FOOD_SLOT_GAP) + FOOD_PAD * 2 - FOOD_SLOT_GAP) panel:SetHeight(80) panel:SetPoint("BOTTOMLEFT", self.frame, "TOPLEFT", -22, 4) SFrames:CreateUnitBackdrop(panel) panel:EnableMouse(true) panel:Hide() local titleBar = CreateFrame("Frame", nil, panel) titleBar:SetPoint("TOPLEFT", panel, "TOPLEFT", 1, -1) titleBar:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -1, -1) titleBar:SetHeight(18) titleBar:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8" }) local hdr = A.headerBg or A.panelBg titleBar:SetBackdropColor(hdr[1], hdr[2], hdr[3], (hdr[4] or 0.9) * 0.6) local titleText = titleBar:CreateFontString(nil, "OVERLAY") titleText:SetFont(SFrames:GetFont(), 10, SFrames.Media.fontOutline or "OUTLINE") titleText:SetPoint("LEFT", titleBar, "LEFT", FOOD_PAD, 0) titleText:SetTextColor(A.title[1], A.title[2], A.title[3]) titleText:SetText("选择食物") panel.titleText = titleText local hintText = panel:CreateFontString(nil, "OVERLAY") hintText:SetFont(SFrames:GetFont(), 9, SFrames.Media.fontOutline or "OUTLINE") hintText:SetPoint("BOTTOMLEFT", panel, "BOTTOMLEFT", FOOD_PAD, 4) hintText:SetPoint("BOTTOMRIGHT", panel, "BOTTOMRIGHT", -FOOD_PAD, 4) hintText:SetJustifyH("LEFT") local dim = A.dimText or { 0.5, 0.5, 0.5 } hintText:SetTextColor(dim[1], dim[2], dim[3]) hintText:SetText("点击喂食 | 可拖拽食物到此面板") panel.hintText = hintText local emptyText = panel:CreateFontString(nil, "OVERLAY") emptyText:SetFont(SFrames:GetFont(), 10, SFrames.Media.fontOutline or "OUTLINE") emptyText:SetPoint("CENTER", panel, "CENTER", 0, 0) emptyText:SetTextColor(dim[1], dim[2], dim[3]) emptyText:SetText("背包中没有可喂食的食物") emptyText:Hide() panel.emptyText = emptyText local pet = self panel:SetScript("OnReceiveDrag", function() if CursorHasItem() then local curTex = pet:GetCursorItemTexture() DropItemOnUnit("pet") if curTex then pet:SetFoodIcon(curTex) end end end) table.insert(UISpecialFrames, "SFramesPetFoodPanel") panel.slots = {} self.foodPanel = panel return panel end function SFrames.Pet:CreateFoodSlot(parent, index) local A = SFrames.ActiveTheme local slot = CreateFrame("Button", "SFramesPetFoodSlot" .. index, parent) slot:SetWidth(FOOD_SLOT_SIZE) slot:SetHeight(FOOD_SLOT_SIZE) slot:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", tile = false, tileSize = 0, edgeSize = 1, insets = { left = 1, right = 1, top = 1, bottom = 1 } }) slot:SetBackdropColor(A.slotBg[1], A.slotBg[2], A.slotBg[3], A.slotBg[4] or 0.9) slot:SetBackdropBorderColor(0, 0, 0, 1) local icon = slot:CreateTexture(nil, "ARTWORK") icon:SetPoint("TOPLEFT", 2, -2) icon:SetPoint("BOTTOMRIGHT", -2, 2) slot.icon = icon local count = slot:CreateFontString(nil, "OVERLAY") count:SetFont(SFrames:GetFont(), 10, "OUTLINE") count:SetPoint("BOTTOMRIGHT", -2, 2) count:SetJustifyH("RIGHT") count:SetTextColor(1, 1, 1) slot.count = count slot:RegisterForClicks("LeftButtonUp", "RightButtonUp") local pet = self slot:SetScript("OnClick", function() if CursorHasItem() then local curTex = pet:GetCursorItemTexture() DropItemOnUnit("pet") if curTex then pet:SetFoodIcon(curTex) end return end if IsShiftKeyDown() and this.foodLink then if ChatFrameEditBox and ChatFrameEditBox:IsVisible() then ChatFrameEditBox:Insert(this.foodLink) end return end if this.foodBag and this.foodSlot then pet:FeedPet(this.foodBag, this.foodSlot) if pet.foodPanel then pet.foodPanel:Hide() end end end) slot:SetScript("OnEnter", function() if this.foodBag and this.foodSlot then GameTooltip:SetOwner(this, "ANCHOR_RIGHT") GameTooltip:SetBagItem(this.foodBag, this.foodSlot) GameTooltip:AddLine(" ") GameTooltip:AddLine("点击: 喂食宠物", 0.5, 1, 0.5) GameTooltip:AddLine("Shift+点击: 链接到聊天", 0.7, 0.7, 0.7) GameTooltip:Show() end this:SetBackdropBorderColor(0.4, 0.4, 0.4, 1) end) slot:SetScript("OnLeave", function() GameTooltip:Hide() this:SetBackdropBorderColor(0, 0, 0, 1) end) slot:SetScript("OnReceiveDrag", function() if CursorHasItem() then DropItemOnUnit("pet") end end) return slot end function SFrames.Pet:ShowFoodPanel() self:CreateFoodPanel() self:RefreshFoodPanel() self.foodPanel:Show() end function SFrames.Pet:RefreshFoodPanel() local panel = self.foodPanel if not panel then return end local foods = self:ScanBagsForFood() local numFoods = table.getn(foods) for i = 1, table.getn(panel.slots) do panel.slots[i]:Hide() end if numFoods == 0 then panel:SetHeight(60) panel.emptyText:Show() panel.hintText:Hide() return end panel.emptyText:Hide() panel.hintText:Show() local rows = math.ceil(numFoods / FOOD_COLS) local panelH = FOOD_PAD + 20 + rows * (FOOD_SLOT_SIZE + FOOD_SLOT_GAP) + 18 panel:SetHeight(panelH) for i = 1, numFoods do local food = foods[i] local slot = panel.slots[i] if not slot then slot = self:CreateFoodSlot(panel, i) panel.slots[i] = slot end local col = mod(i - 1, FOOD_COLS) local row = math.floor((i - 1) / FOOD_COLS) slot:ClearAllPoints() slot:SetPoint("TOPLEFT", panel, "TOPLEFT", FOOD_PAD + col * (FOOD_SLOT_SIZE + FOOD_SLOT_GAP), -(FOOD_PAD + 18 + row * (FOOD_SLOT_SIZE + FOOD_SLOT_GAP))) slot.icon:SetTexture(food.texture) slot.count:SetText(food.count > 1 and tostring(food.count) or "") slot.foodBag = food.bag slot.foodSlot = food.slot slot.foodName = food.name slot.foodLink = food.link slot:Show() end end function SFrames.Pet:GetCursorItemTexture() for bag = 0, 4 do for slot = 1, GetContainerNumSlots(bag) do local texture, count, locked = GetContainerItemInfo(bag, slot) if locked and texture then return texture end end end return nil end function SFrames.Pet:SetFoodIcon(tex) if not tex or not self.foodButton then return end self.foodButton.icon:SetTexture(tex) if not SFramesDB then SFramesDB = {} end SFramesDB.lastPetFoodIcon = tex end function SFrames.Pet:InitFoodFeature() local _, playerClass = UnitClass("player") if playerClass ~= "HUNTER" then return end self:CreateFoodButton() if SFramesDB and SFramesDB.lastPetFoodIcon then self.foodButton.icon:SetTexture(SFramesDB.lastPetFoodIcon) end local pet = self SFrames:RegisterEvent("BAG_UPDATE", function() if pet.foodPanel and pet.foodPanel:IsShown() then pet:RefreshFoodPanel() end end) end function SFrames.Pet:UpdateFoodButton() if not self.foodButton then return end local _, class = UnitClass("player") if class == "HUNTER" and UnitExists("pet") then self.foodButton:Show() local happiness = GetPetHappiness() if happiness and happiness == 1 then self.foodButton.icon:SetVertexColor(1, 0.3, 0.3) elseif happiness and happiness == 2 then self.foodButton.icon:SetVertexColor(1, 0.8, 0.4) else self.foodButton.icon:SetVertexColor(1, 1, 1) end else self.foodButton:Hide() if self.foodPanel then self.foodPanel:Hide() end end end -------------------------------------------------------------------------------- -- Pet Buff / Debuff Auras -------------------------------------------------------------------------------- local PET_AURA_SIZE = 20 local PET_AURA_SPACING = 2 local PET_AURA_ROW_SPACING = 1 local PET_AURAS_PER_ROW = 6 local PET_BUFF_COUNT = 16 local PET_DEBUFF_COUNT = 16 function SFrames.Pet:CreateAuras() local f = self.frame f.buffs = {} f.debuffs = {} for i = 1, PET_BUFF_COUNT do local b = CreateFrame("Button", "SFramesPetBuff" .. i, f) b:SetWidth(PET_AURA_SIZE) b:SetHeight(PET_AURA_SIZE) SFrames:CreateUnitBackdrop(b) b.icon = b:CreateTexture(nil, "ARTWORK") b.icon:SetAllPoints() b.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93) b.cdText = SFrames:CreateFontString(b, 8, "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) b:SetScript("OnEnter", function() GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT") GameTooltip:SetUnitBuff("pet", this:GetID()) end) b:SetScript("OnLeave", function() GameTooltip:Hide() end) if i == 1 then b:SetPoint("TOPLEFT", f, "BOTTOMLEFT", 0, -1) elseif math.mod(i - 1, PET_AURAS_PER_ROW) == 0 then b:SetPoint("TOP", f.buffs[i - PET_AURAS_PER_ROW], "BOTTOM", 0, -PET_AURA_ROW_SPACING) else b:SetPoint("LEFT", f.buffs[i - 1], "RIGHT", PET_AURA_SPACING, 0) end b:Hide() f.buffs[i] = b end for i = 1, PET_DEBUFF_COUNT do local b = CreateFrame("Button", "SFramesPetDebuff" .. i, f) b:SetWidth(PET_AURA_SIZE) b:SetHeight(PET_AURA_SIZE) SFrames:CreateUnitBackdrop(b) b.icon = b:CreateTexture(nil, "ARTWORK") b.icon:SetAllPoints() b.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93) b.cdText = SFrames:CreateFontString(b, 8, "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) b:SetScript("OnEnter", function() GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT") GameTooltip:SetUnitDebuff("pet", this:GetID()) end) b:SetScript("OnLeave", function() GameTooltip:Hide() end) if i == 1 then b:SetPoint("TOPLEFT", f, "BOTTOMLEFT", 0, -1) elseif math.mod(i - 1, PET_AURAS_PER_ROW) == 0 then b:SetPoint("TOP", f.debuffs[i - PET_AURAS_PER_ROW], "BOTTOM", 0, -PET_AURA_ROW_SPACING) else b:SetPoint("LEFT", f.debuffs[i - 1], "RIGHT", PET_AURA_SPACING, 0) end b:Hide() f.debuffs[i] = b end SFrames:RegisterEvent("UNIT_AURA", function() if arg1 == "pet" then SFrames.Pet:UpdateAuras() end end) self.petAuraUpdater = CreateFrame("Frame", nil, f) self.petAuraUpdater.timer = 0 self.petAuraUpdater:SetScript("OnUpdate", function() this.timer = this.timer + arg1 if this.timer >= 0.25 then SFrames.Pet:TickAuras() SFrames.Pet:UpdateHealPrediction() this.timer = 0 end end) end function SFrames.Pet:UpdateAuras() if not UnitExists("pet") then return end local f = self.frame if not f.buffs then return end local numBuffs = 0 for i = 1, PET_BUFF_COUNT do local texture = UnitBuff("pet", i) local b = f.buffs[i] b:SetID(i) if texture then b.icon:SetTexture(texture) if SFrames.Tooltip then SFrames.Tooltip:SetOwner(UIParent, "ANCHOR_NONE") SFrames.Tooltip:ClearLines() SFrames.Tooltip:SetUnitBuff("pet", i) end local timeLeft = SFrames:GetAuraTimeLeft("pet", i, true) if SFrames.Tooltip then SFrames.Tooltip:Hide() end if timeLeft and timeLeft > 0 then b.expirationTime = GetTime() + timeLeft b.cdText:SetText(SFrames:FormatTime(timeLeft)) else b.expirationTime = nil b.cdText:SetText("") end b:Show() numBuffs = numBuffs + 1 else b.expirationTime = nil b.cdText:SetText("") b:Hide() end end local firstDebuff = f.debuffs[1] if firstDebuff then firstDebuff:ClearAllPoints() if numBuffs > 0 then local lastRowStart = math.floor((numBuffs - 1) / PET_AURAS_PER_ROW) * PET_AURAS_PER_ROW + 1 firstDebuff:SetPoint("TOP", f.buffs[lastRowStart], "BOTTOM", 0, -PET_AURA_ROW_SPACING) else firstDebuff:SetPoint("TOPLEFT", f, "BOTTOMLEFT", 0, -1) end end local hasNP = NanamiPlates_SpellDB and NanamiPlates_SpellDB.UnitDebuff local npFormat = NanamiPlates_Auras and NanamiPlates_Auras.FormatTime for i = 1, PET_DEBUFF_COUNT do local texture, debuffCount, debuffType = UnitDebuff("pet", i) local b = f.debuffs[i] b:SetID(i) if texture then b.icon:SetTexture(texture) if debuffType and DebuffTypeColor and DebuffTypeColor[debuffType] then local c = DebuffTypeColor[debuffType] b:SetBackdropBorderColor(c.r, c.g, c.b, 1) else b:SetBackdropBorderColor(0.8, 0, 0, 1) end local timeLeft = 0 local effectName = nil if hasNP then local effect, rank, _, stacks, dtype, duration, npTimeLeft, isOwn = NanamiPlates_SpellDB:UnitDebuff("pet", i) effectName = effect if npTimeLeft and npTimeLeft > 0 then timeLeft = npTimeLeft elseif effect and effect ~= "" and duration and duration > 0 and NanamiPlates_Auras and NanamiPlates_Auras.timers then local unitKey = (UnitGUID and UnitGUID("pet")) or UnitName("pet") or "" local cached = NanamiPlates_Auras.timers[unitKey .. "_" .. effect] if not cached and UnitName("pet") then cached = NanamiPlates_Auras.timers[UnitName("pet") .. "_" .. effect] end if cached and cached.startTime and cached.duration then local remaining = cached.duration - (GetTime() - cached.startTime) if remaining > 0 then timeLeft = remaining end end end end if timeLeft <= 0 then if SFrames.Tooltip then SFrames.Tooltip:SetOwner(UIParent, "ANCHOR_NONE") SFrames.Tooltip:ClearLines() SFrames.Tooltip:SetUnitDebuff("pet", i) end timeLeft = SFrames:GetAuraTimeLeft("pet", i, false) if SFrames.Tooltip then SFrames.Tooltip:Hide() end end if timeLeft and timeLeft > 0 then b.expirationTime = GetTime() + timeLeft b.effectName = effectName if npFormat then local text, r, g, bc, a = npFormat(timeLeft) b.cdText:SetText(text) if r then b.cdText:SetTextColor(r, g, bc, a or 1) end else b.cdText:SetText(SFrames:FormatTime(timeLeft)) end else b.expirationTime = nil b.effectName = nil b.cdText:SetText("") end b:Show() else b.expirationTime = nil b.effectName = nil b.cdText:SetText("") b:SetBackdropBorderColor(0, 0, 0, 1) b:Hide() end end end function SFrames.Pet:TickAuras() if not UnitExists("pet") then return end local f = self.frame if not f.buffs then return end local timeNow = GetTime() local npFormat = NanamiPlates_Auras and NanamiPlates_Auras.FormatTime local hasNP = NanamiPlates_SpellDB and NanamiPlates_SpellDB.FindEffectData local petName, petLevel, petGUID if hasNP then petName = UnitName("pet") petLevel = UnitLevel("pet") or 0 petGUID = UnitGUID and UnitGUID("pet") end for i = 1, PET_BUFF_COUNT do local b = f.buffs[i] if b:IsShown() and b.expirationTime then local timeLeft = b.expirationTime - timeNow if timeLeft > 0 and timeLeft < 3600 then if npFormat then local text, r, g, bc, a = npFormat(timeLeft) b.cdText:SetText(text) if r then b.cdText:SetTextColor(r, g, bc, a or 1) end else b.cdText:SetText(SFrames:FormatTime(timeLeft)) end else b.cdText:SetText("") end end end for i = 1, PET_DEBUFF_COUNT do local b = f.debuffs[i] if b:IsShown() then local timeLeft = nil if hasNP and b.effectName then local data = petGUID and NanamiPlates_SpellDB:FindEffectData(petGUID, petLevel, b.effectName) if not data and petName then data = NanamiPlates_SpellDB:FindEffectData(petName, petLevel, b.effectName) end if data and data.start and data.duration then local remaining = data.duration + data.start - timeNow if remaining > 0 then timeLeft = remaining b.expirationTime = timeNow + remaining end end end if not timeLeft and b.expirationTime then timeLeft = b.expirationTime - timeNow end if timeLeft and timeLeft > 0 and timeLeft < 3600 then if npFormat then local text, r, g, bc, a = npFormat(timeLeft) b.cdText:SetText(text) if r then b.cdText:SetTextColor(r, g, bc, a or 1) end else b.cdText:SetText(SFrames:FormatTime(timeLeft)) end else b.cdText:SetText("") end end end end function SFrames.Pet:HideAuras() local f = self.frame if not f or not f.buffs then return end for i = 1, PET_BUFF_COUNT do f.buffs[i].expirationTime = nil f.buffs[i].cdText:SetText("") f.buffs[i]:Hide() end for i = 1, PET_DEBUFF_COUNT do f.debuffs[i].expirationTime = nil f.debuffs[i].effectName = nil f.debuffs[i].cdText:SetText("") f.debuffs[i]:SetBackdropBorderColor(0, 0, 0, 1) f.debuffs[i]:Hide() end end -------------------------------------------------------------------------------- -- Pet Happiness Warning -------------------------------------------------------------------------------- local WARNING_REMIND_INTERVAL_YELLOW = 60 local WARNING_REMIND_INTERVAL_RED = 30 function SFrames.Pet:CreateHappinessWarning() local f = self.frame local fontPath = SFrames:GetFont() local outline = (SFrames.Media and SFrames.Media.fontOutline) or "OUTLINE" local warnFrame = CreateFrame("Frame", "SFramesPetHappinessWarn", f) warnFrame:SetWidth(180) warnFrame:SetHeight(22) warnFrame:SetPoint("BOTTOM", f, "TOP", 0, 2) warnFrame:SetFrameStrata("HIGH") local warnBg = warnFrame:CreateTexture(nil, "BACKGROUND") warnBg:SetAllPoints() warnBg:SetTexture("Interface\\Buttons\\WHITE8X8") warnBg:SetVertexColor(0, 0, 0, 0.55) warnFrame.bg = warnBg local warnText = warnFrame:CreateFontString(nil, "OVERLAY") warnText:SetFont(fontPath, 11, outline) warnText:SetPoint("CENTER", warnFrame, "CENTER", 0, 0) warnText:SetShadowColor(0, 0, 0, 1) warnText:SetShadowOffset(1, -1) warnFrame.text = warnText warnFrame:Hide() self.warnFrame = warnFrame self.lastHappiness = nil self.lastWarnTime = 0 self.warnFlashAlpha = 1 self.warnFlashDir = -1 warnFrame:SetScript("OnUpdate", function() SFrames.Pet:WarningFlashUpdate() end) end function SFrames.Pet:WarningFlashUpdate() if not self.warnFrame or not self.warnFrame:IsShown() then return end if not self.warnSeverity or self.warnSeverity ~= "red" then return end local speed = 2.5 local dt = arg1 or 0.016 self.warnFlashAlpha = self.warnFlashAlpha + self.warnFlashDir * speed * dt if self.warnFlashAlpha <= 0.25 then self.warnFlashAlpha = 0.25 self.warnFlashDir = 1 elseif self.warnFlashAlpha >= 1 then self.warnFlashAlpha = 1 self.warnFlashDir = -1 end self.warnFrame.text:SetAlpha(self.warnFlashAlpha) self.warnFrame.bg:SetVertexColor(0.4, 0, 0, 0.55 * self.warnFlashAlpha) end function SFrames.Pet:ShowHappinessWarning(happiness) if not self.warnFrame then return end if happiness == 3 then self:HideHappinessWarning() return end local now = GetTime() local isNewState = (self.lastHappiness ~= happiness) if happiness == 2 then self.warnFrame.text:SetText("宠物心情一般,攻击力受影响!") self.warnFrame.text:SetTextColor(1, 0.82, 0.2) self.warnFrame.bg:SetVertexColor(0.3, 0.25, 0, 0.55) self.warnFrame.text:SetAlpha(1) self.warnSeverity = "yellow" self.warnFrame:Show() if isNewState or (now - self.lastWarnTime >= WARNING_REMIND_INTERVAL_YELLOW) then SFrames:Print("|cffffff00宠物心情一般|r - 攻击力下降,请及时喂食!") self.lastWarnTime = now end elseif happiness == 1 then self.warnFrame.text:SetText("宠物很不开心,快要跑了!") self.warnFrame.text:SetTextColor(1, 0.2, 0.2) self.warnFrame.bg:SetVertexColor(0.4, 0, 0, 0.55) self.warnFlashAlpha = 1 self.warnFlashDir = -1 self.warnSeverity = "red" self.warnFrame:Show() if isNewState or (now - self.lastWarnTime >= WARNING_REMIND_INTERVAL_RED) then SFrames:Print("|cffff3333宠物非常不开心,即将离你而去!|r 请立即喂食!") UIErrorsFrame:AddMessage("宠物快要跑了!请立即喂食!", 1, 0.2, 0.2, 1, 3) self.lastWarnTime = now end end self.lastHappiness = happiness end function SFrames.Pet:HideHappinessWarning() if self.warnFrame then self.warnFrame:Hide() end self.warnSeverity = nil self.lastHappiness = nil end -------------------------------------------------------------------------------- -- Pet Castbar -------------------------------------------------------------------------------- function SFrames.Pet:CreateCastbar() local f = self.frame local cbHeight = SFrames.Config.castbarHeight local cb = SFrames:CreateStatusBar(f, "SFramesPetCastbar") cb:SetHeight(cbHeight) cb:SetPoint("BOTTOMLEFT", f, "TOPLEFT", 0, 4) cb:SetPoint("BOTTOMRIGHT", f, "TOPRIGHT", -(cbHeight + 6), 4) local cbbg = CreateFrame("Frame", nil, f) 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(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 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(cbHeight + 2) cb.icon:SetHeight(cbHeight + 2) cb.icon:SetPoint("LEFT", cb, "RIGHT", 4, 0) cb.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93) local ibg = CreateFrame("Frame", nil, f) 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() f.castbar = cb f.castbar.cbbg = cbbg f.castbar.ibg = ibg f.castbarUpdater = CreateFrame("Frame", nil, f) f.castbarUpdater:SetScript("OnUpdate", function() SFrames.Pet:CastbarOnUpdate() end) end function SFrames.Pet:CastbarOnUpdate() local cb = self.frame.castbar if not UnitExists("pet") then cb:Hide() cb.cbbg:Hide() cb.icon:Hide() cb.ibg:Hide() return end local cast, texture, startTime, endTime, channel -- 1) Try native UnitCastingInfo / UnitChannelInfo (TurtleWoW extended API) local _UnitCastingInfo = UnitCastingInfo or (ShaguTweaks and ShaguTweaks.UnitCastingInfo) if _UnitCastingInfo then local c, _, _, tex, st, et = _UnitCastingInfo("pet") if c then cast, texture, startTime, endTime = c, tex, st, et end end if not cast then local _UnitChannelInfo = UnitChannelInfo or (ShaguTweaks and ShaguTweaks.UnitChannelInfo) if _UnitChannelInfo then local c, _, _, tex, st, et = _UnitChannelInfo("pet") if c then cast, texture, startTime, endTime = c, tex, st, et channel = true end end end -- 2) Fallback: SuperWoW castdb (GUID-based) if not cast and SFrames.castdb and UnitGUID then local guid = UnitGUID("pet") if guid then local entry = SFrames.castdb[guid] if entry and entry.cast and entry.start and entry.casttime then local elapsed = GetTime() - entry.start local duration = entry.casttime / 1000 if elapsed < duration + 0.5 then cast = entry.cast texture = entry.icon startTime = entry.start * 1000 endTime = (entry.start + duration) * 1000 channel = entry.channel end end end end if cast and startTime and endTime then local duration = (endTime - startTime) / 1000 local cur = GetTime() - (startTime / 1000) if channel then cur = duration + (startTime / 1000) - GetTime() end if cur > duration then cur = duration end if cur < 0 then cur = 0 end cb:SetMinMaxValues(0, duration) cb:SetValue(cur) cb.text:SetText(cast) cb.time:SetText(string.format("%.1f", channel and cur or math.max(duration - cur, 0))) if texture then cb.icon:SetTexture(texture) end cb:SetAlpha(1) cb.cbbg:SetAlpha(1) cb.icon:SetAlpha(1) cb.ibg:SetAlpha(1) cb:Show() cb.cbbg:Show() cb.icon:Show() cb.ibg:Show() else if cb:IsShown() 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:Hide() cb.cbbg:Hide() cb.icon:Hide() cb.ibg:Hide() end end end end