Compare commits
5 commits
6a44458443
...
5089a1dea4
| Author | SHA1 | Date | |
|---|---|---|---|
| 5089a1dea4 | |||
| 0680020808 | |||
| ffc693a63c | |||
| a4b2e69cc8 | |||
| ac01e4474c |
9 changed files with 598 additions and 246 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
## Title: AltSystem
|
## Title: AltSystem
|
||||||
## Notes: Enhances RP gameplay with a custom rolling system
|
## Notes: Enhances RP gameplay with a custom rolling system
|
||||||
## Author: Rukira
|
## Author: Rukira
|
||||||
## Version: 1.1
|
## Version: 2.0
|
||||||
## Dependencies: totalRP3, totalRP3_Extended
|
## Dependencies: totalRP3, totalRP3_Extended
|
||||||
## SavedVariables: AltSystemDB
|
## SavedVariables: AltSystemDB
|
||||||
|
|
||||||
|
|
|
||||||
37
Core.lua
37
Core.lua
|
|
@ -6,11 +6,14 @@ AltSystem.State = {
|
||||||
selectedSkillIndex = 1,
|
selectedSkillIndex = 1,
|
||||||
selectedSkillName = nil, -- skill name used to restore selection across sessions
|
selectedSkillName = nil, -- skill name used to restore selection across sessions
|
||||||
selectedItemIndex = 1, -- 1 = No item
|
selectedItemIndex = 1, -- 1 = No item
|
||||||
selectedDefenseIndex = 1, -- 1 = Base armor
|
selectedDefenseIndex = 1, -- 1 = No Armor
|
||||||
shieldEnabled = false,
|
shieldEnabled = false,
|
||||||
petSummonEnabled = false,
|
petSummonEnabled = false,
|
||||||
announceEnabled = false,
|
announceEnabled = false,
|
||||||
announceChannelIndex = 1, -- 1 = Emote, 2 = Party, 3 = Raid, 4 = Guild
|
announceChannelIndex = 1, -- 1 = Emote, 2 = Party, 3 = Raid, 4 = Guild
|
||||||
|
rollType = "attack", -- "attack" or "defense"
|
||||||
|
announceOptionIndex = 1, -- 1 = Self Roll, 2+ = channel from AnnounceChannels
|
||||||
|
rollLog = {}, -- array of { text = "...", timestamp = time() }, max 100, not persisted
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Channel definitions for announcing rolls
|
-- Channel definitions for announcing rolls
|
||||||
|
|
@ -35,12 +38,6 @@ end)
|
||||||
function AltSystem:Init()
|
function AltSystem:Init()
|
||||||
-- Load saved settings
|
-- Load saved settings
|
||||||
AltSystemDB = AltSystemDB or {}
|
AltSystemDB = AltSystemDB or {}
|
||||||
if AltSystemDB.announceEnabled ~= nil then
|
|
||||||
AltSystem.State.announceEnabled = AltSystemDB.announceEnabled
|
|
||||||
end
|
|
||||||
if AltSystemDB.announceChannelIndex then
|
|
||||||
AltSystem.State.announceChannelIndex = AltSystemDB.announceChannelIndex
|
|
||||||
end
|
|
||||||
if AltSystemDB.petSummonEnabled ~= nil then
|
if AltSystemDB.petSummonEnabled ~= nil then
|
||||||
AltSystem.State.petSummonEnabled = AltSystemDB.petSummonEnabled
|
AltSystem.State.petSummonEnabled = AltSystemDB.petSummonEnabled
|
||||||
end
|
end
|
||||||
|
|
@ -56,6 +53,28 @@ function AltSystem:Init()
|
||||||
if AltSystemDB.selectedSkillName then
|
if AltSystemDB.selectedSkillName then
|
||||||
AltSystem.State.selectedSkillName = AltSystemDB.selectedSkillName
|
AltSystem.State.selectedSkillName = AltSystemDB.selectedSkillName
|
||||||
end
|
end
|
||||||
|
if AltSystemDB.rollType then
|
||||||
|
AltSystem.State.rollType = AltSystemDB.rollType
|
||||||
|
end
|
||||||
|
-- Migrate from old announce settings or load new announceOptionIndex
|
||||||
|
if AltSystemDB.announceOptionIndex then
|
||||||
|
AltSystem.State.announceOptionIndex = AltSystemDB.announceOptionIndex
|
||||||
|
elseif AltSystemDB.announceEnabled ~= nil then
|
||||||
|
-- Backwards compat: derive from old announceEnabled + announceChannelIndex
|
||||||
|
if AltSystemDB.announceEnabled and AltSystemDB.announceChannelIndex then
|
||||||
|
AltSystem.State.announceOptionIndex = AltSystemDB.announceChannelIndex + 1
|
||||||
|
else
|
||||||
|
AltSystem.State.announceOptionIndex = 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Derive announceEnabled and announceChannelIndex from announceOptionIndex
|
||||||
|
if AltSystem.State.announceOptionIndex > 1 then
|
||||||
|
AltSystem.State.announceEnabled = true
|
||||||
|
AltSystem.State.announceChannelIndex = AltSystem.State.announceOptionIndex - 1
|
||||||
|
else
|
||||||
|
AltSystem.State.announceEnabled = false
|
||||||
|
AltSystem.State.announceChannelIndex = 1
|
||||||
|
end
|
||||||
|
|
||||||
-- Register slash command /altsystem
|
-- Register slash command /altsystem
|
||||||
SLASH_ALTSYSTEM1 = "/altsystem"
|
SLASH_ALTSYSTEM1 = "/altsystem"
|
||||||
|
|
@ -68,13 +87,13 @@ function AltSystem:Init()
|
||||||
saveFrame:RegisterEvent("PLAYER_LOGOUT")
|
saveFrame:RegisterEvent("PLAYER_LOGOUT")
|
||||||
saveFrame:SetScript("OnEvent", function()
|
saveFrame:SetScript("OnEvent", function()
|
||||||
AltSystemDB = AltSystemDB or {}
|
AltSystemDB = AltSystemDB or {}
|
||||||
AltSystemDB.announceEnabled = AltSystem.State.announceEnabled
|
AltSystemDB.announceOptionIndex = AltSystem.State.announceOptionIndex
|
||||||
AltSystemDB.announceChannelIndex = AltSystem.State.announceChannelIndex
|
|
||||||
AltSystemDB.petSummonEnabled = AltSystem.State.petSummonEnabled
|
AltSystemDB.petSummonEnabled = AltSystem.State.petSummonEnabled
|
||||||
AltSystemDB.shieldEnabled = AltSystem.State.shieldEnabled
|
AltSystemDB.shieldEnabled = AltSystem.State.shieldEnabled
|
||||||
AltSystemDB.selectedItemIndex = AltSystem.State.selectedItemIndex
|
AltSystemDB.selectedItemIndex = AltSystem.State.selectedItemIndex
|
||||||
AltSystemDB.selectedDefenseIndex = AltSystem.State.selectedDefenseIndex
|
AltSystemDB.selectedDefenseIndex = AltSystem.State.selectedDefenseIndex
|
||||||
AltSystemDB.selectedSkillName = AltSystem.State.selectedSkillName
|
AltSystemDB.selectedSkillName = AltSystem.State.selectedSkillName
|
||||||
|
AltSystemDB.rollType = AltSystem.State.rollType
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Build the main frame now that saved variables are loaded
|
-- Build the main frame now that saved variables are loaded
|
||||||
|
|
|
||||||
8
Data.lua
8
Data.lua
|
|
@ -147,11 +147,11 @@ AltSystem.Data.Items = {
|
||||||
{ name = "Epic item", modifier = 5 },
|
{ name = "Epic item", modifier = 5 },
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Defense options: name and modifier
|
-- Defense / Armor options: name and modifier
|
||||||
AltSystem.Data.Defenses = {
|
AltSystem.Data.Defenses = {
|
||||||
{ name = "Base armor", modifier = 0 },
|
{ name = "None", modifier = 0 },
|
||||||
{ name = "Extra small armor", modifier = 1 },
|
{ name = "Partial", modifier = 1 },
|
||||||
{ name = "Extra large armor", modifier = 2 },
|
{ name = "Full", modifier = 2 },
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Shield modifier
|
-- Shield modifier
|
||||||
|
|
|
||||||
105
Roll.lua
105
Roll.lua
|
|
@ -27,10 +27,42 @@ local function BuildModifierString(modifiers)
|
||||||
return table.concat(parts, " ")
|
return table.concat(parts, " ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Build the log message string (always uses the non-emote format with character name)
|
||||||
|
local function BuildLogMessage(rollValue, modifiers, total)
|
||||||
|
local name = GetCharacterName()
|
||||||
|
local modStr = BuildModifierString(modifiers)
|
||||||
|
if modStr ~= "" then
|
||||||
|
return name .. " rolled " .. rollValue .. " " .. modStr .. " = " .. total
|
||||||
|
else
|
||||||
|
return name .. " rolled " .. rollValue .. " = " .. total
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Build the critical roll log message
|
||||||
|
local function BuildCriticalLogMessage(isCriticalSuccess)
|
||||||
|
local name = GetCharacterName()
|
||||||
|
if isCriticalSuccess then
|
||||||
|
return name .. " rolled a Critical Success!"
|
||||||
|
else
|
||||||
|
return name .. " rolled a Critical Failure!"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Add an entry to the roll log and refresh the UI
|
||||||
|
local function AddLogEntry(text)
|
||||||
|
local rollLog = AltSystem.State.rollLog
|
||||||
|
table.insert(rollLog, { text = text, timestamp = time() })
|
||||||
|
-- Cap at 100 entries
|
||||||
|
if #rollLog > 100 then
|
||||||
|
table.remove(rollLog, 1)
|
||||||
|
end
|
||||||
|
-- Refresh the log panel if it exists
|
||||||
|
if AltSystem.RefreshLogPanel then
|
||||||
|
AltSystem:RefreshLogPanel()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Send a message to the given channel.
|
-- Send a message to the given channel.
|
||||||
-- Note: EMOTE channel uses SendChatMessage like all others. The WoW API
|
|
||||||
-- SendChatMessage(msg, "EMOTE") sends a proper /e emote from any context,
|
|
||||||
-- unlike RunMacroText which is a protected function requiring a hardware event.
|
|
||||||
local function SendToChannel(msg, channel)
|
local function SendToChannel(msg, channel)
|
||||||
SendChatMessage(msg, channel)
|
SendChatMessage(msg, channel)
|
||||||
end
|
end
|
||||||
|
|
@ -64,6 +96,24 @@ local function AnnounceRoll(rollValue, modifiers, total)
|
||||||
SendToChannel(msg, channelDef.channel)
|
SendToChannel(msg, channelDef.channel)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Announce a critical roll result
|
||||||
|
local function AnnounceCritical(isCriticalSuccess)
|
||||||
|
if not AltSystem.State.announceEnabled then return end
|
||||||
|
|
||||||
|
local channelDef = AltSystem.AnnounceChannels[AltSystem.State.announceChannelIndex]
|
||||||
|
if not channelDef then return end
|
||||||
|
|
||||||
|
local critText = isCriticalSuccess and "rolled a Critical Success!" or "rolled a Critical Failure!"
|
||||||
|
local msg
|
||||||
|
if channelDef.channel == "EMOTE" then
|
||||||
|
msg = critText
|
||||||
|
else
|
||||||
|
msg = GetCharacterName() .. " " .. critText
|
||||||
|
end
|
||||||
|
|
||||||
|
SendToChannel(msg, channelDef.channel)
|
||||||
|
end
|
||||||
|
|
||||||
-- Perform a roll: triggers the WoW native /roll 20 command
|
-- Perform a roll: triggers the WoW native /roll 20 command
|
||||||
function AltSystem:PerformRoll(rollType)
|
function AltSystem:PerformRoll(rollType)
|
||||||
pendingPetRoll = nil
|
pendingPetRoll = nil
|
||||||
|
|
@ -116,32 +166,12 @@ end)
|
||||||
function AltSystem:CalculateAndDisplayResult(rollType, rollValue, petRollValue)
|
function AltSystem:CalculateAndDisplayResult(rollType, rollValue, petRollValue)
|
||||||
-- Critical rolls bypass normal calculation
|
-- Critical rolls bypass normal calculation
|
||||||
if rollValue == 1 then
|
if rollValue == 1 then
|
||||||
if AltSystem.ResultText then
|
AddLogEntry(BuildCriticalLogMessage(false))
|
||||||
AltSystem.ResultText:SetText("|cffff0000Critical Failure|r")
|
AnnounceCritical(false)
|
||||||
end
|
|
||||||
if AltSystem.State.announceEnabled then
|
|
||||||
local channelDef = AltSystem.AnnounceChannels[AltSystem.State.announceChannelIndex]
|
|
||||||
if channelDef then
|
|
||||||
local critMsg = channelDef.channel == "EMOTE"
|
|
||||||
and "rolled a Critical Failure!"
|
|
||||||
or (GetCharacterName() .. " rolled a Critical Failure!")
|
|
||||||
SendToChannel(critMsg, channelDef.channel)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return
|
return
|
||||||
elseif rollValue == 20 then
|
elseif rollValue == 20 then
|
||||||
if AltSystem.ResultText then
|
AddLogEntry(BuildCriticalLogMessage(true))
|
||||||
AltSystem.ResultText:SetText("|cff00ff00Critical Success|r")
|
AnnounceCritical(true)
|
||||||
end
|
|
||||||
if AltSystem.State.announceEnabled then
|
|
||||||
local channelDef = AltSystem.AnnounceChannels[AltSystem.State.announceChannelIndex]
|
|
||||||
if channelDef then
|
|
||||||
local critMsg = channelDef.channel == "EMOTE"
|
|
||||||
and "rolled a Critical Success!"
|
|
||||||
or (GetCharacterName() .. " rolled a Critical Success!")
|
|
||||||
SendToChannel(critMsg, channelDef.channel)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -153,7 +183,6 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue, petRollValue)
|
||||||
local itemMod = item and item.modifier or 0
|
local itemMod = item and item.modifier or 0
|
||||||
|
|
||||||
local total = rollValue
|
local total = rollValue
|
||||||
local breakdown = "Roll: " .. rollValue
|
|
||||||
|
|
||||||
local modifiers = {}
|
local modifiers = {}
|
||||||
|
|
||||||
|
|
@ -166,18 +195,14 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue, petRollValue)
|
||||||
total = rollValue + skillMod + itemMod + petMod
|
total = rollValue + skillMod + itemMod + petMod
|
||||||
|
|
||||||
if not isBaseRoll then
|
if not isBaseRoll then
|
||||||
breakdown = breakdown .. "\nSkill: " .. FormatModifier(skillMod)
|
|
||||||
table.insert(modifiers, { name = skill and skill.name or "Skill", value = skillMod })
|
table.insert(modifiers, { name = skill and skill.name or "Skill", value = skillMod })
|
||||||
end
|
end
|
||||||
if itemMod ~= 0 then
|
if itemMod ~= 0 then
|
||||||
breakdown = breakdown .. " | Item: " .. FormatModifier(itemMod)
|
|
||||||
table.insert(modifiers, { name = item and item.name or "Item", value = itemMod })
|
table.insert(modifiers, { name = item and item.name or "Item", value = itemMod })
|
||||||
end
|
end
|
||||||
if petMod ~= 0 then
|
if petMod ~= 0 then
|
||||||
breakdown = breakdown .. " | Pet: +" .. petMod
|
|
||||||
table.insert(modifiers, { name = "Pet", value = petMod })
|
table.insert(modifiers, { name = "Pet", value = petMod })
|
||||||
end
|
end
|
||||||
breakdown = breakdown .. "\n|cffffd100Attack Total: " .. total .. "|r"
|
|
||||||
|
|
||||||
elseif rollType == "defense" then
|
elseif rollType == "defense" then
|
||||||
-- Defense Roll = roll + skill modifier + item modifier + defense modifier + shield modifier
|
-- Defense Roll = roll + skill modifier + item modifier + defense modifier + shield modifier
|
||||||
|
|
@ -188,30 +213,26 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue, petRollValue)
|
||||||
total = rollValue + skillMod + itemMod + defenseMod + shieldMod + petMod
|
total = rollValue + skillMod + itemMod + defenseMod + shieldMod + petMod
|
||||||
|
|
||||||
if not isBaseRoll then
|
if not isBaseRoll then
|
||||||
breakdown = breakdown .. "\nSkill: " .. FormatModifier(skillMod)
|
|
||||||
table.insert(modifiers, { name = skill and skill.name or "Skill", value = skillMod })
|
table.insert(modifiers, { name = skill and skill.name or "Skill", value = skillMod })
|
||||||
end
|
end
|
||||||
if itemMod ~= 0 then
|
if itemMod ~= 0 then
|
||||||
breakdown = breakdown .. " | Item: " .. FormatModifier(itemMod)
|
|
||||||
table.insert(modifiers, { name = item and item.name or "Item", value = itemMod })
|
table.insert(modifiers, { name = item and item.name or "Item", value = itemMod })
|
||||||
end
|
end
|
||||||
breakdown = breakdown .. "\nDefense: " .. FormatModifier(defenseMod)
|
if defenseMod ~= 0 then
|
||||||
table.insert(modifiers, { name = defense and defense.name or "Defense", value = defenseMod })
|
table.insert(modifiers, { name = defense and defense.name or "Armor", value = defenseMod })
|
||||||
|
end
|
||||||
if shieldMod ~= 0 then
|
if shieldMod ~= 0 then
|
||||||
breakdown = breakdown .. " | Shield: " .. FormatModifier(shieldMod)
|
|
||||||
table.insert(modifiers, { name = "Shield", value = shieldMod })
|
table.insert(modifiers, { name = "Shield", value = shieldMod })
|
||||||
end
|
end
|
||||||
if petMod ~= 0 then
|
if petMod ~= 0 then
|
||||||
breakdown = breakdown .. " | Pet: +" .. petMod
|
|
||||||
table.insert(modifiers, { name = "Pet", value = petMod })
|
table.insert(modifiers, { name = "Pet", value = petMod })
|
||||||
end
|
end
|
||||||
breakdown = breakdown .. "\n|cff00ccffDefense Total: " .. total .. "|r"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if AltSystem.ResultText then
|
-- Add to log (always, regardless of announce setting)
|
||||||
AltSystem.ResultText:SetText(breakdown)
|
AddLogEntry(BuildLogMessage(rollValue, modifiers, total))
|
||||||
end
|
|
||||||
|
|
||||||
|
-- Announce to chat (if enabled)
|
||||||
AnnounceRoll(rollValue, modifiers, total)
|
AnnounceRoll(rollValue, modifiers, total)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
549
UI.lua
549
UI.lua
|
|
@ -1,14 +1,17 @@
|
||||||
-- AltSystem UI
|
-- AltSystem UI
|
||||||
-- Creates the main dialog window with all interface elements.
|
-- Creates the main dialog window with tabbed layout.
|
||||||
|
-- The "Use Skills" tab contains controls on the left and a Log panel on the right.
|
||||||
-- Uses the modern DropdownButton API (WoW 10.2.5+ / 12.0+).
|
-- Uses the modern DropdownButton API (WoW 10.2.5+ / 12.0+).
|
||||||
|
|
||||||
AltSystem = AltSystem or {}
|
AltSystem = AltSystem or {}
|
||||||
|
|
||||||
local WINDOW_WIDTH = 300
|
local WINDOW_WIDTH = 700
|
||||||
local WINDOW_HEIGHT = 478
|
local WINDOW_HEIGHT = 500
|
||||||
|
local CONTROLS_WIDTH = 350
|
||||||
|
local LOG_WIDTH = 350
|
||||||
local PADDING = 12
|
local PADDING = 12
|
||||||
local ROW_HEIGHT = 30
|
local ROW_HEIGHT = 26
|
||||||
local LABEL_WIDTH = 80
|
local SECTION_GAP = 10
|
||||||
|
|
||||||
-- Helper: Build the skill option list from current AltSystem.Data.Skills
|
-- Helper: Build the skill option list from current AltSystem.Data.Skills
|
||||||
local function BuildSkillOptions()
|
local function BuildSkillOptions()
|
||||||
|
|
@ -27,38 +30,98 @@ local function BuildSkillOptions()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Helper: Create a modern dropdown (WowStyle1DropdownTemplate)
|
-- Helper: Create a modern dropdown (WowStyle1DropdownTemplate)
|
||||||
local function CreateDropdown(parent, name, yOffset, labelText, options, defaultIndex, onSelect)
|
local function CreateDropdown(parent, name, labelText, options, defaultIndex, onSelect, labelFont)
|
||||||
local label = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
local container = CreateFrame("Frame", nil, parent)
|
||||||
label:SetPoint("TOPLEFT", parent, "TOPLEFT", PADDING, yOffset)
|
container:SetHeight(ROW_HEIGHT)
|
||||||
|
|
||||||
|
local label
|
||||||
|
if labelText and labelText ~= "" then
|
||||||
|
label = container:CreateFontString(nil, "OVERLAY", labelFont or "GameFontNormal")
|
||||||
|
label:SetPoint("LEFT", container, "LEFT", 0, 0)
|
||||||
label:SetText(labelText)
|
label:SetText(labelText)
|
||||||
label:SetWidth(LABEL_WIDTH)
|
|
||||||
label:SetJustifyH("LEFT")
|
label:SetJustifyH("LEFT")
|
||||||
|
end
|
||||||
|
|
||||||
local selectedIndex = defaultIndex or 1
|
local selectedIndex = defaultIndex or 1
|
||||||
|
|
||||||
local dropdown = CreateFrame("DropdownButton", name, parent, "WowStyle1DropdownTemplate")
|
local dropdown = CreateFrame("DropdownButton", name, container, "WowStyle1DropdownTemplate")
|
||||||
dropdown:SetPoint("LEFT", label, "RIGHT", 4, 0)
|
if label then
|
||||||
dropdown:SetWidth(160)
|
dropdown:SetPoint("RIGHT", container, "RIGHT", 0, 0)
|
||||||
|
else
|
||||||
|
dropdown:SetPoint("LEFT", container, "LEFT", 0, 0)
|
||||||
|
end
|
||||||
|
dropdown:SetWidth(190)
|
||||||
|
|
||||||
dropdown:SetupMenu(function(dropdown, rootDescription)
|
dropdown:SetupMenu(function(dropdown, 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) return data == selectedIndex end,
|
function(data)
|
||||||
|
return data == selectedIndex
|
||||||
|
end,
|
||||||
function(data)
|
function(data)
|
||||||
selectedIndex = data
|
selectedIndex = data
|
||||||
if onSelect then onSelect(data, options[data]) end
|
if onSelect then
|
||||||
|
onSelect(data, options[data])
|
||||||
|
end
|
||||||
end,
|
end,
|
||||||
i
|
i
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
return dropdown, function() return selectedIndex end, function(idx) selectedIndex = idx end
|
return container, dropdown, function()
|
||||||
|
return selectedIndex
|
||||||
|
end, function(idx)
|
||||||
|
selectedIndex = idx
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Helper: Create a radio button (CheckButton with radio texture)
|
||||||
|
local function CreateRadioButton(parent, name, text, x, y, isChecked, onClick)
|
||||||
|
local radio = CreateFrame("CheckButton", name, parent, "UIRadioButtonTemplate")
|
||||||
|
radio:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
|
||||||
|
radio:SetChecked(isChecked)
|
||||||
|
|
||||||
|
local radioText = radio:GetFontString()
|
||||||
|
if radioText then
|
||||||
|
radioText:SetText(text)
|
||||||
|
radioText:SetFontObject("GameFontHighlight")
|
||||||
|
else
|
||||||
|
local t = radio:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
|
||||||
|
t:SetPoint("LEFT", radio, "RIGHT", 4, 0)
|
||||||
|
t:SetText(text)
|
||||||
|
end
|
||||||
|
|
||||||
|
radio:SetScript("OnClick", function(self)
|
||||||
|
onClick(self)
|
||||||
|
end)
|
||||||
|
|
||||||
|
return radio
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Helper: Create a section header (golden text)
|
||||||
|
local function CreateSectionHeader(parent, text, x, y)
|
||||||
|
local header = parent:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
|
||||||
|
header:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
|
||||||
|
header:SetText(text)
|
||||||
|
header:SetTextColor(0.9, 0.75, 0.2)
|
||||||
|
return header
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Helper: Create a sub-label (smaller golden text)
|
||||||
|
local function CreateSubLabel(parent, text, x, y)
|
||||||
|
local label = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||||
|
label:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
|
||||||
|
label:SetText(text)
|
||||||
|
label:SetTextColor(0.9, 0.75, 0.2)
|
||||||
|
return label
|
||||||
end
|
end
|
||||||
|
|
||||||
function AltSystem:CreateMainFrame()
|
function AltSystem:CreateMainFrame()
|
||||||
if AltSystem.MainFrame then return end
|
if AltSystem.MainFrame then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
-- Main frame
|
-- Main frame
|
||||||
local f = CreateFrame("Frame", "AltSystemMainFrame", UIParent, "BasicFrameTemplateWithInset")
|
local f = CreateFrame("Frame", "AltSystemMainFrame", UIParent, "BasicFrameTemplateWithInset")
|
||||||
|
|
@ -76,26 +139,142 @@ function AltSystem:CreateMainFrame()
|
||||||
f.title:SetPoint("TOPLEFT", f.TitleBg, "TOPLEFT", 5, -3)
|
f.title:SetPoint("TOPLEFT", f.TitleBg, "TOPLEFT", 5, -3)
|
||||||
f.title:SetText("AltSystem")
|
f.title:SetText("AltSystem")
|
||||||
|
|
||||||
-- Track current Y offset for layout
|
---------------------
|
||||||
local yPos = -40
|
-- TAB BUTTONS (span full window width)
|
||||||
|
---------------------
|
||||||
|
local contentTop = -24
|
||||||
|
local tabHeight = 28
|
||||||
|
local contentWidth = WINDOW_WIDTH - 8 -- 4px inset on each side
|
||||||
|
local tabWidth = contentWidth / 2
|
||||||
|
|
||||||
|
local tabUseSkills = CreateFrame("Button", "AltSystemTabUseSkills", f)
|
||||||
|
tabUseSkills:SetSize(tabWidth, tabHeight)
|
||||||
|
tabUseSkills:SetPoint("TOPLEFT", f, "TOPLEFT", 4, contentTop)
|
||||||
|
tabUseSkills:SetNormalFontObject("GameFontHighlight")
|
||||||
|
tabUseSkills:SetHighlightFontObject("GameFontHighlight")
|
||||||
|
tabUseSkills:SetText("Use Skills")
|
||||||
|
|
||||||
|
local tabUseSkillsBg = tabUseSkills:CreateTexture(nil, "BACKGROUND")
|
||||||
|
tabUseSkillsBg:SetAllPoints()
|
||||||
|
tabUseSkillsBg:SetColorTexture(0.15, 0.15, 0.15, 1)
|
||||||
|
|
||||||
|
local tabBuildSkills = CreateFrame("Button", "AltSystemTabBuildSkills", f)
|
||||||
|
tabBuildSkills:SetSize(tabWidth, tabHeight)
|
||||||
|
tabBuildSkills:SetPoint("TOPLEFT", tabUseSkills, "TOPRIGHT", 0, 0)
|
||||||
|
tabBuildSkills:SetNormalFontObject("GameFontHighlight")
|
||||||
|
tabBuildSkills:SetHighlightFontObject("GameFontHighlight")
|
||||||
|
tabBuildSkills:SetText("Build Skills")
|
||||||
|
|
||||||
|
local tabBuildSkillsBg = tabBuildSkills:CreateTexture(nil, "BACKGROUND")
|
||||||
|
tabBuildSkillsBg:SetAllPoints()
|
||||||
|
tabBuildSkillsBg:SetColorTexture(0.3, 0.3, 0.3, 1)
|
||||||
|
|
||||||
|
---------------------
|
||||||
|
-- TAB CONTENT FRAMES
|
||||||
|
---------------------
|
||||||
|
local tabContentTop = contentTop - tabHeight
|
||||||
|
local tabContentHeight = WINDOW_HEIGHT - 28 - tabHeight
|
||||||
|
|
||||||
|
local useSkillsContent = CreateFrame("Frame", "AltSystemUseSkillsContent", f)
|
||||||
|
useSkillsContent:SetPoint("TOPLEFT", f, "TOPLEFT", 4, tabContentTop)
|
||||||
|
useSkillsContent:SetSize(contentWidth, tabContentHeight)
|
||||||
|
|
||||||
|
local buildSkillsContent = CreateFrame("Frame", "AltSystemBuildSkillsContent", f)
|
||||||
|
buildSkillsContent:SetPoint("TOPLEFT", f, "TOPLEFT", 4, tabContentTop)
|
||||||
|
buildSkillsContent:SetSize(contentWidth, tabContentHeight)
|
||||||
|
buildSkillsContent:Hide()
|
||||||
|
|
||||||
|
local buildPlaceholder = buildSkillsContent:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
|
||||||
|
buildPlaceholder:SetPoint("CENTER")
|
||||||
|
buildPlaceholder:SetText("Coming soon")
|
||||||
|
|
||||||
|
-- Tab switching logic
|
||||||
|
local function SelectTab(tabIndex)
|
||||||
|
if tabIndex == 1 then
|
||||||
|
useSkillsContent:Show()
|
||||||
|
buildSkillsContent:Hide()
|
||||||
|
tabUseSkillsBg:SetColorTexture(0.15, 0.15, 0.15, 1)
|
||||||
|
tabBuildSkillsBg:SetColorTexture(0.3, 0.3, 0.3, 1)
|
||||||
|
else
|
||||||
|
useSkillsContent:Hide()
|
||||||
|
buildSkillsContent:Show()
|
||||||
|
tabUseSkillsBg:SetColorTexture(0.3, 0.3, 0.3, 1)
|
||||||
|
tabBuildSkillsBg:SetColorTexture(0.15, 0.15, 0.15, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
tabUseSkills:SetScript("OnClick", function()
|
||||||
|
SelectTab(1)
|
||||||
|
end)
|
||||||
|
tabBuildSkills:SetScript("OnClick", function()
|
||||||
|
SelectTab(2)
|
||||||
|
end)
|
||||||
|
|
||||||
|
---------------------
|
||||||
|
-- USE SKILLS TAB CONTENT (left: controls, right: log)
|
||||||
|
---------------------
|
||||||
|
local controlsPanel = CreateFrame("Frame", nil, useSkillsContent)
|
||||||
|
controlsPanel:SetPoint("TOPLEFT", useSkillsContent, "TOPLEFT", 0, 0)
|
||||||
|
controlsPanel:SetSize(CONTROLS_WIDTH, tabContentHeight)
|
||||||
|
|
||||||
|
local content = controlsPanel
|
||||||
|
local yPos = -PADDING
|
||||||
|
|
||||||
|
-- Section: Define Your Base Roll
|
||||||
|
CreateSectionHeader(content, "Define Your Base Roll", PADDING, yPos)
|
||||||
|
yPos = yPos - 20
|
||||||
|
|
||||||
|
-- Roll Type label
|
||||||
|
CreateSubLabel(content, "Roll Type", PADDING, yPos)
|
||||||
|
yPos = yPos - 20
|
||||||
|
|
||||||
|
-- Roll Type radio buttons
|
||||||
|
local attackRadio, defenseRadio
|
||||||
|
|
||||||
|
local function UpdateRollTypeSelection(rollType)
|
||||||
|
AltSystem.State.rollType = rollType
|
||||||
|
attackRadio:SetChecked(rollType == "attack")
|
||||||
|
defenseRadio:SetChecked(rollType == "defense")
|
||||||
|
-- Update roll button text
|
||||||
|
if AltSystem.RollButton then
|
||||||
|
local label = rollType == "attack" and "Roll Attack" or "Roll Defense"
|
||||||
|
AltSystem.RollButton:SetText(label)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
attackRadio = CreateRadioButton(content, "AltSystemAttackRadio", "Attack Roll", PADDING, yPos,
|
||||||
|
AltSystem.State.rollType == "attack",
|
||||||
|
function()
|
||||||
|
UpdateRollTypeSelection("attack")
|
||||||
|
end)
|
||||||
|
|
||||||
|
defenseRadio = CreateRadioButton(content, "AltSystemDefenseRadio", "Defense Roll", CONTROLS_WIDTH / 2, yPos,
|
||||||
|
AltSystem.State.rollType == "defense",
|
||||||
|
function()
|
||||||
|
UpdateRollTypeSelection("defense")
|
||||||
|
end)
|
||||||
|
|
||||||
|
yPos = yPos - ROW_HEIGHT - SECTION_GAP
|
||||||
|
|
||||||
-------------------------
|
|
||||||
-- Skill dropdown
|
-- Skill dropdown
|
||||||
-------------------------
|
|
||||||
local skillOptions = BuildSkillOptions()
|
local skillOptions = BuildSkillOptions()
|
||||||
local UpdateSkillWarning -- forward declaration
|
local UpdateSkillWarning -- forward declaration
|
||||||
|
|
||||||
local skillDropdown, getSkillIndex, setSkillIndex = CreateDropdown(
|
local skillContainer, skillDropdown, getSkillIndex, setSkillIndex = CreateDropdown(
|
||||||
f, "AltSystemSkillDropdown", yPos, "Skill:", skillOptions,
|
content, "AltSystemSkillDropdown", "Skill", skillOptions,
|
||||||
AltSystem.State.selectedSkillIndex,
|
AltSystem.State.selectedSkillIndex,
|
||||||
function(index)
|
function(index)
|
||||||
AltSystem.State.selectedSkillIndex = index
|
AltSystem.State.selectedSkillIndex = index
|
||||||
AltSystem.State.selectedSkillName = AltSystem.Data.Skills[index] and AltSystem.Data.Skills[index].name or nil
|
AltSystem.State.selectedSkillName = AltSystem.Data.Skills[index] and AltSystem.Data.Skills[index].name or nil
|
||||||
UpdateSkillWarning(index)
|
UpdateSkillWarning(index)
|
||||||
end)
|
end,
|
||||||
|
"GameFontNormal"
|
||||||
|
)
|
||||||
|
skillContainer:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos)
|
||||||
|
skillContainer:SetWidth(CONTROLS_WIDTH - PADDING * 2)
|
||||||
|
|
||||||
-- Warning icon for skill mismatch (shown next to dropdown when value doesn't match keyword)
|
-- Warning icon for skill mismatch
|
||||||
local skillWarning = CreateFrame("Frame", nil, f)
|
local skillWarning = CreateFrame("Frame", nil, skillContainer)
|
||||||
skillWarning:SetSize(20, 20)
|
skillWarning:SetSize(20, 20)
|
||||||
skillWarning:SetPoint("LEFT", skillDropdown, "RIGHT", 4, 0)
|
skillWarning:SetPoint("LEFT", skillDropdown, "RIGHT", 4, 0)
|
||||||
|
|
||||||
|
|
@ -115,7 +294,6 @@ function AltSystem:CreateMainFrame()
|
||||||
end)
|
end)
|
||||||
skillWarning:Hide()
|
skillWarning:Hide()
|
||||||
|
|
||||||
-- Update the warning icon visibility based on the currently selected skill
|
|
||||||
UpdateSkillWarning = function(index)
|
UpdateSkillWarning = function(index)
|
||||||
local skill = AltSystem.Data.Skills[index]
|
local skill = AltSystem.Data.Skills[index]
|
||||||
if skill and skill.warning then
|
if skill and skill.warning then
|
||||||
|
|
@ -131,186 +309,243 @@ function AltSystem:CreateMainFrame()
|
||||||
AltSystem.GetSkillIndex = getSkillIndex
|
AltSystem.GetSkillIndex = getSkillIndex
|
||||||
AltSystem.SetSkillIndex = setSkillIndex
|
AltSystem.SetSkillIndex = setSkillIndex
|
||||||
AltSystem.UpdateSkillWarning = UpdateSkillWarning
|
AltSystem.UpdateSkillWarning = UpdateSkillWarning
|
||||||
yPos = yPos - ROW_HEIGHT - 8
|
|
||||||
|
|
||||||
-------------------------
|
yPos = yPos - ROW_HEIGHT - SECTION_GAP
|
||||||
-- Item dropdown
|
|
||||||
-------------------------
|
|
||||||
local itemOptions = {}
|
|
||||||
for _, item in ipairs(AltSystem.Data.Items) do
|
|
||||||
local sign = item.modifier >= 0 and "+" or ""
|
|
||||||
local modText = item.modifier ~= 0 and (" (" .. sign .. item.modifier .. ")") or ""
|
|
||||||
table.insert(itemOptions, {
|
|
||||||
text = item.name .. modText,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
CreateDropdown(f, "AltSystemItemDropdown", yPos, "Item:", itemOptions, AltSystem.State.selectedItemIndex,
|
-- Armor label
|
||||||
function(index)
|
CreateSubLabel(content, "Extra Armor", PADDING, yPos)
|
||||||
AltSystem.State.selectedItemIndex = index
|
yPos = yPos - 20
|
||||||
end)
|
|
||||||
yPos = yPos - ROW_HEIGHT - 8
|
|
||||||
|
|
||||||
-------------------------
|
-- Armor radio buttons
|
||||||
-- Defense dropdown
|
local armorRadios = {}
|
||||||
-------------------------
|
|
||||||
local defenseOptions = {}
|
|
||||||
for _, def in ipairs(AltSystem.Data.Defenses) do
|
|
||||||
local sign = def.modifier >= 0 and "+" or ""
|
|
||||||
local modText = " (" .. sign .. def.modifier .. ")"
|
|
||||||
table.insert(defenseOptions, {
|
|
||||||
text = def.name .. modText,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
CreateDropdown(f, "AltSystemDefenseDropdown", yPos, "Defense:", defenseOptions, AltSystem.State.selectedDefenseIndex,
|
local function UpdateArmorSelection(index)
|
||||||
function(index)
|
|
||||||
AltSystem.State.selectedDefenseIndex = index
|
AltSystem.State.selectedDefenseIndex = index
|
||||||
|
for i, radio in ipairs(armorRadios) do
|
||||||
|
radio:SetChecked(i == index)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local armorX = PADDING
|
||||||
|
for i, def in ipairs(AltSystem.Data.Defenses) do
|
||||||
|
local text = def.name
|
||||||
|
if def.modifier > 0 then
|
||||||
|
text = text .. " (+" .. def.modifier .. ")"
|
||||||
|
end
|
||||||
|
local radio = CreateRadioButton(content, "AltSystemArmorRadio" .. i, text, armorX, yPos,
|
||||||
|
AltSystem.State.selectedDefenseIndex == i,
|
||||||
|
function()
|
||||||
|
UpdateArmorSelection(i)
|
||||||
end)
|
end)
|
||||||
yPos = yPos - ROW_HEIGHT - 8
|
table.insert(armorRadios, radio)
|
||||||
|
armorX = armorX + 110
|
||||||
|
end
|
||||||
|
|
||||||
|
yPos = yPos - ROW_HEIGHT - SECTION_GAP
|
||||||
|
|
||||||
|
-- Item label
|
||||||
|
CreateSubLabel(content, "Item", PADDING, yPos)
|
||||||
|
yPos = yPos - 20
|
||||||
|
|
||||||
|
-- Item radio buttons
|
||||||
|
local itemRadios = {}
|
||||||
|
|
||||||
|
local function UpdateItemSelection(index)
|
||||||
|
AltSystem.State.selectedItemIndex = index
|
||||||
|
for i, radio in ipairs(itemRadios) do
|
||||||
|
radio:SetChecked(i == index)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local itemX = PADDING
|
||||||
|
for i, item in ipairs(AltSystem.Data.Items) do
|
||||||
|
local text = item.name
|
||||||
|
if item.modifier > 0 then
|
||||||
|
text = text .. " (+" .. item.modifier .. ")"
|
||||||
|
end
|
||||||
|
local radio = CreateRadioButton(content, "AltSystemItemRadio" .. i, text, itemX, yPos,
|
||||||
|
AltSystem.State.selectedItemIndex == i,
|
||||||
|
function()
|
||||||
|
UpdateItemSelection(i)
|
||||||
|
end)
|
||||||
|
table.insert(itemRadios, radio)
|
||||||
|
itemX = itemX + 110
|
||||||
|
end
|
||||||
|
|
||||||
|
yPos = yPos - ROW_HEIGHT - SECTION_GAP
|
||||||
|
|
||||||
|
-- Section: Modifiers (optional)
|
||||||
|
CreateSectionHeader(content, "Modifiers (optional)", PADDING, yPos)
|
||||||
|
yPos = yPos - 18
|
||||||
|
--CreateSubLabel(content, "Label", PADDING, yPos)
|
||||||
|
--yPos = yPos - 22
|
||||||
|
|
||||||
-------------------------
|
|
||||||
-- Shield checkbox
|
-- Shield checkbox
|
||||||
-------------------------
|
local shieldCheck = CreateFrame("CheckButton", "AltSystemShieldCheck", content, "UICheckButtonTemplate")
|
||||||
local shieldLabel = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
shieldCheck:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos)
|
||||||
shieldLabel:SetPoint("TOPLEFT", f, "TOPLEFT", PADDING, yPos)
|
|
||||||
shieldLabel:SetText("Shield:")
|
|
||||||
shieldLabel:SetWidth(LABEL_WIDTH)
|
|
||||||
shieldLabel:SetJustifyH("LEFT")
|
|
||||||
|
|
||||||
local shieldCheck = CreateFrame("CheckButton", "AltSystemShieldCheck", f, "UICheckButtonTemplate")
|
|
||||||
shieldCheck:SetPoint("LEFT", shieldLabel, "RIGHT", -6, 0)
|
|
||||||
shieldCheck:SetChecked(AltSystem.State.shieldEnabled)
|
shieldCheck:SetChecked(AltSystem.State.shieldEnabled)
|
||||||
|
|
||||||
local shieldText = shieldCheck:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
|
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:SetPoint("LEFT", shieldCheck, "RIGHT", 2, 0)
|
||||||
shieldText:SetText("+1 modifier")
|
shieldText:SetText("Shield (+ 1)")
|
||||||
|
end
|
||||||
|
|
||||||
shieldCheck:SetScript("OnClick", function(self)
|
shieldCheck:SetScript("OnClick", function(self)
|
||||||
AltSystem.State.shieldEnabled = self:GetChecked()
|
AltSystem.State.shieldEnabled = self:GetChecked()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
yPos = yPos - ROW_HEIGHT - 8
|
-- Pet checkbox
|
||||||
|
local petCheck = CreateFrame("CheckButton", "AltSystemPetSummonCheck", content, "UICheckButtonTemplate")
|
||||||
-------------------------
|
petCheck:SetPoint("LEFT", shieldCheck, "RIGHT", 80, 0)
|
||||||
-- Pet/Summon checkbox
|
|
||||||
-------------------------
|
|
||||||
local petLabel = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
|
||||||
petLabel:SetPoint("TOPLEFT", f, "TOPLEFT", PADDING, yPos)
|
|
||||||
petLabel:SetText("Pet:")
|
|
||||||
petLabel:SetWidth(LABEL_WIDTH)
|
|
||||||
petLabel:SetJustifyH("LEFT")
|
|
||||||
|
|
||||||
local petCheck = CreateFrame("CheckButton", "AltSystemPetSummonCheck", f, "UICheckButtonTemplate")
|
|
||||||
petCheck:SetPoint("LEFT", petLabel, "RIGHT", -6, 0)
|
|
||||||
petCheck:SetChecked(AltSystem.State.petSummonEnabled)
|
petCheck:SetChecked(AltSystem.State.petSummonEnabled)
|
||||||
|
|
||||||
local petText = petCheck:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
|
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:SetPoint("LEFT", petCheck, "RIGHT", 2, 0)
|
||||||
petText:SetText("+d5 modifier")
|
petText:SetText("Pet (+d5)")
|
||||||
|
end
|
||||||
|
|
||||||
petCheck:SetScript("OnClick", function(self)
|
petCheck:SetScript("OnClick", function(self)
|
||||||
AltSystem.State.petSummonEnabled = self:GetChecked()
|
AltSystem.State.petSummonEnabled = self:GetChecked()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
yPos = yPos - ROW_HEIGHT - 8
|
yPos = yPos - ROW_HEIGHT - SECTION_GAP
|
||||||
|
|
||||||
-------------------------
|
-- Section: Roll Dice
|
||||||
-- Announce checkbox + channel dropdown
|
CreateSectionHeader(content, "Roll Dice", PADDING, yPos)
|
||||||
-------------------------
|
yPos = yPos - 22
|
||||||
local announceLabel = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
|
||||||
announceLabel:SetPoint("TOPLEFT", f, "TOPLEFT", PADDING, yPos)
|
|
||||||
announceLabel:SetText("Announce:")
|
|
||||||
announceLabel:SetWidth(LABEL_WIDTH)
|
|
||||||
announceLabel:SetJustifyH("LEFT")
|
|
||||||
|
|
||||||
local announceCheck = CreateFrame("CheckButton", "AltSystemAnnounceCheck", f, "UICheckButtonTemplate")
|
-- Announce Roll dropdown (Self Roll + channels)
|
||||||
announceCheck:SetPoint("LEFT", announceLabel, "RIGHT", -6, 0)
|
local announceOptions = { { text = "Self Roll" } }
|
||||||
announceCheck:SetChecked(AltSystem.State.announceEnabled)
|
|
||||||
|
|
||||||
-- Channel dropdown (shown only when announce is enabled)
|
|
||||||
local channelOptions = {}
|
|
||||||
for _, ch in ipairs(AltSystem.AnnounceChannels) do
|
for _, ch in ipairs(AltSystem.AnnounceChannels) do
|
||||||
table.insert(channelOptions, { text = ch.name })
|
table.insert(announceOptions, { text = ch.name })
|
||||||
end
|
end
|
||||||
|
|
||||||
local channelDropdown, getChannelIndex, setChannelIndex = CreateDropdown(
|
local announceContainer, announceDropdown = CreateDropdown(
|
||||||
f, "AltSystemChannelDropdown", yPos, "", channelOptions,
|
content, "AltSystemAnnounceDropdown", "Announce Roll", announceOptions,
|
||||||
AltSystem.State.announceChannelIndex,
|
AltSystem.State.announceOptionIndex,
|
||||||
function(index)
|
function(index)
|
||||||
AltSystem.State.announceChannelIndex = index
|
AltSystem.State.announceOptionIndex = index
|
||||||
end)
|
if index > 1 then
|
||||||
channelDropdown:SetPoint("LEFT", announceCheck, "RIGHT", 2, 0)
|
AltSystem.State.announceEnabled = true
|
||||||
channelDropdown:SetWidth(130)
|
AltSystem.State.announceChannelIndex = index - 1
|
||||||
|
|
||||||
-- Show/hide channel dropdown based on checkbox state
|
|
||||||
local function UpdateChannelDropdownVisibility()
|
|
||||||
if AltSystem.State.announceEnabled then
|
|
||||||
channelDropdown:Show()
|
|
||||||
else
|
else
|
||||||
channelDropdown:Hide()
|
AltSystem.State.announceEnabled = false
|
||||||
end
|
AltSystem.State.announceChannelIndex = 1
|
||||||
end
|
end
|
||||||
|
end,
|
||||||
|
"GameFontNormal")
|
||||||
|
announceContainer:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos)
|
||||||
|
announceContainer:SetWidth(CONTROLS_WIDTH - PADDING * 2)
|
||||||
|
|
||||||
announceCheck:SetScript("OnClick", function(self)
|
yPos = yPos - ROW_HEIGHT - SECTION_GAP
|
||||||
AltSystem.State.announceEnabled = self:GetChecked()
|
|
||||||
UpdateChannelDropdownVisibility()
|
-- Roll button
|
||||||
|
local rollBtn = CreateFrame("Button", "AltSystemRollBtn", content, "UIPanelButtonTemplate")
|
||||||
|
rollBtn:SetSize(CONTROLS_WIDTH - PADDING * 2, 32)
|
||||||
|
rollBtn:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos)
|
||||||
|
local rollLabel = AltSystem.State.rollType == "attack" and "Roll Attack" or "Roll Defense"
|
||||||
|
rollBtn:SetText(rollLabel)
|
||||||
|
|
||||||
|
rollBtn:SetScript("OnClick", function()
|
||||||
|
AltSystem:PerformRoll(AltSystem.State.rollType)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
UpdateChannelDropdownVisibility()
|
AltSystem.RollButton = rollBtn
|
||||||
|
|
||||||
yPos = yPos - ROW_HEIGHT - 12
|
---------------------
|
||||||
|
-- LOG PANEL (right side of Use Skills tab)
|
||||||
|
---------------------
|
||||||
|
local logPanel = CreateFrame("Frame", nil, useSkillsContent)
|
||||||
|
logPanel:SetPoint("TOPLEFT", useSkillsContent, "TOPLEFT", CONTROLS_WIDTH, 0)
|
||||||
|
logPanel:SetSize(LOG_WIDTH, tabContentHeight)
|
||||||
|
|
||||||
-------------------------
|
-- Log header
|
||||||
-- Roll buttons
|
local logHeader = logPanel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||||
-------------------------
|
logHeader:SetPoint("TOPLEFT", logPanel, "TOPLEFT", PADDING, -4)
|
||||||
local btnWidth = (WINDOW_WIDTH - PADDING * 3) / 2
|
logHeader:SetText("Log")
|
||||||
|
logHeader:SetTextColor(0.9, 0.75, 0.2)
|
||||||
|
|
||||||
local attackBtn = CreateFrame("Button", "AltSystemAttackRollBtn", f, "UIPanelButtonTemplate")
|
-- Log scroll area background
|
||||||
attackBtn:SetSize(btnWidth, 28)
|
local logBg = CreateFrame("Frame", nil, logPanel, "InsetFrameTemplate")
|
||||||
attackBtn:SetPoint("TOPLEFT", f, "TOPLEFT", PADDING, yPos)
|
logBg:SetPoint("TOPLEFT", logPanel, "TOPLEFT", 4, -22)
|
||||||
attackBtn:SetText("Attack/Skill Roll")
|
logBg:SetPoint("BOTTOMRIGHT", logPanel, "BOTTOMRIGHT", -4, 4)
|
||||||
|
|
||||||
local defenseBtn = CreateFrame("Button", "AltSystemDefenseRollBtn", f, "UIPanelButtonTemplate")
|
-- Scroll frame for log entries
|
||||||
defenseBtn:SetSize(btnWidth, 28)
|
local scrollFrame = CreateFrame("ScrollFrame", "AltSystemLogScrollFrame", logBg, "UIPanelScrollFrameTemplate")
|
||||||
defenseBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -PADDING, yPos)
|
scrollFrame:SetPoint("TOPLEFT", logBg, "TOPLEFT", 6, -6)
|
||||||
defenseBtn:SetText("Defense Roll")
|
scrollFrame:SetPoint("BOTTOMRIGHT", logBg, "BOTTOMRIGHT", -28, 6)
|
||||||
|
|
||||||
yPos = yPos - 40
|
local scrollChild = CreateFrame("Frame", "AltSystemLogScrollChild", scrollFrame)
|
||||||
|
scrollChild:SetWidth(scrollFrame:GetWidth() or (LOG_WIDTH - 50))
|
||||||
|
scrollChild:SetHeight(1) -- Will be updated dynamically
|
||||||
|
scrollFrame:SetScrollChild(scrollChild)
|
||||||
|
|
||||||
-------------------------
|
AltSystem.LogScrollFrame = scrollFrame
|
||||||
-- Roll result area
|
AltSystem.LogScrollChild = scrollChild
|
||||||
-------------------------
|
|
||||||
local resultBg = CreateFrame("Frame", nil, f, "InsetFrameTemplate")
|
|
||||||
resultBg:SetPoint("TOPLEFT", f, "TOPLEFT", PADDING, yPos)
|
|
||||||
resultBg:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -PADDING, PADDING)
|
|
||||||
|
|
||||||
local resultText = resultBg:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge")
|
|
||||||
resultText:SetPoint("CENTER")
|
|
||||||
resultText:SetText("Roll result will appear here")
|
|
||||||
resultText:SetJustifyH("CENTER")
|
|
||||||
resultText:SetWidth(resultBg:GetWidth() - 10)
|
|
||||||
|
|
||||||
AltSystem.ResultText = resultText
|
|
||||||
|
|
||||||
-- Button click handlers
|
|
||||||
attackBtn:SetScript("OnClick", function()
|
|
||||||
AltSystem:PerformRoll("attack")
|
|
||||||
end)
|
|
||||||
|
|
||||||
defenseBtn:SetScript("OnClick", function()
|
|
||||||
AltSystem:PerformRoll("defense")
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- Refresh skills from TRP3 profile each time the window is shown
|
-- Refresh skills from TRP3 profile each time the window is shown
|
||||||
f:SetScript("OnShow", function()
|
f:SetScript("OnShow", function()
|
||||||
AltSystem:RefreshSkillDropdown()
|
AltSystem:RefreshSkillDropdown()
|
||||||
|
AltSystem:RefreshLogPanel()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
f:Hide()
|
f:Hide()
|
||||||
AltSystem.MainFrame = f
|
AltSystem.MainFrame = f
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Refresh the log panel UI from AltSystem.State.rollLog
|
||||||
|
function AltSystem:RefreshLogPanel()
|
||||||
|
local scrollChild = AltSystem.LogScrollChild
|
||||||
|
if not scrollChild then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Remove existing log entry fontstrings
|
||||||
|
if scrollChild.entries then
|
||||||
|
for _, entry in ipairs(scrollChild.entries) do
|
||||||
|
entry:Hide()
|
||||||
|
entry:SetText("")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
scrollChild.entries = scrollChild.entries or {}
|
||||||
|
|
||||||
|
local rollLog = AltSystem.State.rollLog
|
||||||
|
local entryHeight = 16
|
||||||
|
local spacing = 4
|
||||||
|
local yPos = 0
|
||||||
|
local childWidth = AltSystem.LogScrollFrame:GetWidth() or 250
|
||||||
|
|
||||||
|
-- Entries are newest-first
|
||||||
|
for i = #rollLog, 1, -1 do
|
||||||
|
local idx = #rollLog - i + 1
|
||||||
|
local logEntry = rollLog[i]
|
||||||
|
local fontStr = scrollChild.entries[idx]
|
||||||
|
|
||||||
|
if not fontStr then
|
||||||
|
fontStr = scrollChild:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
|
||||||
|
scrollChild.entries[idx] = fontStr
|
||||||
|
end
|
||||||
|
|
||||||
|
fontStr:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 2, -yPos)
|
||||||
|
fontStr:SetWidth(childWidth - 4)
|
||||||
|
fontStr:SetJustifyH("LEFT")
|
||||||
|
fontStr:SetText(logEntry.text)
|
||||||
|
fontStr:Show()
|
||||||
|
|
||||||
|
yPos = yPos + entryHeight + spacing
|
||||||
|
end
|
||||||
|
|
||||||
|
scrollChild:SetHeight(math.max(yPos, 1))
|
||||||
|
end
|
||||||
|
|
||||||
-- Refresh the skill dropdown with current TRP3 profile data
|
-- Refresh the skill dropdown with current TRP3 profile data
|
||||||
function AltSystem:RefreshSkillDropdown()
|
function AltSystem:RefreshSkillDropdown()
|
||||||
AltSystem.Data:RefreshSkills()
|
AltSystem.Data:RefreshSkills()
|
||||||
|
|
@ -342,7 +577,9 @@ function AltSystem:RefreshSkillDropdown()
|
||||||
for i, option in ipairs(skillOptions) do
|
for i, option in ipairs(skillOptions) do
|
||||||
rootDescription:CreateRadio(
|
rootDescription:CreateRadio(
|
||||||
option.text,
|
option.text,
|
||||||
function(data) return data == AltSystem.State.selectedSkillIndex end,
|
function(data)
|
||||||
|
return data == AltSystem.State.selectedSkillIndex
|
||||||
|
end,
|
||||||
function(data)
|
function(data)
|
||||||
AltSystem.State.selectedSkillIndex = data
|
AltSystem.State.selectedSkillIndex = data
|
||||||
AltSystem.State.selectedSkillName = AltSystem.Data.Skills[data] and AltSystem.Data.Skills[data].name or nil
|
AltSystem.State.selectedSkillName = AltSystem.Data.Skills[data] and AltSystem.Data.Skills[data].name or nil
|
||||||
|
|
|
||||||
78
docs/4-redesign.md
Normal file
78
docs/4-redesign.md
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
# Feature: Major Redesign
|
||||||
|
This redesign of the Addon's window will start by following the following [design](./roll_tab_design.png)
|
||||||
|
|
||||||
|
- The window will be a tabbed window
|
||||||
|
- The 'Use Skills' tab will correspond to the current roll screen
|
||||||
|
- The 'Build Skills' tab will be a new screen, to be implemented later. For now, leave it empty.
|
||||||
|
- The 'Log' should record all rolls made by the user, and be displayed in a scrollable list.
|
||||||
|
- They should be displayed in the same style as the announced rolls, even when the announce option is off
|
||||||
|
- The log should store a maximum of 100 rolls
|
||||||
|
|
||||||
|
## Implementation plan
|
||||||
|
|
||||||
|
### 1. Restructure the main window layout (UI.lua)
|
||||||
|
- Increase `WINDOW_WIDTH` to roughly double (≈660) to accommodate the two-column layout (left panel for tabs, right panel for the log)
|
||||||
|
- Replace `BasicFrameTemplateWithInset` or layer a new structure inside it:
|
||||||
|
- **Left column (~50% width):** contains two tabs ("Use Skills", "Build Skills") and their content panels
|
||||||
|
- **Right column (~50% width):** contains the "Log" header and a scrollable log list
|
||||||
|
- Keep the title bar ("AltSystem") and close button at the top spanning the full width
|
||||||
|
|
||||||
|
### 2. Implement the tab system (UI.lua)
|
||||||
|
- Create two tab buttons ("Use Skills" and "Build Skills") anchored at the top of the left column
|
||||||
|
- Use `PanelTemplates_SetNumTabs` / `PanelTemplates_SetTab` or manual highlight toggling to switch active tab styling
|
||||||
|
- **"Use Skills" tab content:** migrate all existing UI elements (Roll Type radios → Skill dropdown → Armor radios → Modifiers checkboxes → Announce dropdown → Roll button) into this tab's content frame
|
||||||
|
- Adapt the current layout from `CreateMainFrame` — re-parent all widgets to the tab content frame instead of `f` directly
|
||||||
|
- **"Build Skills" tab content:** create an empty placeholder frame (can show a "Coming soon" label)
|
||||||
|
- Toggling tabs shows/hides the corresponding content frame
|
||||||
|
|
||||||
|
### 3. Redesign the "Use Skills" tab to match the mockup (UI.lua)
|
||||||
|
- Replace the current separate Attack/Defense buttons with a **Roll Type** radio-button group ("Attack Roll" / "Defense Roll") that sets `AltSystem.State.rollType`
|
||||||
|
- Keep the **Skill** dropdown as-is (already matches the mockup)
|
||||||
|
- Replace the current Defense dropdown with an **Armor** radio-button group ("No Armor" / "Basic Armor (+1)" / "Heavy Armor (+2)")
|
||||||
|
- Map these to the existing `AltSystem.Data.Defenses` entries; show armor options only when Defense Roll is selected, or always visible per mockup
|
||||||
|
- Group **Shield** and **Pet** checkboxes under a "Modifiers (optional)" section header with a "Label" sub-header matching the mockup
|
||||||
|
- Replace the Announce checkbox + channel dropdown with a single **"Announce Roll"** dropdown whose options are "Self Roll" (no announce) plus the existing channel list (Emote, Party, Raid, Guild)
|
||||||
|
- "Self Roll" maps to `announceEnabled = false`; any other selection maps to `announceEnabled = true` with the corresponding channel index
|
||||||
|
- Add a single **"Roll $rollType"** button at the bottom (text dynamically reflects "Roll Attack" or "Roll Defense")
|
||||||
|
- Remove the old roll-result text area from this tab (results now go to the Log panel)
|
||||||
|
|
||||||
|
### 4. Build the Log panel (UI.lua)
|
||||||
|
- Create a right-side panel with a "Log" header label
|
||||||
|
- Inside, create a `ScrollFrame` (using `UIPanelScrollFrameTemplate` or a manual scroll child) to hold log entries
|
||||||
|
- Each log entry is a small frame/fontstring displaying the roll result in the same format as the announced message:
|
||||||
|
- `"[Name] rolled [d20 result] [modifiers] = [total]"` (reuse `BuildModifierString` from Roll.lua)
|
||||||
|
- Critical rolls show "rolled a Critical Failure!" or "rolled a Critical Success!"
|
||||||
|
- Entries are listed newest-first (most recent at top) in a vertically stacked layout
|
||||||
|
|
||||||
|
### 5. Implement the roll log data store (Core.lua / Roll.lua)
|
||||||
|
- Add `AltSystem.State.rollLog = {}` — an array of log entry tables, each containing: `{ text = "...", timestamp = time() }`
|
||||||
|
- In `Roll.lua`, after every roll result is calculated (in `CalculateAndDisplayResult`), build the log message string (same format as announce) and insert it into `AltSystem.State.rollLog`
|
||||||
|
- Cap the log at **100 entries**: if `#rollLog > 100`, remove the oldest entry (`table.remove(rollLog, 1)`)
|
||||||
|
- After inserting, call a UI refresh function to update the scroll frame content
|
||||||
|
- The log is **always populated**, regardless of the announce setting (per the requirement: "displayed in the same style as the announced rolls, even when the announce option is off")
|
||||||
|
- Log does **not** need to persist across sessions (not mentioned in requirements); keep it in memory only
|
||||||
|
|
||||||
|
### 6. Wire up the new Roll button (Roll.lua)
|
||||||
|
- The single "Roll $rollType" button calls `AltSystem:PerformRoll(state.rollType)` where `state.rollType` is set by the radio-button group ("attack" or "defense")
|
||||||
|
- Existing `PerformRoll` and `CalculateAndDisplayResult` logic remains largely unchanged; only the final display step changes from setting `ResultText` to appending to the log + refreshing the log UI
|
||||||
|
|
||||||
|
### 7. Update state persistence (Core.lua)
|
||||||
|
- Save/restore `rollType` selection (attack/defense) in `AltSystemDB`
|
||||||
|
- Update announce state handling to work with the new single-dropdown approach (save selected option index)
|
||||||
|
- Armor selection (radio group) replaces `selectedDefenseIndex` — reuse same key or migrate
|
||||||
|
|
||||||
|
### 8. Update the .toc file if needed (AltSystem.toc)
|
||||||
|
- No new Lua files are expected (all changes fit in existing files), but verify the load order is still correct
|
||||||
|
|
||||||
|
### 9. Testing checklist
|
||||||
|
- [ ] Window opens at new size, tabs switch correctly
|
||||||
|
- [ ] "Use Skills" tab shows all controls matching the mockup layout
|
||||||
|
- [ ] "Build Skills" tab is empty / shows placeholder
|
||||||
|
- [ ] Attack and Defense rolls work correctly via the new single Roll button
|
||||||
|
- [ ] Log panel populates with each roll, formatted like announce messages
|
||||||
|
- [ ] Log scrolls when entries exceed visible area
|
||||||
|
- [ ] Log caps at 100 entries, oldest removed first
|
||||||
|
- [ ] Log populates even when announce is set to "Self Roll" (off)
|
||||||
|
- [ ] Announce still works when a channel is selected
|
||||||
|
- [ ] State (roll type, armor, announce option) persists across sessions
|
||||||
|
- [ ] Window is draggable and clamps to screen
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
- Adding default 'Unskilled (-4)' option
|
|
||||||
- The roll window now remembers all user selections (skill, item, armor type, shield, pet, announce and channel) across sessions
|
|
||||||
- Fixed an issue where the wrong rolls were displayed when in a large enough group
|
|
||||||
BIN
docs/roll_tab_design.png
Normal file
BIN
docs/roll_tab_design.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
|
|
@ -7,7 +7,7 @@ OUTPUT_FILE="$OUTPUT_DIR/$ADDON_NAME.zip"
|
||||||
STAGING_DIR="$(mktemp -d)"
|
STAGING_DIR="$(mktemp -d)"
|
||||||
|
|
||||||
mkdir -p "$STAGING_DIR/$ADDON_NAME"
|
mkdir -p "$STAGING_DIR/$ADDON_NAME"
|
||||||
rsync -a --exclude='.DS_Store' --exclude='__MACOSX' --exclude='.git' "$SCRIPT_DIR/" "$STAGING_DIR/$ADDON_NAME/"
|
rsync -a --exclude='.DS_Store' --exclude='__MACOSX' --exclude='.git' --exclude='docs' "$SCRIPT_DIR/" "$STAGING_DIR/$ADDON_NAME/"
|
||||||
|
|
||||||
cd "$STAGING_DIR" && \
|
cd "$STAGING_DIR" && \
|
||||||
zip -r "$OUTPUT_FILE" "$ADDON_NAME"
|
zip -r "$OUTPUT_FILE" "$ADDON_NAME"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue