• 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 My onPositionChange and isInArea being called every time

henkas

Well-Known Member
Joined
Jul 8, 2015
Messages
993
Solutions
5
Reaction score
55
Hey, I'm facing an issue that I can't fix. I've tried almost everything, but I can't find a solution. Basically, this script displays text when entering a described area, which is from the top-left corner to the bottom-right corner. So, when you enter the area, it shows the text, which is perfectly fine. However, if you move within this area, it still keeps displaying the text when it shouldn't. This is because I'm already inside the area, and there is no point in displaying the text every time I move inside. It should only display if I leave the area and re-enter. I don't know why it keeps displaying it with every step I make.

Lua:
function isInArea(position, fromPos, toPos)
  local x = position.x
  local y = position.y
  local z = position.z
  if z ~= fromPos.z and z ~= toPos.z then
    return false
  end
  return x >= fromPos.x and x <= toPos.x and y >= fromPos.y and y <= toPos.y
end

function onPositionChange(player, newPos, oldPos)
  local isInAnyArea = false

  for i = 1, #areas do
    local area = areas[i]

    if isInArea(newPos, area.from, area.to) then
      if currentArea ~= area.name then
        currentArea = area.name
        nameLabel:setText(area.name)
        nameLabel:unlock()
        g_effects.fadeIn(window, 750)
        
        if fadeOutEvent then
          removeEvent(fadeOutEvent)
        end

        fadeOutEvent = scheduleEvent(function()
          currentArea = ""
          
          g_effects.fadeOut(window, 750)
          
          if fadeOutEvent then
            removeEvent(fadeOutEvent)
          end
        end, 3000)

        isInAnyArea = true
        break
      else
        isInAnyArea = true
        break
      end
    end
  end
 
Just create a variable outside of the function scope and check its not the same as the current area.

Also, never create a function within another function, it's just a waste to keep recreating it. (although inline addEvent functions are kinda ok, but still preferable to reference an already created func).

I would also probably add a 5 second exhaust or something, so it will prevent multiple firings when walking between two areas...

Try something like this(untested):
Lua:
local currentArea = nil
local fadeOutEvent = nil

function isInArea(position, fromPos, toPos)
    local x, y = position.x, position.y
    return x >= fromPos.x and x <= toPos.x and y >= fromPos.y and y <= toPos.y
end

function fadeOut()
    if not fadeOutEvent then
        return
    end
 
    removeEvent(fadeOutEvent)
    g_effects.fadeOut(window, 750)
    fadeOutEvent = nil
end

function onPositionChange(player, newPos, oldPos)
    for index, area in ipairs(areas) do
        if isInArea(newPos, area.from, area.to) and currentArea ~= index then
            currentArea = index
            nameLabel:setText(area.name)
            nameLabel:unlock()
            g_effects.fadeIn(window, 750)

            fadeOutEvent = scheduleEvent(fadeOut, 3000)
        end
    end
end
 
Last edited:
A bit better but now it shows just one time even if you leave it and re-enter it doesnt show the name anymore
 
A bit better but now it shows just one time even if you leave it and re-enter it doesnt show the name anymore
Sure, just base it on time instead, you can change the required interval at the first line:

Lua:
local interval = 7 --seconds

local lastAreaEntered = 0
local fadeOutEvent = nil

function isInArea(position, fromPos, toPos)
    local x, y = position.x, position.y
    return x >= fromPos.x and x <= toPos.x and y >= fromPos.y and y <= toPos.y
end

function fadeOut()
    if not fadeOutEvent then
        return
    end
 
    removeEvent(fadeOutEvent)
    g_effects.fadeOut(window, 750)
    fadeOutEvent = nil
end

function onPositionChange(player, newPos, oldPos)
    local timeNow = os.time()
    if timeNow < lastAreaEntered + interval then
        return
    end

    for index, area in ipairs(areas) do
        if isInArea(newPos, area.from, area.to) then
            lastAreaEntered = timeNow
            nameLabel:setText(area.name)
            nameLabel:unlock()
            g_effects.fadeIn(window, 750)

            fadeOutEvent = scheduleEvent(fadeOut, 3000)
        end
    end
end
 
Sure, just base it on time instead, you can change the required interval at the first line:

Lua:
local interval = 7 --seconds

local lastAreaEntered = 0
local fadeOutEvent = nil

