diff --git a/AltSystem.toc b/AltSystem.toc index a085d34..e541ac4 100644 --- a/AltSystem.toc +++ b/AltSystem.toc @@ -2,7 +2,7 @@ ## Title: AltSystem ## Notes: Enhances RP gameplay with a custom rolling system ## Author: Rukira -## Version: 2.0 +## Version: 1.1 ## Dependencies: totalRP3, totalRP3_Extended ## SavedVariables: AltSystemDB diff --git a/Core.lua b/Core.lua index 865bf63..a3d1ab9 100644 --- a/Core.lua +++ b/Core.lua @@ -6,14 +6,11 @@ AltSystem.State = { selectedSkillIndex = 1, selectedSkillName = nil, -- skill name used to restore selection across sessions selectedItemIndex = 1, -- 1 = No item - selectedDefenseIndex = 1, -- 1 = No Armor + selectedDefenseIndex = 1, -- 1 = Base 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 @@ -38,6 +35,12 @@ 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 @@ -53,28 +56,6 @@ 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" @@ -87,13 +68,13 @@ function AltSystem:Init() saveFrame:RegisterEvent("PLAYER_LOGOUT") saveFrame:SetScript("OnEvent", function() AltSystemDB = AltSystemDB or {} - AltSystemDB.announceOptionIndex = AltSystem.State.announceOptionIndex + AltSystemDB.announceEnabled = AltSystem.State.announceEnabled + AltSystemDB.announceChannelIndex = AltSystem.State.announceChannelIndex 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 d1e5f56..e46d887 100644 --- a/Data.lua +++ b/Data.lua @@ -147,11 +147,11 @@ AltSystem.Data.Items = { { name = "Epic item", modifier = 5 }, } --- Defense / Armor options: name and modifier +-- Defense options: name and modifier AltSystem.Data.Defenses = { - { name = "None", modifier = 0 }, - { name = "Partial", modifier = 1 }, - { name = "Full", modifier = 2 }, + { name = "Base armor", modifier = 0 }, + { name = "Extra small armor", modifier = 1 }, + { name = "Extra large armor", modifier = 2 }, } -- Shield modifier diff --git a/Roll.lua b/Roll.lua index 29d9190..a37ca8b 100644 --- a/Roll.lua +++ b/Roll.lua @@ -27,42 +27,10 @@ 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 @@ -96,24 +64,6 @@ 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 @@ -166,12 +116,32 @@ end) function AltSystem:CalculateAndDisplayResult(rollType, rollValue, petRollValue) -- Critical rolls bypass normal calculation if rollValue == 1 then - AddLogEntry(BuildCriticalLogMessage(false)) - AnnounceCritical(false) + 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 return elseif rollValue == 20 then - AddLogEntry(BuildCriticalLogMessage(true)) - AnnounceCritical(true) + 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 return end @@ -183,6 +153,7 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue, petRollValue) local itemMod = item and item.modifier or 0 local total = rollValue + local breakdown = "Roll: " .. rollValue local modifiers = {} @@ -195,14 +166,18 @@ 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 @@ -213,26 +188,30 @@ 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 - if defenseMod ~= 0 then - table.insert(modifiers, { name = defense and defense.name or "Armor", value = defenseMod }) - end + breakdown = breakdown .. "\nDefense: " .. FormatModifier(defenseMod) + table.insert(modifiers, { name = defense and defense.name or "Defense", value = defenseMod }) 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 - -- Add to log (always, regardless of announce setting) - AddLogEntry(BuildLogMessage(rollValue, modifiers, total)) + if AltSystem.ResultText then + AltSystem.ResultText:SetText(breakdown) + end - -- Announce to chat (if enabled) AnnounceRoll(rollValue, modifiers, total) end diff --git a/UI.lua b/UI.lua index 0b989bb..5162da2 100644 --- a/UI.lua +++ b/UI.lua @@ -1,17 +1,14 @@ -- 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. +-- Creates the main dialog window with all interface elements. -- 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 +local WINDOW_WIDTH = 300 +local WINDOW_HEIGHT = 478 +local PADDING = 12 +local ROW_HEIGHT = 30 +local LABEL_WIDTH = 80 -- Helper: Build the skill option list from current AltSystem.Data.Skills local function BuildSkillOptions() @@ -30,98 +27,38 @@ local function BuildSkillOptions() end -- Helper: Create a modern dropdown (WowStyle1DropdownTemplate) -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 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 selectedIndex = defaultIndex or 1 - local dropdown = CreateFrame("DropdownButton", name, container, "WowStyle1DropdownTemplate") - if label then - dropdown:SetPoint("RIGHT", container, "RIGHT", 0, 0) - else - dropdown:SetPoint("LEFT", container, "LEFT", 0, 0) - end - dropdown:SetWidth(190) + local dropdown = CreateFrame("DropdownButton", name, parent, "WowStyle1DropdownTemplate") + dropdown:SetPoint("LEFT", label, "RIGHT", 4, 0) + dropdown:SetWidth(160) 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 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", "GameFontNormal") - label:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) - label:SetText(text) - label:SetTextColor(0.9, 0.75, 0.2) - return label + return dropdown, function() return selectedIndex end, function(idx) selectedIndex = idx end 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") @@ -139,142 +76,26 @@ function AltSystem:CreateMainFrame() 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() - - 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 + -- Track current Y offset for layout + local yPos = -40 + ------------------------- -- 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) + 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) - -- Warning icon for skill mismatch - local skillWarning = CreateFrame("Frame", nil, skillContainer) + -- Warning icon for skill mismatch (shown next to dropdown when value doesn't match keyword) + local skillWarning = CreateFrame("Frame", nil, f) skillWarning:SetSize(20, 20) skillWarning:SetPoint("LEFT", skillDropdown, "RIGHT", 4, 0) @@ -294,6 +115,7 @@ 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 @@ -309,243 +131,186 @@ function AltSystem:CreateMainFrame() AltSystem.GetSkillIndex = getSkillIndex AltSystem.SetSkillIndex = setSkillIndex AltSystem.UpdateSkillWarning = UpdateSkillWarning + yPos = yPos - ROW_HEIGHT - 8 - 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 + ------------------------- + -- 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, + }) 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 + 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, + }) 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) - 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: Modifiers (optional) - CreateSectionHeader(content, "Modifiers (optional)", PADDING, yPos) - yPos = yPos - 18 - --CreateSubLabel(content, "Label", PADDING, yPos) - --yPos = yPos - 22 + CreateDropdown(f, "AltSystemDefenseDropdown", yPos, "Defense:", defenseOptions, AltSystem.State.selectedDefenseIndex, + function(index) + AltSystem.State.selectedDefenseIndex = index + end) + yPos = yPos - ROW_HEIGHT - 8 + ------------------------- -- Shield checkbox - local shieldCheck = CreateFrame("CheckButton", "AltSystemShieldCheck", content, "UICheckButtonTemplate") - shieldCheck:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos) + ------------------------- + 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) shieldCheck:SetChecked(AltSystem.State.shieldEnabled) - 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 + local shieldText = shieldCheck:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + shieldText:SetPoint("LEFT", shieldCheck, "RIGHT", 2, 0) + shieldText:SetText("+1 modifier") shieldCheck:SetScript("OnClick", function(self) AltSystem.State.shieldEnabled = self:GetChecked() end) - -- Pet checkbox - local petCheck = CreateFrame("CheckButton", "AltSystemPetSummonCheck", content, "UICheckButtonTemplate") - petCheck:SetPoint("LEFT", shieldCheck, "RIGHT", 80, 0) + 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) petCheck:SetChecked(AltSystem.State.petSummonEnabled) - 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 + local petText = petCheck:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + petText:SetPoint("LEFT", petCheck, "RIGHT", 2, 0) + petText:SetText("+d5 modifier") petCheck:SetScript("OnClick", function(self) AltSystem.State.petSummonEnabled = self:GetChecked() end) - yPos = yPos - ROW_HEIGHT - SECTION_GAP + yPos = yPos - ROW_HEIGHT - 8 - -- Section: Roll Dice - CreateSectionHeader(content, "Roll Dice", PADDING, yPos) - yPos = yPos - 22 + ------------------------- + -- 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") - -- Announce Roll dropdown (Self Roll + channels) - local announceOptions = { { text = "Self Roll" } } + 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 = {} for _, ch in ipairs(AltSystem.AnnounceChannels) do - table.insert(announceOptions, { text = ch.name }) + table.insert(channelOptions, { 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) + 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) - yPos = yPos - ROW_HEIGHT - SECTION_GAP + -- Show/hide channel dropdown based on checkbox state + local function UpdateChannelDropdownVisibility() + if AltSystem.State.announceEnabled then + channelDropdown:Show() + else + channelDropdown:Hide() + end + end - -- 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) + announceCheck:SetScript("OnClick", function(self) + AltSystem.State.announceEnabled = self:GetChecked() + UpdateChannelDropdownVisibility() end) - AltSystem.RollButton = rollBtn + UpdateChannelDropdownVisibility() - --------------------- - -- 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) + yPos = yPos - ROW_HEIGHT - 12 - -- 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) + ------------------------- + -- Roll buttons + ------------------------- + local btnWidth = (WINDOW_WIDTH - PADDING * 3) / 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) + local attackBtn = CreateFrame("Button", "AltSystemAttackRollBtn", f, "UIPanelButtonTemplate") + attackBtn:SetSize(btnWidth, 28) + attackBtn:SetPoint("TOPLEFT", f, "TOPLEFT", PADDING, yPos) + attackBtn:SetText("Attack/Skill 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) + local defenseBtn = CreateFrame("Button", "AltSystemDefenseRollBtn", f, "UIPanelButtonTemplate") + defenseBtn:SetSize(btnWidth, 28) + defenseBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -PADDING, yPos) + defenseBtn:SetText("Defense Roll") - local scrollChild = CreateFrame("Frame", "AltSystemLogScrollChild", scrollFrame) - scrollChild:SetWidth(scrollFrame:GetWidth() or (LOG_WIDTH - 50)) - scrollChild:SetHeight(1) -- Will be updated dynamically - scrollFrame:SetScrollChild(scrollChild) + yPos = yPos - 40 - AltSystem.LogScrollFrame = scrollFrame - AltSystem.LogScrollChild = 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) -- 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() @@ -576,21 +341,19 @@ 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) diff --git a/docs/4-redesign.md b/docs/4-redesign.md deleted file mode 100644 index fcd504c..0000000 --- a/docs/4-redesign.md +++ /dev/null @@ -1,78 +0,0 @@ -# Feature: Major Redesign -This redesign of the Addon's window will start by following the following [design](./roll_tab_design.png) - -- The window will be a tabbed window -- The 'Use Skills' tab will correspond to the current roll screen -- The 'Build Skills' tab will be a new screen, to be implemented later. For now, leave it empty. -- The 'Log' should record all rolls made by the user, and be displayed in a scrollable list. - - They should be displayed in the same style as the announced rolls, even when the announce option is off -- The log should store a maximum of 100 rolls - -## Implementation plan - -### 1. Restructure the main window layout (UI.lua) -- Increase `WINDOW_WIDTH` to roughly double (≈660) to accommodate the two-column layout (left panel for tabs, right panel for the log) -- Replace `BasicFrameTemplateWithInset` or layer a new structure inside it: - - **Left column (~50% width):** contains two tabs ("Use Skills", "Build Skills") and their content panels - - **Right column (~50% width):** contains the "Log" header and a scrollable log list -- Keep the title bar ("AltSystem") and close button at the top spanning the full width - -### 2. Implement the tab system (UI.lua) -- Create two tab buttons ("Use Skills" and "Build Skills") anchored at the top of the left column -- Use `PanelTemplates_SetNumTabs` / `PanelTemplates_SetTab` or manual highlight toggling to switch active tab styling -- **"Use Skills" tab content:** migrate all existing UI elements (Roll Type radios → Skill dropdown → Armor radios → Modifiers checkboxes → Announce dropdown → Roll button) into this tab's content frame - - Adapt the current layout from `CreateMainFrame` — re-parent all widgets to the tab content frame instead of `f` directly -- **"Build Skills" tab content:** create an empty placeholder frame (can show a "Coming soon" label) -- Toggling tabs shows/hides the corresponding content frame - -### 3. Redesign the "Use Skills" tab to match the mockup (UI.lua) -- Replace the current separate Attack/Defense buttons with a **Roll Type** radio-button group ("Attack Roll" / "Defense Roll") that sets `AltSystem.State.rollType` -- Keep the **Skill** dropdown as-is (already matches the mockup) -- Replace the current Defense dropdown with an **Armor** radio-button group ("No Armor" / "Basic Armor (+1)" / "Heavy Armor (+2)") - - Map these to the existing `AltSystem.Data.Defenses` entries; show armor options only when Defense Roll is selected, or always visible per mockup -- Group **Shield** and **Pet** checkboxes under a "Modifiers (optional)" section header with a "Label" sub-header matching the mockup -- Replace the Announce checkbox + channel dropdown with a single **"Announce Roll"** dropdown whose options are "Self Roll" (no announce) plus the existing channel list (Emote, Party, Raid, Guild) - - "Self Roll" maps to `announceEnabled = false`; any other selection maps to `announceEnabled = true` with the corresponding channel index -- Add a single **"Roll $rollType"** button at the bottom (text dynamically reflects "Roll Attack" or "Roll Defense") -- Remove the old roll-result text area from this tab (results now go to the Log panel) - -### 4. Build the Log panel (UI.lua) -- Create a right-side panel with a "Log" header label -- Inside, create a `ScrollFrame` (using `UIPanelScrollFrameTemplate` or a manual scroll child) to hold log entries -- Each log entry is a small frame/fontstring displaying the roll result in the same format as the announced message: - - `"[Name] rolled [d20 result] [modifiers] = [total]"` (reuse `BuildModifierString` from Roll.lua) - - Critical rolls show "rolled a Critical Failure!" or "rolled a Critical Success!" -- Entries are listed newest-first (most recent at top) in a vertically stacked layout - -### 5. Implement the roll log data store (Core.lua / Roll.lua) -- Add `AltSystem.State.rollLog = {}` — an array of log entry tables, each containing: `{ text = "...", timestamp = time() }` -- In `Roll.lua`, after every roll result is calculated (in `CalculateAndDisplayResult`), build the log message string (same format as announce) and insert it into `AltSystem.State.rollLog` -- Cap the log at **100 entries**: if `#rollLog > 100`, remove the oldest entry (`table.remove(rollLog, 1)`) -- After inserting, call a UI refresh function to update the scroll frame content -- The log is **always populated**, regardless of the announce setting (per the requirement: "displayed in the same style as the announced rolls, even when the announce option is off") -- Log does **not** need to persist across sessions (not mentioned in requirements); keep it in memory only - -### 6. Wire up the new Roll button (Roll.lua) -- The single "Roll $rollType" button calls `AltSystem:PerformRoll(state.rollType)` where `state.rollType` is set by the radio-button group ("attack" or "defense") -- Existing `PerformRoll` and `CalculateAndDisplayResult` logic remains largely unchanged; only the final display step changes from setting `ResultText` to appending to the log + refreshing the log UI - -### 7. Update state persistence (Core.lua) -- Save/restore `rollType` selection (attack/defense) in `AltSystemDB` -- Update announce state handling to work with the new single-dropdown approach (save selected option index) -- Armor selection (radio group) replaces `selectedDefenseIndex` — reuse same key or migrate - -### 8. Update the .toc file if needed (AltSystem.toc) -- No new Lua files are expected (all changes fit in existing files), but verify the load order is still correct - -### 9. Testing checklist -- [ ] Window opens at new size, tabs switch correctly -- [ ] "Use Skills" tab shows all controls matching the mockup layout -- [ ] "Build Skills" tab is empty / shows placeholder -- [ ] Attack and Defense rolls work correctly via the new single Roll button -- [ ] Log panel populates with each roll, formatted like announce messages -- [ ] Log scrolls when entries exceed visible area -- [ ] Log caps at 100 entries, oldest removed first -- [ ] Log populates even when announce is set to "Self Roll" (off) -- [ ] Announce still works when a channel is selected -- [ ] State (roll type, armor, announce option) persists across sessions -- [ ] Window is draggable and clamps to screen \ No newline at end of file diff --git a/docs/Changelog.md b/docs/Changelog.md index e69de29..dd05c6a 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -0,0 +1,3 @@ +- Adding default 'Unskilled (-4)' option +- The roll window now remembers all user selections (skill, item, armor type, shield, pet, announce and channel) across sessions +- Fixed an issue where the wrong rolls were displayed when in a large enough group \ No newline at end of file diff --git a/docs/roll_tab_design.png b/docs/roll_tab_design.png deleted file mode 100644 index 5754915..0000000 Binary files a/docs/roll_tab_design.png and /dev/null differ diff --git a/package.sh b/package.sh index 194f78f..6febfa8 100755 --- a/package.sh +++ b/package.sh @@ -7,7 +7,7 @@ OUTPUT_FILE="$OUTPUT_DIR/$ADDON_NAME.zip" STAGING_DIR="$(mktemp -d)" mkdir -p "$STAGING_DIR/$ADDON_NAME" -rsync -a --exclude='.DS_Store' --exclude='__MACOSX' --exclude='.git' --exclude='docs' "$SCRIPT_DIR/" "$STAGING_DIR/$ADDON_NAME/" +rsync -a --exclude='.DS_Store' --exclude='__MACOSX' --exclude='.git' "$SCRIPT_DIR/" "$STAGING_DIR/$ADDON_NAME/" cd "$STAGING_DIR" && \ zip -r "$OUTPUT_FILE" "$ADDON_NAME"