view ClassPlan.lua @ 78:fadf00b437fb v1.4.1

- Fixed pins not being removed upon completion - Fixed animation stuttering
author Nenue
date Mon, 10 Apr 2017 13:14:05 -0400
parents 79e5e96e5f18
children b29b35cb8539
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 '')
  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: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)
  self.FadeOut:SetScript('OnFinished', function()
    self:Hide()
    self.data.IsShown = nil
    self.isAnimating = nil
  end)
  self.FadeIn:SetScript('OnPlay', function()
    self.isAnimating = true
    self:SetShown(true)
  end)

  --hooksecurefunc(C_Garrison, 'RequestLandingPageShipmentInfo', function()
  --  WorldPlan:print("Requesting shipments data.")
  --end)
  C_Garrison.RequestLandingPageShipmentInfo();
  self.isStale = true
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')

  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] = {}

  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()
  if self:IsShown() then
    self.FadeOut:Play()
  else
    self.data.IsShown = true
    self.FadeIn:Play()
  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()')
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:OnMouseWheel(delta)
  self.scrollOffset = (self.scrollOffset or 0) - ((delta > 0) and 1 or -1)
  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.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)
  self.ProgressBG:Hide()
  self.ProgressBar:Hide()
  if not expires then
    return
  end

  -- calculate here since time isn't available
  local timeLeft = expires - time()
  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))
  end

  if (timeLeft > 0) and 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)
  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