From 150e52842eaea55cd087c346fe0b702b06f53829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Sat, 11 Apr 2026 16:05:04 +0100 Subject: [PATCH 01/74] First working version --- .idea/.gitignore | 10 +++ .idea/misc.xml | 6 ++ .idea/modules.xml | 8 ++ AltSystem.iml | 11 +++ AltSystem.toc | 12 +++ Core.lua | 97 ++++++++++++++++++++++ Data.lua | 44 ++++++++++ README.md | 13 +++ Roll.lua | 85 ++++++++++++++++++++ UI.lua | 190 ++++++++++++++++++++++++++++++++++++++++++++ docs/1-interface.md | 38 +++++++++ docs/2-skills.md | 1 + 12 files changed, 515 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 AltSystem.iml create mode 100644 AltSystem.toc create mode 100644 Core.lua create mode 100644 Data.lua create mode 100644 README.md create mode 100644 Roll.lua create mode 100644 UI.lua create mode 100644 docs/1-interface.md create mode 100644 docs/2-skills.md diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..4b151ab --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..2aaa495 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/AltSystem.iml b/AltSystem.iml new file mode 100644 index 0000000..0495363 --- /dev/null +++ b/AltSystem.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/AltSystem.toc b/AltSystem.toc new file mode 100644 index 0000000..57c8602 --- /dev/null +++ b/AltSystem.toc @@ -0,0 +1,12 @@ +## Interface: 120001 +## Title: AltSystem +## Notes: Enhances RP gameplay with a custom rolling system +## Author: Goncalo +## Version: 1.0.0 +## Dependencies: totalRP3, totalRP3_Extended +## SavedVariables: AltSystemDB + +Data.lua +Core.lua +UI.lua +Roll.lua diff --git a/Core.lua b/Core.lua new file mode 100644 index 0000000..a2118e3 --- /dev/null +++ b/Core.lua @@ -0,0 +1,97 @@ +-- AltSystem Core +-- Addon initialization, slash command registration, and TRP3 integration. + +AltSystem = AltSystem or {} +AltSystem.State = { + selectedSkillIndex = 1, + selectedItemIndex = 1, -- 1 = No item + selectedDefenseIndex = 1, -- 1 = Base armor + shieldEnabled = false, +} + +-- Initialization on ADDON_LOADED +local frame = CreateFrame("Frame") +frame:RegisterEvent("ADDON_LOADED") +frame:RegisterEvent("PLAYER_LOGIN") + +frame:SetScript("OnEvent", function(self, event, arg1) + if event == "ADDON_LOADED" and arg1 == "AltSystem" then + AltSystem:Init() + elseif event == "PLAYER_LOGIN" then + AltSystem:RegisterTRP3Button() + AltSystem:RegisterMinimapButton() + end +end) + +function AltSystem:Init() + -- Register slash command /altsystem + SLASH_ALTSYSTEM1 = "/altsystem" + SlashCmdList["ALTSYSTEM"] = function() + AltSystem:ToggleWindow() + end + + print("|cff00ccffAltSystem|r loaded. Type /altsystem to open.") +end + +function AltSystem:ToggleWindow() + if AltSystem.MainFrame then + if AltSystem.MainFrame:IsShown() then + AltSystem.MainFrame:Hide() + else + AltSystem.MainFrame:Show() + end + end +end + +-- TRP3 toolbar button registration +function AltSystem:RegisterTRP3Button() + if TRP3_API and TRP3_API.toolbar then + local toolbarButton = { + id = "altsystem_toolbar_btn", + icon = "Interface\\Icons\\INV_Misc_Dice_02", + tooltip = "AltSystem", + tooltipSub = "Open the AltSystem rolling window", + onClick = function() + AltSystem:ToggleWindow() + end, + } + TRP3_API.toolbar.toolbarAddButton(toolbarButton) + end +end + +-- Minimap button using LibDBIcon (lightweight fallback if not available) +function AltSystem:RegisterMinimapButton() + -- Simple minimap button without external libraries + local minimapButton = CreateFrame("Button", "AltSystemMinimapButton", Minimap) + minimapButton:SetSize(32, 32) + minimapButton:SetFrameStrata("MEDIUM") + minimapButton:SetFrameLevel(8) + minimapButton:SetPoint("TOPLEFT", Minimap, "TOPLEFT", 2, -2) + + local icon = minimapButton:CreateTexture(nil, "ARTWORK") + icon:SetTexture("Interface\\Icons\\INV_Misc_Dice_02") + icon:SetSize(20, 20) + icon:SetPoint("CENTER") + + local border = minimapButton:CreateTexture(nil, "OVERLAY") + border:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder") + border:SetSize(54, 54) + border:SetPoint("TOPLEFT", -2, 2) + + minimapButton:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight") + + minimapButton:SetScript("OnClick", function() + AltSystem:ToggleWindow() + end) + + minimapButton:SetScript("OnEnter", function(self) + GameTooltip:SetOwner(self, "ANCHOR_LEFT") + GameTooltip:AddLine("AltSystem") + GameTooltip:AddLine("Click to toggle the rolling window", 1, 1, 1) + GameTooltip:Show() + end) + + minimapButton:SetScript("OnLeave", function() + GameTooltip:Hide() + end) +end diff --git a/Data.lua b/Data.lua new file mode 100644 index 0000000..ab7984a --- /dev/null +++ b/Data.lua @@ -0,0 +1,44 @@ +-- AltSystem Data Definitions +-- Contains dummy skill data, item options, defense options, and modifier constants. + +AltSystem = AltSystem or {} +AltSystem.Data = {} + +-- Skill levels and their modifiers +AltSystem.Data.SkillLevels = { + ["Novice"] = -2, + ["Adept"] = 0, + ["Expert"] = 2, + ["Master"] = 4, +} + +-- Dummy skill data: each skill has a name, icon, and level +AltSystem.Data.Skills = { + { name = "Swordsmanship", icon = "Interface\\Icons\\INV_Sword_04", level = "Expert" }, + { name = "Archery", icon = "Interface\\Icons\\INV_Weapon_Bow_05", level = "Adept" }, + { name = "Fire Magic", icon = "Interface\\Icons\\Spell_Fire_FlameBolt", level = "Master" }, + { name = "Healing", icon = "Interface\\Icons\\Spell_Holy_FlashHeal", level = "Novice" }, + { name = "Stealth", icon = "Interface\\Icons\\Ability_Stealth", level = "Adept" }, +} + +-- Pre-compute modifier for each skill based on its level +for _, skill in ipairs(AltSystem.Data.Skills) do + skill.modifier = AltSystem.Data.SkillLevels[skill.level] or 0 +end + +-- Item options: name and modifier (first entry = no item) +AltSystem.Data.Items = { + { name = "No item", modifier = 0 }, + { name = "Rare item", modifier = 3 }, + { name = "Epic item", modifier = 5 }, +} + +-- Defense options: name and modifier +AltSystem.Data.Defenses = { + { name = "Base armor", modifier = 0 }, + { name = "Extra small armor", modifier = 1 }, + { name = "Extra large armor", modifier = 2 }, +} + +-- Shield modifier +AltSystem.Data.ShieldModifier = 1 diff --git a/README.md b/README.md new file mode 100644 index 0000000..35b32f7 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# AltSystem +This project is a WoW Addon, designed to enhance RP gameplay while using a specific rolling system. +The rolling system relies on TRP3 and TRP3:Enhanced addons, so these are explicit dependencies. + +## Features +- Main features defined in the /docs folder + +## Installation +To use AltSystem, ensure you have the following addons installed: +- TRP3 +- TRP3:Enhanced + +Follow the installation instructions for these addons before installing AltSystem. \ No newline at end of file diff --git a/Roll.lua b/Roll.lua new file mode 100644 index 0000000..28f3752 --- /dev/null +++ b/Roll.lua @@ -0,0 +1,85 @@ +-- AltSystem Roll Logic +-- Handles /roll 20, captures the result, and applies modifiers. + +AltSystem = AltSystem or {} + +local pendingRollType = nil -- "attack" or "defense" + +-- Perform a roll: triggers the WoW native /roll 20 command +function AltSystem:PerformRoll(rollType) + pendingRollType = rollType + RandomRoll(1, 20) +end + +-- Listen for the system message that contains the roll result +local rollListener = CreateFrame("Frame") +rollListener:RegisterEvent("CHAT_MSG_SYSTEM") + +rollListener:SetScript("OnEvent", function(self, event, message) + if not pendingRollType then return end + + -- Match the roll result pattern: "PlayerName rolls X (1-20)" + local roll = message:match("rolls (%d+) %(1%-20%)") + if not roll then return end + + local rollValue = tonumber(roll) + local rollType = pendingRollType + pendingRollType = nil + + AltSystem:CalculateAndDisplayResult(rollType, rollValue) +end) + +-- Calculate the final result based on the roll type and selected modifiers +function AltSystem:CalculateAndDisplayResult(rollType, rollValue) + local state = AltSystem.State + local skill = AltSystem.Data.Skills[state.selectedSkillIndex] + local item = AltSystem.Data.Items[state.selectedItemIndex] + + local skillMod = skill and skill.modifier or 0 + local itemMod = item and item.modifier or 0 + + local total = rollValue + local breakdown = "Roll: " .. rollValue + + if rollType == "attack" then + -- Attack Roll = roll + skill modifier + item modifier + total = rollValue + skillMod + itemMod + + breakdown = breakdown .. "\nSkill: " .. FormatModifier(skillMod) + if itemMod ~= 0 then + breakdown = breakdown .. " | Item: " .. FormatModifier(itemMod) + end + breakdown = breakdown .. "\n|cffffd100Attack Total: " .. total .. "|r" + + elseif rollType == "defense" then + -- Defense Roll = roll + skill modifier + item modifier + defense modifier + shield modifier + local defense = AltSystem.Data.Defenses[state.selectedDefenseIndex] + local defenseMod = defense and defense.modifier or 0 + local shieldMod = state.shieldEnabled and AltSystem.Data.ShieldModifier or 0 + + total = rollValue + skillMod + itemMod + defenseMod + shieldMod + + breakdown = breakdown .. "\nSkill: " .. FormatModifier(skillMod) + if itemMod ~= 0 then + breakdown = breakdown .. " | Item: " .. FormatModifier(itemMod) + end + breakdown = breakdown .. "\nDefense: " .. FormatModifier(defenseMod) + if shieldMod ~= 0 then + breakdown = breakdown .. " | Shield: " .. FormatModifier(shieldMod) + end + breakdown = breakdown .. "\n|cff00ccffDefense Total: " .. total .. "|r" + end + + if AltSystem.ResultText then + AltSystem.ResultText:SetText(breakdown) + end +end + +-- Format a modifier value with sign +function FormatModifier(value) + if value >= 0 then + return "+" .. value + else + return tostring(value) + end +end diff --git a/UI.lua b/UI.lua new file mode 100644 index 0000000..c5c4a69 --- /dev/null +++ b/UI.lua @@ -0,0 +1,190 @@ +-- AltSystem UI +-- 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 = 300 +local WINDOW_HEIGHT = 380 +local PADDING = 12 +local ROW_HEIGHT = 30 +local LABEL_WIDTH = 80 + +-- 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 selectedIndex = defaultIndex or 1 + + 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 + ) + end + end) + + return dropdown +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") + + -- Track current Y offset for layout + local yPos = -40 + + ------------------------- + -- Skill dropdown + ------------------------- + local skillOptions = {} + for _, skill in ipairs(AltSystem.Data.Skills) do + local levelMod = skill.modifier + local sign = levelMod >= 0 and "+" or "" + table.insert(skillOptions, { + text = skill.name .. " (" .. skill.level .. " " .. sign .. levelMod .. ")", + icon = skill.icon, + }) + end + + CreateDropdown(f, "AltSystemSkillDropdown", yPos, "Skill:", skillOptions, AltSystem.State.selectedSkillIndex, + function(index) + AltSystem.State.selectedSkillIndex = index + end) + 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, + }) + 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, + }) + end + + CreateDropdown(f, "AltSystemDefenseDropdown", yPos, "Defense:", defenseOptions, AltSystem.State.selectedDefenseIndex, + function(index) + AltSystem.State.selectedDefenseIndex = index + end) + yPos = yPos - ROW_HEIGHT - 8 + + ------------------------- + -- 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) + shieldCheck:SetChecked(AltSystem.State.shieldEnabled) + + 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) + + yPos = yPos - ROW_HEIGHT - 12 + + ------------------------- + -- Roll buttons + ------------------------- + local btnWidth = (WINDOW_WIDTH - PADDING * 3) / 2 + + local attackBtn = CreateFrame("Button", "AltSystemAttackRollBtn", f, "UIPanelButtonTemplate") + attackBtn:SetSize(btnWidth, 28) + attackBtn:SetPoint("TOPLEFT", f, "TOPLEFT", PADDING, yPos) + attackBtn:SetText("Attack Roll") + + local defenseBtn = CreateFrame("Button", "AltSystemDefenseRollBtn", f, "UIPanelButtonTemplate") + defenseBtn:SetSize(btnWidth, 28) + defenseBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -PADDING, yPos) + defenseBtn:SetText("Defense Roll") + + yPos = yPos - 40 + + ------------------------- + -- 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) + + f:Hide() + AltSystem.MainFrame = f +end + +-- Create the frame on file load so it's ready when Init runs +AltSystem:CreateMainFrame() diff --git a/docs/1-interface.md b/docs/1-interface.md new file mode 100644 index 0000000..d6a4f75 --- /dev/null +++ b/docs/1-interface.md @@ -0,0 +1,38 @@ +# Feature: Interface +- The addon's main interface is a simple dialog window. +- This window can be shown/hidden via: + - A slash command `/altsystem` + - A button inside the TRP toolbar + - A button in the minimap menu + +## Window features +### Skill dropdown +- A small dropdown menu to select the skill to be used. (use dummy data for now) +- 1 skill per line +- Each skill has a name, an icon, a level (Novice, Adept, Expert, Master), and a corresponding modifier + - Novice: -2 + - Adept: +0 + - Expert: +2 + - Master: +4 +### Item dropdown +- A small dropdown menu to select if an item should be used or not. +- Default to no item selected +- Static data for available selections: + - Rare item (+3 modifier) + - Epic item (+5 modifier) +### Defense dropdown +- A small dropdown menu to select the defense to be used. +- Static data for available selections: + - Base armor (+0) + - Extra small armor (+1) + - Extra large armor (+2) +- Default to Base armor +### Shield checkbox +- A checkbox to select if a shield (+1) should be used or not. +- Default to unchecked +### Roll buttons +- Two buttons, each will perform the WoW native `/roll 20` command when clicked +### Roll result +- A small area, to show the result of the rolls based on which button was clicked +- The 'Attack Roll' button will show the sum of the roll, plus any skill and item modifiers +- The 'Defense Roll' button will show the sum of the roll, plus any skill, item, defense, and shield modifier \ No newline at end of file diff --git a/docs/2-skills.md b/docs/2-skills.md new file mode 100644 index 0000000..9eae222 --- /dev/null +++ b/docs/2-skills.md @@ -0,0 +1 @@ +# Feature: Skills \ No newline at end of file From 68ab7b7757faeaac5c7be2c26e004e3d9dccc32b Mon Sep 17 00:00:00 2001 From: Rukira Date: Sat, 11 Apr 2026 16:05:04 +0100 Subject: [PATCH 02/74] First working version --- .idea/.gitignore | 10 +++ .idea/misc.xml | 6 ++ .idea/modules.xml | 8 ++ AltSystem.iml | 11 +++ AltSystem.toc | 12 +++ Core.lua | 97 ++++++++++++++++++++++ Data.lua | 44 ++++++++++ README.md | 13 +++ Roll.lua | 85 ++++++++++++++++++++ UI.lua | 190 ++++++++++++++++++++++++++++++++++++++++++++ docs/1-interface.md | 38 +++++++++ docs/2-skills.md | 1 + 12 files changed, 515 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 AltSystem.iml create mode 100644 AltSystem.toc create mode 100644 Core.lua create mode 100644 Data.lua create mode 100644 README.md create mode 100644 Roll.lua create mode 100644 UI.lua create mode 100644 docs/1-interface.md create mode 100644 docs/2-skills.md diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..4b151ab --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..2aaa495 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/AltSystem.iml b/AltSystem.iml new file mode 100644 index 0000000..0495363 --- /dev/null +++ b/AltSystem.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/AltSystem.toc b/AltSystem.toc new file mode 100644 index 0000000..57c8602 --- /dev/null +++ b/AltSystem.toc @@ -0,0 +1,12 @@ +## Interface: 120001 +## Title: AltSystem +## Notes: Enhances RP gameplay with a custom rolling system +## Author: Goncalo +## Version: 1.0.0 +## Dependencies: totalRP3, totalRP3_Extended +## SavedVariables: AltSystemDB + +Data.lua +Core.lua +UI.lua +Roll.lua diff --git a/Core.lua b/Core.lua new file mode 100644 index 0000000..a2118e3 --- /dev/null +++ b/Core.lua @@ -0,0 +1,97 @@ +-- AltSystem Core +-- Addon initialization, slash command registration, and TRP3 integration. + +AltSystem = AltSystem or {} +AltSystem.State = { + selectedSkillIndex = 1, + selectedItemIndex = 1, -- 1 = No item + selectedDefenseIndex = 1, -- 1 = Base armor + shieldEnabled = false, +} + +-- Initialization on ADDON_LOADED +local frame = CreateFrame("Frame") +frame:RegisterEvent("ADDON_LOADED") +frame:RegisterEvent("PLAYER_LOGIN") + +frame:SetScript("OnEvent", function(self, event, arg1) + if event == "ADDON_LOADED" and arg1 == "AltSystem" then + AltSystem:Init() + elseif event == "PLAYER_LOGIN" then + AltSystem:RegisterTRP3Button() + AltSystem:RegisterMinimapButton() + end +end) + +function AltSystem:Init() + -- Register slash command /altsystem + SLASH_ALTSYSTEM1 = "/altsystem" + SlashCmdList["ALTSYSTEM"] = function() + AltSystem:ToggleWindow() + end + + print("|cff00ccffAltSystem|r loaded. Type /altsystem to open.") +end + +function AltSystem:ToggleWindow() + if AltSystem.MainFrame then + if AltSystem.MainFrame:IsShown() then + AltSystem.MainFrame:Hide() + else + AltSystem.MainFrame:Show() + end + end +end + +-- TRP3 toolbar button registration +function AltSystem:RegisterTRP3Button() + if TRP3_API and TRP3_API.toolbar then + local toolbarButton = { + id = "altsystem_toolbar_btn", + icon = "Interface\\Icons\\INV_Misc_Dice_02", + tooltip = "AltSystem", + tooltipSub = "Open the AltSystem rolling window", + onClick = function() + AltSystem:ToggleWindow() + end, + } + TRP3_API.toolbar.toolbarAddButton(toolbarButton) + end +end + +-- Minimap button using LibDBIcon (lightweight fallback if not available) +function AltSystem:RegisterMinimapButton() + -- Simple minimap button without external libraries + local minimapButton = CreateFrame("Button", "AltSystemMinimapButton", Minimap) + minimapButton:SetSize(32, 32) + minimapButton:SetFrameStrata("MEDIUM") + minimapButton:SetFrameLevel(8) + minimapButton:SetPoint("TOPLEFT", Minimap, "TOPLEFT", 2, -2) + + local icon = minimapButton:CreateTexture(nil, "ARTWORK") + icon:SetTexture("Interface\\Icons\\INV_Misc_Dice_02") + icon:SetSize(20, 20) + icon:SetPoint("CENTER") + + local border = minimapButton:CreateTexture(nil, "OVERLAY") + border:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder") + border:SetSize(54, 54) + border:SetPoint("TOPLEFT", -2, 2) + + minimapButton:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight") + + minimapButton:SetScript("OnClick", function() + AltSystem:ToggleWindow() + end) + + minimapButton:SetScript("OnEnter", function(self) + GameTooltip:SetOwner(self, "ANCHOR_LEFT") + GameTooltip:AddLine("AltSystem") + GameTooltip:AddLine("Click to toggle the rolling window", 1, 1, 1) + GameTooltip:Show() + end) + + minimapButton:SetScript("OnLeave", function() + GameTooltip:Hide() + end) +end diff --git a/Data.lua b/Data.lua new file mode 100644 index 0000000..ab7984a --- /dev/null +++ b/Data.lua @@ -0,0 +1,44 @@ +-- AltSystem Data Definitions +-- Contains dummy skill data, item options, defense options, and modifier constants. + +AltSystem = AltSystem or {} +AltSystem.Data = {} + +-- Skill levels and their modifiers +AltSystem.Data.SkillLevels = { + ["Novice"] = -2, + ["Adept"] = 0, + ["Expert"] = 2, + ["Master"] = 4, +} + +-- Dummy skill data: each skill has a name, icon, and level +AltSystem.Data.Skills = { + { name = "Swordsmanship", icon = "Interface\\Icons\\INV_Sword_04", level = "Expert" }, + { name = "Archery", icon = "Interface\\Icons\\INV_Weapon_Bow_05", level = "Adept" }, + { name = "Fire Magic", icon = "Interface\\Icons\\Spell_Fire_FlameBolt", level = "Master" }, + { name = "Healing", icon = "Interface\\Icons\\Spell_Holy_FlashHeal", level = "Novice" }, + { name = "Stealth", icon = "Interface\\Icons\\Ability_Stealth", level = "Adept" }, +} + +-- Pre-compute modifier for each skill based on its level +for _, skill in ipairs(AltSystem.Data.Skills) do + skill.modifier = AltSystem.Data.SkillLevels[skill.level] or 0 +end + +-- Item options: name and modifier (first entry = no item) +AltSystem.Data.Items = { + { name = "No item", modifier = 0 }, + { name = "Rare item", modifier = 3 }, + { name = "Epic item", modifier = 5 }, +} + +-- Defense options: name and modifier +AltSystem.Data.Defenses = { + { name = "Base armor", modifier = 0 }, + { name = "Extra small armor", modifier = 1 }, + { name = "Extra large armor", modifier = 2 }, +} + +-- Shield modifier +AltSystem.Data.ShieldModifier = 1 diff --git a/README.md b/README.md new file mode 100644 index 0000000..35b32f7 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# AltSystem +This project is a WoW Addon, designed to enhance RP gameplay while using a specific rolling system. +The rolling system relies on TRP3 and TRP3:Enhanced addons, so these are explicit dependencies. + +## Features +- Main features defined in the /docs folder + +## Installation +To use AltSystem, ensure you have the following addons installed: +- TRP3 +- TRP3:Enhanced + +Follow the installation instructions for these addons before installing AltSystem. \ No newline at end of file diff --git a/Roll.lua b/Roll.lua new file mode 100644 index 0000000..28f3752 --- /dev/null +++ b/Roll.lua @@ -0,0 +1,85 @@ +-- AltSystem Roll Logic +-- Handles /roll 20, captures the result, and applies modifiers. + +AltSystem = AltSystem or {} + +local pendingRollType = nil -- "attack" or "defense" + +-- Perform a roll: triggers the WoW native /roll 20 command +function AltSystem:PerformRoll(rollType) + pendingRollType = rollType + RandomRoll(1, 20) +end + +-- Listen for the system message that contains the roll result +local rollListener = CreateFrame("Frame") +rollListener:RegisterEvent("CHAT_MSG_SYSTEM") + +rollListener:SetScript("OnEvent", function(self, event, message) + if not pendingRollType then return end + + -- Match the roll result pattern: "PlayerName rolls X (1-20)" + local roll = message:match("rolls (%d+) %(1%-20%)") + if not roll then return end + + local rollValue = tonumber(roll) + local rollType = pendingRollType + pendingRollType = nil + + AltSystem:CalculateAndDisplayResult(rollType, rollValue) +end) + +-- Calculate the final result based on the roll type and selected modifiers +function AltSystem:CalculateAndDisplayResult(rollType, rollValue) + local state = AltSystem.State + local skill = AltSystem.Data.Skills[state.selectedSkillIndex] + local item = AltSystem.Data.Items[state.selectedItemIndex] + + local skillMod = skill and skill.modifier or 0 + local itemMod = item and item.modifier or 0 + + local total = rollValue + local breakdown = "Roll: " .. rollValue + + if rollType == "attack" then + -- Attack Roll = roll + skill modifier + item modifier + total = rollValue + skillMod + itemMod + + breakdown = breakdown .. "\nSkill: " .. FormatModifier(skillMod) + if itemMod ~= 0 then + breakdown = breakdown .. " | Item: " .. FormatModifier(itemMod) + end + breakdown = breakdown .. "\n|cffffd100Attack Total: " .. total .. "|r" + + elseif rollType == "defense" then + -- Defense Roll = roll + skill modifier + item modifier + defense modifier + shield modifier + local defense = AltSystem.Data.Defenses[state.selectedDefenseIndex] + local defenseMod = defense and defense.modifier or 0 + local shieldMod = state.shieldEnabled and AltSystem.Data.ShieldModifier or 0 + + total = rollValue + skillMod + itemMod + defenseMod + shieldMod + + breakdown = breakdown .. "\nSkill: " .. FormatModifier(skillMod) + if itemMod ~= 0 then + breakdown = breakdown .. " | Item: " .. FormatModifier(itemMod) + end + breakdown = breakdown .. "\nDefense: " .. FormatModifier(defenseMod) + if shieldMod ~= 0 then + breakdown = breakdown .. " | Shield: " .. FormatModifier(shieldMod) + end + breakdown = breakdown .. "\n|cff00ccffDefense Total: " .. total .. "|r" + end + + if AltSystem.ResultText then + AltSystem.ResultText:SetText(breakdown) + end +end + +-- Format a modifier value with sign +function FormatModifier(value) + if value >= 0 then + return "+" .. value + else + return tostring(value) + end +end diff --git a/UI.lua b/UI.lua new file mode 100644 index 0000000..c5c4a69 --- /dev/null +++ b/UI.lua @@ -0,0 +1,190 @@ +-- AltSystem UI +-- 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 = 300 +local WINDOW_HEIGHT = 380 +local PADDING = 12 +local ROW_HEIGHT = 30 +local LABEL_WIDTH = 80 + +-- 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 selectedIndex = defaultIndex or 1 + + 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 + ) + end + end) + + return dropdown +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") + + -- Track current Y offset for layout + local yPos = -40 + + ------------------------- + -- Skill dropdown + ------------------------- + local skillOptions = {} + for _, skill in ipairs(AltSystem.Data.Skills) do + local levelMod = skill.modifier + local sign = levelMod >= 0 and "+" or "" + table.insert(skillOptions, { + text = skill.name .. " (" .. skill.level .. " " .. sign .. levelMod .. ")", + icon = skill.icon, + }) + end + + CreateDropdown(f, "AltSystemSkillDropdown", yPos, "Skill:", skillOptions, AltSystem.State.selectedSkillIndex, + function(index) + AltSystem.State.selectedSkillIndex = index + end) + 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, + }) + 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, + }) + end + + CreateDropdown(f, "AltSystemDefenseDropdown", yPos, "Defense:", defenseOptions, AltSystem.State.selectedDefenseIndex, + function(index) + AltSystem.State.selectedDefenseIndex = index + end) + yPos = yPos - ROW_HEIGHT - 8 + + ------------------------- + -- 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) + shieldCheck:SetChecked(AltSystem.State.shieldEnabled) + + 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) + + yPos = yPos - ROW_HEIGHT - 12 + + ------------------------- + -- Roll buttons + ------------------------- + local btnWidth = (WINDOW_WIDTH - PADDING * 3) / 2 + + local attackBtn = CreateFrame("Button", "AltSystemAttackRollBtn", f, "UIPanelButtonTemplate") + attackBtn:SetSize(btnWidth, 28) + attackBtn:SetPoint("TOPLEFT", f, "TOPLEFT", PADDING, yPos) + attackBtn:SetText("Attack Roll") + + local defenseBtn = CreateFrame("Button", "AltSystemDefenseRollBtn", f, "UIPanelButtonTemplate") + defenseBtn:SetSize(btnWidth, 28) + defenseBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -PADDING, yPos) + defenseBtn:SetText("Defense Roll") + + yPos = yPos - 40 + + ------------------------- + -- 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) + + f:Hide() + AltSystem.MainFrame = f +end + +-- Create the frame on file load so it's ready when Init runs +AltSystem:CreateMainFrame() diff --git a/docs/1-interface.md b/docs/1-interface.md new file mode 100644 index 0000000..d6a4f75 --- /dev/null +++ b/docs/1-interface.md @@ -0,0 +1,38 @@ +# Feature: Interface +- The addon's main interface is a simple dialog window. +- This window can be shown/hidden via: + - A slash command `/altsystem` + - A button inside the TRP toolbar + - A button in the minimap menu + +## Window features +### Skill dropdown +- A small dropdown menu to select the skill to be used. (use dummy data for now) +- 1 skill per line +- Each skill has a name, an icon, a level (Novice, Adept, Expert, Master), and a corresponding modifier + - Novice: -2 + - Adept: +0 + - Expert: +2 + - Master: +4 +### Item dropdown +- A small dropdown menu to select if an item should be used or not. +- Default to no item selected +- Static data for available selections: + - Rare item (+3 modifier) + - Epic item (+5 modifier) +### Defense dropdown +- A small dropdown menu to select the defense to be used. +- Static data for available selections: + - Base armor (+0) + - Extra small armor (+1) + - Extra large armor (+2) +- Default to Base armor +### Shield checkbox +- A checkbox to select if a shield (+1) should be used or not. +- Default to unchecked +### Roll buttons +- Two buttons, each will perform the WoW native `/roll 20` command when clicked +### Roll result +- A small area, to show the result of the rolls based on which button was clicked +- The 'Attack Roll' button will show the sum of the roll, plus any skill and item modifiers +- The 'Defense Roll' button will show the sum of the roll, plus any skill, item, defense, and shield modifier \ No newline at end of file diff --git a/docs/2-skills.md b/docs/2-skills.md new file mode 100644 index 0000000..9eae222 --- /dev/null +++ b/docs/2-skills.md @@ -0,0 +1 @@ +# Feature: Skills \ No newline at end of file From ce1c6768e15a9b9f8b7d92d160185c4eefd425fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Sat, 11 Apr 2026 18:29:53 +0100 Subject: [PATCH 03/74] TRP integration --- Core.lua | 70 +++++++++++++++++++++++--------------------------------- 1 file changed, 28 insertions(+), 42 deletions(-) diff --git a/Core.lua b/Core.lua index a2118e3..fff36a1 100644 --- a/Core.lua +++ b/Core.lua @@ -12,14 +12,11 @@ AltSystem.State = { -- Initialization on ADDON_LOADED local frame = CreateFrame("Frame") frame:RegisterEvent("ADDON_LOADED") -frame:RegisterEvent("PLAYER_LOGIN") frame:SetScript("OnEvent", function(self, event, arg1) if event == "ADDON_LOADED" and arg1 == "AltSystem" then AltSystem:Init() - elseif event == "PLAYER_LOGIN" then - AltSystem:RegisterTRP3Button() - AltSystem:RegisterMinimapButton() + AltSystem:RegisterTRP3Module() end end) @@ -43,55 +40,44 @@ function AltSystem:ToggleWindow() end end --- TRP3 toolbar button registration +-- Register as a TRP3 module so our onStart runs in the correct lifecycle phase +-- (after the toolbar module is initialized, but before WORKFLOW_ON_FINISH locks it) +function AltSystem:RegisterTRP3Module() + if not TRP3_API or not TRP3_API.module then + return + end + + local MODULE_STRUCTURE = { + ["name"] = "AltSystem", + ["description"] = "AltSystem rolling window toolbar button", + ["version"] = 1.000, + ["id"] = "altsystem_module", + ["onStart"] = function() + AltSystem:RegisterTRP3Button() + end, + ["minVersion"] = 3, + ["requiredDeps"] = { + {"trp3_tool_bar", 1}, + }, + } + + TRP3_API.module.registerModule(MODULE_STRUCTURE) +end + +-- TRP3 toolbar button registration (called from the TRP3 module onStart) function AltSystem:RegisterTRP3Button() if TRP3_API and TRP3_API.toolbar then local toolbarButton = { id = "altsystem_toolbar_btn", - icon = "Interface\\Icons\\INV_Misc_Dice_02", + icon = "Interface\\Icons\\INV_Misc_Dice_01", tooltip = "AltSystem", tooltipSub = "Open the AltSystem rolling window", onClick = function() AltSystem:ToggleWindow() end, + visible = 1, } TRP3_API.toolbar.toolbarAddButton(toolbarButton) end end --- Minimap button using LibDBIcon (lightweight fallback if not available) -function AltSystem:RegisterMinimapButton() - -- Simple minimap button without external libraries - local minimapButton = CreateFrame("Button", "AltSystemMinimapButton", Minimap) - minimapButton:SetSize(32, 32) - minimapButton:SetFrameStrata("MEDIUM") - minimapButton:SetFrameLevel(8) - minimapButton:SetPoint("TOPLEFT", Minimap, "TOPLEFT", 2, -2) - - local icon = minimapButton:CreateTexture(nil, "ARTWORK") - icon:SetTexture("Interface\\Icons\\INV_Misc_Dice_02") - icon:SetSize(20, 20) - icon:SetPoint("CENTER") - - local border = minimapButton:CreateTexture(nil, "OVERLAY") - border:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder") - border:SetSize(54, 54) - border:SetPoint("TOPLEFT", -2, 2) - - minimapButton:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight") - - minimapButton:SetScript("OnClick", function() - AltSystem:ToggleWindow() - end) - - minimapButton:SetScript("OnEnter", function(self) - GameTooltip:SetOwner(self, "ANCHOR_LEFT") - GameTooltip:AddLine("AltSystem") - GameTooltip:AddLine("Click to toggle the rolling window", 1, 1, 1) - GameTooltip:Show() - end) - - minimapButton:SetScript("OnLeave", function() - GameTooltip:Hide() - end) -end From a2eac70213c9b7032739b395860f6774b6ff4578 Mon Sep 17 00:00:00 2001 From: Rukira Date: Sat, 11 Apr 2026 18:29:53 +0100 Subject: [PATCH 04/74] TRP integration --- Core.lua | 70 +++++++++++++++++++++++--------------------------------- 1 file changed, 28 insertions(+), 42 deletions(-) diff --git a/Core.lua b/Core.lua index a2118e3..fff36a1 100644 --- a/Core.lua +++ b/Core.lua @@ -12,14 +12,11 @@ AltSystem.State = { -- Initialization on ADDON_LOADED local frame = CreateFrame("Frame") frame:RegisterEvent("ADDON_LOADED") -frame:RegisterEvent("PLAYER_LOGIN") frame:SetScript("OnEvent", function(self, event, arg1) if event == "ADDON_LOADED" and arg1 == "AltSystem" then AltSystem:Init() - elseif event == "PLAYER_LOGIN" then - AltSystem:RegisterTRP3Button() - AltSystem:RegisterMinimapButton() + AltSystem:RegisterTRP3Module() end end) @@ -43,55 +40,44 @@ function AltSystem:ToggleWindow() end end --- TRP3 toolbar button registration +-- Register as a TRP3 module so our onStart runs in the correct lifecycle phase +-- (after the toolbar module is initialized, but before WORKFLOW_ON_FINISH locks it) +function AltSystem:RegisterTRP3Module() + if not TRP3_API or not TRP3_API.module then + return + end + + local MODULE_STRUCTURE = { + ["name"] = "AltSystem", + ["description"] = "AltSystem rolling window toolbar button", + ["version"] = 1.000, + ["id"] = "altsystem_module", + ["onStart"] = function() + AltSystem:RegisterTRP3Button() + end, + ["minVersion"] = 3, + ["requiredDeps"] = { + {"trp3_tool_bar", 1}, + }, + } + + TRP3_API.module.registerModule(MODULE_STRUCTURE) +end + +-- TRP3 toolbar button registration (called from the TRP3 module onStart) function AltSystem:RegisterTRP3Button() if TRP3_API and TRP3_API.toolbar then local toolbarButton = { id = "altsystem_toolbar_btn", - icon = "Interface\\Icons\\INV_Misc_Dice_02", + icon = "Interface\\Icons\\INV_Misc_Dice_01", tooltip = "AltSystem", tooltipSub = "Open the AltSystem rolling window", onClick = function() AltSystem:ToggleWindow() end, + visible = 1, } TRP3_API.toolbar.toolbarAddButton(toolbarButton) end end --- Minimap button using LibDBIcon (lightweight fallback if not available) -function AltSystem:RegisterMinimapButton() - -- Simple minimap button without external libraries - local minimapButton = CreateFrame("Button", "AltSystemMinimapButton", Minimap) - minimapButton:SetSize(32, 32) - minimapButton:SetFrameStrata("MEDIUM") - minimapButton:SetFrameLevel(8) - minimapButton:SetPoint("TOPLEFT", Minimap, "TOPLEFT", 2, -2) - - local icon = minimapButton:CreateTexture(nil, "ARTWORK") - icon:SetTexture("Interface\\Icons\\INV_Misc_Dice_02") - icon:SetSize(20, 20) - icon:SetPoint("CENTER") - - local border = minimapButton:CreateTexture(nil, "OVERLAY") - border:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder") - border:SetSize(54, 54) - border:SetPoint("TOPLEFT", -2, 2) - - minimapButton:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight") - - minimapButton:SetScript("OnClick", function() - AltSystem:ToggleWindow() - end) - - minimapButton:SetScript("OnEnter", function(self) - GameTooltip:SetOwner(self, "ANCHOR_LEFT") - GameTooltip:AddLine("AltSystem") - GameTooltip:AddLine("Click to toggle the rolling window", 1, 1, 1) - GameTooltip:Show() - end) - - minimapButton:SetScript("OnLeave", function() - GameTooltip:Hide() - end) -end From 9e629aec31ea74079032b4eab464b566b3332b0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Sat, 11 Apr 2026 19:38:19 +0100 Subject: [PATCH 05/74] Skills from TRP profile --- .idea/vcs.xml | 6 ++++ Data.lua | 92 ++++++++++++++++++++++++++++++++++++++++++------ UI.lua | 75 ++++++++++++++++++++++++++++++++------- docs/2-skills.md | 12 ++++++- 4 files changed, 161 insertions(+), 24 deletions(-) create mode 100644 .idea/vcs.xml diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Data.lua b/Data.lua index ab7984a..1291929 100644 --- a/Data.lua +++ b/Data.lua @@ -1,5 +1,5 @@ -- AltSystem Data Definitions --- Contains dummy skill data, item options, defense options, and modifier constants. +-- Contains skill data (fetched from TRP3 profile), item options, defense options, and modifier constants. AltSystem = AltSystem or {} AltSystem.Data = {} @@ -12,18 +12,88 @@ AltSystem.Data.SkillLevels = { ["Master"] = 4, } --- Dummy skill data: each skill has a name, icon, and level -AltSystem.Data.Skills = { - { name = "Swordsmanship", icon = "Interface\\Icons\\INV_Sword_04", level = "Expert" }, - { name = "Archery", icon = "Interface\\Icons\\INV_Weapon_Bow_05", level = "Adept" }, - { name = "Fire Magic", icon = "Interface\\Icons\\Spell_Fire_FlameBolt", level = "Master" }, - { name = "Healing", icon = "Interface\\Icons\\Spell_Holy_FlashHeal", level = "Novice" }, - { name = "Stealth", icon = "Interface\\Icons\\Ability_Stealth", level = "Adept" }, +-- The "Unskilled" entry is always the first (default) skill +local UNSKILLED_ENTRY = { name = "Unskilled", level = "Unskilled", modifier = -4 } + +-- Default/fallback skill list used when no TRP3 profile skills are found +local DEFAULT_SKILLS = { + { name = "Unskilled", level = "Unskilled", modifier = -4 }, + { name = "Novice Skill", level = "Novice", modifier = -2 }, + { name = "Adept Skill", level = "Adept", modifier = 0 }, + { name = "Expert Skill", level = "Expert", modifier = 2 }, + { name = "Master Skill", level = "Master", modifier = 4 }, } --- Pre-compute modifier for each skill based on its level -for _, skill in ipairs(AltSystem.Data.Skills) do - skill.modifier = AltSystem.Data.SkillLevels[skill.level] or 0 +-- Parse the skill level from the right-side text of a TRP3 personality trait. +-- The level keyword must appear at the start of the string; anything after it is ignored. +-- Returns the level string and modifier, or nil if not recognized. +local function ParseSkillLevel(rightText) + if not rightText or rightText == "" then return nil, nil end + + -- Trim leading/trailing whitespace + local trimmed = rightText:match("^%s*(.-)%s*$") + if not trimmed or trimmed == "" then return nil, nil end + + -- Check if the text starts with a recognized level keyword + for _, levelName in ipairs({"Master", "Expert", "Adept", "Novice"}) do + if trimmed == levelName or trimmed:match("^" .. levelName .. "[%s%p]") then + return levelName, AltSystem.Data.SkillLevels[levelName] + end + end + + return nil, nil +end + +-- Fetch skills from the current TRP3 profile's personality traits. +-- Returns an array of skill entries, always starting with "Unskilled". +-- Falls back to the default list if no profile or no valid skills are found. +function AltSystem.Data:RefreshSkills() + local skills = {} + + -- Always add Unskilled as the first entry + table.insert(skills, { name = UNSKILLED_ENTRY.name, level = UNSKILLED_ENTRY.level, modifier = UNSKILLED_ENTRY.modifier }) + + local foundAny = false + + -- Try to read personality traits from the active TRP3 profile + if TRP3_API and TRP3_API.profile and TRP3_API.profile.getData then + local ok, characteristics = pcall(TRP3_API.profile.getData, "player/characteristics") + if ok and characteristics and characteristics.PS then + for _, trait in ipairs(characteristics.PS) do + local skillName = trait.LT + local rightText = trait.RT + + if skillName and skillName ~= "" then + local level, modifier = ParseSkillLevel(rightText) + if level and modifier then + foundAny = true + table.insert(skills, { + name = skillName, + level = level, + modifier = modifier, + }) + end + end + end + end + end + + -- If no valid skills were found, use the default fallback list (skip first "Unskilled" since we already added it) + if not foundAny then + skills = {} + for _, skill in ipairs(DEFAULT_SKILLS) do + table.insert(skills, { name = skill.name, level = skill.level, modifier = skill.modifier }) + end + end + + AltSystem.Data.Skills = skills + return skills +end + +-- Initialize with the default skill list +AltSystem.Data.Skills = {} +for _, skill in ipairs(DEFAULT_SKILLS) do + table.insert(AltSystem.Data.Skills, { name = skill.name, level = skill.level, modifier = skill.modifier }) end -- Item options: name and modifier (first entry = no item) diff --git a/UI.lua b/UI.lua index c5c4a69..5d64ea6 100644 --- a/UI.lua +++ b/UI.lua @@ -10,6 +10,22 @@ 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() + local options = {} + for _, skill in ipairs(AltSystem.Data.Skills) do + local sign = skill.modifier >= 0 and "+" or "" + local text + if skill.level == "Unskilled" 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 modern dropdown (WowStyle1DropdownTemplate) local function CreateDropdown(parent, name, yOffset, labelText, options, defaultIndex, onSelect) local label = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal") @@ -38,7 +54,7 @@ local function CreateDropdown(parent, name, yOffset, labelText, options, default end end) - return dropdown + return dropdown, function() return selectedIndex end, function(idx) selectedIndex = idx end end function AltSystem:CreateMainFrame() @@ -66,20 +82,19 @@ function AltSystem:CreateMainFrame() ------------------------- -- Skill dropdown ------------------------- - local skillOptions = {} - for _, skill in ipairs(AltSystem.Data.Skills) do - local levelMod = skill.modifier - local sign = levelMod >= 0 and "+" or "" - table.insert(skillOptions, { - text = skill.name .. " (" .. skill.level .. " " .. sign .. levelMod .. ")", - icon = skill.icon, - }) - end + local skillOptions = BuildSkillOptions() - CreateDropdown(f, "AltSystemSkillDropdown", yPos, "Skill:", skillOptions, AltSystem.State.selectedSkillIndex, + local skillDropdown, getSkillIndex, setSkillIndex = CreateDropdown( + f, "AltSystemSkillDropdown", yPos, "Skill:", skillOptions, + AltSystem.State.selectedSkillIndex, function(index) AltSystem.State.selectedSkillIndex = index end) + + -- Store references for refreshing + AltSystem.SkillDropdown = skillDropdown + AltSystem.GetSkillIndex = getSkillIndex + AltSystem.SetSkillIndex = setSkillIndex yPos = yPos - ROW_HEIGHT - 8 ------------------------- @@ -149,7 +164,7 @@ function AltSystem:CreateMainFrame() local attackBtn = CreateFrame("Button", "AltSystemAttackRollBtn", f, "UIPanelButtonTemplate") attackBtn:SetSize(btnWidth, 28) attackBtn:SetPoint("TOPLEFT", f, "TOPLEFT", PADDING, yPos) - attackBtn:SetText("Attack Roll") + attackBtn:SetText("Attack/Skill Roll") local defenseBtn = CreateFrame("Button", "AltSystemDefenseRollBtn", f, "UIPanelButtonTemplate") defenseBtn:SetSize(btnWidth, 28) @@ -182,9 +197,45 @@ function AltSystem:CreateMainFrame() AltSystem:PerformRoll("defense") end) + -- Refresh skills from TRP3 profile each time the window is shown + f:SetScript("OnShow", function() + AltSystem:RefreshSkillDropdown() + end) + f:Hide() AltSystem.MainFrame = f end +-- Refresh the skill dropdown with current TRP3 profile data +function AltSystem:RefreshSkillDropdown() + AltSystem.Data:RefreshSkills() + + -- Reset selection to 1 (Unskilled) since the skill list may have changed + AltSystem.State.selectedSkillIndex = 1 + if AltSystem.SetSkillIndex then + AltSystem.SetSkillIndex(1) + end + + -- Rebuild the dropdown menu with the new skill list + if AltSystem.SkillDropdown then + local skillOptions = BuildSkillOptions() + 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 + if AltSystem.SetSkillIndex then + AltSystem.SetSkillIndex(data) + end + end, + i + ) + end + end) + end +end + -- Create the frame on file load so it's ready when Init runs AltSystem:CreateMainFrame() diff --git a/docs/2-skills.md b/docs/2-skills.md index 9eae222..1086cb5 100644 --- a/docs/2-skills.md +++ b/docs/2-skills.md @@ -1 +1,11 @@ -# Feature: Skills \ No newline at end of file +# Feature: Skills +- The selectable skills should come from the currently active TRP profile +- The skills are defined in the TRP profile as 'Personality traits' +- For each trait: + - the left field represents the skill name + - the right field represents the skill level as a string (Novice, Adept, Expert, Master) + - should the right field have anything after the level, it is to be ignored + - should a skill have no recognizable level, it should be omitted from the list +- The list should have a default selected value of "Unskilled" which corresponds to a -4 modifier +- In case no skills are found in the profile, or no profile is selected, a default list should be displayed + - Unskilled, Novice Skill, Adept Skill, Expert Skill, Master Skill \ No newline at end of file From 4bd445392a343573cd56f5b16861163a76d7aa5c Mon Sep 17 00:00:00 2001 From: Rukira Date: Sat, 11 Apr 2026 19:38:19 +0100 Subject: [PATCH 06/74] Skills from TRP profile --- .idea/vcs.xml | 6 ++++ Data.lua | 92 ++++++++++++++++++++++++++++++++++++++++++------ UI.lua | 75 ++++++++++++++++++++++++++++++++------- docs/2-skills.md | 12 ++++++- 4 files changed, 161 insertions(+), 24 deletions(-) create mode 100644 .idea/vcs.xml diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Data.lua b/Data.lua index ab7984a..1291929 100644 --- a/Data.lua +++ b/Data.lua @@ -1,5 +1,5 @@ -- AltSystem Data Definitions --- Contains dummy skill data, item options, defense options, and modifier constants. +-- Contains skill data (fetched from TRP3 profile), item options, defense options, and modifier constants. AltSystem = AltSystem or {} AltSystem.Data = {} @@ -12,18 +12,88 @@ AltSystem.Data.SkillLevels = { ["Master"] = 4, } --- Dummy skill data: each skill has a name, icon, and level -AltSystem.Data.Skills = { - { name = "Swordsmanship", icon = "Interface\\Icons\\INV_Sword_04", level = "Expert" }, - { name = "Archery", icon = "Interface\\Icons\\INV_Weapon_Bow_05", level = "Adept" }, - { name = "Fire Magic", icon = "Interface\\Icons\\Spell_Fire_FlameBolt", level = "Master" }, - { name = "Healing", icon = "Interface\\Icons\\Spell_Holy_FlashHeal", level = "Novice" }, - { name = "Stealth", icon = "Interface\\Icons\\Ability_Stealth", level = "Adept" }, +-- The "Unskilled" entry is always the first (default) skill +local UNSKILLED_ENTRY = { name = "Unskilled", level = "Unskilled", modifier = -4 } + +-- Default/fallback skill list used when no TRP3 profile skills are found +local DEFAULT_SKILLS = { + { name = "Unskilled", level = "Unskilled", modifier = -4 }, + { name = "Novice Skill", level = "Novice", modifier = -2 }, + { name = "Adept Skill", level = "Adept", modifier = 0 }, + { name = "Expert Skill", level = "Expert", modifier = 2 }, + { name = "Master Skill", level = "Master", modifier = 4 }, } --- Pre-compute modifier for each skill based on its level -for _, skill in ipairs(AltSystem.Data.Skills) do - skill.modifier = AltSystem.Data.SkillLevels[skill.level] or 0 +-- Parse the skill level from the right-side text of a TRP3 personality trait. +-- The level keyword must appear at the start of the string; anything after it is ignored. +-- Returns the level string and modifier, or nil if not recognized. +local function ParseSkillLevel(rightText) + if not rightText or rightText == "" then return nil, nil end + + -- Trim leading/trailing whitespace + local trimmed = rightText:match("^%s*(.-)%s*$") + if not trimmed or trimmed == "" then return nil, nil end + + -- Check if the text starts with a recognized level keyword + for _, levelName in ipairs({"Master", "Expert", "Adept", "Novice"}) do + if trimmed == levelName or trimmed:match("^" .. levelName .. "[%s%p]") then + return levelName, AltSystem.Data.SkillLevels[levelName] + end + end + + return nil, nil +end + +-- Fetch skills from the current TRP3 profile's personality traits. +-- Returns an array of skill entries, always starting with "Unskilled". +-- Falls back to the default list if no profile or no valid skills are found. +function AltSystem.Data:RefreshSkills() + local skills = {} + + -- Always add Unskilled as the first entry + table.insert(skills, { name = UNSKILLED_ENTRY.name, level = UNSKILLED_ENTRY.level, modifier = UNSKILLED_ENTRY.modifier }) + + local foundAny = false + + -- Try to read personality traits from the active TRP3 profile + if TRP3_API and TRP3_API.profile and TRP3_API.profile.getData then + local ok, characteristics = pcall(TRP3_API.profile.getData, "player/characteristics") + if ok and characteristics and characteristics.PS then + for _, trait in ipairs(characteristics.PS) do + local skillName = trait.LT + local rightText = trait.RT + + if skillName and skillName ~= "" then + local level, modifier = ParseSkillLevel(rightText) + if level and modifier then + foundAny = true + table.insert(skills, { + name = skillName, + level = level, + modifier = modifier, + }) + end + end + end + end + end + + -- If no valid skills were found, use the default fallback list (skip first "Unskilled" since we already added it) + if not foundAny then + skills = {} + for _, skill in ipairs(DEFAULT_SKILLS) do + table.insert(skills, { name = skill.name, level = skill.level, modifier = skill.modifier }) + end + end + + AltSystem.Data.Skills = skills + return skills +end + +-- Initialize with the default skill list +AltSystem.Data.Skills = {} +for _, skill in ipairs(DEFAULT_SKILLS) do + table.insert(AltSystem.Data.Skills, { name = skill.name, level = skill.level, modifier = skill.modifier }) end -- Item options: name and modifier (first entry = no item) diff --git a/UI.lua b/UI.lua index c5c4a69..5d64ea6 100644 --- a/UI.lua +++ b/UI.lua @@ -10,6 +10,22 @@ 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() + local options = {} + for _, skill in ipairs(AltSystem.Data.Skills) do + local sign = skill.modifier >= 0 and "+" or "" + local text + if skill.level == "Unskilled" 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 modern dropdown (WowStyle1DropdownTemplate) local function CreateDropdown(parent, name, yOffset, labelText, options, defaultIndex, onSelect) local label = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal") @@ -38,7 +54,7 @@ local function CreateDropdown(parent, name, yOffset, labelText, options, default end end) - return dropdown + return dropdown, function() return selectedIndex end, function(idx) selectedIndex = idx end end function AltSystem:CreateMainFrame() @@ -66,20 +82,19 @@ function AltSystem:CreateMainFrame() ------------------------- -- Skill dropdown ------------------------- - local skillOptions = {} - for _, skill in ipairs(AltSystem.Data.Skills) do - local levelMod = skill.modifier - local sign = levelMod >= 0 and "+" or "" - table.insert(skillOptions, { - text = skill.name .. " (" .. skill.level .. " " .. sign .. levelMod .. ")", - icon = skill.icon, - }) - end + local skillOptions = BuildSkillOptions() - CreateDropdown(f, "AltSystemSkillDropdown", yPos, "Skill:", skillOptions, AltSystem.State.selectedSkillIndex, + local skillDropdown, getSkillIndex, setSkillIndex = CreateDropdown( + f, "AltSystemSkillDropdown", yPos, "Skill:", skillOptions, + AltSystem.State.selectedSkillIndex, function(index) AltSystem.State.selectedSkillIndex = index end) + + -- Store references for refreshing + AltSystem.SkillDropdown = skillDropdown + AltSystem.GetSkillIndex = getSkillIndex + AltSystem.SetSkillIndex = setSkillIndex yPos = yPos - ROW_HEIGHT - 8 ------------------------- @@ -149,7 +164,7 @@ function AltSystem:CreateMainFrame() local attackBtn = CreateFrame("Button", "AltSystemAttackRollBtn", f, "UIPanelButtonTemplate") attackBtn:SetSize(btnWidth, 28) attackBtn:SetPoint("TOPLEFT", f, "TOPLEFT", PADDING, yPos) - attackBtn:SetText("Attack Roll") + attackBtn:SetText("Attack/Skill Roll") local defenseBtn = CreateFrame("Button", "AltSystemDefenseRollBtn", f, "UIPanelButtonTemplate") defenseBtn:SetSize(btnWidth, 28) @@ -182,9 +197,45 @@ function AltSystem:CreateMainFrame() AltSystem:PerformRoll("defense") end) + -- Refresh skills from TRP3 profile each time the window is shown + f:SetScript("OnShow", function() + AltSystem:RefreshSkillDropdown() + end) + f:Hide() AltSystem.MainFrame = f end +-- Refresh the skill dropdown with current TRP3 profile data +function AltSystem:RefreshSkillDropdown() + AltSystem.Data:RefreshSkills() + + -- Reset selection to 1 (Unskilled) since the skill list may have changed + AltSystem.State.selectedSkillIndex = 1 + if AltSystem.SetSkillIndex then + AltSystem.SetSkillIndex(1) + end + + -- Rebuild the dropdown menu with the new skill list + if AltSystem.SkillDropdown then + local skillOptions = BuildSkillOptions() + 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 + if AltSystem.SetSkillIndex then + AltSystem.SetSkillIndex(data) + end + end, + i + ) + end + end) + end +end + -- Create the frame on file load so it's ready when Init runs AltSystem:CreateMainFrame() diff --git a/docs/2-skills.md b/docs/2-skills.md index 9eae222..1086cb5 100644 --- a/docs/2-skills.md +++ b/docs/2-skills.md @@ -1 +1,11 @@ -# Feature: Skills \ No newline at end of file +# Feature: Skills +- The selectable skills should come from the currently active TRP profile +- The skills are defined in the TRP profile as 'Personality traits' +- For each trait: + - the left field represents the skill name + - the right field represents the skill level as a string (Novice, Adept, Expert, Master) + - should the right field have anything after the level, it is to be ignored + - should a skill have no recognizable level, it should be omitted from the list +- The list should have a default selected value of "Unskilled" which corresponds to a -4 modifier +- In case no skills are found in the profile, or no profile is selected, a default list should be displayed + - Unskilled, Novice Skill, Adept Skill, Expert Skill, Master Skill \ No newline at end of file From 3e4ac9888a3c83f947e0fca010c82ae56533b340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Sat, 11 Apr 2026 19:43:11 +0100 Subject: [PATCH 07/74] Better skill detection --- Data.lua | 45 ++++++++++++++++++++++++++++----------------- docs/2-skills.md | 10 +++++++--- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/Data.lua b/Data.lua index 1291929..d95773d 100644 --- a/Data.lua +++ b/Data.lua @@ -24,24 +24,35 @@ local DEFAULT_SKILLS = { { name = "Master Skill", level = "Master", modifier = 4 }, } --- Parse the skill level from the right-side text of a TRP3 personality trait. --- The level keyword must appear at the start of the string; anything after it is ignored. --- Returns the level string and modifier, or nil if not recognized. -local function ParseSkillLevel(rightText) - if not rightText or rightText == "" then return nil, nil end +-- Valid skill level keywords that must appear in the trait's right field (RT) +local VALID_SKILL_KEYWORDS = { "Novice", "Adept", "Expert", "Master" } - -- Trim leading/trailing whitespace - local trimmed = rightText:match("^%s*(.-)%s*$") - if not trimmed or trimmed == "" then return nil, nil end - - -- Check if the text starts with a recognized level keyword - for _, levelName in ipairs({"Master", "Expert", "Adept", "Novice"}) do - if trimmed == levelName or trimmed:match("^" .. levelName .. "[%s%p]") then - return levelName, AltSystem.Data.SkillLevels[levelName] +-- Check if the trait's right text field contains a valid skill keyword. +-- Returns true if any keyword is found, false otherwise. +local function HasSkillKeyword(rightText) + if not rightText or rightText == "" then return false end + for _, keyword in ipairs(VALID_SKILL_KEYWORDS) do + if rightText:find(keyword) then + return true end end + return false +end - return nil, nil +-- Determine the skill level from the trait's numeric value (V2 field, 0-20 range). +-- Returns the level string and modifier, or nil if the value is 0 or absent. +local function ParseSkillLevel(numericValue) + if not numericValue or numericValue <= 0 then return nil, nil end + + if numericValue >= 20 then + return "Master", AltSystem.Data.SkillLevels["Master"] + elseif numericValue >= 11 then + return "Expert", AltSystem.Data.SkillLevels["Expert"] + elseif numericValue >= 6 then + return "Adept", AltSystem.Data.SkillLevels["Adept"] + else + return "Novice", AltSystem.Data.SkillLevels["Novice"] + end end -- Fetch skills from the current TRP3 profile's personality traits. @@ -61,10 +72,10 @@ function AltSystem.Data:RefreshSkills() if ok and characteristics and characteristics.PS then for _, trait in ipairs(characteristics.PS) do local skillName = trait.LT - local rightText = trait.RT + local numericValue = trait.V2 - if skillName and skillName ~= "" then - local level, modifier = ParseSkillLevel(rightText) + if skillName and skillName ~= "" and HasSkillKeyword(trait.RT) then + local level, modifier = ParseSkillLevel(numericValue) if level and modifier then foundAny = true table.insert(skills, { diff --git a/docs/2-skills.md b/docs/2-skills.md index 1086cb5..c7ec7fb 100644 --- a/docs/2-skills.md +++ b/docs/2-skills.md @@ -3,9 +3,13 @@ - The skills are defined in the TRP profile as 'Personality traits' - For each trait: - the left field represents the skill name - - the right field represents the skill level as a string (Novice, Adept, Expert, Master) - - should the right field have anything after the level, it is to be ignored - - should a skill have no recognizable level, it should be omitted from the list + - the right field must contain a valid skill keyword (Novice, Adept, Expert, Master) — traits without one of these keywords are omitted + - the first numeric value (V2) determines the skill level based on these ranges: + - Novice: 1-5 + - Adept: 6-10 + - Expert: 11-19 + - Master: 20 + - should a skill have a value of 0 or no value, it should be omitted from the list - The list should have a default selected value of "Unskilled" which corresponds to a -4 modifier - In case no skills are found in the profile, or no profile is selected, a default list should be displayed - Unskilled, Novice Skill, Adept Skill, Expert Skill, Master Skill \ No newline at end of file From 518389303ed916a7268559f6e0435ecf1b458ebd Mon Sep 17 00:00:00 2001 From: Rukira Date: Sat, 11 Apr 2026 19:43:11 +0100 Subject: [PATCH 08/74] Better skill detection --- Data.lua | 45 ++++++++++++++++++++++++++++----------------- docs/2-skills.md | 10 +++++++--- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/Data.lua b/Data.lua index 1291929..d95773d 100644 --- a/Data.lua +++ b/Data.lua @@ -24,24 +24,35 @@ local DEFAULT_SKILLS = { { name = "Master Skill", level = "Master", modifier = 4 }, } --- Parse the skill level from the right-side text of a TRP3 personality trait. --- The level keyword must appear at the start of the string; anything after it is ignored. --- Returns the level string and modifier, or nil if not recognized. -local function ParseSkillLevel(rightText) - if not rightText or rightText == "" then return nil, nil end +-- Valid skill level keywords that must appear in the trait's right field (RT) +local VALID_SKILL_KEYWORDS = { "Novice", "Adept", "Expert", "Master" } - -- Trim leading/trailing whitespace - local trimmed = rightText:match("^%s*(.-)%s*$") - if not trimmed or trimmed == "" then return nil, nil end - - -- Check if the text starts with a recognized level keyword - for _, levelName in ipairs({"Master", "Expert", "Adept", "Novice"}) do - if trimmed == levelName or trimmed:match("^" .. levelName .. "[%s%p]") then - return levelName, AltSystem.Data.SkillLevels[levelName] +-- Check if the trait's right text field contains a valid skill keyword. +-- Returns true if any keyword is found, false otherwise. +local function HasSkillKeyword(rightText) + if not rightText or rightText == "" then return false end + for _, keyword in ipairs(VALID_SKILL_KEYWORDS) do + if rightText:find(keyword) then + return true end end + return false +end - return nil, nil +-- Determine the skill level from the trait's numeric value (V2 field, 0-20 range). +-- Returns the level string and modifier, or nil if the value is 0 or absent. +local function ParseSkillLevel(numericValue) + if not numericValue or numericValue <= 0 then return nil, nil end + + if numericValue >= 20 then + return "Master", AltSystem.Data.SkillLevels["Master"] + elseif numericValue >= 11 then + return "Expert", AltSystem.Data.SkillLevels["Expert"] + elseif numericValue >= 6 then + return "Adept", AltSystem.Data.SkillLevels["Adept"] + else + return "Novice", AltSystem.Data.SkillLevels["Novice"] + end end -- Fetch skills from the current TRP3 profile's personality traits. @@ -61,10 +72,10 @@ function AltSystem.Data:RefreshSkills() if ok and characteristics and characteristics.PS then for _, trait in ipairs(characteristics.PS) do local skillName = trait.LT - local rightText = trait.RT + local numericValue = trait.V2 - if skillName and skillName ~= "" then - local level, modifier = ParseSkillLevel(rightText) + if skillName and skillName ~= "" and HasSkillKeyword(trait.RT) then + local level, modifier = ParseSkillLevel(numericValue) if level and modifier then foundAny = true table.insert(skills, { diff --git a/docs/2-skills.md b/docs/2-skills.md index 1086cb5..c7ec7fb 100644 --- a/docs/2-skills.md +++ b/docs/2-skills.md @@ -3,9 +3,13 @@ - The skills are defined in the TRP profile as 'Personality traits' - For each trait: - the left field represents the skill name - - the right field represents the skill level as a string (Novice, Adept, Expert, Master) - - should the right field have anything after the level, it is to be ignored - - should a skill have no recognizable level, it should be omitted from the list + - the right field must contain a valid skill keyword (Novice, Adept, Expert, Master) — traits without one of these keywords are omitted + - the first numeric value (V2) determines the skill level based on these ranges: + - Novice: 1-5 + - Adept: 6-10 + - Expert: 11-19 + - Master: 20 + - should a skill have a value of 0 or no value, it should be omitted from the list - The list should have a default selected value of "Unskilled" which corresponds to a -4 modifier - In case no skills are found in the profile, or no profile is selected, a default list should be displayed - Unskilled, Novice Skill, Adept Skill, Expert Skill, Master Skill \ No newline at end of file From 93cf0744087395f5b5881358db80bb3d8e3ccd2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Sat, 11 Apr 2026 19:46:25 +0100 Subject: [PATCH 09/74] Fixing icon --- Core.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Core.lua b/Core.lua index fff36a1..ead7cd8 100644 --- a/Core.lua +++ b/Core.lua @@ -69,11 +69,14 @@ function AltSystem:RegisterTRP3Button() if TRP3_API and TRP3_API.toolbar then local toolbarButton = { id = "altsystem_toolbar_btn", - icon = "Interface\\Icons\\INV_Misc_Dice_01", + icon = "INV_Misc_Dice_01", + configText = "AltSystem", tooltip = "AltSystem", tooltipSub = "Open the AltSystem rolling window", - onClick = function() - AltSystem:ToggleWindow() + onClick = function(_, _, button) + if button == "LeftButton" then + AltSystem:ToggleWindow() + end end, visible = 1, } From 102e725578ba5eb8cba0f1e597c55eea852eaeb8 Mon Sep 17 00:00:00 2001 From: Rukira Date: Sat, 11 Apr 2026 19:46:25 +0100 Subject: [PATCH 10/74] Fixing icon --- Core.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Core.lua b/Core.lua index fff36a1..ead7cd8 100644 --- a/Core.lua +++ b/Core.lua @@ -69,11 +69,14 @@ function AltSystem:RegisterTRP3Button() if TRP3_API and TRP3_API.toolbar then local toolbarButton = { id = "altsystem_toolbar_btn", - icon = "Interface\\Icons\\INV_Misc_Dice_01", + icon = "INV_Misc_Dice_01", + configText = "AltSystem", tooltip = "AltSystem", tooltipSub = "Open the AltSystem rolling window", - onClick = function() - AltSystem:ToggleWindow() + onClick = function(_, _, button) + if button == "LeftButton" then + AltSystem:ToggleWindow() + end end, visible = 1, } From 00d6d32e7ce0fc7113c4a8aac6eb623f66132cf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Sat, 11 Apr 2026 19:50:01 +0100 Subject: [PATCH 11/74] Install info --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 35b32f7..434c0a0 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,11 @@ The rolling system relies on TRP3 and TRP3:Enhanced addons, so these are explici - Main features defined in the /docs folder ## Installation + +1. Download the [latest release](https://git.asarius.site/rukira/AltSystem/releases/latest) ZIP file. +2. Extract the contents to your World of Warcraft AddOns folder. + +### Note To use AltSystem, ensure you have the following addons installed: - TRP3 - TRP3:Enhanced From fa445dd767d73a09b7939b2e8b75507e04d88c29 Mon Sep 17 00:00:00 2001 From: Rukira Date: Sat, 11 Apr 2026 19:50:01 +0100 Subject: [PATCH 12/74] Install info --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 35b32f7..434c0a0 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,11 @@ The rolling system relies on TRP3 and TRP3:Enhanced addons, so these are explici - Main features defined in the /docs folder ## Installation + +1. Download the [latest release](https://git.asarius.site/rukira/AltSystem/releases/latest) ZIP file. +2. Extract the contents to your World of Warcraft AddOns folder. + +### Note To use AltSystem, ensure you have the following addons installed: - TRP3 - TRP3:Enhanced From 39b273ad1cb00d6816e36349067350ada6c036d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Sat, 11 Apr 2026 19:51:02 +0100 Subject: [PATCH 13/74] TOC file update --- AltSystem.toc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AltSystem.toc b/AltSystem.toc index 57c8602..f72ae9f 100644 --- a/AltSystem.toc +++ b/AltSystem.toc @@ -1,8 +1,8 @@ ## Interface: 120001 ## Title: AltSystem ## Notes: Enhances RP gameplay with a custom rolling system -## Author: Goncalo -## Version: 1.0.0 +## Author: Rukira +## Version: 0.2 ## Dependencies: totalRP3, totalRP3_Extended ## SavedVariables: AltSystemDB From 35dce5dd78866671c5446a1bff5455efcd8fc31b Mon Sep 17 00:00:00 2001 From: Rukira Date: Sat, 11 Apr 2026 19:51:02 +0100 Subject: [PATCH 14/74] TOC file update --- AltSystem.toc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AltSystem.toc b/AltSystem.toc index 57c8602..f72ae9f 100644 --- a/AltSystem.toc +++ b/AltSystem.toc @@ -1,8 +1,8 @@ ## Interface: 120001 ## Title: AltSystem ## Notes: Enhances RP gameplay with a custom rolling system -## Author: Goncalo -## Version: 1.0.0 +## Author: Rukira +## Version: 0.2 ## Dependencies: totalRP3, totalRP3_Extended ## SavedVariables: AltSystemDB From e17370a89f90c66904938fef8e608e242a6ec893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Sat, 11 Apr 2026 20:27:46 +0100 Subject: [PATCH 15/74] Fixes --- Data.lua | 12 ++++++------ Roll.lua | 13 +++++++++++++ UI.lua | 4 ++-- docs/2-skills.md | 4 ++-- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/Data.lua b/Data.lua index d95773d..38ccf11 100644 --- a/Data.lua +++ b/Data.lua @@ -12,12 +12,12 @@ AltSystem.Data.SkillLevels = { ["Master"] = 4, } --- The "Unskilled" entry is always the first (default) skill -local UNSKILLED_ENTRY = { name = "Unskilled", level = "Unskilled", modifier = -4 } +-- The "Inept" entry is always the first (default) skill +local UNSKILLED_ENTRY = { name = "Inept", level = "Inept", modifier = -4 } -- Default/fallback skill list used when no TRP3 profile skills are found local DEFAULT_SKILLS = { - { name = "Unskilled", level = "Unskilled", modifier = -4 }, + { name = "Inept", level = "Inept", modifier = -4 }, { name = "Novice Skill", level = "Novice", modifier = -2 }, { name = "Adept Skill", level = "Adept", modifier = 0 }, { name = "Expert Skill", level = "Expert", modifier = 2 }, @@ -56,12 +56,12 @@ local function ParseSkillLevel(numericValue) end -- Fetch skills from the current TRP3 profile's personality traits. --- Returns an array of skill entries, always starting with "Unskilled". +-- Returns an array of skill entries, always starting with "Inept". -- Falls back to the default list if no profile or no valid skills are found. function AltSystem.Data:RefreshSkills() local skills = {} - -- Always add Unskilled as the first entry + -- Always add Inept as the first entry table.insert(skills, { name = UNSKILLED_ENTRY.name, level = UNSKILLED_ENTRY.level, modifier = UNSKILLED_ENTRY.modifier }) local foundAny = false @@ -89,7 +89,7 @@ function AltSystem.Data:RefreshSkills() end end - -- If no valid skills were found, use the default fallback list (skip first "Unskilled" since we already added it) + -- If no valid skills were found, use the default fallback list (skip first "Inept" since we already added it) if not foundAny then skills = {} for _, skill in ipairs(DEFAULT_SKILLS) do diff --git a/Roll.lua b/Roll.lua index 28f3752..aa1f97e 100644 --- a/Roll.lua +++ b/Roll.lua @@ -31,6 +31,19 @@ end) -- Calculate the final result based on the roll type and selected modifiers function AltSystem:CalculateAndDisplayResult(rollType, rollValue) + -- Critical rolls bypass normal calculation + if rollValue == 1 then + if AltSystem.ResultText then + AltSystem.ResultText:SetText("|cffff0000Critical Failure|r") + end + return + elseif rollValue == 20 then + if AltSystem.ResultText then + AltSystem.ResultText:SetText("|cff00ff00Critical Success|r") + end + return + end + local state = AltSystem.State local skill = AltSystem.Data.Skills[state.selectedSkillIndex] local item = AltSystem.Data.Items[state.selectedItemIndex] diff --git a/UI.lua b/UI.lua index 5d64ea6..cea1152 100644 --- a/UI.lua +++ b/UI.lua @@ -16,7 +16,7 @@ local function BuildSkillOptions() for _, skill in ipairs(AltSystem.Data.Skills) do local sign = skill.modifier >= 0 and "+" or "" local text - if skill.level == "Unskilled" then + if skill.level == "Inept" then text = skill.name .. " (" .. sign .. skill.modifier .. ")" else text = skill.name .. " (" .. skill.level .. " " .. sign .. skill.modifier .. ")" @@ -210,7 +210,7 @@ end function AltSystem:RefreshSkillDropdown() AltSystem.Data:RefreshSkills() - -- Reset selection to 1 (Unskilled) since the skill list may have changed + -- Reset selection to 1 (Inept) since the skill list may have changed AltSystem.State.selectedSkillIndex = 1 if AltSystem.SetSkillIndex then AltSystem.SetSkillIndex(1) diff --git a/docs/2-skills.md b/docs/2-skills.md index c7ec7fb..f118ffe 100644 --- a/docs/2-skills.md +++ b/docs/2-skills.md @@ -10,6 +10,6 @@ - Expert: 11-19 - Master: 20 - should a skill have a value of 0 or no value, it should be omitted from the list -- The list should have a default selected value of "Unskilled" which corresponds to a -4 modifier +- The list should have a default selected value of "Inept" which corresponds to a -4 modifier - In case no skills are found in the profile, or no profile is selected, a default list should be displayed - - Unskilled, Novice Skill, Adept Skill, Expert Skill, Master Skill \ No newline at end of file + - Inept, Novice Skill, Adept Skill, Expert Skill, Master Skill \ No newline at end of file From ed7a77e3f2e38a17f18baaeaa5ec7ecf82b88ec3 Mon Sep 17 00:00:00 2001 From: Rukira Date: Sat, 11 Apr 2026 20:27:46 +0100 Subject: [PATCH 16/74] Fixes --- Data.lua | 12 ++++++------ Roll.lua | 13 +++++++++++++ UI.lua | 4 ++-- docs/2-skills.md | 4 ++-- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/Data.lua b/Data.lua index d95773d..38ccf11 100644 --- a/Data.lua +++ b/Data.lua @@ -12,12 +12,12 @@ AltSystem.Data.SkillLevels = { ["Master"] = 4, } --- The "Unskilled" entry is always the first (default) skill -local UNSKILLED_ENTRY = { name = "Unskilled", level = "Unskilled", modifier = -4 } +-- The "Inept" entry is always the first (default) skill +local UNSKILLED_ENTRY = { name = "Inept", level = "Inept", modifier = -4 } -- Default/fallback skill list used when no TRP3 profile skills are found local DEFAULT_SKILLS = { - { name = "Unskilled", level = "Unskilled", modifier = -4 }, + { name = "Inept", level = "Inept", modifier = -4 }, { name = "Novice Skill", level = "Novice", modifier = -2 }, { name = "Adept Skill", level = "Adept", modifier = 0 }, { name = "Expert Skill", level = "Expert", modifier = 2 }, @@ -56,12 +56,12 @@ local function ParseSkillLevel(numericValue) end -- Fetch skills from the current TRP3 profile's personality traits. --- Returns an array of skill entries, always starting with "Unskilled". +-- Returns an array of skill entries, always starting with "Inept". -- Falls back to the default list if no profile or no valid skills are found. function AltSystem.Data:RefreshSkills() local skills = {} - -- Always add Unskilled as the first entry + -- Always add Inept as the first entry table.insert(skills, { name = UNSKILLED_ENTRY.name, level = UNSKILLED_ENTRY.level, modifier = UNSKILLED_ENTRY.modifier }) local foundAny = false @@ -89,7 +89,7 @@ function AltSystem.Data:RefreshSkills() end end - -- If no valid skills were found, use the default fallback list (skip first "Unskilled" since we already added it) + -- If no valid skills were found, use the default fallback list (skip first "Inept" since we already added it) if not foundAny then skills = {} for _, skill in ipairs(DEFAULT_SKILLS) do diff --git a/Roll.lua b/Roll.lua index 28f3752..aa1f97e 100644 --- a/Roll.lua +++ b/Roll.lua @@ -31,6 +31,19 @@ end) -- Calculate the final result based on the roll type and selected modifiers function AltSystem:CalculateAndDisplayResult(rollType, rollValue) + -- Critical rolls bypass normal calculation + if rollValue == 1 then + if AltSystem.ResultText then + AltSystem.ResultText:SetText("|cffff0000Critical Failure|r") + end + return + elseif rollValue == 20 then + if AltSystem.ResultText then + AltSystem.ResultText:SetText("|cff00ff00Critical Success|r") + end + return + end + local state = AltSystem.State local skill = AltSystem.Data.Skills[state.selectedSkillIndex] local item = AltSystem.Data.Items[state.selectedItemIndex] diff --git a/UI.lua b/UI.lua index 5d64ea6..cea1152 100644 --- a/UI.lua +++ b/UI.lua @@ -16,7 +16,7 @@ local function BuildSkillOptions() for _, skill in ipairs(AltSystem.Data.Skills) do local sign = skill.modifier >= 0 and "+" or "" local text - if skill.level == "Unskilled" then + if skill.level == "Inept" then text = skill.name .. " (" .. sign .. skill.modifier .. ")" else text = skill.name .. " (" .. skill.level .. " " .. sign .. skill.modifier .. ")" @@ -210,7 +210,7 @@ end function AltSystem:RefreshSkillDropdown() AltSystem.Data:RefreshSkills() - -- Reset selection to 1 (Unskilled) since the skill list may have changed + -- Reset selection to 1 (Inept) since the skill list may have changed AltSystem.State.selectedSkillIndex = 1 if AltSystem.SetSkillIndex then AltSystem.SetSkillIndex(1) diff --git a/docs/2-skills.md b/docs/2-skills.md index c7ec7fb..f118ffe 100644 --- a/docs/2-skills.md +++ b/docs/2-skills.md @@ -10,6 +10,6 @@ - Expert: 11-19 - Master: 20 - should a skill have a value of 0 or no value, it should be omitted from the list -- The list should have a default selected value of "Unskilled" which corresponds to a -4 modifier +- The list should have a default selected value of "Inept" which corresponds to a -4 modifier - In case no skills are found in the profile, or no profile is selected, a default list should be displayed - - Unskilled, Novice Skill, Adept Skill, Expert Skill, Master Skill \ No newline at end of file + - Inept, Novice Skill, Adept Skill, Expert Skill, Master Skill \ No newline at end of file From 907e0464eddb71d2780be6b8fa23752668b31748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Sun, 12 Apr 2026 20:48:49 +0100 Subject: [PATCH 17/74] Chat announce rolls --- AltSystem.toc | 2 +- Core.lua | 28 ++++++++++++++++++++ Roll.lua | 65 ++++++++++++++++++++++++++++++++++++++++++++++ UI.lua | 48 +++++++++++++++++++++++++++++++++- docs/3-announce.md | 13 ++++++++++ 5 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 docs/3-announce.md diff --git a/AltSystem.toc b/AltSystem.toc index f72ae9f..969c40b 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: 0.2 +## Version: 0.3 ## Dependencies: totalRP3, totalRP3_Extended ## SavedVariables: AltSystemDB diff --git a/Core.lua b/Core.lua index ead7cd8..c5bb3a8 100644 --- a/Core.lua +++ b/Core.lua @@ -7,6 +7,16 @@ AltSystem.State = { selectedItemIndex = 1, -- 1 = No item selectedDefenseIndex = 1, -- 1 = Base armor shieldEnabled = false, + announceEnabled = false, + announceChannelIndex = 1, -- 1 = Emote, 2 = Party, 3 = Raid, 4 = Guild +} + +-- Channel definitions for announcing rolls +AltSystem.AnnounceChannels = { + { name = "Emote (/e)", channel = "EMOTE" }, + { name = "Party (/p)", channel = "PARTY" }, + { name = "Raid (/ra)", channel = "RAID" }, + { name = "Guild (/g)", channel = "GUILD" }, } -- Initialization on ADDON_LOADED @@ -21,12 +31,30 @@ frame:SetScript("OnEvent", function(self, event, arg1) 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 + -- Register slash command /altsystem SLASH_ALTSYSTEM1 = "/altsystem" SlashCmdList["ALTSYSTEM"] = function() AltSystem:ToggleWindow() end + -- Save settings on logout + local saveFrame = CreateFrame("Frame") + saveFrame:RegisterEvent("PLAYER_LOGOUT") + saveFrame:SetScript("OnEvent", function() + AltSystemDB = AltSystemDB or {} + AltSystemDB.announceEnabled = AltSystem.State.announceEnabled + AltSystemDB.announceChannelIndex = AltSystem.State.announceChannelIndex + end) + print("|cff00ccffAltSystem|r loaded. Type /altsystem to open.") end diff --git a/Roll.lua b/Roll.lua index aa1f97e..cdfead6 100644 --- a/Roll.lua +++ b/Roll.lua @@ -5,6 +5,49 @@ AltSystem = AltSystem or {} local pendingRollType = nil -- "attack" or "defense" +-- Get the TRP3 character first name, falling back to the WoW unit name +local function GetCharacterName() + if TRP3_API and TRP3_API.profile and TRP3_API.profile.getData then + local ok, characteristics = pcall(TRP3_API.profile.getData, "player/characteristics") + if ok and characteristics and characteristics.FN and characteristics.FN ~= "" then + return characteristics.FN + end + end + return UnitName("player") +end + +-- Build the modifier description string for the announce message +local function BuildModifierString(modifiers) + local parts = {} + for _, mod in ipairs(modifiers) do + if mod.value ~= 0 then + local sign = mod.value >= 0 and "+" or "" + table.insert(parts, sign .. mod.value .. " (" .. mod.name .. ")") + end + end + return table.concat(parts, " ") +end + +-- Announce the roll result to the selected chat channel +local function AnnounceRoll(rollValue, modifiers, total) + if not AltSystem.State.announceEnabled then return end + + local channelDef = AltSystem.AnnounceChannels[AltSystem.State.announceChannelIndex] + if not channelDef then return end + + local name = GetCharacterName() + local modStr = BuildModifierString(modifiers) + + local msg + if modStr ~= "" then + msg = name .. " rolled " .. rollValue .. " " .. modStr .. " = " .. total + else + msg = name .. " rolled " .. rollValue .. " = " .. total + end + + SendChatMessage(msg, channelDef.channel) +end + -- Perform a roll: triggers the WoW native /roll 20 command function AltSystem:PerformRoll(rollType) pendingRollType = rollType @@ -36,11 +79,23 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) 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 + SendChatMessage(GetCharacterName() .. " rolled a Critical Failure!", channelDef.channel) + end + end 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 + SendChatMessage(GetCharacterName() .. " rolled a Critical Success!", channelDef.channel) + end + end return end @@ -54,13 +109,17 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) local total = rollValue local breakdown = "Roll: " .. rollValue + local modifiers = {} + if rollType == "attack" then -- Attack Roll = roll + skill modifier + item modifier total = rollValue + skillMod + itemMod breakdown = breakdown .. "\nSkill: " .. FormatModifier(skillMod) + table.insert(modifiers, { name = skill and skill.name or "Skill", value = skillMod }) if itemMod ~= 0 then breakdown = breakdown .. " | Item: " .. FormatModifier(itemMod) + table.insert(modifiers, { name = item and item.name or "Item", value = itemMod }) end breakdown = breakdown .. "\n|cffffd100Attack Total: " .. total .. "|r" @@ -73,12 +132,16 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) total = rollValue + skillMod + itemMod + defenseMod + shieldMod breakdown = breakdown .. "\nSkill: " .. FormatModifier(skillMod) + table.insert(modifiers, { name = skill and skill.name or "Skill", value = skillMod }) 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 shieldMod ~= 0 then breakdown = breakdown .. " | Shield: " .. FormatModifier(shieldMod) + table.insert(modifiers, { name = "Shield", value = shieldMod }) end breakdown = breakdown .. "\n|cff00ccffDefense Total: " .. total .. "|r" end @@ -86,6 +149,8 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) if AltSystem.ResultText then AltSystem.ResultText:SetText(breakdown) end + + AnnounceRoll(rollValue, modifiers, total) end -- Format a modifier value with sign diff --git a/UI.lua b/UI.lua index cea1152..74c45d4 100644 --- a/UI.lua +++ b/UI.lua @@ -5,7 +5,7 @@ AltSystem = AltSystem or {} local WINDOW_WIDTH = 300 -local WINDOW_HEIGHT = 380 +local WINDOW_HEIGHT = 440 local PADDING = 12 local ROW_HEIGHT = 30 local LABEL_WIDTH = 80 @@ -154,6 +154,52 @@ function AltSystem:CreateMainFrame() AltSystem.State.shieldEnabled = self:GetChecked() end) + yPos = yPos - ROW_HEIGHT - 8 + + ------------------------- + -- 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") + + 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(channelOptions, { 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) + + -- Show/hide channel dropdown based on checkbox state + local function UpdateChannelDropdownVisibility() + if AltSystem.State.announceEnabled then + channelDropdown:Show() + else + channelDropdown:Hide() + end + end + + announceCheck:SetScript("OnClick", function(self) + AltSystem.State.announceEnabled = self:GetChecked() + UpdateChannelDropdownVisibility() + end) + + UpdateChannelDropdownVisibility() + yPos = yPos - ROW_HEIGHT - 12 ------------------------- diff --git a/docs/3-announce.md b/docs/3-announce.md new file mode 100644 index 0000000..2961e7b --- /dev/null +++ b/docs/3-announce.md @@ -0,0 +1,13 @@ +# Feature: Announcing rolls +- There should be a setting to allow announcing rolls in chat +- This setting can be enabled or disabled through a checkbox +- When enabled, a dropdown selection of channels should be shown: + - Emote (/e) + - Party (/p) + - Raid (/ra) + - Guild (/g) +- Selection and enabled/disabled state should be saved +- When enabled, rolls should be announced in the selected channel in the following format: + - "[Name] rolled [result of d20] + [modifier] = [total]" + - Modifiers should be shown with names in parentheses, for example "-2 (First Aid) +2 (Item)" + - Name should be the TRP character first name \ No newline at end of file From 2b53cafc466e1800820a9e663d8a4c3a1e290e76 Mon Sep 17 00:00:00 2001 From: Rukira Date: Sun, 12 Apr 2026 20:48:49 +0100 Subject: [PATCH 18/74] Chat announce rolls --- AltSystem.toc | 2 +- Core.lua | 28 ++++++++++++++++++++ Roll.lua | 65 ++++++++++++++++++++++++++++++++++++++++++++++ UI.lua | 48 +++++++++++++++++++++++++++++++++- docs/3-announce.md | 13 ++++++++++ 5 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 docs/3-announce.md diff --git a/AltSystem.toc b/AltSystem.toc index f72ae9f..969c40b 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: 0.2 +## Version: 0.3 ## Dependencies: totalRP3, totalRP3_Extended ## SavedVariables: AltSystemDB diff --git a/Core.lua b/Core.lua index ead7cd8..c5bb3a8 100644 --- a/Core.lua +++ b/Core.lua @@ -7,6 +7,16 @@ AltSystem.State = { selectedItemIndex = 1, -- 1 = No item selectedDefenseIndex = 1, -- 1 = Base armor shieldEnabled = false, + announceEnabled = false, + announceChannelIndex = 1, -- 1 = Emote, 2 = Party, 3 = Raid, 4 = Guild +} + +-- Channel definitions for announcing rolls +AltSystem.AnnounceChannels = { + { name = "Emote (/e)", channel = "EMOTE" }, + { name = "Party (/p)", channel = "PARTY" }, + { name = "Raid (/ra)", channel = "RAID" }, + { name = "Guild (/g)", channel = "GUILD" }, } -- Initialization on ADDON_LOADED @@ -21,12 +31,30 @@ frame:SetScript("OnEvent", function(self, event, arg1) 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 + -- Register slash command /altsystem SLASH_ALTSYSTEM1 = "/altsystem" SlashCmdList["ALTSYSTEM"] = function() AltSystem:ToggleWindow() end + -- Save settings on logout + local saveFrame = CreateFrame("Frame") + saveFrame:RegisterEvent("PLAYER_LOGOUT") + saveFrame:SetScript("OnEvent", function() + AltSystemDB = AltSystemDB or {} + AltSystemDB.announceEnabled = AltSystem.State.announceEnabled + AltSystemDB.announceChannelIndex = AltSystem.State.announceChannelIndex + end) + print("|cff00ccffAltSystem|r loaded. Type /altsystem to open.") end diff --git a/Roll.lua b/Roll.lua index aa1f97e..cdfead6 100644 --- a/Roll.lua +++ b/Roll.lua @@ -5,6 +5,49 @@ AltSystem = AltSystem or {} local pendingRollType = nil -- "attack" or "defense" +-- Get the TRP3 character first name, falling back to the WoW unit name +local function GetCharacterName() + if TRP3_API and TRP3_API.profile and TRP3_API.profile.getData then + local ok, characteristics = pcall(TRP3_API.profile.getData, "player/characteristics") + if ok and characteristics and characteristics.FN and characteristics.FN ~= "" then + return characteristics.FN + end + end + return UnitName("player") +end + +-- Build the modifier description string for the announce message +local function BuildModifierString(modifiers) + local parts = {} + for _, mod in ipairs(modifiers) do + if mod.value ~= 0 then + local sign = mod.value >= 0 and "+" or "" + table.insert(parts, sign .. mod.value .. " (" .. mod.name .. ")") + end + end + return table.concat(parts, " ") +end + +-- Announce the roll result to the selected chat channel +local function AnnounceRoll(rollValue, modifiers, total) + if not AltSystem.State.announceEnabled then return end + + local channelDef = AltSystem.AnnounceChannels[AltSystem.State.announceChannelIndex] + if not channelDef then return end + + local name = GetCharacterName() + local modStr = BuildModifierString(modifiers) + + local msg + if modStr ~= "" then + msg = name .. " rolled " .. rollValue .. " " .. modStr .. " = " .. total + else + msg = name .. " rolled " .. rollValue .. " = " .. total + end + + SendChatMessage(msg, channelDef.channel) +end + -- Perform a roll: triggers the WoW native /roll 20 command function AltSystem:PerformRoll(rollType) pendingRollType = rollType @@ -36,11 +79,23 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) 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 + SendChatMessage(GetCharacterName() .. " rolled a Critical Failure!", channelDef.channel) + end + end 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 + SendChatMessage(GetCharacterName() .. " rolled a Critical Success!", channelDef.channel) + end + end return end @@ -54,13 +109,17 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) local total = rollValue local breakdown = "Roll: " .. rollValue + local modifiers = {} + if rollType == "attack" then -- Attack Roll = roll + skill modifier + item modifier total = rollValue + skillMod + itemMod breakdown = breakdown .. "\nSkill: " .. FormatModifier(skillMod) + table.insert(modifiers, { name = skill and skill.name or "Skill", value = skillMod }) if itemMod ~= 0 then breakdown = breakdown .. " | Item: " .. FormatModifier(itemMod) + table.insert(modifiers, { name = item and item.name or "Item", value = itemMod }) end breakdown = breakdown .. "\n|cffffd100Attack Total: " .. total .. "|r" @@ -73,12 +132,16 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) total = rollValue + skillMod + itemMod + defenseMod + shieldMod breakdown = breakdown .. "\nSkill: " .. FormatModifier(skillMod) + table.insert(modifiers, { name = skill and skill.name or "Skill", value = skillMod }) 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 shieldMod ~= 0 then breakdown = breakdown .. " | Shield: " .. FormatModifier(shieldMod) + table.insert(modifiers, { name = "Shield", value = shieldMod }) end breakdown = breakdown .. "\n|cff00ccffDefense Total: " .. total .. "|r" end @@ -86,6 +149,8 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) if AltSystem.ResultText then AltSystem.ResultText:SetText(breakdown) end + + AnnounceRoll(rollValue, modifiers, total) end -- Format a modifier value with sign diff --git a/UI.lua b/UI.lua index cea1152..74c45d4 100644 --- a/UI.lua +++ b/UI.lua @@ -5,7 +5,7 @@ AltSystem = AltSystem or {} local WINDOW_WIDTH = 300 -local WINDOW_HEIGHT = 380 +local WINDOW_HEIGHT = 440 local PADDING = 12 local ROW_HEIGHT = 30 local LABEL_WIDTH = 80 @@ -154,6 +154,52 @@ function AltSystem:CreateMainFrame() AltSystem.State.shieldEnabled = self:GetChecked() end) + yPos = yPos - ROW_HEIGHT - 8 + + ------------------------- + -- 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") + + 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(channelOptions, { 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) + + -- Show/hide channel dropdown based on checkbox state + local function UpdateChannelDropdownVisibility() + if AltSystem.State.announceEnabled then + channelDropdown:Show() + else + channelDropdown:Hide() + end + end + + announceCheck:SetScript("OnClick", function(self) + AltSystem.State.announceEnabled = self:GetChecked() + UpdateChannelDropdownVisibility() + end) + + UpdateChannelDropdownVisibility() + yPos = yPos - ROW_HEIGHT - 12 ------------------------- diff --git a/docs/3-announce.md b/docs/3-announce.md new file mode 100644 index 0000000..2961e7b --- /dev/null +++ b/docs/3-announce.md @@ -0,0 +1,13 @@ +# Feature: Announcing rolls +- There should be a setting to allow announcing rolls in chat +- This setting can be enabled or disabled through a checkbox +- When enabled, a dropdown selection of channels should be shown: + - Emote (/e) + - Party (/p) + - Raid (/ra) + - Guild (/g) +- Selection and enabled/disabled state should be saved +- When enabled, rolls should be announced in the selected channel in the following format: + - "[Name] rolled [result of d20] + [modifier] = [total]" + - Modifiers should be shown with names in parentheses, for example "-2 (First Aid) +2 (Item)" + - Name should be the TRP character first name \ No newline at end of file From 158c1742fe2bfcff855ebf617ea24d46a71fe212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Mon, 13 Apr 2026 14:10:52 +0100 Subject: [PATCH 19/74] Packaging script --- package.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100755 package.sh diff --git a/package.sh b/package.sh new file mode 100755 index 0000000..d3cdbae --- /dev/null +++ b/package.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +ADDON_NAME="AltSystem" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +OUTPUT_DIR="$(mktemp -d)" +OUTPUT_FILE="$OUTPUT_DIR/$ADDON_NAME.zip" + +cd "$SCRIPT_DIR/.." && \ +zip -r "$OUTPUT_FILE" "$ADDON_NAME" \ + -x "*/.DS_Store" \ + -x "*/__MACOSX/*" \ + -x "*/.git/*" \ + -x "*/.git" + +echo "$OUTPUT_FILE" From 0605098da566fddc34fcd0df694513c270c730b3 Mon Sep 17 00:00:00 2001 From: Rukira Date: Mon, 13 Apr 2026 14:10:52 +0100 Subject: [PATCH 20/74] Packaging script --- package.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100755 package.sh diff --git a/package.sh b/package.sh new file mode 100755 index 0000000..d3cdbae --- /dev/null +++ b/package.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +ADDON_NAME="AltSystem" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +OUTPUT_DIR="$(mktemp -d)" +OUTPUT_FILE="$OUTPUT_DIR/$ADDON_NAME.zip" + +cd "$SCRIPT_DIR/.." && \ +zip -r "$OUTPUT_FILE" "$ADDON_NAME" \ + -x "*/.DS_Store" \ + -x "*/__MACOSX/*" \ + -x "*/.git/*" \ + -x "*/.git" + +echo "$OUTPUT_FILE" From f8182ca30a8c7006642680a72f3f889c6fa4cce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Tue, 14 Apr 2026 00:44:08 +0100 Subject: [PATCH 21/74] Fixing emote announce while in raid --- Roll.lua | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Roll.lua b/Roll.lua index cdfead6..7b368b0 100644 --- a/Roll.lua +++ b/Roll.lua @@ -28,6 +28,20 @@ local function BuildModifierString(modifiers) return table.concat(parts, " ") end +-- Send a message to the given channel, handling EMOTE specially +-- to avoid the message being redirected to raid chat when in a raid group. +local function SendToChannel(msg, channel) + if channel == "EMOTE" then + local editBox = ChatFrame1 and ChatFrame1.editBox or ChatFrame1EditBox + if editBox then + ChatFrame_OpenChat("/e " .. msg, ChatFrame1) + ChatEdit_SendText(editBox) + end + return + end + SendChatMessage(msg, channel) +end + -- Announce the roll result to the selected chat channel local function AnnounceRoll(rollValue, modifiers, total) if not AltSystem.State.announceEnabled then return end @@ -45,7 +59,7 @@ local function AnnounceRoll(rollValue, modifiers, total) msg = name .. " rolled " .. rollValue .. " = " .. total end - SendChatMessage(msg, channelDef.channel) + SendToChannel(msg, channelDef.channel) end -- Perform a roll: triggers the WoW native /roll 20 command @@ -82,7 +96,7 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) if AltSystem.State.announceEnabled then local channelDef = AltSystem.AnnounceChannels[AltSystem.State.announceChannelIndex] if channelDef then - SendChatMessage(GetCharacterName() .. " rolled a Critical Failure!", channelDef.channel) + SendToChannel(GetCharacterName() .. " rolled a Critical Failure!", channelDef.channel) end end return @@ -93,7 +107,7 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) if AltSystem.State.announceEnabled then local channelDef = AltSystem.AnnounceChannels[AltSystem.State.announceChannelIndex] if channelDef then - SendChatMessage(GetCharacterName() .. " rolled a Critical Success!", channelDef.channel) + SendToChannel(GetCharacterName() .. " rolled a Critical Success!", channelDef.channel) end end return From a732383c8ecee52c6931825515a7c4e440fb978a Mon Sep 17 00:00:00 2001 From: Rukira Date: Tue, 14 Apr 2026 00:44:08 +0100 Subject: [PATCH 22/74] Fixing emote announce while in raid --- Roll.lua | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Roll.lua b/Roll.lua index cdfead6..7b368b0 100644 --- a/Roll.lua +++ b/Roll.lua @@ -28,6 +28,20 @@ local function BuildModifierString(modifiers) return table.concat(parts, " ") end +-- Send a message to the given channel, handling EMOTE specially +-- to avoid the message being redirected to raid chat when in a raid group. +local function SendToChannel(msg, channel) + if channel == "EMOTE" then + local editBox = ChatFrame1 and ChatFrame1.editBox or ChatFrame1EditBox + if editBox then + ChatFrame_OpenChat("/e " .. msg, ChatFrame1) + ChatEdit_SendText(editBox) + end + return + end + SendChatMessage(msg, channel) +end + -- Announce the roll result to the selected chat channel local function AnnounceRoll(rollValue, modifiers, total) if not AltSystem.State.announceEnabled then return end @@ -45,7 +59,7 @@ local function AnnounceRoll(rollValue, modifiers, total) msg = name .. " rolled " .. rollValue .. " = " .. total end - SendChatMessage(msg, channelDef.channel) + SendToChannel(msg, channelDef.channel) end -- Perform a roll: triggers the WoW native /roll 20 command @@ -82,7 +96,7 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) if AltSystem.State.announceEnabled then local channelDef = AltSystem.AnnounceChannels[AltSystem.State.announceChannelIndex] if channelDef then - SendChatMessage(GetCharacterName() .. " rolled a Critical Failure!", channelDef.channel) + SendToChannel(GetCharacterName() .. " rolled a Critical Failure!", channelDef.channel) end end return @@ -93,7 +107,7 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) if AltSystem.State.announceEnabled then local channelDef = AltSystem.AnnounceChannels[AltSystem.State.announceChannelIndex] if channelDef then - SendChatMessage(GetCharacterName() .. " rolled a Critical Success!", channelDef.channel) + SendToChannel(GetCharacterName() .. " rolled a Critical Success!", channelDef.channel) end end return From 5a0d0f2ca49948610d8055bfd86cfe37ae1ddf5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Tue, 14 Apr 2026 00:45:30 +0100 Subject: [PATCH 23/74] Showing +0 mods in announce message --- Roll.lua | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Roll.lua b/Roll.lua index 7b368b0..9fb29c5 100644 --- a/Roll.lua +++ b/Roll.lua @@ -20,10 +20,8 @@ end local function BuildModifierString(modifiers) local parts = {} for _, mod in ipairs(modifiers) do - if mod.value ~= 0 then - local sign = mod.value >= 0 and "+" or "" - table.insert(parts, sign .. mod.value .. " (" .. mod.name .. ")") - end + local sign = mod.value >= 0 and "+" or "" + table.insert(parts, sign .. mod.value .. " (" .. mod.name .. ")") end return table.concat(parts, " ") end From e91dac028d5324aaa53cc5cf00b2525ddedbf18d Mon Sep 17 00:00:00 2001 From: Rukira Date: Tue, 14 Apr 2026 00:45:30 +0100 Subject: [PATCH 24/74] Showing +0 mods in announce message --- Roll.lua | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Roll.lua b/Roll.lua index 7b368b0..9fb29c5 100644 --- a/Roll.lua +++ b/Roll.lua @@ -20,10 +20,8 @@ end local function BuildModifierString(modifiers) local parts = {} for _, mod in ipairs(modifiers) do - if mod.value ~= 0 then - local sign = mod.value >= 0 and "+" or "" - table.insert(parts, sign .. mod.value .. " (" .. mod.name .. ")") - end + local sign = mod.value >= 0 and "+" or "" + table.insert(parts, sign .. mod.value .. " (" .. mod.name .. ")") end return table.concat(parts, " ") end From 865404ffb75d5761fda5c8ac6863fe5cc86c89f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Tue, 14 Apr 2026 00:49:04 +0100 Subject: [PATCH 25/74] Add a 'base roll' skill --- Data.lua | 13 +++++++++---- Roll.lua | 14 ++++++++++---- UI.lua | 2 +- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/Data.lua b/Data.lua index 38ccf11..6dc2a35 100644 --- a/Data.lua +++ b/Data.lua @@ -12,11 +12,15 @@ AltSystem.Data.SkillLevels = { ["Master"] = 4, } --- The "Inept" entry is always the first (default) skill +-- The "Base roll" entry is always the first (default) skill +local BASE_ROLL_ENTRY = { name = "Base roll", level = "Base", modifier = 0 } + +-- The "Inept" entry is always the second skill local UNSKILLED_ENTRY = { name = "Inept", level = "Inept", modifier = -4 } -- Default/fallback skill list used when no TRP3 profile skills are found local DEFAULT_SKILLS = { + { name = "Base roll", level = "Base", modifier = 0 }, { name = "Inept", level = "Inept", modifier = -4 }, { name = "Novice Skill", level = "Novice", modifier = -2 }, { name = "Adept Skill", level = "Adept", modifier = 0 }, @@ -56,12 +60,13 @@ local function ParseSkillLevel(numericValue) end -- Fetch skills from the current TRP3 profile's personality traits. --- Returns an array of skill entries, always starting with "Inept". +-- Returns an array of skill entries, always starting with "Base roll" and "Inept". -- Falls back to the default list if no profile or no valid skills are found. function AltSystem.Data:RefreshSkills() local skills = {} - -- Always add Inept as the first entry + -- Always add Base roll as the first entry, then Inept + table.insert(skills, { name = BASE_ROLL_ENTRY.name, level = BASE_ROLL_ENTRY.level, modifier = BASE_ROLL_ENTRY.modifier }) table.insert(skills, { name = UNSKILLED_ENTRY.name, level = UNSKILLED_ENTRY.level, modifier = UNSKILLED_ENTRY.modifier }) local foundAny = false @@ -89,7 +94,7 @@ function AltSystem.Data:RefreshSkills() end end - -- If no valid skills were found, use the default fallback list (skip first "Inept" since we already added it) + -- If no valid skills were found, use the default fallback list if not foundAny then skills = {} for _, skill in ipairs(DEFAULT_SKILLS) do diff --git a/Roll.lua b/Roll.lua index 9fb29c5..49e890c 100644 --- a/Roll.lua +++ b/Roll.lua @@ -123,12 +123,16 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) local modifiers = {} + local isBaseRoll = skill and skill.name == "Base roll" + if rollType == "attack" then -- Attack Roll = roll + skill modifier + item modifier total = rollValue + skillMod + itemMod - breakdown = breakdown .. "\nSkill: " .. FormatModifier(skillMod) - table.insert(modifiers, { name = skill and skill.name or "Skill", value = skillMod }) + 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 }) @@ -143,8 +147,10 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) total = rollValue + skillMod + itemMod + defenseMod + shieldMod - breakdown = breakdown .. "\nSkill: " .. FormatModifier(skillMod) - table.insert(modifiers, { name = skill and skill.name or "Skill", value = skillMod }) + 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 }) diff --git a/UI.lua b/UI.lua index 74c45d4..8b7e8d8 100644 --- a/UI.lua +++ b/UI.lua @@ -16,7 +16,7 @@ local function BuildSkillOptions() for _, skill in ipairs(AltSystem.Data.Skills) do local sign = skill.modifier >= 0 and "+" or "" local text - if skill.level == "Inept" then + if skill.level == "Base" or skill.level == "Inept" then text = skill.name .. " (" .. sign .. skill.modifier .. ")" else text = skill.name .. " (" .. skill.level .. " " .. sign .. skill.modifier .. ")" From c3994187dfd9e41e2321ef5db3ff35f79b7f23bf Mon Sep 17 00:00:00 2001 From: Rukira Date: Tue, 14 Apr 2026 00:49:04 +0100 Subject: [PATCH 26/74] Add a 'base roll' skill --- Data.lua | 13 +++++++++---- Roll.lua | 14 ++++++++++---- UI.lua | 2 +- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/Data.lua b/Data.lua index 38ccf11..6dc2a35 100644 --- a/Data.lua +++ b/Data.lua @@ -12,11 +12,15 @@ AltSystem.Data.SkillLevels = { ["Master"] = 4, } --- The "Inept" entry is always the first (default) skill +-- The "Base roll" entry is always the first (default) skill +local BASE_ROLL_ENTRY = { name = "Base roll", level = "Base", modifier = 0 } + +-- The "Inept" entry is always the second skill local UNSKILLED_ENTRY = { name = "Inept", level = "Inept", modifier = -4 } -- Default/fallback skill list used when no TRP3 profile skills are found local DEFAULT_SKILLS = { + { name = "Base roll", level = "Base", modifier = 0 }, { name = "Inept", level = "Inept", modifier = -4 }, { name = "Novice Skill", level = "Novice", modifier = -2 }, { name = "Adept Skill", level = "Adept", modifier = 0 }, @@ -56,12 +60,13 @@ local function ParseSkillLevel(numericValue) end -- Fetch skills from the current TRP3 profile's personality traits. --- Returns an array of skill entries, always starting with "Inept". +-- Returns an array of skill entries, always starting with "Base roll" and "Inept". -- Falls back to the default list if no profile or no valid skills are found. function AltSystem.Data:RefreshSkills() local skills = {} - -- Always add Inept as the first entry + -- Always add Base roll as the first entry, then Inept + table.insert(skills, { name = BASE_ROLL_ENTRY.name, level = BASE_ROLL_ENTRY.level, modifier = BASE_ROLL_ENTRY.modifier }) table.insert(skills, { name = UNSKILLED_ENTRY.name, level = UNSKILLED_ENTRY.level, modifier = UNSKILLED_ENTRY.modifier }) local foundAny = false @@ -89,7 +94,7 @@ function AltSystem.Data:RefreshSkills() end end - -- If no valid skills were found, use the default fallback list (skip first "Inept" since we already added it) + -- If no valid skills were found, use the default fallback list if not foundAny then skills = {} for _, skill in ipairs(DEFAULT_SKILLS) do diff --git a/Roll.lua b/Roll.lua index 9fb29c5..49e890c 100644 --- a/Roll.lua +++ b/Roll.lua @@ -123,12 +123,16 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) local modifiers = {} + local isBaseRoll = skill and skill.name == "Base roll" + if rollType == "attack" then -- Attack Roll = roll + skill modifier + item modifier total = rollValue + skillMod + itemMod - breakdown = breakdown .. "\nSkill: " .. FormatModifier(skillMod) - table.insert(modifiers, { name = skill and skill.name or "Skill", value = skillMod }) + 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 }) @@ -143,8 +147,10 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) total = rollValue + skillMod + itemMod + defenseMod + shieldMod - breakdown = breakdown .. "\nSkill: " .. FormatModifier(skillMod) - table.insert(modifiers, { name = skill and skill.name or "Skill", value = skillMod }) + 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 }) diff --git a/UI.lua b/UI.lua index 74c45d4..8b7e8d8 100644 --- a/UI.lua +++ b/UI.lua @@ -16,7 +16,7 @@ local function BuildSkillOptions() for _, skill in ipairs(AltSystem.Data.Skills) do local sign = skill.modifier >= 0 and "+" or "" local text - if skill.level == "Inept" then + if skill.level == "Base" or skill.level == "Inept" then text = skill.name .. " (" .. sign .. skill.modifier .. ")" else text = skill.name .. " (" .. skill.level .. " " .. sign .. skill.modifier .. ")" From c4075d1ee906d863dbab86775ffa35491cf25a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Tue, 14 Apr 2026 01:05:11 +0100 Subject: [PATCH 27/74] More fixes to emote channel --- AltSystem.toc | 2 +- Roll.lua | 14 ++++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/AltSystem.toc b/AltSystem.toc index 969c40b..3be4abb 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: 0.3 +## Version: 0.4 ## Dependencies: totalRP3, totalRP3_Extended ## SavedVariables: AltSystemDB diff --git a/Roll.lua b/Roll.lua index 49e890c..e632497 100644 --- a/Roll.lua +++ b/Roll.lua @@ -26,17 +26,11 @@ local function BuildModifierString(modifiers) return table.concat(parts, " ") end --- Send a message to the given channel, handling EMOTE specially --- to avoid the message being redirected to raid chat when in a raid group. +-- 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) - if channel == "EMOTE" then - local editBox = ChatFrame1 and ChatFrame1.editBox or ChatFrame1EditBox - if editBox then - ChatFrame_OpenChat("/e " .. msg, ChatFrame1) - ChatEdit_SendText(editBox) - end - return - end SendChatMessage(msg, channel) end From 2250de7d1909971cdd364b621d2ea5535f036a26 Mon Sep 17 00:00:00 2001 From: Rukira Date: Tue, 14 Apr 2026 01:05:11 +0100 Subject: [PATCH 28/74] More fixes to emote channel --- AltSystem.toc | 2 +- Roll.lua | 14 ++++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/AltSystem.toc b/AltSystem.toc index 969c40b..3be4abb 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: 0.3 +## Version: 0.4 ## Dependencies: totalRP3, totalRP3_Extended ## SavedVariables: AltSystemDB diff --git a/Roll.lua b/Roll.lua index 49e890c..e632497 100644 --- a/Roll.lua +++ b/Roll.lua @@ -26,17 +26,11 @@ local function BuildModifierString(modifiers) return table.concat(parts, " ") end --- Send a message to the given channel, handling EMOTE specially --- to avoid the message being redirected to raid chat when in a raid group. +-- 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) - if channel == "EMOTE" then - local editBox = ChatFrame1 and ChatFrame1.editBox or ChatFrame1EditBox - if editBox then - ChatFrame_OpenChat("/e " .. msg, ChatFrame1) - ChatEdit_SendText(editBox) - end - return - end SendChatMessage(msg, channel) end From 88f09f4fe48c26884a2e10613c0efd10c70203ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Tue, 14 Apr 2026 01:08:58 +0100 Subject: [PATCH 29/74] Fix emote announce text --- Roll.lua | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/Roll.lua b/Roll.lua index e632497..71b4438 100644 --- a/Roll.lua +++ b/Roll.lua @@ -41,14 +41,23 @@ local function AnnounceRoll(rollValue, modifiers, total) local channelDef = AltSystem.AnnounceChannels[AltSystem.State.announceChannelIndex] if not channelDef then return end - local name = GetCharacterName() + local isEmote = channelDef.channel == "EMOTE" local modStr = BuildModifierString(modifiers) local msg - if modStr ~= "" then - msg = name .. " rolled " .. rollValue .. " " .. modStr .. " = " .. total + if isEmote then + if modStr ~= "" then + msg = "rolled " .. rollValue .. " " .. modStr .. " = " .. total + else + msg = "rolled " .. rollValue .. " = " .. total + end else - msg = name .. " rolled " .. rollValue .. " = " .. total + local name = GetCharacterName() + if modStr ~= "" then + msg = name .. " rolled " .. rollValue .. " " .. modStr .. " = " .. total + else + msg = name .. " rolled " .. rollValue .. " = " .. total + end end SendToChannel(msg, channelDef.channel) @@ -88,7 +97,10 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) if AltSystem.State.announceEnabled then local channelDef = AltSystem.AnnounceChannels[AltSystem.State.announceChannelIndex] if channelDef then - SendToChannel(GetCharacterName() .. " rolled a Critical Failure!", channelDef.channel) + local critMsg = channelDef.channel == "EMOTE" + and "rolled a Critical Failure!" + or (GetCharacterName() .. " rolled a Critical Failure!") + SendToChannel(critMsg, channelDef.channel) end end return @@ -99,7 +111,10 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) if AltSystem.State.announceEnabled then local channelDef = AltSystem.AnnounceChannels[AltSystem.State.announceChannelIndex] if channelDef then - SendToChannel(GetCharacterName() .. " rolled a Critical Success!", channelDef.channel) + local critMsg = channelDef.channel == "EMOTE" + and "rolled a Critical Success!" + or (GetCharacterName() .. " rolled a Critical Success!") + SendToChannel(critMsg, channelDef.channel) end end return From ed3e3d21f4de8a9288cd64a30e7292ccbc785d85 Mon Sep 17 00:00:00 2001 From: Rukira Date: Tue, 14 Apr 2026 01:08:58 +0100 Subject: [PATCH 30/74] Fix emote announce text --- Roll.lua | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/Roll.lua b/Roll.lua index e632497..71b4438 100644 --- a/Roll.lua +++ b/Roll.lua @@ -41,14 +41,23 @@ local function AnnounceRoll(rollValue, modifiers, total) local channelDef = AltSystem.AnnounceChannels[AltSystem.State.announceChannelIndex] if not channelDef then return end - local name = GetCharacterName() + local isEmote = channelDef.channel == "EMOTE" local modStr = BuildModifierString(modifiers) local msg - if modStr ~= "" then - msg = name .. " rolled " .. rollValue .. " " .. modStr .. " = " .. total + if isEmote then + if modStr ~= "" then + msg = "rolled " .. rollValue .. " " .. modStr .. " = " .. total + else + msg = "rolled " .. rollValue .. " = " .. total + end else - msg = name .. " rolled " .. rollValue .. " = " .. total + local name = GetCharacterName() + if modStr ~= "" then + msg = name .. " rolled " .. rollValue .. " " .. modStr .. " = " .. total + else + msg = name .. " rolled " .. rollValue .. " = " .. total + end end SendToChannel(msg, channelDef.channel) @@ -88,7 +97,10 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) if AltSystem.State.announceEnabled then local channelDef = AltSystem.AnnounceChannels[AltSystem.State.announceChannelIndex] if channelDef then - SendToChannel(GetCharacterName() .. " rolled a Critical Failure!", channelDef.channel) + local critMsg = channelDef.channel == "EMOTE" + and "rolled a Critical Failure!" + or (GetCharacterName() .. " rolled a Critical Failure!") + SendToChannel(critMsg, channelDef.channel) end end return @@ -99,7 +111,10 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) if AltSystem.State.announceEnabled then local channelDef = AltSystem.AnnounceChannels[AltSystem.State.announceChannelIndex] if channelDef then - SendToChannel(GetCharacterName() .. " rolled a Critical Success!", channelDef.channel) + local critMsg = channelDef.channel == "EMOTE" + and "rolled a Critical Success!" + or (GetCharacterName() .. " rolled a Critical Success!") + SendToChannel(critMsg, channelDef.channel) end end return From 2c878669b6697cc0a60596beee3c6b58247b4227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Tue, 14 Apr 2026 01:12:51 +0100 Subject: [PATCH 31/74] Improved packaging script --- package.sh | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/package.sh b/package.sh index d3cdbae..6febfa8 100755 --- a/package.sh +++ b/package.sh @@ -4,12 +4,14 @@ ADDON_NAME="AltSystem" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" OUTPUT_DIR="$(mktemp -d)" OUTPUT_FILE="$OUTPUT_DIR/$ADDON_NAME.zip" +STAGING_DIR="$(mktemp -d)" -cd "$SCRIPT_DIR/.." && \ -zip -r "$OUTPUT_FILE" "$ADDON_NAME" \ - -x "*/.DS_Store" \ - -x "*/__MACOSX/*" \ - -x "*/.git/*" \ - -x "*/.git" +mkdir -p "$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" + +rm -rf "$STAGING_DIR" echo "$OUTPUT_FILE" From 287c03570de1c0bf07a0046ec8c0a16409402fe2 Mon Sep 17 00:00:00 2001 From: Rukira Date: Tue, 14 Apr 2026 01:12:51 +0100 Subject: [PATCH 32/74] Improved packaging script --- package.sh | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/package.sh b/package.sh index d3cdbae..6febfa8 100755 --- a/package.sh +++ b/package.sh @@ -4,12 +4,14 @@ ADDON_NAME="AltSystem" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" OUTPUT_DIR="$(mktemp -d)" OUTPUT_FILE="$OUTPUT_DIR/$ADDON_NAME.zip" +STAGING_DIR="$(mktemp -d)" -cd "$SCRIPT_DIR/.." && \ -zip -r "$OUTPUT_FILE" "$ADDON_NAME" \ - -x "*/.DS_Store" \ - -x "*/__MACOSX/*" \ - -x "*/.git/*" \ - -x "*/.git" +mkdir -p "$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" + +rm -rf "$STAGING_DIR" echo "$OUTPUT_FILE" From 52c27010751d370709730d43ad505100dc2ecb5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Wed, 22 Apr 2026 16:16:31 +0100 Subject: [PATCH 33/74] TOC bump --- AltSystem.toc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AltSystem.toc b/AltSystem.toc index 3be4abb..031f3db 100644 --- a/AltSystem.toc +++ b/AltSystem.toc @@ -1,4 +1,4 @@ -## Interface: 120001 +## Interface: 120005 ## Title: AltSystem ## Notes: Enhances RP gameplay with a custom rolling system ## Author: Rukira From c210078e675c97984691d26f6d15294ed12ca08d Mon Sep 17 00:00:00 2001 From: Rukira Date: Wed, 22 Apr 2026 16:16:31 +0100 Subject: [PATCH 34/74] TOC bump --- AltSystem.toc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AltSystem.toc b/AltSystem.toc index 3be4abb..031f3db 100644 --- a/AltSystem.toc +++ b/AltSystem.toc @@ -1,4 +1,4 @@ -## Interface: 120001 +## Interface: 120005 ## Title: AltSystem ## Notes: Enhances RP gameplay with a custom rolling system ## Author: Rukira From af5a3d2d41a5c6844fa1934b52068891550dfb57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Wed, 22 Apr 2026 16:17:06 +0100 Subject: [PATCH 35/74] TOC bump --- AltSystem.toc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AltSystem.toc b/AltSystem.toc index 031f3db..3e5feca 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: 0.4 +## Version: 0.5 ## Dependencies: totalRP3, totalRP3_Extended ## SavedVariables: AltSystemDB From 77965acd21bec5146a2ae5323885d382e4283558 Mon Sep 17 00:00:00 2001 From: Rukira Date: Wed, 22 Apr 2026 16:17:06 +0100 Subject: [PATCH 36/74] TOC bump --- AltSystem.toc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AltSystem.toc b/AltSystem.toc index 031f3db..3e5feca 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: 0.4 +## Version: 0.5 ## Dependencies: totalRP3, totalRP3_Extended ## SavedVariables: AltSystemDB From 961d642018cf5e9a40293a1756225670d9de46a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Fri, 8 May 2026 16:14:45 +0100 Subject: [PATCH 37/74] Inept skill level --- AltSystem.toc | 2 +- Data.lua | 27 +++++++++++++-------------- UI.lua | 2 +- docs/2-skills.md | 6 +++--- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/AltSystem.toc b/AltSystem.toc index 3e5feca..b304469 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: 0.5 +## Version: 0.6 ## Dependencies: totalRP3, totalRP3_Extended ## SavedVariables: AltSystemDB diff --git a/Data.lua b/Data.lua index 6dc2a35..164eae0 100644 --- a/Data.lua +++ b/Data.lua @@ -6,6 +6,7 @@ AltSystem.Data = {} -- Skill levels and their modifiers AltSystem.Data.SkillLevels = { + ["Inept"] = -4, ["Novice"] = -2, ["Adept"] = 0, ["Expert"] = 2, @@ -15,13 +16,9 @@ AltSystem.Data.SkillLevels = { -- The "Base roll" entry is always the first (default) skill local BASE_ROLL_ENTRY = { name = "Base roll", level = "Base", modifier = 0 } --- The "Inept" entry is always the second skill -local UNSKILLED_ENTRY = { name = "Inept", level = "Inept", modifier = -4 } - -- Default/fallback skill list used when no TRP3 profile skills are found local DEFAULT_SKILLS = { { name = "Base roll", level = "Base", modifier = 0 }, - { name = "Inept", level = "Inept", modifier = -4 }, { name = "Novice Skill", level = "Novice", modifier = -2 }, { name = "Adept Skill", level = "Adept", modifier = 0 }, { name = "Expert Skill", level = "Expert", modifier = 2 }, @@ -29,7 +26,7 @@ local DEFAULT_SKILLS = { } -- Valid skill level keywords that must appear in the trait's right field (RT) -local VALID_SKILL_KEYWORDS = { "Novice", "Adept", "Expert", "Master" } +local VALID_SKILL_KEYWORDS = { "Inept", "Novice", "Adept", "Expert", "Master" } -- Check if the trait's right text field contains a valid skill keyword. -- Returns true if any keyword is found, false otherwise. @@ -43,11 +40,14 @@ local function HasSkillKeyword(rightText) return false end --- Determine the skill level from the trait's numeric value (V2 field, 0-20 range). --- Returns the level string and modifier, or nil if the value is 0 or absent. -local function ParseSkillLevel(numericValue) +-- Determine the skill level from the trait's right text and numeric value (V2 field, 0-20 range). +-- If the trait is marked as Inept, always returns "Inept" with -4 modifier regardless of numeric value. +-- Otherwise returns the level string and modifier based on numeric value, or nil if the value is 0 or absent. +local function ParseSkillLevel(rightText, numericValue) + if rightText and rightText:find("Inept") then + return "Inept", AltSystem.Data.SkillLevels["Inept"] + end if not numericValue or numericValue <= 0 then return nil, nil end - if numericValue >= 20 then return "Master", AltSystem.Data.SkillLevels["Master"] elseif numericValue >= 11 then @@ -60,14 +60,13 @@ local function ParseSkillLevel(numericValue) end -- Fetch skills from the current TRP3 profile's personality traits. --- Returns an array of skill entries, always starting with "Base roll" and "Inept". +-- Returns an array of skill entries, always starting with "Base roll". +-- Traits marked as Inept get a -4 modifier regardless of their numeric value. -- Falls back to the default list if no profile or no valid skills are found. function AltSystem.Data:RefreshSkills() local skills = {} - - -- Always add Base roll as the first entry, then Inept + -- Always add Base roll as the first entry table.insert(skills, { name = BASE_ROLL_ENTRY.name, level = BASE_ROLL_ENTRY.level, modifier = BASE_ROLL_ENTRY.modifier }) - table.insert(skills, { name = UNSKILLED_ENTRY.name, level = UNSKILLED_ENTRY.level, modifier = UNSKILLED_ENTRY.modifier }) local foundAny = false @@ -80,7 +79,7 @@ function AltSystem.Data:RefreshSkills() local numericValue = trait.V2 if skillName and skillName ~= "" and HasSkillKeyword(trait.RT) then - local level, modifier = ParseSkillLevel(numericValue) + local level, modifier = ParseSkillLevel(trait.RT, numericValue) if level and modifier then foundAny = true table.insert(skills, { diff --git a/UI.lua b/UI.lua index 8b7e8d8..839cd58 100644 --- a/UI.lua +++ b/UI.lua @@ -256,7 +256,7 @@ end function AltSystem:RefreshSkillDropdown() AltSystem.Data:RefreshSkills() - -- Reset selection to 1 (Inept) since the skill list may have changed + -- Reset selection to 1 (Base roll) since the skill list may have changed AltSystem.State.selectedSkillIndex = 1 if AltSystem.SetSkillIndex then AltSystem.SetSkillIndex(1) diff --git a/docs/2-skills.md b/docs/2-skills.md index f118ffe..cd4429d 100644 --- a/docs/2-skills.md +++ b/docs/2-skills.md @@ -3,13 +3,13 @@ - The skills are defined in the TRP profile as 'Personality traits' - For each trait: - the left field represents the skill name - - the right field must contain a valid skill keyword (Novice, Adept, Expert, Master) — traits without one of these keywords are omitted + - the right field must contain a valid skill keyword (Inept, Novice, Adept, Expert, Master) — traits without one of these keywords are omitted + - if the right field contains "Inept", the trait always gets a -4 modifier regardless of its numeric value - the first numeric value (V2) determines the skill level based on these ranges: - Novice: 1-5 - Adept: 6-10 - Expert: 11-19 - Master: 20 - should a skill have a value of 0 or no value, it should be omitted from the list -- The list should have a default selected value of "Inept" which corresponds to a -4 modifier - In case no skills are found in the profile, or no profile is selected, a default list should be displayed - - Inept, Novice Skill, Adept Skill, Expert Skill, Master Skill \ No newline at end of file + - Base roll, Novice Skill, Adept Skill, Expert Skill, Master Skill \ No newline at end of file From 2abe9c817b2ba82aca36bbb6de1fb8e12984e4f8 Mon Sep 17 00:00:00 2001 From: Rukira Date: Fri, 8 May 2026 16:14:45 +0100 Subject: [PATCH 38/74] Inept skill level --- AltSystem.toc | 2 +- Data.lua | 27 +++++++++++++-------------- UI.lua | 2 +- docs/2-skills.md | 6 +++--- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/AltSystem.toc b/AltSystem.toc index 3e5feca..b304469 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: 0.5 +## Version: 0.6 ## Dependencies: totalRP3, totalRP3_Extended ## SavedVariables: AltSystemDB diff --git a/Data.lua b/Data.lua index 6dc2a35..164eae0 100644 --- a/Data.lua +++ b/Data.lua @@ -6,6 +6,7 @@ AltSystem.Data = {} -- Skill levels and their modifiers AltSystem.Data.SkillLevels = { + ["Inept"] = -4, ["Novice"] = -2, ["Adept"] = 0, ["Expert"] = 2, @@ -15,13 +16,9 @@ AltSystem.Data.SkillLevels = { -- The "Base roll" entry is always the first (default) skill local BASE_ROLL_ENTRY = { name = "Base roll", level = "Base", modifier = 0 } --- The "Inept" entry is always the second skill -local UNSKILLED_ENTRY = { name = "Inept", level = "Inept", modifier = -4 } - -- Default/fallback skill list used when no TRP3 profile skills are found local DEFAULT_SKILLS = { { name = "Base roll", level = "Base", modifier = 0 }, - { name = "Inept", level = "Inept", modifier = -4 }, { name = "Novice Skill", level = "Novice", modifier = -2 }, { name = "Adept Skill", level = "Adept", modifier = 0 }, { name = "Expert Skill", level = "Expert", modifier = 2 }, @@ -29,7 +26,7 @@ local DEFAULT_SKILLS = { } -- Valid skill level keywords that must appear in the trait's right field (RT) -local VALID_SKILL_KEYWORDS = { "Novice", "Adept", "Expert", "Master" } +local VALID_SKILL_KEYWORDS = { "Inept", "Novice", "Adept", "Expert", "Master" } -- Check if the trait's right text field contains a valid skill keyword. -- Returns true if any keyword is found, false otherwise. @@ -43,11 +40,14 @@ local function HasSkillKeyword(rightText) return false end --- Determine the skill level from the trait's numeric value (V2 field, 0-20 range). --- Returns the level string and modifier, or nil if the value is 0 or absent. -local function ParseSkillLevel(numericValue) +-- Determine the skill level from the trait's right text and numeric value (V2 field, 0-20 range). +-- If the trait is marked as Inept, always returns "Inept" with -4 modifier regardless of numeric value. +-- Otherwise returns the level string and modifier based on numeric value, or nil if the value is 0 or absent. +local function ParseSkillLevel(rightText, numericValue) + if rightText and rightText:find("Inept") then + return "Inept", AltSystem.Data.SkillLevels["Inept"] + end if not numericValue or numericValue <= 0 then return nil, nil end - if numericValue >= 20 then return "Master", AltSystem.Data.SkillLevels["Master"] elseif numericValue >= 11 then @@ -60,14 +60,13 @@ local function ParseSkillLevel(numericValue) end -- Fetch skills from the current TRP3 profile's personality traits. --- Returns an array of skill entries, always starting with "Base roll" and "Inept". +-- Returns an array of skill entries, always starting with "Base roll". +-- Traits marked as Inept get a -4 modifier regardless of their numeric value. -- Falls back to the default list if no profile or no valid skills are found. function AltSystem.Data:RefreshSkills() local skills = {} - - -- Always add Base roll as the first entry, then Inept + -- Always add Base roll as the first entry table.insert(skills, { name = BASE_ROLL_ENTRY.name, level = BASE_ROLL_ENTRY.level, modifier = BASE_ROLL_ENTRY.modifier }) - table.insert(skills, { name = UNSKILLED_ENTRY.name, level = UNSKILLED_ENTRY.level, modifier = UNSKILLED_ENTRY.modifier }) local foundAny = false @@ -80,7 +79,7 @@ function AltSystem.Data:RefreshSkills() local numericValue = trait.V2 if skillName and skillName ~= "" and HasSkillKeyword(trait.RT) then - local level, modifier = ParseSkillLevel(numericValue) + local level, modifier = ParseSkillLevel(trait.RT, numericValue) if level and modifier then foundAny = true table.insert(skills, { diff --git a/UI.lua b/UI.lua index 8b7e8d8..839cd58 100644 --- a/UI.lua +++ b/UI.lua @@ -256,7 +256,7 @@ end function AltSystem:RefreshSkillDropdown() AltSystem.Data:RefreshSkills() - -- Reset selection to 1 (Inept) since the skill list may have changed + -- Reset selection to 1 (Base roll) since the skill list may have changed AltSystem.State.selectedSkillIndex = 1 if AltSystem.SetSkillIndex then AltSystem.SetSkillIndex(1) diff --git a/docs/2-skills.md b/docs/2-skills.md index f118ffe..cd4429d 100644 --- a/docs/2-skills.md +++ b/docs/2-skills.md @@ -3,13 +3,13 @@ - The skills are defined in the TRP profile as 'Personality traits' - For each trait: - the left field represents the skill name - - the right field must contain a valid skill keyword (Novice, Adept, Expert, Master) — traits without one of these keywords are omitted + - the right field must contain a valid skill keyword (Inept, Novice, Adept, Expert, Master) — traits without one of these keywords are omitted + - if the right field contains "Inept", the trait always gets a -4 modifier regardless of its numeric value - the first numeric value (V2) determines the skill level based on these ranges: - Novice: 1-5 - Adept: 6-10 - Expert: 11-19 - Master: 20 - should a skill have a value of 0 or no value, it should be omitted from the list -- The list should have a default selected value of "Inept" which corresponds to a -4 modifier - In case no skills are found in the profile, or no profile is selected, a default list should be displayed - - Inept, Novice Skill, Adept Skill, Expert Skill, Master Skill \ No newline at end of file + - Base roll, Novice Skill, Adept Skill, Expert Skill, Master Skill \ No newline at end of file From 0d9ec1913d436f8cd78a1c5a78b485aa69a4b705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Fri, 8 May 2026 16:33:36 +0100 Subject: [PATCH 39/74] Skill level mismatch warning --- Data.lua | 35 +++++++++++++++++++++++++++++------ UI.lua | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/Data.lua b/Data.lua index 164eae0..19d5ca9 100644 --- a/Data.lua +++ b/Data.lua @@ -28,16 +28,37 @@ local DEFAULT_SKILLS = { -- Valid skill level keywords that must appear in the trait's right field (RT) local VALID_SKILL_KEYWORDS = { "Inept", "Novice", "Adept", "Expert", "Master" } +-- Expected numeric value ranges for each skill keyword +local SKILL_KEYWORD_RANGES = { + ["Novice"] = { min = 1, max = 5 }, + ["Adept"] = { min = 6, max = 10 }, + ["Expert"] = { min = 11, max = 19 }, + ["Master"] = { min = 20, max = 20 }, +} + -- Check if the trait's right text field contains a valid skill keyword. --- Returns true if any keyword is found, false otherwise. -local function HasSkillKeyword(rightText) - if not rightText or rightText == "" then return false end +-- Returns the matched keyword if found, nil otherwise. +local function FindSkillKeyword(rightText) + if not rightText or rightText == "" then return nil end for _, keyword in ipairs(VALID_SKILL_KEYWORDS) do if rightText:find(keyword) then - return true + return keyword end end - return false + return nil +end + +-- Check whether the numeric value matches the expected range for the given keyword. +-- Returns a warning string if mismatched, or nil if everything is fine. +local function CheckSkillMismatch(keyword, numericValue) + if not keyword or keyword == "Inept" then return nil end + local range = SKILL_KEYWORD_RANGES[keyword] + if not range then return nil end + if not numericValue or numericValue <= 0 then return nil end + if numericValue < range.min or numericValue > range.max then + return "Skill is marked as " .. keyword .. " (" .. range.min .. "-" .. range.max .. "), but the profile has a value of " .. numericValue .. "." + end + return nil end -- Determine the skill level from the trait's right text and numeric value (V2 field, 0-20 range). @@ -78,7 +99,8 @@ function AltSystem.Data:RefreshSkills() local skillName = trait.LT local numericValue = trait.V2 - if skillName and skillName ~= "" and HasSkillKeyword(trait.RT) then + local keyword = FindSkillKeyword(trait.RT) + if skillName and skillName ~= "" and keyword then local level, modifier = ParseSkillLevel(trait.RT, numericValue) if level and modifier then foundAny = true @@ -86,6 +108,7 @@ function AltSystem.Data:RefreshSkills() name = skillName, level = level, modifier = modifier, + warning = CheckSkillMismatch(keyword, numericValue), }) end end diff --git a/UI.lua b/UI.lua index 839cd58..81b3bd3 100644 --- a/UI.lua +++ b/UI.lua @@ -83,18 +83,53 @@ function AltSystem:CreateMainFrame() -- 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 + UpdateSkillWarning(index) end) + -- 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) + + 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() + + -- 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 + 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 - 8 ------------------------- @@ -261,6 +296,9 @@ function AltSystem:RefreshSkillDropdown() if AltSystem.SetSkillIndex then AltSystem.SetSkillIndex(1) end + if AltSystem.UpdateSkillWarning then + AltSystem.UpdateSkillWarning(1) + end -- Rebuild the dropdown menu with the new skill list if AltSystem.SkillDropdown then @@ -275,6 +313,9 @@ function AltSystem:RefreshSkillDropdown() if AltSystem.SetSkillIndex then AltSystem.SetSkillIndex(data) end + if AltSystem.UpdateSkillWarning then + AltSystem.UpdateSkillWarning(data) + end end, i ) From 02b2afedcbc8d784619b73cf5605981269a3838e Mon Sep 17 00:00:00 2001 From: Rukira Date: Fri, 8 May 2026 16:33:36 +0100 Subject: [PATCH 40/74] Skill level mismatch warning --- Data.lua | 35 +++++++++++++++++++++++++++++------ UI.lua | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/Data.lua b/Data.lua index 164eae0..19d5ca9 100644 --- a/Data.lua +++ b/Data.lua @@ -28,16 +28,37 @@ local DEFAULT_SKILLS = { -- Valid skill level keywords that must appear in the trait's right field (RT) local VALID_SKILL_KEYWORDS = { "Inept", "Novice", "Adept", "Expert", "Master" } +-- Expected numeric value ranges for each skill keyword +local SKILL_KEYWORD_RANGES = { + ["Novice"] = { min = 1, max = 5 }, + ["Adept"] = { min = 6, max = 10 }, + ["Expert"] = { min = 11, max = 19 }, + ["Master"] = { min = 20, max = 20 }, +} + -- Check if the trait's right text field contains a valid skill keyword. --- Returns true if any keyword is found, false otherwise. -local function HasSkillKeyword(rightText) - if not rightText or rightText == "" then return false end +-- Returns the matched keyword if found, nil otherwise. +local function FindSkillKeyword(rightText) + if not rightText or rightText == "" then return nil end for _, keyword in ipairs(VALID_SKILL_KEYWORDS) do if rightText:find(keyword) then - return true + return keyword end end - return false + return nil +end + +-- Check whether the numeric value matches the expected range for the given keyword. +-- Returns a warning string if mismatched, or nil if everything is fine. +local function CheckSkillMismatch(keyword, numericValue) + if not keyword or keyword == "Inept" then return nil end + local range = SKILL_KEYWORD_RANGES[keyword] + if not range then return nil end + if not numericValue or numericValue <= 0 then return nil end + if numericValue < range.min or numericValue > range.max then + return "Skill is marked as " .. keyword .. " (" .. range.min .. "-" .. range.max .. "), but the profile has a value of " .. numericValue .. "." + end + return nil end -- Determine the skill level from the trait's right text and numeric value (V2 field, 0-20 range). @@ -78,7 +99,8 @@ function AltSystem.Data:RefreshSkills() local skillName = trait.LT local numericValue = trait.V2 - if skillName and skillName ~= "" and HasSkillKeyword(trait.RT) then + local keyword = FindSkillKeyword(trait.RT) + if skillName and skillName ~= "" and keyword then local level, modifier = ParseSkillLevel(trait.RT, numericValue) if level and modifier then foundAny = true @@ -86,6 +108,7 @@ function AltSystem.Data:RefreshSkills() name = skillName, level = level, modifier = modifier, + warning = CheckSkillMismatch(keyword, numericValue), }) end end diff --git a/UI.lua b/UI.lua index 839cd58..81b3bd3 100644 --- a/UI.lua +++ b/UI.lua @@ -83,18 +83,53 @@ function AltSystem:CreateMainFrame() -- 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 + UpdateSkillWarning(index) end) + -- 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) + + 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() + + -- 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 + 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 - 8 ------------------------- @@ -261,6 +296,9 @@ function AltSystem:RefreshSkillDropdown() if AltSystem.SetSkillIndex then AltSystem.SetSkillIndex(1) end + if AltSystem.UpdateSkillWarning then + AltSystem.UpdateSkillWarning(1) + end -- Rebuild the dropdown menu with the new skill list if AltSystem.SkillDropdown then @@ -275,6 +313,9 @@ function AltSystem:RefreshSkillDropdown() if AltSystem.SetSkillIndex then AltSystem.SetSkillIndex(data) end + if AltSystem.UpdateSkillWarning then + AltSystem.UpdateSkillWarning(data) + end end, i ) From ed1e696eb5db61ed6d666d7f29b1b59a8283a8b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Sat, 9 May 2026 02:11:24 +0100 Subject: [PATCH 41/74] Pet modifier --- AltSystem.toc | 2 +- Core.lua | 5 +++++ Roll.lua | 41 +++++++++++++++++++++++++++++++++++++---- UI.lua | 25 ++++++++++++++++++++++++- 4 files changed, 67 insertions(+), 6 deletions(-) diff --git a/AltSystem.toc b/AltSystem.toc index b304469..0b03820 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: 0.6 +## Version: 1.0 ## Dependencies: totalRP3, totalRP3_Extended ## SavedVariables: AltSystemDB diff --git a/Core.lua b/Core.lua index c5bb3a8..68c41ac 100644 --- a/Core.lua +++ b/Core.lua @@ -7,6 +7,7 @@ AltSystem.State = { selectedItemIndex = 1, -- 1 = No item selectedDefenseIndex = 1, -- 1 = Base armor shieldEnabled = false, + petSummonEnabled = false, announceEnabled = false, announceChannelIndex = 1, -- 1 = Emote, 2 = Party, 3 = Raid, 4 = Guild } @@ -39,6 +40,9 @@ function AltSystem:Init() if AltSystemDB.announceChannelIndex then AltSystem.State.announceChannelIndex = AltSystemDB.announceChannelIndex end + if AltSystemDB.petSummonEnabled ~= nil then + AltSystem.State.petSummonEnabled = AltSystemDB.petSummonEnabled + end -- Register slash command /altsystem SLASH_ALTSYSTEM1 = "/altsystem" @@ -53,6 +57,7 @@ function AltSystem:Init() AltSystemDB = AltSystemDB or {} AltSystemDB.announceEnabled = AltSystem.State.announceEnabled AltSystemDB.announceChannelIndex = AltSystem.State.announceChannelIndex + AltSystemDB.petSummonEnabled = AltSystem.State.petSummonEnabled end) print("|cff00ccffAltSystem|r loaded. Type /altsystem to open.") diff --git a/Roll.lua b/Roll.lua index 71b4438..1e98b0c 100644 --- a/Roll.lua +++ b/Roll.lua @@ -4,6 +4,7 @@ AltSystem = AltSystem or {} local pendingRollType = nil -- "attack" or "defense" +local pendingPetRoll = nil -- { rollType, mainRollValue } when awaiting pet/summon roll -- Get the TRP3 character first name, falling back to the WoW unit name local function GetCharacterName() @@ -65,6 +66,7 @@ end -- Perform a roll: triggers the WoW native /roll 20 command function AltSystem:PerformRoll(rollType) + pendingPetRoll = nil pendingRollType = rollType RandomRoll(1, 20) end @@ -74,6 +76,20 @@ local rollListener = CreateFrame("Frame") rollListener:RegisterEvent("CHAT_MSG_SYSTEM") rollListener:SetScript("OnEvent", function(self, event, message) + -- Phase 2: waiting for pet/summon roll result (1-5) + if pendingPetRoll then + local petRoll = message:match("rolls (%d+) %(1%-5%)") + if not petRoll then return end + + local petValue = tonumber(petRoll) + local info = pendingPetRoll + pendingPetRoll = nil + + AltSystem:CalculateAndDisplayResult(info.rollType, info.mainRollValue, petValue) + return + end + + -- Phase 1: waiting for main roll result (1-20) if not pendingRollType then return end -- Match the roll result pattern: "PlayerName rolls X (1-20)" @@ -84,11 +100,18 @@ rollListener:SetScript("OnEvent", function(self, event, message) local rollType = pendingRollType pendingRollType = nil + -- If pet/summon is enabled, trigger a second roll (1-5) + if AltSystem.State.petSummonEnabled then + pendingPetRoll = { rollType = rollType, mainRollValue = rollValue } + RandomRoll(1, 5) + return + end + AltSystem:CalculateAndDisplayResult(rollType, rollValue) end) -- Calculate the final result based on the roll type and selected modifiers -function AltSystem:CalculateAndDisplayResult(rollType, rollValue) +function AltSystem:CalculateAndDisplayResult(rollType, rollValue, petRollValue) -- Critical rolls bypass normal calculation if rollValue == 1 then if AltSystem.ResultText then @@ -134,9 +157,11 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) local isBaseRoll = skill and skill.name == "Base roll" + local petMod = petRollValue or 0 + if rollType == "attack" then - -- Attack Roll = roll + skill modifier + item modifier - total = rollValue + skillMod + itemMod + -- Attack Roll = roll + skill modifier + item modifier + pet/summon modifier + total = rollValue + skillMod + itemMod + petMod if not isBaseRoll then breakdown = breakdown .. "\nSkill: " .. FormatModifier(skillMod) @@ -146,6 +171,10 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) 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 @@ -154,7 +183,7 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) local defenseMod = defense and defense.modifier or 0 local shieldMod = state.shieldEnabled and AltSystem.Data.ShieldModifier or 0 - total = rollValue + skillMod + itemMod + defenseMod + shieldMod + total = rollValue + skillMod + itemMod + defenseMod + shieldMod + petMod if not isBaseRoll then breakdown = breakdown .. "\nSkill: " .. FormatModifier(skillMod) @@ -170,6 +199,10 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) 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 diff --git a/UI.lua b/UI.lua index 81b3bd3..c425d9d 100644 --- a/UI.lua +++ b/UI.lua @@ -5,7 +5,7 @@ AltSystem = AltSystem or {} local WINDOW_WIDTH = 300 -local WINDOW_HEIGHT = 440 +local WINDOW_HEIGHT = 478 local PADDING = 12 local ROW_HEIGHT = 30 local LABEL_WIDTH = 80 @@ -191,6 +191,29 @@ function AltSystem:CreateMainFrame() 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: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 - 8 + ------------------------- -- Announce checkbox + channel dropdown ------------------------- From 3198afc455bf1142bd5bc1110377447ea213c909 Mon Sep 17 00:00:00 2001 From: Rukira Date: Sat, 9 May 2026 02:11:24 +0100 Subject: [PATCH 42/74] Pet modifier --- AltSystem.toc | 2 +- Core.lua | 5 +++++ Roll.lua | 41 +++++++++++++++++++++++++++++++++++++---- UI.lua | 25 ++++++++++++++++++++++++- 4 files changed, 67 insertions(+), 6 deletions(-) diff --git a/AltSystem.toc b/AltSystem.toc index b304469..0b03820 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: 0.6 +## Version: 1.0 ## Dependencies: totalRP3, totalRP3_Extended ## SavedVariables: AltSystemDB diff --git a/Core.lua b/Core.lua index c5bb3a8..68c41ac 100644 --- a/Core.lua +++ b/Core.lua @@ -7,6 +7,7 @@ AltSystem.State = { selectedItemIndex = 1, -- 1 = No item selectedDefenseIndex = 1, -- 1 = Base armor shieldEnabled = false, + petSummonEnabled = false, announceEnabled = false, announceChannelIndex = 1, -- 1 = Emote, 2 = Party, 3 = Raid, 4 = Guild } @@ -39,6 +40,9 @@ function AltSystem:Init() if AltSystemDB.announceChannelIndex then AltSystem.State.announceChannelIndex = AltSystemDB.announceChannelIndex end + if AltSystemDB.petSummonEnabled ~= nil then + AltSystem.State.petSummonEnabled = AltSystemDB.petSummonEnabled + end -- Register slash command /altsystem SLASH_ALTSYSTEM1 = "/altsystem" @@ -53,6 +57,7 @@ function AltSystem:Init() AltSystemDB = AltSystemDB or {} AltSystemDB.announceEnabled = AltSystem.State.announceEnabled AltSystemDB.announceChannelIndex = AltSystem.State.announceChannelIndex + AltSystemDB.petSummonEnabled = AltSystem.State.petSummonEnabled end) print("|cff00ccffAltSystem|r loaded. Type /altsystem to open.") diff --git a/Roll.lua b/Roll.lua index 71b4438..1e98b0c 100644 --- a/Roll.lua +++ b/Roll.lua @@ -4,6 +4,7 @@ AltSystem = AltSystem or {} local pendingRollType = nil -- "attack" or "defense" +local pendingPetRoll = nil -- { rollType, mainRollValue } when awaiting pet/summon roll -- Get the TRP3 character first name, falling back to the WoW unit name local function GetCharacterName() @@ -65,6 +66,7 @@ end -- Perform a roll: triggers the WoW native /roll 20 command function AltSystem:PerformRoll(rollType) + pendingPetRoll = nil pendingRollType = rollType RandomRoll(1, 20) end @@ -74,6 +76,20 @@ local rollListener = CreateFrame("Frame") rollListener:RegisterEvent("CHAT_MSG_SYSTEM") rollListener:SetScript("OnEvent", function(self, event, message) + -- Phase 2: waiting for pet/summon roll result (1-5) + if pendingPetRoll then + local petRoll = message:match("rolls (%d+) %(1%-5%)") + if not petRoll then return end + + local petValue = tonumber(petRoll) + local info = pendingPetRoll + pendingPetRoll = nil + + AltSystem:CalculateAndDisplayResult(info.rollType, info.mainRollValue, petValue) + return + end + + -- Phase 1: waiting for main roll result (1-20) if not pendingRollType then return end -- Match the roll result pattern: "PlayerName rolls X (1-20)" @@ -84,11 +100,18 @@ rollListener:SetScript("OnEvent", function(self, event, message) local rollType = pendingRollType pendingRollType = nil + -- If pet/summon is enabled, trigger a second roll (1-5) + if AltSystem.State.petSummonEnabled then + pendingPetRoll = { rollType = rollType, mainRollValue = rollValue } + RandomRoll(1, 5) + return + end + AltSystem:CalculateAndDisplayResult(rollType, rollValue) end) -- Calculate the final result based on the roll type and selected modifiers -function AltSystem:CalculateAndDisplayResult(rollType, rollValue) +function AltSystem:CalculateAndDisplayResult(rollType, rollValue, petRollValue) -- Critical rolls bypass normal calculation if rollValue == 1 then if AltSystem.ResultText then @@ -134,9 +157,11 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) local isBaseRoll = skill and skill.name == "Base roll" + local petMod = petRollValue or 0 + if rollType == "attack" then - -- Attack Roll = roll + skill modifier + item modifier - total = rollValue + skillMod + itemMod + -- Attack Roll = roll + skill modifier + item modifier + pet/summon modifier + total = rollValue + skillMod + itemMod + petMod if not isBaseRoll then breakdown = breakdown .. "\nSkill: " .. FormatModifier(skillMod) @@ -146,6 +171,10 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) 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 @@ -154,7 +183,7 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) local defenseMod = defense and defense.modifier or 0 local shieldMod = state.shieldEnabled and AltSystem.Data.ShieldModifier or 0 - total = rollValue + skillMod + itemMod + defenseMod + shieldMod + total = rollValue + skillMod + itemMod + defenseMod + shieldMod + petMod if not isBaseRoll then breakdown = breakdown .. "\nSkill: " .. FormatModifier(skillMod) @@ -170,6 +199,10 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue) 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 diff --git a/UI.lua b/UI.lua index 81b3bd3..c425d9d 100644 --- a/UI.lua +++ b/UI.lua @@ -5,7 +5,7 @@ AltSystem = AltSystem or {} local WINDOW_WIDTH = 300 -local WINDOW_HEIGHT = 440 +local WINDOW_HEIGHT = 478 local PADDING = 12 local ROW_HEIGHT = 30 local LABEL_WIDTH = 80 @@ -191,6 +191,29 @@ function AltSystem:CreateMainFrame() 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: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 - 8 + ------------------------- -- Announce checkbox + channel dropdown ------------------------- From ab8388e27c6bc5038fc5fcb13ec923f97f5c4d3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Mon, 11 May 2026 12:20:26 +0100 Subject: [PATCH 43/74] Fixing roll detection by character name --- AltSystem.toc | 2 +- Roll.lua | 6 ++++-- docs/Changelog.md | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 docs/Changelog.md diff --git a/AltSystem.toc b/AltSystem.toc index 0b03820..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: 1.0 +## Version: 1.1 ## Dependencies: totalRP3, totalRP3_Extended ## SavedVariables: AltSystemDB diff --git a/Roll.lua b/Roll.lua index 1e98b0c..a37ca8b 100644 --- a/Roll.lua +++ b/Roll.lua @@ -78,8 +78,9 @@ rollListener:RegisterEvent("CHAT_MSG_SYSTEM") rollListener:SetScript("OnEvent", function(self, event, message) -- Phase 2: waiting for pet/summon roll result (1-5) if pendingPetRoll then - local petRoll = message:match("rolls (%d+) %(1%-5%)") + local petRoller, petRoll = message:match("(.+) rolls (%d+) %(1%-5%)") if not petRoll then return end + if petRoller ~= UnitName("player") then return end local petValue = tonumber(petRoll) local info = pendingPetRoll @@ -93,8 +94,9 @@ rollListener:SetScript("OnEvent", function(self, event, message) if not pendingRollType then return end -- Match the roll result pattern: "PlayerName rolls X (1-20)" - local roll = message:match("rolls (%d+) %(1%-20%)") + local roller, roll = message:match("(.+) rolls (%d+) %(1%-20%)") if not roll then return end + if roller ~= UnitName("player") then return end local rollValue = tonumber(roll) local rollType = pendingRollType diff --git a/docs/Changelog.md b/docs/Changelog.md new file mode 100644 index 0000000..dad8bdc --- /dev/null +++ b/docs/Changelog.md @@ -0,0 +1 @@ +- Fixed an issue where the wrong rolls were displayed when in a large enough group \ No newline at end of file From fa0df4fde71ef8201bb9bcb7721a391928415e0e Mon Sep 17 00:00:00 2001 From: Rukira Date: Mon, 11 May 2026 12:20:26 +0100 Subject: [PATCH 44/74] Fixing roll detection by character name --- AltSystem.toc | 2 +- Roll.lua | 6 ++++-- docs/Changelog.md | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 docs/Changelog.md diff --git a/AltSystem.toc b/AltSystem.toc index 0b03820..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: 1.0 +## Version: 1.1 ## Dependencies: totalRP3, totalRP3_Extended ## SavedVariables: AltSystemDB diff --git a/Roll.lua b/Roll.lua index 1e98b0c..a37ca8b 100644 --- a/Roll.lua +++ b/Roll.lua @@ -78,8 +78,9 @@ rollListener:RegisterEvent("CHAT_MSG_SYSTEM") rollListener:SetScript("OnEvent", function(self, event, message) -- Phase 2: waiting for pet/summon roll result (1-5) if pendingPetRoll then - local petRoll = message:match("rolls (%d+) %(1%-5%)") + local petRoller, petRoll = message:match("(.+) rolls (%d+) %(1%-5%)") if not petRoll then return end + if petRoller ~= UnitName("player") then return end local petValue = tonumber(petRoll) local info = pendingPetRoll @@ -93,8 +94,9 @@ rollListener:SetScript("OnEvent", function(self, event, message) if not pendingRollType then return end -- Match the roll result pattern: "PlayerName rolls X (1-20)" - local roll = message:match("rolls (%d+) %(1%-20%)") + local roller, roll = message:match("(.+) rolls (%d+) %(1%-20%)") if not roll then return end + if roller ~= UnitName("player") then return end local rollValue = tonumber(roll) local rollType = pendingRollType diff --git a/docs/Changelog.md b/docs/Changelog.md new file mode 100644 index 0000000..dad8bdc --- /dev/null +++ b/docs/Changelog.md @@ -0,0 +1 @@ +- Fixed an issue where the wrong rolls were displayed when in a large enough group \ No newline at end of file From 5b36bfdb1b1d5cdce217f6a7be633c5ad637e40b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Mon, 11 May 2026 12:24:48 +0100 Subject: [PATCH 45/74] Adding unskilled default option --- Data.lua | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Data.lua b/Data.lua index 19d5ca9..e46d887 100644 --- a/Data.lua +++ b/Data.lua @@ -14,15 +14,18 @@ AltSystem.Data.SkillLevels = { } -- The "Base roll" entry is always the first (default) skill -local BASE_ROLL_ENTRY = { name = "Base roll", level = "Base", modifier = 0 } +local BASE_ROLL_ENTRY = { name = "Base roll", level = "Base", modifier = 0 } +-- The "Unskilled" entry is always the last skill +local UNSKILLED_ENTRY = { name = "Unskilled", level = "Unskilled", modifier = -4 } -- Default/fallback skill list used when no TRP3 profile skills are found local DEFAULT_SKILLS = { { name = "Base roll", level = "Base", modifier = 0 }, - { name = "Novice Skill", level = "Novice", modifier = -2 }, - { name = "Adept Skill", level = "Adept", modifier = 0 }, + { name = "Novice Skill", level = "Novice", modifier = -2 }, + { name = "Adept Skill", level = "Adept", modifier = 0 }, { name = "Expert Skill", level = "Expert", modifier = 2 }, { name = "Master Skill", level = "Master", modifier = 4 }, + { name = "Unskilled", level = "Unskilled", modifier = -4 }, } -- Valid skill level keywords that must appear in the trait's right field (RT) @@ -124,6 +127,9 @@ function AltSystem.Data:RefreshSkills() end end + -- Always add Unskilled as the last entry + table.insert(skills, { name = UNSKILLED_ENTRY.name, level = UNSKILLED_ENTRY.level, modifier = UNSKILLED_ENTRY.modifier }) + AltSystem.Data.Skills = skills return skills end From 48e6557eb3e7f2ebcc7d8662753423c5b850a289 Mon Sep 17 00:00:00 2001 From: Rukira Date: Mon, 11 May 2026 12:24:48 +0100 Subject: [PATCH 46/74] Adding unskilled default option --- Data.lua | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Data.lua b/Data.lua index 19d5ca9..e46d887 100644 --- a/Data.lua +++ b/Data.lua @@ -14,15 +14,18 @@ AltSystem.Data.SkillLevels = { } -- The "Base roll" entry is always the first (default) skill -local BASE_ROLL_ENTRY = { name = "Base roll", level = "Base", modifier = 0 } +local BASE_ROLL_ENTRY = { name = "Base roll", level = "Base", modifier = 0 } +-- The "Unskilled" entry is always the last skill +local UNSKILLED_ENTRY = { name = "Unskilled", level = "Unskilled", modifier = -4 } -- Default/fallback skill list used when no TRP3 profile skills are found local DEFAULT_SKILLS = { { name = "Base roll", level = "Base", modifier = 0 }, - { name = "Novice Skill", level = "Novice", modifier = -2 }, - { name = "Adept Skill", level = "Adept", modifier = 0 }, + { name = "Novice Skill", level = "Novice", modifier = -2 }, + { name = "Adept Skill", level = "Adept", modifier = 0 }, { name = "Expert Skill", level = "Expert", modifier = 2 }, { name = "Master Skill", level = "Master", modifier = 4 }, + { name = "Unskilled", level = "Unskilled", modifier = -4 }, } -- Valid skill level keywords that must appear in the trait's right field (RT) @@ -124,6 +127,9 @@ function AltSystem.Data:RefreshSkills() end end + -- Always add Unskilled as the last entry + table.insert(skills, { name = UNSKILLED_ENTRY.name, level = UNSKILLED_ENTRY.level, modifier = UNSKILLED_ENTRY.modifier }) + AltSystem.Data.Skills = skills return skills end From c27a6cd033c665dfe5891c67a3487e735c3c4796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Mon, 11 May 2026 13:59:44 +0100 Subject: [PATCH 47/74] Remember user selections across frame instances --- Core.lua | 20 ++++++++++++++++++++ UI.lua | 24 ++++++++++++++++++------ docs/Changelog.md | 1 + 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/Core.lua b/Core.lua index 68c41ac..a3d1ab9 100644 --- a/Core.lua +++ b/Core.lua @@ -4,6 +4,7 @@ AltSystem = AltSystem or {} AltSystem.State = { selectedSkillIndex = 1, + selectedSkillName = nil, -- skill name used to restore selection across sessions selectedItemIndex = 1, -- 1 = No item selectedDefenseIndex = 1, -- 1 = Base armor shieldEnabled = false, @@ -43,6 +44,18 @@ function AltSystem:Init() if AltSystemDB.petSummonEnabled ~= nil then AltSystem.State.petSummonEnabled = AltSystemDB.petSummonEnabled end + if AltSystemDB.shieldEnabled ~= nil then + AltSystem.State.shieldEnabled = AltSystemDB.shieldEnabled + end + if AltSystemDB.selectedItemIndex then + AltSystem.State.selectedItemIndex = AltSystemDB.selectedItemIndex + end + if AltSystemDB.selectedDefenseIndex then + AltSystem.State.selectedDefenseIndex = AltSystemDB.selectedDefenseIndex + end + if AltSystemDB.selectedSkillName then + AltSystem.State.selectedSkillName = AltSystemDB.selectedSkillName + end -- Register slash command /altsystem SLASH_ALTSYSTEM1 = "/altsystem" @@ -58,8 +71,15 @@ function AltSystem:Init() 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 end) + -- Build the main frame now that saved variables are loaded + AltSystem:CreateMainFrame() + print("|cff00ccffAltSystem|r loaded. Type /altsystem to open.") end diff --git a/UI.lua b/UI.lua index c425d9d..5162da2 100644 --- a/UI.lua +++ b/UI.lua @@ -90,6 +90,7 @@ function AltSystem:CreateMainFrame() 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) @@ -314,13 +315,24 @@ end function AltSystem:RefreshSkillDropdown() AltSystem.Data:RefreshSkills() - -- Reset selection to 1 (Base roll) since the skill list may have changed - AltSystem.State.selectedSkillIndex = 1 + -- 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(1) + AltSystem.SetSkillIndex(newIndex) end if AltSystem.UpdateSkillWarning then - AltSystem.UpdateSkillWarning(1) + AltSystem.UpdateSkillWarning(newIndex) end -- Rebuild the dropdown menu with the new skill list @@ -333,6 +345,7 @@ function AltSystem:RefreshSkillDropdown() 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 @@ -347,5 +360,4 @@ function AltSystem:RefreshSkillDropdown() end end --- Create the frame on file load so it's ready when Init runs -AltSystem:CreateMainFrame() +-- Frame is created by AltSystem:Init() in Core.lua, after saved variables are loaded diff --git a/docs/Changelog.md b/docs/Changelog.md index dad8bdc..405d8f0 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1 +1,2 @@ +- 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 From c98405b2a39504b4d453e7cf490d671886cde963 Mon Sep 17 00:00:00 2001 From: Rukira Date: Mon, 11 May 2026 13:59:44 +0100 Subject: [PATCH 48/74] Remember user selections across frame instances --- Core.lua | 20 ++++++++++++++++++++ UI.lua | 24 ++++++++++++++++++------ docs/Changelog.md | 1 + 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/Core.lua b/Core.lua index 68c41ac..a3d1ab9 100644 --- a/Core.lua +++ b/Core.lua @@ -4,6 +4,7 @@ AltSystem = AltSystem or {} AltSystem.State = { selectedSkillIndex = 1, + selectedSkillName = nil, -- skill name used to restore selection across sessions selectedItemIndex = 1, -- 1 = No item selectedDefenseIndex = 1, -- 1 = Base armor shieldEnabled = false, @@ -43,6 +44,18 @@ function AltSystem:Init() if AltSystemDB.petSummonEnabled ~= nil then AltSystem.State.petSummonEnabled = AltSystemDB.petSummonEnabled end + if AltSystemDB.shieldEnabled ~= nil then + AltSystem.State.shieldEnabled = AltSystemDB.shieldEnabled + end + if AltSystemDB.selectedItemIndex then + AltSystem.State.selectedItemIndex = AltSystemDB.selectedItemIndex + end + if AltSystemDB.selectedDefenseIndex then + AltSystem.State.selectedDefenseIndex = AltSystemDB.selectedDefenseIndex + end + if AltSystemDB.selectedSkillName then + AltSystem.State.selectedSkillName = AltSystemDB.selectedSkillName + end -- Register slash command /altsystem SLASH_ALTSYSTEM1 = "/altsystem" @@ -58,8 +71,15 @@ function AltSystem:Init() 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 end) + -- Build the main frame now that saved variables are loaded + AltSystem:CreateMainFrame() + print("|cff00ccffAltSystem|r loaded. Type /altsystem to open.") end diff --git a/UI.lua b/UI.lua index c425d9d..5162da2 100644 --- a/UI.lua +++ b/UI.lua @@ -90,6 +90,7 @@ function AltSystem:CreateMainFrame() 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) @@ -314,13 +315,24 @@ end function AltSystem:RefreshSkillDropdown() AltSystem.Data:RefreshSkills() - -- Reset selection to 1 (Base roll) since the skill list may have changed - AltSystem.State.selectedSkillIndex = 1 + -- 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(1) + AltSystem.SetSkillIndex(newIndex) end if AltSystem.UpdateSkillWarning then - AltSystem.UpdateSkillWarning(1) + AltSystem.UpdateSkillWarning(newIndex) end -- Rebuild the dropdown menu with the new skill list @@ -333,6 +345,7 @@ function AltSystem:RefreshSkillDropdown() 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 @@ -347,5 +360,4 @@ function AltSystem:RefreshSkillDropdown() end end --- Create the frame on file load so it's ready when Init runs -AltSystem:CreateMainFrame() +-- Frame is created by AltSystem:Init() in Core.lua, after saved variables are loaded diff --git a/docs/Changelog.md b/docs/Changelog.md index dad8bdc..405d8f0 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1 +1,2 @@ +- 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 From 6a444584431d81fe64c422682657001ab0cc9e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Mon, 11 May 2026 14:00:10 +0100 Subject: [PATCH 49/74] Unskilled --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index 405d8f0..dd05c6a 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,2 +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 From c58d046310792c65d0711fda55f069ad6c43ba34 Mon Sep 17 00:00:00 2001 From: Rukira Date: Mon, 11 May 2026 14:00:10 +0100 Subject: [PATCH 50/74] Unskilled --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index 405d8f0..dd05c6a 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,2 +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 From ac01e4474c82f5f505e508a0680eb99c32a65ed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Tue, 12 May 2026 15:42:52 +0100 Subject: [PATCH 51/74] Setup for redesign feature --- AltSystem.toc | 2 +- docs/4-redesign.md | 12 ++++++++++++ docs/Changelog.md | 3 --- docs/roll_tab_design.png | Bin 0 -> 71752 bytes package.sh | 2 +- 5 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 docs/4-redesign.md create mode 100644 docs/roll_tab_design.png diff --git a/AltSystem.toc b/AltSystem.toc index e541ac4..a085d34 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: 1.1 +## Version: 2.0 ## Dependencies: totalRP3, totalRP3_Extended ## SavedVariables: AltSystemDB diff --git a/docs/4-redesign.md b/docs/4-redesign.md new file mode 100644 index 0000000..4a11115 --- /dev/null +++ b/docs/4-redesign.md @@ -0,0 +1,12 @@ +# 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 details +TODO \ No newline at end of file diff --git a/docs/Changelog.md b/docs/Changelog.md index dd05c6a..e69de29 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,3 +0,0 @@ -- 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 new file mode 100644 index 0000000000000000000000000000000000000000..5754915dd6627db6dca2b293a92cc0fa9a70050b GIT binary patch literal 71752 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 Date: Tue, 12 May 2026 15:45:33 +0100 Subject: [PATCH 52/74] Plan of action --- docs/4-redesign.md | 70 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/docs/4-redesign.md b/docs/4-redesign.md index 4a11115..fcd504c 100644 --- a/docs/4-redesign.md +++ b/docs/4-redesign.md @@ -8,5 +8,71 @@ This redesign of the Addon's window will start by following the following [desig - 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 details -TODO \ No newline at end of file +## 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 From ffc693a63ce1571ba03affc2864bed04b8f1436a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Tue, 12 May 2026 16:20:52 +0100 Subject: [PATCH 53/74] Roll tab --- Core.lua | 37 +++- Data.lua | 8 +- Roll.lua | 105 ++++++---- UI.lua | 578 +++++++++++++++++++++++++++++++++++++------------------ 4 files changed, 487 insertions(+), 241 deletions(-) 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) From 0680020808db44d9c4b1156eb601b1e5d9df96e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Tue, 12 May 2026 16:31:54 +0100 Subject: [PATCH 54/74] Minor adjustments --- UI.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/UI.lua b/UI.lua index fdbdace..4412ec0 100644 --- a/UI.lua +++ b/UI.lua @@ -5,10 +5,10 @@ AltSystem = AltSystem or {} -local WINDOW_WIDTH = 726 +local WINDOW_WIDTH = 700 local WINDOW_HEIGHT = 500 -local CONTROLS_WIDTH = 363 -local LOG_WIDTH = 363 +local CONTROLS_WIDTH = 350 +local LOG_WIDTH = 350 local PADDING = 12 local ROW_HEIGHT = 26 local SECTION_GAP = 10 @@ -46,7 +46,7 @@ local function CreateDropdown(parent, name, labelText, options, defaultIndex, on local dropdown = CreateFrame("DropdownButton", name, container, "WowStyle1DropdownTemplate") if label then - dropdown:SetPoint("LEFT", label, "RIGHT", 8, 0) + dropdown:SetPoint("RIGHT", container, "RIGHT", 0, 0) else dropdown:SetPoint("LEFT", container, "LEFT", 0, 0) end @@ -111,7 +111,7 @@ end -- Helper: Create a sub-label (smaller golden text) local function CreateSubLabel(parent, text, x, y) - local label = parent:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + 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) @@ -268,7 +268,7 @@ function AltSystem:CreateMainFrame() AltSystem.State.selectedSkillName = AltSystem.Data.Skills[index] and AltSystem.Data.Skills[index].name or nil UpdateSkillWarning(index) end, - "GameFontNormalSmall" + "GameFontNormal" ) skillContainer:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos) skillContainer:SetWidth(CONTROLS_WIDTH - PADDING * 2) @@ -410,7 +410,7 @@ function AltSystem:CreateMainFrame() AltSystem.State.announceChannelIndex = 1 end end, - "GameFontNormalSmall") + "GameFontNormal") announceContainer:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos) announceContainer:SetWidth(CONTROLS_WIDTH - PADDING * 2) From 5089a1dea45c9f9f252fc4a6c3ec0e2d01126f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Tue, 12 May 2026 16:36:26 +0100 Subject: [PATCH 55/74] Messing with sizes and padding --- UI.lua | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/UI.lua b/UI.lua index 4412ec0..0b989bb 100644 --- a/UI.lua +++ b/UI.lua @@ -50,7 +50,7 @@ local function CreateDropdown(parent, name, labelText, options, defaultIndex, on else dropdown:SetPoint("LEFT", container, "LEFT", 0, 0) end - dropdown:SetWidth(180) + dropdown:SetWidth(190) dropdown:SetupMenu(function(dropdown, rootDescription) for i, option in ipairs(options) do @@ -343,6 +343,37 @@ function AltSystem:CreateMainFrame() 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 From e08d9f63cb09f240121ef896367f367693816d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Fri, 15 May 2026 11:58:28 +0100 Subject: [PATCH 56/74] Build skills feature docs --- docs/5-build_skills.md | 39 +++++++++++++++++++++++++++++++ docs/build_skills_tab_design.png | Bin 0 -> 100436 bytes docs/roll_tab_design.png | Bin 71752 -> 77503 bytes 3 files changed, 39 insertions(+) create mode 100644 docs/5-build_skills.md create mode 100644 docs/build_skills_tab_design.png diff --git a/docs/5-build_skills.md b/docs/5-build_skills.md new file mode 100644 index 0000000..ed573ec --- /dev/null +++ b/docs/5-build_skills.md @@ -0,0 +1,39 @@ +# Feature: Build Skills tab +The second tab of the addon will follow these [designs](./build_skills_tab_design.png). + +## Acceptance Criteria +- This screen should show the same skills we use in the main screen, which come from the TRP profile +- The skills should be sorted by level +- The skill list should be scrollable, with the "Save" button pinned/sticky to the bottom +## Editing skills +- The user should be able to edit the name, level, and numerical score of each skill +- Edits should not be saved until the user explicitly clicks the "Save" button +### Skill Level and Value +- When a skill level is selected, the numerical score dropdown should update to only allow values within the skill level + - Inept: 0 + - Novice: 1-5 + - Adept: 6-10 + - Expert: 11-19 + - Master: 20 +### Deleting skills +- Clicking the "Delete" button should remove the skill from the list +### Adding Skills +- Clicking the "Add a Row" button should add a new skill row to the list +- Default values for the new skill should be: + - Name: Skillname + - Level: Novice + - Value: 1 +### Saving Skills +- When clicking the "Save" button: + - Newly added skills should be added to the TRP profile + - Existing skills should be updated in the TRP profile + - Skills that were deleted should also be removed from the TRP profile +- Icons should not be changed +## References +- Refer to [Data.lua](../Data.lua) for details on how we currently fetch the skills from TRP +- Refer to the TRP3 source code in case it's necessary [here](https://github.com/Total-RP/Total-RP-3) + +--- + +## Implementation Plan +TODO \ No newline at end of file diff --git a/docs/build_skills_tab_design.png b/docs/build_skills_tab_design.png new file mode 100644 index 0000000000000000000000000000000000000000..ddbfdd3285606e326bd67833d8876c6172c9c878 GIT binary patch literal 100436 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

