• 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!

parseAreaMap - extract positions from "map" table

Colandus

Advanced OT User
Senator
Joined
Jun 6, 2007
Messages
2,434
Solutions
19
Reaction score
218
Location
Sweden
This function will convert a table like this:
Lua:
local areaMap = {
    {1,2,0,2,1},
    {2,0,0,0,2},
    {0,0,3,0,0},
    {2,0,0,0,2},
    {1,2,0,2,1}
}

Into this:
Lua:
{
   [1] = {
      {x=-2, y=-2},
      {x=2, y=-2},
      {x=2, y=2},
      {x=-2, y=2}
   },
   [2] = {
      {x=-2, y=-1},
      {x=-2, y=-1},
      .... ETC (all coordinates where "2" is, relative to center "3").
   },
}
(Note: you will not get the center position into the table.)

It's also possible to set center position coordinates, which would instead return a table with real positions, and not positions relative to the area maps center.

You can define the number which represents the center position on your own. Also, to use numbers above 10, I recommend using variables such as A, B, C etc... Or even starting from 1, considering it might make your area map seem clearer.



Usage:

parseAreaMap(areaMapTable[, centerVal=2])
parseAreaMap(areaMapTable[, centerPos[, centerVal=2] ])

Parameters:
areaMapTable: A multi-dimensional table consisting of values (preferably numbers).
centerPos: [OPTIONAL]: The center position (e.g. a creature or tile position)
centerVal: [OPTIONAL]: The number representing center position in the area map (default 2)

Return value: Table consisting of tables with Position objects.



Example:
Lua:
local areaMap = {
    {1,2,0,2,1},
    {2,0,0,0,2},
    {0,0,3,0,0},
    {2,0,0,0,2},
    {1,2,0,2,1}
}

local positions = parseAreaMap(areaMap, player:getPosition(), 3) -- setting 3 as center
local firstPositions = positions[1]
local secondPositions = positions[2]

for _, position in ipairs(firstPositions) do
    position:sendMagicEffect(CONST_ME_MAGIC_BLUE)
    -- and some more code here
end

for _, position in ipairs(secondPositions) do
    position:sendMagicEffect(CONST_ME_MAGIC_GREEN)
    -- some other code here.....
end

Or, you might loop all areas at the same time:
Lua:
local positionEffects = {[1] = CONST_ME_MAGIC_BLUE, [2] = CONST_ME_MAGIC_GREEN}

local areaPositions = parseAreaMap(areaMap, player:getPosition(), 3)
for i, positions in ipairs(areaPositions) do
    for _, position in ipairs(positions) do
        position:sendMagicEffect(positionEffects[i]) -- send magic effect based on value on area map...
    end
end


The function:
Lua:
function parseAreaMap(t, ...)
    if type(t) ~= 'table' then
        return false, error("Error: table expected for 'parseAreaMap', got " .. type(t), 2)
    end

    local args = {...}
    local centerVal, centerPos
    if type(args[1]) == 'table' then
        centerPos = args[1]
        centerVal = args[2]
    end

    centerVal = centerVal or (not centerPos and args[1]) or 2

    local rawValues, values, center = {}, {}

    for iy, yt in ipairs(t) do
        for ix, v in ipairs(yt) do
            if v ~= 0 then
                if v == centerVal then
                    if center then
                        return error("Error: Duplicate center positions!", 2)
                    end

                    center = Position(-ix, -iy, 0)
                    if(centerPos) then
                        center = centerPos + center
                    end

                    -- With center pos just discovered, correct the raw values and insert into values table.
                    for _, raw in ipairs(rawValues) do
                        values[raw.value] = values[raw.value] or {}
                        table.insert(values[raw.value], Position({x = center.x + raw.x, y = center.y + raw.y, z = center.z}))
                    end
                elseif center then
                    -- We have a center pos, place directly into values.
                    values[v] = values[v] or {}
                    table.insert(values[v], Position({x = center.x + ix, y = center.y + iy, z = center.z}))
                else
                    -- We don't have a center pos yet. Store raw.
                    table.insert(rawValues, {value = v, x = ix, y = iy})
                end
            end
        end
    end

    if not center then
        return false, error("Error: no center position found!", 2)
    end

    return values
end



Any questions, bugs, suggestions, please reply.
 
Last edited:
This function does the same as this guy charges 20€ for over here :eek: Maybe I should make a business of selling overpriced functions? :D I would be rich!

