Mercurial > wow > worldplan
view ClassPlan.lua @ 19:6016ec3c8adf v1.0-rc5
ClassPlan:
- Colored progress bars accompany the mission and shipment journal entries.
- Significant load time improvements in the order hall.
* Data events are spammed gratuitously at load-in, so any sort of operation that results should be done from a delayed enclosure. In this case: using a self-destructing C_Timer identified by the function handle, and setting a flag that frame scripts can respond to.
author | Nenue |
---|---|
date | Mon, 24 Oct 2016 18:32:00 -0400 |
parents | 08b03bcdfeac |
children | e49efad51698 |
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 = {}, Queued = {} } 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() self.anchorParent = UIParent local anchorTo = 'TOP' 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 local last_invoc = {} 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 self.isStale = true if self:IsVisible() then else for handler, func in pairs(self.events[event]) do if not self.Queued[handler] then print('scheduling update for handler', handler) self.Queued[handler] = C_Timer.NewTimer(0.25, function() func(handler) self.Queued[handler] = nil end) end end 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) 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 if self.isStale then print('updating items on show') self:Refresh() 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 = (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,1) 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 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 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 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 if self.isComplete then self.TimeLeft:SetText('Complete!') self.Background:SetColorTexture(0.5,0.5,0.5) elseif (self.shipmentsReady and (self.shipmentsReady > 0)) then self.Background:SetColorTexture(0.5,0.5,0.5,.5) else self.Background:SetColorTexture(0,0,0,0.5) end end local time = time function ShipmentsHandler: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 = 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 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, 1) self.ProgressBG:SetColorTexture(r, g, 0, .125) self.ProgressBG:Show() self.ProgressBar:Show() else self.ProgressBG:Hide() self.ProgressBar:Hide() 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