v1.2: start -> end rows, routes for where you are, both ends timed
- Rows read 'start -> end' with the timer for the start; right-click reverses a row - Default view shows only routes with an end in your zone, already facing your end; click the title or /ob all for every route - New route: Grom'gol <-> Undercity zeppelin - Far-end timings built in for every route, and learned automatically when you ride (no recording needed) - Window auto-shows at either end of any route - Built-in cycles from ~5 h of sightings - Fixes: far-end learning while recording; baseline ignored when the first sighting was a departure - Add CHANGELOG.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyQKCL92ZLwPi9qmYQtNdc
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
720534e06a
commit
11e9ccae55
+275
-77
@@ -1,9 +1,11 @@
|
||||
--[[
|
||||
OctoBus
|
||||
-------
|
||||
Arrival timers for the zeppelins at the Orgrimmar towers (plus the Sparkwater Port boat).
|
||||
A small icon on the minimap edge shows / hides a timer window; the window opens by itself
|
||||
in Orgrimmar and Durotar.
|
||||
Arrival timers for the Horde zeppelins and boats: Orgrimmar to Undercity, Grom'gol, Kargath
|
||||
and Thunder Bluff, Grom'gol to Undercity, Sparkwater to Revantusk, and Ratchet to Booty Bay.
|
||||
Each row reads "start -> end" and times the transport at the start; right-click reverses
|
||||
it. The window shows the routes for where you are (click its title for all of them) and
|
||||
opens by itself at either end of any route. A small minimap icon shows / hides it.
|
||||
|
||||
HOW IT WORKS
|
||||
Needs ZepSense.dll (listed in dlls.txt). The DLL adds one function to the client,
|
||||
@@ -23,14 +25,15 @@
|
||||
/ob list print all timers in chat
|
||||
/ob alert <sec> bell alerts fire this many seconds before arrival (default 45)
|
||||
/ob sound|chat|flash toggle that alert type
|
||||
/ob zone toggle "auto-show only in Orgrimmar / Durotar"
|
||||
/ob zone toggle "auto-show only at route ends" (off = always show)
|
||||
/ob all all routes / only routes for where you are (or click the title)
|
||||
/ob boat show / hide the Sparkwater Port boat
|
||||
/ob scale <n> window size, 0.6 to 2 (or drag the dots in the bottom-right corner)
|
||||
/ob shape square|round|auto minimap icon placement (auto looks at your minimap)
|
||||
/ob where print your zone / sub-zone and whether the window auto-shows
|
||||
/ob allow <name> also auto-show in this zone or sub-zone
|
||||
/ob disallow <name> take a place off that list
|
||||
/ob name <n> <text> rename route number n (from /ob list)
|
||||
/ob name <n> <text> rename the far end of route number n (from /ob list)
|
||||
/ob period <n> <sec> set a route's cycle length by hand
|
||||
/ob calibrate optional: watch every transport on purpose (stand between the towers).
|
||||
Not required: any arrival you see near the towers calibrates automatically.
|
||||
@@ -59,20 +62,55 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
-- period/anchor are starting values measured from a 25 minute log; anchor is the
|
||||
-- time() of one observed arrival. Real observations replace them automatically.
|
||||
------------------------------------------------------------------------
|
||||
-- Zones (or sub-zones) of each end, for the "where you are" view: homeZ = the home end,
|
||||
-- awayZ = the far end, planZ = places where the route is shown for planning (Ratchet from
|
||||
-- Orgrimmar, as "fly").
|
||||
local ORG = { "Orgrimmar", "Durotar" }
|
||||
local ROUTES = {
|
||||
{ entry = 164871, name = "Undercity", kind = "zeppelin", x = 1318.1, y = -4658.0, period = 356.3, dwell = 63, anchor = 1791422404 },
|
||||
{ entry = 175080, name = "Grom'gol", kind = "zeppelin", x = 1360.8, y = -4631.3, period = 303.5, dwell = 63, anchor = 1791422441 },
|
||||
{ entry = 190552, name = "Kargath", kind = "zeppelin", x = 1206.1, y = -4158.7, period = 374.0, dwell = 63, anchor = 1791422484 },
|
||||
{ entry = 190549, name = "Thunder Bluff", kind = "zeppelin", x = 1125.4, y = -4135.0, period = 567.0, dwell = 63, anchor = 1791422131 },
|
||||
{ entry = 190550, name = "Sparkwater", kind = "boat", x = 876.1, y = -5204.1, period = 244.75, dwell = 43, anchor = 1791422323, boat = true },
|
||||
-- Built-in starting values from Phridgey's sightings on 8 Oct 2026: cycle measured over
|
||||
-- ~5 h (origin = first sighting, anchor = latest), stay at the dock, and where measured
|
||||
-- on a ride, the far end (flipOffset = seconds after docking here that it docks there).
|
||||
-- A new copy refines all of this from its own sightings.
|
||||
{ entry = 164871, homeZ = ORG, awayZ = { "Tirisfal Glades", "Undercity" },
|
||||
name = "Undercity", kind = "zeppelin", x = 1318.1, y = -4658.0,
|
||||
period = 356.286, dwell = 64.0, origin = 1791471571.34, anchor = 1791489385.64,
|
||||
flipFrom = "Undercity", flipTo = "Orgrimmar", flipOffset = 191.1, flipDwell = 64.0 },
|
||||
{ entry = 175080, homeZ = ORG, awayZ = { "Stranglethorn Vale" },
|
||||
name = "Grom'gol", kind = "zeppelin", x = 1360.8, y = -4631.3,
|
||||
period = 303.458, dwell = 64.1, origin = 1791471600.26, anchor = 1791489504.28,
|
||||
flipFrom = "Grom'gol", flipTo = "Orgrimmar", flipOffset = 138.2, flipDwell = 64.1 },
|
||||
{ entry = 190552, homeZ = ORG, awayZ = { "Badlands" },
|
||||
name = "Kargath", kind = "zeppelin", x = 1206.1, y = -4158.7,
|
||||
period = 374.073, dwell = 63.9, origin = 1791471857.57, anchor = 1791489438.99,
|
||||
flipFrom = "Kargath", flipTo = "Orgrimmar", flipOffset = 198.0, flipDwell = 64.2 },
|
||||
{ entry = 190549, homeZ = ORG, awayZ = { "Mulgore", "Thunder Bluff" },
|
||||
name = "Thunder Bluff", kind = "zeppelin", x = 1125.4, y = -4135.0,
|
||||
period = 566.785, dwell = 64.2, origin = 1791472003.14, anchor = 1791490140.28,
|
||||
flipFrom = "Thunder Bluff", flipTo = "Orgrimmar", flipOffset = 275.8, flipDwell = 64.0 },
|
||||
{ entry = 190550, homeZ = ORG, awayZ = { "The Hinterlands" },
|
||||
name = "Sparkwater", kind = "boat", x = 876.1, y = -5204.1, boat = true,
|
||||
period = 245.030, dwell = 44.1, origin = 1791471573.69, anchor = 1791489460.91,
|
||||
flipFrom = "Revantusk", flipTo = "Sparkwater", flipOffset = 123.2, flipDwell = 44.0 },
|
||||
-- Grom'gol <-> Undercity zeppelin, timed at the Grom'gol dock (the only end measured).
|
||||
-- Seen leaving Grom'gol 1791480459.89 and arriving 1791486390.25: 18 cycles of 333.02 s
|
||||
-- (assuming the usual 64 s stay); an earlier glimpse docked fits too. The Undercity end
|
||||
-- is learned on the first ride from Grom'gol.
|
||||
{ entry = 176495, homeZ = { "Stranglethorn Vale" }, awayZ = { "Tirisfal Glades", "Undercity" },
|
||||
name = "Undercity", kind = "zeppelin", x = -12406.9, y = 211.8,
|
||||
period = 333.02, dwell = 64.0, origin = 1791480395.89, anchor = 1791486390.25,
|
||||
flipFrom = "Undercity", flipTo = "Grom'gol" },
|
||||
-- Ratchet -> Booty Bay boat, timed at the Ratchet dock. Never visible from Orgrimmar, so
|
||||
-- the row shows when to fly out ("fly") rather than when it docks. Measured on two rides
|
||||
-- (8 Oct 2026): Ratchet arrivals 1791479926.67 and 1791480654.81 are 2 cycles apart
|
||||
-- (364.07 s; legs 64 s dock + 114.8 s crossing + 64.2 s Booty Bay + 121.1 s back).
|
||||
-- origin seeds the baseline so later arrivals keep refining the cycle.
|
||||
{ entry = 20808, name = "Ratchet", kind = "boat", x = -1005.6, y = -3841.6, period = 364.07,
|
||||
dwell = 64, anchor = 1791480654.81, origin = 1791479926.67, far = true },
|
||||
-- the row shows when to fly out ("fly") rather than when it docks. Ratchet arrivals
|
||||
-- 1791479926.67 .. 1791490117.34 are 28 cycles apart (363.95 s). Booty Bay end seen on
|
||||
-- two days' rides: docks 178.6 s after Ratchet, stays 64.2 s.
|
||||
{ entry = 20808, homeZ = { "Ratchet" }, awayZ = { "Stranglethorn Vale" }, planZ = ORG,
|
||||
name = "Ratchet", kind = "boat", x = -1005.6, y = -3841.6, far = true,
|
||||
period = 363.952, dwell = 63.7, origin = 1791479926.67, anchor = 1791490117.34,
|
||||
flipFrom = "Booty Bay", flipTo = "Ratchet", flipOffset = 178.6, flipDwell = 64.2 },
|
||||
}
|
||||
|
||||
-- Right-click on a row reverses it: times for the far end (flipFrom, going to flipTo). Where the other end was never measured (flipOffset unset), it is assumed to
|
||||
-- dock halfway through the cycle and to stay as long as at this end: a rough estimate.
|
||||
local DOCK_RADIUS = 2.0
|
||||
|
||||
------------------------------------------------------------------------
|
||||
@@ -98,6 +136,9 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
t.origin = t.origin or {}
|
||||
t.dwell = t.dwell or {}
|
||||
t.lastObs = t.lastObs or {}
|
||||
t.flip = t.flip or {}
|
||||
t.farOff = t.farOff or {}
|
||||
t.farDwell = t.farDwell or {}
|
||||
t.cfg = t.cfg or {}
|
||||
local c = t.cfg
|
||||
if c.alert == nil then c.alert = 45 end
|
||||
@@ -139,14 +180,66 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
return string.format("%d:%02d", math.floor(s / 60), math.mod(s, 60))
|
||||
end
|
||||
|
||||
-- "<place> <kind>": the place can be renamed with /ob name; in the window the kind is
|
||||
-- shown in the same grey as the "docks" / "leaves" labels.
|
||||
local function RouteName(r) return (DB.names[r.entry] or r.name) .. " " .. r.kind end
|
||||
local function RowName(r) return (DB.names[r.entry] or r.name) .. " |cffaaaaaa" .. r.kind .. "|r" end
|
||||
-- Rows read "start -> end". flipTo is the home end (Orgrimmar, Sparkwater, Ratchet),
|
||||
-- flipFrom the far end; /ob name renames the far end.
|
||||
local function Away(r) return DB.names[r.entry] or r.flipFrom end
|
||||
local function RouteName(r) return r.flipTo .. " -> " .. Away(r) end
|
||||
local function Period(r) return DB.period[r.entry] or r.period end
|
||||
local function Anchor(r) return DB.anchor[r.entry] or r.anchor end
|
||||
local function Watched(r) return DB.bell[r.entry] == true end
|
||||
local function Visible(r) return (not r.boat) or (not CFG.hideBoat) end
|
||||
-- Names are compared without capitals, quotes or outer spaces.
|
||||
local function Norm(n)
|
||||
n = string.lower(n or "")
|
||||
n = string.gsub(n, "[\"']", "")
|
||||
n = string.gsub(n, "^%s+", "")
|
||||
n = string.gsub(n, "%s+$", "")
|
||||
return n
|
||||
end
|
||||
|
||||
local seenState = {} -- entry -> { atDock = bool, arrivedAt = epoch or nil }
|
||||
|
||||
-- Where the player is. LOC.z / LOC.sz = current zone / sub-zone; LOC.mz / LOC.msz = the
|
||||
-- place the "where you are" view is built for: the current one, or the last place that
|
||||
-- had routes (CFG.hub) when you are somewhere without any.
|
||||
local LOC = { z = "", sz = "", mz = "", msz = "", key = nil }
|
||||
local manualFlip = {} -- right-clicks in the "where you are" view; cleared on zone change
|
||||
local function InListAt(t, z, sz)
|
||||
if not t then return false end
|
||||
for i = 1, table.getn(t) do
|
||||
local a = Norm(t[i])
|
||||
if a == z or a == sz then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
local function AtHome(r) return InListAt(r.homeZ, LOC.mz, LOC.msz) end
|
||||
local function AtAway(r) return InListAt(r.awayZ, LOC.mz, LOC.msz) end
|
||||
local function AtPlan(r) return InListAt(r.planZ, LOC.mz, LOC.msz) end
|
||||
local function UpdateLoc()
|
||||
local z, sz = Norm(GetZoneText()), Norm(GetSubZoneText())
|
||||
if z ~= LOC.key then LOC.key = z; manualFlip = {} end
|
||||
LOC.z, LOC.sz = z, sz
|
||||
local any = false
|
||||
for i = 1, table.getn(ROUTES) do
|
||||
local r = ROUTES[i]
|
||||
if InListAt(r.homeZ, z, sz) or InListAt(r.awayZ, z, sz) or InListAt(r.planZ, z, sz) then any = true end
|
||||
end
|
||||
if any then
|
||||
LOC.mz, LOC.msz = z, sz
|
||||
CFG.hub = { z = z, sz = sz }
|
||||
elseif CFG.hub then
|
||||
LOC.mz, LOC.msz = CFG.hub.z, CFG.hub.sz
|
||||
else
|
||||
LOC.mz, LOC.msz = z, sz
|
||||
end
|
||||
end
|
||||
|
||||
-- Shown rows: "all routes" (CFG.full) or only the ones for where you are (either end,
|
||||
-- a planning place, or the transport in view, e.g. while riding it).
|
||||
local function Visible(r)
|
||||
if r.boat and CFG.hideBoat then return false end
|
||||
if CFG.full then return true end
|
||||
return AtHome(r) or AtAway(r) or AtPlan(r) or seenState[r.entry] ~= nil
|
||||
end
|
||||
|
||||
-- confirmed[entry] = true once the transport has been SEEN docking/leaving this session
|
||||
local confirmed = {}
|
||||
@@ -166,13 +259,38 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
local function Dwell(r) return DB.dwell[r.entry] or r.dwell end
|
||||
|
||||
-- returns "docked", secondsUntilDeparture or "wait", secondsUntilArrival
|
||||
local function Predict(r)
|
||||
-- Reversed? "All routes": as set with right-click (kept). "Where you are": starts from
|
||||
-- your end automatically; a right-click reverses it until you change zone.
|
||||
local function Flipped(r)
|
||||
if not r.flipFrom then return false end
|
||||
if CFG.full then return DB.flip[r.entry] == true end
|
||||
local auto = AtAway(r) and not AtHome(r) and not AtPlan(r)
|
||||
if manualFlip[r.entry] then return not auto end
|
||||
return auto
|
||||
end
|
||||
local function FlipName(r) return Away(r) .. " -> " .. r.flipTo end
|
||||
local function ShownName(r) if Flipped(r) then return FlipName(r) end return RouteName(r) end
|
||||
local function StartName(r) if Flipped(r) then return Away(r) end return r.flipTo end
|
||||
local RowName = ShownName
|
||||
|
||||
-- Far end timing: learned on a ride (DB.farOff / DB.farDwell), else built in, else the
|
||||
-- rough "halfway through the cycle, same stay" assumption. FarKnown = not a guess.
|
||||
local function FarKnown(r) return (DB.farOff[r.entry] or r.flipOffset) ~= nil end
|
||||
local function FarOff(r, P) return DB.farOff[r.entry] or r.flipOffset or P / 2 end
|
||||
local function FarDwell(r) return DB.farDwell[r.entry] or r.flipDwell or Dwell(r) end
|
||||
|
||||
-- otherEnd = true: the same schedule seen from the far end of the route
|
||||
local function Predict(r, otherEnd)
|
||||
local a, P = Anchor(r), Period(r)
|
||||
if not a or not P or P <= 0 then return nil end
|
||||
local d = Dwell(r)
|
||||
if otherEnd then
|
||||
a = a + FarOff(r, P)
|
||||
d = FarDwell(r)
|
||||
end
|
||||
local e = EpochNow() - a
|
||||
local ph = math.mod(e, P)
|
||||
if ph < 0 then ph = ph + P end
|
||||
local d = Dwell(r)
|
||||
if ph < d then return "docked", d - ph end
|
||||
return "wait", P - ph
|
||||
end
|
||||
@@ -278,7 +396,6 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
------------------------------------------------------------------------
|
||||
-- Watching the DLL
|
||||
------------------------------------------------------------------------
|
||||
local seenState = {} -- entry -> { atDock = bool, arrivedAt = epoch or nil }
|
||||
local dllOk = false
|
||||
local lastGood = 0
|
||||
|
||||
@@ -344,6 +461,7 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
end
|
||||
else
|
||||
-- never saw it arrive, but it leaves 'dwell' seconds after arriving
|
||||
if not DB.origin[e] and r.origin then DB.origin[e] = r.origin end
|
||||
DB.anchor[e] = t - Dwell(r)
|
||||
if not DB.origin[e] then
|
||||
DB.origin[e] = t - Dwell(r)
|
||||
@@ -392,6 +510,66 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
end
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Far-end learning. A tracked transport that stands still for 20 s somewhere other than
|
||||
-- its home dock is docked at the far end. Its arrival there (when it first came within
|
||||
-- DOCK_RADIUS of where it stopped) is stored as seconds into the cycle, and its stay is
|
||||
-- stored when it leaves. Only learned when the home-end schedule was confirmed this
|
||||
-- session, so the offset is measured against a fresh schedule. Nothing is printed.
|
||||
------------------------------------------------------------------------
|
||||
local FAR_STILL = 20
|
||||
local farW = {} -- entry -> { hist = {{t,x,y}...}, sx, sy, st, arr }
|
||||
local function FarWatch(r, now, x, y, atDock)
|
||||
local e = r.entry
|
||||
if not r.flipFrom or atDock or (x == 0 and y == 0) then farW[e] = nil; return end
|
||||
local w = farW[e]
|
||||
if not w then w = { hist = {} }; farW[e] = w end
|
||||
-- keep only samples where it moved, so the approach is still in the history after
|
||||
-- 20 s standing still (polling is 10 per second while recording)
|
||||
local hn = table.getn(w.hist)
|
||||
local last = w.hist[hn]
|
||||
if not last or math.abs(last[2] - x) >= 0.05 or math.abs(last[3] - y) >= 0.05 then
|
||||
table.insert(w.hist, { now, x, y })
|
||||
if hn + 1 > 120 then table.remove(w.hist, 1) end
|
||||
end
|
||||
if w.sx and math.abs(x - w.sx) < 0.3 and math.abs(y - w.sy) < 0.3 then
|
||||
if not w.arr and (now - w.st) >= FAR_STILL then
|
||||
-- docked: arrival = first recent sample within DOCK_RADIUS of the stop point
|
||||
local arr, approached = w.st, false
|
||||
for i = table.getn(w.hist), 1, -1 do
|
||||
local h = w.hist[i]
|
||||
local dx, dy = h[2] - w.sx, h[3] - w.sy
|
||||
if math.sqrt(dx * dx + dy * dy) > DOCK_RADIUS then approached = true; break end
|
||||
arr = h[1]
|
||||
end
|
||||
w.arr = arr
|
||||
local P = Period(r)
|
||||
-- only when it was seen coming in (not already docked when it came into view)
|
||||
if approached and confirmed[e] and P and P > 0 then
|
||||
local off = math.mod(arr - Anchor(r), P)
|
||||
if off < 0 then off = off + P end
|
||||
local old = DB.farOff[e]
|
||||
if old and math.abs(old - off) < 20 then off = old * 0.5 + off * 0.5 end
|
||||
DB.farOff[e] = off
|
||||
w.learn = true
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
if w.arr and w.sx then
|
||||
local dx, dy = x - w.sx, y - w.sy
|
||||
if math.sqrt(dx * dx + dy * dy) <= DOCK_RADIUS then return end -- still easing out
|
||||
local d = now - w.arr
|
||||
if w.learn and d > 20 and d < 150 then
|
||||
local old = DB.farDwell[e]
|
||||
DB.farDwell[e] = old and (old * 0.6 + d * 0.4) or d
|
||||
end
|
||||
w.arr = nil
|
||||
w.learn = nil
|
||||
end
|
||||
w.sx, w.sy, w.st = x, y, now
|
||||
end
|
||||
|
||||
local function Poll()
|
||||
if type(ZepTransports) ~= "function" then dllOk = false; return end
|
||||
local ok, str = pcall(ZepTransports)
|
||||
@@ -414,6 +592,7 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
present[de] = true
|
||||
local dx, dy = x - r.x, y - r.y
|
||||
local atDock = (math.sqrt(dx * dx + dy * dy) <= DOCK_RADIUS)
|
||||
FarWatch(r, now, x, y, atDock)
|
||||
local st = seenState[de]
|
||||
if not st then
|
||||
seenState[de] = { atDock = atDock }
|
||||
@@ -437,6 +616,7 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
for i = 1, table.getn(ROUTES) do
|
||||
local e = ROUTES[i].entry
|
||||
if seenState[e] and not present[e] then seenState[e] = nil end
|
||||
if not present[e] then farW[e] = nil end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -567,6 +747,24 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
panel:SetScript("OnDragStart", StartDrag)
|
||||
panel:SetScript("OnDragStop", StopDrag)
|
||||
|
||||
-- click the title: switch between all routes and routes for where you are
|
||||
local titleBtn = CreateFrame("Button", nil, panel)
|
||||
titleBtn:SetPoint("TOPLEFT", panel, "TOPLEFT", 4, -2)
|
||||
titleBtn:SetWidth(150)
|
||||
titleBtn:SetHeight(16)
|
||||
titleBtn:RegisterForClicks("LeftButtonUp")
|
||||
titleBtn:RegisterForDrag("LeftButton")
|
||||
titleBtn:SetScript("OnDragStart", StartDrag)
|
||||
titleBtn:SetScript("OnDragStop", StopDrag)
|
||||
titleBtn:SetScript("OnClick", function() CFG.full = not CFG.full end)
|
||||
titleBtn:SetScript("OnEnter", function()
|
||||
GameTooltip:SetOwner(this, "ANCHOR_TOP")
|
||||
GameTooltip:AddLine(CFG.full and "Showing all routes" or "Showing routes for where you are", 1, 1, 1)
|
||||
GameTooltip:AddLine("Click: " .. (CFG.full and "only routes for where you are" or "all routes") .. " (/ob all)", 0.2, 1, 0.8)
|
||||
GameTooltip:Show()
|
||||
end)
|
||||
titleBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
||||
|
||||
-- Resize grip, bottom-right: drag to scale the whole window (0.6x to 2x). The top-left
|
||||
-- corner stays where it is. Hidden while the window is locked.
|
||||
local SCALE_MIN, SCALE_MAX = 0.6, 2.0
|
||||
@@ -638,7 +836,7 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
row:SetHeight(ROW_H)
|
||||
row:SetPoint("TOPLEFT", panel, "TOPLEFT", 4, -(TOP + (i - 1) * ROW_H))
|
||||
row:SetPoint("RIGHT", panel, "RIGHT", -4, 0)
|
||||
row:RegisterForClicks("LeftButtonUp")
|
||||
row:RegisterForClicks("LeftButtonUp", "RightButtonUp")
|
||||
row:RegisterForDrag("LeftButton")
|
||||
row:SetScript("OnDragStart", StartDrag)
|
||||
row:SetScript("OnDragStop", StopDrag)
|
||||
@@ -658,9 +856,6 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
row.name:SetPoint("LEFT", row.icon, "RIGHT", 4, 0)
|
||||
row.name:SetJustifyH("LEFT")
|
||||
|
||||
row.kind = row:CreateFontString(nil, "OVERLAY")
|
||||
row.kind:SetFont(font, 10, "OUTLINE")
|
||||
row.kind:SetTextColor(0.667, 0.667, 0.667)
|
||||
|
||||
row.time = row:CreateFontString(nil, "OVERLAY")
|
||||
row.time:SetFont(font, 10, "OUTLINE")
|
||||
@@ -675,15 +870,25 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
row.bell:Hide()
|
||||
|
||||
row:SetScript("OnClick", function()
|
||||
if not this.route then return end
|
||||
DB.bell[this.route.entry] = (not Watched(this.route)) or nil
|
||||
if Watched(this.route) then this.bell:Show() else this.bell:Hide() end
|
||||
local r = this.route
|
||||
if not r then return end
|
||||
if arg1 == "RightButton" then
|
||||
if r.flipFrom then
|
||||
if CFG.full then DB.flip[r.entry] = (not Flipped(r)) or nil
|
||||
else manualFlip[r.entry] = (not manualFlip[r.entry]) or nil end
|
||||
local f = this:GetScript("OnEnter")
|
||||
if f and MouseIsOver and MouseIsOver(this) then GameTooltip:Hide(); f() end
|
||||
end
|
||||
return
|
||||
end
|
||||
DB.bell[r.entry] = (not Watched(r)) or nil
|
||||
if Watched(r) then this.bell:Show() else this.bell:Hide() end
|
||||
end)
|
||||
row:SetScript("OnEnter", function()
|
||||
local r = this.route
|
||||
if not r then return end
|
||||
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
|
||||
GameTooltip:AddLine(RouteName(r), 1, 1, 1)
|
||||
GameTooltip:AddLine(ShownName(r) .. " " .. r.kind, 1, 1, 1)
|
||||
GameTooltip:AddLine((Period(r) and ("Cycle " .. string.format("%.1f", Period(r)) .. "s, ") or "Cycle not measured, ") .. "stays " .. string.format("%.0f", Dwell(r)) .. "s (object " .. r.entry .. ")", 0.7, 0.7, 0.7)
|
||||
if DB.origin[r.entry] and DB.anchor[r.entry] then
|
||||
local span = DB.anchor[r.entry] - DB.origin[r.entry]
|
||||
@@ -691,7 +896,15 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
GameTooltip:AddLine("Cycle measured over " .. (span >= 86400 and string.format("%.1f days", span / 86400) or string.format("%.0f min", span / 60)) .. " of observations.", 0.7, 0.7, 0.7)
|
||||
end
|
||||
end
|
||||
if r.far then
|
||||
if Flipped(r) then
|
||||
GameTooltip:AddLine("Times are for " .. Away(r) .. ": when it docks there and leaves for " .. r.flipTo .. ".", 0.7, 0.7, 0.7)
|
||||
if FarKnown(r) then
|
||||
GameTooltip:AddLine(Away(r) .. " end measured on a ride: docks there " .. string.format("%.0f", FarOff(r, Period(r))) .. "s into the cycle, stays " .. string.format("%.0f", FarDwell(r)) .. "s.", 0.7, 0.7, 0.7)
|
||||
else
|
||||
GameTooltip:AddLine(Away(r) .. " end not measured: assumed to dock halfway through the cycle, so treat it as rough. Ride it once (window open) to measure it.", 1, 0.6, 0.2)
|
||||
end
|
||||
GameTooltip:AddLine("Click: " .. (Watched(r) and "remove the bell (no chat alert)" or "add a bell: chat message " .. CFG.alert .. "s before it arrives at " .. Away(r)), 0.2, 1, 0.8)
|
||||
elseif r.far then
|
||||
local lo = DB.lastObs[r.entry] or DB.anchor[r.entry] or r.anchor
|
||||
if r.periodGuess and not DB.period[r.entry] then
|
||||
GameTooltip:AddLine("Cycle is a rough estimate until the boat is seen docking at Ratchet once more.", 1, 0.6, 0.2)
|
||||
@@ -708,6 +921,9 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
GameTooltip:AddLine(confirmed[r.entry] and "Seen this session: exact." or "Not seen this session: estimate (~).", 0.7, 0.7, 0.7)
|
||||
GameTooltip:AddLine("Click: " .. (Watched(r) and "remove the bell (no chat alert)" or "add a bell: chat message " .. CFG.alert .. "s before it arrives"), 0.2, 1, 0.8)
|
||||
end
|
||||
if r.flipFrom then
|
||||
GameTooltip:AddLine("Right-click: reverse to " .. (Flipped(r) and RouteName(r) or FlipName(r)), 0.2, 1, 0.8)
|
||||
end
|
||||
GameTooltip:Show()
|
||||
end)
|
||||
row:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
||||
@@ -717,6 +933,7 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
|
||||
-- returns state ("docked" / "wait" / nil), seconds (may be nil if unknown)
|
||||
local function Status(r)
|
||||
if Flipped(r) then return Predict(r, true) end
|
||||
local ps, psecs = Predict(r)
|
||||
local live = seenState[r.entry]
|
||||
if live then
|
||||
@@ -748,7 +965,7 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
end
|
||||
|
||||
local function RowText(r)
|
||||
if r.far and not seenState[r.entry] then
|
||||
if r.far and not seenState[r.entry] and not Flipped(r) and not AtHome(r) then
|
||||
local fly, sail = FlyBy(r)
|
||||
if not fly then return RowName(r), nil, nil, "|cff888888not measured yet|r" end
|
||||
local color = "|cffffffff"
|
||||
@@ -758,8 +975,10 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
end
|
||||
local state, secs = Status(r)
|
||||
local est = confirmed[r.entry] and "" or "|cff888888~|r"
|
||||
local mark = ""
|
||||
local nm = mark .. RowName(r)
|
||||
local nm = RowName(r)
|
||||
if Flipped(r) then
|
||||
if not (confirmed[r.entry] and FarKnown(r)) then est = "|cff888888~|r" end
|
||||
end
|
||||
if state == "docked" then
|
||||
if secs then
|
||||
return nm, state, secs, est .. "|cff55ff55leaves|r |cffffffff" .. FormatTime(secs) .. "|r"
|
||||
@@ -778,7 +997,6 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
|
||||
local function Refresh()
|
||||
local n = 0
|
||||
local kindL, kindR = nil, nil
|
||||
for i = 1, table.getn(ROUTES) do
|
||||
local r = ROUTES[i]
|
||||
if Visible(r) then
|
||||
@@ -787,7 +1005,7 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
row:Show()
|
||||
row.route = r
|
||||
local _, state, secs, txt = RowText(r)
|
||||
local nm = DB.names[r.entry] or r.name
|
||||
local nm = ShownName(r)
|
||||
if cal and cal.need[r.entry] then
|
||||
if (cal.got[r.entry] or 0) >= cal.need[r.entry] then
|
||||
nm = "|cff55ff55+|r " .. nm
|
||||
@@ -797,14 +1015,6 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
end
|
||||
row.name:SetText(nm)
|
||||
row.time:SetText(txt)
|
||||
row.kind:SetText(r.kind)
|
||||
local nw, tw = row.name:GetStringWidth(), row.time:GetStringWidth()
|
||||
if type(nw) ~= "number" then nw = 60 end
|
||||
if type(tw) ~= "number" then tw = 60 end
|
||||
local nameRight = 2 + 14 + 4 + nw
|
||||
local timeLeft = (WIDTH - 8) - 20 - tw
|
||||
if not kindL or nameRight > kindL then kindL = nameRight end
|
||||
if not kindR or timeLeft < kindR then kindR = timeLeft end
|
||||
if Watched(r) then row.bell:Show() else row.bell:Hide() end
|
||||
if state == "docked" then
|
||||
row.icon:SetTexture(ICON_HERE)
|
||||
@@ -821,17 +1031,9 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
end
|
||||
end
|
||||
end
|
||||
-- transport types in one column, centred between the longest place name and the
|
||||
-- widest timer of all rows
|
||||
if kindL and kindR then
|
||||
local cx = math.floor((kindL + kindR) / 2 + 0.5)
|
||||
for i = 1, n do
|
||||
rows[i].kind:ClearAllPoints()
|
||||
rows[i].kind:SetPoint("CENTER", rows[i], "LEFT", cx, 0)
|
||||
end
|
||||
end
|
||||
for i = n + 1, table.getn(rows) do rows[i]:Hide() end
|
||||
panel:SetHeight(TOP + n * ROW_H + 18)
|
||||
panel.title:SetText("|cff33ffccTransports|r" .. (CFG.full and " |cff777777- all|r" or ""))
|
||||
if recOn then
|
||||
panel.status:SetText("|cffff4444REC|r " .. (type(DB.rec) == "table" and table.getn(DB.rec) or 0))
|
||||
else
|
||||
@@ -868,28 +1070,19 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
end
|
||||
end
|
||||
|
||||
-- Auto-show areas: Orgrimmar and Durotar (zone or sub-zone name), plus anything added with /ob allow.
|
||||
-- Names are compared without capitals, quotes or outer spaces. The window also stays
|
||||
-- open while a far route's transport (the Ratchet boat) is in view: on its dock and for
|
||||
-- the whole crossing, which runs through zones that are not on the list.
|
||||
local function Norm(n)
|
||||
n = string.lower(n or "")
|
||||
n = string.gsub(n, "[\"']", "")
|
||||
n = string.gsub(n, "^%s+", "")
|
||||
n = string.gsub(n, "%s+$", "")
|
||||
return n
|
||||
end
|
||||
-- Auto-show: in any zone or sub-zone at either end of a route (or a planning place),
|
||||
-- in a place added with /ob allow, or while a tracked transport is in view.
|
||||
local function InOrg()
|
||||
UpdateLoc()
|
||||
if not CFG.zoneOnly then return true end
|
||||
local z = Norm(GetZoneText())
|
||||
local sz = Norm(GetSubZoneText())
|
||||
if z == "orgrimmar" or sz == "orgrimmar" or z == "durotar" or sz == "durotar" then return true end
|
||||
local z, sz = LOC.z, LOC.sz
|
||||
for i = 1, table.getn(CFG.allow) do
|
||||
local a = Norm(CFG.allow[i])
|
||||
if a ~= "" and (z == a or sz == a) then return true end
|
||||
end
|
||||
for i = 1, table.getn(ROUTES) do
|
||||
if ROUTES[i].far and seenState[ROUTES[i].entry] then return true end
|
||||
local r = ROUTES[i]
|
||||
if InListAt(r.homeZ, z, sz) or InListAt(r.awayZ, z, sz) or InListAt(r.planZ, z, sz) or seenState[r.entry] then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
@@ -932,7 +1125,7 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
GameTooltip:SetOwner(this, "ANCHOR_LEFT")
|
||||
GameTooltip:AddLine("Transports", 0.2, 1, 0.8)
|
||||
GameTooltip:AddLine("Click: " .. (panel:IsShown() and "hide" or "show") .. " the window (works anywhere)", 1, 1, 1)
|
||||
GameTooltip:AddLine("Opens by itself in Orgrimmar and Durotar.", 0.7, 0.7, 0.7)
|
||||
GameTooltip:AddLine("Opens by itself at either end of any route.", 0.7, 0.7, 0.7)
|
||||
GameTooltip:AddLine("Drag: move this icon around the minimap", 0.7, 0.7, 0.7)
|
||||
GameTooltip:Show()
|
||||
end)
|
||||
@@ -948,7 +1141,7 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
local function RunAlerts()
|
||||
for i = 1, table.getn(ROUTES) do
|
||||
local r = ROUTES[i]
|
||||
if Visible(r) and r.far then
|
||||
if Visible(r) and r.far and not Flipped(r) then
|
||||
local fly = FlyBy(r)
|
||||
if fly and Watched(r) and CFG.alert > 0 and fly <= CFG.alert then
|
||||
local stamp = math.floor(EpochNow() + fly + 0.5)
|
||||
@@ -968,7 +1161,7 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
if alerted[r.entry] ~= stamp then
|
||||
alerted[r.entry] = stamp
|
||||
if CFG.chat then
|
||||
Say(RouteName(r) .. " arrives in " .. FormatTime(secs) .. ".")
|
||||
Say(ShownName(r) .. ": docks at " .. StartName(r) .. " in " .. FormatTime(secs) .. ".")
|
||||
end
|
||||
if CFG.sound then PlaySound("RaidWarning") end
|
||||
if CFG.flash then flashUntil = GetTime() + 8 end
|
||||
@@ -1058,7 +1251,7 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
elseif cmd == "sound" then Toggle("sound alert", "sound")
|
||||
elseif cmd == "chat" then Toggle("chat alert", "chat")
|
||||
elseif cmd == "flash" then Toggle("minimap icon flash", "flash")
|
||||
elseif cmd == "zone" then Toggle("auto-show limited to Orgrimmar/Durotar (off = always show)", "zoneOnly")
|
||||
elseif cmd == "zone" then Toggle("auto-show only at route ends (off = always show)", "zoneOnly")
|
||||
elseif cmd == "where" then
|
||||
Say("zone: \"" .. (GetZoneText() or "") .. "\" sub-zone: \"" .. (GetSubZoneText() or "") .. "\" auto-shows here: " .. tostring(InOrg()))
|
||||
elseif cmd == "allow" then
|
||||
@@ -1095,7 +1288,7 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
|
||||
elseif cmd == "allowclear" then
|
||||
CFG.allow = {}
|
||||
Say("extra places cleared (Orgrimmar and Durotar remain).")
|
||||
Say("extra places cleared (route ends still open the window).")
|
||||
elseif cmd == "shape" then
|
||||
local v = string.lower(w[2] or "")
|
||||
if v == "square" or v == "round" then
|
||||
@@ -1108,6 +1301,9 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
end
|
||||
PlaceButton()
|
||||
Say("minimap icon placement: " .. (CFG.shape or "auto") .. ".")
|
||||
elseif cmd == "all" then
|
||||
CFG.full = not CFG.full
|
||||
Say(CFG.full and "showing all routes." or "showing routes for where you are.")
|
||||
elseif cmd == "boat" then
|
||||
CFG.hideBoat = not CFG.hideBoat
|
||||
Say("Sparkwater Port boat " .. (CFG.hideBoat and "hidden" or "shown") .. ".")
|
||||
@@ -1141,7 +1337,7 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
DB.names[r.entry] = text
|
||||
Say("route " .. w[2] .. " is now called: " .. RouteName(r))
|
||||
else
|
||||
Say("usage: /ob name <number from /ob list> <new place name> (the zeppelin / boat part stays)")
|
||||
Say("usage: /ob name <number from /ob list> <new place name> (renames the far end; the home end stays)")
|
||||
end
|
||||
|
||||
elseif cmd == "period" then
|
||||
@@ -1179,6 +1375,8 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
DB.period = {}
|
||||
DB.origin = {}
|
||||
DB.dwell = {}
|
||||
DB.farOff = {}
|
||||
DB.farDwell = {}
|
||||
DB.lastCal = nil
|
||||
DB.lastObs = {}
|
||||
DB.pending = {}
|
||||
@@ -1222,7 +1420,7 @@ local OB_OK, OB_ERR = pcall(function ()
|
||||
if recOn then Say("recording: on, " .. table.getn(DB.rec) .. " lines so far.") end
|
||||
|
||||
else
|
||||
Say("/ob (window on/off) | list | alert <s> | sound|chat|flash|zone|boat|lock | scale <n> | where | shape <square|round|auto> | allow|disallow <name> | resetpos | name <n> <text> | period <n> <s> | travel <s> | calibrate | reset | debug [on|off]")
|
||||
Say("/ob (window on/off) | list | alert <s> | all|sound|chat|flash|zone|boat|lock | scale <n> | where | shape <square|round|auto> | allow|disallow <name> | resetpos | name <n> <text> | period <n> <s> | travel <s> | calibrate | reset | debug [on|off]")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user