And this would be an almost complete conversion of his code (working, but missing a function, so it will only show effect but do no damage):
Code:
local n = 0
local areaT = {
    {n,n,4,4,4,n,n},
    {n,4,3,3,3,4,n},
    {4,3,2,2,2,3,4},
    {4,3,2,1,2,3,4},
    {4,3,2,2,2,3,4},
    {n,4,3,3,3,4,n},
    {n,n,4,4,4,n,n},
}

function touchExplosiveBarrel(barrel)
    local barrelPos = barrel:getPosition()
    local area = parseAreaMap(areaT, barrelPos, 1)
    local timer = 5000

    -- good job expensive scripter filling the queue stack with events!!
    for i, posT in pairs(area) do
        for _, pos in pairs(posT) do
            addEvent(Position.sendMagicEffect, timer + (i-1)*150, pos, CONST_ME_FIREAREA)
        end
    end

    for msgDelay = 1000, timer, 1000 do
        local secondsLeft = (timer - msgDelay) /1000
        local msg =  secondsLeft.." seconds left"
   
        if secondsLeft == 0 then msg = "Kaboom!" end
        addEvent(text, msgDelay, msg, barrelPos)
    end
end

function text(msg, pos)
    local players = Game.getSpectators(pos, false, true)
    for _, player in ipairs(players) do
        player:say(msg, TALKTYPE_MONSTER_SAY, false, player, pos)
    end
end

Anyone who cares could make the "dealDamagePos" function as seen in his original code! This was solely for demonstration and to let you know you have profited 20€ :rolleyes: You're welcome.
 
Last edited:
replace all table.inserts with regular table inserts, e.g:

