view ClassPlan.lua @ 113:03e4a8b93012 v7.3.0-2

7.3 Updates - Added some map frame adjustments to keep the filter bar from obstructing edge of zone clicks KNOWN ISSUES: - Argus quests can only be filtered when viewing Argus maps - Main Argus map in the WorldMapFrame has misaligned pins; this is due to it using a different map texture from what is used in the teleporter map, so all the POI coordinates are based on that map in the client info
author Nenue
date Tue, 05 Sep 2017 03:14:34 -0400
parents b2be2de2b133
children a4dfdd4f1cf3
line wrap: on
line source
local _, db = ...
local wipe, tinsert, sort = table.wipe, tinsert, table.sort
local pairs, ipairs = pairs, ipairs
local floor, mod, time = floor, mod, time
local max, min = math.max, math.min
local GetTime = GetTime
local GI_currentTime = time()
local print = DEVIAN_WORKSPACE and function(...) print('ClassPlan', ...) end or nop

local CP_REPLACE_LANDINGPAGE = true
local CP_HEADER_SIZE = 24
db.ClassPlanDefaultType = {

  backgroundColor = {0, 0, 0, 0.5},
  textColor = {1,1,1,1}
}
db.ClassPlanTypes = setmetatable({}, {__index = db.ClassPlanDefaultType})
db.ClassPlanTypes.inProgress = {
  backgroundColor = {0, 0, 0, 0.5},
  textColor = {1,1,1}
}
db.ClassPlanTypes.shipmentsReady = {
  backgroundColor = {1, 1, 0, 0.25 },
  textColor = {1, 1, 0}
}
db.ClassPlanTypes.complete = {
  backgroundColor = {0, 1, 0, 0.25 },
  textColor = {0, 1, 0}
}

local GetTimeLeftString = function(timeLeft)
  local days = floor(timeLeft/(24*3600))
  local hours = floor(mod(timeLeft, (24*3600)) / 3600)
  local minutes = floor(mod(timeLeft, 3600) / 60)
  local seconds = mod(timeLeft, 60)
  if days >= 1 then
    return (days .. 'd' .. ' ') .. ((hours > 0) and (hours .. 'h') or '')
  elseif timeLeft < 60 then
    return (seconds .. ' sec')
  else
    return ((hours > 0) and (hours .. 'h') or '') .. ((minutes > 0) and (' ' ..minutes .. ' min') or '')
  end
end

ClassOrderPlanCore = {
  events = {},
  freeBlocks = {},
  characterButtons = {},
  blocks = {},
  sortedItems = {},
  timers = {},
  shipments = {},
  playerFirst = false,
  prototypes = {},
  Queued = {},
  Timers = {},
  ReportChunks = {},
}
ClassPlanHandlerBase = {
  numBlocks = 0,
  isStale = true,
  maxItems = 10
}
ClassPlanEntryBase = {}

local ClassPlan = ClassOrderPlanCore
local Embed = function(object, ...)
  for i = 1, select('#', ...) do
    local src = select(i, ...)
    for k,v in pairs(src) do
      if not object[k] then
        object[k] = v
      end
    end
  end
  return object
end

function ClassPlan:OnLoad ()
  self:RegisterEvent('PLAYER_LOGIN')
  self:RegisterEvent('ADDON_LOADED')
  self:RegisterEvent('PLAYER_REGEN_ENABLED')
  self:RegisterEvent('PLAYER_REGEN_DISABLED')
  self:RegisterEvent('GARRISON_SHOW_LANDING_PAGE')
  self:RegisterForDrag('LeftButton')
  self:EnableMouse(true)
  self:SetMovable(true)
  self:SetToplevel(true)

  SLASH_CLASSPLAN1 = "/classplan"
  SLASH_CLASSPLAN2 = "/cp"
  SlashCmdList.CLASSPLAN = function(args)
    self:Toggle()
  end

  local originalScript = GarrisonLandingPageMinimapButton:GetScript('OnClick')
  GarrisonLandingPageMinimapButton:SetScript("OnClick", function(minimap, button)
    if button == 'LeftButton' and (not IsShiftKeyDown()) then
      self:Toggle()
    else
      originalScript(minimap, button)
    end
  end)
  local OnStopped = function()
    print('Fade.OnStop()')
    self.isAnimating = nil
    self:SetShown(self.data.IsShown)
  end

  self.FadeOut:SetScript('OnStop', OnStopped)
  self.FadeOut:SetScript('OnFinished', function()
    print('FadeOut.OnFinished()')
    self.isAnimating = nil
    self:SetShown(false)
  end)
  self.FadeIn:SetScript('OnPlay', function()
    print('FadeIn.OnPlay()')
    self.isAnimating = true
    self.data.IsShown = true
  end)
  self.FadeIn:SetScript('OnStop', OnStopped)

  --hooksecurefunc(C_Garrison, 'RequestLandingPageShipmentInfo', function()
  --  WorldPlan:print("Requesting shipments data.")
  --end)
  C_Garrison.RequestLandingPageShipmentInfo();
  self.isStale = true
  UIPanelWindows[self:GetName()] =				{ area = "right",		pushable = 3,	whileDead = 1 };
  tinsert(UISpecialFrames, self:GetName())
