"Removing" a player meta table

I’m trying to get a insight of how OOP works, and I gotta say it’s fun but I couldn’t figure out a way to clear the metatable upon on playerremove, I know how to set it up on playerAdded but not quite on playerremoved since it’s linked to player.

local RisTal = {}
RisTal.__index = RisTal

function RisTal.New(Player)
	local PlayerMorph = {}
	PlayerMorph.Name = Player.Name
	PlayerMorph.Morph = ""
	setmetatable(PlayerMorph, RisTal)
	return PlayerMorph
end
-- example function
function RisTal:Get(Player: Player?)
	print(self)
	print(self.Morph)
end
return RisTal
1 Like

I’m confused on what you mwan by “clear the metatable”. Are you really sure you want to clear the keys of the metatable? This will cause the class to stop working entirely.

No I just wanna clear the player’s table when they leave, sorta like the opposite of .New

I’m guessing you want to deep clean the table? Here is an example

Complete post

Sorta, yeah. It’s the same as clearing a table of players
Like this:

local P = {}
Players.PlayerAdded:Connect(function(Player)
    table.insert(P, Player)
end)
Players.PlayerRemoving:Connect(function(Player)
	table.remove(P, Player)
end)

You set the metatable to nil to “remove” a metatable.

Correct me if i’m wrong, but wouldn’t that clear every metatable for each player assigned to it?
This is how I set up the metatable btw.

local Handler = require(game:GetService("ServerScriptService").GameHandler.OOPTest)
Players.PlayerAdded:Connect(function(Player)
	Handler.New(Player)
end)
script.Parent.Touched:Connect(function(Player)
	local PlayerReal = Players:GetPlayerFromCharacter(Player.Parent)
	if PlayerReal ~= nil then
		Handler:Get(true)
	end
end)
1 Like

I’m not exactly sure,

But as far as im aware, setting the metatable to nil removes it.

Well I tried it, it kind off works? But another problem occurs is that table now applies to everybody. Like a player wants to change their Morph to “Test”, it would change for every player morph.

Players.PlayerAdded:Connect(function(Player)
	Handler.New(Player)
end)
script.Parent.ClickDetector.MouseClick:Connect(function(Player)
	Handler:Get("string")
end)
script.Parent.ProximityPrompt.TriggerEnded:Connect(function(Player)
	Handler:Change("Lol")
end)
---
function RisTal.New(Player)
	local PlayerMorph = {}
	PlayerMorph.Name = Player.Name
	PlayerMorph.Morphed = false
	PlayerMorph.Morph = ""
	setmetatable(PlayerMorph, RisTal)
	return PlayerMorph
end
function RisTal:Change(Morph: string?)
	self.Morphed = true
	self.Morph = Morph
end

If you be confused, i can explain what happens in the script. Here is the script:

--Module
local RisTal = {}
RisTal.__index = RisTal

function RisTal.New(Player)
	local PlayerMorph = {}
	PlayerMorph.Name = nil
	PlayerMorph.Morph = nil
	setmetatable(PlayerMorph, RisTal)
	return PlayerMorph
end
-- example function
function RisTal:Get()
	print(self)
	print(self.Morph)
end

return RisTal
--ServerScript
local Module = require(script.Parent.ModuleScript)
local ConnectToMetatable = Module.New()

game:GetService("Players").PlayerAdded:Connect(function(Player)
	ConnectToMetatable.Name = Player.Name
	ConnectToMetatable.Morph = ""
	
	ConnectToMetatable:Get()
end)

game:GetService("Players").PlayerRemoving:Connect(function(Player)
	ConnectToMetatable.Name = nil
	ConnectToMetatable.Morph = nil
	
	ConnectToMetatable:Get()
end)

Tried that, however :Change() applies for all players.

function RisTal:Change(Morph: string?)
	self.Morphed = true
	self.Morph = Morph
end
--
local Connection = Handler.New() 
Players.PlayerAdded:Connect(function(Player)
	Connection.Name = Player.Name
	Player.CharacterAdded:Connect(function()
		Connection.Morph = ""
	end)
end)
script.Parent.ProximityPrompt.TriggerEnded:Connect(function(Player)
	Connection:Change(Player.Name.. "test") -- Player1 Triggered proximity meaning it will replace morph for all of players metatable.
end)

@TheH0meLands I’m aware of that, it was just a quick demonstration.

first off, that aint how you do remove. The 2nd argument in .remove is the index. You gotta do

table.remove(P, table.find(P, Player))
``