1oa-EAKTA<|Rsu63VUjTNtBH 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 literal 0 HcmV?d00001 diff --git a/docs/roll_tab_design.png b/docs/roll_tab_design.png index 5754915dd6627db6dca2b293a92cc0fa9a70050b..3ebb626ffc9b75b311eb1a735a392aaa3db34064 100644 GIT binary patch literal 77503 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 Co3e5M literal 71752 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 Date: Fri, 15 May 2026 12:15:09 +0100 Subject: [PATCH 57/74] Build skills plan --- docs/5-build_skills.md | 95 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/docs/5-build_skills.md b/docs/5-build_skills.md index ed573ec..9b76326 100644 --- a/docs/5-build_skills.md +++ b/docs/5-build_skills.md @@ -36,4 +36,97 @@ The second tab of the addon will follow these [designs](./build_skills_tab_desig --- ## Implementation Plan -TODO \ No newline at end of file + +### 1. Add a `SaveSkills` function to Data.lua +- Create `AltSystem.Data:SaveSkills(editedSkills)` that writes skills back to the TRP3 profile +- Access the TRP3 profile via `TRP3_API.profile.getData("player/characteristics")` to get the `characteristics.PS` (personality traits) array +- For each edited skill, update or insert entries in `PS`: + - `LT` = skill name + - `RT` = level keyword (e.g. "Novice", "Adept", etc.) + - `V2` = numeric value (0–20) + - `IC` = preserve existing icon (do not change); for new skills, use a sensible default icon (e.g. `"inv_misc_questionmark"`) +- Remove any PS entries that were deleted by the user +- After writing, call `TRP3_API.dashboard.showCharacteristics()` or fire the appropriate TRP3 event if needed to refresh TRP3's own UI +- **Edge case:** If the TRP3 API is unavailable, show a warning message and abort save + +### 2. Extract skill-level constants into shared lookup tables in Data.lua +- The acceptance criteria defines value ranges per level (Inept: 0, Novice: 1–5, Adept: 6–10, Expert: 11–19, Master: 20) +- `SKILL_KEYWORD_RANGES` already exists but excludes Inept/Master min-max correctly for the Build tab's needs; extend or create a new table `AltSystem.Data.SkillValueRanges` that is accessible from UI.lua: + ``` + { Inept = {min=0, max=0}, Novice = {min=1, max=5}, Adept = {min=6, max=10}, Expert = {min=11, max=19}, Master = {min=20, max=20} } + ``` +- Create `AltSystem.Data.SkillLevelOrder` — an ordered array `{"Inept", "Novice", "Adept", "Expert", "Master"}` for populating the level dropdown in display order +- Create a helper `AltSystem.Data:GetDefaultValueForLevel(level)` that returns the minimum value for that level (used when the user changes level to auto-set the value) + +### 3. Add a function to read raw skills (with numeric values) from TRP3 in Data.lua +- Currently `RefreshSkills()` converts TRP3 traits into `{name, level, modifier}` — the Build tab needs the raw **numeric value** and the **icon** as well +- Create `AltSystem.Data:GetEditableSkills()` that returns an array of `{name, level, value, icon}` for each valid skill trait in the TRP3 profile (excluding Base Roll and Unskilled, which are system-generated entries) +- Sort the returned skills by level using `AltSystem.Data.SkillLevelOrder` ordering (Inept first, Master last) — matching the acceptance criteria "sorted by level" +- Reuse existing helpers `FindSkillKeyword` and `ParseSkillLevel` (promote them from local to module-level if needed, or call internally) + +### 4. Build the Build Skills tab UI (new file: BuildSkillsUI.lua) +- Create a new file to keep UI.lua manageable; register it in `AltSystem.toc` between `UI.lua` and `Roll.lua` +- Create `AltSystem:CreateBuildSkillsContent(parentFrame)` called from `CreateMainFrame` in UI.lua (replacing the placeholder) +- **Layout structure:** + - **Info text** at top — two golden/yellow paragraphs explaining that skills come from TRP (matches mockup) + - **"Skill list" section header** + - **Column headers**: Name, Level, Value (bold golden text) + - **Scrollable skill list** — a `ScrollFrame` containing dynamically created skill rows + - **"Add A Row" button** — anchored below the last skill row, inside the scroll child + - **"Save Skills to TRP" button** — pinned/sticky at the bottom of the tab, outside the scroll frame + +### 5. Implement editable skill rows +- Each skill row is a frame containing: + - **Name**: `EditBox` (text input) — pre-filled with current skill name + - **Level**: `DropdownButton` (WowStyle1DropdownTemplate) — options: Inept, Novice, Adept, Expert, Master + - **Value**: `DropdownButton` — options dynamically generated based on selected level (e.g. Novice → 1,2,3,4,5) + - **Delete button**: A button with a trash-can icon/red texture that removes the row +- Store all row data in a local working copy array (`editableSkills`), not directly in `AltSystem.Data.Skills` +- When the **level dropdown** changes: + - Update the value dropdown options to only show valid values for the new level + - Auto-set the value to the minimum for that level (e.g. switching to Adept → value becomes 6) +- **Row management:** + - `CreateSkillRow(parent, index, skillData)` — creates or recycles a row frame + - `RefreshSkillRows()` — rebuilds/repositions all rows and updates scroll child height + - Deleting a row removes it from `editableSkills` and calls `RefreshSkillRows()` + +### 6. Implement "Add A Row" functionality +- Clicking "Add A Row" inserts a new entry into `editableSkills`: + - `{ name = "Skillname", level = "Novice", value = 1, icon = "inv_misc_questionmark", isNew = true }` +- Calls `RefreshSkillRows()` to render the new row +- The scroll frame should auto-scroll to show the new row + +### 7. Implement "Save Skills to TRP" functionality +- On click, call `AltSystem.Data:SaveSkills(editableSkills)` which: + 1. Reads current `characteristics.PS` from TRP3 + 2. Rebuilds the PS array: keeps non-skill traits untouched, updates/adds/removes skill traits based on `editableSkills` + 3. Writes the updated PS back to the TRP3 profile data + 4. Calls `RefreshSkills()` so the Use Skills tab dropdown reflects the changes immediately +- Show a confirmation message (print to chat or a brief on-screen text) on successful save +- **Edge cases:** + - Empty skill list: allowed — just remove all skill traits from PS + - Duplicate skill names: allowed (TRP3 doesn't enforce uniqueness) + - Unsaved changes + tab switch: no confirmation dialog required (per spec, changes are just lost) + +### 8. Wire up tab switching to populate Build Skills tab +- In `SelectTab(2)` (UI.lua), call `AltSystem:RefreshBuildSkillsList()` to reload skills from TRP3 into the working copy +- This ensures the Build tab always shows the latest TRP3 data when opened, and any unsaved edits are discarded on tab switch + +### 9. Update AltSystem.toc +- Add `BuildSkillsUI.lua` to the file list (after `UI.lua`, before `Roll.lua`) + +### 10. Testing checklist +- [ ] Build Skills tab shows skills from TRP3 profile, sorted by level +- [ ] Skill name is editable via text input +- [ ] Level dropdown shows all 5 levels; changing level updates value dropdown options and auto-selects minimum value +- [ ] Value dropdown only shows values valid for the current level +- [ ] Delete button removes the row immediately +- [ ] "Add A Row" adds a row with defaults (Skillname, Novice, 1) +- [ ] Skill list scrolls when rows exceed visible area +- [ ] "Save" button is always visible (pinned to bottom) +- [ ] Save writes correct data to TRP3 profile (LT, RT, V2, IC preserved) +- [ ] Save does not modify icons of existing skills +- [ ] After save, Use Skills tab dropdown reflects the updated skills +- [ ] Switching tabs discards unsaved changes and reloads from TRP3 +- [ ] Works correctly with 0 skills (empty profile) +- [ ] Works correctly with many skills (20+) — scroll behavior \ No newline at end of file From d8f842f85494e40831c5126b5d0867dfbb489d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Fri, 15 May 2026 12:45:30 +0100 Subject: [PATCH 58/74] Build skills tab --- .idea/.gitignore | 0 .idea/misc.xml | 0 .idea/modules.xml | 0 .idea/vcs.xml | 0 AltSystem.iml | 0 AltSystem.toc | 1 + BuildSkillsUI.lua | 310 +++++++++++++++++++++++++++++++ Core.lua | 0 Data.lua | 109 +++++++++++ README.md | 0 Roll.lua | 0 UI.lua | 6 +- docs/1-interface.md | 0 docs/2-skills.md | 0 docs/3-announce.md | 0 docs/4-redesign.md | 0 docs/5-build_skills.md | 0 docs/Changelog.md | 0 docs/build_skills_tab_design.png | Bin docs/roll_tab_design.png | Bin 20 files changed, 423 insertions(+), 3 deletions(-) mode change 100644 => 100755 .idea/.gitignore mode change 100644 => 100755 .idea/misc.xml mode change 100644 => 100755 .idea/modules.xml mode change 100644 => 100755 .idea/vcs.xml mode change 100644 => 100755 AltSystem.iml mode change 100644 => 100755 AltSystem.toc create mode 100755 BuildSkillsUI.lua mode change 100644 => 100755 Core.lua mode change 100644 => 100755 Data.lua mode change 100644 => 100755 README.md mode change 100644 => 100755 Roll.lua mode change 100644 => 100755 UI.lua mode change 100644 => 100755 docs/1-interface.md mode change 100644 => 100755 docs/2-skills.md mode change 100644 => 100755 docs/3-announce.md mode change 100644 => 100755 docs/4-redesign.md mode change 100644 => 100755 docs/5-build_skills.md mode change 100644 => 100755 docs/Changelog.md mode change 100644 => 100755 docs/build_skills_tab_design.png mode change 100644 => 100755 docs/roll_tab_design.png diff --git a/.idea/.gitignore b/.idea/.gitignore old mode 100644 new mode 100755 diff --git a/.idea/misc.xml b/.idea/misc.xml old mode 100644 new mode 100755 diff --git a/.idea/modules.xml b/.idea/modules.xml old mode 100644 new mode 100755 diff --git a/.idea/vcs.xml b/.idea/vcs.xml old mode 100644 new mode 100755 diff --git a/AltSystem.iml b/AltSystem.iml old mode 100644 new mode 100755 diff --git a/AltSystem.toc b/AltSystem.toc old mode 100644 new mode 100755 index a085d34..3044c78 --- a/AltSystem.toc +++ b/AltSystem.toc @@ -8,5 +8,6 @@ Data.lua Core.lua +BuildSkillsUI.lua UI.lua Roll.lua diff --git a/BuildSkillsUI.lua b/BuildSkillsUI.lua new file mode 100755 index 0000000..edefa13 --- /dev/null +++ b/BuildSkillsUI.lua @@ -0,0 +1,310 @@ +-- AltSystem Build Skills UI +-- Creates the Build Skills tab content with editable skill rows, add/delete functionality, and save to TRP. + +AltSystem = AltSystem or {} + +local PADDING = 12 +local ROW_HEIGHT = 36 +local ROW_SPACING = 8 +local NAME_WIDTH = 340 +local LEVEL_WIDTH = 140 +local VALUE_WIDTH = 80 +local DELETE_WIDTH = 30 + +-- Working copy of skills being edited (not saved until user clicks Save) +local editableSkills = {} + +-- Pool of created row frames for reuse +local skillRowFrames = {} + +-- References set during creation +local scrollFrame, scrollChild, addRowButton + +-- ─── Create a single skill row frame ──────────────────────────────────────── + +local function CreateSkillRowFrame(index) + local row = CreateFrame("Frame", "AltSystemSkillRow" .. index, scrollChild) + row:SetHeight(ROW_HEIGHT) + + -- Dark background + local bg = row:CreateTexture(nil, "BACKGROUND") + bg:SetAllPoints() + bg:SetColorTexture(0.1, 0.1, 0.1, 0.8) + row.bg = bg + + -- Name EditBox + local nameBox = CreateFrame("EditBox", "AltSystemSkillName" .. index, row, "InputBoxTemplate") + nameBox:SetPoint("LEFT", row, "LEFT", 8, 0) + nameBox:SetSize(NAME_WIDTH, 24) + nameBox:SetAutoFocus(false) + nameBox:SetFontObject("GameFontHighlight") + row.nameBox = nameBox + + -- Level Dropdown + local levelDropdown = CreateFrame("DropdownButton", "AltSystemSkillLevel" .. index, row, "WowStyle1DropdownTemplate") + levelDropdown:SetPoint("LEFT", nameBox, "RIGHT", 12, 0) + levelDropdown:SetWidth(LEVEL_WIDTH) + row.levelDropdown = levelDropdown + + -- Value Dropdown + local valueDropdown = CreateFrame("DropdownButton", "AltSystemSkillValue" .. index, row, "WowStyle1DropdownTemplate") + valueDropdown:SetPoint("LEFT", levelDropdown, "RIGHT", 8, 0) + valueDropdown:SetWidth(VALUE_WIDTH) + row.valueDropdown = valueDropdown + + -- Delete Button + local deleteBtn = CreateFrame("Button", "AltSystemSkillDelete" .. index, row) + deleteBtn:SetSize(DELETE_WIDTH, DELETE_WIDTH) + deleteBtn:SetPoint("LEFT", valueDropdown, "RIGHT", 8, 0) + + local deleteBg = deleteBtn:CreateTexture(nil, "BACKGROUND") + deleteBg:SetAllPoints() + deleteBg:SetColorTexture(0.5, 0.1, 0.1, 0.9) + deleteBtn.bg = deleteBg + + local deleteText = deleteBtn:CreateFontString(nil, "OVERLAY", "GameFontNormal") + deleteText:SetPoint("CENTER") + deleteText:SetText("X") + deleteText:SetTextColor(1, 0.3, 0.3) + + deleteBtn:SetScript("OnEnter", function(self) + self.bg:SetColorTexture(0.7, 0.15, 0.15, 1) + end) + deleteBtn:SetScript("OnLeave", function(self) + self.bg:SetColorTexture(0.5, 0.1, 0.1, 0.9) + end) + + row.deleteBtn = deleteBtn + + return row +end + +-- ─── Refresh all skill rows ───────────────────────────────────────────────── + +local function RefreshSkillRows() + -- Hide all existing row frames + for _, row in ipairs(skillRowFrames) do + row:Hide() + end + + local yPos = 0 + + for i, skillData in ipairs(editableSkills) do + local row = skillRowFrames[i] + if not row then + row = CreateSkillRowFrame(i) + skillRowFrames[i] = row + end + + -- Clear previous anchor points before repositioning + row:ClearAllPoints() + row:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 0, -yPos) + row:SetPoint("TOPRIGHT", scrollChild, "TOPRIGHT", 0, -yPos) + + -- Populate name + row.nameBox:SetText(skillData.name or "") + row.nameBox:SetScript("OnTextChanged", function(self) + local idx = nil + for j, s in ipairs(editableSkills) do + if skillRowFrames[j] == row then + idx = j + break + end + end + if idx then + editableSkills[idx].name = self:GetText() + end + end) + + -- Setup level dropdown + local currentRowIndex = i + row.levelDropdown:SetupMenu(function(dropdown, rootDescription) + for _, levelName in ipairs(AltSystem.Data.SkillLevelOrder) do + rootDescription:CreateRadio( + levelName, + function() + return editableSkills[currentRowIndex] and editableSkills[currentRowIndex].level == levelName + end, + function() + if editableSkills[currentRowIndex] then + editableSkills[currentRowIndex].level = levelName + editableSkills[currentRowIndex].value = AltSystem.Data:GetDefaultValueForLevel(levelName) + RefreshSkillRows() + end + end + ) + end + end) + + -- Setup value dropdown based on current level + local range = AltSystem.Data.SkillValueRanges[skillData.level] + if range then + row.valueDropdown:SetupMenu(function(dropdown, rootDescription) + for v = range.min, range.max do + rootDescription:CreateRadio( + tostring(v), + function() + return editableSkills[currentRowIndex] and editableSkills[currentRowIndex].value == v + end, + function() + if editableSkills[currentRowIndex] then + editableSkills[currentRowIndex].value = v + end + end + ) + end + end) + end + + -- Bind delete button for current index + row.deleteBtn:SetScript("OnClick", function() + table.remove(editableSkills, currentRowIndex) + RefreshSkillRows() + end) + + row:Show() + yPos = yPos + ROW_HEIGHT + ROW_SPACING + end + + -- Reposition Add A Row button + if addRowButton then + addRowButton:ClearAllPoints() + addRowButton:SetPoint("TOPRIGHT", scrollChild, "TOPRIGHT", -PADDING, -(yPos + 4)) + end + + -- Update scroll child height + local totalHeight = yPos + ROW_HEIGHT + 20 -- extra space for Add A Row button + if scrollChild then + scrollChild:SetHeight(math.max(totalHeight, 1)) + end +end + +-- ─── Create Build Skills Content ──────────────────────────────────────────── + +function AltSystem:CreateBuildSkillsContent(parentFrame) + local contentWidth = parentFrame:GetWidth() or 692 + local contentHeight = parentFrame:GetHeight() or 444 + + local yPos = -PADDING + + -- Info text paragraph 1 + local info1 = parentFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal") + info1:SetPoint("TOPLEFT", parentFrame, "TOPLEFT", PADDING, yPos) + info1:SetPoint("TOPRIGHT", parentFrame, "TOPRIGHT", -PADDING, yPos) + info1:SetJustifyH("LEFT") + info1:SetText("All skills below are directly extracted from your TRP's characteristics sheet. You can view and edit them there at any time.") + info1:SetTextColor(0.9, 0.75, 0.2) + info1:SetWordWrap(true) + + yPos = yPos - (info1:GetStringHeight() or 16) - 10 + + -- Info text paragraph 2 + local info2 = parentFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal") + info2:SetPoint("TOPLEFT", parentFrame, "TOPLEFT", PADDING, yPos) + info2:SetPoint("TOPRIGHT", parentFrame, "TOPRIGHT", -PADDING, yPos) + info2:SetJustifyH("LEFT") + info2:SetText("This menu serves as an easy alternative for if you want to use this system fast without diving deep into understanding it and/or styling your TRP sheet at this point in time. All your changes made will not be saved to TRP up until you hit the specific button to do so.") + info2:SetTextColor(0.9, 0.75, 0.2) + info2:SetWordWrap(true) + + yPos = yPos - (info2:GetStringHeight() or 32) - 16 + + -- "Skill list" section header + local sectionHeader = parentFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge") + sectionHeader:SetPoint("TOPLEFT", parentFrame, "TOPLEFT", PADDING, yPos) + sectionHeader:SetText("Skill list") + sectionHeader:SetTextColor(1, 1, 1) + + yPos = yPos - 24 + + -- Column headers + local nameHeader = parentFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal") + nameHeader:SetPoint("TOPLEFT", parentFrame, "TOPLEFT", PADDING + 8, yPos) + nameHeader:SetText("Name") + nameHeader:SetTextColor(0.9, 0.75, 0.2) + + local levelHeader = parentFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal") + levelHeader:SetPoint("TOPLEFT", parentFrame, "TOPLEFT", PADDING + 8 + NAME_WIDTH + 12, yPos) + levelHeader:SetText("Level") + levelHeader:SetTextColor(0.9, 0.75, 0.2) + + local valueHeader = parentFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal") + valueHeader:SetPoint("TOPLEFT", parentFrame, "TOPLEFT", PADDING + 8 + NAME_WIDTH + 12 + LEVEL_WIDTH + 8, yPos) + valueHeader:SetText("Value") + valueHeader:SetTextColor(0.9, 0.75, 0.2) + + yPos = yPos - 20 + + -- Save button (pinned to bottom, outside scroll frame) + local saveButton = CreateFrame("Button", "AltSystemSaveSkillsButton", parentFrame, "UIPanelButtonTemplate") + saveButton:SetSize(180, 30) + saveButton:SetPoint("BOTTOM", parentFrame, "BOTTOM", 0, PADDING) + saveButton:SetText("Save Skills to TRP") + + -- Style the save button with a dark red tint + local saveBg = saveButton:CreateTexture(nil, "BACKGROUND") + saveBg:SetAllPoints() + saveBg:SetColorTexture(0.4, 0.08, 0.08, 0.9) + + saveButton:SetScript("OnClick", function() + local success = AltSystem.Data:SaveSkills(editableSkills) + if success and AltSystem.RefreshSkillDropdown then + AltSystem:RefreshSkillDropdown() + end + end) + + -- Scrollable skill list area (between column headers and save button) + scrollFrame = CreateFrame("ScrollFrame", "AltSystemBuildSkillsScrollFrame", parentFrame, "UIPanelScrollFrameTemplate") + scrollFrame:SetPoint("TOPLEFT", parentFrame, "TOPLEFT", PADDING, yPos) + scrollFrame:SetPoint("BOTTOMRIGHT", parentFrame, "BOTTOMRIGHT", -PADDING - 22, saveButton:GetHeight() + PADDING + 8) + + scrollChild = CreateFrame("Frame", "AltSystemBuildSkillsScrollChild", scrollFrame) + scrollChild:SetWidth(scrollFrame:GetWidth() or (contentWidth - PADDING * 2 - 22)) + scrollChild:SetHeight(1) + scrollFrame:SetScrollChild(scrollChild) + + -- Update scroll child width when frame resizes + scrollFrame:SetScript("OnSizeChanged", function(self) + scrollChild:SetWidth(self:GetWidth()) + end) + + -- "Add A Row" button (inside scroll child, below last row) + addRowButton = CreateFrame("Button", "AltSystemAddRowButton", scrollChild, "UIPanelButtonTemplate") + addRowButton:SetSize(130, 26) + addRowButton:SetText("+ Add A Row") + addRowButton:SetPoint("TOPRIGHT", scrollChild, "TOPRIGHT", -PADDING, 0) + + -- Style add row button + local addBg = addRowButton:CreateTexture(nil, "BACKGROUND") + addBg:SetAllPoints() + addBg:SetColorTexture(0.4, 0.08, 0.08, 0.9) + + addRowButton:SetScript("OnClick", function() + table.insert(editableSkills, { + name = "Skillname", + level = "Novice", + value = 1, + icon = "inv_misc_questionmark", + isNew = true, + }) + RefreshSkillRows() + + -- Auto-scroll to bottom to show the new row + C_Timer.After(0.05, function() + if scrollFrame then + scrollFrame:SetVerticalScroll(scrollFrame:GetVerticalScrollRange()) + end + end) + end) + + AltSystem.BuildSkillsScrollFrame = scrollFrame + AltSystem.BuildSkillsScrollChild = scrollChild +end + +-- ─── Refresh Build Skills list (called on tab switch) ─────────────────────── + +function AltSystem:RefreshBuildSkillsList() + -- Reload skills from TRP3 into working copy, discarding any unsaved edits + editableSkills = AltSystem.Data:GetEditableSkills() + RefreshSkillRows() +end diff --git a/Core.lua b/Core.lua old mode 100644 new mode 100755 diff --git a/Data.lua b/Data.lua old mode 100644 new mode 100755 index d1e5f56..7ce6ffc --- a/Data.lua +++ b/Data.lua @@ -39,6 +39,27 @@ local SKILL_KEYWORD_RANGES = { ["Master"] = { min = 20, max = 20 }, } +-- Shared lookup tables for the Build Skills tab +AltSystem.Data.SkillValueRanges = { + ["Inept"] = { min = 0, max = 0 }, + ["Novice"] = { min = 1, max = 5 }, + ["Adept"] = { min = 6, max = 10 }, + ["Expert"] = { min = 11, max = 19 }, + ["Master"] = { min = 20, max = 20 }, +} + +-- Ordered list of skill levels for dropdown display +AltSystem.Data.SkillLevelOrder = { "Inept", "Novice", "Adept", "Expert", "Master" } + +-- Returns the minimum value for a given skill level +function AltSystem.Data:GetDefaultValueForLevel(level) + local range = AltSystem.Data.SkillValueRanges[level] + if range then + return range.min + end + return 1 +end + -- Check if the trait's right text field contains a valid skill keyword. -- Returns the matched keyword if found, nil otherwise. local function FindSkillKeyword(rightText) @@ -134,6 +155,94 @@ function AltSystem.Data:RefreshSkills() return skills end +-- Returns an array of {name, level, value, icon} for each valid skill trait in the TRP3 profile. +-- Excludes Base Roll and Unskilled (system-generated). Sorted by level order (Inept first, Master last). +function AltSystem.Data:GetEditableSkills() + local skills = {} + + if TRP3_API and TRP3_API.profile and TRP3_API.profile.getData then + local ok, characteristics = pcall(TRP3_API.profile.getData, "player/characteristics") + if ok and characteristics and characteristics.PS then + for _, trait in ipairs(characteristics.PS) do + local skillName = trait.LT + local numericValue = trait.V2 or 0 + local keyword = FindSkillKeyword(trait.RT) + if skillName and skillName ~= "" and keyword then + table.insert(skills, { + name = skillName, + level = keyword, + value = numericValue, + icon = trait.IC or "inv_misc_questionmark", + }) + end + end + end + end + + -- Sort by level order descending (Master first, Inept last) + local levelOrderMap = {} + for i, lvl in ipairs(AltSystem.Data.SkillLevelOrder) do + levelOrderMap[lvl] = i + end + table.sort(skills, function(a, b) + return (levelOrderMap[a.level] or 99) > (levelOrderMap[b.level] or 99) + end) + + return skills +end + +-- Saves the edited skills back to the TRP3 profile. +-- editedSkills is an array of {name, level, value, icon}. +function AltSystem.Data:SaveSkills(editedSkills) + if not TRP3_API or not TRP3_API.profile or not TRP3_API.profile.getData then + print("|cFFFF0000AltSystem:|r TRP3 API is unavailable. Cannot save skills.") + return false + end + + local ok, characteristics = pcall(TRP3_API.profile.getData, "player/characteristics") + if not ok or not characteristics then + print("|cFFFF0000AltSystem:|r Could not access TRP3 profile characteristics. Cannot save skills.") + return false + end + + -- Ensure PS array exists + if not characteristics.PS then + characteristics.PS = {} + end + + -- Separate non-skill traits from skill traits in the existing PS + local nonSkillTraits = {} + for _, trait in ipairs(characteristics.PS) do + local keyword = FindSkillKeyword(trait.RT) + if not keyword then + -- This is not a skill trait, preserve it + table.insert(nonSkillTraits, trait) + end + end + + -- Rebuild PS: non-skill traits first, then edited skills + local newPS = {} + for _, trait in ipairs(nonSkillTraits) do + table.insert(newPS, trait) + end + for _, skill in ipairs(editedSkills) do + table.insert(newPS, { + LT = skill.name, + RT = skill.level, + V2 = skill.value, + IC = skill.icon or "inv_misc_questionmark", + }) + end + + characteristics.PS = newPS + + -- Refresh the Use Skills tab data + AltSystem.Data:RefreshSkills() + + print("|cFF00FF00AltSystem:|r Skills saved to TRP profile successfully.") + return true +end + -- Initialize with the default skill list AltSystem.Data.Skills = {} for _, skill in ipairs(DEFAULT_SKILLS) do diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/Roll.lua b/Roll.lua old mode 100644 new mode 100755 diff --git a/UI.lua b/UI.lua old mode 100644 new mode 100755 index 0b989bb..58efbb7 --- a/UI.lua +++ b/UI.lua @@ -184,9 +184,8 @@ function AltSystem:CreateMainFrame() buildSkillsContent:SetSize(contentWidth, tabContentHeight) buildSkillsContent:Hide() - local buildPlaceholder = buildSkillsContent:CreateFontString(nil, "OVERLAY", "GameFontHighlight") - buildPlaceholder:SetPoint("CENTER") - buildPlaceholder:SetText("Coming soon") + -- Build Skills tab content (created in BuildSkillsUI.lua) + AltSystem:CreateBuildSkillsContent(buildSkillsContent) -- Tab switching logic local function SelectTab(tabIndex) @@ -200,6 +199,7 @@ function AltSystem:CreateMainFrame() buildSkillsContent:Show() tabUseSkillsBg:SetColorTexture(0.3, 0.3, 0.3, 1) tabBuildSkillsBg:SetColorTexture(0.15, 0.15, 0.15, 1) + AltSystem:RefreshBuildSkillsList() end end diff --git a/docs/1-interface.md b/docs/1-interface.md old mode 100644 new mode 100755 diff --git a/docs/2-skills.md b/docs/2-skills.md old mode 100644 new mode 100755 diff --git a/docs/3-announce.md b/docs/3-announce.md old mode 100644 new mode 100755 diff --git a/docs/4-redesign.md b/docs/4-redesign.md old mode 100644 new mode 100755 diff --git a/docs/5-build_skills.md b/docs/5-build_skills.md old mode 100644 new mode 100755 diff --git a/docs/Changelog.md b/docs/Changelog.md old mode 100644 new mode 100755 diff --git a/docs/build_skills_tab_design.png b/docs/build_skills_tab_design.png old mode 100644 new mode 100755 diff --git a/docs/roll_tab_design.png b/docs/roll_tab_design.png old mode 100644 new mode 100755 From 57756acbff11768eade4f2edc2c78640100e5645 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Fri, 15 May 2026 13:04:28 +0100 Subject: [PATCH 59/74] Delete button --- BuildSkillsUI.lua | 49 +++++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/BuildSkillsUI.lua b/BuildSkillsUI.lua index edefa13..f101d89 100755 --- a/BuildSkillsUI.lua +++ b/BuildSkillsUI.lua @@ -26,11 +26,6 @@ local function CreateSkillRowFrame(index) local row = CreateFrame("Frame", "AltSystemSkillRow" .. index, scrollChild) row:SetHeight(ROW_HEIGHT) - -- Dark background - local bg = row:CreateTexture(nil, "BACKGROUND") - bg:SetAllPoints() - bg:SetColorTexture(0.1, 0.1, 0.1, 0.8) - row.bg = bg -- Name EditBox local nameBox = CreateFrame("EditBox", "AltSystemSkillName" .. index, row, "InputBoxTemplate") @@ -52,26 +47,48 @@ local function CreateSkillRowFrame(index) valueDropdown:SetWidth(VALUE_WIDTH) row.valueDropdown = valueDropdown - -- Delete Button + -- Delete Button (trash can icon with dark red background, matching design) local deleteBtn = CreateFrame("Button", "AltSystemSkillDelete" .. index, row) deleteBtn:SetSize(DELETE_WIDTH, DELETE_WIDTH) deleteBtn:SetPoint("LEFT", valueDropdown, "RIGHT", 8, 0) - local deleteBg = deleteBtn:CreateTexture(nil, "BACKGROUND") - deleteBg:SetAllPoints() - deleteBg:SetColorTexture(0.5, 0.1, 0.1, 0.9) - deleteBtn.bg = deleteBg - local deleteText = deleteBtn:CreateFontString(nil, "OVERLAY", "GameFontNormal") - deleteText:SetPoint("CENTER") - deleteText:SetText("X") - deleteText:SetTextColor(1, 0.3, 0.3) + local deleteIcon = deleteBtn:CreateTexture(nil, "ARTWORK") + deleteIcon:SetSize(DELETE_WIDTH - 4, DELETE_WIDTH - 4) + deleteIcon:SetPoint("CENTER") + deleteIcon:SetTexture("Interface\\Buttons\\UI-GroupLoot-Pass-Up") + deleteBtn.icon = deleteIcon + + -- Shimmer animation on hover + local shimmer = deleteBtn:CreateTexture(nil, "OVERLAY") + shimmer:SetSize(DELETE_WIDTH - 4, DELETE_WIDTH - 4) + shimmer:SetPoint("CENTER") + shimmer:SetTexture("Interface\\Buttons\\UI-GroupLoot-Pass-Up") + shimmer:SetBlendMode("ADD") + shimmer:SetAlpha(0) + deleteBtn.shimmer = shimmer + + local shimmerAnim = shimmer:CreateAnimationGroup() + shimmerAnim:SetLooping("REPEAT") + local fadeIn = shimmerAnim:CreateAnimation("Alpha") + fadeIn:SetFromAlpha(0) + fadeIn:SetToAlpha(0.5) + fadeIn:SetDuration(0.5) + fadeIn:SetOrder(1) + local fadeOut = shimmerAnim:CreateAnimation("Alpha") + fadeOut:SetFromAlpha(0.5) + fadeOut:SetToAlpha(0) + fadeOut:SetDuration(0.5) + fadeOut:SetOrder(2) + deleteBtn.shimmerAnim = shimmerAnim deleteBtn:SetScript("OnEnter", function(self) - self.bg:SetColorTexture(0.7, 0.15, 0.15, 1) + self.shimmer:SetAlpha(0) + self.shimmerAnim:Play() end) deleteBtn:SetScript("OnLeave", function(self) - self.bg:SetColorTexture(0.5, 0.1, 0.1, 0.9) + self.shimmerAnim:Stop() + self.shimmer:SetAlpha(0) end) row.deleteBtn = deleteBtn From 9423171d545b394eda4a5555c633954316ec92ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Fri, 15 May 2026 13:10:59 +0100 Subject: [PATCH 60/74] Redesigned dropdowns --- BuildSkillsUI.lua | 104 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 88 insertions(+), 16 deletions(-) diff --git a/BuildSkillsUI.lua b/BuildSkillsUI.lua index f101d89..a189554 100755 --- a/BuildSkillsUI.lua +++ b/BuildSkillsUI.lua @@ -20,31 +20,100 @@ local skillRowFrames = {} -- References set during creation local scrollFrame, scrollChild, addRowButton +-- ─── Helper: Create a flat dark input box matching the blocky design ──────── + +local function CreateFlatEditBox(name, parent, width) + local container = CreateFrame("Frame", name .. "Container", parent, "BackdropTemplate") + container:SetSize(width, 28) + container:SetBackdrop({ + bgFile = "Interface\\ChatFrame\\ChatFrameBackground", + edgeFile = "Interface\\ChatFrame\\ChatFrameBackground", + edgeSize = 1, + }) + container:SetBackdropColor(0.12, 0.12, 0.12, 1) + container:SetBackdropBorderColor(0.25, 0.25, 0.25, 1) + + local editBox = CreateFrame("EditBox", name, container) + editBox:SetPoint("LEFT", 8, 0) + editBox:SetPoint("RIGHT", -8, 0) + editBox:SetHeight(28) + editBox:SetAutoFocus(false) + editBox:SetFontObject("GameFontHighlight") + + return container, editBox +end + +-- ─── Helper: Create a flat dark dropdown button matching the blocky design ── + +local function CreateFlatDropdown(name, parent, width) + local btn = CreateFrame("Button", name, parent, "BackdropTemplate") + btn:SetSize(width, 28) + btn:SetBackdrop({ + bgFile = "Interface\\ChatFrame\\ChatFrameBackground", + edgeFile = "Interface\\ChatFrame\\ChatFrameBackground", + edgeSize = 1, + }) + btn:SetBackdropColor(0.12, 0.12, 0.12, 1) + btn:SetBackdropBorderColor(0.25, 0.25, 0.25, 1) + + -- Text label (left-aligned) + local text = btn:CreateFontString(nil, "OVERLAY", "GameFontHighlight") + text:SetPoint("LEFT", 8, 0) + text:SetPoint("RIGHT", -24, 0) + text:SetJustifyH("LEFT") + btn.label = text + + -- Gold arrow icon (right side) + local arrow = btn:CreateTexture(nil, "OVERLAY") + arrow:SetSize(12, 12) + arrow:SetPoint("RIGHT", -6, 0) + arrow:SetTexture("Interface\\ChatFrame\\ChatFrameExpandArrow") + arrow:SetVertexColor(0.9, 0.75, 0.2, 1) + btn.arrow = arrow + + -- Hover highlight + btn:SetScript("OnEnter", function(self) + self:SetBackdropColor(0.18, 0.18, 0.18, 1) + end) + btn:SetScript("OnLeave", function(self) + self:SetBackdropColor(0.12, 0.12, 0.12, 1) + end) + + -- Menu storage + btn.menuSetup = nil + btn:SetScript("OnClick", function(self) + if self.menuSetup then + MenuUtil.CreateContextMenu(self, self.menuSetup) + end + end) + + function btn:SetupMenu(setupFunc) + self.menuSetup = setupFunc + end + + return btn +end + -- ─── Create a single skill row frame ──────────────────────────────────────── local function CreateSkillRowFrame(index) local row = CreateFrame("Frame", "AltSystemSkillRow" .. index, scrollChild) row:SetHeight(ROW_HEIGHT) - - -- Name EditBox - local nameBox = CreateFrame("EditBox", "AltSystemSkillName" .. index, row, "InputBoxTemplate") - nameBox:SetPoint("LEFT", row, "LEFT", 8, 0) - nameBox:SetSize(NAME_WIDTH, 24) - nameBox:SetAutoFocus(false) - nameBox:SetFontObject("GameFontHighlight") + -- Name EditBox (flat dark style) + local nameContainer, nameBox = CreateFlatEditBox("AltSystemSkillName" .. index, row, NAME_WIDTH) + nameContainer:SetPoint("LEFT", row, "LEFT", 8, 0) row.nameBox = nameBox + row.nameContainer = nameContainer - -- Level Dropdown - local levelDropdown = CreateFrame("DropdownButton", "AltSystemSkillLevel" .. index, row, "WowStyle1DropdownTemplate") - levelDropdown:SetPoint("LEFT", nameBox, "RIGHT", 12, 0) - levelDropdown:SetWidth(LEVEL_WIDTH) + -- Level Dropdown (flat dark style) + local levelDropdown = CreateFlatDropdown("AltSystemSkillLevel" .. index, row, LEVEL_WIDTH) + levelDropdown:SetPoint("LEFT", nameContainer, "RIGHT", 12, 0) row.levelDropdown = levelDropdown - -- Value Dropdown - local valueDropdown = CreateFrame("DropdownButton", "AltSystemSkillValue" .. index, row, "WowStyle1DropdownTemplate") + -- Value Dropdown (flat dark style) + local valueDropdown = CreateFlatDropdown("AltSystemSkillValue" .. index, row, VALUE_WIDTH) valueDropdown:SetPoint("LEFT", levelDropdown, "RIGHT", 8, 0) - valueDropdown:SetWidth(VALUE_WIDTH) row.valueDropdown = valueDropdown -- Delete Button (trash can icon with dark red background, matching design) @@ -135,7 +204,8 @@ local function RefreshSkillRows() -- Setup level dropdown local currentRowIndex = i - row.levelDropdown:SetupMenu(function(dropdown, rootDescription) + row.levelDropdown.label:SetText(skillData.level or "Novice") + row.levelDropdown:SetupMenu(function(owner, rootDescription) for _, levelName in ipairs(AltSystem.Data.SkillLevelOrder) do rootDescription:CreateRadio( levelName, @@ -154,9 +224,10 @@ local function RefreshSkillRows() end) -- Setup value dropdown based on current level + row.valueDropdown.label:SetText(tostring(skillData.value or 1)) local range = AltSystem.Data.SkillValueRanges[skillData.level] if range then - row.valueDropdown:SetupMenu(function(dropdown, rootDescription) + row.valueDropdown:SetupMenu(function(owner, rootDescription) for v = range.min, range.max do rootDescription:CreateRadio( tostring(v), @@ -166,6 +237,7 @@ local function RefreshSkillRows() function() if editableSkills[currentRowIndex] then editableSkills[currentRowIndex].value = v + row.valueDropdown.label:SetText(tostring(v)) end end ) From cfdef3cc2673d674a29b487fab867b6935b7cadc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Fri, 15 May 2026 13:15:57 +0100 Subject: [PATCH 61/74] Redesigned dropdowns --- BuildSkillsUI.lua | 7 ++++--- UI.lua | 53 ++++++++++++++++++++++++++++------------------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/BuildSkillsUI.lua b/BuildSkillsUI.lua index a189554..73bced0 100755 --- a/BuildSkillsUI.lua +++ b/BuildSkillsUI.lua @@ -44,8 +44,9 @@ local function CreateFlatEditBox(name, parent, width) end -- ─── Helper: Create a flat dark dropdown button matching the blocky design ── +-- Shared globally so other files (UI.lua) can reuse it. -local function CreateFlatDropdown(name, parent, width) +function AltSystem.CreateFlatDropdown(name, parent, width) local btn = CreateFrame("Button", name, parent, "BackdropTemplate") btn:SetSize(width, 28) btn:SetBackdrop({ @@ -107,12 +108,12 @@ local function CreateSkillRowFrame(index) row.nameContainer = nameContainer -- Level Dropdown (flat dark style) - local levelDropdown = CreateFlatDropdown("AltSystemSkillLevel" .. index, row, LEVEL_WIDTH) + local levelDropdown = AltSystem.CreateFlatDropdown("AltSystemSkillLevel" .. index, row, LEVEL_WIDTH) levelDropdown:SetPoint("LEFT", nameContainer, "RIGHT", 12, 0) row.levelDropdown = levelDropdown -- Value Dropdown (flat dark style) - local valueDropdown = CreateFlatDropdown("AltSystemSkillValue" .. index, row, VALUE_WIDTH) + local valueDropdown = AltSystem.CreateFlatDropdown("AltSystemSkillValue" .. index, row, VALUE_WIDTH) valueDropdown:SetPoint("LEFT", levelDropdown, "RIGHT", 8, 0) row.valueDropdown = valueDropdown diff --git a/UI.lua b/UI.lua index 58efbb7..09e0cab 100755 --- a/UI.lua +++ b/UI.lua @@ -29,7 +29,7 @@ local function BuildSkillOptions() return options end --- Helper: Create a modern dropdown (WowStyle1DropdownTemplate) +-- 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) @@ -44,28 +44,32 @@ local function CreateDropdown(parent, name, labelText, options, defaultIndex, on local selectedIndex = defaultIndex or 1 - local dropdown = CreateFrame("DropdownButton", name, container, "WowStyle1DropdownTemplate") + 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 - dropdown:SetWidth(190) - dropdown:SetupMenu(function(dropdown, rootDescription) + -- 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(data) - return data == selectedIndex + function() + return i == selectedIndex end, - function(data) - selectedIndex = data + function() + selectedIndex = i + dropdown.label:SetText(option.text) if onSelect then - onSelect(data, options[data]) + onSelect(i, option) end - end, - i + end ) end end) @@ -74,6 +78,9 @@ local function CreateDropdown(parent, name, labelText, options, defaultIndex, on return selectedIndex end, function(idx) selectedIndex = idx + if options[idx] then + dropdown.label:SetText(options[idx].text) + end end end @@ -573,24 +580,28 @@ function AltSystem:RefreshSkillDropdown() -- Rebuild the dropdown menu with the new skill list if AltSystem.SkillDropdown then local skillOptions = BuildSkillOptions() - AltSystem.SkillDropdown:SetupMenu(function(dropdown, rootDescription) + -- 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(data) - return data == AltSystem.State.selectedSkillIndex + function() + return i == 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 + 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(data) + AltSystem.SetSkillIndex(i) end if AltSystem.UpdateSkillWarning then - AltSystem.UpdateSkillWarning(data) + AltSystem.UpdateSkillWarning(i) end - end, - i + end ) end end) From 26f25d969cc3e71b72f259020846bfd17224b6a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Fri, 15 May 2026 13:27:31 +0100 Subject: [PATCH 62/74] Redesigned radio buttons --- UI.lua | 123 ++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 91 insertions(+), 32 deletions(-) diff --git a/UI.lua b/UI.lua index 09e0cab..c838c90 100755 --- a/UI.lua +++ b/UI.lua @@ -84,27 +84,46 @@ local function CreateDropdown(parent, name, labelText, options, defaultIndex, on end end --- Helper: Create a radio button (CheckButton with radio texture) +-- 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 radio = CreateFrame("CheckButton", name, parent, "UIRadioButtonTemplate") - radio:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) - radio:SetChecked(isChecked) + local size = 20 + local btn = CreateFrame("CheckButton", name, parent) + btn:SetSize(size, size) + btn:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) - 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) + -- 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 - radio:SetScript("OnClick", function(self) + 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) - return radio + btn.UpdateVisual = UpdateVisual + return btn end -- Helper: Create a section header (golden text) @@ -242,6 +261,8 @@ function AltSystem:CreateMainFrame() 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" @@ -330,6 +351,7 @@ function AltSystem:CreateMainFrame() AltSystem.State.selectedDefenseIndex = index for i, radio in ipairs(armorRadios) do radio:SetChecked(i == index) + radio.UpdateVisual() end end @@ -361,6 +383,7 @@ function AltSystem:CreateMainFrame() AltSystem.State.selectedItemIndex = index for i, radio in ipairs(itemRadios) do radio:SetChecked(i == index) + radio.UpdateVisual() end end @@ -387,40 +410,76 @@ function AltSystem:CreateMainFrame() --CreateSubLabel(content, "Label", PADDING, yPos) --yPos = yPos - 22 - -- Shield checkbox - local shieldCheck = CreateFrame("CheckButton", "AltSystemShieldCheck", content, "UICheckButtonTemplate") + -- 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 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)") + 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 - local petCheck = CreateFrame("CheckButton", "AltSystemPetSummonCheck", content, "UICheckButtonTemplate") + -- 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 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)") + 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 From 8da464dcd27d059690a15156b7fb5405168c6e20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Fri, 15 May 2026 13:35:19 +0100 Subject: [PATCH 63/74] More restyling --- Data.lua | 4 ++-- UI.lua | 66 ++++++++++++++++++++++++++++---------------------------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/Data.lua b/Data.lua index 7ce6ffc..6706c49 100755 --- a/Data.lua +++ b/Data.lua @@ -252,8 +252,8 @@ end -- Item options: name and modifier (first entry = no item) AltSystem.Data.Items = { { name = "No item", modifier = 0 }, - { name = "Rare item", modifier = 3 }, - { name = "Epic item", modifier = 5 }, + { name = "Rare", modifier = 3 }, + { name = "Epic", modifier = 5 }, } -- Defense / Armor options: name and modifier diff --git a/UI.lua b/UI.lua index c838c90..0bccae5 100755 --- a/UI.lua +++ b/UI.lua @@ -372,41 +372,9 @@ function AltSystem:CreateMainFrame() 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: Modifiers (optional) CreateSectionHeader(content, "Modifiers (optional)", PADDING, yPos) - yPos = yPos - 18 + yPos = yPos - 28 --CreateSubLabel(content, "Label", PADDING, yPos) --yPos = yPos - 22 @@ -484,6 +452,38 @@ function AltSystem:CreateMainFrame() 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 From cdbaf7db9344303cf9fd39ff108372c289051a3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Fri, 15 May 2026 13:42:08 +0100 Subject: [PATCH 64/74] More restyling --- BuildSkillsUI.lua | 46 ++++++++++++++++++++++++++++++++++++++-------- UI.lua | 6 ++---- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/BuildSkillsUI.lua b/BuildSkillsUI.lua index 73bced0..c102705 100755 --- a/BuildSkillsUI.lua +++ b/BuildSkillsUI.lua @@ -95,6 +95,43 @@ function AltSystem.CreateFlatDropdown(name, parent, width) return btn end +-- ─── Helper: Create a flat dark button matching the blocky design ──────────── +-- Shared globally so other files (UI.lua) can reuse it. + +function AltSystem.CreateFlatButton(name, parent, width, height, text) + local btn = CreateFrame("Button", name, parent, "BackdropTemplate") + btn:SetSize(width, height) + btn:SetBackdrop({ + bgFile = "Interface\\ChatFrame\\ChatFrameBackground", + edgeFile = "Interface\\ChatFrame\\ChatFrameBackground", + edgeSize = 1, + }) + btn:SetBackdropColor(0.4, 0.08, 0.08, 0.9) + btn:SetBackdropBorderColor(0.25, 0.25, 0.25, 1) + + local label = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal") + label:SetPoint("CENTER") + label:SetText(text or "") + btn.label = label + + btn:SetScript("OnEnter", function(self) + self:SetBackdropColor(0.55, 0.12, 0.12, 0.9) + end) + btn:SetScript("OnLeave", function(self) + self:SetBackdropColor(0.4, 0.08, 0.08, 0.9) + end) + + function btn:SetText(newText) + self.label:SetText(newText) + end + + function btn:GetText() + return self.label:GetText() + end + + return btn +end + -- ─── Create a single skill row frame ──────────────────────────────────────── local function CreateSkillRowFrame(index) @@ -326,15 +363,8 @@ function AltSystem:CreateBuildSkillsContent(parentFrame) yPos = yPos - 20 -- Save button (pinned to bottom, outside scroll frame) - local saveButton = CreateFrame("Button", "AltSystemSaveSkillsButton", parentFrame, "UIPanelButtonTemplate") - saveButton:SetSize(180, 30) + local saveButton = AltSystem.CreateFlatButton("AltSystemSaveSkillsButton", parentFrame, 180, 30, "Save Skills to TRP") saveButton:SetPoint("BOTTOM", parentFrame, "BOTTOM", 0, PADDING) - saveButton:SetText("Save Skills to TRP") - - -- Style the save button with a dark red tint - local saveBg = saveButton:CreateTexture(nil, "BACKGROUND") - saveBg:SetAllPoints() - saveBg:SetColorTexture(0.4, 0.08, 0.08, 0.9) saveButton:SetScript("OnClick", function() local success = AltSystem.Data:SaveSkills(editableSkills) diff --git a/UI.lua b/UI.lua index 0bccae5..bfc7013 100755 --- a/UI.lua +++ b/UI.lua @@ -514,11 +514,9 @@ function AltSystem:CreateMainFrame() 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) + local rollBtn = AltSystem.CreateFlatButton("AltSystemRollBtn", content, CONTROLS_WIDTH - PADDING * 2, 32, rollLabel) + rollBtn:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos) rollBtn:SetScript("OnClick", function() AltSystem:PerformRoll(AltSystem.State.rollType) From 78ce755f713ea89b73b0ea9bfb56dabb8585f484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Fri, 15 May 2026 13:51:22 +0100 Subject: [PATCH 65/74] Button styling --- UI.lua | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/UI.lua b/UI.lua index bfc7013..9dc091b 100755 --- a/UI.lua +++ b/UI.lua @@ -91,22 +91,35 @@ local function CreateRadioButton(parent, name, text, x, y, isChecked, onClick) 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) + -- Outer grey circle (always visible, acts as border) + local border = btn:CreateTexture(nil, "BACKGROUND") + border:SetAllPoints() + border:SetColorTexture(0.3, 0.3, 0.3, 1) + local borderMask = btn:CreateMaskTexture() + borderMask:SetAllPoints() + borderMask:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE") + border:AddMaskTexture(borderMask) + + -- Inner yellow circle (shown when selected, slightly smaller to reveal grey border) + local inner = btn:CreateTexture(nil, "BORDER") + local inset = 3 + inner:SetPoint("TOPLEFT", btn, "TOPLEFT", inset, -inset) + inner:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -inset, inset) + inner:SetColorTexture(0.9, 0.75, 0.2, 1) + local innerMask = btn:CreateMaskTexture() + innerMask:SetPoint("TOPLEFT", btn, "TOPLEFT", inset, -inset) + innerMask:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -inset, inset) + innerMask:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE") + inner:AddMaskTexture(innerMask) + inner:Hide() btn.checkTex = nil -- unused, kept for compatibility local function UpdateVisual() if btn:GetChecked() then - bg:SetColorTexture(0.9, 0.75, 0.2, 1) + inner:Show() else - bg:SetColorTexture(0.3, 0.3, 0.3, 1) + inner:Hide() end end @@ -248,7 +261,7 @@ function AltSystem:CreateMainFrame() -- Section: Define Your Base Roll CreateSectionHeader(content, "Define Your Base Roll", PADDING, yPos) - yPos = yPos - 20 + yPos = yPos - 25 -- Roll Type label CreateSubLabel(content, "Roll Type", PADDING, yPos) @@ -532,14 +545,11 @@ function AltSystem:CreateMainFrame() 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) + local logHeader = CreateSectionHeader(logPanel, "Log", PADDING, -PADDING) -- Log scroll area background local logBg = CreateFrame("Frame", nil, logPanel, "InsetFrameTemplate") - logBg:SetPoint("TOPLEFT", logPanel, "TOPLEFT", 4, -22) + logBg:SetPoint("TOPLEFT", logPanel, "TOPLEFT", 4, -38) logBg:SetPoint("BOTTOMRIGHT", logPanel, "BOTTOMRIGHT", -4, 4) -- Scroll frame for log entries From 15da0fedf25d8e18fe14297ad650145fc0044600 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Fri, 15 May 2026 14:30:50 +0100 Subject: [PATCH 66/74] Label styling --- UI.lua | 58 +++++++++++++++++++++++---------------- docs/roll_tab_design.png | Bin 77503 -> 75385 bytes 2 files changed, 34 insertions(+), 24 deletions(-) mode change 100755 => 100644 docs/roll_tab_design.png diff --git a/UI.lua b/UI.lua index 9dc091b..b3235e9 100755 --- a/UI.lua +++ b/UI.lua @@ -6,12 +6,16 @@ AltSystem = AltSystem or {} local WINDOW_WIDTH = 700 -local WINDOW_HEIGHT = 500 -local CONTROLS_WIDTH = 350 -local LOG_WIDTH = 350 +local WINDOW_HEIGHT = 520 +local CONTROLS_WIDTH = 360 +local LOG_WIDTH = 340 local PADDING = 12 +local PADDING_HEADER = 6 local ROW_HEIGHT = 26 -local SECTION_GAP = 10 +local LABEL_HEIGHT = 14 -- approximate height of GameFontNormal text +local LABEL_GAP = 4 -- gap between a sub-label and its input +local ITEM_GAP = 10 -- gap between inputs within the same section +local SECTION_GAP = 22 -- gap between major sections -- Helper: Build the skill option list from current AltSystem.Data.Skills local function BuildSkillOptions() @@ -29,26 +33,32 @@ local function BuildSkillOptions() return options end --- Helper: Create a flat dark dropdown with optional label (reuses shared CreateFlatDropdown) +-- Helper: Create a flat dark dropdown with label on top (reuses shared CreateFlatDropdown) local function CreateDropdown(parent, name, labelText, options, defaultIndex, onSelect, labelFont) + local hasLabel = labelText and labelText ~= "" + local containerHeight = hasLabel and (LABEL_HEIGHT + LABEL_GAP + 28) or ROW_HEIGHT + local container = CreateFrame("Frame", nil, parent) - container:SetHeight(ROW_HEIGHT) + container:SetHeight(containerHeight) local label - if labelText and labelText ~= "" then + if hasLabel then label = container:CreateFontString(nil, "OVERLAY", labelFont or "GameFontNormal") - label:SetPoint("LEFT", container, "LEFT", 0, 0) + label:SetPoint("TOPLEFT", container, "TOPLEFT", 0, 0) label:SetText(labelText) label:SetJustifyH("LEFT") + label:SetTextColor(0.9, 0.75, 0.2) end local selectedIndex = defaultIndex or 1 local dropdown = AltSystem.CreateFlatDropdown(name, container, 190) - if label then - dropdown:SetPoint("RIGHT", container, "RIGHT", 0, 0) + if hasLabel then + dropdown:SetPoint("TOPLEFT", container, "TOPLEFT", 0, -(LABEL_HEIGHT + LABEL_GAP)) + dropdown:SetPoint("TOPRIGHT", container, "TOPRIGHT", 0, -(LABEL_HEIGHT + LABEL_GAP)) else dropdown:SetPoint("LEFT", container, "LEFT", 0, 0) + dropdown:SetPoint("RIGHT", container, "RIGHT", 0, 0) end -- Set initial label text @@ -260,12 +270,12 @@ function AltSystem:CreateMainFrame() local yPos = -PADDING -- Section: Define Your Base Roll - CreateSectionHeader(content, "Define Your Base Roll", PADDING, yPos) - yPos = yPos - 25 + CreateSectionHeader(content, "Base Roll", PADDING_HEADER, yPos) + yPos = yPos - 26 -- Roll Type label CreateSubLabel(content, "Roll Type", PADDING, yPos) - yPos = yPos - 20 + yPos = yPos - (LABEL_HEIGHT + LABEL_GAP) -- Roll Type radio buttons local attackRadio, defenseRadio @@ -295,7 +305,7 @@ function AltSystem:CreateMainFrame() UpdateRollTypeSelection("defense") end) - yPos = yPos - ROW_HEIGHT - SECTION_GAP + yPos = yPos - ROW_HEIGHT - ITEM_GAP -- Skill dropdown local skillOptions = BuildSkillOptions() @@ -351,11 +361,11 @@ function AltSystem:CreateMainFrame() AltSystem.SetSkillIndex = setSkillIndex AltSystem.UpdateSkillWarning = UpdateSkillWarning - yPos = yPos - ROW_HEIGHT - SECTION_GAP + yPos = yPos - (LABEL_HEIGHT + LABEL_GAP + 28) - ITEM_GAP -- Armor label CreateSubLabel(content, "Extra Armor", PADDING, yPos) - yPos = yPos - 20 + yPos = yPos - (LABEL_HEIGHT + LABEL_GAP) -- Armor radio buttons local armorRadios = {} @@ -386,8 +396,8 @@ function AltSystem:CreateMainFrame() yPos = yPos - ROW_HEIGHT - SECTION_GAP -- Section: Modifiers (optional) - CreateSectionHeader(content, "Modifiers (optional)", PADDING, yPos) - yPos = yPos - 28 + CreateSectionHeader(content, "Modifiers (optional)", PADDING_HEADER, yPos) + yPos = yPos - 26 --CreateSubLabel(content, "Label", PADDING, yPos) --yPos = yPos - 22 @@ -463,11 +473,11 @@ function AltSystem:CreateMainFrame() UpdatePetVisual() end) - yPos = yPos - ROW_HEIGHT - SECTION_GAP + yPos = yPos - ROW_HEIGHT - ITEM_GAP -- Item label CreateSubLabel(content, "Item", PADDING, yPos) - yPos = yPos - 20 + yPos = yPos - (LABEL_HEIGHT + LABEL_GAP) -- Item radio buttons local itemRadios = {} @@ -498,8 +508,8 @@ function AltSystem:CreateMainFrame() yPos = yPos - ROW_HEIGHT - SECTION_GAP -- Section: Roll Dice - CreateSectionHeader(content, "Roll Dice", PADDING, yPos) - yPos = yPos - 22 + CreateSectionHeader(content, "Roll Mode", PADDING_HEADER, yPos) + yPos = yPos - 26 -- Announce Roll dropdown (Self Roll + channels) local announceOptions = { { text = "Self Roll" } } @@ -524,7 +534,7 @@ function AltSystem:CreateMainFrame() announceContainer:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos) announceContainer:SetWidth(CONTROLS_WIDTH - PADDING * 2) - yPos = yPos - ROW_HEIGHT - SECTION_GAP + yPos = yPos - (LABEL_HEIGHT + LABEL_GAP + 28) - ITEM_GAP -- Roll button local rollLabel = AltSystem.State.rollType == "attack" and "Roll Attack" or "Roll Defense" @@ -545,7 +555,7 @@ function AltSystem:CreateMainFrame() logPanel:SetSize(LOG_WIDTH, tabContentHeight) -- Log header - local logHeader = CreateSectionHeader(logPanel, "Log", PADDING, -PADDING) + local logHeader = CreateSectionHeader(logPanel, "Log", 0, -PADDING) -- Log scroll area background local logBg = CreateFrame("Frame", nil, logPanel, "InsetFrameTemplate") diff --git a/docs/roll_tab_design.png b/docs/roll_tab_design.png old mode 100755 new mode 100644 index 3ebb626ffc9b75b311eb1a735a392aaa3db34064..c80f5dc03faa3d615c90c381e79f74c9b3bc178b GIT binary patch delta 46882 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 z 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 delta 46486 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 From 57e69568aab11fafc79455af6b7e0b725c87a3bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Fri, 15 May 2026 14:32:55 +0100 Subject: [PATCH 67/74] Label styling --- UI.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UI.lua b/UI.lua index b3235e9..86d5ba2 100755 --- a/UI.lua +++ b/UI.lua @@ -518,7 +518,7 @@ function AltSystem:CreateMainFrame() end local announceContainer, announceDropdown = CreateDropdown( - content, "AltSystemAnnounceDropdown", "Announce Roll", announceOptions, + content, "AltSystemAnnounceDropdown", "", announceOptions, AltSystem.State.announceOptionIndex, function(index) AltSystem.State.announceOptionIndex = index @@ -534,7 +534,7 @@ function AltSystem:CreateMainFrame() announceContainer:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos) announceContainer:SetWidth(CONTROLS_WIDTH - PADDING * 2) - yPos = yPos - (LABEL_HEIGHT + LABEL_GAP + 28) - ITEM_GAP + yPos = yPos - ROW_HEIGHT - ITEM_GAP -- Roll button local rollLabel = AltSystem.State.rollType == "attack" and "Roll Attack" or "Roll Defense" From 825128084d8b158d086006d289feb86e50968fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Fri, 15 May 2026 14:46:30 +0100 Subject: [PATCH 68/74] Tab styling --- BuildSkillsUI.lua | 2 +- UI.lua | 32 +++++++++++++++++++++----------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/BuildSkillsUI.lua b/BuildSkillsUI.lua index c102705..d8e7f8f 100755 --- a/BuildSkillsUI.lua +++ b/BuildSkillsUI.lua @@ -340,7 +340,7 @@ function AltSystem:CreateBuildSkillsContent(parentFrame) local sectionHeader = parentFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge") sectionHeader:SetPoint("TOPLEFT", parentFrame, "TOPLEFT", PADDING, yPos) sectionHeader:SetText("Skill list") - sectionHeader:SetTextColor(1, 1, 1) + --sectionHeader:SetTextColor(1, 1, 1) yPos = yPos - 24 diff --git a/UI.lua b/UI.lua index 86d5ba2..534d061 100755 --- a/UI.lua +++ b/UI.lua @@ -5,10 +5,10 @@ AltSystem = AltSystem or {} -local WINDOW_WIDTH = 700 +local WINDOW_WIDTH = 720 local WINDOW_HEIGHT = 520 local CONTROLS_WIDTH = 360 -local LOG_WIDTH = 340 +local LOG_WIDTH = 360 local PADDING = 12 local PADDING_HEADER = 6 local ROW_HEIGHT = 26 @@ -192,32 +192,38 @@ function AltSystem:CreateMainFrame() -- TAB BUTTONS (span full window width) --------------------- local contentTop = -24 - local tabHeight = 28 + local tabHeight = 36 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:SetNormalFontObject("GameFontNormalLarge") + tabUseSkills:SetHighlightFontObject("GameFontNormalLarge") tabUseSkills:SetText("Use Skills") local tabUseSkillsBg = tabUseSkills:CreateTexture(nil, "BACKGROUND") tabUseSkillsBg:SetAllPoints() - tabUseSkillsBg:SetColorTexture(0.15, 0.15, 0.15, 1) + tabUseSkillsBg:SetColorTexture(0, 0, 0, 0) + + local tabUseSkillsText = tabUseSkills:GetFontString() + tabUseSkillsText:SetTextColor(0.9, 0.75, 0.2, 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:SetNormalFontObject("GameFontNormalLarge") + tabBuildSkills:SetHighlightFontObject("GameFontNormalLarge") tabBuildSkills:SetText("Build Skills") local tabBuildSkillsBg = tabBuildSkills:CreateTexture(nil, "BACKGROUND") tabBuildSkillsBg:SetAllPoints() tabBuildSkillsBg:SetColorTexture(0.3, 0.3, 0.3, 1) + local tabBuildSkillsText = tabBuildSkills:GetFontString() + tabBuildSkillsText:SetTextColor(1, 1, 1, 1) + --------------------- -- TAB CONTENT FRAMES --------------------- @@ -241,13 +247,17 @@ function AltSystem:CreateMainFrame() if tabIndex == 1 then useSkillsContent:Show() buildSkillsContent:Hide() - tabUseSkillsBg:SetColorTexture(0.15, 0.15, 0.15, 1) + tabUseSkillsBg:SetColorTexture(0, 0, 0, 0) tabBuildSkillsBg:SetColorTexture(0.3, 0.3, 0.3, 1) + tabUseSkillsText:SetTextColor(0.9, 0.75, 0.2, 1) + tabBuildSkillsText:SetTextColor(1, 1, 1, 1) else useSkillsContent:Hide() buildSkillsContent:Show() tabUseSkillsBg:SetColorTexture(0.3, 0.3, 0.3, 1) - tabBuildSkillsBg:SetColorTexture(0.15, 0.15, 0.15, 1) + tabBuildSkillsBg:SetColorTexture(0, 0, 0, 0) + tabUseSkillsText:SetTextColor(1, 1, 1, 1) + tabBuildSkillsText:SetTextColor(0.9, 0.75, 0.2, 1) AltSystem:RefreshBuildSkillsList() end end @@ -560,7 +570,7 @@ function AltSystem:CreateMainFrame() -- Log scroll area background local logBg = CreateFrame("Frame", nil, logPanel, "InsetFrameTemplate") logBg:SetPoint("TOPLEFT", logPanel, "TOPLEFT", 4, -38) - logBg:SetPoint("BOTTOMRIGHT", logPanel, "BOTTOMRIGHT", -4, 4) + logBg:SetPoint("BOTTOMRIGHT", logPanel, "BOTTOMRIGHT", -PADDING, 4) -- Scroll frame for log entries local scrollFrame = CreateFrame("ScrollFrame", "AltSystemLogScrollFrame", logBg, "UIPanelScrollFrameTemplate") From 810fa100095a5a516407025a322378fd2c2f2720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Fri, 15 May 2026 14:51:23 +0100 Subject: [PATCH 69/74] Fixing roll labels for items/armor --- Data.lua | 16 ++++++++-------- Roll.lua | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Data.lua b/Data.lua index 6706c49..d1a1ae2 100755 --- a/Data.lua +++ b/Data.lua @@ -249,18 +249,18 @@ for _, skill in ipairs(DEFAULT_SKILLS) do table.insert(AltSystem.Data.Skills, { name = skill.name, level = skill.level, modifier = skill.modifier }) end --- Item options: name and modifier (first entry = no item) +-- Item options: name, label (used in roll messages), and modifier (first entry = no item) AltSystem.Data.Items = { - { name = "No item", modifier = 0 }, - { name = "Rare", modifier = 3 }, - { name = "Epic", modifier = 5 }, + { name = "No item", label = "No item", modifier = 0 }, + { name = "Rare", label = "Rare item", modifier = 3 }, + { name = "Epic", label = "Epic item", modifier = 5 }, } --- Defense / Armor options: name and modifier +-- Defense / Armor options: name, label (used in roll messages), and modifier AltSystem.Data.Defenses = { - { name = "None", modifier = 0 }, - { name = "Partial", modifier = 1 }, - { name = "Full", modifier = 2 }, + { name = "None", label = "No armor", modifier = 0 }, + { name = "Partial", label = "Extra armor", modifier = 1 }, + { name = "Full", label = "Extra armor", modifier = 2 }, } -- Shield modifier diff --git a/Roll.lua b/Roll.lua index 29d9190..036a680 100755 --- a/Roll.lua +++ b/Roll.lua @@ -198,7 +198,7 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue, petRollValue) table.insert(modifiers, { name = skill and skill.name or "Skill", value = skillMod }) end if itemMod ~= 0 then - table.insert(modifiers, { name = item and item.name or "Item", value = itemMod }) + table.insert(modifiers, { name = item and (item.label or item.name) or "Item", value = itemMod }) end if petMod ~= 0 then table.insert(modifiers, { name = "Pet", value = petMod }) @@ -216,10 +216,10 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue, petRollValue) table.insert(modifiers, { name = skill and skill.name or "Skill", value = skillMod }) end if itemMod ~= 0 then - table.insert(modifiers, { name = item and item.name or "Item", value = itemMod }) + table.insert(modifiers, { name = item and (item.label or item.name) or "Item", value = itemMod }) end if defenseMod ~= 0 then - table.insert(modifiers, { name = defense and defense.name or "Armor", value = defenseMod }) + table.insert(modifiers, { name = defense and (defense.label or defense.name) or "Armor", value = defenseMod }) end if shieldMod ~= 0 then table.insert(modifiers, { name = "Shield", value = shieldMod }) From 2b3db5230f2d00d589d9f2f5a3a99e750ac4a302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Fri, 15 May 2026 14:52:30 +0100 Subject: [PATCH 70/74] Versioning --- AltSystem.toc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AltSystem.toc b/AltSystem.toc index 3044c78..3b8554d 100755 --- 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.2.0 ## Dependencies: totalRP3, totalRP3_Extended ## SavedVariables: AltSystemDB From 7aa874ca18b057ee7d7deb6f627a732f14155920 Mon Sep 17 00:00:00 2001 From: Rukira Date: Fri, 15 May 2026 14:53:15 +0100 Subject: [PATCH 71/74] feat/redesign (#1) Reviewed-on: https://git.asarius.site/rukira/AltSystem/pulls/1 Co-authored-by: Rukira Co-committed-by: Rukira --- .idea/.gitignore | 0 .idea/misc.xml | 0 .idea/modules.xml | 0 .idea/vcs.xml | 0 AltSystem.iml | 0 AltSystem.toc | 3 +- BuildSkillsUI.lua | 430 ++++++++++++++++++ Core.lua | 37 +- Data.lua | 125 +++++- README.md | 0 Roll.lua | 109 +++-- UI.lua | 749 ++++++++++++++++++++++--------- docs/1-interface.md | 0 docs/2-skills.md | 0 docs/3-announce.md | 0 docs/4-redesign.md | 78 ++++ docs/5-build_skills.md | 132 ++++++ docs/Changelog.md | 3 - docs/build_skills_tab_design.png | Bin 0 -> 100436 bytes docs/roll_tab_design.png | Bin 0 -> 75385 bytes package.sh | 2 +- 21 files changed, 1395 insertions(+), 273 deletions(-) mode change 100644 => 100755 .idea/.gitignore mode change 100644 => 100755 .idea/misc.xml mode change 100644 => 100755 .idea/modules.xml mode change 100644 => 100755 .idea/vcs.xml mode change 100644 => 100755 AltSystem.iml mode change 100644 => 100755 AltSystem.toc create mode 100755 BuildSkillsUI.lua mode change 100644 => 100755 Core.lua mode change 100644 => 100755 Data.lua mode change 100644 => 100755 README.md mode change 100644 => 100755 Roll.lua mode change 100644 => 100755 UI.lua mode change 100644 => 100755 docs/1-interface.md mode change 100644 => 100755 docs/2-skills.md mode change 100644 => 100755 docs/3-announce.md create mode 100755 docs/4-redesign.md create mode 100755 docs/5-build_skills.md mode change 100644 => 100755 docs/Changelog.md create mode 100755 docs/build_skills_tab_design.png create mode 100644 docs/roll_tab_design.png diff --git a/.idea/.gitignore b/.idea/.gitignore old mode 100644 new mode 100755 diff --git a/.idea/misc.xml b/.idea/misc.xml old mode 100644 new mode 100755 diff --git a/.idea/modules.xml b/.idea/modules.xml old mode 100644 new mode 100755 diff --git a/.idea/vcs.xml b/.idea/vcs.xml old mode 100644 new mode 100755 diff --git a/AltSystem.iml b/AltSystem.iml old mode 100644 new mode 100755 diff --git a/AltSystem.toc b/AltSystem.toc old mode 100644 new mode 100755 index e541ac4..3b8554d --- a/AltSystem.toc +++ b/AltSystem.toc @@ -2,11 +2,12 @@ ## Title: AltSystem ## Notes: Enhances RP gameplay with a custom rolling system ## Author: Rukira -## Version: 1.1 +## Version: 1.2.0 ## Dependencies: totalRP3, totalRP3_Extended ## SavedVariables: AltSystemDB Data.lua Core.lua +BuildSkillsUI.lua UI.lua Roll.lua diff --git a/BuildSkillsUI.lua b/BuildSkillsUI.lua new file mode 100755 index 0000000..d8e7f8f --- /dev/null +++ b/BuildSkillsUI.lua @@ -0,0 +1,430 @@ +-- AltSystem Build Skills UI +-- Creates the Build Skills tab content with editable skill rows, add/delete functionality, and save to TRP. + +AltSystem = AltSystem or {} + +local PADDING = 12 +local ROW_HEIGHT = 36 +local ROW_SPACING = 8 +local NAME_WIDTH = 340 +local LEVEL_WIDTH = 140 +local VALUE_WIDTH = 80 +local DELETE_WIDTH = 30 + +-- Working copy of skills being edited (not saved until user clicks Save) +local editableSkills = {} + +-- Pool of created row frames for reuse +local skillRowFrames = {} + +-- References set during creation +local scrollFrame, scrollChild, addRowButton + +-- ─── Helper: Create a flat dark input box matching the blocky design ──────── + +local function CreateFlatEditBox(name, parent, width) + local container = CreateFrame("Frame", name .. "Container", parent, "BackdropTemplate") + container:SetSize(width, 28) + container:SetBackdrop({ + bgFile = "Interface\\ChatFrame\\ChatFrameBackground", + edgeFile = "Interface\\ChatFrame\\ChatFrameBackground", + edgeSize = 1, + }) + container:SetBackdropColor(0.12, 0.12, 0.12, 1) + container:SetBackdropBorderColor(0.25, 0.25, 0.25, 1) + + local editBox = CreateFrame("EditBox", name, container) + editBox:SetPoint("LEFT", 8, 0) + editBox:SetPoint("RIGHT", -8, 0) + editBox:SetHeight(28) + editBox:SetAutoFocus(false) + editBox:SetFontObject("GameFontHighlight") + + return container, editBox +end + +-- ─── Helper: Create a flat dark dropdown button matching the blocky design ── +-- Shared globally so other files (UI.lua) can reuse it. + +function AltSystem.CreateFlatDropdown(name, parent, width) + local btn = CreateFrame("Button", name, parent, "BackdropTemplate") + btn:SetSize(width, 28) + btn:SetBackdrop({ + bgFile = "Interface\\ChatFrame\\ChatFrameBackground", + edgeFile = "Interface\\ChatFrame\\ChatFrameBackground", + edgeSize = 1, + }) + btn:SetBackdropColor(0.12, 0.12, 0.12, 1) + btn:SetBackdropBorderColor(0.25, 0.25, 0.25, 1) + + -- Text label (left-aligned) + local text = btn:CreateFontString(nil, "OVERLAY", "GameFontHighlight") + text:SetPoint("LEFT", 8, 0) + text:SetPoint("RIGHT", -24, 0) + text:SetJustifyH("LEFT") + btn.label = text + + -- Gold arrow icon (right side) + local arrow = btn:CreateTexture(nil, "OVERLAY") + arrow:SetSize(12, 12) + arrow:SetPoint("RIGHT", -6, 0) + arrow:SetTexture("Interface\\ChatFrame\\ChatFrameExpandArrow") + arrow:SetVertexColor(0.9, 0.75, 0.2, 1) + btn.arrow = arrow + + -- Hover highlight + btn:SetScript("OnEnter", function(self) + self:SetBackdropColor(0.18, 0.18, 0.18, 1) + end) + btn:SetScript("OnLeave", function(self) + self:SetBackdropColor(0.12, 0.12, 0.12, 1) + end) + + -- Menu storage + btn.menuSetup = nil + btn:SetScript("OnClick", function(self) + if self.menuSetup then + MenuUtil.CreateContextMenu(self, self.menuSetup) + end + end) + + function btn:SetupMenu(setupFunc) + self.menuSetup = setupFunc + end + + return btn +end + +-- ─── Helper: Create a flat dark button matching the blocky design ──────────── +-- Shared globally so other files (UI.lua) can reuse it. + +function AltSystem.CreateFlatButton(name, parent, width, height, text) + local btn = CreateFrame("Button", name, parent, "BackdropTemplate") + btn:SetSize(width, height) + btn:SetBackdrop({ + bgFile = "Interface\\ChatFrame\\ChatFrameBackground", + edgeFile = "Interface\\ChatFrame\\ChatFrameBackground", + edgeSize = 1, + }) + btn:SetBackdropColor(0.4, 0.08, 0.08, 0.9) + btn:SetBackdropBorderColor(0.25, 0.25, 0.25, 1) + + local label = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal") + label:SetPoint("CENTER") + label:SetText(text or "") + btn.label = label + + btn:SetScript("OnEnter", function(self) + self:SetBackdropColor(0.55, 0.12, 0.12, 0.9) + end) + btn:SetScript("OnLeave", function(self) + self:SetBackdropColor(0.4, 0.08, 0.08, 0.9) + end) + + function btn:SetText(newText) + self.label:SetText(newText) + end + + function btn:GetText() + return self.label:GetText() + end + + return btn +end + +-- ─── Create a single skill row frame ──────────────────────────────────────── + +local function CreateSkillRowFrame(index) + local row = CreateFrame("Frame", "AltSystemSkillRow" .. index, scrollChild) + row:SetHeight(ROW_HEIGHT) + + -- Name EditBox (flat dark style) + local nameContainer, nameBox = CreateFlatEditBox("AltSystemSkillName" .. index, row, NAME_WIDTH) + nameContainer:SetPoint("LEFT", row, "LEFT", 8, 0) + row.nameBox = nameBox + row.nameContainer = nameContainer + + -- Level Dropdown (flat dark style) + local levelDropdown = AltSystem.CreateFlatDropdown("AltSystemSkillLevel" .. index, row, LEVEL_WIDTH) + levelDropdown:SetPoint("LEFT", nameContainer, "RIGHT", 12, 0) + row.levelDropdown = levelDropdown + + -- Value Dropdown (flat dark style) + local valueDropdown = AltSystem.CreateFlatDropdown("AltSystemSkillValue" .. index, row, VALUE_WIDTH) + valueDropdown:SetPoint("LEFT", levelDropdown, "RIGHT", 8, 0) + row.valueDropdown = valueDropdown + + -- Delete Button (trash can icon with dark red background, matching design) + local deleteBtn = CreateFrame("Button", "AltSystemSkillDelete" .. index, row) + deleteBtn:SetSize(DELETE_WIDTH, DELETE_WIDTH) + deleteBtn:SetPoint("LEFT", valueDropdown, "RIGHT", 8, 0) + + + local deleteIcon = deleteBtn:CreateTexture(nil, "ARTWORK") + deleteIcon:SetSize(DELETE_WIDTH - 4, DELETE_WIDTH - 4) + deleteIcon:SetPoint("CENTER") + deleteIcon:SetTexture("Interface\\Buttons\\UI-GroupLoot-Pass-Up") + deleteBtn.icon = deleteIcon + + -- Shimmer animation on hover + local shimmer = deleteBtn:CreateTexture(nil, "OVERLAY") + shimmer:SetSize(DELETE_WIDTH - 4, DELETE_WIDTH - 4) + shimmer:SetPoint("CENTER") + shimmer:SetTexture("Interface\\Buttons\\UI-GroupLoot-Pass-Up") + shimmer:SetBlendMode("ADD") + shimmer:SetAlpha(0) + deleteBtn.shimmer = shimmer + + local shimmerAnim = shimmer:CreateAnimationGroup() + shimmerAnim:SetLooping("REPEAT") + local fadeIn = shimmerAnim:CreateAnimation("Alpha") + fadeIn:SetFromAlpha(0) + fadeIn:SetToAlpha(0.5) + fadeIn:SetDuration(0.5) + fadeIn:SetOrder(1) + local fadeOut = shimmerAnim:CreateAnimation("Alpha") + fadeOut:SetFromAlpha(0.5) + fadeOut:SetToAlpha(0) + fadeOut:SetDuration(0.5) + fadeOut:SetOrder(2) + deleteBtn.shimmerAnim = shimmerAnim + + deleteBtn:SetScript("OnEnter", function(self) + self.shimmer:SetAlpha(0) + self.shimmerAnim:Play() + end) + deleteBtn:SetScript("OnLeave", function(self) + self.shimmerAnim:Stop() + self.shimmer:SetAlpha(0) + end) + + row.deleteBtn = deleteBtn + + return row +end + +-- ─── Refresh all skill rows ───────────────────────────────────────────────── + +local function RefreshSkillRows() + -- Hide all existing row frames + for _, row in ipairs(skillRowFrames) do + row:Hide() + end + + local yPos = 0 + + for i, skillData in ipairs(editableSkills) do + local row = skillRowFrames[i] + if not row then + row = CreateSkillRowFrame(i) + skillRowFrames[i] = row + end + + -- Clear previous anchor points before repositioning + row:ClearAllPoints() + row:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 0, -yPos) + row:SetPoint("TOPRIGHT", scrollChild, "TOPRIGHT", 0, -yPos) + + -- Populate name + row.nameBox:SetText(skillData.name or "") + row.nameBox:SetScript("OnTextChanged", function(self) + local idx = nil + for j, s in ipairs(editableSkills) do + if skillRowFrames[j] == row then + idx = j + break + end + end + if idx then + editableSkills[idx].name = self:GetText() + end + end) + + -- Setup level dropdown + local currentRowIndex = i + row.levelDropdown.label:SetText(skillData.level or "Novice") + row.levelDropdown:SetupMenu(function(owner, rootDescription) + for _, levelName in ipairs(AltSystem.Data.SkillLevelOrder) do + rootDescription:CreateRadio( + levelName, + function() + return editableSkills[currentRowIndex] and editableSkills[currentRowIndex].level == levelName + end, + function() + if editableSkills[currentRowIndex] then + editableSkills[currentRowIndex].level = levelName + editableSkills[currentRowIndex].value = AltSystem.Data:GetDefaultValueForLevel(levelName) + RefreshSkillRows() + end + end + ) + end + end) + + -- Setup value dropdown based on current level + row.valueDropdown.label:SetText(tostring(skillData.value or 1)) + local range = AltSystem.Data.SkillValueRanges[skillData.level] + if range then + row.valueDropdown:SetupMenu(function(owner, rootDescription) + for v = range.min, range.max do + rootDescription:CreateRadio( + tostring(v), + function() + return editableSkills[currentRowIndex] and editableSkills[currentRowIndex].value == v + end, + function() + if editableSkills[currentRowIndex] then + editableSkills[currentRowIndex].value = v + row.valueDropdown.label:SetText(tostring(v)) + end + end + ) + end + end) + end + + -- Bind delete button for current index + row.deleteBtn:SetScript("OnClick", function() + table.remove(editableSkills, currentRowIndex) + RefreshSkillRows() + end) + + row:Show() + yPos = yPos + ROW_HEIGHT + ROW_SPACING + end + + -- Reposition Add A Row button + if addRowButton then + addRowButton:ClearAllPoints() + addRowButton:SetPoint("TOPRIGHT", scrollChild, "TOPRIGHT", -PADDING, -(yPos + 4)) + end + + -- Update scroll child height + local totalHeight = yPos + ROW_HEIGHT + 20 -- extra space for Add A Row button + if scrollChild then + scrollChild:SetHeight(math.max(totalHeight, 1)) + end +end + +-- ─── Create Build Skills Content ──────────────────────────────────────────── + +function AltSystem:CreateBuildSkillsContent(parentFrame) + local contentWidth = parentFrame:GetWidth() or 692 + local contentHeight = parentFrame:GetHeight() or 444 + + local yPos = -PADDING + + -- Info text paragraph 1 + local info1 = parentFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal") + info1:SetPoint("TOPLEFT", parentFrame, "TOPLEFT", PADDING, yPos) + info1:SetPoint("TOPRIGHT", parentFrame, "TOPRIGHT", -PADDING, yPos) + info1:SetJustifyH("LEFT") + info1:SetText("All skills below are directly extracted from your TRP's characteristics sheet. You can view and edit them there at any time.") + info1:SetTextColor(0.9, 0.75, 0.2) + info1:SetWordWrap(true) + + yPos = yPos - (info1:GetStringHeight() or 16) - 10 + + -- Info text paragraph 2 + local info2 = parentFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal") + info2:SetPoint("TOPLEFT", parentFrame, "TOPLEFT", PADDING, yPos) + info2:SetPoint("TOPRIGHT", parentFrame, "TOPRIGHT", -PADDING, yPos) + info2:SetJustifyH("LEFT") + info2:SetText("This menu serves as an easy alternative for if you want to use this system fast without diving deep into understanding it and/or styling your TRP sheet at this point in time. All your changes made will not be saved to TRP up until you hit the specific button to do so.") + info2:SetTextColor(0.9, 0.75, 0.2) + info2:SetWordWrap(true) + + yPos = yPos - (info2:GetStringHeight() or 32) - 16 + + -- "Skill list" section header + local sectionHeader = parentFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge") + sectionHeader:SetPoint("TOPLEFT", parentFrame, "TOPLEFT", PADDING, yPos) + sectionHeader:SetText("Skill list") + --sectionHeader:SetTextColor(1, 1, 1) + + yPos = yPos - 24 + + -- Column headers + local nameHeader = parentFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal") + nameHeader:SetPoint("TOPLEFT", parentFrame, "TOPLEFT", PADDING + 8, yPos) + nameHeader:SetText("Name") + nameHeader:SetTextColor(0.9, 0.75, 0.2) + + local levelHeader = parentFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal") + levelHeader:SetPoint("TOPLEFT", parentFrame, "TOPLEFT", PADDING + 8 + NAME_WIDTH + 12, yPos) + levelHeader:SetText("Level") + levelHeader:SetTextColor(0.9, 0.75, 0.2) + + local valueHeader = parentFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal") + valueHeader:SetPoint("TOPLEFT", parentFrame, "TOPLEFT", PADDING + 8 + NAME_WIDTH + 12 + LEVEL_WIDTH + 8, yPos) + valueHeader:SetText("Value") + valueHeader:SetTextColor(0.9, 0.75, 0.2) + + yPos = yPos - 20 + + -- Save button (pinned to bottom, outside scroll frame) + local saveButton = AltSystem.CreateFlatButton("AltSystemSaveSkillsButton", parentFrame, 180, 30, "Save Skills to TRP") + saveButton:SetPoint("BOTTOM", parentFrame, "BOTTOM", 0, PADDING) + + saveButton:SetScript("OnClick", function() + local success = AltSystem.Data:SaveSkills(editableSkills) + if success and AltSystem.RefreshSkillDropdown then + AltSystem:RefreshSkillDropdown() + end + end) + + -- Scrollable skill list area (between column headers and save button) + scrollFrame = CreateFrame("ScrollFrame", "AltSystemBuildSkillsScrollFrame", parentFrame, "UIPanelScrollFrameTemplate") + scrollFrame:SetPoint("TOPLEFT", parentFrame, "TOPLEFT", PADDING, yPos) + scrollFrame:SetPoint("BOTTOMRIGHT", parentFrame, "BOTTOMRIGHT", -PADDING - 22, saveButton:GetHeight() + PADDING + 8) + + scrollChild = CreateFrame("Frame", "AltSystemBuildSkillsScrollChild", scrollFrame) + scrollChild:SetWidth(scrollFrame:GetWidth() or (contentWidth - PADDING * 2 - 22)) + scrollChild:SetHeight(1) + scrollFrame:SetScrollChild(scrollChild) + + -- Update scroll child width when frame resizes + scrollFrame:SetScript("OnSizeChanged", function(self) + scrollChild:SetWidth(self:GetWidth()) + end) + + -- "Add A Row" button (inside scroll child, below last row) + addRowButton = CreateFrame("Button", "AltSystemAddRowButton", scrollChild, "UIPanelButtonTemplate") + addRowButton:SetSize(130, 26) + addRowButton:SetText("+ Add A Row") + addRowButton:SetPoint("TOPRIGHT", scrollChild, "TOPRIGHT", -PADDING, 0) + + -- Style add row button + local addBg = addRowButton:CreateTexture(nil, "BACKGROUND") + addBg:SetAllPoints() + addBg:SetColorTexture(0.4, 0.08, 0.08, 0.9) + + addRowButton:SetScript("OnClick", function() + table.insert(editableSkills, { + name = "Skillname", + level = "Novice", + value = 1, + icon = "inv_misc_questionmark", + isNew = true, + }) + RefreshSkillRows() + + -- Auto-scroll to bottom to show the new row + C_Timer.After(0.05, function() + if scrollFrame then + scrollFrame:SetVerticalScroll(scrollFrame:GetVerticalScrollRange()) + end + end) + end) + + AltSystem.BuildSkillsScrollFrame = scrollFrame + AltSystem.BuildSkillsScrollChild = scrollChild +end + +-- ─── Refresh Build Skills list (called on tab switch) ─────────────────────── + +function AltSystem:RefreshBuildSkillsList() + -- Reload skills from TRP3 into working copy, discarding any unsaved edits + editableSkills = AltSystem.Data:GetEditableSkills() + RefreshSkillRows() +end diff --git a/Core.lua b/Core.lua old mode 100644 new mode 100755 index a3d1ab9..865bf63 --- 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 old mode 100644 new mode 100755 index e46d887..d1a1ae2 --- a/Data.lua +++ b/Data.lua @@ -39,6 +39,27 @@ local SKILL_KEYWORD_RANGES = { ["Master"] = { min = 20, max = 20 }, } +-- Shared lookup tables for the Build Skills tab +AltSystem.Data.SkillValueRanges = { + ["Inept"] = { min = 0, max = 0 }, + ["Novice"] = { min = 1, max = 5 }, + ["Adept"] = { min = 6, max = 10 }, + ["Expert"] = { min = 11, max = 19 }, + ["Master"] = { min = 20, max = 20 }, +} + +-- Ordered list of skill levels for dropdown display +AltSystem.Data.SkillLevelOrder = { "Inept", "Novice", "Adept", "Expert", "Master" } + +-- Returns the minimum value for a given skill level +function AltSystem.Data:GetDefaultValueForLevel(level) + local range = AltSystem.Data.SkillValueRanges[level] + if range then + return range.min + end + return 1 +end + -- Check if the trait's right text field contains a valid skill keyword. -- Returns the matched keyword if found, nil otherwise. local function FindSkillKeyword(rightText) @@ -134,24 +155,112 @@ function AltSystem.Data:RefreshSkills() return skills end +-- Returns an array of {name, level, value, icon} for each valid skill trait in the TRP3 profile. +-- Excludes Base Roll and Unskilled (system-generated). Sorted by level order (Inept first, Master last). +function AltSystem.Data:GetEditableSkills() + local skills = {} + + if TRP3_API and TRP3_API.profile and TRP3_API.profile.getData then + local ok, characteristics = pcall(TRP3_API.profile.getData, "player/characteristics") + if ok and characteristics and characteristics.PS then + for _, trait in ipairs(characteristics.PS) do + local skillName = trait.LT + local numericValue = trait.V2 or 0 + local keyword = FindSkillKeyword(trait.RT) + if skillName and skillName ~= "" and keyword then + table.insert(skills, { + name = skillName, + level = keyword, + value = numericValue, + icon = trait.IC or "inv_misc_questionmark", + }) + end + end + end + end + + -- Sort by level order descending (Master first, Inept last) + local levelOrderMap = {} + for i, lvl in ipairs(AltSystem.Data.SkillLevelOrder) do + levelOrderMap[lvl] = i + end + table.sort(skills, function(a, b) + return (levelOrderMap[a.level] or 99) > (levelOrderMap[b.level] or 99) + end) + + return skills +end + +-- Saves the edited skills back to the TRP3 profile. +-- editedSkills is an array of {name, level, value, icon}. +function AltSystem.Data:SaveSkills(editedSkills) + if not TRP3_API or not TRP3_API.profile or not TRP3_API.profile.getData then + print("|cFFFF0000AltSystem:|r TRP3 API is unavailable. Cannot save skills.") + return false + end + + local ok, characteristics = pcall(TRP3_API.profile.getData, "player/characteristics") + if not ok or not characteristics then + print("|cFFFF0000AltSystem:|r Could not access TRP3 profile characteristics. Cannot save skills.") + return false + end + + -- Ensure PS array exists + if not characteristics.PS then + characteristics.PS = {} + end + + -- Separate non-skill traits from skill traits in the existing PS + local nonSkillTraits = {} + for _, trait in ipairs(characteristics.PS) do + local keyword = FindSkillKeyword(trait.RT) + if not keyword then + -- This is not a skill trait, preserve it + table.insert(nonSkillTraits, trait) + end + end + + -- Rebuild PS: non-skill traits first, then edited skills + local newPS = {} + for _, trait in ipairs(nonSkillTraits) do + table.insert(newPS, trait) + end + for _, skill in ipairs(editedSkills) do + table.insert(newPS, { + LT = skill.name, + RT = skill.level, + V2 = skill.value, + IC = skill.icon or "inv_misc_questionmark", + }) + end + + characteristics.PS = newPS + + -- Refresh the Use Skills tab data + AltSystem.Data:RefreshSkills() + + print("|cFF00FF00AltSystem:|r Skills saved to TRP profile successfully.") + return true +end + -- Initialize with the default skill list AltSystem.Data.Skills = {} for _, skill in ipairs(DEFAULT_SKILLS) do table.insert(AltSystem.Data.Skills, { name = skill.name, level = skill.level, modifier = skill.modifier }) end --- Item options: name and modifier (first entry = no item) +-- Item options: name, label (used in roll messages), and modifier (first entry = no item) AltSystem.Data.Items = { - { name = "No item", modifier = 0 }, - { name = "Rare item", modifier = 3 }, - { name = "Epic item", modifier = 5 }, + { name = "No item", label = "No item", modifier = 0 }, + { name = "Rare", label = "Rare item", modifier = 3 }, + { name = "Epic", label = "Epic item", modifier = 5 }, } --- Defense options: name and modifier +-- Defense / Armor options: name, label (used in roll messages), and modifier AltSystem.Data.Defenses = { - { name = "Base armor", modifier = 0 }, - { name = "Extra small armor", modifier = 1 }, - { name = "Extra large armor", modifier = 2 }, + { name = "None", label = "No armor", modifier = 0 }, + { name = "Partial", label = "Extra armor", modifier = 1 }, + { name = "Full", label = "Extra armor", modifier = 2 }, } -- Shield modifier diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/Roll.lua b/Roll.lua old mode 100644 new mode 100755 index a37ca8b..036a680 --- 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 }) + table.insert(modifiers, { name = item and (item.label or 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 }) + table.insert(modifiers, { name = item and (item.label or item.name) or "Item", value = itemMod }) + end + if defenseMod ~= 0 then + table.insert(modifiers, { name = defense and (defense.label or 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 - 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 old mode 100644 new mode 100755 index 5162da2..534d061 --- a/UI.lua +++ b/UI.lua @@ -1,14 +1,21 @@ -- 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 = 720 +local WINDOW_HEIGHT = 520 +local CONTROLS_WIDTH = 360 +local LOG_WIDTH = 360 +local PADDING = 12 +local PADDING_HEADER = 6 +local ROW_HEIGHT = 26 +local LABEL_HEIGHT = 14 -- approximate height of GameFontNormal text +local LABEL_GAP = 4 -- gap between a sub-label and its input +local ITEM_GAP = 10 -- gap between inputs within the same section +local SECTION_GAP = 22 -- gap between major sections -- Helper: Build the skill option list from current AltSystem.Data.Skills local function BuildSkillOptions() @@ -26,39 +33,144 @@ local function BuildSkillOptions() return options 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") +-- Helper: Create a flat dark dropdown with label on top (reuses shared CreateFlatDropdown) +local function CreateDropdown(parent, name, labelText, options, defaultIndex, onSelect, labelFont) + local hasLabel = labelText and labelText ~= "" + local containerHeight = hasLabel and (LABEL_HEIGHT + LABEL_GAP + 28) or ROW_HEIGHT + + local container = CreateFrame("Frame", nil, parent) + container:SetHeight(containerHeight) + + local label + if hasLabel then + label = container:CreateFontString(nil, "OVERLAY", labelFont or "GameFontNormal") + label:SetPoint("TOPLEFT", container, "TOPLEFT", 0, 0) + label:SetText(labelText) + label:SetJustifyH("LEFT") + label:SetTextColor(0.9, 0.75, 0.2) + 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 = AltSystem.CreateFlatDropdown(name, container, 190) + if hasLabel then + dropdown:SetPoint("TOPLEFT", container, "TOPLEFT", 0, -(LABEL_HEIGHT + LABEL_GAP)) + dropdown:SetPoint("TOPRIGHT", container, "TOPRIGHT", 0, -(LABEL_HEIGHT + LABEL_GAP)) + else + dropdown:SetPoint("LEFT", container, "LEFT", 0, 0) + dropdown:SetPoint("RIGHT", container, "RIGHT", 0, 0) + end - dropdown:SetupMenu(function(dropdown, rootDescription) + -- 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(data) return data == selectedIndex end, - function(data) - selectedIndex = data - if onSelect then onSelect(data, options[data]) end - end, - i + 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 dropdown, function() return selectedIndex end, function(idx) selectedIndex = idx 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) + + -- Outer grey circle (always visible, acts as border) + local border = btn:CreateTexture(nil, "BACKGROUND") + border:SetAllPoints() + border:SetColorTexture(0.3, 0.3, 0.3, 1) + local borderMask = btn:CreateMaskTexture() + borderMask:SetAllPoints() + borderMask:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE") + border:AddMaskTexture(borderMask) + + -- Inner yellow circle (shown when selected, slightly smaller to reveal grey border) + local inner = btn:CreateTexture(nil, "BORDER") + local inset = 3 + inner:SetPoint("TOPLEFT", btn, "TOPLEFT", inset, -inset) + inner:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -inset, inset) + inner:SetColorTexture(0.9, 0.75, 0.2, 1) + local innerMask = btn:CreateMaskTexture() + innerMask:SetPoint("TOPLEFT", btn, "TOPLEFT", inset, -inset) + innerMask:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -inset, inset) + innerMask:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE") + inner:AddMaskTexture(innerMask) + inner:Hide() + + btn.checkTex = nil -- unused, kept for compatibility + + local function UpdateVisual() + if btn:GetChecked() then + inner:Show() + else + inner:Hide() + 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 + if AltSystem.MainFrame then + return + end -- Main frame local f = CreateFrame("Frame", "AltSystemMainFrame", UIParent, "BasicFrameTemplateWithInset") @@ -76,26 +188,154 @@ 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 = 36 + 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("GameFontNormalLarge") + tabUseSkills:SetHighlightFontObject("GameFontNormalLarge") + tabUseSkills:SetText("Use Skills") + + local tabUseSkillsBg = tabUseSkills:CreateTexture(nil, "BACKGROUND") + tabUseSkillsBg:SetAllPoints() + tabUseSkillsBg:SetColorTexture(0, 0, 0, 0) + + local tabUseSkillsText = tabUseSkills:GetFontString() + tabUseSkillsText:SetTextColor(0.9, 0.75, 0.2, 1) + + local tabBuildSkills = CreateFrame("Button", "AltSystemTabBuildSkills", f) + tabBuildSkills:SetSize(tabWidth, tabHeight) + tabBuildSkills:SetPoint("TOPLEFT", tabUseSkills, "TOPRIGHT", 0, 0) + tabBuildSkills:SetNormalFontObject("GameFontNormalLarge") + tabBuildSkills:SetHighlightFontObject("GameFontNormalLarge") + tabBuildSkills:SetText("Build Skills") + + local tabBuildSkillsBg = tabBuildSkills:CreateTexture(nil, "BACKGROUND") + tabBuildSkillsBg:SetAllPoints() + tabBuildSkillsBg:SetColorTexture(0.3, 0.3, 0.3, 1) + + local tabBuildSkillsText = tabBuildSkills:GetFontString() + tabBuildSkillsText:SetTextColor(1, 1, 1, 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, 0, 0, 0) + tabBuildSkillsBg:SetColorTexture(0.3, 0.3, 0.3, 1) + tabUseSkillsText:SetTextColor(0.9, 0.75, 0.2, 1) + tabBuildSkillsText:SetTextColor(1, 1, 1, 1) + else + useSkillsContent:Hide() + buildSkillsContent:Show() + tabUseSkillsBg:SetColorTexture(0.3, 0.3, 0.3, 1) + tabBuildSkillsBg:SetColorTexture(0, 0, 0, 0) + tabUseSkillsText:SetTextColor(1, 1, 1, 1) + tabBuildSkillsText:SetTextColor(0.9, 0.75, 0.2, 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, "Base Roll", PADDING_HEADER, yPos) + yPos = yPos - 26 + + -- Roll Type label + CreateSubLabel(content, "Roll Type", PADDING, yPos) + yPos = yPos - (LABEL_HEIGHT + LABEL_GAP) + + -- 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 - ITEM_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, + "GameFontNormal" + ) + 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 +355,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 +370,276 @@ 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, - }) - end + yPos = yPos - (LABEL_HEIGHT + LABEL_GAP + 28) - ITEM_GAP - CreateDropdown(f, "AltSystemItemDropdown", yPos, "Item:", itemOptions, AltSystem.State.selectedItemIndex, - function(index) - AltSystem.State.selectedItemIndex = index - end) - yPos = yPos - ROW_HEIGHT - 8 + -- Armor label + CreateSubLabel(content, "Extra Armor", PADDING, yPos) + yPos = yPos - (LABEL_HEIGHT + LABEL_GAP) - ------------------------- - -- 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 + -- Armor radio buttons + local armorRadios = {} - CreateDropdown(f, "AltSystemDefenseDropdown", yPos, "Defense:", defenseOptions, AltSystem.State.selectedDefenseIndex, - function(index) - AltSystem.State.selectedDefenseIndex = index - end) - yPos = yPos - ROW_HEIGHT - 8 - - ------------------------- - -- 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) - shieldCheck:SetChecked(AltSystem.State.shieldEnabled) - - 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) - - 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: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 - 8 - - ------------------------- - -- 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") - - 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(channelOptions, { 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) - - -- Show/hide channel dropdown based on checkbox state - local function UpdateChannelDropdownVisibility() - if AltSystem.State.announceEnabled then - channelDropdown:Show() - else - channelDropdown:Hide() + local function UpdateArmorSelection(index) + AltSystem.State.selectedDefenseIndex = index + for i, radio in ipairs(armorRadios) do + radio:SetChecked(i == index) + radio.UpdateVisual() end end - announceCheck:SetScript("OnClick", function(self) - AltSystem.State.announceEnabled = self:GetChecked() - UpdateChannelDropdownVisibility() + 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_HEADER, yPos) + yPos = yPos - 26 + --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) - UpdateChannelDropdownVisibility() + -- 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) - yPos = yPos - ROW_HEIGHT - 12 + local petBg = petCheck:CreateTexture(nil, "BACKGROUND") + petBg:SetAllPoints() + petBg:SetColorTexture(0.3, 0.3, 0.3, 1) - ------------------------- - -- Roll buttons - ------------------------- - local btnWidth = (WINDOW_WIDTH - PADDING * 3) / 2 + 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 attackBtn = CreateFrame("Button", "AltSystemAttackRollBtn", f, "UIPanelButtonTemplate") - attackBtn:SetSize(btnWidth, 28) - attackBtn:SetPoint("TOPLEFT", f, "TOPLEFT", PADDING, yPos) - attackBtn:SetText("Attack/Skill Roll") + 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 defenseBtn = CreateFrame("Button", "AltSystemDefenseRollBtn", f, "UIPanelButtonTemplate") - defenseBtn:SetSize(btnWidth, 28) - defenseBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -PADDING, yPos) - defenseBtn:SetText("Defense Roll") + local petLabel = petCheck:CreateFontString(nil, "OVERLAY", "GameFontHighlight") + petLabel:SetPoint("LEFT", petCheck, "RIGHT", 6, 0) + petLabel:SetText("Pet (+d5)") - yPos = yPos - 40 - - ------------------------- - -- 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") + petCheck:SetScript("OnClick", function(self) + AltSystem.State.petSummonEnabled = self:GetChecked() + UpdatePetVisual() end) - defenseBtn:SetScript("OnClick", function() - AltSystem:PerformRoll("defense") + yPos = yPos - ROW_HEIGHT - ITEM_GAP + + -- Item label + CreateSubLabel(content, "Item", PADDING, yPos) + yPos = yPos - (LABEL_HEIGHT + LABEL_GAP) + + -- 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 Mode", PADDING_HEADER, yPos) + yPos = yPos - 26 + + -- 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", "", 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 - ITEM_GAP + + -- Roll button + local rollLabel = AltSystem.State.rollType == "attack" and "Roll Attack" or "Roll Defense" + local rollBtn = AltSystem.CreateFlatButton("AltSystemRollBtn", content, CONTROLS_WIDTH - PADDING * 2, 32, rollLabel) + rollBtn:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos) + + 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 = CreateSectionHeader(logPanel, "Log", 0, -PADDING) + + -- Log scroll area background + local logBg = CreateFrame("Frame", nil, logPanel, "InsetFrameTemplate") + logBg:SetPoint("TOPLEFT", logPanel, "TOPLEFT", 4, -38) + logBg:SetPoint("BOTTOMRIGHT", logPanel, "BOTTOMRIGHT", -PADDING, 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() @@ -338,22 +667,28 @@ function AltSystem:RefreshSkillDropdown() -- Rebuild the dropdown menu with the new skill list if AltSystem.SkillDropdown then local skillOptions = BuildSkillOptions() - AltSystem.SkillDropdown:SetupMenu(function(dropdown, rootDescription) + -- 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(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) + 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 - if AltSystem.UpdateSkillWarning then - AltSystem.UpdateSkillWarning(data) - end - end, - i ) end end) diff --git a/docs/1-interface.md b/docs/1-interface.md old mode 100644 new mode 100755 diff --git a/docs/2-skills.md b/docs/2-skills.md old mode 100644 new mode 100755 diff --git a/docs/3-announce.md b/docs/3-announce.md old mode 100644 new mode 100755 diff --git a/docs/4-redesign.md b/docs/4-redesign.md new file mode 100755 index 0000000..fcd504c --- /dev/null +++ b/docs/4-redesign.md @@ -0,0 +1,78 @@ +# 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/5-build_skills.md b/docs/5-build_skills.md new file mode 100755 index 0000000..9b76326 --- /dev/null +++ b/docs/5-build_skills.md @@ -0,0 +1,132 @@ +# Feature: Build Skills tab +The second tab of the addon will follow these [designs](./build_skills_tab_design.png). + +## Acceptance Criteria +- This screen should show the same skills we use in the main screen, which come from the TRP profile +- The skills should be sorted by level +- The skill list should be scrollable, with the "Save" button pinned/sticky to the bottom +## Editing skills +- The user should be able to edit the name, level, and numerical score of each skill +- Edits should not be saved until the user explicitly clicks the "Save" button +### Skill Level and Value +- When a skill level is selected, the numerical score dropdown should update to only allow values within the skill level + - Inept: 0 + - Novice: 1-5 + - Adept: 6-10 + - Expert: 11-19 + - Master: 20 +### Deleting skills +- Clicking the "Delete" button should remove the skill from the list +### Adding Skills +- Clicking the "Add a Row" button should add a new skill row to the list +- Default values for the new skill should be: + - Name: Skillname + - Level: Novice + - Value: 1 +### Saving Skills +- When clicking the "Save" button: + - Newly added skills should be added to the TRP profile + - Existing skills should be updated in the TRP profile + - Skills that were deleted should also be removed from the TRP profile +- Icons should not be changed +## References +- Refer to [Data.lua](../Data.lua) for details on how we currently fetch the skills from TRP +- Refer to the TRP3 source code in case it's necessary [here](https://github.com/Total-RP/Total-RP-3) + +--- + +## Implementation Plan + +### 1. Add a `SaveSkills` function to Data.lua +- Create `AltSystem.Data:SaveSkills(editedSkills)` that writes skills back to the TRP3 profile +- Access the TRP3 profile via `TRP3_API.profile.getData("player/characteristics")` to get the `characteristics.PS` (personality traits) array +- For each edited skill, update or insert entries in `PS`: + - `LT` = skill name + - `RT` = level keyword (e.g. "Novice", "Adept", etc.) + - `V2` = numeric value (0–20) + - `IC` = preserve existing icon (do not change); for new skills, use a sensible default icon (e.g. `"inv_misc_questionmark"`) +- Remove any PS entries that were deleted by the user +- After writing, call `TRP3_API.dashboard.showCharacteristics()` or fire the appropriate TRP3 event if needed to refresh TRP3's own UI +- **Edge case:** If the TRP3 API is unavailable, show a warning message and abort save + +### 2. Extract skill-level constants into shared lookup tables in Data.lua +- The acceptance criteria defines value ranges per level (Inept: 0, Novice: 1–5, Adept: 6–10, Expert: 11–19, Master: 20) +- `SKILL_KEYWORD_RANGES` already exists but excludes Inept/Master min-max correctly for the Build tab's needs; extend or create a new table `AltSystem.Data.SkillValueRanges` that is accessible from UI.lua: + ``` + { Inept = {min=0, max=0}, Novice = {min=1, max=5}, Adept = {min=6, max=10}, Expert = {min=11, max=19}, Master = {min=20, max=20} } + ``` +- Create `AltSystem.Data.SkillLevelOrder` — an ordered array `{"Inept", "Novice", "Adept", "Expert", "Master"}` for populating the level dropdown in display order +- Create a helper `AltSystem.Data:GetDefaultValueForLevel(level)` that returns the minimum value for that level (used when the user changes level to auto-set the value) + +### 3. Add a function to read raw skills (with numeric values) from TRP3 in Data.lua +- Currently `RefreshSkills()` converts TRP3 traits into `{name, level, modifier}` — the Build tab needs the raw **numeric value** and the **icon** as well +- Create `AltSystem.Data:GetEditableSkills()` that returns an array of `{name, level, value, icon}` for each valid skill trait in the TRP3 profile (excluding Base Roll and Unskilled, which are system-generated entries) +- Sort the returned skills by level using `AltSystem.Data.SkillLevelOrder` ordering (Inept first, Master last) — matching the acceptance criteria "sorted by level" +- Reuse existing helpers `FindSkillKeyword` and `ParseSkillLevel` (promote them from local to module-level if needed, or call internally) + +### 4. Build the Build Skills tab UI (new file: BuildSkillsUI.lua) +- Create a new file to keep UI.lua manageable; register it in `AltSystem.toc` between `UI.lua` and `Roll.lua` +- Create `AltSystem:CreateBuildSkillsContent(parentFrame)` called from `CreateMainFrame` in UI.lua (replacing the placeholder) +- **Layout structure:** + - **Info text** at top — two golden/yellow paragraphs explaining that skills come from TRP (matches mockup) + - **"Skill list" section header** + - **Column headers**: Name, Level, Value (bold golden text) + - **Scrollable skill list** — a `ScrollFrame` containing dynamically created skill rows + - **"Add A Row" button** — anchored below the last skill row, inside the scroll child + - **"Save Skills to TRP" button** — pinned/sticky at the bottom of the tab, outside the scroll frame + +### 5. Implement editable skill rows +- Each skill row is a frame containing: + - **Name**: `EditBox` (text input) — pre-filled with current skill name + - **Level**: `DropdownButton` (WowStyle1DropdownTemplate) — options: Inept, Novice, Adept, Expert, Master + - **Value**: `DropdownButton` — options dynamically generated based on selected level (e.g. Novice → 1,2,3,4,5) + - **Delete button**: A button with a trash-can icon/red texture that removes the row +- Store all row data in a local working copy array (`editableSkills`), not directly in `AltSystem.Data.Skills` +- When the **level dropdown** changes: + - Update the value dropdown options to only show valid values for the new level + - Auto-set the value to the minimum for that level (e.g. switching to Adept → value becomes 6) +- **Row management:** + - `CreateSkillRow(parent, index, skillData)` — creates or recycles a row frame + - `RefreshSkillRows()` — rebuilds/repositions all rows and updates scroll child height + - Deleting a row removes it from `editableSkills` and calls `RefreshSkillRows()` + +### 6. Implement "Add A Row" functionality +- Clicking "Add A Row" inserts a new entry into `editableSkills`: + - `{ name = "Skillname", level = "Novice", value = 1, icon = "inv_misc_questionmark", isNew = true }` +- Calls `RefreshSkillRows()` to render the new row +- The scroll frame should auto-scroll to show the new row + +### 7. Implement "Save Skills to TRP" functionality +- On click, call `AltSystem.Data:SaveSkills(editableSkills)` which: + 1. Reads current `characteristics.PS` from TRP3 + 2. Rebuilds the PS array: keeps non-skill traits untouched, updates/adds/removes skill traits based on `editableSkills` + 3. Writes the updated PS back to the TRP3 profile data + 4. Calls `RefreshSkills()` so the Use Skills tab dropdown reflects the changes immediately +- Show a confirmation message (print to chat or a brief on-screen text) on successful save +- **Edge cases:** + - Empty skill list: allowed — just remove all skill traits from PS + - Duplicate skill names: allowed (TRP3 doesn't enforce uniqueness) + - Unsaved changes + tab switch: no confirmation dialog required (per spec, changes are just lost) + +### 8. Wire up tab switching to populate Build Skills tab +- In `SelectTab(2)` (UI.lua), call `AltSystem:RefreshBuildSkillsList()` to reload skills from TRP3 into the working copy +- This ensures the Build tab always shows the latest TRP3 data when opened, and any unsaved edits are discarded on tab switch + +### 9. Update AltSystem.toc +- Add `BuildSkillsUI.lua` to the file list (after `UI.lua`, before `Roll.lua`) + +### 10. Testing checklist +- [ ] Build Skills tab shows skills from TRP3 profile, sorted by level +- [ ] Skill name is editable via text input +- [ ] Level dropdown shows all 5 levels; changing level updates value dropdown options and auto-selects minimum value +- [ ] Value dropdown only shows values valid for the current level +- [ ] Delete button removes the row immediately +- [ ] "Add A Row" adds a row with defaults (Skillname, Novice, 1) +- [ ] Skill list scrolls when rows exceed visible area +- [ ] "Save" button is always visible (pinned to bottom) +- [ ] Save writes correct data to TRP3 profile (LT, RT, V2, IC preserved) +- [ ] Save does not modify icons of existing skills +- [ ] After save, Use Skills tab dropdown reflects the updated skills +- [ ] Switching tabs discards unsaved changes and reloads from TRP3 +- [ ] Works correctly with 0 skills (empty profile) +- [ ] Works correctly with many skills (20+) — scroll behavior \ No newline at end of file diff --git a/docs/Changelog.md b/docs/Changelog.md old mode 100644 new mode 100755 index dd05c6a..e69de29 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,3 +0,0 @@ -- 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/build_skills_tab_design.png b/docs/build_skills_tab_design.png new file mode 100755 index 0000000000000000000000000000000000000000..ddbfdd3285606e326bd67833d8876c6172c9c878 GIT binary patch literal 100436 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

1oa-EAKTA<|Rsu63VUjTNtBH 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 literal 0 HcmV?d00001 diff --git a/docs/roll_tab_design.png b/docs/roll_tab_design.png new file mode 100644 index 0000000000000000000000000000000000000000..c80f5dc03faa3d615c90c381e79f74c9b3bc178b GIT binary patch literal 75385 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�*fc=V@KO!p1olbL-( zGAu=rBMb-twGU2S>Yw4?3q2{bhpGl%=w* zKD~ce1lU>UU)C*3Vfti4JV@}nE@*jAmg)v*m`Z@vrwm+dS7t)(Y!?!$I~(iG>Je_V+FlQkwLI9@|R!L&A{ z-B5a4gN#g?cWd2JJPlN;btYx$B=nw}8=&{tIi@HEz=l^^Tea94uL7X)QmIsP{yJV! zkPl3X+iVj;|5x#gy1W4PzSw11!|hI5H{g5-+hbmQJ^fTzwEy`%*(z8iWJ27^JFFRj?7o$^m@$od90D>?Lb z*UJsEMx6)LmEM+Bn3hz*qN4#=dBWF6MJo&37#Z!>u#i5R4U83p@deMWQ9dO@T)Q`! zk&wkWTh2^abs3g0Hvu*!nyA zN60-wv+q<}o$r)%eg3FEsfZgbm6Oq=$MUVbe`lZ^ zzXRNc-3(ZqXkfKZq3%AwbL{J*-oF_?{`D48M@%qlVN>{H)M+)y{m1q2A2-NOF?VE> Wey)1NU1Wo4Z{0Au4!>#_{yzX7^4@j; literal 0 HcmV?d00001 diff --git a/package.sh b/package.sh index 6febfa8..194f78f 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' "$SCRIPT_DIR/" "$STAGING_DIR/$ADDON_NAME/" +rsync -a --exclude='.DS_Store' --exclude='__MACOSX' --exclude='.git' --exclude='docs' "$SCRIPT_DIR/" "$STAGING_DIR/$ADDON_NAME/" cd "$STAGING_DIR" && \ zip -r "$OUTPUT_FILE" "$ADDON_NAME" From ee9989d1897419d8682ecb833e7cd5347938dc16 Mon Sep 17 00:00:00 2001 From: Rukira Date: Mon, 18 May 2026 12:07:22 +0100 Subject: [PATCH 72/74] Fixing skill name displaying wrong skill --- UI.lua | 3 --- docs/roll_tab_design.png | Bin 2 files changed, 3 deletions(-) mode change 100644 => 100755 docs/roll_tab_design.png diff --git a/UI.lua b/UI.lua index 534d061..642d328 100755 --- a/UI.lua +++ b/UI.lua @@ -682,9 +682,6 @@ function AltSystem:RefreshSkillDropdown() 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 diff --git a/docs/roll_tab_design.png b/docs/roll_tab_design.png old mode 100644 new mode 100755 From 9f2b00b663f556378f990adf4f67512cc770388c Mon Sep 17 00:00:00 2001 From: Rukira Date: Mon, 18 May 2026 12:09:59 +0100 Subject: [PATCH 73/74] Fixing double 'Unskilled' option --- Data.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/Data.lua b/Data.lua index d1a1ae2..44914b4 100755 --- a/Data.lua +++ b/Data.lua @@ -25,7 +25,6 @@ local DEFAULT_SKILLS = { { name = "Adept Skill", level = "Adept", modifier = 0 }, { name = "Expert Skill", level = "Expert", modifier = 2 }, { name = "Master Skill", level = "Master", modifier = 4 }, - { name = "Unskilled", level = "Unskilled", modifier = -4 }, } -- Valid skill level keywords that must appear in the trait's right field (RT) From 778fe52b3fa6f49982b338a7fb3b8cb5d16188ac Mon Sep 17 00:00:00 2001 From: Rukira Date: Wed, 17 Jun 2026 15:10:56 +0100 Subject: [PATCH 74/74] TOC bump --- AltSystem.toc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AltSystem.toc b/AltSystem.toc index 3b8554d..b2d1ca3 100755 --- a/AltSystem.toc +++ b/AltSystem.toc @@ -1,8 +1,8 @@ -## Interface: 120005 +## Interface: 120007 ## Title: AltSystem ## Notes: Enhances RP gameplay with a custom rolling system ## Author: Rukira -## Version: 1.2.0 +## Version: 1.2.1 ## Dependencies: totalRP3, totalRP3_Extended ## SavedVariables: AltSystemDB