mirror of
https://github.com/Nighthawk42/wow_bejeweled.git
synced 2026-08-30 04:30:22 +00:00
feat: restore minimap launcher
This commit is contained in:
@@ -32,3 +32,4 @@ UI\About.lua
|
||||
UI\Legal.lua
|
||||
UI\MainWindow.lua
|
||||
UI\Compartment.lua
|
||||
UI\Minimap.lua
|
||||
|
||||
@@ -27,6 +27,7 @@ function addon:Initialize(accountData, profileData)
|
||||
assert(self.Legal, "Legal module is not loaded")
|
||||
assert(self.MainWindow, "MainWindow module is not loaded")
|
||||
assert(self.Compartment, "Compartment module is not loaded")
|
||||
assert(self.MinimapButton, "MinimapButton module is not loaded")
|
||||
assert(self.Input, "Input module is not loaded")
|
||||
assert(self.Session, "Session module is not loaded")
|
||||
|
||||
@@ -45,6 +46,7 @@ function addon:Initialize(accountData, profileData)
|
||||
self.legalFactory = self.Legal
|
||||
self.mainWindowFactory = self.MainWindow
|
||||
self.compartment = self.Compartment
|
||||
self.minimapButtonFactory = self.MinimapButton
|
||||
self.inputFactory = self.Input
|
||||
self.sessionFactory = self.Session
|
||||
self.initialized = true
|
||||
@@ -55,9 +57,34 @@ function addon:Initialize(accountData, profileData)
|
||||
return self
|
||||
end
|
||||
|
||||
function addon:EnsureMinimapButton(options)
|
||||
if self.minimapButton then
|
||||
return self.minimapButton
|
||||
end
|
||||
options = options or {}
|
||||
local minimap = options.minimap or Minimap
|
||||
local uiParent = options.uiParent or UIParent
|
||||
if not minimap or not uiParent then
|
||||
return nil
|
||||
end
|
||||
self.minimapButton = self.MinimapButton:New({
|
||||
minimap = minimap,
|
||||
uiParent = uiParent,
|
||||
settings = self.profileData.settings,
|
||||
createFrame = options.createFrame,
|
||||
tooltip = options.minimapTooltip,
|
||||
cursorPosition = options.cursorPosition,
|
||||
runtimeProvider = function()
|
||||
return self.runtime
|
||||
end,
|
||||
})
|
||||
return self.minimapButton
|
||||
end
|
||||
|
||||
function addon:StartRuntime(options)
|
||||
assert(self.initialized, "addon must be initialized before starting the runtime")
|
||||
if self.runtime then
|
||||
self:EnsureMinimapButton(options)
|
||||
return self.runtime
|
||||
end
|
||||
options = options or {}
|
||||
@@ -79,7 +106,16 @@ function addon:StartRuntime(options)
|
||||
hintsEnabled = options.hintsEnabled,
|
||||
flightOptionProvider = options.flightOptionProvider,
|
||||
onFlightTimedRequested = options.onFlightTimedRequested,
|
||||
onSettingsChanged = function(key, value, window)
|
||||
if key == "hideMinimap" and self.minimapButton then
|
||||
self.minimapButton:RefreshVisibility()
|
||||
end
|
||||
if options.onSettingsChanged then
|
||||
return options.onSettingsChanged(key, value, window)
|
||||
end
|
||||
end,
|
||||
})
|
||||
self:EnsureMinimapButton(options)
|
||||
self.runtime:Show()
|
||||
return self.runtime
|
||||
end
|
||||
|
||||
@@ -367,6 +367,7 @@ function MainWindow:New(uiParent, options)
|
||||
onSessionStopped = ValidateCallback(options.onSessionStopped, "onSessionStopped"),
|
||||
flightOptionProvider = ValidateCallback(options.flightOptionProvider, "flightOptionProvider"),
|
||||
onFlightTimedRequested = ValidateCallback(options.onFlightTimedRequested, "onFlightTimedRequested"),
|
||||
onSettingsChanged = ValidateCallback(options.onSettingsChanged, "onSettingsChanged"),
|
||||
grid = options.grid or Grid:New(options.random),
|
||||
session = nil,
|
||||
activeOverlay = nil,
|
||||
@@ -457,6 +458,9 @@ function MainWindow:ApplySettings(key)
|
||||
if key == nil or key == "gameAlpha" then
|
||||
self.frame:SetAlpha(self.profile.settings.gameAlpha or 1)
|
||||
end
|
||||
if self.onSettingsChanged then
|
||||
self.onSettingsChanged(key, key and self.profile.settings[key] or nil, self)
|
||||
end
|
||||
return key and self.profile.settings[key] or self.profile.settings
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
local _, addon = ...
|
||||
|
||||
local Constants = assert(addon.Constants, "Constants module is not loaded")
|
||||
|
||||
local MinimapButton = {}
|
||||
MinimapButton.__index = MinimapButton
|
||||
|
||||
local BUTTON_SIZE = 33
|
||||
local ICON_SIZE = 26
|
||||
local ATTACHED_RADIUS = 105
|
||||
local TRACKING_BORDER = "Interface\\Minimap\\MiniMap-TrackingBorder"
|
||||
local HIGHLIGHT_TEXTURE = "Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight"
|
||||
|
||||
local function Atan2(y, x)
|
||||
if x > 0 then
|
||||
return math.atan(y / x)
|
||||
elseif x < 0 and y >= 0 then
|
||||
return math.atan(y / x) + math.pi
|
||||
elseif x < 0 then
|
||||
return math.atan(y / x) - math.pi
|
||||
elseif y > 0 then
|
||||
return math.pi / 2
|
||||
elseif y < 0 then
|
||||
return -math.pi / 2
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
local function SetTextureSize(texture, width, height)
|
||||
texture:SetWidth(width)
|
||||
texture:SetHeight(height)
|
||||
end
|
||||
|
||||
function MinimapButton:New(options)
|
||||
options = options or {}
|
||||
assert(type(options) == "table", "minimap-button options must be a table")
|
||||
assert(options.minimap ~= nil, "minimap button requires Minimap")
|
||||
assert(options.uiParent ~= nil, "minimap button requires UIParent")
|
||||
assert(type(options.settings) == "table", "minimap button requires profile settings")
|
||||
assert(type(options.runtimeProvider) == "function", "minimap button requires a runtime provider")
|
||||
|
||||
local instance = setmetatable({
|
||||
minimap = options.minimap,
|
||||
uiParent = options.uiParent,
|
||||
settings = options.settings,
|
||||
runtimeProvider = options.runtimeProvider,
|
||||
createFrame = options.createFrame or CreateFrame,
|
||||
tooltip = options.tooltip,
|
||||
cursorPosition = options.cursorPosition or GetCursorPosition,
|
||||
}, self)
|
||||
assert(type(instance.createFrame) == "function", "CreateFrame is unavailable for minimap button")
|
||||
assert(type(instance.cursorPosition) == "function", "cursor position provider must be a function")
|
||||
|
||||
local frame = instance.createFrame("Frame", "BejeweledMinimapIcon", instance.minimap)
|
||||
frame:SetWidth(BUTTON_SIZE)
|
||||
frame:SetHeight(BUTTON_SIZE)
|
||||
if type(frame.SetFrameStrata) == "function" then
|
||||
frame:SetFrameStrata("HIGH")
|
||||
end
|
||||
frame:EnableMouse(true)
|
||||
if type(frame.SetClampedToScreen) == "function" then
|
||||
frame:SetClampedToScreen(true)
|
||||
end
|
||||
|
||||
frame.icon = frame:CreateTexture(nil, "BACKGROUND")
|
||||
SetTextureSize(frame.icon, ICON_SIZE, ICON_SIZE)
|
||||
frame.icon:SetPoint("CENTER", frame, "CENTER", -1, 1)
|
||||
frame.icon:SetTexture(Constants.IMAGE_ROOT .. "windowIcon")
|
||||
|
||||
frame.border = frame:CreateTexture(nil, "ARTWORK")
|
||||
SetTextureSize(frame.border, 52, 52)
|
||||
frame.border:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)
|
||||
frame.border:SetTexture(TRACKING_BORDER)
|
||||
|
||||
frame.highlight = frame:CreateTexture(nil, "OVERLAY")
|
||||
SetTextureSize(frame.highlight, 32, 32)
|
||||
frame.highlight:SetPoint("CENTER", frame, "CENTER", 0, 0)
|
||||
frame.highlight:SetTexture(HIGHLIGHT_TEXTURE)
|
||||
frame.highlight:SetBlendMode("ADD")
|
||||
frame.highlight:Hide()
|
||||
|
||||
frame:SetScript("OnMouseDown", function(button, mouseButton)
|
||||
button.icon:ClearAllPoints()
|
||||
button.icon:SetPoint("CENTER", button, "CENTER", 0, 0)
|
||||
if mouseButton == "RightButton" then
|
||||
button.moving = true
|
||||
end
|
||||
end)
|
||||
frame:SetScript("OnMouseUp", function(button, mouseButton)
|
||||
button.icon:ClearAllPoints()
|
||||
button.icon:SetPoint("CENTER", button, "CENTER", -1, 1)
|
||||
button.moving = nil
|
||||
if mouseButton == "LeftButton" then
|
||||
instance:ToggleRuntime()
|
||||
end
|
||||
end)
|
||||
frame:SetScript("OnEnter", function(button)
|
||||
instance:OnEnter(button)
|
||||
end)
|
||||
frame:SetScript("OnLeave", function(button)
|
||||
instance:OnLeave(button)
|
||||
end)
|
||||
frame:SetScript("OnUpdate", function(button)
|
||||
if button.moving then
|
||||
instance:UpdateDrag()
|
||||
end
|
||||
end)
|
||||
|
||||
instance.frame = frame
|
||||
instance:RefreshPosition()
|
||||
instance:RefreshVisibility()
|
||||
return instance
|
||||
end
|
||||
|
||||
function MinimapButton:GetTooltip()
|
||||
return self.tooltip or GameTooltip
|
||||
end
|
||||
|
||||
function MinimapButton:GetUIScale()
|
||||
if type(self.uiParent.GetEffectiveScale) == "function" then
|
||||
return self.uiParent:GetEffectiveScale()
|
||||
end
|
||||
if type(self.uiParent.GetScale) == "function" then
|
||||
return self.uiParent:GetScale()
|
||||
end
|
||||
return 1
|
||||
end
|
||||
|
||||
function MinimapButton:SetAttachedPosition(angle)
|
||||
angle = tonumber(angle) or 0
|
||||
self.frame:ClearAllPoints()
|
||||
self.frame:SetPoint(
|
||||
"CENTER",
|
||||
self.minimap,
|
||||
"CENTER",
|
||||
-(ATTACHED_RADIUS * math.cos(math.rad(angle))),
|
||||
ATTACHED_RADIUS * math.sin(math.rad(angle))
|
||||
)
|
||||
return angle
|
||||
end
|
||||
|
||||
function MinimapButton:SetDetachedPosition(x, y)
|
||||
x = assert(tonumber(x), "detached minimap X position must be numeric")
|
||||
y = assert(tonumber(y), "detached minimap Y position must be numeric")
|
||||
self.frame:ClearAllPoints()
|
||||
self.frame:SetPoint("CENTER", self.uiParent, "BOTTOMLEFT", x, y)
|
||||
return x, y
|
||||
end
|
||||
|
||||
function MinimapButton:RefreshPosition()
|
||||
local settings = self.settings
|
||||
local x = tonumber(settings.minimapX)
|
||||
local y = tonumber(settings.minimapY)
|
||||
if settings.minimapDetached and x and y then
|
||||
return self:SetDetachedPosition(x, y)
|
||||
end
|
||||
settings.minimapDetached = nil
|
||||
return self:SetAttachedPosition(settings.minimapAngle)
|
||||
end
|
||||
|
||||
function MinimapButton:RefreshVisibility()
|
||||
if self.settings.hideMinimap then
|
||||
self.frame:Hide()
|
||||
return false
|
||||
end
|
||||
self.frame:Show()
|
||||
return true
|
||||
end
|
||||
|
||||
function MinimapButton:SetHidden(hidden)
|
||||
self.settings.hideMinimap = hidden and 1 or nil
|
||||
return self:RefreshVisibility()
|
||||
end
|
||||
|
||||
function MinimapButton:ToggleRuntime()
|
||||
local runtime = self.runtimeProvider()
|
||||
if not runtime or type(runtime.Toggle) ~= "function" or type(runtime.IsShown) ~= "function" then
|
||||
return nil
|
||||
end
|
||||
runtime:Toggle()
|
||||
return runtime:IsShown()
|
||||
end
|
||||
|
||||
function MinimapButton:OnEnter(frame)
|
||||
frame.highlight:Show()
|
||||
local tooltip = self:GetTooltip()
|
||||
if not tooltip or type(tooltip.SetOwner) ~= "function" or type(tooltip.SetText) ~= "function"
|
||||
or type(tooltip.AddLine) ~= "function" or type(tooltip.Show) ~= "function" then
|
||||
return false
|
||||
end
|
||||
tooltip:SetOwner(frame, "ANCHOR_LEFT")
|
||||
tooltip:SetText("Bejeweled")
|
||||
tooltip:AddLine("Left-click to show or hide the game.", 1, 1, 1)
|
||||
tooltip:AddLine("Right-drag to move the icon.", 0.75, 0.75, 0.75)
|
||||
tooltip:Show()
|
||||
return true
|
||||
end
|
||||
|
||||
function MinimapButton:OnLeave(frame)
|
||||
frame.highlight:Hide()
|
||||
local tooltip = self:GetTooltip()
|
||||
if tooltip and type(tooltip.Hide) == "function" then
|
||||
tooltip:Hide()
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function MinimapButton:UpdateDrag()
|
||||
local cursorX, cursorY = self.cursorPosition()
|
||||
assert(type(cursorX) == "number" and type(cursorY) == "number", "cursor position must be numeric")
|
||||
local scale = self:GetUIScale()
|
||||
assert(type(scale) == "number" and scale > 0, "UI scale must be positive")
|
||||
cursorX = cursorX / scale
|
||||
cursorY = cursorY / scale
|
||||
|
||||
local minimapLeft = assert(self.minimap:GetLeft(), "Minimap left position is unavailable")
|
||||
local minimapBottom = assert(self.minimap:GetBottom(), "Minimap bottom position is unavailable")
|
||||
local minimapWidth = assert(self.minimap:GetWidth(), "Minimap width is unavailable")
|
||||
local minimapHeight = assert(self.minimap:GetHeight(), "Minimap height is unavailable")
|
||||
local centerX = minimapLeft + minimapWidth / 2
|
||||
local centerY = minimapBottom + minimapHeight / 2
|
||||
local offsetX = cursorX - centerX
|
||||
local offsetY = cursorY - centerY
|
||||
|
||||
if math.sqrt(offsetX * offsetX + offsetY * offsetY) > minimapWidth then
|
||||
self.settings.minimapDetached = true
|
||||
self.settings.minimapX = cursorX
|
||||
self.settings.minimapY = cursorY
|
||||
self:SetDetachedPosition(cursorX, cursorY)
|
||||
return "detached", cursorX, cursorY
|
||||
end
|
||||
|
||||
local angle = math.deg(Atan2(offsetY, -offsetX))
|
||||
self.settings.minimapAngle = angle
|
||||
self.settings.minimapDetached = nil
|
||||
self:SetAttachedPosition(angle)
|
||||
return "attached", angle
|
||||
end
|
||||
|
||||
MinimapButton.BUTTON_SIZE = BUTTON_SIZE
|
||||
MinimapButton.ICON_SIZE = ICON_SIZE
|
||||
MinimapButton.ATTACHED_RADIUS = ATTACHED_RADIUS
|
||||
|
||||
addon.MinimapButton = MinimapButton
|
||||
@@ -15,6 +15,7 @@ local DEFINITIONS = {
|
||||
{ key = "soundMode", label = "Sound", kind = "sound" },
|
||||
{ key = "disableHints", label = "Disable Hints", kind = "toggle" },
|
||||
{ key = "lockWindow", label = "Lock Window", kind = "toggle" },
|
||||
{ key = "hideMinimap", label = "Hide Minimap Icon", kind = "toggle" },
|
||||
{ key = "publishSkillGains", label = "Chat Skill Gains", kind = "toggle" },
|
||||
{ key = "publishRankGains", label = "Guild Rank Gains", kind = "toggle" },
|
||||
{ key = "publishScores", label = "Publish Scores", kind = "toggle" },
|
||||
@@ -136,7 +137,11 @@ function Options:Activate(definition)
|
||||
elseif definition.kind == "sound" then
|
||||
self:CycleSound()
|
||||
else
|
||||
self.settings[definition.key] = self.settings[definition.key] and nil or 1
|
||||
if self.settings[definition.key] then
|
||||
self.settings[definition.key] = nil
|
||||
else
|
||||
self.settings[definition.key] = 1
|
||||
end
|
||||
end
|
||||
self.onChanged(definition.key, self.settings[definition.key], self)
|
||||
self:Refresh()
|
||||
|
||||
@@ -6,7 +6,7 @@ This branch contains the analysis-complete, Mainline-first modernization of the
|
||||
|
||||
The preserved 8,401-line Mainline source must be analyzed sequentially, in evidence-backed batches, before runtime work begins. All behavior-critical shortened symbols must be resolved and all batches must be complete before any public Lua API, runtime module, modern TOC, packaging, or release work is added.
|
||||
|
||||
All 17 batches (lines 1–8,401) are documented, the cross-batch identifier audit has no remaining `working` or `unresolved` declarations, and the retained Retail API contracts are pinned in `docs/api-baseline.md`. The analysis phase gate is closed. Runtime implementation now includes wire-compatible SavedVariables initialization, authenticated Classic restoration and personal-best persistence, deterministic grid/match/cascade/scoring transitions, pause/resume and stable-state autosave coordination, session-owned level/game-over handoffs, no-move and timed-expiry detection, click selection, validated swap/rollback sessions, skills, levels, legacy-compatible audio cue scheduling, persistent board rendering and effects, the Classic/Timed HUD, complete local summary/Feats/settings/about/legal presentation, and an automatically assembled movable window with one authoritative runtime update path.
|
||||
All 17 batches (lines 1–8,401) are documented, the cross-batch identifier audit has no remaining `working` or `unresolved` declarations, and the retained Retail API contracts are pinned in `docs/api-baseline.md`. The analysis phase gate is closed. Runtime implementation now includes wire-compatible SavedVariables initialization, authenticated Classic restoration and personal-best persistence, deterministic grid/match/cascade/scoring transitions, pause/resume and stable-state autosave coordination, session-owned level/game-over handoffs, no-move and timed-expiry detection, click selection, validated swap/rollback sessions, skills, levels, legacy-compatible audio cue scheduling, persistent board rendering and effects, the Classic/Timed HUD, complete local summary/Feats/settings/about/legal presentation, minimap and addon-compartment access, and an automatically assembled movable window with one authoritative runtime update path.
|
||||
|
||||
## Goal
|
||||
|
||||
@@ -19,7 +19,7 @@ The eventual addon will target current Retail/Mainline World of Warcraft while p
|
||||
- `Bejeweled/sounds/` — immutable legacy sounds.
|
||||
- `Bejeweled/Core/` — private addon initialization, constants, supported audio playback, non-destructive SavedVariables defaulting, legacy-authenticated Classic save encoding, terminal save clearing, account game counts, and authenticated personal bests.
|
||||
- `Bejeweled/Engine/` — deterministic gameplay state; currently the 8×8 grid, selection/swap and pause/resume sessions, authenticated restoration, session-locked level/game-over transitions, legal moves, legacy cell encoding, stable cascade resolution, legacy score formulas, move accounting, final summary metrics, statistics, skill gains, achievements, and level thresholds.
|
||||
- `Bejeweled/UI/` — Retail-safe frame/rendering boundaries; currently bundled-to-standard font fallback, shared backdrop construction, board tiles, persistent gem-frame projection, recorded cascade-transition playback, pooled legacy-cadence board effects, the session-bound HUD, full local summary and Feats/statistics/leaderboard presentation, settings, About/legal screens, the playable main-window/session shell with Timed setup, and addon-compartment access that can restore a closed window.
|
||||
- `Bejeweled/UI/` — Retail-safe frame/rendering boundaries; currently bundled-to-standard font fallback, shared backdrop construction, board tiles, persistent gem-frame projection, recorded cascade-transition playback, pooled legacy-cadence board effects, the session-bound HUD, full local summary and Feats/statistics/leaderboard presentation, settings, About/legal screens, the playable main-window/session shell with Timed setup, and minimap/addon-compartment access that can restore a closed window.
|
||||
- `docs/analysis/` — sequential batch reports, identifier ledger, and exact coverage schedule.
|
||||
- `docs/architecture.md` — module ownership and authoritative runtime data flow.
|
||||
- `docs/api-baseline.md` — verified Retail API constraints for implementation.
|
||||
|
||||
@@ -16,6 +16,7 @@ The modernization baseline is the authoritative WoW UI/API source snapshot for l
|
||||
| Main window | `SimpleFrame` exposes `RegisterForDrag`, `SetMovable`, protected `StartMoving`, and protected `StopMovingOrSizing` in all environments. `UnitName(unit)` accepts a non-nil unit token and returns non-nil name/server strings, subject to the documented identity restriction. | `UI/MainWindow.lua` begins movement only from its direct left-button drag callback when the profile is unlocked. `Core/Init.lua` supplies `UnitName("player")` lazily as the existing SavedVariables authentication identity. |
|
||||
| Line rendering | `SimpleFrame:CreateLine` returns `SimpleLine`; its current all-environment contract exposes `ClearAllPoints`, `SetStartPoint`, `SetEndPoint`, and `SetThickness`. Line regions inherit texture-region operations used here for addon texture/color, additive blending, alpha, and visibility. | `UI/Animations.lua` replaces the removed legacy `DrawRouteLine` call with pooled, board-relative lightning lines and preserves the 15-tick alternating-highlight lifetime. |
|
||||
| Addon compartment | TOC metadata names global callbacks. In the pinned Mainline implementation, click invokes the global with `(addonName, buttonName)`; hover enter/leave invoke their globals with `(addonName, menuButtonFrame)`. Current Blizzard code uses `GameTooltip:SetOwner`, `SetText`, `AddLine`, `Show`, and `Hide` for frame hover help. | `UI/Compartment.lua` registers the three distinct callbacks, toggles only on a left click, and anchors concise hover help to the supplied menu button. |
|
||||
| Minimap launcher | Current frames expose the positioning, sizing, scale, mouse-script, and clamping operations used by the preserved launcher. Pinned Mainline code continues to normalize `GetCursorPosition()` by effective UI scale before positioning a frame. | `UI/Minimap.lua` restores the 33-pixel local launcher, left-click window toggle, tooltip, right-drag attached/detached placement, and the existing `hideMinimap`, `minimapAngle`, `minimapDetached`, `minimapX`, and `minimapY` profile fields. |
|
||||
| Addon lifecycle | `ADDON_LOADED` is synchronous and supplies `addOnName` followed by `containsBindings`. Frames expose `RegisterEvent(eventName)` and `UnregisterEvent(eventName)` in all environments. | `Core/Init.lua` filters the event by addon name, unregisters its one-shot listener, and performs idempotent initialization after every TOC module is loaded. |
|
||||
| Friends | `C_FriendList.GetFriendInfoByIndex(index)` may return nothing; otherwise it returns one `FriendInfo` table with `name` and Boolean `connected` fields. | Replace all legacy positional `GetFriendInfo(index)` reads with a nil-checked table read and whisper only when `info.connected`. |
|
||||
| Battlefield queues | Current Blizzard Mainline code still reads `local status = GetBattlefieldStatus(index)` and compares the first return with queue states. | Keep the queue scan local and preserve the legacy `status == "queued"` behavior. |
|
||||
@@ -38,6 +39,7 @@ API existence alone does not prove behavioral equivalence. Each future substitut
|
||||
- Animation-group lifecycle, translation, and alpha contracts: [`SimpleAnimGroupAPIDocumentation.lua`](https://github.com/Gethe/wow-ui-source/blob/81d15e42f16f3473131880500e7a8c8eb88fa5e6/Interface/AddOns/Blizzard_APIDocumentationGenerated/SimpleAnimGroupAPIDocumentation.lua), [`SimpleAnimTranslationAPIDocumentation.lua`](https://github.com/Gethe/wow-ui-source/blob/81d15e42f16f3473131880500e7a8c8eb88fa5e6/Interface/AddOns/Blizzard_APIDocumentationGenerated/SimpleAnimTranslationAPIDocumentation.lua), and [`SimpleAnimAlphaAPIDocumentation.lua`](https://github.com/Gethe/wow-ui-source/blob/81d15e42f16f3473131880500e7a8c8eb88fa5e6/Interface/AddOns/Blizzard_APIDocumentationGenerated/SimpleAnimAlphaAPIDocumentation.lua)
|
||||
- Animation ordering: [`SimpleAnimAPIDocumentation.lua`](https://github.com/Gethe/wow-ui-source/blob/81d15e42f16f3473131880500e7a8c8eb88fa5e6/Interface/AddOns/Blizzard_APIDocumentationGenerated/SimpleAnimAPIDocumentation.lua)
|
||||
- Addon-compartment metadata dispatch and current callback signatures: [`AddonCompartment.lua`](https://github.com/Gethe/wow-ui-source/blob/81d15e42f16f3473131880500e7a8c8eb88fa5e6/Interface/AddOns/Blizzard_Minimap/Mainline/AddonCompartment.lua)
|
||||
- Minimap-button frame operations and effective-scale cursor positioning: [`SimpleFrameAPIDocumentation.lua`](https://github.com/Gethe/wow-ui-source/blob/81d15e42f16f3473131880500e7a8c8eb88fa5e6/Interface/AddOns/Blizzard_APIDocumentationGenerated/SimpleFrameAPIDocumentation.lua) and [`LootFrame.lua`](https://github.com/Gethe/wow-ui-source/blob/81d15e42f16f3473131880500e7a8c8eb88fa5e6/Interface/AddOns/Blizzard_UIPanels_Game/Mainline/LootFrame.lua)
|
||||
- Friend contract: [`FriendListDocumentation.lua`](https://github.com/Gethe/wow-ui-source/blob/81d15e42f16f3473131880500e7a8c8eb88fa5e6/Interface/AddOns/Blizzard_APIDocumentationGenerated/FriendListDocumentation.lua)
|
||||
- Combat-log event/access environments: [`CombatLogDocumentation.lua`](https://github.com/Gethe/wow-ui-source/blob/81d15e42f16f3473131880500e7a8c8eb88fa5e6/Interface/AddOns/Blizzard_APIDocumentationGenerated/CombatLogDocumentation.lua), [`CombatLogInternalDocumentation.lua`](https://github.com/Gethe/wow-ui-source/blob/81d15e42f16f3473131880500e7a8c8eb88fa5e6/Interface/AddOns/Blizzard_APIDocumentationGenerated/CombatLogInternalDocumentation.lua), and [`CombatLogSecureDocumentation.lua`](https://github.com/Gethe/wow-ui-source/blob/81d15e42f16f3473131880500e7a8c8eb88fa5e6/Interface/AddOns/Blizzard_APIDocumentationGenerated/CombatLogSecureDocumentation.lua)
|
||||
- Ready-check contract: [`PartyInfoDocumentation.lua`](https://github.com/Gethe/wow-ui-source/blob/81d15e42f16f3473131880500e7a8c8eb88fa5e6/Interface/AddOns/Blizzard_APIDocumentationGenerated/PartyInfoDocumentation.lua)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Target architecture (post-analysis)
|
||||
|
||||
The analysis phase gate is satisfied. This ownership map now governs runtime implementation; currently implemented modules are `Core/Init.lua`, `Core/Constants.lua`, `Core/Audio.lua`, `Core/SavedVariables.lua`, `Engine/Grid.lua`, `Engine/Matches.lua`, `Engine/Cascade.lua`, `Engine/Scoring.lua`, `Engine/Input.lua`, `Engine/Session.lua`, `UI/Fonts.lua`, `UI/Backdrops.lua`, `UI/GemPool.lua`, `UI/Animations.lua`, `UI/HUD.lua`, `UI/Summary.lua`, `UI/Skills.lua`, `UI/Options.lua`, `UI/About.lua`, `UI/Legal.lua`, `UI/MainWindow.lua`, and `UI/Compartment.lua`.
|
||||
The analysis phase gate is satisfied. This ownership map now governs runtime implementation; currently implemented modules are `Core/Init.lua`, `Core/Constants.lua`, `Core/Audio.lua`, `Core/SavedVariables.lua`, `Engine/Grid.lua`, `Engine/Matches.lua`, `Engine/Cascade.lua`, `Engine/Scoring.lua`, `Engine/Input.lua`, `Engine/Session.lua`, `UI/Fonts.lua`, `UI/Backdrops.lua`, `UI/GemPool.lua`, `UI/Animations.lua`, `UI/HUD.lua`, `UI/Summary.lua`, `UI/Skills.lua`, `UI/Options.lua`, `UI/About.lua`, `UI/Legal.lua`, `UI/MainWindow.lua`, `UI/Compartment.lua`, and `UI/Minimap.lua`.
|
||||
|
||||
## Load order and ownership
|
||||
|
||||
@@ -26,6 +26,7 @@ The analysis phase gate is satisfied. This ownership map now governs runtime imp
|
||||
20. `UI/Legal.lua` — first-run and menu-accessible legal/attribution presentation. Explicit acknowledgement writes only the established account-wide `legalDisplayed` field before returning through an injected callback.
|
||||
21. `UI/MainWindow.lua` — the 448×510 movable runtime shell, 400×400 board construction, complete local Menu/New Game/Feats/Settings/About/Legal navigation, Classic Continue/New Game, and a Timed setup screen with a 2–10 minute unit-step slider defaulting to five. It owns session replacement, full-summary handoff, live setting application, first-run legal routing, and show/hide pause behavior; its frame update delegates authoritative elapsed time to `Engine/Session.lua`, HUD expiry to `UI/HUD.lua`, and queued sound flushing to `Core/Audio.lua`. `Core/Init.lua` assembles and shows this shell after `ADDON_LOADED` when live `UIParent` and player identity are available.
|
||||
22. `UI/Compartment.lua` — the minimal public-global boundary required by Retail TOC metadata. Its left-click callback acquires or reuses the private runtime and toggles `UI/MainWindow.lua`; its distinct hover callbacks own anchored `GameTooltip` help. Right clicks and callbacks for another addon name are ignored.
|
||||
23. `UI/Minimap.lua` — the legacy local launcher boundary. It renders the preserved icon inside Blizzard minimap chrome, toggles `UI/MainWindow.lua` on left click, owns hover help, and converts right-drag cursor coordinates into persisted attached angles or detached UIParent coordinates using the established profile fields. `UI/Options.lua` changes visibility through the injected `UI/MainWindow.lua` settings callback; the launcher owns no gameplay state.
|
||||
|
||||
Flight timing remains an explicit integration boundary rather than a third engine mode. `UI/MainWindow.lua` accepts paired `flightOptionProvider(window)` and `onFlightTimedRequested(state, window)` callbacks. The provider returns `nil` when inactive or a copied `{ seconds = nonnegativeNumber, learning = optionalBoolean, ... }` state. Known routes under 60 seconds are shown as too short; learning and longer known routes may be selected. Only the paired request callback can consume that state, so the later Retail event adapter can own taxi observation and session transition without reintroducing the legacy estimator's unreachable APIs.
|
||||
|
||||
|
||||
+193
-1
@@ -61,6 +61,7 @@ LoadAddonFile("Bejeweled/UI/About.lua", addon)
|
||||
LoadAddonFile("Bejeweled/UI/Legal.lua", addon)
|
||||
LoadAddonFile("Bejeweled/UI/MainWindow.lua", addon)
|
||||
LoadAddonFile("Bejeweled/UI/Compartment.lua", addon)
|
||||
LoadAddonFile("Bejeweled/UI/Minimap.lua", addon)
|
||||
|
||||
AssertEqual(eventFrame.registeredEvent, "ADDON_LOADED", "initializer event registration")
|
||||
AssertEqual(addon.Constants.GRID_WIDTH, 8, "grid width")
|
||||
@@ -152,6 +153,9 @@ local function CreateMockTexture(layer)
|
||||
self.points = self.points or {}
|
||||
self.points[#self.points + 1] = { ... }
|
||||
end
|
||||
function texture:ClearAllPoints()
|
||||
self.points = {}
|
||||
end
|
||||
function texture:SetTexture(path)
|
||||
self.path = path
|
||||
return true
|
||||
@@ -375,6 +379,18 @@ local function CreateGemPoolFrame(frameType, name, parent, template)
|
||||
function frame:SetFrameLevel(frameLevel)
|
||||
self.frameLevel = frameLevel
|
||||
end
|
||||
function frame:SetFrameStrata(frameStrata)
|
||||
self.frameStrata = frameStrata
|
||||
end
|
||||
function frame:GetWidth()
|
||||
return self.width
|
||||
end
|
||||
function frame:GetHeight()
|
||||
return self.height
|
||||
end
|
||||
function frame:IsShown()
|
||||
return self.shown and true or false
|
||||
end
|
||||
function frame:GetFrameLevel()
|
||||
if self.frameLevel then
|
||||
return self.frameLevel
|
||||
@@ -419,6 +435,9 @@ local gemPoolParent = {
|
||||
GetFrameLevel = function()
|
||||
return 10
|
||||
end,
|
||||
GetEffectiveScale = function()
|
||||
return 1
|
||||
end,
|
||||
}
|
||||
local enteredGem
|
||||
local gemPool = addon.GemPool:New(gemPoolParent, {
|
||||
@@ -1782,6 +1801,7 @@ function runtimeAudio:Update(elapsed)
|
||||
end
|
||||
local sessionStarts = {}
|
||||
local sessionStops = {}
|
||||
local settingsChanges = {}
|
||||
local flightState
|
||||
local flightRequests = {}
|
||||
local runtime = addon.MainWindow:New(gemPoolParent, {
|
||||
@@ -1797,6 +1817,9 @@ local runtime = addon.MainWindow:New(gemPoolParent, {
|
||||
onSessionStopped = function(result)
|
||||
sessionStops[#sessionStops + 1] = result
|
||||
end,
|
||||
onSettingsChanged = function(key, value)
|
||||
settingsChanges[#settingsChanges + 1] = { key = key, value = value }
|
||||
end,
|
||||
flightOptionProvider = function()
|
||||
return flightState
|
||||
end,
|
||||
@@ -1905,6 +1928,11 @@ AssertEqual(runtime.options:GetSoundMode(), "Quiet", "runtime sound-mode setting
|
||||
runtime.options.rows[4].scripts.OnClick()
|
||||
assert(runtimeProfile.settings.disableHints, "runtime hint setting")
|
||||
assert(not runtime.hud:AreHintsEnabled(), "runtime hint setting did not reach the HUD")
|
||||
runtime.options.rows[6].scripts.OnClick()
|
||||
assert(runtimeProfile.settings.hideMinimap, "runtime minimap visibility setting")
|
||||
AssertEqual(settingsChanges[#settingsChanges].key, "hideMinimap", "runtime minimap setting callback")
|
||||
runtime.options.rows[6].scripts.OnClick()
|
||||
assert(not runtimeProfile.settings.hideMinimap, "runtime minimap visibility setting did not toggle off")
|
||||
runtime.options.backButton.scripts.OnClick()
|
||||
AssertEqual(runtime.activeOverlay, "menu", "runtime Settings Back action")
|
||||
runtimeProfile.settings.gameAlpha = 1
|
||||
@@ -2095,12 +2123,125 @@ AssertEqual(#sessionStarts, 6, "runtime session-start callback count")
|
||||
AssertEqual(#sessionStops, 5, "runtime session-stop callback count")
|
||||
end
|
||||
|
||||
function addon:TestMinimapButtonForTest()
|
||||
local minimap = {
|
||||
left = 100,
|
||||
bottom = 200,
|
||||
width = 140,
|
||||
height = 140,
|
||||
}
|
||||
function minimap:GetLeft()
|
||||
return self.left
|
||||
end
|
||||
function minimap:GetBottom()
|
||||
return self.bottom
|
||||
end
|
||||
function minimap:GetWidth()
|
||||
return self.width
|
||||
end
|
||||
function minimap:GetHeight()
|
||||
return self.height
|
||||
end
|
||||
local uiParent = {
|
||||
GetEffectiveScale = function()
|
||||
return 2
|
||||
end,
|
||||
}
|
||||
local tooltip = { lines = {} }
|
||||
function tooltip:SetOwner(owner, anchor)
|
||||
self.owner = owner
|
||||
self.anchor = anchor
|
||||
end
|
||||
function tooltip:SetText(value)
|
||||
self.text = value
|
||||
end
|
||||
function tooltip:AddLine(value)
|
||||
self.lines[#self.lines + 1] = value
|
||||
end
|
||||
function tooltip:Show()
|
||||
self.shown = true
|
||||
end
|
||||
function tooltip:Hide()
|
||||
self.shown = false
|
||||
end
|
||||
local runtime = { shown = true }
|
||||
function runtime:Toggle()
|
||||
self.shown = not self.shown
|
||||
end
|
||||
function runtime:IsShown()
|
||||
return self.shown
|
||||
end
|
||||
local cursorX = 340
|
||||
local cursorY = 680
|
||||
local settings = {}
|
||||
local button = self.MinimapButton:New({
|
||||
minimap = minimap,
|
||||
uiParent = uiParent,
|
||||
settings = settings,
|
||||
createFrame = CreateGemPoolFrame,
|
||||
tooltip = tooltip,
|
||||
cursorPosition = function()
|
||||
return cursorX, cursorY
|
||||
end,
|
||||
runtimeProvider = function()
|
||||
return runtime
|
||||
end,
|
||||
})
|
||||
AssertEqual(button.frame.width, self.MinimapButton.BUTTON_SIZE, "minimap button width")
|
||||
AssertEqual(button.frame.height, self.MinimapButton.BUTTON_SIZE, "minimap button height")
|
||||
AssertEqual(button.frame.icon.width, self.MinimapButton.ICON_SIZE, "minimap icon width")
|
||||
AssertEqual(button.frame.icon.path, self.Constants.IMAGE_ROOT .. "windowIcon", "minimap icon texture")
|
||||
assert(button.frame.shown, "default minimap button remained hidden")
|
||||
AssertEqual(button.frame.points[1][2], minimap, "default minimap button parent anchor")
|
||||
AssertEqual(button.frame.points[1][4], -self.MinimapButton.ATTACHED_RADIUS, "default minimap button radius")
|
||||
|
||||
button.frame.scripts.OnMouseDown(button.frame, "LeftButton")
|
||||
button.frame.scripts.OnMouseUp(button.frame, "LeftButton")
|
||||
assert(not runtime.shown, "minimap left-click did not hide the runtime")
|
||||
button.frame.scripts.OnMouseDown(button.frame, "LeftButton")
|
||||
button.frame.scripts.OnMouseUp(button.frame, "LeftButton")
|
||||
assert(runtime.shown, "minimap left-click did not restore the runtime")
|
||||
|
||||
button.frame.scripts.OnEnter(button.frame)
|
||||
assert(button.frame.highlight.shown, "minimap hover highlight remained hidden")
|
||||
AssertEqual(tooltip.owner, button.frame, "minimap tooltip owner")
|
||||
AssertEqual(tooltip.anchor, "ANCHOR_LEFT", "minimap tooltip anchor")
|
||||
AssertEqual(tooltip.lines[1], "Left-click to show or hide the game.", "minimap tooltip click help")
|
||||
button.frame.scripts.OnLeave(button.frame)
|
||||
assert(not button.frame.highlight.shown and not tooltip.shown, "minimap tooltip or highlight remained visible")
|
||||
|
||||
button.frame.scripts.OnMouseDown(button.frame, "RightButton")
|
||||
button.frame.scripts.OnUpdate(button.frame)
|
||||
button.frame.scripts.OnMouseUp(button.frame, "RightButton")
|
||||
assert(not settings.minimapDetached, "near-minimap drag detached the button")
|
||||
assert(math.abs(settings.minimapAngle - 90) < 0.001, "attached minimap angle was not persisted")
|
||||
AssertEqual(button.frame.points[1][2], minimap, "attached minimap drag anchor")
|
||||
|
||||
cursorX = 1000
|
||||
cursorY = 1000
|
||||
button.frame.scripts.OnMouseDown(button.frame, "RightButton")
|
||||
button.frame.scripts.OnUpdate(button.frame)
|
||||
button.frame.scripts.OnMouseUp(button.frame, "RightButton")
|
||||
assert(settings.minimapDetached, "distant minimap drag did not detach the button")
|
||||
AssertEqual(settings.minimapX, 500, "detached minimap X position")
|
||||
AssertEqual(settings.minimapY, 500, "detached minimap Y position")
|
||||
AssertEqual(button.frame.points[1][2], uiParent, "detached minimap parent anchor")
|
||||
AssertEqual(button.frame.points[1][3], "BOTTOMLEFT", "detached minimap relative anchor")
|
||||
|
||||
button:SetHidden(true)
|
||||
assert(not button.frame.shown and settings.hideMinimap, "minimap visibility setting did not hide the button")
|
||||
button:SetHidden(false)
|
||||
assert(button.frame.shown and not settings.hideMinimap, "minimap visibility setting did not restore the button")
|
||||
end
|
||||
|
||||
TestSessionRestore()
|
||||
TestGameOverTransitions()
|
||||
addon:TestHUDPresentationForTest()
|
||||
addon.TestHUDPresentationForTest = nil
|
||||
addon:TestMainWindowForTest()
|
||||
addon.TestMainWindowForTest = nil
|
||||
addon:TestMinimapButtonForTest()
|
||||
addon.TestMinimapButtonForTest = nil
|
||||
|
||||
FillStablePattern(cascadeGrid)
|
||||
for x = 2, 5 do
|
||||
@@ -2466,6 +2607,7 @@ assert(addon.aboutFactory == addon.About, "addon initialization did not install
|
||||
assert(addon.legalFactory == addon.Legal, "addon initialization did not install Legal")
|
||||
assert(addon.mainWindowFactory == addon.MainWindow, "addon initialization did not install MainWindow")
|
||||
assert(addon.compartment == addon.Compartment, "addon initialization did not install Compartment")
|
||||
assert(addon.minimapButtonFactory == addon.MinimapButton, "addon initialization did not install MinimapButton")
|
||||
assert(addon.inputFactory == addon.Input, "addon initialization did not install Input")
|
||||
assert(addon.sessionFactory == addon.Session, "addon initialization did not install Session")
|
||||
assert(eventFrame.registeredEvent == nil, "initializer event was not unregistered")
|
||||
@@ -2492,18 +2634,68 @@ assert(addon.aboutFactory == addon.About, "About initialization is not idempoten
|
||||
assert(addon.legalFactory == addon.Legal, "Legal initialization is not idempotent")
|
||||
assert(addon.mainWindowFactory == addon.MainWindow, "MainWindow initialization is not idempotent")
|
||||
assert(addon.compartment == addon.Compartment, "Compartment initialization is not idempotent")
|
||||
assert(addon.minimapButtonFactory == addon.MinimapButton, "MinimapButton initialization is not idempotent")
|
||||
assert(addon.inputFactory == initializedInputFactory, "Input initialization is not idempotent")
|
||||
assert(addon.sessionFactory == initializedSessionFactory, "Session initialization is not idempotent")
|
||||
addon.runtimeMinimapForTest = {
|
||||
left = 100,
|
||||
bottom = 200,
|
||||
width = 140,
|
||||
height = 140,
|
||||
}
|
||||
function addon.runtimeMinimapForTest:GetLeft()
|
||||
return self.left
|
||||
end
|
||||
function addon.runtimeMinimapForTest:GetBottom()
|
||||
return self.bottom
|
||||
end
|
||||
function addon.runtimeMinimapForTest:GetWidth()
|
||||
return self.width
|
||||
end
|
||||
function addon.runtimeMinimapForTest:GetHeight()
|
||||
return self.height
|
||||
end
|
||||
addon.runtimeMinimapTooltipForTest = { lines = {} }
|
||||
function addon.runtimeMinimapTooltipForTest:SetOwner(owner, anchor)
|
||||
self.owner = owner
|
||||
self.anchor = anchor
|
||||
end
|
||||
function addon.runtimeMinimapTooltipForTest:SetText(value)
|
||||
self.text = value
|
||||
end
|
||||
function addon.runtimeMinimapTooltipForTest:AddLine(value)
|
||||
self.lines[#self.lines + 1] = value
|
||||
end
|
||||
function addon.runtimeMinimapTooltipForTest:Show()
|
||||
self.shown = true
|
||||
end
|
||||
function addon.runtimeMinimapTooltipForTest:Hide()
|
||||
self.shown = false
|
||||
end
|
||||
addon.runtimeForTest = addon:StartRuntime({
|
||||
uiParent = gemPoolParent,
|
||||
createFrame = CreateGemPoolFrame,
|
||||
playerName = "Nighthawk",
|
||||
random = MakeRandom(13001),
|
||||
minimap = addon.runtimeMinimapForTest,
|
||||
minimapTooltip = addon.runtimeMinimapTooltipForTest,
|
||||
cursorPosition = function()
|
||||
return 170, 270
|
||||
end,
|
||||
})
|
||||
addon.runtimeMinimapForTest = nil
|
||||
addon.runtimeMinimapTooltipForTest = nil
|
||||
assert(addon.runtimeForTest == addon.runtime, "StartRuntime did not retain the playable shell")
|
||||
assert(addon.minimapButton and addon.minimapButton.frame.shown, "StartRuntime did not create the minimap launcher")
|
||||
AssertEqual(addon.runtimeForTest.activeOverlay, "legal", "StartRuntime did not open the first-run legal notice")
|
||||
addon.runtimeForTest.legal.okayButton.scripts.OnClick()
|
||||
AssertEqual(addon.runtimeForTest.activeOverlay, "menu", "StartRuntime legal acknowledgement did not open the menu")
|
||||
addon.runtimeForTest:ShowOptions()
|
||||
addon.runtimeForTest.options.rows[6].scripts.OnClick()
|
||||
assert(not addon.minimapButton.frame.shown, "live Hide Minimap setting did not hide the launcher")
|
||||
addon.runtimeForTest.options.rows[6].scripts.OnClick()
|
||||
assert(addon.minimapButton.frame.shown, "live Hide Minimap setting did not restore the launcher")
|
||||
addon.runtimeForTest:ShowMenu()
|
||||
assert(addon:StartRuntime() == addon.runtimeForTest, "StartRuntime is not idempotent")
|
||||
|
||||
function addon:TestCompartmentForTest()
|
||||
@@ -2577,4 +2769,4 @@ end
|
||||
addon:TestCompartmentForTest()
|
||||
addon.TestCompartmentForTest = nil
|
||||
|
||||
print("Runtime verification passed: local skill chat, aligned skill/footer presentation, full local summary, Timed setup/flight boundary, addon-compartment access, playable Classic/Timed window shell, HUD, pause/restore/level/game-over sessions, input, cascade/effect animation, gem projection, UI backdrops, audio, SavedVariables, and deterministic gameplay engine.")
|
||||
print("Runtime verification passed: minimap/addon-compartment access, local skill chat, aligned skill/footer presentation, full local summary, Timed setup/flight boundary, playable Classic/Timed window shell, HUD, pause/restore/level/game-over sessions, input, cascade/effect animation, gem projection, UI backdrops, audio, SavedVariables, and deterministic gameplay engine.")
|
||||
|
||||
@@ -45,7 +45,8 @@ try {
|
||||
"UI\About.lua",
|
||||
"UI\Legal.lua",
|
||||
"UI\MainWindow.lua",
|
||||
"UI\Compartment.lua"
|
||||
"UI\Compartment.lua",
|
||||
"UI\Minimap.lua"
|
||||
)
|
||||
$actualFiles = @($toc | Where-Object { $_ -match "\.lua$" })
|
||||
if (Compare-Object -ReferenceObject $expectedFiles -DifferenceObject $actualFiles -SyncWindow 0) {
|
||||
@@ -59,7 +60,7 @@ try {
|
||||
}
|
||||
}
|
||||
|
||||
Write-Output "Verified: Retail TOC order, complete local presentation screens, Timed setup/flight boundary, addon-compartment access, and Lua 5.1-compatible playable window/session runtime."
|
||||
Write-Output "Verified: Retail TOC order, complete local presentation screens, Timed setup/flight boundary, minimap/addon-compartment access, and Lua 5.1-compatible playable window/session runtime."
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
|
||||
Reference in New Issue
Block a user