OOP Character Inclusion

I’m trying to reference the character in my OOP module script for action functions but it comes as nil in the SetUp function. Everything else prints out correctly except the character reference. Thanks for any help.

local PlayerProfiles = {}

local playerProfile = {}

local profileTable = {}

playerProfile["Name"] = "Loading..."
playerProfile["Ability"] = "Hacker"
playerProfile["UserId"] = nil
playerProfile["Character"] = nil



function PlayerProfiles.new(UserId)
	local function deepCopy(original)
		local copy = {}
		for k, v in pairs(original) do
			copy[k] = type(v) == "table" and deepCopy(v) or v
		end
		return copy
	end

	local newProfile = deepCopy(playerProfile)
	PlayerProfiles[tostring(UserId)] = newProfile

	table.insert(profileTable, UserId) -- Store the UserId, not the whole profile
	print(profileTable)
	return newProfile
end

function PlayerProfiles.getPlayerProfile(plrId)
	local stringId = tostring(plrId)
	if PlayerProfiles[stringId] then
		return PlayerProfiles[stringId]
	else
		print("profile not found")
		return nil -- Return nil if the profile isn't found
	end
end

-- Player Specific

function playerProfile:SetUp(plr)
	self["Name"] = plr.Name
	self["UserId"] = plr.UserId
	self["Character"] = plr.Character
	print(self["Name"])
	print(self["UserId"])
	print(self["Character"])
end

--Player Moves

local active = false
function playerProfile:BasicAttack()
	print("DidSomething")
	print(self["Character"])

		local hitboxCFrame = self["Character"]:FindFirstChild("HumanoidRootPart").CFrame * CFrame.new(0, 0, 6)
		local hitboxSize = Vector3.new(6, 6, 6)

		local createHitbox = workspace:GetPartBoundsInBox(hitboxCFrame, hitboxSize)

		local HumanoidsHit = {}

		for i, v in pairs(createHitbox) do
			if v.Parent:FindFirstChild("Humanoid") and not HumanoidsHit[v.Parent.Name] then
				HumanoidsHit[v.Parent.Name] = true

				v.Parent.Humanoid.Health -= 10
			end
		end

end

return PlayerProfiles

Server Script

local players = game:GetService("Players")
local playerSetup = require(game.ReplicatedStorage["Player Setup"])

players.PlayerAdded:Connect(function(plr)
	playerSetup.new(plr.UserId)

	local playersProfile = playerSetup.getPlayerProfile(plr.UserId)
	playersProfile:SetUp(plr)
	print(playersProfile)
end)

can we see how you call the playerProfile.SetUp function?
send your code that uses this module in order for us to see what’s wrong.

1 Like

This is a script in Server Script Service

local players = game:GetService("Players")
local playerSetup = require(game.ReplicatedStorage["Player Setup"])

players.PlayerAdded:Connect(function(plr)
	playerSetup.new(plr.UserId)

	local playersProfile = playerSetup.getPlayerProfile(plr.UserId)
	playersProfile:SetUp(plr)
	print(playersProfile)
end)

i’m unsure as of why you are doing playerSetup.new(plr.UserId) then disregarding the return value which should be the instantiated oop instance.

i dont know how either of those methods look like (new, getPlayerProfile), so it would also be helpful if you could provide the entire oop module.

Sorry, I thought all of it wouldn’t be needed

local PlayerProfiles = {}

local playerProfile = {}

local profileTable = {}

playerProfile["Name"] = "Loading..."
playerProfile["Ability"] = "Hacker"
playerProfile["UserId"] = nil
playerProfile["Character"] = nil



function PlayerProfiles.new(UserId)
	local function deepCopy(original)
		local copy = {}
		for k, v in pairs(original) do
			copy[k] = type(v) == "table" and deepCopy(v) or v
		end
		return copy
	end

	local newProfile = deepCopy(playerProfile)
	PlayerProfiles[tostring(UserId)] = newProfile

	table.insert(profileTable, UserId) -- Store the UserId, not the whole profile
	print(profileTable)
	return newProfile
end

function PlayerProfiles.getPlayerProfile(plrId)
	local stringId = tostring(plrId)
	if PlayerProfiles[stringId] then
		return PlayerProfiles[stringId]
	else
		print("profile not found")
		return nil -- Return nil if the profile isn't found
	end
end

-- Player Specific

function playerProfile:SetUp(plr)
	self["Name"] = plr.Name
	self["UserId"] = plr.UserId
	self["Character"] = plr.Character
	print(self["Name"])
	print(self["UserId"])
	print(self["Character"])
end

--Player Moves

local active = false
function playerProfile:BasicAttack()
	print("DidSomething")
	print(self["Character"])

		local hitboxCFrame = self["Character"]:FindFirstChild("HumanoidRootPart").CFrame * CFrame.new(0, 0, 6)
		local hitboxSize = Vector3.new(6, 6, 6)

		local createHitbox = workspace:GetPartBoundsInBox(hitboxCFrame, hitboxSize)

		local HumanoidsHit = {}

		for i, v in pairs(createHitbox) do
			if v.Parent:FindFirstChild("Humanoid") and not HumanoidsHit[v.Parent.Name] then
				HumanoidsHit[v.Parent.Name] = true

				v.Parent.Humanoid.Health -= 10
			end
		end

