• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

OTClient problem with spellbar otcv8

WikiArk21

Member
Joined
Oct 24, 2023
Messages
30
Reaction score
6
I have a problem that I've tried to solve in several ways, but I think my knowledge wasn't sufficient. The problem is that when I reset a slot or change a hotkey spell, it duplicates. For example, if I press 1, the sword strike appears once. If I reset the slot and press 1 again, the sword strike appears twice, and this is used to reset or change a slot spell of any spell in the list. This only happens with hotkeys (1 to 0); this bug doesn't occur with left-click.


LUA:
local spellBarWindow = nil
local clickedSlot = nil
local newKey = nil
local slotHotkey = nil
local comboLabel = nil
local comboPreview = nil
local vocation = 0

local STORAGE_KEY = 'custom_spellbar_slots_v1'
local customSlots = {}

local function explode(str, sep)
    if str == nil then return {} end
    local t = {}
    for s in string.gmatch(str, "([^"..sep.."]+)") do
        table.insert(t, s)
    end
    return t
end

local function loadCustomSlots()
    local raw = g_settings.get(STORAGE_KEY) or ''
    if raw == '' then
        customSlots = {}
        return
    end
    local parts = explode(raw, '|')
    for i = 1, 10 do
        customSlots[i] = parts[i] and parts[i] ~= '' and parts[i] or nil
    end
end

local function saveCustomSlots()
    local parts = {}
    for i = 1, 10 do
        parts[i] = customSlots[i] or ''
    end
    g_settings.set(STORAGE_KEY, table.concat(parts, '|'))
end

local spelllist = {
    [1] = {
        [1] = {
        ["sword strike"] = {name = 'Sword Strike', exhaustion = 2, icon = 'Academy/sword_strike', level = 1, mana = 10, key = "1", desc = "Delivers a basic slash with your zanpakuto. A fundamental technique taught to every Shinigami recruit. \nReliable and swift, it forms the base of all future swordsmanship."},
        ["rest"] = {name = 'Rest', exhaustion = 60, icon = 'Academy/rest', level = 5, mana = 0, key = "2", desc = "Assumes a brief stance of meditation, channeling your spiritual energy. \nRestores 5% of your maximum Health and Mana each second for 10 seconds."},
        ["run"] = {name = 'Run', exhaustion = 30, icon = 'Academy/run', level = 10, mana = 30, key = "3", desc = "Focuses spiritual pressure into your legs, greatly enhancing your movement. \nGain +100 Movement Speed for 33 seconds."},
        ["reiatsu explosion"] = {name = 'Reiatsu Explosion', exhaustion = 2, icon = 'Academy/reiatsu_explosion', level = 20, mana = 20, key = "4", desc = "Reiatsu Explosion"},
        ["jump"] = {name = 'Jump', exhaustion = 2, icon = 'Academy/jump', level = 40, mana = 20, key = "5", desc = "Harnesses reiatsu in your legs to leap over obstacles. \nShinigami rely on this technique to scale walls and reach heights without ladders or ropes."},
        ["6"] = {name = '6', exhaustion = 2, icon = 'SpellsBase/slot', level = 60, mana = 20, key = "6", desc = "Leap over obstacles using reiatsu."},
        ["7"] = {name = '7', exhaustion = 2, icon = 'SpellsBase/slot', level = 70, mana = 20, key = "7", desc = "Leap over obstacles using reiatsu."},
        ["8"] = {name = '8', exhaustion = 2, icon = 'SpellsBase/slot', level = 80, mana = 20, key = "8", desc = "Leap over obstacles using reiatsu."},
        ["9"] = {name = '9', exhaustion = 2, icon = 'SpellsBase/slot', level = 90, mana = 20, key = "9", desc = "Leap over obstacles using reiatsu."},
        ["10"] = {name = '10', exhaustion = 2, icon = 'SpellsBase/slot', level = 100, mana = 20, key = "0", desc = "Leap over obstacles using reiatsu."},
        }
    },
}

