view WorldQuests.lua @ 43:77c2ffb5c7f5 v1.0-rc11

- Removed possible refresh loop occurring in non-BrokenIsle zones
author Nenue
date Sun, 25 Dec 2016 15:47:17 -0500
parents 79e5e96e5f18
children db570c6a0ffb
line wrap: on
line source
-- WorldPlan
-- WorldQuests.lua
-- Created: 11/2/2016 3:40 PM
-- %file-revision%
local _, db = ...
local WorldQuests = WorldPlanQuestsMixin

local MC_GetNumZones, MC_GetZoneInfo = C_MapCanvas.GetNumZones, C_MapCanvas.GetZoneInfo
local TQ_GetQuestsForPlayerByMapID = C_TaskQuest.GetQuestsForPlayerByMapID -- This function is not yet documented
local TQ_GetQuestZoneID = C_TaskQuest.GetQuestZoneID
local GetMapInfo = GetMapInfo
local print = DEVIAN_WORKSPACE and function(...) _G.print('WorldQuests', ...) end or function() end
local rprint = DEVIAN_WORKSPACE and function(...) _G.print('WQRefresh', ...) end or function() end
local qprint = DEVIAN_WORKSPACE and function(...) _G.print('POI', ...) end or function() end
local wprint = DEVIAN_WORKSPACE and function(...) _G.print('WP', ...) end or function() end
local mprint = DEVIAN_WORKSPACE and function(...) _G.print('Canvas', ...) end or function() end


local PinBaseIndex = 1200
local BROKEN_ISLES_ID, DALARAN_ID, AZSUNA_ID, VALSHARAH_ID, HIGHMOUNTAIN_ID, STORMHEIM_ID, SURAMAR_ID, EOA_ID = 1007, 1014, 1015,1018, 1024, 1017, 1033, 1096
local WORLD_QUEST_MAPS = { [DALARAN_ID] = 'Dalaran70',  [AZSUNA_ID] = 'Azsuna',  [VALSHARAH_ID] = "Val'sharah",
  [HIGHMOUNTAIN_ID] = 'Highmountain', [STORMHEIM_ID] = 'Stormheim',  [SURAMAR_ID] = 'Suramar',  [EOA_ID] = 'EyeOfAszhara', }

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 numPins = 0
local NumPinFrames = 1
WorldQuests.TasksByID = {}

--%debug%
local SetTimedCallbackForAllPins = function(seconds, callback)
  C_Timer.After(seconds, function()
    for id, pin in pairs(WorldPlanQuests.QuestsByID) do
      callback(pin)
    end
  end)
end

function WorldQuests:OnUpdate(sinceLast)
  if self.filtersDirty or self.isStale then
    self:Refresh()
  end
end

function WorldQuests:Setup()
  print('|cFFFF4400'..self:GetName()..':Setup()')

  for mapID, mapName in pairs(WORLD_QUEST_MAPS) do
    db.QuestsByZone[mapID] = {}
  end


  -- refresh positions any time blizzard does so (i.e. mousewheel zoom)
  hooksecurefunc("WorldMapScrollFrame_ReanchorQuestPOIs", function()
    print('|cFFFF4400WorldMapScrollFrame_ReanchorQuestPOIs')
    self:Refresh(true)
  end)


  -- hide the original world quest POIs

  hooksecurefunc("WorldMap_UpdateQuestBonusObjectives", function()
    print('|cFFFF4400WorldMap_UpdateQuestBonusObjectives')
    for i = 1, NUM_WORLDMAP_TASK_POIS do
      local button = _G['WorldMapFrameTaskPOI'..i]
      if button and button.worldQuest then
        button:Hide()
      end
    end
  end)
