Advanced Damage System (AD)

Created by Samtyy (Aka Armahan, Prymitif, Durkheim)
Server-Side ModuleScript

The Advanced Damage System (AD) is a highly detailed, localized damage engine for Roblox characters. It introduces limb-based health points, an advanced armor penetration mathematical model, dynamic bleeding states, and procedural visual degradation (heating metal effect) that eventually leads to physical dismemberment.

Architecture Strictness: This system requires a specific hierarchy to function correctly. The character's model must contain Folders representing limbs (e.g., LeftArm), and those folders must contain the actual BaseParts (e.g., LeftHand, LeftLowerArm) as well as the required Attributes.

Setup & Installation

Ensure your character rigs are configured properly before attempting to initialize the engine:

  1. Place the AD ModuleScript inside ReplicatedStorage or ServerScriptService.
  2. Structure your custom rig so that limbs are grouped within Folders (e.g., Character.Body.LeftArm).
  3. Assign a Health attribute (Number) to the specific limb Folders you want to be destructible.
  4. Call AD.InitCharacter(rig) on the server as soon as the character spawns.

System Architecture

The AD module processes damage using proximity-based calculations and hierarchical scanning:

Server Methods

Interact with the AD system through these public module functions.

AD.InitCharacter(character)

Server

Scans the provided character model for valid limb folders, registers their base colors and materials to memory, and hooks up the global death explosion sequences.

AD.ApplyAreaDamage(character, impactPosition, baseDamage, armorPenetration, radius, force)

Server

The core damage calculation function. Scans all limb folders within the defined radius of the impact point and applies the mathematical armor and distance modifiers.

Parameters

Name Type Description
character Model The target character model containing a Humanoid.
impactPosition Vector3 The exact center of the explosion or bullet impact.
baseDamage number The maximum possible damage dealt at point-blank range.
armorPenetration number The penetration value to compare against the folder's ArmorLevel.
radius number The spherical radius of the damage effect in studs.
force number The physical velocity applied to parts when a limb shatters.

AD.CleanUp(character)

Server

Clears the character's original states from memory and cancels any active bleeding threads. Automatically called upon the Humanoid's death.

Events

The module exposes a public registry of BindableEvents allowing external scripts to react to the combat states.

Event Name Arguments Passed Trigger Condition
OnDamageTaken (character, folderName, finalDamage, newHealth) Fires whenever a limb folder successfully takes damage after armor checks.
OnPartBroken (character, folderName) Fires the exact moment a limb folder's health drops to 0.
OnBleedingStarted (character, folderName, bleedDamagePerSecond) Fires when a non-vital limb breaks, initiating a bleeding thread.
OnFatalDamage (character) Fires when a vital limb breaks, or the Humanoid completely dies.

Folder Attributes

Attach these Custom Attributes to the Folders containing your BaseParts to define their mechanical properties.

Attribute Name Value Type Behavior
Health number (Required) The current health of the limb folder. If missing, the folder is ignored.
MaxHealth number Used for bleeding calculation and visual ratios. Auto-generated if omitted.
ArmorLevel number Defines resistance against the incoming armorPenetration parameter. Defaults to 1.
BleedingLevel number Multiplier for the base bleeding rate (10% of MaxHealth per second). Setting to 0 disables bleeding for this limb entirely.
Vital boolean If set to true, the destruction of this limb will instantly set the global Humanoid health to 0, killing the character.

Example Usage

A simple server implementation initializing the system and applying localized damage.

local AdvancedDamageSystem = require(game.ReplicatedStorage.AD)

local impactPosition = workspace.Rig.Body.LeftArm.LeftLowerArm.Position
local baseDamage = 1500
local armorPenetration = 1
local radius = 3
local force = 50

AdvancedDamageSystem.InitCharacter(workspace.Rig)
workspace.Rig.Humanoid.Died:Connect(function()
	AdvancedDamageSystem.CleanUp(workspace.Rig)
end)

task.wait(2)
AdvancedDamageSystem.ApplyAreaDamage(workspace.Rig, impactPosition, baseDamage, armorPenetration, radius, force)
task.wait(2)
AdvancedDamageSystem.ApplyAreaDamage(workspace.Rig, impactPosition, baseDamage, armorPenetration, radius, force)

Source Code

ModuleScript (AD)

Paste the following into your AD ModuleScript.

--// Services
local Debris = game:GetService("Debris")

--// Module Definition
local Module = {}

--// Public Variables
Module.DebugMode = true
Module.UseDeathExplosion = true
Module.UseLimbExplosion = false

--// Public Events Registry
Module.Events = {
	OnDamageTaken = Instance.new("BindableEvent"),
	OnPartBroken = Instance.new("BindableEvent"),
	OnBleedingStarted = Instance.new("BindableEvent"),
	OnFatalDamage = Instance.new("BindableEvent")
}

