view ClassPlan.lua @ 3:c006ce87a147

prototype structure
author Nenue
date Sat, 15 Oct 2016 09:54:56 -0400
parents b8a19781f79b
children 4c7e9efec4b5
line wrap: on
line source
local wipe = table.wipe
local pairs, ipairs = pairs, ipairs
local GetTime = GetTime
local GI_currentTime = time()


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 = {}
}
ClassPlanBlockMixin = {
  templateName = 'ClassPlanBlock',
  events = {'GARRISON_MISSION_LIST_UPDATE', 'GARRISON_MISSION_FINISHED', 'GARRISON_MISSION_FINISHED'},}
ClassPlanShipmentMixin = {
  templateName = 'ClassPlanShipment',
  parent = false,
  point = 'TOPRIGHT',
  relativePoint ='TOPRIGHT',
  events = {'GARRISON_LANDINGPAGE_SHIPMENTS', 'GARRISON_TALENT_UPDATE', "GARRISON_TALENT_COMPLETE", 'GARRISON_TALENT_COMPLETE', 'GARRISON_SHIPMENT_RECEIVED', "GARRISON_SHIPMENT_RECEIVED"},
}
setmetatable(ClassPlanShipmentMixin, {__index = ClassPlanBlockMixin})
local core, MissionsHandler, ShipmentsHandler = ClassOrderPlanCore, ClassPlanBlockMixin, ClassPlanShipmentMixin
local print = DEVIAN_WORKSPACE and function(...) print('ClassPlan', ...) end or nop



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

  if #self.items >= 1 then
    wipe(self.profile.missions)
    for index, data in ipairs(self.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

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

MissionsHandler.SortHandler = function (a,b)
  local result = false
  if not a or not b then
    result = 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
      result = ( b.missionEndTime > a.missionEndTime)
    end
  end
  return result
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 = -1*floor(timeLeft/data.duration)

      data.originalCreationTime = data.creationTime
      data.originalShipmentsReady = data.shipmentsReady

      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
  end
  return (a.creationTime) < (b.creationTime)
end

ShipmentsHandler.GetPlayerData = function (self)
  if not self.profile then
    return
  end
  wipe(self.shipments)


  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);
    tinsert(self.shipments,
      {
        shipmentType = 'Work Order',
        name = name,
        icon = texture,
        shipmentCapacity = shipmentCapacity,
        shipmentsReady = shipmentsReady,
        shipmentsTotal = shipmentsTotal,
        creationTime = creationTime,
        duration = duration,
        timeleftString = timeleftString,
        itemName = itemName,
        itemIcon = itemIcon,
        itemQuality = itemQuality,
        itemID = 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]);
    tinsert(self.shipments,
      {
        shipmentType = '',
        name = name,
        icon = texture,
        shipmentCapacity = shipmentCapacity,
        shipmentsReady = shipmentsReady,
        shipmentsTotal = shipmentsTotal,
        creationTime = creationTime,
        duration = duration,
        timeleftString = timeleftString,
        followerID = 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]);
    tinsert(self.shipments,
      {
        shipmentType = '',
        name = name,
        icon = texture,
        shipmentCapacity = shipmentCapacity,
        shipmentsReady = shipmentsReady,
        shipmentsTotal = shipmentsTotal,
        creationTime = creationTime,
        duration = duration,
        timeleftString = 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) then
          showTalent = true;
        end
        if (talent.id == completeTalentID) then
          showTalent = true;
        end
        if (showTalent) then
          print(talent.name)
          talent.creationTime = talent.researchStartTime
          talent.duration = talent.researchDuration
          talent.shipmentType = 'Talent: '
          tinsert(self.shipments, talent)
        end
      end
    end
  end

  self.profile.shipments = self.profile.shipments or {}
  if #self.shipments >= 1 then


    wipe(self.profile.shipments)
    for index, data in ipairs(self.shipments) do
      print(' ', data.shipmentType .. data.name, data.creationTime, data.duration)
      tinsert(self.profile.shipments, data)
    end
    self.isStale = true
  end

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

function core:OnLoad ()
  self:RegisterUnitEvent('UNIT_PORTRAIT_UPDATE', 'player')
  self:RegisterEvent('PLAYER_LOGIN')
  self:RegisterEvent('PLAYER_ENTERING_WORLD')
  self:RegisterEvent('ADDON_LOADED')
  self:RegisterEvent('PLAYER_REGEN_ENABLED')

  self:AddHandler('missions', MissionsHandler)
  self:AddHandler('shipments', ShipmentsHandler)
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:OnEvent (event, ...)
  print(event)
  if event == 'UNIT_PORTRAIT_UPDATE' then
    SetPortraitTexture(self.portrait, 'player')
  elseif event == 'PLAYER_LOGIN' then
    if not self.initialized then
      if IsLoggedIn() then
        WorldPlanData.OrderHall = WorldPlanData.OrderHall or {}
        self.data = WorldPlanData.OrderHall


        local name, realm = UnitName('player')
        realm  = realm or GetRealmName()
        self.profileKey = name .. '-' .. realm
        if not self.data[self.profileKey] then
          self.data[self.profileKey] = {}
        end
        self.profile = self.data[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'))]

        C_Garrison.RequestLandingPageShipmentInfo();

        if self.data.IsShown then
          self:Show()
        end
        self.initialized = true
      end
    end
  elseif self.events[event] then
    for ptype, eventFunc in pairs(self.events[event]) do
      print('|cFF88FF00' .. tostring(ptype) .. '|r:GetPlayerData()')
      eventFunc(self, event)
    end
  end
end

function core:UpdateNotifications()
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)
      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
    block:Refresh(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

  if OrderHallCommandBar and OrderHallCommandBar:IsVisible() then
    self:ClearAllPoints()
    self:SetPoint('TOP', OrderHallCommandBar, 'BOTTOM')
  else

    local posX = self.data.posX or 0
    local posY = self.data.posY or -24
    local point = self.point or 'TOP'
    local relativePoint = self.point or 'TOP'
    self:SetPoint(point, UIParent, relativePoint, posX, posY)
  end

  self:SetHeight(self.currentHeight)
end

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

  if self.data then
    self.data.IsShown = self:IsVisible()
  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
  ClassPlanButton:SetPoint('TOP', self, 'TOP', 0, 0)
end
function core:OnHide()
  ClassPlanButton:SetPoint('TOP', UIParent, 'TOP', 0, 0)
end

local GI_profileKey, GI_profile, GI_isMine
local GetItemList = 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 {r = 0.7, g = 0.7, b =0.7}
    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 = GetItemList(profile[key], sortedItems, ptype.OnGetItem)
        print(' - ', name, results, 'items')

    end

    if ptype.SortHandler then
      print(' sorting', key, #sortedItems)
      table.sort(sortedItems, ptype.SortHandler)
    end
  end
end



function core:UpdateItems ()
end

function MissionsHandler:OnComplete()
  self.data.isComplete = true
  self:Refresh()
end

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

function MissionsHandler:OnUpdate()
  if self.data.isComplete then
    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
  else
    self.TimeLeft:SetText(self.missionEndTime)
  end
end

local SetClassColors = function(self, data)

  if self.lastProfile ~= data.profileKey then
    self.Owner:SetText(data.profileKey)
    self.Owner:SetTextColor(data.classColor.r, data.classColor.g, data.classColor.b)
  else
    self.Owner:SetText(nil)
  end
  self.Background:SetColorTexture(data.classColor.r, data.classColor.g, data.classColor.b,
    (data.isComplete and 0.5 or 0.1))
end

function MissionsHandler:Refresh(data)
  data = data or self.data
  self.data = data

  self.isComplete = data.isComplete
  self.missionEndTime = data.missionEndTime

  local r,g,b = 1, 1, 1
  if data.isRare then
    r,g,b = 0.1, 0.4, 1
  end


  --self.missionData = data
  self.Label:SetText(data.name)
  self.Label:SetTextColor(r, g, b)

  if #data.rewards >= 1 then
    self.Icon:SetTexture(data.rewards[1].icon or GetItemIcon(data.rewards[1].itemID))
    self.rewardInfo = data.rewards[1]
  else
    self.Icon:SetAtlas(data.typeAtlas, false)
  end
  if self.isComplete then
    self.TimeLeft:SetText('Complete!')
  end
  SetClassColors(self, data)
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(data)
  data = data or self.data

  --[[
  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(data.icon)
  self.data = data


  local isComplete = data.isComplete



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


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

  data.isComplete = isComplete

  SetClassColors(self, data)
end
function ShipmentsHandler:OnUpdate()
  local data = self.data
  if (data.shipmentsReady and data.shipmentsTotal) and (data.shipmentsReady ~= data.shipmentsTotal) then
    local timeLeft = data.creationTime + data.duration - time()
    self.TimeLeft:SetText('Next: '.. GetTimeLeftString(timeLeft) .. ' |cFFFFFF00'..data.shipmentsTotal..' orders|r')
  elseif data.isStale then
    self.TimeLeft:SetText('|cFFFF0000Needs refresh|r')
  elseif data.isBeingResearched then
    self.TimeLeft:SetText(GetTimeLeftString(data.researchStartTime + data.researchDuration - time()))
  else
    self.TimeLeft:SetText('Complete!')
  end

end

function ShipmentsHandler:OnEnter()
  local data = self.data
  if ( data.shipmentsReady and data.shipmentsTotal ) then
    GameTooltip:SetOwner(self, 'ANCHOR_LEFT')
    GameTooltip:AddLine(data.shipmentsReady .. ' of '.. data.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
    for name, profile in pairs(ClassOrderPlan.data) do
      for index, shipment in pairs(profile.shipments) do
        if shipment == self.data then
          profile.shipments[index] = nil
          ClassOrderPlan:Refresh()
        end
      end
    end
  end
end