# HG changeset patch # User Nenue # Date 1476220837 14400 # Node ID 3830a592cb0fbc020c9f616d27a823dc32feb719 initial commmit diff -r 000000000000 -r 3830a592cb0f WorldPlan.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WorldPlan.lua Tue Oct 11 17:20:37 2016 -0400 @@ -0,0 +1,1410 @@ +-- Veneer +-- WorldPlan.lua +-- Created: 8/16/2016 8:19 AM +-- %file-revision% +--[[ + Summary: + Adds reward icons to the world quest POI markers, and adds said markers to the continent map. + + Issues: + Dalaran quests aren't visible until that map has been specifically viewed by the player. +--]] + +WorldPlanCore = {} +WorldPlanPOIMixin = {} +WorldPlanFilterPinMixin = {} +local WorldPlanFlightMapMixin = setmetatable({}, {__tostring = function() return 'FlightMapHandler' end}) +local WorldQuests = setmetatable({ QuestsByID = {}, freePins = {} }, {__tostring = function() return 'QuestHandler' end}) +local FilterBar = setmetatable({ SummaryHeaders = {} }, {__tostring = function() return 'FilterBar' end}) + +local WorldPlan = WorldPlanCore +local QuestPOI = WorldPlanPOIMixin +local FilterPin = WorldPlanFilterPinMixin +local WP_VERSION = "1.0" + +local print = DEVIAN_WORKSPACE and function(...) _G.print('WP', ...) end or function() end +local wipe, tremove, tinsert, pairs, floor, tContains = table.wipe, table.remove, table.insert, pairs, floor, tContains +local TQ_GetQuestInfoByQuestID = C_TaskQuest.GetQuestInfoByQuestID -- Return the name of a quest with a given ID +local TQ_GetQuestsForPlayerByMapID = C_TaskQuest.GetQuestsForPlayerByMapID -- This function is not yet documented +local TQ_GetQuestTimeLeftMinutes = C_TaskQuest.GetQuestTimeLeftMinutes +local TQ_RequestPreloadRewardData = C_TaskQuest.RequestPreloadRewardData +local TQ_GetQuestLocation = C_TaskQuest.GetQuestLocation +local TQ_IsActive = C_TaskQuest.IsActive +local ITEM_QUALITY_COLORS = ITEM_QUALITY_COLORS +local WorldMap_DoesWorldQuestInfoPassFilters = WorldMap_DoesWorldQuestInfoPassFilters +local QuestMapFrame_IsQuestWorldQuest = QuestMapFrame_IsQuestWorldQuest +local GameTooltip = GameTooltip +local GetItemIcon = GetItemIcon + + +local GetMapInfo, QuestPOIGetIconInfo = GetMapInfo, QuestPOIGetIconInfo +local GetQuestTagInfo, HaveQuestData = GetQuestTagInfo, HaveQuestData +local GetNumQuestLogRewards, GetNumQuestLogRewardCurrencies, GetQuestLogRewardMoney = GetNumQuestLogRewards, GetNumQuestLogRewardCurrencies, GetQuestLogRewardMoney +local GetQuestLogRewardInfo, GetQuestLogRewardCurrencyInfo, GetMoneyString = GetQuestLogRewardInfo, GetQuestLogRewardCurrencyInfo, GetMoneyString + +local GetCurrentMapAreaID, GetMapNameByID, GetSuperTrackedQuestID = GetCurrentMapAreaID, GetMapNameByID, GetSuperTrackedQuestID +local MC_GetNumZones, MC_GetZoneInfo = C_MapCanvas.GetNumZones, C_MapCanvas.GetZoneInfo + +local qprint = DEVIAN_WORKSPACE and function(...) _G.print('POI', ...) end or function() end +local iprint = DEVIAN_WORKSPACE and function(...) _G.print('ItemScan', ...) end or function() end + +local SearchFaction +local PinBaseIndex = 1600 +local ShowAllProfessionQuests = false +local DisplayContinentSummary = true +local DisplayContinentPins = true +local NotifyWhenNewQuests = true +local PinRewardFilter, PinTagFilter +local BROKEN_ISLES_ID, DALARAN_ID, AZSUNA_ID, VALSHARAH_ID, HIGHMOUNTAIN_ID, STORMHEIM_ID, SURAMAR_ID, EOA_ID = 1007, 1014, 1015,1018, 1024, 1017, 1033, 1096 + +-- maps where we do our own anchors +local CONTINENT_MAPS = { [BROKEN_ISLES_ID] = BROKEN_ISLES_ID, } +local WORLD_QUEST_MAPS = { [DALARAN_ID] = 'Dalaran70', [AZSUNA_ID] = 'Azsuna', [VALSHARAH_ID] = "Val'sharah", + [HIGHMOUNTAIN_ID] = 'Highmountain', [STORMHEIM_ID] = 'Stormheim', [SURAMAR_ID] = 'Suramar', [EOA_ID] = 'EyeOfAszhara', } +-- default color templates +local ARTIFACT_COLOR = ITEM_QUALITY_COLORS[LE_ITEM_QUALITY_ARTIFACT] +local MONEY_COLOR = {hex ='|cFFFFFF00', r=1, g=1, b=0} +local COMMON_COLOR = ITEM_QUALITY_COLORS[LE_ITEM_QUALITY_COMMON] + +-- operating flags +local superTrackedID +local currentMapName +local hasNewQuestPins +local isContinentMap + +local ICON_UNKNOWN = "Interface\\ICONS\\inv_misc_questionmark" +local ICON_MONEY = "Interface\\Buttons\\UI-GroupLoot-Coin-Up" + +local POI_BORDER_MASK = "Interface\\Minimap\\UI-Minimap-Background" +local POI_BORDER_FILL = "Interface\\BUTTONS\\YELLOWORANGE64" +local POI_BORDER_BLUE = "Interface\\BUTTONS\\GRADBLUE" +local POI_BORDER_RED = "Interface\\BUTTONS\\RedGrad64" +local POI_BORDER_YELLOW = "Interface\\BUTTONS\\YELLOWORANGE64" +local POI_BORDER_GREEN = "Interface\\BUTTONS\\GREENGRAD64" + +local REWARD_CASH = 1001 +local REWARD_ARTIFACT_POWER = 1002 +local REWARD_GEAR = 1003 +local REWARD_CURRENCY = 1004 +local REWARD_ITEM = 1005 +local REWARD_REAGENT = 1006 + +local POI_DEFAULT_TYPE = { + a = 1, + r = 1, g = 1, b = 1, + x = 0, y = 0, + desaturated = true, + mask = POI_BORDER_MASK, + texture = POI_BORDER_FILL, + continent = { + PinSize = 18, + Border = 3, + TrackingBorder = 2, + TagSize = 6, + TimeleftStage = 3, + }, + zone = { + PinSize = 22, + Border = 3, + TrackingBorder = 2, + TagSize = 12, + TimeleftStage = 3, + }, + minimized = { + PinSize = 4, + Border = 1, + TrackingBorder = 2, + NoIcon = true, + TimeleftStage = 1, + } +} +local POI_REWARD_TYPE = setmetatable({}, { + __newindex = function(t, k, v) + if type(v) == 'table' then + setmetatable(v, {__index = POI_DEFAULT_TYPE}) + end + rawset(t,k,v) + end +}) +local POI_FILTER_STYLE = setmetatable({ + continentBorder = 2, + zoneBorder = 2, +}, {__index = POI_DEFAULT_TYPE}) + +local LE_QUEST_TAG_TYPE_PVP = LE_QUEST_TAG_TYPE_PVP +local LE_QUEST_TAG_TYPE_PET_BATTLE = LE_QUEST_TAG_TYPE_PET_BATTLE +local LE_QUEST_TAG_TYPE_DUNGEON = LE_QUEST_TAG_TYPE_DUNGEON +local LE_QUEST_TAG_TYPE_PROFESSION = LE_QUEST_TAG_TYPE_PROFESSION +local LE_QUEST_TAG_TYPE_NORMAL = LE_QUEST_TAG_TYPE_NORMAL + +-- Pin color/display variables +POI_REWARD_TYPE[REWARD_ITEM] = { + r = 1, g = 1, b = 1, +} +POI_REWARD_TYPE[REWARD_REAGENT] = { + r = 0, g = 1, b = 1, +} +POI_REWARD_TYPE[REWARD_ARTIFACT_POWER] = { + r = 1, g = .25, b = .5, + showNumber = true, +} +POI_REWARD_TYPE[REWARD_GEAR] = { + r = .1, g = .2, b = 1, +} +POI_REWARD_TYPE[REWARD_CURRENCY] = { + r = 1, g = 1, b = 0, +} +POI_REWARD_TYPE[REWARD_CASH] = { + r = 0, g = 0, b = 0, + --x = 0, y = -1, + --mask = ICON_MONEY, + --continentBorder = 1, + --zoneBorder = 1, +} + + +-- Summary header structure +local POI_FILTER_OPTIONS = { + { label = 'Filters', texture = "Interface\\WorldMap\\WorldMap-Icon" }, + { filterKey= 'rewardType', filterValue = REWARD_ARTIFACT_POWER, label = 'Artifact Power', texture = "Interface\\ICONS\\inv_7xp_inscription_talenttome01" }, + { filterKey= 'rewardType', filterValue = REWARD_CURRENCY,label = 'Currency', texture = "Interface\\ICONS\\inv_misc_elvencoins" }, + { filterKey= 'rewardType', filterValue = REWARD_ITEM, label = 'Item', texture = "Interface\\ICONS\\inv_crate_01" }, + { filterKey= 'rewardType', filterValue = REWARD_GEAR, label = 'Equipment', texture = "Interface\\ICONS\\garrison_bluearmorupgrade" }, + { filterKey= 'rewardType', filterValue = REWARD_REAGENT, label = 'Reagents', texture = 1417744 }, + { filterKey= 'rewardType', filterValue = REWARD_CASH, label = 'Reagents', texture = ICON_MONEY }, + { filterKey= 'worldQuestType', filterValue = LE_QUEST_TAG_TYPE_PVP, label = 'PvP', texture = "Interface\\Icons\\Ability_PVP_GladiatorMedallion", spacing = 10 }, + { filterKey= 'worldQuestType', filterValue = LE_QUEST_TAG_TYPE_PET_BATTLE, label = 'Pet Battle', texture = "Interface\\Icons\\PetJournalPortrait", }, + { filterKey= 'worldQuestType', filterValue = LE_QUEST_TAG_TYPE_DUNGEON, label = 'Dungeon', texture = "Interface\\LFGFRAME\\UI-LFR-PORTRAIT", }, +} + +local PIN_TIME_CONTEXT = { + {max = 60, + r=1, g=0.25, b =0, format = function (minutes) return '|cFFFF4400'.. minutes .. 'm' end, + continentAlpha = 1, swipeTime = 1440, + }, + {max = 240, + r=1, g=.5, b=0, format = function(minutes) return '|cFFFF4400'.. floor(minutes/60) .. 'h' end, + continentAlpha = 1, swipeTime = 1440, + }, + {max = 1440, + r=1, g=1, b=0, format = function(minutes) return '|cFFFFFF00'.. floor(minutes/60) .. 'h' end, + continentAlpha = .55, swipeTime = 1440 + }, + {max = 10081, + r=0, g=1, b=0, + continentAlpha = .3, + }, -- 7 days + 1 minute +} + +local numPins = 0 +local QuestsByZone = {} +local QuestsByFaction = {} +local QuestsByReward = {} +local QuestsByTag = {} +local QuestsByID = {} +local QuestPositions = {} +local FilterInclusions = {rewardType = {}, worldQuestType = {}} +local FilterExclusions = {rewardType = {}, worldQuestType = {}} +local ZoneInfo = {} +local SummaryHeaders = {} + +local FreePins = {} +local NumPinFrames = 1 + +local hasPendingQuestData +local notifyPlayed +local scanner, wmtt, WorldMapPOIFrame + + +local tasksQueue = {} +local function OnNext (func) + if #tasksQueue == 0 then + _G.WorldPlan:SetScript('OnUpdate', function() + local func = tremove(tasksQueue, 1) + if func then + func() + end + if #tasksQueue == 0 then + _G.WorldPlan:SetScript('OnUpdate', nil) + end + end) + end + tinsert(tasksQueue, func) +end + +-- combines templates +local function DoMixins(frame,...) + for i = 1, select('#', ...) do + for k,v in pairs(select(i,...)) do + frame[k] = v + end + end + return frame +end + +-- use tooltip object to extract item details +local ParseItemReward = function(questID) + local rewardType = REWARD_ITEM + local name, icon, quantity, quality, _, itemID = GetQuestLogRewardInfo(1, questID) + if not itemID then + return REWARD_ITEM + end + + scanner:SetOwner(WorldPlan, "ANCHOR_NONE") + scanner:SetItemByID(itemID) + local ttl1 = _G['WorldPlanTooltipTextLeft1'] + local ttl2 = _G['WorldPlanTooltipTextLeft2'] + local ttl3 = _G['WorldPlanTooltipTextLeft3'] + local ttl4 = _G['WorldPlanTooltipTextLeft4'] + if ttl2 then + local text = ttl2:GetText() + -- Artifact Power + if text and text:match("|cFFE6CC80") then + --print('AP token!', text) + local power + if ttl4 then + local text = ttl4:GetText() + --print('tip line 4', text) + if text then + power = text:gsub("%p", ""):match("%d+") + power = tonumber(power) + end + + end + rewardType = REWARD_ARTIFACT_POWER + icon = "Interface\\ICONS\\inv_7xp_inscription_talenttome01" + quantity = power + elseif text and text:match("Item Level") then + --print('equipment!', text) + rewardType = REWARD_GEAR + quantity = text:match("Item Level ([%d\+]+)") + end + end + if ttl3 then + local text = ttl3:GetText() + -- Crafting Reagent + if text and text:match("Crafting Reagent") then + --print('reagent', text) + rewardType = REWARD_REAGENT + end + end + iprint(' item:', name, rewardType, icon, quantity) + return rewardType, icon, quantity, name, itemID +end + +-- update a masked texture without messing up its blending mask +local SetMaskedTexture = function(region, file, mask) + mask = mask or POI_BORDER_MASK + region:SetMask(nil) + region:SetTexture(file) + region:SetMask(mask) +end + +-- tracking menu toggler +local DropDown_OnClick = function(self) + local key = self.value + if key then + if WorldPlanData[key] then + WorldPlanData[key] = nil + else + WorldPlanData[key] = true + end + end +end + +function WorldPlan:print(...) + local msg + for i = 1, select('#', ...) do + msg = (msg and (msg .. ' ') or '') .. tostring(select(i, ...)) + end + DEFAULT_CHAT_FRAME:AddMessage("|cFF0088FFWorldPlan|r: " .. msg) +end + +function WorldPlan:OnLoad () + WorldPlan = self + scanner = _G.WorldPlanTooltip + wmtt = _G.WorldMapTooltip + WorldMapPOIFrame = _G.WorldMapPOIFrame + + WorldPlan:print('v'..WP_VERSION) + + self:RegisterEvent("QUESTLINE_UPDATE") + self:RegisterEvent("QUEST_LOG_UPDATE") + self:RegisterEvent("WORLD_MAP_UPDATE") + self:RegisterEvent("WORLD_QUEST_COMPLETED_BY_SPELL") + self:RegisterEvent("SUPER_TRACKED_QUEST_CHANGED") + self:RegisterEvent("SKILL_LINES_CHANGED") + self:RegisterEvent("ARTIFACT_XP_UPDATE") + self:RegisterEvent("ADDON_LOADED") + + WorldPlan.modules = { + WorldQuests, FilterBar, WorldPlanFlightMapMixin, + } + hooksecurefunc("UIDropDownMenu_Initialize", self.OnDropDownInitialize) +end + +function WorldPlan:OnEvent (event, ...) + print() + print(event, ...) + if event == 'ADDON_LOADED' then + local addon = ... + if addon == "Blizzard_FlightMap" then + print('do mixin junk') + self.OnFlightMapLoaded() + + end + if IsLoggedIn() and not self.initialized then + self:Setup() + end + elseif event == 'WORLD_MAP_UPDATE' then + self:RefreshAll() + end + for i, module in ipairs(self.modules) do + if module.OnEvent then + print('forwarding to', tostring(module)) + module:OnEvent(event, ...) + end + end +end + + +function WorldPlan:Setup () + if not WorldPlanData then + WorldPlanData = {key = 0 } + end + WorldPlanData.key = (WorldPlanData.key or 0) + 1 + self.db = WorldPlanData + + for i, module in ipairs(self.modules) do + if module.Setup then module:Setup() end + if not module.RegisterEvent then + module.RegisterEvent = self.RegisterEvent + end + end + self.initialized = true + +end + +function WorldPlan:RefreshAll (forced) + for i, module in ipairs(self.modules) do + if module.Refresh then + module:Refresh() + end + end +end +function WorldPlan:UpdateAnchors () + for i, module in ipairs(self.modules) do + if module.UpdateAnchors then + module:UpdateAnchors() + end + end +end + +-- insert visual options into the tracking button menu +WorldPlan.OnDropDownInitialize = function (self, callback, dropType) + if self ~= WorldMapFrameDropDown then + return + end + + local info = UIDropDownMenu_CreateInfo() + info.text = "" + info.isTitle = true + UIDropDownMenu_AddButton(info) + info.text = "|cFF00AAFFWorldPlan|r" + info.isTitle = true + UIDropDownMenu_AddButton(info) + info.isTitle = nil + info.disabled = nil + info.keepShownOnClick = true + info.tooltipOnButton = 1 + + info.text = "Hidden World Quests" + info.isNotRadio = true + info.value = "ShowAllProfessionQuests" + info.checked = ShowAllProfessionQuests + info.tooltipTitle = "Hidden Quests" + info.tooltipText = "Display work order and profession-related quests that are skipped by the default UI." + info.func = DropDown_OnClick + UIDropDownMenu_AddButton(info) + + info.text = "Show Continent Pins" + info.isNotRadio = true + info.value = "DisplayContinentPins" + info.checked = DisplayContinentPins + info.tooltipTitle = "Continent Pins" + info.tooltipText = "Display quest pins on the continent map (may get cramped)." + info.func = DropDown_OnClick + UIDropDownMenu_AddButton(info) + + info.text = "Show Summary" + info.isNotRadio = true + info.value = "DisplayContinentSummary" + info.tooltipTitle = "Summary Bar" + info.tooltipText = "Display a summary of active world quests. Note: requires directly viewing Broken Isle and Dalaran maps to gain complete info." + info.checked = DisplayContinentSummary + info.func = DropDown_OnClick + UIDropDownMenu_AddButton(info) +end + +function WorldQuests:Setup() + -- refresh positions any time blizzard does so (i.e. mousewheel zoom) + hooksecurefunc("WorldMapScrollFrame_ReanchorQuestPOIs", function() + self:Refresh() + end) + + -- hide the original world quest POIs + hooksecurefunc("WorldMap_UpdateQuestBonusObjectives", function() + for i = 1, NUM_WORLDMAP_TASK_POIS do + local button = _G['WorldMapFrameTaskPOI'..i] + if button and button.worldQuest then + button:Hide() + end + end + end) + + +end + +function WorldQuests:OnEvent (event, ...) + print('|cFFFFFF00'..tostring(self)..':OnEvent()'..event..'|r', ...) + if event == 'QUEST_LOG_UPDATE' then + local questID, added = ... + if questID and added then + self:GetPinByQuestID(questID) + else + self:GetPinsForMap() + end + elseif event == 'WORLD_QUEST_COMPLETED_BY_SPELL' then + local questID = ... + if questID and QuestsByID[questID] then + self:ReleasePin(QuestsByID[questID]) + end + elseif event == 'SKILL_LINES_CHANGED' then + self:Refresh() + end +end + +function WorldQuests:AcquirePin (questID, pinTable) + local pin = QuestsByID[questID] + local isNew = false + if not pin then + isNew = true + local numFree = #self.freePins + if numFree >= 1 then + pin = tremove(self.freePins, numFree) + --print('|cFF00FF00Re-using', pin:GetName()) + else + local name = 'WorldPlanQuestMarker' .. NumPinFrames + --print('|cFF00FF00Creating', name) + pin = CreateFrame('Frame', name, WorldMapPOIFrame, 'WorldPlanQuestPin') + pin:SetFrameStrata('HIGH') + pin:SetScript('OnEnter', function(self) + TaskPOI_OnEnter(self) + end) + pin:SetScript('OnLeave', function(self) + TaskPOI_OnLeave(self) + end) + pin:SetScript('OnMouseDown', TaskPOI_OnClick) + + NumPinFrames = NumPinFrames + 1 + + --pin.iconBorder:SetVertexColor(0,0,0,1) + + end + QuestsByID[questID] = pin + pin.isNew = true + pin.currentWidth = nil + + -- used by TaskPOI_x scripts + pin.questID = questID + pin.worldQuest = true + else + --print('|cFF00FF00Using', pin:GetName()) + end + + -- set display flags accordingly + if pinTable then + for k,v in pairs(pinTable) do + pin[k] = v + end + end + pin.throttle = nil + pin.timeThreschold = nil + return pin, isNew +end + +-- remove from index and add it to the recycling heap +function WorldQuests:ReleasePin (pin) + + local id = pin.questId + if id then + QuestsByID[id] = nil + for i, zone in pairs(QuestsByZone) do + print('-', i, zone[i]) + zone[id] = nil + end + end + if pin.factionID then + QuestsByFaction[pin.factionID][id] = nil + end + pin:Hide() + pin:ClearAllPoints() + tinsert(self.freePins, pin) + print('|cFFFF4400Clearing out', pin:GetName(),id) +end + +-- create of update quest pins for a map and its underlying zones +function WorldQuests:GetPinsForMap (mapID) + mapID = mapID or GetCurrentMapAreaID() + superTrackedID = GetSuperTrackedQuestID() + if not mapID then + -- info not available yet + return + end + if mapID == BROKEN_ISLES_ID then + hasPendingQuestData = nil + print('|cFF00FFFFRefreshQuestsForMap|r', mapID, GetMapNameByID(mapID), superTrackedID) + self.fullSearch = true + for i = 1, MC_GetNumZones(mapID) do + local submapID, name, depth = MC_GetZoneInfo(mapID, i) + self:GetPinsForMap(submapID) + end + self.fullSearch = nil + elseif QuestsByZone[mapID] then + local taskInfo = TQ_GetQuestsForPlayerByMapID(mapID) + local quest = QuestsByZone[mapID] + local numQuests = 0 + if taskInfo and #taskInfo >= 1 then + print('|cFF00FFFFRefreshQuestsForMap|r', mapID, GetMapNameByID(mapID), #taskInfo) + wipe(QuestsByZone[mapID]) + ZoneInfo[mapID] = taskInfo + for taskID, info in pairs(taskInfo) do + local questID = info.questId + + info.mapID = mapID + QuestsByZone[mapID][questID] = self:GetPinByQuestID(questID, info) + numQuests = numQuests + 1 + end + end + end + if hasNewQuestPins and not self.fullSearch then + print('|cFF00FF00NEW PINS DO ANCHOR THINGS') + self:Refresh () + hasNewQuestPins = nil + end +end + +-- create or update the pin using the given questID and C_TaskQuest results +function WorldQuests:GetPinByQuestID (questID, taskInfo) + if (QuestMapFrame_IsQuestWorldQuest (questID)) then + local questTitle, rewardIcon, rewardName, rewardCount, rewardStyle, rewardType, itemID, quantity, quality, _ + local pin = self:AcquirePin(questID, taskInfo) + + if pin.isNew then + if not hasNewQuestPins then + print('triggering new quest pins event') + end + + hasNewQuestPins = true + end + + if not HaveQuestData(questID) then + print('|cFFFF4400Retrieval failed.') + TQ_RequestPreloadRewardData(questID) + hasPendingQuestData = true + else + print('|cFF00FF88HaveQuestData|r') + pin.mapID = pin.mapID or C_TaskQuest.GetQuestZoneID(questID) + + -- set reward category + local numRewards = GetNumQuestLogRewards(questID) + local numCurrency = GetNumQuestLogRewardCurrencies(questID) + local money = GetQuestLogRewardMoney(questID) + if numRewards >= 1 then + rewardType, rewardIcon, rewardCount, rewardName, itemID = ParseItemReward(questID) + elseif numCurrency >= 1 then + rewardName, rewardIcon, rewardCount = GetQuestLogRewardCurrencyInfo(1, questID) + rewardType = REWARD_CURRENCY + elseif money >= 1 then + rewardIcon = ICON_MONEY + rewardName = GetMoneyString(money) + rewardType = REWARD_CASH + end + rewardStyle = POI_REWARD_TYPE[rewardType] or POI_DEFAULT_TYPE + + pin.itemNumber = rewardCount or pin.itemNumber + pin.rewardType = rewardType or REWARD_ITEM + pin.style = rewardStyle + QuestsByID[questID] = pin + + -- title, faction, capped state + local questTitle, factionID, capped = TQ_GetQuestInfoByQuestID(questID) + if factionID then + QuestsByFaction[factionID] = QuestsByFaction[factionID] or {} + QuestsByFaction[factionID][questID] = pin + end + pin.title = questTitle or "|cFFFF0000Retrieving..." + pin.factionID = factionID + pin.capped = capped + + -- set tag details + local tagID, tagName, worldQuestType, rarity, isElite, tradeskillLineIndex = GetQuestTagInfo(questID); + local tagAtlas + if worldQuestType == LE_QUEST_TAG_TYPE_PET_BATTLE then + tagAtlas = "worldquest-icon-petbattle" + elseif worldQuestType == LE_QUEST_TAG_TYPE_PVP then + tagAtlas = "worldquest-icon-pvp-ffa" + elseif worldQuestType == LE_QUEST_TAG_TYPE_PROFESSION then + local id = tradeskillLineIndex and select(7, GetProfessionInfo(tradeskillLineIndex)) + if id then + tagAtlas = WORLD_QUEST_ICONS_BY_PROFESSION[id] + end + elseif worldQuestType == LE_QUEST_TAG_TYPE_DUNGEON then + tagAtlas = "worldquest-icon-dungeon" + end + pin.tagID = tagID + pin.tagName = tagName + pin.worldQuestType = worldQuestType + pin.isElite = isElite + pin.tradeskillLineIndex = tradeskillLineIndex + pin.rarity = rarity + pin.tagAtlas = tagAtlas + end + + -- flag unresolved info + if not (rewardIcon and rewardName) then + if not pin.isPending then + pin.isPending = true + TQ_RequestPreloadRewardData (questID) + pin.rewardType = pin.rewardType or REWARD_ITEM + pin.style = pin.style or POI_REWARD_TYPE[REWARD_ITEM] + + if not hasPendingQuestData then + hasPendingQuestData = true + PlaySoundKitID(229) + end + --WorldPlan:print('|cFFFFFF00'..tostring(pin.title)..'|r waiting on texture info') + end + else + if (rewardIcon and rewardName) then + --WorldPlan:print('|cFF00FF00'..tostring(pin.title)..'|r has info', rewardIcon, rewardName) + pin.hasUpdate = true + end + pin.isPending = nil + end + pin.itemTexture = rewardIcon or pin.itemTexture + pin.itemName = rewardName or pin.itemName + + qprint(' |cFF00FFFF'..questID..'|r:->', (HaveQuestData(questID) and "|cFF00FF00HaveQuestData" or "-"), (C_TaskQuest.IsActive(questID) and "|cFF88FF00IsActive|r" or "")) + qprint(' ', pin.title, pin.itemTexture, 'rewardType:', pin.rewardType, 'tag:', pin.tagID, 'style', pin.style ) + end + return QuestsByID[questID] +end + +function WorldQuests:Refresh(forced) + print('pushing |cFF00FF88'..tostring(self)..':Refresh()|r') + if not WorldMapPOIFrame:IsVisible() then + return + end + self:Reset() + self:UpdateAnchors() + self:Cleanup () +end + +-- prepares elements for a map update +function WorldQuests:Reset () + wipe(QuestPositions) + wipe(QuestsByReward) + wipe(QuestsByTag) + for questID, pin in pairs(QuestsByID) do + pin.used = nil + end + qprint(tostring(self)..':|cFFFFFF00Reset()|r') +end + +-- update visibility states of all pins +function WorldQuests:UpdateAnchors (submapID) + local mapFileName, textureHeight, textureWidth, isMicroDungeon, microDungeonMapName = GetMapInfo() + if isMicroDungeon then + return + end + + local currentMap = GetCurrentMapAreaID() + local submapID = submapID or currentMap + + if submapID == BROKEN_ISLES_ID and (not DisplayContinentPins) then + print('not updating map for reasons') + return + end + print('|cFF88FF00'..tostring(self)..':UpdateAnchors|r', submapID, GetMapNameByID(submapID), 'pin count:', numPins) + local numZones = MC_GetNumZones(submapID) + if numZones then + for i = 1, numZones do + local subMapID = MC_GetZoneInfo(submapID, i) + self:UpdateAnchors(subMapID) + end + end + local pins = QuestsByZone[submapID] + + if pins then + local hostFrame = WorldMapPOIFrame + local mapWidth, mapHeight = hostFrame:GetSize() + for questID, pin in pairs(pins) do + if pin:IsShowable(true) then + pin:SetFrameLevel(PinBaseIndex+numPins) + pin:SetAnchor(WorldMapPOIFrame, currentMap, mapWidth, mapHeight) + pin.used = true + numPins = numPins + 1 + end + end + end +end + + + + +-- shows, animates, or hides pins based on their current visibility flags +local debug_show = {} +local debug_animate = {} +local debug_hide = {} +function WorldQuests:Cleanup () + local showQuestPOI = GetCVarBool("questPOI") + print('|cFFFFFF00'..tostring(self)..':Cleanup()|r') + wipe(QuestsByReward) + wipe(QuestsByTag) + wipe(debug_show) + wipe(debug_animate) + wipe(debug_hide) + local mapID = GetCurrentMapAreaID() + isContinentMap = (mapID == BROKEN_ISLES_ID) + + -- continent or zone sizing + local subStyle = isContinentMap and 'continent' or 'zone' + + numPins = 0 + for questID, pin in pairs(QuestsByID) do + -- can we show it? + if showQuestPOI and (pin.used) then + pin.subStyle = pin.filtered and 'minimized' or subStyle + pin:GetSizeMetrics() + + + -- is it a new quest? + if pin.isNew then + if not pin.isAnimating then + pin.isAnimating = true + OnNext(function() + pin.isNew = nil + pin:Show() + pin.FadeIn:Play() + end) + if not notifyPlayed then + notifyPlayed = true + PlaySoundKitID(23404) + end + tinsert(debug_animate,questID) + end + else + -- hard show existing pin + pin:Show() + tinsert(debug_show,questID) + end + else + pin:Hide() + tinsert(debug_hide,questID) + end + + -- is it part of the current map? + if pin.used then + local rewardType = pin.rewardType + local tagType = pin.worldQuestType + if rewardType then + QuestsByReward[rewardType] = QuestsByReward[rewardType] or {} + tinsert(QuestsByReward[rewardType], pin) + end + if tagType then + QuestsByTag[tagType] = QuestsByTag[tagType] or {} + tinsert(QuestsByTag[tagType], pin) + end + end + end + print(' adding:', table.concat(debug_animate, ',' )) + print(' showing:', table.concat(debug_show, ',' )) + hasNewQuestPins = nil +end + +-- data provider manipulations for the taxi map +WorldPlan.OnFlightMapLoaded = function() + if true then return end + -- todo: figure out how to layer inside the map canvas + local res = {} + local t = {} + for k,v in pairs(FlightMapFrame) do + tinsert(res, tostring(k)) + end + + table.sort(res) + for i, k in ipairs(res) do + print(k) + end + hooksecurefunc(FlightMapFrame, 'RefreshAll', function(self) + print('|cFF0088FFWQDP RefreshAllData ', GetTime()) + + WorldPlan:GetPinsForMap(self:GetMapID()) + + for pin in self:EnumerateAllPins() do + if pin.worldQuest then + --print('got pin #', pin.questID) + local wp = QuestsByID[pin.questID] + if wp then + wp:ClearAllPoints() + wp:SetParent(FlightMapFrame.ScrollContainer) + wp:SetFrameStrata('MEDIUM') + wp:SetPoint('CENTER', pin, 'CENTER') + wp:Show() + end + end + end + end) +end + + + +local throttle = 0 +local tooltip = CreateFrame ("GameTooltip", "VeneerWorldQuestsScanner", nil, "GameTooltipTemplate") +local tooltipLine1 = _G['VeneerWorldQuestsScannerTextLeft1'] +local tooltipLine3 = _G['VeneerWorldQuestsScannerTextLeft3'] +local GetTime, mod = GetTime, mod + +function QuestPOI:OnLoad() + self:RegisterEvent('SUPER_TRACKED_QUEST_CHANGED') +end + +function QuestPOI:OnEvent(event, ...) + if event == 'SUPER_TRACKED_QUEST_CHANGED' then + if self:IsVisible() then + self:Refresh() + end + end +end + +local PIN_UPDATE_DELAY = .016 +local TOP_PIN_ID +function QuestPOI:OnUpdate (sinceLast) + -- control update check intervals + self.throttle = (self.throttle or PIN_UPDATE_DELAY) - sinceLast + if self.throttle <= 0 then + -- factor overtime into the throttle timer + self.throttle = PIN_UPDATE_DELAY - self.throttle + else + return + end + + -- query for reward data if it wasn't found in the original scan + local questID = self.questID + if self.isPending then + WorldQuests:GetPinByQuestID(questID) + if not (self.PendingFade:IsPlaying() or self.isAnimating) then + self.PendingFade:Play() + end + return + else + if self.PendingFade:IsPlaying() then + self.PendingFade:Stop() + end + end + + if self.hasUpdate then + self:Refresh() + self.hasUpdate = nil + end + + -- update time elements + local tl = self.timeThreschold + local timeLeft = TQ_GetQuestTimeLeftMinutes(questID) + if timeLeft > 0 then + for i, context in ipairs(PIN_TIME_CONTEXT) do + if i > self.TimeleftStage then + self.timeLabel:SetText(nil) + break + end + + + if timeLeft <= context.max then + if tl ~= i then + tl = i + end + + if context.format then + self.timeLabel:SetText(context.format(timeLeft)) + else + self.timeLabel:SetText(nil) + end + break + end + end + else + -- remove self in a timely manner + if not self.isPending then + self:Hide() + end + end + self.timeThreschold = tl + + if self:IsMouseOver() then + self.MouseGlow:Show() + else + self.MouseGlow:Hide() + end +end + +function QuestPOI:OnShow () + qprint('|cFFFFFF00["'..tostring(self.title)..'"]|r:OnShow()') + -- pop this on principle + self:Refresh() +end +function QuestPOI:OnHide() + --qprint('|cFFFFFF00["'..tostring(self.title)..'"]|r:OnHide()') +end +function QuestPOI:Refresh () + print('|cFF00FF88["'..tostring(self.title)..'"]|r:Refresh()', tostring(self.title), "|T"..tostring(self.itemTexture)..":12:12|t", tostring(self.itemName)) + qprint(self.style) + + local questID = self.questId + local style = self.style + local borderMask = style.mask + local borderFill = style.texture + local iconBorder = self.iconBorder + local icon = self.icon + local count = self.count + + --WorldPlan:print(tostring(self.title), "|T"..tostring(self.itemTexture)..":16:16|t", tostring(self.itemName)) + SetMaskedTexture(icon, self.itemTexture or ICON_UNKNOWN, borderMask) + icon:SetAllPoints(self) + + if self.itemName then + local color = self.rewardColor or COMMON_COLOR + if self.itemNumber and self.target then + self.count:SetText(color.hex .. tostring(self.itemNumber)) + else + self.count:SetText(nil) + end + end + + SetMaskedTexture(iconBorder, borderFill, borderMask) + local border = (self.rewardType and POI_REWARD_TYPE[self.rewardType]) or (WORLD_QUEST_QUALITY_COLORS[self.rarity] or COMMON_COLOR) + iconBorder:SetVertexColor(border.r, border.g, border.b, border.a) + iconBorder:SetDesaturated(true) + + local trackingBorder = self.supertrackBorder + + self.highlight:SetMask(nil) + if questID == GetSuperTrackedQuestID() then + trackingBorder:SetVertexColor(0,0,0,1) + else + trackingBorder:SetVertexColor(0,0,0,0.5) + end + self.highlight:SetAllPoints(trackingBorder) + + SetMaskedTexture(trackingBorder, borderFill, borderMask) + self.highlight:SetMask(borderMask) + + local qType = self.worldQuestType + self.tagIcon:SetAtlas(self.tagAtlas) + self.tagIcon:SetTexCoord(0,1,0,1) + + + if self.isElite then + self.EliteDecal:Show() + else + self.EliteDecal:Hide() + end + + if style.showNumber then + self.label:SetText(self.itemNumber) + else + self.label:SetText(nil) + end + qprint('|cFF88FF00updated', questID, self.title, self.rewardType, (style.showNumber and self.itemNumber) or '') +end + +function QuestPOI:SetAnchor(frame, mapID, mapWidth, mapHeight) + self:ClearAllPoints() + local dX, dY = TQ_GetQuestLocation(self.questID, mapID) + if not dX or dX == 0 then + local _, x, y = QuestPOIGetIconInfo(self.questID) + if x and floor(x) ~= 0 then + dX, dY = x, y + else + dX, dY = self.x, self.y + end + end + self.x = dX + self.y = dY + + print(' |cFF00FF00'..self.questID..':|r', format("%0.2f %0.2f", dX, dY)) + + local pX = (dX * mapWidth) + local pY = (-dY * mapHeight) + + self:SetParent(WorldMapPOIFrame) + self:SetPoint('CENTER', frame, 'TOPLEFT', pX, pY) +end + + +function QuestPOI:IsShowable (ignoreFilters) + local qType = self.worldQuestType + local rType = self.rewardType + self.filtered = nil + print(' |cFFFF4400IsShowable()|r', self.tradeskillLineIndex, self.title) + + local whiteListed, blackListed + for filterKey, includes in pairs(FilterInclusions) do + local controlValue = self[filterKey] + if FilterInclusions[filterKey][controlValue] then + whiteListed = true + break + end + if FilterExclusions[filterKey][controlValue] then + blackListed = true + end + end + print(blackListed, whiteListed) + self.filtered = (blackListed and (not whiteListed)) + + if not TQ_IsActive(self.questID) then + print(' quest is dead') + return false + end + if qType == LE_QUEST_TAG_TYPE_PROFESSION then + + if not (ShowAllProfessionQuests or (self.tradeskillLineIndex and GetProfessionInfo(self.tradeskillLineIndex))) then + print(' non-profession') + return false + end + end + return true +end + +function QuestPOI:UpdateTimer (timeLeft, timeType) + print('|cFF0088FFUpdatePinTimer()|r') +end + +--- Re-acquires size information and triggers things +function QuestPOI:GetSizeMetrics (style, subStyle) + self.style = self.style or POI_DEFAULT_TYPE + self.subStyle = self.subStyle or 'continent' + + style = style or self.style + subStyle = style[subStyle or self.subStyle] + + + self.currentWidth = subStyle.PinSize + self.borderSize = subStyle.Border + self.trackingBorderSize = subStyle.TrackingBorder + self.tagSize = subStyle.TagSize + self.TimeleftStage = subStyle.TimeleftStage + self.NoIcon = subStyle.NoIcon + + self:SetSize(self.currentWidth, self.currentWidth) +end + +-- triggered by OnSizeChanged script +function QuestPOI:OnSizeChanged () + local iconBorder = self.iconBorder + local trackingBorder = self.supertrackBorder + local tag = self.tagIcon + local style = self.style or POI_DEFAULT_TYPE + local mask = style.mask or POI_BORDER_FILL + + self.icon:SetMask(nil) + self.iconBorder:SetMask(nil) + self.supertrackBorder:SetMask(nil) + + + local borderWidth = self.borderSize + local trackingWidth = self.trackingBorderSize + + iconBorder:ClearAllPoints() + iconBorder:SetPoint('BOTTOMLEFT', self, 'BOTTOMLEFT', -borderWidth + (style.x or 0), -borderWidth + (style.y or 0)) + iconBorder:SetPoint('TOPRIGHT', self, 'TOPRIGHT', borderWidth + (style.x or 0), borderWidth + (style.y or 0)) + + trackingBorder:ClearAllPoints() + trackingBorder:SetPoint('BOTTOMLEFT', iconBorder, 'BOTTOMLEFT', -trackingWidth, -trackingWidth) + trackingBorder:SetPoint('TOPRIGHT', iconBorder, 'TOPRIGHT', trackingWidth, trackingWidth) + + if self.tagSize then + tag:Show() + tag:ClearAllPoints() + tag:SetPoint('BOTTOMRIGHT', self, 'BOTTOMRIGHT', borderWidth, -borderWidth) + else + tag:Hide() + end + + self.icon:SetMask(mask) + self.iconBorder:SetMask(mask) + self.supertrackBorder:SetMask(mask) + + if self.NoIcon then + self.icon:Hide() + else + self.icon:Show() + end + +end + + + +function FilterBar:OnEvent(event) + if event == 'QUEST_LOG_UPDATE' then + self:Refresh() + end +end + +function FilterBar:PassesFilterSet(filterKey, pin) + local passesFilter = true + for filterKey, filters in pairs(QuestFilters) do + for rewardType, value in pairs(QuestFilters[filterKey]) do + if value == 1 and rewardType == pin[filterKey] then + passesFilter = true + elseif value == -1 and rewardType == pin[filterKey] then + passesFilter = false + end + end + end + return passesFilter +end + +local debug_headers = {} +function FilterBar:Refresh() + local mapID = GetCurrentMapAreaID() + local blocks = self.SummaryHeaders + local numHeaders = 0 + local lastButton + local pinSize, borderSize, trackingSize = 20, 2, 2 + print('|cFF00FF88'..tostring(self)..':Refresh()|r') + for index, info in pairs(POI_FILTER_OPTIONS) do + local numQuests = 0 + local quests = QuestsByZone[mapID] or QuestsByID + + for questID, pin in pairs(quests) do + if pin.used then + if not info.filterKey then + numQuests = numQuests + 1 + elseif pin[info.filterKey] == info.filterValue then + numQuests = numQuests + 1 + end + end + end + print(tostring(index).. ' ("'..tostring(info.label)..'"'.. tostring(rewardFilter).. ', '..tostring(tagFilter) .. ', '..tostring(numQuests)..')') + + if numQuests >= 1 then + numHeaders = numHeaders + 1 + local button = blocks[numHeaders] + if not blocks[numHeaders] then + button = CreateFrame('Button', 'WorldPlanFilterButton'..numHeaders, WorldMapScrollFrame, 'WorldPlanFilterPin') + button.iconBorder:SetTexture(info.fill or POI_BORDER_FILL) + button.iconBorder:SetMask(info.mask or POI_BORDER_MASK) + button.iconBorder:SetDesaturated(info.desaturated) + button.supertrackBorder:SetTexture(info.fill or POI_BORDER_FILL) + button.supertrackBorder:SetMask(info.mask or POI_BORDER_MASK) + button.supertrackBorder:SetDesaturated(true) + blocks[numHeaders] = button + end + + button:SetID(index) + button.lastButton = lastButton + button:Refresh(info, (numHeaders == 1), numQuests) + button:Show() + lastButton = button + end + + end + -- hide trailing buttons + for i = numHeaders + 1, #POI_FILTER_OPTIONS do + if blocks[i] then + blocks[i]:Hide() + end + end +end + + +function FilterPin:Refresh(info, isFirst, numQuests) + info = info or POI_FILTER_OPTIONS[self:GetID()] + isFirst = isFirst or self.isFirst + numQuests = numQuests or self.numQuests + + self.isFirst = isFirst + self.numQuests = numQuests + self.filterKey = info.filterKey + self.filterValue = info.filterValue + self.tagID = info.tagID + + self.icon:ClearAllPoints() + self.icon:SetTexture(info.texture) + self.icon:SetAllPoints(self) + self.supertrackBorder:Hide() + self.label:SetText(numQuests) + self:Show() + + + if isFirst then + self:SetPoint('TOP', WorldMapFrame.UIElementsFrame.TrackingOptionsButton, 'BOTTOM', 0, -5) + else + self:SetPoint('TOPRIGHT', self.lastButton, 'BOTTOMRIGHT', 0, -(3*2 + 1 + (info.spacing or 0))) + end + + print(self.filterKey, self.filterValue) + local r, g, b, a = 1,1,1,1 + if self.filterKey then + if FilterInclusions[self.filterKey][self.filterValue] == true then + r, g, b = 0, 1, 0 + elseif FilterExclusions[self.filterKey][self.filterValue] then + r, g, b = 1, 0, 0 + end + end + self.iconBorder:SetVertexColor(r, g, b, a) + + + self:GetSizeMetrics() +end + +function FilterPin:OnLoad() + self:RegisterForClicks('AnyUp') + self:SetFrameStrata('HIGH') + self:SetFrameLevel(151) + self:SetScript('OnUpdate', nil) +end + +function FilterPin:OnUpdate () + +end + +function FilterPin:OnEnter () + local filter = POI_FILTER_OPTIONS[self:GetID()] + local mapID = GetCurrentMapAreaID() + local quests = (mapID == BROKEN_ISLES_ID) and QuestsByID or QuestsByZone[mapID] + if quests then + GameTooltip:SetOwner(self, 'ANCHOR_RIGHT') + GameTooltip:AddLine(filter.label) + local filterKey = self.filterKey + local filterValue = self.filterValue + if filterKey then + for questID, pin in pairs(quests) do + if pin.used and not pin.filtered then + if pin[filterKey] == filterValue then + local style = pin.style or POI_FILTER_STYLE + GameTooltip:AddLine(pin.title) + end + end + end + else + GameTooltip:AddLine('Reset all filters') + end + GameTooltip:Show() + end +end +function FilterPin:OnLeave () + if GameTooltip:IsOwned(self) then + GameTooltip:Hide() + end +end + +-- shift-click: reset filter +-- click: rotate through include(1), exclude(-1), ignore(nil) +function FilterPin:OnClick (button) + local filterKey = self.filterKey + local filterValue = self.filterValue + + print('click', filterKey, filterValue) + + if not filterKey then + -- resetting + for k,v in pairs(FilterInclusions) do + wipe(v) + end + for k,v in pairs(FilterExclusions) do + wipe(v) + end + else + local setExclude = (button == 'RightButton') + if IsControlKeyDown() then + if setExclude then + if FilterExclusions[filterKey][filterValue] then + FilterExclusions[filterKey][filterValue] = nil + else + FilterExclusions[filterKey][filterValue] = true + end + FilterInclusions[filterKey][filterValue] = nil + else + if FilterInclusions[filterKey][filterValue] == true then + FilterInclusions[filterKey][filterValue] = nil + else + FilterInclusions[filterKey][filterValue] = true + end + FilterExclusions[filterKey][filterValue] = nil + end + elseif IsShiftKeyDown() then + FilterInclusions[filterKey][filterValue] = nil + FilterExclusions[filterKey][filterValue] = nil + else + print('limit to', filterKey, filterValue) + if setExclude then + FilterExclusions[filterKey][filterValue] = true + FilterInclusions[filterKey][filterValue] = nil + else + FilterInclusions[filterKey][filterValue] = true + FilterExclusions[filterKey][filterValue] = nil + end + + for k, info in ipairs(POI_FILTER_OPTIONS) do + if info.filterKey and ((info.filterKey ~= filterKey) or (info.filterValue ~= filterValue)) then + if setExclude then + FilterExclusions[info.filterKey][info.filterValue] = nil + FilterInclusions[info.filterKey][info.filterValue] = true + else + FilterExclusions[info.filterKey][info.filterValue] = true + FilterInclusions[info.filterKey][info.filterValue] = nil + end + + end + end + + end + end + + + WorldPlan:RefreshAll() +end + +--%debug% +local SetTimedCallbackForAllPins = function(seconds, callback) + C_Timer.After(seconds, function() + for id, pin in pairs(QuestsByID) do + callback(pin) + end + end) +end + +SLASH_WORLDPLAN1 = "/worldplan" +SLASH_WORLDPLAN2 = "/wp" +SlashCmdList.WORLDPLAN = function() + print('command pop') + WorldPlan:GetPinsForMap() + WorldPlan:RefreshPins() + + SetTimedCallbackForAllPins(0, function(self) self.FadeIn:Play() self.FlashIn:Play() end) + SetTimedCallbackForAllPins(5, function(self) self.PendingFade:Play() end) + SetTimedCallbackForAllPins(8, function(self) self.PendingFade:Stop() end) +end +--%end-debug% + +for mapID, mapName in pairs(WORLD_QUEST_MAPS) do + QuestsByZone[mapID] = {} +end +for index, color in pairs(ITEM_QUALITY_COLORS) do + POI_REWARD_TYPE[index] = { + r = color.r, g = color.g, b = color.b, + hex = color.hex, + } +end diff -r 000000000000 -r 3830a592cb0f WorldPlan.toc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WorldPlan.toc Tue Oct 11 17:20:37 2016 -0400 @@ -0,0 +1,12 @@ +## Interface: 70000 +## Title: WorldPlan +## Notes: World Quest planning helper +## Author: Krakyn +## Version: 1.0-@project-revision@ +## SavedVariables: WorldPlanData +## X-Category: Interface Enhancements +## DefaultState: Enabled +## LoadOnDemand: 0 +## Dependencies: Veneer + +WorldPlan.xml \ No newline at end of file diff -r 000000000000 -r 3830a592cb0f WorldPlan.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WorldPlan.xml Tue Oct 11 17:20:37 2016 -0400 @@ -0,0 +1,165 @@ + + +