--// Private Variables
local originalStates = {}
local activeBleedings = {}

--// Private Debug Functions
local function DebugLog(msg, isWarning)
	if not Module.DebugMode then return end
	if isWarning then
		warn("[DBG-DMG] " .. msg)
	else
		print("[DBG-DMG] " .. msg)
	end
end

local function DebugVisualSphere(pos, radius)
	if not Module.DebugMode then return end
	local sphere = Instance.new("Part")
	sphere.Shape = Enum.PartType.Ball
	sphere.Size = Vector3.new(radius * 2, radius * 2, radius * 2)
	sphere.Position = pos
	sphere.Anchored = true
	sphere.CanCollide = false
	sphere.CanQuery = false
	sphere.CastShadow = false
	sphere.Transparency = 0.7
	sphere.Color = Color3.fromRGB(255, 0, 0)
	sphere.Material = Enum.Material.ForceField
	sphere.Parent = workspace
	Debris:AddItem(sphere, 2)
end

local function DebugVisualLine(p1, p2, color)
	if not Module.DebugMode then return end
	local distance = (p2 - p1).Magnitude
	local line = Instance.new("Part")
	line.Size = Vector3.new(0.05, 0.05, distance)
	line.CFrame = CFrame.lookAt(p1, p2) * CFrame.new(0, 0, -distance / 2)
	line.Anchored = true
	line.CanCollide = false
	line.CanQuery = false
	line.Color = color or Color3.fromRGB(255, 255, 0)
	line.Material = Enum.Material.Neon
	line.Parent = workspace
	Debris:AddItem(line, 2)
end

--// Private FX Functions
local function CreateVisualExplosion(position, radius)
	local explosion = Instance.new("Explosion")
	explosion.Position = position
	explosion.BlastRadius = radius or 5
	explosion.BlastPressure = 0
	explosion.DestroyJointRadiusPercent = 0
	explosion.Visible = true
	explosion.Parent = workspace
end

local function ShatterFolder(folder, originPos, explodeForce)
	for _, part in pairs(folder:GetChildren()) do
		if part:IsA("BasePart") then
			for _, joint in pairs(part:GetJoints()) do
				joint:Destroy()
			end

			local forceDir = (part.Position - originPos).Unit
			local randomSpin = Vector3.new(math.random(-50,50), math.random(-50,50), math.random(-50,50))

			local bodyVelocity = Instance.new("BodyVelocity")
			bodyVelocity.Velocity = forceDir * explodeForce + randomSpin
			bodyVelocity.MaxForce = Vector3.new(100000, 100000, 100000)
			bodyVelocity.Parent = part
			Debris:AddItem(bodyVelocity, 0.1)

			local bodyAngularVelocity = Instance.new("BodyAngularVelocity")
			bodyAngularVelocity.AngularVelocity = randomSpin
			bodyAngularVelocity.MaxTorque = Vector3.new(100000, 100000, 100000)
			bodyAngularVelocity.Parent = part
			Debris:AddItem(bodyAngularVelocity, 0.2)
		end
	end
end

--// Private Functions
local function StartBleeding(humanoid, healthFolder, character)
	local maxHealth = healthFolder:GetAttribute("MaxHealth") or 100
	local bleedingLevel = healthFolder:GetAttribute("BleedingLevel") or 1
	local bleedDamagePerSecond = (maxHealth / 10) * bleedingLevel

	DebugLog("Bleeding Start | " .. healthFolder.Name .. " | " .. string.format("%.1f", bleedDamagePerSecond) .. "/s", true)
	Module.Events.OnBleedingStarted:Fire(character, healthFolder.Name, bleedDamagePerSecond)

	if bleedDamagePerSecond > 0 then
		activeBleedings[character] = activeBleedings[character] or {}
		table.insert(activeBleedings[character], task.spawn(function()
			while humanoid and humanoid.Health > 0 do
				humanoid:TakeDamage(bleedDamagePerSecond)
				task.wait(1)
			end
		end))
	end
end

--// Public Functions
function Module.InitCharacter(character)
	originalStates[character] = {}

	for _, folder in pairs(character:GetDescendants()) do
		if folder:IsA("Folder") and folder:GetAttribute("Health") then
			if not folder:GetAttribute("MaxHealth") then
				folder:SetAttribute("MaxHealth", folder:GetAttribute("Health"))
			end

			for _, part in pairs(folder:GetChildren()) do
				if part:IsA("BasePart") then
					originalStates[character][part] = {
						Color = part.Color,
						Material = part.Material
					}
				end
			end
		end
	end

	local humanoid = character:WaitForChild("Humanoid")
	humanoid.Died:Connect(function()
		Module.Events.OnFatalDamage:Fire(character)

		if Module.UseDeathExplosion then
			local originPosition = character.PrimaryPart and character.PrimaryPart.Position or character:GetPivot().Position

			-- Optional: Global visual explosion
			CreateVisualExplosion(originPosition, 10)

			-- Shatter EVERY remaining valid folder
			for _, folder in pairs(character:GetDescendants()) do
				if folder:IsA("Folder") and folder:GetAttribute("Health") then
					-- Check if it hasn't already been destroyed by a 0 HP call
					if folder:GetAttribute("Health") > 0 then
						ShatterFolder(folder, originPosition, 50) -- Apply standard force for full body shatter
					end
				end
			end
		end

		Module.CleanUp(character)
	end)

	DebugLog("Init | " .. character.Name)
