• 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 error [C]: in function 'getSpectatorsInRangeEx' otv8

bpm91

Intermediate OT User
Joined
May 23, 2019
Messages
881
Solutions
7
Reaction score
122
Location
Brazil
YouTube
caruniawikibr
hello, I was testing my client, and when trying to relog a character over the same character in another client, I came across this error that only appears when I do this action of relogging the same player over himself.

1674007329849.png

after black screen, everything returns to normal, however this error appears.

1674007544452.png

stack traceback:
[builtin#146]: at 0x013d7160
[C]: in function 'getSpectatorsInRangeEx'
/modules/game_battle/battle.lua:244: in function 'checkCreatures'
/modules/game_battle/battle.lua:230: in function </modules/game_battle/battle.lua:227>

battle.lua
Lua:
-- functions
function updateBattleList()
  removeEvent(updateEvent)
    updateEvent = scheduleEvent(updateBattleList, 100)
  checkCreatures()
end


I realized that my Store does not open after this event either

Lua:
function toggle()
  if not gameStoreWindow then
    return
  end
  if gameStoreWindow:isVisible() then
    return hide()
  end
  show()
end


function show()
  if not gameStoreWindow or not gameStoreButton then
    return
  end
  gameStoreWindow:getChildById("categories"):getChildByIndex(1):focus()
  hideHistory()
  gameStoreWindow:show()
  gameStoreWindow:raise()
  gameStoreWindow:focus()
end

does anyone know how to fix this?
 
Last edited:
Can you post full store.lua and full battle.lua?
store throw error because it trying to call something like that:

local index
local table = {example1,example2,example3}
print(table[index])

where index is null, but we can't tell why it's null without full scripts.
 
Lua:
battleWindow = nil
battleButton = nil
battlePanel = nil
filterPanel = nil
toggleFilterButton = nil

mouseWidget = nil
updateEvent = nil

hoveredCreature = nil
newHoveredCreature = nil
prevCreature = nil

battleButtons = {}
local ageNumber = 1
local ages = {}

inventoryBattleButton = modules.game_inventory.getBattlez()

function init() 
  g_ui.importStyle('battlebutton')
  battleButton = modules.client_topmenu.addRightGameToggleButton('battleButton', tr('Battle') .. ' (Ctrl+B)', '/images/topbuttons/battle', toggle, false, 2)
  battleButton:setOn(true)
  inventoryBattleButton:setOn(true)
  battleWindow = g_ui.loadUI('battle', modules.game_interface.getRightPanel())
  g_keyboard.bindKeyDown('Ctrl+B', toggle)

  -- this disables scrollbar auto hiding
  local scrollbar = battleWindow:getChildById('miniwindowScrollBar')
  scrollbar:mergeStyle({ ['$!on'] = { }})

  battlePanel = battleWindow:recursiveGetChildById('battlePanel')

  filterPanel = battleWindow:recursiveGetChildById('filterPanel')
  toggleFilterButton = battleWindow:recursiveGetChildById('toggleFilterButton')

  if isHidingFilters() then
    hideFilterPanel()
  end

  local sortTypeBox = filterPanel.sortPanel.sortTypeBox
  local sortOrderBox = filterPanel.sortPanel.sortOrderBox

  mouseWidget = g_ui.createWidget('UIButton')
  mouseWidget:setVisible(false)
  mouseWidget:setFocusable(false)
  mouseWidget.cancelNextRelease = false

  battleWindow:setContentMinimumHeight(80)

  sortTypeBox:addOption('Name', 'name')
  sortTypeBox:addOption('Distance', 'distance')
  sortTypeBox:addOption('Total age', 'age')
  sortTypeBox:addOption('Screen age', 'screenage')
  sortTypeBox:addOption('Health', 'health')
  sortTypeBox:setCurrentOptionByData(getSortType())
  sortTypeBox.onOptionChange = onChangeSortType

  sortOrderBox:addOption('Asc.', 'asc')
  sortOrderBox:addOption('Desc.', 'desc')
  sortOrderBox:setCurrentOptionByData(getSortOrder())
  sortOrderBox.onOptionChange = onChangeSortOrder

  battleWindow:setup()
 
  for i=1,30 do
    local battleButton = g_ui.createWidget('BattleButton', battlePanel)
    battleButton:setup()
    battleButton:hide()
    battleButton.onHoverChange = onBattleButtonHoverChange
    battleButton.onMouseRelease = onBattleButtonMouseRelease
    table.insert(battleButtons, battleButton)
  end
 
  updateBattleList()
 
  connect(LocalPlayer, {
    onPositionChange = onPlayerPositionChange
  })
  connect(Creature, {
    onAppear = updateSquare,
    onDisappear = updateSquare
  }) 
  connect(g_game, {
    onAttackingCreatureChange = updateSquare,
    onFollowingCreatureChange = updateSquare
  })
end

function terminate()
  if battleButton == nil then
    return
  end
 
  battleButtons = {}
 
  g_keyboard.unbindKeyDown('Ctrl+B')
  battleButton:destroy()
  battleWindow:destroy()
  mouseWidget:destroy()
    
  disconnect(LocalPlayer, {
    onPositionChange = onPlayerPositionChange
  })
  disconnect(Creature, {
    onAppear = onCreatureAppear,
    onDisappear = onCreatureDisappear
  }) 
  disconnect(g_game, {
    onAttackingCreatureChange = updateSquare,
    onFollowingCreatureChange = updateSquare
  })

  removeEvent(updateEvent)
end

function toggle()
  if battleButton:isOn() then
    battleWindow:close()
    battleButton:setOn(false)
    inventoryBattleButton:setOn(false)
  else
    battleWindow:open()
    battleButton:setOn(true)
    inventoryBattleButton:setOn(true)
  end
end

function onMiniWindowClose()
  battleButton:setOn(false)
  inventoryBattleButton:setOn(false)
end

function getSortType()
  local settings = g_settings.getNode('BattleList')
  if not settings then
    if g_app.isMobile() then
      return 'distance'
    else
      return 'name'
    end
  end
  return settings['sortType']
end

function setSortType(state)
  settings = {}
  settings['sortType'] = state
  g_settings.mergeNode('BattleList', settings)

  checkCreatures()
end

function getSortOrder()
  local settings = g_settings.getNode('BattleList')
  if not settings then
    return 'asc'
  end
  return settings['sortOrder']
end

function setSortOrder(state)
  settings = {}
  settings['sortOrder'] = state
  g_settings.mergeNode('BattleList', settings)

  checkCreatures()
end

function isSortAsc()
    return getSortOrder() == 'asc'
end

function isSortDesc()
    return getSortOrder() == 'desc'
end

function isHidingFilters()
  local settings = g_settings.getNode('BattleList')
  if not settings then
    return false
  end
  return settings['hidingFilters']
end

function setHidingFilters(state)
  settings = {}
  settings['hidingFilters'] = state
  g_settings.mergeNode('BattleList', settings)
end

function hideFilterPanel()
  filterPanel.originalHeight = filterPanel:getHeight()
  filterPanel:setHeight(0)
  --toggleFilterButton:getParent():setMarginTop(0)
  --toggleFilterButton:setImageClip(torect("0 0 21 12"))
  setHidingFilters(true)
  filterPanel:setVisible(false)
end

function showFilterPanel()
  --toggleFilterButton:getParent():setMarginTop(5)
  filterPanel:setHeight(filterPanel.originalHeight)
  --toggleFilterButton:setImageClip(torect("21 0 21 12"))
  setHidingFilters(false)
  filterPanel:setVisible(true)
end

function toggleFilterPanel()
  if filterPanel:isVisible() then
    hideFilterPanel()
  else
    showFilterPanel()
  end
end

function onChangeSortType(comboBox, option, value)
  setSortType(value:lower())
end

function onChangeSortOrder(comboBox, option, value)
  -- Replace dot in option name
  setSortOrder(value:lower():gsub('[.]', ''))
end

-- functions
function updateBattleList()
  removeEvent(updateEvent)
    updateEvent = scheduleEvent(updateBattleList, 100)
  checkCreatures()
end

function checkCreatures()
  if not battlePanel or not g_game.isOnline() then
    return
  end

  local player = g_game.getLocalPlayer()
  if not player then
    return
  end
 
  local dimension = modules.game_interface.getMapPanel():getVisibleDimension()
  local spectators = g_map.getSpectatorsInRangeEx(player:getPosition(), false, math.floor(dimension.width / 2), math.floor(dimension.width / 2), math.floor(dimension.height / 2), math.floor(dimension.height / 2))
  local maxCreatures = battlePanel:getChildCount()
 
  local creatures = {}
  local now = g_clock.millis()
  local resetAgePoint = now - 250
  for _, creature in ipairs(spectators) do
    if doCreatureFitFilters(creature) and #creatures < maxCreatures then
      if not creature.lastSeen or creature.lastSeen < resetAgePoint then
        creature.screenAge = now       
      end     
      creature.lastSeen = now
      if not ages[creature:getId()] then
        if ageNumber > 1000 then
          ageNumber = 1
          ages = {}
        end
        ages[creature:getId()] = ageNumber
        ageNumber = ageNumber + 1
      end
      table.insert(creatures, creature)   
    end
  end
 
  updateSquare()
  sortCreatures(creatures)
  battlePanel:getLayout():disableUpdates()
 
  -- sorting
  local ascOrder = isSortAsc()
  for i=1,#creatures do 
      local creature = creatures[i]
      if ascOrder then
      creature = creatures[#creatures - i + 1]
      end
    local battleButton = battleButtons[i]     
    battleButton:creatureSetup(creature)
    battleButton:show()
    battleButton:setOn(true)
  end
 
  if g_app.isMobile() and #creatures > 0 then
    onBattleButtonHoverChange(battleButtons[1], true)
  end
    
  for i=#creatures + 1,maxCreatures do
    if battleButtons[i]:isHidden() then break end
    battleButtons[i]:hide()
    battleButton:setOn(false)
  end

  battlePanel:getLayout():enableUpdates()
  battlePanel:getLayout():update()
end

function doCreatureFitFilters(creature)
  if creature:isLocalPlayer() then
    return false
  end
  if creature:getHealthPercent() <= 0 then
    return false
  end

  local pos = creature:getPosition()
  if not pos then return false end

  local localPlayer = g_game.getLocalPlayer()
  if pos.z ~= localPlayer:getPosition().z or not creature:canBeSeen() then return false end

  local hidePlayers = filterPanel.buttons.hidePlayers:isChecked()
  local hideNPCs = filterPanel.buttons.hideNPCs:isChecked()
  local hideMonsters = filterPanel.buttons.hideMonsters:isChecked()
  local hideSkulls = filterPanel.buttons.hideSkulls:isChecked()
  local hideParty = filterPanel.buttons.hideParty:isChecked()

  if hidePlayers and creature:isPlayer() then
    return false
  elseif hideNPCs and creature:isNpc() then
    return false
  elseif hideMonsters and creature:isMonster() then
    return false
  elseif hideSkulls and creature:isPlayer() and creature:getSkull() == SkullNone then
    return false
  elseif hideParty and creature:getShield() > ShieldWhiteBlue then
    return false
  end

  return true
end

local function getDistanceBetween(p1, p2)
    return math.max(math.abs(p1.x - p2.x), math.abs(p1.y - p2.y))
end

function sortCreatures(creatures)
  local player = g_game.getLocalPlayer()
 
  if getSortType() == 'distance' then
    local playerPos = player:getPosition()
    table.sort(creatures, function(a, b)
      if getDistanceBetween(playerPos, a:getPosition()) == getDistanceBetween(playerPos, b:getPosition()) then
        return ages[a:getId()] > ages[b:getId()]
      end
      return getDistanceBetween(playerPos, a:getPosition()) > getDistanceBetween(playerPos, b:getPosition())
    end)
  elseif getSortType() == 'health' then
    table.sort(creatures, function(a, b)
      if a:getHealthPercent() == b:getHealthPercent() then
        return ages[a:getId()] > ages[b:getId()]
      end
      return a:getHealthPercent() > b:getHealthPercent()
    end)
  elseif getSortType() == 'age' then
    table.sort(creatures, function(a, b) return ages[a:getId()] > ages[b:getId()] end)
  elseif getSortType() == 'screenage' then
    table.sort(creatures, function(a, b) return a.screenAge > b.screenAge end)
  else -- name
    table.sort(creatures, function(a, b)
      if a:getName():lower() == b:getName():lower() then
        return ages[a:getId()] > ages[b:getId()]
      end
      return a:getName():lower() > b:getName():lower()
    end)
  end
end

-- other functions
function onBattleButtonMouseRelease(self, mousePosition, mouseButton)
  if mouseWidget.cancelNextRelease then
    mouseWidget.cancelNextRelease = false
    return false
  end
  if not self.creature then
    return false
  end
  if ((g_mouse.isPressed(MouseLeftButton) and mouseButton == MouseRightButton)
    or (g_mouse.isPressed(MouseRightButton) and mouseButton == MouseLeftButton)) then
    mouseWidget.cancelNextRelease = true
    g_game.look(self.creature, true)
    return true
  elseif mouseButton == MouseLeftButton and g_keyboard.isShiftPressed() then
    g_game.look(self.creature, true)
    return true
  elseif mouseButton == MouseRightButton and not g_mouse.isPressed(MouseLeftButton) then
    modules.game_interface.createThingMenu(mousePosition, nil, nil, self.creature)
    return true
  elseif mouseButton == MouseLeftButton and not g_mouse.isPressed(MouseRightButton) then
    if self.isTarget then
      g_game.cancelAttack()
    else
      g_game.attack(self.creature)
    end
    return true
  end
  return false
end

function onBattleButtonHoverChange(battleButton, hovered)
  if not hovered then
    newHoveredCreature = nil   
  else
    newHoveredCreature = battleButton.creature
  end
  if battleButton.isHovered ~= hovered then
    battleButton.isHovered = hovered
    battleButton:update()
  end
  updateSquare()
end

function onPlayerPositionChange(creature, newPos, oldPos)
  addEvent(checkCreatures)
end

local CreatureButtonColors = {
  onIdle = {notHovered = '#afafaf', hovered = '#f7f7f7' },
  onTargeted = {notHovered = '#e04040', hovered = '#f8a4a4' },
  onFollowed = {notHovered = '#40e040', hovered = '#b4f8b4' }
}

function updateSquare()
  local following = g_game.getFollowingCreature()
  local attacking = g_game.getAttackingCreature()
    
  if newHoveredCreature == nil then
    if hoveredCreature ~= nil then
      hoveredCreature:hideStaticSquare()
      hoveredCreature = nil
    end
  else
    if hoveredCreature ~= nil then
      hoveredCreature:hideStaticSquare()
    end
    hoveredCreature = newHoveredCreature
    hoveredCreature:showStaticSquare(CreatureButtonColors.onIdle.hovered)
  end
 
  local color = CreatureButtonColors.onIdle
  local creature = nil
  if attacking then
    color = CreatureButtonColors.onTargeted
    creature = attacking
  elseif following then
    color = CreatureButtonColors.onFollowed
    creature = following
  end

  if prevCreature ~= creature then
    if prevCreature ~= nil then
      prevCreature:hideStaticSquare()
    end
    prevCreature = creature
  end
 
  if not creature then
    return
  end
 
  color = creature == hoveredCreature and color.hovered or color.notHovered
  creature:showStaticSquare(color)
end
Post automatically merged:

maybe found problem here i can found problem in store
 
Last edited:
Do you have any onLogin script on server that executes anything related to 'store'?
onLogin Lua event is executed only, when you login into character. When someone logins into logged character, it just replaces connection on server side to new game client, but does not execute logout/login scripts. Maybe there is something in onLogin script that sends some data to client ex. 'transferable coins count' etc.
 
yes i have
player:registerEvent("GameStoreLogin")
Post automatically merged:

is there any way to fix this?
 
Last edited:
yes i have
player:registerEvent("GameStoreLogin")
Post automatically merged:

is there any way to fix this?
You must create new event 'onConnect' and call it from C++, when new client connects to player already online.
All tibia protocol related packets required by tibia client are called from C++ ex. 'send all items and creatures on screen' etc.
 
you know where i found this function? have idea?

i found this on my protocolgame.cpp
XML:
void ProtocolGame::onConnect()
{
    /*auto output = OutputMessagePool::getOutputMessage();
    static std::random_device rd;
    static std::ranlux24 generator(rd());
    static std::uniform_int_distribution<uint16_t> randNumber(0x00, 0xFF);


    // Skip checksum
    output->skipBytes(sizeof(uint32_t));


    // Packet length & type
    output->add<uint16_t>(0x0006);
    output->addByte(0x1F);


    // Add timestamp & random number
    challengeTimestamp = static_cast<uint32_t>(time(nullptr));
    output->add<uint32_t>(challengeTimestamp);


    challengeRandom = randNumber(generator);
    output->addByte(challengeRandom);


    // Go back and write checksum
    output->skipBytes(-12);
    output->add<uint32_t>(adlerChecksum(output->getOutputBuffer() + sizeof(uint32_t), 8));


    send(output);*/
}

all the code is commented. it comes from tfs 1.5 original nekiro
Post automatically merged:

just a question, should I no longer have the onlogin option? just onconect?
 
Last edited:
HELLO, I DON'T KNOW IF IT WORKS FOR YOU BUT I INSTALLED THE FOLLOWING FEATURE LINK AND IT GAVE ME THAT ERROR, I UNINSTALLED IT AND AFTER THAT THAT PROBLEM WAS NO MORE PRESENTED
 
i have this function :/
IDK if there were any fixes in recent versions of OTCv8, but when I've tested it around 2 years ago it was bugged (sendFeatures from server).
I had to remove it and configure 'features' on client side, not load them from server.
 
if i remove no have connection the server with client..
i will try
Post automatically merged:

I can't do what you sent me, because my tfs already has this thing implemented because the show ping didn't work, so I would have to have more modifications in the base, I did that and an error appeared for several different files
 
Last edited:
Can someone help me? I'm still having this problem, I have the login functions in the store, but when the character is already logged in, the script gives an error because it's like a kick or reconect and not login, but how can I fix this function?

my store and my frags are buggy because both have a login function
 
initial code store
Lua:
local GAME_STORE = nil

local LoginEvent = CreatureEvent("GameStoreLogin")

function LoginEvent.onLogin(player)
    player:registerEvent("GameStoreExtended")
    return true
end

function gameStoreInitialize()
    GAME_STORE = {
        categories = {},
        offers = {}
    }
extended function to have tfs connection
Lua:
local ExtendedEvent = CreatureEvent("GameStoreExtended")

function ExtendedEvent.onExtendedOpcode(player, opcode, buffer)
    if opcode == ExtendedOPCodes.CODE_GAMESTORE then
        if not GAME_STORE then
            gameStoreInitialize()
            addEvent(refreshPlayersPoints, 10 * 1000)
        end

        local status, json_data =
            pcall(
            function()
                return json.decode(buffer)
            end
        )
        if not status then
            return
        end

        local action = json_data.action
        local data = json_data.data
        if not action or not data then
            return
        end
    

        if action == "fetch" then
            gameStoreFetch(player)
        elseif action == "purchase" then
            gameStorePurchase(player, data)
        elseif action == "gift" then
            gameStorePurchaseGift(player, data)
        elseif action == "giftCoins" then
            gameStoreGiftCoins(player, data)
        end
    end
end

why servers manage to have store/frags/ping and mine doesn't work? any problem in tf 1.5? could someone help me to solve? the problem is when logging into a character that is already online.
LoginEvent:type("login")
LoginEvent:register()
ExtendedEvent:type("extendedopcode")
ExtendedEvent:register()

an option for this to be resolved would be to block the player from being able to log in a character that is already logged in, so he would not be able to relogin a character that is still online. however that would be bad. but it would be an alternative.
can someone help
 
Lua:
void ProtocolGame::sendFeatures()
{
    if (!otclientV8)
        return;

    std::map<GameFeature, bool> features;

    if (features.empty())
        return;

    auto msg = getOutputBuffer(1024);
    msg->addByte(0x43);
    msg->add<uint16_t>(features.size());
    for (auto& feature : features) {
        msg->addByte((uint8_t)feature.first);
        msg->addByte(feature.second ? 1 : 0);
    }

    send(std::move(getCurrentBuffer())); // send this packet immediately
}

try
 
Back
Top