Automated update from: http://smariot.hopto.org/translate
[QuestHelper.git] / pathfinding.lua
blobf23df6138cffc7dbcfcb43c65550e383dd5cc523
1 QuestHelper_File["pathfinding.lua"] = "Development Version"
2 QuestHelper_Loadtime["pathfinding.lua"] = GetTime()
4 local IRONFORGE_PORTAL = {25,0.255,0.084, "Ironforge portal site"}
5 local STORMWIND_CITY_PORTAL = QuestHelper_ConvertCoordsToWrath({36,0.387,0.802, "Stormwind City portal site"}, true) -- Old pre-Wrath coordinates. I could fix it, but . . . meh.
6 local DARNASSUS_PORTAL = {21,0.397,0.824, "Darnassus portal site"}
7 local EXODAR_PORTAL = {12,0.476,0.598, "Exodar portal site"}
9 local SHATTRATH_CITY_PORTAL = {60,0.530,0.492, "Shattrath City portal site"}
10 local DALARAN_PORTAL = {67,0.500,0.394, "Dalaran portal site"}
11 local MOONGLADE_PORTAL = {20,0.563,0.320, "Moonglade portal site"}
13 local SILVERMOON_CITY_PORTAL = {52,0.583,0.192, "Silvermoon City portal site"}
14 local UNDERCITY_PORTAL = {45,0.846,0.163, "Undercity portal site"}
15 local ORGRIMMAR_PORTAL = {1,0.386,0.859, "Orgrimmar portal site"}
16 local THUNDER_BLUFF_PORTAL = {23,0.222,0.168, "Thunder Bluff portal site"}
18 local static_horde_routes =
20 {{7, 0.505, 0.124}, {38, 0.313, 0.303}, 210}, -- Durotar <--> Grom'gol Base Camp
21 {{38, 0.316, 0.289}, {43, 0.621, 0.591}, 210}, -- Grom'gol Base Camp <--> Tirisfal Glades
22 {{43, 0.605, 0.587}, {7, 0.509, 0.141}, 210}, -- Tirisfal Glades <--> Durotar
23 {{45, 0.549, 0.11}, {52, 0.495, 0.148}, 60}, -- Undercity <--> Silvermoon City
25 {{7, 0.413, 0.178}, {65, 0.414, 0.536}, 210}, -- Durotar <--> Warsong Hold
26 {{43, 0.591, 0.590}, {70, 0.777, 0.283}, 210}, -- Tirisfal Glades <--> Vengeance Landing
28 {{60, 0.592, 0.483}, SILVERMOON_CITY_PORTAL, 60, true, nil, "SILVERMOON_CITY_PORTAL"}, -- Shattrath City --> Silvermoon City
29 {{60, 0.528, 0.531}, THUNDER_BLUFF_PORTAL, 60, true, nil, "THUNDER_BLUFF_PORTAL"}, -- Shattrath City --> Thunder Bluff
30 {{60, 0.522, 0.529}, ORGRIMMAR_PORTAL, 60, true, nil, "ORGRIMMAR_PORTAL"}, -- Shattrath City --> Orgrimmar
31 {{60, 0.517, 0.525}, UNDERCITY_PORTAL, 60, true, nil, "UNDERCITY_PORTAL"}, -- Shattrath City --> Undercity
33 {{67, 0.583, 0.216}, SILVERMOON_CITY_PORTAL, 60, true, nil, "SILVERMOON_CITY_PORTAL"}, -- Dalaran --> Silvermoon City
34 {{67, 0.573, 0.219}, THUNDER_BLUFF_PORTAL, 60, true, nil, "THUNDER_BLUFF_PORTAL"}, -- Dalaran --> Thunder Bluff
35 {{67, 0.553, 0.255}, ORGRIMMAR_PORTAL, 60, true, nil, "ORGRIMMAR_PORTAL"}, -- Dalaran --> Orgrimmar
36 {{67, 0.556, 0.238}, UNDERCITY_PORTAL, 60, true, nil, "UNDERCITY_PORTAL"}, -- Dalaran --> Undercity
37 {{67, 0.563, 0.226}, SHATTRATH_CITY_PORTAL, 60, true, nil, "SHATTRATH_CITY_PORTAL"}, -- Dalaran --> Shatt
41 local static_alliance_routes =
43 {{36, 0.639, 0.083}, {25, 0.764, 0.512}, 180}, -- Deeprun Tram
44 {{10, 0.718, 0.565}, {51, 0.047, 0.636}, 210}, -- Theramore Isle <--> Menethil Harmor
45 {{36, 0.228, 0.560}, {16, 0.323, 0.441}, 210}, -- Stormwind City <--> Auberdine
47 {{36, 0.183, 0.255}, {65, 0.597, 0.694}, 210}, -- Stormwind City <--> Valiance Keep
48 {{51, 0.047, 0.571}, {70, 0.612, 0.626}, 210}, -- Menethil <--> Daggercap Bay
50 {{60, 0.558, 0.366}, STORMWIND_CITY_PORTAL, 60, true, nil, "STORMWIND_CITY_PORTAL"}, -- Shattrath City --> Stormwind City
51 {{60, 0.563, 0.37}, IRONFORGE_PORTAL, 60, true, nil, "IRONFORGE_PORTAL"}, -- Shattrath City --> Ironforge
52 {{60, 0.552, 0.364}, DARNASSUS_PORTAL, 60, true, nil, "DARNASSUS_PORTAL"}, -- Shattrath City --> Darnassus
53 {{60, 0.596, 0.467}, EXODAR_PORTAL, 60, true, nil, "EXODAR_PORTAL"}, -- Shattrath City --> Exodar
55 {{67, 0.401, 0.628}, STORMWIND_CITY_PORTAL, 60, true, nil, "STORMWIND_CITY_PORTAL"}, -- Dalaran --> Stormwind City
56 {{67, 0.395, 0.640}, IRONFORGE_PORTAL, 60, true, nil, "IRONFORGE_PORTAL"}, -- Dalaran --> Ironforge
57 {{67, 0.389, 0.651}, DARNASSUS_PORTAL, 60, true, nil, "DARNASSUS_PORTAL"}, -- Dalaran --> Darnassus
58 {{67, 0.382, 0.664}, EXODAR_PORTAL, 60, true, nil, "EXODAR_PORTAL"}, -- Dalaran --> Exodar
59 {{67, 0.371, 0.667}, SHATTRATH_CITY_PORTAL, 60, true, nil, "SHATTRATH_CITY_PORTAL"}, -- Dalaran --> Shatt
62 local static_shared_routes =
64 {{11, 0.638, 0.387}, {38, 0.257, 0.73}, 210}, -- Ratchet <--> Booty Bay
65 {{40, 0.318, 0.503}, {32, 0.347, 0.84}, 130}, -- Burning Steppes <--> Searing Gorge
67 -- More Alliance routes than anything, but without them theres no valid path to these areas for Horde characters.
68 {{24, 0.559, 0.896}, {21, 0.305, 0.414}, 5}, -- Rut'Theran Village <--> Darnassus
69 {{16, 0.332, 0.398}, {24, 0.548, 0.971}, 210}, -- Auberdine <--> Rut'Theran Village
70 {{16, 0.306, 0.409}, {3, 0.2, 0.546}, 210}, -- Auberdine <--> Azuremyst Isle
72 -- Route to new zone. Not valid, exists only to keep routing from exploding if you don't have the flight routes there.
73 {{41, 0.5, 0.5}, {64, 0.5, 0.5}, 7200}, -- Eversong Woods <--> Sunwell
75 {{70, 0.235, 0.578}, {68, 0.496, 0.784}, 210}, -- Kamagua <--> Moa'ki
76 {{65, 0.789, 0.536}, {68, 0.480, 0.787}, 210}, -- Unu'pe <--> Moa'ki
77 {{67, 0.559, 0.467}, {66, 0.158, 0.428}, 5, true}, -- Dalaran --> Violet Stand
78 {{66, 0.157, 0.425}, {67, 0.559, 0.468}, 5, true}, -- Violent Stand --> Dalaran (slightly different coordinates, may be important once solid walls are in)
80 {{34, 0.817, 0.461}, {78, 0.492, 0.312}, 86400}, -- EPL Ebon Hold <--> Scarlet Enclave Ebon Hold. Exists solely to fix some pathing crashes. 24-hour boat ride :D
82 {{67, 0.256, 0.515}, {8, 0.659, 0.498}, 5, true}, -- Dalaran --> CoT
84 -- Wrath instance entrances
85 {{80, 0.693, 0.730}, {70, 0.573, 0.467}, 5}, -- UK
86 {{86, 0.362, 0.880}, {65, 0.275, 0.260}, 5}, -- Nexus
87 {{92, 0.094, 0.933}, {68, 0.260, 0.508}, 5}, -- AN
88 {{94, 0.900, 0.791}, {68, 0.285, 0.517}, 5}, -- AK
89 {{96, 0.294, 0.810}, {75, 0.286, 0.869}, 5}, -- Draktharon
90 {{100, 0.469, 0.780}, {67, 0.679, 0.694}, 5}, -- VH
91 {{102, 0.590, 0.309}, {75, 0.764, 0.214}, 5}, -- Gundrak NW
92 {{102, 0.344, 0.312}, {75, 0.810, 0.286}, 5}, -- Gundrak SE
93 {{104, 0.344, 0.362}, {73, 0.397, 0.269}, 5}, -- HoS
94 {{106, 0.020, 0.538}, {73, 0.453, 0.216}, 5}, -- HoL
95 {{110, 0.613, 0.476}, {65, 0.275, 0.266}, 5}, -- Oculus
96 {{120, 0.875, 0.712}, {8, 0.614, 0.626}, 5}, -- CoT
97 {{124, 0.445, 0.161}, {70, 0.573, 0.467}, 5}, -- UP
98 {{126, 0.500, 0.500}, {74, 0.500, 0.115}, 5}, -- VoA, zone-in link is incorrect
99 {{128, 0.500, 0.500}, {68, 0.873, 0.510}, 5}, -- Naxx, zone-in link is incorrect (but might be close)
100 {{140, 0.635, 0.501}, {68, 0.600, 0.566}, 5}, -- Sarth
101 {{142, 0.500, 0.500}, {65, 0.275, 0.267}, 5}, -- Malygos, zone-in link is incorrect (not that it matters with malygos)
102 {{144, 0.500, 0.500}, {73, 0.416, 0.179}, 5}, -- Ulduar, zone-in link is incorrect
104 -- Wrath in-zone links, all currently incorrect
105 -- UK
106 {{80, 0.500, 0.500}, {82, 0.500, 0.500}, 5},
107 {{80, 0.500, 0.500}, {84, 0.500, 0.500}, 5},
109 -- AN
110 {{88, 0.500, 0.500}, {90, 0.500, 0.500}, 5},
111 {{88, 0.500, 0.500}, {92, 0.500, 0.500}, 5},
113 -- Drak
114 {{96, 0.500, 0.500}, {98, 0.500, 0.500}, 5},
116 -- HoL
117 {{106, 0.500, 0.500}, {108, 0.500, 0.500}, 5},
119 -- Oculus
120 {{110, 0.500, 0.500}, {112, 0.500, 0.500}, 5},
121 {{110, 0.500, 0.500}, {114, 0.500, 0.500}, 5},
122 {{110, 0.500, 0.500}, {116, 0.500, 0.500}, 5},
124 -- CoT
125 {{120, 0.500, 0.500}, {118, 0.500, 0.500}, 5},
127 -- UP
128 {{122, 0.500, 0.500}, {124, 0.500, 0.500}, 5},
130 -- Naxx
131 {{128, 0.500, 0.500}, {130, 0.500, 0.500}, 5},
132 {{128, 0.500, 0.500}, {132, 0.500, 0.500}, 5},
133 {{128, 0.500, 0.500}, {134, 0.500, 0.500}, 5},
134 {{128, 0.500, 0.500}, {136, 0.500, 0.500}, 5},
135 {{128, 0.500, 0.500}, {138, 0.500, 0.500}, 5},
137 -- Ulduar
138 {{144, 0.500, 0.500}, {146, 0.500, 0.500}, 5},
139 {{144, 0.500, 0.500}, {148, 0.500, 0.500}, 5},
140 {{144, 0.500, 0.500}, {150, 0.500, 0.500}, 5},
141 {{144, 0.500, 0.500}, {152, 0.500, 0.500}, 5},
144 -- Darkportal is handled specially, depending on whether or not you're level 58+ or not.
145 local dark_portal_route = {{33, 0.587, 0.599}, {56, 0.898, 0.502}, 60}
147 local static_zone_transitions =
149 {2, 11, 0.687, 0.872}, -- Ashenvale <--> The Barrens
150 {2, 6, 0.423, 0.711}, -- Ashenvale <--> Stonetalon Mountains
151 {2, 15, 0.954, 0.484}, -- Ashenvale <--> Azshara
152 {2, 16, 0.289, 0.144}, -- Ashenvale <--> Darkshore
153 {2, 13, 0.557, 0.29}, -- Ashenvale <--> Felwood
154 {21, 24, 0.894, 0.358}, -- Darnassus <--> Teldrassil
155 {22, 11, 0.697, 0.604}, -- Mulgore <--> The Barrens
156 {22, 23, 0.376, 0.33}, -- Mulgore <--> Thunder Bluff
157 {22, 23, 0.403, 0.193}, -- Mulgore <--> Thunder Bluff
158 {3, 12, 0.247, 0.494}, -- Azuremyst Isle <--> The Exodar
159 {3, 12, 0.369, 0.469}, -- Azuremyst Isle <--> The Exodar
160 {3, 12, 0.310, 0.487}, -- Azuremyst Isle <--> The Exodar
161 {3, 12, 0.335, 0.494}, -- Azuremyst Isle <--> The Exodar
162 {3, 9, 0.42, 0.013}, -- Azuremyst Isle <--> Bloodmyst Isle
163 {4, 6, 0.539, 0.032}, -- Desolace <--> Stonetalon Mountains
164 {4, 17, 0.428, 0.976}, -- Desolace <--> Feralas
165 {5, 18, 0.865, 0.115}, -- Silithus <--> Un'Goro Crater
166 {7, 11, 0.341, 0.424}, -- Durotar <--> The Barrens
167 {7, 1, 0.455, 0.121}, -- Durotar <--> Orgrimmar
168 {8, 18, 0.269, 0.516}, -- Tanaris <--> Un'Goro Crater
169 {8, 14, 0.512, 0.21}, -- Tanaris <--> Thousand Needles
170 {10, 11, 0.287, 0.472}, -- Dustwallow Marsh <--> The Barrens
171 {10, 11, 0.563, 0.077}, -- Dustwallow Marsh <--> The Barrens
172 {11, 14, 0.442, 0.915}, -- The Barrens <--> Thousand Needles
173 {13, 19, 0.685, 0.06}, -- Felwood <--> Winterspring
174 {13, 20, 0.669, -0.063}, -- Felwood <--> Moonglade
175 {1, 11, 0.118, 0.69}, -- Orgrimmar <--> The Barrens
176 {17, 14, 0.899, 0.46}, -- Feralas <--> Thousand Needles
177 {6, 11, 0.836, 0.973}, -- Stonetalon Mountains <--> The Barrens
178 {26, 48, 0.521, 0.7}, -- Alterac Mountains <--> Hillsbrad Foothills
179 {26, 35, 0.173, 0.482}, -- Alterac Mountains <--> Silverpine Forest
180 {26, 50, 0.807, 0.347}, -- Alterac Mountains <--> Western Plaguelands
181 {39, 51, 0.454, 0.89}, -- Arathi Highlands <--> Wetlands
182 {39, 48, 0.2, 0.293}, -- Arathi Highlands <--> Hillsbrad Foothills
183 {27, 29, 0.49, 0.071}, -- Badlands <--> Loch Modan
184 -- {27, 32, -0.005, 0.636}, -- Badlands <--> Searing Gorge -- This is the "alliance-only" locked path, I'm disabling it for now entirely
185 {33, 46, 0.519, 0.051}, -- Blasted Lands <--> Swamp of Sorrows
186 {40, 30, 0.79, 0.842}, -- Burning Steppes <--> Redridge Mountains
187 {47, 31, 0.324, 0.363}, -- Deadwind Pass <--> Duskwood
188 {47, 46, 0.605, 0.41}, -- Deadwind Pass <--> Swamp of Sorrows
189 {28, 25, 0.534, 0.349}, -- Dun Morogh <--> Ironforge
190 {28, 29, 0.863, 0.514}, -- Dun Morogh <--> Loch Modan
191 {28, 29, 0.844, 0.31}, -- Dun Morogh <--> Loch Modan
192 {31, 37, 0.801, 0.158}, -- Duskwood <--> Elwynn Forest
193 {31, 37, 0.15, 0.214}, -- Duskwood <--> Elwynn Forest
194 {31, 38, 0.447, 0.884}, -- Duskwood <--> Stranglethorn Vale
195 {31, 38, 0.209, 0.863}, -- Duskwood <--> Stranglethorn Vale
196 {31, 30, 0.941, 0.103}, -- Duskwood <--> Redridge Mountains
197 {31, 49, 0.079, 0.638}, -- Duskwood <--> Westfall
198 {34, 50, 0.077, 0.661}, -- Eastern Plaguelands <--> Western Plaguelands
199 {34, 44, 0.575, 0.000}, -- Eastern Plaguelands <--> Ghostlands
200 {37, 36, 0.321, 0.493}, -- Elwynn Forest <--> Stormwind City -- Don't need to convert because it's in Elwynn coordinates, not Stormwind coordinates
201 {37, 49, 0.202, 0.804}, -- Elwynn Forest <--> Westfall
202 {37, 30, 0.944, 0.724}, -- Elwynn Forest <--> Redridge Mountains
203 {41, 52, 0.567, 0.494}, -- Eversong Woods <--> Silvermoon City
204 {41, 44, 0.486, 0.916}, -- Eversong Woods <--> Ghostlands
205 {35, 43, 0.678, 0.049}, -- Silverpine Forest <--> Tirisfal Glades
206 {42, 50, 0.217, 0.264}, -- The Hinterlands <--> Western Plaguelands
207 {43, 45, 0.619, 0.651}, -- Tirisfal Glades <--> Undercity
208 {43, 50, 0.851, 0.703}, -- Tirisfal Glades <--> Western Plaguelands
209 {38, 49, 0.292, 0.024}, -- Stranglethorn Vale <--> Westfall
210 {48, 35, 0.137, 0.458}, -- Hillsbrad Foothills <--> Silverpine Forest
211 {48, 42, 0.899, 0.253}, -- Hillsbrad Foothills <--> The Hinterlands
212 {29, 51, 0.252, 0}, -- Loch Modan <--> Wetlands
214 -- These are just guesses, since I haven't actually been to these areas.
215 {58, 60, 0.783, 0.545}, -- Nagrand <--> Shattrath City
216 {60, 55, 0.782, 0.492}, -- Shattrath City <--> Terokkar Forest
217 {54, 59, 0.842, 0.284}, -- Blade's Edge Mountains <--> Netherstorm
218 {54, 57, 0.522, 0.996}, -- Blade's Edge Mountains <--> Zangarmarsh
219 {54, 57, 0.312, 0.94}, -- Blade's Edge Mountains <--> Zangarmarsh
220 {56, 55, 0.353, 0.901}, -- Hellfire Peninsula <--> Terokkar Forest
221 {56, 57, 0.093, 0.519}, -- Hellfire Peninsula <--> Zangarmarsh
222 {58, 55, 0.8, 0.817}, -- Nagrand <--> Terokkar Forest
223 {58, 57, 0.343, 0.159}, -- Nagrand <--> Zangarmarsh
224 {58, 57, 0.754, 0.331}, -- Nagrand <--> Zangarmarsh
225 {53, 55, 0.208, 0.271}, -- Shadowmoon Valley <--> Terokkar Forest
226 {55, 57, 0.341, 0.098}, -- Terokkar Forest <--> Zangarmarsh
228 -- Most of these are also guesses :)
229 {65, 72, 0.547, 0.059}, -- Borean Tundra <--> Sholazar Basin
230 {65, 68, 0.967, 0.359}, -- Borean Tundra <--> Dragonblight
231 {74, 72, 0.208, 0.191}, -- Wintergrasp <--> Sholazar
232 {68, 74, 0.250, 0.410}, -- Dragonblight <--> Wintergrasp
233 {68, 71, 0.359, 0.155}, -- Dragonblight <--> Icecrown
234 {68, 66, 0.612, 0.142}, -- Dragonblight <--> Crystalsong
235 {68, 75, 0.900, 0.200}, -- Dragonblight <--> Zul'Drak
236 {68, 69, 0.924, 0.304}, -- Dragonblight <--> Grizzly Hills
237 {68, 69, 0.931, 0.634}, -- Dragonblight <--> Grizzly Hills
238 {70, 69, 0.540, 0.042}, -- Howling Fjord <--> Grizzly Hills
239 {70, 69, 0.233, 0.074}, -- Howling Fjord <--> Grizzly Hills
240 {70, 69, 0.753, 0.060}, -- Howling Fjord <--> Grizzly Hills
241 {69, 75, 0.432, 0.253}, -- Grizzly Hills <--> Zul'Drak
242 {69, 75, 0.583, 0.136}, -- Grizzly Hills <--> Zul'Drak
243 {66, 75, 0.967, 0.599}, -- Crystalsong <--> Zul'Drak
244 {66, 71, 0.156, 0.085}, -- Crystalsong <--> Icecrown
245 {66, 73, 0.706, 0.315}, -- Crystalsong <--> Storm Peaks
246 {66, 73, 0.839, 0.340}, -- Crystalsong <--> Storm Peaks
247 {71, 73, 0.920, 0.767}, -- Icecrown <--> Storm Peaks
250 local walkspeed_multiplier = 1/7 -- Every yard walked takes this many seconds.
252 QuestHelper.prepared_objectives = {}
253 QuestHelper.named_nodes = {}
255 local function cont_dist(a, b)
256 local x, y = a.x-b.x, a.y-b.y
257 return math.sqrt(x*x+y*y)
260 function QuestHelper:ComputeRoute(p1, p2)
261 for i in ipairs(p1[1]) do QuestHelper: Assert(p1[2][i], "p1 nil flightpath error resurgence!") end
262 for i in ipairs(p2[1]) do QuestHelper: Assert(p2[2][i], "p2 nil flightpath error resurgence!") end
264 if not p1 or not p2 then QuestHelper:Error("Boom!") end
265 local graph = self.world_graph
267 graph:PrepareSearch()
269 local l = p2[2]
270 local el = p2[1]
271 for i, n in ipairs(el) do
272 n.e, n.w = l[i], 1
273 n.s = 3
276 l = p1[2]
277 for n in pairs(graph.open) do QuestHelper: Assert(nil, "not empty in preparesearch within computeroute") end
278 for i, n in ipairs(p1[1]) do
279 graph:AddRouteStartNode(n, l[i], el)
282 local e = graph:DoRouteSearch(el)
284 assert(e)
286 local d = e.g+e.e
288 if p1[1] == p2[1] then
289 local x, y = p1[3]-p2[3], p1[4]-p2[4]
290 local d2 = math.sqrt(x*x+y*y)
291 if d2 < d then
292 d = d2
293 e = nil
297 return e, d
300 -- Let's annotate the hell out of this
301 -- 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.)
302 -- 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 :(
303 function QuestHelper:ComputeTravelTime(p1, p2)
304 if not p1 or not p2 then QuestHelper:Error("Boom!") end
305 local graph = self.world_graph
307 graph:PrepareSearch()
309 local l = p2[2] -- Distance to zone boundaries in p2
310 local el = p2[1] -- Zone object for the zone that p2 is in
311 for i, n in ipairs(el) do -- i is the zone index, n is the zone data
312 n.e, n.w = l[i], 1 -- n.e is distance from p2 to the current zone boundary, n.w is 1 (weight?)
313 assert(n.e)
314 n.s = 3 -- this is "state", I think it means "visited". TODO: untangle n.s and make it suck less than it currently does
317 l = p1[2] -- Distance to zone boundaries, again
318 for i, n in ipairs(p1[1]) do
319 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?
322 local e = graph:DoSearch(el)
324 assert(e)
326 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
328 if p1[1] == p2[1] then -- if they're in the same zone, we allow the user to walk from one point to another
329 local x, y = p1[3]-p2[3], p1[4]-p2[4]
330 d = math.min(d, math.sqrt(x*x+y*y))
333 return d
336 function QuestHelper:CreateGraphNode(c, x, y, n)
337 local node = self.world_graph:CreateNode()
339 if y then
340 node.c = c
341 node.x = x
342 node.y = y
343 node.name = n
344 else
345 QuestHelper: Assert(QuestHelper_ZoneLookup[c[1]], "Zone couldn't be found, and should have been")
346 local cont, zone = unpack(QuestHelper_ZoneLookup[c[1]])
347 node.c = cont
348 node.x, node.y = self.Astrolabe:TranslateWorldMapPosition(cont, zone, c[2], c[3], cont, 0)
349 node.x = node.x * self.continent_scales_x[node.c]
350 node.y = node.y * self.continent_scales_y[node.c]
351 node.name = c[5] or QuestHelper_NameLookup[c[1]]
354 node.w = 1
355 return node
358 function QuestHelper:CreateAndAddZoneNode(z, c, x, y)
359 local node = self:CreateGraphNode(c, x, y)
360 if not node then return end -- exception for Wrath changeover
361 -- Not going to merge nodes.
362 --[[local closest, travel_time = nil, 0
364 for i, n in ipairs(z) do
365 local t = math.sqrt((n.x-node.x)*(n.x-node.x)+(n.y-node.y)*(n.y-node.y))
366 if not closest or t < travel_time then
367 closest, travel_time = n, t
371 if closest and travel_time < 10 then
372 closest.x = (closest.x * closest.w + node.x)/(closest.w+1)
373 closest.y = (closest.y * closest.w + node.y)/(closest.w+1)
374 closest.w = closest.w + 1
375 self.world_graph:DestroyNode(node)
376 return closest
377 else]]
378 table.insert(z, node)
379 return node
380 --end
383 function QuestHelper:CreateAndAddStaticNodePair(data)
384 local node1, node2
386 if data[5] and self.named_nodes[data[5]] then
387 node1 = self.named_nodes[data[5]]
388 else
389 node1 = self:CreateAndAddZoneNode(self.zone_nodes[data[1][1]], data[1])
390 if not node1 then return end -- exception for Wrath changeover
391 if data[5] then self.named_nodes[data[5]] = node1 end
394 if data[6] and self.named_nodes[data[6]] then
395 node2 = self.named_nodes[data[6]]
396 else
397 node2 = self:CreateAndAddZoneNode(self.zone_nodes[data[2][1]], data[2])
398 if not node2 then return end -- exception for Wrath changeover
399 if data[6] then self.named_nodes[data[6]] = node2 end
402 node1.name = node1.name or "route to "..QuestHelper_NameLookup[data[2][1]]
403 node2.name = node2.name or "route to "..QuestHelper_NameLookup[data[1][1]]
405 node1:Link(node2, data[3])
407 if not data[4] then -- If data[4] is true, then this is a one-way trip.
408 node2:Link(node1, data[3])
411 QH_Timeslice_Yield()
412 return node1, node2
415 function QuestHelper:GetNodeByName(name, fallback_data)
416 local node = self.named_nodes[name]
417 if not node and fallback_data then
418 node = self:CreateAndAddZoneNode(self.zone_nodes[fallback_data[1]], fallback_data)
419 self.named_nodes[name] = node
421 return node
424 local function nodeLeavesContinent(node, c)
425 if node.c == c then
426 for n, d in pairs(node.n) do
427 if n.c ~= c then
428 return true
432 return false
435 local function isGoodPath(start_node, end_node, i, j)
436 -- Checks to make sure a path doesn't leave the continent only to reenter it.
437 while true do
438 if end_node.p then
439 if end_node.c == i then
440 return false
442 end_node = end_node.p
443 if end_node.c == j then
444 return false
446 else
447 return end_node == start_node
452 local function shouldLink(a, b)
453 -- TODO: Need to have objectives not create links to unreachable nodes.
454 return a ~= b
455 --[[
456 if a == b then
457 return false
458 else
459 for id in pairs(a.id_from) do
460 if not b.id_to[id] then
461 for id in pairs(b.id_to) do
462 if not a.id_from[id] then
463 return true
469 return false
470 end]]
473 local function getNPCNode(npc)
474 local npc_objective = QuestHelper:GetObjective("monster", npc)
475 if npc_objective:Known() then
476 npc_objective:PrepareRouting()
477 local p = npc_objective:Position()
478 local node = nil
480 if p then
481 node = QuestHelper:CreateAndAddZoneNode(p[1], p[1].c, p[3], p[4])
484 npc_objective:DoneRouting()
485 return node
487 return nil
490 function QuestHelper:CreateAndAddTransitionNode(z1, z2, pos)
491 QuestHelper: Assert(z1 and z2, "Zone couldn't be found, and should have been")
493 local node = self:CreateGraphNode(pos)
495 local closest, travel_time = nil, 0
497 for i, n in ipairs(z1) do
498 local t = math.sqrt((n.x-node.x)*(n.x-node.x)+(n.y-node.y)*(n.y-node.y))
499 if not closest or t < travel_time then
500 closest, travel_time = n, t
504 if z1 ~= z2 then
505 for i, n in ipairs(z2) do
506 local t = math.sqrt((n.x-node.x)*(n.x-node.x)+(n.y-node.y)*(n.y-node.y))
507 if not closest or t < travel_time then
508 closest, travel_time = n, t
513 if closest and travel_time < 10 then
514 --QuestHelper:TextOut("Node already exists at "..closest.x..", "..closest.y..", name="..(closest.name or "nil"))
515 closest.x = (closest.x * closest.w + node.x)/(closest.w+1)
516 closest.y = (closest.y * closest.w + node.y)/(closest.w+1)
517 closest.w = closest.w + 1
518 local z1_has, z2_has = false, false
520 -- Just because the node already exists, doesn't mean its already in both lists!
522 for i, n in ipairs(z1) do
523 if n == closest then
524 z1_has = true
525 break
529 if z1 ~= z2 then
530 for i, n in ipairs(z2) do
531 if n == closest then
532 z2_has = true
533 break
536 else
537 z2_has = true
540 if not z1_has then table.insert(z1, closest) end
541 if not z2_has then table.insert(z2, closest) end
543 self.world_graph:DestroyNode(node)
544 QH_Timeslice_Yield()
545 return closest
546 else
547 table.insert(z1, node)
548 if z1 ~= z2 then table.insert(z2, node) end
549 QH_Timeslice_Yield()
550 return node
554 function QuestHelper:ReleaseObjectivePathingInfo(o)
555 if o.setup then
556 for z, pl in pairs(o.p) do
557 self:ReleaseTable(o.d[z])
559 for i, p in ipairs(pl) do
560 self:ReleaseTable(p[2])
561 self:ReleaseTable(p)
564 self:ReleaseTable(pl)
567 self:ReleaseTable(o.d)
568 self:ReleaseTable(o.p)
569 self:ReleaseTable(o.nm)
570 self:ReleaseTable(o.nm2)
571 self:ReleaseTable(o.nl)
573 local cache = o.distance_cache
574 for k, v in pairs(cache) do
575 self:ReleaseTable(v)
576 cache[k] = nil
578 self:ReleaseTable(cache)
580 o.d, o.p, o.nm, o.nm2, o.nl = nil, nil, nil, nil, nil
581 o.distance_cache = nil
582 o.pos, o.sop = nil, nil -- ResetPathing will preserve these values if needed.
583 o.setup = nil
587 function QuestHelper:SetupTeleportInfo(info, can_create)
588 self:TeleportInfoClear(info)
590 if QuestHelper_Home then
591 local node = self:GetNodeByName("HOME_PORTAL", can_create and QuestHelper_Home)
592 if node then
593 local cooldown = self:ItemCooldown(6948)
594 if cooldown then
595 self:SetTeleportInfoTarget(info, node, GetTime()-60*60+cooldown, 60*60, 10)
597 else
598 self.defered_graph_reset = true
602 -- TODO: Compact this. . . and find a better way to tell if the player has a spell.
604 if GetSpellTexture("Teleport: Darnassus") then
605 local node = self:GetNodeByName("DARNASSUS_PORTAL", can_create and DARNASSUS_PORTAL)
606 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
609 if GetSpellTexture("Teleport: Exodar") then
610 local node = self:GetNodeByName("EXODAR_PORTAL", can_create and EXODAR_PORTAL)
611 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
614 if GetSpellTexture("Teleport: Ironforge") then
615 local node = self:GetNodeByName("IRONFORGE_PORTAL", can_create and IRONFORGE_PORTAL)
616 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
619 if GetSpellTexture("Teleport: Moonglade") then
620 local node = self:GetNodeByName("MOONGLADE_PORTAL", can_create and MOONGLADE_PORTAL)
621 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10) else self.defered_graph_reset = true end
624 if GetSpellTexture("Teleport: Orgrimmar") then
625 local node = self:GetNodeByName("ORGRIMMAR_PORTAL", can_create and ORGRIMMAR_PORTAL)
626 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
629 if GetSpellTexture("Teleport: Shattrath") then
630 local node = self:GetNodeByName("SHATTRATH_CITY_PORTAL", can_create and SHATTRATH_CITY_PORTAL)
631 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
634 if GetSpellTexture("Teleport: Silvermoon") then
635 local node = self:GetNodeByName("SILVERMOON_CITY_PORTAL", can_create and SILVERMOON_CITY_PORTAL)
636 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
639 if GetSpellTexture("Teleport: Stormwind") then
640 local node = self:GetNodeByName("STORMWIND_CITY_PORTAL", can_create and STORMWIND_CITY_PORTAL)
641 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
644 if GetSpellTexture("Teleport: Thunder Bluff") then
645 local node = self:GetNodeByName("THUNDER_BLUFF_PORTAL", can_create and THUNDER_BLUFF_PORTAL)
646 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
649 if GetSpellTexture("Teleport: Undercity") then
650 local node = self:GetNodeByName("UNDERCITY_PORTAL", can_create and UNDERCITY_PORTAL)
651 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
654 self:SetTeleportInfoReagent(info, 17031, self:CountItem(17031))
657 function QuestHelper:ResetPathing()
658 for key in pairs(self.named_nodes) do
659 self.named_nodes[key] = nil
662 -- Objectives may include cached information that depends on the world graph.
663 local i = 1
665 while i <= #self.prepared_objectives do
666 local o = self.prepared_objectives[i]
668 if o.setup_count == 0 then
669 table.remove(self.prepared_objectives, i)
670 self:ReleaseObjectivePathingInfo(o)
671 else
672 -- Routing should reset the positions of objectives in the route after the reset is complete.
673 o.pos = nil
674 self:ReleaseObjectivePathingInfo(o)
675 i = i + 1
679 local to_readd = self.prepared_objectives
680 self.prepared_objectives = self.old_prepared_objectives or {}
681 self.old_prepared_objectives = to_readd
683 local zone_nodes = self.zone_nodes
684 if not zone_nodes then
685 zone_nodes = {}
686 self.zone_nodes = zone_nodes
689 local flight_master_nodes = self.flight_master_nodes
690 if not flight_master_nodes then
691 flight_master_nodes = {}
692 self.flight_master_nodes = flight_master_nodes
693 else
694 for key in pairs(flight_master_nodes) do
695 flight_master_nodes[key] = nil
699 self.world_graph:Reset()
700 QH_Timeslice_Yield()
702 local continent_scales_x, continent_scales_y = self.continent_scales_x, self.continent_scales_y
703 if not continent_scales_x then
704 continent_scales_x = {}
705 continent_scales_y = {}
706 self.continent_scales_x = continent_scales_x
707 self.continent_scales_y = continent_scales_y
710 for c in pairs(self.Astrolabe:GetMapVirtualContinents()) do
711 if not continent_scales_x[c] then
712 local _, x, y = self.Astrolabe:ComputeDistance(c, 0, 0.25, 0.25, c, 0, 0.75, 0.75)
714 continent_scales_x[c] = x*walkspeed_multiplier*2
715 continent_scales_y[c] = y*walkspeed_multiplier*2
719 for i, name in pairs(QuestHelper_NameLookup) do
720 local z = zone_nodes[i]
721 if not z then
722 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.
723 zone_nodes[i] = z
724 z.i, z.c, z.z = i, unpack(QuestHelper_ZoneLookup[i])
725 else
726 for key in pairs(z) do
727 z[key] = nil
729 z.i, z.c, z.z = i, unpack(QuestHelper_ZoneLookup[i])
733 self:SetupTeleportInfo(self.teleport_info, true)
734 QH_Timeslice_Yield()
736 --[[for node, info in pairs(self.teleport_info.node) do
737 self:TextOut("You can teleport to "..(node.name or "nil").. " in "..self:TimeString(info[1]+info[2]-GetTime()))
738 end]]
740 if self.faction == 1 then
741 for i, data in ipairs(static_alliance_routes) do
742 self:CreateAndAddStaticNodePair(data)
744 elseif self.faction == 2 then
745 for i, data in ipairs(static_horde_routes) do
746 self:CreateAndAddStaticNodePair(data)
750 for i, data in ipairs(static_shared_routes) do
751 self:CreateAndAddStaticNodePair(data)
754 if self.player_level >= 58 then
755 dark_portal_route[3] = 5
756 else
757 -- If you can't take the route yet, we'll still add it and just pretend it will take a really long time.
758 dark_portal_route[3] = 86400
761 self:CreateAndAddStaticNodePair(dark_portal_route)
763 local st = self:CreateTable("ResetPathing local st")
765 for i, data in pairs(static_zone_transitions) do
766 st[1], st[2], st[3] = data[1], data[3], data[4]
768 local transnode = self:CreateAndAddTransitionNode(zone_nodes[data[1]],
769 zone_nodes[data[2]],
771 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
774 self:ReleaseTable(st)
776 -- Create and link the flight route nodes.
777 local flight_times = self.flight_times
778 if not flight_times then
779 self:buildFlightTimes()
780 flight_times = self.flight_times
783 for start, list in pairs(flight_times) do
784 for dest, duration in pairs(list) do
785 local a_npc, b_npc = self:getFlightInstructor(start), self:getFlightInstructor(dest)
787 if a_npc and b_npc then
788 local a, b = flight_master_nodes[start], flight_master_nodes[dest]
790 if not a then
791 a = getNPCNode(a_npc)
792 if a then
793 flight_master_nodes[start] = a
794 a.name = (select(3, string.find(start, "^(.*),")) or start).." flight point"
798 if not b then
799 b = getNPCNode(b_npc)
800 if b then
801 flight_master_nodes[dest] = b
802 b.name = (select(3, string.find(dest, "^(.*),")) or dest).." flight point"
806 if a and b then
807 a:Link(b, duration+5)
811 QH_Timeslice_Yield()
814 -- id_from, id_to, and id_local will be used in determining whether there is a point to linking nodes together.
815 for i, n in ipairs(self.world_graph.nodes) do
816 n.id_from = self:CreateTable("ResetPathing n.id_from")
817 n.id_to = self:CreateTable("ResetPathing n.id_to")
818 n.id_local = self:CreateTable("ResetPathing n.id_local")
821 -- Setup the local ids a node exists in.
822 for i, list in pairs(zone_nodes) do
823 for _, n in ipairs(list) do
824 n.id_local[i] = true
825 n.id_to[i] = true
826 n.id_from[i] = true
830 -- Figure out where each node can come from or go to.
831 for i, list in pairs(zone_nodes) do
832 for _, node in ipairs(list) do
833 for n in pairs(node.n) do
834 for id in pairs(n.id_local) do node.id_to[id] = true end
835 for id in pairs(node.id_local) do n.id_from[id] = true end
840 -- We'll treat 0 as a special id for where ever it is the player happens to be.
841 for node in pairs(self.teleport_info.node) do
842 node.id_from[0] = true
845 -- Will go through each zone and link all the nodes we have so far with every other node.
846 for _, list in pairs(zone_nodes) do
847 for i = 1,#list do
848 for j = 1,#list do
849 if shouldLink(list[i], list[j]) then
850 list[i]:Link(list[j], cont_dist(list[i], list[j]))
856 QH_Timeslice_Yield()
858 -- We don't need to know where the nodes can go or come from now.
859 for i, n in ipairs(self.world_graph.nodes) do
860 self:ReleaseTable(n.id_from)
861 self:ReleaseTable(n.id_to)
862 self:ReleaseTable(n.id_local)
863 n.id_from, n.id_to, n.id_local = nil, nil, nil
866 -- TODO: This is a work around until I fix shouldLink
867 for start, list in pairs(flight_times) do
868 for dest, duration in pairs(list) do
869 local a, b = flight_master_nodes[start], flight_master_nodes[dest]
870 if a and b then
871 a:Link(b, duration+5)
876 QH_Timeslice_Yield()
877 -- self.world_graph:SanityCheck()
879 -- Remove objectives again, since we created some for the flight masters.
880 while true do
881 local o = table.remove(self.prepared_objectives)
882 if not o then break end
884 self:ReleaseObjectivePathingInfo(o)
886 if o.setup_count > 0 then
887 -- There's a chance an objective could end up in the list twice, but we'll deal with that by not actually
888 -- adding locations for it if it's already setup.
889 table.insert(to_readd, o)
893 while true do
894 local obj = table.remove(to_readd)
895 if not obj then break end
897 if not obj.setup then -- In case the objective was added multiple times to the to_readd list.
898 obj.d = QuestHelper:CreateTable("ResetPathing obj.d")
899 obj.p = QuestHelper:CreateTable("ResetPathing obj.p")
900 obj.nm = QuestHelper:CreateTable("ResetPathing obj.nm")
901 obj.nm2 = QuestHelper:CreateTable("ResetPathing obj.nm2")
902 obj.nl = QuestHelper:CreateTable("ResetPathing obj.nl")
903 obj.distance_cache = QuestHelper:CreateTable("ResetPathing obj.distance_cache")
904 obj:AppendPositions(obj, 1, nil)
905 obj:FinishAddLoc()
909 if self.i then
910 self.pos[1] = self.zone_nodes[self.i]
911 for i, n in ipairs(self.pos[1]) do
912 local a, b = n.x-self.pos[3], n.y-self.pos[4]
913 self.pos[2][i] = math.sqrt(a*a+b*b)
917 -- And if all went according to plan, we now have a graph we can follow to get from anywhere to anywhere.
919 if self.graph_walker then
920 self.graph_walker:GraphChanged()
925 function QuestHelper:Disallowed(index)
926 return QuestHelper_RestrictedZones[index] ~= QuestHelper_RestrictedZones[self.i]