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 local function getSrcDest(id
)
51 for i
= 1, NumTaxiNodes() do
52 if GetNumRoutes(i
) == 0 then
53 snode
= TaxiNodeName(i
)
57 local dnode
= TaxiNodeName(id
)
61 local function getEtaEstimate(snode
, dnode
)
62 local eta
, estimate
= nil, false
63 if QH_Flight_Distances
[snode
] and QH_Flight_Distances
[snode
][dnode
] then
64 eta
, estimate
= unpack(QH_Flight_Distances
[snode
][dnode
])
69 TaxiNodeOnButtonEnter
= function(btn
, ...)
70 QuestHelper
: Assert(btn
)
71 local rv
= real_TaxiNodeOnButtonEnter(btn
, ...)
73 if QuestHelper_Pref
.flight_time
then
74 local index
= btn
:GetID()
75 if TaxiNodeGetType(index
) == "REACHABLE" then
77 local snode
, dnode
= getSrcDest(index
)
79 local eta
, estimate
= getEtaEstimate(snode
, dnode
)
81 if eta
then -- Going to replace the tooltip.
82 GameTooltip
:SetOwner(btn
, "ANCHOR_RIGHT")
83 GameTooltip
:ClearLines()
84 GameTooltip
:AddLine(TaxiNodeName(index
), "", 1.0, 1.0, 1.0)
85 GameTooltip
:AddDoubleLine(QHText("TRAVEL_ESTIMATE"), (estimate
and "|cffffffff≈|r " or "")..QHFormat("TRAVEL_ESTIMATE_VALUE", eta
))
86 local cost
= TaxiNodeCost(index
)
88 SetTooltipMoney(GameTooltip
, cost
)
98 TakeTaxiNode
= function(id
)
99 local src
, dest
= getSrcDest(id
)
102 local flight_data
= QuestHelper
.flight_data
103 if not flight_data
then
104 flight_data
= QuestHelper
:CreateTable()
105 QuestHelper
.flight_data
= flight_data
108 flight_data
.src
= src
109 flight_data
.dest
= dest
110 flight_data
.start_time
= nil
111 flight_data
.end_time
= nil
112 flight_data
.end_time_estimate
= nil
115 real_TakeTaxiNode(id
)
118 function QuestHelper
:getFlightInstructor(area
)
119 local fi_table
= QuestHelper_FlightInstructors_Local
[self
.faction
]
121 local npc
= fi_table
[area
]
127 local static
= QuestHelper_StaticData
[QuestHelper_Locale
]
130 fi_table
= static
.flight_instructors
and static
.flight_instructors
[self
.faction
]
132 return fi_table
[area
]
137 local function getTime(tbl
, orig
, dest
, hash
)
138 tbl
= tbl
and tbl
[orig
]
139 tbl
= tbl
and tbl
[dest
]
140 return tbl
and tbl
[hash
] ~= true and tbl
[hash
]
143 -- 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 . . .
144 -- For each pair of "origin/dest" in tbl, determine if there is a direct path. (If there is, the hash will be 0.)
145 -- If so, find the flightpath distance and the "walking" distance. Add up walking and flightpath separately, and return the sums.
146 local function getWalkToFlight(tbl
, fi1
, fi2
)
150 for origin
, list
in pairs(tbl
) do
151 for dest
, hashlist
in pairs(list
) do
152 if type(hashlist
[0]) == "number" then
153 local npc1
, npc2
= (fi1
and fi1
[origin
]) or (fi2
and fi2
[origin
]), (fi1
and fi1
[dest
]) or (fi2
and fi2
[dest
])
154 if npc1
and npc2
then
155 local obj1
, obj2
= QuestHelper
:GetObjective("monster", npc1
), QuestHelper
:GetObjective("monster", npc2
)
156 obj1
:PrepareRouting(true, {failable
= true})
157 obj2
:PrepareRouting(true, {failable
= true})
159 local pos1
, pos2
= obj1
:Position(), obj2
:Position()
161 if pos1
and pos2
then
162 local x
, y
= pos1
[3]-pos2
[3], pos1
[4]-pos2
[4]
163 w
= w
+ math
.sqrt(x
*x
+y
*y
)
178 -- Determines the general multiple faster than flying is than walking.
179 function QuestHelper
:computeWalkToFlightMult()
180 local l
= QuestHelper_FlightRoutes_Local
[self
.faction
]
181 local s
= QuestHelper_StaticData
[self
.locale
]
182 s
= s
and s
.flight_routes
183 s
= s
and s
[self
.faction
]
185 local fi1
= QuestHelper_FlightInstructors_Local
[self
.faction
]
186 local fi2
= QuestHelper_StaticData
[self
.locale
]
187 fi2
= fi2
and fi2
.flight_instructors
188 fi2
= fi2
and fi2
[self
.faction
]
190 local f1
, w1
= getWalkToFlight(l
, fi1
, fi2
)
191 local f2
, w2
= getWalkToFlight(s
, fi1
, fi2
)
192 return (f1
+f2
+0.032876)/(w1
+w2
+0.1)
195 function QuestHelper
:computeLinkTime(origin
, dest
, hash
, fallback
)
196 -- Only works for directly connected flight points.
197 if origin
== dest
then
201 local l
= QuestHelper_FlightRoutes_Local
[self
.faction
]
202 local s
= QuestHelper_StaticData
[self
.locale
]
203 s
= s
and s
.flight_routes
204 s
= s
and s
[self
.faction
]
208 -- Will try to lookup flight time there, failing that, will use the time from there to here.
209 local t
= getTime(l
, origin
, dest
, hash
) or getTime(s
, origin
, dest
, hash
) or
210 getTime(l
, dest
, origin
, hash
) or getTime(s
, dest
, origin
, hash
) or fallback
212 if t
== nil then -- Don't have any recored information on this flight time, will estimate based on distances.
213 l
= QuestHelper_FlightInstructors_Local
[self
.faction
]
214 s
= QuestHelper_StaticData
[self
.locale
]
215 s
= s
and s
.flight_instructors
216 s
= s
and s
[self
.faction
]
218 local npc1
, npc2
= (l
and l
[origin
]) or (s
and s
[origin
]),
219 (l
and l
[dest
]) or (s
and s
[dest
])
221 if npc1
and npc2
then
222 local obj1
, obj2
= self
:GetObjective("monster", npc1
), self
:GetObjective("monster", npc2
)
223 obj1
:PrepareRouting(true)
224 obj2
:PrepareRouting(true)
226 local pos1
, pos2
= obj1
:Position(), obj2
:Position()
228 if pos1
and pos2
then
229 local x
, y
= pos1
[3]-pos2
[3], pos1
[4]-pos2
[4]
231 t
= math
.sqrt(x
*x
+y
*y
)*self
.flight_scalar
239 if t
and type(t
) ~= "number" then
240 QuestHelper
:AppendNotificationError("2008-10-11 computelinktime is not a number", string.format("%s %s", type(t
), fallback
and type(fallback
) or "(nil)"))
246 local moonglade_fp
= nil
248 function QuestHelper
:addLinkInfo(data
, flight_times
)
250 if select(2, UnitClass("player")) ~= "DRUID" then
251 -- As only druids can use the flight point in moonglade, we need to figure out
252 -- where it is so we can ignore it.
254 if not moonglade_fp
then
256 local fi_table
= QuestHelper_FlightInstructors_Local
[self
.faction
]
258 if fi_table
then for area
, npc
in pairs(fi_table
) do
259 local npc_obj
= self
:GetObjective("monster", npc
)
260 npc_obj
:PrepareRouting(true, {failable
= true})
261 local pos
= npc_obj
:Position()
262 if pos
and QuestHelper_IndexLookup
[pos
[1].c
][pos
[1].z
] == 20 and string.find(area
, ",") then -- I'm kind of guessing here
264 npc_obj
:DoneRouting()
267 npc_obj
:DoneRouting()
270 if not moonglade_fp
then
271 fi_table
= QuestHelper_StaticData
[QuestHelper_Locale
]
272 fi_table
= fi_table
and fi_table
.flight_instructors
and fi_table
.flight_instructors
[self
.faction
]
274 if fi_table
then for area
, npc
in pairs(fi_table
) do
275 local npc_obj
= self
:GetObjective("monster", npc
)
276 npc_obj
:PrepareRouting(true, {failable
= true})
277 local pos
= npc_obj
:Position()
278 if pos
and QuestHelper_IndexLookup
[pos
[1].c
][pos
[1].z
] == 20 and string.find(area
, ",") then
280 npc_obj
:DoneRouting()
283 npc_obj
:DoneRouting()
287 if not moonglade_fp
then
288 -- This will always be unknown for the session, even if you call buildFlightTimes again
289 -- but if it's unknown then you won't be able to
290 -- get the waypoint this session since you're not a druid
292 moonglade_fp
= "unknown"
297 for origin
, list
in pairs(data
) do
298 local tbl
= flight_times
[origin
]
300 tbl
= self
:CreateTable("Flightpath AddLinkInfo origin table")
301 flight_times
[origin
] = tbl
304 for dest
, hashs
in pairs(list
) do
305 if origin
~= moonglade_fp
and QuestHelper_KnownFlightRoutes
[dest
] and hashs
[0] then
306 local tbl2
= tbl
[dest
]
308 local t
= self
:computeLinkTime(origin
, dest
)
310 tbl2
= self
:CreateTable("Flightpath AddLinkInfo origin->dest data table")
324 local function getDataTime(ft
, origin
, dest
)
326 local data
= ft
[origin
][dest
]
329 for key
in pairs(visited
) do visited
[key
] = nil end
334 -- We might be asked about a route that visits the same point multiple times, and
335 -- since this is effectively a linked list, we need to check for this to avoid
337 if visited
[n
] then return end
340 local temp
= QuestHelper
:computeLinkTime(origin
, n
, str
and QuestHelper
:HashString(str
) or 0, false)
343 t
= temp
+ (n
== dest
and 0 or ft
[n
][dest
][1])
346 if n
== dest
then break end
347 str
= string.format("%s/%s", str
or "", n
)
354 -- Used for loading status results. This is a messy solution.
355 QuestHelper_Flight_Updates
= 0
356 QuestHelper_Flight_Updates_Current
= 0
358 function QuestHelper
:buildFlightTimes()
359 self
.flight_scalar
= self
:computeWalkToFlightMult()
361 local flight_times
= self
.flight_times
362 if not flight_times
then
363 flight_times
= self
:CreateTable()
364 self
.flight_times
= flight_times
367 for key
, list
in pairs(flight_times
) do
368 self
:ReleaseTable(list
)
369 flight_times
[key
] = nil
372 local l
= QuestHelper_FlightRoutes_Local
[self
.faction
]
373 local s
= QuestHelper_StaticData
[self
.locale
]
374 s
= s
and s
.flight_routes
375 s
= s
and s
[self
.faction
]
377 self
:addLinkInfo(l
, flight_times
)
378 self
:addLinkInfo(s
, flight_times
)
380 QuestHelper_Flight_Updates_Current
= 0
382 -- 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.
388 origin
= next(flight_times
, origin
)
389 if not origin
then break end
390 local list
= flight_times
[origin
]
392 for dest
, data
in pairs(list
) do
393 QuestHelper_Flight_Updates_Current
= QuestHelper_Flight_Updates_Current
+ 1
394 if flight_times
[dest
] then for dest2
, data2
in pairs(flight_times
[dest
]) do
395 if dest2
~= origin
then
396 local dat
= list
[dest2
]
399 dat
= self
:CreateTable()
400 dat
[1], dat
[2] = data
[1]+data2
[1], dest
402 dat
[1] = getDataTime(flight_times
, origin
, dest2
)
405 self
:ReleaseTable(dat
)
411 local o1
, o2
= dat
[1], dat
[2] -- Temporarly replace old data for the sake of looking up its time.
413 dat
[1], dat
[2] = data
[1]+data2
[1], dest
414 local t2
= getDataTime(flight_times
, origin
, dest2
)
416 if t2
and t2
< o1
then
420 dat
[1], dat
[2] = o1
, o2
431 QuestHelper_Flight_Updates
= QuestHelper_Flight_Updates_Current
433 -- Replace the tables with simple times.
434 for orig
, list
in pairs(flight_times
) do
435 for dest
, data
in pairs(list
) do
437 self
:ReleaseTable(data
)
443 function QuestHelper
:taxiMapOpened()
444 for i
= 1,NumTaxiNodes() do
445 local name
= TaxiNodeName(i
)
446 if not QuestHelper_KnownFlightRoutes
[name
] then
447 QuestHelper_KnownFlightRoutes
[name
] = true
448 self
:TextOut("New flight master: " .. name
)
449 QH_Route_FlightPathRecalc()
455 local function flight_updater(frame
, delta
)
456 elapsed
= elapsed
+ delta
458 elapsed
= elapsed
- 1
459 local data
= QuestHelper
.flight_data
461 frame
:SetText(string.format("%s: %s", QuestHelper
:HighlightText(select(3, string.find(data
.dest
, "^(.-),")) or data
.dest
),
462 QuestHelper
:TimeString(math
.max(0, data
.end_time_estimate
-time()))))
465 QH_Hook(frame
, "OnUpdate", nil)
470 function QuestHelper
:flightBegan()
471 if self
.flight_data
and not self
.flight_data
.start_time
then
472 self
.flight_data
.start_time
= GetTime()
473 local src
, dest
= self
.flight_data
.src
, self
.flight_data
.dest
476 local eta
, estimate
= getEtaEstimate(src
, dest
)
479 local npc = self:getFlightInstructor(self.flight_data.dest) -- Will inform QuestHelper that we're going to be at this NPC in whenever.
481 local npc_obj = self:GetObjective("monster", npc)
482 npc_obj:PrepareRouting(true)
483 local pos = npc_obj:Position()
485 local c, z = pos[1].c, pos[1].z
486 local x, y = self.Astrolabe:TranslateWorldMapPosition(c, 0,
487 pos[3]/self.continent_scales_x[c],
488 pos[4]/self.continent_scales_y[c], c, z)
490 self:SetTargetLocation(QuestHelper_IndexLookup[c][z], x, y, eta)
493 npc_obj:DoneRouting()
497 local loc
= QH_Flight_Destinations
[dest
]
498 if loc
then -- sometimes we just don't have a loc, I think due to flightpath recalculations going on right then
499 QuestHelper
.routing_ac
, QuestHelper
.routing_ax
, QuestHelper
.routing_ay
, QuestHelper
.routing_c
, QuestHelper
.routing_z
= QuestHelper_ParentLookup
[loc
.p
], loc
.x
, loc
.y
, QuestHelper_ZoneLookup
[loc
.p
][1], QuestHelper_ZoneLookup
[loc
.p
][2]
503 if eta
and QuestHelper_Pref
.flight_time
then
504 self
.flight_data
.end_time_estimate
= time() + eta
505 self
:PerformCustomSearch(flight_updater
) -- Reusing the search status indicator to display ETA for flight.
510 function QuestHelper
:flightEnded(interrupted
)
511 local flight_data
= self
.flight_data
512 if flight_data
and not flight_data
.end_time
then
513 flight_data
.end_time
= GetTime()
515 self
:UnsetTargetLocation()
516 self
:StopCustomSearch()