otclient-redemption/modules/game_interface/widgets/statsbar.lua
Rodrigo Paixão 07369287f7
Cleanup: Prevent errors and warnings in Lua code (#1179)
* fix: mainpanel

* Minor fixes

* Removed dead code (Outfit)
* Fixed type error SpellGroups vs game_cooldown
* Fixed Keybind
* And more

* more fixes

* ...

* Changes in shop (store)

* Added function addOutfit;
* Fixed entries per page (history);
* Fixed Extras Category
* Added function Game.getMountIdByClientId
2025-06-02 13:15:29 -03:00

700 lines
24 KiB
Lua

local statsBarTop
local statsBarBottom
local statsBars = {}
local statsBarDeepInfo = {}
-- If you want to add more placements/dimensions, you'll need to add them here.
-- This is used in getStatsBarMenuOptions(), createStatsBarWidgets(),
-- hideAll() and destroyAllIcons() functions.
local statsBarsPlacements = {
"Top",
"Bottom"
}
-- This is used in constructStatsBar(), getStatsBarMenuOptions(),
-- reloadCurrentTab(), createStatsBarWidgets(),
-- hideAll() and destroyALlIcons functions.
local statsBarsDimensions = {
Large = {
height = 35
},
Default = {
height = 35
},
Parallel = {
height = 35
},
Compact = {
height = 20
}
}
local firstCall = true
local currentStats = {
dimension = "hide",
placement = "hide"
}
local skillsLineHeight = 20
local skillsTuples = {
{skill = nil, key = 'experience', icon = '/images/icons/icon_experience', placement = 'center', order = 0, name = "Level"},
{skill = nil, key = 'magic', icon = '/images/icons/icon_magic', placement = 'left', order = 1, name = "Magic Level"},
{skill = Skill.Axe, key = 'axe', icon = '/images/icons/icon_axe', placement = 'right', order = 1, name = "Axe Fighting Skill"},
{skill = Skill.Club, key = 'club', icon = '/images/icons/icon_club', placement = 'left', order = 2, name = "Club Fighting Skill"},
{skill = Skill.Distance, key = 'distance', icon = '/images/icons/icon_distance', placement = 'right', order = 2, name = "Distance Fighting Skill"},
{skill = Skill.Fist, key = 'fist', icon = '/images/icons/icon_fist', placement = 'left', order = 3, name = "Fist Fighting Skill"},
{skill = Skill.Shielding, key = 'shielding', icon = '/images/icons/icon_shielding', placement = 'right', order = 3, name = "Shielding Fighting Skill"},
{skill = Skill.Sword, key = 'sword', icon = '/images/icons/icon_sword', placement = 'left', order = 4, name = "Sword Fighting Skill"},
{skill = Skill.Fishing, key = 'fishing', icon = '/images/icons/icon_fishing', placement = 'right', order = 4, name = "Fishing Fighting Skill"},
}
StatsBar = {}
function getConfigurations()
-- This method will return all the stats bar configurations.
local configs = {}
for _, statsBar in pairs(statsBars) do
for _, placement in ipairs(statsBarsPlacements) do
for dimension, _ in pairs(statsBarsDimensions) do
local dimensionOnPlacement = tostring(dimension):lower() .. "On" .. placement
local key = "statsBar" .. placement:gsub("^%l", string.upper)
if statsBar[key] then
table.insert(configs, statsBar[key][dimensionOnPlacement])
end
end
end
end
return configs
end
local function reloadSkillsTab(skills, parent)
-- This method might need some refactoring if you want to add side stats bars.
local player = g_game.getLocalPlayer()
if not player then
return
end
local tuples = {}
for i = 1, #skillsTuples do
local skillTuple = skillsTuples[i]
if skillTuple and g_settings.getBoolean('top_statsbar_' .. skillTuple.key) then
table.insert(tuples, skillTuple)
end
end
local statsBar = StatsBar.getCurrentStatsBar()
if not statsBar then
return
end
statsBar:setHeight(statsBar:getHeight() - skills:getHeight())
parent:setHeight(parent:getHeight() - (40 + skills:getHeight()))
skills:setHeight(0)
skills:destroyChildren()
local lines = 0
local lastPlacement = 'left'
for i = 1, #tuples do
local skillTuple = tuples[i]
local widget = g_ui.createWidget('TopStatsSkillElement', skills)
widget:setId('statsbar_skill_' .. skillTuple.key)
widget:addAnchor(AnchorTop, 'parent', AnchorTop)
if lastPlacement == 'left' then
widget:setMarginTop(lines * skillsLineHeight)
else
widget:setMarginTop((lines - 1) * skillsLineHeight)
end
widget.level = widget:getChildById('level')
widget.icon = widget:getChildById('icon')
widget.bar = widget:getChildById('bar')
widget.icon:setImageSource(skillTuple.icon)
widget.icon:setTooltip(skillTuple.name)
widget.bar.statsGrade = 4
widget.bar.statsGradeColor = '#070707ff'
widget.bar:reloadBorder()
widget.bar.showText = false
if skillTuple.key == 'experience' then
widget.bar.statsType = 'experience'
else
widget.bar.statsType = 'skill'
end
if skillTuple.placement == 'center' or (i == #tuples and lastPlacement == 'left') then
widget:addAnchor(AnchorLeft, 'parent', AnchorLeft)
widget:addAnchor(AnchorRight, 'parent', AnchorRight)
lines = lines + 1
elseif lastPlacement == 'left' then
widget:addAnchor(AnchorLeft, 'parent', AnchorLeft)
widget:addAnchor(AnchorRight, 'parent', AnchorHorizontalCenter)
lines = lines + 1
lastPlacement = 'right'
elseif lastPlacement == 'right' then
widget:addAnchor(AnchorRight, 'parent', AnchorRight)
widget:addAnchor(AnchorLeft, 'parent', AnchorHorizontalCenter)
lastPlacement = 'left'
end
if skillTuple.key == 'experience' then
widget.level:setText(player:getLevel())
widget.bar:setValue(player:getLevelPercent(), 100)
elseif skillTuple.key == 'magic' then
widget.level:setText(player:getMagicLevel())
widget.bar:setValue(player:getMagicLevelPercent(), 100)
else
widget.level:setText(player:getSkillLevel(skillTuple.skill))
widget.bar:setValue(player:getSkillLevelPercent(skillTuple.skill), 100)
end
end
skills:setHeight((lines * skillsLineHeight) + 5)
parent:setHeight(40 + skills:getHeight())
statsBar:setHeight(statsBar:getHeight() + skills:getHeight())
end
function StatsBar.getAllStatsBarWithPosition()
-- This method will return all the stats bars based on their placement and dimension.
-- i.e statsBarTop.largeOnTop, statsBarTop.parallelOnTop, statsBarTop.defaultOnTop, statsBarTop.compactOnTop [..]
local statsBarsWithPosition = {}
for _, statsBar in pairs(statsBars) do
for _, placement in ipairs(statsBarsPlacements) do
for dimension, _ in pairs(statsBarsDimensions) do
local dimensionOnPlacement = tostring(dimension):lower() .. "On" .. placement
if statsBar[dimensionOnPlacement] then
statsBarsWithPosition[#statsBarsWithPosition+1] = statsBar[dimensionOnPlacement]
end
end
end
end
return statsBarsWithPosition
end
function StatsBar.getCurrentStatsBarWithPosition()
-- This method will return the statsbar based on its placement and dimension.
-- i.e "largeOnTop" will return statsBarTop.largeOnTop.
-- It's made this way so we can call the current stats bar without having to use a switch statement.
-- And if it's necessary to add more stats bars like "largeOnLeft" it will be easier to add them
-- Without changing this code.
if currentStats.dimension == 'hide' or currentStats.placement == 'hide' then
return nil
end
-- -- Get full position as a single string.
-- -- i.e. largeOnTop // parallelOnBottom
local placement = currentStats.placement:gsub("^%l", string.upper)
local fullPosition = currentStats.dimension .. "On" .. placement
local statsBar = StatsBar.getCurrentStatsBar()
if not statsBar then
return nil
end
if statsBar[fullPosition] then
-- Return the stats bar based on the full position.
-- i.e. statsBarTop.largeOnTop
return statsBar[fullPosition]
else
print("No stats bar with position found for:", statsBar)
end
return nil
end
function StatsBar.getCurrentStatsBar()
-- This method will return the statsbar based on its placement.
-- i.e statsBarTop // statsBarBottom
if currentStats.dimension == 'hide' or currentStats.placement == 'hide' then
return nil
end
-- -- Get full placement.
-- -- i.e. Top // Bottom
local placement = currentStats.placement:gsub("^%l", string.upper)
-- -- Get the stats bar based on the placement.
-- -- i.e. statsBarTop // statsBarBottom
local statsBar = "statsBar" .. placement
if statsBars[statsBar] then
return statsBars[statsBar]
else
print("No stats bar found for:", statsBar)
end
return nil
end
function StatsBar.reloadCurrentStatsBarQuickInfo()
local player = g_game.getLocalPlayer()
if not player then
return
end
local bar = StatsBar.getCurrentStatsBarWithPosition()
if not bar then
return
end
bar.health:setValue(player:getHealth(), player:getMaxHealth())
bar.mana:setValue(player:getMana(), player:getMaxMana())
end
local function loadIcon(bitChanged, content, topmenu)
local icon = g_ui.createWidget('ConditionWidget', content)
icon:setId(Icons[bitChanged].id)
icon:setImageSource("/images/game/states/player-state-flags")
icon:setImageClip(((Icons[bitChanged].clip - 1) * 9) .. ' 0 9 9')
local tooltip = Icons[bitChanged].tooltip
if tooltip == "You are GoshnarTaint" then
tooltip = "Goshnar's Lairs Penalties:\n" ..
"- 10% chance of creature teleportation to you\n" ..
"- 0.5% chance of new creature spawn when hitting another\n" ..
"- 15% increased damage received\n" ..
"- 10% chance of creature full heal instead of dying\n" ..
"- Lose 10% of current HP and mana every 10 seconds"
end
icon:setTooltip(tooltip)
icon:setImageSize(tosize("9 9"))
icon:setMarginRight(-1)
if topmenu then
icon:setMarginTop(5)
icon:setMarginLeft(2)
icon:setMarginRight(-2)
end
return icon
end
local function getStatsBarsIconContent()
local iconContents = {}
local statsBars = StatsBar.getAllStatsBarWithPosition()
for _, statsBar in ipairs(statsBars) do
iconContents[#iconContents+1] = { content = statsBar.icons, loadIconTransparent = true }
end
iconContents[#iconContents+1] = { content = modules.game_inventory.getIconsPanelOn(), loadIconTransparent = false }
iconContents[#iconContents+1] = { content = modules.game_inventory.getIconsPanelOff(), loadIconTransparent = false }
return iconContents
end
local function toggleIcon(bitChanged)
local contents = getStatsBarsIconContent()
local iconId = Icons[bitChanged]
if not iconId then
g_logger.warning(string.format("No icon ID %s (%s) found. Check Icons array in modules/gamelib/player.lua.", tostring(bitChanged), tostring(math.log(bitChanged) / math.log(2))))
return
end
for _, contentData in ipairs(contents) do
local icon = contentData.content:getChildById(iconId.id)
if icon then
icon:destroy()
else
icon = loadIcon(bitChanged, contentData.content, contentData.loadIconTransparent)
icon:setParent(contentData.content)
end
end
end
function processIcon(id, action, createIfMissing)
-- game_rewardwall
for _, contentData in ipairs(getStatsBarsIconContent()) do
local icon = contentData.content:getChildById(id)
if icon then
action(icon)
elseif createIfMissing then
icon = loadIcon(id, contentData.content, contentData.loadIconTransparent)
icon:setParent(contentData.content)
action(icon)
end
end
end
function StatsBar.reloadCurrentStatsBarQuickInfo_state(localPlayer, now, old)
local player = g_game.getLocalPlayer()
if not player then
return
end
if now == old then
return
end
local bitsChanged = bit.bxor(now, old)
for i = 1, 32 do
local pow = math.pow(2, i - 1)
if pow > bitsChanged then
break
end
local bitChanged = bit.band(bitsChanged, pow)
if bitChanged ~= 0 then
toggleIcon(bitChanged)
end
end
end
function StatsBar.reloadCurrentStatsBarDeepInfo()
local player = g_game.getLocalPlayer()
if not player then
return
end
local bar = StatsBar.getCurrentStatsBarWithPosition()
if not bar then
return
end
for _, skillTuple in ipairs(skillsTuples) do
local widget = bar:recursiveGetChildById('statsbar_skill_' .. skillTuple.key)
if widget then
if skillTuple.key == 'experience' then
widget.level:setText(player:getLevel())
widget.bar:setValue(player:getLevelPercent(), 100)
elseif skillTuple.key == 'magic' then
widget.level:setText(player:getMagicLevel())
widget.bar:setValue(player:getMagicLevelPercent(), 100)
else
widget.level:setText(player:getSkillLevel(skillTuple.skill))
widget.bar:setValue(player:getSkillLevelPercent(skillTuple.skill), 100)
end
end
end
end
function constructStatsBar(dimension, placement)
local dimensionString = dimension:gsub("^%u", string.lower)
StatsBar.updateCurrentStats(dimensionString, placement)
local dimensionOnPlacement = dimensionString:gsub("^%u", string.lower) .. "On" .. placement:gsub("^%l", string.upper)
local statsBar = statsBars["statsBar" .. placement:gsub("^%l", string.upper)]
if statsBar[dimensionOnPlacement] then
statsBar:setHeight(statsBarsDimensions[dimension].height)
statsBar[dimensionOnPlacement]:setHeight(statsBarsDimensions[dimension].height)
statsBar[dimensionOnPlacement]:show()
statsBar[dimensionOnPlacement]:setPhantom(false)
statsBar[dimensionOnPlacement].health = statsBar[dimensionOnPlacement]:getChildById('health')
statsBar[dimensionOnPlacement].mana = statsBar[dimensionOnPlacement]:getChildById('mana')
statsBar[dimensionOnPlacement].skills = statsBar[dimensionOnPlacement]:getChildById('skills')
reloadSkillsTab(statsBar[dimensionOnPlacement].skills, statsBar[dimensionOnPlacement])
StatsBar.reloadCurrentStatsBarQuickInfo()
modules.game_healthcircle.setStatsBarOption()
else
print("No stats bar found for:", dimensionOnPlacement .. " on constructStatsBar()")
end
end
function StatsBar.updateCurrentStats(dimension, placement)
currentStats = {
dimension = dimension,
placement = placement
}
end
local function openDropMenu(mousePos)
local menu = g_ui.createWidget('PopupMenu')
menu:setGameMenu(true)
local current = StatsBar.getCurrentStatsBarWithPosition()
local menuOptions = getStatsBarMenuOptions(current)
-- Add options to the menu based on the current stats bar
for _, option in ipairs(menuOptions) do
menu:addOption(tr(option.label), function()
StatsBar.hideAll()
constructStatsBar(option.dimension, option.placement)
end)
end
menu:addSeparator()
local current = StatsBar.getCurrentStatsBarWithPosition()
if current and current.skills then
for _, skillTuple in ipairs(skillsTuples) do
if not g_settings.getBoolean('top_statsbar_' .. skillTuple.key) then
menu:addOption(tr('Show') .. ' ' .. tr(skillTuple.name), function()
g_settings.set('top_statsbar_' .. skillTuple.key, true)
reloadSkillsTab(current.skills, current)
end)
else
menu:addOption(tr('Hide') .. ' ' .. tr(skillTuple.name), function()
g_settings.set('top_statsbar_' .. skillTuple.key, false)
reloadSkillsTab(current.skills, current)
end)
end
end
end
menu:addSeparator()
menu:addOption(tr('Hide Customisable Status Bars'), function()
StatsBar.hideAll()
modules.game_healthcircle.setStatsBarOption("hide")
end)
menu:display(mousePos)
end
function shouldAddStatsBarOption(current, placement, style)
local id = current:getId()
return string.find(id, placement) and id ~= style
end
function getStatsBarMenuOptions(current)
local optionsMenu = {}
-- Create options available based on statsBarsPlacements and statsBarsDimensions tables.
for _, placement in ipairs(statsBarsPlacements) do
for dimension, _ in pairs(statsBarsDimensions) do
local style = tostring(dimension):gsub("^%u", string.lower) .. 'On' .. placement
if shouldAddStatsBarOption(current, placement, style) then
optionsMenu[#optionsMenu+1] = {
label = 'Switch to ' .. tostring(dimension) .. ' Style',
dimension = dimension,
placement = placement:gsub("^%u", string.lower),
style = style
}
end
end
end
-- Create options available to switch placements keeping the same style
-- i.e. from bottom to top // from top to bottom
for _, placement in ipairs(statsBarsPlacements) do
if not string.find(current:getId(), placement) then
optionsMenu[#optionsMenu+1] = {
label = 'Switch to ' .. placement .. ' Style',
dimension = currentStats.dimension:gsub("^%l", string.upper),
placement = placement:gsub("^%u", string.lower),
construct = constructStatsBar,
style = current:getId()
}
end
end
return optionsMenu
end
local function onStatsMousePress(tab, mousePos, mouseButton)
if mouseButton == MouseRightButton then
openDropMenu(mousePos)
return true
end
end
function StatsBar.reloadCurrentTab()
if currentStats.dimension == "hide" then
return
end
local dimension = currentStats.dimension:gsub("^%l", string.upper)
if statsBarsDimensions[dimension] then
return constructStatsBar(dimension, currentStats.placement)
else
print("No stats bars dimensions found: ", dimension, " on reloadCurrentTab()")
return
end
end
function StatsBar.updateStatsBarOption(dimension)
StatsBar.hideAll()
StatsBar.firstLoadSettings()
if currentStats.dimension ~= "hide" and dimension ~= "hide" then
StatsBar.reloadCurrentTab()
end
end
local function getSettingOrDefault(setting, default)
local value = g_settings.getString(setting)
return value ~= "" and value or default
end
local function setSetting(setting, value)
g_settings.set(setting, value)
end
function StatsBar.loadSettings()
currentStats = {
dimension = getSettingOrDefault('statsbar_dimension', "compact"),
placement = getSettingOrDefault('statsbar_placement', "top")
}
end
function StatsBar.saveSettings()
setSetting('statsbar_dimension', currentStats.dimension)
setSetting('statsbar_placement', currentStats.placement)
end
function StatsBar.firstLoadSettings()
if firstCall then
currentStats.dimension = getSettingOrDefault("statsbar_dimension", "compact")
currentStats.placement = getSettingOrDefault("statsbar_placement", "top")
firstCall = false
end
StatsBar.saveSettings()
StatsBar.loadSettings()
end
function StatsBar.OnGameEnd()
StatsBar.saveSettings()
StatsBar.hideAll()
modules.game_inventory.getIconsPanelOn():destroyChildren()
modules.game_inventory.getIconsPanelOff():destroyChildren()
StatsBar.destroyAllIcons()
end
function StatsBar.OnGameStart()
StatsBar.loadSettings()
StatsBar.reloadCurrentTab()
modules.game_healthcircle.setStatsBarOption()
end
function createStatsBarWidgets(statsBar)
-- This method will create the widgets based on the statsBar, statsBarsPlacements and statsBarsDimensions tables.
local widget = statsBar
for _, placement in ipairs(statsBarsPlacements) do
for dimension, _ in pairs(statsBarsDimensions) do
local elementName = tostring(dimension):gsub("^%u", string.lower) .. "On" .. placement
widget[elementName] = statsBar:getChildById(elementName)
end
end
widget.onMousePress = onStatsMousePress
return widget
end
function StatsBar.init()
statsBarTop = modules.game_interface.getGameTopStatsBar()
statsBarBottom = modules.game_interface.getGameBottomStatsBar()
statsBars = {
statsBarTop = statsBarTop,
statsBarBottom = statsBarBottom
}
if not statsBarTop then
return
end
if not statsBarBottom then
return
end
-- Create widgets based on statsBars table.
for _, statBar in pairs(statsBars) do
statBar = createStatsBarWidgets(statBar)
end
statsBarDeepInfo = {
onExperienceChange = StatsBar.reloadCurrentStatsBarDeepInfo,
onLevelChange = StatsBar.reloadCurrentStatsBarDeepInfo,
onHealthChange = StatsBar.reloadCurrentStatsBarQuickInfo,
onManaChange = StatsBar.reloadCurrentStatsBarQuickInfo,
onMagicLevelChange = StatsBar.reloadCurrentStatsBarDeepInfo,
onBaseMagicLevelChange = StatsBar.reloadCurrentStatsBarDeepInfo,
onSkillChange = StatsBar.reloadCurrentStatsBarDeepInfo,
onBaseSkillChange = StatsBar.reloadCurrentStatsBarDeepInfo,
onStatesChange = StatsBar.reloadCurrentStatsBarQuickInfo_state
}
StatsBar.hideAll()
connect(LocalPlayer, statsBarDeepInfo)
connect(g_game, {
onGameStart = StatsBar.OnGameStart,
onGameEnd = StatsBar.OnGameEnd
})
end
function StatsBar.hideAll()
-- This iterates between the tables: statsBar -> statsBarsPlacements -> statsBarsDimensions
-- And hides all the stats bars based on these tables.
for _, bar in pairs(statsBars) do
for _, placement in pairs(statsBarsPlacements) do
for dimension, _ in pairs(statsBarsDimensions) do
local key = tostring(dimension):lower() .. "On" .. placement
if bar[key] and bar[key].skills then
bar[key].skills:destroyChildren()
bar[key].skills:setHeight(0)
bar[key]:setHeight(0)
bar[key]:hide()
end
end
end
bar:setHeight(0)
end
end
function StatsBar.destroyAllIcons()
-- This iterates between the tables: statsBar -> statsBarsPlacements -> statsBarsDimensions
-- And destroy all icons based on these tables.
for _, bar in pairs(statsBars) do
for _, placement in pairs(statsBarsPlacements) do
for dimension, _ in pairs(statsBarsDimensions) do
local key = tostring(dimension):lower() .. "On" .. placement
if bar[key] and bar[key].skills then
bar[key].icons:destroyChildren()
end
end
end
bar:setHeight(0)
end
end
function StatsBar.destroyAllBars()
-- This iterates between the tables: statsBars
-- And destroy all bars based on these tables.
for _, bar in pairs(statsBars) do
bar:destroy()
end
end
function StatsBar.terminate()
StatsBar.saveSettings()
disconnect(LocalPlayer, statsBarDeepInfo)
disconnect(g_game, {
onGameStart = StatsBar.OnGameStart,
OnGameEnd = StatsBar.OnGameEnd
})
StatsBar.destroyAllBars()
end
function StatsBar.onHungryChange(regenerationTime, alert)
local contents = getStatsBarsIconContent()
local info = Icons[PlayerStates.Hungry]
if regenerationTime <= alert then
for _, contentData in ipairs(contents) do
local icon = contentData.content:getChildById(info.id)
if not icon then
icon = g_ui.createWidget('ConditionWidget', contentData.content)
icon:setId(info.id)
icon:setImageSource("/images/game/states/player-state-flags")
icon:setImageClip(((info.clip - 1) * 9) .. ' 0 9 9')
icon:setTooltip(info.tooltip)
icon:setImageSize(tosize("9 9"))
if contentData.loadIconTransparent then
icon:setMarginTop(5)
end
end
end
else
for _, contentData in ipairs(contents) do
local icon = contentData.content:getChildById(info.id)
if icon then
icon:destroy()
icon = nil
end
end
end
end