end
local defaults = {}
local REWARD_UNKNOWN = 768
function WorldQuests:OnLoad()
  print('|cFFFF4400'..self:GetName()..':OnLoad()')

  self:SetParent(WorldMapFrame)
  WorldPlan:AddHandler(self, defaults)

  local rgbWhite = {1, 1, 1 }
  WorldPlan:AddTypeInfo(self, REWARD_UNKNOWN, { r = 1, g = 1, b = 1})
  WorldPlan:AddTypeInfo(self, REWARD_REAGENT, { r = 0, g = 1, b = 1 })
  WorldPlan:AddTypeInfo(self, REWARD_ARTIFACT_POWER, { r = 1, g = .25, b = .5, hasNumeric = true, numberRGB = rgbWhite })
  WorldPlan:AddTypeInfo(self, REWARD_GEAR, { r = .1, g = .2, b = 1 })
  WorldPlan:AddTypeInfo(self, REWARD_CURRENCY, { r = 1, g = 1, b = 0, hasNumeric = true, numberRGB = {1,1,0}, })
  WorldPlan:AddTypeInfo(self, REWARD_CASH, { r = 1, g = 1, b = .32, pinMask = false, rewardMask = false })

  for areaID, fileName in pairs(WORLD_QUEST_MAPS) do
    db.QuestsByZone[areaID] = {}
  end

  -- WORLD_MAP_UPDATE and PLAYER_ENTERING_WORLD are passed down from a higher level
  self:RegisterEvent('WORLD_QUEST_COMPLETED_BY_SPELL')
  self:RegisterEvent('SKILL_LINES_CHANGED')

  WorldMapPOIFrame = _G.WorldMapPOIFrame

end

function WorldQuests:OnMapInfo()
  if self:IsVisible() then
    self:Refresh()
  else
    self.isStale = true
  end
end

function WorldQuests:OnEvent (event, ...)

  print('|cFFFFFF00'..self:GetName()..':OnEvent() '..event..'|r', GetTime(), ...)
  if event == 'QUEST_LOG_UPDATE' then
    local questID, added = ...
    if questID and added then
      local questPOI = self:AcquirePin(questID)
      questPOI:GetQuestInfo()
      questPOI.isStale = true
      self.isStale = true
    else
      self:Refresh(true)
    end
    print('WorldMapFrame', WorldMapFrame:IsVisible(), 'hasUpdates:', self.isStale)
  elseif event == 'WORLD_QUEST_COMPLETED_BY_SPELL' then
    local questID = ...
    if questID and db.QuestsByID[questID] then
      self:ReleasePin(db.QuestsByID[questID])
      rprint('|cFFFF4400release|r', questID)
    end
  elseif event == 'SKILL_LINES_CHANGED' then
    self:SetFilteredPins()
  end
end

local totalPins = 0
local TQ_GetQuestLocation = C_TaskQuest.GetQuestLocation
function WorldQuests:AcquirePin (info)
  local questID = info.questId
  if not questID then
    return nil
  end

  if not QuestUtils_IsQuestWorldQuest(questID) then
    return nil
  end


  local pin = db.QuestsByID[questID]
  if not pin then
    local numFree = #db.FreePins
    if numFree >= 1 then
      pin = tremove(db.FreePins, numFree)
      --print('|cFF00FF00Re-using', pin:GetName())
    else
      totalPins = totalPins + 1
      local name = 'WorldPlanQuestMarker' .. NumPinFrames
      --print('|cFF00FF00Creating', name)
      pin = CreateFrame('Frame', name, WorldMapPOIFrame, 'WorldPlanQuestPin')

      pin:SetFrameStrata('HIGH')
      pin.GetTypeInfo = function(frame, typeID)
        return self:GetTypeInfo(typeID)
      end
      pin:SetID(totalPins)
      NumPinFrames = NumPinFrames + 1
      --pin.iconBorder:SetVertexColor(0,0,0,1)
    end
    pin.questID = questID
    pin.worldQuest = true
    pin.throttle = 1
    pin.isNew = true
    pin.currentWidth = nil
    db.QuestsByID[questID] = pin
    tinsert(db.UsedPins, pin)
  end

  if pin and info then
    pin.inProgress = info.inProgress
    pin.floor = info.floor
    pin.numObjectives = info.numObjectives or 0
    if info.x and info.y then
      pin.x = info.x or pin.x
      pin.y = info.y or pin.y
      rprint('|cFFFF4400coords|r', info.x, info.y)
    end
  end

  pin:GetData()
  C_TaskQuest.RequestPreloadRewardData(info.questId)
  return pin
