1 QuestHelper_File
["flightpath.lua"] = "Development Version"
2 QuestHelper_Loadtime
["flightpath.lua"] = GetTime()
4 local real_TakeTaxiNode
= TakeTaxiNode
5 local real_TaxiNodeOnButtonEnter
= TaxiNodeOnButtonEnter
7 assert(type(real_TakeTaxiNode
) == "function")
8 assert(type(real_TaxiNodeOnButtonEnter
) == "function")
10 local function LookupName(x
, y
)
12 for i
= 1,NumTaxiNodes() do
13 local u
, v
= TaxiNodePosition(i
)
17 if not best
or u
< d2
then
18 best
, d2
= TaxiNodeName(i
), u
25 local function getRoute(id
)
26 for i
= 1,NumTaxiNodes() do
27 if GetNumRoutes(i
) == 0 then
28 local routes
= GetNumRoutes(id
)
29 if routes
and routes
> 0 and routes
< 100 then
30 local origin
, dest
= TaxiNodeName(i
), TaxiNodeName(id
)
37 path_str
= string.format("%s/%s", path_str
, LookupName(TaxiGetDestX(id
, j
), TaxiGetDestY(id
, j
)))
40 path_hash
= QuestHelper
:HashString(path_str
)
43 return origin
, dest
, path_hash
49 TaxiNodeOnButtonEnter
= function(btn
, ...)
50 QuestHelper
: Assert(btn
)
51 local rv
= real_TaxiNodeOnButtonEnter(btn
, ...)
53 if QuestHelper_Pref
.flight_time
then
54 local index
= btn
:GetID()
55 if TaxiNodeGetType(index
) == "REACHABLE" then
56 local origin
, dest
, hash
= getRoute(index
)
57 local eta
, estimate
= nil, false
59 eta
= QuestHelper
:computeLinkTime(origin
, dest
, hash
, false)
61 eta
= QuestHelper
.flight_times
[origin
] and QuestHelper
.flight_times
[origin
][dest
]
66 if eta
then -- Going to replace the tooltip.
67 GameTooltip
:SetOwner(btn
, "ANCHOR_RIGHT")
68 GameTooltip
:ClearLines()
69 GameTooltip
:AddLine(dest
, "", 1.0, 1.0, 1.0)
70 GameTooltip
:AddDoubleLine(QHText("TRAVEL_ESTIMATE"), (estimate
and "|cffffffff≈|r " or "")..QHFormat("TRAVEL_ESTIMATE_VALUE", eta
))
71 local cost
= TaxiNodeCost(index
)
73 SetTooltipMoney(GameTooltip
, cost
)
83 TakeTaxiNode
= function(id
)
84 local origin
, dest
, hash
= getRoute(id
)
87 local flight_data
= QuestHelper
.flight_data
88 if not flight_data
then
89 flight_data
= QuestHelper
:CreateTable()
90 QuestHelper
.flight_data
= flight_data
93 flight_data
.origin
= origin
94 flight_data
.dest
= dest
95 flight_data
.hash
= hash
96 flight_data
.start_time
= nil
97 flight_data
.end_time
= nil
98 flight_data
.end_time_estimate
= nil
101 real_TakeTaxiNode(id
)
104 function QuestHelper
:processFlightData(data
, interrupted
)
105 local npc
= self
:getFlightInstructor(data
.dest
)
107 self
:TextOut(QHText("TALK_TO_FLIGHT_MASTER"))
111 local npc_obj
= self
:GetObjective("monster", npc
)
112 npc_obj
:PrepareRouting(true)
114 local pos
= npc_obj
:Position()
116 -- Don't know te location of the flight instructor.
117 self
:TextOut(QHText("TALK_TO_FLIGHT_MASTER"))
118 npc_obj
:DoneRouting()
124 if pos
[1].c
~= self
.c
then
127 local x
, y
= self
.Astrolabe
:TranslateWorldMapPosition(self
.c
, self
.z
, self
.x
, self
.y
, self
.c
, 0)
128 x
= x
* self
.continent_scales_x
[self
.c
]
129 y
= y
* self
.continent_scales_y
[self
.c
]
130 local t
= (x
-pos
[3])*(x
-pos
[3])+(y
-pos
[4])*(y
-pos
[4])
132 --self:TextOut(string.format("(%f,%f) vs (%f,%f) is %f", x, y, pos[3], pos[4], t))
139 npc_obj
:DoneRouting()
145 if data
.start_time
and data
.end_time
and data
.end_time
> data
.start_time
then
146 local routes
= QuestHelper_FlightRoutes_Local
[self
.faction
]
149 QuestHelper_FlightRoutes_Local
[self
.faction
] = routes
152 local origin
= routes
[data
.origin
]
155 routes
[data
.origin
] = origin
158 local dest
= origin
[data
.dest
]
161 origin
[data
.dest
] = dest
164 dest
[data
.hash
] = data
.end_time
- data
.start_time
166 if interrupted
then -- I'm assuming this doesn't depend on the hash, since I really doubt the routing system would let a player go through zone boundaries if it wasn't mandatory
167 dest
.interrupt_count
= (dest
.interrupt_count
or 0) + 1
169 dest
.no_interrupt_count
= (dest
.no_interrupt_count
or 0) + 1
176 function QuestHelper
:getFlightInstructor(area
)
177 local fi_table
= QuestHelper_FlightInstructors_Local
[self
.faction
]
179 local npc
= fi_table
[area
]
185 local static
= QuestHelper_StaticData
[QuestHelper_Locale
]
188 fi_table
= static
.flight_instructors
and static
.flight_instructors
[self
.faction
]
190 return fi_table
[area
]
195 local function getTime(tbl
, orig
, dest
, hash
)
196 tbl
= tbl
and tbl
[orig
]
197 tbl
= tbl
and tbl
[dest
]
198 return tbl
and tbl
[hash
] ~= true and tbl
[hash
]
201 -- Okay, I think I've figured out what this is. Given fi1 and fi2, the standard horrifying "canonical/fallback" stuff that all this code does . . .
202 -- For each pair of "origin/dest" in tbl, determine if there is a direct path. (If there is, the hash will be 0.)
203 -- If so, find the flightpath distance and the "walking" distance. Add up walking and flightpath separately, and return the sums.
204 local function getWalkToFlight(tbl
, fi1
, fi2
)
208 for origin
, list
in pairs(tbl
) do
209 for dest
, hashlist
in pairs(list
) do
210 if type(hashlist
[0]) == "number" then
211 local npc1
, npc2
= (fi1
and fi1
[origin
]) or (fi2
and fi2
[origin
]), (fi1
and fi1
[dest
]) or (fi2
and fi2
[dest
])
212 if npc1
and npc2
then
213 local obj1
, obj2
= QuestHelper
:GetObjective("monster", npc1
), QuestHelper
:GetObjective("monster", npc2
)
214 obj1
:PrepareRouting(true, {failable
= true})
215 obj2
:PrepareRouting(true, {failable
= true})
217 local pos1
, pos2
= obj1
:Position(), obj2
:Position()
219 if pos1
and pos2
then
220 local x
, y
= pos1
[3]-pos2
[3], pos1
[4]-pos2
[4]
221 w
= w
+ math
.sqrt(x
*x
+y
*y
)
236 -- Determines the general multiple faster than flying is than walking.
237 function QuestHelper
:computeWalkToFlightMult()
238 local l
= QuestHelper_FlightRoutes_Local
[self
.faction
]
239 local s
= QuestHelper_StaticData
[self
.locale
]
240 s
= s
and s
.flight_routes
241 s
= s
and s
[self
.faction
]
243 local fi1
= QuestHelper_FlightInstructors_Local
[self
.faction
]
244 local fi2
= QuestHelper_StaticData
[self
.locale
]
245 fi2
= fi2
and fi2
.flight_instructors
246 fi2
= fi2
and fi2
[self
.faction
]
248 local f1
, w1
= getWalkToFlight(l
, fi1
, fi2
)
249 local f2
, w2
= getWalkToFlight(s
, fi1
, fi2
)
250 return (f1
+f2
+0.032876)/(w1
+w2
+0.1)
253 function QuestHelper
:computeLinkTime(origin
, dest
, hash
, fallback
)
254 -- Only works for directly connected flight points.
255 if origin
== dest
then
259 local l
= QuestHelper_FlightRoutes_Local
[self
.faction
]
260 local s
= QuestHelper_StaticData
[self
.locale
]
261 s
= s
and s
.flight_routes
262 s
= s
and s
[self
.faction
]
266 -- Will try to lookup flight time there, failing that, will use the time from there to here.
267 local t
= getTime(l
, origin
, dest
, hash
) or getTime(s
, origin
, dest
, hash
) or
268 getTime(l
, dest
, origin
, hash
) or getTime(s
, dest
, origin
, hash
) or fallback
270 if t
== nil then -- Don't have any recored information on this flight time, will estimate based on distances.
271 l
= QuestHelper_FlightInstructors_Local
[self
.faction
]
272 s
= QuestHelper_StaticData
[self
.locale
]
273 s
= s
and s
.flight_instructors
274 s
= s
and s
[self
.faction
]
276 local npc1
, npc2
= (l
and l
[origin
]) or (s
and s
[origin
]),
277 (l
and l
[dest
]) or (s
and s
[dest
])
279 if npc1
and npc2
then
280 local obj1
, obj2
= self
:GetObjective("monster", npc1
), self
:GetObjective("monster", npc2
)
281 obj1
:PrepareRouting(true)
282 obj2
:PrepareRouting(true)
284 local pos1
, pos2
= obj1
:Position(), obj2
:Position()
286 if pos1
and pos2
then
287 local x
, y
= pos1
[3]-pos2
[3], pos1
[4]-pos2
[4]
289 t
= math
.sqrt(x
*x
+y
*y
)*self
.flight_scalar
297 if t
and type(t
) ~= "number" then
298 QuestHelper
:AppendNotificationError("2008-10-11 computelinktime is not a number", string.format("%s %s", type(t
), fallback
and type(fallback
) or "(nil)"))
304 local moonglade_fp
= nil
306 function QuestHelper
:addLinkInfo(data
, flight_times
)
308 if select(2, UnitClass("player")) ~= "DRUID" then
309 -- As only druids can use the flight point in moonglade, we need to figure out
310 -- where it is so we can ignore it.
312 if not moonglade_fp
then
314 local fi_table
= QuestHelper_FlightInstructors_Local
[self
.faction
]
316 if fi_table
then for area
, npc
in pairs(fi_table
) do
317 local npc_obj
= self
:GetObjective("monster", npc
)
318 npc_obj
:PrepareRouting(true, {failable
= true})
319 local pos
= npc_obj
:Position()
320 if pos
and QuestHelper_IndexLookup
[pos
[1].c
][pos
[1].z
] == 20 and string.find(area
, ",") then -- I'm kind of guessing here
322 npc_obj
:DoneRouting()
325 npc_obj
:DoneRouting()
328 if not moonglade_fp
then
329 fi_table
= QuestHelper_StaticData
[QuestHelper_Locale
]
330 fi_table
= fi_table
and fi_table
.flight_instructors
and fi_table
.flight_instructors
[self
.faction
]
332 if fi_table
then for area
, npc
in pairs(fi_table
) do
333 local npc_obj
= self
:GetObjective("monster", npc
)
334 npc_obj
:PrepareRouting(true, {failable
= true})
335 local pos
= npc_obj
:Position()
336 if pos
and QuestHelper_IndexLookup
[pos
[1].c
][pos
[1].z
] == 20 and string.find(area
, ",") then
338 npc_obj
:DoneRouting()
341 npc_obj
:DoneRouting()
345 if not moonglade_fp
then
346 -- This will always be unknown for the session, even if you call buildFlightTimes again
347 -- but if it's unknown then you won't be able to
348 -- get the waypoint this session since you're not a druid
350 moonglade_fp
= "unknown"
355 for origin
, list
in pairs(data
) do
356 local tbl
= flight_times
[origin
]
358 tbl
= self
:CreateTable("Flightpath AddLinkInfo origin table")
359 flight_times
[origin
] = tbl
362 for dest
, hashs
in pairs(list
) do
363 if origin
~= moonglade_fp
and QuestHelper_KnownFlightRoutes
[dest
] and hashs
[0] then
364 local tbl2
= tbl
[dest
]
366 local t
= self
:computeLinkTime(origin
, dest
)
368 tbl2
= self
:CreateTable("Flightpath AddLinkInfo origin->dest data table")
382 local function getDataTime(ft
, origin
, dest
)
384 local data
= ft
[origin
][dest
]
387 for key
in pairs(visited
) do visited
[key
] = nil end
392 -- We might be asked about a route that visits the same point multiple times, and
393 -- since this is effectively a linked list, we need to check for this to avoid
395 if visited
[n
] then return end
398 local temp
= QuestHelper
:computeLinkTime(origin
, n
, str
and QuestHelper
:HashString(str
) or 0, false)
401 t
= temp
+ (n
== dest
and 0 or ft
[n
][dest
][1])
404 if n
== dest
then break end
405 str
= string.format("%s/%s", str
or "", n
)
412 -- Used for loading status results. This is a messy solution.
413 QuestHelper_Flight_Updates
= 0
414 QuestHelper_Flight_Updates_Current
= 0
416 function QuestHelper
:buildFlightTimes()
417 self
.flight_scalar
= self
:computeWalkToFlightMult()
419 local flight_times
= self
.flight_times
420 if not flight_times
then
421 flight_times
= self
:CreateTable()
422 self
.flight_times
= flight_times
425 for key
, list
in pairs(flight_times
) do
426 self
:ReleaseTable(list
)
427 flight_times
[key
] = nil
430 local l
= QuestHelper_FlightRoutes_Local
[self
.faction
]
431 local s
= QuestHelper_StaticData
[self
.locale
]
432 s
= s
and s
.flight_routes
433 s
= s
and s
[self
.faction
]
435 self
:addLinkInfo(l
, flight_times
)
436 self
:addLinkInfo(s
, flight_times
)
438 QuestHelper_Flight_Updates_Current
= 0
440 -- This appears to set up flight_times so it gives directions from any node to any other node. I'm not sure what the getDataTime() call is all about, and I'm also not sure what dat[2] is for. In any case, I don't see anything immediately suspicious about this, just dubious.
446 origin
= next(flight_times
, origin
)
447 if not origin
then break end
448 local list
= flight_times
[origin
]
450 for dest
, data
in pairs(list
) do
451 QuestHelper_Flight_Updates_Current
= QuestHelper_Flight_Updates_Current
+ 1
452 if flight_times
[dest
] then for dest2
, data2
in pairs(flight_times
[dest
]) do
453 if dest2
~= origin
then
454 local dat
= list
[dest2
]
457 dat
= self
:CreateTable()
458 dat
[1], dat
[2] = data
[1]+data2
[1], dest
460 dat
[1] = getDataTime(flight_times
, origin
, dest2
)
463 self
:ReleaseTable(dat
)
469 local o1
, o2
= dat
[1], dat
[2] -- Temporarly replace old data for the sake of looking up its time.
471 dat
[1], dat
[2] = data
[1]+data2
[1], dest
472 local t2
= getDataTime(flight_times
, origin
, dest2
)
474 if t2
and t2
< o1
then
478 dat
[1], dat
[2] = o1
, o2
489 QuestHelper_Flight_Updates
= QuestHelper_Flight_Updates_Current
491 -- Replace the tables with simple times.
492 for orig
, list
in pairs(flight_times
) do
493 for dest
, data
in pairs(list
) do
495 self
:ReleaseTable(data
)
501 function QuestHelper
:taxiMapOpened()
502 local routes
= QuestHelper_FlightRoutes_Local
[self
.faction
]
506 QuestHelper_FlightRoutes_Local
[self
.faction
] = routes
509 local sroutes
= QuestHelper_StaticData
[self
.locale
]
510 sroutes
= sroutes
and sroutes
.flight_routes
511 sroutes
= sroutes
and sroutes
[self
.faction
]
513 local origin
, altered
= nil, false
515 for i
= 1,NumTaxiNodes() do
516 local name
= TaxiNodeName(i
)
517 if not QuestHelper_KnownFlightRoutes
[name
] then
518 QuestHelper_KnownFlightRoutes
[name
] = true
520 self
:TextOut("New flight master: " .. name
)
523 if GetNumRoutes(i
) == 0 then -- Zero hops from this location, must be where we are.
529 local npc
= UnitName("npc")
532 -- Record who the flight instructor for this location is.
533 local fi_table
= QuestHelper_FlightInstructors_Local
[self
.faction
]
536 QuestHelper_FlightInstructors_Local
[self
.faction
] = fi_table
539 fi_table
[origin
] = npc
542 if not self
.flight_times
[origin
] then
543 -- If this is true, then we probably either didn't who the flight instructor here was,
544 -- or did know but didn't know where.
545 -- As we should now know, the flight times should be updated.
549 if self
.flight_data
and self
:processFlightData(self
.flight_data
) then
550 self
:TextOut(QHText("TALK_TO_FLIGHT_MASTER_COMPLETE"))
551 self
:ReleaseTable(self
.flight_data
)
552 self
.flight_data
= nil
555 for j
= 1,NumTaxiNodes() do
556 local node_count
= GetNumRoutes(j
)
557 if node_count
and i
~= j
and node_count
> 0 and node_count
< 100 then
558 for k
= 1,node_count
do
559 local n1
, n2
= LookupName(TaxiGetSrcX(j
, k
), TaxiGetSrcY(j
, k
)), LookupName(TaxiGetDestX(j
, k
), TaxiGetDestY(j
, k
))
561 -- let's make this a bit easier and faster
562 if not QuestHelper_KnownFlightRoutes
[n2
] then
563 QuestHelper_KnownFlightRoutes
[n2
] = true
565 self
:TextOut("New flight master implied: " .. n2
)
568 --QuestHelper:TextOut(string.format("taxi %d: %d is %s/%s", j, k, n1, n2))
570 assert(n1
and n2
and n1
~= n2
)
572 local dest1
, dest2
= routes
[n1
], routes
[n2
]
584 local hash1
, hash2
= dest1
[n2
], dest2
[n1
]
597 if not (sroutes
and sroutes
[n1
] and sroutes
[n1
][n2
] and sroutes
[n1
][n2
][0]) then
598 -- hadn't been considering this link in pathing.
599 self
:TextOut(string.format("Found new link between %s and %s", n1
, n2
))
606 if not (sroutes
and sroutes
[n2
] and sroutes
[n2
][n1
] and sroutes
[n2
][n1
][0]) then
607 -- hadn't been considering this link in pathing.
608 self
:TextOut(string.format("Found new link between %s and %s", n2
, n1
))
619 self
:TextOut(QHText("ROUTES_CHANGED"))
620 self
:TextOut(QHText("WILL_RESET_PATH"))
621 self
.defered_graph_reset
= true
622 self
.defered_flight_times
= true
623 --self:buildFlightTimes()
628 local function flight_updater(frame
, delta
)
629 elapsed
= elapsed
+ delta
631 elapsed
= elapsed
- 1
632 local data
= QuestHelper
.flight_data
634 frame
:SetText(string.format("%s: %s", QuestHelper
:HighlightText(select(3, string.find(data
.dest
, "^(.-),")) or data
.dest
),
635 QuestHelper
:TimeString(math
.max(0, data
.end_time_estimate
-time()))))
638 frame
:SetScript("OnUpdate", nil)
643 function QuestHelper
:flightBegan()
644 if self
.flight_data
and not self
.flight_data
.start_time
then
645 self
.flight_data
.start_time
= GetTime()
646 local origin
, dest
= self
.flight_data
.origin
, self
.flight_data
.dest
647 local eta
= self
:computeLinkTime(origin
, dest
, self
.flight_data
.hash
,
648 self
.flight_times
[origin
] and self
.flight_times
[origin
][dest
]) or 0
650 local npc
= self
:getFlightInstructor(self
.flight_data
.dest
) -- Will inform QuestHelper that we're going to be at this NPC in whenever.
652 local npc_obj
= self
:GetObjective("monster", npc
)
653 npc_obj
:PrepareRouting(true)
654 local pos
= npc_obj
:Position()
656 local c
, z
= pos
[1].c
, pos
[1].z
657 local x
, y
= self
.Astrolabe
:TranslateWorldMapPosition(c
, 0,
658 pos
[3]/self
.continent_scales_x
[c
],
659 pos
[4]/self
.continent_scales_y
[c
], c
, z
)
661 self
:SetTargetLocation(QuestHelper_IndexLookup
[c
][z
], x
, y
, eta
)
664 npc_obj
:DoneRouting()
667 if QuestHelper_Pref
.flight_time
then
668 self
.flight_data
.end_time_estimate
= time()+eta
669 self
:PerformCustomSearch(flight_updater
) -- Reusing the search status indicator to display ETA for flight.
674 function QuestHelper
:flightEnded(interrupted
)
675 local flight_data
= self
.flight_data
676 if flight_data
and not flight_data
.end_time
then
677 flight_data
.end_time
= GetTime()
679 if self
:processFlightData(flight_data
, interrupted
) then
680 self
:ReleaseTable(flight_data
)
681 self
.flight_data
= nil
684 self
:UnsetTargetLocation()
685 self
:StopCustomSearch()