function init()
    bottomPanel = modules.game_interface.gameBottomPanel
    spellBarWindow = g_ui.loadUI('game_spellbar', bottomPanel)
    spellBarWindow:hide()

    loadCustomSlots()

    connect(g_game, {onGameStart = onGameStart})
    connect(g_game, {onGameEnd = offline})
    connect(LocalPlayer, {onLevelChange = onLevelChange})
    connect(LocalPlayer, {onManaChange = onManaChange})
    connect(g_game, 'onTalk', onTalk)

    ProtocolGame.registerExtendedOpcode(17, 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(17)
    spellBarWindow:destroy()
end

function createSpellBar(buffer)
    vocation = tonumber(buffer) or 1
    if not spelllist[vocation] then
        print('Vocation '..vocation..' não tem lista de spells definida.')
        return
    end
    createSpellsIcons()
    spellBarWindow:show()
    onManaChange(g_game.getLocalPlayer(), g_game.getLocalPlayer():getMana(), g_game.getLocalPlayer():getMaxMana())
end

function onGameStart()
    scheduleEvent(function()
        g_game.getProtocolGame():sendExtendedOpcode(17, 'refresh')
    end, 10)
end

function offline()
    spellBarWindow:hide()
    for i = 1, 10 do
        local w = spellBarWindow:recursiveGetChildById('slot-'..i)
        if w then w:destroy() end
    end
end

function onLevelChange(localPlayer, value, percent)
    if not spelllist[vocation] then return end

    for _, list in pairs(spelllist[vocation]) do
        if list and list.name then
            local spell = spellBarWindow:recursiveGetChildById('spell-'..list.name:lower())
            if spell then
                local progress = spellBarWindow:recursiveGetChildById('progress-'..list.name:lower())
                if progress and localPlayer:getMana() >= spell.mana then
                    if progress:getText() == '' then
                        if localPlayer:getLevel() < spell.lvl 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 not spelllist[vocation] then return end

    for _, list in pairs(spelllist[vocation]) do
        if list and list.name then
            local spell = spellBarWindow:recursiveGetChildById('spell-'..list.name:lower())
            if spell then
                local progress = spellBarWindow:recursiveGetChildById('progress-'..list.name:lower())
                if progress and localPlayer:getLevel() >= spell.lvl then
                    if progress:getText() == '' then
                        if localPlayer:getMana() < spell.mana then
                            progress:setPercent(0)
                        else
                            progress:setPercent(100)
                        end
                    end
                end
            end
        end
    end
end

function createSpellsIcons()
    if not g_game.getLocalPlayer() then return end
    local spells_icon = {}
    for _, group in pairs(spelllist[vocation] or {}) do
        for spellName, spellData in pairs(group) do
            table.insert(spells_icon, spellData)
        end
    end
    table.sort(spells_icon, function(a, b) return a.level < b.level end)

    for i = 1, 10 do
        local slotSpell = g_ui.createWidget('SlotSpell', spellBarWindow)
        slotSpell:setId('slot-'..i)
        slotSpell:setMarginLeft((i - 1) * 46 + 68)

        local assigned = customSlots[i]
        local chosenSpell = nil

        if assigned then
            for _, s in ipairs(spells_icon) do
                if s.name:lower() == assigned then
                    chosenSpell = s
                    break
                end
            end
        end

        if not chosenSpell then
            chosenSpell = spells_icon[i]
        end

        if chosenSpell then
            local icon = slotSpell:getChildById('spellButton')
            icon:setId('spell-'..chosenSpell.name:lower())
            icon:setTooltip('Name: '..chosenSpell.name..'\nEnergy: '..chosenSpell.mana..'\nExhaustion: '..chosenSpell.exhaustion..'\n'..chosenSpell.desc)
            icon.exhaustion = chosenSpell.exhaustion
            icon:setImageSource('img/vocations/'..chosenSpell.icon)
            icon.lvl = chosenSpell.level
            icon.mana = chosenSpell.mana
            icon.nowexausted = os.time()
            icon.key = g_keyboard.bindKeyPress(chosenSpell.key, function() useSpell(chosenSpell.name:lower()) end, modules.game_interface.getRootPanel())
            icon.keypress = chosenSpell.key

            icon.onMousePress = function(self, pos, button)
                if button == MouseRightButton then
                    openSpellSelect(i)
                    return true
                elseif button == MouseLeftButton then
                    useSpell(chosenSpell.name:lower())
                    return true
                end
                return false
            end

            local progress = slotSpell:getChildById('spellProgress')
            progress:setId('progress-'..chosenSpell.name:lower())
            progress:setText('')
            progress:setPercent(100)
            if g_game.getLocalPlayer():getLevel() < chosenSpell.level then
                progress:setImageSource('img/lockedicon')
                progress:setPercent(0)
            end
            if g_game.getLocalPlayer():getMana() < chosenSpell.mana then
                progress:setPercent(0)
            end
        end

        local label = slotSpell:getChildById('spellHotkey')
        label:setId('htk-'..i)
        label:setText(i < 10 and i or 0)
    end
end

local currentSpellPage = 1

function openSpellSelect(slotIndex)
    if not g_game.getLocalPlayer() then return end
    local vocationData = spelllist[vocation]
    if not vocationData then return end

    local totalPages = 0
    for k, _ in pairs(vocationData) do
        totalPages = totalPages + 1
    end

    local function buildMenu(page)
        local pageData = vocationData[page]
        if not pageData then return end

        local sortedSpells = {}
        for _, spellData in pairs(pageData) do
            table.insert(sortedSpells, spellData)
        end
        table.sort(sortedSpells, function(a, b) return a.level < b.level end)

        local menu = g_ui.createWidget('PopupMenu')
        menu:setGameMenu(true)

        menu:addOption('Reset Slot', function()
            customSlots[slotIndex] = nil
            saveCustomSlots()
            reloadSpellbar()
        end)
        menu:addSeparator()

        for _, s in ipairs(sortedSpells) do
            local label = s.name .. ' (lvl ' .. s.level .. ' / ' .. s.mana .. ' mana)'
            menu:addOption(label, function()
                customSlots[slotIndex] = s.name:lower()
                saveCustomSlots()
                reloadSpellbar()
            end)
        end

        menu:addSeparator()

        if page > 1 then
            menu:addOption('Previous Page', function()
                currentSpellPage = page - 1
                buildMenu(currentSpellPage)
            end)
        end
        if page < totalPages then
            menu:addOption('Next Page', function()
                currentSpellPage = page + 1
                buildMenu(currentSpellPage)
            end)
        end

        local slotWidget = spellBarWindow:recursiveGetChildById('slot-'..slotIndex)
        if slotWidget then
            local pos = slotWidget:getPosition()
            menu:display({x = pos.x+25, y = pos.y-20})
        else
            menu:display()
        end
    end

    buildMenu(currentSpellPage)
end

function reloadSpellbar()
    for i = 1, 10 do
        local s = spellBarWindow:recursiveGetChildById('slot-'..i)
        if s then s:destroy() end
    end
    createSpellsIcons()
end

function useSpell(getspell)
    if not g_game.getLocalPlayer() then return end

    local spellData = nil
    for _, group in pairs(spelllist[vocation] or {}) do
        for spellName, s in pairs(group) do
            if s.name:lower() == getspell then
                spellData = s
                break
            end
        end
    end
    if not spellData then return end

    local spellWidget = spellBarWindow:recursiveGetChildById('spell-'..getspell)
    local progress = spellBarWindow:recursiveGetChildById('progress-'..getspell)
    if not spellWidget or not progress then return end
    if progress:getText() ~= '' or progress:getPercent() < 100 then return end

    g_game.talk(spellData.name)
end

function onTalk(name, level, mode, text, channelId, pos)
    if not g_game.getLocalPlayer() then return end
    if g_game.getLocalPlayer():getName() ~= name then return end
    for _, group in pairs(spelllist[vocation] or {}) do
        if group[text:lower()] then
            startDownDelay(text:lower())
        end
    end
end

function startDownDelay(getspell)
    local spell = spellBarWindow:recursiveGetChildById('spell-'..getspell)
    local progress = spellBarWindow:recursiveGetChildById('progress-'..getspell)
    if not spell then return end
    progress:setPercent(0)
    spell.nowexausted = os.time()+spell.exhaustion
    scheduleEvent(function() spellReload(getspell) end, 100)
end

function spellReload(getspell)
    if not g_game.getLocalPlayer() then return end
    local spell = spellBarWindow:recursiveGetChildById('spell-'..getspell)
    local progress = spellBarWindow:recursiveGetChildById('progress-'..getspell)
    if not spell then return end
    local exausted = spell.nowexausted

    if g_game.getLocalPlayer():getLevel() < spell.lvl then
        progress:setImageSource('img/lockedicon')
        progress:setPercent(0)
        progress:setText('')
        return true
    else
        local perc = math.ceil((spell.exhaustion-(exausted-os.time()))*100/spell.exhaustion)
        local timeall = tonumber(math.ceil(exausted-os.time()))
        local timepart = ((1-(math.ceil(timeall/60)-(timeall/60)))*60)
        if timepart >= 60 then timepart = 0 end
        if progress:getPercent() >= 100 or timeall < 0 then return true end
        if progress:getPercent() < 100 then
            if perc < 100 then
                progress:setPercent(perc)
                if timeall > 60 then
                    progress:setText(''..math.floor(timeall/60)..'m\n'..timepart..'s')
                else
                    progress:setText(''..timeall..'s')
                end
            else
                if g_game.getLocalPlayer():getMana() < spell.mana then
                    progress:setPercent(0)
                else
                    progress:setPercent(100)
                end
                progress:setText('')
            end
        end
    end
    scheduleEvent(function() spellReload(getspell, exausted) end, 500)
end
 
Back
Top