Added machine translated descriptions in the TOC file.
[QuestHelper.git] / pathfinding.lua
blobbd977c1fa99afff8e050519f87052cd470b4025b
1 local IRONFORGE_PORTAL = {25,0.255,0.084, "Ironforge portal site"}
2 local STORMWIND_CITY_PORTAL = {36,0.387,0.802, "Stormwind City portal site"}
3 local DARNASSUS_PORTAL = {21,0.397,0.824, "Darnassus portal site"}
4 local EXODAR_PORTAL = {12,0.476,0.598, "Exodar portal site"}
6 local SHATTRATH_CITY_PORTAL = {60,0.530,0.492, "Shattrath City portal site"}
7 local MOONGLADE_PORTAL = {20,0.563,0.320, "Moonglade portal site"}
9 local SILVERMOON_CITY_PORTAL = {52,0.583,0.192, "Silvermoon City portal site"}
10 local UNDERCITY_PORTAL = {45,0.846,0.163, "Undercity portal site"}
11 local ORGRIMMAR_PORTAL = {1,0.386,0.859, "Orgrimmar portal site"}
12 local THUNDER_BLUFF_PORTAL = {23,0.222,0.168, "Thunder Bluff portal site"}
14 local static_horde_routes =
16 {{7, 0.505, 0.124}, {38, 0.313, 0.303}, 210}, -- Durotar <--> Grom'gol Base Camp
17 {{38, 0.316, 0.289}, {43, 0.621, 0.591}, 210}, -- Grom'gol Base Camp <--> Tirisfal Glades
18 {{43, 0.605, 0.587}, {7, 0.509, 0.141}, 210}, -- Tirisfal Glades <--> Durotar
19 {{45, 0.549, 0.11}, {52, 0.495, 0.148}, 5}, -- Undercity <--> Silvermoon City
21 {{60, 0.592, 0.483}, SILVERMOON_CITY_PORTAL, 5, true, nil, "SILVERMOON_CITY_PORTAL"}, -- Shattrath City --> Silvermoon City
22 {{60, 0.528, 0.531}, THUNDER_BLUFF_PORTAL, 5, true, nil, "THUNDER_BLUFF_PORTAL"}, -- Shattrath City --> Thunder Bluff
23 {{60, 0.522, 0.529}, ORGRIMMAR_PORTAL, 5, true, nil, "ORGRIMMAR_PORTAL"}, -- Shattrath City --> Orgrimmar
24 {{60, 0.517, 0.525}, UNDERCITY_PORTAL, 5, true, nil, "UNDERCITY_PORTAL"} -- Shattrath City --> Undercity
27 local static_alliance_routes =
29 {{36, 0.639, 0.083}, {25, 0.764, 0.512}, 120}, -- Deeprun Tram
30 {{51, 0.044, 0.569}, {16, 0.323, 0.441}, 210}, --Menethil Harbor <--> Auberdine
31 {{10, 0.718, 0.565}, {51, 0.047, 0.636}, 210}, -- Theramore Isle <--> Menethil Harmor
33 {{60, 0.558, 0.366}, STORMWIND_CITY_PORTAL, 5, true, nil, "STORMWIND_CITY_PORTAL"}, -- Shattrath City --> Stormwind City
34 {{60, 0.563, 0.37}, IRONFORGE_PORTAL, 5, true, nil, "IRONFORGE_PORTAL"}, -- Shattrath City --> Ironforge
35 {{60, 0.552, 0.364}, DARNASSUS_PORTAL, 5, true, nil, "DARNASSUS_PORTAL"}, -- Shattrath City --> Darnassus
36 {{60, 0.596, 0.467}, EXODAR_PORTAL, 5, true, nil, "EXODAR_PORTAL"} -- Shattrath City --> Exodar
39 local static_shared_routes =
41 {{11, 0.638, 0.387}, {38, 0.257, 0.73}, 210}, -- Ratchet <--> Booty Bay
42 {{40, 0.318, 0.503}, {32, 0.347, 0.84}, 130}, -- Burning Steppes <--> Searing Gorge
44 -- More Alliance routes than anything, but without them theres no valid path to these areas for Horde characters.
45 {{24, 0.559, 0.896}, {21, 0.305, 0.414}, 5}, -- Rut'Theran Village <--> Darnassus
46 {{16, 0.332, 0.398}, {24, 0.548, 0.971}, 210}, -- Auberdine <--> Rut'Theran Village
47 {{16, 0.306, 0.409}, {3, 0.2, 0.546}, 210}, -- Auberdine <--> Azuremyst Isle
49 -- Route to new zone. Not valid, exists only to keep routing from exploding if you don't have the flight routes there.
50 {{41, 0.5, 0.5}, {64, 0.5, 0.5}, 7200} -- Eversong Woods <--> Sunwell
53 -- Darkportal is handled specially, depending on whether or not you're level 58+ or not.
54 local dark_portal_route = {{33, 0.587, 0.599}, {56, 0.898, 0.502}, 5}
56 local static_zone_transitions =
58 {2, 11, 0.687, 0.872}, -- Ashenvale <--> The Barrens
59 {2, 6, 0.423, 0.711}, -- Ashenvale <--> Stonetalon Mountains
60 {2, 15, 0.954, 0.484}, -- Ashenvale <--> Azshara
61 {2, 16, 0.289, 0.144}, -- Ashenvale <--> Darkshore
62 {2, 13, 0.557, 0.29}, -- Ashenvale <--> Felwood
63 {21, 24, 0.894, 0.358}, -- Darnassus <--> Teldrassil
64 {22, 11, 0.697, 0.604}, -- Mulgore <--> The Barrens
65 {22, 23, 0.376, 0.33}, -- Mulgore <--> Thunder Bluff
66 {22, 23, 0.403, 0.193}, -- Mulgore <--> Thunder Bluff
67 {3, 12, 0.247, 0.494}, -- Azuremyst Isle <--> The Exodar
68 {3, 12, 0.369, 0.469}, -- Azuremyst Isle <--> The Exodar
69 {3, 9, 0.42, 0.013}, -- Azuremyst Isle <--> Bloodmyst Isle
70 {4, 6, 0.539, 0.032}, -- Desolace <--> Stonetalon Mountains
71 {4, 17, 0.428, 0.976}, -- Desolace <--> Feralas
72 {5, 18, 0.865, 0.115}, -- Silithus <--> Un'Goro Crater
73 {7, 11, 0.341, 0.424}, -- Durotar <--> The Barrens
74 {7, 1, 0.455, 0.121}, -- Durotar <--> Orgrimmar
75 {8, 18, 0.269, 0.516}, -- Tanaris <--> Un'Goro Crater
76 {8, 14, 0.512, 0.21}, -- Tanaris <--> Thousand Needles
77 {10, 11, 0.287, 0.472}, -- Dustwallow Marsh <--> The Barrens
78 {10, 11, 0.563, 0.077}, -- Dustwallow Marsh <--> The Barrens
79 {11, 14, 0.442, 0.915}, -- The Barrens <--> Thousand Needles
80 {13, 19, 0.685, 0.06}, -- Felwood <--> Winterspring
81 {13, 20, 0.669, -0.063}, -- Felwood <--> Moonglade
82 {1, 11, 0.118, 0.69}, -- Orgrimmar <--> The Barrens
83 {17, 14, 0.899, 0.46}, -- Feralas <--> Thousand Needles
84 {6, 11, 0.836, 0.973}, -- Stonetalon Mountains <--> The Barrens
85 {26, 48, 0.521, 0.7}, -- Alterac Mountains <--> Hillsbrad Foothills
86 {26, 35, 0.173, 0.482}, -- Alterac Mountains <--> Silverpine Forest
87 {26, 50, 0.807, 0.347}, -- Alterac Mountains <--> Western Plaguelands
88 {39, 51, 0.454, 0.89}, -- Arathi Highlands <--> Wetlands
89 {39, 48, 0.2, 0.293}, -- Arathi Highlands <--> Hillsbrad Foothills
90 {27, 29, 0.49, 0.071}, -- Badlands <--> Loch Modan
91 {27, 32, -0.005, 0.636}, -- Badlands <--> Searing Gorge
92 {33, 46, 0.519, 0.051}, -- Blasted Lands <--> Swamp of Sorrows
93 {40, 30, 0.79, 0.842}, -- Burning Steppes <--> Redridge Mountains
94 {47, 31, 0.324, 0.363}, -- Deadwind Pass <--> Duskwood
95 {47, 46, 0.605, 0.41}, -- Deadwind Pass <--> Swamp of Sorrows
96 {28, 25, 0.534, 0.349}, -- Dun Morogh <--> Ironforge
97 {28, 29, 0.863, 0.514}, -- Dun Morogh <--> Loch Modan
98 {28, 29, 0.844, 0.31}, -- Dun Morogh <--> Loch Modan
99 {31, 37, 0.801, 0.158}, -- Duskwood <--> Elwynn Forest
100 {31, 37, 0.15, 0.214}, -- Duskwood <--> Elwynn Forest
101 {31, 38, 0.447, 0.884}, -- Duskwood <--> Stranglethorn Vale
102 {31, 38, 0.209, 0.863}, -- Duskwood <--> Stranglethorn Vale
103 {31, 30, 0.941, 0.103}, -- Duskwood <--> Redridge Mountains
104 {31, 49, 0.079, 0.638}, -- Duskwood <--> Westfall
105 {34, 50, 0.107, 0.726}, -- Eastern Plaguelands <--> Western Plaguelands
106 {34, 44, 0.625, 0.03}, -- Eastern Plaguelands <--> Ghostlands
107 {37, 36, 0.321, 0.493}, -- Elwynn Forest <--> Stormwind City
108 {37, 49, 0.202, 0.804}, -- Elwynn Forest <--> Westfall
109 {37, 30, 0.944, 0.724}, -- Elwynn Forest <--> Redridge Mountains
110 {41, 52, 0.567, 0.494}, -- Eversong Woods <--> Silvermoon City
111 {41, 44, 0.486, 0.916}, -- Eversong Woods <--> Ghostlands
112 {35, 43, 0.678, 0.049}, -- Silverpine Forest <--> Tirisfal Glades
113 {42, 50, 0.217, 0.264}, -- The Hinterlands <--> Western Plaguelands
114 {43, 45, 0.619, 0.651}, -- Tirisfal Glades <--> Undercity
115 {43, 50, 0.851, 0.703}, -- Tirisfal Glades <--> Western Plaguelands
116 {38, 49, 0.292, 0.024}, -- Stranglethorn Vale <--> Westfall
117 {48, 35, 0.137, 0.458}, -- Hillsbrad Foothills <--> Silverpine Forest
118 {48, 42, 0.899, 0.253}, -- Hillsbrad Foothills <--> The Hinterlands
119 {29, 51, 0.252, 0}, -- Loch Modan <--> Wetlands
121 -- These are just guesses, since I haven't actually been to these areas.
122 {58, 60, 0.783, 0.545}, -- Nagrand <--> Shattrath City
123 {60, 55, 0.782, 0.492}, -- Shattrath City <--> Terokkar Forest
124 {54, 59, 0.842, 0.284}, -- Blade's Edge Mountains <--> Netherstorm
125 {54, 57, 0.522, 0.996}, -- Blade's Edge Mountains <--> Zangarmarsh
126 {54, 57, 0.312, 0.94}, -- Blade's Edge Mountains <--> Zangarmarsh
127 {56, 55, 0.353, 0.901}, -- Hellfire Peninsula <--> Terokkar Forest
128 {56, 57, 0.093, 0.519}, -- Hellfire Peninsula <--> Zangarmarsh
129 {58, 55, 0.8, 0.817}, -- Nagrand <--> Terokkar Forest
130 {58, 57, 0.343, 0.159}, -- Nagrand <--> Zangarmarsh
131 {58, 57, 0.754, 0.331}, -- Nagrand <--> Zangarmarsh
132 {53, 55, 0.208, 0.271}, -- Shadowmoon Valley <--> Terokkar Forest
133 {55, 57, 0.341, 0.098}, -- Terokkar Forest <--> Zangarmarsh
136 local walkspeed_multiplier = 1/7 -- Every yard walked takes this many seconds.
138 local cont_heuristic = {} -- Contains a 2D table of heuristics to use for getting from one continent to another.
140 local function nil_heuristic(a, b)
141 return 0
144 QuestHelper.prepared_objectives = {}
145 QuestHelper.named_nodes = {}
147 local function heuristic(a, b)
148 if type(b) ~= "table" then QuestHelper:Error("Boom?!") end
149 QuestHelper:TextOut("c="..a.c.." x="..a.x.." y="..a.y.." to c="..b.c.." x="..b.x.." y="..b.y.." ="..cont_heuristic[a.c][b.c](a, b))
150 return cont_heuristic[a.c][b.c](a, b)
153 local function same_cont_heuristic(a, b)
154 local x, y = a.x-b.x, a.y-b.y
155 return math.sqrt(x*x+y*y)
158 function QuestHelper:ComputeRoute(p1, p2)
159 if not p1 or not p2 then QuestHelper:Error("Boom!") end
160 local graph = self.world_graph
162 graph:PrepareSearch()
164 local l = p2[2]
165 local el = p2[1]
166 for i, n in ipairs(el) do
167 n.e, n.w = l[i], 1
168 n.s = 3
171 l = p1[2]
172 for i, n in ipairs(p1[1]) do
173 graph:AddRouteStartNode(n, l[i], el)
176 local e = graph:DoRouteSearch(el)
178 assert(e)
180 local d = e.g+e.e
182 if p1[1] == p2[1] then
183 local x, y = p1[3]-p2[3], p1[4]-p2[4]
184 local d2 = math.sqrt(x*x+y*y)
185 if d2 < d then
186 d = d2
187 e = nil
191 return e, d
194 function QuestHelper:ComputeTravelTime(p1, p2)
195 if not p1 or not p2 then QuestHelper:Error("Boom!") end
196 local graph = self.world_graph
198 graph:PrepareSearch()
200 local l = p2[2]
201 local el = p2[1]
202 for i, n in ipairs(el) do
203 n.e, n.w = l[i], 1
204 assert(n.e)
205 n.s = 3
208 l = p1[2]
209 for i, n in ipairs(p1[1]) do
210 graph:AddStartNode(n, l[i], el)
213 local e = graph:DoSearch(el)
215 assert(e)
217 local d = e.g+e.e
219 if p1[1] == p2[1] then
220 local x, y = p1[3]-p2[3], p1[4]-p2[4]
221 d = math.min(d, math.sqrt(x*x+y*y))
224 return d
227 function QuestHelper:CreateGraphNode(c, x, y, n)
228 local node = self.world_graph:CreateNode()
230 if y then
231 node.c = c
232 node.x = x
233 node.y = y
234 node.name = n
235 else
236 local cont, zone = unpack(QuestHelper_ZoneLookup[c[1]])
237 node.c = cont
238 node.x, node.y = self.Astrolabe:TranslateWorldMapPosition(cont, zone, c[2], c[3], cont, 0)
239 node.x = node.x * self.continent_scales_x[node.c]
240 node.y = node.y * self.continent_scales_y[node.c]
241 node.name = c[5] or QuestHelper_NameLookup[c[1]]
244 node.w = 1
245 return node
248 function QuestHelper:CreateAndAddZoneNode(z, c, x, y)
249 local node = self:CreateGraphNode(c, x, y)
251 -- Not going to merge nodes.
252 --[[local closest, travel_time = nil, 0
254 for i, n in ipairs(z) do
255 local t = math.sqrt((n.x-node.x)*(n.x-node.x)+(n.y-node.y)*(n.y-node.y))
256 if not closest or t < travel_time then
257 closest, travel_time = n, t
261 if closest and travel_time < 10 then
262 closest.x = (closest.x * closest.w + node.x)/(closest.w+1)
263 closest.y = (closest.y * closest.w + node.y)/(closest.w+1)
264 closest.w = closest.w + 1
265 self.world_graph:DestroyNode(node)
266 return closest
267 else]]
268 table.insert(z, node)
269 return node
270 --end
273 function QuestHelper:CreateAndAddStaticNodePair(data)
274 local node1, node2
276 if data[5] and self.named_nodes[data[5]] then
277 node1 = self.named_nodes[data[5]]
278 else
279 node1 = self:CreateAndAddZoneNode(self.zone_nodes[data[1][1]], data[1])
280 if data[5] then self.named_nodes[data[5]] = node1 end
283 if data[6] and self.named_nodes[data[6]] then
284 node2 = self.named_nodes[data[6]]
285 else
286 node2 = self:CreateAndAddZoneNode(self.zone_nodes[data[2][1]], data[2])
287 if data[6] then self.named_nodes[data[6]] = node2 end
290 node1.name = node1.name or "route to "..QuestHelper_NameLookup[data[2][1]]
291 node2.name = node2.name or "route to "..QuestHelper_NameLookup[data[1][1]]
293 node1:Link(node2, data[3])
295 if not data[4] then -- If data[4] is true, then this is a one-way trip.
296 node2:Link(node1, data[3])
299 return node1, node2
302 function QuestHelper:GetNodeByName(name, fallback_data)
303 local node = self.named_nodes[name]
304 if not node and fallback_data then
305 node = self:CreateAndAddZoneNode(self.zone_nodes[fallback_data[1]], fallback_data)
306 self.named_nodes[name] = node
308 return node
311 local function nodeLeavesContinent(node, c)
312 if node.c == c then
313 for n, d in pairs(node.n) do
314 if n.c ~= c then
315 return true
319 return false
322 local function isGoodPath(start_node, end_node, i, j)
323 -- Checks to make sure a path doesn't leave the continent only to reenter it.
324 while true do
325 if end_node.p then
326 if end_node.c == i then
327 return false
329 end_node = end_node.p
330 if end_node.c == j then
331 return false
333 else
334 return end_node == start_node
339 local function shouldLink(a, b)
340 -- TODO: Need to have objectives not create links to unreachable nodes.
341 return a ~= b
342 --[[
343 if a == b then
344 return false
345 else
346 for id in pairs(a.id_from) do
347 if not b.id_to[id] then
348 for id in pairs(b.id_to) do
349 if not a.id_from[id] then
350 return true
356 return false
357 end]]
360 local function getNPCNode(npc)
361 local npc_objective = QuestHelper:GetObjective("monster", npc)
362 if npc_objective:Known() then
363 npc_objective:PrepareRouting()
364 local p = npc_objective:Position()
365 local node = nil
367 if p then
368 node = QuestHelper:CreateAndAddZoneNode(p[1], p[1].c, p[3], p[4])
371 npc_objective:DoneRouting()
372 return node
374 return nil
377 function QuestHelper:CreateAndAddTransitionNode(z1, z2, pos)
378 local node = self:CreateGraphNode(pos)
380 local closest, travel_time = nil, 0
382 for i, n in ipairs(z1) do
383 local t = math.sqrt((n.x-node.x)*(n.x-node.x)+(n.y-node.y)*(n.y-node.y))
384 if not closest or t < travel_time then
385 closest, travel_time = n, t
389 if z1 ~= z2 then
390 for i, n in ipairs(z2) do
391 local t = math.sqrt((n.x-node.x)*(n.x-node.x)+(n.y-node.y)*(n.y-node.y))
392 if not closest or t < travel_time then
393 closest, travel_time = n, t
398 if closest and travel_time < 10 then
399 --QuestHelper:TextOut("Node already exists at "..closest.x..", "..closest.y..", name="..(closest.name or "nil"))
400 closest.x = (closest.x * closest.w + node.x)/(closest.w+1)
401 closest.y = (closest.y * closest.w + node.y)/(closest.w+1)
402 closest.w = closest.w + 1
403 local z1_has, z2_has = false, false
405 -- Just because the node already exists, doesn't mean its already in both lists!
407 for i, n in ipairs(z1) do
408 if n == closest then
409 z1_has = true
410 break
414 if z1 ~= z2 then
415 for i, n in ipairs(z2) do
416 if n == closest then
417 z2_has = true
418 break
421 else
422 z2_has = true
425 if not z1_has then table.insert(z1, closest) end
426 if not z2_has then table.insert(z2, closest) end
428 self.world_graph:DestroyNode(node)
429 return closest
430 else
431 table.insert(z1, node)
432 if z1 ~= z2 then table.insert(z2, node) end
433 return node
437 function QuestHelper:ReleaseObjectivePathingInfo(o)
438 if o.setup then
439 for z, pl in pairs(o.p) do
440 self:ReleaseTable(o.d[z])
442 for i, p in ipairs(pl) do
443 self:ReleaseTable(p[2])
444 self:ReleaseTable(p)
447 self:ReleaseTable(pl)
450 self:ReleaseTable(o.d)
451 self:ReleaseTable(o.p)
452 self:ReleaseTable(o.nm)
453 self:ReleaseTable(o.nm2)
454 self:ReleaseTable(o.nl)
456 o.d, o.p, o.nm, o.nm2, o.nl = nil, nil, nil, nil, nil
457 o.pos, o.sop = nil, nil -- ResetPathing will preserve these values if needed.
458 o.setup = nil
462 function QuestHelper:SetupTeleportInfo(info, can_create)
463 self:TeleportInfoClear(info)
465 if QuestHelper_Home then
466 local node = self:GetNodeByName("HOME_PORTAL", can_create and QuestHelper_Home)
467 if node then
468 local cooldown = self:ItemCooldown(6948)
469 if cooldown then
470 self:SetTeleportInfoTarget(info, node, GetTime()-60*60+cooldown, 60*60, 10)
472 else
473 self.defered_graph_reset = true
477 -- TODO: Compact this. . . and find a better way to tell if the player has a spell.
479 if GetSpellTexture("Teleport: Darnassus") then
480 local node = self:GetNodeByName("DARNASSUS_PORTAL", can_create and DARNASSUS_PORTAL)
481 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
484 if GetSpellTexture("Teleport: Exodar") then
485 local node = self:GetNodeByName("EXODAR_PORTAL", can_create and EXODAR_PORTAL)
486 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
489 if GetSpellTexture("Teleport: Ironforge") then
490 local node = self:GetNodeByName("IRONFORGE_PORTAL", can_create and IRONFORGE_PORTAL)
491 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
494 if GetSpellTexture("Teleport: Moonglade") then
495 local node = self:GetNodeByName("MOONGLADE_PORTAL", can_create and MOONGLADE_PORTAL)
496 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10) else self.defered_graph_reset = true end
499 if GetSpellTexture("Teleport: Orgrimmar") then
500 local node = self:GetNodeByName("ORGRIMMAR_PORTAL", can_create and ORGRIMMAR_PORTAL)
501 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
504 if GetSpellTexture("Teleport: Shattrath") then
505 local node = self:GetNodeByName("SHATTRATH_CITY_PORTAL", can_create and SHATTRATH_CITY_PORTAL)
506 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
509 if GetSpellTexture("Teleport: Silvermoon") then
510 local node = self:GetNodeByName("SILVERMOON_CITY_PORTAL", can_create and SILVERMOON_CITY_PORTAL)
511 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
514 if GetSpellTexture("Teleport: Stormwind") then
515 local node = self:GetNodeByName("STORMWIND_CITY_PORTAL", can_create and STORMWIND_CITY_PORTAL)
516 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
519 if GetSpellTexture("Teleport: Thunder Bluff") then
520 local node = self:GetNodeByName("THUNDER_BLUFF_PORTAL", can_create and THUNDER_BLUFF_PORTAL)
521 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
524 if GetSpellTexture("Teleport: Undercity") then
525 local node = self:GetNodeByName("UNDERCITY_PORTAL", can_create and UNDERCITY_PORTAL)
526 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
529 self:SetTeleportInfoReagent(info, 17031, self:CountItem(17031))
532 function QuestHelper:ResetPathing()
533 for key in pairs(self.named_nodes) do
534 self.named_nodes[key] = nil
537 -- Objectives may include cached information that depends on the world graph.
538 local i = 1
540 while i <= #self.prepared_objectives do
541 local o = self.prepared_objectives[i]
543 if o.setup_count == 0 then
544 table.remove(self.prepared_objectives, i)
545 self:ReleaseObjectivePathingInfo(o)
546 else
547 if o.pos then
548 o.old_pos = self:CreateTable()
549 o.old_pos[1], o.old_pos[2], o.old_pos[3] = o.pos[1].c, o.pos[3], o.pos[4]
552 if o.sop then
553 o.old_sop = self:CreateTable()
554 o.old_sop[1], o.old_sop[2], o.old_sop[3] = o.sop[1].c, o.sop[3], o.sop[4]
557 self:ReleaseObjectivePathingInfo(o)
558 i = i + 1
562 local to_readd = self.prepared_objectives
563 self.prepared_objectives = self.old_prepared_objectives or {}
564 self.old_prepared_objectives = to_readd
566 self.world_graph:SetHeuristic(nil_heuristic)
568 local zone_nodes = self.zone_nodes
569 if not zone_nodes then
570 zone_nodes = {}
571 self.zone_nodes = zone_nodes
574 local flight_master_nodes = self.flight_master_nodes
575 if not flight_master_nodes then
576 flight_master_nodes = {}
577 self.flight_master_nodes = flight_master_nodes
578 else
579 for key in pairs(flight_master_nodes) do
580 flight_master_nodes[key] = nil
584 self.world_graph:Reset()
586 local continent_scales_x, continent_scales_y = self.continent_scales_x, self.continent_scales_y
587 if not continent_scales_x then
588 continent_scales_x = {}
589 continent_scales_y = {}
590 self.continent_scales_x = continent_scales_x
591 self.continent_scales_y = continent_scales_y
594 for c=1,select("#", GetMapContinents()) do
595 if not continent_scales_x[c] then
596 local _, x, y = self.Astrolabe:ComputeDistance(c, 0, 0.25, 0.25, c, 0, 0.75, 0.75)
598 continent_scales_x[c] = x*walkspeed_multiplier*2
599 continent_scales_y[c] = y*walkspeed_multiplier*2
602 if not cont_heuristic[c] then
603 cont_heuristic[c] = {}
607 for i, name in pairs(QuestHelper_NameLookup) do
608 if not zone_nodes[i] then
609 zone_nodes[i] = {}
610 else
611 for key in pairs(zone_nodes[i]) do
612 zone_nodes[i][key] = nil
616 zone_nodes[i].c, zone_nodes[i].z = unpack(QuestHelper_ZoneLookup[i])
619 self:SetupTeleportInfo(self.teleport_info, true)
621 --[[for node, info in pairs(self.teleport_info.node) do
622 self:TextOut("You can teleport to "..(node.name or "nil").. " in "..self:TimeString(info[1]+info[2]-GetTime()))
623 end]]
625 if self.faction == 1 then
626 for i, data in ipairs(static_alliance_routes) do
627 self:CreateAndAddStaticNodePair(data)
629 elseif self.faction == 2 then
630 for i, data in ipairs(static_horde_routes) do
631 self:CreateAndAddStaticNodePair(data)
635 for i, data in ipairs(static_shared_routes) do
636 self:CreateAndAddStaticNodePair(data)
639 if self.player_level >= 58 then
640 dark_portal_route[3] = 5
641 else
642 -- If you can't take the route yet, we'll still add it and just pretend it will take a really long time.
643 dark_portal_route[3] = 86400
646 self:CreateAndAddStaticNodePair(dark_portal_route)
648 local st = self:CreateTable()
650 for i, data in pairs(static_zone_transitions) do
651 st[1], st[2], st[3] = data[1], data[3], data[4]
653 self:CreateAndAddTransitionNode(zone_nodes[data[1]],
654 zone_nodes[data[2]],
655 st).name = QHFormat("ZONE_BORDER", QuestHelper_NameLookup[data[1]], QuestHelper_NameLookup[data[2]])
658 self:ReleaseTable(st)
660 -- Create and link the flight route nodes.
661 local flight_times = self.flight_times
662 if not flight_times then
663 self:buildFlightTimes()
664 flight_times = self.flight_times
667 for start, list in pairs(flight_times) do
668 for dest, duration in pairs(list) do
669 local a_npc, b_npc = self:getFlightInstructor(start), self:getFlightInstructor(dest)
671 if a_npc and b_npc then
672 local a, b = flight_master_nodes[start], flight_master_nodes[dest]
674 if not a then
675 a = getNPCNode(a_npc)
676 if a then
677 flight_master_nodes[start] = a
678 a.name = (select(3, string.find(start, "^(.*),")) or start).." flight point"
682 if not b then
683 b = getNPCNode(b_npc)
684 if b then
685 flight_master_nodes[dest] = b
686 b.name = (select(3, string.find(dest, "^(.*),")) or dest).." flight point"
690 if a and b then
691 a:Link(b, duration+5)
697 -- id_from, id_to, and id_local will be used in determining whether there is a point to linking nodes together.
698 for i, n in ipairs(self.world_graph.nodes) do
699 n.id_from = self:CreateTable()
700 n.id_to = self:CreateTable()
701 n.id_local = self:CreateTable()
704 -- Setup the local ids a node exists in.
705 for i, list in pairs(zone_nodes) do
706 for _, n in ipairs(list) do
707 n.id_local[i] = true
708 n.id_to[i] = true
709 n.id_from[i] = true
713 -- Figure out where each node can come from or go to.
714 for i, list in pairs(zone_nodes) do
715 for _, node in ipairs(list) do
716 for n in pairs(node.n) do
717 for id in pairs(n.id_local) do node.id_to[id] = true end
718 for id in pairs(node.id_local) do n.id_from[id] = true end
723 -- We'll treat 0 as a special id for where ever it is the player happens to be.
724 for node in pairs(self.teleport_info.node) do
725 node.id_from[0] = true
728 -- Will go through each zone and link all the nodes we have so far with every other node.
729 for _, list in pairs(zone_nodes) do
730 for i = 1,#list do
731 for j = 1,#list do
732 if shouldLink(list[i], list[j]) then
733 list[i]:Link(list[j], same_cont_heuristic(list[i], list[j]))
739 -- We don't need to know where the nodes can go or come from now.
740 for i, n in ipairs(self.world_graph.nodes) do
741 self:ReleaseTable(n.id_from)
742 self:ReleaseTable(n.id_to)
743 self:ReleaseTable(n.id_local)
744 n.id_from, n.id_to, n.id_local = nil, nil, nil
747 -- TODO: This is a work around until I fix shouldLink
748 for start, list in pairs(flight_times) do
749 for dest, duration in pairs(list) do
750 local a, b = flight_master_nodes[start], flight_master_nodes[dest]
751 if a and b then
752 a:Link(b, duration+5)
757 -- TODO: Create a heuristic for this.
758 --[[
759 for i = 1,select("#", GetMapContinents()) do
760 for j = 1,select("#", GetMapContinents()) do
761 if i == j then
762 cont_heuristic[i][j] = same_cont_heuristic
763 else
764 local nodes = {}
766 local index = 1
767 local code1 = ""
768 local code2 = ""
769 local single = true
771 for _, start_node in ipairs(self.world_graph.nodes) do
772 if nodeLeavesContinent(start_node, i) then
773 for _, end_node in ipairs(self.world_graph.nodes) do
774 if nodeLeavesContinent(end_node, j) then
775 if self.world_graph:Search(start_node, end_node) then
776 if isGoodPath(start_node, end_node, i, j) then
777 if not nodes[start_node] then
778 nodes[start_node] = "n"..index
779 code1 = code1.."local "..nodes[start_node].."=math.sqrt(a.x*a.x+a.y*a.y-a.x*("..(2*start_node.x)..")-a.y*("..(2*start_node.y)..")+("..(start_node.x*start_node.x+start_node.y*start_node.y).."))\n"
780 index = index + 1
782 if not nodes[end_node] then
783 nodes[end_node] = "n"..index
784 code1 = code1.."local "..nodes[end_node].."=math.sqrt(b.x*b.x+b.y*b.y-b.x*("..(2*end_node.x)..")-b.y*("..(2*end_node.y)..")+("..(end_node.x*end_node.x+end_node.y*end_node.y).."))\n"
785 index = index + 1
787 if code2 ~= "" then code2 = code2 .. ", " single = false end
788 code2 = code2 .. nodes[start_node] .. "+"..nodes[end_node] .. "+"..(end_node.g)
796 cont_heuristic[i][j] = loadstring("local a,b = ...\n"..code1.."return "..(single and code2 or ("math.min("..code2..")")))
801 -- self.world_graph:SanityCheck()
803 -- TODO: heuristic returns NaNs, fix this.
804 --self.world_graph:SetHeuristic(heuristic)
806 -- Remove objectives again, since we created some for the flight masters.
807 while true do
808 local o = table.remove(self.prepared_objectives)
809 if not o then break end
811 self:ReleaseObjectivePathingInfo(o)
813 if o.setup_count > 0 then
814 -- There's a chance an objective could end up in the list twice, but we'll deal with that by not actually
815 -- adding locations for it if it's already setup.
816 table.insert(to_readd, o)
820 while true do
821 local obj = table.remove(to_readd)
822 if not obj then break end
824 if not obj.setup then -- In case the objective was added multiple times to the to_readd list.
825 obj.d = QuestHelper:CreateTable()
826 obj.p = QuestHelper:CreateTable()
827 obj.nm = QuestHelper:CreateTable()
828 obj.nm2 = QuestHelper:CreateTable()
829 obj.nl = QuestHelper:CreateTable()
830 obj:AppendPositions(obj, 1, nil)
831 obj:FinishAddLoc()
833 -- Make sure the objectives still contain the positions set by routing.
834 -- The might have shifted slightly, but there's not much I can do about that.
836 if obj.old_pos then
837 local p, d = nil, 0
839 for z, pl in pairs(obj.p) do
840 for i, point in ipairs(pl) do
841 if obj.old_pos[1] == point[1].c then
842 local x, y = obj.old_pos[2]-point[3], obj.old_pos[3]-point[4]
843 local d2 = x*x+y*y
844 if not p or d2 < d then
845 p, d = point, d2
851 assert(p)
853 obj.pos = p
854 self:ReleaseTable(obj.old_pos)
855 obj.old_pos = nil
858 if obj.old_sop then
859 local p, d = nil, 0
861 for z, pl in pairs(obj.p) do
862 for i, point in ipairs(pl) do
863 if obj.old_sop[1] == point[1].c then
864 local x, y = obj.old_sop[2]-point[3], obj.old_sop[3]-point[4]
865 local d2 = x*x+y*y
866 if not p or d2 < d then
867 p, d = point, d2
873 assert(p)
875 obj.sop = p
876 self:ReleaseTable(obj.old_sop)
877 obj.old_sop = nil
882 if self.i then
883 self.pos[1] = self.zone_nodes[self.i]
884 for i, n in ipairs(self.pos[1]) do
885 local a, b = n.x-self.pos[3], n.y-self.pos[4]
886 self.pos[2][i] = math.sqrt(a*a+b*b)
890 -- And if all went according to plan, we now have a graph we can follow to get from anywhere to anywhere.
892 if self.graph_walker then
893 self.graph_walker:GraphChanged()