Files
Nanami-UI/Units/Pet.lua
rucky ec9e3c29d6 完成多出修改
修复拾取界面点击无效问题
修复宠物训练界面不显示训练点问题
新增天赋分享到聊天界面
修复飞行界面无法关闭问题
修复术士宠物的显示问题
为天赋界面添加默认数据库支持
框架现在也可自主选择是否启用
玩家框架和目标框架可取消显示3D头像以及透明度修改
背包和银行也添加透明度自定义支持
优化tooltip性能和背包物品显示方式
修复布局模式在ui缩放后不能正常定位的问题
添加硬核模式危险和死亡的工会通报
添加拾取和已拾取的框体
等等
2026-03-23 10:25:25 +08:00

1174 lines
40 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)
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()
GameTooltip_SetDefaultAnchor(GameTooltip, this)
GameTooltip:SetUnit("pet")
GameTooltip:Show()
end)
f:SetScript("OnLeave", function()
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)
-- 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: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
return
end
self.frame:Show()
self:UpdateHealth()
self:UpdatePowerType()
self:UpdatePower()
self:UpdateHappiness()
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
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
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: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
else
self.frame.happinessBG:Hide()
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 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