1851 lines
64 KiB
Lua
1851 lines
64 KiB
Lua
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)
|
||
|
||
local frameScale = (SFramesDB and type(SFramesDB.petFrameScale) == "number") and SFramesDB.petFrameScale or 1
|
||
f:SetScale(Clamp(frameScale, 0.7, 1.8))
|
||
|
||
if SFramesDB and SFramesDB.Positions and SFramesDB.Positions["PetFrame"] then
|
||
local pos = SFramesDB.Positions["PetFrame"]
|
||
local fScale = f:GetEffectiveScale() / UIParent:GetEffectiveScale()
|
||
if fScale > 0.01 and math.abs(fScale - 1) > 0.001 then
|
||
f:SetPoint(pos.point, UIParent, pos.relativePoint,
|
||
(pos.xOfs or 0) / fScale, (pos.yOfs or 0) / fScale)
|
||
else
|
||
f:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs or 0, pos.yOfs or 0)
|
||
end
|
||
else
|
||
f:SetPoint("TOPLEFT", SFramesPlayerFrame, "BOTTOMLEFT", 0, -75)
|
||
end
|
||
|
||
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()
|
||
local fScale = f:GetEffectiveScale() / UIParent:GetEffectiveScale()
|
||
if fScale > 0.01 and math.abs(fScale - 1) > 0.001 then
|
||
xOfs = (xOfs or 0) * fScale
|
||
yOfs = (yOfs or 0) * fScale
|
||
end
|
||
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.healthBGFrame = 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.powerBGFrame = 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:ApplyConfig()
|
||
self:UpdateAll()
|
||
|
||
if SFrames.Movers and SFrames.Movers.RegisterMover and self.frame then
|
||
SFrames.Movers:RegisterMover("PetFrame", self.frame, "宠物",
|
||
"TOPLEFT", "SFramesPlayerFrame", "BOTTOMLEFT", 0, -75,
|
||
nil, { alwaysShowInLayout = true })
|
||
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:ApplyConfig()
|
||
if not self.frame then return end
|
||
local f = self.frame
|
||
|
||
-- Apply bar textures
|
||
SFrames:ApplyStatusBarTexture(f.health, "petHealthTexture", "barTexture")
|
||
SFrames:ApplyStatusBarTexture(f.power, "petPowerTexture", "barTexture")
|
||
local healthTex = SFrames:ResolveBarTexture("petHealthTexture", "barTexture")
|
||
local powerTex = SFrames:ResolveBarTexture("petPowerTexture", "barTexture")
|
||
if f.health and f.health.bg then f.health.bg:SetTexture(healthTex) end
|
||
if f.power and f.power.bg then f.power.bg:SetTexture(powerTex) end
|
||
|
||
if SFrames:IsGradientStyle() then
|
||
-- Strip backdrops
|
||
SFrames:ClearBackdrop(f)
|
||
SFrames:ClearBackdrop(f.healthBGFrame)
|
||
SFrames:ClearBackdrop(f.powerBGFrame)
|
||
-- Health bar full width
|
||
if f.health then
|
||
f.health:ClearAllPoints()
|
||
f.health:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
|
||
f.health:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, 0)
|
||
f.health:SetHeight(18)
|
||
end
|
||
-- Power bar full width
|
||
if f.power then
|
||
f.power:ClearAllPoints()
|
||
f.power:SetPoint("TOPLEFT", f.health, "BOTTOMLEFT", 0, -2)
|
||
f.power:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
|
||
end
|
||
-- Apply gradient overlays
|
||
SFrames:ApplyGradientStyle(f.health)
|
||
SFrames:ApplyGradientStyle(f.power)
|
||
-- Flush BG frames
|
||
if f.healthBGFrame then
|
||
f.healthBGFrame:ClearAllPoints()
|
||
f.healthBGFrame:SetPoint("TOPLEFT", f.health, "TOPLEFT", 0, 0)
|
||
f.healthBGFrame:SetPoint("BOTTOMRIGHT", f.health, "BOTTOMRIGHT", 0, 0)
|
||
end
|
||
if f.powerBGFrame then
|
||
f.powerBGFrame:ClearAllPoints()
|
||
f.powerBGFrame:SetPoint("TOPLEFT", f.power, "TOPLEFT", 0, 0)
|
||
f.powerBGFrame:SetPoint("BOTTOMRIGHT", f.power, "BOTTOMRIGHT", 0, 0)
|
||
end
|
||
-- Hide bar backgrounds (transparent)
|
||
if f.healthBGFrame then f.healthBGFrame:Hide() end
|
||
if f.powerBGFrame then f.powerBGFrame:Hide() end
|
||
if f.health and f.health.bg then f.health.bg:Hide() end
|
||
if f.power and f.power.bg then f.power.bg:Hide() end
|
||
else
|
||
-- Classic style: restore backdrops
|
||
SFrames:CreateUnitBackdrop(f)
|
||
if f.health and f.health.bg then f.health.bg:Show() end
|
||
if f.power and f.power.bg then f.power.bg:Show() end
|
||
if f.healthBGFrame then
|
||
SFrames:CreateUnitBackdrop(f.healthBGFrame)
|
||
f.healthBGFrame:Show()
|
||
f.healthBGFrame:ClearAllPoints()
|
||
f.healthBGFrame:SetPoint("TOPLEFT", f.health, "TOPLEFT", -1, 1)
|
||
f.healthBGFrame:SetPoint("BOTTOMRIGHT", f.health, "BOTTOMRIGHT", 1, -1)
|
||
end
|
||
if f.powerBGFrame then
|
||
SFrames:CreateUnitBackdrop(f.powerBGFrame)
|
||
f.powerBGFrame:Show()
|
||
f.powerBGFrame:ClearAllPoints()
|
||
f.powerBGFrame:SetPoint("TOPLEFT", f.power, "TOPLEFT", -1, 1)
|
||
f.powerBGFrame:SetPoint("BOTTOMRIGHT", f.power, "BOTTOMRIGHT", 1, -1)
|
||
end
|
||
if f.health then
|
||
f.health:ClearAllPoints()
|
||
f.health:SetPoint("TOPLEFT", f, "TOPLEFT", 1, -1)
|
||
f.health:SetPoint("TOPRIGHT", f, "TOPRIGHT", -1, -1)
|
||
f.health:SetHeight(18)
|
||
end
|
||
if f.power then
|
||
f.power:ClearAllPoints()
|
||
f.power:SetPoint("TOPLEFT", f.health, "BOTTOMLEFT", 0, -1)
|
||
f.power:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -1, 1)
|
||
end
|
||
SFrames:RemoveGradientStyle(f.health)
|
||
SFrames:RemoveGradientStyle(f.power)
|
||
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)
|
||
if SFrames:IsGradientStyle() then
|
||
SFrames:ApplyBarGradient(self.frame.health)
|
||
end
|
||
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 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
|
||
predMine:Hide(); predOther:Hide(); predOver:Hide()
|
||
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
|
||
predMine:Hide(); predOther:Hide(); predOver:Hide()
|
||
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
|
||
predMine:Hide(); predOther:Hide(); predOver:Hide()
|
||
return
|
||
end
|
||
|
||
local barWidth = self.frame.health:GetWidth()
|
||
if barWidth <= 0 then
|
||
predMine:Hide(); predOther:Hide(); predOver:Hide()
|
||
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
|
||
predMine:Hide(); predOther:Hide(); predOver:Hide()
|
||
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
|
||
if SFrames:IsGradientStyle() then
|
||
SFrames:ApplyBarGradient(self.frame.power)
|
||
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)
|
||
SFrames:UpdateRainbowBar(self.frame.power, power, maxPower, "pet")
|
||
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
|