1 QuestHelper_File
["flightpath.lua"] = "Development Version"
3 local real_TakeTaxiNode
= TakeTaxiNode
4 local real_TaxiNodeOnButtonEnter
= TaxiNodeOnButtonEnter
6 assert(type(real_TakeTaxiNode
) == "function")
7 assert(type(real_TaxiNodeOnButtonEnter
) == "function")
9 local function LookupName(x
, y
)
11 for i
= 1,NumTaxiNodes() do
12 local u
, v
= TaxiNodePosition(i
)
16 if not best
or u
< d2
then
17 best
, d2
= TaxiNodeName(i
), u
24 local function getRoute(id
)
25 for i
= 1,NumTaxiNodes() do
26 if GetNumRoutes(i
) == 0 then
27 local routes
= GetNumRoutes(id
)
28 if routes
and routes
> 0 and routes
< 100 then
29 local origin
, dest
= TaxiNodeName(i
), TaxiNodeName(id
)
36 path_str
= string.format("%s/%s", path_str
, LookupName(TaxiGetDestX(id
, j
), TaxiGetDestY(id
, j
)))
39 path_hash
= QuestHelper
:HashString(path_str
)
42 return origin
, dest
, path_hash
48 TaxiNodeOnButtonEnter
= function(btn
)
49 real_TaxiNodeOnButtonEnter(btn
)
51 if QuestHelper_Pref
.flight_time
then
52 local index
= btn
:GetID()
53 if TaxiNodeGetType(index
) == "REACHABLE" then
54 local origin
, dest
, hash
= getRoute(index
)
55 local eta
, estimate
= nil, false
57 eta
= QuestHelper
:computeLinkTime(origin
, dest
, hash
, false)
59 eta
= QuestHelper
.flight_times
[origin
] and QuestHelper
.flight_times
[origin
][dest
]
64 if eta
then -- Going to replace the tooltip.
65 GameTooltip
:SetOwner(btn
, "ANCHOR_RIGHT")
66 GameTooltip
:ClearLines()
67 GameTooltip
:AddLine(dest
, "", 1.0, 1.0, 1.0)
68 GameTooltip
:AddDoubleLine(QHText("TRAVEL_ESTIMATE"), (estimate
and "|cffffffff≈|r " or "")..QHFormat("TRAVEL_ESTIMATE_VALUE", eta
))
69 local cost
= TaxiNodeCost(index
)
71 SetTooltipMoney(GameTooltip
, cost
)
79 TakeTaxiNode
= function(id
)
80 local origin
, dest
, hash
= getRoute(id
)
83 local flight_data
= QuestHelper
.flight_data
84 if not flight_data
then
85 flight_data
= QuestHelper
:CreateTable()
86 QuestHelper
.flight_data
= flight_data
89 flight_data
.origin
= origin
90 flight_data
.dest
= dest
91 flight_data
.hash
= hash
92 flight_data
.start_time
= nil
93 flight_data
.end_time
= nil
94 flight_data
.end_time_estimate
= nil
100 function QuestHelper
:processFlightData(data
)
101 local npc
= self
:getFlightInstructor(data
.dest
)
103 self
:TextOut(QHText("TALK_TO_FLIGHT_MASTER"))
107 local npc_obj
= self
:GetObjective("monster", npc
)
108 npc_obj
:PrepareRouting()
110 local pos
= npc_obj
:Position()
112 -- Don't know te location of the flight instructor.
113 self
:TextOut(QHText("TALK_TO_FLIGHT_MASTER"))
114 npc_obj
:DoneRouting()
120 if pos
[1].c
~= self
.c
then
123 local x
, y
= self
.Astrolabe
:TranslateWorldMapPosition(self
.c
, self
.z
, self
.x
, self
.y
, self
.c
, 0)
124 x
= x
* self
.continent_scales_x
[self
.c
]
125 y
= y
* self
.continent_scales_y
[self
.c
]
126 local t
= (x
-pos
[3])*(x
-pos
[3])+(y
-pos
[4])*(y
-pos
[4])
133 npc_obj
:DoneRouting()
139 if data
.start_time
and data
.end_time
and data
.end_time
> data
.start_time
then
140 local routes
= QuestHelper_FlightRoutes
[self
.faction
]
143 QuestHelper_FlightRoutes
[self
.faction
] = routes
146 local origin
= routes
[data
.origin
]
149 routes
[data
.origin
] = origin
152 local dest
= origin
[data
.dest
]
155 origin
[data
.dest
] = dest
158 dest
[data
.hash
] = data
.end_time
- data
.start_time
164 function QuestHelper
:getFlightInstructor(area
)
165 local fi_table
= QuestHelper_FlightInstructors
[self
.faction
]
167 local npc
= fi_table
[area
]
173 local static
= QuestHelper_StaticData
[QuestHelper_Locale
]
176 fi_table
= static
.flight_instructors
and static
.flight_instructors
[self
.faction
]
178 return fi_table
[area
]
183 local function getTime(tbl
, orig
, dest
, hash
)
184 tbl
= tbl
and tbl
[orig
]
185 tbl
= tbl
and tbl
[dest
]
186 return tbl
and tbl
[hash
] ~= true and tbl
[hash
]
189 local function getWalkToFlight(tbl
, fi1
, fi2
)
193 for origin
, list
in pairs(tbl
) do
194 for dest
, hashlist
in pairs(list
) do
195 if type(hashlist
[0]) == "number" then
196 local npc1
, npc2
= (fi1
and fi1
[origin
]) or (fi2
and fi2
[origin
]), (fi1
and fi1
[dest
]) or (fi2
and fi2
[dest
])
197 if npc1
and npc2
then
198 local obj1
, obj2
= QuestHelper
:GetObjective("monster", npc1
), QuestHelper
:GetObjective("monster", npc2
)
199 obj1
:PrepareRouting()
200 obj2
:PrepareRouting()
202 local pos1
, pos2
= obj1
:Position(), obj2
:Position()
204 if pos1
and pos2
then
205 local x
, y
= pos1
[3]-pos2
[3], pos1
[4]-pos2
[4]
206 w
= w
+ math
.sqrt(x
*x
+y
*y
)
221 function QuestHelper
:computeWalkToFlightMult()
222 local l
= QuestHelper_FlightRoutes
[self
.faction
]
223 local s
= QuestHelper_StaticData
[self
.locale
]
224 s
= s
and s
.flight_routes
225 s
= s
and s
[self
.faction
]
227 local fi1
= QuestHelper_FlightInstructors
[self
.faction
]
228 local fi2
= QuestHelper_StaticData
[self
.locale
]
229 fi2
= fi2
and fi2
.flight_instructors
230 fi2
= fi2
and fi2
[self
.faction
]
232 local f1
, w1
= getWalkToFlight(l
, fi1
, fi2
)
233 local f2
, w2
= getWalkToFlight(s
, fi1
, fi2
)
234 return (f1
+f2
+0.032876)/(w1
+w2
+0.1)
237 function QuestHelper
:computeLinkTime(origin
, dest
, hash
, fallback
)
238 -- Only works for directly connected flight points.
240 if origin
== dest
then
244 local l
= QuestHelper_FlightRoutes
[self
.faction
]
245 local s
= QuestHelper_StaticData
[self
.locale
]
246 s
= s
and s
.flight_routes
247 s
= s
and s
[self
.faction
]
251 -- Will try to lookup flight time there, failing that, will use the time from there to here.
252 local t
= getTime(l
, origin
, dest
, hash
) or getTime(s
, origin
, dest
, hash
) or
253 getTime(l
, dest
, origin
, hash
) or getTime(s
, dest
, origin
, hash
) or fallback
255 if t
== nil then -- Don't have any recored information on this flight time, will estimate based on distances.
256 l
= QuestHelper_FlightInstructors
[self
.faction
]
257 s
= QuestHelper_StaticData
[self
.locale
]
258 s
= s
and s
.flight_instructors
259 s
= s
and s
[self
.faction
]
261 local npc1
, npc2
= (l
and l
[origin
]) or (s
and s
[origin
]),
262 (l
and l
[dest
]) or (s
and s
[dest
])
264 if npc1
and npc2
then
265 local obj1
, obj2
= self
:GetObjective("monster", npc1
), self
:GetObjective("monster", npc2
)
266 obj1
:PrepareRouting()
267 obj2
:PrepareRouting()
269 local pos1
, pos2
= obj1
:Position(), obj2
:Position()
271 if pos1
and pos2
then
272 local x
, y
= pos1
[3]-pos2
[3], pos1
[4]-pos2
[4]
274 t
= math
.sqrt(x
*x
+y
*y
)*self
.flight_scalar
285 local moonglade_fp
= nil
287 function QuestHelper
:addLinkInfo(data
, flight_times
)
289 local ignored_fp
= nil
291 if select(2, UnitClass("player")) ~= "DRUID" then
292 -- As only druids can use the flight point in moonglade, we need to figure out
293 -- where it is so we can ignore it.
295 if not moonglade_fp
then
297 local fi_table
= QuestHelper_FlightInstructors
[self
.faction
]
299 if fi_table
then for area
, npc
in pairs(fi_table
) do
300 local npc_obj
= self
:GetObjective("monster", npc
)
301 npc_obj
:PrepareRouting()
302 local pos
= npc_obj
:Position()
303 if pos
and QuestHelper_IndexLookup
[pos
[1].c
][pos
[1].z
] == 20 then
305 npc_obj
:DoneRouting()
308 npc_obj
:DoneRouting()
311 if not moonglade_fp
then
312 fi_table
= QuestHelper_StaticData
[QuestHelper_Locale
]
313 fi_table
= fi_table
and fi_table
.flight_instructors
and fi_table
.flight_instructors
[self
.faction
]
315 if fi_table
then for area
, npc
in pairs(fi_table
) do
316 local npc_obj
= self
:GetObjective("monster", npc
)
317 npc_obj
:PrepareRouting()
318 local pos
= npc_obj
:Position()
319 if pos
and QuestHelper_IndexLookup
[pos
[1].c
][pos
[1].z
] == 20 then
321 npc_obj
:DoneRouting()
324 npc_obj
:DoneRouting()
328 if not moonglade_fp
then
329 -- This will always be unknown for the session, even if you call buildFlightTimes again
330 -- but if it's unknown then you won't be able to
331 -- get the waypoint this session since you're not a druid
333 moonglade_fp
= "unknown"
337 ignored_fp
= moonglade_fp
340 for origin
, list
in pairs(data
) do
341 local tbl
= flight_times
[origin
]
343 tbl
= self
:CreateTable()
344 flight_times
[origin
] = tbl
347 for dest
, hashs
in pairs(list
) do
348 if origin
~= ignored_fp
and QuestHelper_KnownFlightRoutes
[dest
] and hashs
[0] then
349 local tbl2
= tbl
[dest
]
351 local t
= self
:computeLinkTime(origin
, dest
)
353 tbl2
= self
:CreateTable()
367 local function getDataTime(ft
, origin
, dest
)
369 local data
= ft
[origin
][dest
]
372 for key
in pairs(visited
) do visited
[key
] = nil end
377 -- We might be asked about a route that visits the same point multiple times, and
378 -- since this is effectively a linked list, we need to check for this to avoid
380 if visited
[n
] then return end
383 local temp
= QuestHelper
:computeLinkTime(origin
, n
, str
and QuestHelper
:HashString(str
) or 0, false)
386 t
= temp
+ (n
== dest
and 0 or ft
[n
][dest
][1])
389 if n
== dest
then break end
390 str
= string.format("%s/%s", str
or "", n
)
397 function QuestHelper
:buildFlightTimes()
398 self
.flight_scalar
= self
:computeWalkToFlightMult()
400 local flight_times
= self
.flight_times
401 if not flight_times
then
402 flight_times
= self
:CreateTable()
403 self
.flight_times
= flight_times
406 for key
, list
in pairs(flight_times
) do
407 self
:ReleaseTable(list
)
408 flight_times
[key
] = nil
411 local l
= QuestHelper_FlightRoutes
[self
.faction
]
412 local s
= QuestHelper_StaticData
[self
.locale
]
413 s
= s
and s
.flight_routes
414 s
= s
and s
[self
.faction
]
416 self
:addLinkInfo(l
, flight_times
)
417 self
:addLinkInfo(s
, flight_times
)
424 origin
= next(flight_times
, origin
)
425 if not origin
then break end
426 local list
= flight_times
[origin
]
428 for dest
, data
in pairs(list
) do
429 if flight_times
[dest
] then for dest2
, data2
in pairs(flight_times
[dest
]) do
430 if dest2
~= origin
then
431 local dat
= list
[dest2
]
434 dat
= self
:CreateTable()
435 dat
[1], dat
[2] = data
[1]+data2
[1], dest
437 dat
[1] = getDataTime(flight_times
, origin
, dest2
)
440 self
:ReleaseTable(dat
)
446 local o1
, o2
= dat
[1], dat
[2] -- Temporarly replace old data for the sake of looking up its time.
448 dat
[1], dat
[2] = data
[1]+data2
[1], dest
449 local t2
= getDataTime(flight_times
, origin
, dest2
)
451 if t2
and t2
< o1
then
455 dat
[1], dat
[2] = o1
, o2
465 -- Replace the tables with simple times.
466 for orig
, list
in pairs(flight_times
) do
467 for dest
, data
in pairs(list
) do
469 self
:ReleaseTable(data
)
475 function QuestHelper
:taxiMapOpened()
476 local routes
= QuestHelper_FlightRoutes
[self
.faction
]
480 QuestHelper_FlightRoutes
[self
.faction
] = routes
483 local sroutes
= QuestHelper_StaticData
[self
.locale
]
484 sroutes
= sroutes
and sroutes
.flight_routes
485 sroutes
= sroutes
and sroutes
[self
.faction
]
487 local origin
, altered
= nil, false
489 for i
= 1,NumTaxiNodes() do
490 local name
= TaxiNodeName(i
)
491 if not QuestHelper_KnownFlightRoutes
[name
] then
492 QuestHelper_KnownFlightRoutes
[name
] = true
496 if GetNumRoutes(i
) == 0 then -- Zero hops from this location, must be where we are.
502 if not QuestHelper_KnownFlightRoutes
[origin
] then
503 -- Player didn't previously have this flight point, will need to recalculate pathing data to account for it.
504 QuestHelper_KnownFlightRoutes
[origin
] = true
508 local npc
= UnitName("npc")
511 -- Record who the flight instructor for this location is.
512 local fi_table
= QuestHelper_FlightInstructors
[self
.faction
]
515 QuestHelper_FlightInstructors
[self
.faction
] = fi_table
518 fi_table
[origin
] = npc
521 if not self
.flight_times
[origin
] then
522 -- If this is true, then we probably either didn't who the flight instructor here was,
523 -- or did know but didn't know where.
524 -- As we should now know, the flight times should be updated.
528 if self
.flight_data
and self
:processFlightData(self
.flight_data
) then
529 self
:TextOut(QHText("TALK_TO_FLIGHT_MASTER_COMPLETE"))
530 self
:ReleaseTable(self
.flight_data
)
531 self
.flight_data
= nil
534 for j
= 1,NumTaxiNodes() do
535 local node_count
= GetNumRoutes(j
)
536 if node_count
and i
~= j
and node_count
> 0 and node_count
< 100 then
537 for k
= 1,node_count
do
538 local n1
, n2
= LookupName(TaxiGetSrcX(j
, k
), TaxiGetSrcY(j
, k
)), LookupName(TaxiGetDestX(j
, k
), TaxiGetDestY(j
, k
))
540 assert(n1
and n2
and n1
~= n2
)
542 local dest1
, dest2
= routes
[n1
], routes
[n2
]
554 local hash1
, hash2
= dest1
[n2
], dest2
[n1
]
567 if not (slinks
and slinks
[n1
] and slinks
[n1
][n2
] and slinks
[n1
][n2
][0]) then
568 -- hadn't been considering this link in pathing.
575 if not (slinks
and slinks
[n2
] and slinks
[n2
][n1
] and slinks
[n2
][n1
][0]) then
576 -- hadn't been considering this link in pathing.
587 self
:TextOut(QHText("ROUTES_CHANGED"))
588 self
:TextOut(QHText("WILL_RESET_PATH"))
589 self
.defered_graph_reset
= true
590 self
:buildFlightTimes()
595 local function flight_updater(frame
, delta
)
596 elapsed
= elapsed
+ delta
598 elapsed
= elapsed
- 1
599 local data
= QuestHelper
.flight_data
601 frame
:SetText(string.format("%s: %s", QuestHelper
:HighlightText(select(3, string.find(data
.dest
, "^(.-),")) or data
.dest
),
602 QuestHelper
:TimeString(math
.max(0, data
.end_time_estimate
-time()))))
605 frame
:SetScript("OnUpdate", nil)
610 function QuestHelper
:flightBegan()
611 if self
.flight_data
and not self
.flight_data
.start_time
then
612 self
.flight_data
.start_time
= GetTime()
613 local origin
, dest
= self
.flight_data
.origin
, self
.flight_data
.dest
614 local eta
= self
:computeLinkTime(origin
, dest
, self
.flight_data
.hash
,
615 self
.flight_times
[origin
] and self
.flight_times
[origin
][dest
]) or 0
617 local npc
= self
:getFlightInstructor(self
.flight_data
.dest
) -- Will inform QuestHelper that we're going to be at this NPC in whenever.
619 local npc_obj
= self
:GetObjective("monster", npc
)
620 npc_obj
:PrepareRouting()
621 local pos
= npc_obj
:Position()
623 local c
, z
= pos
[1].c
, pos
[1].z
624 local x
, y
= self
.Astrolabe
:TranslateWorldMapPosition(c
, 0,
625 pos
[3]/self
.continent_scales_x
[c
],
626 pos
[4]/self
.continent_scales_y
[c
], c
, z
)
628 self
:SetTargetLocation(QuestHelper_IndexLookup
[c
][z
], x
, y
, eta
)
631 npc_obj
:DoneRouting()
634 if QuestHelper_Pref
.flight_time
then
635 self
.flight_data
.end_time_estimate
= time()+eta
636 self
:PerformCustomSearch(flight_updater
) -- Reusing the search status indicator to display ETA for flight.
641 function QuestHelper
:flightEnded()
642 local flight_data
= self
.flight_data
643 if flight_data
and not flight_data
.end_time
then
644 flight_data
.end_time
= GetTime()
646 if self
:processFlightData(flight_data
) then
647 self
:ReleaseTable(flight_data
)
648 self
.flight_data
= nil
651 self
:UnsetTargetLocation()
652 self
:StopCustomSearch()