Files
Nanami-UI/Units/Pet.lua
2026-03-31 18:03:23 +08:00

1750 lines
60 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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