diff --git a/modules/game_battle/battle.lua b/modules/game_battle/battle.lua index 0f03d4fe..b391d57e 100644 --- a/modules/game_battle/battle.lua +++ b/modules/game_battle/battle.lua @@ -1,11 +1,16 @@ battleWindow = nil battleButton = nil battlePanel = nil +filterPanel = nil +toggleFilterButton = nil lastBattleButtonSwitched = nil battleButtonsByCreaturesList = {} +creatureAgeList = {} mouseWidget = nil +sortTypeBox = nil +sortOrderBox = nil hidePlayersButton = nil hideNPCsButton = nil hideMonstersButton = nil @@ -25,6 +30,15 @@ function init() battlePanel = battleWindow:recursiveGetChildById('battlePanel') + filterPanel = battleWindow:recursiveGetChildById('filterPanel') + toggleFilterButton = battleWindow:recursiveGetChildById('toggleFilterButton') + + if isHidingFilters() then + hideFilterPanel() + end + + sortTypeBox = battleWindow:recursiveGetChildById('sortTypeBox') + sortOrderBox = battleWindow:recursiveGetChildById('sortOrderBox') hidePlayersButton = battleWindow:recursiveGetChildById('hidePlayers') hideNPCsButton = battleWindow:recursiveGetChildById('hideNPCs') hideMonstersButton = battleWindow:recursiveGetChildById('hideMonsters') @@ -38,6 +52,18 @@ function init() battleWindow:setContentMinimumHeight(80) + sortTypeBox:addOption('Name', 'name') + sortTypeBox:addOption('Distance', 'distance') + sortTypeBox:addOption('Age', 'age') + sortTypeBox:addOption('Health', 'health') + sortTypeBox:setCurrentOptionByData(getSortType()) + sortTypeBox.onOptionChange = onChangeSortType + + sortOrderBox:addOption('Asc.', 'asc') + sortOrderBox:addOption('Desc.', 'desc') + sortOrderBox:setCurrentOptionByData(getSortOrder()) + sortOrderBox.onOptionChange = onChangeSortOrder + connect(Creature, { onSkullChange = updateCreatureSkull, onEmblemChange = updateCreatureEmblem, @@ -47,6 +73,10 @@ function init() onAppear = onCreatureAppear, onDisappear = onCreatureDisappear }) + + connect(LocalPlayer, { + onPositionChange = onCreaturePositionChange + }) connect(g_game, { onAttackingCreatureChange = onAttack, @@ -75,6 +105,10 @@ function terminate() onDisappear = onCreatureDisappear }) + disconnect(LocalPlayer, { + onPositionChange = onCreaturePositionChange + }) + disconnect(g_game, { onAttackingCreatureChange = onAttack, onFollowingCreatureChange = onFollow, @@ -96,6 +130,93 @@ function onMiniWindowClose() battleButton:setOn(false) end +function getSortType() + local settings = g_settings.getNode('BattleList') + if not settings then + return 'name' + end + return settings['sortType'] +end + +function setSortType(state) + settings = {} + settings['sortType'] = state + g_settings.mergeNode('BattleList', settings) + + checkCreatures() +end + +function getSortOrder() + local settings = g_settings.getNode('BattleList') + if not settings then + return 'asc' + end + return settings['sortOrder'] +end + +function setSortOrder(state) + settings = {} + settings['sortOrder'] = state + g_settings.mergeNode('BattleList', settings) + + checkCreatures() +end + +function isSortAsc() + return getSortOrder() == 'asc' +end + +function isSortDesc() + return getSortOrder() == 'desc' +end + +function isHidingFilters() + local settings = g_settings.getNode('BattleList') + if not settings then + return false + end + return settings['hidingFilters'] +end + +function setHidingFilters(state) + settings = {} + settings['hidingFilters'] = state + g_settings.mergeNode('BattleList', settings) +end + +function hideFilterPanel() + filterPanel.originalHeight = filterPanel:getHeight() + filterPanel:setHeight(0) + toggleFilterButton:getParent():setMarginTop(0) + toggleFilterButton:setImageClip(torect("0 0 21 12")) + setHidingFilters(true) + filterPanel:setVisible(false) +end + +function showFilterPanel() + toggleFilterButton:getParent():setMarginTop(5) + filterPanel:setHeight(filterPanel.originalHeight) + toggleFilterButton:setImageClip(torect("21 0 21 12")) + setHidingFilters(false) + filterPanel:setVisible(true) +end + +function toggleFilterPanel() + if filterPanel:isVisible() then + hideFilterPanel() + else + showFilterPanel() + end +end + +function onChangeSortType(comboBox, option) + setSortType(option:lower()) +end + +function onChangeSortOrder(comboBox, option) + setSortOrder(option:lower():gsub('[.]', '')) -- Replace dot in option name +end + function checkCreatures() removeAllCreatures() @@ -151,15 +272,42 @@ end function onCreatureHealthPercentChange(creature, health) local battleButton = battleButtonsByCreaturesList[creature:getId()] if battleButton then + if getSortType() == 'health' then + removeCreature(creature) + addCreature(creature) + return + end battleButton:setLifeBarPercent(creature:getHealthPercent()) end end +local function getDistanceBetween(p1, p2) + return math.max(math.abs(p1.x - p2.x), math.abs(p1.y - p2.y)) +end + function onCreaturePositionChange(creature, newPos, oldPos) if creature:isLocalPlayer() then if oldPos and newPos and newPos.z ~= oldPos.z then checkCreatures() else + -- Distance will change when moving, recalculate and move to correct index + if getSortType() == 'distance' then + local distanceList = {} + for id, creatureButton in pairs(battleButtonsByCreaturesList) do + table.insert(distanceList, {distance = getDistanceBetween(newPos, creatureButton.creature:getPosition()), widget = creatureButton}) + end + + if isSortAsc() then + table.sort(distanceList, function(a, b) return a.distance < b.distance end) + else + table.sort(distanceList, function(a, b) return a.distance > b.distance end) + end + + for i = 1, #distanceList do + battlePanel:moveChildToIndex(distanceList[i].widget, i) + end + end + for id, creatureButton in pairs(battleButtonsByCreaturesList) do addCreature(creatureButton.creature) end @@ -170,6 +318,9 @@ function onCreaturePositionChange(creature, newPos, oldPos) if has and not fit then removeCreature(creature) elseif fit then + if has and getSortType() == 'distance' then + removeCreature(creature) + end addCreature(creature) end end @@ -201,8 +352,13 @@ function addCreature(creature) local creatureId = creature:getId() local battleButton = battleButtonsByCreaturesList[creatureId] + -- Register when creature is added to battlelist for the first time + if not creatureAgeList[creatureId] then + creatureAgeList[creatureId] = os.time() + end + if not battleButton then - battleButton = g_ui.createWidget('BattleButton', battlePanel) + battleButton = g_ui.createWidget('BattleButton') battleButton:setup(creature) battleButton.onHoverChange = onBattleButtonHoverChange @@ -217,6 +373,77 @@ function addCreature(creature) if creature == g_game.getFollowingCreature() then onFollow(creature) end + + local inserted = false + local nameLower = creature:getName():lower() + local healthPercent = creature:getHealthPercent() + local playerPosition = g_game.getLocalPlayer():getPosition() + local distance = getDistanceBetween(playerPosition, creature:getPosition()) + local age = creatureAgeList[creatureId] + + local childCount = battlePanel:getChildCount() + for i = 1, childCount do + local child = battlePanel:getChildByIndex(i) + local childName = child:getCreature():getName():lower() + local equal = false + if getSortType() == 'age' then + local childAge = creatureAgeList[child:getCreature():getId()] + if (age < childAge and isSortAsc()) or (age > childAge and isSortDesc()) then + battlePanel:insertChild(i, battleButton) + inserted = true + break + elseif age == childAge then + equal = true + end + elseif getSortType() == 'distance' then + local childDistance = getDistanceBetween(child:getCreature():getPosition(), playerPosition) + if (distance < childDistance and isSortAsc()) or (distance > childDistance and isSortDesc()) then + battlePanel:insertChild(i, battleButton) + inserted = true + break + elseif childDistance == distance then + equal = true + end + elseif getSortType() == 'health' then + local childHealth = child:getCreature():getHealthPercent() + if (healthPercent < childHealth and isSortAsc()) or (healthPercent > childHealth and isSortDesc()) then + battlePanel:insertChild(i, battleButton) + inserted = true + break + elseif healthPercent == childHealth then + equal = true + end + end + + -- If any other sort type is selected and values are equal, sort it by name also + if getSortType() == 'name' or equal then + local length = math.min(childName:len(), nameLower:len()) + for j=1,length do + if (nameLower:byte(j) < childName:byte(j) and isSortAsc()) or (nameLower:byte(j) > childName:byte(j) and isSortDesc()) then + battlePanel:insertChild(i, battleButton) + inserted = true + break + elseif (nameLower:byte(j) > childName:byte(j) and isSortAsc()) or (nameLower:byte(j) < childName:byte(j) and isSortDesc()) then + break + elseif j == nameLower:len() and isSortAsc() then + battlePanel:insertChild(i, battleButton) + inserted = true + elseif j == childName:len() and isSortDesc() then + battlePanel:insertChild(i, battleButton) + inserted = true + end + end + end + + if inserted then + break + end + end + + -- Insert at the end if no other place is found + if not inserted then + battlePanel:insertChild(childCount + 1, battleButton) + end else battleButton:setLifeBarPercent(creature:getHealthPercent()) end @@ -226,6 +453,7 @@ function addCreature(creature) end function removeAllCreatures() + creatureAgeList = {} for i, v in pairs(battleButtonsByCreaturesList) do removeCreature(v.creature) end diff --git a/modules/game_battle/battle.otui b/modules/game_battle/battle.otui index 6eb2d752..2bdaa596 100644 --- a/modules/game_battle/battle.otui +++ b/modules/game_battle/battle.otui @@ -45,11 +45,12 @@ MiniWindow &save: true Panel + id: filterPanel margin-top: 26 anchors.top: parent.top anchors.left: parent.left anchors.right: miniwindowScrollBar.left - height: 20 + height: 45 Panel anchors.top: parent.top @@ -85,16 +86,55 @@ MiniWindow !tooltip: tr('Hide party members') @onCheckChange: modules.game_battle.checkCreatures() - HorizontalSeparator + Panel + anchors.top: prev.bottom + anchors.horizontalCenter: parent.horizontalCenter + height: 20 + width: 128 + margin-top: 6 + + ComboBox + id: sortTypeBox + width: 74 + anchors.top: parent.top + anchors.left: prev.right + margin-left: 5 + + ComboBox + id: sortOrderBox + width: 54 + anchors.top: parent.top + anchors.left: prev.right + margin-left: 4 + + Panel + height: 18 anchors.top: prev.bottom anchors.left: parent.left anchors.right: miniwindowScrollBar.left - margin-right: 1 - margin-top: 4 + margin-top: 5 + UIWidget + id: toggleFilterButton + anchors.top: prev.top + width: 21 + anchors.horizontalCenter: parent.horizontalCenter + image-source: /images/ui/arrow_vertical + image-rect: 0 0 21 12 + image-clip: 21 0 21 12 + @onClick: modules.game_battle.toggleFilterPanel() + phantom: false + + HorizontalSeparator + anchors.top: prev.top + anchors.left: parent.left + anchors.right: miniwindowScrollBar.left + margin-right: 1 + margin-top: 11 + MiniWindowContents anchors.top: prev.bottom - margin-top: 0 + margin-top: 2 Panel id: battlePanel