Release OctoBus 1.3.9 with persistent window controls
This commit is contained in:
@@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user