• 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 How to receive information from the server to the otclient?

MorganaSacani

Active Member
Joined
Sep 20, 2022
Messages
87
Solutions
1
Reaction score
32
Hello dear friends!
I managed to create a window that shows my character's attributes, but I don't know how to make the values of these attributes appear.

Here is the script I created for my Otclient:
Lua:
attributesWindow = nil
attributesButton = nil

function init()
  connect(LocalPlayer, {
    onHealthChange = onHealthChange,
  })
  connect(g_game, {
    onGameStart = refresh,
    onGameEnd = offline
  })

  attributesButton = modules.client_topmenu.addRightGameToggleButton('attributesButton', tr('Attributes'), '/images/topbuttons/attributes', toggle, false, 1)
  attributesButton:setOn(true)
  attributesWindow = g_ui.loadUI('attributes', modules.game_interface.getRightPanel())
 
  attributesWindow:setup()
end

function terminate()
  disconnect(LocalPlayer, {
    onHealthChange = onHealthChange,
  })
  disconnect(g_game, {
    onGameStart = refresh,
    onGameEnd = offline
  })

  attributesWindow:destroy()
  attributesButton:destroy()
end

function refresh()
  local player = g_game.getLocalPlayer()
  local protocolGame = g_game.getProtocolGame()
 
  if not player then return end
 
  onHealthChange(player, player:getHealth(), player:getMaxHealth())
  onStaminaChange(player, "Here I need it to return the current value", "Here I need it to return the maximum value")

  local contentsPanel = attributesWindow:getChildById('contentsPanel')
  attributesWindow:setContentMinimumHeight(44)
  attributesWindow:setContentMaximumHeight(390)
 
end

function toggle()
  if attributesButton:isOn() then
    attributesWindow:close()
    attributesButton:setOn(false)
  else
    attributesWindow:open()
    attributesButton:setOn(true)
  end
end

function setAttributeValue(id, value)
  local attribute = attributesWindow:recursiveGetChildById(id)
  local widget = attribute:getChildById('value')
  widget:setText(value)
end

function setAttributeColor(id, value)
  local attribute = attributesWindow:recursiveGetChildById(id)
  local widget = attribute:getChildById('value')
  widget:setColor(value)
end

function resetAttributeColor(id)
  local attribute = attributesWindow:recursiveGetChildById(id)
  local widget = attribute:getChildById('value')
  widget:setColor('#bbbbbb')
end

function checkAlert(id, value, maxValue, threshold, greaterThan)
  if greaterThan == nil then greaterThan = false end
  local alert = false

  if type(maxValue) == 'boolean' then
    if maxValue then
      return
    end

    if greaterThan then
      if value > threshold then
        alert = true
      end
    else
      if value < threshold then
        alert = true
      end
    end
  elseif type(maxValue) == 'number' then
    if maxValue < 0 then
      return
    end

    local percent = math.floor((value / maxValue) * 100)
    if greaterThan then
      if percent > threshold then
        alert = true
      end
    else
      if percent < threshold then
        alert = true
      end
    end
  end

  if alert then
    setAttributeColor(id, '#b22222') -- red
  else
    resetAttributeColor(id)
  end
end

function onHealthChange(localPlayer, value, maxValue)
  setAttributeValue('health', value)
  checkAlert('health', value, maxValue, 30)
end

function onStaminaChange(localPlayer, value, maxValue)
  setAttributeValue('stamina', value)
  checkAlert('stamina', value, maxValue, 30)
end

On my server I created two functions to return me stamina attribute values:
Lua:
function getPlayerAttrMaxStamina(target) return getPlayerStorageValue(target, ATTR_MAX_STAMINA_STORAGE_ID) end
function getPlayerAttrStamina(target) return getPlayerStorageValue(target, ATTR_STAMINA_STORAGE_ID) end

I imagine that for me to receive information from the server to the Otclient, I need to create some script in creaturescripts.
I imagine on the server it should be something like sendExtendedOpcode, and maybe on the Otclient it's something like getExtendedOpcode.
Can someone help me? My server is a TFS 0.4
 
Solution
this can also be an option:
Lua:
function receiveStorageFromServer(protocol, opcode, packet)
    local player = g_game.getLocalPlayer()
    if player then
        onHealthChange(player, player:getHealth(), player:getMaxHealth())
        onStaminaChange(player, tonumber(packet), 100)
    end
