-- AltSystem UI -- Creates the main dialog window with tabbed layout. -- The "Use Skills" tab contains controls on the left and a Log panel on the right. -- Uses the modern DropdownButton API (WoW 10.2.5+ / 12.0+). AltSystem = AltSystem or {} local WINDOW_WIDTH = 700 local WINDOW_HEIGHT = 500 local CONTROLS_WIDTH = 350 local LOG_WIDTH = 350 local PADDING = 12 local ROW_HEIGHT = 26 local SECTION_GAP = 10 -- Helper: Build the skill option list from current AltSystem.Data.Skills local function BuildSkillOptions() local options = {} for _, skill in ipairs(AltSystem.Data.Skills) do local sign = skill.modifier >= 0 and "+" or "" local text if skill.level == "Base" or skill.level == "Inept" then text = skill.name .. " (" .. sign .. skill.modifier .. ")" else text = skill.name .. " (" .. skill.level .. " " .. sign .. skill.modifier .. ")" end table.insert(options, { text = text }) end return options end -- Helper: Create a flat dark dropdown with optional label (reuses shared CreateFlatDropdown) local function CreateDropdown(parent, name, labelText, options, defaultIndex, onSelect, labelFont) local container = CreateFrame("Frame", nil, parent) container:SetHeight(ROW_HEIGHT) local label if labelText and labelText ~= "" then label = container:CreateFontString(nil, "OVERLAY", labelFont or "GameFontNormal") label:SetPoint("LEFT", container, "LEFT", 0, 0) label:SetText(labelText) label:SetJustifyH("LEFT") end local selectedIndex = defaultIndex or 1 local dropdown = AltSystem.CreateFlatDropdown(name, container, 190) if label then dropdown:SetPoint("RIGHT", container, "RIGHT", 0, 0) else dropdown:SetPoint("LEFT", container, "LEFT", 0, 0) end -- Set initial label text if options[selectedIndex] then dropdown.label:SetText(options[selectedIndex].text) end dropdown:SetupMenu(function(owner, rootDescription) for i, option in ipairs(options) do rootDescription:CreateRadio( option.text, function() return i == selectedIndex end, function() selectedIndex = i dropdown.label:SetText(option.text) if onSelect then onSelect(i, option) end end ) end end) return container, dropdown, function() return selectedIndex end, function(idx) selectedIndex = idx if options[idx] then dropdown.label:SetText(options[idx].text) end end end -- Helper: Create a custom flat radio button (gold circle when selected, grey when not) local function CreateRadioButton(parent, name, text, x, y, isChecked, onClick) local size = 20 local btn = CreateFrame("CheckButton", name, parent) btn:SetSize(size, size) btn:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) -- Background (grey when unselected, yellow when selected) — circular via mask local bg = btn:CreateTexture(nil, "BACKGROUND") bg:SetAllPoints() bg:SetColorTexture(0.3, 0.3, 0.3, 1) local mask = btn:CreateMaskTexture() mask:SetAllPoints() mask:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE") bg:AddMaskTexture(mask) btn.checkTex = nil -- unused, kept for compatibility local function UpdateVisual() if btn:GetChecked() then bg:SetColorTexture(0.9, 0.75, 0.2, 1) else bg:SetColorTexture(0.3, 0.3, 0.3, 1) end end btn:SetChecked(isChecked) UpdateVisual() local label = btn:CreateFontString(nil, "OVERLAY", "GameFontHighlight") label:SetPoint("LEFT", btn, "RIGHT", 6, 0) label:SetText(text) btn:SetScript("OnClick", function(self) onClick(self) UpdateVisual() end) btn.UpdateVisual = UpdateVisual return btn end -- Helper: Create a section header (golden text) local function CreateSectionHeader(parent, text, x, y) local header = parent:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge") header:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) header:SetText(text) header:SetTextColor(0.9, 0.75, 0.2) return header end -- Helper: Create a sub-label (smaller golden text) local function CreateSubLabel(parent, text, x, y) local label = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal") label:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) label:SetText(text) label:SetTextColor(0.9, 0.75, 0.2) return label end function AltSystem:CreateMainFrame() if AltSystem.MainFrame then return end -- Main frame local f = CreateFrame("Frame", "AltSystemMainFrame", UIParent, "BasicFrameTemplateWithInset") f:SetSize(WINDOW_WIDTH, WINDOW_HEIGHT) f:SetPoint("CENTER") f:SetMovable(true) f:EnableMouse(true) f:RegisterForDrag("LeftButton") f:SetScript("OnDragStart", f.StartMoving) f:SetScript("OnDragStop", f.StopMovingOrSizing) f:SetClampedToScreen(true) f.TitleBg:SetHeight(30) f.title = f:CreateFontString(nil, "OVERLAY", "GameFontHighlight") f.title:SetPoint("TOPLEFT", f.TitleBg, "TOPLEFT", 5, -3) f.title:SetText("AltSystem") --------------------- -- TAB BUTTONS (span full window width) --------------------- local contentTop = -24 local tabHeight = 28 local contentWidth = WINDOW_WIDTH - 8 -- 4px inset on each side local tabWidth = contentWidth / 2 local tabUseSkills = CreateFrame("Button", "AltSystemTabUseSkills", f) tabUseSkills:SetSize(tabWidth, tabHeight) tabUseSkills:SetPoint("TOPLEFT", f, "TOPLEFT", 4, contentTop) tabUseSkills:SetNormalFontObject("GameFontHighlight") tabUseSkills:SetHighlightFontObject("GameFontHighlight") tabUseSkills:SetText("Use Skills") local tabUseSkillsBg = tabUseSkills:CreateTexture(nil, "BACKGROUND") tabUseSkillsBg:SetAllPoints() tabUseSkillsBg:SetColorTexture(0.15, 0.15, 0.15, 1) local tabBuildSkills = CreateFrame("Button", "AltSystemTabBuildSkills", f) tabBuildSkills:SetSize(tabWidth, tabHeight) tabBuildSkills:SetPoint("TOPLEFT", tabUseSkills, "TOPRIGHT", 0, 0) tabBuildSkills:SetNormalFontObject("GameFontHighlight") tabBuildSkills:SetHighlightFontObject("GameFontHighlight") tabBuildSkills:SetText("Build Skills") local tabBuildSkillsBg = tabBuildSkills:CreateTexture(nil, "BACKGROUND") tabBuildSkillsBg:SetAllPoints() tabBuildSkillsBg:SetColorTexture(0.3, 0.3, 0.3, 1) --------------------- -- TAB CONTENT FRAMES --------------------- local tabContentTop = contentTop - tabHeight local tabContentHeight = WINDOW_HEIGHT - 28 - tabHeight local useSkillsContent = CreateFrame("Frame", "AltSystemUseSkillsContent", f) useSkillsContent:SetPoint("TOPLEFT", f, "TOPLEFT", 4, tabContentTop) useSkillsContent:SetSize(contentWidth, tabContentHeight) local buildSkillsContent = CreateFrame("Frame", "AltSystemBuildSkillsContent", f) buildSkillsContent:SetPoint("TOPLEFT", f, "TOPLEFT", 4, tabContentTop) buildSkillsContent:SetSize(contentWidth, tabContentHeight) buildSkillsContent:Hide() -- Build Skills tab content (created in BuildSkillsUI.lua) AltSystem:CreateBuildSkillsContent(buildSkillsContent) -- Tab switching logic local function SelectTab(tabIndex) if tabIndex == 1 then useSkillsContent:Show() buildSkillsContent:Hide() tabUseSkillsBg:SetColorTexture(0.15, 0.15, 0.15, 1) tabBuildSkillsBg:SetColorTexture(0.3, 0.3, 0.3, 1) else useSkillsContent:Hide() buildSkillsContent:Show() tabUseSkillsBg:SetColorTexture(0.3, 0.3, 0.3, 1) tabBuildSkillsBg:SetColorTexture(0.15, 0.15, 0.15, 1) AltSystem:RefreshBuildSkillsList() end end tabUseSkills:SetScript("OnClick", function() SelectTab(1) end) tabBuildSkills:SetScript("OnClick", function() SelectTab(2) end) --------------------- -- USE SKILLS TAB CONTENT (left: controls, right: log) --------------------- local controlsPanel = CreateFrame("Frame", nil, useSkillsContent) controlsPanel:SetPoint("TOPLEFT", useSkillsContent, "TOPLEFT", 0, 0) controlsPanel:SetSize(CONTROLS_WIDTH, tabContentHeight) local content = controlsPanel local yPos = -PADDING -- Section: Define Your Base Roll CreateSectionHeader(content, "Define Your Base Roll", PADDING, yPos) yPos = yPos - 20 -- Roll Type label CreateSubLabel(content, "Roll Type", PADDING, yPos) yPos = yPos - 20 -- Roll Type radio buttons local attackRadio, defenseRadio local function UpdateRollTypeSelection(rollType) AltSystem.State.rollType = rollType attackRadio:SetChecked(rollType == "attack") defenseRadio:SetChecked(rollType == "defense") attackRadio.UpdateVisual() defenseRadio.UpdateVisual() -- Update roll button text if AltSystem.RollButton then local label = rollType == "attack" and "Roll Attack" or "Roll Defense" AltSystem.RollButton:SetText(label) end end attackRadio = CreateRadioButton(content, "AltSystemAttackRadio", "Attack Roll", PADDING, yPos, AltSystem.State.rollType == "attack", function() UpdateRollTypeSelection("attack") end) defenseRadio = CreateRadioButton(content, "AltSystemDefenseRadio", "Defense Roll", CONTROLS_WIDTH / 2, yPos, AltSystem.State.rollType == "defense", function() UpdateRollTypeSelection("defense") end) yPos = yPos - ROW_HEIGHT - SECTION_GAP -- Skill dropdown local skillOptions = BuildSkillOptions() local UpdateSkillWarning -- forward declaration local skillContainer, skillDropdown, getSkillIndex, setSkillIndex = CreateDropdown( content, "AltSystemSkillDropdown", "Skill", skillOptions, AltSystem.State.selectedSkillIndex, function(index) AltSystem.State.selectedSkillIndex = index AltSystem.State.selectedSkillName = AltSystem.Data.Skills[index] and AltSystem.Data.Skills[index].name or nil UpdateSkillWarning(index) end, "GameFontNormal" ) skillContainer:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos) skillContainer:SetWidth(CONTROLS_WIDTH - PADDING * 2) -- Warning icon for skill mismatch local skillWarning = CreateFrame("Frame", nil, skillContainer) skillWarning:SetSize(20, 20) skillWarning:SetPoint("LEFT", skillDropdown, "RIGHT", 4, 0) local skillWarningIcon = skillWarning:CreateTexture(nil, "ARTWORK") skillWarningIcon:SetAllPoints() skillWarningIcon:SetAtlas("services-icon-warning") skillWarning:SetScript("OnEnter", function(self) if self.tooltipText then GameTooltip:SetOwner(self, "ANCHOR_RIGHT") GameTooltip:SetText(self.tooltipText, 1, 0.82, 0) GameTooltip:Show() end end) skillWarning:SetScript("OnLeave", function() GameTooltip:Hide() end) skillWarning:Hide() UpdateSkillWarning = function(index) local skill = AltSystem.Data.Skills[index] if skill and skill.warning then skillWarning.tooltipText = skill.warning skillWarning:Show() else skillWarning:Hide() end end -- Store references for refreshing AltSystem.SkillDropdown = skillDropdown AltSystem.GetSkillIndex = getSkillIndex AltSystem.SetSkillIndex = setSkillIndex AltSystem.UpdateSkillWarning = UpdateSkillWarning yPos = yPos - ROW_HEIGHT - SECTION_GAP -- Armor label CreateSubLabel(content, "Extra Armor", PADDING, yPos) yPos = yPos - 20 -- Armor radio buttons local armorRadios = {} local function UpdateArmorSelection(index) AltSystem.State.selectedDefenseIndex = index for i, radio in ipairs(armorRadios) do radio:SetChecked(i == index) radio.UpdateVisual() end end local armorX = PADDING for i, def in ipairs(AltSystem.Data.Defenses) do local text = def.name if def.modifier > 0 then text = text .. " (+" .. def.modifier .. ")" end local radio = CreateRadioButton(content, "AltSystemArmorRadio" .. i, text, armorX, yPos, AltSystem.State.selectedDefenseIndex == i, function() UpdateArmorSelection(i) end) table.insert(armorRadios, radio) armorX = armorX + 110 end yPos = yPos - ROW_HEIGHT - SECTION_GAP -- Section: Modifiers (optional) CreateSectionHeader(content, "Modifiers (optional)", PADDING, yPos) yPos = yPos - 28 --CreateSubLabel(content, "Label", PADDING, yPos) --yPos = yPos - 22 -- Shield checkbox (flat square toggle) local shieldCheck = CreateFrame("CheckButton", "AltSystemShieldCheck", content) shieldCheck:SetSize(20, 20) shieldCheck:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos) shieldCheck:SetChecked(AltSystem.State.shieldEnabled) local shieldBg = shieldCheck:CreateTexture(nil, "BACKGROUND") shieldBg:SetAllPoints() shieldBg:SetColorTexture(0.3, 0.3, 0.3, 1) local shieldCheckMark = shieldCheck:CreateTexture(nil, "ARTWORK") shieldCheckMark:SetSize(14, 14) shieldCheckMark:SetPoint("CENTER") shieldCheckMark:SetTexture("Interface\\RAIDFRAME\\ReadyCheck-Ready") shieldCheckMark:SetVertexColor(0.9, 0.75, 0.2, 1) local function UpdateShieldVisual() if shieldCheck:GetChecked() then shieldCheckMark:Show() shieldBg:SetColorTexture(0.25, 0.25, 0.25, 1) else shieldCheckMark:Hide() shieldBg:SetColorTexture(0.3, 0.3, 0.3, 1) end end UpdateShieldVisual() local shieldLabel = shieldCheck:CreateFontString(nil, "OVERLAY", "GameFontHighlight") shieldLabel:SetPoint("LEFT", shieldCheck, "RIGHT", 6, 0) shieldLabel:SetText("Shield (+ 1)") shieldCheck:SetScript("OnClick", function(self) AltSystem.State.shieldEnabled = self:GetChecked() UpdateShieldVisual() end) -- Pet checkbox (flat square toggle) local petCheck = CreateFrame("CheckButton", "AltSystemPetSummonCheck", content) petCheck:SetSize(20, 20) petCheck:SetPoint("LEFT", shieldCheck, "RIGHT", 80, 0) petCheck:SetChecked(AltSystem.State.petSummonEnabled) local petBg = petCheck:CreateTexture(nil, "BACKGROUND") petBg:SetAllPoints() petBg:SetColorTexture(0.3, 0.3, 0.3, 1) local petCheckMark = petCheck:CreateTexture(nil, "ARTWORK") petCheckMark:SetSize(14, 14) petCheckMark:SetPoint("CENTER") petCheckMark:SetTexture("Interface\\RAIDFRAME\\ReadyCheck-Ready") petCheckMark:SetVertexColor(0.9, 0.75, 0.2, 1) local function UpdatePetVisual() if petCheck:GetChecked() then petCheckMark:Show() petBg:SetColorTexture(0.25, 0.25, 0.25, 1) else petCheckMark:Hide() petBg:SetColorTexture(0.3, 0.3, 0.3, 1) end end UpdatePetVisual() local petLabel = petCheck:CreateFontString(nil, "OVERLAY", "GameFontHighlight") petLabel:SetPoint("LEFT", petCheck, "RIGHT", 6, 0) petLabel:SetText("Pet (+d5)") petCheck:SetScript("OnClick", function(self) AltSystem.State.petSummonEnabled = self:GetChecked() UpdatePetVisual() end) yPos = yPos - ROW_HEIGHT - SECTION_GAP -- Item label CreateSubLabel(content, "Item", PADDING, yPos) yPos = yPos - 20 -- Item radio buttons local itemRadios = {} local function UpdateItemSelection(index) AltSystem.State.selectedItemIndex = index for i, radio in ipairs(itemRadios) do radio:SetChecked(i == index) radio.UpdateVisual() end end local itemX = PADDING for i, item in ipairs(AltSystem.Data.Items) do local text = item.name if item.modifier > 0 then text = text .. " (+" .. item.modifier .. ")" end local radio = CreateRadioButton(content, "AltSystemItemRadio" .. i, text, itemX, yPos, AltSystem.State.selectedItemIndex == i, function() UpdateItemSelection(i) end) table.insert(itemRadios, radio) itemX = itemX + 110 end yPos = yPos - ROW_HEIGHT - SECTION_GAP -- Section: Roll Dice CreateSectionHeader(content, "Roll Dice", PADDING, yPos) yPos = yPos - 22 -- Announce Roll dropdown (Self Roll + channels) local announceOptions = { { text = "Self Roll" } } for _, ch in ipairs(AltSystem.AnnounceChannels) do table.insert(announceOptions, { text = ch.name }) end local announceContainer, announceDropdown = CreateDropdown( content, "AltSystemAnnounceDropdown", "Announce Roll", announceOptions, AltSystem.State.announceOptionIndex, function(index) AltSystem.State.announceOptionIndex = index if index > 1 then AltSystem.State.announceEnabled = true AltSystem.State.announceChannelIndex = index - 1 else AltSystem.State.announceEnabled = false AltSystem.State.announceChannelIndex = 1 end end, "GameFontNormal") announceContainer:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos) announceContainer:SetWidth(CONTROLS_WIDTH - PADDING * 2) yPos = yPos - ROW_HEIGHT - SECTION_GAP -- Roll button local rollBtn = CreateFrame("Button", "AltSystemRollBtn", content, "UIPanelButtonTemplate") rollBtn:SetSize(CONTROLS_WIDTH - PADDING * 2, 32) rollBtn:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos) local rollLabel = AltSystem.State.rollType == "attack" and "Roll Attack" or "Roll Defense" rollBtn:SetText(rollLabel) rollBtn:SetScript("OnClick", function() AltSystem:PerformRoll(AltSystem.State.rollType) end) AltSystem.RollButton = rollBtn --------------------- -- LOG PANEL (right side of Use Skills tab) --------------------- local logPanel = CreateFrame("Frame", nil, useSkillsContent) logPanel:SetPoint("TOPLEFT", useSkillsContent, "TOPLEFT", CONTROLS_WIDTH, 0) logPanel:SetSize(LOG_WIDTH, tabContentHeight) -- Log header local logHeader = logPanel:CreateFontString(nil, "OVERLAY", "GameFontNormal") logHeader:SetPoint("TOPLEFT", logPanel, "TOPLEFT", PADDING, -4) logHeader:SetText("Log") logHeader:SetTextColor(0.9, 0.75, 0.2) -- Log scroll area background local logBg = CreateFrame("Frame", nil, logPanel, "InsetFrameTemplate") logBg:SetPoint("TOPLEFT", logPanel, "TOPLEFT", 4, -22) logBg:SetPoint("BOTTOMRIGHT", logPanel, "BOTTOMRIGHT", -4, 4) -- Scroll frame for log entries local scrollFrame = CreateFrame("ScrollFrame", "AltSystemLogScrollFrame", logBg, "UIPanelScrollFrameTemplate") scrollFrame:SetPoint("TOPLEFT", logBg, "TOPLEFT", 6, -6) scrollFrame:SetPoint("BOTTOMRIGHT", logBg, "BOTTOMRIGHT", -28, 6) local scrollChild = CreateFrame("Frame", "AltSystemLogScrollChild", scrollFrame) scrollChild:SetWidth(scrollFrame:GetWidth() or (LOG_WIDTH - 50)) scrollChild:SetHeight(1) -- Will be updated dynamically scrollFrame:SetScrollChild(scrollChild) AltSystem.LogScrollFrame = scrollFrame AltSystem.LogScrollChild = scrollChild -- Refresh skills from TRP3 profile each time the window is shown f:SetScript("OnShow", function() AltSystem:RefreshSkillDropdown() AltSystem:RefreshLogPanel() end) f:Hide() AltSystem.MainFrame = f end -- Refresh the log panel UI from AltSystem.State.rollLog function AltSystem:RefreshLogPanel() local scrollChild = AltSystem.LogScrollChild if not scrollChild then return end -- Remove existing log entry fontstrings if scrollChild.entries then for _, entry in ipairs(scrollChild.entries) do entry:Hide() entry:SetText("") end end scrollChild.entries = scrollChild.entries or {} local rollLog = AltSystem.State.rollLog local entryHeight = 16 local spacing = 4 local yPos = 0 local childWidth = AltSystem.LogScrollFrame:GetWidth() or 250 -- Entries are newest-first for i = #rollLog, 1, -1 do local idx = #rollLog - i + 1 local logEntry = rollLog[i] local fontStr = scrollChild.entries[idx] if not fontStr then fontStr = scrollChild:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall") scrollChild.entries[idx] = fontStr end fontStr:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 2, -yPos) fontStr:SetWidth(childWidth - 4) fontStr:SetJustifyH("LEFT") fontStr:SetText(logEntry.text) fontStr:Show() yPos = yPos + entryHeight + spacing end scrollChild:SetHeight(math.max(yPos, 1)) end -- Refresh the skill dropdown with current TRP3 profile data function AltSystem:RefreshSkillDropdown() AltSystem.Data:RefreshSkills() -- Try to restore the previously selected skill by name; fall back to 1 (Base roll) local newIndex = 1 local savedName = AltSystem.State.selectedSkillName if savedName then for i, skill in ipairs(AltSystem.Data.Skills) do if skill.name == savedName then newIndex = i break end end end AltSystem.State.selectedSkillIndex = newIndex AltSystem.State.selectedSkillName = AltSystem.Data.Skills[newIndex] and AltSystem.Data.Skills[newIndex].name or nil if AltSystem.SetSkillIndex then AltSystem.SetSkillIndex(newIndex) end if AltSystem.UpdateSkillWarning then AltSystem.UpdateSkillWarning(newIndex) end -- Rebuild the dropdown menu with the new skill list if AltSystem.SkillDropdown then local skillOptions = BuildSkillOptions() -- Update the displayed label text if skillOptions[newIndex] then AltSystem.SkillDropdown.label:SetText(skillOptions[newIndex].text) end AltSystem.SkillDropdown:SetupMenu(function(owner, rootDescription) for i, option in ipairs(skillOptions) do rootDescription:CreateRadio( option.text, function() return i == AltSystem.State.selectedSkillIndex end, function() AltSystem.State.selectedSkillIndex = i AltSystem.State.selectedSkillName = AltSystem.Data.Skills[i] and AltSystem.Data.Skills[i].name or nil AltSystem.SkillDropdown.label:SetText(option.text) if AltSystem.SetSkillIndex then AltSystem.SetSkillIndex(i) end if AltSystem.UpdateSkillWarning then AltSystem.UpdateSkillWarning(i) end end ) end end) end end -- Frame is created by AltSystem:Init() in Core.lua, after saved variables are loaded