annotate WorldQuests.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 0100d923d8c3
children 21bcff08b0f4
rev   line source
Nenue@33 1 -- WorldPlan
Nenue@33 2 -- WorldQuests.lua
Nenue@33 3 -- Created: 11/2/2016 3:40 PM
Nenue@33 4 -- %file-revision%
Nenue@33 5
Nenue@35 6 WorldPlanDataProvider = {}
Nenue@35 7 WorldPlanDataPinMixin = {}
Nenue@33 8 WorldPlanQuestsMixin = {
Nenue@33 9 QuestsByZone = {},
Nenue@33 10 QuestsByID = {},
Nenue@33 11 freePins = {},
Nenue@33 12 }
Nenue@33 13 local WorldQuests = WorldPlanQuestsMixin
Nenue@33 14
Nenue@33 15 local MC_GetNumZones, MC_GetZoneInfo = C_MapCanvas.GetNumZones, C_MapCanvas.GetZoneInfo
Nenue@33 16 local TQ_GetQuestsForPlayerByMapID = C_TaskQuest.GetQuestsForPlayerByMapID -- This function is not yet documented
Nenue@33 17 local TQ_GetQuestZoneID = C_TaskQuest.GetQuestZoneID
Nenue@33 18 local GetMapInfo = GetMapInfo
Nenue@33 19 local print = DEVIAN_WORKSPACE and function(...) _G.print('WP', ...) end or function() end
Nenue@33 20 local qprint = DEVIAN_WORKSPACE and function(...) _G.print('POI', ...) end or function() end
Nenue@33 21 local wqprint = DEVIAN_WORKSPACE and function(...) _G.print('WorldQuests', ...) end or function() end
Nenue@33 22 local wprint = DEVIAN_WORKSPACE and function(...) _G.print('WP', ...) end or function() end
Nenue@35 23 local mprint = DEVIAN_WORKSPACE and function(...) _G.print('Canvas', ...) end or function() end
Nenue@33 24
Nenue@33 25
Nenue@33 26 local PinBaseIndex = 1600
Nenue@33 27 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
Nenue@33 28 local WORLD_QUEST_MAPS = { [DALARAN_ID] = 'Dalaran70', [AZSUNA_ID] = 'Azsuna', [VALSHARAH_ID] = "Val'sharah",
Nenue@33 29 [HIGHMOUNTAIN_ID] = 'Highmountain', [STORMHEIM_ID] = 'Stormheim', [SURAMAR_ID] = 'Suramar', [EOA_ID] = 'EyeOfAszhara', }
Nenue@33 30
Nenue@33 31 local REWARD_CASH = WORLD_QUEST_REWARD_TYPE_FLAG_GOLD
Nenue@33 32 local REWARD_ARTIFACT_POWER = WORLD_QUEST_REWARD_TYPE_FLAG_ARTIFACT_POWER
Nenue@33 33 local REWARD_GEAR = WORLD_QUEST_REWARD_TYPE_FLAG_EQUIPMENT
Nenue@33 34 local REWARD_CURRENCY = WORLD_QUEST_REWARD_TYPE_FLAG_ORDER_RESOURCES
Nenue@33 35 local REWARD_REAGENT = WORLD_QUEST_REWARD_TYPE_FLAG_MATERIALS
Nenue@33 36
Nenue@33 37
Nenue@33 38 local numPins = 0
Nenue@33 39 local ZoneInfo = {}
Nenue@33 40 local NumPinFrames = 1
Nenue@33 41
Nenue@33 42
Nenue@33 43 --%debug%
Nenue@33 44 local SetTimedCallbackForAllPins = function(seconds, callback)
Nenue@33 45 C_Timer.After(seconds, function()
Nenue@33 46 for id, pin in pairs(WorldPlanQuests.QuestsByID) do
Nenue@33 47 callback(pin)
Nenue@33 48 end
Nenue@33 49 end)
Nenue@33 50 end
Nenue@33 51
Nenue@33 52 function WorldQuests:Setup()
Nenue@33 53
Nenue@33 54
Nenue@33 55 for mapID, mapName in pairs(WORLD_QUEST_MAPS) do
Nenue@33 56 self.QuestsByZone[mapID] = {}
Nenue@33 57 end
Nenue@33 58
Nenue@33 59
Nenue@33 60 -- refresh positions any time blizzard does so (i.e. mousewheel zoom)
Nenue@33 61 hooksecurefunc("WorldMapScrollFrame_ReanchorQuestPOIs", function()
Nenue@33 62 self:Refresh(true)
Nenue@33 63 end)
Nenue@33 64
Nenue@33 65 -- hide the original world quest POIs
Nenue@33 66 hooksecurefunc("WorldMap_UpdateQuestBonusObjectives", function()
Nenue@33 67 for i = 1, NUM_WORLDMAP_TASK_POIS do
Nenue@33 68 local button = _G['WorldMapFrameTaskPOI'..i]
Nenue@33 69 if button and button.worldQuest then
Nenue@33 70 button:Hide()
Nenue@33 71 end
Nenue@33 72 end
Nenue@33 73 end)
Nenue@33 74 end
Nenue@33 75 local WorldMapPOIFrame
Nenue@33 76 local defaults = {}
Nenue@33 77 function WorldQuests:OnLoad()
Nenue@33 78 print('|cFF00FF88'..self:GetName()..':OnLoad')
Nenue@33 79
Nenue@33 80 WorldPlan:AddHandler(self, defaults)
Nenue@33 81
Nenue@33 82 local rgbWhite = {1, 1, 1}
Nenue@33 83 WorldPlan:AddTypeInfo(self, REWARD_REAGENT, { r = 0, g = 1, b = 1 })
Nenue@33 84 WorldPlan:AddTypeInfo(self, REWARD_ARTIFACT_POWER, { r = 1, g = .25, b = .5, hasNumeric = true, numberRGB = rgbWhite })
Nenue@33 85 WorldPlan:AddTypeInfo(self, REWARD_GEAR, { r = .1, g = .2, b = 1 })
Nenue@33 86 WorldPlan:AddTypeInfo(self, REWARD_CURRENCY, { r = 1, g = 1, b = 0, hasNumeric = true, numberRGB = {1,1,0}, })
Nenue@33 87 WorldPlan:AddTypeInfo(self, REWARD_CASH, { r = 0, g = 0, b = 0, })
Nenue@33 88
Nenue@33 89 for areaID, fileName in pairs(WORLD_QUEST_MAPS) do
Nenue@33 90 self.QuestsByZone[areaID] = {}
Nenue@33 91 end
Nenue@33 92
Nenue@33 93 self:RegisterEvent('WORLD_QUEST_COMPLETED_BY_SPELL')
Nenue@33 94 self:RegisterEvent('SKILL_LINES_CHANGED')
Nenue@33 95
Nenue@33 96 WorldMapPOIFrame = _G.WorldMapPOIFrame
Nenue@33 97
Nenue@33 98 end
Nenue@33 99
Nenue@33 100 function WorldQuests:OnEvent (event, ...)
Nenue@33 101 local print = wqprint
Nenue@33 102 print('|cFFFFFF00'..self:GetName()..':OnEvent()'..event..'|r', GetTime(), ...)
Nenue@33 103 if event == 'QUEST_LOG_UPDATE' then
Nenue@33 104 local questID, added = ...
Nenue@33 105 if questID and added then
Nenue@33 106 local questPOI = self:AcquirePin(questID)
Nenue@33 107 self.isStale, self.isPending = questPOI:RefreshData()
Nenue@33 108 else
Nenue@33 109 self:RefreshData()
Nenue@33 110 end
Nenue@33 111 print('WorldMapFrame', WorldMapFrame:IsVisible(), 'hasUpdates:', self.isStale)
Nenue@34 112 elseif event == 'WORLD_MAP_UPDATE' or event == 'PLAYER_ENTERING_WORLD' then
Nenue@33 113 self.isStale = true
Nenue@33 114 elseif event == 'WORLD_QUEST_COMPLETED_BY_SPELL' then
Nenue@33 115 local questID = ...
Nenue@33 116 if questID and self.QuestsByID[questID] then
Nenue@33 117 self:ReleasePin(self.QuestsByID[questID])
Nenue@33 118 end
Nenue@33 119 elseif event == 'SKILL_LINES_CHANGED' then
Nenue@33 120 self.isStale = true
Nenue@33 121 end
Nenue@33 122 end
Nenue@33 123
Nenue@33 124 local TQ_GetQuestLocation = C_TaskQuest.GetQuestLocation
Nenue@33 125 function WorldQuests:AcquirePin (questID, mapID)
Nenue@33 126 local pin = self.QuestsByID[questID]
Nenue@33 127 local isNew = false
Nenue@33 128 if not pin then
Nenue@33 129 isNew = true
Nenue@33 130 local numFree = #self.freePins
Nenue@33 131 if numFree >= 1 then
Nenue@33 132 pin = tremove(self.freePins, numFree)
Nenue@33 133 --print('|cFF00FF00Re-using', pin:GetName())
Nenue@33 134 else
Nenue@33 135 local name = 'WorldPlanQuestMarker' .. NumPinFrames
Nenue@33 136 --print('|cFF00FF00Creating', name)
Nenue@33 137 pin = CreateFrame('Frame', name, WorldMapPOIFrame, 'WorldPlanQuestPin')
Nenue@33 138
Nenue@33 139 pin:SetFrameStrata('HIGH')
Nenue@33 140 pin.GetTypeInfo = function(frame, typeID)
Nenue@33 141 return self:GetTypeInfo(typeID)
Nenue@33 142 end
Nenue@33 143 NumPinFrames = NumPinFrames + 1
Nenue@33 144 --pin.iconBorder:SetVertexColor(0,0,0,1)
Nenue@33 145 end
Nenue@33 146 pin:SetID(questID)
Nenue@33 147 pin.isNew = true
Nenue@33 148 pin.currentWidth = nil
Nenue@33 149
Nenue@33 150 -- used by TaskPOI_x scripts
Nenue@33 151 pin.questID = questID
Nenue@33 152 pin.worldQuest = true
Nenue@33 153
Nenue@33 154 self.QuestsByID[questID] = pin
Nenue@33 155 else
Nenue@33 156 --print('|cFF00FF00Using', pin:GetName())
Nenue@33 157 end
Nenue@33 158 mapID = mapID or TQ_GetQuestZoneID(questID)
Nenue@33 159 self.QuestsByZone[mapID][questID] = pin
Nenue@33 160
Nenue@33 161 return pin, isNew
Nenue@33 162 end
Nenue@33 163
Nenue@33 164 -- remove from index and add it to the recycling heap
Nenue@33 165 function WorldQuests:ReleasePin (pin)
Nenue@33 166
Nenue@33 167 local id = pin.questId
Nenue@33 168 if id then
Nenue@33 169 self.QuestsByID[id] = nil
Nenue@33 170 for i, zone in pairs(self.QuestsByZone) do
Nenue@33 171 print('-', i, zone[i])
Nenue@33 172 zone[id] = nil
Nenue@33 173 end
Nenue@33 174 end
Nenue@33 175 pin:Hide()
Nenue@33 176 pin:ClearAllPoints()
Nenue@33 177 tinsert(self.freePins, pin)
Nenue@33 178 print('|cFFFF4400Clearing out', pin:GetName(),id)
Nenue@33 179 end
Nenue@33 180
Nenue@33 181 -- create of update quest pins for a map and its underlying zones
Nenue@33 182 function WorldQuests:RefreshData (mapID)
Nenue@33 183 local print = wqprint
Nenue@35 184 mapID = mapID or WorldPlan.currentMapID
Nenue@33 185 if not mapID then
Nenue@33 186 -- info not available yet
Nenue@33 187 return
Nenue@33 188 end
Nenue@33 189
Nenue@34 190
Nenue@34 191 print('|cFF00FF88'..self:GetName()..':RefreshData()|r', 'map:', mapID, 'realMap:', GetCurrentMapAreaID())
Nenue@33 192
Nenue@33 193 if mapID == BROKEN_ISLES_ID then
Nenue@33 194 self.isStale = false
Nenue@33 195 print('|cFF00FFFFContinent:|r', mapID, GetMapNameByID(mapID), superTrackedID)
Nenue@33 196 self.fullSearch = true
Nenue@33 197 for i = 1, MC_GetNumZones(mapID) do
Nenue@33 198 local submapID, name, depth = MC_GetZoneInfo(mapID, i)
Nenue@33 199 self:RefreshData(submapID)
Nenue@33 200 end
Nenue@33 201 self.fullSearch = nil
Nenue@33 202 elseif self.QuestsByZone[mapID] then
Nenue@35 203 local taskInfo = TQ_GetQuestsForPlayerByMapID(mapID, WorldPlan.currentMapID)
Nenue@33 204 local numQuests = 0
Nenue@33 205 if taskInfo and #taskInfo >= 1 then
Nenue@33 206 print('|cFF00FFFF Zone:|r', mapID, GetMapNameByID(mapID), #taskInfo)
Nenue@33 207 wipe(self.QuestsByZone[mapID])
Nenue@33 208 ZoneInfo[mapID] = taskInfo
Nenue@33 209 qprint('|cFFFF4400START of', GetMapNameByID(mapID))
Nenue@33 210 for taskID, info in pairs(taskInfo) do
Nenue@33 211 local questID = info.questId
Nenue@33 212 info.mapID = mapID
Nenue@33 213 local questPOI = self:AcquirePin(questID, mapID)
Nenue@33 214 local hasUpdate, isPending = questPOI:RefreshData(info)
Nenue@34 215 -- WorldPlan:print('|cFF0088FF'..questPOI.title..'|r', hasUpdate)
Nenue@33 216 self.isStale = (self.isStale or hasUpdate)
Nenue@33 217 self.isPending = (self.isPending or isPending)
Nenue@33 218 numQuests = numQuests + 1
Nenue@33 219 end
Nenue@33 220 qprint('|cFFFF4400END of', GetMapNameByID(mapID))
Nenue@33 221 end
Nenue@33 222 end
Nenue@33 223
Nenue@33 224 if not self.fullSearch then
Nenue@33 225 print(' hasUpdate:', self.isStale, 'isPending:', self.isPending, 'timer:', (self.OnNext and 'waiting' or ''))
Nenue@33 226 --WorldPlan.isStale = (self.isStale or WorldPlan.isStale)
Nenue@33 227 end
Nenue@33 228
Nenue@33 229 end
Nenue@33 230
Nenue@35 231 function WorldQuests:Refresh(forced)
Nenue@33 232 local print = wqprint
Nenue@33 233 print('|cFF00FF88'..self:GetName()..':Refresh()|r')
Nenue@34 234 if not self:IsVisible() then
Nenue@34 235 self.isStale = true
Nenue@34 236 print('frame closed, do it later')
Nenue@34 237 return
Nenue@34 238 end
Nenue@34 239
Nenue@33 240 self:Reset()
Nenue@33 241 self:UpdateAnchors()
Nenue@33 242 self:Cleanup ()
Nenue@33 243 end
Nenue@33 244
Nenue@33 245 -- prepares elements for a map update
Nenue@33 246 function WorldQuests:Reset ()
Nenue@33 247 local print = wqprint
Nenue@33 248 print('|cFF00FF88'..self:GetName()..':Reset()|r')
Nenue@33 249 for questID, pin in pairs(self.QuestsByID) do
Nenue@33 250 pin.used = nil
Nenue@33 251 end
Nenue@33 252 end
Nenue@33 253
Nenue@33 254 -- update visibility states of all pins
Nenue@33 255 function WorldQuests:UpdateAnchors (submapID)
Nenue@33 256
Nenue@33 257 local print = wqprint
Nenue@33 258 local db = WorldPlan.db
Nenue@33 259 local mapFileName, textureHeight, textureWidth, isMicroDungeon, microDungeonMapName = GetMapInfo()
Nenue@33 260 if isMicroDungeon then
Nenue@33 261 return
Nenue@33 262 end
Nenue@33 263
Nenue@33 264 local currentMap = GetCurrentMapAreaID()
Nenue@33 265 local submapID = submapID or currentMap
Nenue@33 266
Nenue@33 267 if submapID == BROKEN_ISLES_ID and (not db.DisplayContinentPins) then
Nenue@33 268 print('not updating map for reasons')
Nenue@33 269 return
Nenue@33 270 end
Nenue@33 271 print('|cFF88FF00'..self:GetName()..':UpdateAnchors|r', submapID, GetMapNameByID(submapID), 'pin count:', numPins)
Nenue@33 272 local numZones = MC_GetNumZones(submapID)
Nenue@33 273 if numZones then
Nenue@33 274 for i = 1, numZones do
Nenue@33 275 local subMapID = MC_GetZoneInfo(submapID, i)
Nenue@33 276 self:UpdateAnchors(subMapID)
Nenue@33 277 end
Nenue@33 278 end
Nenue@33 279 local pins = self.QuestsByZone[submapID]
Nenue@33 280
Nenue@33 281 if pins then
Nenue@33 282 local hostFrame = WorldMapPOIFrame
Nenue@33 283 local mapWidth, mapHeight = hostFrame:GetSize()
Nenue@33 284 for questID, pin in pairs(pins) do
Nenue@33 285 pin:IsShowable()
Nenue@33 286 if pin.used then
Nenue@35 287 pin.isStale = true
Nenue@35 288 pin:SetFrameLevel(PinBaseIndex+ numPins)
Nenue@35 289 print('level', PinBaseIndex+ numPins)
Nenue@34 290 pin:SetAnchor(_G.WorldMapPOIFrame, currentMap, mapWidth, mapHeight)
Nenue@33 291 numPins = numPins + 1
Nenue@33 292 end
Nenue@33 293 end
Nenue@33 294 end
Nenue@33 295 end
Nenue@33 296
Nenue@33 297 -- shows, animates, or hides pins based on their current visibility flags
Nenue@33 298 local debug_show = {}
Nenue@33 299 local debug_animate = {}
Nenue@33 300 local debug_hide = {}
Nenue@33 301 function WorldQuests:Cleanup ()
Nenue@33 302 local print = wqprint
Nenue@34 303 local showQuestPOI = WorldPlan.db.EnablePins
Nenue@35 304 print('|cFFFFFF00'..self:GetName()..':Cleanup()|r')
Nenue@34 305 -- continent or zone sizing
Nenue@33 306
Nenue@33 307
Nenue@33 308 numPins = 0
Nenue@33 309 for questID, pin in pairs(self.QuestsByID) do
Nenue@34 310 pin:SetShown((showQuestPOI and pin.used))
Nenue@33 311 end
Nenue@33 312 self.isStale = nil
Nenue@33 313 end
Nenue@33 314
Nenue@33 315 function WorldQuests:FilterCheckByID(questID)
Nenue@33 316 local pin = WorldQuests:GetPinByQuestID(questID)
Nenue@33 317 return pin:IsShowable()
Nenue@33 318 end
Nenue@35 319
Nenue@35 320
Nenue@35 321
Nenue@35 322
Nenue@35 323 function WorldPlanDataProvider:OnShow()
Nenue@35 324 assert(self.ticker == nil);
Nenue@35 325 self.ticker = C_Timer.NewTicker(10, function() self:RefreshAllData() end);
Nenue@35 326 end
Nenue@35 327 function WorldPlanDataProvider:OnHide()
Nenue@35 328 self.ticker:Cancel();
Nenue@35 329 self.ticker = nil;
Nenue@35 330 end
Nenue@35 331
Nenue@35 332 function WorldPlanDataProvider:OnAdded(mapCanvas)
Nenue@35 333 self.activePins = {};
Nenue@35 334 self.owningMap = mapCanvas
Nenue@35 335 end
Nenue@35 336
Nenue@35 337
Nenue@35 338
Nenue@35 339 function WorldPlanDataProvider:RefreshAllData()
Nenue@35 340 local print = mprint
Nenue@35 341 print('|cFFFF0088'..self.owningMap:GetName()..':RefreshAllData()|r')
Nenue@35 342
Nenue@35 343
Nenue@35 344 local pinsToRemove = {};
Nenue@35 345 for questId in pairs(self.activePins) do
Nenue@35 346 pinsToRemove[questId] = true;
Nenue@35 347 end
Nenue@35 348
Nenue@35 349 SetMapZoom(8)
Nenue@35 350
Nenue@35 351 local mapAreaID = self:GetMap():GetMapID();
Nenue@35 352 for zoneIndex = 1, C_MapCanvas.GetNumZones(mapAreaID) do
Nenue@35 353 local zoneMapID, zoneName, zoneDepth, left, right, top, bottom = C_MapCanvas.GetZoneInfo(mapAreaID, zoneIndex);
Nenue@35 354 print(zoneMapID, zoneName)
Nenue@35 355 if zoneDepth <= 1 then -- Exclude subzones
Nenue@35 356 local taskInfo = C_TaskQuest.GetQuestsForPlayerByMapID(zoneMapID, mapAreaID);
Nenue@35 357
Nenue@35 358 if taskInfo then
Nenue@35 359 for i, info in ipairs(taskInfo) do
Nenue@35 360 if HaveQuestData(info.questId) then
Nenue@35 361 if QuestUtils_IsQuestWorldQuest(info.questId) then
Nenue@35 362 local pin = WorldPlanQuests:AcquirePin(info.questId, zoneMapID)
Nenue@35 363 pin:RefreshData(info)
Nenue@35 364 pin:IsShowable()
Nenue@35 365 if pin.used then
Nenue@35 366 print(i, pin.x, pin.y, pin.used, pin.isNew, pin.isStale, pin:IsShown(), pin:GetAlpha())
Nenue@35 367 pinsToRemove[info.questId] = nil;
Nenue@35 368
Nenue@35 369 local frame = self.activePins[info.questId]
Nenue@35 370 if not frame then
Nenue@35 371 frame = self:GetMap():AcquirePin("WorldPlanPinContainer")
Nenue@35 372 frame:SetAlphaLimits(1, 0.7, 1)
Nenue@35 373 frame:SetScalingLimits(1, 3, 1.5);
Nenue@35 374 frame:SetFrameLevel(1000 + self:GetMap():GetNumActivePinsByTemplate("WorldPlanPinContainer"));
Nenue@35 375 frame:Show()
Nenue@35 376 self.activePins[info.questId] = frame
Nenue@35 377 end
Nenue@35 378 frame:SetPosition(info.x, info.y)
Nenue@35 379 frame.pin = pin
Nenue@35 380
Nenue@35 381 pin.isStale = true
Nenue@35 382 pin:SetParent(frame)
Nenue@35 383 pin:ClearAllPoints()
Nenue@35 384 pin:SetPoint('CENTER', frame, 'CENTER')
Nenue@35 385
Nenue@35 386 end
Nenue@35 387 pin:SetShown(pin.used)
Nenue@35 388 end
Nenue@35 389 end
Nenue@35 390 end
Nenue@35 391 end
Nenue@35 392 end
Nenue@35 393 end
Nenue@35 394
Nenue@35 395 for questId in pairs(pinsToRemove) do
Nenue@35 396 self:GetMap():RemovePin(self.activePins[questId]);
Nenue@35 397 self.activePins[questId] = nil;
Nenue@35 398 end
Nenue@35 399 self:GetMap():RemoveAllPinsByTemplate("WorldQuestPinTemplate");
Nenue@35 400
Nenue@35 401 end
Nenue@35 402
Nenue@35 403 function WorldPlanDataPinMixin:OnShow()
Nenue@35 404 mprint('|cFFFFFF00'..tostring(self:GetName())..':OnShow()|r')
Nenue@35 405 end
Nenue@35 406
Nenue@35 407 function WorldPlanDataPinMixin:OnMouseEnter ()
Nenue@35 408 self.pin:OnEnter()
Nenue@35 409 end
Nenue@35 410
Nenue@35 411 function WorldPlanDataPinMixin:OnMouseLeave ()
Nenue@35 412 self.pin:OnLeave()
Nenue@35 413 end
Nenue@35 414
Nenue@35 415 function WorldPlanDataPinMixin:RemoveAllData()
Nenue@35 416 wipe(self.activePins);
Nenue@35 417 self:GetMap():RemoveAllPinsByTemplate("WorldQuestPinTemplate");
Nenue@35 418 end