Update translations
[QuestHelper.git] / pathfinding.lua
blob5eeb0cf5a1f695568c1e6ebabfb1b3a5089518bf
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 -- this is aldor-only
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 function load_graph_links()
251 local function convert_coordinate(coord)
252 QuestHelper: Assert(coord[1] and coord[2] and coord[3])
253 local c, x, y = QuestHelper.Astrolabe:GetAbsoluteContinentPosition(QuestHelper_ZoneLookup[coord[1]][1], QuestHelper_ZoneLookup[coord[1]][2], coord[2], coord[3])
254 QuestHelper: Assert(c)
255 return {x = x, y = y, p = coord[1], c = c}
258 local function do_routes(routes)
259 for _, v in ipairs(routes) do
260 local src = convert_coordinate(v[1])
261 local dst = convert_coordinate(v[2])
262 QuestHelper: Assert(src.x and dst.x)
263 src.map_desc = {QHFormat("WAYPOINT_REASON", QuestHelper_NameLookup[v[2][1]])}
264 dst.map_desc = {QHFormat("WAYPOINT_REASON", QuestHelper_NameLookup[v[1][1]])}
266 local rev_cost = v[3]
267 if v[4] then rev_cost = nil end
268 QH_Graph_Plane_Makelink("static_route", src, dst, v[3], rev_cost) -- this couldn't possibly fail
272 local faction_db
273 if UnitFactionGroup("player") == "Alliance" then
274 faction_db = static_alliance_routes
275 else
276 faction_db = static_horde_routes
279 do_routes(faction_db)
280 do_routes(static_shared_routes)
282 for _, v in ipairs(static_zone_transitions) do
283 local src = convert_coordinate({v[1], v[3], v[4]})
284 local dst = convert_coordinate({v[1], v[3], v[4]})
285 dst.p = v[2]
286 src.map_desc = {QHFormat("WAYPOINT_REASON", QHFormat("ZONE_BORDER", QuestHelper_NameLookup[v[1]], QuestHelper_NameLookup[v[2]]))}
287 dst.map_desc = {QHFormat("WAYPOINT_REASON", QHFormat("ZONE_BORDER", QuestHelper_NameLookup[v[2]], QuestHelper_NameLookup[v[1]]))}
288 QH_Graph_Plane_Makelink("static_transition", src, dst, 0, 0)
292 local src = convert_coordinate(dark_portal_route[1])
293 local dst = convert_coordinate(dark_portal_route[2])
294 src.map_desc = {QHFormat("WAYPOINT_REASON", QHFormat("ZONE_BORDER", QuestHelper_NameLookup[dark_portal_route[1]], QuestHelper_NameLookup[dark_portal_route[2]]))}
295 dst.map_desc = {QHFormat("WAYPOINT_REASON", QHFormat("ZONE_BORDER", QuestHelper_NameLookup[dark_portal_route[2]], QuestHelper_NameLookup[dark_portal_route[1]]))}
296 QH_Graph_Plane_Makelink("dark_portal", src, dst, 15, 15)
300 -- pretty much everything after this is going to eventually end up eviscerated very, very soon
301 do return end -- like NOW
303 local walkspeed_multiplier = 1/7 -- Every yard walked takes this many seconds.
305 QuestHelper.prepared_objectives = {}
306 QuestHelper.named_nodes = {}
308 local function cont_dist(a, b)
309 local x, y = a.x-b.x, a.y-b.y
310 return math.sqrt(x*x+y*y)
313 function QuestHelper:ComputeRoute(p1, p2)
314 for i in ipairs(p1[1]) do QuestHelper: Assert(p1[2][i], "p1 nil flightpath error resurgence!") end
315 for i in ipairs(p2[1]) do QuestHelper: Assert(p2[2][i], "p2 nil flightpath error resurgence!") end
317 if not p1 or not p2 then QuestHelper:Error("Boom!") end
318 local graph = self.world_graph
320 graph:PrepareSearch()
322 local l = p2[2]
323 local el = p2[1]
324 for i, n in ipairs(el) do
325 n.e, n.w = l[i], 1
326 n.s = 3
329 l = p1[2]
330 for n in pairs(graph.open) do QuestHelper: Assert(nil, "not empty in preparesearch within computeroute") end
331 for i, n in ipairs(p1[1]) do
332 graph:AddRouteStartNode(n, l[i], el)
335 local e = graph:DoRouteSearch(el)
337 assert(e)
339 local d = e.g+e.e
341 if p1[1] == p2[1] then
342 local x, y = p1[3]-p2[3], p1[4]-p2[4]
343 local d2 = math.sqrt(x*x+y*y)
344 if d2 < d then
345 d = d2
346 e = nil
350 return e, d
353 -- Let's annotate the hell out of this
354 -- 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.)
355 -- 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 :(
356 function QuestHelper:ComputeTravelTime(p1, p2)
357 if not p1 or not p2 then QuestHelper:Error("Boom!") end
358 local graph = self.world_graph
360 graph:PrepareSearch()
362 local l = p2[2] -- Distance to zone boundaries in p2
363 local el = p2[1] -- Zone object for the zone that p2 is in
364 for i, n in ipairs(el) do -- i is the zone index, n is the zone data
365 n.e, n.w = l[i], 1 -- n.e is distance from p2 to the current zone boundary, n.w is 1 (weight?)
366 assert(n.e)
367 n.s = 3 -- this is "state", I think it means "visited". TODO: untangle n.s and make it suck less than it currently does
370 l = p1[2] -- Distance to zone boundaries, again
371 for i, n in ipairs(p1[1]) do
372 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?
375 local e = graph:DoSearch(el)
377 assert(e)
379 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
381 if p1[1] == p2[1] then -- if they're in the same zone, we allow the user to walk from one point to another
382 local x, y = p1[3]-p2[3], p1[4]-p2[4]
383 d = math.min(d, math.sqrt(x*x+y*y))
386 return d
389 function QuestHelper:CreateGraphNode(c, x, y, n)
390 local node = self.world_graph:CreateNode()
392 if y then
393 node.c = c
394 node.x = x
395 node.y = y
396 node.name = n
397 else
398 QuestHelper: Assert(QuestHelper_ZoneLookup[c[1]], "Zone couldn't be found, and should have been")
399 local cont, zone = unpack(QuestHelper_ZoneLookup[c[1]])
400 node.c = cont
401 node.x, node.y = self.Astrolabe:TranslateWorldMapPosition(cont, zone, c[2], c[3], cont, 0)
402 node.x = node.x * self.continent_scales_x[node.c]
403 node.y = node.y * self.continent_scales_y[node.c]
404 node.name = c[5] or QuestHelper_NameLookup[c[1]]
407 node.w = 1
408 return node
411 function QuestHelper:CreateAndAddZoneNode(z, c, x, y)
412 local node = self:CreateGraphNode(c, x, y)
413 if not node then return end -- exception for Wrath changeover
414 -- Not going to merge nodes.
415 --[[local closest, travel_time = nil, 0
417 for i, n in ipairs(z) do
418 local t = math.sqrt((n.x-node.x)*(n.x-node.x)+(n.y-node.y)*(n.y-node.y))
419 if not closest or t < travel_time then
420 closest, travel_time = n, t
424 if closest and travel_time < 10 then
425 closest.x = (closest.x * closest.w + node.x)/(closest.w+1)
426 closest.y = (closest.y * closest.w + node.y)/(closest.w+1)
427 closest.w = closest.w + 1
428 self.world_graph:DestroyNode(node)
429 return closest
430 else]]
431 table.insert(z, node)
432 return node
433 --end
436 function QuestHelper:CreateAndAddStaticNodePair(data)
437 local node1, node2
439 if data[5] and self.named_nodes[data[5]] then
440 node1 = self.named_nodes[data[5]]
441 else
442 node1 = self:CreateAndAddZoneNode(self.zone_nodes[data[1][1]], data[1])
443 if not node1 then return end -- exception for Wrath changeover
444 if data[5] then self.named_nodes[data[5]] = node1 end
447 if data[6] and self.named_nodes[data[6]] then
448 node2 = self.named_nodes[data[6]]
449 else
450 node2 = self:CreateAndAddZoneNode(self.zone_nodes[data[2][1]], data[2])
451 if not node2 then return end -- exception for Wrath changeover
452 if data[6] then self.named_nodes[data[6]] = node2 end
455 node1.name = node1.name or "route to "..QuestHelper_NameLookup[data[2][1]]
456 node2.name = node2.name or "route to "..QuestHelper_NameLookup[data[1][1]]
458 node1:Link(node2, data[3])
460 if not data[4] then -- If data[4] is true, then this is a one-way trip.
461 node2:Link(node1, data[3])
464 QH_Timeslice_Yield()
465 return node1, node2
468 function QuestHelper:GetNodeByName(name, fallback_data)
469 local node = self.named_nodes[name]
470 if not node and fallback_data then
471 node = self:CreateAndAddZoneNode(self.zone_nodes[fallback_data[1]], fallback_data)
472 self.named_nodes[name] = node
474 return node
477 local function nodeLeavesContinent(node, c)
478 if node.c == c then
479 for n, d in pairs(node.n) do
480 if n.c ~= c then
481 return true
485 return false
488 local function isGoodPath(start_node, end_node, i, j)
489 -- Checks to make sure a path doesn't leave the continent only to reenter it.
490 while true do
491 if end_node.p then
492 if end_node.c == i then
493 return false
495 end_node = end_node.p
496 if end_node.c == j then
497 return false
499 else
500 return end_node == start_node
505 local function shouldLink(a, b)
506 -- TODO: Need to have objectives not create links to unreachable nodes.
507 return a ~= b
508 --[[
509 if a == b then
510 return false
511 else
512 for id in pairs(a.id_from) do
513 if not b.id_to[id] then
514 for id in pairs(b.id_to) do
515 if not a.id_from[id] then
516 return true
522 return false
523 end]]
526 local function getNPCNode(npc)
527 local npc_objective = QuestHelper:GetObjective("monster", npc)
528 if npc_objective:Known() then
529 npc_objective:PrepareRouting()
530 local p = npc_objective:Position()
531 local node = nil
533 if p then
534 node = QuestHelper:CreateAndAddZoneNode(p[1], p[1].c, p[3], p[4])
537 npc_objective:DoneRouting()
538 return node
540 return nil
543 function QuestHelper:CreateAndAddTransitionNode(z1, z2, pos)
544 QuestHelper: Assert(z1 and z2, "Zone couldn't be found, and should have been")
546 local node = self:CreateGraphNode(pos)
548 local closest, travel_time = nil, 0
550 for i, n in ipairs(z1) do
551 local t = math.sqrt((n.x-node.x)*(n.x-node.x)+(n.y-node.y)*(n.y-node.y))
552 if not closest or t < travel_time then
553 closest, travel_time = n, t
557 if z1 ~= z2 then
558 for i, n in ipairs(z2) do
559 local t = math.sqrt((n.x-node.x)*(n.x-node.x)+(n.y-node.y)*(n.y-node.y))
560 if not closest or t < travel_time then
561 closest, travel_time = n, t
566 if closest and travel_time < 10 then
567 --QuestHelper:TextOut("Node already exists at "..closest.x..", "..closest.y..", name="..(closest.name or "nil"))
568 closest.x = (closest.x * closest.w + node.x)/(closest.w+1)
569 closest.y = (closest.y * closest.w + node.y)/(closest.w+1)
570 closest.w = closest.w + 1
571 local z1_has, z2_has = false, false
573 -- Just because the node already exists, doesn't mean its already in both lists!
575 for i, n in ipairs(z1) do
576 if n == closest then
577 z1_has = true
578 break
582 if z1 ~= z2 then
583 for i, n in ipairs(z2) do
584 if n == closest then
585 z2_has = true
586 break
589 else
590 z2_has = true
593 if not z1_has then table.insert(z1, closest) end
594 if not z2_has then table.insert(z2, closest) end
596 self.world_graph:DestroyNode(node)
597 QH_Timeslice_Yield()
598 return closest
599 else
600 table.insert(z1, node)
601 if z1 ~= z2 then table.insert(z2, node) end
602 QH_Timeslice_Yield()
603 return node
607 function QuestHelper:ReleaseObjectivePathingInfo(o)
608 if o.setup then
609 for z, pl in pairs(o.p) do
610 self:ReleaseTable(o.d[z])
612 for i, p in ipairs(pl) do
613 self:ReleaseTable(p[2])
614 self:ReleaseTable(p)
617 self:ReleaseTable(pl)
620 self:ReleaseTable(o.d)
621 self:ReleaseTable(o.p)
622 self:ReleaseTable(o.nm)
623 self:ReleaseTable(o.nm2)
624 self:ReleaseTable(o.nl)
626 local cache = o.distance_cache
627 for k, v in pairs(cache) do
628 self:ReleaseTable(v)
629 cache[k] = nil
631 self:ReleaseTable(cache)
633 o.d, o.p, o.nm, o.nm2, o.nl = nil, nil, nil, nil, nil
634 o.distance_cache = nil
635 o.pos, o.sop = nil, nil -- ResetPathing will preserve these values if needed.
636 o.setup = nil
640 function QuestHelper:SetupTeleportInfo(info, can_create)
641 self:TeleportInfoClear(info)
643 if QuestHelper_Home then
644 local node = self:GetNodeByName("HOME_PORTAL", can_create and QuestHelper_Home)
645 if node then
646 local cooldown = self:ItemCooldown(6948)
647 if cooldown then
648 self:SetTeleportInfoTarget(info, node, GetTime()-60*60+cooldown, 60*60, 10)
650 else
651 self.defered_graph_reset = true
655 -- TODO: Compact this. . . and find a better way to tell if the player has a spell.
657 if GetSpellTexture("Teleport: Darnassus") then
658 local node = self:GetNodeByName("DARNASSUS_PORTAL", can_create and DARNASSUS_PORTAL)
659 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
662 if GetSpellTexture("Teleport: Exodar") then
663 local node = self:GetNodeByName("EXODAR_PORTAL", can_create and EXODAR_PORTAL)
664 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
667 if GetSpellTexture("Teleport: Ironforge") then
668 local node = self:GetNodeByName("IRONFORGE_PORTAL", can_create and IRONFORGE_PORTAL)
669 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
672 if GetSpellTexture("Teleport: Moonglade") then
673 local node = self:GetNodeByName("MOONGLADE_PORTAL", can_create and MOONGLADE_PORTAL)
674 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10) else self.defered_graph_reset = true end
677 if GetSpellTexture("Teleport: Orgrimmar") then
678 local node = self:GetNodeByName("ORGRIMMAR_PORTAL", can_create and ORGRIMMAR_PORTAL)
679 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
682 if GetSpellTexture("Teleport: Shattrath") then
683 local node = self:GetNodeByName("SHATTRATH_CITY_PORTAL", can_create and SHATTRATH_CITY_PORTAL)
684 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
687 if GetSpellTexture("Teleport: Silvermoon") then
688 local node = self:GetNodeByName("SILVERMOON_CITY_PORTAL", can_create and SILVERMOON_CITY_PORTAL)
689 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
692 if GetSpellTexture("Teleport: Stormwind") then
693 local node = self:GetNodeByName("STORMWIND_CITY_PORTAL", can_create and STORMWIND_CITY_PORTAL)
694 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
697 if GetSpellTexture("Teleport: Thunder Bluff") then
698 local node = self:GetNodeByName("THUNDER_BLUFF_PORTAL", can_create and THUNDER_BLUFF_PORTAL)
699 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
702 if GetSpellTexture("Teleport: Undercity") then
703 local node = self:GetNodeByName("UNDERCITY_PORTAL", can_create and UNDERCITY_PORTAL)
704 if node then self:SetTeleportInfoTarget(info, node, 0, 0, 10, 17031) else self.defered_graph_reset = true end
707 self:SetTeleportInfoReagent(info, 17031, self:CountItem(17031))
710 function QuestHelper:ResetPathing()
711 for key in pairs(self.named_nodes) do
712 self.named_nodes[key] = nil
715 -- Objectives may include cached information that depends on the world graph.
716 local i = 1
718 while i <= #self.prepared_objectives do
719 local o = self.prepared_objectives[i]
721 if o.setup_count == 0 then
722 table.remove(self.prepared_objectives, i)
723 self:ReleaseObjectivePathingInfo(o)
724 else
725 -- Routing should reset the positions of objectives in the route after the reset is complete.
726 o.pos = nil
727 self:ReleaseObjectivePathingInfo(o)
728 i = i + 1
732 local to_readd = self.prepared_objectives
733 self.prepared_objectives = self.old_prepared_objectives or {}
734 self.old_prepared_objectives = to_readd
736 local zone_nodes = self.zone_nodes
737 if not zone_nodes then
738 zone_nodes = {}
739 self.zone_nodes = zone_nodes
742 local flight_master_nodes = self.flight_master_nodes
743 if not flight_master_nodes then
744 flight_master_nodes = {}
745 self.flight_master_nodes = flight_master_nodes
746 else
747 for key in pairs(flight_master_nodes) do
748 flight_master_nodes[key] = nil
752 self.world_graph:Reset()
753 QH_Timeslice_Yield()
755 local continent_scales_x, continent_scales_y = self.continent_scales_x, self.continent_scales_y
756 if not continent_scales_x then
757 continent_scales_x = {}
758 continent_scales_y = {}
759 self.continent_scales_x = continent_scales_x
760 self.continent_scales_y = continent_scales_y
763 for c in pairs(self.Astrolabe:GetMapVirtualContinents()) do
764 if not continent_scales_x[c] then
765 local _, x, y = self.Astrolabe:ComputeDistance(c, 0, 0.25, 0.25, c, 0, 0.75, 0.75)
767 continent_scales_x[c] = x*walkspeed_multiplier*2
768 continent_scales_y[c] = y*walkspeed_multiplier*2
772 for i, name in pairs(QuestHelper_NameLookup) do
773 local z = zone_nodes[i]
774 if not z then
775 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.
776 zone_nodes[i] = z
777 z.i, z.c, z.z = i, unpack(QuestHelper_ZoneLookup[i])
778 else
779 for key in pairs(z) do
780 z[key] = nil
782 z.i, z.c, z.z = i, unpack(QuestHelper_ZoneLookup[i])
786 self:SetupTeleportInfo(self.teleport_info, true)
787 QH_Timeslice_Yield()
789 --[[for node, info in pairs(self.teleport_info.node) do
790 self:TextOut("You can teleport to "..(node.name or "nil").. " in "..self:TimeString(info[1]+info[2]-GetTime()))
791 end]]
793 if self.faction == 1 then
794 for i, data in ipairs(static_alliance_routes) do
795 self:CreateAndAddStaticNodePair(data)
797 elseif self.faction == 2 then
798 for i, data in ipairs(static_horde_routes) do
799 self:CreateAndAddStaticNodePair(data)
803 for i, data in ipairs(static_shared_routes) do
804 self:CreateAndAddStaticNodePair(data)
807 if self.player_level >= 58 then
808 dark_portal_route[3] = 5
809 else
810 -- If you can't take the route yet, we'll still add it and just pretend it will take a really long time.
811 dark_portal_route[3] = 86400
814 self:CreateAndAddStaticNodePair(dark_portal_route)
816 local st = self:CreateTable("ResetPathing local st")
818 for i, data in pairs(static_zone_transitions) do
819 st[1], st[2], st[3] = data[1], data[3], data[4]
821 local transnode = self:CreateAndAddTransitionNode(zone_nodes[data[1]],
822 zone_nodes[data[2]],
824 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
827 self:ReleaseTable(st)
829 -- Create and link the flight route nodes.
830 local flight_times = self.flight_times
831 if not flight_times then
832 self:buildFlightTimes()
833 flight_times = self.flight_times
836 for start, list in pairs(flight_times) do
837 for dest, duration in pairs(list) do
838 local a_npc, b_npc = self:getFlightInstructor(start), self:getFlightInstructor(dest)
840 if a_npc and b_npc then
841 local a, b = flight_master_nodes[start], flight_master_nodes[dest]
843 if not a then
844 a = getNPCNode(a_npc)
845 if a then
846 flight_master_nodes[start] = a
847 a.name = (select(3, string.find(start, "^(.*),")) or start).." flight point"
851 if not b then
852 b = getNPCNode(b_npc)
853 if b then
854 flight_master_nodes[dest] = b
855 b.name = (select(3, string.find(dest, "^(.*),")) or dest).." flight point"
859 if a and b then
860 a:Link(b, duration+5)
864 QH_Timeslice_Yield()
867 -- id_from, id_to, and id_local will be used in determining whether there is a point to linking nodes together.
868 for i, n in ipairs(self.world_graph.nodes) do
869 n.id_from = self:CreateTable("ResetPathing n.id_from")
870 n.id_to = self:CreateTable("ResetPathing n.id_to")
871 n.id_local = self:CreateTable("ResetPathing n.id_local")
874 -- Setup the local ids a node exists in.
875 for i, list in pairs(zone_nodes) do
876 for _, n in ipairs(list) do
877 n.id_local[i] = true
878 n.id_to[i] = true
879 n.id_from[i] = true
883 -- Figure out where each node can come from or go to.
884 for i, list in pairs(zone_nodes) do
885 for _, node in ipairs(list) do
886 for n in pairs(node.n) do
887 for id in pairs(n.id_local) do node.id_to[id] = true end
888 for id in pairs(node.id_local) do n.id_from[id] = true end
893 -- We'll treat 0 as a special id for where ever it is the player happens to be.
894 for node in pairs(self.teleport_info.node) do
895 node.id_from[0] = true
898 -- Will go through each zone and link all the nodes we have so far with every other node.
899 for _, list in pairs(zone_nodes) do
900 for i = 1,#list do
901 for j = 1,#list do
902 if shouldLink(list[i], list[j]) then
903 list[i]:Link(list[j], cont_dist(list[i], list[j]))
909 QH_Timeslice_Yield()
911 -- We don't need to know where the nodes can go or come from now.
912 for i, n in ipairs(self.world_graph.nodes) do
913 self:ReleaseTable(n.id_from)
914 self:ReleaseTable(n.id_to)
915 self:ReleaseTable(n.id_local)
916 n.id_from, n.id_to, n.id_local = nil, nil, nil
919 -- TODO: This is a work around until I fix shouldLink
920 for start, list in pairs(flight_times) do
921 for dest, duration in pairs(list) do
922 local a, b = flight_master_nodes[start], flight_master_nodes[dest]
923 if a and b then
924 a:Link(b, duration+5)
929 QH_Timeslice_Yield()
930 -- self.world_graph:SanityCheck()
932 -- Remove objectives again, since we created some for the flight masters.
933 while true do
934 local o = table.remove(self.prepared_objectives)
935 if not o then break end
937 self:ReleaseObjectivePathingInfo(o)
939 if o.setup_count > 0 then
940 -- There's a chance an objective could end up in the list twice, but we'll deal with that by not actually
941 -- adding locations for it if it's already setup.
942 table.insert(to_readd, o)
946 while true do
947 local obj = table.remove(to_readd)
948 if not obj then break end
950 if not obj.setup then -- In case the objective was added multiple times to the to_readd list.
951 obj.d = QuestHelper:CreateTable("ResetPathing obj.d")
952 obj.p = QuestHelper:CreateTable("ResetPathing obj.p")
953 obj.nm = QuestHelper:CreateTable("ResetPathing obj.nm")
954 obj.nm2 = QuestHelper:CreateTable("ResetPathing obj.nm2")
955 obj.nl = QuestHelper:CreateTable("ResetPathing obj.nl")
956 obj.distance_cache = QuestHelper:CreateTable("ResetPathing obj.distance_cache")
957 obj:AppendPositions(obj, 1, nil)
958 obj:FinishAddLoc()
962 if self.i then
963 self.pos[1] = self.zone_nodes[self.i]
964 for i, n in ipairs(self.pos[1]) do
965 local a, b = n.x-self.pos[3], n.y-self.pos[4]
966 self.pos[2][i] = math.sqrt(a*a+b*b)
970 -- And if all went according to plan, we now have a graph we can follow to get from anywhere to anywhere.
972 if self.graph_walker then
973 self.graph_walker:GraphChanged()
978 function QuestHelper:Disallowed(index)
979 return QuestHelper_RestrictedZones[index] ~= QuestHelper_RestrictedZones[self.i]