diff --git a/modules/game/creature.lua b/modules/game/creature.lua index c7e357c6..4c915423 100644 --- a/modules/game/creature.lua +++ b/modules/game/creature.lua @@ -23,53 +23,74 @@ EmblemGreen = 1 EmblemRed = 2 EmblemBlue = 3 -function Creature:onSkullChange(skullId) +function getSkullImagePath(skullId) if skullId == SkullYellow then - self:setSkullTexture(resolvepath('images/skull_yellow.png')) + return 'images/skull_yellow.png' elseif skullId == SkullGreen then - self:setSkullTexture(resolvepath('images/skull_green.png')) + return 'images/skull_green.png' elseif skullId == SkullWhite then - self:setSkullTexture(resolvepath('images/skull_white.png')) + return 'images/skull_white.png' elseif skullId == SkullRed then - self:setSkullTexture(resolvepath('images/skull_red.png')) + return 'images/skull_red.png' elseif skullId == SkullBlack then - self:setSkullTexture(resolvepath('images/skull_black.png')) + return 'images/skull_black.png' elseif skullId == SkullOrange then - self:setSkullTexture(resolvepath('images/skull_orange.png')) + return 'images/skull_orange.png' end end -function Creature:onShieldChange(shieldId) +function getShieldImagePathAndBlink(shieldId) if shieldId == ShieldWhiteYellow then - self:setShieldTexture(resolvepath('images/shield_yellow_white.png'), false) + return 'images/shield_yellow_white.png', false elseif shieldId == ShieldWhiteBlue then - self:setShieldTexture(resolvepath('images/shield_blue_white.png'), false) + return 'images/shield_blue_white.png', false elseif shieldId == ShieldBlue then - self:setShieldTexture(resolvepath('images/shield_blue.png'), false) + return 'images/shield_blue.png', false elseif shieldId == ShieldYellow then - self:setShieldTexture(resolvepath('images/shield_yellow.png'), false) + return 'images/shield_yellow.png', false elseif shieldId == ShieldBlueSharedExp then - self:setShieldTexture(resolvepath('images/shield_blue_shared.png'), false) + return 'images/shield_blue_shared.png', false elseif shieldId == ShieldYellowSharedExp then - self:setShieldTexture(resolvepath('images/shield_yellow_shared.png'), false) + return 'images/shield_yellow_shared.png', false elseif shieldId == ShieldBlueNoSharedExpBlink then - self:setShieldTexture(resolvepath('images/shield_blue_not_shared.png'), true) + return 'images/shield_blue_not_shared.png', true elseif shieldId == ShieldYellowNoSharedExpBlink then - self:setShieldTexture(resolvepath('images/shield_yellow_not_shared.png'), true) + return 'images/shield_yellow_not_shared.png', true elseif shieldId == ShieldBlueNoSharedExp then - self:setShieldTexture(resolvepath('images/shield_blue_not_shared.png'), false) + return 'images/shield_blue_not_shared.png', false elseif shieldId == ShieldYellowNoSharedExp then - self:setShieldTexture(resolvepath('images/shield_yellow_not_shared.png'), false) + return 'images/shield_yellow_not_shared.png', false end end -function Creature:onEmblemChange(emblemId) +function getEmblemImagePath(emblemId) if emblemId == EmblemGreen then - self:setEmblemTexture(resolvepath('images/emblem_green.png')) + return 'images/emblem_green.png' elseif emblemId == EmblemRed then - self:setEmblemTexture(resolvepath('images/emblem_red.png')) + return 'images/emblem_red.png' elseif emblemId == EmblemBlue then - self:setEmblemTexture(resolvepath('images/emblem_blue.png')) + return 'images/emblem_blue.png' + end +end + +function Creature:onSkullChange(skullId) + local imagePath = getSkullImagePath(skullId) + if imagePath then + self:setSkullTexture(resolvepath(imagePath)) + end +end + +function Creature:onShieldChange(shieldId) + local imagePath, blink = getShieldImagePathAndBlink(shieldId) + if imagePath then + self:setShieldTexture(resolvepath(imagePath), blink) + end +end + +function Creature:onEmblemChange(emblemId) + local imagePath = getEmblemImagePath(emblemId) + if imagePath then + self:setEmblemTexture(resolvepath(imagePath)) end end diff --git a/modules/game/game.lua b/modules/game/game.lua index 3c7c58be..7a0b9952 100644 --- a/modules/game/game.lua +++ b/modules/game/game.lua @@ -27,6 +27,12 @@ local function onUseWithMouseRelease(self, mousePosition, mouseButton) end elseif clickedWidget:getClassName() == 'UIItem' and not clickedWidget:isVirtual() then g_game.useWith(g_game.selectedThing, clickedWidget:getItem()) + elseif clickedWidget.isBattleButton or clickedWidget.battleButtonChild then + local battleButton = clickedWidget + if clickedWidget.battleButtonChild then + battleButton = clickedWidget:getParent() + end + g_game.useWith(g_game.selectedThing, battleButton.creature) end end end diff --git a/modules/game/game.otmod b/modules/game/game.otmod index a32671f7..54d25fa0 100644 --- a/modules/game/game.otmod +++ b/modules/game/game.otmod @@ -15,6 +15,7 @@ Module - game_containers - game_combatcontrols - game_hotkeys + - game_battle onLoad: | dofile 'game' diff --git a/modules/game_battle/battle.lua b/modules/game_battle/battle.lua new file mode 100644 index 00000000..e8c9517e --- /dev/null +++ b/modules/game_battle/battle.lua @@ -0,0 +1,365 @@ +Battle = {} + +--TODO +--onCreatureAppears onCreatureHealthChange onCreatureDisappears +--reloadable/disconnects + +-- private variables +local battleWindow +local battleButton +local battlePanel +local lastBattleButtonSwitched +local checkCreaturesEvent +local battleButtonsByCreaturesList = {} + +local mouseWidget + +local hidePlayersButton +local hideNPCsButton +local hideMonstersButton +local hideSkullsButton +local hidePartyButton + +local battleButtonColors = { + onIdle = {notHovered = '#888888', hovered = '#FFFFFF' }, + onTargeted = {notHovered = '#FF0000', hovered = '#FF8888' }, + onFollowed = {notHovered = '#00FF00', hovered = '#88FF88' } +} + +local lifeBarColors = {} --Must be sorted by percentAbose +table.insert(lifeBarColors, {percentAbove = 92, color = '#00BC00' } ) +table.insert(lifeBarColors, {percentAbove = 60, color = '#50A150' } ) +table.insert(lifeBarColors, {percentAbove = 30, color = '#A1A100' } ) +table.insert(lifeBarColors, {percentAbove = 8, color = '#3C2727' } ) +table.insert(lifeBarColors, {percentAbove = 3, color = '#3C0000' } ) +table.insert(lifeBarColors, {percentAbove = -1, color = '#4F0000' } ) + +-- public functions +function Battle.create() + battleWindow = displayUI('battle.otui', { parent = g_game.gameRightPanel }) + battleWindow:hide() + battleButton = TopMenu.addGameButton('battleButton', 'Battle (Ctrl+B)', '/game_battle/battle.png', Battle.toggle) + Keyboard.bindKeyDown('Ctrl+B', Battle.toggle) + + battlePannel = battleWindow:getChildById('battlePanel') + + hidePlayersButton = battleWindow:getChildById('hidePlayers') + hideNPCsButton = battleWindow:getChildById('hideNPCs') + hideMonstersButton = battleWindow:getChildById('hideMonsters') + hideSkullsButton = battleWindow:getChildById('hideSkulls') + hidePartyButton = battleWindow:getChildById('hideParty') + + mouseWidget = createWidget('UIButton') + mouseWidget:setVisible(false) + mouseWidget:setFocusable(false) + + connect(UIWidget, { onHoverChange = Battle.onbattlePannelHoverChange, + onMouseRelease = Battle.onMouseRelease } ) + + connect(Creature, { onSkullChange = Battle.checkCreatureSkull, + onEmblemChange = Battle.checkCreatureEmblem } ) + + connect(g_game, { onAttackingCreatureChange = Battle.onAttack, + onFollowingCreatureChange = Battle.onFollow } ) + + addEvent(Battle.addAllCreatures) + checkCreaturesEvent = scheduleEvent(Battle.checkCreatures, 200) +end + +function Battle.destroy() + Keyboard.unbindKeyDown('Ctrl+B') + battlePannel = nil + lastBattleButtonTargeted = nil + lastBattleButtonFollowed = nil + battleButtonsByCreaturesList = {} + removeEvent(checkCreaturesEvent) + hidePlayersButton = nil + hideNPCsButton = nil + hideMonstersButton = nil + hideSkullsButton = nil + hidePartyButton = nil + checkCreaturesEvent = nil + battleButton:destroy() + battleButton = nil + battleWindow:destroy() + battleWindow = nil + + disconnect(UIWidget, { onHoverChange = Battle.onbattlePannelHoverChange, + onMouseRelease = Battle.onMouseRelease } ) + + disconnect(Creature, { onSkullChange = Battle.checkCreatureSkull, + onEmblemChange = Battle.checkCreatureEmblem } ) + + disconnect(g_game, { onAttackingCreatureChange = Battle.onAttack } ) +end + +function Battle.toggle() + local visible = not battleWindow:isExplicitlyVisible() + battleWindow:setVisible(visible) + battleButton:setOn(visible) +end + +function Battle.addAllCreatures() + local spectators = {} + local player = g_game.getLocalPlayer() + if player then + creatures = g_map.getSpectators(player:getPosition(), false) + for i, creature in ipairs(creatures) do + if creature ~= player and Battle.doCreatureFitFilters(creature) then + table.insert(spectators, creature) + end + end + end + + for i, v in pairs(spectators) do + Battle.addCreature(v) + end +end + +function Battle.doCreatureFitFilters(creature) + local hidePlayers = hidePlayersButton:isChecked() + local hideNPCs = hideNPCsButton:isChecked() + local hideMonsters = hideMonstersButton:isChecked() + local hideSkulls = hideSkullsButton:isChecked() + local hideParty = hidePartyButton:isChecked() + + if hidePlayers and not creature:asMonster() and not creature:asNpc() then + return false + elseif hideNPCs and creature:asNpc() then + return false + elseif hideMonsters and creature:asMonster() then + return false + elseif hideSkulls and creature:getSkull() == SkullNone then + return false + elseif hideParty and creature:getShield() > ShieldWhiteBlue then + return false + end + + return true +end + +function Battle.checkCreatures() + local player = g_game.getLocalPlayer() + if player then + local spectators = {} + + -- reloading list of spectators + local creaturesAppeared = {} + creatures = g_map.getSpectators(player:getPosition(), false) + for i, creature in ipairs(creatures) do + if creature ~= player and Battle.doCreatureFitFilters(creature) then + -- searching for creatures that appeared on battle list + local battleButton = battleButtonsByCreaturesList[creature:getId()] + if battleButton == nil then + table.insert(creaturesAppeared, creature) + else + Battle.setLifeBarPercent(battleButton, creature:getHealthPercent()) + end + spectators[creature:getId()] = creature + end + end + + for i, v in pairs(creaturesAppeared) do + Battle.addCreature(v) + end + + -- searching for creatures that disappeared from battle list + local creaturesDisappeared = {} + for i, creature in pairs(battleButtonsByCreaturesList) do + if spectators[creature.creatureId] == nil then + table.insert(creaturesDisappeared, creature.creature) + end + end + + for i, v in pairs(creaturesDisappeared) do + Battle.removeCreature(v) + end + end + checkCreaturesEvent = scheduleEvent(Battle.checkCreatures, 500) +end + +function Battle.addCreature(creature) + local creatureId = creature:getId() + + if battleButtonsByCreaturesList[creatureId] == nil then + local battleButton = displayUI('battleButton.otui', { parent = battlePannel }) + local creatureWidget = battleButton:getChildById('creature') + local labelWidget = battleButton:getChildById('label') + local lifeBarWidget = battleButton:getChildById('lifeBar') + + battleButton:setId('BattleButton_' .. creature:getName():gsub('%s','_')) + battleButton.creatureId = creatureId + battleButton.creature = creature + battleButton.isHovered = false + battleButton.isTarget = false + battleButton.isFollowed = false + + labelWidget:setText(creature:getName()) + creatureWidget:setCreature(creature) + Battle.setLifeBarPercent(battleButton, creature:getHealthPercent()) + + battleButtonsByCreaturesList[creatureId] = battleButton + + Battle.checkCreatureSkull(battleButton.creature) + Battle.checkCreatureEmblem(battleButton.creature) + end +end + +function Battle.checkCreatureSkull(creature, skullId) + local battleButton = battleButtonsByCreaturesList[creature:getId()] + if battleButton then + local skullWidget = battleButton:getChildById('skull') + local labelWidget = battleButton:getChildById('label') + local creature = battleButton.creature + + if creature:getSkull() ~= SkullNone then + skullWidget:setWidth(skullWidget:getHeight()) + local imagePath = getSkullImagePath(creature:getSkull()) + skullWidget:setImageSource('/game/' .. imagePath) + labelWidget:setMarginLeft(5) + else + skullWidget:setWidth(0) + if creature:getEmblem() == EmblemNone then + labelWidget:setMarginLeft(2) + end + end + end +end + +function Battle.checkCreatureEmblem(creature, emblemId) + local battleButton = battleButtonsByCreaturesList[creature:getId()] + if battleButton then + local emblemId = emblemId or creature:getEmblem() + local emblemWidget = battleButton:getChildById('emblem') + local labelWidget = battleButton:getChildById('label') + local creature = battleButton.creature + + if emblemId ~= EmblemNone then + emblemWidget:setWidth(emblemWidget:getHeight()) + local imagePath = getEmblemImagePath(emblemId) + emblemWidget:setImageSource('/game/' .. imagePath) + emblemWidget:setMarginLeft(5) + labelWidget:setMarginLeft(5) + else + emblemWidget:setWidth(0) + emblemWidget:setMarginLeft(0) + if creature:getSkull() == SkullNone then + labelWidget:setMarginLeft(2) + end + end + end +end + +function Battle.onMouseRelease(self, mousePosition, mouseButton) + if mouseButton == MouseRightButton then + local clickedWidget = g_game.gameUi:recursiveGetChildByPos(mousePosition) + if clickedWidget then + if clickedWidget:getStyleName() == 'BattleButton' then + g_game.createThingMenu(mousePosition, nil, nil, clickedWidget.creature) + return true + end + end + end +end + +function Battle.removeCreature(creature) + local creatureId = creature:getId() + + if battleButtonsByCreaturesList[creatureId] ~= nil then + if lastBattleButtonSwitched == battleButtonsByCreaturesList[creatureId] then + lastBattleButtonSwitched = nil + end + + battleButtonsByCreaturesList[creatureId]:destroy() + battleButtonsByCreaturesList[creatureId] = nil + end +end + +function Battle.setLifeBarPercent(battleButton, percent) + local lifeBarWidget = battleButton:getChildById('lifeBar') + lifeBarWidget:setPercent(percent) + + local color + for i, v in pairs(lifeBarColors) do + if percent > v.percentAbove then + color = v.color + break + end + end + + lifeBarWidget:setBackgroundColor(color) +end + +function Battle.onButtonClick(battleButton) + if battleButton then + if battleButton.isTarget then + g_game.cancelAttack() + else + g_game.attack(battleButton.creature) + end + end +end + +function Battle.onbattlePannelHoverChange(widget, hovered) + if widget.isBattleButton or widget.battleButtonChild then + local battleButton = widget + if widget.battleButtonChild then + battleButton = widget:getParent() + end + + if battleButton then + battleButton.isHovered = hovered + Battle.checkBattleButton(battleButton) + end + end +end + +function Battle.onAttack(creature) + local battleButton = creature and battleButtonsByCreaturesList[creature:getId()] or lastBattleButtonSwitched + if battleButton then + battleButton.isTarget = creature and true or false + Battle.checkBattleButton(battleButton) + end +end + +function Battle.onFollow(creature) + local battleButton = creature and battleButtonsByCreaturesList[creature:getId()] or lastBattleButtonSwitched + if battleButton then + battleButton.isFollowed = creature and true or false + Battle.checkBattleButton(battleButton) + end +end + +function Battle.checkBattleButton(battleButton) + local color = battleButtonColors.onIdle + if battleButton.isTarget then + color = battleButtonColors.onTargeted + elseif battleButton.isFollowed then + color = battleButtonColors.onFollowed + end + + color = battleButton.isHovered and color.hovered or color.notHovered + + if battleButton.isHovered or battleButton.isTarget or battleButton.isFollowed then + battleButton.creature:showStaticSquare(color) + battleButton:getChildById('creature'):setBorderWidth(1) + battleButton:getChildById('creature'):setBorderColor(color) + battleButton:getChildById('label'):setColor(color) + else + battleButton.creature:hideStaticSquare() + battleButton:getChildById('creature'):setBorderWidth(0) + battleButton:getChildById('label'):setColor(color) + end + + if battleButton.isTarget or battleButton.isFollowed then + if lastBattleButtonSwitched and lastBattleButtonSwitched ~= battleButton then + lastBattleButtonSwitched.isTarget = false + lastBattleButtonSwitched.isFollowed = false + Battle.checkBattleButton(lastBattleButtonSwitched) + end + lastBattleButtonSwitched = battleButton + end +end + +connect(g_game, { onGameStart = Battle.create, + onGameEnd = Battle.destroy } ) \ No newline at end of file diff --git a/modules/game_battle/battle.otmod b/modules/game_battle/battle.otmod new file mode 100644 index 00000000..885e430b --- /dev/null +++ b/modules/game_battle/battle.otmod @@ -0,0 +1,7 @@ +Module + name: game_battle + description: Manage battle window + author: OTClient team + website: https://github.com/edubart/otclient + onLoad: | + dofile 'battle' diff --git a/modules/game_battle/battle.otui b/modules/game_battle/battle.otui new file mode 100644 index 00000000..f235c4ca --- /dev/null +++ b/modules/game_battle/battle.otui @@ -0,0 +1,91 @@ +BattleIcon < UICheckBox + size: 20 20 + image-color: white + image-rect: 0 0 20 20 + + $hover !disabled: + color: #cccccc + + $!checked: + image-clip: 0 0 20 20 + + $hover !checked: + image-clip: 0 40 20 20 + + $checked: + image-clip: 0 20 20 20 + + $hover checked: + image-clip: 0 60 20 20 + + $disabled: + image-color: #ffffff88 + +BattlePlayers < BattleIcon + image-source: /game_battle/battle_players.png + +BattleNPCs < BattleIcon + image-source: /game_battle/battle_npcs.png + +BattleMonsters < BattleIcon + image-source: /game_battle/battle_monsters.png + +BattleSkulls < BattleIcon + image-source: /game_battle/battle_skulls.png + +BattleParty < BattleIcon + image-source: /game_battle/battle_party.png + +MiniWindow + id: battleWindow + text: Battle + height: 250 + + BattlePlayers + id: hidePlayers + tooltip: Hide players + anchors.top: parent.top + anchors.right: next.left + margin-right: 5 + + BattleNPCs + id: hideNPCs + tooltip: Hide Npc's + anchors.top: parent.top + anchors.right: next.left + margin-right: 5 + + BattleMonsters + id: hideMonsters + tooltip: Hide monsters + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + + BattleSkulls + id: hideSkulls + tooltip: Hide non-skull players + anchors.top: prev.top + anchors.left: prev.right + margin-left: 5 + + BattleParty + id: hideParty + tooltip: Hide party members + anchors.top: prev.top + anchors.left: prev.right + margin-left: 5 + + HorizontalSeparator + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 5 + + Panel + id: battlePanel + anchors.fill: parent + anchors.top: prev.bottom + margin-top: 5 + layout: verticalBox + + diff --git a/modules/game_battle/battle.png b/modules/game_battle/battle.png new file mode 100644 index 00000000..247c6ec6 Binary files /dev/null and b/modules/game_battle/battle.png differ diff --git a/modules/game_battle/battleButton.otui b/modules/game_battle/battleButton.otui new file mode 100644 index 00000000..838ce8e8 --- /dev/null +++ b/modules/game_battle/battleButton.otui @@ -0,0 +1,56 @@ +BattleButton < UIButton + +BattleButton + height: 20 + margin-top: 5 + fixed-size: true + @onClick: Battle.onButtonClick(self) + &isBattleButton: true + + UICreature + id: creature + size: 20 20 + anchors.left: parent.left + anchors.top: parent.top + @onClick: Battle.onButtonClick(parent) + &battleButtonChild: true + + UIWidget + id: spacer + width: 5 + anchors.left: creature.right + anchors.top: creature.top + &battleButtonChild: true + + UIWidget + id: skull + height: 11 + anchors.left: spacer.right + anchors.top: spacer.top + &battleButtonChild: true + + UIWidget + id: emblem + height: 11 + anchors.left: skull.right + anchors.top: creature.top + &battleButtonChild: true + + LargerLabel + id: label + anchors.left: emblem.right + anchors.top: creature.top + color: #888888 + margin-left: 2 + @onClick: Battle.onButtonClick(parent) + &battleButtonChild: true + + ProgressBar + id: lifeBar + height: 5 + anchors.left: spacer.right + anchors.right: parent.right + anchors.top: label.bottom + margin-top: 2 + @onClick: Battle.onButtonClick(parent) + &battleButtonChild: true \ No newline at end of file diff --git a/modules/game_battle/battle_monsters.png b/modules/game_battle/battle_monsters.png new file mode 100644 index 00000000..9c01361c Binary files /dev/null and b/modules/game_battle/battle_monsters.png differ diff --git a/modules/game_battle/battle_npcs.png b/modules/game_battle/battle_npcs.png new file mode 100644 index 00000000..d19635d1 Binary files /dev/null and b/modules/game_battle/battle_npcs.png differ diff --git a/modules/game_battle/battle_party.png b/modules/game_battle/battle_party.png new file mode 100644 index 00000000..77fd67ee Binary files /dev/null and b/modules/game_battle/battle_party.png differ diff --git a/modules/game_battle/battle_players.png b/modules/game_battle/battle_players.png new file mode 100644 index 00000000..84c74b91 Binary files /dev/null and b/modules/game_battle/battle_players.png differ diff --git a/modules/game_battle/battle_skulls.png b/modules/game_battle/battle_skulls.png new file mode 100644 index 00000000..3f3a2ec5 Binary files /dev/null and b/modules/game_battle/battle_skulls.png differ