view ClassPlan.lua @ 32:e8679ecb48d8

ClassPlan: - Available missions are now recorded; the mission list can be toggled between in-progress and available by clicking the heading.
author Nenue
date Tue, 01 Nov 2016 10:48:50 -0400
parents 4a7e89bffbcb
children 26dfa661daa7
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 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 CG_GetBuildings = C_Garrison.GetBuildings
local CG_GetFollowerShipments = C_Garrison.GetFollowerShipments
local CG_GetLooseShipments = C_Garrison.GetLooseShipments
local CG_GetTalentTrees = C_Garrison.GetTalentTrees
local CG_GetCompleteTalent = C_Garrison.GetCompleteTalent
local CG_GetLandingPageShipmentInfo = C_Garrison.GetLandingPageShipmentInfo
local CG_GetLandingPageShipmentInfoByContainerID = C_Garrison.GetLandingPageShipmentInfoByContainerID

local CP_REPLACE_LANDINGPAGE = true
local CP_HEADER_SIZE = 24
local CP_BACKGROUND_COLOR = {
  inProgress = {0, 0, 0, 0.5},
  shipmentsReady = {0, 0, 0, 0.25},
  complete = {0.5, 0.5, 0.5, 0.5}
}

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 = {},
  blocks = {},
  sortedItems = {},
  timers = {},
  shipments = {},
  playerFirst = false,
  prototypes = {},
  Queued = {}
}
local MissionList = {
  templateName = 'ClassPlanMissionEntry',
  listKey = {'missions', 'available'},
  listTitle = {'In Progress', 'Available'},

  point = 'TOPLEFT',
  relativePoint ='TOPLEFT',
  events = {
    'GARRISON_MISSION_LIST_UPDATE',
    'GARRISON_LANDINGPAGE_SHIPMENTS'},
}
local ShipmentList = {
  templateName = 'ClassPlanShipmentEntry',
  listKey = {'shipments'},
  listTitle = {'Work Orders'},
  events = {
    'GARRISON_MISSION_LIST_UPDATE',
    'GARRISON_LANDINGPAGE_SHIPMENTS',
    'GARRISON_TALENT_UPDATE',
    "GARRISON_TALENT_COMPLETE",
    "GARRISON_SHIPMENT_RECEIVED",
    'GARRISON_FOLLOWER_LIST_UPDATE',
    'GARRISON_SHOW_LANDING_PAGE'},
}
local SharedHandlers = {
  numBlocks = 0,
  isStale = true,
}
local SharedEntry = {}
local ShipmentEntry = {}
local MissionEntry = {}

local ClassPlan = ClassOrderPlanCore

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


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

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 {}
  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.Background:SetColorTexture(classColor.r, classColor.g, classColor.b, 0.5)
  self.profile.classColor = classColor
  self.profile.className = className
  return self.profile
end

function ClassPlan:SetupHandler(handler)
  print('|cFF00FF00'..handler:GetName()..' loaded')
  for i, event in ipairs(handler.events) do
    print('|cFF00FF00  event', event)
    handler:RegisterEvent(event)
  end
  for index, listKey in ipairs(handler.listKey) do
    self.profile[listKey] = self.profile[listKey] or {}
    local listTitle = handler.listTitle[index]
    setmetatable(self.profile[listKey], { __tostring = listTitle })
  end
  handler:SetList(1)
  handler.sortedItems = {}
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
  end
end

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

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


--- Update space

  local max = math.max
  function ClassPlan:Update()
    print('|cFF00FFFFRefresh()|r')
    self.currentHeight = 0
    for index, handler in pairs(self.Handlers) do
      if handler.isStale then
        print('  |cFF00FF00'..index..' '..handler:GetName()..'|r')
        local sortedItems = handler.sortedItems
        local activeKey = handler.activeKey
        handler.profile = self.profile[handler.activeKey]

        wipe(sortedItems)
        handler:GetPlayerData(self.profile)
        for key, profile in pairs(self.data.characters) do
          print('profile', key, activeKey)
          local profileList = profile[activeKey]
          if profileList and #profileList >= 1 then
            local classColor = profile.classColor or defaultClassColor
            local isMine = (profile == self.profile)
            for index, data in ipairs(profileList) do
              data.classColor = classColor
              data.profileKey = key
              data.isMine = isMine
              if handler.OnGetItem then
                handler.OnGetItem(data)
              end
              tinsert(sortedItems, data)
            end
          end
        end

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

      end
      handler.isStale = nil
      local itemsHeight = handler:UpdateItems()
      self.currentHeight = max(itemsHeight, self.currentHeight)

    end

    self.isStale = nil
    self:Reanchor()
    self:SetHeight(self.currentHeight + CP_HEADER_SIZE)
  end


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

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

