Checking if data has been added to a table

This is my data table, that is given to everybody when they first join the game. It has all the default data, numbers, etc. That a beginning player should have

local data = {
	Classes = {
		Knight = {
			ID = 1,
			Owned = true,
			Weapons = {
				'Sword',
			},
			Armours = {
				'Griswold',
			},
			Equipped = {
				Weapon = 'Sword', 
				Armour = 'Griswold',
			},
		},
		Archer = {
			ID = 2,
			Owned = true,
			Weapons = {
				'Bow',
			},
			Armours = {
				'Iron',
			},
			Equipped = {
				Weapon = 'Bow', 
				Armour = 'Iron',
			},
		},
		Scout = {
			ID = 3,
			Owned = false,
			Weapons = {
				'Dagger',
			},
			Armours = {
				'Finn',
			},
			Equipped = {
				Weapon = 'Dagger', 
				Armour = 'Finn', 
			},
		},
		Hunter = {
			ID = 4,
			Owned = false,
			Weapons = {
				'Crossbow',
			},
			Armours = {
				'Bombo',
			},
			Equipped = {
				Weapon = 'Crossbow', 
				Armour = 'Bombo', 
			},
		},
	},
	
	Trails = {
		'None',
	},
	
	EquippedClass = 'Knight',
	EquippedTrail = 'None',
	
	Level = 1,
	Exp = 0,
	Gold = 0,
	Gems = 0,
	Games = 0,
	Wins = 0,
	Losses = 0,
	Kills = 0,
	Deaths = 0,	
}

return data

Now, a problem I faced in the past and coming back again to be a problem, is when edits are made to the tables within the data. Changing the Gold, ID and Owned inside the classes, etc. all saved perfectly fine. However, when changes are made to a table inside it then they don’t load properly.

This is what I do to load in the data when a plyaer joins

function updateObject(old, new)
    local update = {}
    for i, v in next, new do
        update[i] = (type(v) == 'table' and old[i] and updateObject(old[i], v)) or old[i] or v
	end
    return update
end

function dataManager:GetData(player)
	local loadJSON = playerDataStore:GetAsync(player.UserId)
	local setData = (loadJSON and httpService:JSONDecode(loadJSON)) or {}
	
	playersData[player.UserId] = updateObject(setData, data)

	dataReady = true
end

So the problem arises in this updateObject() function. I had put this in a while back, because I was having problems of if a player joins, they get given that default data, but if I ADD a new section to the data, they will still load their old original data table. So the point of this updateObject() function was to look for changes inside the default data and add them to the players previously saved data. However, this ends up reverting any tables in the function back to the default data.

The example being, if I was to add a weapon to the Knight class, so

table.insert(data.Classes.Knight.Weapons, 'New sword')

then it does add this new item to the data.Classes.Knight.Weapons but this gets wiped when the updateObject() function is called

3 Likes

I can’t quite determine the correlation between the thread’s title and the post contents, so I’m not quite sure what is the real problem or desire here.

As far as I’m aware, there is no way to check for changes in a table outside of using metamethods. What you’d want to use is the __newindex metamethod, which determines what to do if something tries to add to the table. It’s like, you can process or do whatever you want first before setting something.

I don’t know if this is a good idea, but it’s the closest I could get to detecting changes within a table with the knowledge I currently possess. Hope someone could correct me if I’m not using metamethods right in this circumstance as well.

local function proxyTable(originalTable)
	return setmetatable({}, {
		__index = originalTable,
		__newindex = function(self, index, value)
			print(originalTable[index], "update")
			originalTable[index] = value
			print(originalTable[index], "new")
		end
	})
end

local yes = proxyTable({})
yes["hi"] = "no"
yes["hi"] = "yes"

--[[ Output:

First Call: yes["hi"] = "no"
--> nil update
--> no new

Second Call: yes["hi"] = "yes"
--> no update
--> yes new

For your updateObject function, I’m not quite sure how to follow it - it’s 5:00 AM, so I’m wrapping up posting on any unread Scripting Support topics before signing off. Looks like quite some complicated re-entry stuff though. I assume there may need to be variable rework done somewhere here.

2 Likes

I can probably try explaining it a bit more as I’ve been testing for a while since.

Let’s say this is the players data when they first join

data = {
    Classes = {
        Knight = {
            Weapons = {
                'Sword',
            }
        }
    }
}

Now if they leave and rejoin, it’s gonna load this data back. Now when I add ‘New Sword’ to that list:

data = {
    Classes = {
        Knight = {
            Weapons = {
                'Sword', 'New Sword'
            }
        }
    }
}

And the player rejoins, the updateObject() will notice a change in the players data and add the New Sword to the players data. However, if I just keep it to ‘Sword’ and then insert ‘New Sword’ through some code instead of just inserting it into the default table:

table.insert(data.Classes.Knight.Weapons, 'New Sword')

and then leave and come back, it doesn’t appear in the players data.

When I did this:

local loadJSON = playerDataStore:GetAsync(player.UserId)
print(loadJSON)

It shows that ‘New Sword’ was added to the list, however for some reason updateObject() removes it from the list. The answer would seem obvious, just remove the whole updateObject() function and just go

local loadData = playerDataStore:GetAsync(player.UserId)
playersData[player.UserId] = loadData 

Which this, yes, works. BUT as I mentioned earlier, the point of updateObject was to add any data that may be missing from a players previous saved data.

So if a player joins, they get given the default data table, and when they rejoin they get given their saved table. But if I make additions to the default table, then they won’t be given them and they’ll be stuck with old data. So it was either wipe all their data when I add new stuff to the data table, or find a way to compare their current data with the default data table, and add in any data they might be missing from previous saves.

I’m hoping this is making sense :sweat_smile:

I actually dealt with this problem the other day. Here’s a function that I came up with to solve this problem. It compares the current data with a backbone, and updates the value(s) if it does not have them.

To call it, just do deep_update(BackboneTable, PlayerData) and it will return the updated data.

local function deep_update(backbone, update)
	local new = update
	
	if not update then return backbone end
	
	local function recurse(tab, ...)
		local args = {...}
		local target = new
		
		for i = select("#", ...), 1, -1 do
			target = target[args[i]]
		end
		
		for i,v in tab do
			if type(v) ~= "table" or (type(v) == "table" and target[i] == nil) then
				target[i] = v
			else
				recurse(tab[i], i, ...)
			end
		end
	end

	recurse(backbone)
	
	return new
end

Also, for dealing with player data, I highly recommend using DataStore2. I’ve used it on previous games with no issues at all, and I’m using it on a game I’m working on right now too.

4 Likes

Little confused by the term ‘backbone’

Is that the default one or the players previous saved data??

Backbone = Default data

I’m getting a problem where when I make an edit inside the table it reverts back when rejoining.

In more detail, I can add certain things inide a table, so I can add multiple weapons, like so

data = {
    Classes = {
        Knight = {
            Weapons = {
                'Sword', 'Sword2', 'Sword3'
            }
        }
    }
}

But if I have something like this

data = {
    Classes = {
        Knight = {
            Equipped = 'Sword'
        }
    }
}

And I change that Equipped variable through scripts and leave and then use Crazyman32’s datastore viewer to see the saved item, and it says the new equipped item. But when I rejoin it gets reverted back to the default ‘Sword’

EDIT Fixed it, works when backbone is the players saved data, update is the default data

So I think you should do it like this:

table.insert(playerData[player.userId].Knight.Weapons, 'NewSword')

-- When done
dataManager:SaveData(playerData[player.userId])

Oops, I’ll fix that right now

1 Like
local ZombieClass = {}
ZombieClass.__index = ZombieClass


function ZombieClass.new()
	local Zombie = setmetatable({}, ZombieClass)
	Zombie.MaxHealth = 100
	Zombie.MaxStamina = 200
	
	Zombie.Stamina = 200
	Zombie.Health = 100
	
	local ReadOnly = {
		MaxHealth = true,
		MaxStamina = true
	}
	
	local mt = {
		__index = Zombie,
		__newindex = function(tab, index, value)
			local found = false
			if ReadOnly[index] ~= nil then
				error("The value is ReadOnly")
			else
				print("The Zombie object was just changed")
			end	
		end
	}
	
	return setmetatable({}, mt)
end



local Zombie = ZombieClass.new()
print(Zombie.MaxHealth)
print(Zombie.Stamina)

Zombie["New Sword"] = "Lol"
Zombie.Stamina = 20
Zombie.MaxHealth = 40


--[[
	Output:
	
	print(Zombie.MaxHealth) : 100
	print(Zombie.Stamina) : 200
	
	Zombie["New Sword"] = "Lol" : Triggers the change
	Zombie.Stamina = 20 : Triggers the change
	Zombie.MaxHealth = 40 : Throws an error
--]]

In this example i detect changes to a zombie object created by a class. Seems to be working fine based on the pretty quick testing i did. I hope this sample will help you. You basically let a metatable act as a proxy.

1 Like