end

function Module.ApplyAreaDamage(character, impactPosition, baseDamage, armorPenetration, radius, force)
	baseDamage = baseDamage or 10
	armorPenetration = armorPenetration or 1
	radius = radius or 1
	force = force or 0

	local humanoid = character:FindFirstChildOfClass("Humanoid")
	if not humanoid or humanoid.Health <= 0 then return end

	DebugLog(string.format("Hit @ %s | R:%.1f | Dmg:%.1f | Pen:%d", tostring(impactPosition), radius, baseDamage, armorPenetration))
	DebugVisualSphere(impactPosition, radius)

	for _, folder in pairs(character:GetDescendants()) do
		if folder:IsA("Folder") and folder:GetAttribute("Health") then
			local currentHealth = folder:GetAttribute("Health")
			if currentHealth <= 0 then continue end

			local closestDistance = math.huge
			local closestPart = nil

			for _, part in pairs(folder:GetChildren()) do
				if part:IsA("BasePart") then
					local dist = (part.Position - impactPosition).Magnitude
					if dist < closestDistance then
						closestDistance = dist
						closestPart = part
					end
				end
			end

			if closestPart and closestDistance <= radius then
				local distanceMultiplier = 1 - (closestDistance / radius)
				local localizedDamage = baseDamage * distanceMultiplier

				local armorLevel = folder:GetAttribute("ArmorLevel") or 1
				local diff = armorLevel - armorPenetration
				local finalDamage = 0

				if diff >= 2 then
					finalDamage = 0
				elseif diff == 1 then
					finalDamage = localizedDamage / 2
				else
					finalDamage = localizedDamage
				end

				if finalDamage > 0 then
					local newHealth = math.max(0, currentHealth - finalDamage)
					folder:SetAttribute("Health", newHealth)

					local maxHealth = folder:GetAttribute("MaxHealth")
					local healthRatio = newHealth / maxHealth

					DebugLog(string.format("%s | Dist:%.1f | Dmg:%.1f | HP:%.1f/%.1f", folder.Name, closestDistance, finalDamage, newHealth, maxHealth))
					DebugVisualLine(impactPosition, closestPart.Position, Color3.fromRGB(255, 170, 0))

					Module.Events.OnDamageTaken:Fire(character, folder.Name, finalDamage, newHealth)

					local targetColor = Color3.fromRGB(165, 113, 22)

					for _, part in pairs(folder:GetChildren()) do
						if part:IsA("BasePart") then
							local origState = originalStates[character] and originalStates[character][part]
							if origState then
								if newHealth <= 0 then
									part.Color = targetColor
									part.Material = Enum.Material.Neon
								else
									part.Color = origState.Color:Lerp(targetColor, 1 - healthRatio)
									if healthRatio <= 0.3 then
										part.Material = Enum.Material.Neon
									end
								end
							end
						end
					end

					if newHealth <= 0 then
						DebugLog(folder.Name .. " BROKEN", true)
						DebugVisualLine(impactPosition, closestPart.Position, Color3.fromRGB(255, 0, 0))

						Module.Events.OnPartBroken:Fire(character, folder.Name)

						if Module.UseLimbExplosion then
							CreateVisualExplosion(closestPart.Position, 5)
						end

						ShatterFolder(folder, impactPosition, force)

						local isVital = folder:GetAttribute("Vital") or false
						if isVital then
							DebugLog("FATAL | " .. folder.Name, true)
							humanoid.Health = 0
						else
							StartBleeding(humanoid, folder, character)
						end
					end
				else
					DebugLog(string.format("%s | BLOCKED (Armor:%d vs Pen:%d)", folder.Name, armorLevel, armorPenetration))
					DebugVisualLine(impactPosition, closestPart.Position, Color3.fromRGB(150, 150, 150))
				end
			end
		end
	end
end

function Module.CleanUp(character)
	if originalStates[character] then
		originalStates[character] = nil
	end
	if activeBleedings[character] then
		for _, thread in ipairs(activeBleedings[character]) do
			task.cancel(thread)
		end
		activeBleedings[character] = nil
	end
	DebugLog("CleanUp | " .. character.Name)
end

--// Return
return Module