view QuestPOI.lua @ 95:b29b35cb8539

- Fixed quest completion checking and handling - Changed animation method to hopefully stop weird flickering. - Pins are now visible before full reward data is loaded - Filter bar redesigned: - aligned horizontally along the top of the map display - filter buttons display a '+' when there are matches in both current and other zones, and '*' when there only matches in other zones - button tooltips separate local and global quests - button categories are highlighted and labeled when the cursor is over them - Fixed invalid POI targets appearing when the spell targeting cursor is active - ClassOrderPlan can be closed with the game menu button
author Nenue
date Mon, 08 May 2017 22:38:52 -0400
parents 98b5e08b75ed
children 8591401ec278
line wrap: on
line source
-- WorldPlan
-- QuestPOI.lua
-- Created: 10/1/2016 7:21 PM
-- %file-revision%
-- Big TODOs:
-- -- generate frames using `WorldMap_GetOrCreateTaskPOI' to remove tainting issues
--
local _, db = ...
local TQ_GetQuestInfoByQuestID = C_TaskQuest.GetQuestInfoByQuestID
local TQ_GetQuestLocation = C_TaskQuest.GetQuestLocation
local TQ_GetQuestTimeLeftMinutes = C_TaskQuest.GetQuestTimeLeftMinutes
local TQ_IsActive = C_TaskQuest.IsActive
local TQ_RequestPreloadRewardData = C_TaskQuest.RequestPreloadRewardData
local QuestPOIGetIconInfo, WorldMapPOIFrame =  QuestPOIGetIconInfo, WorldMapPOIFrame
local WorldMap_DoesWorldQuestInfoPassFilters = WorldMap_DoesWorldQuestInfoPassFilters
local QuestMapFrame_IsQuestWorldQuest = QuestMapFrame_IsQuestWorldQuest
local GetAchievementNumCriteria, GetAchievementCriteriaInfo, GetAchievementInfo = GetAchievementNumCriteria, GetAchievementCriteriaInfo, GetAchievementInfo

local IsQuestFlaggedCompleted = IsQuestFlaggedCompleted
local GetFactionInfoByID, GetQuestObjectiveInfo = GetFactionInfoByID, GetQuestObjectiveInfo
local GetQuestTagInfo, GetProfessionInfo = GetQuestTagInfo, GetProfessionInfo
local GetNumQuestLogRewards, GetNumQuestLogRewardCurrencies, HaveQuestData = GetNumQuestLogRewards, GetNumQuestLogRewardCurrencies, HaveQuestData
local GetQuestLogRewardMoney, GetQuestLogRewardCurrencyInfo, GetMoneyString = GetQuestLogRewardMoney, GetQuestLogRewardCurrencyInfo, GetMoneyString
local SpellCanTargetQuest, GetCVarBool = SpellCanTargetQuest, GetCVarBool
local SetSuperTrackedQuestID = SetSuperTrackedQuestID
local HaveQuestRewardData = HaveQuestRewardData


local pairs, ipairs, tinsert, tremove, unpack, select = pairs, ipairs, tinsert, tremove, unpack, select
local floor, mod, tostring, tonumber, GetSuperTrackedQuestID = floor, mod, tostring, tonumber, GetSuperTrackedQuestID
local GameTooltip = GameTooltip
local GetItemIcon = GetItemIcon

local print = DEVIAN_WORKSPACE and function(...) _G.print('POI', ...) end or nop
local qprint = DEVIAN_WORKSPACE and function(...) _G.print('POI', ...) end or nop
local wprint = DEVIAN_WORKSPACE and function(...) _G.print('WP', ...) end or nop
local wqprint = DEVIAN_WORKSPACE and function(...) _G.print('WorldQuests', ...) end or nop
local iprint = DEVIAN_WORKSPACE and function(...) _G.print('ItemScan', ...) end or nop
local rprint = DEVIAN_WORKSPACE and function(...) _G.print('WQRefresh', ...) end or nop
local dprint = DEVIAN_WORKSPACE and function(...) _G.print('WQData', ...) end or nop
local QuestPOI = WorldPlanPOIMixin

local pinBaseIndex = 1320
local overlayBaseIndex = 1380
local previousHighlight

local FADE_TIMING_MULTIPLIER = 3
local DATA_DEBUG = false
local PIN_REFRESH_DELAY = 1
local PIN_REQUEST_DELAY = .2
local ICON_UNKNOWN = "Interface\\ICONS\\inv_misc_questionmark"
local ICON_MONEY = "Interface\\Buttons\\UI-GroupLoot-Coin-Up"

local WORLD_QUEST_BORDER = "Interface\\UNITPOWERBARALT\\Generic1Target_Circular_Frame"
local PENDING_BORDER = "Interface\\BUTTONS\\YELLOWORANGE64"
local PENDING_ICON = "Interface\\BUTTONS\\YELLOWORANGE64"

local REWARD_CASH = WORLD_QUEST_REWARD_TYPE_FLAG_GOLD
local REWARD_ARTIFACT_POWER = WORLD_QUEST_REWARD_TYPE_FLAG_ARTIFACT_POWER
local REWARD_GEAR = WORLD_QUEST_REWARD_TYPE_FLAG_EQUIPMENT
local REWARD_CURRENCY = WORLD_QUEST_REWARD_TYPE_FLAG_ORDER_RESOURCES
local REWARD_REAGENT = WORLD_QUEST_REWARD_TYPE_FLAG_MATERIALS

local REWARD_TYPE_NAMES = {
  [REWARD_CASH] = 'Money',
  [REWARD_ARTIFACT_POWER] = 'AP',
  [REWARD_GEAR] = 'Equipment',
  [REWARD_CURRENCY] = 'Currency',
  [REWARD_REAGENT] = 'Trade Goods'
}

local LE_QUEST_TAG_TYPE_PVP = LE_QUEST_TAG_TYPE_PVP
local LE_QUEST_TAG_TYPE_PET_BATTLE = LE_QUEST_TAG_TYPE_PET_BATTLE
local LE_QUEST_TAG_TYPE_DUNGEON = LE_QUEST_TAG_TYPE_DUNGEON
local LE_QUEST_TAG_TYPE_PROFESSION = LE_QUEST_TAG_TYPE_PROFESSION
local LE_QUEST_TAG_TYPE_NORMAL = LE_QUEST_TAG_TYPE_NORMAL

local LE_QUEST_TAG_TYPE_PVP = LE_QUEST_TAG_TYPE_PVP
local LE_QUEST_TAG_TYPE_PET_BATTLE = LE_QUEST_TAG_TYPE_PET_BATTLE
local LE_QUEST_TAG_TYPE_DUNGEON = LE_QUEST_TAG_TYPE_DUNGEON
local LE_QUEST_TAG_TYPE_PROFESSION = LE_QUEST_TAG_TYPE_PROFESSION
local LE_QUEST_TAG_TYPE_NORMAL = LE_QUEST_TAG_TYPE_NORMAL

local DEFAULT_STYLE = {
  border = {1,1,1},
  textColor = {1,1,1,1},
  x = 0, y = 0,
  desaturated = true,
  iconMask = "Interface\\Minimap\\UI-Minimap-Background",
  borderMask = "Interface\\Minimap\\UI-Minimap-Background",
  rewardMark = "Interface\\Minimap\\UI-Minimap-Background",
  scaleFactors = {0.25, 0.7, 1},
  iconWidth = 18,
  borderWidth = 2,
  highlightWidth = 2,
  TagSize = 8,
  maxAlertLevel = 3,
  numberFontObject = 'WorldPlanFont'
}
local MINIMIZED_STYLE = {
  hideNumber = true,
  hideIcon = true,
  iconWidth = 3,
}
local REWARD_TYPE_STYLES = {
  [REWARD_CASH] = {
    hideNumber = true,
  },
  [REWARD_ARTIFACT_POWER] = {},
  [REWARD_GEAR] = {
    hideNumber = true,
  },
  [REWARD_CURRENCY] = {
    textColor = {1,1,0,.5}
  },
  [REWARD_REAGENT] = {

    hideNumber = true,
  },
}
local BORDER_SELECTED_BOUNTY = {1, 1, 0, 1 }
local BORDER_CRITERIA = {.25, .5, 1, 1}

local LE_ITEM_CLASS_WEAPON, LE_ITEM_CLASS_ARMOR, LE_ITEM_CLASS_GEM, LE_ITEM_GEM_ARTIFACTRELIC, LE_ITEM_CLASS_TRADEGOODS = LE_ITEM_CLASS_WEAPON, LE_ITEM_CLASS_ARMOR, LE_ITEM_CLASS_GEM, LE_ITEM_GEM_ARTIFACTRELIC, LE_ITEM_CLASS_TRADEGOODS


local STYLE_TYPE_PENDING = 768


-- Pin color/display variables
db.TooltipExtras = db.TooltipExtras or {} -- idiot-proofing
db.PinAlpha = 1

local familiars = {
    [42159] = 'Nightwatcher Merayl',
    [40277] = 'Tiffany Nelson',
    [40298] = 'Sir Galveston',
    [40282] = 'Grixis Tinypop',
    [40278] = 'Robert Craig',
    [48195] = 'Aulier',
    [41990] = 'Varenne',
    [41860] = 'Xorvasc',
    [40299] = 'Bodhi Sunwayver',
    [42442] = 'Amalia',
    [40280] = 'Bredda Tenderhide',
    [41687] = 'Odrogg',
    [41944] = 'Trapper Jarrun',
    [40337] = 'Master Tamer Flummox',
    [40279] = 'Durian Strongfruit'
}
local falcosaurs = {
  [44895] = {44881, 'Sharptalon Hatchling', 115786},
  [44894] = {44882, 'Bloodgazer Hatchling', 115787},
  [44893] = {44880, 'Direbeak Hatchling', 115785},
  [44892] = {44879, 'Snowfeather Hatchling', 115784},
}
local familiars_id = 9696
for questID, name in pairs(familiars) do
  db.TooltipExtras[questID] = {{
    achievementID = familiars_id,
    name = name
  }}
end
for questID, info in pairs(falcosaurs) do
  local trackingQuestID, petName, petID = unpack(info)

  db.TooltipExtras[questID] = {{
    questID = trackingQuestID,
    pet = petName,
    petID = petID
  }}
end


do
  local timeStates = {
    {maxSeconds = 60,
      r=1, g=0.25, b =0, format = function (minutes) return '|cFFFF4400'.. minutes .. 'm' end,
    },
    {maxSeconds = 240,
      r=1, g=.5, b=0, format = function(minutes) return '|cFFFF4400'.. floor(minutes/60) .. 'h' end,
    },
    {maxSeconds = 1440,
      r=1, g=1, b=0, format = function(minutes) return '|cFFFFFF00'.. floor(minutes/60) .. 'h' end,
    },
    {maxSeconds = 10081,
      r=0, g=1, b=0,
    }, -- 7 days + 1 minute
  }
  -- Generates a timeleft string
  function QuestPOI:GetTimeInfo(timeLeft, limit)
    for index = 1, limit do
      local state = timeStates[index]
      if timeLeft <= state.maxSeconds then
        local text
        if state.format then
          text = state.format(timeLeft)
        end
        return text, index, state
      end
    end
    return
  end
end


local GetAchievementTooltipExtras = function(info)

  local hasInfo
  local achievementID = info.achievementID
  local _, name, _, completed, _, _, _, _, _, icon = GetAchievementInfo(achievementID)
  if not completed then

    local numItems = GetAchievementNumCriteria(achievementID)
    local numNeeded = 0
    local tooltipLines = {}
    for i =1, numItems do
      local criteriaName, criteriaType, completed, _, _, _, _, subAchievementID = GetAchievementCriteriaInfo(achievementID, i)
      --print(GetAchievementCriteriaInfo(achievementID, i))

      if not completed then
        --print('::', criteriaName, completed, subAchievementID)
        if criteriaType == 8 then
          local _, _, _, completed, _, _, _, _, _, subIcon = GetAchievementInfo(subAchievementID)
          --print(' -', criteriaName, completed, subIcon)
          if not completed then
            local numCompleted = 0
            local numSubItems = GetAchievementNumCriteria(subAchievementID)
            local subCriteriaLine
            for j = 1, numSubItems do
              local subName, _, completed = GetAchievementCriteriaInfo(subAchievementID, j)

              --print('   -',subName, completed)
              if completed then
                numCompleted = numCompleted + 1
              else
                numNeeded = numNeeded + 1
                if subName:match(info.name) then
                  hasInfo = true
                  subCriteriaLine = '  |T'..subIcon..':16:16|t ' .. criteriaName
                end
              end

            end
            if subCriteriaLine then
              tinsert(tooltipLines, subCriteriaLine .. ' ('..numCompleted..'/'..numSubItems..')')
            end
          end
        elseif criteriaName:match(info.name) and (not completed) then
          numNeeded = numNeeded + 1
          tinsert(tooltipLines, criteriaName)
        end
      end
    end
    if hasInfo then
      WorldMapTooltip:AddLine(" ")
      WorldMapTooltip:AddLine("Achievements:")
      WorldMapTooltip:AddLine(' |T'..icon..':20:20|t '..name)
      for i, line in ipairs(tooltipLines) do
        WorldMapTooltip:AddLine(line)
      end
    end
  end
  return hasInfo
end

local GetQuestTooltipExtras = function(info)
  local questID = info.questID
  local hasInfo

  if info.pet then
    local index, guid = C_PetJournal.FindPetIDByName(info.pet)
    if not index then
      hasInfo = true
      WorldMapTooltip:AddLine('Pets:')
      WorldMapTooltip:AddLine(' - ' .. info.petName)

      if not IsQuestFlaggedCompleted(questID) then
        WorldMapTooltip:AddLine('  Required Quest Flags', 1, 1, 0)
      else
        WorldMapTooltip:AddLine('  Quest Flags Complete!', 0, 1, 0)
      end

    end
  end

end

function QuestPOI:OnLoad()
  --qprint('|cFF00FF88'..self:GetName()..':OnLoad()|r',db.Config)
  self.debugTimer = 4
  self.title = '|cFF0088FF' .. RETRIEVING_DATA..'|r'
  self.count = self.Overlay.count
  self.timeLabel = self.Overlay.timeLabel
  self.Description = self.Overlay.Description
  self.updateRate = PIN_REQUEST_DELAY
  self.itemName = '|cFF0088FF' .. RETRIEVING_DATA..'|r'


  self.IconBackdrop:SetVertexColor(0,0,0,1)
  self.Overlay:SetPoint('TOPLEFT', self, 'TOPLEFT', 0, 4)
  self.Overlay:SetPoint('BOTTOMRIGHT', self, 'BOTTOMRIGHT', 0, -4)
end

function QuestPOI:OnShow ()
  if self.isStale then
    --print('|cFF00FF00refresh on show')
    self:Refresh('POI_ONSHOW_STALE')
  end

  if self.questID and IsQuestComplete(self.questID) then
    self:Release()
    return
  end

  self:RegisterEvent('QUEST_TURNED_IN')
  self:RegisterEvent('QUEST_LOG_UPDATE')
  self:HideOrShowFrames(true)
end

function QuestPOI:OnEvent(event, questID)
  if (self.questID == questID) and IsQuestComplete(self.questID) then
    db.log(self.questID .. " Marked completed.")
    self:Release()
  end
end

function QuestPOI:OnHide()
  --DEFAULT_CHAT_FRAME:AddMessage('|cFFFFFF00'..self:GetName()..'|r:OnHide()')
  self:HideOrShowFrames(false)
  -- reset flags
  self:SetAlpha(db.PinAlpha)
  self.isAnimating = nil
  if db.Config.DebugEnabled then
    db.log(tostring(self.questID) .. ' ' .. tostring(self.title) .. "\n" .. tostring(REWARD_TYPE_NAMES[self.rewardType]) .. ' ' .. tostring(self.itemName) .. ' ' .. tostring(self.itemNumber) .. "\n|cFFFF4400" .. (self.hideReason or 'NO_MESSAGE') .. "|r\n|cFF00FFFF" ..  debugstack(2,3,0) .. '|r')
  end
  self.hideReason = nil
end

function QuestPOI:OnEnter()
  if self.filtered and (self.questID ~= GetSuperTrackedQuestID()) then
    return
  end
  WorldMap_HijackTooltip(self.owningFrame);
  self:SetFrameLevel(pinBaseIndex+100)
  self.Overlay:SetFrameLevel(overlayBaseIndex+100)

  if previousHighlight then
    previousHighlight:SetFrameLevel(pinBaseIndex+previousHighlight:GetID())
    previousHighlight.Overlay:SetFrameLevel(overlayBaseIndex+previousHighlight:GetID())
  end

  previousHighlight = self


  WorldMapTooltip:SetOwner(self, "ANCHOR_RIGHT");
  --print('doing tooltip stuff')

  -- Can't add stuff after, so most of the blizzard tooltip hook is simply copied over
  local questID = self.questID
  local color = WORLD_QUEST_QUALITY_COLORS[self.rarity] or NORMAL_FONT_COLOR;


  WorldMapTooltip:SetText(self.title, color.r, color.g, color.b);
  QuestUtils_AddQuestTypeToTooltip(WorldMapTooltip, questID, NORMAL_FONT_COLOR);

  if ( self.factionID ) then
    local factionName = GetFactionInfoByID(self.factionID);
    if ( factionName ) then
      if (self.capped) then
        WorldMapTooltip:AddLine(factionName, GRAY_FONT_COLOR:GetRGB());
      else
        WorldMapTooltip:AddLine(factionName);
      end
    end
  end

  if self.worldQuest then
    WorldMap_AddQuestTimeToTooltip(questID);
  end


  for objectiveIndex = 1, self.numObjectives do
    local objectiveText, objectiveType, finished = GetQuestObjectiveInfo(questID, objectiveIndex, false);
    if ( objectiveText and #objectiveText > 0 ) then
      local color = finished and GRAY_FONT_COLOR or HIGHLIGHT_FONT_COLOR;
      WorldMapTooltip:AddLine(QUEST_DASH .. objectiveText, color.r, color.g, color.b, true);
    end
  end

  local percent = C_TaskQuest.GetQuestProgressBarInfo(self.questID);
  if ( percent ) then
    GameTooltip_InsertFrame(WorldMapTooltip, WorldMapTaskTooltipStatusBar);
    WorldMapTaskTooltipStatusBar.Bar:SetValue(percent);
    WorldMapTaskTooltipStatusBar.Bar.Label:SetFormattedText(PERCENTAGE_STRING, percent);
  end

  if db.TooltipExtras[self.questID] then
    for index, info in pairs(db.TooltipExtras[questID]) do
      if info.achievementID then
        GetAchievementTooltipExtras(info)
      end
      if info.questID then
        GetQuestTooltipExtras(info)
      end
    end
  end
  GameTooltip_AddQuestRewardsToTooltip(WorldMapTooltip, questID)

  WorldMapTooltip:Show()
  --WorldMapTooltip.recalculatePadding = true;
  --print(WorldMapTooltip:GetParent())
  --print(WorldMapTooltip:IsVisible())
end

function QuestPOI:OnMouseDown(button)
  if button == 'RightButton' then
    SetSuperTrackedQuestID(nil)
  else
    TaskPOI_OnClick(self, button)
  end
end

function QuestPOI:OnLeave()
  if self.filtered and (self.questID ~= GetSuperTrackedQuestID()) then
    return
  end
  WorldMap_RestoreTooltip()
  WorldMapTooltip:Hide();
end

local updateTime, markTime
function QuestPOI:OnUpdate (sinceLast)
  -- control update check intervals

  if self.toAlpha then
    if not self.alphaStart then
      self.alphaStart = GetTime()
    end


    local alpha = self.icon:GetAlpha()
    local alphaMod = ((GetTime()-self.alphaStart) *FADE_TIMING_MULTIPLIER)
    if self:GetID() == 1 then
    print(alpha, self.toAlpha, sinceLast, alphaMod)
    end
    if alpha > self.toAlpha then
      alpha = alpha - (sinceLast*FADE_TIMING_MULTIPLIER)

      if (alpha <= self.toAlpha) then
        alpha = self.toAlpha
        self.toAlpha = nil
      elseif (alpha <= 0)  then
        alpha = 0
        self.toAlpha = nil
      end
    elseif alpha < self.toAlpha then
      alpha = alpha + alphaMod

      if (alpha >= self.toAlpha) then
        alpha = self.toAlpha
        self.toAlpha = nil
      elseif (alpha >= 1) then
        alpha = 1
        self.toAlpha = nil
      end

    else
      self.toAlpha = nil
    end

    if not self.toAlpha then
      self.alphaStart = nil
    else
      self.alphaStart = GetTime()
    end

    self.icon:SetAlpha(alpha)
    self.RewardBorder:SetAlpha(alpha)
  end

  self.throttle = (self.throttle or self.updateRate) + sinceLast
  if self.throttle >= self.updateRate then
    -- factor overtime into the throttle timer
    self.throttle = self.throttle - self.updateRate
  else
    return
  end
  --@debug@
  if DATA_DEBUG then
    self.debugTimer = self.debugTimer - sinceLast
    if self.debugTimer >= 0 then
      print(self.debugTimer)
      return
    end
  end
  --@end-debug@

  -- query for reward data if it wasn't found in the original scan

  if not self.dataLoaded then
    local dataLoaded = self:GetData()
    if dataLoaded and not tContains(db.UpdatedPins, self) then
      -- self.PendingFade:Stop()
      -- scale info from the parent module is needed, so deal with it there
      print('|cFF00FF88  queueing for update')
      self.isNew = true
      tinsert(db.UpdatedPins, self)
    else

      --print('|cFFFF4400OnUpdate(|r'..self:GetID()..'|cFFFF4400)|r poll failed')
    end
    return
  end

  if self.maxAlertLevel then
    self:UpdateStatus()
  end
end


function QuestPOI:StartFading()
  if not self.toAlpha then
    print('setting toAlpha')
    self.toAlpha = 1
    self.icon:SetAlpha(0)
    self.RewardBorder:SetAlpha(0)
  end
end

-- attempt to pull pin data
function QuestPOI:GetData ()
  --dprint('|cFF00FF88'..self:GetID()..':GetData()|r')
  local questID = self.questID
  if not questID then
    --dprint('|cFFFF4400bad pin|r', self:GetName())
    return nil
  end

  local questTitle, factionID, capped = TQ_GetQuestInfoByQuestID(questID)
  -- if the title is nil, then wait and try later
  if not questTitle then
    --dprint('|cFFBB8844dataLoaded|r = false|cFF00FFFF', self.questId)
    return false
  else
    self.title, self.factionID, self.capped = questTitle, factionID, capped
    --print(questTitle, factionID, capped)
    -- set tag details
    local worldQuestType
    self.tagID, self.tagName, worldQuestType, self.rarity, self.isElite, self.tradeskillLineIndex = GetQuestTagInfo(questID);
    local tagAtlas
    if worldQuestType == LE_QUEST_TAG_TYPE_PET_BATTLE then
      tagAtlas = "worldquest-icon-petbattle"
    elseif worldQuestType == LE_QUEST_TAG_TYPE_PVP then
      tagAtlas = "worldquest-icon-pvp-ffa"
    elseif worldQuestType == LE_QUEST_TAG_TYPE_PROFESSION then
      self.isKnownProfession = nil
      local id = self.tradeskillLineIndex and select(7, GetProfessionInfo(self.tradeskillLineIndex))
      if id then
        self.isKnownProfession = true
        --qprint('profession' , self.title, id)
        tagAtlas = WORLD_QUEST_ICONS_BY_PROFESSION[id]
      end
    elseif worldQuestType == LE_QUEST_TAG_TYPE_DUNGEON then
      tagAtlas = "worldquest-icon-dungeon"
    end
    self.worldQuestType = worldQuestType
    self.tagAtlas = tagAtlas

    local dataLoaded, rewardType, itemName, itemTexture, itemNumber, quality, xpType, xpName, xpIcon, xpCount = self:UpdateRewards()
    --dprint('|cFFBB8844  dataLoaded|r =', dataLoaded, rewardType, itemName, itemTexture, itemNumber, quality)
    if dataLoaded then
      self.rewardType = rewardType
      self.itemName = itemName
      self.itemTexture = itemTexture
      self.itemNumber = itemNumber
      self.itemQuality = quality
      self.dataLoaded = true
      self.isStale = true
      self.xpType, self.xpName, self.xpIcon, self.xpCount = xpType, xpName, xpIcon, xpCount
      WorldPlan.dataFlush = true
    end
  end


  return self.dataLoaded
end

local ID_RESOURCES = 1220
local ID_LEGIONFALL = 1342

--- Returns true if data has changed (either from loading in or qualifications changed)
function QuestPOI:UpdateRewards()
  local questID = self.questID
  if not HaveQuestRewardData(questID) then
    C_TaskQuest.RequestPreloadRewardData(questID);
    return false;
  else
    local rewardMoney, rewardAP, rewardHonor
    local rewardItems, rewardCurrency = {}, {}
    local rewardIcon, rewardName, rewardCount, rewardStyle, rewardType, itemID, quantity, quality
    local xpIcon, xpName, xpCount, xpType
    local foundPrimary

    if (GetNumQuestLogRewardCurrencies(questID) > 0 or GetNumQuestLogRewards(questID) > 0 or GetQuestLogRewardMoney(questID) > 0 or GetQuestLogRewardArtifactXP(questID) > 0 or GetQuestLogRewardHonor(questID)) then
      local money = GetQuestLogRewardMoney(questID)
      if money > 0 then
        rewardMoney = money
        rewardIcon = ICON_MONEY
        rewardName = GetMoneyString(money)
        rewardCount = floor(money/10000)
        rewardType = REWARD_CASH
      end

      local artifactXP = GetQuestLogRewardArtifactXP(questID);
      if artifactXP > 0 then
        rewardAP = artifactXP
        rewardIcon = "Interface\\ICONS\\inv_7xp_inscription_talenttome01"
        rewardCount = artifactXP
        rewardType = REWARD_ARTIFACT_POWER
        --dprint('  artifactXP', artifactXP)
        foundPrimary = true
      end

      local numQuestCurrencies = GetNumQuestLogRewardCurrencies(questID);
      for i = 1, numQuestCurrencies do
        local name, texture, numItems, currencyID = GetQuestLogRewardCurrencyInfo(i, questID);
        local text = BONUS_OBJECTIVE_REWARD_WITH_COUNT_FORMAT:format(texture, numItems, name);
        tinsert(rewardCurrency, {
          name = name,
          texture = texture,
          numItems = numItems,
          text = text
        })

        --dprint('  currency', i, currencyID, name, " |T"..tostring(texture)..":12:12|t")
        if currencyID == ID_RESOURCES then
          rewardIcon = texture
          rewardCount = numItems
          rewardName = name
          rewardType = REWARD_CURRENCY
          foundPrimary = true
        elseif currencyID == ID_LEGIONFALL then
          xpIcon = texture
          xpCount = numItems
          xpName = name
          xpType = REWARD_CURRENCY
        end
      end

      local honorAmount = GetQuestLogRewardHonor(questID);
      if honorAmount > 0 then
        xpIcon = "Interface\\ICONS\\Achievement_LegionPVPTier4"
        xpCount = honorAmount
        xpName = HONOR
        xpType = HONOR_CURRENCY
        -- let items override
      end

      local numQuestRewards = GetNumQuestLogRewards(questID);
      if numQuestRewards > 0 then
        for i = 1, numQuestRewards do
          local name, texture, numItems, quality, isUsable, itemID = GetQuestLogRewardInfo(i, questID)

          if itemID then
            local itemName, itemLink, itemRarity, itemLevel, itemMinLevel, itemType, itemSubType, itemStackCount, itemEquipLoc, itemTexture, sellPrice, classID, subclassID = GetItemInfo(itemID);
            if ( classID == LE_ITEM_CLASS_WEAPON or classID == LE_ITEM_CLASS_ARMOR or (classID == LE_ITEM_CLASS_GEM and subclassID == LE_ITEM_GEM_ARTIFACTRELIC) ) then
              rewardType = REWARD_GEAR
              rewardIcon = texture
              rewardName = name
              rewardCount = numItems
              foundPrimary = true
            elseif IsArtifactPowerItem(itemID) then
              rewardType = REWARD_ARTIFACT_POWER
              rewardIcon = texture
              rewardName = name
              rewardCount = self:UpdateArtifactPower(itemLink)
              foundPrimary = true
              --dprint('is an AP token')
            elseif classID == LE_ITEM_CLASS_TRADEGOODS then
              rewardType = REWARD_REAGENT
              rewardIcon = texture
              rewardName = name
              rewardCount = numItems
              foundPrimary = true
            end
            --dprint('  reward', i, name, " |T"..tostring(texture)..":12:12|t", quality, isUsable, itemID)
            tinsert(rewardItems, {
              name = name,
              texture = texture,
              numItems = numItems,
              quality = quality,
              isUsable = isUsable
            })
            if not foundPrimary then
              --print('  -updating primary')
              rewardType = REWARD_REAGENT

              rewardIcon = texture
              rewardName = name
              rewardCount = numItems
            end

          end

        end
      end

      --dprint('  '..self.questID..':|cFFFFFF00UpdateRewards():', numQuestRewards, rewardType)
      --dprint('  ', tostring(self.title), " |T"..tostring(self.itemTexture)..":12:12|t", tostring(self.itemName))

      if rewardType and ((self.itemNumber ~= rewardCount) or (self.rewardType ~= rewardType) or (self.itemName ~= rewardName) or (self.itemTexture ~= rewardIcon)) then
        if DATA_DEBUG and (self.debugTimer <= 0) then
          return false
        end

        return true, rewardType, rewardName, rewardIcon, rewardCount, quality, xpType, xpName, xpIcon, xpCount

      else
        return false
      end
    end



    --[[ set reward category
    local numRewards = GetNumQuestLogRewards(questID)
    local numCurrency = GetNumQuestLogRewardCurrencies(questID)
    local money = GetQuestLogRewardMoney(questID)
    if numRewards >= 1 then
      rewardType, rewardIcon, rewardCount, rewardName, itemID, quality = WorldPlanQuests:GetRewardHeader(questID)
    elseif numCurrency >= 1 then
      rewardName, rewardIcon, rewardCount = GetQuestLogRewardCurrencyInfo(1, questID)
      rewardType = REWARD_CURRENCY
    elseif money >= 1 then
      rewardName = GetMoneyString(money)
    end
    --]]

  end
end


function QuestPOI:UpdateArtifactPower(rewardLink)
  if not (rewardLink or self.rewardLink) then
    return
  end

  self.rewardLink = rewardLink or self.rewardLink

  local rewardCount
  WorldPlanTooltip:SetOwner(self, 'ANCHOR_NONE')
  WorldPlanTooltip:SetHyperlink(rewardLink or self.rewardLink)
  for i = 1, WorldPlanTooltip:NumLines() do
    local line = _G['WorldPlanTooltipTextLeft' .. i]
    local text = line and line:GetText()
    local multiplier = (text:match('million') and 1000000) or 1
    if text then
      text = text:gsub(',', '')
      local ap = text:match('([%d]+) '..ARTIFACT_POWER)
      if not ap then
        ap = text:match('([%d%.]+) million '..ARTIFACT_POWER)
        if ap then
          ap = tonumber(ap) * 1000000
        end
      end
      if ap then
        rewardCount = tonumber(ap)
      end
    end
  end
  return rewardCount
end

-- Applies position and sizing parameters to the pin data
function QuestPOI:SetAnchor(owner, dX, dY, mapWidth, mapHeight, scaleFactor)
  --dprint(self:GetName()..':SetAnchor()', owner, dX, dY, scaleFactor, self.filtered, self.used)
  if not self.used then
    self.hideReason = 'SetAnchor() on an unused frame.'
    self:HideOrShowFrames(false)
    return
  end


  if owner then
    self:SetParent(owner)
    self.Overlay:SetParent(owner)
    self:SetFrameStrata('HIGH')
    self:SetFrameLevel(pinBaseIndex + self:GetID())
    self.Overlay:SetFrameStrata('HIGH')
    self.Overlay:SetFrameLevel(overlayBaseIndex + self:GetID())
    self.isStale = true
  else
    owner = self:GetParent()
  end


  if scaleFactor then
    print('scaleFactor')
    self:SetScale(scaleFactor)
    self.Overlay:SetScale(scaleFactor)
    self.scaleFactor = scaleFactor
  end

  mapWidth = mapWidth or owner:GetWidth()
  mapHeight = mapHeight or owner:GetHeight()
  dX = dX or self.x
  dY = dY or self.y
  --print(owner:GetName() or tostring(owner), self:GetName(), owner:GetScale(), scaleFactor)
  if (self.x ~= dX) or (self.y ~= dY) or scaleFactor then

    self.x = dX
    self.y = dY
    local pX = (dX * mapWidth) * (1 / scaleFactor)
    local pY = (-dY * mapHeight) * (1 / scaleFactor)
    --print('to', pX, pY, self:GetScale())
    self:ClearAllPoints()
    self:SetPoint('CENTER', owner, 'TOPLEFT', pX, pY)
  end

end

-- Non-hieriarchical display states, checked separately from used/filtered states
function QuestPOI:GetCriteriaState()
  local isCriteria, isBounty, isSpellTarget



  for index, bounty in pairs(db.Bounties) do
    if (not IsQuestComplete(bounty.questID)) and IsQuestCriteriaForBounty(self.questID, bounty.questID) then
      isCriteria = true
      if db.selectedBounty == bounty then
        isBounty = true
      end
      --dprint('|cFF00FF88Criteria:|r', self.questID, bounty.questID, isCriteria, isBounty)
    end
  end
  isSpellTarget = IsQuestIDValidSpellTarget(self.questID)

  if (self.isBounty ~= isBounty) or (self.isCriteria ~= isCriteria) or (self.isSpellTarget ~= isSpellTarget) then
    --wqprint('|cFF00FF00criteria state changed')
    self.isBounty = isBounty
    self.isCriteria = isCriteria
    self.isSpellTarget = isSpellTarget
    self.isStale = true
    return true
  end
end

local cvar_check = {
  [REWARD_CASH] = 'worldQuestFilterGold',
  [REWARD_ARTIFACT_POWER] = 'worldQuestFilterArtifactPower',
  [REWARD_CURRENCY] = 'worldQuestFilterOrderResources',
  [REWARD_REAGENT]= 'worldQuestFilterProfessionMaterials',
  [REWARD_GEAR] = 'worldQuestFilterEquipment',
}


function QuestPOI:CheckFilterRules ()
  local print = qprint
  local qType = self.worldQuestType
  if not TQ_IsActive(self.questID) then
    self.hideReason = 'Filter check ended because quest is inactive.'
    self.used = nil
    self:SetShown(false)
    return
  end

  if qType == LE_QUEST_TAG_TYPE_PROFESSION then
    if not(self.isKnownProfession or db.Config.ShowAllProfessionQuests) then
      self.hideReason = 'Failed profession check.'
      self.used = nil
      self:SetShown(false)
      return
    end
  end

  local filtered
  for filterKey, value in pairs(db.UsedFilters) do
    if self[filterKey] ~= value then
      if not self.filtered then
        print('|cFFFF4400filtering', filterKey, value, '~=', self[filterKey], self.title)
      end
      filtered = true
    end
  end
  if self.rewardType  and cvar_check[self.rewardType] then
    if not GetCVarBool(cvar_check[self.rewardType])  then
      filtered = true
    end
  end

  print('  '..self.questID..':|cFFFFFF00CheckFilterRules()|r', filtered, self.title)
  if self.filtered ~= filtered then
    wqprint('|cFF00FF00filter changed')
    self.isStale = true
  end

  self.filtered = filtered
  self.used = true
  self:SetShown(true)
end

function QuestPOI:Refresh (event)
  print('|cFF00FF88Refresh(|r'..self:GetID()..'|cFF00FF88)|r', event, self.title)


  local style = DEFAULT_STYLE
  if self.filtered then
    print('choose minimized')
    style = MINIMIZED_STYLE
  elseif self.dataLoaded then
    print('choose reward type')
    style = REWARD_TYPE_STYLES[self.rewardType]
  else
    print('choose default')
  end

  local currentWidth = style.iconWidth or DEFAULT_STYLE.iconWidth



  local borderWidth = style.borderWidth or DEFAULT_STYLE.borderWidth
  local highlightWidth = style.highlightWidth or DEFAULT_STYLE.highlightWidth
  local tagSize = style.TagSize or DEFAULT_STYLE.TagSize
  local hideIcon = style.hideIcon or DEFAULT_STYLE.hideIcon
  local borderColor = style.border or DEFAULT_STYLE.border
  local textColor = style.textColor or DEFAULT_STYLE.textColor
  local questID = self.questID
  local iconBorder = self.RewardBorder
  local trackingBorder = self.HighlightBorder
  local icon = self.icon
  local count = self.count
  local hideNumbers = style.hideNumber or DEFAULT_STYLE.hideNumber


  local tagIcon = self.tagIcon
  self.maxAlertLevel = style.maxAlertLevel or DEFAULT_STYLE.maxAlertLevel

  if self.dataLoaded then
    if self.isNew then
      print('new pin, has data, cue fade')
      self:StartFading()
      self.isNew = nil
    end
  else
    if not self.toAlpha then
      icon:SetAlpha(0)
      iconBorder:SetAlpha(0)
    end
  end

  if self.itemName then
    if self.itemNumber and (self.itemNumber > 1) and (not hideNumbers) then
      local numberString = self.itemNumber
      if self.itemNumber >= 1000000 then
        numberString = (floor(self.itemNumber/100000)/10) .. 'M'
      elseif self.itemNumber >= 10000 then
        numberString = floor(self.itemNumber/1000) .. 'k'
      elseif self.itemNumber >= 1000 then
        local numeral = floor(self.itemNumber/1000)
        local decimal = mod(self.itemNumber, 1000)
        numberString = numeral
        if decimal > 100 then
          numberString = numberString .. '.' .. tostring(floor(decimal/100))
        end
        numberString = numberString .. 'k'
      end

      self.count:SetText(numberString)
      self.count:SetTextColor(unpack(textColor))
      self.count:Show()
    else
      self.count:SetText(nil)
      self.count:Hide()
    end
  else
    self.count:Hide()
  end

  if db.Config.ShowVerboseInfo then
    self.Description:SetText(self.title .. "\n" .. floor(self.x*100+.5) .. "," .. floor(self.y*100+.5))
  end


  icon:SetSize(currentWidth, currentWidth)
  icon:SetMask(style.iconMask or DEFAULT_STYLE.iconMask)
  if self.itemTexture then
    --iconBorder:SetTexture(WORLD_QUEST_BORDER)

    if hideIcon then
      icon:SetTexture(PENDING_ICON)
      icon:SetDesaturated(true)
      icon:SetVertexColor(unpack(borderColor))
    else
      icon:SetTexture(self.itemTexture)
      icon:SetDesaturated(false)
      icon:SetVertexColor(1, 1, 1)
    end
  else
    --
    --icon:SetTexture(PENDING_ICON)
    --icon:SetDesaturated(true)
    --icon:SetVertexColor(unpack(borderColor))
  end
  local borderMask = style.borderMask or DEFAULT_STYLE.borderMask
  local borderSize = currentWidth + (borderWidth * 2) + (self.isCriteria and 2 or 0)


  iconBorder:SetSize(borderSize, borderSize)

  iconBorder:SetMask(borderMask)
  iconBorder:SetTexture(PENDING_BORDER)

  iconBorder:SetDesaturated(true)

  local highlightSize = borderSize + (highlightWidth * 2)
  trackingBorder:SetSize(highlightSize, highlightSize)
  trackingBorder:SetMask(borderMask)
  trackingBorder:SetTexture(PENDING_BORDER)

  self:SetSize(borderSize, borderSize)
  self.IconBackdrop:SetSize(currentWidth, currentWidth)



  iconBorder:SetPoint('CENTER', (style.x or 0), (style.y or 0))
  trackingBorder:SetPoint('CENTER', (style.x or 0), (style.y or 0))

  self.tagIcon:SetShown((not self.filtered) and true or false)
  self.tagIcon:SetAtlas(self.tagAtlas)
  self.EliteBorder:SetShown(self.isElite and not self.filtered)

  self:UpdateStatus()

  if self.isBounty then
    --print('is bounty')
    iconBorder:SetVertexColor(trackingBorder:GetVertexColor())
    trackingBorder:SetVertexColor(unpack(BORDER_SELECTED_BOUNTY))
  elseif self.isCriteria then
    --print('is criteria of a bounty')
    iconBorder:SetVertexColor(trackingBorder:GetVertexColor())
    trackingBorder:SetVertexColor(unpack(BORDER_CRITERIA))
  else

    iconBorder:SetVertexColor(unpack(borderColor))
    trackingBorder:SetVertexColor(0,0,0,.5)
  end

  if SpellCanTargetQuest() then
    if IsQuestIDValidSpellTarget(self.questID) then
      icon:SetVertexColor(1,1,1)
    else
      icon:SetVertexColor(1,0,0,1)
    end
    self:EnableMouse(false)
  else
    self:EnableMouse(true)
  end
  self.isStale = nil

  -- signal filter info update
  WorldPlanSummary.isStale = true
end

-- Called at static intervals and with Refresh
function QuestPOI:UpdateStatus()
  -- update time elements
  self.isActive = TQ_IsActive(self.questID)
  --print(self.maxAlertLevel)
  local border = (self.isBounty or self.isCriteria) and self.RewardBorder or self.HighlightBorder

  if self.isActive then
    local tl = self.alertLevel
    local timeLeft = TQ_GetQuestTimeLeftMinutes(self.questID)
    if timeLeft > 0 then

      local text, timeState, style = self:GetTimeInfo(timeLeft, self.maxAlertLevel)
      if tl ~= timeState then
        tl = timeState
        self.timeLabel:SetText(text)
      end
      if style then
        self.RewardBorder:SetVertexColor(style.r, style.g, style.b, style.a)
      end
    end

    self.alertLevel = tl
    self.timeLabel:SetShown(self.worldQuest and (self.maxAlertLevel >= 1))
  else
    self.hideReason = "No longer active."
    self:HideOrShowFrames(false)

  end
end

-- Show/Hide the text overlays associated with the quest pin; they aren't hierarchically linked
function QuestPOI:HideOrShowFrames(isShown)
  if not isShown then
    -- print('|cFFFFFF00' ..self:GetName()..':HideOrShowFrames()')
    -- do not SetShown() here
    if not self.hideReason then
      self.hideReason = "HideOrShowFrames() called without a reason."
    end
  end
  self.Overlay:SetShown(isShown)
  self.count:SetShown(isShown)
  self.timeLabel:SetShown(isShown)
end

function QuestPOI:Release()
  print('|cFFFF4400'..self:GetID()..':Release()', self.hideReason)
  self.hideReason = 'Released by script.'
  self:HideOrShowFrames(false)
  if self.questID then
    db.QuestsByID[self.questID] = nil
    for _, map in pairs(db.QuestsByZone) do
      map[self.questID] = nil
    end
    self.questID = nil
  end
  self.isActive = nil
  self.complete = nil
  self.used = nil
  self.dataLoaded = nil
  self.rewardType = nil
  self.itemTexture = nil
  self.itemName = nil
  self.itemNumber = nil
  self:SetShown(false)
  self.animating = nil
  self.icon:SetAlpha(0)
  self.RewardBorder:SetAlpha(0)
  self:UnregisterEvent('QUEST_TURNED_IN')
  self:UnregisterEvent('QUEST_LOG_UPDATE')

  for i, pin in ipairs(db.UsedPins) do
    if pin == self then
      print('|cFFFF4400cleared from UsedPins|r')
      tremove(db.UsedPins, i)
      break
    end
  end

  tinsert(db.FreePins, self)
  WorldPlan.dataFlush = true
  WorldPlanSummary.isStale = true

  return true
end