end

-- remove from index and add it to the recycling heap
function WorldQuests:ReleasePin (pin)

  local id = pin.questID
  if id then
    db.QuestsByID[id] = nil

    for i, zone in pairs(db.QuestsByZone) do
      print('-', i, zone[i])
      zone[id] = nil
    end
    db.TasksByID[id] = nil
  end
  pin:SetShown(false)
  pin:ClearAllPoints()
  tinsert(db.FreePins, pin)

  print('|cFF00FF00-'.. (pin.mapID and GetMapNameByID(pin.mapID) or '???') ..'|r', id, pin.title)
end

function WorldQuests:GetBonusObjectives()


  local tasksTable = GetTasksTable()
  if tasksTable ~= nil then
    print('|cFF00FF88'..self:GetName()..':BonusObjectives()|r ')
    self.numTasks = #tasksTable
    for i, taskID in ipairs(tasksTable) do
      if not QuestUtils_IsQuestWorldQuest(taskID) then
      local info = db.TasksByID[taskID]
        if not info then
          local isInArea, isOnMap, numObjectives, taskName, displayAsObjective = GetTaskInfo(taskID)
          if isOnMap then
            print(' * '..i, taskID, GetTaskInfo(taskID))
            info = {
              questID = taskID,
              numObjectives = numObjectives,
              title = taskName,
              isInArea = isInArea,
              isOnMap = isOnMap,
              displayAsObjective = displayAsObjective,
              worldQuest = false,
              isPending = false,
              isNew = true,
            }


            db.TasksByID[taskID] = info

            local pin = self:AcquirePin(taskID)
            for k,v in pairs(info) do
              pin[k] = v
            end
            pin:GetBonusObjectiveInfo(info)
          end
        end
      end


    end
  end
end




-- use tooltip object to extract item details
function WorldQuests:GetRewardHeader(questID)
  local name, icon, quantity, quality, _, itemID = GetQuestLogRewardInfo(1, questID)
  local scanner = _G.WorldPlanTooltip
  local print = qprint
  if not itemID then
    return
  end
  --print('GetRewardHeader', questID)

  scanner:SetOwner(WorldPlan, "ANCHOR_NONE")
  scanner:SetItemByID(itemID)
  scanner:Show()
  local ttl1 = _G['WorldPlanTooltipTextLeft1']
  local ttl2 = _G['WorldPlanTooltipTextLeft2']
  local ttl3 = _G['WorldPlanTooltipTextLeft3']
  local ttl4 = _G['WorldPlanTooltipTextLeft4']
  --print(ttl2, ttl3, ttl4)
  if ttl2 then
    local text = ttl2:GetText()
    -- Artifact Power
    --print(text)
    if text then
      if text:match("|cFFE6CC80") then
        --print('AP token!', text)
        local power
        if ttl4 then
          local text = ttl4:GetText()
          --print('tip line 4', text)
          if text then
            power = text:gsub("%p", ""):match("%d+")
            power = tonumber(power)
          end

        end
        return REWARD_ARTIFACT_POWER, "Interface\\ICONS\\inv_7xp_inscription_talenttome01", power, name, itemID, quality
      elseif text:match("Item Level") then
        --print('equipment!', text)
        quantity = text:match("Item Level ([%d\+]+)")
        return REWARD_GEAR, icon, quantity, name, itemID, quality
      elseif  text:match("Crafting Reagent") then
        --print('|cFFFF4400it is a reagent', text)
        return REWARD_REAGENT, icon, quantity, name, itemID, quality
      end
    end
  end

  if ttl3 then
    local text = ttl3:GetText()
    if text and text:match("Crafting Reagent") then
      --print('|cFFFF4400it is a reagent', text)
      return REWARD_REAGENT, icon, quantity, name, itemID, quality
    end
  end
  return 128, icon, quantity, name, itemID, quality
