Mercurial > wow > worldplan
view ClassPlan.lua @ 35:26dfa661daa7
WorldPlan:
- Quest pins will appear in the flight map. They follow the filter settings applied from the world map.
- Reward filter toggle changed to clear out other reward filters. The assumption being that one is most often looking only for that particular type of quest when they go to use it.
- Fixed filter bar info falling out of sync after player-triggered world map updates.
- Code stuff:
-- Quest pin shown-state management makes better use of OnShow OnHide handlers, SetShown is toggled and it all goes from there
-- WorldQuests module re-factored outside of the top level frame script.
ClassPlan:
- Available missions are now recorded; the mission list can be toggled between in-progress and available by clicking the heading.
author | Nenue |
---|---|
date | Thu, 03 Nov 2016 17:29:15 -0400 |
parents | e8679ecb48d8 |
children | 589c444d4837 |
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 = {}, characterButtons = {}, 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, maxItems = 10 } 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 self.profile.characterName = name self.profile.characterRealm = realm 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] handler.currentTime = GI_currentTime handler:GetPlayerData(self.profile) wipe(sortedItems) 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 RAID_CLASS_COLORS['HUNTER'] 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 local index = 1 for id, profile in pairs(self.data.characters) do 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('BOTTOMLEFT', self, 'TOPLEFT', 0, 0) else button:SetPoint('BOTTOMLEFT', self.lastButton, 'BOTTOMRIGHT', 2, 0) end self.lastButton = button end if not profile.characterName then profile.characterName, profile.characterRealm = id:match("%(.+)%-(.+)^") end button.className = profile.className button.classColor = profile.classColor button.characterName = profile.characterName button.characterRealm = profile.characterRealm button.hideItems = (profile.showItems == false) and (profile ~= self.profile) button.isMine = (profile == self.profile) button:Update() button:Show() index = index + 1 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() local ListTab = frame.ListTab if ListTab then ListTab:ClearAllPoints() ListTab:SetPoint('TOPLEFT', frame, 'TOPLEFT', 0, CP_HEADER_SIZE) ListTab:SetPoint('BOTTOMRIGHT', frame, 'TOPRIGHT', 0, 0) ListTab.Label:SetText(frame.listTitle[frame.currentListIndex]) ListTab:Show() print(ListTab:GetSize()) end 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:OnMouseWheel(delta) self.scrollOffset = (self.scrollOffset or 0) - ((delta > 0) and 1 or -1) self:UpdateItems() 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:Acquire(id) end function SharedHandlers:FreeBlock (block) end function SharedHandlers:UpdateItems() self.MoreItemsUp:Hide() self.MoreItemsDown:Hide() local sortedItems = self.sortedItems 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.blocks = self.blocks or {} local blocks = self.blocks local lastProfile local totalHeight = 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 break end local block = blocks[i] if not block then block = CreateFrame('Button', nil, self, self.templateName) block.listType = self.activeKey block.handler = self self.numBlocks = self.numBlocks + 1 blocks[i] = block end block:SetID(index) print('RefreshItem', block) 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', 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.scrollOffset = scrollOffset 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 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 function SharedEntry: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 - GI_currentTime if timeLeft < 0 then -- handle being complete 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:GetWidth() * progress) end end -- Update shipment flags data local SetActualShipmentTime = function(self) if self.isComplete then return nil, nil end local timestamp = time() local timeLeft = self.creationTime + self.duration - timestamp local duration = self.duration * self.shipmentsTotal 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 local expires = (self.originalCreationTime or self.creationTime) + duration return expires, duration end function ShipmentList:OnGetItem (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 ShipmentList:OnLoad() C_Garrison.RequestLandingPageShipmentInfo(); end function ShipmentList:OnShow() print('|cFF00FF88'..self:GetName()..':OnShow()|r') C_Garrison.RequestLandingPageShipmentInfo() 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 expires, duration = SetActualShipmentTime(self) if self.isComplete then self.TimeLeft:SetText('Complete!') self.TimeLeft:SetTextColor(0,1,1) elseif self.shipmentsReady >= 1 then self:SetTimeLeft(expires, duration) self.TimeLeft:SetTextColor(0,1,0) else self:SetTimeLeft(expires, duration) self.TimeLeft:SetTextColor(1,1,1) end elseif self.isBeingResearched then self:SetTimeLeft(self.researchStartTime + self.researchDuration - time(), self.researchDuration) self.TimeLeft:SetTextColor(1,1,1) else self.TimeLeft:SetText('Complete!') self.TimeLeft:SetTextColor(0,1,0) 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) ClassPlanMissionEntryMixin = Mixin(MissionEntry, SharedEntry) ClassPlanShipmentHandler = Mixin(ShipmentList, SharedHandlers) ClassPlanShipmentEntryMixin = Mixin(ShipmentEntry,SharedEntry) ClassPlanHeaderMixin = { OnClick = function(self) self:GetParent():SetList() self:GetParent().isStale = true ClassOrderPlan:Update() end } ClassPlanCharacterButtonMixin = { Update = function(self) 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(self.showItems) self.SelectGlow:SetShown(self.isMine) end } function ClassPlanCharacterButtonMixin:OnEnter() end function ClassPlanCharacterButtonMixin:OnLeave() end function ClassPlanCharacterButtonMixin:OnClick() end