update changes
[QuestHelper.git] / pathfinding.lua
blobab5bd3a565e31df294193374af76d9c507ee7b46
1 QuestHelper_File["pathfinding.lua"] = "Development Version"
3 local IRONFORGE_PORTAL = {25,0.255,0.084, "Ironforge portal site"}
4 local STORMWIND_CITY_PORTAL = QuestHelper_ConvertCoordsFromWrath(QuestHelper_ConvertCoordsToWrath({36,0.387,0.802, "Stormwind City portal site"}, true)) -- The annoying "wrath" construction here first forces it into Wrath format, then puts it into Native format.
5 local DARNASSUS_PORTAL = {21,0.397,0.824, "Darnassus portal site"}
6 local EXODAR_PORTAL = {12,0.476,0.598, "Exodar portal site"}
8 local SHATTRATH_CITY_PORTAL = {60,0.530,0.492, "Shattrath City portal site"}
9 local MOONGLADE_PORTAL = {20,0.563,0.320, "Moonglade portal site"}
11 local SILVERMOON_CITY_PORTAL = {52,0.583,0.192, "Silvermoon City portal site"}
12 local UNDERCITY_PORTAL = {45,0.846,0.163, "Undercity portal site"}
13 local ORGRIMMAR_PORTAL = {1,0.386,0.859, "Orgrimmar portal site"}
14 local THUNDER_BLUFF_PORTAL = {23,0.222,0.168, "Thunder Bluff portal site"}
16 local static_horde_routes =
18 {{7, 0.505, 0.124}, {38, 0.313, 0.303}, 210}, -- Durotar <--> Grom'gol Base Camp
19 {{38, 0.316, 0.289}, {43, 0.621, 0.591}, 210}, -- Grom'gol Base Camp <--> Tirisfal Glades
20 {{43, 0.605, 0.587}, {7, 0.509, 0.141}, 210}, -- Tirisfal Glades <--> Durotar
21 {{45, 0.549, 0.11}, {52, 0.495, 0.148}, 5}, -- Undercity <--> Silvermoon City
23 {{7, 0.413, 0.178}, {65, 0.414, 0.536}, 5}, -- Durotar <--> Warsong Hold
24 {{43, 0.591, 0.590}, {70, 0.777, 0.283}, 5}, -- Tirisfal Glades <--> Vengeance Landing
26 {{60, 0.592, 0.483}, SILVERMOON_CITY_PORTAL, 5, true, nil, "SILVERMOON_CITY_PORTAL"}, -- Shattrath City --> Silvermoon City
27 {{60, 0.528, 0.531}, THUNDER_BLUFF_PORTAL, 5, true, nil, "THUNDER_BLUFF_PORTAL"}, -- Shattrath City --> Thunder Bluff
28 {{60, 0.522, 0.529}, ORGRIMMAR_PORTAL, 5, true, nil, "ORGRIMMAR_PORTAL"}, -- Shattrath City --> Orgrimmar
29 {{60, 0.517, 0.525}, UNDERCITY_PORTAL, 5, true, nil, "UNDERCITY_PORTAL"} -- Shattrath City --> Undercity
33 local static_alliance_routes =
35 {{36, 0.639, 0.083}, {25, 0.764, 0.512}, 120}, -- Deeprun Tram
36 {{10, 0.718, 0.565}, {51, 0.047, 0.636}, 210}, -- Theramore Isle <--> Menethil Harmor
38 {QuestHelper_ConvertCoordsFromWrath({36, 0.183, 0.255}), {65, 0.597, 0.694}, 210}, -- Stormwind City <--> Valiance Keep (note: new Stormwind location)
39 {{51, 0.047, 0.571}, {70, 0.612, 0.626}, 210}, -- Menethil <--> Daggercap Bay
41 {{60, 0.558, 0.366}, STORMWIND_CITY_PORTAL, 5, true, nil, "STORMWIND_CITY_PORTAL"}, -- Shattrath City --> Stormwind City
42 {{60, 0.563, 0.37}, IRONFORGE_PORTAL, 5, true, nil, "IRONFORGE_PORTAL"}, -- Shattrath City --> Ironforge
43 {{60, 0.552, 0.364}, DARNASSUS_PORTAL, 5, true, nil, "DARNASSUS_PORTAL"}, -- Shattrath City --> Darnassus
44 {{60, 0.596, 0.467}, EXODAR_PORTAL, 5, true, nil, "EXODAR_PORTAL"} -- Shattrath City --> Exodar
47 if QuestHelper:IsWrath() then -- siiiiigh
48 table.insert(static_alliance_routes, {{36, 0.228, 0.560}, {16, 0.323, 0.441}, 210}) -- Stormwind City <--> Auberdine (note: new Stormwind location)
49 else
50 table.insert(static_alliance_routes, {{51, 0.044, 0.569}, {16, 0.323, 0.441}, 210}) -- Menethil Harbor <--> Auberdine
51 end
53 local static_shared_routes =
55 {{11, 0.638, 0.387}, {38, 0.257, 0.73}, 210}, -- Ratchet <--> Booty Bay
56 {{40, 0.318, 0.503}, {32, 0.347, 0.84}, 130}, -- Burning Steppes <--> Searing Gorge
58 -- More Alliance routes than anything, but without them theres no valid path to these areas for Horde characters.
59 {{24, 0.559, 0.896}, {21, 0.305, 0.414}, 5}, -- Rut'Theran Village <--> Darnassus
60 {{16, 0.332, 0.398}, {24, 0.548, 0.971}, 210}, -- Auberdine <--> Rut'Theran Village
61 {{16, 0.306, 0.409}, {3, 0.2, 0.546}, 210}, -- Auberdine <--> Azuremyst Isle
63 -- Route to new zone. Not valid, exists only to keep routing from exploding if you don't have the flight routes there.
64 {{41, 0.5, 0.5}, {64, 0.5, 0.5}, 7200}, -- Eversong Woods <--> Sunwell
66 {{70, 0.235, 0.578}, {68, 0.496, 0.784}, 210}, -- Kamagua <--> Moa'ki
67 {{65, 0.789, 0.536}, {68, 0.480, 0.787}, 210}, -- Unu'pe <--> Moa'ki
70 -- Darkportal is handled specially, depending on whether or not you're level 58+ or not.
71 local dark_portal_route = {{33, 0.587, 0.599}, {56, 0.898, 0.502}, 5}
73 local static_zone_transitions =
75 {2, 11, 0.687, 0.872}, -- Ashenvale <--> The Barrens
76 {2, 6, 0.423, 0.711}, -- Ashenvale <--> Stonetalon Mountains
77 {2, 15, 0.954, 0.484}, -- Ashenvale <--> Azshara
78 {2, 16, 0.289, 0.144}, -- Ashenvale <--> Darkshore
79 {2, 13, 0.557, 0.29}, -- Ashenvale <--> Felwood
80 {21, 24, 0.894, 0.358}, -- Darnassus <--> Teldrassil
81 {22, 11, 0.697, 0.604}, -- Mulgore <--> The Barrens
82 {22, 23, 0.376, 0.33}, -- Mulgore <--> Thunder Bluff
83 {22, 23, 0.403, 0.193}, -- Mulgore <--> Thunder Bluff
84 {3, 12, 0.247, 0.494}, -- Azuremyst Isle <--> The Exodar
85 {3, 12, 0.369, 0.469}, -- Azuremyst Isle <--> The Exodar
86 {3, 9, 0.42, 0.013}, -- Azuremyst Isle <--> Bloodmyst Isle
87 {4, 6, 0.539, 0.032}, -- Desolace <--> Stonetalon Mountains
88 {4, 17, 0.428, 0.976}, -- Desolace <--> Feralas
89 {5, 18, 0.865, 0.115}, -- Silithus <--> Un'Goro Crater
90 {7, 11, 0.341, 0.424}, -- Durotar <--> The Barrens
91 {7, 1, 0.455, 0.121}, -- Durotar <--> Orgrimmar
92 {8, 18, 0.269, 0.516}, -- Tanaris <--> Un'Goro Crater
93 {8, 14, 0.512, 0.21}, -- Tanaris <--> Thousand Needles
94 {10, 11, 0.287, 0.472}, -- Dustwallow Marsh <--> The Barrens
95 {10, 11, 0.563, 0.077}, -- Dustwallow Marsh <--> The Barrens
96 {11, 14, 0.442, 0.915}, -- The Barrens <--> Thousand Needles
97 {13, 19, 0.685, 0.06}, -- Felwood <--> Winterspring
98 {13, 20, 0.669, -0.063}, -- Felwood <--> Moonglade
99 {1, 11, 0.118, 0.69}, -- Orgrimmar <--> The Barrens
100 {17, 14, 0.899, 0.46}, -- Feralas <--> Thousand Needles
101 {6, 11, 0.836, 0.973}, -- Stonetalon Mountains <--> The Barrens
102 {26, 48, 0.521, 0.7}, -- Alterac Mountains <--> Hillsbrad Foothills
103 {26, 35, 0.173, 0.482}, -- Alterac Mountains <--> Silverpine Forest
104 {26, 50, 0.807, 0.347}, -- Alterac Mountains <--> Western Plaguelands
105 {39, 51, 0.454, 0.89}, -- Arathi Highlands <--> Wetlands
106 {39, 48, 0.2, 0.293}, -- Arathi Highlands <--> Hillsbrad Foothills
107 {27, 29, 0.49, 0.071}, -- Badlands <--> Loch Modan
108 {27, 32, -0.005, 0.636}, -- Badlands <--> Searing Gorge
109 {33, 46, 0.519, 0.051}, -- Blasted Lands <--> Swamp of Sorrows
110 {40, 30, 0.79, 0.842}, -- Burning Steppes <--> Redridge Mountains
111 {47, 31, 0.324, 0.363}, -- Deadwind Pass <--> Duskwood
112 {47, 46, 0.605, 0.41}, -- Deadwind Pass <--> Swamp of Sorrows
113 {28, 25, 0.534, 0.349}, -- Dun Morogh <--> Ironforge
114 {28, 29, 0.863, 0.514}, -- Dun Morogh <--> Loch Modan
115 {28, 29, 0.844, 0.31}, -- Dun Morogh <--> Loch Modan
116 {31, 37, 0.801, 0.158}, -- Duskwood <--> Elwynn Forest
117 {31, 37, 0.15, 0.214}, -- Duskwood <--> Elwynn Forest
118 {31, 38, 0.447, 0.884}, -- Duskwood <--> Stranglethorn Vale
119 {31, 38, 0.209, 0.863}, -- Duskwood <--> Stranglethorn Vale
120 {31, 30, 0.941, 0.103}, -- Duskwood <--> Redridge Mountains
121 {31, 49, 0.079, 0.638}, -- Duskwood <--> Westfall
122 {34, 50, 0.107, 0.726}, -- Eastern Plaguelands <--> Western Plaguelands
123 {34, 44, 0.625, 0.03}, -- Eastern Plaguelands <--> Ghostlands
124 {37, 36, 0.321, 0.493}, -- Elwynn Forest <--> Stormwind City -- Don't need to convert because it's in Elwynn coordinates, not Stormwind coordinates
125 {37, 49, 0.202, 0.804}, -- Elwynn Forest <--> Westfall
126 {37, 30, 0.944, 0.724}, -- Elwynn Forest <--> Redridge Mountains
127 {41, 52, 0.567, 0.494}, -- Eversong Woods <--> Silvermoon City
128 {41, 44, 0.486, 0.916}, -- Eversong Woods <--> Ghostlands
129 {35, 43, 0.678, 0.049}, -- Silverpine Forest <--> Tirisfal Glades
130 {42, 50, 0.217, 0.264}, -- The Hinterlands <--> Western Plaguelands
131 {43, 45, 0.619, 0.651}, -- Tirisfal Glades <--> Undercity
132 {43, 50, 0.851, 0.703}, -- Tirisfal Glades <--> Western Plaguelands
133 {38, 49, 0.292, 0.024}, -- Stranglethorn Vale <--> Westfall
134 {48, 35, 0.137, 0.458}, -- Hillsbrad Foothills <--> Silverpine Forest
135 {48, 42, 0.899, 0.253}, -- Hillsbrad Foothills <--> The Hinterlands
136 {29, 51, 0.252, 0}, -- Loch Modan <--> Wetlands
138 -- These are just guesses, since I haven't actually been to these areas.
139 {58, 60, 0.783, 0.545}, -- Nagrand <--> Shattrath City
140 {60, 55, 0.782, 0.492}, -- Shattrath City <--> Terokkar Forest
141 {54, 59, 0.842, 0.284}, -- Blade's Edge Mountains <--> Netherstorm
142 {54, 57, 0.522, 0.996}, -- Blade's Edge Mountains <--> Zangarmarsh
143 {54, 57, 0.312, 0.94}, -- Blade's Edge Mountains <--> Zangarmarsh
144 {56, 55, 0.353, 0.901}, -- Hellfire Peninsula <--> Terokkar Forest
145 {56, 57, 0.093, 0.519}, -- Hellfire Peninsula <--> Zangarmarsh
146 {58, 55, 0.8, 0.817}, -- Nagrand <--> Terokkar Forest
147 {58, 57, 0.343, 0.159}, -- Nagrand <--> Zangarmarsh
148 {58, 57, 0.754, 0.331}, -- Nagrand <--> Zangarmarsh
149 {53, 55, 0.208, 0.271}, -- Shadowmoon Valley <--> Terokkar Forest
150 {55, 57, 0.341, 0.098}, -- Terokkar Forest <--> Zangarmarsh
152 -- Most of these are also guesses :)
153 {65, 72, 0.547, 0.059}, -- Borean Tundra <--> Sholazar Basin
154 {65, 68, 0.967, 0.359}, -- Borean Tundra <--> Dragonblight
155 {74, 72, 0.208, 0.191}, -- Wintergrasp <--> Sholazar
156 {68, 74, 0.250, 0.410}, -- Dragonblight <--> Wintergrasp
157 {68, 71, 0.359, 0.155}, -- Dragonblight <--> Icecrown
158 {68, 66, 0.612, 0.142}, -- Dragonblight <--> Crystalsong
159 {68, 75, 0.900, 0.200}, -- Dragonblight <--> Zul'Drak
160 {68, 69, 0.924, 0.304}, -- Dragonblight <--> Grizzly Hills
161 {68, 69, 0.931, 0.634}, -- Dragonblight <--> Grizzly Hills
162 {70, 69, 0.540, 0.042}, -- Howling Fjord <--> Grizzly Hills
163 {70, 69, 0.233, 0.074}, -- Howling Fjord <--> Grizzly Hills
164 {70, 69, 0.753, 0.060}, -- Howling Fjord <--> Grizzly Hills
165 {69, 75, 0.432, 0.253}, -- Grizzly Hills <--> Zul'Drak
166 {69, 75, 0.583, 0.136}, -- Grizzly Hills <--> Zul'Drak
167 {66, 75, 0.967, 0.599}, -- Crystalsong <--> Zul'Drak
168 {66, 71, 0.156, 0.085}, -- Crystalsong <--> Icecrown
169 {66, 73, 0.706, 0.315}, -- Crystalsong <--> Storm Peaks
170 {66, 73, 0.839, 0.340}, -- Crystalsong <--> Storm Peaks
171 {71, 73, 0.920, 0.767}, -- Icecrown <--> Storm Peaks
174 local walkspeed_multiplier = 1/7 -- Every yard walked takes this many seconds.
176 QuestHelper.prepared_objectives = {}
177 QuestHelper.named_nodes = {}
179 local function cont_dist(a, b)
180 local x, y = a.x-b.x, a.y-b.y
181 return math.sqrt(x*x+y*y)
184 function QuestHelper:ComputeRoute(p1, p2)
185 if not p1 or not p2 then QuestHelper:Error("Boom!") end
186 local graph = self.world_graph
188 graph:PrepareSearch()
190 local l = p2[2]
191 local el = p2[1]
192 for i, n in ipairs(el) do
193 n.e, n.w = l[i], 1
194 n.s = 3
197 l = p1[2]
198 for i, n in ipairs(p1[1]) do
199 graph:AddRouteStartNode(n, l[i], el)
202 local e = graph:DoRouteSearch(el)
204 assert(e)
206 local d = e.g+e.e
208 if p1[1] == p2[1] then
209 local x, y = p1[3]-p2[3], p1[4]-p2[4]
210 local d2 = math.sqrt(x*x+y*y)
211 if d2 < d then
212 d = d2
213 e = nil
217 return e, d
220 -- Let's annotate the hell out of this
221 -- ComputeTravelTime finds the shortest path between points p1 and p2. It will cheerfully use route boundaries, ships, etc. It returns the distance of that path, gleefully throwing away the path itself. Thanks, ComputeTravelTime! Thomputetraveltime. (That joke does not work as well in this case.)
222 -- ZORBANOTE: Given that Graph works properly, this does too! Almost - it doesn't actually keep track of the last leg when optimizing, though it does create a valid path with a valid cost. Yaaaaaaaay :(
223 function QuestHelper:ComputeTravelTime(p1, p2)
224 if not p1 or not p2 then QuestHelper:Error("Boom!") end
225 local graph = self.world_graph
227 graph:PrepareSearch()
229 local l = p2[2] -- Distance to zone boundaries in p2
230 local el = p2[1] -- Zone object for the zone that p2 is in
231 for i, n in ipairs(el) do -- i is the zone index, n is the zone data
232 n.e, n.w = l[i], 1 -- n.e is distance from p2 to the current zone boundary, n.w is 1 (weight?)
233 assert(n.e)
234 n.s = 3 -- this is "state", I think it means "visited". TODO: untangle n.s and make it suck less than it currently does
237 l = p1[2] -- Distance to zone boundaries, again
238 for i, n in ipairs(p1[1]) do
239 graph:AddStartNode(n, l[i], el) -- We're adding start nodes - a prebuilt cost of the distance-to-that-zone. Each startnode also contains its own endlist, for reasons unknown yet byzantine. "n" is the zone link itself, l[i] is the cost that we still have stored. Why does this need to be in both n.e and AddStartNode?
242 local e = graph:DoSearch(el)
244 assert(e)
246 local d = e.g+e.e -- e.e is presumably the same n.e we introduced earlier. e.g - graph cost? wait a second - does this mean that the graph system is not taking e.e into account? ha ha no it isn't, oh boy oh boy
248 if p1[1] == p2[1] then -- if they're in the same zone, we allow the user to walk from one point to another
249 local x, y = p1[3]-p2[3], p1[4]-p2[4]
250 d = math.min(d, math.sqrt(x*x+y*y))
253 return d
256 function QuestHelper:CreateGraphNode(c, x, y, n)
257 local node = self.world_graph:CreateNode()
259 if y then
260 node.c = c
261 node.x = x
262 node.y = y
263 node.name = n
264 else
265 if not QuestHelper_ZoneLookup[c[1]] then -- exception for Wrath changeover
266 QuestHelper:Assert(not QuestHelper:IsWrath(), "Zone couldn't be found, and should have been")
267 return
269 local cont, zone = unpack(QuestHelper_ZoneLookup[c[1]])
270 node.c = cont
271 node.x, node.y = self.Astrolabe:TranslateWorldMapPosition(cont, zone, c[2], c[3], cont, 0)
272 node.x = node.x * self.continent_scales_x[node.c]
273 node.y = node.y * self.continent_scales_y[node.c]
274 node.name = c[5] or QuestHelper_NameLookup[c[1]]
277 node.w = 1
278 return node
281 function QuestHelper:CreateAndAddZoneNode(z, c, x, y)
282 local node = self:CreateGraphNode(c, x, y)
283 if not node then return end -- exception for Wrath changeover
284 -- Not going to merge nodes.
285 --[[local closest, travel_time = nil, 0
287 for i, n in ipairs(z) do
288 local t = math.sqrt((n.x-node.x)*(n.x-node.x)+(n.y-node.y)*(n.y-node.y))
289 if not closest or t < travel_time then
290 closest, travel_time = n, t
294 if closest and travel_time < 10 then
295 closest.x = (closest.x * closest.w + node.x)/(closest.w+1)
296 closest.y = (closest.y * closest.w + node.y)/(closest.w+1)
297 closest.w = closest.w + 1
298 self.world_graph:DestroyNode(node)
299 return closest
300 else]]
301 table.insert(z, node)
302 return node
303 --end
306 function QuestHelper:CreateAndAddStaticNodePair(data)
307 local node1, node2
309 if data[5] and self.named_nodes[data[5]] then
310 node1 = self.named_nodes[data[5]]
311 else
312 node1 = self:CreateAndAddZoneNode(self.zone_nodes[data[1][1]], data[1])
313 if not node1 then return end -- exception for Wrath changeover
314 if data[5] then self.named_nodes[data[5]] = node1 end
317 if data[6] and self.named_nodes[data[6]] then
318 node2 = self.named_nodes[data[6]]
319 else
320 node2 = self:CreateAndAddZoneNode(self.zone_nodes[data[2][1]], data[2])
321 if not node2 then return end -- exception for Wrath changeover
322 if data[6] then self.named_nodes[data[6]] = node2 end
325 node1.name = node1.name or "route to "..QuestHelper_NameLookup[data[2][1]]
326 node2.name = node2.name or "route to "..QuestHelper_NameLookup[data[1][1]]
328 node1:Link(node2, data[3])
330 if not data[4] then -- If data[4] is true, then this is a one-way trip.
331 node2:Link(node1, data[3])
334 self:yieldIfNeeded(0.1)
335 return node1, node2
338 function QuestHelper:GetNodeByName(name, fallback_data)
339 local node = self.named_nodes[name]
340 if not node and fallback_data then
341 node = self:CreateAndAddZoneNode(self.zone_nodes[fallback_data[1]], fallback_data)
342 self.named_nodes[name] = node
344 return node
347 local function nodeLeavesContinent(node, c)
348 if node.c == c then
349 for n, d in pairs(node.n) do
350 if n.c ~= c then
351 return true
355 return false
358 local function isGoodPath(start_node, end_node, i, j)
359 -- Checks to make sure a path doesn't leave the continent only to reenter it.
360 while true do
361 if end_node.p then
362 if end_node.c == i then
363 return false
365 end_node = end_node.p
366 if end_node.c == j then
367 return false
369 else
370 return end_node == start_node
375 local function shouldLink(a, b)
376 -- TODO: Need to have objectives not create links to unreachable nodes.
377 return a ~= b
378 --[[
379 if a == b then
380 return false
381 else
382 for id in pairs(a.id_from) do
383 if not b.id_to[id] then
384 for id in pairs(b.id_to) do
385 if not a.id_from[id] then
386 return true
392 return false
393 end]]
396 local function getNPCNode(npc)
397 local npc_objective = QuestHelper:GetObjective("monster", npc)
398 if npc_objective:Known() then
399 npc_objective:PrepareRouting()
400 local p = npc_objective:Position()
401 local node = nil
403 if p then
404 node = QuestHelper:CreateAndAddZoneNode(p[1], p[1].c, p[3], p[4])
407 npc_objective:DoneRouting()
408 return node
410 return nil
413 function QuestHelper:CreateAndAddTransitionNode(z1, z2, pos)
414 if not z1 or not z2 then
415 QuestHelper:Assert(not QuestHelper:IsWrath(), "Zone couldn't be found, and should have been")
416 return
419 local node = self:CreateGraphNode(pos)
421 local closest, travel_time = nil, 0
423 for i, n in ipairs(z1) do
424 local t = math.sqrt((n.x-node.x)*(n.x-node.x)+(n.y-node.y)*(n.y-node.y))
425 if not closest or t < travel_time then
426 closest, travel_time = n, t
430 if z1 ~= z2 then
431 for i, n in ipairs(z2) do
432 local t = math.sqrt((n.x-node.x)*(n.x-node.x)+(n.y-node.y)*(n.y-node.y))
433 if not closest or t < travel_time then
434 closest, travel_time = n, t
439 if closest and travel_time < 10 then
440 --QuestHelper:TextOut("Node already exists at "..closest.x..", "..closest.y..", name="..(closest.name or "nil"))
441 closest.x = (closest.x * closest.w + node.x)/(closest.w+1)
442 closest.y = (closest.y * closest.w + node.y)/(closest.w+1)
443 closest.w = closest.w + 1
444 local z1_has, z2_has = false, false
446 -- Just because the node already exists, doesn't mean its already in both lists!
448 for i, n in ipairs(z1) do
449 if n == closest then
450 z1_has = true
451 break
455 if z1 ~= z2 then
456 for i, n in ipairs(z2) do
457 if n == closest then
458 z2_has = true
459 break
462 else
463 z2_has = true
466 if not z1_has then table.insert(z1, closest) end
467 if not z2_has then table.insert(z2, closest) end
469 self.world_graph:DestroyNode(node)
470 self:yieldIfNeeded(0.2)
471 return closest
472 else
473 table.insert(z1, node)
474 if z1 ~= z2 then table.insert(z2, node) end
475 self:yieldIfNeeded(0.1)
476 return node
480 function QuestHelper:ReleaseObjectivePathingInfo(o)
481 if o.setup then
482 for z, pl in pairs(o.p) do
483 self:ReleaseTable(o.d[z])
485 for i, p in ipairs(pl) do
486 self:ReleaseTable(p[2])
487 self:ReleaseTable(p)
490 self:ReleaseTable(pl)
493 self:ReleaseTable(o.d)
494 self:ReleaseTable(o.p)
495 self:ReleaseTable(o.nm)
496 self:ReleaseTable(o.nm2)
497 self:ReleaseTable(o.nl)
499 local cache = o.distance_cache
500 for k, v in pairs(cache) do
501 self:ReleaseTable(v)
502 cache[k] = nil
504 self:ReleaseTable(cache)
506 o.d, o.p, o.nm, o.nm2, o.nl = nil, nil, nil, nil, nil
507 o.distance_cache = nil
508 o.pos, o.sop = nil, nil -- ResetPathing will preserve these values if needed.
509 o.setup = nil
513 function QuestHelper:SetupTeleportInfo(info, can_create)
514 self:TeleportInfoClear(info)
516 if QuestHelper_Home then
517 local node = self:GetNodeByName("HOME_PORTAL", can_create and QuestHelper_Home)
518 if node then
519 local cooldown = self:ItemCooldown(6948)
520 if cooldown then
521 self:SetTeleportInfoTarget(info, node, GetTime()-60*60+cooldown, 60*60, 10)
523 else
524 self.defered_graph_reset = true
528 -- TODO: Compact this. . . and find a better way to tell if the player has a spell.
530 if GetSpellTexture("Teleport: Darnassus") then
531 local node = self:GetNodeByName("DARNASSUS_PORTAL", can_create and DARNASSUS_PORTAL)
532 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
535 if GetSpellTexture("Teleport: Exodar") then
536 local node = self:GetNodeByName("EXODAR_PORTAL", can_create and EXODAR_PORTAL)
537 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
540 if GetSpellTexture("Teleport: Ironforge") then
541 local node = self:GetNodeByName("IRONFORGE_PORTAL", can_create and IRONFORGE_PORTAL)
542 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
545 if GetSpellTexture("Teleport: Moonglade") then
546 local node = self:GetNodeByName("MOONGLADE_PORTAL", can_create and MOONGLADE_PORTAL)
547 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10) else self.defered_graph_reset = true end
550 if GetSpellTexture("Teleport: Orgrimmar") then
551 local node = self:GetNodeByName("ORGRIMMAR_PORTAL", can_create and ORGRIMMAR_PORTAL)
552 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
555 if GetSpellTexture("Teleport: Shattrath") then
556 local node = self:GetNodeByName("SHATTRATH_CITY_PORTAL", can_create and SHATTRATH_CITY_PORTAL)
557 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
560 if GetSpellTexture("Teleport: Silvermoon") then
561 local node = self:GetNodeByName("SILVERMOON_CITY_PORTAL", can_create and SILVERMOON_CITY_PORTAL)
562 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
565 if GetSpellTexture("Teleport: Stormwind") then
566 local node = self:GetNodeByName("STORMWIND_CITY_PORTAL", can_create and STORMWIND_CITY_PORTAL)
567 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
570 if GetSpellTexture("Teleport: Thunder Bluff") then
571 local node = self:GetNodeByName("THUNDER_BLUFF_PORTAL", can_create and THUNDER_BLUFF_PORTAL)
572 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
575 if GetSpellTexture("Teleport: Undercity") then
576 local node = self:GetNodeByName("UNDERCITY_PORTAL", can_create and UNDERCITY_PORTAL)
577 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
580 self:SetTeleportInfoReagent(info, 17031, self:CountItem(17031))
583 function QuestHelper:ResetPathing()
584 for key in pairs(self.named_nodes) do
585 self.named_nodes[key] = nil
588 -- Objectives may include cached information that depends on the world graph.
589 local i = 1
591 while i <= #self.prepared_objectives do
592 local o = self.prepared_objectives[i]
594 if o.setup_count == 0 then
595 table.remove(self.prepared_objectives, i)
596 self:ReleaseObjectivePathingInfo(o)
597 else
598 -- Routing should reset the positions of objectives in the route after the reset is complete.
599 o.pos = nil
600 self:ReleaseObjectivePathingInfo(o)
601 i = i + 1
605 local to_readd = self.prepared_objectives
606 self.prepared_objectives = self.old_prepared_objectives or {}
607 self.old_prepared_objectives = to_readd
609 local zone_nodes = self.zone_nodes
610 if not zone_nodes then
611 zone_nodes = {}
612 self.zone_nodes = zone_nodes
615 local flight_master_nodes = self.flight_master_nodes
616 if not flight_master_nodes then
617 flight_master_nodes = {}
618 self.flight_master_nodes = flight_master_nodes
619 else
620 for key in pairs(flight_master_nodes) do
621 flight_master_nodes[key] = nil
625 self.world_graph:Reset()
626 self:yieldIfNeeded(0.1)
628 local continent_scales_x, continent_scales_y = self.continent_scales_x, self.continent_scales_y
629 if not continent_scales_x then
630 continent_scales_x = {}
631 continent_scales_y = {}
632 self.continent_scales_x = continent_scales_x
633 self.continent_scales_y = continent_scales_y
636 for c=1,select("#", GetMapContinents()) do
637 if not continent_scales_x[c] then
638 local _, x, y = self.Astrolabe:ComputeDistance(c, 0, 0.25, 0.25, c, 0, 0.75, 0.75)
640 continent_scales_x[c] = x*walkspeed_multiplier*2
641 continent_scales_y[c] = y*walkspeed_multiplier*2
645 for i, name in pairs(QuestHelper_NameLookup) do
646 local z = zone_nodes[i]
647 if not z then
648 z = QuestHelper:CreateTable("zone") -- This was originally z = {}. I'm pretty sure that the CreateTable/ReleaseTable system is (largely) immune to leaks, and I'm also pretty sure that zones are only created once, at the beginning of the system. Keep this in mind if leaks start occuring.
649 zone_nodes[i] = z
650 z.i, z.c, z.z = i, unpack(QuestHelper_ZoneLookup[i])
651 else
652 for key in pairs(z) do
653 z[key] = nil
655 z.i, z.c, z.z = i, unpack(QuestHelper_ZoneLookup[i])
659 self:SetupTeleportInfo(self.teleport_info, true)
660 self:yieldIfNeeded(0.1)
662 --[[for node, info in pairs(self.teleport_info.node) do
663 self:TextOut("You can teleport to "..(node.name or "nil").. " in "..self:TimeString(info[1]+info[2]-GetTime()))
664 end]]
666 if self.faction == 1 then
667 for i, data in ipairs(static_alliance_routes) do
668 self:CreateAndAddStaticNodePair(data)
670 elseif self.faction == 2 then
671 for i, data in ipairs(static_horde_routes) do
672 self:CreateAndAddStaticNodePair(data)
676 for i, data in ipairs(static_shared_routes) do
677 self:CreateAndAddStaticNodePair(data)
680 if self.player_level >= 58 then
681 dark_portal_route[3] = 5
682 else
683 -- If you can't take the route yet, we'll still add it and just pretend it will take a really long time.
684 dark_portal_route[3] = 86400
687 self:CreateAndAddStaticNodePair(dark_portal_route)
689 local st = self:CreateTable("ResetPathing local st")
691 for i, data in pairs(static_zone_transitions) do
692 st[1], st[2], st[3] = data[1], data[3], data[4]
694 local transnode = self:CreateAndAddTransitionNode(zone_nodes[data[1]],
695 zone_nodes[data[2]],
697 if transnode then transnode.name = QHFormat("ZONE_BORDER", QuestHelper_NameLookup[data[1]], QuestHelper_NameLookup[data[2]]) end -- if the transition node wasn't creatable, we obviously can't name it
700 self:ReleaseTable(st)
702 -- Create and link the flight route nodes.
703 local flight_times = self.flight_times
704 if not flight_times then
705 self:buildFlightTimes()
706 flight_times = self.flight_times
709 for start, list in pairs(flight_times) do
710 for dest, duration in pairs(list) do
711 local a_npc, b_npc = self:getFlightInstructor(start), self:getFlightInstructor(dest)
713 if a_npc and b_npc then
714 local a, b = flight_master_nodes[start], flight_master_nodes[dest]
716 if not a then
717 a = getNPCNode(a_npc)
718 if a then
719 flight_master_nodes[start] = a
720 a.name = (select(3, string.find(start, "^(.*),")) or start).." flight point"
724 if not b then
725 b = getNPCNode(b_npc)
726 if b then
727 flight_master_nodes[dest] = b
728 b.name = (select(3, string.find(dest, "^(.*),")) or dest).." flight point"
732 if a and b then
733 a:Link(b, duration+5)
737 self:yieldIfNeeded(0.1)
740 -- id_from, id_to, and id_local will be used in determining whether there is a point to linking nodes together.
741 for i, n in ipairs(self.world_graph.nodes) do
742 n.id_from = self:CreateTable("ResetPathing n.id_from")
743 n.id_to = self:CreateTable("ResetPathing n.id_to")
744 n.id_local = self:CreateTable("ResetPathing n.id_local")
747 -- Setup the local ids a node exists in.
748 for i, list in pairs(zone_nodes) do
749 for _, n in ipairs(list) do
750 n.id_local[i] = true
751 n.id_to[i] = true
752 n.id_from[i] = true
756 -- Figure out where each node can come from or go to.
757 for i, list in pairs(zone_nodes) do
758 for _, node in ipairs(list) do
759 for n in pairs(node.n) do
760 for id in pairs(n.id_local) do node.id_to[id] = true end
761 for id in pairs(node.id_local) do n.id_from[id] = true end
766 -- We'll treat 0 as a special id for where ever it is the player happens to be.
767 for node in pairs(self.teleport_info.node) do
768 node.id_from[0] = true
771 -- Will go through each zone and link all the nodes we have so far with every other node.
772 for _, list in pairs(zone_nodes) do
773 for i = 1,#list do
774 for j = 1,#list do
775 if shouldLink(list[i], list[j]) then
776 list[i]:Link(list[j], cont_dist(list[i], list[j]))
782 self:yieldIfNeeded(0.1)
784 -- We don't need to know where the nodes can go or come from now.
785 for i, n in ipairs(self.world_graph.nodes) do
786 self:ReleaseTable(n.id_from)
787 self:ReleaseTable(n.id_to)
788 self:ReleaseTable(n.id_local)
789 n.id_from, n.id_to, n.id_local = nil, nil, nil
792 -- TODO: This is a work around until I fix shouldLink
793 for start, list in pairs(flight_times) do
794 for dest, duration in pairs(list) do
795 local a, b = flight_master_nodes[start], flight_master_nodes[dest]
796 if a and b then
797 a:Link(b, duration+5)
802 self:yieldIfNeeded(0.1)
803 -- self.world_graph:SanityCheck()
805 -- Remove objectives again, since we created some for the flight masters.
806 while true do
807 local o = table.remove(self.prepared_objectives)
808 if not o then break end
810 self:ReleaseObjectivePathingInfo(o)
812 if o.setup_count > 0 then
813 -- There's a chance an objective could end up in the list twice, but we'll deal with that by not actually
814 -- adding locations for it if it's already setup.
815 table.insert(to_readd, o)
819 while true do
820 local obj = table.remove(to_readd)
821 if not obj then break end
823 if not obj.setup then -- In case the objective was added multiple times to the to_readd list.
824 obj.d = QuestHelper:CreateTable("ResetPathing obj.d")
825 obj.p = QuestHelper:CreateTable("ResetPathing obj.p")
826 obj.nm = QuestHelper:CreateTable("ResetPathing obj.nm")
827 obj.nm2 = QuestHelper:CreateTable("ResetPathing obj.nm2")
828 obj.nl = QuestHelper:CreateTable("ResetPathing obj.nl")
829 obj.distance_cache = QuestHelper:CreateTable("ResetPathing obj.distance_cache")
830 obj:AppendPositions(obj, 1, nil)
831 obj:FinishAddLoc()
835 if self.i then
836 self.pos[1] = self.zone_nodes[self.i]
837 for i, n in ipairs(self.pos[1]) do
838 local a, b = n.x-self.pos[3], n.y-self.pos[4]
839 self.pos[2][i] = math.sqrt(a*a+b*b)
843 -- And if all went according to plan, we now have a graph we can follow to get from anywhere to anywhere.
845 if self.graph_walker then
846 self.graph_walker:GraphChanged()