function isInArea(position, fromPos, toPos)
    local x, y = position.x, position.y
    return x >= fromPos.x and x <= toPos.x and y >= fromPos.y and y <= toPos.y
end

function fadeOut()
    if not fadeOutEvent then
        return
    end
 
    removeEvent(fadeOutEvent)
    g_effects.fadeOut(window, 750)
    fadeOutEvent = nil
end

function onPositionChange(player, newPos, oldPos)
    local timeNow = os.time()
    if timeNow < lastAreaEntered + interval then
        return
    end

    for index, area in ipairs(areas) do
        if isInArea(newPos, area.from, area.to) then
            lastAreaEntered = timeNow
            nameLabel:setText(area.name)
            nameLabel:unlock()
            g_effects.fadeIn(window, 750)

            fadeOutEvent = scheduleEvent(fadeOut, 3000)
        end
    end
end
So this brings to issue number 1 where you can walk in the area and still get
nameLabel:setText(area.name)
 
In fact, you could do a hybrid of both time and checking current v last area:

Lua:
local interval = 7
local lastArea = {
    name = nil,
    timestamp = 0,
    event = nil
}

function isInArea(position, fromPos, toPos)
    local x, y = position.x, position.y
    return x >= fromPos.x and x <= toPos.x and y >= fromPos.y and y <= toPos.y
end

function fadeOut()
    if not lastArea.event then
        return
    end
 
    removeEvent(lastArea.event)
    g_effects.fadeOut(window, 750)
    lastArea.event = nil
end

function onPositionChange(player, newPos, oldPos)
    local currentArea = nil
    for _, area in ipairs(areas) do
        if isInArea(newPos, area.from, area.to) then
            currentArea = area
        end
    end

    local timeNow = os.time()
    if not currentArea
    or currentArea.name == lastArea.name
    or (currentArea.name ~= lastArea.name and timeNow < lastArea.timestamp + interval) then
        return
    end

    nameLabel:setText(currentArea.name)
    nameLabel:unlock()
    g_effects.fadeIn(window, 750)

    lastArea = {
        name = currentArea.name,
        timestamp = timeNow,
        event = scheduleEvent(fadeOut, 3000)
    }
end
Post automatically merged:

So this brings to issue number 1 where you can walk in the area and still get
Unfortunately, I've already explained Im not going to help you with that in a previous thread. But happy to help you and fix this issue ^^

Updated the above code.
 
Last edited:
In fact, you could do a hybrid of both time and checking current v last area:

Lua:
local interval = 7
local lastArea = {
    name = nil,
    timestamp = nil,
    event = nil
}

function isInArea(position, fromPos, toPos)
    local x, y = position.x, position.y
    return x >= fromPos.x and x <= toPos.x and y >= fromPos.y and y <= toPos.y
end

function fadeOut()
    if not lastArea.event then
        return
    end
 
    removeEvent(lastArea.event)
    g_effects.fadeOut(window, 750)
    lastArea.event = nil
end

function onPositionChange(player, newPos, oldPos)
    local currentArea = nil
    for _, area in ipairs(areas) do
        if isInArea(newPos, area.from, area.to) then
            currentArea = area
        end
    end

    local timeNow = os.time()
    if not currentArea
    or currentArea.name == lastArea.name
    or (currentArea.name ~= lastArea.name and timeNow < lastArea.timestamp + interval) then
        return
    end

    nameLabel:setText(currentArea.name)
    nameLabel:unlock()
    g_effects.fadeIn(window, 750)

    lastArea = {
        name = currentArea.name,
        timestamp = timeNow,
        event = scheduleEvent(fadeOut, 3000)
    }
end
Post automatically merged:


Unfortunately, I've already explained Im not going to help you with that in a previous thread. But happy to help you and fix this issue ^^

Updated the above code.
timestamp is a nil value
 
oh yeah sorry, change to 0 from nil on line 4. Also, double check the code is the same as i've updated it multiple times above
nameLabel:setText(area.name) is being displayed just once, even if you re-enter the area
 
nameLabel:setText(area.name) is being displayed just once, even if you re-enter the area
Lua:
local interval = 7
local lastArea = {
    name = nil,
    timestamp = 0,
    event = nil
}

