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.
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 …).
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.