local spellBarWindow = nil local clickedSlot = nil local newKey = nil local slotHotkey = nil local comboLabel = nil local spellCooldowns = {} -- Tabela de cooldowns. Estrutura: spellCooldowns["PlayerName"]["nomeSpell"] = endTime local comboPreview = nil local vocation = 0 local hotkeys = {} -- Tabela para armazenar os hotkeys salvos local lastUsedSpell = nil -- Armazena o último feitiço usado (para reverter cooldown se falhar) -- Variável para o cooldown global atual (será definido conforme a magia usada) local globalCooldownEnd = 0 -- Variável para bloquear movimento enquanto a hotkey é usada local movementBlocker = nil ---------------------------------------------------------------------- -- LAYOUT RESPONSIVO (posição, tamanho, quebra de linha) -- ---------------------------------------------------------------------- local SLOT_COUNT = 10 local SLOT_SIZE = 36 local SLOT_HEIGHT = 45 -- slot + hotkey number under it local SLOT_SPACING = 10 local ROW_SPACING = 6 local DEFAULT_BOTTOM = 90 -- Position relative to the bottom-center of the game screen (like CSS -- "position: fixed; bottom: Npx; left: 50%"), so it follows window resizes. local barOffset = { x = 0, bottom = DEFAULT_BOTTOM } local rootPanel = nil local function barWidth(columns) return columns * SLOT_SIZE + (columns - 1) * SLOT_SPACING end -- Client option "Show action bar" (client_options, id: actionBar) function isBarEnabled() return not modules.client_options or modules.client_options.getOption('actionBar') ~= false end function setBarEnabled(enabled) if not spellBarWindow then return end if enabled and g_game.isOnline() and spellBarWindow:getChildById('slot-1') then spellBarWindow:show() relayout() else spellBarWindow:hide() end end -- Re-anchors the bar to the bottom-center of the game screen, clamped so it -- never leaves the screen. function applyPosition() if not spellBarWindow then return end local parent = spellBarWindow:getParent() if not parent or parent:getWidth() <= 0 then return end local maxX = math.max(0, math.floor((parent:getWidth() - spellBarWindow:getWidth()) / 2)) local maxBottom = math.max(0, parent:getHeight() - spellBarWindow:getHeight()) local x = math.max(-maxX, math.min(maxX, barOffset.x)) local bottom = math.max(0, math.min(maxBottom, barOffset.bottom)) spellBarWindow:breakAnchors() spellBarWindow:addAnchor(AnchorBottom, 'parent', AnchorBottom) spellBarWindow:addAnchor(AnchorHorizontalCenter, 'parent', AnchorHorizontalCenter) spellBarWindow:setMarginBottom(bottom) spellBarWindow:setMarginLeft(math.max(0, x)) spellBarWindow:setMarginRight(math.max(0, -x)) end -- One row of 10 slots, or two rows of 5 when the game screen is too narrow. function relayout() if not spellBarWindow then return end local parent = spellBarWindow:getParent() if not parent or parent:getWidth() <= 0 then return end local columns = SLOT_COUNT if parent:getWidth() - 20 < barWidth(SLOT_COUNT) then columns = math.ceil(SLOT_COUNT / 2) end local rows = math.ceil(SLOT_COUNT / columns) spellBarWindow:setSize({ width = barWidth(columns), height = rows * SLOT_HEIGHT + (rows - 1) * ROW_SPACING }) for i = 1, SLOT_COUNT do local slot = spellBarWindow:getChildById('slot-' .. i) if slot then slot:setMarginLeft(((i - 1) % columns) * (SLOT_SIZE + SLOT_SPACING)) slot:setMarginTop(math.floor((i - 1) / columns) * (SLOT_HEIGHT + ROW_SPACING)) end end applyPosition() end function resetPosition() barOffset.x = 0 barOffset.bottom = DEFAULT_BOTTOM g_settings.set('spellBarOffsetX', barOffset.x) g_settings.set('spellBarOffsetBottom', barOffset.bottom) applyPosition() end -- After a drag, remember where the player put it (relative to bottom-center) -- and anchor it again, instead of leaving it at fixed pixels. local function onBarDragLeave(widget, droppedWidget, mousePos) local parent = widget:getParent() if not parent then return end barOffset.x = math.floor(widget:getX() + widget:getWidth() / 2 - (parent:getX() + parent:getWidth() / 2)) barOffset.bottom = math.floor(parent:getY() + parent:getHeight() - (widget:getY() + widget:getHeight())) g_settings.set('spellBarOffsetX', barOffset.x) g_settings.set('spellBarOffsetBottom', barOffset.bottom) applyPosition() end local function onBarMousePress(widget, mousePos, mouseButton) if mouseButton ~= MouseRightButton then return false end local menu = g_ui.createWidget('PopupMenu') menu:setGameMenu(true) menu:addOption(tr('Reset spell bar position'), resetPosition) menu:display(mousePos) return true end ---------------------------------------------------------------------- -- BLOQUEIO E LIBERAÇÃO DE MOVIMENTO -- ---------------------------------------------------------------------- function blockMovement() if not movementBlocker then -- Cria um widget invisível que captura o teclado e impede o movimento. -- Usamos o estilo "UIWidget", que é padrão. movementBlocker = g_ui.createWidget('UIWidget', modules.game_interface.getRootPanel()) if movementBlocker then movementBlocker:setSize({ width = 1, height = 1 }) movementBlocker:setOpacity(0) -- Torna o widget invisível movementBlocker:grabKeyboard() movementBlocker.onKeyDown = function(widget, keyCode, keyboardModifiers) local keyCombo = determineKeyComboDesc(keyCode, keyboardModifiers) if keyCombo == "Up" or keyCombo == "Down" or keyCombo == "Left" or keyCombo == "Right" or keyCombo:lower() == "w" or keyCombo:lower() == "a" or keyCombo:lower() == "s" or keyCombo:lower() == "d" then return true -- Bloqueia as teclas de movimento end return false end else print("Failed to create movementBlocker widget") end end end function unblockMovement() if movementBlocker then movementBlocker:destroy() movementBlocker = nil end end ---------------------------------------------------------------------- -- LISTA DE SPELLS -- ---------------------------------------------------------------------- local spelllist = { [1] = { -- Gon ["Punch"] = { name = '@#$0', exhaustion = 1, globalCooldown = 0.5, -- Cooldown global editável para esta magia icon = 'HXH/gon/1', level = 1, mana = 0, key = "F1", desc = "Gon da um soco rápido no inimigo.", movementBlockTime = 300 -- bloqueia movimento por 500ms }, ["Sequence Punch"] = { name = '@#$1', exhaustion = 2, globalCooldown = 0.8, icon = 'HXH/gon/10', level = 5, mana = 0, key = "F2", desc = "Gon atinge o alvo com uma sequencia de socos.", movementBlockTime = 500 }, ["Jajaken Gu"] = { name = '@#$2', exhaustion = 3, globalCooldown = 1, icon = 'HXH/gon/15', level = 15, mana = 0, key = "F3", desc = "Gon concentra aura em seu punho e desfere um soco de Nen.", movementBlockTime = 800 }, ["Dash"] = { name = '@#$3', exhaustion = 7, globalCooldown = 1, icon = 'HXH/gon/25', level = 20, mana = 0, key = "F4", desc = "Gon usa seu instinto selvagem para se esquivar.", movementBlockTime = 0 }, ["Ren"] = { name = '@#$4', exhaustion = 10, globalCooldown = 1, icon = 'HXH/gon/20', level = 25, mana = 0, key = "F5", desc = "Expande e intensifica a aura o máximo possível.", movementBlockTime = 300 }, ["Rock Smash"] = { name = '@#$5', exhaustion = 20, globalCooldown = 1.5, icon = 'HXH/gon/35', level = 35, mana = 0, key = "F6", desc = "Gon acumula uma grande quantidade de aura em seu punho e atinge o solo.", movementBlockTime = 1600 }, ["Jajaken Chi"] = { name = '@#$6', exhaustion = 10, globalCooldown = 0.7, icon = 'HXH/gon/50', level = 50, mana = 0, key = "F7", desc = "Gon transforma sua aura em uma lâmina de Nen.", movementBlockTime = 1200 }, ["Jajaken Pa"] = { name = '@#$7', exhaustion = 15, globalCooldown = 1.3, icon = 'HXH/gon/55', level = 55, mana = 0, key = "F8", desc = "Gon transforma sua aura em uma esfera de Nen e a dispara em direção ao alvo.", movementBlockTime = 2000 } }, [2] = { -- Killua ["Claws"] = { name = 'Claws', exhaustion = 1, globalCooldown = 1, icon = 'HXH/killua/1', level = 1, mana = 0, key = "F1", desc = "Killua ataca com suas mãos afiadas.", movementBlockTime = 300 }, ["Double Claws"] = { name = 'Double Claws', exhaustion = 5, globalCooldown = 1.2, icon = 'HXH/killua/10', level = 10, mana = 0, key = "F2", desc = "Killua se move rapidamente criando imagens residuais para confundir o inimigo.", movementBlockTime = 400 }, ["Critical Claws"] = { name = 'Critical Claws', exhaustion = 3, globalCooldown = 1.05, icon = 'HXH/killua/15', level = 15, mana = 0, key = "F3", desc = "Killua libera uma descarga elétrica concentrada em sua palma.", movementBlockTime = 300 }, ["dark steps"] = { name = 'dark steps', exhaustion = 5, globalCooldown = 2, icon = 'HXH/killua/20', level = 20, mana = 0, key = "F4", desc = "Killua dispara um raio elétrico em seus inimigos.", movementBlockTime = 700 }, ["Rhythm Echo"] = { name = 'Rhythm Echo', exhaustion = 15, globalCooldown = 1, icon = 'HXH/Killua/dash', level = 25, mana = 0, key = "F5", desc = "Killua atinge sua velocidade máxima com Nen elétrico.", movementBlockTime = 300 }, ["Kanden shi"] = { name = 'Kanden shi', exhaustion = 10, globalCooldown = 1, icon = 'HXH/Killua/40', level = 40, mana = 0, key = "F6", desc = "Killua atinge sua velocidade máxima com Nen elétrico.", movementBlockTime = 1250 } }, [3] = { -- Kurapika ["basic chain attack"] = { name = 'Basic Chain Attack', exhaustion = 2, globalCooldown = 1, icon = 'HXH/Kurapika_basic', level = 1, mana = 0, key = "Q", desc = "Kurapika ataca com sua corrente básica.", movementBlockTime = 300 }, ["chain jail"] = { name = 'Chain Jail', exhaustion = 10, globalCooldown = 1, icon = 'HXH/Kurapika1', level = 30, mana = 0, key = "1", desc = "Kurapika prende o alvo com correntes de Nen que restringem seu movimento.", movementBlockTime = 500 }, ["holy chain"] = { name = 'Holy Chain', exhaustion = 30, globalCooldown = 1, icon = 'HXH/Kurapika3', level = 50, mana = 0, key = "2", desc = "Correntes de cura restauram HP rapidamente.", movementBlockTime = 600 }, ["judgment chain"] = { name = 'Judgment Chain', exhaustion = 60, globalCooldown = 1, icon = 'HXH/Kurapika2', level = 70, mana = 0, key = "3", desc = "Uma corrente mortal que perfura o coração do inimigo e o condena.", movementBlockTime = 700 }, ["scarlet eyes mode"] = { name = 'Scarlet Eyes Mode', exhaustion = 90, globalCooldown = 1, icon = 'HXH/Kurapika4', level = 100, mana = 0, key = "4", desc = "Kurapika ativa seus olhos escarlates, aumentando drasticamente seu poder.", movementBlockTime = 800 } }, [4] = { -- Hisoka ["basic card throw"] = { name = 'Basic Card Throw', exhaustion = 2, globalCooldown = 1, icon = 'HXH/Hisoka_basic', level = 1, mana = 0, key = "Q", desc = "Hisoka arremessa uma carta de baralho afiada no inimigo.", movementBlockTime = 300 }, ["bungee gum trap"] = { name = 'Bungee Gum Trap', exhaustion = 10, globalCooldown = 1, icon = 'HXH/Hisoka1', level = 30, mana = 0, key = "1", desc = "Hisoka prende o inimigo com sua elasticidade especial.", movementBlockTime = 400 }, ["card barrage"] = { name = 'Card Barrage', exhaustion = 30, globalCooldown = 1, icon = 'HXH/Hisoka2', level = 50, mana = 0, key = "2", desc = "Hisoka lança várias cartas mortais simultaneamente.", movementBlockTime = 500 }, ["exploding trick"] = { name = 'Exploding Trick', exhaustion = 60, globalCooldown = 1, icon = 'HXH/Hisoka3', level = 75, mana = 0, key = "3", desc = "Hisoka engana o inimigo com uma ilusão antes de um golpe fatal.", movementBlockTime = 600 }, ["full power mode"] = { name = 'Full Power Mode', exhaustion = 90, globalCooldown = 1, icon = 'HXH/Hisoka4', level = 100, mana = 0, key = "4", desc = "Hisoka libera seu verdadeiro poder, aumentando sua força e velocidade por um tempo limitado.", movementBlockTime = 700 } } } -- Define a função global para buscar feitiços _G.getSpellByLowerName = function(voc, lowerName) for key, spell in pairs(spelllist[voc]) do if spell.name:lower() == lowerName then return spell end end return nil end ---------------------------------------------------------------------- -- SALVAR/CARREGAR HOTKEYS E COOLDOWNS -- ---------------------------------------------------------------------- function loadHotkeys() if g_settings then local data = g_settings.get("SpellbarHotkeys") if data and data ~= "" then local success, result = pcall(json.decode, data) if success and result then hotkeys = result end end end end function saveHotkeys() if g_settings then local encoded = json.encode(hotkeys) g_settings.set("SpellbarHotkeys", encoded) end end -- Retorna o nome do player para indexar cooldowns local function getPlayerNameForStorage() local player = g_game.getLocalPlayer() if not player then return nil end return player:getName() end -- Carrega cooldowns salvos em g_settings local function loadCooldowns() local data = g_settings.get("SpellCooldowns") if not data or data == "" then return end local success, result = pcall(json.decode, data) if not success or not result then return end local playerName = getPlayerNameForStorage() if not playerName or not result[playerName] then return end -- Cria a tabela do player se não existir if not spellCooldowns[playerName] then spellCooldowns[playerName] = {} end -- Reaplica o endTime = os.time() + timeLeft for spellName, timeLeft in pairs(result[playerName]) do spellCooldowns[playerName][spellName] = os.time() + timeLeft end end -- Salva cooldowns em g_settings local function saveCooldowns() local playerName = getPlayerNameForStorage() if not playerName then return end local data = {} -- Lê o que já existe no arquivo (para não sobrescrever outros chars) local oldData = g_settings.get("SpellCooldowns") local success, oldResult = pcall(json.decode, oldData or "") if success and oldResult then data = oldResult end -- Cria a tabela do player se não existir if not data[playerName] then data[playerName] = {} end -- Pega a tabela de cooldowns do player atual if spellCooldowns[playerName] then for spellName, endTime in pairs(spellCooldowns[playerName]) do local timeLeft = endTime - os.time() if timeLeft > 0 then data[playerName][spellName] = timeLeft else data[playerName][spellName] = nil end end end local encoded = json.encode(data) g_settings.set("SpellCooldowns", encoded) end ---------------------------------------------------------------------- -- INICIALIZAÇÃO -- ---------------------------------------------------------------------- function init() spellBarWindow = g_ui.loadUI('game_spellbar.otui', modules.game_interface.getRootPanel()) if not spellBarWindow then print("Erro: Falha ao carregar a SpellBar") return end spellBarWindow:setDraggable(true) spellBarWindow.onDragLeave = onBarDragLeave spellBarWindow.onMousePress = onBarMousePress barOffset.x = tonumber(g_settings.get('spellBarOffsetX')) or 0 barOffset.bottom = tonumber(g_settings.get('spellBarOffsetBottom')) or DEFAULT_BOTTOM -- follow the game screen size (window resize, side panels, fullscreen) rootPanel = modules.game_interface.getRootPanel() connect(rootPanel, { onGeometryChange = relayout }) loadHotkeys() connect(g_game, {onGameStart = onGameStart, onGameEnd = offline}) connect(LocalPlayer, {onLevelChange = onLevelChange, onManaChange = onManaChange}) connect(g_game, {onTextMessage = onTextMessage}) connect(g_game, 'onTalk', onTalk) ProtocolGame.registerExtendedOpcode(193, function(protocol, opcode, buffer) createSpellBar(buffer) end) end function terminate() disconnect(g_game, {onGameStart = onGameStart}) disconnect(g_game, {onGameEnd = offline}) disconnect(LocalPlayer, {onLevelChange = onLevelChange}) disconnect(LocalPlayer, {onManaChange = onManaChange}) disconnect(g_game, 'onTalk', onTalk) ProtocolGame.unregisterExtendedOpcode(193) if rootPanel then disconnect(rootPanel, { onGeometryChange = relayout }) rootPanel = nil end spellBarWindow:destroy() end ---------------------------------------------------------------------- -- CAPTURA MENSAGENS DE ERRO -- ---------------------------------------------------------------------- function onTextMessage(mode, text) local msgLower = text:lower() if msgLower:find("this action is not permitted in a protection zone") or msgLower:find("you can not cast that here") or msgLower:find("sorry, not possible") or msgLower:find("não pode usar isso aqui") or msgLower:find("you can only use it on creatures") then revertCooldown(lastUsedSpell) lastUsedSpell = nil end end function onTalk(name, level, mode, text, channelId, pos) local msgLower = text:lower() if msgLower:find("this action is not permitted in a protection zone") or msgLower:find("you can not cast that here") or msgLower:find("sorry, not possible") or msgLower:find("não pode usar isso aqui ou sem alvo") then revertCooldown(lastUsedSpell) lastUsedSpell = nil end end ---------------------------------------------------------------------- -- OFFLINE E CRIAÇÃO DA SPELLBAR -- ---------------------------------------------------------------------- function offline() -- Salva cooldowns antes de destruir a janela saveCooldowns() spellBarWindow:hide() for i = 1, 10 do local slot = spellBarWindow:recursiveGetChildById('slot-'..i) if slot ~= nil then slot:destroy() end end end function createSpellBar(buffer) vocation = tonumber(buffer) createSpellsIcons() relayout() if isBarEnabled() then spellBarWindow:show() end -- Ao criar a spellbar, carregamos os cooldowns loadCooldowns() onManaChange(g_game.getLocalPlayer(), g_game.getLocalPlayer():getMana(), g_game.getLocalPlayer():getMaxMana()) end function onGameStart() scheduleEvent(function() g_game.getProtocolGame():sendExtendedOpcode(193, 'refresh') -- Carrega cooldowns após o login loadCooldowns() end, 10) end ---------------------------------------------------------------------- -- FUNÇÕES DE REVERTER COOLDOWN E TRATAR ERROS -- ---------------------------------------------------------------------- function revertCooldown(spellName) if not spellName then return end local player = g_game.getLocalPlayer() if not player then return end local playerName = getPlayerNameForStorage() if not playerName then return end if spellCooldowns[playerName] and spellCooldowns[playerName][spellName] then spellCooldowns[playerName][spellName] = nil -- Reseta a barra de progresso local progress = spellBarWindow:recursiveGetChildById('progress-'..spellName) if progress then progress:setPercent(100) progress:setText('') end modules.game_textmessage.displayFailureMessage("não pode usar isso aqui ou sem alvo.") end end ---------------------------------------------------------------------- -- MUDANÇA DE LEVEL/MANA E SPELLS ICÔNICOS -- ---------------------------------------------------------------------- function onLevelChange(localPlayer, value, percent) if spelllist[vocation] then -- Verificar si spelllist[vocation] existe for _, list in pairs(spelllist[vocation]) do local spellWidget = spellBarWindow:recursiveGetChildById('spell-'..list.name:lower()) if spellWidget then local progress = spellBarWindow:recursiveGetChildById('progress-'..list.name:lower()) if localPlayer:getMana() >= list.mana then if progress:getText() == '' then if localPlayer:getLevel() < list.level then progress:setImageSource('img/lockedicon') progress:setPercent(0) else progress:setImageSource() progress:setPercent(100) end end end end end end end function onManaChange(localPlayer, mana, maxMana) if spelllist[vocation] then -- Verificar si spelllist[vocation] existe for _, list in pairs(spelllist[vocation]) do local spellWidget = spellBarWindow:recursiveGetChildById('spell-'..list.name:lower()) if not spellWidget then return end local progress = spellBarWindow:recursiveGetChildById('progress-'..list.name:lower()) if localPlayer:getLevel() >= list.level then if progress:getText() == '' then if localPlayer:getMana() < list.mana then progress:setPercent(0) else progress:setPercent(100) end end end end end end function createSpellsIcons() if not g_game.getLocalPlayer() then return end if not spelllist[vocation] or type(spelllist[vocation]) ~= "table" then return end local spells_icon = {} for _, list in pairs(spelllist[vocation]) do table.insert(spells_icon, { name = list.name, exhaustion = list.exhaustion, globalCooldown = list.globalCooldown, icon = list.icon, level = list.level, mana = list.mana, key = list.key, desc = list.desc }) end table.sort(spells_icon, function(a, b) return a.level < b.level end) -- the server can send the spell list again (refresh): don't stack a second set of slots for i = 1, SLOT_COUNT do local oldSlot = spellBarWindow:getChildById('slot-'..i) if oldSlot then oldSlot:destroy() end end -- positions are set by relayout() for i = 1, SLOT_COUNT do local slotSpell = g_ui.createWidget('SlotSpell', spellBarWindow) slotSpell:setId('slot-'..i) local spell = spells_icon[i] if spell then local icon = slotSpell:getChildById('spellButton') icon:setId('spell-'..spell.name:lower()) icon:setTooltip('\nEnergy: '..spell.mana..'\nExhaustion: '..spell.exhaustion..'\nDescription: '..spell.desc) icon.exhaustion = spell.exhaustion icon:setImageSource('img/vocations/'..spell.icon) icon.lvl = spell.level icon.mana = spell.mana icon.nowexausted = os.time() icon.onMousePress = function(widget, mousePos, mouseButton) if mouseButton == MouseLeftButton or mouseButton == MouseRightButton then useSpell(spell.name:lower()) end return true end local savedKey = hotkeys[spell.name:lower()] if savedKey then icon.key = g_keyboard.bindKeyPress(savedKey, function() useSpell(spell.name:lower()) end, modules.game_interface.getRootPanel()) icon.keypress = savedKey else icon.key = g_keyboard.bindKeyPress(spell.key, function() useSpell(spell.name:lower()) end, modules.game_interface.getRootPanel()) icon.keypress = spell.key end local progress = slotSpell:getChildById('spellProgress') progress:setId('progress-'..spell.name:lower()) progress:setText('') progress:setPercent(100) if g_game.getLocalPlayer():getLevel() < spell.level then progress:setImageSource('img/lockedicon') progress:setPercent(0) end if g_game.getLocalPlayer():getMana() < spell.mana then progress:setPercent(0) end end local label = slotSpell:getChildById('spellHotkey') label:setId('htk-'..i) label:setText(i < 10 and tostring(i) or "0") end end ---------------------------------------------------------------------- -- CHECAR ÁREA PROTEGIDA (OPCIONAL) -- ---------------------------------------------------------------------- function canCastSpell(spellData) local player = g_game.getLocalPlayer() if player and type(player.isInProtectedArea) == "function" then if player:isInProtectedArea() then return false end end return true end ---------------------------------------------------------------------- -- USO DE SPELL -- ---------------------------------------------------------------------- function useSpell(getspell) if not g_game.getLocalPlayer() then return end local now = os.time() if now < globalCooldownEnd then modules.game_textmessage.displayFailureMessage("Aguarde " .. (globalCooldownEnd - now) .. "s para usar outra magia.") return false end local spellWidget = spellBarWindow:recursiveGetChildById('spell-'..getspell) local progress = spellBarWindow:recursiveGetChildById('progress-'..getspell) if not spellWidget then return end if progress:getText() ~= '' then return end if progress:getPercent() < 100 then return end local spellData = getSpellByLowerName(vocation, getspell) if spellData then -- Define o tempo de bloqueio específico para este feitiço (em ms) ou usa 300ms como padrão local blockTime = spellData.movementBlockTime or 300 blockMovement() scheduleEvent(function() unblockMovement() end, blockTime) local playerName = getPlayerNameForStorage() if not playerName then return end if not spellCooldowns[playerName] then spellCooldowns[playerName] = {} end if spellCooldowns[playerName][getspell] then local remainingTime = spellCooldowns[playerName][getspell] - now if remainingTime > 0 then modules.game_textmessage.displayFailureMessage("Aguarde ["..remainingTime.."s] para usar esta magia novamente.") return false end end lastUsedSpell = getspell local individualCooldown = math.max(spellData.exhaustion, 1) spellCooldowns[playerName][getspell] = now + individualCooldown local gcool = spellData.globalCooldown or 1 globalCooldownEnd = now + gcool -- Verificar si spelllist[vocation] existe antes de iterarlo if spelllist[vocation] then for _, spell in pairs(spelllist[vocation]) do local lowerSpellName = spell.name:lower() if not spellCooldowns[playerName][lowerSpellName] or spellCooldowns[playerName][lowerSpellName] < globalCooldownEnd then spellCooldowns[playerName][lowerSpellName] = globalCooldownEnd end end end g_game.talk(spellData.name) startDownDelay(getspell) end end ---------------------------------------------------------------------- -- CONTAGEM REGRESSIVA -- ---------------------------------------------------------------------- function startDownDelay(getspell) local spellWidget = spellBarWindow:recursiveGetChildById('spell-'..getspell) local progress = spellBarWindow:recursiveGetChildById('progress-'..getspell) if not spellWidget then return end progress:setPercent(0) local spellData = getSpellByLowerName(vocation, getspell) if not spellData then return end local playerName = getPlayerNameForStorage() if not playerName then return end spellWidget.nowexausted = os.time() + spellData.exhaustion scheduleEvent(function() spellReload(getspell) end, 100) end function spellReload(getspell) local player = g_game.getLocalPlayer() if not player then return end local spellWidget = spellBarWindow:recursiveGetChildById('spell-'..getspell) local progress = spellBarWindow:recursiveGetChildById('progress-'..getspell) if not spellWidget then return end local spellData = getSpellByLowerName(vocation, getspell) if not spellData then return end local playerName = getPlayerNameForStorage() if not playerName then return end local exhausted = spellCooldowns[playerName][getspell] if not exhausted then progress:setPercent(100) progress:setText('') return end if player:getLevel() < spellData.level then progress:setImageSource('img/lockedicon') progress:setPercent(0) progress:setText('') return else local remainingTime = exhausted - os.time() if remainingTime <= 0 then progress:setPercent(100) progress:setText('') return end local perc = math.ceil((spellData.exhaustion - remainingTime) * 100 / spellData.exhaustion) progress:setPercent(perc) if remainingTime > 60 then local minutos = math.floor(remainingTime / 60) local segundos = remainingTime % 60 progress:setText(minutos .. 'm\n' .. segundos .. 's') else progress:setText(remainingTime .. 's') end end scheduleEvent(function() spellReload(getspell) end, 500) end ---------------------------------------------------------------------- -- MUDAR HOTKEY VIA POPUPMENU -- ---------------------------------------------------------------------- function createButtonKey(slot) local icon = spellBarWindow:recursiveGetChildById('spell-'..slot) local menu = g_ui.createWidget('PopupMenu') menu:setGameMenu(true) menu:addOption(tr('Set Hotkey to this slot'), function() buttonChangeKey() end) menu:display({x = icon:getPosition().x+5, y = icon:getPosition().y-20}) clickedSlot = icon end function buttonChangeKey() slotHotkey = g_ui.createWidget('SlotHotkey', rootWidget) slotHotkey:grabKeyboard() comboLabel = slotHotkey:getChildById('comboPreview') comboLabel.keyCombo = '' slotHotkey.onKeyDown = hotkeyCapture end function hotkeyCapture(slotHotkey, keyCode, keyboardModifiers) local keyCombo = determineKeyComboDesc(keyCode, keyboardModifiers) if keyCombo:match("^%d+$") then local num = tonumber(keyCombo) if num >= 1 and num <= 12 then keyCombo = "F" .. num else g_game.getLocalPlayer():sendCancelMessage("Hotkey inválido! Use apenas números de 1 a 12 ou teclas F1 até F12.") return true end end local fNum = keyCombo:match("^F(%d+)$") if not fNum then g_game.getLocalPlayer():sendCancelMessage("Hotkey inválido! Use apenas teclas F1 até F12.") return true end local num = tonumber(fNum) if not (num >= 1 and num <= 12) then g_game.getLocalPlayer():sendCancelMessage("Hotkey inválido! Use apenas teclas F1 até F12.") return true end comboPreview = slotHotkey:getChildById('comboPreview') comboPreview:setText(tr('Current hotkey to add: %s', keyCombo)) comboPreview.keyCombo = keyCombo comboPreview:resizeToText() slotHotkey:getChildById('addButton'):enable() newKey = keyCombo return true end function changeKey() if clickedSlot ~= nil then if newKey ~= 'none' then for _, list in pairs(spelllist[vocation]) do local spellWidget = spellBarWindow:recursiveGetChildById('spell-'..list.name:lower()) if spellWidget then if spellWidget.keypress == newKey or spellWidget.keypress2 == newKey then return true end end end local id = string.explode(clickedSlot:getId(), '-') local spellName = id[2] local keypress = clickedSlot.keypress2 if keypress ~= 'none' then clickedSlot.key2 = g_keyboard.unbindKeyPress(keypress, nil, modules.game_interface.getRootPanel()) end clickedSlot.key2 = g_keyboard.bindKeyPress(newKey, function() useSpell(spellName) end, modules.game_interface.getRootPanel()) clickedSlot.keypress2 = newKey hotkeys[spellName] = newKey saveHotkeys() clickedSlot = nil end end end function toggleLock() if not spellBarWindow then return end isLocked = not isLocked -- Alterna o estado spellBarWindow:setDraggable(not isLocked) -- Ativa/desativa movimento local lockButton = spellBarWindow:recursiveGetChildById('lockButton') if lockButton then if isLocked then lockButton:setText('??') -- ícone de destravado lockButton:setTooltip(tr('Unlock Spell Bar')) else lockButton:setText('??') -- ícone de travado lockButton:setTooltip(tr('Lock Spell Bar')) end end end