The class you’ve created is fine but how you’re using it is wrong: in subsequent code samples you’ve shared in the replies, you’re calling the methods at the class-level when you instead want them called at the object-level. What you need is to have a reference to the object.

You can make this as simple or complex as you want, such as storing your cache in the class module itself (not entirely sure I recommend for readability’s sake?) or somewhere else. As an example of how you can write your script’s cache:

local PlayerMorphClass = require(player_morph_class)

local playerMorphs = {}

local function playerAdded(player)
    playerMorphs[player] = PlayerMorphClass.New()
end

local function playerRemoving(player)
    playerMorph[player] = nil
end

Players.PlayerAdded:Connect(playerAdded)
Players.PlayerRemoving:Connect(playerRemoving)
for _, player in Players:GetPlayers() do
    playerAdded(player)
end

You can then reference the playerMorphs table to get the player. I recommend keeping your cache in a ModuleScript if you need multiple scripts to access it.

An example of how you could have a ProximityPrompt handled in the same script:

local function promptTriggered(player)
    local playerMorph = playerMorphs[player]
    if not playerMorph then return end -- No morph object for player

    playerMorph:Change("foobar")
end

ProximityPrompt.Triggered:Connect(promptTriggered)

Personally, I change how I write my classes from the standard paradigm to one that uses a prototype table. For me it’s more of an organisational thing but for others I think it could help identify some very common problems while using OOP, especially when it comes to confusing the class- and object-level. Though really the latter thing is more about deploying your class properly and getting a good explanation rather than having to change the way you approach Luau OOP.

-- Standard paradigm
local Class = {}
Class.__index = Class

-- My edition
local Class = {}
Class.prototype = {}
Class.__index = Class.prototype

function Class.new()
    return setmetatable({}, Class)
end

function Class.prototype:Foobar()
    print("Foobar")
end

local object = Class.new()
object:Foobar() --> Foobar
Class:Foobar() -- This will error
2 Likes

Did you wrote this or did you got from ChatGPT because chatgpt was gave me the same scripts.

Edit: Im just asking by the way. I just wondered.

Very good explanation, thank you so much but I am confusd on this, i thought only one __index is?

Also this. Is it possible to change it to:

function Class.prototype:Foobar(string)
     Class.prototype = string
end
function Class:GetFoobar()
     print(Class.prototype)   -- return Class.prototype
end
--- 
object:Foobar("Test")
object:GetFooBar()

__index by itself means nothing because we’re just setting it as a member of the Class table. __index’s relevance comes into play when you call setmetatable; whatever table is passed as the second argument becomes the metatable, so this makes __index point towards the prototype table.

Regarding the bottom half: the prototype table is only meant to make sure that object-level methods can’t be called from the class-level. This can prevent common mistakes but it’s not necessarily the answer; the main issue for you is that you’re not caching the objects you create from the class for players.

You don’t need to change that bit and you shouldn’t. Foobar is an example function that would be able to be called on an object returned from Class.new. For you, you’d be writing something along the lines of Class.prototype:ChangeMorph. It’s just an alternate suggestion though; ultimately, again, the way you wrote your original class wasn’t wrong, but rather how you deployed it.


I don’t know where you get off accusing me of using ChatGPT. If we’re going to make unsubstantiated accusations of using AI against people for attempting to help, then I worry about the future state of helping users on the forum. Please don’t do this to me or anyone else.

1 Like

Your method worked! I had to rewrote the .New function to meet the new standards!
Edit: Accidentally marked your other comment as solution, my bad. Changed it to the correct one.

function RisTal.New()
	local PlayerMorph = {}
	PlayerMorph.Morphed = false
	PlayerMorph.Morph = ""
	setmetatable(PlayerMorph, RisTal)
	return PlayerMorph
end

Before this topic closes, I have one more question, will this work with global tables? Because I wanna have only one script that sets up classes while allowing others to use the class without the need of creating a new table. Like this:

_G.Classes = {}
Players.PlayerAdded:Connect(function(Player)
	_G.Classes[Player] = CLN.New()
end)
Players.PlayerRemoving:Connect(function(Player)
	_G.Classes[Player] = nil
end)
--different script
local CLN = require(game:GetService("ServerScriptService").Handlers.CFEngine)

script.Parent.ProximityPrompt.TriggerEnded:Connect(function(Player)
	local Class = _G.Classes[Player]
	Class:Get(true)
end)

Although my personal advice to developers is to stay away from using the global table because of the practice implications it brings, namely but not limited to namespace conflictions, yes this will work there as well – it’ll work in any place that supports accepting a table value. I mean you’re still creating a new table at the end of the day though.

3 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.