view ClassPlan.lua @ 12:3396a1171081

Added tag v1.0-rc2 for changeset a42820c45da9
author Nenue
date Sun, 23 Oct 2016 17:22:21 -0400
parents a2b623043970
children 08b03bcdfeac
line wrap: on
line source
local wipe, tinsert, sort = table.wipe, tinsert, table.sort
local pairs, ipairs = pairs, ipairs
local floor, mod, time = floor, mod, time
local GetTime = GetTime
local GI_currentTime = time()

local BOUND_FRAMES = {}
local blockTemplate = {
  point = 'TOPLEFT',
  relativePoint ='TOPLEFT',
}

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

ClassOrderPlanCore = {
  events = {},
  freeBlocks = {},
  blocks = {},
  sortedItems = {},
  timers = {},
  shipments = {},
  playerFirst = false,
  prototypes = {}
}

ClassPlanMissionMixin = {
  templateName = 'ClassPlanMissionEntry',
  events = {'GARRISON_MISSION_LIST_UPDATE', 'GARRISON_MISSION_STARTED', 'GARRISON_MISSION_FINISHED', 'GARRISON_LANDINGPAGE_SHIPMENTS'},}
ClassPlanShipmentMixin = {
  templateName = 'ClassPlanShipmentEntry',
  parent = false,
  point = 'TOPRIGHT',
  relativePoint ='TOPRIGHT',
  events = {'GARRISON_LANDINGPAGE_SHIPMENTS', 'GARRISON_TALENT_UPDATE', "GARRISON_TALENT_COMPLETE", "GARRISON_SHIPMENT_RECEIVED"},
}
setmetatable(ClassPlanShipmentMixin, {__index = ClassPlanMissionMixin})
local core, MissionsHandler, ShipmentsHandler = ClassOrderPlanCore, ClassPlanMissionMixin, ClassPlanShipmentMixin
local print = DEVIAN_WORKSPACE and function(...) print('ClassPlan', ...) end or nop

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

