Implicit reference in ModuleScript functions

Take the following example code from a ModuleScript called “PlayerFunctions” (has some helpful code that would be relevant for fetching/sending player-related data, whatever):

local PlayerFunctions = {}

-- Option #1: using a method via the . operator
function PlayerFunctions.getStatFromPlayer(plr, statToGet)
	return plr:findFirstChild(statToGet).Value
end

--Option #2: using a function via the : operator
function PlayerFunctions:getStat(statToGet)
	return ["??????"]:findFirstChild(statToGet).Value --idk
end
	
return PlayerFunctions

As you can see, Option #1 and Option #2 are meant to do the same thing.

Option #1 should work fine, since it has plr as an argument; I could use this in another script with no fuss, as long as I inputted a valid Player instance into that parameter.

I am fine with sticking with Option #1, but I am curious to know if there’s a reliable way to do Option #2 (primarily because it just feels less redundant). As far as I can tell, it doesn’t seem to be possible because there’s no way to implicitly reference the “plr” as seen in Option #1.

TL;DR: Is there a way to (in another script ofc) point PlayerFunctions to a Player of interest (maybe via some obscure declaration I’m not aware of) so that I can use PlayerFunctions:doBlah() instead of PlayerFunctions.doBlahTo(plr)?

Just want to add that I’ve done a bit of OOP in a few other coding languages but if the solution is right there and I’m missing it, I apologize, I think ModuleScripts are just tripping me up a bit :stuck_out_tongue:

1 Like

There is not really a good way I can think of.
I’m not sure how much you know exactly, so here’s a quick recap written for someone who understands OOP basics.

function class.Function(self, ...)
is the same as
function class:Function(...)
and in both of these cases, self is all lowercase and refers to class.
They can be switched around, so I can call the first one with the method call syntax : and I can call the second one with the function call syntax . so long as I provide a working self to use.
Here is another example.
game.Destroy(game.Workspace.BasePlate)
Here, I take what is usually performed with : and instead used the function with another self argument, being BasePlate.

If you wanted to use the : method call syntax, you would need PlayerFunctions to be some bizarre, messy, and inefficient metatable so that it can redirect your findFirstChild method call.

Alright, no guarantees, but I think I did this right. It’s late and I haven’t tested it. LocalPlayer won’t work directly with this example. Good luck!

local PlayerObj do -- this creates a throwaway scope to hide all variables involved in class creation and allow us to minimize in the editor

	PlayerObj = {ClassName = "PlayerObj"} -- Table for our object, set static variables.
	_PlayerObj = {} -- Metatable

	function PlayerObj:new(player)
		local object = {}
		object.Player = assert(player and player:IsA("Player"), "Method PlayerObj:new(player) does not have a Player as the first parameter")
		setmetatable(object, _PlayerObj) -- If you don't know metatables, learn. They are fun and almost essential for OOP.
		return object
	end

	function PlayerObj:getStat(statToGet)
		assert(self.ClassName == PlayerObj, "Method PlayerObj:getStat must be called with a valid PlayerObj as the 'self' argument")
		return assert(type(statToGet)=="string" and self:FindFirstChild(statToGet) and self[statToGet].Value, "Method PlayerObj:getStat(statToGet) either does not have a string as the first parameter or can't find matching stat")
	end

	function _PlayerObj:__index(key)
		return PlayerObj[key] or self[key] or self.Player[key]
	end

	function _PlayerObj:__newindex(key, value)
		if self[key] then -- Depending on your use case, these and others may need to be rearranged.
			self[key] = value -- Currently this defaults to custom variables (which must be declared either in a custom method or in PlayerObj:new())
		else
			self.Player[key] = value
		end
	end
end

local players = {}

game.Players.PlayerAdded:Connect(function(p))
	players[p.Name] = PlayerObj:new(p)
end)
game.Players.PlayerRemoving:Connect(function(p)
	players[p.Name] = nil
end)

print(players.Tokamak:getStat("TestStat"))
print(players[game.Players.LocalPlayer.Name]:getStat("TestStat"))
2 Likes

No there is no other way unless you use objects.

1 Like

Thanks for the write-out, yeah that does seem to be the only plausible way to do it. I agree that it’s a bit unnecessary though… I think I’ll just stick with my little dot symbol + extra argument instead :stuck_out_tongue: