view WorldQuests.lua @ 35:26dfa661daa7

WorldPlan: - Quest pins will appear in the flight map. They follow the filter settings applied from the world map. - Reward filter toggle changed to clear out other reward filters. The assumption being that one is most often looking only for that particular type of quest when they go to use it. - Fixed filter bar info falling out of sync after player-triggered world map updates. - Code stuff: -- Quest pin shown-state management makes better use of OnShow OnHide handlers, SetShown is toggled and it all goes from there -- WorldQuests module re-factored outside of the top level frame script. ClassPlan: - Available missions are now recorded; the mission list can be toggled between in-progress and available by clicking the heading.
author Nenue
date Thu, 03 Nov 2016 17:29:15 -0400
parents 0100d923d8c3
children 21bcff08b0f4
line wrap: on
line source
-- WorldPlan
-- WorldQuests.lua
-- Created: 11/2/2016 3:40 PM
-- %file-revision%

WorldPlanDataProvider = {}
WorldPlanDataPinMixin = {}
WorldPlanQuestsMixin = {
  QuestsByZone = {},
  QuestsByID = {},
  freePins = {},
}
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('WP', ...) end or function() end
local qprint = DEVIAN_WORKSPACE and function(...) _G.print('POI', ...) end or function() end
local wqprint = DEVIAN_WORKSPACE and function(...) _G.print('WorldQuests', ...) 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 = 1600
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 ZoneInfo = {}
local NumPinFrames = 1


--%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:Setup()


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


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

  -- hide the original world quest POIs
  hooksecurefunc("WorldMap_UpdateQuestBonusObjectives", function()
    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 WorldMapPOIFrame
local defaults = {}
function WorldQuests:OnLoad()
  print('|cFF00FF88'..self:GetName()..':OnLoad')

  WorldPlan:AddHandler(self, defaults)

  local rgbWhite = {1, 1, 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 = 0, g = 0, b = 0, })

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

  self:RegisterEvent('WORLD_QUEST_COMPLETED_BY_SPELL')
  self:RegisterEvent('SKILL_LINES_CHANGED')

  WorldMapPOIFrame = _G.WorldMapPOIFrame

end

function WorldQuests:OnEvent (event, ...)
  local print = wqprint
  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)
      self.isStale, self.isPending = questPOI:RefreshData()
    else
      self:RefreshData()
    end
    print('WorldMapFrame', WorldMapFrame:IsVisible(), 'hasUpdates:', self.isStale)
  elseif event == 'WORLD_MAP_UPDATE' or event == 'PLAYER_ENTERING_WORLD' then
    self.isStale = true
  elseif event == 'WORLD_QUEST_COMPLETED_BY_SPELL' then
    local questID = ...
    if questID and self.QuestsByID[questID] then
      self:ReleasePin(self.QuestsByID[questID])
    end
  elseif event == 'SKILL_LINES_CHANGED' then
    self.isStale = true
  end
end

local TQ_GetQuestLocation = C_TaskQuest.GetQuestLocation
function WorldQuests:AcquirePin (questID, mapID)
  local pin = self.QuestsByID[questID]
  local isNew = false
  if not pin then
    isNew = true
    local numFree = #self.freePins
    if numFree >= 1 then
      pin = tremove(self.freePins, numFree)
      --print('|cFF00FF00Re-using', pin:GetName())
    else
      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
      NumPinFrames = NumPinFrames + 1
      --pin.iconBorder:SetVertexColor(0,0,0,1)
    end
    pin:SetID(questID)
    pin.isNew = true
    pin.currentWidth = nil

    -- used by TaskPOI_x scripts
    pin.questID = questID
    pin.worldQuest = true

    self.QuestsByID[questID] = pin
  else
    --print('|cFF00FF00Using', pin:GetName())
  end
  mapID = mapID or TQ_GetQuestZoneID(questID)
  self.QuestsByZone[mapID][questID] = pin

  return pin, isNew
end

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

  local id = pin.questId
  if id then
    self.QuestsByID[id] = nil
    for i, zone in pairs(self.QuestsByZone) do
      print('-', i, zone[i])
      zone[id] = nil
    end
  end
  pin:Hide()
  pin:ClearAllPoints()
  tinsert(self.freePins, pin)
  print('|cFFFF4400Clearing out', pin:GetName(),id)
