diff --git a/Core.lua b/Core.lua index a3d1ab9..865bf63 100644 --- a/Core.lua +++ b/Core.lua @@ -6,11 +6,14 @@ AltSystem.State = { selectedSkillIndex = 1, selectedSkillName = nil, -- skill name used to restore selection across sessions selectedItemIndex = 1, -- 1 = No item - selectedDefenseIndex = 1, -- 1 = Base armor + selectedDefenseIndex = 1, -- 1 = No Armor shieldEnabled = false, petSummonEnabled = false, announceEnabled = false, announceChannelIndex = 1, -- 1 = Emote, 2 = Party, 3 = Raid, 4 = Guild + rollType = "attack", -- "attack" or "defense" + announceOptionIndex = 1, -- 1 = Self Roll, 2+ = channel from AnnounceChannels + rollLog = {}, -- array of { text = "...", timestamp = time() }, max 100, not persisted } -- Channel definitions for announcing rolls @@ -35,12 +38,6 @@ end) function AltSystem:Init() -- Load saved settings AltSystemDB = AltSystemDB or {} - if AltSystemDB.announceEnabled ~= nil then - AltSystem.State.announceEnabled = AltSystemDB.announceEnabled - end - if AltSystemDB.announceChannelIndex then - AltSystem.State.announceChannelIndex = AltSystemDB.announceChannelIndex - end if AltSystemDB.petSummonEnabled ~= nil then AltSystem.State.petSummonEnabled = AltSystemDB.petSummonEnabled end @@ -56,6 +53,28 @@ function AltSystem:Init() if AltSystemDB.selectedSkillName then AltSystem.State.selectedSkillName = AltSystemDB.selectedSkillName end + if AltSystemDB.rollType then + AltSystem.State.rollType = AltSystemDB.rollType + end + -- Migrate from old announce settings or load new announceOptionIndex + if AltSystemDB.announceOptionIndex then + AltSystem.State.announceOptionIndex = AltSystemDB.announceOptionIndex + elseif AltSystemDB.announceEnabled ~= nil then + -- Backwards compat: derive from old announceEnabled + announceChannelIndex + if AltSystemDB.announceEnabled and AltSystemDB.announceChannelIndex then + AltSystem.State.announceOptionIndex = AltSystemDB.announceChannelIndex + 1 + else + AltSystem.State.announceOptionIndex = 1 + end + end + -- Derive announceEnabled and announceChannelIndex from announceOptionIndex + if AltSystem.State.announceOptionIndex > 1 then + AltSystem.State.announceEnabled = true + AltSystem.State.announceChannelIndex = AltSystem.State.announceOptionIndex - 1 + else + AltSystem.State.announceEnabled = false + AltSystem.State.announceChannelIndex = 1 + end -- Register slash command /altsystem SLASH_ALTSYSTEM1 = "/altsystem" @@ -68,13 +87,13 @@ function AltSystem:Init() saveFrame:RegisterEvent("PLAYER_LOGOUT") saveFrame:SetScript("OnEvent", function() AltSystemDB = AltSystemDB or {} - AltSystemDB.announceEnabled = AltSystem.State.announceEnabled - AltSystemDB.announceChannelIndex = AltSystem.State.announceChannelIndex + AltSystemDB.announceOptionIndex = AltSystem.State.announceOptionIndex AltSystemDB.petSummonEnabled = AltSystem.State.petSummonEnabled AltSystemDB.shieldEnabled = AltSystem.State.shieldEnabled AltSystemDB.selectedItemIndex = AltSystem.State.selectedItemIndex AltSystemDB.selectedDefenseIndex = AltSystem.State.selectedDefenseIndex AltSystemDB.selectedSkillName = AltSystem.State.selectedSkillName + AltSystemDB.rollType = AltSystem.State.rollType end) -- Build the main frame now that saved variables are loaded diff --git a/Data.lua b/Data.lua index e46d887..d1e5f56 100644 --- a/Data.lua +++ b/Data.lua @@ -147,11 +147,11 @@ AltSystem.Data.Items = { { name = "Epic item", modifier = 5 }, } --- Defense options: name and modifier +-- Defense / Armor options: name and modifier AltSystem.Data.Defenses = { - { name = "Base armor", modifier = 0 }, - { name = "Extra small armor", modifier = 1 }, - { name = "Extra large armor", modifier = 2 }, + { name = "None", modifier = 0 }, + { name = "Partial", modifier = 1 }, + { name = "Full", modifier = 2 }, } -- Shield modifier diff --git a/Roll.lua b/Roll.lua index a37ca8b..29d9190 100644 --- a/Roll.lua +++ b/Roll.lua @@ -27,10 +27,42 @@ local function BuildModifierString(modifiers) return table.concat(parts, " ") end +-- Build the log message string (always uses the non-emote format with character name) +local function BuildLogMessage(rollValue, modifiers, total) + local name = GetCharacterName() + local modStr = BuildModifierString(modifiers) + if modStr ~= "" then + return name .. " rolled " .. rollValue .. " " .. modStr .. " = " .. total + else + return name .. " rolled " .. rollValue .. " = " .. total + end +end + +-- Build the critical roll log message +local function BuildCriticalLogMessage(isCriticalSuccess) + local name = GetCharacterName() + if isCriticalSuccess then + return name .. " rolled a Critical Success!" + else + return name .. " rolled a Critical Failure!" + end +end + +-- Add an entry to the roll log and refresh the UI +local function AddLogEntry(text) + local rollLog = AltSystem.State.rollLog + table.insert(rollLog, { text = text, timestamp = time() }) + -- Cap at 100 entries + if #rollLog > 100 then + table.remove(rollLog, 1) + end + -- Refresh the log panel if it exists + if AltSystem.RefreshLogPanel then + AltSystem:RefreshLogPanel() + end +end + -- Send a message to the given channel. --- Note: EMOTE channel uses SendChatMessage like all others. The WoW API --- SendChatMessage(msg, "EMOTE") sends a proper /e emote from any context, --- unlike RunMacroText which is a protected function requiring a hardware event. local function SendToChannel(msg, channel) SendChatMessage(msg, channel) end @@ -64,6 +96,24 @@ local function AnnounceRoll(rollValue, modifiers, total) SendToChannel(msg, channelDef.channel) end +-- Announce a critical roll result +local function AnnounceCritical(isCriticalSuccess) + if not AltSystem.State.announceEnabled then return end + + local channelDef = AltSystem.AnnounceChannels[AltSystem.State.announceChannelIndex] + if not channelDef then return end + + local critText = isCriticalSuccess and "rolled a Critical Success!" or "rolled a Critical Failure!" + local msg + if channelDef.channel == "EMOTE" then + msg = critText + else + msg = GetCharacterName() .. " " .. critText + end + + SendToChannel(msg, channelDef.channel) +end + -- Perform a roll: triggers the WoW native /roll 20 command function AltSystem:PerformRoll(rollType) pendingPetRoll = nil @@ -116,32 +166,12 @@ end) function AltSystem:CalculateAndDisplayResult(rollType, rollValue, petRollValue) -- Critical rolls bypass normal calculation if rollValue == 1 then - if AltSystem.ResultText then - AltSystem.ResultText:SetText("|cffff0000Critical Failure|r") - end - if AltSystem.State.announceEnabled then - local channelDef = AltSystem.AnnounceChannels[AltSystem.State.announceChannelIndex] - if channelDef then - local critMsg = channelDef.channel == "EMOTE" - and "rolled a Critical Failure!" - or (GetCharacterName() .. " rolled a Critical Failure!") - SendToChannel(critMsg, channelDef.channel) - end - end + AddLogEntry(BuildCriticalLogMessage(false)) + AnnounceCritical(false) return elseif rollValue == 20 then - if AltSystem.ResultText then - AltSystem.ResultText:SetText("|cff00ff00Critical Success|r") - end - if AltSystem.State.announceEnabled then - local channelDef = AltSystem.AnnounceChannels[AltSystem.State.announceChannelIndex] - if channelDef then - local critMsg = channelDef.channel == "EMOTE" - and "rolled a Critical Success!" - or (GetCharacterName() .. " rolled a Critical Success!") - SendToChannel(critMsg, channelDef.channel) - end - end + AddLogEntry(BuildCriticalLogMessage(true)) + AnnounceCritical(true) return end @@ -153,7 +183,6 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue, petRollValue) local itemMod = item and item.modifier or 0 local total = rollValue - local breakdown = "Roll: " .. rollValue local modifiers = {} @@ -166,18 +195,14 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue, petRollValue) total = rollValue + skillMod + itemMod + petMod if not isBaseRoll then - breakdown = breakdown .. "\nSkill: " .. FormatModifier(skillMod) table.insert(modifiers, { name = skill and skill.name or "Skill", value = skillMod }) end if itemMod ~= 0 then - breakdown = breakdown .. " | Item: " .. FormatModifier(itemMod) table.insert(modifiers, { name = item and item.name or "Item", value = itemMod }) end if petMod ~= 0 then - breakdown = breakdown .. " | Pet: +" .. petMod table.insert(modifiers, { name = "Pet", value = petMod }) end - breakdown = breakdown .. "\n|cffffd100Attack Total: " .. total .. "|r" elseif rollType == "defense" then -- Defense Roll = roll + skill modifier + item modifier + defense modifier + shield modifier @@ -188,30 +213,26 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue, petRollValue) total = rollValue + skillMod + itemMod + defenseMod + shieldMod + petMod if not isBaseRoll then - breakdown = breakdown .. "\nSkill: " .. FormatModifier(skillMod) table.insert(modifiers, { name = skill and skill.name or "Skill", value = skillMod }) end if itemMod ~= 0 then - breakdown = breakdown .. " | Item: " .. FormatModifier(itemMod) table.insert(modifiers, { name = item and item.name or "Item", value = itemMod }) end - breakdown = breakdown .. "\nDefense: " .. FormatModifier(defenseMod) - table.insert(modifiers, { name = defense and defense.name or "Defense", value = defenseMod }) + if defenseMod ~= 0 then + table.insert(modifiers, { name = defense and defense.name or "Armor", value = defenseMod }) + end if shieldMod ~= 0 then - breakdown = breakdown .. " | Shield: " .. FormatModifier(shieldMod) table.insert(modifiers, { name = "Shield", value = shieldMod }) end if petMod ~= 0 then - breakdown = breakdown .. " | Pet: +" .. petMod table.insert(modifiers, { name = "Pet", value = petMod }) end - breakdown = breakdown .. "\n|cff00ccffDefense Total: " .. total .. "|r" end - if AltSystem.ResultText then - AltSystem.ResultText:SetText(breakdown) - end + -- Add to log (always, regardless of announce setting) + AddLogEntry(BuildLogMessage(rollValue, modifiers, total)) + -- Announce to chat (if enabled) AnnounceRoll(rollValue, modifiers, total) end diff --git a/UI.lua b/UI.lua index 5162da2..fdbdace 100644 --- a/UI.lua +++ b/UI.lua @@ -1,14 +1,17 @@ -- AltSystem UI --- Creates the main dialog window with all interface elements. +-- 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 = 300 -local WINDOW_HEIGHT = 478 -local PADDING = 12 -local ROW_HEIGHT = 30 -local LABEL_WIDTH = 80 +local WINDOW_WIDTH = 726 +local WINDOW_HEIGHT = 500 +local CONTROLS_WIDTH = 363 +local LOG_WIDTH = 363 +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() @@ -27,38 +30,98 @@ local function BuildSkillOptions() end -- Helper: Create a modern dropdown (WowStyle1DropdownTemplate) -local function CreateDropdown(parent, name, yOffset, labelText, options, defaultIndex, onSelect) - local label = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal") - label:SetPoint("TOPLEFT", parent, "TOPLEFT", PADDING, yOffset) - label:SetText(labelText) - label:SetWidth(LABEL_WIDTH) - label:SetJustifyH("LEFT") +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 = CreateFrame("DropdownButton", name, parent, "WowStyle1DropdownTemplate") - dropdown:SetPoint("LEFT", label, "RIGHT", 4, 0) - dropdown:SetWidth(160) + local dropdown = CreateFrame("DropdownButton", name, container, "WowStyle1DropdownTemplate") + if label then + dropdown:SetPoint("LEFT", label, "RIGHT", 8, 0) + else + dropdown:SetPoint("LEFT", container, "LEFT", 0, 0) + end + dropdown:SetWidth(180) dropdown:SetupMenu(function(dropdown, rootDescription) for i, option in ipairs(options) do rootDescription:CreateRadio( - option.text, - function(data) return data == selectedIndex end, - function(data) - selectedIndex = data - if onSelect then onSelect(data, options[data]) end - end, - i + option.text, + function(data) + return data == selectedIndex + end, + function(data) + selectedIndex = data + if onSelect then + onSelect(data, options[data]) + end + end, + i ) end end) - return dropdown, function() return selectedIndex end, function(idx) selectedIndex = idx end + return container, dropdown, function() + return selectedIndex + end, function(idx) + selectedIndex = idx + end +end + +-- Helper: Create a radio button (CheckButton with radio texture) +local function CreateRadioButton(parent, name, text, x, y, isChecked, onClick) + local radio = CreateFrame("CheckButton", name, parent, "UIRadioButtonTemplate") + radio:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) + radio:SetChecked(isChecked) + + local radioText = radio:GetFontString() + if radioText then + radioText:SetText(text) + radioText:SetFontObject("GameFontHighlight") + else + local t = radio:CreateFontString(nil, "OVERLAY", "GameFontHighlight") + t:SetPoint("LEFT", radio, "RIGHT", 4, 0) + t:SetText(text) + end + + radio:SetScript("OnClick", function(self) + onClick(self) + end) + + return radio +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", "GameFontNormalSmall") + 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 + if AltSystem.MainFrame then + return + end -- Main frame local f = CreateFrame("Frame", "AltSystemMainFrame", UIParent, "BasicFrameTemplateWithInset") @@ -76,26 +139,142 @@ function AltSystem:CreateMainFrame() f.title:SetPoint("TOPLEFT", f.TitleBg, "TOPLEFT", 5, -3) f.title:SetText("AltSystem") - -- Track current Y offset for layout - local yPos = -40 + --------------------- + -- 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() + + local buildPlaceholder = buildSkillsContent:CreateFontString(nil, "OVERLAY", "GameFontHighlight") + buildPlaceholder:SetPoint("CENTER") + buildPlaceholder:SetText("Coming soon") + + -- 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) + 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") + -- 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 skillDropdown, getSkillIndex, setSkillIndex = CreateDropdown( - f, "AltSystemSkillDropdown", yPos, "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) + 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, + "GameFontNormalSmall" + ) + skillContainer:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos) + skillContainer:SetWidth(CONTROLS_WIDTH - PADDING * 2) - -- Warning icon for skill mismatch (shown next to dropdown when value doesn't match keyword) - local skillWarning = CreateFrame("Frame", nil, f) + -- Warning icon for skill mismatch + local skillWarning = CreateFrame("Frame", nil, skillContainer) skillWarning:SetSize(20, 20) skillWarning:SetPoint("LEFT", skillDropdown, "RIGHT", 4, 0) @@ -115,7 +294,6 @@ function AltSystem:CreateMainFrame() end) skillWarning:Hide() - -- Update the warning icon visibility based on the currently selected skill UpdateSkillWarning = function(index) local skill = AltSystem.Data.Skills[index] if skill and skill.warning then @@ -131,186 +309,212 @@ function AltSystem:CreateMainFrame() AltSystem.GetSkillIndex = getSkillIndex AltSystem.SetSkillIndex = setSkillIndex AltSystem.UpdateSkillWarning = UpdateSkillWarning - yPos = yPos - ROW_HEIGHT - 8 - ------------------------- - -- Item dropdown - ------------------------- - local itemOptions = {} - for _, item in ipairs(AltSystem.Data.Items) do - local sign = item.modifier >= 0 and "+" or "" - local modText = item.modifier ~= 0 and (" (" .. sign .. item.modifier .. ")") or "" - table.insert(itemOptions, { - text = item.name .. modText, - }) + 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) + end end - CreateDropdown(f, "AltSystemItemDropdown", yPos, "Item:", itemOptions, AltSystem.State.selectedItemIndex, - function(index) - AltSystem.State.selectedItemIndex = index - end) - yPos = yPos - ROW_HEIGHT - 8 - - ------------------------- - -- Defense dropdown - ------------------------- - local defenseOptions = {} - for _, def in ipairs(AltSystem.Data.Defenses) do - local sign = def.modifier >= 0 and "+" or "" - local modText = " (" .. sign .. def.modifier .. ")" - table.insert(defenseOptions, { - text = def.name .. modText, - }) + 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 - CreateDropdown(f, "AltSystemDefenseDropdown", yPos, "Defense:", defenseOptions, AltSystem.State.selectedDefenseIndex, - function(index) - AltSystem.State.selectedDefenseIndex = index - end) - yPos = yPos - ROW_HEIGHT - 8 + yPos = yPos - ROW_HEIGHT - SECTION_GAP + + -- Section: Modifiers (optional) + CreateSectionHeader(content, "Modifiers (optional)", PADDING, yPos) + yPos = yPos - 18 + --CreateSubLabel(content, "Label", PADDING, yPos) + --yPos = yPos - 22 - ------------------------- -- Shield checkbox - ------------------------- - local shieldLabel = f:CreateFontString(nil, "OVERLAY", "GameFontNormal") - shieldLabel:SetPoint("TOPLEFT", f, "TOPLEFT", PADDING, yPos) - shieldLabel:SetText("Shield:") - shieldLabel:SetWidth(LABEL_WIDTH) - shieldLabel:SetJustifyH("LEFT") - - local shieldCheck = CreateFrame("CheckButton", "AltSystemShieldCheck", f, "UICheckButtonTemplate") - shieldCheck:SetPoint("LEFT", shieldLabel, "RIGHT", -6, 0) + local shieldCheck = CreateFrame("CheckButton", "AltSystemShieldCheck", content, "UICheckButtonTemplate") + shieldCheck:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos) shieldCheck:SetChecked(AltSystem.State.shieldEnabled) - local shieldText = shieldCheck:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") - shieldText:SetPoint("LEFT", shieldCheck, "RIGHT", 2, 0) - shieldText:SetText("+1 modifier") + local shieldText = shieldCheck:GetFontString() + if shieldText then + shieldText:SetText("Shield (+ 1)") + else + shieldText = shieldCheck:CreateFontString(nil, "OVERLAY", "GameFontHighlight") + shieldText:SetPoint("LEFT", shieldCheck, "RIGHT", 2, 0) + shieldText:SetText("Shield (+ 1)") + end shieldCheck:SetScript("OnClick", function(self) AltSystem.State.shieldEnabled = self:GetChecked() end) - yPos = yPos - ROW_HEIGHT - 8 - - ------------------------- - -- Pet/Summon checkbox - ------------------------- - local petLabel = f:CreateFontString(nil, "OVERLAY", "GameFontNormal") - petLabel:SetPoint("TOPLEFT", f, "TOPLEFT", PADDING, yPos) - petLabel:SetText("Pet:") - petLabel:SetWidth(LABEL_WIDTH) - petLabel:SetJustifyH("LEFT") - - local petCheck = CreateFrame("CheckButton", "AltSystemPetSummonCheck", f, "UICheckButtonTemplate") - petCheck:SetPoint("LEFT", petLabel, "RIGHT", -6, 0) + -- Pet checkbox + local petCheck = CreateFrame("CheckButton", "AltSystemPetSummonCheck", content, "UICheckButtonTemplate") + petCheck:SetPoint("LEFT", shieldCheck, "RIGHT", 80, 0) petCheck:SetChecked(AltSystem.State.petSummonEnabled) - local petText = petCheck:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") - petText:SetPoint("LEFT", petCheck, "RIGHT", 2, 0) - petText:SetText("+d5 modifier") + local petText = petCheck:GetFontString() + if petText then + petText:SetText("Pet (+d5)") + else + petText = petCheck:CreateFontString(nil, "OVERLAY", "GameFontHighlight") + petText:SetPoint("LEFT", petCheck, "RIGHT", 2, 0) + petText:SetText("Pet (+d5)") + end petCheck:SetScript("OnClick", function(self) AltSystem.State.petSummonEnabled = self:GetChecked() end) - yPos = yPos - ROW_HEIGHT - 8 + yPos = yPos - ROW_HEIGHT - SECTION_GAP - ------------------------- - -- Announce checkbox + channel dropdown - ------------------------- - local announceLabel = f:CreateFontString(nil, "OVERLAY", "GameFontNormal") - announceLabel:SetPoint("TOPLEFT", f, "TOPLEFT", PADDING, yPos) - announceLabel:SetText("Announce:") - announceLabel:SetWidth(LABEL_WIDTH) - announceLabel:SetJustifyH("LEFT") + -- Section: Roll Dice + CreateSectionHeader(content, "Roll Dice", PADDING, yPos) + yPos = yPos - 22 - local announceCheck = CreateFrame("CheckButton", "AltSystemAnnounceCheck", f, "UICheckButtonTemplate") - announceCheck:SetPoint("LEFT", announceLabel, "RIGHT", -6, 0) - announceCheck:SetChecked(AltSystem.State.announceEnabled) - - -- Channel dropdown (shown only when announce is enabled) - local channelOptions = {} + -- Announce Roll dropdown (Self Roll + channels) + local announceOptions = { { text = "Self Roll" } } for _, ch in ipairs(AltSystem.AnnounceChannels) do - table.insert(channelOptions, { text = ch.name }) + table.insert(announceOptions, { text = ch.name }) end - local channelDropdown, getChannelIndex, setChannelIndex = CreateDropdown( - f, "AltSystemChannelDropdown", yPos, "", channelOptions, - AltSystem.State.announceChannelIndex, - function(index) - AltSystem.State.announceChannelIndex = index - end) - channelDropdown:SetPoint("LEFT", announceCheck, "RIGHT", 2, 0) - channelDropdown:SetWidth(130) + 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, + "GameFontNormalSmall") + announceContainer:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos) + announceContainer:SetWidth(CONTROLS_WIDTH - PADDING * 2) - -- Show/hide channel dropdown based on checkbox state - local function UpdateChannelDropdownVisibility() - if AltSystem.State.announceEnabled then - channelDropdown:Show() - else - channelDropdown:Hide() - end - end + yPos = yPos - ROW_HEIGHT - SECTION_GAP - announceCheck:SetScript("OnClick", function(self) - AltSystem.State.announceEnabled = self:GetChecked() - UpdateChannelDropdownVisibility() + -- 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) - UpdateChannelDropdownVisibility() + AltSystem.RollButton = rollBtn - yPos = yPos - ROW_HEIGHT - 12 + --------------------- + -- 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) - ------------------------- - -- Roll buttons - ------------------------- - local btnWidth = (WINDOW_WIDTH - PADDING * 3) / 2 + -- 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) - local attackBtn = CreateFrame("Button", "AltSystemAttackRollBtn", f, "UIPanelButtonTemplate") - attackBtn:SetSize(btnWidth, 28) - attackBtn:SetPoint("TOPLEFT", f, "TOPLEFT", PADDING, yPos) - attackBtn:SetText("Attack/Skill Roll") + -- 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) - local defenseBtn = CreateFrame("Button", "AltSystemDefenseRollBtn", f, "UIPanelButtonTemplate") - defenseBtn:SetSize(btnWidth, 28) - defenseBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -PADDING, yPos) - defenseBtn:SetText("Defense Roll") + -- 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) - yPos = yPos - 40 + local scrollChild = CreateFrame("Frame", "AltSystemLogScrollChild", scrollFrame) + scrollChild:SetWidth(scrollFrame:GetWidth() or (LOG_WIDTH - 50)) + scrollChild:SetHeight(1) -- Will be updated dynamically + scrollFrame:SetScrollChild(scrollChild) - ------------------------- - -- Roll result area - ------------------------- - local resultBg = CreateFrame("Frame", nil, f, "InsetFrameTemplate") - resultBg:SetPoint("TOPLEFT", f, "TOPLEFT", PADDING, yPos) - resultBg:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -PADDING, PADDING) - - local resultText = resultBg:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge") - resultText:SetPoint("CENTER") - resultText:SetText("Roll result will appear here") - resultText:SetJustifyH("CENTER") - resultText:SetWidth(resultBg:GetWidth() - 10) - - AltSystem.ResultText = resultText - - -- Button click handlers - attackBtn:SetScript("OnClick", function() - AltSystem:PerformRoll("attack") - end) - - defenseBtn:SetScript("OnClick", function() - AltSystem:PerformRoll("defense") - end) + 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() @@ -341,19 +545,21 @@ function AltSystem:RefreshSkillDropdown() AltSystem.SkillDropdown:SetupMenu(function(dropdown, rootDescription) for i, option in ipairs(skillOptions) do rootDescription:CreateRadio( - option.text, - function(data) return data == AltSystem.State.selectedSkillIndex end, - function(data) - AltSystem.State.selectedSkillIndex = data - AltSystem.State.selectedSkillName = AltSystem.Data.Skills[data] and AltSystem.Data.Skills[data].name or nil - if AltSystem.SetSkillIndex then - AltSystem.SetSkillIndex(data) - end - if AltSystem.UpdateSkillWarning then - AltSystem.UpdateSkillWarning(data) - end - end, - i + option.text, + function(data) + return data == AltSystem.State.selectedSkillIndex + end, + function(data) + AltSystem.State.selectedSkillIndex = data + AltSystem.State.selectedSkillName = AltSystem.Data.Skills[data] and AltSystem.Data.Skills[data].name or nil + if AltSystem.SetSkillIndex then + AltSystem.SetSkillIndex(data) + end + if AltSystem.UpdateSkillWarning then + AltSystem.UpdateSkillWarning(data) + end + end, + i ) end end)