end


function ClassPlan:GetCurrentProfile()
  WorldPlanData.OrderHall = WorldPlanData.OrderHall or {}
  local db = WorldPlanData.OrderHall
  self.data = db

  local characters = db.characters or {}
  db.characters = characters

  local name, realm = UnitName('player')
  realm  = realm or GetRealmName()

  local profileName = name .. '-' .. realm
  self.profile = characters[profileName] or {
    showItems = true
  }
  self.characters = characters
  characters[profileName] = self.profile

  local classColor = RAID_CLASS_COLORS[select(2, UnitClass('player'))]
  local className = UnitClass('player')
  print('|cFFFFFF00Loaded:|r', classColor.hex, className, profileName)
  self.profile.classColor = classColor
  self.profile.className = className
  self.profile.characterName = name
  self.profile.characterRealm = realm
  -- flip it on
  self.profile.showItems = true

  self.HeaderInset:SetHeight(CP_HEADER_SIZE)
  self.ClassStripe:SetColorTexture(classColor.r, classColor.g, classColor.b, 1)
  self.ClassStripe:SetPoint('TOPLEFT', self.HeaderInset, 'BOTTOMLEFT')

  self.maxItems = db.maxItems or self.maxItems

  return self.profile
end

function ClassPlan:AddHandler(frame)
  print('|cFF00FF00'..frame:GetName()..' loaded')
  for i, event in ipairs(frame.events) do
    print('|cFF00FF00  event', event)
    frame:RegisterEvent(event)
  end
  frame.sortedItems = {}
  for index, listKey in ipairs(frame.listKey) do
    frame.profile = self.profile
    frame.data = self.data
    self.profile[listKey] = self.profile[listKey] or {}
    local listTitle = frame.listTitle[index]
    setmetatable(self.profile[listKey], { __tostring = function() return listTitle end })
    frame.sortedItems[listKey] = {}
    frame.maxItems = self.maxItems
  end
  frame.owningFrame = self
  frame:SetList(1)
end

function ClassPlan:OnEvent (event, arg)
  print(event, arg)
  if event == 'PLAYER_REGEN_DISABLED' then
    if self:IsVisible() then
      self.combatHide = true
      self:SetShown(false)
    end

  elseif event == 'PLAYER_REGEN_ENABLED' then
    if self.combatHide == true then
      self.combatHide = nil
      self:SetShown(true)
    end
  elseif event == 'ADDON_LOADED' then
    if arg == 'Blizzard_GarrisonUI' then
      self:Reanchor()
    end
  elseif event == 'PLAYER_LOGIN' then
    if not self.initialized then
      self:Setup()
    end
  elseif event == 'GARRISON_SHOW_LANDING_PAGE' then
    self:RefreshData()
  end
end

function ClassPlan:Setup()
  if IsLoggedIn() then
    print('|cFFFFFF00'..self:GetName()..':Setup()|r')

    self:GetCurrentProfile()
    for _, handler in ipairs(self.Handlers) do
      self:AddHandler(handler)
      handler.initialized = true
    end
    self.initialized = true
    self:SetShown(self.data.IsShown)

    GarrisonMinimap_HidePulse(GarrisonLandingPageMinimapButton, 1) -- buildings
    GarrisonMinimap_HidePulse(GarrisonLandingPageMinimapButton, 5) -- missions
  end
