Local Script → FireServer → Server Script picks up the event call → Server Script then requires a module and calls a function in that module to trigger something.
Then Inside that module, If I were to call a remotefunction in the module, for example, ‘InvokeClient’ to get the player’s most recent mouse hit returned to the module.
If an exploiter comes in and puts a huge wait(3123123) in their client so that the remotefunction doesn’t return the mouse hit but instead yields, does this affect other people who use the same server script and require the module that calls a remotefunction on them to get their most recent mouse hit position? (according to the set up I demonstrated above ^^^^)
Thanks for the reply. How comes it will affect everyone? I’m asking because when different players require the same module isn’t that module they required specifically for them (so if a hacker chooses to put a wait(!23123) in their client, does that only affect their module), Sorry if I’m misunderstanding / saying wrong things
ModuleScripts behave in the context in which they were required in, a ModuleScript required by the server acts as a server script, a ModuleScript required by the client acts as a local script.
No, OnClientInvoke’s and OnServerEvent’s connected functions are called on another thread. This won’t freeze your server or anything, and i’m pretty sure remote functions have throttling to keep memory from being hogged by exploiters. Just remember that the client can return anything so always have your error handing in place
Reason why I’m asking this question is because, I’m thinking of changing the way I have my skills-system set up, right now it’s set up like:
Client UIS Input (press a key) → FireServer → SkillHandler (Server-Script) picks it up and then calls that skill according to it’s name in the module-skill-handler → In a single module i have all the skills laid out and there’s over 20 skills, so e.g. [“SkillOne”] = function()…, [“SkillTwenty”] = function()…, all in the same single module script,
1st Problem is I don’t think the way I set it up is good because it’s so many skills in one single module script
2nd Problem is, some skills are projectiles, and may have start-ups so they’re not instantly fired out, however this may cause a slight delay in where they’re aiming at because the Mouse.Hit.Position the server received, if I apply a wait(0.35) then that Mouse.Hit.Position is 0.35 seconds ago and not the most recent Mouse Position of where the play is currently aiming at 0.35 seconds after.
Client Input KeyBind (same as before) → Calls a Skill Module located in replicatedstorage → The Skill Module will handle the start-ups and et cetera, then after it will then call a remote function :InvokeServer(“SkillName”) → A Server Script picks it up → Server Script then calls the Actual Skill Module located server-side e.g. [“SkillOne”] = function()… → The skill module that was called will then do the skill things and return true if successfully done, else false. (I may also split the skills into individual modules)
No, I’m pretty sure it doesn’t yield everyone else, although I didn’t test it. When you fire the server and it calls a function. It only yields the function and/or the stack, like, all the functions that called the functions in the module.
I think the best way would be this: Client Input > Fire Event > Server Picks it up locates the skill in a module.
The layout is pretty simple there’s no need to make it more complicated, Of course if you want to split your moves into more scripts to easily manage it you can. For example you could have a ModuleScript called “SkillManager” and have all the skills and children of the module script. Then in the “SkillManager” script you can have it get the remoteEvent, locate the skill and do what needs to be done. The SkillManager could look something like this, personally this is what I would do.
function SkillsManager:FindSkill(player : Player, skillName : string)
local SkillModule = script:FindFirstChild(skillName)
if SkillModule then
require(SkillModule):Play(player) -- Assuming the function is called :Play() and player is passed through since you might need it for the animations
else
warn("Couldn't locate SkillModule") -- Do what you need to here, really it's just for testing
return
end
end
Instead of that you could have a Initialize function which loops through all the modules and adds them to a table so you don’t have to keep requiring them.
Right, but what about mouse aim based projectiles with starts ups, for example,
User → FireServer(MousePosition) → Server receives it → Server initiates the skill → The skill has a wait(0.35) as a start up so the skill doesn’t instantly come out, so people can at-least react to it → due to the start up, the MousePosition it received is 0.35s delayed, so if the User moves their mouse at last second the server doesn’t pick it up and instead refers to the MousePosition it received at first. How can I make it so it gets the updated mouseposition even after the wait(0.35) start up?
Unless the aim-based projectile follows the mouse I don’t see it as that much of an issue, especially considering a player has already clicked which means they’ve clicked where they wanted it to go.
Of course, you could always :InvokeClient although this isn’t recommended, you can use pcalls and task.spawn to make it safer. For example when needed to you would invoke the client which would be wrapped in a pcall, after a couple seconds if it hasn’t received anything just kick the player. Something like this:
local Result, err = pcall(function()
-- InvokeClient here
end)
task.delay(5, function()
if not Result then
player:Kick()
else
-- Recieved clients mouse pos
end
end)
No, but this is assuming you’re connecting a function to OnServerEvent since every time a client informs something to the server, the connected function is called on a new coroutine.
local function moduleThatSendsARemoteFunctionToSomePlayer(...)
return remoteFunction:InvokeClient(...)
end
remote.OnServerEvent:Connect(function(player, ...)
moduleThatSendsARemoteFunctionToSomePlayer(...)
...
end)
However, if your code is setup something like …
local function moduleThatSendsARemoteFunctionToSomePlayer(...)
return remoteFunction:InvokeClient(...)
end
while true do
local args = {remote.OnServerEvent:Wait()}
moduleThatSendsARemoteFunctionToSomePlayer(args) -- Big possibly issue!
end
Albeit, this is a rather inefficient and not an ideal way of listening to responses from the client, however no one exactly knows what your code is setup like.
With this approach, there is a problem. If an exploiter decides to stall the response of OnClientInvoke, then moduleThatSendsARemoteFunctionToSomePlayer would stall for as long as there is no response, which would stall the thread the while loop is on and thus other clients will face the issue of the server not responding to their requests. Of course there are solutions to this issue by calling moduleThatSendsARemoteFunctionToSomePlayer on a new coroutine, but this can just simply be avoided by ensuring your code follows the same conventional setup.
@TheH0meLands Avoid boldly informing OP and others of information which you seem to have no knowledge of, invoking the client for a response would not completely break the game unless your code is setup in a synchronous way for client-responses, for e.g:
local function moduleThatSendsARemoteFunctionToSomePlayer(...)
return remoteFunction:InvokeClient(...)
end
while true do
local args = {remote.OnServerEvent:Wait()}
moduleThatSendsARemoteFunctionToSomePlayer(args)
end
Instead of kicking the player, you should ideally just ignore the response. There is no guarantee that a response from a client will always necessarily be received in take less than some specified interval.
To implement an effective timeout solution, I would do something like:
local function SafeInvokeClient(remote, client, timeout, ...)
local currentThread = coroutine.running()
local safeResumerThread = task.delay(
timeout,
task.spawn,
currentThread,
nil
)
local args = {...}
task.spawn(function()
local response = {remote:InvokeClient(client, table.unpack(args))}
task.spawn(currentThread, table.unpack(response))
task.cancel(safeResumerThread)
end)
return coroutine.yield()
end
Yeah, your way is 100% better, I was just giving a little example of what you could do if the server hasn’t heard back from the client. Of course, it’s not really ideal but it’s more of a safety precaution because it’s impossible to tell whether the Invoke is being manipulated or just taking a while.