Issue with table

local essences = {} -- The full essence table, every essence can be inherited to gain abilities.

local abilities = {} -- These are the abilities that essences hold.

abilities.TimeSweeper = {
	backTrack = function(point1, point2, char) -- The way that backtrack works is it lets the user set up two points and then they can telaport betweent the two
		if not point1 then
			point1 = char.PrimaryPart.Position
			print("A")
		elseif not point2 then
			point2 = char.PrimaryPart.Position
			print("B")
		else
			print("C")
		end
	end,
}

setmetatable(abilities, essences) -- Essences is the parent class to Abilities

essences.users = {}
essences.users.essenceType = nil

setmetatable(essences.users, abilities) -- Abilities are the parent class to Users

essences.inheritEssence = function(player, essenceType) -- You can create users using this method
	essences.users[player] = { -- The player table that is created inside of the users table.
		abilityKeybind1 = {
			keyCode = Enum.KeyCode.Q,
			abilityType = nil,
			cooldown = false
		},

		abilityKeybind2 = {
			keyCode = Enum.KeyCode.E,
			abilityType = nil,
			cooldown = false
		},
		abilityKeybind3 = {
			keyCode = Enum.KeyCode.Z,
			abilityType = nil,
			cooldown = false
		},
		abilityKeybind4 = {
			keyCode = Enum.KeyCode.X,
			abilityType = nil,
			cooldown = false
		},
		abilityKeybind5 = {
			keyCode = Enum.KeyCode.C,
			abilityType = nil,
			cooldown = false
		},
	}
	
	print(essences.users[player])
	
	task.spawn(function()
		while task.wait(.5) do
			print(essences.users)
		end
	end)
	
	
	if essenceType then essences.users[player].essenceType = essenceType end -- Each essence tool can send a essenceType argument. This will set the user's essenceType to that.

	setmetatable(essences.users[player], essences.users) -- Users is the parent class to the players
	
	print(essences.users[player].essenceType) -- this simply prints the essence type of the user.

	return "Completed" -- returns completed to the essence tool
end

essences.useAbility = function(player, ability) -- the function that lets the player use abilities (not finished)
	local userEssence = essences.users[player].essenceType
	local ability = abilities[userEssence][ability]
end

essences.checkForEssence = function(player) -- the function that checks if the player has (a) essence(s).
	print(player)
	print(essences.users)
	print(essences.users[player])
	print(essences.users[player].essenceType)
	if essences.users[player] ~= nil then return essences.users[player].essenceType else return nil end
end

return essences

That instance is the player, and the hex number is an internal reference for the LuaVM.

local user = {}
user[player] = {...}
print(user) -- similar output to yours

If the value is nil, the player is not in the table or in the metatable (if there is one). The problem is most likely hidden somewhere else in your code.

1 Like

Ok, so I figured out that even though I established the user[player] table on the server through the module’s function essences.inheritEssence, for some weird reason this is not replicated to the client, which is why it shows up as nil on the client.

So what should I do?

p.s I just posted the code onto the post

That’s it then. If a client and a server require the same module, they both get their own version, and same goes for all other connected clients. You have to find other ways to share the data (remotes, attributes, value instances …).

1 Like

@fellow_meerkat

I’m trying to use a remote called checkForEssenceOnServer using a server script in ServerScriptService, but the issue is that even when I return the user[player] table it still returns as nil.

Error

  14:47:12.607  nil  -  Client - EssenceAbilities:14
  14:47:12.622   â–Ľ  {
                    ["abilityKeybind1"] =  â–¶ {...},
                    ["abilityKeybind2"] =  â–¶ {...},
                    ["abilityKeybind3"] =  â–¶ {...},
                    ["abilityKeybind4"] =  â–¶ {...},
                    ["abilityKeybind5"] =  â–¶ {...},
                    ["essenceType"] = "Timesweeper"
                 }  -  Server - ClientServerEssence:12

Client

local player = game.Players.LocalPlayer

local replicatedStorage = game:GetService("ReplicatedStorage")
local uis = game:GetService("UserInputService")
local remotes = replicatedStorage:WaitForChild("Remotes")

local essenceModule = require(replicatedStorage:WaitForChild("Essence"))
local checkForEssenceRemote = remotes:WaitForChild('checkForEssenceOnServer')

uis.InputBegan:Connect(function(inputObject, gpe)
	local essenceUser = checkForEssenceRemote:FireServer()
	print(essenceUser)
	if essenceUser ~= nil then -- checks if the player has an essence
		if inputObject.KeyCode == essenceUser.abilityKeybind1.keyCode then
			if essenceUser.abilityKeybind1.abilityType ~= nil then
				essenceModule.useAbility(player, essenceModule.users[player].abilityKeybind1.abilityType)
			end
		end
	end
end)

Server

--!strict
-- this script allows for communication between the client and server for the essence module.

local replicatedStorage = game:GetService("ReplicatedStorage")
local essenceModule = require(replicatedStorage:WaitForChild("Essence"))
local remotes = replicatedStorage:WaitForChild("Remotes")

local checkForEssenceRemote = remotes:WaitForChild('checkForEssenceOnServer')

checkForEssenceRemote.OnServerEvent:Connect(function(player)
	local essenceUser = essenceModule.checkForEssence(player)
	print(essenceUser)
	return essenceUser
end)

I may be mistaken but I think that when a module is required both by server and client both instances load a different copy of said module. Meaning that the table user on the server isn’t the table user on the client.

Yes, and I tried to implement a remote event but for some reason its not working, look at the comment above your’s.

I see. Remote events are not callbacks and don’t return a result like a remote function does. (Article on remotes: Remote Events and Callbacks | Documentation - Roblox Creator Hub).


From organisational point of view, I suggest splitting this module into multiple modules, based on responsibility. Server module, client module, maybe some constant shared info module, abilities module, potentially with multiple (sub-)modules for each ability.

For example, the client doesn’t have to store “essences” and “abilities” for every player like the server has to. Don’t be afraid to have a standalone client version and a server version.

Currently the abilities themselves are quite functionally oriented; they can also be treated as objects - preference.

For reference, try looking at various dev forum resources and YouTube videos to see how others handle skills and abilities. There is no single correct approach, however.


Regarding remote functions

I feel like I should shorty mention the drawback of asking the server for confirmation before even starting an ability, and that is latency. InvokeServer is a yielding operation, information has to travel, as we might say, through the wire and back,with average time “ping”.

Sometimes this is sufficient, other times players are looking for particularly high responsiveness. Involving the server is inevitable, but if the client has enough information to know which abilities and when it can trigger, visuals (like animations) can be started before communicating it to the server.

If the server keeps the client up to date (via remote events, attributes, replicated values…) about available abilities, the client can initiate an ability itself. You might even choose a wrapper like ReplicaService for easier communication.

1 Like