end

return PlayerProfiles

you dont seem to be setting the metatable of oop modules for their return value and also the __index metamethod in the module itself.

calling SetUp on the instance will not work because of this. your code should be structured like this for oop:

local PlayerProfiles = {}
PlayerProfiles.__index = PlayerProfiles

...

function PlayerProfiles.new(UserId)
	...
	return setmetatable(newProfile, PlayerProfiles)
end

...

return PlayerProfiles

the ... refers to original code that i didn’t wanna bother writing out. this will make it so that when you run SetUp on an oop instance the metatable will make it go to the oop method SetUp instead of just in the raw table itself.

let me know how this works for you

1 Like

I’m a little confused on where the newProfile object would come from. (This is my first time trying to use OOP on a project so some parts are modified from a tutorial)

Very questionable OOP style ngl, anyway it’s probably because your setup code runs before the player’s character is loaded, so just do:

--previous code
plr.CharacterAdded:Wait()--wait for char to load
playersProfile:SetUp(plr)

wait until the character is loaded before doing anything, also if the character dies your code will have a reference to an old character, that therefore can’t be garbage collected, just keep that in mind.

Hope this helps!

1 Like

This works too but I would like to know the basic idea of what makes my code different than how its normally done

it is weird, you’re deep copying the playerProfile table so it also deepcopies the functions because if you look closely, you’re not adding the fns to the main class but a separate table you’re returning, so you doesn’t need metatables.

This is not how traditional oop is done though.

1 Like

I used Ludius tutorial on youtube about OOP and modified it to fit my need but he didn’t mention it was different than how its normally done. I see what you mean though

okay, so normally, you have ONE table:

local class = {}

you add an __index metamethod to that table that points to the same table, this way instances of that table can link to the class table.

class.__index = class

then you add your static functions using the dot:

function class.static_fn()
end

and your member functions using colon, because they need the implicit self parameter:

function class:member()
end

all of this is in the same table btw, then finally you add a proper constructor:

function class.new()
   return setmetatable({}, class)--'finish linking' the newly created instance table to the class table so it can access fns
end

then you return the class table for use in other scripts:

return class--at the end of the module

What you did is, you have a separate table for functions, which you deepcopy in the constructor, (this is slower and also requires an extra table) and you put the static fns in a separate table which you return from the module!

edit: here’s something to read: metatables
this will help you understand the ‘linking’, and that it actually is just using luau metatables. __index points to a table that luau will look through if a member isn’t found in self and setmetatable makes it so the self table inherits the __index member from the class table. You can also just manually add the __index but that would not be nice.

edit: here’s what your module would look like in traditional OOP:

local PlayerProfile = {}
PlayerProfile.__index = PlayerProfile

local profileTable = {}

function PlayerProfile.new(player: Player)--passing in the player here is better
	local newProfile = setmetatable({
             Name = player.Name,
             Ability = "Hacker",
             UserId = player.UserId,
             Character = player.Character or player.CharacterAdded:Wait()
        }, PlayerProfile)

        ProfileTable[tostring(player.UserId)] = newProfile
	return newProfile
end

function PlayerProfile.getPlayerProfile(plrId: number)
	return ProfileTable[tostring(plrId)]
end

-- no need for a setup function in traditional oop

local active = false
function playerProfile:BasicAttack()
	print("DidSomething")
	print(self["Character"])

		local hitboxCFrame = self["Character"]:FindFirstChild("HumanoidRootPart").CFrame * CFrame.new(0, 0, 6)
		local hitboxSize = Vector3.new(6, 6, 6)

		local createHitbox = workspace:GetPartBoundsInBox(hitboxCFrame, hitboxSize)

		local HumanoidsHit = {}

		for i, v in pairs(createHitbox) do
			if v.Parent:FindFirstChild("Humanoid") and not HumanoidsHit[v.Parent.Name] then
				HumanoidsHit[v.Parent.Name] = true

				v.Parent.Humanoid.Health -= 10
			end
		end

end

return table.freeze(PlayerProfile)
2 Likes

you already make the newProfile object, there’s nothing new in that regard, unless you are referring to something else?

the only change is that now the module methods are being called with this setmetatable call.
also i made a mistake in my original response, you should have the stored object in the PlayerProfiles also have the metatable since you retrieve it later with getPlayerProfile

Solution
function PlayerProfiles.new(UserId)
	local function deepCopy(original)
		local copy = {}
		for k, v in pairs(original) do
			copy[k] = type(v) == "table" and deepCopy(v) or v
		end
		return copy
	end

	local newProfile = deepCopy(playerProfile)
	setmetatable(newProfile, PlayerProfiles)
	PlayerProfiles[tostring(UserId)] = newProfile

	table.insert(profileTable, UserId) -- Store the UserId, not the whole profile
	print(profileTable)
	return newProfile
end

you should mark an answer if your problem is fixed.

1 Like