Compare commits

...

9 commits

20 changed files with 828 additions and 88 deletions

0
.idea/.gitignore generated vendored Normal file → Executable file
View file

0
.idea/misc.xml generated Normal file → Executable file
View file

0
.idea/modules.xml generated Normal file → Executable file
View file

0
.idea/vcs.xml generated Normal file → Executable file
View file

0
AltSystem.iml Normal file → Executable file
View file

1
AltSystem.toc Normal file → Executable file
View file

@ -8,5 +8,6 @@
Data.lua Data.lua
Core.lua Core.lua
BuildSkillsUI.lua
UI.lua UI.lua
Roll.lua Roll.lua

430
BuildSkillsUI.lua Executable file
View file

@ -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

0
Core.lua Normal file → Executable file
View file

113
Data.lua Normal file → Executable file
View file

@ -39,6 +39,27 @@ local SKILL_KEYWORD_RANGES = {
["Master"] = { min = 20, max = 20 }, ["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. -- Check if the trait's right text field contains a valid skill keyword.
-- Returns the matched keyword if found, nil otherwise. -- Returns the matched keyword if found, nil otherwise.
local function FindSkillKeyword(rightText) local function FindSkillKeyword(rightText)
@ -134,6 +155,94 @@ function AltSystem.Data:RefreshSkills()
return skills return skills
end 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 -- Initialize with the default skill list
AltSystem.Data.Skills = {} AltSystem.Data.Skills = {}
for _, skill in ipairs(DEFAULT_SKILLS) do for _, skill in ipairs(DEFAULT_SKILLS) do
@ -143,8 +252,8 @@ end
-- Item options: name and modifier (first entry = no item) -- Item options: name and modifier (first entry = no item)
AltSystem.Data.Items = { AltSystem.Data.Items = {
{ name = "No item", modifier = 0 }, { name = "No item", modifier = 0 },
{ name = "Rare item", modifier = 3 }, { name = "Rare", modifier = 3 },
{ name = "Epic item", modifier = 5 }, { name = "Epic", modifier = 5 },
} }
-- Defense / Armor options: name and modifier -- Defense / Armor options: name and modifier

0
README.md Normal file → Executable file
View file

0
Roll.lua Normal file → Executable file
View file

240
UI.lua Normal file → Executable file
View file

@ -29,7 +29,7 @@ local function BuildSkillOptions()
return options return options
end 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 function CreateDropdown(parent, name, labelText, options, defaultIndex, onSelect, labelFont)
local container = CreateFrame("Frame", nil, parent) local container = CreateFrame("Frame", nil, parent)
container:SetHeight(ROW_HEIGHT) container:SetHeight(ROW_HEIGHT)
@ -44,28 +44,32 @@ local function CreateDropdown(parent, name, labelText, options, defaultIndex, on
local selectedIndex = defaultIndex or 1 local selectedIndex = defaultIndex or 1
local dropdown = CreateFrame("DropdownButton", name, container, "WowStyle1DropdownTemplate") local dropdown = AltSystem.CreateFlatDropdown(name, container, 190)
if label then if label then
dropdown:SetPoint("RIGHT", container, "RIGHT", 0, 0) dropdown:SetPoint("RIGHT", container, "RIGHT", 0, 0)
else else
dropdown:SetPoint("LEFT", container, "LEFT", 0, 0) dropdown:SetPoint("LEFT", container, "LEFT", 0, 0)
end 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 for i, option in ipairs(options) do
rootDescription:CreateRadio( rootDescription:CreateRadio(
option.text, option.text,
function(data) function()
return data == selectedIndex return i == selectedIndex
end, end,
function(data) function()
selectedIndex = data selectedIndex = i
dropdown.label:SetText(option.text)
if onSelect then if onSelect then
onSelect(data, options[data]) onSelect(i, option)
end end
end, end
i
) )
end end
end) end)
@ -74,30 +78,52 @@ local function CreateDropdown(parent, name, labelText, options, defaultIndex, on
return selectedIndex return selectedIndex
end, function(idx) end, function(idx)
selectedIndex = idx selectedIndex = idx
if options[idx] then
dropdown.label:SetText(options[idx].text)
end
end end
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 function CreateRadioButton(parent, name, text, x, y, isChecked, onClick)
local radio = CreateFrame("CheckButton", name, parent, "UIRadioButtonTemplate") local size = 20
radio:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) local btn = CreateFrame("CheckButton", name, parent)
radio:SetChecked(isChecked) btn:SetSize(size, size)
btn:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
local radioText = radio:GetFontString() -- Background (grey when unselected, yellow when selected) — circular via mask
if radioText then local bg = btn:CreateTexture(nil, "BACKGROUND")
radioText:SetText(text) bg:SetAllPoints()
radioText:SetFontObject("GameFontHighlight") bg:SetColorTexture(0.3, 0.3, 0.3, 1)
else local mask = btn:CreateMaskTexture()
local t = radio:CreateFontString(nil, "OVERLAY", "GameFontHighlight") mask:SetAllPoints()
t:SetPoint("LEFT", radio, "RIGHT", 4, 0) mask:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
t:SetText(text) 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 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) onClick(self)
UpdateVisual()
end) end)
return radio btn.UpdateVisual = UpdateVisual
return btn
end end
-- Helper: Create a section header (golden text) -- Helper: Create a section header (golden text)
@ -184,9 +210,8 @@ function AltSystem:CreateMainFrame()
buildSkillsContent:SetSize(contentWidth, tabContentHeight) buildSkillsContent:SetSize(contentWidth, tabContentHeight)
buildSkillsContent:Hide() buildSkillsContent:Hide()
local buildPlaceholder = buildSkillsContent:CreateFontString(nil, "OVERLAY", "GameFontHighlight") -- Build Skills tab content (created in BuildSkillsUI.lua)
buildPlaceholder:SetPoint("CENTER") AltSystem:CreateBuildSkillsContent(buildSkillsContent)
buildPlaceholder:SetText("Coming soon")
-- Tab switching logic -- Tab switching logic
local function SelectTab(tabIndex) local function SelectTab(tabIndex)
@ -200,6 +225,7 @@ function AltSystem:CreateMainFrame()
buildSkillsContent:Show() buildSkillsContent:Show()
tabUseSkillsBg:SetColorTexture(0.3, 0.3, 0.3, 1) tabUseSkillsBg:SetColorTexture(0.3, 0.3, 0.3, 1)
tabBuildSkillsBg:SetColorTexture(0.15, 0.15, 0.15, 1) tabBuildSkillsBg:SetColorTexture(0.15, 0.15, 0.15, 1)
AltSystem:RefreshBuildSkillsList()
end end
end end
@ -235,6 +261,8 @@ function AltSystem:CreateMainFrame()
AltSystem.State.rollType = rollType AltSystem.State.rollType = rollType
attackRadio:SetChecked(rollType == "attack") attackRadio:SetChecked(rollType == "attack")
defenseRadio:SetChecked(rollType == "defense") defenseRadio:SetChecked(rollType == "defense")
attackRadio.UpdateVisual()
defenseRadio.UpdateVisual()
-- Update roll button text -- Update roll button text
if AltSystem.RollButton then if AltSystem.RollButton then
local label = rollType == "attack" and "Roll Attack" or "Roll Defense" local label = rollType == "attack" and "Roll Attack" or "Roll Defense"
@ -323,6 +351,7 @@ function AltSystem:CreateMainFrame()
AltSystem.State.selectedDefenseIndex = index AltSystem.State.selectedDefenseIndex = index
for i, radio in ipairs(armorRadios) do for i, radio in ipairs(armorRadios) do
radio:SetChecked(i == index) radio:SetChecked(i == index)
radio.UpdateVisual()
end end
end end
@ -343,6 +372,86 @@ function AltSystem:CreateMainFrame()
yPos = yPos - ROW_HEIGHT - SECTION_GAP yPos = yPos - ROW_HEIGHT - SECTION_GAP
-- Section: Modifiers (optional)
CreateSectionHeader(content, "Modifiers (optional)", PADDING, yPos)
yPos = yPos - 28
--CreateSubLabel(content, "Label", PADDING, yPos)
--yPos = yPos - 22
-- Shield checkbox (flat square toggle)
local shieldCheck = CreateFrame("CheckButton", "AltSystemShieldCheck", content)
shieldCheck:SetSize(20, 20)
shieldCheck:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos)
shieldCheck:SetChecked(AltSystem.State.shieldEnabled)
local shieldBg = shieldCheck:CreateTexture(nil, "BACKGROUND")
shieldBg:SetAllPoints()
shieldBg:SetColorTexture(0.3, 0.3, 0.3, 1)
local shieldCheckMark = shieldCheck:CreateTexture(nil, "ARTWORK")
shieldCheckMark:SetSize(14, 14)
shieldCheckMark:SetPoint("CENTER")
shieldCheckMark:SetTexture("Interface\\RAIDFRAME\\ReadyCheck-Ready")
shieldCheckMark:SetVertexColor(0.9, 0.75, 0.2, 1)
local function UpdateShieldVisual()
if shieldCheck:GetChecked() then
shieldCheckMark:Show()
shieldBg:SetColorTexture(0.25, 0.25, 0.25, 1)
else
shieldCheckMark:Hide()
shieldBg:SetColorTexture(0.3, 0.3, 0.3, 1)
end
end
UpdateShieldVisual()
local shieldLabel = shieldCheck:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
shieldLabel:SetPoint("LEFT", shieldCheck, "RIGHT", 6, 0)
shieldLabel:SetText("Shield (+ 1)")
shieldCheck:SetScript("OnClick", function(self)
AltSystem.State.shieldEnabled = self:GetChecked()
UpdateShieldVisual()
end)
-- Pet checkbox (flat square toggle)
local petCheck = CreateFrame("CheckButton", "AltSystemPetSummonCheck", content)
petCheck:SetSize(20, 20)
petCheck:SetPoint("LEFT", shieldCheck, "RIGHT", 80, 0)
petCheck:SetChecked(AltSystem.State.petSummonEnabled)
local petBg = petCheck:CreateTexture(nil, "BACKGROUND")
petBg:SetAllPoints()
petBg:SetColorTexture(0.3, 0.3, 0.3, 1)
local petCheckMark = petCheck:CreateTexture(nil, "ARTWORK")
petCheckMark:SetSize(14, 14)
petCheckMark:SetPoint("CENTER")
petCheckMark:SetTexture("Interface\\RAIDFRAME\\ReadyCheck-Ready")
petCheckMark:SetVertexColor(0.9, 0.75, 0.2, 1)
local function UpdatePetVisual()
if petCheck:GetChecked() then
petCheckMark:Show()
petBg:SetColorTexture(0.25, 0.25, 0.25, 1)
else
petCheckMark:Hide()
petBg:SetColorTexture(0.3, 0.3, 0.3, 1)
end
end
UpdatePetVisual()
local petLabel = petCheck:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
petLabel:SetPoint("LEFT", petCheck, "RIGHT", 6, 0)
petLabel:SetText("Pet (+d5)")
petCheck:SetScript("OnClick", function(self)
AltSystem.State.petSummonEnabled = self:GetChecked()
UpdatePetVisual()
end)
yPos = yPos - ROW_HEIGHT - SECTION_GAP
-- Item label -- Item label
CreateSubLabel(content, "Item", PADDING, yPos) CreateSubLabel(content, "Item", PADDING, yPos)
yPos = yPos - 20 yPos = yPos - 20
@ -354,6 +463,7 @@ function AltSystem:CreateMainFrame()
AltSystem.State.selectedItemIndex = index AltSystem.State.selectedItemIndex = index
for i, radio in ipairs(itemRadios) do for i, radio in ipairs(itemRadios) do
radio:SetChecked(i == index) radio:SetChecked(i == index)
radio.UpdateVisual()
end end
end end
@ -374,50 +484,6 @@ function AltSystem:CreateMainFrame()
yPos = yPos - ROW_HEIGHT - SECTION_GAP 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 shieldCheck = CreateFrame("CheckButton", "AltSystemShieldCheck", content, "UICheckButtonTemplate")
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)")
end
shieldCheck:SetScript("OnClick", function(self)
AltSystem.State.shieldEnabled = self:GetChecked()
end)
-- Pet checkbox
local petCheck = CreateFrame("CheckButton", "AltSystemPetSummonCheck", content, "UICheckButtonTemplate")
petCheck:SetPoint("LEFT", shieldCheck, "RIGHT", 80, 0)
petCheck:SetChecked(AltSystem.State.petSummonEnabled)
local petText = petCheck:GetFontString()
if petText then
petText:SetText("Pet (+d5)")
else
petText = petCheck:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
petText:SetPoint("LEFT", petCheck, "RIGHT", 2, 0)
petText:SetText("Pet (+d5)")
end
petCheck:SetScript("OnClick", function(self)
AltSystem.State.petSummonEnabled = self:GetChecked()
end)
yPos = yPos - ROW_HEIGHT - SECTION_GAP
-- Section: Roll Dice -- Section: Roll Dice
CreateSectionHeader(content, "Roll Dice", PADDING, yPos) CreateSectionHeader(content, "Roll Dice", PADDING, yPos)
yPos = yPos - 22 yPos = yPos - 22
@ -448,11 +514,9 @@ function AltSystem:CreateMainFrame()
yPos = yPos - ROW_HEIGHT - SECTION_GAP yPos = yPos - ROW_HEIGHT - SECTION_GAP
-- Roll button -- 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" 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() rollBtn:SetScript("OnClick", function()
AltSystem:PerformRoll(AltSystem.State.rollType) AltSystem:PerformRoll(AltSystem.State.rollType)
@ -573,24 +637,28 @@ function AltSystem:RefreshSkillDropdown()
-- Rebuild the dropdown menu with the new skill list -- Rebuild the dropdown menu with the new skill list
if AltSystem.SkillDropdown then if AltSystem.SkillDropdown then
local skillOptions = BuildSkillOptions() 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 for i, option in ipairs(skillOptions) do
rootDescription:CreateRadio( rootDescription:CreateRadio(
option.text, option.text,
function(data) function()
return data == AltSystem.State.selectedSkillIndex return i == AltSystem.State.selectedSkillIndex
end, end,
function(data) function()
AltSystem.State.selectedSkillIndex = data AltSystem.State.selectedSkillIndex = i
AltSystem.State.selectedSkillName = AltSystem.Data.Skills[data] and AltSystem.Data.Skills[data].name or nil 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 if AltSystem.SetSkillIndex then
AltSystem.SetSkillIndex(data) AltSystem.SetSkillIndex(i)
end end
if AltSystem.UpdateSkillWarning then if AltSystem.UpdateSkillWarning then
AltSystem.UpdateSkillWarning(data) AltSystem.UpdateSkillWarning(i)
end end
end, end
i
) )
end end
end) end)

0
docs/1-interface.md Normal file → Executable file
View file

0
docs/2-skills.md Normal file → Executable file
View file

0
docs/3-announce.md Normal file → Executable file
View file

0
docs/4-redesign.md Normal file → Executable file
View file

132
docs/5-build_skills.md Executable file
View file

@ -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 (020)
- `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: 15, Adept: 610, Expert: 1119, 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

0
docs/Changelog.md Normal file → Executable file
View file

BIN
docs/build_skills_tab_design.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

BIN
docs/roll_tab_design.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Before After
Before After