end

-- create of update quest pins for a map and its underlying zones
function WorldQuests:RefreshData (mapID)
  local print = wqprint
  mapID = mapID or WorldPlan.currentMapID
  if not mapID then
    -- info not available yet
    return
  end


    print('|cFF00FF88'..self:GetName()..':RefreshData()|r', 'map:', mapID, 'realMap:', GetCurrentMapAreaID())

  if mapID == BROKEN_ISLES_ID then
    self.isStale = false
    print('|cFF00FFFFContinent:|r', mapID, GetMapNameByID(mapID), superTrackedID)
    self.fullSearch = true
    for i = 1, MC_GetNumZones(mapID) do
      local submapID, name, depth = MC_GetZoneInfo(mapID, i)
      self:RefreshData(submapID)
    end
    self.fullSearch = nil
  elseif self.QuestsByZone[mapID] then
    local taskInfo = TQ_GetQuestsForPlayerByMapID(mapID, WorldPlan.currentMapID)
    local numQuests = 0
    if taskInfo and #taskInfo >= 1 then
      print('|cFF00FFFF  Zone:|r', mapID, GetMapNameByID(mapID), #taskInfo)
      wipe(self.QuestsByZone[mapID])
      ZoneInfo[mapID] = taskInfo
      qprint('|cFFFF4400START of', GetMapNameByID(mapID))
      for taskID, info in pairs(taskInfo) do
        local questID = info.questId
        info.mapID = mapID
        local questPOI = self:AcquirePin(questID, mapID)
        local hasUpdate, isPending = questPOI:RefreshData(info)
        --  WorldPlan:print('|cFF0088FF'..questPOI.title..'|r', hasUpdate)
        self.isStale = (self.isStale or hasUpdate)
        self.isPending = (self.isPending or isPending)
        numQuests = numQuests + 1
      end
      qprint('|cFFFF4400END of', GetMapNameByID(mapID))
    end
  end

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

end

function WorldQuests:Refresh(forced)
  local print = wqprint
  print('|cFF00FF88'..self:GetName()..':Refresh()|r')
  if not self:IsVisible() then
    self.isStale = true
    print('frame closed, do it later')
    return
  end

  self:Reset()
  self:UpdateAnchors()
  self:Cleanup ()
end

-- prepares elements for a map update
function WorldQuests:Reset ()
  local print = wqprint
  print('|cFF00FF88'..self:GetName()..':Reset()|r')
  for questID, pin in pairs(self.QuestsByID) do
    pin.used = nil
  end
end

-- update visibility states of all pins
function WorldQuests:UpdateAnchors (submapID)

  local print = wqprint
  local db = WorldPlan.db
  local mapFileName, textureHeight, textureWidth, isMicroDungeon, microDungeonMapName = GetMapInfo()
  if isMicroDungeon then
    return
  end

  local currentMap = GetCurrentMapAreaID()
  local submapID = submapID or currentMap

  if submapID == BROKEN_ISLES_ID and (not db.DisplayContinentPins) then
    print('not updating map for reasons')
    return
  end
  print('|cFF88FF00'..self:GetName()..':UpdateAnchors|r', submapID, GetMapNameByID(submapID), 'pin count:', numPins)
  local numZones = MC_GetNumZones(submapID)
  if numZones then
    for i = 1, numZones do
      local subMapID = MC_GetZoneInfo(submapID, i)
      self:UpdateAnchors(subMapID)
    end
  end
  local pins = self.QuestsByZone[submapID]

  if pins then
    local hostFrame = WorldMapPOIFrame
    local mapWidth, mapHeight = hostFrame:GetSize()
    for questID, pin in pairs(pins) do
      pin:IsShowable()
      if pin.used then
        pin.isStale = true
        pin:SetFrameLevel(PinBaseIndex+ numPins)
        print('level', PinBaseIndex+ numPins)
        pin:SetAnchor(_G.WorldMapPOIFrame, currentMap, mapWidth, mapHeight)
        numPins = numPins + 1
      end
    end
  end
end

-- shows, animates, or hides pins based on their current visibility flags
local debug_show = {}
local debug_animate = {}
local debug_hide = {}
function WorldQuests:Cleanup ()
  local print = wqprint
  local showQuestPOI = WorldPlan.db.EnablePins
  print('|cFFFFFF00'..self:GetName()..':Cleanup()|r')
  -- continent or zone sizing


  numPins = 0
  for questID, pin in pairs(self.QuestsByID) do
    pin:SetShown((showQuestPOI and pin.used))
  end
  self.isStale = nil
end

function WorldQuests:FilterCheckByID(questID)
  local pin = WorldQuests:GetPinByQuestID(questID)
  return pin:IsShowable()
end




function WorldPlanDataProvider:OnShow()
  assert(self.ticker == nil);
  self.ticker = C_Timer.NewTicker(10, function() self:RefreshAllData() end);
end
function WorldPlanDataProvider:OnHide()
  self.ticker:Cancel();
  self.ticker = nil;
end

function WorldPlanDataProvider:OnAdded(mapCanvas)
  self.activePins = {};
  self.owningMap = mapCanvas
end



function WorldPlanDataProvider:RefreshAllData()
  local print = mprint
  print('|cFFFF0088'..self.owningMap:GetName()..':RefreshAllData()|r')


  local pinsToRemove = {};
  for questId in pairs(self.activePins) do
    pinsToRemove[questId] = true;
  end

  SetMapZoom(8)

  local mapAreaID = self:GetMap():GetMapID();
  for zoneIndex = 1, C_MapCanvas.GetNumZones(mapAreaID) do
    local zoneMapID, zoneName, zoneDepth, left, right, top, bottom = C_MapCanvas.GetZoneInfo(mapAreaID, zoneIndex);
    print(zoneMapID, zoneName)
    if zoneDepth <= 1 then -- Exclude subzones
    local taskInfo = C_TaskQuest.GetQuestsForPlayerByMapID(zoneMapID, mapAreaID);

    if taskInfo then
      for i, info in ipairs(taskInfo) do
        if HaveQuestData(info.questId) then
          if QuestUtils_IsQuestWorldQuest(info.questId) then
            local pin = WorldPlanQuests:AcquirePin(info.questId, zoneMapID)
            pin:RefreshData(info)
            pin:IsShowable()
            if pin.used then
              print(i, pin.x, pin.y, pin.used, pin.isNew, pin.isStale, pin:IsShown(), pin:GetAlpha())
              pinsToRemove[info.questId] = nil;

              local frame = self.activePins[info.questId]
              if not frame then
                frame = self:GetMap():AcquirePin("WorldPlanPinContainer")
                frame:SetAlphaLimits(1, 0.7, 1)
                frame:SetScalingLimits(1, 3, 1.5);
                frame:SetFrameLevel(1000 + self:GetMap():GetNumActivePinsByTemplate("WorldPlanPinContainer"));
                frame:Show()
                self.activePins[info.questId] = frame
              end
              frame:SetPosition(info.x, info.y)
              frame.pin = pin

              pin.isStale = true
              pin:SetParent(frame)
              pin:ClearAllPoints()
              pin:SetPoint('CENTER', frame, 'CENTER')

            end
            pin:SetShown(pin.used)
          end
        end
      end
    end
    end
  end

  for questId in pairs(pinsToRemove) do
    self:GetMap():RemovePin(self.activePins[questId]);
    self.activePins[questId] = nil;
  end
  self:GetMap():RemoveAllPinsByTemplate("WorldQuestPinTemplate");

end

function WorldPlanDataPinMixin:OnShow()
  mprint('|cFFFFFF00'..tostring(self:GetName())..':OnShow()|r')
end

function WorldPlanDataPinMixin:OnMouseEnter ()
  self.pin:OnEnter()
end

function WorldPlanDataPinMixin:OnMouseLeave ()
  self.pin:OnLeave()
end

function WorldPlanDataPinMixin:RemoveAllData()
  wipe(self.activePins);
  self:GetMap():RemoveAllPinsByTemplate("WorldQuestPinTemplate");
end