MissionsHandler.GetPlayerData = function(self)
  if not self.profile then
    return
  end
  local items = C_Garrison.GetLandingPageItems(LE_GARRISON_TYPE_7_0)
  print(#items)

  wipe(self.profile.missions)
  for index, data in ipairs(items) do
    print('  ',data.name, '|cFF00FF00'.. data.timeLeft .. '|r', date("%A %I:%m %p", data.missionEndTime))
    tinsert(self.profile.missions, data)
  end
  print('items update pending')
  self.isStale = true
end

MissionsHandler.OnGetItem = function(data)
  if data.missionEndTime < GI_currentTime then
    data.isComplete = true
  end
end

MissionsHandler.FreeBlock = function(self, block)
end

MissionsHandler.SortHandler = function (a,b)
  local result = false
  --if not a or not b then
  --  return true
  --else
    --if (a.isMine ~= b.isMine) then
    --  result = a.isMine
    --else
      --if (not b.missionEndTime) or (not a.missionEndTime) then
      --  print('missing article', b.missionEndTime, a.missionEndTime)
      --end
      return ( b.missionEndTime > a.missionEndTime)
    --end
  --end
end


ShipmentsHandler.OnGetItem = function(data)
  if data.shipmentsTotal then
    local timeLeft = data.creationTime + data.duration - GI_currentTime
    if (timeLeft <= 0) and (data.shipmentsReady < data.shipmentsTotal) then
      local numOrders = min(-1*floor(timeLeft/data.duration), (data.shipmentsTotal - data.shipmentsReady))

      if not data.originalCreationTime then
        data.originalCreationTime = data.creationTime
        data.originalShipmentsReady = data.shipmentsReady
      end

      data.creationTime = data.creationTime + numOrders*data.duration
      data.shipmentsReady = data.shipmentsReady + numOrders
      print(data.profileKey, 'shipment "'.. data.name..'" reconciling', numOrders, 'lapsed orders. -->', data.creationTime, data.shipmentsReady)
    end
  end
end

ShipmentsHandler.SortHandler = function(a, b)
  if b.isComplete ~= a.isComplete then
    return a.isComplete and true or false
  elseif a.shipmentsReady or b.shipmentsReady then
    return (a.shipmentsReady or 0) > (b.shipmentsReady or 0)
  else
    return (a.creationTime) < (b.creationTime)
  end
end

local ShipmentsInfo = {}
local AddShipmentInfo = function(shipmentType, name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString, itemName, itemIcon, itemQuality, itemID, followerID)
  -- early login queries may return empty tables, causing the sorter to compare nil
  if not creationTime then
    return
  end
  --print(shipmentType, name, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString)
  tinsert(ShipmentsInfo,
    {
      shipmentType = shipmentType,
      name = name,
      icon = texture,
      shipmentCapacity = shipmentCapacity,
      shipmentsReady = shipmentsReady,
      shipmentsTotal = shipmentsTotal,
      creationTime = creationTime,
      duration = duration,
      timeleftString = timeleftString,
      itemName = itemName,
      itemIcon = itemIcon,
      itemQuality = itemQuality,
      itemID = itemID,
      followerID = followerID,
    })
end
ShipmentsHandler.GetPlayerData = function (self)
  if not self.profile then
    return
  end
  wipe(ShipmentsInfo)


  local garrisonType = LE_GARRISON_TYPE_7_0
  local buildings = C_Garrison.GetBuildings(garrisonType);
  local shipmentIndex = 0
  --print('Buildings:')
  for i = 1, #buildings do
    local name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString, itemName, itemIcon, itemQuality, itemID = C_Garrison.GetLandingPageShipmentInfo(buildingID);
    AddShipmentInfo('Building', name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString, itemName, itemIcon, itemQuality, itemID)
  end

  --print('Follower:')
  local followerShipments = C_Garrison.GetFollowerShipments(garrisonType);
  for i = 1, #followerShipments do
    local name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString, _, _, _, _, followerID = C_Garrison.GetLandingPageShipmentInfoByContainerID(followerShipments[i]);
    AddShipmentInfo('Follower', name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString, nil, nil, nil, nil, followerID)
  end

  --print('Loose:')
  local looseShipments = C_Garrison.GetLooseShipments(garrisonType)
  for i = 1, #looseShipments do
    local name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString = C_Garrison.GetLandingPageShipmentInfoByContainerID(looseShipments[i]);
    AddShipmentInfo('Misc', name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString)
  end

  local talentTrees = C_Garrison.GetTalentTrees(garrisonType, select(3, UnitClass("player")));
  -- this is a talent that has completed, but has not been seen in the talent UI yet.
  local completeTalentID = C_Garrison.GetCompleteTalent(garrisonType);
  --print('Talents:')
  if (talentTrees) then
    for treeIndex, tree in ipairs(talentTrees) do
      for talentIndex, talent in ipairs(tree) do
        local showTalent = false;
        if (talent.isBeingResearched) or (talent.id == completeTalentID) then
          AddShipmentInfo('Talent', talent.name, talent.icon, 1, (talent.isBeingResearched and 0 or 1), 1, talent.researchStartTime, talent.researchDuration, talent.timeleftString)
        end
      end
    end
  end

  self.profile.shipments = self.profile.shipments or {}
  wipe(self.profile.shipments)
  for index, data in ipairs(ShipmentsInfo) do
    --DEFAULT_CHAT_FRAME:AddMessage(data.shipmentType ..' '.. tostring(data.name) ..' '.. tostring(data.creationTime) ..' '.. tostring(data.duration))
    tinsert(self.profile.shipments, data)
  end
  self.isStale = true

end

function core:OnLoad ()
  self:RegisterEvent('PLAYER_LOGIN')
  self:RegisterEvent('ADDON_LOADED')
  self:RegisterEvent('PLAYER_REGEN_ENABLED')
  self:RegisterEvent('PLAYER_REGEN_DISABLED')
  -- Blizzard_GarrisonUI already fires a shipment data request for GARRISON_SHIPMENT_RECEIVED; this is unlikely to


  self:AddHandler('missions', MissionsHandler)
  self:AddHandler('shipments', ShipmentsHandler)


  self:Reanchor()
end
local parentFrames = {'VeneerWorldState', 'OrderHallCommandBar'}
function core:Reanchor()


  self:ClearAllPoints()

  local anchorTo = 'TOP'
  self.anchorParent = UIParent
  for i, name in ipairs(parentFrames) do
    local frame = _G[name]
    if frame then
      if not BOUND_FRAMES[frame] then
        BOUND_FRAMES[frame] = {visible = (frame:IsVisible() and frame:IsShown())}
        hooksecurefunc(frame, 'Show', function()
          BOUND_FRAMES[frame].visible = true
          print(frame:GetName(), 'Show', 'reanchor trigger')
          self:Reanchor()
        end)
        hooksecurefunc(frame, 'Hide', function()
          BOUND_FRAMES[frame].visible = nil
          print(frame:GetName(), 'Hide', 'reanchor trigger')
          self:Reanchor()
        end)
      end
      print('f:', frame:GetName(), BOUND_FRAMES[frame].visible)
      if BOUND_FRAMES[frame].visible then
        self.anchorParent = frame
        anchorTo = 'BOTTOM'
        break
      end
    end
  end
  print('|cFFFF8800Using ' .. tostring(self.anchorParent:GetName()) .. '-'..anchorTo..' as anchor point')

  if self:IsShown() then
    ClassPlanButton.Background:Show()
    ClassPlanButton:SetWidth(600)
  else
    ClassPlanButton.Background:Hide()
    ClassPlanButton:SetWidth(200)
  end

  self:SetPoint('TOP', ClassPlanButton, 'BOTTOM', 0, 0)
  ClassPlanButton:ClearAllPoints()
  ClassPlanButton:SetPoint('TOP', self.anchorParent, anchorTo, 0, 0)

  print(ClassPlanButton:GetPoint(1))
end

function core:AddHandler(name, prototype)
  self.prototypes[name] = setmetatable(prototype, {
    __index = blockTemplate,
    __tostring = function() return name end
    })

  for i, event in ipairs(prototype.events) do
    if not self.events[event] then
      self:RegisterEvent(event)
      self.events[event] = {}
      print('|cFF00FF00registering', event)
    end

    prototype.numBlocks = 0
    if not self.events[event][name] then
      print('adding', name, 'to', event)
      self.events[event][name] = prototype.GetPlayerData
    end
  end
  self.sortedItems[name] = {}
end

function core:Setup()
  if IsLoggedIn() then
    WorldPlanData.OrderHall = WorldPlanData.OrderHall or {}
    self.data = WorldPlanData.OrderHall
    self.data.characters = self.data.characters or {}

    local name, realm = UnitName('player')
    realm  = realm or GetRealmName()
    self.profileKey = name .. '-' .. realm
    if not self.data.characters[self.profileKey] then
      self.data.characters[self.profileKey] = {}
    end
    self.profile = self.data.characters[self.profileKey]

    self.profile.shipments = self.profile.shipments or {}
    self.profile.missions = self.profile.missions or {}
    self.profile.classColor = RAID_CLASS_COLORS[select(2, UnitClass('player'))]

    if self.data.IsShown then
      self:Show()
    end
    self.initialized = true
  end
end

function core:OnEvent (event, ...)
  print(event)
  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 == 'PLAYER_LOGIN' then
    if not self.initialized then
      self:Setup()
    end
  elseif self.initialized and self.events[event] then
    local numCalls = 0
    for ptype, eventFunc in pairs(self.events[event]) do
      numCalls = numCalls + 1
      print('|cFF88FF00' .. tostring(ptype) .. '|r:GetPlayerData() --', numCalls)
      eventFunc(self, event)
    end


    if self:IsVisible() then
      self:Refresh()
    end
  end
end

function core:UpdateNotifications()
end


local SetOwnerData = function(self, data)
  local name, realm = string.match(data.profileKey, "(.+)%-(.+)")
  local ownerText = '|c'.. data.classColor.colorStr .. name .. '|r'
  --if realm ~= GI_currentRealm then
    --ownerText = ownerText .. ' (' .. realm .. ')'
  --end
  self.Owner:SetText(ownerText)
  self.Name:SetTextColor(data.classColor.r, data.classColor.g, data.classColor.b)

  if self.isComplete then
    self.TimeLeft:SetText('Complete!')
    self.Background:SetColorTexture(.25,.25,.25,1)
  else
    self.Background:SetColorTexture(0,0,0,0.5)
  end

end

function core:RefreshItems(configKey, prototype)
  local sortedItems = self.sortedItems[configKey]

  self.blocks[configKey] = self.blocks[configKey] or {}
  local blocks = self.blocks[configKey]

  local lastProfile
  local numItems = #sortedItems
  local totalHeight = 0
  for i, data in ipairs(sortedItems) do
    local block = blocks[i]

    if not block then
      block = CreateFrame('Button', nil, self, prototype.templateName)
      block:SetID(i)
      block.handler = prototype
      prototype.numBlocks = prototype.numBlocks + 1

      if prototype.lastBlock then
        block:SetPoint('TOPLEFT', prototype.lastBlock, 'BOTTOMLEFT', 0, 0)
      else
        block:SetPoint(prototype.point, self[prototype.parent] or self, prototype.relativePoint, 0, 0)
      end
      prototype.lastBlock = block
      blocks[i] = block
    end

    totalHeight = totalHeight + block:GetHeight()
    block.lastProfile = lastProfile
    for k,v in pairs(data) do
      if type(block[k]) ~= 'function' then
        block[k] = v
      end
    end
    block:Refresh(data)
    print(block.isComplete, block.missionEndTime, block.name)
    SetOwnerData(block, data)

    block:Show()
    lastProfile = data.profileKey
  end

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

  return totalHeight
end
local max = math.max
function core:Refresh()
  if self.isStale then
    self:SortItems()
  end
  self.isStale = nil

  self.currentHeight = 0
  for name, info in pairs(self.prototypes) do
    local itemsHeight = self:RefreshItems(name, info)
    self.currentHeight = max(itemsHeight, self.currentHeight)
  end

  self:Reanchor()
  self:SetHeight(self.currentHeight)
end

function core:Toggle()
  if self:IsShown() then
    self:Hide()
  else
    self:Show()
  end

  if self.data then
    self.data.IsShown = self:IsShown()
  end
end

function core:OnUpdate()
  if self.fadeTimer and self.fadeTimer < GetTime() then
    self:Hide()
  end
end

function core:OnShow()
  if self.isStale then
    print('updating items on show')
    self:Refresh()
  end
  -- grab this at least once
  C_Garrison.RequestLandingPageShipmentInfo();
  ClassPlanButton.Background:Show()
  ClassPlanButton.Grip:SetShown(true)
end
function core:OnHide()
  ClassPlanButton.Background:Hide()
  ClassPlanButton.Grip:SetShown(false)
end

local GI_profileKey, GI_profile, GI_isMine
local defaultClassColor = {r = 0.7, g = 0.7, b =0.7, colorStr = "ffffffff"}
local DoItemList = function (source, dest, onGetItem)
  if not source then
    return
  end
  local numItems = 0
  for index, data in ipairs(source) do
    data.classColor = GI_profile.classColor or defaultClassColor
    data.profileKey = GI_profileKey
    data.isMine = GI_isMine
    if onGetItem then
      onGetItem(data)
    end
    numItems = numItems + 1
    tinsert(dest, data)
  end
  return numItems
end

function core:SortItems()
  print('|cFF0088FFSortItems()|r')
  GI_currentTime = time()

  for key, sortedItems in pairs(self.sortedItems) do
    wipe(sortedItems)
    local ptype = self.prototypes[key]
    --print(  'object:', ptype)
    for name, profile in pairs(self.data.characters) do
      GI_profileKey = name
      GI_profile = profile
      GI_isMine = (profile == self.profile)
      local results = DoItemList(profile[key], sortedItems, ptype.OnGetItem)
      --print(' - ', name, results, 'items')
    end

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

function MissionsHandler:OnComplete()
  print('flagging complete', self.name)
  self:Refresh()
end

function MissionsHandler:OnUpdate(sinceLast)
  if self.isComplete then
    return
  end
  self.throttle = (self.throttle or .5) + sinceLast
  if self.throttle >= .5 then
    self.throttle = self.throttle - .5
  else
    return
  end


  if self.missionEndTime then
    local timeLeft = self.missionEndTime - time()
    if timeLeft < 0 then
      self:OnComplete()
    else
      self.TimeLeft:SetText(GetTimeLeftString(timeLeft))

      if timeLeft > 3600 then
        self.TimeLeft:SetTextColor(1,1,1)
      else
        self.TimeLeft:SetTextColor(1,1,0)
      end
    end

    if not self.isComplete then
      local progress = (self.missionEndTime - time()) / self.durationSeconds
      local w = self.ProgressBG:GetWidth()
      if progress >= 1 then
        self.ProgressBar:SetWidth(w)
      else
        self.ProgressBar:SetWidth(w - (progress * w))
      end

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

  else
    self.TimeLeft:SetText(self.missionEndTime)
  end
end

function MissionsHandler:Refresh()


  local r,g,b = 1, 1, 1
  if self.isRare then
    r,g,b = 0.1, 0.4, 1
    self.IconBorder:SetVertexColor(r, g, b, 1)
  end


  --self.missionData = data
  self.Name:SetText(self.name)

  if #self.rewards >= 1 then
    self.Icon:SetTexture(self.rewards[1].icon or GetItemIcon(self.rewards[1].itemID))
    self.rewardInfo = self.rewards[1]
  else
    self.Icon:SetAtlas(self.typeAtlas, false)
  end

  if self.isComplete then
    self.Done:Show()
  else
    self.Done:Hide()
  end

end


function MissionsHandler:OnEnter()
  if self.rewardInfo and self.rewardInfo.itemID then
    GameTooltip:SetOwner(self, 'ANCHOR_LEFT')
    GameTooltip:SetItemByID(self.rewardInfo.itemID)
    GameTooltip:Show()
  end
end
function MissionsHandler:OnLeave()
  if GameTooltip:IsOwned(self) then
    GameTooltip:Hide()
  end
end



function ShipmentsHandler:Refresh()

  --[[
  self.icon = data.icon
  self.shipmentCapacity = data.shipmentCapacity
  self.shipmentsReady = data.shipmentsReady
  self.shipmentsTotal = data.shipmentsTotal
  self.creationTime = data.creationTime
  self.duration = data.duration
  self.itemID = data.itemID
  self.itemQuality = data.itemQuality
      icon = texture,
      shipmentCapacity = shipmentCapacity,
      shipmentsReady = shipmentsReady,
      shipmentsTotal = shipmentsTotal,
      creationTime = creationTime,
      duration = duration,
      timeleftString = timeleftString,
      itemName = itemName,
      itemIcon = itemIcon,
      itemQuality = itemQuality,
      itemID = itemID

  --]]

  self.Icon:SetTexture(self.icon)

  self.Name:SetText(self.name)
  self.Count:SetText(self.shipmentsReady)
  self.Done:SetShown(self.shipmentsReady and (self.shipmentsReady >= 1))


  -- flag as complete
  if ( self.shipmentsReady >= self.shipmentsTotal ) and (not self.isBeingResearched) then
    self.Swipe:SetCooldownUNIX(0, 0);
    self.Done:Show();
    self.isComplete = true
  else
    self.Swipe:SetCooldownUNIX(self.creationTime or 0 , self.duration or 0);
  end

  local hasPickups = (self.isComplete or (self.shipmentsTotal and (self.shipmentsReady > 0)))
  self.Background:SetAlpha(hasPickups and 1 or 0.1)
end
local time = time
function ShipmentsHandler:OnUpdate()

  if (self.shipmentsReady and self.shipmentsTotal) and (self.shipmentsReady < self.shipmentsTotal) then
    local timeLeft = self.creationTime + self.duration - time()
    if self.shipmentsReady >= 1 then
      self.TimeLeft:SetText(GetTimeLeftString(timeLeft))
      self.TimeLeft:SetTextColor(0,1,0)
    else
      self.TimeLeft:SetText(GetTimeLeftString(timeLeft))
      self.TimeLeft:SetTextColor(1,1,1)
    end
    if (timeLeft < 0) then
      if self.shipmentsReady < self.shipmentsTotal then
        self.shipmentsReady = self.shipmentsReady + 1
        self.creationTime = self.creationTime + self.duration
        -- text will be set on next update
      end
    end

  elseif self.isBeingResearched then
    self.TimeLeft:SetText(GetTimeLeftString(self.researchStartTime + self.researchDuration - time()))
    self.TimeLeft:SetTextColor(1,1,0)
  else
    self.TimeLeft:SetText('Complete!')
    self.TimeLeft:SetTextColor(0,1,0)
  end

end

function ShipmentsHandler:OnEnter()

  if ( self.shipmentsReady and self.shipmentsTotal ) then
    GameTooltip:SetOwner(self, 'ANCHOR_LEFT')

    GameTooltip:AddLine(self.Owner:GetText(), self.Owner:GetTextColor())
    GameTooltip:AddLine(self.shipmentType)
    GameTooltip:AddLine(self.shipmentsReady .. ' of '.. self.shipmentsTotal)
    GameTooltip:Show()
  end
end

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

function ShipmentsHandler:OnClick(button)
  if button == 'RightButton' then
    self.handler:FreeBlock(self)
  end
end