How would I make a hitbox appear on the Right Arm on pressing R?

Title explains it all. I need some help.
LOCALSCRIPT:

local remote = game.ReplicatedStorage.ClickHitbox
local player = game.Players.LocalPlayer
game:GetService("UserInputService").InputBegan:Connect(function(Input,gameProcessed)
    if not gameProcessed then
		        if Input.KeyCode == Enum.KeyCode.F then
remote:FireServer("ClickHitbox")
end
	end
	end)

SERVERSCRIPT:

local remote = game.ReplicatedStorage.ClickHitbox
remote.OnServerEvent(function(player, ClickHitbox)
local part = Instance.new("Part")
part.Name = "Hitbox"
part.Transparency = 1
part.Size = ...
part.CFrame = ...
part.Parent = player.Character.RightArm
-- What is next

Sorry if the title is confusing.

What is the error?

There is no error, I’m not sure how to make hitboxes, so let me edit the script.

What do you mean by hitbox? What are you trying to achieve exactly? Need more details to help.

So I made an uppercut animation, I need a hitbox so it can deal damage.
https://gyazo.com/ad3bd1d1c1b07fe487c64d4dce1425b0
Not a good scripter either, so a good explanation would be appreciated.

You can insert a damage script on a part in front of the player’s hand (a very short one, and not touching the player), and disable it. You can make it so when the uppercut tool is equipped, it enables.

It’s not a tool, the animation is played on R

I’ve added comments to help you learn, but tell me if there’s anything you don’t understand. For this to work, however, you need to modify your game hierarchy as I’ve written in the scripts (and commented), or you need to change the script. Similarly, you will also need to add a Marker Signal to your animation called ‘ActionFrame’ which will determine when to fire the remote event to the server. This won’t work if your animation is looped, so make sure it’s not looped either.

On the server:

local remotes = {
	uppercut = game:GetService('ReplicatedStorage'):WaitForChild('ClickHitbox');
}


local function isAlive(player)
	local char = player.Character
	if not char or not char:IsDescendantOf(game.Workspace) or not char:FindFirstChild "Humanoid" or char.Humanoid:GetState() == Enum.HumanoidStateType.Dead then
		return
	end
	return char
end

local function getArms(character, side) -- gets all the arm parts of the side the player wants to damage from so we can check length
	-- Will only work for R15
	local upper = character:FindFirstChild(side .. 'UpperArm')
	local lower = character:FindFirstChild(side .. 'LowerArm')
	local hand  = character:FindFirstChild(side .. 'Arm')
	if upper and lower and hand then
		return {
			upper = upper;
			lower = lower;
			hand  = hand;
		}
	end
end


remotes.uppercut.OnServerEvent:connect(function (player, arm)
	if player and arm and typeof(arm) == 'string' and arm == 'Left' or arm == 'Right' then
		-- some typechecking, highly recommend you also add a check to limit the amount of times a player can fire
		-- based on the cooldown of each attack etc to stop exploiting
		local character = isAlive(player)
		if character then
			local arms = getArms(character, arm)
			if arms then
				local active = { } -- make an active list of all player's characters except your own so you can raycast towards them
				for i, v in next, game.Players:GetPlayers() do
					if v and v ~= player then
						local char = isAlive(v)
						if char then
							active[#active + 1] = char
						end
					end
				end
			
				local length   = arms.upper.Size.y + arms.lower.Size.y + arms.hand.Size.y
				local bounding = character.PrimaryPart
				
				local ray = Ray.new(bounding.Position, bounding.CFrame.lookVector * (length + 1)) -- Fire a ray from our character's torso, towards its lookvector with a length of our arm size
				local hit, pos = game.Workspace:FindPartOnRayWithWhitelist(ray, active) -- find any parts we hit that's a part of a character's
				
				if hit and hit.Parent then
					local char = hit.Parent
					local hum  = hit.Parent:FindFirstChild 'Humanoid'
					if hum then
						hum:TakeDamage(10) -- Whatever we want to do to that other player's character
					end
				end
			end
		end
	end
end)

On the client:

local player     = game.Players.LocalPlayer
local character

local animationFolder = game:GetService('ReplicatedStorage'):WaitForChild('AnimationFolder'):GetChildren() --> Change 'AnimationFolder' to a folder with all of your animations in it
local anims = { }
--> This will create an easy to access dict for our anims, for this, we need an animation in there called the name of the action, in this case:
--> 'uppercut' will need to be in the animation folder
for i, v in next, animationFolder do
	anims[v.Name] = v
end

local remotes = {
	--> Create a table of remotes, so that you can access them at will for each command
	uppercut = game:GetService('ReplicatedStorage'):WaitForChild('Remotes'):WaitForChild('ClickHitbox');
	--[?] To add another, you would write: otherAnimation = game:GetService('ReplicatedStorage'):WaitForChild('Remotes'):WaitForChild('otherRemote')';
}

local states = { --> This is used to keep track of what our player is currently doing
	playing = nil; --> This determines whether or not we're performing an action currently so that we can't perform another
	last    = {
		--> This is where we'll store the last performed action times
	};
}

local cooldowns = { --[!] You could put this in a module for both the server + the client to access
	uppercut = 1; --> How long you should wait before someone can perform another uppercut (in seconds)
}

local actions = { --[!] This could also be put in a module as above
	uppercut = Enum.KeyCode.F; --> What input are we looking for when we want to perform this action
}

local function isAlive()
	local char = character or player.Character
	if not char or not char:IsDescendantOf(game.Workspace) or not char:FindFirstChild "Humanoid" or char.Humanoid:GetState() == Enum.HumanoidStateType.Dead then
		return
	end
	return char
end

local function checkCooldown(class)
	if cooldowns[class] then --> Do we have a cooldown value to compare to for this class of action?
		if (time() - (states.last[class] or 0)) > cooldowns[class] then	--> Returns true if the last time we performed this action has passed the length of the cooldown
			return true
		end
	end
	return false
end

local function performAction(class, args)
	local anim   = anims[class]
	local remote = remotes[class]
	if anim and remote then
		local track = character.Humanoid:LoadAnimation(anim)
		track.Priority = Enum.AnimationPriority.Action
		track:GetMarkerReachedSignal("ActionFrame"):Connect(function ()
			--> Tell the server we're at the action frame of the animation, so it needs to look to do any damage
			remote:FireServer(args)
			
			--> Update our states so we can perform other actions again + add our last time to the list
			states.last[class] = time()
			states.playing = false
		end)
		track:Play()
	end
end

game:GetService("UserInputService").InputBegan:Connect(function(Input, gameProcessed)
	character = isAlive()
	if not character or gameProcessed then
		return
	end
    if Input.UserInputType == Enum.UserInputType.Keyboard then
		if Input.KeyCode == actions.uppercut then 
			if not states.playing and checkCooldown('uppercut') then
				states.playing = true
				performAction('uppercut', 'Right') -- Change 'Right' to whatever side of the arm the uppercut is happening on
			end
		end
	end
end)

Appreciated, however the Animation and my game is in R6.

This is why details are important in these posts :slight_smile:.

To make this work for R6 is pretty easy, just change the getArms() function to :FindFirstChild(side … ‘Arm’) and return that arm. Then lower down, where it says ‘local length = arms.upper.size.y’ you would need to change it to ‘local length = arms.Size.y’.

Feel free to post what you change if you struggle and I’ll explain why it’s not working if you have difficulty.

2 Likes

Oh, I thought it was a tool. My apologies.

local player     = game.Players.LocalPlayer
local character

local animationFolder = game:GetService('ReplicatedStorage'):WaitForChild('AnimationFolder'):GetChildren() --> Change 'AnimationFolder' to a folder with all of your animations in it
local anims = {"Uppercut"}
--> This will create an easy to access dict for our anims, for this, we need an animation in there called the name of the action, in this case:
--> 'uppercut' will need to be in the animation folder
for i, v in next, animationFolder do
	anims[v.Name] = v
end

local remotes = {"ClickHitbox", "Remotes") 
	--> Create a table of remotes, so that you can access them at will for each command
	Uppercut = game:GetService('ReplicatedStorage'):WaitForChild('Remotes'):WaitForChild('ClickHitbox');
	--[?] To add another, you would write: otherAnimation = game:GetService('ReplicatedStorage'):WaitForChild('Remotes'):WaitForChild('otherRemote')';
}

local states = { --> This is used to keep track of what our player is currently doing
	playing = nil; --> This determines whether or not we're performing an action currently so that we can't perform another
	last    = {
		>-- This is where we'll store the last performed action times
	};
}

local cooldowns = { --[!] You could put this in a module for both the server + the client to access
	uppercut = 6; --> How long you should wait before someone can perform another uppercut (in seconds)
}

local actions = { --[!] This could also be put in a module as above
	uppercut = Enum.KeyCode.F; --> What input are we looking for when we want to perform this action
}

local function isAlive()
	local char = character or player.Character
	if not char or not char:IsDescendantOf(game.Workspace) or not char:FindFirstChild "Humanoid" or char.Humanoid:GetState() == Enum.HumanoidStateType.Dead then
		return
	end
	return char
end

local function checkCooldown(class)
	if cooldowns[class] then --> Do we have a cooldown value to compare to for this class of action?
		if (time() - (states.last[class] or 0)) > cooldowns[class] then	--> Returns true if the last time we performed this action has passed the length of the cooldown
			return true
		end
	end
	return false
end

local function performAction(class, args)
	local anim   = anims[class]
	local remote = remotes[class]
	if anim and remote then
		local track = character.Humanoid:LoadAnimation(anim)
		track.Priority = Enum.AnimationPriority.Action
		track:GetMarkerReachedSignal("ActionFrame"):Connect(function ()
			--> Tell the server we're at the action frame of the animation, so it needs to look to do any damage
			remote:FireServer(args)
			
			--> Update our states so we can perform other actions again + add our last time to the list
			states.last[class] = time()
			states.playing = false
		end)
		track:Play()
	end
end

game:GetService("UserInputService").InputBegan:Connect(function(Input, gameProcessed)
	character = isAlive()
	if not character or gameProcessed then
		return
	end
    if Input.UserInputType == Enum.UserInputType.Keyboard then
		if Input.KeyCode == actions.uppercut then 
			if not states.playing and checkCooldown('uppercut') then
				states.playing = true
				performAction('uppercut', 'Right')  
			end
		end
	end
end)

Currently on this script, if you see unfilled lines, those are the ones im confused about,
image Also not sure how to do this.

On line 5 you wrote:

local anims = {"Uppercut"}

But as I commented, you don’t need to do that, all you need to do is create a folder in ReplicatedStorage, put your uppercut animation in there and name it ‘uppercut’ or change all references of ‘uppercut’ in the script to the name of the uppercut animation (case sensitive!).

On line 12, you wrote:

local remotes = {"ClickHitbox", "Remotes"}

But again, you don’t need to do that because as I commented, you need to add the table indexes in the format:

local remotes = {
uppercut = game:GetService('ReplicatedStorage'):WaitForChild('Remotes'):WaitForChild('ClickHitbox');
someDifferentAction = game:GetService('ReplicatedStorage'):WaitForChild('Remotes'):WaitForChild('SomeDifferentAnimationRemote');
}

This looks for a folder called ‘Remotes’ in ReplicatedStorage, which has the child ‘ClickHitbox’ as a RemoteEvent.

Since you wanted to use the uppercase ‘Uppercut’, I have changed the script accordingly:

local player     = game.Players.LocalPlayer
local character

local animationFolder = game:GetService('ReplicatedStorage'):WaitForChild('AnimationFolder'):GetChildren() --> Change 'AnimationFolder' to a folder with all of your animations in it
local anims = { }
--> This will create an easy to access dict for our anims, for this, we need an animation in there called the name of the action, in this case:
--> 'Uppercut' will need to be in the animation folder
for i, v in next, animationFolder do
	anims[v.Name] = v
end

local remotes = { 
	--> Create a table of remotes, so that you can access them at will for each command
	Uppercut = game:GetService('ReplicatedStorage'):WaitForChild('Remotes'):WaitForChild('ClickHitbox');
	--[?] To add another, you would write: otherAnimation = game:GetService('ReplicatedStorage'):WaitForChild('Remotes'):WaitForChild('otherRemote')';
}

local states = { --> This is used to keep track of what our player is currently doing
	playing = nil; --> This determines whether or not we're performing an action currently so that we can't perform another
	last    = {
		--> This is where we'll store the last performed action times
	};
}

local cooldowns = { --[!] You could put this in a module for both the server + the client to access
	Uppercut = 6; --> How long you should wait before someone can perform another Uppercut (in seconds)
}

local actions = { --[!] This could also be put in a module as above
	Uppercut = Enum.KeyCode.F; --> What input are we looking for when we want to perform this action
}

local function isAlive()
	local char = character or player.Character
	if not char or not char:IsDescendantOf(game.Workspace) or not char:FindFirstChild "Humanoid" or char.Humanoid:GetState() == Enum.HumanoidStateType.Dead then
		return
	end
	return char
end

local function checkCooldown(class)
	if cooldowns[class] then --> Do we have a cooldown value to compare to for this class of action?
		if (time() - (states.last[class] or 0)) > cooldowns[class] then	--> Returns true if the last time we performed this action has passed the length of the cooldown
			return true
		end
	end
	return false
end

local function performAction(class, args)
	local anim   = anims[class]
	local remote = remotes[class]
	if anim and remote then
		local track = character.Humanoid:LoadAnimation(anim)
		track.Priority = Enum.AnimationPriority.Action
		track:GetMarkerReachedSignal("ActionFrame"):Connect(function ()
			--> Tell the server we're at the action frame of the animation, so it needs to look to do any damage
			remote:FireServer(args)
			
			--> Update our states so we can perform other actions again + add our last time to the list
			states.last[class] = time()
			states.playing = false
		end)
		track:Play()
	end
end

game:GetService("UserInputService").InputBegan:Connect(function(Input, gameProcessed)
	character = isAlive()
	if not character or gameProcessed then
		return
	end
    if Input.UserInputType == Enum.UserInputType.Keyboard then
		if Input.KeyCode == actions.Uppercut then 
			if not states.playing and checkCooldown('Uppercut') then
				states.playing = true
				performAction('Uppercut', 'Right')  
			end
		end
	end
end)

However, don’t forget to have the folder ‘Remotes’ in ReplicatedStorage with all your remotes inside, and another folder called ‘AnimationFolder’ with the animation ‘Uppercut’ (Case sensitive!) with that animation as a child.

1 Like

Also, in regard to this:

image

As I said in the post above, you need to name the event ‘ActionFrame’. To do this, you open your animation back up, and click the frame that you want the player to start damaging people i.e. when it’s facing back and the character’s arm has reached uppercut position. Right click that frame, then add event, and name it ‘ActionFrame’.

Save it, then export it and add it to the AnimationsFolder as I mentioned above.

1 Like

Alright, also, one question.

				local active = { } -- make an active list of all player's characters except your own so you can raycast towards them

What do you mean by this?

Hopefully these comments should help:

local active = { } -- a table we've defined as active, which we'll store a bunch of characters within
for i, v in next, game.Players:GetPlayers() do -- get all players in the game, and loop through them
	if v and v ~= player then -- if the player we're looking at in the list of the players in the game is NOT the player who sent this remote then
		local char = isAlive(v) 
		if char then -- is this player that we're looking at alive?
			active[#active + 1] = char -- add that character to the 'active list'
		end
	end
end

The reason I’ve done this is to ensure that our raycast only looks for hits of a player’s part (e.g. a whitelist of characters that’s not our own) within the distance of our arm size, which is seen in these lines:

local length   = arms.upper.Size.y + arms.lower.Size.y + arms.hand.Size.y
local bounding = character.PrimaryPart

local ray = Ray.new(bounding.Position, bounding.CFrame.lookVector * (length + 1)) -- Fire a ray from our character's torso, towards its lookvector with a length of our arm size
local hit, pos = game.Workspace:FindPartOnRayWithWhitelist(ray, active) -- find any parts we hit that's a part of a character's
1 Like

server script:

local remotes = {
	uppercut = game:GetService('ReplicatedStorage'):WaitForChild('ClickHitbox');
}


local function isAlive(player)
	local char = player.Character
	if not char or not char:IsDescendantOf(game.Workspace) or not char:FindFirstChild "Humanoid" or char.Humanoid:GetState() == Enum.HumanoidStateType.Dead then
		return
	end
	return char
end

local function getArms(character, side) -- gets all the arm parts of the side the player wants to damage from so we can check length
	-- Will only work for R15
	local hand  = character:FindFirstChild(side .. 'RightArm')
	if hand then
		return {
			hand  = hand;
		}
	end
end


remotes.uppercut.OnServerEvent:connect(function (player, arm)
	if player and arm and typeof(arm) == 'string' and arm == 'Left' or arm == 'Right' then
		-- some typechecking, highly recommend you also add a check to limit the amount of times a player can fire
		-- based on the cooldown of each attack etc to stop exploiting
		local character = isAlive(player)
		if character then
			local arms = getArms(character, arm)
			if arms then
local active = { } -- a table we've defined as active, which we'll store a bunch of characters within
for i, v in next, game.Players:GetPlayers() do -- get all players in the game, and loop through them
	if v and v ~= player then -- if the player we're looking at in the list of the players in the game is NOT the player who sent this remote then
		local char = isAlive(v) 
		if char then -- is this player that we're looking at alive?
			active[#active + 1] = char -- add that character to the 'active list'
		end
	end
end
		
				local length = arms.Size.y
				local bounding = character.PrimaryPart
				
				local ray = Ray.new(bounding.Position, bounding.CFrame.lookVector * (length + 1)) -- Fire a ray from our character's torso, towards its lookvector with a length of our arm size
				local hit, pos = game.Workspace:FindPartOnRayWithWhitelist(ray, active) -- find any parts we hit that's a part of a character's
				
				if hit and hit.Parent then
					local char = hit.Parent
					local hum  = hit.Parent:FindFirstChild 'Humanoid'
					if hum then
						hum:TakeDamage(99) -- Whatever we want to do to that other player's character
					end
				end
			end
		end
	end
end)

Local script

local player     = game.Players.LocalPlayer
local character

local animationFolder = game:GetService('ReplicatedStorage'):WaitForChild('AnimationFolder'):GetChildren() --> Change 'AnimationFolder' to a folder with all of your animations in it
local anims = { }
--> This will create an easy to access dict for our anims, for this, we need an animation in there called the name of the action, in this case:
--> 'Uppercut' will need to be in the animation folder
for i, v in next, animationFolder do
	anims[v.Name] = v
end

local remotes = { 
	--> Create a table of remotes, so that you can access them at will for each command
	Uppercut = game:GetService('ReplicatedStorage'):WaitForChild('Remotes'):WaitForChild('ClickHitbox');
	--[?] To add another, you would write: otherAnimation = game:GetService('ReplicatedStorage'):WaitForChild('Remotes'):WaitForChild('otherRemote')';
}

local states = { --> This is used to keep track of what our player is currently doing
	playing = nil; --> This determines whether or not we're performing an action currently so that we can't perform another
	last    = {
		--> This is where we'll store the last performed action times
	};
}

local cooldowns = { --[!] You could put this in a module for both the server + the client to access
	Uppercut = 6; --> How long you should wait before someone can perform another Uppercut (in seconds)
}

local actions = { --[!] This could also be put in a module as above
	Uppercut = Enum.KeyCode.R; --> What input are we looking for when we want to perform this action
}

local function isAlive()
	local char = character or player.Character
	if not char or not char:IsDescendantOf(game.Workspace) or not char:FindFirstChild "Humanoid" or char.Humanoid:GetState() == Enum.HumanoidStateType.Dead then
		return
	end
	return char
end

local function checkCooldown(class)
	if cooldowns[class] then --> Do we have a cooldown value to compare to for this class of action?
		if (time() - (states.last[class] or 0)) > cooldowns[class] then	--> Returns true if the last time we performed this action has passed the length of the cooldown
			return true
		end
	end
	return false
end

local function performAction(class, args)
	local anim   = anims[class]
	local remote = remotes[class]
	if anim and remote then
		local track = character.Humanoid:LoadAnimation(anim)
		track.Priority = Enum.AnimationPriority.Action
		track:GetMarkerReachedSignal("ActionFrame"):Connect(function ()
			--> Tell the server we're at the action frame of the animation, so it needs to look to do any damage
			remote:FireServer(args)
			
			--> Update our states so we can perform other actions again + add our last time to the list
			states.last[class] = time()
			states.playing = false
		end)
		track:Play()
	end
end

game:GetService("UserInputService").InputBegan:Connect(function(Input, gameProcessed)
	character = isAlive()
	if not character or gameProcessed then
		return
	end
    if Input.UserInputType == Enum.UserInputType.Keyboard then
		if Input.KeyCode == actions.Uppercut then 
			if not states.playing and checkCooldown('Uppercut') then
				states.playing = true
				performAction('Uppercut', 'Right')  
			end
		end
	end
end)

Did I make a mistake?

I’ve commented mistakes with the tag ‘–[!!!]’

local remotes = {
	uppercut = game:GetService('ReplicatedStorage'):WaitForChild('ClickHitbox');
}


local function isAlive(player)
	local char = player.Character
	if not char or not char:IsDescendantOf(game.Workspace) or not char:FindFirstChild "Humanoid" or char.Humanoid:GetState() == Enum.HumanoidStateType.Dead then
		return
	end
	return char
end

local function getArms(character, side) -- gets all the arm parts of the side the player wants to damage from so we can check length
	-- Will only work for R6
	local hand  = character:FindFirstChild(side .. 'Arm') --[!!!!] You wrote 'RightArm', but we already provide it with the variable 'side' which is either 'Left' or 'Right'
	if hand then
		return hand --[!!!!] only needed to return an instance, not a table since it's only 1 part
	end
end


remotes.uppercut.OnServerEvent:connect(function (player, arm)
	if player and arm and typeof(arm) == 'string' and arm == 'Left' or arm == 'Right' then
		-- some typechecking, highly recommend you also add a check to limit the amount of times a player can fire
		-- based on the cooldown of each attack etc to stop exploiting
		local character = isAlive(player)
		if character then
			local arms = getArms(character, arm)
			if arms then
				local active = { } -- a table we've defined as active, which we'll store a bunch of characters within
				for i, v in next, game.Players:GetPlayers() do -- get all players in the game, and loop through them
					if v and v ~= player then -- if the player we're looking at in the list of the players in the game is NOT the player who sent this remote then
						local char = isAlive(v) 
						if char then -- is this player that we're looking at alive?
							active[#active + 1] = char -- add that character to the 'active list'
						end
					end
				end
		
				local length = arms.Size.y
				local bounding = character.PrimaryPart
				
				local ray = Ray.new(bounding.Position, bounding.CFrame.lookVector * (length + 1)) -- Fire a ray from our character's torso, towards its lookvector with a length of our arm size
				local hit, pos = game.Workspace:FindPartOnRayWithWhitelist(ray, active) -- find any parts we hit that's a part of a character's
				
				if hit and hit.Parent then
					local char = hit.Parent
					local hum  = hit.Parent:FindFirstChild 'Humanoid'
					if hum then
						hum:TakeDamage(99) -- Whatever we want to do to that other player's character
					end
				end
			end
		end
	end
end)

Client:

local player     = game.Players.LocalPlayer
local character

local animationFolder = game:GetService('ReplicatedStorage'):WaitForChild('AnimationFolder'):GetChildren() --> Change 'AnimationFolder' to a folder with all of your animations in it
local anims = { }
--> This will create an easy to access dict for our anims, for this, we need an animation in there called the name of the action, in this case:
--> 'Uppercut' will need to be in the animation folder
for i, v in next, animationFolder do
	anims[v.Name] = v
end

local remotes = { 
	--> Create a table of remotes, so that you can access them at will for each command
	Uppercut = game:GetService('ReplicatedStorage'):WaitForChild('Remotes'):WaitForChild('ClickHitbox');
	--[?] To add another, you would write: otherAnimation = game:GetService('ReplicatedStorage'):WaitForChild('Remotes'):WaitForChild('otherRemote')';
}

local states = { --> This is used to keep track of what our player is currently doing
	playing = nil; --> This determines whether or not we're performing an action currently so that we can't perform another
	last    = {
		--> This is where we'll store the last performed action times
	};
}

local cooldowns = { --[!] You could put this in a module for both the server + the client to access
	Uppercut = 6; --> How long you should wait before someone can perform another Uppercut (in seconds)
}

local actions = { --[!] This could also be put in a module as above
	Uppercut = Enum.KeyCode.R; --> What input are we looking for when we want to perform this action
}

local function isAlive()
	local char = character or player.Character
	if not char or not char:IsDescendantOf(game.Workspace) or not char:FindFirstChild "Humanoid" or char.Humanoid:GetState() == Enum.HumanoidStateType.Dead then
		return
	end
	return char
end

local function checkCooldown(class)
	if cooldowns[class] then --> Do we have a cooldown value to compare to for this class of action?
		if (time() - (states.last[class] or 0)) > cooldowns[class] then	--> Returns true if the last time we performed this action has passed the length of the cooldown
			return true
		end
	end
	return false
end

local function performAction(class, args)
	local anim   = anims[class]
	local remote = remotes[class]
	if anim and remote then
		local track = character.Humanoid:LoadAnimation(anim)
		track.Priority = Enum.AnimationPriority.Action
		track:GetMarkerReachedSignal("ActionFrame"):Connect(function ()
			--> Tell the server we're at the action frame of the animation, so it needs to look to do any damage
			remote:FireServer(args)
			
			--> Update our states so we can perform other actions again + add our last time to the list
			states.last[class] = time()
			states.playing = false
		end)
		track:Play()
	end
end

game:GetService("UserInputService").InputBegan:Connect(function(Input, gameProcessed)
	character = isAlive()
	if not character or gameProcessed then
		return
	end
    if Input.UserInputType == Enum.UserInputType.Keyboard then
		if Input.KeyCode == actions.Uppercut then 
			if not states.playing and checkCooldown('Uppercut') then
				states.playing = true
				performAction('Uppercut', 'Right')  
			end
		end
	end
end)

As long as you’ve also created the hierarchy that’s in the script e.g. got a marker signal on your animation called ‘ActionFrame’, got a folder in replicated called ‘AnimationFolder’ with an animation called 'Uppercut, as well as a folder in replicated called ‘Remotes’ with a RemoteEvent called ‘ClickHitbox’ then it will work now :slight_smile:

Similarly, the server code has to go into ‘ServerScriptService’ in a script, and the client code has to go in a LocalScript in the PlayerScripts.