end

local GetCurrentMapAreaID, GetMapNameByID= GetCurrentMapAreaID, GetMapNameByID
local wipe, pairs = wipe, pairs
-- create of update quest pins for a map and its underlying zones
function WorldQuests:UpdateWorldQuests (mapID)

  mapID = mapID or db.currentMapID
  if not mapID then
    -- info not available yet
    return
  end


    print('|cFF00FF88'..self:GetName()..':UpdateWorldQuests()|r', 'map:', mapID, 'realMap:', db.currentMapID)


  self.isStale = nil
  print('|cFF00FFFFContinent:|r', BROKEN_ISLES_ID, GetMapNameByID(BROKEN_ISLES_ID))
  self.isRecursed = true
  for i = 1, MC_GetNumZones(BROKEN_ISLES_ID) do
    local submapID, name, depth = MC_GetZoneInfo(BROKEN_ISLES_ID, i)
    local taskInfo = TQ_GetQuestsForPlayerByMapID(submapID, BROKEN_ISLES_ID)
    if taskInfo then
      local zoneName = GetMapNameByID(submapID)
      print('|cFF00FFFF  Zone:|r', submapID, zoneName, #taskInfo)
      db.QuestsByZone[submapID] = db.QuestsByZone[submapID] or {}
      for i, info in ipairs(taskInfo) do
        if HaveQuestData(info.questId) then
          rprint('|cFF44FF44update|r', info.questId, zoneName)
          local questID = info.questId
          local pin = self:AcquirePin(questID)
          local pin = db.QuestsByID[questID]
          if pin then
            pin.isStale = true
            if pin.isPending then
              self.isPending = true
            end
          end
        else
          rprint('|cFFFF4400no data|r', info.questId, zoneName)
        end
      end
    end
  end

  self:GetBonusObjectives()

  print('  hasUpdate:', self.isStale, 'isPending:', self.isPending, 'timer:', (self.OnNext and 'waiting' or ''))
  --WorldPlan.isStale = (self.isStale or WorldPlan.isStale)
  if self.isStale and self:IsVisible() then
    self:Refresh()
  end
end

function WorldQuests:Report()
  for i, pin in ipairs(db.UsedPins) do
    db:print(i, pin.questID, pin.title)
  end

  for id, pin in pairs(db.QuestsByID) do
    db:print(id, pin.worldQuestType, pin.rewardType, pin.title)
  end
end

function WorldQuests:Refresh(fromUser)
  self.currentMapID = GetCurrentMapAreaID()
  print('|cFF00FF88'..self:GetName()..':Refresh()|r', fromUser or '|cFFFFFF00internal')
  if not self:IsVisible() then
    print('  not visible, flag for later')
    self.isStale = true
    return
  end
  wprint('  |cFF00FF88'..self:GetName()..':Refresh()|r', fromUser or '|cFFFFFF00internal')

  for index, pin in pairs(db.QuestsByID) do
    pin.used = nil
    pin:SetShown(false)
  end

  self:SetFilteredPins(db.QuestsByID)
  self:UpdateAnchors(nil, fromUser)
  self:Cleanup (fromUser)
  self.isStale = nil
end

-- update visibility states of all pins
function WorldQuests:SetFilteredPins(pins)
  print('  |cFFFFFF00'..self:GetName()..':SetFilteredPins()|r', pins)
  pins = pins or db.QuestsByID
  for questID, pin in pairs(pins) do
    pin.filtered = pin:IsFiltered()
    pin.isStale =  true
    rprint('|cFF00FF00filter', pin.questID, pin.filtered, 'used:', pin.used)
  end
end

local abs = math.abs
function WorldQuests:UpdateQuestButton(info, mapID)
  local questID, x, y = info.questId, info.x, info.y
  local pin = self:AcquirePin(info)
  if not pin then
    return
  end


  print('~ ', pin.mapID, pin.questID, pin.title)
  rprint('|cFF00FF00update|r', x, y, pin.title)
  pin:IsShowable()

  if x and y then

    pin.x = x
    pin.y = y
    pin:SetFrameLevel(PinBaseIndex+numPins)
    pin:SetPoint('CENTER', self.hostFrame, 'TOPLEFT', self.hostWidth * pin.x, -self.hostHeight * pin.y)
    pin.throttle = 1
    pin:SetShown(pin.used)
    tinsert(self.UsedPositions, pin)
  end
  pin.owningFrame = self.hostFrame
  pin:SetParent(self.hostFrame)

  if mapID then
    if not db.QuestsByZone[mapID] then
      db.QuestsByZone[mapID] = {}
    end
    db.QuestsByZone[mapID][questID] = pin
  end
end

function WorldQuests:UpdateMap(taskInfo, mapID)
  print('Map', GetMapNameByID(mapID), GetMapNameByID(self.currentMapID))
  for index, info in pairs(taskInfo) do
    self:UpdateQuestButton(info, mapID)
  end
end

function WorldQuests:UpdateAnchors (fromUser)


  wipe(self.UsedPositions)
  print('  |cFF00FF00'..self:GetName()..':UpdateAnchors()', fromUser)
  self.hostFrame = WorldMapPOIFrame
  self.hostWidth, self.hostHeight = self.hostFrame:GetSize()
  self.nudgeThrescholdX = 16/self.hostWidth
  self.nudgeThrescholdY = 16/self.hostHeight
  local print = rprint
  print('|cFF00FF00'..self:GetName()..':UpdateAnchors()', fromUser)
  local mapFileName, textureHeight, textureWidth, isMicroDungeon, microDungeonMapName = GetMapInfo()
  if isMicroDungeon then
    return
  end

  numPins = 0
  local taskInfo = TQ_GetQuestsForPlayerByMapID(self.currentMapID)
  if taskInfo then
    self:UpdateMap(taskInfo, self.currentMapID)
  end
  local numZones = MC_GetNumZones(self.currentMapID)
  if numZones then
    for i = 1, numZones do
      local mapAreaID = MC_GetZoneInfo(self.currentMapID, i)
      local taskInfo = TQ_GetQuestsForPlayerByMapID(mapAreaID, self.currentMapID)
      if taskInfo then
        self:UpdateMap(taskInfo, mapAreaID)
      end
    end
  end
  if self.filtersDirty then
    self:SetFilteredPins(db.QuestsByID)
  end
end

-- shows, animates, or hides pins based on their current visibility flags
function WorldQuests:Cleanup (fromUser)

  print('|cFFFFFF00'..self:GetName()..':Cleanup()|r')
  local print = rprint
  print('|cFFFFFF00'..self:GetName()..':Cleanup()|r')
  --local showQuestPOI = db.Config.EnablePins
  for questID, pin in pairs(db.QuestsByID) do
    local oV = pin:IsShown()
    if pin.used then
      pin:SetShown(true)
      pin.throttle = 1
      if oV == false then
        print('|cFF00FF00cleanup +|r', questID, pin.title)
      end
    else
      if oV == true then
        print('|cFFFF4400 -|r', questID, pin.title)
      end
    end

    if pin.worldQuest and (not C_TaskQuest.IsActive(pin.questID)) then
      self:ReleasePin(pin)
    end
    pin.isStale = true
  end
end