Compare commits
9 commits
5089a1dea4
...
cdbaf7db93
| Author | SHA1 | Date | |
|---|---|---|---|
| cdbaf7db93 | |||
| 8da464dcd2 | |||
| 26f25d969c | |||
| cfdef3cc26 | |||
| 9423171d54 | |||
| 57756acbff | |||
| d8f842f854 | |||
| 1a6a006280 | |||
| e08d9f63cb |
20 changed files with 828 additions and 88 deletions
0
.idea/.gitignore
generated
vendored
Normal file → Executable file
0
.idea/.gitignore
generated
vendored
Normal file → Executable file
0
.idea/misc.xml
generated
Normal file → Executable file
0
.idea/misc.xml
generated
Normal file → Executable file
0
.idea/modules.xml
generated
Normal file → Executable file
0
.idea/modules.xml
generated
Normal file → Executable file
0
.idea/vcs.xml
generated
Normal file → Executable file
0
.idea/vcs.xml
generated
Normal file → Executable file
0
AltSystem.iml
Normal file → Executable file
0
AltSystem.iml
Normal file → Executable file
1
AltSystem.toc
Normal file → Executable file
1
AltSystem.toc
Normal file → Executable 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
430
BuildSkillsUI.lua
Executable 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
0
Core.lua
Normal file → Executable file
113
Data.lua
Normal file → Executable file
113
Data.lua
Normal file → Executable 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
0
README.md
Normal file → Executable file
0
Roll.lua
Normal file → Executable file
0
Roll.lua
Normal file → Executable file
238
UI.lua
Normal file → Executable file
238
UI.lua
Normal file → Executable 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)
|
||||||
|
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
|
else
|
||||||
local t = radio:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
|
bg:SetColorTexture(0.3, 0.3, 0.3, 1)
|
||||||
t:SetPoint("LEFT", radio, "RIGHT", 4, 0)
|
end
|
||||||
t:SetText(text)
|
|
||||||
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
0
docs/1-interface.md
Normal file → Executable file
0
docs/2-skills.md
Normal file → Executable file
0
docs/2-skills.md
Normal file → Executable file
0
docs/3-announce.md
Normal file → Executable file
0
docs/3-announce.md
Normal file → Executable file
0
docs/4-redesign.md
Normal file → Executable file
0
docs/4-redesign.md
Normal file → Executable file
132
docs/5-build_skills.md
Executable file
132
docs/5-build_skills.md
Executable 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 (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
|
||||||
0
docs/Changelog.md
Normal file → Executable file
0
docs/Changelog.md
Normal file → Executable file
BIN
docs/build_skills_tab_design.png
Executable 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
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 |
Loading…
Add table
Add a link
Reference in a new issue