function isInArea(position, fromPos, toPos)
    local x, y = position.x, position.y
    return x >= fromPos.x and x <= toPos.x and y >= fromPos.y and y <= toPos.y
end

function fadeOut()
    if not lastArea.event then
        return
    end
 
    removeEvent(lastArea.event)
    g_effects.fadeOut(window, 750)
    lastArea.event = nil
end

function onPositionChange(player, newPos, oldPos)
    local currentArea = nil
    for _, area in ipairs(areas) do
        if isInArea(newPos, area.from, area.to) then
            currentArea = area
        end
    end
   
    if not currentArea then
        lastArea.name = nil
        lastArea.event = nil
        return
    end

    local timeNow = os.time()
    if currentArea.name == lastArea.name
    or (currentArea.name ~= lastArea.name and timeNow < lastArea.timestamp + interval) then
        return
    end

    nameLabel:setText(currentArea.name)
    nameLabel:unlock()
    g_effects.fadeIn(window, 750)

    lastArea = {
        name = currentArea.name,
        timestamp = timeNow,
        event = scheduleEvent(fadeOut, 3000)
    }
end
 
Lua:
local interval = 7
local lastArea = {
    name = nil,
    timestamp = 0,
    event = nil
}

function isInArea(position, fromPos, toPos)
    local x, y = position.x, position.y
    return x >= fromPos.x and x <= toPos.x and y >= fromPos.y and y <= toPos.y
end

function fadeOut()
    if not lastArea.event then
        return
    end
 
    removeEvent(lastArea.event)
    g_effects.fadeOut(window, 750)
    lastArea.event = nil
end

function onPositionChange(player, newPos, oldPos)
    local currentArea = nil
    for _, area in ipairs(areas) do
        if isInArea(newPos, area.from, area.to) then
            currentArea = area
        end
    end
  
    if not currentArea then
        lastArea.name = nil
        lastArea.event = nil
        return
    end

    local timeNow = os.time()
    if currentArea.name == lastArea.name
    or (currentArea.name ~= lastArea.name and timeNow < lastArea.timestamp + interval) then
        return
    end

    nameLabel:setText(currentArea.name)
    nameLabel:unlock()
    g_effects.fadeIn(window, 750)

    lastArea = {
        name = currentArea.name,
        timestamp = timeNow,
        event = scheduleEvent(fadeOut, 3000)
    }
end
Pretty much perfect, but found a way to bug it, if you enter it and fast leave the area the text freezes permanently until you re-enter the area and you can do it every time
 
Pretty much perfect, but found a way to bug it, if you enter it and fast leave the area the text freezes permanently until you re-enter the area and you can do it every time
Try removing line 33.

I'm not currently at home so I am just typing this from my phone, so I cant test anything I've written. xD
 
is easier to use opcode in a movements onStepIn(actionID on enter area) or use zone by fridai (event="StepIn" your zoneid)

is better than calling onPositionChange every step of the way.
 
Last edited:
is easier to use opcode in a movements onStepIn tile
Easier? Not really. More importantly, is it more efficient? No..

Is there a more efficient way than what I wrote? Yes... you can approach this many different ways. Best way (in regards to future modifications) would be to introduce zoning in both RME/server and then use opcode to just tell the client of a zone change.
 
Try removing line 33.

I'm not currently at home so I am just typing this from my phone, so I cant test anything I've written. xD
Works perfectly
is easier to use opcode in a movements onStepIn(actionID on enter area) or use zone by fridai (event="StepIn" your zoneid)

is better than calling onPositionChange every step of the way.
Easier? Not really. More importantly, is it more efficient? No..

Is there a more efficient way than what I wrote? Yes... you can approach this many different ways. Best way (in regards to future modifications) would be to introduce zoning in both RME/server and then use opcode to just tell the client of a zone change.
Well as far as i know this code shouldnt cause any issues if there isnt many areas added in the table and i gonna have max around 50areas or less, it shouldnt even have any performance hit with 300 areas. Talking about zones and moving it to tfs and sending opcode idk if its good for optimization point doing it trough server side and not just client side but i do have zones in the rme because this feature was introduced Zone System (https://gist.github.com/lyuz1n/2fb07cd6828309941a1f03b385b96a34) so its is possible like you said doing it with zones, but zones cant overlap with each other so if for example you want to zone entire city and that city has already some zones, you cant overlap them so idk if its good option
 
Back
Top