function ClassPlan:OnUpdate()
  if self.isStale then
    print('|cFFFF4400An illusion! What are you hiding?|r')
    self:Update()
  end
end

function ClassPlan:OnShow()
  print('|cFF00FFFFShow()')
  if self.isStale then
    self:Update()
  end
  self:Reanchor()
end

function ClassPlan:OnHide()
  print('|cFF00FFFFHide()')
end

function ClassPlan:Reanchor()
  self:ClearAllPoints()
  self:SetPoint('CENTER', self.data.positionX, self.data.positionY)

  for index, frame in ipairs(self.Handlers) do
    frame:Reanchor()
  end
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 SharedHandlers:SetList(index)
  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.currentListIndex = index
  self.activeKey = self.listKey[index]
  self.activeTitle = self.listTitle[index]

  self.isStale = true
end

function SharedHandlers:RequestData()
  print('|cFF0088FF'..self:GetName()..':RequestData()')
  self.isStale = true
end

function SharedHandlers:OnEvent(event, arg)
  if (event == 'GARRISON_MISSION_LIST_UPDATE') and (arg ~= LE_FOLLOWER_TYPE_GARRISON_7_0) then
    -- ignore non-OrderHall updates
    return
  end
  print('|cFF00FF88'..self:GetName()..':OnEvent()|r', event, arg)
  if self:IsVisible() then
    print('|cFF88FF00  frame visible; get busy')
    self:RequestData()
  else
    if not self.NextData then
      print('|cFF88FF00  setting timer')
      self.NextData = C_Timer.NewTimer(0.25, function()
        if self.initialized then
          self:RequestData()
          self.NextData:Cancel()
          self.NextData = nil
          print('|cFF88FF00'..self:GetName()..' clearing timer')
        end

      end)
    end
  end
end
function SharedHandlers:OnUpdate()
  if self.isStale then
    self:GetParent():Update()
  end
end


