Yeah, at this point things I do in studio work completely, but in game just break, with no errors

So, I am making a Fighting game, and currently most of my things that I have tested in studio work. Then, it becomes severely buggy once I play it in a real game, and the combat that I have been working on will not work at all.


I have been looking and debugging the scripts again and again, to no avail to the combat working.

-- \\ Player-Related Variables //--

local Player = game.Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local HUM = Character:WaitForChild("Humanoid")

-- \\ Get-Service Variables // -- 

local UIS = game:GetService("UserInputService")

local RS = game:GetService("ReplicatedFirst")

-- \\ Server-Script-Variables // --

local SSS = game:GetService("ServerScriptService")

local RS = game:GetService("ReplicatedStorage")

local Combat101 = SSS:FindFirstChild("CombatHandler")

local CAS = game:GetService("ContextActionService")

-- \\ Cooldowns // --

local Debounce = false


local CDS = {

	1,
	2

}

local CurrTime = 0

local PrevTime = 0



-- \\ Misc. Variables // --

local Count = 0

local SwingDelay = 0.5

local AirKickEnabled = false

local AerialDebounce = false

-- \\ Functions // --

HUM.StateChanged:Connect(function(oldState, newState)
	if newState == Enum.HumanoidStateType.Jumping then
		if not AirKickEnabled and AerialDebounce then
			wait(.02)
			if HUM:GetState() == Enum.HumanoidStateType.Freefall then
				
				AirKickEnabled = true 
				AerialDebounce = true
				task.wait(15)
				AirKickEnabled = false
				AerialDebounce = false
			elseif HUM:GetState() == Enum.HumanoidStateType.Landed then
				AirKickEnabled = false
				task.wait(30)
				
			
			end
		end
	end
end)


UIS.InputBegan:Connect(function(Input, Processed)

	if Processed then
		return
		elseif Input.UserInputType == Enum.UserInputType.MouseButton1 and not UIS:IsKeyDown(Enum.KeyCode.F) then
		if Debounce == false then
			Debounce = true 
			CurrTime = os.clock()


			local PT = CurrTime - PrevTime
			if PT < 1 then
				Count += 1
				if Count > 4 then
					print("Resetting Combo.")
					Count = 1
				end
			else
				Count = 1
			end

			RS.Combat:FireServer(Count)
			
			end
		end
end)

RS.Combat.OnClientEvent:Connect(function(bool)	
	PrevTime = CurrTime

	if bool then 
		wait(CDS[2])
		Debounce = false
	else
		Debounce = false
	end
end)

Local Script.

-- \\ Module-Related Variables // --

local CM = require(script.CombatModule)

-- \\ Get-Service Variables // --

local RS = game:GetService("ReplicatedStorage")

-- \\ Events Variables // --

local Combat = RS.Combat
local Clashin = RS.Clashing
local Progressin = RS.Progressing
local DMG = 5

local Key = {

	K = Enum.KeyCode.K,
	
	F = Enum.KeyCode.F,
	
	C = Enum.KeyCode.C,
	
	Z = Enum.KeyCode.Z,
	
	T = Enum.KeyCode.T,
	
	Y = Enum.KeyCode.Y,
	
	P = Enum.KeyCode.P,
	
	O = Enum.KeyCode.O,
	
	E = Enum.KeyCode.E,
}


-- \\ Functions // --