end

function refresh()
    local player = g_game.getLocalPlayer()
    if not player then return end

    local contentsPanel = attributesWindow:getChildById('contentsPanel')
    attributesWindow:setContentMinimumHeight(44)
    attributesWindow:setContentMaximumHeight(390)

    ProtocolGame.registerExtendedOpcode(55, receiveStorageFromServer)
end

I don't know much about OTC methods, so sorry if I don't fully understand what you want.
Remember that...
Maybe you should try searching the forum first, however here is a link that might help.
 
I've been researching and trying to learn about it for three days, I've even accessed this link a few times...
The problem is that I'm using TFS 0.4.
Lua:
local packet = NetworkMessage()
packet:addByte(0x32) -- Extended Opcode (0x32 = 50 (in dec))
packet:addByte(0x37) -- The Opcode of this Request (0x37 = 55 (in dec))
packet:addString(tostring(player:getVocation():getId()))
packet:sendToPlayer(player)
I used this script as an example, but my server doesn't recognize these functions. Maybe they are different in my version.
 
Ok I will show you some possible examples of use for your version:
data/creaturescripts/scripts/extendedopcode.lua
Lua:
function onExtendedOpcode(cid, opcode, buffer)
    if opcode == 1 then -- no buffer, direct response
        local storageValue = getCreatureStorage(cid, 1000)
        doPlayerSendExtendedOpcode(cid, 55, tostring(storageValue))
    elseif opcode == 2 then -- with buffer
        if buffer == "missions" then
            local missions = {}
            for i = 1001, 1004 do
                missions[#missions + 1] = getCreatureStorage(cid, i)
            end
            doPlayerSendExtendedOpcode(cid, 55, table.concat(missions, ","))
        elseif buffer == "items" then
            local items = {}
            for i = 1005, 1008 do
                items[#items + 1] = getCreatureStorage(cid, i)
            end
            doPlayerSendExtendedOpcode(cid, 55, table.concat(items, ","))
        end
    end
    return true
end

data/creaturescripts/creaturescripts.xml
XML:
<event type="extendedopcode" name="ExtendedOpcode" event="script" value="extendedopcode.lua"/>

If you know some basic programming you will be able to realize all the possibilities that there are with buffers, so that a single opcode can serve you for many options and keep everything more organized.
 
I managed to send the information to the Otclient, that my storage Value is 90
I tested it using print() and looking at the terminal

Now I need it to return only the packet for my code to work
Unfortunately it is returning the protocol, opcode, and finally the packet

I need Otclient to check only the packet in the following function:
Lua:
onStaminaChange(player, packetValue, 100)

my_client
attributes.lua:
Lua:
attributesWindow = nil
attributesButton = nil

function init()
  connect(LocalPlayer, {
    onHealthChange = onHealthChange,
  })
  connect(g_game, {
    onGameStart = refresh,
    onGameEnd = offline
  })

  attributesButton = modules.client_topmenu.addRightGameToggleButton('attributesButton', tr('Attributes'), '/images/topbuttons/attributes', toggle, false, 1)
  attributesButton:setOn(true)
  attributesWindow = g_ui.loadUI('attributes', modules.game_interface.getRightPanel())
 
  attributesWindow:setup()
end

function terminate()
  disconnect(LocalPlayer, {
    onHealthChange = onHealthChange,
  })
  disconnect(g_game, {
    onGameStart = refresh,
    onGameEnd = offline
  })

  attributesWindow:destroy()
  attributesButton:destroy()
end

function refresh()
  local player = g_game.getLocalPlayer()
  local protocolGame = g_game.getProtocolGame()
 
  if not player then return end
 
  onHealthChange(player, player:getHealth(), player:getMaxHealth())
  onStaminaChange(player, 30, 100)

  local contentsPanel = attributesWindow:getChildById('contentsPanel')
  attributesWindow:setContentMinimumHeight(44)
  attributesWindow:setContentMaximumHeight(390)
 
  ProtocolGame.registerExtendedOpcode(55, receiveStorageFromServer)
end

function offline()
    ProtocolGame.unregisterExtendedOpcode(55, true)
end

function toggle()
  if attributesButton:isOn() then
    attributesWindow:close()
    attributesButton:setOn(false)
  else
    attributesWindow:open()
    attributesButton:setOn(true)
  end
end

function onMiniWindowClose()
    attributesButton:setOn(false)
end

function setAttributeValue(id, value)
  local attribute = attributesWindow:recursiveGetChildById(id)
  local widget = attribute:getChildById('value')
  widget:setText(value)
end

function setAttributeColor(id, value)
  local attribute = attributesWindow:recursiveGetChildById(id)
  local widget = attribute:getChildById('value')
  widget:setColor(value)
end

function resetAttributeColor(id)
  local attribute = attributesWindow:recursiveGetChildById(id)
  local widget = attribute:getChildById('value')
  widget:setColor('#bbbbbb')
end

function checkAlert(id, value, maxValue, threshold, greaterThan)
  if greaterThan == nil then greaterThan = false end
  local alert = false

  if type(maxValue) == 'boolean' then
    if maxValue then
      return
    end

    if greaterThan then
      if value > threshold then
        alert = true
      end
    else
      if value < threshold then
        alert = true
      end
    end
  elseif type(maxValue) == 'number' then
    if maxValue < 0 then
      return
    end

    local percent = math.floor((value / maxValue) * 100)
    if greaterThan then
      if percent > threshold then
        alert = true
      end
    else
      if percent < threshold then
        alert = true
      end
    end
  end

  if alert then
    setAttributeColor(id, '#b22222') -- red
  else
    resetAttributeColor(id)
  end
end

function onHealthChange(localPlayer, value, maxValue)
  setAttributeValue('health', value)
  checkAlert('health', value, maxValue, 30)
end

function onStaminaChange(localPlayer, value, maxValue)
  setAttributeValue('stamina', value)
  checkAlert('stamina', value, maxValue, 30)
end

function receiveStorageFromServer(protocol, opcode, packet)
    print(protocol, opcode, packet)
end

my_server:
extendedopcode.lua:
Lua:
function onExtendedOpcode(cid, opcode, buffer)
    local storageValue = getCreatureStorage(cid, ATTR_STAMINA_STORAGE_ID)
    doPlayerSendExtendedOpcode(cid, 55, tostring(storageValue))
    return true
end
Post automatically merged:

Finally I managed to get the code working!!
But, it's not 100%.
It only updates the value of my storage, when I log out and then log in
Lua:
attributesWindow = nil
attributesButton = nil

function init()
  connect(LocalPlayer, {
    onHealthChange = onHealthChange,
    onStaminaChange = onStaminaChange,
  })
  connect(g_game, {
    onGameStart = refresh,
    onGameEnd = offline
  })

  attributesButton = modules.client_topmenu.addRightGameToggleButton('attributesButton', tr('Attributes'), '/images/topbuttons/attributes', toggle, false, 1)
  attributesButton:setOn(true)
  attributesWindow = g_ui.loadUI('attributes', modules.game_interface.getRightPanel())
 
  attributesWindow:setup()
end

function terminate()
  disconnect(LocalPlayer, {
    onHealthChange = onHealthChange,
    onStaminaChange = onStaminaChange,
  })
  disconnect(g_game, {
    onGameStart = refresh,
    onGameEnd = offline
  })

  attributesWindow:destroy()
  attributesButton:destroy()
end

function refresh()
  local player = g_game.getLocalPlayer()
  local protocolGame = g_game.getProtocolGame()
 
  if not player then return end
 
  onHealthChange(player, player:getHealth(), player:getMaxHealth())
  function receiveStorageFromServer(protocol, opcode, packet)
    onStaminaChange(player, packet, 100)
  end

  local contentsPanel = attributesWindow:getChildById('contentsPanel')
  attributesWindow:setContentMinimumHeight(44)
  attributesWindow:setContentMaximumHeight(390)
 
  ProtocolGame.registerExtendedOpcode(55, receiveStorageFromServer)
end

function offline()
    ProtocolGame.unregisterExtendedOpcode(55, true)
end

function toggle()
  if attributesButton:isOn() then
    attributesWindow:close()
    attributesButton:setOn(false)
  else
    attributesWindow:open()
    attributesButton:setOn(true)
  end
end

function onMiniWindowClose()
    attributesButton:setOn(false)
end

function setAttributeValue(id, value)
  local attribute = attributesWindow:recursiveGetChildById(id)
  local widget = attribute:getChildById('value')
  widget:setText(value)
end

function setAttributeColor(id, value)
  local attribute = attributesWindow:recursiveGetChildById(id)
  local widget = attribute:getChildById('value')
  widget:setColor(value)
end

function resetAttributeColor(id)
  local attribute = attributesWindow:recursiveGetChildById(id)
  local widget = attribute:getChildById('value')
  widget:setColor('#bbbbbb')
end

function checkAlert(id, value, maxValue, threshold, greaterThan)
  if greaterThan == nil then greaterThan = false end
  local alert = false

  if type(maxValue) == 'boolean' then
    if maxValue then
      return
    end

    if greaterThan then
      if value > threshold then
        alert = true
      end
    else
      if value < threshold then
        alert = true
      end
    end
  elseif type(maxValue) == 'number' then
    if maxValue < 0 then
      return
    end

    local percent = math.floor((value / maxValue) * 100)
    if greaterThan then
      if percent > threshold then
        alert = true
      end
    else
      if percent < threshold then
        alert = true
      end
    end
  end

  if alert then
    setAttributeColor(id, '#b22222') -- red
  else
    resetAttributeColor(id)
  end
end

function onHealthChange(localPlayer, value, maxValue)
  setAttributeValue('health', value)
  checkAlert('health', value, maxValue, 30)
end

function onStaminaChange(localPlayer, value, maxValue)
  setAttributeValue('stamina', value)
  checkAlert('stamina', value, maxValue, 30)
end
 
Last edited:
Maybe there are better ways to do it, but a quick and easy way is to keep a local variable in your file, and update it with a method that fires with every packet that arrives from the server, and use that value wherever you want within your file.

example:
Lua:
local attributesWindow = nil
local attributesButton = nil

local storageValue = -1

function init()
    connect(LocalPlayer, {
        onHealthChange = onHealthChange,
    })

    connect(g_game, {
        onGameStart = refresh,
        onGameEnd = offline
    })

    attributesButton = modules.client_topmenu.addRightGameToggleButton('attributesButton', tr('Attributes'), '/images/topbuttons/attributes', toggle, false, 1)
    attributesButton:setOn(true)
    attributesWindow = g_ui.loadUI('attributes', modules.game_interface.getRightPanel())

    attributesWindow:setup()
end

function terminate()
    disconnect(LocalPlayer, {
        onHealthChange = onHealthChange,
    })

    disconnect(g_game, {
        onGameStart = refresh,
        onGameEnd = offline
    })

    attributesWindow:destroy()
    attributesButton:destroy()
end

function refresh()
    local player = g_game.getLocalPlayer()
    if not player then return end
    onHealthChange(player, player:getHealth(), player:getMaxHealth())
    onStaminaChange(player, 30, 100)
    local contentsPanel = attributesWindow:getChildById('contentsPanel')
    attributesWindow:setContentMinimumHeight(44)
    attributesWindow:setContentMaximumHeight(390)
    ProtocolGame.registerExtendedOpcode(55, receiveStorageFromServer)
end

function offline()
    ProtocolGame.unregisterExtendedOpcode(55, true)
end

function toggle()
    if attributesButton:isOn() then
        attributesWindow:close()
        attributesButton:setOn(false)
    else
        attributesWindow:open()
        attributesButton:setOn(true)
    end
end

function onMiniWindowClose()
    attributesButton:setOn(false)
end

function setAttributeValue(id, value)
    local attribute = attributesWindow:recursiveGetChildById(id)
    local widget = attribute:getChildById('value')
    widget:setText(value)
end

function setAttributeColor(id, value)
    local attribute = attributesWindow:recursiveGetChildById(id)
    local widget = attribute:getChildById('value')
    widget:setColor(value)
end

function resetAttributeColor(id)
    local attribute = attributesWindow:recursiveGetChildById(id)
    local widget = attribute:getChildById('value')
    widget:setColor('#bbbbbb')
end

function checkAlert(id, value, maxValue, threshold, greaterThan)
    if greaterThan == nil then greaterThan = false end
    local alert = false
    if type(maxValue) == 'boolean' then
        if maxValue then
            return
        end
        if greaterThan then
            if value > threshold then
                alert = true
            end
        else
            if value < threshold then
                alert = true
            end
        end
    elseif type(maxValue) == 'number' then
        if maxValue < 0 then
            return
        end
        local percent = math.floor((value / maxValue) * 100)
        if greaterThan then
            if percent > threshold then
                alert = true
            end
        else
            if percent < threshold then
                alert = true
            end
        end
    end
    if alert then
        setAttributeColor(id, '#b22222') -- red
    else
        resetAttributeColor(id)
    end
end

function onHealthChange(localPlayer, value, maxValue)
    setAttributeValue('health', value)
    checkAlert('health', value, maxValue, 30)
end

function onStaminaChange(localPlayer, value, maxValue)
    setAttributeValue('stamina', value)
    checkAlert('stamina', value, maxValue, 30)
end

function receiveStorageFromServer(protocol, opcode, buffer)
    storageValue = tonumber(buffer)
end
 
Maybe there are better ways to do it, but a quick and easy way is to keep a local variable in your file, and update it with a method that fires with every packet that arrives from the server, and use that value wherever you want within your file.

example:
Lua:
local attributesWindow = nil
local attributesButton = nil

local storageValue = -1

function init()
    connect(LocalPlayer, {
        onHealthChange = onHealthChange,
    })

    connect(g_game, {
        onGameStart = refresh,
        onGameEnd = offline
    })

    attributesButton = modules.client_topmenu.addRightGameToggleButton('attributesButton', tr('Attributes'), '/images/topbuttons/attributes', toggle, false, 1)
    attributesButton:setOn(true)
    attributesWindow = g_ui.loadUI('attributes', modules.game_interface.getRightPanel())

    attributesWindow:setup()
end

function terminate()
    disconnect(LocalPlayer, {
        onHealthChange = onHealthChange,
    })

    disconnect(g_game, {
        onGameStart = refresh,
        onGameEnd = offline
    })

    attributesWindow:destroy()
    attributesButton:destroy()
end

function refresh()
    local player = g_game.getLocalPlayer()
    if not player then return end
    onHealthChange(player, player:getHealth(), player:getMaxHealth())
    onStaminaChange(player, 30, 100)
    local contentsPanel = attributesWindow:getChildById('contentsPanel')
    attributesWindow:setContentMinimumHeight(44)
    attributesWindow:setContentMaximumHeight(390)
    ProtocolGame.registerExtendedOpcode(55, receiveStorageFromServer)
end

function offline()
    ProtocolGame.unregisterExtendedOpcode(55, true)
end

function toggle()
    if attributesButton:isOn() then
        attributesWindow:close()
        attributesButton:setOn(false)
    else
        attributesWindow:open()
        attributesButton:setOn(true)
    end
end

function onMiniWindowClose()
    attributesButton:setOn(false)
end

function setAttributeValue(id, value)
    local attribute = attributesWindow:recursiveGetChildById(id)
    local widget = attribute:getChildById('value')
    widget:setText(value)
end

function setAttributeColor(id, value)
    local attribute = attributesWindow:recursiveGetChildById(id)
    local widget = attribute:getChildById('value')
    widget:setColor(value)
end

function resetAttributeColor(id)
    local attribute = attributesWindow:recursiveGetChildById(id)
    local widget = attribute:getChildById('value')
    widget:setColor('#bbbbbb')
end

function checkAlert(id, value, maxValue, threshold, greaterThan)
    if greaterThan == nil then greaterThan = false end
    local alert = false
    if type(maxValue) == 'boolean' then
        if maxValue then
            return
        end
        if greaterThan then
            if value > threshold then
                alert = true
            end
        else
            if value < threshold then
                alert = true
            end
        end
    elseif type(maxValue) == 'number' then
        if maxValue < 0 then
            return
        end
        local percent = math.floor((value / maxValue) * 100)
        if greaterThan then
            if percent > threshold then
                alert = true
            end
        else
            if percent < threshold then
                alert = true
            end
        end
    end
    if alert then
        setAttributeColor(id, '#b22222') -- red
    else
        resetAttributeColor(id)
    end
end

function onHealthChange(localPlayer, value, maxValue)
    setAttributeValue('health', value)
    checkAlert('health', value, maxValue, 30)
end

function onStaminaChange(localPlayer, value, maxValue)
    setAttributeValue('stamina', value)
    checkAlert('stamina', value, maxValue, 30)
end

function receiveStorageFromServer(protocol, opcode, buffer)
    storageValue = tonumber(buffer)
end

You added an example in the wrong script. I'm using the following script, so I don't know how to update the status:
Lua:
attributesWindow = nil
attributesButton = nil

function init()
  connect(LocalPlayer, {
    onHealthChange = onHealthChange,
    onStaminaChange = onStaminaChange,
  })
  connect(g_game, {
    onGameStart = refresh,
    onGameEnd = offline
  })

  attributesButton = modules.client_topmenu.addRightGameToggleButton('attributesButton', tr('Attributes'), '/images/topbuttons/attributes', toggle, false, 1)
  attributesButton:setOn(true)
  attributesWindow = g_ui.loadUI('attributes', modules.game_interface.getRightPanel())
 
  attributesWindow:setup()
end

function terminate()
  disconnect(LocalPlayer, {
    onHealthChange = onHealthChange,
    onStaminaChange = onStaminaChange,
  })
  disconnect(g_game, {
    onGameStart = refresh,
    onGameEnd = offline
  })

  attributesWindow:destroy()
  attributesButton:destroy()
end

function refresh()
  local player = g_game.getLocalPlayer()
  local protocolGame = g_game.getProtocolGame()
 
  if not player then return end
 
  onHealthChange(player, player:getHealth(), player:getMaxHealth())
 
  function receiveStorageFromServer(protocol, opcode, packet)
    onStaminaChange(player, packet, 100)
  end

  local contentsPanel = attributesWindow:getChildById('contentsPanel')
  attributesWindow:setContentMinimumHeight(44)
  attributesWindow:setContentMaximumHeight(390)
 
  ProtocolGame.registerExtendedOpcode(55, receiveStorageFromServer)
end

function offline()
    ProtocolGame.unregisterExtendedOpcode(55, true)
end

function toggle()
  if attributesButton:isOn() then
    attributesWindow:close()
    attributesButton:setOn(false)
  else
    attributesWindow:open()
    attributesButton:setOn(true)
  end
end

function onMiniWindowClose()
    attributesButton:setOn(false)
end

function setAttributeColor(id, value)
  local attribute = attributesWindow:recursiveGetChildById(id)
  local widget = attribute:getChildById('value')
  widget:setColor(value)
end

function resetAttributeColor(id)
  local attribute = attributesWindow:recursiveGetChildById(id)
  local widget = attribute:getChildById('value')
  widget:setColor('#bbbbbb')
end

function onHealthChange(localPlayer, value, maxValue)
  setAttributeValue('health', value)
  checkAlert('health', value, maxValue, 30)
end

function onStaminaChange(localPlayer, value, maxValue)
  setAttributeValue('stamina', value)
  checkAlert('stamina', value, maxValue, 30)
end

function setAttributeValue(id, value)
  local attribute = attributesWindow:recursiveGetChildById(id)
  local widget = attribute:getChildById('value')
  widget:setText(value)
end

function checkAlert(id, value, maxValue, threshold, greaterThan)
  if greaterThan == nil then greaterThan = false end
  local alert = false

  if type(maxValue) == 'boolean' then
    if maxValue then
      return
    end

    if greaterThan then
      if value > threshold then
        alert = true
      end
    else
      if value < threshold then
        alert = true
      end
    end
  elseif type(maxValue) == 'number' then
    if maxValue < 0 then
      return
    end

    local percent = math.floor((value / maxValue) * 100)
    if greaterThan then
      if percent > threshold then
        alert = true
      end
    else
      if percent < threshold then
        alert = true
      end
    end
  end

  if alert then
    setAttributeColor(id, '#b22222') -- red
  else
    resetAttributeColor(id)
  end
end
 
I made some changes to my script.
At first it receives the correct value from my server, however, when it executes the "scheduleEvent" function, the value is blank.

Lua:
attributesWindow = nil
attributesButton = nil

updateEvent = nil

function init()
    connect(LocalPlayer, {
        onHealthChange = onHealthChange,
        onStaminaChange = onStaminaChange,
    } )
    connect(g_game, {
        onGameStart = refresh,
        onGameEnd = offline
    } )

    attributesButton = modules.client_topmenu.addRightGameToggleButton('attributesButton', tr('Attributes'), '/images/topbuttons/attributes', toggle, false, 1)
    attributesButton:setOn(true)
    attributesWindow = g_ui.loadUI('attributes', modules.game_interface.getRightPanel())
    g_keyboard.bindKeyDown('Ctrl+I', toggle)

    attributesWindow:setup()
end

function terminate()
    disconnect(LocalPlayer, {
        onHealthChange = onHealthChange,
        onStaminaChange = onStaminaChange,
    } )
    disconnect(g_game, {
        onGameStart = refresh,
        onGameEnd = offline
    } )

    g_keyboard.unbindKeyDown('Ctrl+I')

    removeEvent(updateEvent)

    attributesWindow:destroy()
    attributesButton:destroy()
end

function refresh()
    local player = g_game.getLocalPlayer()
    local protocolGame = g_game.getProtocolGame()

    if not player then return end

    onHealthChange(player, player:getHealth(), player:getMaxHealth())

    function receiveStorageFromServer(protocol, opcode, packet)
        function updateRegister(protocol, opcode, packet)
            onStaminaChange(player, packet, 100)
            removeEvent(updateEvent)
            updateEvent = scheduleEvent(updateRegister, 1000)
            print("Testing!")
        end
        updateRegister(protocol, opcode, packet)
    end

    local contentsPanel = attributesWindow:getChildById('contentsPanel')
    attributesWindow:setContentMinimumHeight(44)
    attributesWindow:setContentMaximumHeight(390)

    ProtocolGame.registerExtendedOpcode(55, receiveStorageFromServer)
end

function offline()
    ProtocolGame.unregisterExtendedOpcode(55, true)
end

function toggle()
    if attributesButton:isOn() then
        attributesWindow:close()
        attributesButton:setOn(false)
    else
        attributesWindow:open()
        attributesButton:setOn(true)
    end
end

function onMiniWindowClose()
    attributesButton:setOn(false)
end

function setAttributeColor(id, value)
    local attribute = attributesWindow:recursiveGetChildById(id)
    local widget = attribute:getChildById('value')
    widget:setColor(value)
end

function resetAttributeColor(id)
    local attribute = attributesWindow:recursiveGetChildById(id)
    local widget = attribute:getChildById('value')
    widget:setColor('#bbbbbb')
end

function onHealthChange(localPlayer, value, maxValue)
    setAttributeValue('health', value)
    checkAlert('health', value, maxValue, 30)
end

function onStaminaChange(localPlayer, value, maxValue)
    setAttributeValue('stamina', value)
    --checkAlert('stamina', value, maxValue, 30)
end

function setAttributeValue(id, value)
    local attribute = attributesWindow:recursiveGetChildById(id)
    local widget = attribute:getChildById('value')
    widget:setText(value)
end

function checkAlert(id, value, maxValue, threshold, greaterThan)
    if greaterThan == nil then greaterThan = false end
    local alert = false

    if type(maxValue) == 'boolean' then
        if maxValue then
            return
        end

        if greaterThan then
            if value > threshold then
                alert = true
            end
        else
            if value < threshold then
                alert = true
            end
        end
    elseif type(maxValue) == 'number' then
        if maxValue < 0 then
            return
        end

        local percent = math.floor((value / maxValue) * 100)
        if greaterThan then
            if percent > threshold then
                alert = true
            end
        else
            if percent < threshold then
                alert = true
            end
        end
    end

    if alert then
        setAttributeColor(id, '#b22222')
        -- red
    else
        resetAttributeColor(id)
    end
end

I know that the "scheduleEvent" function is working correctly, because I had it print the word "testing", and it is printing every second, as I configured.
 
this can also be an option:
Lua:
function receiveStorageFromServer(protocol, opcode, packet)
    local player = g_game.getLocalPlayer()
    if player then
        onHealthChange(player, player:getHealth(), player:getMaxHealth())
        onStaminaChange(player, tonumber(packet), 100)
    end
end

function refresh()
    local player = g_game.getLocalPlayer()
    if not player then return end

    local contentsPanel = attributesWindow:getChildById('contentsPanel')
    attributesWindow:setContentMinimumHeight(44)
    attributesWindow:setContentMaximumHeight(390)

    ProtocolGame.registerExtendedOpcode(55, receiveStorageFromServer)
end

I don't know much about OTC methods, so sorry if I don't fully understand what you want.
Remember that the game_skills module controls all the character's stat bars, if you don't override the stamina bar, then your script won't work correctly.
The call is only activated when the server sends the information to the client, so if you want to update it from time to time you must do it on the server or do ping pong.

Lua:
function onExtendedOpcode(cid, opcode, buffer)
    if opcode == 55 then
        local storageValue = getCreatureStorage(cid, ATTR_STAMINA_STORAGE_ID)
        doPlayerSendExtendedOpcode(cid, 55, tostring(storageValue))
    end
    return true
end

Lua:
updateEvent = nil
function update()
    updateEvent = scheduleEvent(update, 1000)
    local protocolGame = g_game.getProtocolGame()
    if protocolGame then
        protocolGame:sendExtendedOpcode(55, '')
    end
end

function receiveStorageFromServer(protocol, opcode, packet)
    local player = g_game.getLocalPlayer()
    if player then
        onHealthChange(player, player:getHealth(), player:getMaxHealth())
        onStaminaChange(player, tonumber(packet), 100)
    end
end

function refresh()
    local player = g_game.getLocalPlayer()
    if not player then return end

    local contentsPanel = attributesWindow:getChildById('contentsPanel')
    attributesWindow:setContentMinimumHeight(44)
    attributesWindow:setContentMaximumHeight(390)

    ProtocolGame.registerExtendedOpcode(55, receiveStorageFromServer)
    updateEvent = scheduleEvent(update, 200)
end
 
Last edited:
Solution
Finally it worked 100%!
Thank you very much!!
I've already taken the opportunity to organize the script in a way that is easy to read:
Lua:
attributesWindow = nil
attributesButton = nil

updateEvent = nil

function toggle()
    if attributesButton:isOn() then
        attributesWindow:close()
        attributesButton:setOn(false)
    else
        attributesWindow:open()
        attributesButton:setOn(true)
    end
end

function offline()
    ProtocolGame.unregisterExtendedOpcode(55, true)
end

function setAttributeValue(id, value)
    local attribute = attributesWindow:recursiveGetChildById(id)
    local widget = attribute:getChildById('value')
    widget:setText(value)
end

function onHealthChange(localPlayer, value) setAttributeValue('health', value) end
function update()
    updateEvent = scheduleEvent(update, 250)
    local protocolGame = g_game.getProtocolGame()
    if protocolGame then
        protocolGame:sendExtendedOpcode(55, '')
    end
end

function onStaminaChange(localPlayer, value) setAttributeValue('stamina', value) end
function receiveStaminaAttFromServer(protocol, opcode, packet)
    local player = g_game.getLocalPlayer()
    if player then
        onStaminaChange(player, tonumber(packet))
    end
end

function refresh()
    local player = g_game.getLocalPlayer()
    if not player then return end
    
    onHealthChange(player, player:getHealth(), player:getMaxHealth())

    local contentsPanel = attributesWindow:getChildById('contentsPanel')
    attributesWindow:setContentMinimumHeight(44)
    attributesWindow:setContentMaximumHeight(390)

    ProtocolGame.registerExtendedOpcode(55, receiveStaminaAttFromServer)
    updateEvent = scheduleEvent(update, 250)
end

function init()
    connect(LocalPlayer, {
        onHealthChange = onHealthChange,
    } )
    connect(g_game, {
        onGameStart = refresh,
        onGameEnd = offline
    } )

    attributesButton = modules.client_topmenu.addRightGameToggleButton('attributesButton', tr('Attributes'), '/images/topbuttons/attributes', toggle, false, 1)
    attributesButton:setOn(true)
    attributesWindow = g_ui.loadUI('attributes', modules.game_interface.getRightPanel())
    g_keyboard.bindKeyDown('Ctrl+I', toggle)

    attributesWindow:setup()
end

function terminate()
    disconnect(LocalPlayer, {
        onHealthChange = onHealthChange,
    } )
    disconnect(g_game, {
        onGameStart = refresh,
        onGameEnd = offline
    } )

    g_keyboard.unbindKeyDown('Ctrl+I')

    removeEvent(updateEvent)

    attributesWindow:destroy()
    attributesButton:destroy()
end

function onMiniWindowClose()
    attributesButton:setOn(false)
end
 
Back
Top