-- Stuff set on every list item
function SharedHandlers: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 SharedHandlers:UpdateItems()
  local sortedItems = self.sortedItems

  self.blocks = self.blocks or  {}
  local blocks = self.blocks

  local lastProfile
  local numItems = #sortedItems
  local totalHeight = 0
  self.lastBlock = nil
  self.numActive = 0
  for i, data in ipairs(sortedItems) do
    local block = blocks[i]
    if not block then
      block = CreateFrame('Button', nil, self, self.templateName)
      block:SetID(i)
      block.listType = self.activeKey
      block.handler = self
      self.numBlocks = self.numBlocks + 1
      blocks[i] = block
    end

    print('RefreshItem', block)
    self.numActive = self.numActive + 1

    if self.lastBlock then
      block:SetPoint('TOPLEFT', self.lastBlock, 'BOTTOMLEFT', 0, 0)
      print('--', i, data.isComplete, data.missionEndTime, data.name)
    else
      block:SetPoint('TOPLEFT', 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

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

  self:Reanchor()

  return totalHeight
end


function ShipmentList:Reanchor()
  print('|cFF00FFFF'..self:GetName()..':Reanchor|r')
  self:SetPoint('TOPLEFT', 0, -24)
  self:SetPoint('BOTTOMRIGHT', -ClassOrderPlan:GetWidth()/2, 0)
end

function MissionList:Reanchor()
  self:SetPoint('TOPRIGHT', 0, -24)
  self:SetPoint('BOTTOMLEFT', ClassOrderPlan:GetWidth()/2, 0)

  self.ListTab:ClearAllPoints()
  self.ListTab:SetPoint('TOPLEFT', self, 'TOPLEFT', 0, CP_HEADER_SIZE)
  self.ListTab:SetPoint('BOTTOMRIGHT', self, 'TOPRIGHT', 0, 0)
  self.ListTab.Label:SetText(self.listTitle[self.currentListIndex])
  self.ListTab:Show()
  print(self.ListTab:GetSize())
end

function MissionList:GetPlayerData (profile)
  wipe(profile.missions)
  wipe(profile.available)

  local items = C_Garrison.GetAvailableMissions(GetPrimaryGarrisonFollowerType(LE_GARRISON_TYPE_7_0));
  for i = 1, #items do
    if (not items[i].isBuilding and items[i].isZoneSupport) then
    else

      tinsert(profile.available, items[i])
    end
  end

  local items = C_Garrison.GetLandingPageItems(LE_GARRISON_TYPE_7_0)
  for index, data in ipairs(items) do
    print(' -',data.name, '|cFF00FF00'.. data.timeLeft .. '|r', date("%A %I:%m %p", data.missionEndTime))
    tinsert(profile.missions, data)
  end
  return true
end

do
  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
  function ShipmentList:GetPlayerData (profile)
    if not profile then
      return false
    end
    local profileList = profile.shipments
    wipe(ShipmentsInfo)

    local garrisonType = LE_GARRISON_TYPE_7_0
    local buildings = CG_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 = CG_GetLandingPageShipmentInfo(buildingID);
      AddShipmentInfo('Building', name, texture, shipmentCapacity, shipmentsReady, shipmentsTotal, creationTime, duration, timeleftString, itemName, itemIcon, itemQuality, itemID)
    end

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

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

    local talentTrees = CG_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 = CG_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

    wipe(profileList)
    for index, data in ipairs(ShipmentsInfo) do
      --DEFAULT_CHAT_FRAME:AddMessage(data.shipmentType ..' '.. tostring(data.name) ..' '.. tostring(data.creationTime) ..' '.. tostring(data.duration))
      tinsert(profileList, data)
    end
    self.isStale = true
    return true
  end
end
MissionList.OnGetItem = function(data)
  if data.missionEndTime and (data.missionEndTime < GI_currentTime) then
    data.isComplete = true
  end
end

MissionList.FreeBlock = function(self, block)
end

MissionList.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
      if b.isComplete ~= a.isComplete then
        return a.isComplete
      elseif b.isMine ~= a.isMine then
        return a.isMine
      elseif b.missionEndTime then
        return ( b.missionEndTime > a.missionEndTime)
      else
        return ( b.offerEndTime > a.offerEndTime)
      end

    --end
  --end
end


function MissionList:OnShow()
  print('|cFF00FF88'..self:GetName()..':OnShow()|r')
end

function ShipmentList:OnLoad()
  C_Garrison.RequestLandingPageShipmentInfo();
end
function  ShipmentList:OnShow()
  print('|cFF00FF88'..self:GetName()..':OnShow()|r')
  C_Garrison.RequestLandingPageShipmentInfo()
end

-- Update shipment flags data
local SetActualShipmentTime = function(self)

  if self.isComplete then
    print('|cFF00FF88isComplete '..self.name..'|r')
    return true
  end

  local timestamp = time()
  local timeLeft = self.creationTime + self.duration - timestamp
  local justFinished = false
  while (self.shipmentsReady < self.shipmentsTotal) and (timeLeft <= 0) do
    if not self.originalReady then
      self.originalReady = self.shipmentsReady
      self.originalCreationTime = self.creationTime
    end


    self.shipmentsReady = self.shipmentsReady + 1
    self.creationTime = self.creationTime + self.duration
    timeLeft = timeLeft + self.duration
    print('|cFF00FF88udpating '..self.name..'|r', 'timeLeft:', timeLeft, 'shipments:', self.shipmentsReady, self.shipmentsTotal)
  end

  if (timeLeft <= 0) and (not self.isBeingResearched) then
    self.isComplete = true
    self.isStale = true
  end

  return timeLeft
end

ShipmentList.OnGetItem = function(data)
  print('OnGetItem()')
  if data.shipmentsTotal then
    SetActualShipmentTime(data)
  end
end

ShipmentList.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


function MissionEntry:OnComplete()
  print('flagging complete', self.name)
  self:Update()
end

function MissionEntry:OnUpdate(sinceLast)
  self.throttle = (self.throttle or .5) + sinceLast
  if self.throttle < .5 then
    return
  else
    self.throttle = self.throttle - .5
  end
  if self.offerEndTime then
    local timeLeft = self.offerEndTime
    self.TimeLeft:SetText(GetTimeLeftString(timeLeft))
    self.TimeLeft:SetTextColor(1,1,1)
  elseif self.missionEndTime then

    if self.isComplete then
      return
    end
    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 = (time() - (self.missionEndTime - self.durationSeconds)) / self.durationSeconds
      local w = self.ProgressBG:GetWidth()
      if progress >= 1 then
        self.ProgressBar:SetWidth(w)
      else
        self.ProgressBar:SetWidth(progress * w)
        local r, g = 1, 0
        if progress >= 0.5 then
          g = 1
          r = 1-((progress-0.5)*2)
        else
          g = min(progress * 2, 1)
          r = 1
        end
        self.ProgressBar:SetColorTexture(r,g,0,.4)
        self.ProgressBG:SetColorTexture(r,g,0,0.125)
      end
      self.ProgressBG:Show()
      self.ProgressBar:Show()
    else
      self.ProgressBG:Hide()
      self.ProgressBar:Hide()
    end
  else
    self.TimeLeft:SetText(self.missionEndTime)
  end
end

function MissionEntry:OnLoad()
  print('|cFFFF4400',self:GetName() or tostring(self), 'onload')
  self.Count = self.Overlay.Count
  self.Name = self.Overlay.Name
  self.TimeLeft = self.Overlay.TimeLeft
  self.Owner = self.Overlay.Owner

  self.Icon:SetDesaturated(false)
  self.Done:Hide()
end

function MissionEntry:Update()
  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.TimeLeft:SetText('Complete!')
    self.Background:SetColorTexture(.25,.25,.25,1)
  else
    self.Background:SetColorTexture(0,0,0,0.5)
  end
end

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

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

function ShipmentEntry:OnLoad()
  MissionEntry.OnLoad(self)
end


function ShipmentEntry:Update()
  print('|cFF0088FF'.. self.name..'|r:Update()')
  self.Icon:SetTexture(self.icon)
  self.Count:SetText(self.shipmentsReady)
  self.Done:SetShown(self.shipmentsReady and (self.shipmentsReady >= 1))

  -- flag as complete

  local bgColor = CP_BACKGROUND_COLOR.inProgress
  if ( self.shipmentsReady >= self.shipmentsTotal ) and (not self.isBeingResearched) then
    self.Swipe:SetCooldownUNIX(0, 0);
    self.Done:Show();
    bgColor = CP_BACKGROUND_COLOR.complete
  else
    if (self.shipmentsReady >= 1) and (self.shipmentsReady < self.shipmentsTotal) then
      bgColor = CP_BACKGROUND_COLOR.shipmentsReady
    end
    self.Swipe:SetCooldownUNIX(self.creationTime or 0 , self.duration or 0);
  end
  self.Background:SetColorTexture(unpack(bgColor))

  SetActualShipmentTime(self)

  if self.originalReady then
    print('|cFF00FF88'..self.name..'|r', 'starting ready:', self.originalReady, 'starting time:', self.originalCreationTime)
  end
end

function ShipmentEntry:OnUpdate(sinceLast)
  self.throttle = (self.throttle or 1) + sinceLast
  if self.throttle >= 1 then
    self.throttle = self.throttle - 1
  else
    return
  end


  if (self.shipmentsReady and self.shipmentsTotal) and (self.shipmentsReady < self.shipmentsTotal) then
    local timeLeft = SetActualShipmentTime(self)

    if self.isComplete then
      self.TimeLeft:SetText('Complete!')
      self.TimeLeft:SetTextColor(0,0.5,1)
    elseif 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

  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

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

    local r, g = 1, 0
    if progress >= 0.5 then
      g = 1
      r = 1-((progress-0.5)*2)
    else
      g = min(progress * 2, 1)
      r = 1
    end
    self.ProgressBar:SetColorTexture(r, g, 0, .4)
    self.ProgressBG:SetColorTexture(r, g, 0, .125)
    self.ProgressBG:Show()
    self.ProgressBar:Show()
  else
    self.ProgressBG:Hide()
    self.ProgressBar:Hide()
  end
end

function ShipmentEntry: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 ShipmentEntry:OnLeave()
  if GameTooltip:IsOwned(self) then
    GameTooltip:Hide()
  end
end

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

ClassPlanMissionHandler = Mixin(MissionList, SharedHandlers)
ClassPlanShipmentHandler = Mixin(ShipmentList, SharedHandlers)
ClassPlanMissionEntryMixin = Mixin(MissionEntry, SharedEntry)
ClassPlanShipmentEntryMixin = Mixin(ShipmentEntry,SharedEntry)

ClassPlanHeaderMixin = {
  OnClick = function(self)
    self:GetParent():SetList()
    self:GetParent().isStale = true
    ClassOrderPlan:Update()
  end
}