Can't make an OOP class work with a remote function

I have been experimenting with OOP classes in order to try to create a framework for turn-based RPG games I could make, but recently I have been stuck on a rather annoying problem relating to client input to module scripts. So far a server script uses a few module scripts to create a series of classes including a Battle class which contains tables that contain other classes. The problem resides within the Hero class, which serves as an entity class used by players to fight other enemies. The code for that specific module looks like this:

GetArena = game:GetService("ReplicatedStorage"):WaitForChild("GetArena")
MakeChoice = game:GetService("ReplicatedStorage"):WaitForChild("MakeChoice")
Entity = require(game.ServerScriptService.Classes.Entity)
Hero = {}
Hero.__index = Hero
setmetatable(Hero, Entity)

function Hero.new(maxhp, maxmp, maxsp, bp, hp, mp, sp, character, currentarena, player)
	local newplayer = Entity.new(maxhp, maxmp, maxsp, bp, hp, mp, sp, character, currentarena)
	setmetatable(newplayer, Entity)
	
	newplayer.Player = player
	newplayer.Choice = nil

	return newplayer
end

function Hero:FetchArena()
	return self.Arena
end

function Hero:ListenForRequest()
	GetArena.OnServerInvoke = self:FetchArena()
end

return Hero

Whenever the Battle class tries to call ListenForRequest() from a hero class, it returns this error:
[ServerScriptService.Classes.Battle:40: attempt to call a nil value]
If I were to modify the code so the .new function contains this line

GetArena.OnServerInvoke = function() return newplayer.Arena end

instead of the extra functions, it instead returns
[tables cannot be cyclic]
None of the documentation I have found has helped me with this specific problem and I am unsure what other way I could do so that a Local script can call a function from an OOP class module script run by a server script. The remote function is meant to allow the local script in the PlayerGui to ‘read’ the arena class to be able to pull off functions like selecting all enemies in an arena.
Another thing to be known is that all Entity classes have the Arena property set to nil when they are first created and are set by the Battle class later due to how OOP works.
Are there any solutions for this or any ideas to work around it?

Update 1:
The initial problem has been resolved, but it has changed to something else as shown at the post below:

Try doing this instead.

function Hero:ListenForRequest()
  GetArena.OnServerInvoke = self.FetchArena
end

Reason for this is you’ll be hooking to the reference of the function, rather than calling the function.
On the wiki here you can see that you set the RemoteFunction.OnServerInvoke event with an = to a function.

3 Likes

Unfortunately even with those changes it still returns the same error:
[ ServerScriptService.Classes.Battle:40: attempt to call a nil value]
The problem might be somewhere else.

What is Classes.Battle line 40?

Classes.Battle line 40 resides inside a for in pairs loop:

for i,v in pairs(self.Players) do
		v.Arena = self
		v.ListenForRequest() --Line 40
		if self.Arena.Origin["Player"..i] ~= nil then
			coroutine.wrap(function()
				local Entity = v.Character
				local Slot = self.Arena.Origin["Player"..i]
				Entity.Humanoid:MoveTo(Slot.WorldPosition)
				Entity.Humanoid.MoveToFinished:Wait()
				Entity.HumanoidRootPart.Facing.CFrame = CFrame.new(0,0,0) * CFrame.fromEulerAnglesXYZ(math.rad(Slot.WorldOrientation.X),math.rad(Slot.WorldOrientation.Y),math.rad(Slot.WorldOrientation.Z))
				Entity.HumanoidRootPart.Facing.MaxTorque = Vector3.new(0,math.huge,0)
			end)()
		else
			table.remove(self.Players,i)
		end
	end

It initializes the Hero class, setting the Arena Property which contains a reference to the Battle class to allow the Player to read data from the Battle through the remote function. I tried to make each player listen for the remote function individually using this initialization but it doesn’t seem to work regardless of what I try.

Which line in there is line 40? Can you just put

--Line 40

Next to it?

Or is it that “v.ListenForRequest()”

I marked it for you, it doesn’t seem to matter whether the line is

v.ListenForRequest()

or

v:ListenForRequest()

Put

print(v)

Before it, because I have a feeling that v may be nil and that’s why it’s giving you the obvious, attempt to index nil.

Printing v returns:
[table: 0xf8ece7a042028664]
v is not nil as if line 40 is marked out like this:

--v.ListenForRequest()

v’s properties are not nil and it successfully moves the player model which was associated with that specific Hero class

And you said that doing v:ListenForRequest() rather than the period to access it as a property doesn’t work?

Yep, it always seems to think that function is nil for some strange reason though it was clearly defined in the Hero module. The alternate way only returns a cyclic table which results in an error.

Since you’re trying to reference the self in the ListenForRequest() you should be using the : to make it a method that passes in the self variable to it.

image

I’m trying to think of why your code still errors though when it gets to it.

[ServerScriptService.Classes.Battle:40: attempt to call a nil value]

You are calling nil. You are essentially doing nil()
v exists, yes, but v.ListenForRequest does not exist, it is nil.

v.ListenForRequest should be the reference to the function itself.

@Glitchy_Robot Easy way to see if it is there is by doing

print(v.ListenForRequest)

And see if it prints “Function: 0xMemoryLocation”

In Hero.new, you set the metatable to Entity instead of Hero. Is this intended? This might explain why ListenForRequest isn’t there even if it should.

1 Like

You’re right. I missed that. That would definitely let you access the functions in the Hero table.

Metatables will be lost during the transfer.

Scroll down to “Parameter limitations” for more information.

To combat this, you should be sending the data you need to reconstruct a copy of that class object on either the server or the client.

2 Likes

That is a fair point, however I don’t think that’s the problem here.

The problem was he wasn’t referencing the Hero table in the Hero.new() so it didn’t know to grab the functions from Hero like @Eestlane771 stated.

Surprisingly I missed that, it now returns a new error when I attempt to click a button from the player GUI which calls that remote function.
[1:44.130 - Arena is not a valid member of Player “Players.Glitchy_Robot”]

Then this might be where it goes into what Cody said above where it strips metatables.
I don’t know all of your code so I’m not 100% sure where that error is starting.

But the error looks like you’re trying to reference Arena from the player object itself and not one of your objects.