end


--- Update space

local max = math.max
function ClassPlan:RefreshData()
  local detailsFailed
  for index, handler in pairs(self.Handlers) do
    print(' |cFF00FF00'..handler:GetName()..' data update|r')
    handler:RefreshData()
  end
  if detailsFailed then
    db.print('Unable to obtain player details. Trying again later.')
  else
    self.requestingData = nil
  end
end

function ClassPlan:Update()
  print('|cFF00FFFF'..self:GetName()..'Refresh()|r')

  self.currentHeight = 0
  for index, handler in ipairs(self.Handlers) do
    local itemsHeight = handler:UpdateItems()
    if itemsHeight then
      self.currentHeight = max(itemsHeight, self.currentHeight)
    end
  end

  local index = 1
  for key, info in pairs(self.data.characters) do
    print('cbutton', key)
    if self.data[key] then
      print('|cFFFF4400remove legacy value', key)
      self.data[key] = nil
    end


    local button = self.characterButtons[index]
    if not button then
      button = CreateFrame('Button', nil, self, 'ClassOrderPlanCharacterButton')
      button:SetID(index)
      self.characterButtons[index] = button

      if not self.lastButton then
        button:SetPoint('TOPLEFT', self.HeaderInset, 'TOPLEFT', 0, 0)
      else
        button:SetPoint('TOPLEFT', self.lastButton, 'TOPRIGHT', 2, 0)
      end
      self.lastButton = button
    end
    if not info.characterName then
      info.characterName, info.characterRealm = key:match("%(.+)%-(.+)^")
    end

    print(info.characterName)

    button:SetSize(CP_HEADER_SIZE, CP_HEADER_SIZE)
    button.profileKey = key
    button.className = info.className
    button.classColor = info.classColor
    button.characterName = info.characterName
    button.characterRealm = info.characterRealm
    button.showItems = info.showItems
    button.isMine = (info == self.profile)
    button:Update()
    button:Show()
    index = index + 1
  end

  self.HeaderInset:SetHeight(CP_HEADER_SIZE)
  self.HeaderInset:ClearAllPoints()
  self.HeaderInset:SetPoint('TOPLEFT' ,self, 'TOPLEFT')
  self.HeaderInset:SetPoint('RIGHT' ,self, 'RIGHT')
  self.ClassStripe:ClearAllPoints()
  self.ClassStripe:SetPoint('TOPLEFT', self.HeaderInset, 'BOTTOMLEFT', 0, 0)
  self.ClassStripe:SetPoint('RIGHT')
  self:Reanchor()
  self.isStale = nil
end


function ClassPlan:Toggle()
  print('Toggle()', self:IsVisible(), self:IsShown())
  if self:IsVisible() then
    print('fade out')
    self.data.IsShown = false
    self.FadeOut:Play()
  else
    print('fade in')
    self.data.IsShown = true
    self:SetShown(true)
    self.FadeIn:Play()
  end
end


function ClassPlan:OnMouseDown(button)
  print(button)
  if button == 'RightButton' then
    self:Toggle()
  end

end

function ClassPlan:OnUpdate()
  if self.requestingData then
    self:RefreshData()
  elseif self.isStale then
    -- shouldn't happen, usually
    self:Update()
  end
  if #self.ReportChunks >= 1 then
    db.print(table.concat(self.ReportChunks, ', '))
    wipe(self.ReportChunks)
  end

end

function ClassPlan:OnShow()
  print('|cFF00FFFFShow()')
  self.isStale = true
  self:Reanchor()
end

function ClassPlan:OnHide()
  print('|cFF00FFFFHide()')
  self.isAnimating = nil
end

function ClassPlan:Reanchor()
  if not (self.data.positionX and self.data.positionY) then
    self.data.positionX = 0
    self.data.positionY = -148
  end
  self:ClearAllPoints()
  self:SetPoint('TOP', self.data.positionX, self.data.positionY)
  self.currentHeight = 0
  for index, frame in ipairs(self.Handlers) do
    frame:Reanchor()
    local ListTab = frame.ListTab
    if ListTab then
      ListTab:ClearAllPoints()
      ListTab:SetPoint('TOPLEFT', frame, 'TOPLEFT', 0, 0)
      ListTab:SetPoint('BOTTOMRIGHT', frame, 'TOPRIGHT', 0, -CP_HEADER_SIZE)
      ListTab.Label:SetText(frame.listTitle[frame.currentListIndex])
      ListTab:Show()
      print(ListTab:GetSize())
    end
    self.currentHeight = max(self.currentHeight, frame.currentHeight or 0)
  end
  self:SetHeight(self.currentHeight + self.HeaderInset:GetHeight() + self.ClassStripe:GetHeight())

end

function ClassPlan:OnDragStart()
  self:StartMoving()
end
function ClassPlan:OnDragStop()
  self:StopMovingOrSizing()
  local x,y = self:GetCenter()
  if x and y then
    x = (x - GetScreenWidth()/2)
    y = (y - GetScreenHeight()/2) * -1
    self.data.positionX, self.data.positionY = x,y
    print('saving positions:', x, y)
  end
end

function ClassPlanHandlerBase:ScheduleUpdate(expires)
  -- value will occasionally lag so check here
  local duration = expires - time()
  self.Timers = self.Timers or {}
  if (duration > 0) and  (not self.Timers[expires]) then
    print('  adding timer at', expires, 'c', duration)
    self.Timers[expires] = true
    C_Timer.After(duration, function()
      self.isStale = true
      self:UpdateItems()
      self.Timers[expires] = nil
    end)
  end
end

function ClassPlanHandlerBase:SetList(index)
  local prevIndex = self.currentListIndex
  if not index then
    if self.currentListIndex == #self.listKey then
      index = 1
    else
      index = self.currentListIndex + 1
    end
  end

  print('|cFF0088FF'..self:GetName()..'|r:SetList()', index, self.listKey[index])
  self.currentListIndex = index
  self.activeKey = self.listKey[index]
  self.activeTitle = self.listTitle[index]

  self.ListTab.Label:SetText(self.listTitle[index])
  self.isStale = true

  if self.OnSetList then
    self:OnSetList(self.currentListIndex, prevIndex)
  end
end

function ClassPlanHandlerBase:OnLoad(...)
  print(self:GetName()..':OnLoad()', ...)
  self:EnableMouse(true)
end

function ClassPlanHandlerBase:OnMouseDown(button, down)
  print(self:GetName().. ':OnMouseDown()', button)
  ClassOrderPlan:OnMouseDown(button)
end

function ClassPlanHandlerBase:OnMouseWheel(delta)
  if IsControlKeyDown() then
    if delta > 0 then
      if self.maxItems < 30 then
        self.maxItems = self.maxItems + 1
      end

    else
      if self.maxItems >= 2 then
        self.maxItems = self.maxItems - 1
      end
    end
    if WorldPlanData.OrderHall then
      WorldPlanData.OrderHall.maxItems = self.maxItems
    end
  else
    self.scrollOffset = (self.scrollOffset or 0) - ((delta > 0) and 1 or -1)
  end
  self:UpdateItems()
end

function ClassPlanHandlerBase:RefreshData()
  print('|cFF0088FF'..self:GetName()..':RefreshData()')
  local activeKey = self.activeKey
  local detailsFailed
  self.truncatedItems = 0
  self.currentTime = time()
  if self:GetParent().profile then
    self:GetPlayerData()
  else
    detailsFailed  = true
  end
  for _, listKey in ipairs(self.listKey) do
    local sortedItems = self.sortedItems[listKey]
    wipe(sortedItems)
    for key, profile in pairs(self.data.characters) do
      local isMine = (profile == self.profile)
      print(key, listKey, isMine, profile.showItems)
      local profileList = profile[listKey]
      if profileList and #profileList >= 1 then
        local classColor = profile.classColor or RAID_CLASS_COLORS['HUNTER']
        if profile.showItems then
          for index, data in ipairs(profileList) do
            data.classColor = classColor
            data.profileKey = key
            data.isMine = isMine
            if self.OnGetItem then
              self:OnGetItem(data)
            end
            tinsert(sortedItems, data)
          end
        else
          self.truncatedItems = self.truncatedItems + 1
        end
      end
    end

    if self.SortHandler then
      sort(sortedItems, self.SortHandler)
    end
  end

  for k,v in pairs(self.sortedItems) do
    print('  ', k)
  end

  self.isStale = true
end

function ClassPlanHandlerBase:OnEvent(event, id)
  if (event == 'GARRISON_MISSION_LIST_UPDATE') then
    if (id == LE_FOLLOWER_TYPE_GARRISON_7_0) then
      print('|cFF00FF88'..self:GetName()..':OnEvent()|r', event, id)
      self:RefreshData()
    end
  else
    print('|cFF00FF88'..self:GetName()..':OnEvent()|r', event, id)
  end
end

function ClassPlanHandlerBase:OnUpdate()
  if self.isStale then
    print('|cFF00FF00'..self:GetName()..':OnUpdate()|r')

    self:UpdateItems()
  end
end

-- Stuff set on every list item
function ClassPlanHandlerBase:SetOwnerData (self, data)
  local name, realm = string.match(data.profileKey, "(.+)%-(.+)")
  local ownerText = '|c'.. data.classColor.colorStr .. name .. '|r'
  self.Owner:SetText(ownerText)
  self.Name:SetText(self.name)
  self.Name:SetTextColor(data.classColor.r, data.classColor.g, data.classColor.b)
end

function ClassPlanHandlerBase:Acquire(id)
end
function ClassPlanHandlerBase:FreeBlock (block)
end

function ClassPlanHandlerBase:UpdateItems()
  print('|cFF0088FF '..self:GetName()..':UpdateItems()|r', self.activeKey)
  self.MoreItemsUp:Hide()
  self.MoreItemsDown:Hide()
  local sortedItems = self.sortedItems[self.activeKey]
  local scrollOffset = self.scrollOffset or 0
  local numItems = #sortedItems
  if (not sortedItems[scrollOffset+1]) or (numItems <= self.maxItems) then
    scrollOffset = 0
  elseif (numItems > self.maxItems) and (scrollOffset > (numItems - self.maxItems)) then
    scrollOffset = (numItems - self.maxItems)
  end

  self.ListTab.Count:SetText(numItems)
  self.blocks = self.blocks or  {}
  local blocks = self.blocks
  local lastProfile
  local totalHeight = (self.ListTab:GetHeight() or 0)
  self.lastBlock = nil
  self.numActive = 0
  for i = 1, self.maxItems do
    local index = scrollOffset + i
    local data = sortedItems[index]
    if not data then
      print('|cFFFF4400end of data')
      break
    end

    local block = blocks[i]
    if not block then
      block = CreateFrame('Button', self:GetName()..'ListItem'..i , self, self.templateName)
      block.listType = self.activeKey

      block.doAnimation = true

      block.handler = self
      self.numBlocks = self.numBlocks + 1
      blocks[i] = block
    end
    block:SetID(index)
    self.numActive = self.numActive + 1

    if self.lastBlock then
      block:SetPoint('TOPLEFT', self.lastBlock, 'BOTTOMLEFT', 0, 0)
      --print('--', index, data.isComplete, data.missionEndTime, data.name)
    else
      block:SetPoint('TOPLEFT', self.ListTab, 'BOTTOMLEFT', 0, 0)
      --print('--top')
    end
    self.lastBlock = block

    totalHeight = totalHeight + block:GetHeight()
    block.lastProfile = lastProfile
    -- blot out arbitrary flags
    block.offerEndTime = nil
    block.isComplete = data.isComplete
    block.missionEndTime = nil
    block.creationTime = nil
    block.duration = nil
    block.throttle = 5

    for k,v in pairs(data) do
      if type(block[k]) ~= 'function' then
        block[k] = v
      end
    end

    block:Update()
    self:SetOwnerData(block, data)

    block:Show()
    lastProfile = data.profileKey
  end

  if self.numActive < numItems then
    if scrollOffset < (numItems - self.maxItems) then
      self.MoreItemsDown:Show()
    end
    if scrollOffset > 0 then
      self.MoreItemsUp:Show()
    end
  end

  for i = self.numActive + 1, self.numBlocks do
    if blocks[i] then
      blocks[i]:Hide()
    end
  end

  self:Reanchor()
  if totalHeight ~= self.currentHeight then
    self.currentHeight = totalHeight
    self:SetHeight(self.currentHeight)
    self:GetParent():Reanchor()
  end


  self.scrollOffset = scrollOffset
  self.isStale = nil
  return totalHeight