Combat.OnServerEvent:Connect(function(Player, Count)
	-- removed Count = 0
	local Character = Player.Character
	local Humanoid = Character.Humanoid
	local Humrp = Character.HumanoidRootPart
	
	if _G.CanAttack == false and _G.Blocking == true then
		return
	end
	

	if Character:FindFirstChild("Stun") then return end
	
	if not Character:FindFirstChild("Stun") then
		--local ClashCount = math.random(1, 5)
		
		_G.CanAttack.Value = true
		--_G.ClashChance.Value = ClashCount
		--print(_G.ClashChance.Value)
			print("WERKKKK")
		
		local Action, Length = CM.getAnimation(Humanoid, Count)
		
		Action:Play()
		CM.CombatStarted(Player, Character, Humrp, Count, DMG)
			print(Count)
		--_G.ClashChance.Value = nil
		
		wait(Length)
		
	
	--[[==[local KeyList = {}
	for key, value in pairs(Key) do
		table.insert(KeyList, key)
	end
 
	if not Character:FindFirstChild("Stun") then
		if Humanoid:FindFirstChild("Clashing").Value == true then
			print("It works")
			print(Key[KeyList[math.random(#KeyList)])
			Clashin:FireClient(Player, "Clashing")
			CM.Clashing(Player, Character, Humrp, Key)
			
		end
	end
	]]--
	
	if Count < 4 then
		Combat:FireClient(Player, false)
	else
		Combat:FireClient(Player, true)
		_G.CanAttack.Value = false
		--print(_G.CanAttack.Value)
	end
	else
	Combat:FireClient(Player, false) --Resets their cooldowns


end
	
end)

Server Script.

-- \\ Script-Related Variables // --

local Animation = script.Animations

local Meshes = script.Meshes

local LT = workspace.LivingThings

local StunTime = 0


-- \\ Get-Service Variables // --

local Debris = game:GetService("Debris")

local RS = game:GetService("RunService")

local KeyProvider = game:GetService("KeyframeSequenceProvider")

local CombatHandler = {
	
	MeleeAnims = {

		Animation.Hitting.MeleeAnim1,
		Animation.Hitting.MeleeAnim2,
		Animation.Hitting.MeleeAnim3,
		Animation.Hitting.MeleeAnim4,
	}
}


function CombatHandler.getAnimation(Humanoid, Count)
	local Length = 0

	local KeySequence = KeyProvider:GetKeyframeSequenceAsync(CombatHandler.MeleeAnims[Count].AnimationId)
	--print("Getting the Sequences and loading em")
	local KeyFrame = KeySequence:GetKeyframes()


	for i=1, #KeyFrame do
		local Time = KeyFrame[i].Time
		if Time > Length  then
			Length = Time

		end
	end
	return Humanoid.Animator:LoadAnimation(CombatHandler.MeleeAnims[Count]), Length
end



function CombatHandler.CombatStarted(Player, Character, Humrp, Count, DMG)
	
		
	--------------------------------------------------------
	local Character = Player.Character
	local Hum = Character.Humanoid
	
	
	local RayCastingCheckerFirst = RaycastParams.new()
	RayCastingCheckerFirst.FilterDescendantsInstances = {Character}
	RayCastingCheckerFirst.FilterType = Enum.RaycastFilterType.Blacklist

	--local RayCastResult = workspace:Raycast(Humrp)
	
	local Folder = Instance.new("Folder")
	Folder.Name = Player.Name.."Folder"
	Folder.Parent = workspace
	Debris:AddItem(Folder, 0.4)

	local Hitbox = Meshes.Hitbox:Clone()
	Hitbox.Parent = Folder
	Hitbox.CFrame = Humrp.CFrame * CFrame.new(0,0,-3)

	local Weld = Instance.new("ManualWeld")
	Weld.Part0 = Hitbox
	Weld.Part1 = Humrp
	Weld.C0 = Weld.Part0.CFrame:ToObjectSpace(Weld.Part1.CFrame)
	Weld.Parent = Weld.Part0


	local pos1 = Hitbox.Position - (Hitbox.Size/2)
	local pos2 = Hitbox.Position + (Hitbox.Size/2)
	local Re3 = Region3.new(pos1, pos2)

	local Blacklist = OverlapParams.new()
	Blacklist.FilterType = Enum.RaycastFilterType.Blacklist
	Blacklist.FilterDescendantsInstances = {Character}	

	local HumanoidFound = false

	local HBFinder = workspace:GetPartBoundsInBox(Re3.CFrame, Re3.Size, Blacklist)
	Debris:AddItem(Hitbox, 0.3)
	for i, parts in pairs (HBFinder) do
		local humanoid = parts.Parent:FindFirstChild("Humanoid")
		if humanoid  and humanoid.Health > 0 then --The error is proclaimed to be located here
			
		HumanoidFound = true
		
		--Enemy-Player Variables--
		local enemyChar = parts.Parent
		local enemyRootPart = parts.Parent.HumanoidRootPart	
		local enemyHumanoid = parts.Parent.Humanoid
		----------------------------------------------
		--Status-Variable Creation--	
		local M1Stun = Instance.new("BoolValue")
		M1Stun.Name = "Stun"
		M1Stun.Parent = enemyHumanoid
		----------------------------------------------
			
		--Status-Effects Watching--
		if enemyHumanoid and enemyHumanoid.Health > 0 then
			if enemyHumanoid:FindFirstChild("IsBlocking").Value == true then 
				DMG = DMG/5
			end
			
			if enemyHumanoid:FindFirstChild("Parry").Value == true then
				local Dazed = Instance.new("BoolValue")
				Dazed.Value = true
				Dazed.Parent = Character
				Dazed.Name = "Dazed"
				Hum.WalkSpeed = 2
				Hum.JumpPower = 2
				Debris:AddItem(Dazed, 0.5)
				Hum.WalkSpeed = 16
				Hum.JumpPower = 50.125
				
				
				return
			end

			if Count == 4 and enemyHumanoid:FindFirstChild("IsBlocking").Value == true then

				enemyHumanoid:FindFirstChild("IsBlocking").Value = false

				local GBValue = Instance.new("BoolValue")
				GBValue.Parent = enemyHumanoid
				GBValue.Name = "GB"
				
				enemyHumanoid.WalkSpeed = 0
				enemyHumanoid.JumpPower = 0
				Debris:AddItem(GBValue, 5)
				enemyHumanoid.WalkSpeed = 16
				enemyHumanoid.JumpPower = 50.125 

				DMG = 5
			end
			-----------------------------------------------
				
			--Checking Status-Effects--
			if Character:FindFirstChild("Stun") or Character:FindFirstChild("GB") or Character:FindFirstChild("Dazed") then print("You are stunned?!") return end

			if Hum:FindFirstChild("IsBlocking").Value == true then return end
			-----------------------------------------------	
				
				
			--Damage-Starts--
			enemyHumanoid:TakeDamage(DMG)			
				
			
			Debris:AddItem(M1Stun, 0.5)
				
			M1Stun.Value = true
				
			enemyHumanoid.WalkSpeed = 4
			enemyHumanoid.JumpPower = 0

			print(enemyHumanoid.WalkSpeed)
			print(enemyHumanoid.JumpPower)	

				
			task.delay(.5, function()
				M1Stun.Value = false
				enemyHumanoid.WalkSpeed = 15
				enemyHumanoid.JumpPower = 50.125
			end)

			print(enemyHumanoid.WalkSpeed)	
			print(enemyHumanoid.JumpPower)
			------------------------------------------------
				
			--Knock-Back--
			if Count == 4  then
				local BV = Instance.new("BodyVelocity", parts.Parent.HumanoidRootPart)
				BV.MaxForce = Vector3.new(22500, 22500, 22500)
				BV.P = 33300
				BV.Velocity = -parts.Parent.HumanoidRootPart.CFrame.LookVector * 40
				Debris:AddItem(BV, 0.25)
			--------------------------------------------------
					end
				end
			end
		break
	end
end






return CombatHandler

Module Script.

At this point I can’t deal with it, and it’s generally out of my control as I look for more solutions, the pit gets deeper and deeper. I just need some assistance.

My suggestion is to handle hit detection on the attacker’s client (Raycasts on server scripts have 100% proven unreliable for me and this seems like what’s happening here)

Then do sanity check on server and do damage

Isn’t a client-sided hitbox very exploitable? Along with that could I use it to send over a humanoid parament to the server?

Not sure what you mean by humanoid parament.

Yes it is exploitable which is why you will do sanity checks

What I mean by that is that I could check if the Humanoid was first found on the client, then move it over and make it something like this,

Combat.OnServerEvent:Connect(function(Player, Count)

Also, would the combat be a lot more fps reliant?

Technically it would but generally a player has more frames than the server “generally”. Also, always value client experience.

Not sure how sending the humanoid anywhere helps, but yes value the player experience so detect on the attackers client.

You’re just doing hit detection no need for anything that is affected by fps

Oh. So, would I move something like the hitbox that I have on the module to the client, or remake it with a different method such as Touched or Raycasting?

1 Like

Making a hitbox with raycasting is generally the most reliable and widely used method.

local HBFinder = workspace:GetPartBoundsInBox(Re3.CFrame, Re3.Size, Blacklist)
	Debris:AddItem(Hitbox, 0.3)

Would queries be good aswell?

Well I mean I don’t see a problem if that’s what you want.

My whole question is how would a Client-Side Hitbox work? The same as a server hitbox, or it serves a lot more work to make it somewhat secure?

A client sided hitbox is calculated on the client therefore results are instant for them making it seem smooth unlike the server due to delay. Obviously you would need to check if the range is too far
on the server and possibly add some leeway since there’s delay between the server and client.

It can work however you want it to work. It can work the same as how your server used to if you want. Just make sure you have strong server checks.

When the client fires the remote to say it hit the player here’s some ideas for sanity checks:

-Is the player close enough to actually land the hit (Anti ranged attack exploit)
-How long ago did they last hit? (anti speed attack exploit)
-Does the player have any cooldowns or stuns?

1 Like

This is certainly one of those really crucial anti-exploits that most games seem to miss. (I’m talking more specifically in terms of FPS games, and fire rate exploits)

The easiest way to counteract someone trying to bypass client-sided cooldowns with remotes is to make a memory table on the server, put their ID in the memory table when they last used it, and remove it promptly after sometime. If the client tries to fire the remote when their ID is still in the memory table, the server won’t proceed with their request until the cooldown has been met.

In fact I did this with some of my game features and it’s been a huge help. I highly recommend implementing something like this as a sanity check.

local MemoryTable = {}
local Remote = PATH.TO.REMOTE

Remote.OnServerEvent:Connect(function(Player, ...)
     if table.find(MemoryTable, Player.UserId) then
         warn(Player.Name.." already used this remote!")
     else
         -- allow combat damage on the server
         -- remember to add their UserId to the MemoryTable!
         local NewBlacklist = table.insert(MemoryTable, Player.UserId)

         -- wait a specific amount of time
         task.wait(Number)

         -- rightfully remove their blacklist
         table.remove(MemoryTable, NewBlacklist)
     end
end)

It’s been awhile since I’ve done this particular thing, but hopefully it can be of guidance to your work. (the table.remove portion of my code may not be right! Make sure to double check. I didn’t program this with an IDE, mainly off memory.)

Thank you for this. At the moment I’m just playing around, trying to figure out how the checks should work, and how to fix the Hitbox from coming prematurely.

Also, may I ask what is

the second params purpose is?