local myname, ns = ... local Class = ns.Class local GetPlayerAuraBySpellID = C_UnitAuras and C_UnitAuras.GetPlayerAuraBySpellID or _G.GetPlayerAuraBySpellID ns.conditions = {} --[[ API: condition = ns.Condition.GarrisonTalent(1912, 4) condition:Matched() -> bool condition:Label() -> string ]] local Condition = Class{ Initialize = function(self, id) self.id = id end, Label = function(self) return ('{%s:%d}'):format(self.type, self.id) end, Matched = function() return false end, } local RankedCondition = Class{ __parent = Condition, Initialize = function(self, id, rank) self.id = id self.rank = rank end, Label = function(self) -- this relies greatly on render_string working for self.type local label = Condition.Label(self) if self.rank then return AZERITE_ESSENCE_TOOLTIP_NAME_RANK:format(label, self.rank) end return label end } local Negated = function(parent) return { __parent = parent, Matched = function(self) return not parent.Matched(self) end, } end ns.conditions.Achievement = Class{ __parent = Condition, type = 'achievement', Matched = function(self) return (select(4, GetAchievementInfo(self.id))) end, } ns.conditions.AchievementIncomplete = Class(Negated(ns.conditions.Achievement)) ns.conditions.AuraActive = Class{ __parent = Condition, type = 'spell', Matched = function(self) return GetPlayerAuraBySpellID(self.id) end, } ns.conditions.AuraInactive = Class(Negated(ns.conditions.AuraActive)) ns.conditions.SpellKnown = Class{ __parent = Condition, type = 'spell', Matched = function(self) return IsSpellKnown(self.id) end, } ns.conditions.Profession = Class{ -- See https://wowpedia.fandom.com/wiki/TradeSkillLineID for IDs -- TODO: make work in Classic? Whole different API. __parent = RankedCondition, type = "profession", Matched = function(self) -- The problem: this is only reliable for skill levels after the trade skill has been opened local info = C_TradeSkillUI.GetProfessionInfoBySkillLineID(self.id) if not (info and info.skillLevel) then return false end if info.skillLevel > 0 then -- we have good data return info.skillLevel >= (self.rank or 1) end -- we need to start making guesses return self:CheckProfessions(info, GetProfessions()) end, CheckProfessions = function(self, info, ...) for i = 1, select("#", ...) do if self:CheckProfession(info, select(i, ...)) then return true end end return false end, CheckProfession = function(self, info, professionid) if not professionid then return end local skillName, _, skillLevel, maxSkillLevel, _, _, skillLineID, _, _, _, displayName = GetProfessionInfo(professionid) if info.professionID == skillLineID then -- This is the exact skill! return skillLevel >= (self.rank or 1) end if info.parentProfessionID == skillLineID then -- The overall skill is known if displayName == info.professionName then -- This is the highest expansion skill currently, so the reported skill level is correct return skillLevel >= (self.rank or 1) end -- This is the wrong expansion skill... so ignore the rank check and just claim we know it -- TODO: this the worst case, improve it somehow? return true end end, } ns.conditions.Covenant = Class{ __parent = RankedCondition, type = 'covenant', Matched = function(self) if self.id ~= C_Covenants.GetActiveCovenantID() then return false end if self.rank then return self.rank <= C_CovenantSanctumUI.GetRenownLevel() end return true end, } ns.conditions.Faction = Class{ __parent = RankedCondition, type = 'faction', Matched = function(self) local name, _, standingid = GetFactionInfoByID(self.id) if name and standingid then return self.rank <= standingid end end, } ns.conditions.MajorFaction = Class{ __parent = RankedCondition, type = 'majorfaction', Matched = function(self) local info = C_MajorFactions.GetMajorFactionData(self.id) if info then if self.rank then return self.rank <= info.renownLevel end return info.isUnlocked end end, } ns.conditions.GarrisonTalent = Class{ __parent = Condition, type = 'garrisontalent', Initialize = function(self, id, rank) self.id = id self.rank = rank end, Label = function(self) local info = C_Garrison.GetTalentInfo(self.id) local name = info and info.name and ("{garrisontalent:%d}"):format(self.id) or UNKNOWN if self.rank then return AZERITE_ESSENCE_TOOLTIP_NAME_RANK:format(name, self.rank) end return name end, Matched = function(self) local info = C_Garrison.GetTalentInfo(self.id) return info and info.researched and (not self.rank or info.talentRank >= self.rank) end } ns.conditions.Item = Class{ __parent = Condition, type = 'item', Initialize = function(self, id, count) self.id = id self.count = count end, Label = function(self) if self.count and self.count > 1 then return ("{item:%d} x%d"):format(self.id, self.count) end return Condition.Label(self) end, Matched = function(self) return GetItemCount(self.id, true) >= (self.count or 1) end, } ns.conditions.Toy = Class{ __parent = ns.conditions.Item, Matched = function(self) return PlayerHasToy(self.id) end, } ns.conditions.QuestComplete = Class{ __parent = Condition, type = 'quest', Matched = function(self) return C_QuestLog.IsQuestFlaggedCompleted(self.id) end, } ns.conditions.QuestIncomplete = Class(Negated(ns.conditions.QuestComplete)) ns.conditions.WorldQuestActive = Class{ __parent = Condition, type = 'worldquest', Matched = function(self) return C_TaskQuest.IsActive(self.id) or C_QuestLog.IsQuestFlaggedCompleted(self.id) end, } ns.conditions.OnQuest = Class{ __parent = Condition, type = 'quest', Matched = function(self) return C_QuestLog.IsOnQuest(self.id) end, } ns.conditions.Vignette = Class{ __parent = Condition, type = 'vignette', FindVignette = function(self) local vignettes = C_VignetteInfo.GetVignettes() for _, vignetteGUID in ipairs(vignettes) do local vignetteInfo = C_VignetteInfo.GetVignetteInfo(vignetteGUID) if vignetteInfo and vignetteInfo.vignetteID == self.id then return vignetteInfo end end return false end, Matched = function(self) return self:FindVignette() end, Label = function(self) local vignetteInfo = self:FindVignette() if vignetteInfo and vignetteInfo.name then return vignetteInfo.name end return Condition.Label(self) end, } ns.conditions.Level = Class{ __parent = Condition, type = 'level', Label = function(self) return UNIT_LEVEL_TEMPLATE:format(self.id) end, Matched = function(self) return UnitLevel('player') >= self.id end, } ns.conditions.Class = Class{ __parent = Condition, type = 'class', Label = function(self) local className = ((UnitSex("player") == 2) and LOCALIZED_CLASS_NAMES_MALE or LOCALIZED_CLASS_NAMES_FEMALE)[self.id] or self.id if RAID_CLASS_COLORS[self.id] then return RAID_CLASS_COLORS[self.id]:WrapTextInColorCode(className) end return className end, Matched = function(self) return select(2, UnitClass("player")) == self.id end, } ns.conditions.CalendarEvent = Class{ __parent = Condition, type = 'calendarevent', Label = function(self) local event = self:getEvent() if event and event.title then return event.title end return Condition.Label(self) end, Matched = function(self) if self:getEvent() then return true end end, getEvent = function(self) local offset, day = self:getOffsets() for i=1, C_Calendar.GetNumDayEvents(offset, day) do local event = C_Calendar.GetDayEvent(offset, day, i) if event.eventID == self.id then return true end end end, getOffsets = function(self, current) -- we could call C_Calendar.SetMonth, but that'd jump the calendar around if it's open... so instead, work out the actual offset current = current or C_DateAndTime.GetCurrentCalendarTime() local selected = C_Calendar.GetMonthInfo() local offset = (selected.month - current.month) + ((selected.year - current.year) * 12) if offset >= 1 or offset <= -1 then -- calendar APIs only return information on events within the next month either way if not (_G.CalendarFrame and _G.CalendarFrame:IsVisible()) then -- calendar's not visible, so it's fine to move it around -- SetAbsMonth because when the calendar hasn't been opened yet just SetMonth can jump to an incorrect year... C_Calendar.SetAbsMonth(current.month, current.year) offset = 0 end end return offset, current.monthDay end, } ns.conditions.CalendarEventStartTexture = Class{ __parent = ns.conditions.CalendarEvent, type = 'calendareventtexture', getEvent = function(self) local offset, day = self:getOffsets() for i=1, C_Calendar.GetNumDayEvents(offset, day) do local event = C_Calendar.GetDayEvent(offset, day, i) if event and event.startTime then local startoffset, startday = self:getOffsets(event.startTime) for ii=1, C_Calendar.GetNumDayEvents(startoffset, startday) do local startEvent = C_Calendar.GetDayEvent(startoffset, startday, ii) if startEvent and startEvent.iconTexture == self.id then return event end end end end end } -- Helpers: do local function check(cond) return cond:Matched() end ns.conditions.check = function(conditions) return ns.doTest(check, conditions) end local t = {} ns.conditions.summarize = function(conditions) -- ERR_USE_LOCKED_WITH_ITEM_S table.wipe(t) if type(conditions) == "table" and not conditions.__parent then for _, condition in ipairs(conditions) do table.insert(t, condition:Label()) end return ERR_USE_LOCKED_WITH_ITEM_S:format(string.join(', ', unpack(t))) end return ERR_USE_LOCKED_WITH_ITEM_S:format(conditions:Label()) end end