§ How to · with Picoo

How to make a Roblox combat system (with AI)

By Sametcan Tasgiran, Founder & Developer·Published ·Updated

A Roblox combat system needs the server to own every hit: cooldowns, stamina, the hitbox and the damage. Picoo's combat_system recipe builds that from one prompt, and the client only reports which button the player pressed.

3 scripts · 1,181 lines · one recipe call. Press Play and fight with M1, E, F, Q, R and Shift.

Server-authoritative damage

The client fires one RemoteEvent with an action name and its combo count. CombatServer owns the combo, the cooldowns, stamina, the hitbox query and every point of damage.

Combo, heavy and spin

M1 is a 4-hit chain at 8 damage whose finisher deals 14 with knockback. E is a 22-damage heavy with knockback. R is a 360° spin that hits every Humanoid within 11 studs for 12.

Block, parry and dash

Hold F to take 25% damage. Tap Q for a 0.35-second parry: a hit inside it deals nothing, and an M1 or heavy that lands in it stuns the attacker for 1.1 seconds. Shift dashes with 0.35 seconds of invincibility.

Stamina and cooldowns

100 stamina that regenerates 15 per second. M1, heavy, spin and dash each cost stamina and have their own server-side cooldown, tracked per player.

Box hitbox, never .Touched

HitboxModule queries workspace:GetPartBoundsInBox in front of the attacker (9 studs deep, 7 wide, 7 tall), filters out the attacker's own character and returns each enemy Humanoid once.

Sword, poses and HUD

Every spawn gets a neon StarterSword. The client tweens the shoulder joints for swings, block and parry, and draws a stamina bar, a combo counter and a controls hint.

Files Picoo ships for this prompt

5 files · 1181 lines · one recipe call

ServerScriptService/CombatSystem/HitboxModule.lua

GetPartBoundsInBox hitbox in front of the attacker; returns enemy Humanoids

60 lines

ServerScriptService/CombatSystem/CombatServer.lua

Per-player state, the action handler, damage with block/parry/i-frames, stamina regen, hit effects and the StarterSword

514 lines

StarterPlayer/StarterPlayerScripts/CombatClient.lua

Input, swing/block/parry poses, client dash, stamina and combo HUD. No damage logic

607 lines

ReplicatedStorage/CombatRemotes/CombatAction

RemoteEvent the client fires: action name and combo count

instance

ReplicatedStorage/CombatRemotes/CombatState

RemoteEvent the server fires with stamina and combo

instance

Sample output: ServerScriptService/CombatSystem/CombatServer.lua

local function doM1(player: Player, clientCombo: number)
	local character, _humanoid, root = attackerContext(player)
	if not character then return end
	local s = getState(player)
	local now = os.clock()

	if now < s.stunnedUntil then return end
	if (now - s.lastM1) < M1_COOLDOWN then return end
	if s.stamina < M1_STAMINA then return end

	-- Server owns the combo counter; the client index is only a hint.
	if now > s.comboResetAt then
		s.comboIndex = 0
	end
	s.comboIndex += 1
	if s.comboIndex > COMBO_LENGTH then
		s.comboIndex = 1
	end
	s.comboResetAt = now + COMBO_RESET
	s.lastM1 = now
	s.stamina = math.max(0, s.stamina - M1_STAMINA)

	-- The final hit of the chain does extra damage + knockback (the "finisher").
	local isFinisher = (s.comboIndex == COMBO_LENGTH)
	local damage = isFinisher and (M1_DAMAGE * 1.75) or M1_DAMAGE
	local knockback = isFinisher and KNOCKBACK_FINISHER or 0

	local targets = Hitbox.query(character, HITBOX_REACH, HITBOX_WIDTH, HITBOX_HEIGHT)
	for _, targetHumanoid in ipairs(targets) do
		local parried = damageTarget(root, targetHumanoid, damage, knockback)
		if parried then
			-- The attacker over-committed into a parry → stun them, reset combo.
			s.stunnedUntil = now + PARRY_STUN
			s.comboIndex = 0
			break
		end
	end
end

Building a Roblox combat system

A combat system is the heart of most Roblox PvP and PvE games — and it is the easiest place to ship exploit-friendly code by accident. The single most common mistake is letting the client send a damage number. Once a client can write damage, an exploiter writes 9999 and one-shots the lobby. Picoo's combat_system recipe keeps every damage number on the server, so the client has none to send.

The prompt "build me a combat system" routes to that recipe and produces three scripts. CombatClient binds M1, E, F, Q, R and Shift and fires one RemoteEvent with the action name and its combo count; that is all the client gets to send. CombatServer owns the real combo counter, the cooldowns and the stamina, runs HitboxModule's GetPartBoundsInBox query in front of the attacker, and applies damage through Humanoid:TakeDamage.

Defence is server-side too. A blocked hit deals 25% and no knockback. A hit inside the 0.35-second parry window deals nothing, and an M1 or heavy that lands in it stuns the attacker for 1.1 seconds. A dash grants 0.35 seconds of i-frames on the server while the client applies the LinearVelocity burst, because the client owns its own character's physics. Each hit spawns its particle burst and sound on the server, so every player sees it.

The client side is presentation. Swings, the block guard and the parry are tweens on the shoulder Motor6D joints (R6 and R15), the dash leaves an afterimage, and a HUD shows stamina, the current combo and the controls. Every spawn gets a neon StarterSword. A health bar, floating damage numbers or enemy AI are follow-up prompts.

See more on the Luau generator, the AI Luau script writer, the 3D model generator and the game builder, read how Picoo compares with ChatGPT, or browse the full blog.

Frequently asked

Is the combat system anti-exploit by default?+

Damage is server-side by construction. The client sends an action name and a combo count; the server keeps its own combo counter and checks stun, cooldown and stamina before it runs its own hitbox. A client that spams the remote gets its extra actions refused, and it never sends a damage number.

Can I customize damage, cooldown, range?+

Yes. The top of CombatServer lists the tunables as named constants: M1_DAMAGE, HEAVY_DAMAGE, the cooldowns and stamina costs, BLOCK_REDUCTION, PARRY_WINDOW, HITBOX_REACH and more; DASH_POWER sits at the top of CombatClient. M1 damage, heavy damage, combo length, dash power and max stamina can also be set in the prompt.

Does it work with non-sword weapons?+

The recipe is melee: every attack is a box or radius query around the attacker, and each player is handed a StarterSword. For a ranged weapon, ask for a blaster, a laser gun or a flamethrower in a follow-up prompt; that builds a separate Tool with its own server-validated RemoteEvent.

What about NPC enemies?+

The hitbox hits any Humanoid in front of the attacker, so NPCs take damage, hitstun and knockback like players do. Block, parry and dash i-frames are tracked per player, so an NPC never blocks. Enemy AI that chases and attacks is a follow-up prompt.

What does one prompt build?+

Three scripts and four instances: ServerScriptService.CombatSystem with HitboxModule and CombatServer, StarterPlayerScripts.CombatClient, and ReplicatedStorage.CombatRemotes with CombatAction and CombatState. A health bar or floating damage numbers are follow-up prompts.

Related Picoo prompts