• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[Lua] [solved] Lua Grid System - Need help with OOP design

Status
Not open for further replies.

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,565
I don't know what I'm doing, help me!
Lua:
-- This creates a 2d, 3d, etc. array (table). It needs to be above the variable creation
function newAutotable(dim)
    local MT = {};
    for i=1, dim do
        MT[i] = {__index = function(t, k)
            if i < dim then
                t[k] = setmetatable({}, MT[i+1])
                return t[k];
            end
        end}
    end
    return setmetatable({}, MT[1]);
end

-- GRID SYSTEM --

Grids = newAutotable(2) -- Creates a 2d table using [player id]["grid name"]
Grid  = { id, centerX, centerY, width, height, tile }
Tile  = { grid, image, row, column, centerX, centerY, minX, maxX, minY, maxY }

function Grid:new(id, centerX, centerY, width, height)
    local o = {}
    setmetatable(o, self)
    self.__index = self

    self.id = id or 1
    self.centerX = centerX or 128
    self.centerY = centerX or 128
    self.width = width or 5
    self.height = height or 5
    self.tile = {}
    self:createGridTiles(centerX, centerY, width, height)
    return o
end

function Grid:createGridTiles(startX, startY, width, height)
    local i = 0
    local img
    local posX
    local posY
    startX = startX - 256
    startY = startY - 256
    startX = startX - (128 * (width / 2))
    startY = startY - (128 * (height / 2))

    for x = 1, width do
        for y = 1, height do
            i = i + 1
            img = CreateImage("Gridplane.dds",128,128,0,0,0,0,1,1,1,1)
            SetImageRenderAlways(img, true)
            SetImageColor(img, 255, 255, 255, 255)
            posX = startX + x * 128
            posY = startY + y * 128
            SetImagePosition(img, posX, posY, 0)
            local t = Tile:new(self, img, x, y, posX - 64, posY - 64, posX - 128, posY - 128, posX + 128, posY + 128)
            self.tile[i] = t
            print(i)
            print(t)
            print(self.tile[i].centerX.." / "..self.tile[i].centerY)
        end
    end
end

function Tile:new(grid, image, row, column, centerX, centerY, minX, minY, maxX, maxY)
    local o = {}
    setmetatable(o, self)
    self.__index = self

    self.grid = grid
    self.image = image
    self.row = row
    self.column = column
    self.centerX = centerX
    self.centerY = centerY
    self.minX = minX
    self.minY = minY
    self.maxX = maxX
    self.maxY = maxY
    return o
end

function Test()
    local g = Grid:new(1, 128, 128, 5, 5)
    Grids[g.id]["Build"] = g

    print("Loop started")
    for i = 1, 3 do
        -- Get grid owned by player by grid name, then get tile at index, then print tile info
        local g = Grids[1]["Build"]
        local t = g.tile[i]
        print(i)
        print(t)
        print(t.centerX.." / "..t.centerY)
    end
end
I'm trying to create Grid objects which contain a table of Tile objects that contain information about their x/y coordinates and an Image for visual purposes. I use the Grids table to store all of these Grid objects using [player id] as the first index and ["grid name"] as the second index, allowing Players to have as many Grid objects as they want.

The code seems to work fine during the creation process. It creates a Grid for Player 1 which contains a table of Tiles. These Tiles seem to be created properly since they print the correct information about their centerX and centerY coordinates and appear in-game with the correct Image settings.

However, when I loop over some of the Tiles of my Grid at the end of the Test() function I begin to get weird results.

I'm getting the same exact centerX and centerY values for all three Tiles I loop over. These are the values of the very last Tile created for the Grid so I assume the data is somehow being overwritten. What am I doing wrong here? Is it some kind of metatable issue? Is that newAutotable() function funky, I've been using it for a while now without issues.
 
Last edited:
Level 14
Joined
Feb 7, 2020
Messages
387
Lua:
for i = 1, 3 do
        -- Get grid owned by player by grid name, then get tile at index, then print tile info
        local g = Grids[1]["Build"]
        local t = g.tile[i]
        print(i)
        print(t)
        print(t.centerX.." / "..t.centerY)
end

What happens if you change this to:

Lua:
local t
local g_t = Grids[1]["Build"]
for i = 1, 3 do
        -- Get grid owned by player by grid name, then get tile at index, then print tile info
        t = g_t.tile[i]
        print(i)
        print(t)
        print(t.centerX.." / "..t.centerY)
end
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,565
Lua:
for i = 1, 3 do
        -- Get grid owned by player by grid name, then get tile at index, then print tile info
        local g = Grids[1]["Build"]
        local t = g.tile[i]
        print(i)
        print(t)
        print(t.centerX.." / "..t.centerY)
end

What happens if you change this to:

Lua:
local t
local g_t = Grids[1]["Build"]
for i = 1, 3 do
        -- Get grid owned by player by grid name, then get tile at index, then print tile info
        t = g_t.tile[i]
        print(i)
        print(t)
        print(t.centerX.." / "..t.centerY)
end
That doesn't make a difference unfortunately.

I've updated the code to use Bribe's Total Initialization which didn't change the outcome either.
Lua:
do
    OnInit.final(function()

        -- This creates a 2d, 3d, etc. array (table). It needs to be above the variable creation
        function newAutotable(dim)
            local MT = {};
            for i=1, dim do
                MT[i] = {__index = function(t, k)
                    if i < dim then
                        t[k] = setmetatable({}, MT[i+1])
                        return t[k];
                    end
                end}
            end
            return setmetatable({}, MT[1]);
        end

        -- GRID SYSTEM --
        Grids = newAutotable(2) -- Creates a 2d table using [player id]["grid name"]
        Grid  = { id, centerX, centerY, width, height, tile }
        Tile  = { grid, image, row, column, centerX, centerY, minX, maxX, minY, maxY }

        function Grid:new(id, centerX, centerY, width, height)
            local o = {}
            setmetatable(o, self)
            self.__index = self
            self.id = id or 1
            self.centerX = centerX or 128
            self.centerY = centerX or 128
            self.width = width or 5
            self.height = height or 5
            self.tile = {}
            self:createGridTiles(centerX, centerY, width, height)
            print("grid o", o)
            return o
        end

        function Grid:createGridTiles(startX, startY, width, height)
            local i = 0
            local img
            local posX
            local posY
            startX = startX - 256
            startY = startY - 256
            startX = startX - (128 * (width / 2))
            startY = startY - (128 * (height / 2))
            for x = 1, width do
                for y = 1, height do
                    i = i + 1
                    img = CreateImage("Gridplane.dds",128,128,0,0,0,0,1,1,1,1)
                    SetImageRenderAlways(img, true)
                    SetImageColor(img, 255, 255, 255, 255)
                    posX = startX + x * 128
                    posY = startY + y * 128
                    SetImagePosition(img, posX, posY, 0)
                    -- Create the tile object
                    -- ( grid, image, row, column, centerX, centerY, minX, minY, maxX, maxY )
                    self.tile[i] = Tile:new(self, img, x, y, posX, posY, posX - 64, posY - 64, posX + 64, posY + 64)
                    local str = x.."/"..y
                    TextTag(self.tile[i].centerX, self.tile[i].centerY, str)
                    --print(self.tile[i].centerX.." / "..self.tile[i].centerY)
                end
            end
        end

        function Tile:new(grid, image, row, column, centerX, centerY, minX, minY, maxX, maxY)
            local o = {}
            setmetatable(o, self)
            self.__index = self
            self.grid = grid
            self.image = image
            self.row = row
            self.column = column
            self.centerX = centerX
            self.centerY = centerY
            self.minX = minX
            self.minY = minY
            self.maxX = maxX
            self.maxY = maxY
            return o
        end

        function TextTag(x, y, txt)
            local l = Location(x, y)
            local tt = CreateTextTagLocBJ(txt, l, 10, 10, 100, 100, 100, 0)
            RemoveLocation(l)
        end

        -------------------------------------------------
        -- DEMO
        -- ( id, centerX, centerY, width, height )
        local g = Grid:new(1, 128, 128, 5, 5)
        print("grid g", g)

        print("player id", g.id)
        print("center x", g.centerX)
        print("center y", g.centerY)
        print("width", g.width)
        print("height", g.height)
        print("tile count", #g.tile)

        -- Loop over the tiles in the newly created grid
        print("Looping")
        for i = 1, 3 do
            print(g.tile[i].centerX.." / "..g.tile[i].centerY)
        end

    end)
end

The Grid seems to be created just fine and prints the correct information. It's the Tiles that are messed up:
exa.png

For some reason the first three tiles (1/1, 1/2, and 1/3) have the coordinates of the last tile (5/5). It's like all of them have been overwritten.
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,565
Alright, getting rid of the Tile:new function seemed to fix it. Instead, I'm just manually setting up the tiles during the grid creation like this:
Lua:
        function Grid:createGridTiles(startX, startY, width, height)
            local i = 0
            local img
            local posX
            local posY
            startX = startX - 256
            startY = startY - 256
            startX = startX - (128 * (width / 2))
            startY = startY - (128 * (height / 2))
            for x = 1, width do
                for y = 1, height do
                    i = i + 1
                    img = CreateImage("Gridplane.dds",128,128,0,0,0,0,1,1,1,1)
                    SetImageRenderAlways(img, true)
                    SetImageColor(img, 255, 255, 255, 255)
                    posX = startX + x * 128
                    posY = startY + y * 128
                    SetImagePosition(img, posX, posY, 0)

                    -- Create the tile object
                    -- ( grid, image, row, column, centerX, centerY, minX, minY, maxX, maxY )
                    self.tile[i] = {}
                    self.tile[i].grid = self
                    self.tile[i].image = img
                    self.tile[i].row = x
                    self.tile[i].column = y
                    self.tile[i].centerX = posX
                    self.tile[i].centerY = posY
                    self.tile[i].minX = posX - 64
                    self.tile[i].minY = posY - 64
                    self.tile[i].maxX = posX + 64
                    self.tile[i].maxY = posY + 64
                end
            end
        end
Edit: I made a few other changes and the code seems to be working fine now. Here's the updated code:
Lua:
do
    OnInit.final(function()

        -- GRID SYSTEM --
        Grids = {} -- A 2d table storing the grids of each player -> Grids[player id][grid index]:ShowTiles()
        Grid  = { id, centerX, centerY, width, height, tile, isVisible, index } -- An individual grid
        Tile  = { grid, image, row, column, centerX, centerY, minX, maxX, minY, maxY } -- An individual tile
        PlayerGridCount = __jarray(0) -- The number of grids a player owns -> PlayerGridCount[player id]
        LastCreatedGrid = nil -- This is equal to the last created grid
        -- Note: all indexes including player id are indexed starting at 1

        function Grid:Create(id, centerX, centerY, width, height)
            local o = {}
            setmetatable(o, self)
            self.__index = self

            o.id = id
            o.centerX = centerX
            o.centerY = centerX
            o.width = width
            o.height = height
            o.tile = {}
            o.isVisible = false
            o:CreateTiles(centerX, centerY, width, height)

            -- Store the grid for the player
            if PlayerGridCount[id] == 0 then Grids[id] = {} end
            PlayerGridCount[id] = PlayerGridCount[id] + 1
            local count = PlayerGridCount[id]
            Grids[id][count] = o
            o.index = count

            -- A global reference to the last created grid
            LastCreatedGrid = o
            return o
        end

        function Grid:Destroy()
            local id = self.id
            local index = self.index
            local maxIndex = PlayerGridCount[id]

            -- Reduce the grid count by 1
            PlayerGridCount[id] = PlayerGridCount[id] - 1

            -- Destroy all of the grid's tiles
            for i = 1, #self.tile do
                DestroyImage(self.tile[i].image)
                self.tile[i] = nil
            end

            -- Nil the grid
            Grids[id][index] = nil
            self = nil

            -- If this is the last grid in the table then return early
            if index == maxIndex then
                return
            end
   
            -- Otherwise, sort the player's table of grids so that grid 2 becomes the new grid 1, grid 3 becomes the new grid 2, etc
            local nextIndex
            for i = index, maxIndex do
                nextIndex = i + 1
                if nextIndex <= maxIndex then
                    Grids[id][i] = Grids[id][nextIndex]
                    Grids[id][i].index = i
                else
                    Grids[id][maxIndex] = nil
                    return
                end
            end
        end

        function Grid:CreateTiles(startX, startY, width, height)
            local i = 0
            local img
            local posX
            local posY
            startX = startX - 256
            startY = startY - 256
            startX = startX - (128 * (width / 2))
            startY = startY - (128 * (height / 2))
            for x = 1, width do
                for y = 1, height do
                    i = i + 1
                    img = CreateImage("Gridplane.dds",128,128,0,0,0,0,1,1,1,1)
                    SetImageRenderAlways(img, true)
                    SetImageColor(img, 200, 200, 200, 0)
                    posX = startX + x * 128
                    posY = startY + y * 128
                    SetImagePosition(img, posX, posY, 0)

                    -- Create the tile object
                    -- ( grid, image, row, column, centerX, centerY, minX, minY, maxX, maxY )
                    self.tile[i] = {}
                    self.tile[i].grid = self
                    self.tile[i].image = img
                    self.tile[i].row = x
                    self.tile[i].column = y
                    self.tile[i].centerX = posX
                    self.tile[i].centerY = posY
                    self.tile[i].minX = posX - 64
                    self.tile[i].minY = posY - 64
                    self.tile[i].maxX = posX + 64
                    self.tile[i].maxY = posY + 64
                end
            end
        end

        function Grid:ToggleTiles()
            if (self.isVisible) then
                self:HideTiles()
            else
                self:ShowTiles()
            end
        end

        function Grid:ShowTiles()
            if self.isVisible == true then return end
            local t
            local p = Player(self.id - 1)
            self.isVisible = true
            for i = 1, #self.tile do
                t = self.tile[i]
                if GetLocalPlayer() == p then
                    SetImageColor(t.image, 200, 200, 200, 255)
                end
            end
        end

        function Grid:HideTiles()
            if self.isVisible == false then return end
            local t
            local p = Player(self.id - 1)
            self.isVisible = false
            for i = 1, #self.tile do
                t = self.tile[i]
                if GetLocalPlayer() == p then
                    SetImageColor(t.image, 200, 200, 200, 0)
                end
            end
        end

        function Grid:ColorTiles(red, green, blue)
            local t
            local p = Player(self.id - 1)
            local alpha = 0
            if self.isVisible == true then
                alpha = 255
            end
            for i = 1, #self.tile do
                t = self.tile[i]
                if GetLocalPlayer() == p then
                    SetImageColor(t.image, red, green, blue, alpha)
                end
            end
        end

    end)
end
I also attached a map showing how to use it in GUI. It's missing a bunch of nice features but I doubt anyone will even use this so until then it stays this way. Note that it's using Bribe's Total Initialization which can be found in the test map.
 

Attachments

  • Grid OOP.w3m
    35.7 KB · Views: 5
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,565
Avoid running code outside of functions. Warcraft III's Lua implementation seems to act differently during that time, including handles being lost. Use an initialisation function similar to how JASS code worked.
I'm using Bribe's Total Initialization in my final edited code. Do you know if I used it correctly?
Lua:
do
    OnInit.final(function()

        -- I put all of the code here

end
 
Status
Not open for further replies.
Top