How to make a stun system for your combat system

Firstly add a LocalScript inside of StarterCharacterScripts, and name it whatever you like

local Character = script.Parent

--Services
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
--

RunService.RenderStepped:Connect(function(DeltaTime)
    if Character:GetAttribute("Stun") then
        Character.Humanoid.WalkSpeed = 0
        Character.Humanoid.JumpPower = 0
        Character.Humanoid.JumpHeight = 0
    else
        Character.Humanoid.WalkSpeed = 16
        Character.Humanoid.JumpPower = 50
        Character.JumpHeight = 7.2
    end
end)

So this is quite simple, and likely something you’ve already seen. Basically if the Character has an attribute “Stun”, then - make them stun/put their Speed, and Jump to 0. This is under a RenderStepped event has if a player tries to set their speed and Jump back to normal, then the very next frame the code will set it back to 0 if the player is stunned.

On the server side it is quite simple, add a module inside a folder rename it to “Stun”, and put it inside of ServerScriptService.

local module = {}
module.Stuns = {}

function module.Create(Character,Duration)
    Character:SetAttribute("Stun",true)
    module.Stuns[Character] = workspace:GetServerTimeNow() + Duration
end

return module

All this does is, it adds the attribute “Stun” to the character, and stores a timestamp inside of a table. This will be important later.

Now add a normal script, and put it inside of the same folder as the module. Rename the script to something like “Stun_handler”

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")

local module = require(script.Parent.Stun)

RunService.Heartbeat:Connect(function()
    for Character,Timestamp in pairs(module.Stuns) do
        if (Character and Character.Parent) then
            if workspace:GetServerTimeNow() >= Timestamp then
                Character:SetAttribute("Stun",false)
                module.Stuns[Character] = nil
            elseif not Character:GetAttribute("Stun") then
                Character:SetAttribute("Stun",true)
            end
        end
    end
end)

What this does is it removes the stun off the character, and also removes the stun from the table, when the time has ran out. This is done by a HeartBeat event on the server side. There might be a better way of doing this, but this is what I came up with. So that timestamp comes in handy here as it allows us to stack the stuns so that if the player gets stunned again, whilst already being stunned then that second stun’s timestamp is when the stun will be removed.

Now you may be thinking, can’t the player simply remove the “Stun” attribute? And yes they can, so to counter this we can add a LocalScript inside of StarterCharacterScripts. And add a RemoteEvent inside of ReplicatedStorage renamed to “Detect”- put it wherever in ReplicatedStorage.

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

local Detect = ReplicatedStorage:WaitForChild("Stun").Detect -- This should be whatever path it is for your event

local Player = Players.LocalPlayer

local Character = Player.Character or Player.CharacterAdded:Wait()

Character:GetAttributeChangedSignal("Stun"):Connect(function()
    local Time = workspace:GetServerTimeNow() -- For highest accuracy
    --The stun attribute has changed
    if not Character:GetAttribute("Stun") then
        --The stun attribute has been removed
        Detect:FireServer(Time)
    end
end)

So what happens here is that whenever the “Stun” attribute is changed, and put to false/nil we do a check, asking the server whether or not the Stun was removed or not. However as you may already know their is a slight delay when communicating from Client to Server, so we give the Time of when the “Stun” attribute was changed on the client, as a parameter. That is the “TIme” variable.

Now to finish off, update the “Stun_handler” script

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")

local Detect = ReplicatedStorage.Stun.Detect

local module = require(script.Parent.Stun)

Detect.OnServerEvent:Connect(function(Player,Time)
    -- The client has detected a change in stun - checking if it is correct
    
    local Character = Player.Character
    
    local Timestamp = module.Stuns[Character]
    
    if Timestamp then
        if Time < Timestamp then
            --Client is cheating
            Player:Kick("Cheating")
        end 
    end
    
end)

RunService.Heartbeat:Connect(function()
    for Character,Timestamp in pairs(module.Stuns) do
        if (Character and Character.Parent) then
            if workspace:GetServerTimeNow() >= Timestamp then
                Character:SetAttribute("Stun",false)
                module.Stuns[Character] = nil
            elseif not Character:GetAttribute("Stun") then
                Character:SetAttribute("Stun",true)
            end
        end
    end
end)

Now here we firstly get the Timestamp inside the module, if we find it (this RemoteEvent is ran no matter if the player is cheating or not, so even if the Stun is removed, because it has run out, the RemoteEvent will still run so the Timestamp can be nil). Then if the Timestamp exist, we check if the Time given by the Client is less than the Timestamp, and if it is then, the player must be cheating as the Stun was removed too quickly.

This is it, if you have any ways of improving this system, please let me know, and I’ll update it.

5 Likes