mirror of
https://github.com/mehah/otclient
synced 2026-08-15 16:29:06 -04:00
feat(cyclopedia): implement the Magical Archive tab (#1798)
New Features: - Added a Magical Archives spell browser with search and vocation filtering. - Added alphabetical spell sorting and support for promoted vocations. - Added detailed spell information, including icon, name, formula, vocation, group, and cooldown. - Added localized labels and improved spell list and detail presentation.
This commit is contained in:
parent
478522f36c
commit
1f5df26144
2 changed files with 282 additions and 9 deletions
|
|
@ -1,5 +1,145 @@
|
|||
local UI = nil
|
||||
|
||||
local ARCHIVE_PROFILE = 'Default'
|
||||
local ARCHIVE_FILTER_VOCATION_ANY = 0
|
||||
|
||||
local archiveState = {
|
||||
vocationId = ARCHIVE_FILTER_VOCATION_ANY,
|
||||
search = ''
|
||||
}
|
||||
|
||||
MagicalArchive = MagicalArchive or {}
|
||||
|
||||
local function archiveContains(list, value)
|
||||
if not list then
|
||||
return false
|
||||
end
|
||||
for i = 1, #list do
|
||||
if list[i] == value then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- promoted counterpart of a base vocation (server ids): 1-4 -> 5-8, Monk 9 -> Exalted Monk 10
|
||||
local function archivePromotedId(vocationId)
|
||||
if vocationId >= 1 and vocationId <= 4 then
|
||||
return vocationId + 4
|
||||
end
|
||||
if vocationId == 9 then
|
||||
return 10
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function MagicalArchive.buildSpellNames(spellInfo)
|
||||
local names = {}
|
||||
for name in pairs(spellInfo) do
|
||||
table.insert(names, name)
|
||||
end
|
||||
table.sort(names)
|
||||
return names
|
||||
end
|
||||
|
||||
function MagicalArchive.matchesVocation(info, vocationId)
|
||||
if vocationId == ARCHIVE_FILTER_VOCATION_ANY then
|
||||
return true
|
||||
end
|
||||
return archiveContains(info.vocations, vocationId) or
|
||||
archiveContains(info.vocations, archivePromotedId(vocationId))
|
||||
end
|
||||
|
||||
function MagicalArchive.matchesSearch(name, info, searchText)
|
||||
if not searchText or searchText == '' then
|
||||
return true
|
||||
end
|
||||
local needle = searchText:lower()
|
||||
if name:lower():find(needle, 1, true) then
|
||||
return true
|
||||
end
|
||||
return (info.words or ''):lower():find(needle, 1, true) ~= nil
|
||||
end
|
||||
|
||||
-- promoted vocations are hidden when their base vocation is also present ("Sorcerer" implies "Master Sorcerer")
|
||||
function MagicalArchive.formatVocations(vocations, vocationNames)
|
||||
vocationNames = vocationNames or VocationNames
|
||||
local baseOf = { [5] = 1, [6] = 2, [7] = 3, [8] = 4, [10] = 9 }
|
||||
local result = ''
|
||||
for i = 1, #vocations do
|
||||
local vocationId = vocations[i]
|
||||
local base = baseOf[vocationId]
|
||||
if not base or not archiveContains(vocations, base) then
|
||||
result = result .. (result:len() == 0 and '' or ', ') .. (vocationNames[vocationId] or '?')
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function MagicalArchive.formatGroupAndCooldown(info, spellGroups)
|
||||
spellGroups = spellGroups or SpellGroups
|
||||
local cooldown = (info.exhaustion / 1000) .. 's'
|
||||
local group = ''
|
||||
for groupId, groupName in ipairs(spellGroups) do
|
||||
if info.group[groupId] then
|
||||
group = group .. (group:len() == 0 and '' or ' / ') .. groupName
|
||||
cooldown = cooldown .. ' / ' .. (info.group[groupId] / 1000) .. 's'
|
||||
end
|
||||
end
|
||||
return group, cooldown
|
||||
end
|
||||
|
||||
local function archiveSetDetailRow(rowId, value)
|
||||
local row = UI.spellAndRune.DetailPanel.DetailRows[rowId]
|
||||
row.value:setText(value)
|
||||
end
|
||||
|
||||
local function archiveSelectSpell(widget)
|
||||
local name = widget.spellName or widget:getId()
|
||||
local info = SpellInfo[ARCHIVE_PROFILE][name]
|
||||
if not info then
|
||||
return
|
||||
end
|
||||
|
||||
local detail = UI.spellAndRune.DetailPanel
|
||||
UI.spellAndRune.PlaceholderLabel:setVisible(false)
|
||||
detail:setVisible(true)
|
||||
|
||||
local iconId = tonumber(info.clientId)
|
||||
if iconId then
|
||||
detail.SpellIcon:setImageSource(SpelllistSettings[ARCHIVE_PROFILE].iconFile)
|
||||
detail.SpellIcon:setImageClip(Spells.getImageClip(iconId, ARCHIVE_PROFILE))
|
||||
end
|
||||
|
||||
detail.NameLabel:setText(name)
|
||||
detail.FormulaLabel:setText('\'' .. info.words .. '\'')
|
||||
|
||||
local group, cooldown = MagicalArchive.formatGroupAndCooldown(info)
|
||||
archiveSetDetailRow('RowVocation', MagicalArchive.formatVocations(info.vocations))
|
||||
archiveSetDetailRow('RowGroup', group)
|
||||
archiveSetDetailRow('RowType', info.type)
|
||||
archiveSetDetailRow('RowCooldown', cooldown)
|
||||
archiveSetDetailRow('RowLevel', tostring(info.level))
|
||||
archiveSetDetailRow('RowMana', info.mana .. ' / ' .. info.soul)
|
||||
archiveSetDetailRow('RowPremium', info.premium and 'yes' or 'no')
|
||||
end
|
||||
|
||||
local function archiveRebuildList()
|
||||
local list = UI.InformationBase.selectSpell.SpellListBase.questList
|
||||
list:destroyChildren()
|
||||
for _, name in ipairs(MagicalArchive.buildSpellNames(SpellInfo[ARCHIVE_PROFILE])) do
|
||||
local info = SpellInfo[ARCHIVE_PROFILE][name]
|
||||
if MagicalArchive.matchesVocation(info, archiveState.vocationId) and
|
||||
MagicalArchive.matchesSearch(name, info, archiveState.search) then
|
||||
local row = g_ui.createWidget('ArchiveSpellLabel', list)
|
||||
row:setId(name)
|
||||
row.spellName = name
|
||||
row:setText(name)
|
||||
row.onClick = archiveSelectSpell
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function showMagicalArchives()
|
||||
UI = g_ui.loadUI("magicalArchives", contentContainer)
|
||||
UI:show()
|
||||
|
|
@ -9,5 +149,50 @@ function showMagicalArchives()
|
|||
if g_game.getClientVersion() >= 1410 then
|
||||
controllerCyclopedia.ui.CharmsBase1410:setVisible(false)
|
||||
end
|
||||
end
|
||||
|
||||
archiveState.vocationId = ARCHIVE_FILTER_VOCATION_ANY
|
||||
archiveState.search = ''
|
||||
|
||||
local vocationFilter = UI.InformationBase.selectSpell.VocationFilter
|
||||
vocationFilter:addOption(tr('All Vocations'), ARCHIVE_FILTER_VOCATION_ANY)
|
||||
vocationFilter:addOption(tr('Sorcerer'), VocationsServer.Sorcerer)
|
||||
vocationFilter:addOption(tr('Druid'), VocationsServer.Druid)
|
||||
vocationFilter:addOption(tr('Paladin'), VocationsServer.Paladin)
|
||||
vocationFilter:addOption(tr('Knight'), VocationsServer.Knight)
|
||||
vocationFilter:addOption(tr('Monk'), VocationsServer.Monk)
|
||||
vocationFilter.onOptionChange = function(widget, text, data)
|
||||
archiveState.vocationId = data
|
||||
archiveRebuildList()
|
||||
end
|
||||
|
||||
local searchEdit = UI.InformationBase.selectSpell.SearchBase.SearchEdit
|
||||
searchEdit.onTextChange = function(widget, text)
|
||||
archiveState.search = text
|
||||
archiveRebuildList()
|
||||
end
|
||||
|
||||
local list = UI.InformationBase.selectSpell.SpellListBase.questList
|
||||
connect(list, {
|
||||
onChildFocusChange = function(self, focusedChild)
|
||||
if focusedChild then
|
||||
archiveSelectSpell(focusedChild)
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
local detailRows = UI.spellAndRune.DetailPanel.DetailRows
|
||||
local captions = {
|
||||
RowVocation = tr('Vocation') .. ':',
|
||||
RowGroup = tr('Group') .. ':',
|
||||
RowType = tr('Type') .. ':',
|
||||
RowCooldown = tr('Cooldown') .. ':',
|
||||
RowLevel = tr('Level') .. ':',
|
||||
RowMana = tr('Mana / Soul') .. ':',
|
||||
RowPremium = tr('Premium') .. ':'
|
||||
}
|
||||
for rowId, caption in pairs(captions) do
|
||||
detailRows[rowId].caption:setText(caption)
|
||||
end
|
||||
|
||||
archiveRebuildList()
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,3 +1,39 @@
|
|||
ArchiveSpellLabel < UIWidget
|
||||
font: verdana-11px-antialised
|
||||
height: 20
|
||||
text-align: left
|
||||
text-offset: 5 3
|
||||
color: #C0C0C0
|
||||
background-color: alpha
|
||||
focusable: true
|
||||
phantom: false
|
||||
|
||||
$focus:
|
||||
background-color: #ffffff22
|
||||
color: #F6F6F6
|
||||
|
||||
$hover !focus:
|
||||
background-color: #ffffff11
|
||||
|
||||
ArchiveDetailRow < Panel
|
||||
height: 18
|
||||
|
||||
Label
|
||||
id: caption
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
width: 110
|
||||
font: verdana-11px-antialised
|
||||
color: #909090
|
||||
|
||||
Label
|
||||
id: value
|
||||
anchors.left: caption.right
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
font: verdana-11px-antialised
|
||||
color: #C0C0C0
|
||||
|
||||
UIWidget
|
||||
id: Cat5
|
||||
anchors.fill: parent
|
||||
|
|
@ -22,9 +58,12 @@ UIWidget
|
|||
spacing: 6
|
||||
|
||||
ComboBox
|
||||
id: VocationFilter
|
||||
TextListQuestLog
|
||||
id: SpellListBase
|
||||
height: 330
|
||||
TextEditQuestLog
|
||||
id: SearchBase
|
||||
|
||||
MiniPanel
|
||||
id: spellAndRune
|
||||
|
|
@ -37,15 +76,64 @@ UIWidget
|
|||
margin-bottom: 25
|
||||
|
||||
Label
|
||||
id: PlaceholderLabel
|
||||
anchors.centerIn: parent
|
||||
text: select a spell to view detailed information.
|
||||
Label
|
||||
id: deleteme
|
||||
anchors.centerIn: parent
|
||||
!text: tr("\nis this in the protobuf ?\n in http post ? \nor modify gamelib/spells.lua?")
|
||||
font-scale: 2
|
||||
margin-top: 32
|
||||
text-auto-resize: true
|
||||
text: select a spell to view detailed information.
|
||||
|
||||
Panel
|
||||
id: DetailPanel
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
padding: 12
|
||||
|
||||
UIWidget
|
||||
id: SpellIcon
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
size: 32 32
|
||||
|
||||
Label
|
||||
id: NameLabel
|
||||
anchors.top: parent.top
|
||||
anchors.left: SpellIcon.right
|
||||
anchors.right: parent.right
|
||||
margin-left: 10
|
||||
margin-top: 1
|
||||
font: Verdana Bold-11px
|
||||
color: #F6F6F6
|
||||
|
||||
Label
|
||||
id: FormulaLabel
|
||||
anchors.top: NameLabel.bottom
|
||||
anchors.left: SpellIcon.right
|
||||
anchors.right: parent.right
|
||||
margin-left: 10
|
||||
margin-top: 3
|
||||
font: verdana-11px-antialised
|
||||
color: #C0C0C0
|
||||
|
||||
Panel
|
||||
id: DetailRows
|
||||
anchors.top: SpellIcon.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
margin-top: 16
|
||||
layout:
|
||||
type: verticalBox
|
||||
spacing: 4
|
||||
|
||||
ArchiveDetailRow
|
||||
id: RowVocation
|
||||
ArchiveDetailRow
|
||||
id: RowGroup
|
||||
ArchiveDetailRow
|
||||
id: RowType
|
||||
ArchiveDetailRow
|
||||
id: RowCooldown
|
||||
ArchiveDetailRow
|
||||
id: RowLevel
|
||||
ArchiveDetailRow
|
||||
id: RowMana
|
||||
ArchiveDetailRow
|
||||
id: RowPremium
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue