Remove old metatable before making new one?

When the server loads a character a new table is made in a module script using setmetatable. This table has all the characters stats for the game and their inventory and whatever else. Basically I’ve just now realized that apparently when the player dies it’s not using their new table, but their old one still. So basically on the next game they will have all the items and inventory from the previous game.

function Character.new(Plr, Gamemode: number)--This gets called every time I do player:LoadCharacter() on the server
	local newChar = setmetatable({
		Player = Plr,
		CharStats = CharacterStats.new(Gamemode),--it's really these pesky charstats that don't want to reset despite me setting it to nil down below
	}, Character)
	
	local Humanoid = Plr.Character.Humanoid
	
	table.insert(Characters, newChar)--This characters table is where I put the table to access it later
	
	Humanoid.Died:Connect(function()--This is how I'm currently trying to wipe the players data
		for i = 1, #Characters do
			if Characters[i].Player == Plr then
				Characters[i].Player = nil
				Characters[i] = nil
				newChar.CharStats = nil
				newChar = nil
			end
		end
	end)
end

You can just call setmetatable(t, nil) to remove the metatable instead of setting all properties to nil. Since tables are mutable their data will carry over/mutate because it’s a reference to a sort of table “object”, not just a clone of all the properties.

When the player dies, you could set the metatable to nil and assign them a new one.

Alright I added a line to do that, but the new ‘newChar’ still doesn’t seem to be getting used because it should be a blank CharStats every time, but the stats are still not getting reset. Any ideas?

I’m not too sure by just looking at your script. Are you sure your loop actually finds a match for a character’s player?

What if instead of looking through a table of created characters, you just referenced newChar?

For example

function character.new(player, gamemode)
    -- ...

    humanoid.Died:Connect(function()
        table.clear(newChar)
        setmetatable(newChar, nil)
    end)
end

I also noticed you’re just setting the index to nil without shifting everything after down which would make #t stop at any gap in indexes much like ipairs.

Basically put it this way:

local charTable = {
    [1] = character;
    [2] = character;
    [3] = character;
} 

Then suppose you’re setting charTable[2] to nil,

local charTable = {
    [1] = character;
    [3] = character;
} 

So if you do #charTable, it’ll return 1 because of the missing index. You can use table.remove instead of just setting the index to nil and it’ll shift everything following that index upwards so there isn’t any gap

I think I actually misidentified the issue originally. Basically it’s when I create the newChar table and do CharacterStats.new() I’m using another module that was supposed to return a copy of the table for me to use on the character, but for some reason I think it’s been modifying the original table. I noticed in a multiplayer game if one player picks something up it modifies all their charstats tables (so they must just be using the same one). To try a quick fix I changed it the line ‘CharStats = CharacterStats.new(Gamemode)’ to CharStats = table.clone(CharacterStats.new(Gamemode)), but somehow the base table must be getting modified? So I’m still pretty confused.

Which metamethods are you using? If you’re using __newindex it’s possible it’s writing to the metatable instead of the character table. Could you send how you’re creating/modifying the CharacterStats table?

Here’s the full characterstats module script

local CharacterStats = {}
CharacterStats.__index = CharacterStats

local Gamemodes = {}
Gamemodes.__index = Gamemodes
--Default Gamemode
Gamemodes[1] = {
	Health = 100,
	Armor = 100,
	MaxHealth = 200,
	MaxArmor = 200,
	Inventory = {1,0,0,0,0,0},--Machinegun,Shotgun,Plasma gun,Rocket launcher,Lightning gun,Railgun
	Ammo = {100,0,0,0,0,0},
	MaxAmmo = {200,50,150,50,200,25}
}
--Gamemode2
Gamemodes[2] = table.clone(Gamemodes[1])
Gamemodes[2].Inventory = {1,1,0,0,0,0}
Gamemodes[2].Ammo = {100,25,0,0,0,0}
--Gamemode3
Gamemodes[3] = table.clone(Gamemodes[1])
Gamemodes[3].Health = 200
Gamemodes[3].Armor = 200
Gamemodes[3].Inventory = {1,1,1,1,1,1}
Gamemodes[3].Ammo = {200,50,150,50,200,25}

function CharacterStats.new(Gamemode)
	return Gamemodes[Gamemode]
end

return CharacterStats
``` I would think using table.clone on the returned table would make it impossible for it to be modifying the original though

It doesn’t look to me that you’re overwriting anything in the charstats table. Can you show when you’re creating a new character maybe?

Edit: actually what if when calling CharacterStats.new, you return table.clone(Gamemodes[Gamemode])? I’m not sure if that’ll do anything tho

I think it’s gotta be the above code because I addded the following print:

function CharacterStats.new(Gamemode)
	print(Gamemodes[Gamemode])
	return Gamemodes[Gamemode] -- I also tried table.clone(Gamemodes[Gamemode]) but that didn't work either and it's still printing a modified table
end

It’s not print the default table but an updated one.

Here’s where I spawn a character

local function SpawnCharacter(Player)
	wait(Cooldown)
	Player:LoadCharacter()
	Player.Character.Humanoid.Died:Connect(function()
		SpawnCharacter(Player)
	end)
	_G.ModuleScripts.CharacterModule.new(Player, 2)
end

I’m not too sure now.

When you say an updated table are you talking about one that’s modified further than the one you cloned/overwrote? Can you show the script that actually modifies what’s inside of the player’s CharacterStats? Also can you add a print inside of humanoid.Died to see if that’s firing?

Yes basically a player dies, but when they respawn they do get a new char table, but the new charstats table that gets put inside the ‘newchar’ variable is not the original unmodified one. I only EVER call the charstats.new in the code I showed you already. Then what I do to modify a characters items is just by modifying their char.CharStats variable. Now that you mention it though I had a way of updating the players char table that is likely the culprit somehow or another.

function Character.Update(Player, ReplacementChar)
	for i = 1, #Characters do
		if Characters[i].Player == Player then
			Characters[i] = ReplacementChar
		end
	end
end
``` Still very confusing to me how this could be the culprit but im gonna have to investigate some more

I think I’ve fixed it using a deepcopy function for copying the table.

function deepcopy(orig)
	local orig_type = type(orig)
	local copy
	if orig_type == 'table' then
		copy = {}
		for orig_key, orig_value in next, orig, nil do
			copy[deepcopy(orig_key)] = deepcopy(orig_value)
		end
		setmetatable(copy, deepcopy(getmetatable(orig)))
	else -- number, string, boolean, etc
		copy = orig
	end
	return copy
end


function CharacterStats.new(Gamemode)
	print(Gamemodes[Gamemode])
	local newTable = deepcopy(Gamemodes[Gamemode])
	return newTable
end

Now it prints the default table like it should and the players inventory and stuffs resets when they die like it should thanks for all the help.

2 Likes