local E, L, V, P, G = unpack(select(2, ...)); --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB local NP = E:GetModule('NamePlates') local oUF = E.oUF local _G = _G local pairs, ipairs, ceil, floor, tonumber = pairs, ipairs, ceil, floor, tonumber local wipe, strmatch, strlower, strfind = wipe, strmatch, strlower, strfind local GetLocale = GetLocale local IsInInstance = IsInInstance local UnitIsPlayer = UnitIsPlayer local GetQuestLogSpecialItemInfo = GetQuestLogSpecialItemInfo local C_QuestLog_GetTitleForLogIndex = C_QuestLog.GetTitleForLogIndex local C_QuestLog_GetNumQuestLogEntries = C_QuestLog.GetNumQuestLogEntries local C_QuestLog_GetQuestIDForLogIndex = C_QuestLog.GetQuestIDForLogIndex local ThreatTooltip = THREAT_TOOLTIP:gsub('%%d', '%%d-') local questIcons = { iconTypes = { 'Default', 'Item', 'Skull', 'Chat' }, indexByID = {}, --[questID] = questIndex activeQuests = {} --[questTitle] = questID } NP.QuestIcons = questIcons local typesLocalized = { enUS = { --- short matching applies here so, -- kill: killed, destory: destoryed, etc ... KILL = {'slain', 'destroy', 'eliminate', 'repel', 'kill', 'defeat'}, CHAT = {'speak', 'talk'} }, deDE = { KILL = {'besiegen', 'besiegt', 'getötet', 'töten', 'tötet', 'vernichtet', 'zerstört', 'genährt'}, CHAT = {'befragt', 'sprecht'} }, ruRU = { KILL = {'убит', 'уничтож', 'разбомблен', 'разбит', 'сразит'}, CHAT = {'поговорит', 'спрашивать'} }, esMX = { -- asesinad: asesinado, asesinados, asesinada, asesinadas -- derrota: derrotar, derrotado, derrotados, derrotada, derrotadas -- destrui: destruir, destruido, destruidos, destruida, destruidas -- elimin: eliminar, elimine, eliminadas, eliminada, eliminados, eliminado -- repel: repele, repelido, repelidos, repelida, repelidas KILL = {'asesinad', 'destrui', 'elimin', 'repel', 'derrota'}, CHAT = {'habla', 'pídele'} }, ptBR = { -- destrui: above but also destruição -- repel: repelir, repelido, repelidos, repelida, repelidas KILL = {'morto', 'morta', 'matar', 'destrui', 'elimin', 'repel', 'derrota'}, CHAT = {'falar', 'pedir'} }, frFR = { -- tué: tués, tuée, tuées -- abattu: abattus, abattue -- détrui: détruite, détruire, détruit, détruits, détruites -- repouss: repousser, repoussés, repoussée, repoussées -- élimin: éliminer, éliminé, éliminés, éliminée, éliminées KILL = {'tué', 'tuer', 'attaqué', 'attaque', 'abattre', 'abattu', 'détrui', 'élimin', 'repouss', 'vaincu', 'vaincre'}, -- demande: demander, demandez -- parle: parler, parlez CHAT = {'parle', 'demande'} }, koKR = { KILL = {'쓰러뜨리기', '물리치기', '공격', '파괴'}, CHAT = {'대화'} }, zhCN = { KILL = {'消灭', '摧毁', '击败', '毁灭', '击退'}, CHAT = {'交谈', '谈一谈'} }, zhTW = { KILL = {'毀滅', '擊退', '殺死'}, CHAT = {'交談', '說話'} }, } local questTypes = typesLocalized[GetLocale()] or typesLocalized.enUS local function CheckTextForQuest(text) local x, y = strmatch(text, '(%d+)/(%d+)') if x and y then return floor(y - x) elseif not strmatch(text, ThreatTooltip) then local progress = tonumber(strmatch(text, '([%d%.]+)%%')) if progress and progress <= 100 then return ceil(100 - progress), true end end end NP.QuestIcons.CheckTextForQuest = CheckTextForQuest local function GetQuests(unitID) if IsInInstance() then return end E.ScanTooltip:SetOwner(_G.UIParent, 'ANCHOR_NONE') E.ScanTooltip:SetUnit(unitID) E.ScanTooltip:Show() local QuestList, notMyQuest, activeID for i = 3, E.ScanTooltip:NumLines() do local str = _G['ElvUI_ScanTooltipTextLeft' .. i] local text = str and str:GetText() if not text or text == '' then return end if UnitIsPlayer(text) then notMyQuest = text ~= E.myname elseif text and not notMyQuest then local count, percent = CheckTextForQuest(text) -- this line comes from one line up in the tooltip local activeQuest = questIcons.activeQuests[text] if activeQuest then activeID = activeQuest end if count then local type, index, texture, _ if activeID then index = questIcons.indexByID[activeID] _, texture = GetQuestLogSpecialItemInfo(index) end if texture then type = 'QUEST_ITEM' else local lowerText = strlower(text) -- check kill type first for _, listText in ipairs(questTypes.KILL) do if strfind(lowerText, listText, nil, true) then type = 'KILL' break end end -- check chat type if kill type doesn't exist if not type then for _, listText in ipairs(questTypes.CHAT) do if strfind(lowerText, listText, nil, true) then type = 'CHAT' break end end end end if not QuestList then QuestList = {} end QuestList[#QuestList + 1] = { isPercent = percent, itemTexture = texture, objectiveCount = count, questType = type or 'DEFAULT', -- below keys are currently unused questLogIndex = index, questID = activeID } end end end E.ScanTooltip:Hide() return QuestList end local function hideIcons(element) for _, object in pairs(questIcons.iconTypes) do local icon = element[object] icon:Hide() if icon.Text then icon.Text:SetText('') end end end local function Update(self, event, arg1) local element = self.QuestIcons if not element then return end local unit = (event == 'UNIT_NAME_UPDATE' and arg1) or self.unit if unit ~= self.unit then return end if element.PreUpdate then element:PreUpdate() end hideIcons(element) local QuestList = GetQuests(unit) if QuestList then element:Show() else element:Hide() return end local shownCount for i = 1, #QuestList do local quest = QuestList[i] local objectiveCount = quest.objectiveCount local questType = quest.questType local isPercent = quest.isPercent if isPercent or objectiveCount > 0 then local icon if questType == 'DEFAULT' then icon = element.Default elseif questType == 'KILL' then icon = element.Skull elseif questType == 'CHAT' then icon = element.Chat elseif questType == 'QUEST_ITEM' then icon = element.Item end if icon and not icon:IsShown() then shownCount = (shownCount and shownCount + 1) or 0 local size = icon.size or 25 local setPosition = icon.position or 'TOPLEFT' local newPosition = E.InversePoints[setPosition] local offset = shownCount * (5 + size) icon:Show() icon:ClearAllPoints() icon:Point(newPosition, element, newPosition, (strmatch(setPosition, 'LEFT') and -offset) or offset, 0) if questType ~= 'CHAT' and icon.Text and (isPercent or objectiveCount > 1) then icon.Text:SetText((isPercent and objectiveCount..'%') or objectiveCount) end if questType == 'QUEST_ITEM' then element.Item:SetTexture(quest.itemTexture) end end end end if element.PostUpdate then return element:PostUpdate() end end local function Path(self, ...) return (self.QuestIcons.Override or Update) (self, ...) end local function ForceUpdate(element) return Path(element.__owner, 'ForceUpdate', element.__owner.unit) end local function Enable(self) local element = self.QuestIcons if element then element.__owner = self element.ForceUpdate = ForceUpdate if element.Default:IsObjectType('Texture') and not element.Default:GetAtlas() then element.Default:SetAtlas('SmallQuestBang') end if element.Skull:IsObjectType('Texture') and not element.Skull:GetTexture() then element.Skull:SetTexture(E.Media.Textures.SkullIcon) end if element.Chat:IsObjectType('StatusBar') and not element.Chat:GetTexture() then element.Chat:SetTexture([[Interface\WorldMap\ChatBubble_64.PNG]]) end self:RegisterEvent('QUEST_LOG_UPDATE', Path, true) self:RegisterEvent('UNIT_NAME_UPDATE', Path, true) self:RegisterEvent('PLAYER_ENTERING_WORLD', Path, true) return true end end local function Disable(self) local element = self.QuestIcons if element then element:Hide() hideIcons(element) self:UnregisterEvent('QUEST_LOG_UPDATE', Path) self:UnregisterEvent('UNIT_NAME_UPDATE', Path) self:UnregisterEvent('PLAYER_ENTERING_WORLD', Path) end end local frame = CreateFrame('Frame') frame:RegisterEvent('QUEST_ACCEPTED') frame:RegisterEvent('QUEST_REMOVED') frame:RegisterEvent('PLAYER_ENTERING_WORLD') frame:SetScript('OnEvent', function(self, event) wipe(questIcons.indexByID) wipe(questIcons.activeQuests) for i = 1, C_QuestLog_GetNumQuestLogEntries() do local id = C_QuestLog_GetQuestIDForLogIndex(i) if id and id > 0 then questIcons.indexByID[id] = i local title = C_QuestLog_GetTitleForLogIndex(i) if title then questIcons.activeQuests[title] = id end end end if event == 'PLAYER_ENTERING_WORLD' then self:UnregisterEvent(event) end end) oUF:AddElement('QuestIcons', Path, Enable, Disable)