Release OctoBus 1.3.9 with persistent window controls

This commit is contained in:
2026-11-08 23:06:57 -04:00
parent bc8591d764
commit 8c5d96ae01
6 changed files with 159 additions and 27 deletions
+1
View File
@@ -0,0 +1 @@
* text=auto eol=lf
+27
View File
@@ -1,5 +1,32 @@
# Changelog
## 1.3.9 (6 November 2026)
- Match ShaguDPS Golden UI headers using cropped native stone artwork and the configured ShaguTweaks tint multiplied by its title palette.
- Use the Golden UI border palette for the fading divider, with Darkened UI taking priority.
- Header opacity follows Window opacity independently of Background opacity; 100% Window opacity makes the stone header opaque.
## 1.3.8 (6 November 2026)
- Replace the flat pink title fill with a subdued dark gold gradient when Golden UI is active.
- Map attachment preserves the current position and saves its offset instead of snapping to a fixed edge.
- Allow dragging while attached and unlocked to adjust the offset; detaching also preserves the current position.
## 1.3.7 (6 November 2026)
- Remember manual show/hide choices across reloads, logins and zone changes; `/ob resetpos` restores automatic visibility.
- Match the title strip to ShaguTweaks Golden UI when active.
- Use a solid background so 100% background and window opacity is fully opaque.
- Add optional attachment to the right of the ShaguTweaks windowed world map, following it even while locked.
## 1.3.6 (27 October 2026)
### Fixed
- After a server restart, the timers set from the restart offsets (a first guess) could be shown and shared as if they had been seen. Asking the server's uptime again can give a time one second earlier, which made every guess count as "seen since the restart"; the guesses were then passed to every other OctoBus user as sightings and blocked real shared times from replacing them. Guesses are now marked as such, are never shared, and give way to the first real sighting or shared time. Saved guesses from 1.3.5 are cleaned up at the next login.
## 1.3.5 (12 October 2026)
### New
+105 -15
View File
@@ -502,6 +502,10 @@ local OB_OK, OB_ERR = pcall(function ()
local function OnBoot(boot)
local old = DB.boot or DB.bootPrev
-- the uptime answer is whole seconds, so the same restart can come out a second
-- earlier or later on another ask: keep the first value, or every timer set from it
-- would suddenly count as "seen since the restart"
if DB.boot and math.abs(boot - DB.boot) <= 5 then boot = DB.boot end
DB.boot = boot
DB.bootPrev = nil
local restarted = old and math.abs(boot - old) > 300
@@ -520,7 +524,12 @@ local OB_OK, OB_ERR = pcall(function ()
local e = r.entry
local P = Period(r)
local c = Phase(r)
if c and P and P > 0 and (DB.seenAt[e] or 0) < boot then
-- seenAt within a few seconds of the restart was set by the restart itself (nobody
-- is logged in that early), not by a sighting. A "shared" time that is exactly the
-- old offset again is someone's guess that 1.3.5 passed on as a sighting.
local guessed = DB.src[e] == "shared" and DB.anchor[e] and DB.bootPhaseBoot[e] ~= boot and c
and math.abs(DB.anchor[e] - boot - c) < 2
if c and P and P > 0 and ((DB.seenAt[e] or 0) <= boot + 5 or guessed) then
DB.anchor[e] = boot + c
if not DB.origin[e] or DB.origin[e] < boot then
-- the stored schedule is from an earlier server run: start this run's
@@ -533,13 +542,16 @@ local OB_OK, OB_ERR = pcall(function ()
DB.lastObs[e] = nil
end
DB.corrected[e] = boot
-- a guess, not a sighting: never shared, and a real sighting or shared time replaces it
DB.src[e] = "guess"
if restarted and DB.bootTheory then DB.seenAt[e] = boot end -- "~" instead of "?"
end
end
-- learn offsets from routes already seen since this restart
for i = 1, table.getn(ROUTES) do
local r = ROUTES[i]
if (DB.seenAt[r.entry] or 0) > boot and DB.anchor[r.entry] then LearnPhase(r, DB.anchor[r.entry]) end
local e = r.entry
if (DB.seenAt[e] or 0) > boot + 5 and DB.anchor[e] and DB.src[e] ~= "guess" then LearnPhase(r, DB.anchor[e]) end
end
end
@@ -916,7 +928,7 @@ local OB_OK, OB_ERR = pcall(function ()
local BD_R, BD_G, BD_B = 0.900, 0.850, 0.900
local function CreateBackdrop(f)
f:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 16,
insets = { left = 4, right = 4, top = 4, bottom = 4 },
@@ -1004,6 +1016,17 @@ local OB_OK, OB_ERR = pcall(function ()
panel:EnableMouse(true)
CreateBackdrop(panel)
panel.goldBar = panel:CreateTexture(nil, "ARTWORK")
panel.goldBar:SetTexture("Interface\\PaperDollInfoFrame\\UI-Character-General-TopLeft")
panel.goldBar:SetTexCoord(80/256, 255/256, 18/256, 32/256)
panel.goldBar:SetPoint("TOPLEFT", panel, "TOPLEFT", 4, -4)
panel.goldBar:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -4, -4)
panel.goldBar:Hide()
local function MapAttached()
return CFG.snapMap and ShaguTweaks and ShaguTweaks.WorldMapWindowActive and WorldMapFrame
end
panel.title = panel:CreateFontString(nil, "OVERLAY")
panel.title:SetFont(font, 13)
panel.title:SetPoint("TOP", panel, "TOP", 0, -8)
@@ -1024,8 +1047,13 @@ local OB_OK, OB_ERR = pcall(function ()
local function PlaceDivider(y, a)
panel.divL:SetPoint("TOPLEFT", panel, "TOPLEFT", 10, y)
panel.divR:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -10, y)
panel.divL:SetGradientAlpha("HORIZONTAL", 0.8, 0.8, 0.8, 0, 0.8, 0.8, 0.8, a)
panel.divR:SetGradientAlpha("HORIZONTAL", 0.8, 0.8, 0.8, a, 0.8, 0.8, 0.8, 0)
local r, g, b = .8, .8, .8
if ShaguTweaks and ShaguTweaks.GoldMode and not ShaguTweaks.DarkMode then
local edge = ShaguTweaks.UIBorder
r, g, b = edge and edge.r or .8, edge and edge.g or .62, edge and edge.b or .32
end
panel.divL:SetGradientAlpha("HORIZONTAL", r, g, b, 0, r, g, b, a)
panel.divR:SetGradientAlpha("HORIZONTAL", r, g, b, a, r, g, b, 0)
end
panel.status = panel:CreateFontString(nil, "OVERLAY")
@@ -1040,7 +1068,30 @@ local OB_OK, OB_ERR = pcall(function ()
-- The window is pinned by one corner (CFG.anchor, set in the settings window; default
-- bottom-left), so when the list gets longer or shorter the other edges move.
local function SaveMapOffset()
if not MapAttached() then return end
local left, top = panel:GetLeft(), panel:GetTop()
local ml, mt = WorldMapFrame:GetLeft(), WorldMapFrame:GetTop()
if not left or not top or not ml or not mt then return end
local ratio = WorldMapFrame:GetEffectiveScale() / panel:GetEffectiveScale()
CFG.mapX = left - ml * ratio
CFG.mapY = top - mt * ratio
end
local function AnchorToMap()
if not MapAttached() then return end
if not CFG.mapX or not CFG.mapY then SaveMapOffset() end
if not CFG.mapX or not CFG.mapY then return end
panel:ClearAllPoints()
panel:SetPoint("TOPLEFT", WorldMapFrame, "TOPLEFT", CFG.mapX, CFG.mapY)
end
local function SavePos()
if MapAttached() then
SaveMapOffset()
AnchorToMap()
return
end
local a = CFG.anchor or "BOTTOMLEFT"
local x = string.find(a, "RIGHT") and panel:GetRight() or panel:GetLeft()
local y = string.find(a, "TOP") and panel:GetTop() or panel:GetBottom()
@@ -1112,7 +1163,7 @@ local OB_OK, OB_ERR = pcall(function ()
local l, t = panel:GetLeft(), panel:GetTop()
panel:SetScale(sc)
CFG.scale = sc
if l and t then
if l and t and not MapAttached() then
local newEff = panel:GetEffectiveScale()
panel:ClearAllPoints()
panel:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", l * oldEff / newEff, t * oldEff / newEff)
@@ -1312,6 +1363,21 @@ local OB_OK, OB_ERR = pcall(function ()
ROW_H = math.floor(18 * textScale + 0.5)
TOP = math.floor(14 + 18 * textScale + 0.5)
panel:SetWidth(WIDTH)
panel.goldBar:SetHeight(TOP - 9)
if ShaguTweaks and ShaguTweaks.GoldMode and not ShaguTweaks.DarkMode then
local tint = ShaguTweaks.UITint
local mods, translate = ShaguTweaks.mods, ShaguTweaks.T
local golden = mods and translate and mods[translate["Golden UI"]]
local bar = golden and golden.config and golden.config["goldmode.bar"]
-- Same opaque stone artwork and multiplied palette as ShaguDPS.
panel.goldBar:SetVertexColor(
(tint and tint.r or 1) * (bar and bar.r or .8),
(tint and tint.g or .78) * (bar and bar.g or .63),
(tint and tint.b or .4) * (bar and bar.b or .62), wa)
panel.goldBar:Show()
else
panel.goldBar:Hide()
end
StyleText(panel.title, math.floor(13 * textScale + 0.5))
PlaceDivider(-(TOP - 5), 0.6 * math.min(1, bg + 0.25) * wa)
StyleText(panel.status, math.floor(9 * textScale + 0.5))
@@ -1495,6 +1561,10 @@ local OB_OK, OB_ERR = pcall(function ()
end
local function PlacePanel()
if MapAttached() then
AnchorToMap()
return
end
panel:ClearAllPoints()
if CFG.pos then
panel:SetPoint(CFG.pos.a or "BOTTOMLEFT", UIParent, "BOTTOMLEFT", CFG.pos.x, CFG.pos.y)
@@ -1521,16 +1591,17 @@ local OB_OK, OB_ERR = pcall(function ()
end
-- override: nil = automatic, "show" = forced open anywhere, "hide" = forced closed.
-- Crossing into or out of the auto-show area puts it back to automatic.
-- Manual choices persist until toggled again or /ob resetpos restores auto mode.
local override = nil
local lastArea = nil
local attached, goldMode = nil, nil
local function ApplyVisibility()
local area = InOrg()
if area ~= lastArea then
lastArea = area
override = nil
end
override = CFG.visibility
local map = MapAttached() and true or false
local gold = ShaguTweaks and ShaguTweaks.GoldMode and not ShaguTweaks.DarkMode and true or false
if map ~= attached then attached = map PlacePanel() end
if gold ~= goldMode then goldMode = gold ApplyLook() end
local want
if override == "show" then want = true
elseif override == "hide" then want = false
@@ -1550,6 +1621,7 @@ local OB_OK, OB_ERR = pcall(function ()
local function ToggleShown()
if panel:IsShown() then override = "hide" else override = "show" end
CFG.visibility = override
ApplyVisibility()
end
@@ -1764,6 +1836,7 @@ local OB_OK, OB_ERR = pcall(function ()
CFG.angle = nil
PlaceButton()
override = nil
CFG.visibility = nil
panel:Hide()
ApplyVisibility()
Say("window and minimap icon moved back to their default spots, auto mode restored.")
@@ -1797,6 +1870,7 @@ local OB_OK, OB_ERR = pcall(function ()
else
local n = CalStart()
override = "show"
CFG.visibility = override
ApplyVisibility()
Say("calibrating " .. n .. " transports. Stand where you can see the docks (between the two towers), do not /reload, and wait.")
Say("each transport needs " .. "1 fresh arrival (2 the first time). Rows turn + when done. /ob calibrate stop cancels.")
@@ -1851,6 +1925,7 @@ local OB_OK, OB_ERR = pcall(function ()
recZone, recTaxi = nil, nil
recOn = true
override = "show"
CFG.visibility = override
ApplyVisibility()
Say("recording every transport the DLL reports. /ob debug off to stop; log out or /reload to save it.")
else
@@ -1922,7 +1997,7 @@ local OB_OK, OB_ERR = pcall(function ()
for i = 1, table.getn(ROUTES) do
local e = ROUTES[i].entry
local a = DB.anchor[e]
if a and a > DB.boot and (DB.seenAt[e] or 0) > DB.boot and (DB.src[e] == "dll" or (not own and DB.src[e] == "shared")) then
if a and a > DB.boot and (DB.seenAt[e] or 0) > DB.boot + 5 and (DB.src[e] == "dll" or (not own and DB.src[e] == "shared")) then
table.insert(parts, e .. ":" .. string.format("%.1f", a - DB.boot))
end
end
@@ -1947,7 +2022,11 @@ local OB_OK, OB_ERR = pcall(function ()
if not r or not DB.boot or not CFG.share then return end
if rel < 0 or rel > time() - DB.boot + 600 then return end
-- our own DLL sighting in this run wins
if DB.src[e] == "dll" and (DB.seenAt[e] or 0) > DB.boot then return end
if DB.src[e] == "dll" and (DB.seenAt[e] or 0) > DB.boot + 5 then return end
-- a time that is just the restart offset again is someone's guess passed on as a
-- sighting (OctoBus 1.3.5 did that): ignore it
local c = DB.bootPhaseBoot[e] ~= DB.boot and Phase(r)
if c and math.abs(rel - c) < 2 then return end
local a = DB.boot + rel
if DB.src[e] == "shared" and DB.anchor[e] and math.abs(DB.anchor[e] - a) < 0.5 then return end
local P = Period(r)
@@ -2025,7 +2104,18 @@ local OB_OK, OB_ERR = pcall(function ()
cfg = CFG,
ApplyLook = function() ApplyLook() Refresh() end,
Refresh = function() Refresh() end,
Show = function() override = "show" ApplyVisibility() end,
Show = function() CFG.visibility = "show" ApplyVisibility() end,
SetSnapMap = function(v)
if v then
CFG.snapMap = true
SaveMapOffset()
AnchorToMap()
else
CFG.snapMap = false
-- Keep the current screen position when detaching too.
SavePos()
end
end,
SetAnchor = function(a)
CFG.anchor = a
if panel:GetLeft() then SavePos() end -- keeps the window where it is, pinned by the new corner
+1 -1
View File
@@ -2,7 +2,7 @@
## Title: |cff33ffccOcto|rBus
## Notes: Zeppelin and boat arrival timers for Orgrimmar. Reads live positions from ZepSense.dll; minimap icon toggles the window.
## Author: Phridgey
## Version: 1.3.5
## Version: 1.3.9
## SavedVariables: OctoBusDB
OctoBus.lua
+14 -11
View File
@@ -80,7 +80,7 @@ local function Build()
local C = OB.cfg
frame = CreateFrame("Frame", "OctoBusOptions", UIParent)
frame:SetWidth(W + 2 * PAD)
frame:SetHeight(700 + 2 * PAD)
frame:SetHeight(724 + 2 * PAD)
frame:SetPoint("CENTER", UIParent, "CENTER", 0, 60)
frame:SetFrameStrata("DIALOG")
frame:SetClampedToScreen(true)
@@ -156,25 +156,28 @@ local function Build()
end)
end
Heading("Routes", -440)
Check("Open by itself only at route ends", 14, -458, function() return C.zoneOnly end,
Check("Attach to ShaguTweaks world map", 14, -434, function() return C.snapMap end,
function(v) OB.SetSnapMap(v) end)
Heading("Routes", -464)
Check("Open by itself only at route ends", 14, -482, function() return C.zoneOnly end,
function(v) C.zoneOnly = v end)
Check("Show all routes (not just for where you are)", 14, -482, function() return C.full end,
Check("Show all routes (not just for where you are)", 14, -506, function() return C.full end,
function(v) C.full = v OB.Refresh() end)
Check("Show the Sparkwater Port boat", 14, -506, function() return not C.hideBoat end,
Check("Show the Sparkwater Port boat", 14, -530, function() return not C.hideBoat end,
function(v) C.hideBoat = not v OB.Refresh() end)
Check("Share timers with other OctoBus users", 14, -530, function() return C.share end,
Check("Share timers with other OctoBus users", 14, -554, function() return C.share end,
function(v)
C.share = v
if v then JoinChannelByName("OctoBusSync") else LeaveChannelByName("OctoBusSync") end
end)
Heading("Alerts (click a row to put a bell on it)", -562)
Slider("Warn before arrival", -592, 10, 120, 5, function() return C.alert or 45 end,
Heading("Alerts (click a row to put a bell on it)", -586)
Slider("Warn before arrival", -616, 10, 120, 5, function() return C.alert or 45 end,
function(v) C.alert = v end, function(v) return math.floor(v + 0.5) .. " s" end)
Check("Chat message", 14, -614, function() return C.chat end, function(v) C.chat = v end)
Check("Sound", 120, -614, function() return C.sound end, function(v) C.sound = v end)
Check("Flash the minimap icon", 14, -638, function() return C.flash end, function(v) C.flash = v end)
Check("Chat message", 14, -638, function() return C.chat end, function(v) C.chat = v end)
Check("Sound", 120, -638, function() return C.sound end, function(v) C.sound = v end)
Check("Flash the minimap icon", 14, -662, function() return C.flash end, function(v) C.flash = v end)
local reset = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
reset:SetWidth(150)
+11
View File
@@ -142,3 +142,14 @@ The Ratchet boat cannot be seen from Orgrimmar, so its row is always an estimate
- **Window did not auto-show**: use `/ob where` to see your zone and sub-zone names, then `/ob allow <name>` if you want it there.
- **Ratchet times look wrong**: it has probably been a long time, or there was a server restart, since you last saw it. Visit Ratchet with the window open and wait for the boat to dock.
- **Everything looks off after a big server change** (new route timings): `/ob reset`, then calibrate again.
### Local changes in 1.3.7
Manual show/hide choices (minimap icon, /ob, or the map Transports button) survive reloads, logins and zone changes. /ob resetpos restores automatic visibility. Opening settings shows the panel and saves that choice.
With ShaguTweaks Golden UI active, the Transports title strip uses the same cropped native stone artwork and Golden UI palette as ShaguDPS, with a gold fading divider (Darkened UI takes priority). ShaguTweaks remains optional. Background opacity and Window opacity both at 100% give a fully opaque background; lowering either fades it. The stone title follows Window opacity independently of Background opacity: use 100% Window opacity for an opaque header. Text retains its separate opacity setting.
Enable **Attach to ShaguTweaks world map** in /ob options to keep Transports at its current position and save its offset from the map. It follows map movement even while locked; dragging the unlocked panel adjusts the saved offset. Its own scale and visibility remain independent. Closing the map leaves Transports at the map's anchor location. Turning attachment off keeps the current screen position. Without ShaguTweaks windowed map, standalone behavior is used.
Not tested in game. Check visibility after reload and login, Golden UI on/off, opacity sliders, and map attachment while moving/scaling the map with the panel locked and unlocked.