965e8589c9
I implemented a QuestHelper_GetTime function for the next time Blizzard decides to fiddle with the time functions. It returns debugprofilestop() / 1000, to exactly match the precision of GetTime(). I also re-removed references to Cartographer from the rollback.
522 lignes
16 Kio
Lua
522 lignes
16 Kio
Lua
|
|
local GetTime = QuestHelper_GetTime
|
|
|
|
QuestHelper_File["flightpath.lua"] = "4.0.1.$svnversion$"
|
|
QuestHelper_Loadtime["flightpath.lua"] = GetTime()
|
|
|
|
local real_TakeTaxiNode = TakeTaxiNode
|
|
local real_TaxiNodeOnButtonEnter= TaxiNodeOnButtonEnter
|
|
|
|
--[[ assert(type(real_TakeTaxiNode) == "function") ]]
|
|
--[[ assert(type(real_TaxiNodeOnButtonEnter) == "function") ]]
|
|
|
|
local function LookupName(x, y)
|
|
local best, d2
|
|
for i = 1,NumTaxiNodes() do
|
|
local u, v = TaxiNodePosition(i)
|
|
u = u - x
|
|
v = v - y
|
|
u = u*u+v*v
|
|
if not best or u < d2 then
|
|
best, d2 = TaxiNodeName(i), u
|
|
end
|
|
end
|
|
|
|
return best
|
|
end
|
|
|
|
local function getRoute(id)
|
|
for i = 1,NumTaxiNodes() do
|
|
if GetNumRoutes(i) == 0 then
|
|
local routes = GetNumRoutes(id)
|
|
if routes and routes > 0 and routes < 100 then
|
|
local origin, dest = TaxiNodeName(i), TaxiNodeName(id)
|
|
local path_hash = 0
|
|
|
|
if routes > 1 then
|
|
local path_str = ""
|
|
|
|
for j = 1,routes-1 do
|
|
path_str = string.format("%s/%s", path_str, LookupName(TaxiGetDestX(id, j), TaxiGetDestY(id, j)))
|
|
end
|
|
|
|
path_hash = QuestHelper:HashString(path_str)
|
|
end
|
|
|
|
return origin, dest, path_hash
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function getSrcDest(id)
|
|
local snode
|
|
for i = 1, NumTaxiNodes() do
|
|
if GetNumRoutes(i) == 0 then
|
|
snode = TaxiNodeName(i)
|
|
break
|
|
end
|
|
end
|
|
local dnode = TaxiNodeName(id)
|
|
return snode, dnode
|
|
end
|
|
|
|
local function getEtaEstimate(snode, dnode)
|
|
local eta, estimate = nil, false
|
|
if QH_Flight_Distances[snode] and QH_Flight_Distances[snode][dnode] then
|
|
eta, estimate = unpack(QH_Flight_Distances[snode][dnode])
|
|
end
|
|
return eta, estimate
|
|
end
|
|
|
|
TaxiNodeOnButtonEnter = function(btn, ...)
|
|
QuestHelper: Assert(btn)
|
|
local rv = real_TaxiNodeOnButtonEnter(btn, ...)
|
|
|
|
if QuestHelper_Pref.flight_time then
|
|
local index = btn:GetID()
|
|
if TaxiNodeGetType(index) == "REACHABLE" then
|
|
|
|
local snode, dnode = getSrcDest(index)
|
|
|
|
local eta, estimate = getEtaEstimate(snode, dnode)
|
|
|
|
if eta then -- Going to replace the tooltip.
|
|
GameTooltip:SetOwner(btn, "ANCHOR_RIGHT")
|
|
GameTooltip:ClearLines()
|
|
GameTooltip:AddLine(TaxiNodeName(index), "", 1.0, 1.0, 1.0)
|
|
GameTooltip:AddDoubleLine(QHText("TRAVEL_ESTIMATE"), (estimate and "|cffffffff≈|r " or "")..QHFormat("TRAVEL_ESTIMATE_VALUE", eta))
|
|
local cost = TaxiNodeCost(index)
|
|
if cost > 0 then
|
|
SetTooltipMoney(GameTooltip, cost)
|
|
end
|
|
GameTooltip:Show()
|
|
end
|
|
end
|
|
end
|
|
|
|
return rv
|
|
end
|
|
|
|
TakeTaxiNode = function(id)
|
|
local src, dest = getSrcDest(id)
|
|
|
|
if src then
|
|
local flight_data = QuestHelper.flight_data
|
|
if not flight_data then
|
|
flight_data = QuestHelper:CreateTable()
|
|
QuestHelper.flight_data = flight_data
|
|
end
|
|
|
|
flight_data.src = src
|
|
flight_data.dest = dest
|
|
flight_data.start_time = nil
|
|
flight_data.end_time = nil
|
|
flight_data.end_time_estimate = nil
|
|
end
|
|
|
|
real_TakeTaxiNode(id)
|
|
end
|
|
|
|
function QuestHelper:getFlightInstructor(area)
|
|
local fi_table = QuestHelper_FlightInstructors_Local[self.faction]
|
|
if fi_table then
|
|
local npc = fi_table[area]
|
|
if npc then
|
|
return npc
|
|
end
|
|
end
|
|
|
|
local static = QuestHelper_StaticData[QuestHelper_Locale]
|
|
|
|
if static then
|
|
fi_table = static.flight_instructors and static.flight_instructors[self.faction]
|
|
if fi_table then
|
|
return fi_table[area]
|
|
end
|
|
end
|
|
end
|
|
|
|
local function getTime(tbl, orig, dest, hash)
|
|
tbl = tbl and tbl[orig]
|
|
tbl = tbl and tbl[dest]
|
|
return tbl and tbl[hash] ~= true and tbl[hash]
|
|
end
|
|
|
|
-- 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 . . .
|
|
-- For each pair of "origin/dest" in tbl, determine if there is a direct path. (If there is, the hash will be 0.)
|
|
-- If so, find the flightpath distance and the "walking" distance. Add up walking and flightpath separately, and return the sums.
|
|
local function getWalkToFlight(tbl, fi1, fi2)
|
|
local f, w = 0, 0
|
|
|
|
if tbl then
|
|
for origin, list in pairs(tbl) do
|
|
for dest, hashlist in pairs(list) do
|
|
if type(hashlist[0]) == "number" then
|
|
local npc1, npc2 = (fi1 and fi1[origin]) or (fi2 and fi2[origin]), (fi1 and fi1[dest]) or (fi2 and fi2[dest])
|
|
if npc1 and npc2 then
|
|
local obj1, obj2 = QuestHelper:GetObjective("monster", npc1), QuestHelper:GetObjective("monster", npc2)
|
|
obj1:PrepareRouting(true, {failable = true})
|
|
obj2:PrepareRouting(true, {failable = true})
|
|
|
|
local pos1, pos2 = obj1:Position(), obj2:Position()
|
|
|
|
if pos1 and pos2 then
|
|
local x, y = pos1[3]-pos2[3], pos1[4]-pos2[4]
|
|
w = w + math.sqrt(x*x+y*y)
|
|
f = f + hashlist[0]
|
|
end
|
|
|
|
obj2:DoneRouting()
|
|
obj1:DoneRouting()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return f, w
|
|
end
|
|
|
|
-- Determines the general multiple faster than flying is than walking.
|
|
function QuestHelper:computeWalkToFlightMult()
|
|
local l = QuestHelper_FlightRoutes_Local[self.faction]
|
|
local s = QuestHelper_StaticData[self.locale]
|
|
s = s and s.flight_routes
|
|
s = s and s[self.faction]
|
|
|
|
local fi1 = QuestHelper_FlightInstructors_Local[self.faction]
|
|
local fi2 = QuestHelper_StaticData[self.locale]
|
|
fi2 = fi2 and fi2.flight_instructors
|
|
fi2 = fi2 and fi2[self.faction]
|
|
|
|
local f1, w1 = getWalkToFlight(l, fi1, fi2)
|
|
local f2, w2 = getWalkToFlight(s, fi1, fi2)
|
|
return (f1+f2+0.032876)/(w1+w2+0.1)
|
|
end
|
|
|
|
function QuestHelper:computeLinkTime(origin, dest, hash, fallback)
|
|
-- Only works for directly connected flight points.
|
|
if origin == dest then
|
|
return 0
|
|
end
|
|
|
|
local l = QuestHelper_FlightRoutes_Local[self.faction]
|
|
local s = QuestHelper_StaticData[self.locale]
|
|
s = s and s.flight_routes
|
|
s = s and s[self.faction]
|
|
|
|
hash = hash or 0
|
|
|
|
-- Will try to lookup flight time there, failing that, will use the time from there to here.
|
|
local t = getTime(l, origin, dest, hash) or getTime(s, origin, dest, hash) or
|
|
getTime(l, dest, origin, hash) or getTime(s, dest, origin, hash) or fallback
|
|
|
|
if t == nil then -- Don't have any recored information on this flight time, will estimate based on distances.
|
|
l = QuestHelper_FlightInstructors_Local[self.faction]
|
|
s = QuestHelper_StaticData[self.locale]
|
|
s = s and s.flight_instructors
|
|
s = s and s[self.faction]
|
|
|
|
local npc1, npc2 = (l and l[origin]) or (s and s[origin]),
|
|
(l and l[dest]) or (s and s[dest])
|
|
|
|
if npc1 and npc2 then
|
|
local obj1, obj2 = self:GetObjective("monster", npc1), self:GetObjective("monster", npc2)
|
|
obj1:PrepareRouting(true)
|
|
obj2:PrepareRouting(true)
|
|
|
|
local pos1, pos2 = obj1:Position(), obj2:Position()
|
|
|
|
if pos1 and pos2 then
|
|
local x, y = pos1[3]-pos2[3], pos1[4]-pos2[4]
|
|
|
|
t = math.sqrt(x*x+y*y)*self.flight_scalar
|
|
end
|
|
|
|
obj2:DoneRouting()
|
|
obj1:DoneRouting()
|
|
end
|
|
end
|
|
|
|
if t and type(t) ~= "number" then
|
|
QuestHelper:AppendNotificationError("2008-10-11 computelinktime is not a number", string.format("%s %s", type(t), fallback and type(fallback) or "(nil)"))
|
|
return nil
|
|
end
|
|
return t
|
|
end
|
|
|
|
local moonglade_fp = nil
|
|
|
|
function QuestHelper:addLinkInfo(data, flight_times)
|
|
if data then
|
|
if select(2, UnitClass("player")) ~= "DRUID" then
|
|
-- As only druids can use the flight point in moonglade, we need to figure out
|
|
-- where it is so we can ignore it.
|
|
|
|
if not moonglade_fp then
|
|
|
|
local fi_table = QuestHelper_FlightInstructors_Local[self.faction]
|
|
|
|
if fi_table then for area, npc in pairs(fi_table) do
|
|
local npc_obj = self:GetObjective("monster", npc)
|
|
npc_obj:PrepareRouting(true, {failable = true})
|
|
local pos = npc_obj:Position()
|
|
if pos and QuestHelper_IndexLookup[pos[1].c][pos[1].z] == 20 and string.find(area, ",") then -- I'm kind of guessing here
|
|
moonglade_fp = area
|
|
npc_obj:DoneRouting()
|
|
break
|
|
end
|
|
npc_obj:DoneRouting()
|
|
end end
|
|
|
|
if not moonglade_fp then
|
|
fi_table = QuestHelper_StaticData[QuestHelper_Locale]
|
|
fi_table = fi_table and fi_table.flight_instructors and fi_table.flight_instructors[self.faction]
|
|
|
|
if fi_table then for area, npc in pairs(fi_table) do
|
|
local npc_obj = self:GetObjective("monster", npc)
|
|
npc_obj:PrepareRouting(true, {failable = true})
|
|
local pos = npc_obj:Position()
|
|
if pos and QuestHelper_IndexLookup[pos[1].c][pos[1].z] == 20 and string.find(area, ",") then
|
|
moonglade_fp = area
|
|
npc_obj:DoneRouting()
|
|
break
|
|
end
|
|
npc_obj:DoneRouting()
|
|
end end
|
|
end
|
|
|
|
if not moonglade_fp then
|
|
-- This will always be unknown for the session, even if you call buildFlightTimes again
|
|
-- but if it's unknown then you won't be able to
|
|
-- get the waypoint this session since you're not a druid
|
|
-- so its all good.
|
|
moonglade_fp = "unknown"
|
|
end
|
|
end
|
|
end
|
|
|
|
for origin, list in pairs(data) do
|
|
local tbl = flight_times[origin]
|
|
if not tbl then
|
|
tbl = self:CreateTable("Flightpath AddLinkInfo origin table")
|
|
flight_times[origin] = tbl
|
|
end
|
|
|
|
for dest, hashs in pairs(list) do
|
|
if origin ~= moonglade_fp and QuestHelper_KnownFlightRoutes[dest] and hashs[0] then
|
|
local tbl2 = tbl[dest]
|
|
if not tbl2 then
|
|
local t = self:computeLinkTime(origin, dest)
|
|
if t then
|
|
tbl2 = self:CreateTable("Flightpath AddLinkInfo origin->dest data table")
|
|
tbl[dest] = tbl2
|
|
tbl2[1] = t
|
|
tbl2[2] = dest
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local visited = {}
|
|
|
|
local function getDataTime(ft, origin, dest)
|
|
local str = nil
|
|
local data = ft[origin][dest]
|
|
local t = data[1]
|
|
|
|
for key in pairs(visited) do visited[key] = nil end
|
|
|
|
while true do
|
|
local n = data[2]
|
|
|
|
-- We might be asked about a route that visits the same point multiple times, and
|
|
-- since this is effectively a linked list, we need to check for this to avoid
|
|
-- infinite loops.
|
|
if visited[n] then return end
|
|
visited[n] = true
|
|
|
|
local temp = QuestHelper:computeLinkTime(origin, n, str and QuestHelper:HashString(str) or 0, false)
|
|
|
|
if temp then
|
|
t = temp + (n == dest and 0 or ft[n][dest][1])
|
|
end
|
|
|
|
if n == dest then break end
|
|
str = string.format("%s/%s", str or "", n)
|
|
data = ft[n][dest]
|
|
end
|
|
|
|
return t
|
|
end
|
|
|
|
-- Used for loading status results. This is a messy solution.
|
|
QuestHelper_Flight_Updates = 0
|
|
QuestHelper_Flight_Updates_Current = 0
|
|
|
|
function QuestHelper:buildFlightTimes()
|
|
self.flight_scalar = self:computeWalkToFlightMult()
|
|
|
|
local flight_times = self.flight_times
|
|
if not flight_times then
|
|
flight_times = self:CreateTable()
|
|
self.flight_times = flight_times
|
|
end
|
|
|
|
for key, list in pairs(flight_times) do
|
|
self:ReleaseTable(list)
|
|
flight_times[key] = nil
|
|
end
|
|
|
|
local l = QuestHelper_FlightRoutes_Local[self.faction]
|
|
local s = QuestHelper_StaticData[self.locale]
|
|
s = s and s.flight_routes
|
|
s = s and s[self.faction]
|
|
|
|
self:addLinkInfo(l, flight_times)
|
|
self:addLinkInfo(s, flight_times)
|
|
|
|
QuestHelper_Flight_Updates_Current = 0
|
|
|
|
-- 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.
|
|
local cont = true
|
|
while cont do
|
|
cont = false
|
|
local origin = nil
|
|
while true do
|
|
origin = next(flight_times, origin)
|
|
if not origin then break end
|
|
local list = flight_times[origin]
|
|
|
|
for dest, data in pairs(list) do
|
|
QuestHelper_Flight_Updates_Current = QuestHelper_Flight_Updates_Current + 1
|
|
if flight_times[dest] then for dest2, data2 in pairs(flight_times[dest]) do
|
|
if dest2 ~= origin then
|
|
local dat = list[dest2]
|
|
|
|
if not dat then
|
|
dat = self:CreateTable()
|
|
dat[1], dat[2] = data[1]+data2[1], dest
|
|
list[dest2] = dat
|
|
dat[1] = getDataTime(flight_times, origin, dest2)
|
|
|
|
if not dat[1] then
|
|
self:ReleaseTable(dat)
|
|
list[dest2] = nil
|
|
else
|
|
cont = true
|
|
end
|
|
else
|
|
local o1, o2 = dat[1], dat[2] -- Temporarly replace old data for the sake of looking up its time.
|
|
if o2 ~= dest then
|
|
dat[1], dat[2] = data[1]+data2[1], dest
|
|
local t2 = getDataTime(flight_times, origin, dest2)
|
|
|
|
if t2 and t2 < o1 then
|
|
dat[1] = t2
|
|
cont = true
|
|
else
|
|
dat[1], dat[2] = o1, o2
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end end
|
|
QH_Timeslice_Yield()
|
|
end
|
|
end
|
|
end
|
|
|
|
QuestHelper_Flight_Updates = QuestHelper_Flight_Updates_Current
|
|
|
|
-- Replace the tables with simple times.
|
|
for orig, list in pairs(flight_times) do
|
|
for dest, data in pairs(list) do
|
|
local t = data[1]
|
|
self:ReleaseTable(data)
|
|
list[dest] = t
|
|
end
|
|
end
|
|
end
|
|
|
|
function QuestHelper:taxiMapOpened()
|
|
for i = 1,NumTaxiNodes() do
|
|
local name = TaxiNodeName(i)
|
|
if not QuestHelper_KnownFlightRoutes[name] then
|
|
QuestHelper_KnownFlightRoutes[name] = true
|
|
self:TextOut("New flight master: " .. name)
|
|
QH_Route_FlightPathRecalc()
|
|
end
|
|
end
|
|
end
|
|
|
|
local elapsed = 0
|
|
local function flight_updater(frame, delta)
|
|
elapsed = elapsed + delta
|
|
if elapsed > 1 then
|
|
frame:SetSize(150, 35)
|
|
elapsed = elapsed - 1
|
|
local data = QuestHelper.flight_data
|
|
if data then
|
|
frame:SetText(string.format("%s: %s", QuestHelper:HighlightText(select(3, string.find(data.dest, "^(.-),")) or data.dest),
|
|
QuestHelper:TimeString(math.max(0, data.end_time_estimate-time()))))
|
|
else
|
|
frame:Hide()
|
|
QH_Hook(frame, "OnUpdate", nil)
|
|
end
|
|
end
|
|
end
|
|
|
|
function QuestHelper:flightBegan()
|
|
if self.flight_data and not self.flight_data.start_time then
|
|
self.flight_data.start_time = GetTime()
|
|
local src, dest = self.flight_data.src, self.flight_data.dest
|
|
|
|
|
|
local eta, estimate = getEtaEstimate(src, dest)
|
|
|
|
--[[
|
|
local npc = self:getFlightInstructor(self.flight_data.dest) -- Will inform QuestHelper that we're going to be at this NPC in whenever.
|
|
if npc then
|
|
local npc_obj = self:GetObjective("monster", npc)
|
|
npc_obj:PrepareRouting(true)
|
|
local pos = npc_obj:Position()
|
|
if pos then
|
|
local c, z = pos[1].c, pos[1].z
|
|
local x, y = self.Astrolabe:TranslateWorldMapPosition(c, 0,
|
|
pos[3]/self.continent_scales_x[c],
|
|
pos[4]/self.continent_scales_y[c], c, z)
|
|
|
|
self:SetTargetLocation(QuestHelper_IndexLookup[c][z], x, y, eta)
|
|
|
|
end
|
|
npc_obj:DoneRouting()
|
|
end]]
|
|
|
|
do
|
|
local loc = QH_Flight_Destinations[dest]
|
|
if loc then -- sometimes we just don't have a loc, I think due to flightpath recalculations going on right then
|
|
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]
|
|
end
|
|
end
|
|
|
|
if eta and QuestHelper_Pref.flight_time then
|
|
self.flight_data.end_time_estimate = time() + eta
|
|
self:PerformCustomSearch(flight_updater) -- Reusing the search status indicator to display ETA for flight.
|
|
end
|
|
end
|
|
end
|
|
|
|
function QuestHelper:flightEnded(interrupted)
|
|
local flight_data = self.flight_data
|
|
if flight_data and not flight_data.end_time then
|
|
flight_data.end_time = GetTime()
|
|
|
|
self:UnsetTargetLocation()
|
|
self:StopCustomSearch()
|
|
end
|
|
end
|