Code:
local t = {1, 2, 3}
t[#t + 1] = 4

this would improve performance
 
Last edited:
replace all table.inserts with regular table inserts, e.g:

Code:
local t = {1, 2, 3}
t[#t + 1] = 4

this would improve performance
Yes I know. I just feel using inserts are cleaner. The difference is too minimal to pay any great attention to imo.
 
I'd say it looks much cleaner without the function calls. But it's just a personal preference I guess. Also, it'd require a little refactoring changing it.

Performance-wise it doesn't really matter anyhow I guess since you don't even have to think about performance in terms of Lua scripts like these (as far as you don't do anything wrong) . However, regular table inserts are faster than using function calls with table.insert(). This is what I meant @cbrm . Thanks for the harsh comment though :)

good job anyhow
 
I'd say it looks much cleaner without the function calls. But it's just a personal preference I guess. Also, it'd require a little refactoring changing it.

Performance-wise it doesn't really matter anyhow I guess since you don't even have to think about performance in terms of Lua scripts like these (as far as you don't do anything wrong) . However, regular table inserts are faster than using function calls with table.insert(). This is what I meant @cbrm . Thanks for the harsh comment though :)

good job anyhow
Yes, it's certainly a personal preference. I used to be the other way first, hated table.insert. Then I started liking them. I guess I don't like the fact I have to repeat the table name twice (longerTableName[#longerTableName + 1]). If table names are shorter (as in your case, t), I usually go for the inline expression.

Also, I didn't see anyone being harsh. I don't suppose you meant me. I believe cbrm asked out of sincere curiosity.

All feedback is appreciated though. So, thank you.
 
Last edited:
Yes, it's certainly a personal preference. I used to be the other way first, hated table.insert. Then I started liking them. I guess I don't like the fact I have to repeat the table name twice (longerTableName[#longerTableName + 1]). If table names are shorter (as in your case, t), I usually go for the inline expression.

Also, I didn't see anyone being harsh. I don't suppose you meant me. I believe cbrm asked out of sincere curiosity.

All feedback is appreciated though. So, thank you.
dunno, just got the general impression of sarcasm from his comment "really?". It didn't feel like a question or sense of surprise at all. If it was, I'm sorry for the misunderstanding.
 
I just didn't find any basis for that, I also prefer using table.insert over the other one.
Anyways, not a big deal for the script performance, imo. Sorry if it sounded harsh.
 
Your parse function can give same final result as getAreaPos(), but it not the same thing.
getAreaPos() in other words is sequence mapping with few features not shown in the example code.
Yours formats the areaT to position map.

Why act like you know my functions and say something like this?
This function does the same as this guy charges 20€ for over here :eek: Maybe I should make a business of selling overpriced functions? :D I would be rich!
I charge per hour, not per script/function. I guess it only took you max 1 hour to do the above code, which in my world would be 5€
Let customers/donators/collaborators decide am I overpriced or not.

Other than that, your code looks good to me.

suggestions:
make area rotateable

questions:

-- good job expensive scripter filling the queue stack with events!!
What do you mean by that?
If I'm doing something wrong, care to explain and improve instead of stacking events yourself xD
 
Your parse function can give same final result as getAreaPos(), but it not the same thing.
getAreaPos() in other words is sequence mapping with few features not shown in the example code.
Yours formats the areaT to position map.

Why act like you know my functions and say something like this?
I charge per hour, not per script/function. I guess it only took you max 1 hour to do the above code, which in my world would be 5€
Let customers/donators/collaborators decide am I overpriced or not.

Other than that, your code looks good to me.

suggestions:
make area rotateable

questions:


What do you mean by that?
If I'm doing something wrong, care to explain and improve instead of stacking events yourself xD

You should only call 1 event and inside the event callback call the event again. No it took me max 30 min. Rotateable is easy. I will add it later. No I still won't need 4h to make what you did. But that's not important. Regardless I wouldn't charge someone for 4h for a function. Doesn't make much sense.

Whatever.
 
Last edited:
This function will convert a table like this:
Code:
local areaMap = {
    {1,2,0,2,1},
    {2,0,0,0,2},
    {0,0,3,0,0},
    {2,0,0,0,2},
    {1,2,0,2,1}
}

Into this:
Code:
{
   [1] = {
      {x=-2, y=-2},
      {x=2, y=-2},
      {x=2, y=2},
      {x=-2, y=2}
   },
   [2] = {
      {x=-2, y=-1},
      {x=-2, y=-1},
      .... ETC (all coordinates where "2" is, relative to center "3").
   },
}
(Note: you will not get the center position into the table.)

It's also possible to set center position coordinates, which would instead return a table with real positions, and not positions relative to the area maps center.

You can define the number which represents the center position on your own. Also, to use numbers above 10, I recommend using variables such as A, B, C etc... Or even starting from 1, considering it might make your area map seem clearer.



Usage:

parseAreaMap(areaMapTable[, centerVal=2])
parseAreaMap(areaMapTable[, centerPos[, centerVal=2] ])

Parameters:
areaMapTable: A multi-dimensional table consisting of values (preferably numbers).
centerPos: [OPTIONAL]: The center position (e.g. a creature or tile position)
centerVal: [OPTIONAL]: The number representing center position in the area map (default 2)

Return value: Table consisting of tables with Position objects.



Example:
Code:
local areaMap = {
    {1,2,0,2,1},
    {2,0,0,0,2},
    {0,0,3,0,0},
    {2,0,0,0,2},
    {1,2,0,2,1}
}

local positions = parseAreaMap(areaMap, player:getPosition(), 3) -- setting 3 as center
local firstPositions = positions[1]
local secondPositions = positions[2]

for _, position in ipairs(firstPositions) do
    position:sendMagicEffect(CONST_ME_MAGIC_BLUE)
    -- and some more code here
end

for _, position in ipairs(secondPositions) do
    position:sendMagicEffect(CONST_ME_MAGIC_GREEN)
    -- some other code here.....
end

Or, you might loop all areas at the same time:
Code:
local positionEffects = {[1] = CONST_ME_MAGIC_BLUE, [2] = CONST_ME_MAGIC_GREEN}

local areaPositions = parseAreaMap(areaMap, player:getPosition(), 3)
for i, positions in ipairs(areaPositions) do
    for _, position in ipairs(positions) do
        position:sendMagicEffect(positionEffects[i]) -- send magic effect based on value on area map...
    end
end


The function:
Code:
function parseAreaMap(t, ...)
    if type(t) ~= 'table' then
        return false, error("Error: table expected for 'parseAreaMap', got " .. type(t))
    end

    local args = {...}
    local centerVal, centerPos
    if type(args[1]) == 'table' then
        centerPos = args[1]
        centerVal = args[2]
    end

    centerVal = centerVal or (not centerPos and args[1]) or 2
 
    local rawValues, values, center = {}, {}

    for iy, yt in ipairs(t) do
        for ix, v in ipairs(yt) do
            if v ~= 0 then
                if v == centerVal then
                    if center then
                        return error("Error: Duplicate center positions!")
                    end

                    center = Position(-ix, -iy, 0)
                    if(centerPos) then
                        center = centerPos + center
                    end

                    -- With center pos just discovered, correct the raw values and insert into values table.
                    for areaVal, posT in pairs(rawValues) do
                        values[areaVal] = {}
                        for _, pos in ipairs(posT) do
                            table.insert(values[areaVal], Position({x = center.x + pos.x, y = center.y + pos.y, z = center.z}))
                        end
                    end
                elseif center then
                    -- We have a center pos, place directly into values.
                    if not values[v] then values[v] = {} end
                    table.insert(values[v], Position({x = center.x + ix, y = center.y + iy, z = center.z}))
                else
                    -- We don't have a center pos yet. Store raw.
                    if not rawValues[v] then rawValues[v] = {} end
                    table.insert(rawValues[v], {x = ix, y= iy})
                end
            end
        end
    end

    if not center then
        return false, error("Error: no center position found!")
    end

    return values
end



Any questions, bugs, suggestions, please reply.

As you asked for suggestions:

Here:
Code:
for areaVal, posT in pairs(rawValues) do
    values[areaVal] = {}
    for _, pos in ipairs(posT) do
        table.insert(values[areaVal], Position({x = center.x + pos.x, y = center.y + pos.y, z = center.z}))
    end
end

Instead of storing the raw position in a table indexed by the value, you store a table that has the raw and the value so you will need only 1 for to parse the raw values.

Here:
Code:
if not values[v] then values[v] = {} end

More readable:
Code:
values[v] = values[v] or {}

And for me it's silly to have a function that loops through a table, to generate another table that will also be looped to do something, instead you could make a function that you pass the map and a table looking something like:
Code:
{
    [1] = function(pos) dosomethingwithpos(pos) end,
    [2] = function(pos) doshitwithpos(pos) end,
}
Then for every 1 and 2 values in the table those functions are going to be called passing the position as parameter.

It would make it easier to use imo.

@cbrm https://springrts.com/wiki/Lua_Perf...ng_Table_Items_.28table.insert_vs._.5B_.5D.29
 
As you asked for suggestions:

Here:
Code:
for areaVal, posT in pairs(rawValues) do
    values[areaVal] = {}
    for _, pos in ipairs(posT) do
        table.insert(values[areaVal], Position({x = center.x + pos.x, y = center.y + pos.y, z = center.z}))
    end
end

Instead of storing the raw position in a table indexed by the value, you store a table that has the raw and the value so you will need only 1 for to parse the raw values.

Here:
Code:
if not values[v] then values[v] = {} end

More readable:
Code:
values[v] = values[v] or {}

And for me it's silly to have a function that loops through a table, to generate another table that will also be looped to do something, instead you could make a function that you pass the map and a table looking something like:
Code:
{
    [1] = function(pos) dosomethingwithpos(pos) end,
    [2] = function(pos) doshitwithpos(pos) end,
}
Then for every 1 and 2 values in the table those functions are going to be called passing the position as parameter.

It would make it easier to use imo.

@cbrm https://springrts.com/wiki/Lua_Perf...ng_Table_Items_.28table.insert_vs._.5B_.5D.29

1. I would argue that an if-statement is faster (which I have no proof of though), but then I'd be contradictory (considering my statement on a previous post), so I will agree with you on that.

2. Yes, certainly right. The reason I did so is because I initially made the raw values code before I did the other one, thus I did not consider it.

3. Yes, that would be convenient, however it wouldn't always be preferred. Should definitely be an option though. The reason is because I initially made this function to use them as static positions (basically a tile with some pillars around it) instead of having to write the positions manually. My main concern is also that people will find it too complex to use a callback. OT scripters aren't used to that type of coding. Also it's not always you want entirely different functions for the values, as in the second example. Perhaps an option to return an iterator instead?

Thanks for your feedback, will update once I get home.

Update: Fixed 1 and 2. I am not sure about 3. Need to figure what options to make. Input, @MatheusMkalo?
 
Last edited:
You should only call 1 event and inside the event callback call the event again. No it took me max 30 min. Rotateable is easy. I will add it later. No I still won't need 4h to make what you did. But that's not important. Regardless I wouldn't charge someone for 4h for a function. Doesn't make much sense.

Whatever.
Once again you assume you know everything what does getAreaPos() do. Yeah your function can be used for one of the things what getAreaPos() does, it's still not the same thing.
Just leave it be, don't compare cog with clock.

About events. I still don't understand.
Why would I make event what loops trough positions and puts events in sequence instead of what I did before (loop trough positions and sequence the events)

Or you mean that if 2 or more different positions should execute at the same time, I should group them into 1 addEvent?
In that case I just prefer handling every position 1 by 1, code looks better and less complex + I haven't found a problem in performance by making addEvent for each position.
 
Awesome release @Colandus as always, but the code is kinda sophisticated, it could be much more cleaner by finding the centerPos before generating the positions. For example:
Code:
do
   local function findCenterPos(t, centerValue)
        for iy, yt in ipairs(t) do
            for ix, v in ipairs(yt) do
                if v == centerValue then
                    return iy, ix
                end
            end
        end
        return nil
    end

    function parseAreaMap(t, centerValue)
        local positions = {}
        local centerX, centerY = findCenterPos(t, centerValue)
        for iy, yt in ipairs(t) do
            for ix, v in ipairs(yt) do
                if v ~= 0 and v ~= centerValue then
                    local pos = Position(centerX-ix, centerY-iy, 0)
                    if not positions[v] then
                        positions[v] = {}
                    end
                    table.insert(positions[v], pos)
                end
            end
        end
        return positions
    end
end

Here:
Code:
if not values[v] then values[v] = {} end
More readable:
Code:
values[v] = values[v] or {}
Personally, i don't see how ternary operator would be more readable than an if statement in this case.

And for me it's silly to have a function that loops through a table, to generate another table that will also be looped to do something, instead you could make a function that you pass the map and a table looking something like:
@Colandus provided a useful function that reduces alot of code complexity and fits the majority of people. Unfortunately, creating a code that fit everyone's needs and preference too is impossible. So, you can always modify the function or override it to fit your needs and/or preference. For example:
Code:
do
    local oldParseAreaMap = parseAreaMap
    function parseAreaMap(t, centerValue, posFuncs)
        local areaPositions = oldParseAreaMap(t, centerValue)
        for i, positions in ipairs(areaPositions) do
            for _, pos in ipairs(positions) do
                local func = posFuncs[i]
                if func then
                    func(pos)
                end
            end
        end
    end
end
And, if you want to handle raised errors by yourself i suggest encapsulating the code and use pcall instead of direct function call.
 
Last edited:
Awesome release @Colandus as always, but the code is kinda sophisticated, it could be much more cleaner by finding the centerPos before generating the positions. For example:
Code:
do
   local function findCenterPos(t, centerValue)
        for iy, yt in ipairs(t) do
            for ix, v in ipairs(yt) do
                if v == centerValue then
                    return iy, ix
                end
            end
        end
        return nil
    end

    function parseAreaMap(t, centerValue)
        local positions = {}
        local centerX, centerY = findCenterPos(t, centerValue)
        for iy, yt in ipairs(t) do
            for ix, v in ipairs(yt) do
                if v ~= 0 and v ~= centerValue then
                    local pos = Position(centerX-ix, centerY-iy, 0)
                    if not positions[v] then
                        positions[v] = {}
                    end
                    table.insert(positions[v], pos)
                end
            end
        end
        return positions
    end
end


Personally, i don't see how ternary operator would be more readable than an if statement in this case.


@Colandus provided a useful function that reduces alot of code complexity and fits the majority of people. Unfortunately, creating a code that fit everyone's needs and preference too is impossible. So, you can always modify the function or override it to fit your needs and/or preference. For example:
Code:
do
    local oldParseAreaMap = parseAreaMap
    function parseAreaMap(t, centerValue, posFuncs)
        local areaPositions = oldParseAreaMap(t, centerValue)
        for i, positions in ipairs(areaPositions) do
            for _, pos in ipairs(positions) do
                local func = posFuncs[i]
                if func then
                    func(pos)
                end
            end
        end
    end
end
And, if you want to handle raised errors by yourself i suggest encapsulating the code and use pcall instead of direct function call.
Thanks. I just didn't want to loop twice just to find the center. I'm not sure how important it is that the function is cleaner to that level. Handling the optional parameters could be prettier though.
 
Last edited:
Back
Top