-------------------------------------------------------------------------------- -- Nanami-UI: Quest & NPC Interaction UI (QuestUI.lua) -- Replaces GossipFrame and QuestFrame with Nanami-UI styled interface -------------------------------------------------------------------------------- SFrames = SFrames or {} SFrames.QuestUI = {} local QUI = SFrames.QuestUI SFramesDB = SFramesDB or {} -------------------------------------------------------------------------------- -- Theme: Pink Cat-Paw (matching Nanami-UI) -------------------------------------------------------------------------------- local T = SFrames.Theme:Extend({ questAvail = { 0.9, 0.82, 0.18 }, questActive = { 0.65, 0.65, 0.65 }, questComplete = { 0.9, 0.82, 0.18 }, moneyGold = { 1, 0.84, 0.0 }, moneySilver = { 0.78, 0.78, 0.78 }, moneyCopper = { 0.71, 0.43, 0.18 }, }) local QUALITY_COLORS = { [0] = { 0.62, 0.62, 0.62 }, [1] = { 1, 1, 1 }, [2] = { 0.12, 1, 0 }, [3] = { 0.0, 0.44, 0.87 }, [4] = { 0.64, 0.21, 0.93 }, [5] = { 1, 0.5, 0 }, } local GOSSIP_ICONS = { ["gossip"] = "Interface\\GossipFrame\\GossipGossipIcon", ["vendor"] = "Interface\\GossipFrame\\VendorGossipIcon", ["taxi"] = "Interface\\GossipFrame\\TaxiGossipIcon", ["trainer"] = "Interface\\GossipFrame\\TrainerGossipIcon", ["banker"] = "Interface\\GossipFrame\\BankerGossipIcon", ["petition"] = "Interface\\GossipFrame\\PetitionGossipIcon", ["tabard"] = "Interface\\GossipFrame\\TabardGossipIcon", ["battlemaster"] = "Interface\\GossipFrame\\BattleMasterGossipIcon", } local QUEST_ICON_AVAILABLE = "Interface\\GossipFrame\\AvailableQuestIcon" local QUEST_ICON_ACTIVE = "Interface\\GossipFrame\\ActiveQuestIcon" local GOSSIP_TRIGGER_TEXTS = { ["TRANSMOG_TRIGGER"] = true, } -------------------------------------------------------------------------------- -- Layout -------------------------------------------------------------------------------- local FRAME_W = 340 local FRAME_H = 400 local HEADER_H = 34 local BOTTOM_H = 42 local SIDE_PAD = 14 local CONTENT_W = FRAME_W - SIDE_PAD * 2 local ITEM_SIZE = 36 local ITEM_GAP = 4 local SCROLL_STEP = 40 local MAX_OPTIONS = 24 local MAX_ITEMS = 10 -------------------------------------------------------------------------------- -- State -------------------------------------------------------------------------------- local MainFrame = nil local currentPage = nil local previousPage = nil local selectedReward = 0 local pendingClose = false local closeTimer = 0 local pages = {} -------------------------------------------------------------------------------- -- Helpers -------------------------------------------------------------------------------- local function GetFont() if SFrames and SFrames.GetFont then return SFrames:GetFont() end return "Fonts\\ARIALN.TTF" end local function SetRoundBackdrop(frame) frame: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 }, }) frame:SetBackdropColor(T.panelBg[1], T.panelBg[2], T.panelBg[3], T.panelBg[4]) frame:SetBackdropBorderColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3], T.panelBorder[4]) end local function CreateShadow(parent) local s = CreateFrame("Frame", nil, parent) s:SetPoint("TOPLEFT", parent, "TOPLEFT", -4, 4) s:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 4, -4) s: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 }, }) s:SetBackdropColor(0, 0, 0, 0.45) s:SetBackdropBorderColor(0, 0, 0, 0.6) s:SetFrameLevel(math.max(0, parent:GetFrameLevel() - 1)) return s end local function MakeSep(parent, width) local sep = parent:CreateTexture(nil, "ARTWORK") sep:SetTexture("Interface\\Buttons\\WHITE8X8") sep:SetWidth(width or CONTENT_W) sep:SetHeight(1) sep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4]) return sep end local function GetDiffColor(level) if GetDifficultyColor then local c = GetDifficultyColor(level) if c then return c.r, c.g, c.b end end local pLvl = UnitLevel("player") or 60 local diff = (level or pLvl) - pLvl if diff >= 5 then return 1, 0.1, 0.1 elseif diff >= 3 then return 1, 0.5, 0.25 elseif diff >= -2 then return 1, 1, 0 else return 0.25, 0.75, 0.25 end end local function TextHeight(fs, fallback) if not fs then return fallback or 14 end local h = fs.GetStringHeight and fs:GetStringHeight() if h and h > 0 then return h end h = fs:GetHeight() if h and h > 1 then return h end return fallback or 14 end local function ForwardScrollWheel(frame) frame:EnableMouseWheel(true) frame:SetScript("OnMouseWheel", function() local p = this:GetParent() while p do if p:GetObjectType() == "ScrollFrame" then local cur = p:GetVerticalScroll() local maxVal = p:GetVerticalScrollRange() if arg1 > 0 then p:SetVerticalScroll(math.max(0, cur - SCROLL_STEP)) else p:SetVerticalScroll(math.min(maxVal, cur + SCROLL_STEP)) end return end p = p:GetParent() end end) end local function FormatMoney(copper) if not copper or copper <= 0 then return nil end local g = math.floor(copper / 10000) local s = math.floor(math.mod(copper, 10000) / 100) local c = math.mod(copper, 100) return g, s, c end local LINK_QUALITY_MAP = { ["9d9d9d"] = 0, ["ffffff"] = 1, ["1eff00"] = 2, ["0070dd"] = 3, ["a335ee"] = 4, ["ff8000"] = 5, ["e6cc80"] = 6, } local function QualityFromLink(link) if not link then return nil end local _, _, hex = string.find(link, "|c(%x%x%x%x%x%x%x%x)|") if hex and string.len(hex) == 8 then local color = string.lower(string.sub(hex, 3, 8)) return LINK_QUALITY_MAP[color] end return nil end local function GetItemQuality(itemType, index) local name, texture, numItems, quality, isUsable = GetQuestItemInfo(itemType, index) if quality and quality > 0 then return quality end local link if GetQuestItemLink then link = GetQuestItemLink(itemType, index) end if link then if GetItemInfo then local _, _, q = GetItemInfo(link) if q and q > 0 then return q end end local q = QualityFromLink(link) if q then return q end end return nil end -------------------------------------------------------------------------------- -- Scroll Area Factory -------------------------------------------------------------------------------- local function CreateScrollArea(parent, name) local scroll = CreateFrame("ScrollFrame", name, parent) local content = CreateFrame("Frame", name .. "Content", scroll) content:SetWidth(CONTENT_W) content:SetHeight(1) scroll:SetScrollChild(content) scroll:EnableMouseWheel(true) scroll:SetScript("OnMouseWheel", function() local cur = this:GetVerticalScroll() local maxVal = this:GetVerticalScrollRange() if arg1 > 0 then this:SetVerticalScroll(math.max(0, cur - SCROLL_STEP)) else this:SetVerticalScroll(math.min(maxVal, cur + SCROLL_STEP)) end end) scroll.content = content return scroll end -------------------------------------------------------------------------------- -- Action Button Factory -------------------------------------------------------------------------------- local function CreateActionBtn(parent, text, w) local btn = CreateFrame("Button", nil, parent) btn:SetWidth(w or 100) btn:SetHeight(28) btn: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 }, }) 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(GetFont(), 11, "OUTLINE") fs:SetPoint("CENTER", 0, 0) fs:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3]) fs:SetText(text) btn.label = fs btn.disabled = false function btn:SetDisabled(flag) self.disabled = flag if flag then self.label:SetTextColor(T.btnDisabledText[1], T.btnDisabledText[2], T.btnDisabledText[3]) self:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], 0.5) self:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], 0.5) else self.label:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3]) self:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4]) self:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], T.btnBorder[4]) end end btn:SetScript("OnEnter", function() if not this.disabled then 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 end) btn:SetScript("OnLeave", function() if not this.disabled then 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 end) btn:SetScript("OnMouseDown", function() if not this.disabled then this:SetBackdropColor(T.btnDownBg[1], T.btnDownBg[2], T.btnDownBg[3], T.btnDownBg[4]) end end) btn:SetScript("OnMouseUp", function() if not this.disabled then this:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4]) end end) return btn end -------------------------------------------------------------------------------- -- Gossip / Quest Option Button Factory -------------------------------------------------------------------------------- local function CreateOptionButton(parent) local btn = CreateFrame("Button", nil, parent) btn:SetHeight(22) btn:SetWidth(CONTENT_W) local icon = btn:CreateTexture(nil, "ARTWORK") icon:SetWidth(16) icon:SetHeight(16) icon:SetPoint("LEFT", btn, "LEFT", 2, 0) btn.icon = icon local text = btn:CreateFontString(nil, "OVERLAY") text:SetFont(GetFont(), 12) text:SetPoint("LEFT", icon, "RIGHT", 6, 0) text:SetPoint("RIGHT", btn, "RIGHT", -4, 0) text:SetJustifyH("LEFT") text:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3]) btn.text = text btn:SetHighlightTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight", "ADD") btn.normalR = T.nameText[1] btn.normalG = T.nameText[2] btn.normalB = T.nameText[3] btn:SetScript("OnEnter", function() this.text:SetTextColor(T.gold[1], T.gold[2], T.gold[3]) end) btn:SetScript("OnLeave", function() this.text:SetTextColor(this.normalR, this.normalG, this.normalB) end) ForwardScrollWheel(btn) return btn end -------------------------------------------------------------------------------- -- Item Slot Factory -------------------------------------------------------------------------------- local function CreateItemSlot(parent) local slot = CreateFrame("Button", nil, parent) slot:SetWidth(CONTENT_W) slot:SetHeight(ITEM_SIZE + 4) -- Selection highlight background (covers entire row) local selBg = slot:CreateTexture(nil, "BACKGROUND") selBg:SetTexture("Interface\\Buttons\\WHITE8X8") selBg:SetAllPoints(slot) selBg:SetVertexColor(T.questSelected[1], T.questSelected[2], T.questSelected[3], T.questSelected[4] or 0.45) selBg:Hide() slot.selBg = selBg -- Selection glow border around entire row local selGlow = CreateFrame("Frame", nil, slot) selGlow:SetPoint("TOPLEFT", slot, "TOPLEFT", -2, 2) selGlow:SetPoint("BOTTOMRIGHT", slot, "BOTTOMRIGHT", 2, -2) selGlow:SetBackdrop({ edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", edgeSize = 12, insets = { left = 2, right = 2, top = 2, bottom = 2 }, }) selGlow:SetBackdropBorderColor(T.questSelBorder[1], T.questSelBorder[2], T.questSelBorder[3], T.questSelBorder[4] or 0.9) selGlow:Hide() slot.selGlow = selGlow local iconFrame = CreateFrame("Frame", nil, slot) iconFrame:SetWidth(ITEM_SIZE) iconFrame:SetHeight(ITEM_SIZE) iconFrame:SetPoint("LEFT", slot, "LEFT", 0, 0) iconFrame:SetBackdrop({ bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", tile = true, tileSize = 16, edgeSize = 14, insets = { left = 2, right = 2, top = 2, bottom = 2 }, }) iconFrame:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4]) iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4]) slot.iconFrame = iconFrame local icon = iconFrame:CreateTexture(nil, "ARTWORK") icon:SetTexCoord(0.08, 0.92, 0.08, 0.92) icon:SetPoint("TOPLEFT", iconFrame, "TOPLEFT", 3, -3) icon:SetPoint("BOTTOMRIGHT", iconFrame, "BOTTOMRIGHT", -3, 3) slot.icon = icon local qualGlow = iconFrame:CreateTexture(nil, "OVERLAY") qualGlow:SetTexture("Interface\\Buttons\\UI-ActionButton-Border") qualGlow:SetBlendMode("ADD") qualGlow:SetAlpha(0.7) qualGlow:SetWidth(ITEM_SIZE * 1.8) qualGlow:SetHeight(ITEM_SIZE * 1.8) qualGlow:SetPoint("CENTER", iconFrame, "CENTER", 0, 0) qualGlow:Hide() slot.qualGlow = qualGlow local countFS = iconFrame:CreateFontString(nil, "OVERLAY") countFS:SetFont("Fonts\\ARIALN.TTF", 11, "OUTLINE") countFS:SetPoint("BOTTOMRIGHT", iconFrame, "BOTTOMRIGHT", -2, 2) countFS:SetJustifyH("RIGHT") slot.countFS = countFS local nameFS = slot:CreateFontString(nil, "OVERLAY") nameFS:SetFont(GetFont(), 12, "OUTLINE") nameFS:SetPoint("LEFT", iconFrame, "RIGHT", 8, 0) nameFS:SetPoint("RIGHT", slot, "RIGHT", -4, 0) nameFS:SetJustifyH("LEFT") nameFS:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3]) slot.nameFS = nameFS slot.itemType = nil slot.itemIndex = nil slot:SetScript("OnEnter", function() if this.itemType and this.itemIndex then GameTooltip:SetOwner(this, "ANCHOR_RIGHT") GameTooltip:SetQuestItem(this.itemType, this.itemIndex) GameTooltip:Show() end this.iconFrame:SetBackdropBorderColor(T.slotHover[1], T.slotHover[2], T.slotHover[3], T.slotHover[4]) end) slot:SetScript("OnLeave", function() GameTooltip:Hide() if this.selected then this.iconFrame:SetBackdropBorderColor(T.slotSelected[1], T.slotSelected[2], T.slotSelected[3], T.slotSelected[4]) else local qc = QUALITY_COLORS[this._quality] if qc and this._quality and this._quality >= 2 then this.iconFrame:SetBackdropBorderColor(qc[1], qc[2], qc[3], 1) else this.iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4]) end end end) function slot:ApplyItemData(name, texture, numItems, quality) self.icon:SetTexture(texture) self.nameFS:SetText(name or "") if numItems and numItems > 1 then self.countFS:SetText(numItems) else self.countFS:SetText("") end self._quality = quality local qc = QUALITY_COLORS[quality] if qc and quality and quality >= 2 then self.qualGlow:SetVertexColor(qc[1], qc[2], qc[3]) self.qualGlow:Show() self.nameFS:SetTextColor(qc[1], qc[2], qc[3]) self.iconFrame:SetBackdropBorderColor(qc[1], qc[2], qc[3], 1) else self.qualGlow:Hide() self.nameFS:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3]) self.iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4]) end end function slot:SetItemInfo(iType, idx) self.itemType = iType self.itemIndex = idx local name, texture, numItems, quality, isUsable = GetQuestItemInfo(iType, idx) if not quality or quality <= 0 then quality = GetItemQuality(iType, idx) end self:ApplyItemData(name, texture, numItems, quality or 1) self:Show() self:SetScript("OnUpdate", nil) local needRetry = (not name or name == "") or (quality == nil) or (not texture or texture == "") if needRetry then self._retryElapsed = 0 self._retryAttempts = 0 self:SetScript("OnUpdate", function() this._retryElapsed = (this._retryElapsed or 0) + (arg1 or 0) if this._retryElapsed < 0.2 then return end this._retryElapsed = 0 this._retryAttempts = (this._retryAttempts or 0) + 1 if this._retryAttempts > 15 then this:SetScript("OnUpdate", nil) return end if this.itemType and this.itemIndex then local n, t, ni, q = GetQuestItemInfo(this.itemType, this.itemIndex) if not q or q <= 0 then q = GetItemQuality(this.itemType, this.itemIndex) end local nameOk = n and n ~= "" local texOk = t and t ~= "" local qualOk = q ~= nil if nameOk and texOk and qualOk then this:ApplyItemData(n, t, ni, q) this:SetScript("OnUpdate", nil) elseif this._retryAttempts >= 15 then if nameOk then this:ApplyItemData(n, t, ni, q or 1) end this:SetScript("OnUpdate", nil) end end end) end end function slot:SetSelected(flag) self.selected = flag if flag then self.iconFrame:SetBackdropBorderColor(T.slotSelected[1], T.slotSelected[2], T.slotSelected[3], T.slotSelected[4]) self.iconFrame:SetBackdropColor(T.slotSelected[1], T.slotSelected[2], T.slotSelected[3], T.slotSelected[4] or 0.95) self.selBg:Show() self.selGlow:Show() self.nameFS:SetTextColor(T.title[1], T.title[2], T.title[3]) else local qc = QUALITY_COLORS[self._quality] if qc and self._quality and self._quality >= 2 then self.iconFrame:SetBackdropBorderColor(qc[1], qc[2], qc[3], 1) self.nameFS:SetTextColor(qc[1], qc[2], qc[3]) else self.iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4]) self.nameFS:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3]) end self.iconFrame:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4]) self.selBg:Hide() self.selGlow:Hide() end end function slot:Clear() self:SetScript("OnUpdate", nil) self.itemType = nil self.itemIndex = nil self._quality = nil self.icon:SetTexture(nil) self.nameFS:SetText("") self.countFS:SetText("") self.qualGlow:Hide() self.nameFS:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3]) self.selected = false self.iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4]) self.iconFrame:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4]) self.selBg:Hide() self.selGlow:Hide() self:Hide() end ForwardScrollWheel(slot) return slot end -------------------------------------------------------------------------------- -- Money Display -------------------------------------------------------------------------------- local function CreateMoneyLine(parent) local frame = CreateFrame("Frame", nil, parent) frame:SetWidth(CONTENT_W) frame:SetHeight(16) local font = GetFont() local gTxt = frame:CreateFontString(nil, "OVERLAY") gTxt:SetFont(font, 12, "OUTLINE") local gTex = frame:CreateTexture(nil, "ARTWORK") gTex:SetTexture("Interface\\MoneyFrame\\UI-MoneyIcons") gTex:SetTexCoord(0, 0.25, 0, 1) gTex:SetWidth(13); gTex:SetHeight(13) local sTxt = frame:CreateFontString(nil, "OVERLAY") sTxt:SetFont(font, 12, "OUTLINE") local sTex = frame:CreateTexture(nil, "ARTWORK") sTex:SetTexture("Interface\\MoneyFrame\\UI-MoneyIcons") sTex:SetTexCoord(0.25, 0.5, 0, 1) sTex:SetWidth(13); sTex:SetHeight(13) local cTxt = frame:CreateFontString(nil, "OVERLAY") cTxt:SetFont(font, 12, "OUTLINE") local cTex = frame:CreateTexture(nil, "ARTWORK") cTex:SetTexture("Interface\\MoneyFrame\\UI-MoneyIcons") cTex:SetTexCoord(0.5, 0.75, 0, 1) cTex:SetWidth(13); cTex:SetHeight(13) frame.gTxt = gTxt; frame.gTex = gTex frame.sTxt = sTxt; frame.sTex = sTex frame.cTxt = cTxt; frame.cTex = cTex function frame:SetMoney(copper) local g, s, c = FormatMoney(copper) if not g then self:Hide(); return end local x = 0 if g > 0 then self.gTxt:SetText(g) self.gTxt:SetTextColor(T.moneyGold[1], T.moneyGold[2], T.moneyGold[3]) self.gTxt:ClearAllPoints() self.gTxt:SetPoint("LEFT", self, "LEFT", x, 0) x = x + self.gTxt:GetStringWidth() + 1 self.gTex:ClearAllPoints() self.gTex:SetPoint("LEFT", self, "LEFT", x, 0) x = x + 15 self.gTxt:Show(); self.gTex:Show() else self.gTxt:Hide(); self.gTex:Hide() end if s > 0 or g > 0 then self.sTxt:SetText(s) self.sTxt:SetTextColor(T.moneySilver[1], T.moneySilver[2], T.moneySilver[3]) self.sTxt:ClearAllPoints() self.sTxt:SetPoint("LEFT", self, "LEFT", x, 0) x = x + self.sTxt:GetStringWidth() + 1 self.sTex:ClearAllPoints() self.sTex:SetPoint("LEFT", self, "LEFT", x, 0) x = x + 15 self.sTxt:Show(); self.sTex:Show() else self.sTxt:Hide(); self.sTex:Hide() end if c > 0 or (g == 0 and s == 0) then self.cTxt:SetText(c) self.cTxt:SetTextColor(T.moneyCopper[1], T.moneyCopper[2], T.moneyCopper[3]) self.cTxt:ClearAllPoints() self.cTxt:SetPoint("LEFT", self, "LEFT", x, 0) x = x + self.cTxt:GetStringWidth() + 1 self.cTex:ClearAllPoints() self.cTex:SetPoint("LEFT", self, "LEFT", x, 0) self.cTxt:Show(); self.cTex:Show() else self.cTxt:Hide(); self.cTex:Hide() end self:Show() end return frame end -------------------------------------------------------------------------------- -- Gossip data parsers -------------------------------------------------------------------------------- local function ParseGossipAvailableQuests() local data = { GetGossipAvailableQuests() } local out = {} local i = 1 while i <= table.getn(data) do if type(data[i]) == "string" then table.insert(out, { title = data[i], level = tonumber(data[i + 1]) or 0 }) i = i + 2 else i = i + 1 end end return out end local function ParseGossipActiveQuests() local data = { GetGossipActiveQuests() } local out = {} local i = 1 while i <= table.getn(data) do if type(data[i]) == "string" then local q = { title = data[i], level = tonumber(data[i + 1]) or 0 } if i + 2 <= table.getn(data) and type(data[i + 2]) ~= "string" then q.isComplete = data[i + 2] i = i + 3 else i = i + 2 end table.insert(out, q) else i = i + 1 end end return out end local function ParseGossipOptions() local data = { GetGossipOptions() } local out = {} local i = 1 while i <= table.getn(data) do if i + 1 <= table.getn(data) then table.insert(out, { title = data[i], gtype = data[i + 1] or "gossip" }) end i = i + 2 end return out end -------------------------------------------------------------------------------- -- AutoGossip logic (integrated from AutoGossip.lua) -------------------------------------------------------------------------------- local function TryAutoGossip() if SFramesDB and SFramesDB.autoGossip == false then return false end if IsShiftKeyDown() then return false end local active = { GetGossipActiveQuests() } local avail = { GetGossipAvailableQuests() } local opts = { GetGossipOptions() } local nActive = table.getn(active) or 0 local nAvail = table.getn(avail) or 0 local nOpts = table.getn(opts) or 0 if nActive == 0 and nAvail == 0 and nOpts == 2 then SelectGossipOption(1) return true end return false end -------------------------------------------------------------------------------- -- Page Management -------------------------------------------------------------------------------- local function HideAllPages() for _, p in pairs(pages) do if p and p.Hide then p:Hide() end end end local function ShowPage(name) HideAllPages() currentPage = name if pages[name] then pages[name]:Show() if pages[name].scroll then pages[name].scroll:SetVerticalScroll(0) end end if MainFrame then MainFrame.btn1:Hide() MainFrame.btn2:Hide() end end local function GoBack() if DeclineQuest then DeclineQuest() end end local function CloseFrame() currentPage = nil MainFrame:Hide() end local function UpdateBottomButtons() if not MainFrame then return end local bL = MainFrame.btn1 local bR = MainFrame.btn2 local hasBack = previousPage and (currentPage ~= "gossip") and (currentPage ~= "greeting") if currentPage == "gossip" or currentPage == "greeting" then bL:Hide() bR.label:SetText("关闭") bR:SetDisabled(false) bR:SetScript("OnClick", function() if currentPage == "gossip" then if CloseGossip then CloseGossip() end end currentPage = nil MainFrame:Hide() end) bR:Show() elseif currentPage == "detail" then bL.label:SetText("接受") bL:SetDisabled(false) bL:SetScript("OnClick", function() if AcceptQuest then AcceptQuest() end end) bL:Show() bR.label:SetText(hasBack and "返回" or "拒绝") bR:SetDisabled(false) bR:SetScript("OnClick", function() if hasBack then GoBack() else if DeclineQuest then DeclineQuest() end end end) bR:Show() elseif currentPage == "progress" then local canComplete = IsQuestCompletable and IsQuestCompletable() bL.label:SetText("继续") bL:SetDisabled(not canComplete) bL:SetScript("OnClick", function() if not this.disabled and CompleteQuest then CompleteQuest() end end) bL:Show() bR.label:SetText(hasBack and "返回" or "关闭") bR:SetDisabled(false) bR:SetScript("OnClick", function() if hasBack then GoBack() else CloseFrame() end end) bR:Show() elseif currentPage == "complete" then local numChoices = GetNumQuestChoices and GetNumQuestChoices() or 0 local canFinish = (numChoices == 0) or (selectedReward > 0) bL.label:SetText("完成任务") bL:SetDisabled(not canFinish) bL:SetScript("OnClick", function() if this.disabled then return end if GetQuestReward then local nc = GetNumQuestChoices and GetNumQuestChoices() or 0 if nc > 0 then GetQuestReward(selectedReward) else GetQuestReward() end end end) bL:Show() bR.label:SetText(hasBack and "返回" or "关闭") bR:SetDisabled(false) bR:SetScript("OnClick", function() if hasBack then GoBack() else CloseFrame() end end) bR:Show() end end -------------------------------------------------------------------------------- -- GOSSIP PAGE -------------------------------------------------------------------------------- local gossipOptionBtns = {} local gossipSectionLabels = {} local function CreateGossipPage(parent) local page = CreateFrame("Frame", nil, parent) page:SetAllPoints(parent) page:Hide() local scroll = CreateScrollArea(page, "SFramesQuestGossipScroll") scroll:SetPoint("TOPLEFT", page, "TOPLEFT", 0, 0) scroll:SetPoint("BOTTOMRIGHT", page, "BOTTOMRIGHT", 0, 0) page.scroll = scroll local content = scroll.content local npcText = content:CreateFontString(nil, "OVERLAY") npcText:SetFont(GetFont(), 12) npcText:SetWidth(CONTENT_W - 4) npcText:SetJustifyH("LEFT") npcText:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3]) page.npcText = npcText for i = 1, 3 do local lbl = content:CreateFontString(nil, "OVERLAY") lbl:SetFont(GetFont(), 11, "OUTLINE") lbl:SetWidth(CONTENT_W) lbl:SetJustifyH("LEFT") lbl:SetTextColor(T.sectionTitle[1], T.sectionTitle[2], T.sectionTitle[3]) gossipSectionLabels[i] = lbl end for i = 1, MAX_OPTIONS do gossipOptionBtns[i] = CreateOptionButton(content) end page.Update = function() local y = 6 local gossipText = GetGossipText and GetGossipText() or "" page.npcText:SetText(gossipText) page.npcText:ClearAllPoints() page.npcText:SetPoint("TOPLEFT", content, "TOPLEFT", 2, -y) y = y + TextHeight(page.npcText, 14) + 12 local availQuests = ParseGossipAvailableQuests() local activeQuests = ParseGossipActiveQuests() local gossipOpts = ParseGossipOptions() local btnIdx = 1 for i = 1, 3 do gossipSectionLabels[i]:Hide() end for i = 1, MAX_OPTIONS do gossipOptionBtns[i]:Hide() end if table.getn(availQuests) > 0 then gossipSectionLabels[1]:SetText("可接任务") gossipSectionLabels[1]:ClearAllPoints() gossipSectionLabels[1]:SetPoint("TOPLEFT", content, "TOPLEFT", 2, -y) gossipSectionLabels[1]:Show() y = y + 16 for qi = 1, table.getn(availQuests) do local q = availQuests[qi] if btnIdx <= MAX_OPTIONS then local btn = gossipOptionBtns[btnIdx] btn.icon:SetTexture(QUEST_ICON_AVAILABLE) local levelStr = q.level > 0 and ("[" .. q.level .. "] ") or "" btn.text:SetText(levelStr .. (q.title or "")) if q.level > 0 then local r, g, b = GetDiffColor(q.level) btn.normalR = r; btn.normalG = g; btn.normalB = b btn.text:SetTextColor(r, g, b) else btn.normalR = T.questAvail[1]; btn.normalG = T.questAvail[2]; btn.normalB = T.questAvail[3] btn.text:SetTextColor(T.questAvail[1], T.questAvail[2], T.questAvail[3]) end btn:ClearAllPoints() btn:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -y) local idx = qi btn:SetScript("OnClick", function() currentPage = nil MainFrame:Hide() SelectGossipAvailableQuest(idx) end) btn:Show() y = y + 22 btnIdx = btnIdx + 1 end end y = y + 4 end if table.getn(activeQuests) > 0 then gossipSectionLabels[2]:SetText("进行中任务") gossipSectionLabels[2]:ClearAllPoints() gossipSectionLabels[2]:SetPoint("TOPLEFT", content, "TOPLEFT", 2, -y) gossipSectionLabels[2]:Show() y = y + 16 for qi = 1, table.getn(activeQuests) do local q = activeQuests[qi] if btnIdx <= MAX_OPTIONS then local btn = gossipOptionBtns[btnIdx] btn.icon:SetTexture(QUEST_ICON_ACTIVE) local levelStr = q.level > 0 and ("[" .. q.level .. "] ") or "" btn.text:SetText(levelStr .. (q.title or "")) if q.isComplete then btn.normalR = T.questComplete[1]; btn.normalG = T.questComplete[2]; btn.normalB = T.questComplete[3] btn.text:SetTextColor(T.questComplete[1], T.questComplete[2], T.questComplete[3]) else btn.normalR = T.questActive[1]; btn.normalG = T.questActive[2]; btn.normalB = T.questActive[3] btn.text:SetTextColor(T.questActive[1], T.questActive[2], T.questActive[3]) end btn:ClearAllPoints() btn:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -y) local idx = qi btn:SetScript("OnClick", function() currentPage = nil MainFrame:Hide() SelectGossipActiveQuest(idx) end) btn:Show() y = y + 22 btnIdx = btnIdx + 1 end end y = y + 4 end if table.getn(gossipOpts) > 0 then gossipSectionLabels[3]:SetText("对话") gossipSectionLabels[3]:ClearAllPoints() gossipSectionLabels[3]:SetPoint("TOPLEFT", content, "TOPLEFT", 2, -y) gossipSectionLabels[3]:Show() y = y + 16 for oi = 1, table.getn(gossipOpts) do local opt = gossipOpts[oi] if btnIdx <= MAX_OPTIONS then local btn = gossipOptionBtns[btnIdx] btn.icon:SetTexture(GOSSIP_ICONS[opt.gtype] or GOSSIP_ICONS["gossip"]) btn.text:SetText(opt.title or "") btn.normalR = T.nameText[1]; btn.normalG = T.nameText[2]; btn.normalB = T.nameText[3] btn.text:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3]) btn:ClearAllPoints() btn:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -y) local idx = oi btn:SetScript("OnClick", function() currentPage = nil MainFrame:Hide() SelectGossipOption(idx) end) btn:Show() y = y + 22 btnIdx = btnIdx + 1 end end end content:SetHeight(y + 10) end return page end -------------------------------------------------------------------------------- -- GREETING PAGE (multi-quest NPC via QuestFrame) -------------------------------------------------------------------------------- local greetingOptionBtns = {} local greetingSectionLabels = {} local function CreateGreetingPage(parent) local page = CreateFrame("Frame", nil, parent) page:SetAllPoints(parent) page:Hide() local scroll = CreateScrollArea(page, "SFramesQuestGreetingScroll") scroll:SetPoint("TOPLEFT", page, "TOPLEFT", 0, 0) scroll:SetPoint("BOTTOMRIGHT", page, "BOTTOMRIGHT", 0, 0) page.scroll = scroll local content = scroll.content local npcText = content:CreateFontString(nil, "OVERLAY") npcText:SetFont(GetFont(), 12) npcText:SetWidth(CONTENT_W - 4) npcText:SetJustifyH("LEFT") npcText:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3]) page.npcText = npcText for i = 1, 2 do local lbl = content:CreateFontString(nil, "OVERLAY") lbl:SetFont(GetFont(), 11, "OUTLINE") lbl:SetWidth(CONTENT_W) lbl:SetJustifyH("LEFT") lbl:SetTextColor(T.sectionTitle[1], T.sectionTitle[2], T.sectionTitle[3]) greetingSectionLabels[i] = lbl end for i = 1, MAX_OPTIONS do greetingOptionBtns[i] = CreateOptionButton(content) end page.Update = function() local y = 6 local greeting = GetGreetingText and GetGreetingText() or "" page.npcText:SetText(greeting) page.npcText:ClearAllPoints() page.npcText:SetPoint("TOPLEFT", content, "TOPLEFT", 2, -y) y = y + TextHeight(page.npcText, 14) + 12 local numAvail = GetNumAvailableQuests and GetNumAvailableQuests() or 0 local numActive = GetNumActiveQuests and GetNumActiveQuests() or 0 local btnIdx = 1 for i = 1, 2 do greetingSectionLabels[i]:Hide() end for i = 1, MAX_OPTIONS do greetingOptionBtns[i]:Hide() end if numAvail > 0 then greetingSectionLabels[1]:SetText("可接任务") greetingSectionLabels[1]:ClearAllPoints() greetingSectionLabels[1]:SetPoint("TOPLEFT", content, "TOPLEFT", 2, -y) greetingSectionLabels[1]:Show() y = y + 16 for qi = 1, numAvail do if btnIdx <= MAX_OPTIONS then local title = GetAvailableTitle and GetAvailableTitle(qi) or ("Quest " .. qi) local btn = greetingOptionBtns[btnIdx] btn.icon:SetTexture(QUEST_ICON_AVAILABLE) btn.text:SetText(title) btn.normalR = T.questAvail[1]; btn.normalG = T.questAvail[2]; btn.normalB = T.questAvail[3] btn.text:SetTextColor(T.questAvail[1], T.questAvail[2], T.questAvail[3]) btn:ClearAllPoints() btn:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -y) local idx = qi btn:SetScript("OnClick", function() currentPage = nil MainFrame:Hide() SelectAvailableQuest(idx) end) btn:Show() y = y + 22 btnIdx = btnIdx + 1 end end y = y + 4 end if numActive > 0 then greetingSectionLabels[2]:SetText("进行中任务") greetingSectionLabels[2]:ClearAllPoints() greetingSectionLabels[2]:SetPoint("TOPLEFT", content, "TOPLEFT", 2, -y) greetingSectionLabels[2]:Show() y = y + 16 for qi = 1, numActive do if btnIdx <= MAX_OPTIONS then local title = GetActiveTitle and GetActiveTitle(qi) or ("Quest " .. qi) local btn = greetingOptionBtns[btnIdx] btn.icon:SetTexture(QUEST_ICON_ACTIVE) btn.text:SetText(title) btn.normalR = T.questActive[1]; btn.normalG = T.questActive[2]; btn.normalB = T.questActive[3] btn.text:SetTextColor(T.questActive[1], T.questActive[2], T.questActive[3]) btn:ClearAllPoints() btn:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -y) local idx = qi btn:SetScript("OnClick", function() currentPage = nil MainFrame:Hide() SelectActiveQuest(idx) end) btn:Show() y = y + 22 btnIdx = btnIdx + 1 end end end content:SetHeight(y + 10) end return page end -------------------------------------------------------------------------------- -- DETAIL PAGE (accept / decline quest) -------------------------------------------------------------------------------- local detailItemSlots = {} local function CreateDetailPage(parent) local page = CreateFrame("Frame", nil, parent) page:SetAllPoints(parent) page:Hide() local scroll = CreateScrollArea(page, "SFramesQuestDetailScroll") scroll:SetPoint("TOPLEFT", page, "TOPLEFT", 0, 0) scroll:SetPoint("BOTTOMRIGHT", page, "BOTTOMRIGHT", 0, 0) page.scroll = scroll local content = scroll.content page.titleFS = content:CreateFontString(nil, "OVERLAY") page.titleFS:SetFont(GetFont(), 14, "OUTLINE") page.titleFS:SetWidth(CONTENT_W - 4) page.titleFS:SetJustifyH("LEFT") page.titleFS:SetTextColor(T.gold[1], T.gold[2], T.gold[3]) page.sepLine = MakeSep(content, CONTENT_W) page.descFS = content:CreateFontString(nil, "OVERLAY") page.descFS:SetFont(GetFont(), 12) page.descFS:SetWidth(CONTENT_W - 4) page.descFS:SetJustifyH("LEFT") page.descFS:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3]) page.objLabel = content:CreateFontString(nil, "OVERLAY") page.objLabel:SetFont(GetFont(), 12, "OUTLINE") page.objLabel:SetWidth(CONTENT_W) page.objLabel:SetJustifyH("LEFT") page.objLabel:SetTextColor(T.sectionTitle[1], T.sectionTitle[2], T.sectionTitle[3]) page.objLabel:SetText("任务目标") page.objFS = content:CreateFontString(nil, "OVERLAY") page.objFS:SetFont(GetFont(), 12) page.objFS:SetWidth(CONTENT_W - 4) page.objFS:SetJustifyH("LEFT") page.objFS:SetTextColor(T.objectiveText[1], T.objectiveText[2], T.objectiveText[3]) page.reqLabel = content:CreateFontString(nil, "OVERLAY") page.reqLabel:SetFont(GetFont(), 11, "OUTLINE") page.reqLabel:SetWidth(CONTENT_W) page.reqLabel:SetJustifyH("LEFT") page.reqLabel:SetTextColor(T.sectionTitle[1], T.sectionTitle[2], T.sectionTitle[3]) page.reqLabel:SetText("所需物品") for i = 1, MAX_ITEMS do detailItemSlots[i] = CreateItemSlot(content) end page.rewardLabel = content:CreateFontString(nil, "OVERLAY") page.rewardLabel:SetFont(GetFont(), 11, "OUTLINE") page.rewardLabel:SetWidth(CONTENT_W) page.rewardLabel:SetJustifyH("LEFT") page.rewardLabel:SetTextColor(T.sectionTitle[1], T.sectionTitle[2], T.sectionTitle[3]) page.rewardLabel:SetText("任务奖励") page.detailRewardSlots = {} for i = 1, MAX_ITEMS do page.detailRewardSlots[i] = CreateItemSlot(content) end page.detailChoiceLabel = content:CreateFontString(nil, "OVERLAY") page.detailChoiceLabel:SetFont(GetFont(), 11, "OUTLINE") page.detailChoiceLabel:SetWidth(CONTENT_W) page.detailChoiceLabel:SetJustifyH("LEFT") page.detailChoiceLabel:SetTextColor(T.sectionTitle[1], T.sectionTitle[2], T.sectionTitle[3]) page.detailChoiceLabel:SetText("可选奖励") page.detailChoiceSlots = {} for i = 1, MAX_ITEMS do page.detailChoiceSlots[i] = CreateItemSlot(content) end page.moneyLine = CreateMoneyLine(content) page.Update = function() local y = 6 page.titleFS:SetText(GetTitleText and GetTitleText() or "") page.titleFS:ClearAllPoints() page.titleFS:SetPoint("TOPLEFT", content, "TOPLEFT", 2, -y) y = y + TextHeight(page.titleFS, 16) + 8 page.sepLine:ClearAllPoints() page.sepLine:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -y) page.sepLine:Show() y = y + 6 page.descFS:SetText(GetQuestText and GetQuestText() or "") page.descFS:ClearAllPoints() page.descFS:SetPoint("TOPLEFT", content, "TOPLEFT", 2, -y) y = y + TextHeight(page.descFS, 14) + 12 local obj = GetObjectiveText and GetObjectiveText() or "" if obj ~= "" then page.objLabel:ClearAllPoints() page.objLabel:SetPoint("TOPLEFT", content, "TOPLEFT", 2, -y) page.objLabel:Show(); y = y + 16 page.objFS:SetText(obj) page.objFS:ClearAllPoints() page.objFS:SetPoint("TOPLEFT", content, "TOPLEFT", 2, -y) page.objFS:Show() y = y + TextHeight(page.objFS, 14) + 10 else page.objLabel:Hide(); page.objFS:Hide() end local numReq = GetNumQuestItems and GetNumQuestItems() or 0 for i = 1, MAX_ITEMS do detailItemSlots[i]:Clear() end if numReq > 0 then page.reqLabel:ClearAllPoints() page.reqLabel:SetPoint("TOPLEFT", content, "TOPLEFT", 2, -y) page.reqLabel:Show(); y = y + 16 for i = 1, math.min(numReq, MAX_ITEMS) do detailItemSlots[i]:ClearAllPoints() detailItemSlots[i]:SetPoint("TOPLEFT", content, "TOPLEFT", 4, -y) detailItemSlots[i]:SetItemInfo("required", i) y = y + ITEM_SIZE + ITEM_GAP end y = y + 4 else page.reqLabel:Hide() end local numRew = GetNumQuestRewards and GetNumQuestRewards() or 0 local numCho = GetNumQuestChoices and GetNumQuestChoices() or 0 local money = GetRewardMoney and GetRewardMoney() or 0 for i = 1, MAX_ITEMS do page.detailRewardSlots[i]:Clear(); page.detailChoiceSlots[i]:Clear() end page.rewardLabel:Hide(); page.detailChoiceLabel:Hide(); page.moneyLine:Hide() if numRew > 0 or money > 0 then page.rewardLabel:ClearAllPoints() page.rewardLabel:SetPoint("TOPLEFT", content, "TOPLEFT", 2, -y) page.rewardLabel:Show(); y = y + 16 for i = 1, math.min(numRew, MAX_ITEMS) do page.detailRewardSlots[i]:ClearAllPoints() page.detailRewardSlots[i]:SetPoint("TOPLEFT", content, "TOPLEFT", 4, -y) page.detailRewardSlots[i]:SetItemInfo("reward", i) y = y + ITEM_SIZE + ITEM_GAP end if money > 0 then page.moneyLine:ClearAllPoints() page.moneyLine:SetPoint("TOPLEFT", content, "TOPLEFT", 4, -y) page.moneyLine:SetMoney(money); y = y + 20 end y = y + 4 end if numCho > 0 then page.detailChoiceLabel:ClearAllPoints() page.detailChoiceLabel:SetPoint("TOPLEFT", content, "TOPLEFT", 2, -y) page.detailChoiceLabel:Show(); y = y + 16 for i = 1, math.min(numCho, MAX_ITEMS) do page.detailChoiceSlots[i]:ClearAllPoints() page.detailChoiceSlots[i]:SetPoint("TOPLEFT", content, "TOPLEFT", 4, -y) page.detailChoiceSlots[i]:SetItemInfo("choice", i) y = y + ITEM_SIZE + ITEM_GAP end y = y + 4 end content:SetHeight(y + 10) end return page end -------------------------------------------------------------------------------- -- PROGRESS PAGE (turn-in requirements) -------------------------------------------------------------------------------- local progressItemSlots = {} local function CreateProgressPage(parent) local page = CreateFrame("Frame", nil, parent) page:SetAllPoints(parent) page:Hide() local scroll = CreateScrollArea(page, "SFramesQuestProgressScroll") scroll:SetPoint("TOPLEFT", page, "TOPLEFT", 0, 0) scroll:SetPoint("BOTTOMRIGHT", page, "BOTTOMRIGHT", 0, 0) page.scroll = scroll local content = scroll.content page.titleFS = content:CreateFontString(nil, "OVERLAY") page.titleFS:SetFont(GetFont(), 14, "OUTLINE") page.titleFS:SetWidth(CONTENT_W - 4) page.titleFS:SetJustifyH("LEFT") page.titleFS:SetTextColor(T.gold[1], T.gold[2], T.gold[3]) page.sepLine = MakeSep(content, CONTENT_W) page.progressFS = content:CreateFontString(nil, "OVERLAY") page.progressFS:SetFont(GetFont(), 12) page.progressFS:SetWidth(CONTENT_W - 4) page.progressFS:SetJustifyH("LEFT") page.progressFS:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3]) page.reqLabel = content:CreateFontString(nil, "OVERLAY") page.reqLabel:SetFont(GetFont(), 11, "OUTLINE") page.reqLabel:SetWidth(CONTENT_W) page.reqLabel:SetJustifyH("LEFT") page.reqLabel:SetTextColor(T.sectionTitle[1], T.sectionTitle[2], T.sectionTitle[3]) page.reqLabel:SetText("需要的物品") for i = 1, MAX_ITEMS do progressItemSlots[i] = CreateItemSlot(content) end page.Update = function() local y = 6 page.titleFS:SetText(GetTitleText and GetTitleText() or "") page.titleFS:ClearAllPoints() page.titleFS:SetPoint("TOPLEFT", content, "TOPLEFT", 2, -y) y = y + TextHeight(page.titleFS, 16) + 8 page.sepLine:ClearAllPoints() page.sepLine:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -y) page.sepLine:Show(); y = y + 6 page.progressFS:SetText(GetProgressText and GetProgressText() or "") page.progressFS:ClearAllPoints() page.progressFS:SetPoint("TOPLEFT", content, "TOPLEFT", 2, -y) y = y + TextHeight(page.progressFS, 14) + 12 local numReq = GetNumQuestItems and GetNumQuestItems() or 0 for i = 1, MAX_ITEMS do progressItemSlots[i]:Clear() end if numReq > 0 then page.reqLabel:ClearAllPoints() page.reqLabel:SetPoint("TOPLEFT", content, "TOPLEFT", 2, -y) page.reqLabel:Show(); y = y + 16 for i = 1, math.min(numReq, MAX_ITEMS) do progressItemSlots[i]:ClearAllPoints() progressItemSlots[i]:SetPoint("TOPLEFT", content, "TOPLEFT", 4, -y) progressItemSlots[i]:SetItemInfo("required", i) y = y + ITEM_SIZE + ITEM_GAP end else page.reqLabel:Hide() end content:SetHeight(y + 10) end return page end -------------------------------------------------------------------------------- -- COMPLETE PAGE (reward selection & finish) -------------------------------------------------------------------------------- local completeRewardSlots = {} local completeChoiceSlots = {} local function CreateCompletePage(parent) local page = CreateFrame("Frame", nil, parent) page:SetAllPoints(parent) page:Hide() local scroll = CreateScrollArea(page, "SFramesQuestCompleteScroll") scroll:SetPoint("TOPLEFT", page, "TOPLEFT", 0, 0) scroll:SetPoint("BOTTOMRIGHT", page, "BOTTOMRIGHT", 0, 0) page.scroll = scroll local content = scroll.content page.titleFS = content:CreateFontString(nil, "OVERLAY") page.titleFS:SetFont(GetFont(), 14, "OUTLINE") page.titleFS:SetWidth(CONTENT_W - 4) page.titleFS:SetJustifyH("LEFT") page.titleFS:SetTextColor(T.gold[1], T.gold[2], T.gold[3]) page.sepLine = MakeSep(content, CONTENT_W) page.rewardTextFS = content:CreateFontString(nil, "OVERLAY") page.rewardTextFS:SetFont(GetFont(), 12) page.rewardTextFS:SetWidth(CONTENT_W - 4) page.rewardTextFS:SetJustifyH("LEFT") page.rewardTextFS:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3]) page.rewardLabel = content:CreateFontString(nil, "OVERLAY") page.rewardLabel:SetFont(GetFont(), 11, "OUTLINE") page.rewardLabel:SetWidth(CONTENT_W) page.rewardLabel:SetJustifyH("LEFT") page.rewardLabel:SetTextColor(T.sectionTitle[1], T.sectionTitle[2], T.sectionTitle[3]) page.rewardLabel:SetText("你将获得") for i = 1, MAX_ITEMS do completeRewardSlots[i] = CreateItemSlot(content) end page.moneyLine = CreateMoneyLine(content) page.choiceLabel = content:CreateFontString(nil, "OVERLAY") page.choiceLabel:SetFont(GetFont(), 11, "OUTLINE") page.choiceLabel:SetWidth(CONTENT_W) page.choiceLabel:SetJustifyH("LEFT") page.choiceLabel:SetTextColor(T.sectionTitle[1], T.sectionTitle[2], T.sectionTitle[3]) page.choiceLabel:SetText("选择一项奖励") for i = 1, MAX_ITEMS do local slot = CreateItemSlot(content) local slotIdx = i slot:SetScript("OnClick", function() selectedReward = slotIdx for si = 1, MAX_ITEMS do completeChoiceSlots[si]:SetSelected(si == slotIdx) end UpdateBottomButtons() end) completeChoiceSlots[i] = slot end page.Update = function() selectedReward = 0 local y = 6 page.titleFS:SetText(GetTitleText and GetTitleText() or "") page.titleFS:ClearAllPoints() page.titleFS:SetPoint("TOPLEFT", content, "TOPLEFT", 2, -y) y = y + TextHeight(page.titleFS, 16) + 8 page.sepLine:ClearAllPoints() page.sepLine:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -y) page.sepLine:Show(); y = y + 6 page.rewardTextFS:SetText(GetRewardText and GetRewardText() or "") page.rewardTextFS:ClearAllPoints() page.rewardTextFS:SetPoint("TOPLEFT", content, "TOPLEFT", 2, -y) y = y + TextHeight(page.rewardTextFS, 14) + 12 local numRew = GetNumQuestRewards and GetNumQuestRewards() or 0 local numCho = GetNumQuestChoices and GetNumQuestChoices() or 0 local money = GetRewardMoney and GetRewardMoney() or 0 for i = 1, MAX_ITEMS do completeRewardSlots[i]:Clear(); completeChoiceSlots[i]:Clear() end page.rewardLabel:Hide(); page.choiceLabel:Hide(); page.moneyLine:Hide() if numRew > 0 or money > 0 then page.rewardLabel:ClearAllPoints() page.rewardLabel:SetPoint("TOPLEFT", content, "TOPLEFT", 2, -y) page.rewardLabel:Show(); y = y + 16 for i = 1, math.min(numRew, MAX_ITEMS) do completeRewardSlots[i]:ClearAllPoints() completeRewardSlots[i]:SetPoint("TOPLEFT", content, "TOPLEFT", 4, -y) completeRewardSlots[i]:SetItemInfo("reward", i) y = y + ITEM_SIZE + ITEM_GAP end if money > 0 then page.moneyLine:ClearAllPoints() page.moneyLine:SetPoint("TOPLEFT", content, "TOPLEFT", 4, -y) page.moneyLine:SetMoney(money); y = y + 20 end y = y + 4 end if numCho > 0 then page.choiceLabel:ClearAllPoints() page.choiceLabel:SetPoint("TOPLEFT", content, "TOPLEFT", 2, -y) page.choiceLabel:Show(); y = y + 16 for i = 1, math.min(numCho, MAX_ITEMS) do completeChoiceSlots[i]:ClearAllPoints() completeChoiceSlots[i]:SetPoint("TOPLEFT", content, "TOPLEFT", 4, -y) completeChoiceSlots[i]:SetItemInfo("choice", i) completeChoiceSlots[i]:SetSelected(false) y = y + ITEM_SIZE + ITEM_GAP end if numCho == 1 then selectedReward = 1 completeChoiceSlots[1]:SetSelected(true) end end content:SetHeight(y + 10) end return page end -------------------------------------------------------------------------------- -- INITIALIZE -------------------------------------------------------------------------------- function QUI:Initialize() if MainFrame then return end MainFrame = CreateFrame("Frame", "SFramesQuestFrame", UIParent) MainFrame:SetWidth(FRAME_W) MainFrame:SetHeight(FRAME_H) MainFrame:SetPoint("LEFT", UIParent, "LEFT", 64, 0) MainFrame:SetFrameStrata("HIGH") MainFrame:SetToplevel(true) MainFrame:EnableMouse(true) MainFrame:SetMovable(true) MainFrame:RegisterForDrag("LeftButton") MainFrame:SetScript("OnDragStart", function() this:StartMoving() end) MainFrame:SetScript("OnDragStop", function() this:StopMovingOrSizing() end) SetRoundBackdrop(MainFrame) CreateShadow(MainFrame) local header = CreateFrame("Frame", nil, MainFrame) header:SetPoint("TOPLEFT", MainFrame, "TOPLEFT", 0, 0) header:SetPoint("TOPRIGHT", MainFrame, "TOPRIGHT", 0, 0) header:SetHeight(HEADER_H) local titleIco = SFrames:CreateIcon(header, "quest", 16) titleIco:SetDrawLayer("OVERLAY") titleIco:SetPoint("LEFT", header, "LEFT", SIDE_PAD, 0) titleIco:SetVertexColor(T.gold[1], T.gold[2], T.gold[3]) local npcNameFS = header:CreateFontString(nil, "OVERLAY") npcNameFS:SetFont(GetFont(), 14, "OUTLINE") npcNameFS:SetPoint("LEFT", titleIco, "RIGHT", 5, 0) npcNameFS:SetPoint("RIGHT", header, "RIGHT", -30, 0) npcNameFS:SetJustifyH("LEFT") npcNameFS:SetTextColor(T.gold[1], T.gold[2], T.gold[3]) MainFrame.npcNameFS = npcNameFS local closeBtn = CreateFrame("Button", nil, MainFrame, "UIPanelCloseButton") closeBtn:SetPoint("TOPRIGHT", MainFrame, "TOPRIGHT", 2, 2) closeBtn:SetWidth(24); closeBtn:SetHeight(24) local headerSep = MainFrame:CreateTexture(nil, "ARTWORK") headerSep:SetTexture("Interface\\Buttons\\WHITE8X8") headerSep:SetHeight(1) headerSep:SetPoint("TOPLEFT", MainFrame, "TOPLEFT", 6, -HEADER_H) headerSep:SetPoint("TOPRIGHT", MainFrame, "TOPRIGHT", -6, -HEADER_H) headerSep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4]) local contentArea = CreateFrame("Frame", nil, MainFrame) contentArea:SetPoint("TOPLEFT", MainFrame, "TOPLEFT", SIDE_PAD, -(HEADER_H + 4)) contentArea:SetPoint("BOTTOMRIGHT", MainFrame, "BOTTOMRIGHT", -SIDE_PAD, BOTTOM_H + 4) MainFrame.contentArea = contentArea local bottomSep = MainFrame:CreateTexture(nil, "ARTWORK") bottomSep:SetTexture("Interface\\Buttons\\WHITE8X8") bottomSep:SetHeight(1) bottomSep:SetPoint("BOTTOMLEFT", MainFrame, "BOTTOMLEFT", 6, BOTTOM_H) bottomSep:SetPoint("BOTTOMRIGHT", MainFrame, "BOTTOMRIGHT", -6, BOTTOM_H) bottomSep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4]) MainFrame.btn1 = CreateActionBtn(MainFrame, "", 100) MainFrame.btn1:SetPoint("BOTTOMLEFT", MainFrame, "BOTTOMLEFT", SIDE_PAD, 8) MainFrame.btn1:Hide() MainFrame.btn2 = CreateActionBtn(MainFrame, "", 100) MainFrame.btn2:SetPoint("BOTTOMRIGHT", MainFrame, "BOTTOMRIGHT", -SIDE_PAD, 8) MainFrame.btn2:Hide() pages.gossip = CreateGossipPage(contentArea) pages.greeting = CreateGreetingPage(contentArea) pages.detail = CreateDetailPage(contentArea) pages.progress = CreateProgressPage(contentArea) pages.complete = CreateCompletePage(contentArea) MainFrame:SetScript("OnHide", function() if currentPage then if CloseGossip then pcall(CloseGossip) end if DeclineQuest then pcall(DeclineQuest) end previousPage = nil end currentPage = nil pendingClose = false end) MainFrame:SetScript("OnUpdate", function() if pendingClose then closeTimer = closeTimer + (arg1 or 0) if closeTimer > 0.15 then pendingClose = false closeTimer = 0 previousPage = nil if MainFrame:IsVisible() then currentPage = nil MainFrame:Hide() end end end end) MainFrame:Hide() MainFrame:RegisterEvent("GOSSIP_SHOW") MainFrame:RegisterEvent("GOSSIP_CLOSED") MainFrame:RegisterEvent("QUEST_GREETING") MainFrame:RegisterEvent("QUEST_DETAIL") MainFrame:RegisterEvent("QUEST_PROGRESS") MainFrame:RegisterEvent("QUEST_COMPLETE") MainFrame:RegisterEvent("QUEST_FINISHED") MainFrame:SetScript("OnEvent", function() if event == "GOSSIP_SHOW" then pendingClose = false local gText = GetGossipText() or "" if GOSSIP_TRIGGER_TEXTS[strtrim and strtrim(gText) or gText] then return end if TryAutoGossip() then return end previousPage = "gossip" MainFrame.npcNameFS:SetText(UnitName("npc") or "NPC") ShowPage("gossip") pages.gossip.Update() UpdateBottomButtons() MainFrame:Show() elseif event == "GOSSIP_CLOSED" then if currentPage == "gossip" then pendingClose = true closeTimer = 0 end elseif event == "QUEST_GREETING" then pendingClose = false previousPage = "greeting" MainFrame.npcNameFS:SetText(UnitName("npc") or "NPC") ShowPage("greeting") pages.greeting.Update() UpdateBottomButtons() MainFrame:Show() elseif event == "QUEST_DETAIL" then pendingClose = false MainFrame.npcNameFS:SetText(UnitName("npc") or "NPC") ShowPage("detail") pages.detail.Update() UpdateBottomButtons() MainFrame:Show() elseif event == "QUEST_PROGRESS" then pendingClose = false MainFrame.npcNameFS:SetText(UnitName("npc") or "NPC") ShowPage("progress") pages.progress.Update() UpdateBottomButtons() MainFrame:Show() elseif event == "QUEST_COMPLETE" then pendingClose = false selectedReward = 0 MainFrame.npcNameFS:SetText(UnitName("npc") or "NPC") ShowPage("complete") pages.complete.Update() UpdateBottomButtons() MainFrame:Show() elseif event == "QUEST_FINISHED" then if previousPage then pendingClose = true closeTimer = 0 else pendingClose = false currentPage = nil MainFrame:Hide() end end end) if GossipFrame then GossipFrame:UnregisterEvent("GOSSIP_SHOW") GossipFrame:UnregisterEvent("GOSSIP_CLOSED") end if QuestFrame then QuestFrame:UnregisterEvent("QUEST_GREETING") QuestFrame:UnregisterEvent("QUEST_DETAIL") QuestFrame:UnregisterEvent("QUEST_PROGRESS") QuestFrame:UnregisterEvent("QUEST_COMPLETE") QuestFrame:UnregisterEvent("QUEST_FINISHED") end tinsert(UISpecialFrames, "SFramesQuestFrame") end -------------------------------------------------------------------------------- -- Bootstrap -------------------------------------------------------------------------------- local bootstrap = CreateFrame("Frame") bootstrap:RegisterEvent("PLAYER_LOGIN") bootstrap:SetScript("OnEvent", function() if event == "PLAYER_LOGIN" then if SFramesDB.enableQuestUI == nil then SFramesDB.enableQuestUI = true end if SFramesDB.enableQuestUI ~= false then QUI:Initialize() end end end)