end

function ClassPlanEntryBase:OnAnimFinished()
end
function ClassPlanEntryBase:OnShow()

  print('|cFF44FF00'..self:GetName()..':OnShow()')
  if self.doAnimation then
    self.doAnimation = nil
    if not ClassOrderPlan.isAnimating then
      self.NewBlockFade:Play()
    end
  end
end

function ClassPlanEntryBase:SetTimeLeft(expires, duration)
  if not expires then
    return
  end

  -- calculate here since time isn't available
  local timeLeft = expires - time()
  --print(self:GetName(), timeLeft)
  if timeLeft < 0 then
    -- handle being complete
    if self.shipmentsReady and (self.shipmentsReady < self.shipmentsTotal) then
      self.TimeLeft:SetText('Ready')
    else
      self.TimeLeft:SetText('Complete!')
    end
  else
    self.TimeLeft:SetText(GetTimeLeftString(timeLeft))
    if duration then
      local progress = (duration - timeLeft) / duration
      local r = ((progress >= .5) and (progress/2)) or 1
      local g = ((progress <= .5) and (progress*2)) or 1
      self.ProgressBG:Show()
      self.ProgressBar:Show()
      self.ProgressBG:SetColorTexture(r,g,0,0.25)
      self.ProgressBar:SetColorTexture(r,g,0,0.5)
      self.ProgressBar:SetWidth(self.ProgressBG:GetWidth() * progress)
    else

      self.ProgressBG:Hide()
      self.ProgressBar:Hide()
    end
  end
end



ClassPlanHeaderMixin = {}
function ClassPlanHeaderMixin:OnLoad()
    self:EnableMouse((#self:GetParent().listKey > 1))
    self:RegisterForClicks('AnyUp')
  end
function ClassPlanHeaderMixin:OnClick ()
  local frame = self:GetParent()
  frame:SetList()
  if frame.OnHeaderClick then
    frame.OnHeaderClick(frame)
  end
end

ClassPlanCharacterButtonMixin = {
}
function ClassPlanCharacterButtonMixin:Update ()
  --print(CLASS_ICON_TCOORDS[self.className:upper()])
  if self.className and CLASS_ICON_TCOORDS[self.className:upper()] then
    self.Icon:SetTexCoord(unpack(CLASS_ICON_TCOORDS[self.className:upper()]))
  end
  self.Icon:SetDesaturated((not self.showItems))
  self.SelectGlow:SetShown(self.isMine)
end

function ClassPlanCharacterButtonMixin:OnEnter()
  if not self.profileKey then
    return
  end

  GameTooltip:SetOwner(self, 'ANCHOR_RIGHT')
  local info = ClassOrderPlan.data.characters[self.profileKey]
  GameTooltip:AddLine(self.characterName, self.classColor.r, self.classColor.g, self.classColor.b)
  local numItems = 0
  if info.missions then
    GameTooltip:AddLine(#info.missions .. ' mission'..((#info.missions == 1) and '' or 's')..' in progress')
  end
  if info.shipments then
    GameTooltip:AddLine(#info.shipments .. ' work order' .. ((#info.shipments == 1) and '' or 's'))
  end
  if info.available then
    GameTooltip:AddLine(#info.available .. ' mission'..((#info.available == 1) and '' or 's')..' available')
  end
  GameTooltip:Show()
end

function ClassPlanCharacterButtonMixin:OnLeave()
  if GameTooltip:IsOwned(self) then
    GameTooltip:Hide()
  end
end

function ClassPlanCharacterButtonMixin:OnClick(button, down)
  print('OnClick', self.profileKey)
  local clist = ClassOrderPlan.data.characters

  if button == 'RightButton' then
    for _, profile in pairs(clist) do
      profile.showItems = true
    end
  else
    if clist[self.profileKey].showItems then
      clist[self.profileKey].showItems = nil
    else
      clist[self.profileKey].showItems = true
    end
  end
  for i, handler in ipairs(ClassOrderPlan.Handlers) do
    handler.isStale = true
  end

  ClassOrderPlan:RefreshData()
  ClassOrderPlan:Update()
  print(clist[self.profileKey].showItems)
end