Advanced Damage System (AD)
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.
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:
- Place the AD ModuleScript inside
ReplicatedStorageorServerScriptService. - Structure your custom rig so that limbs are grouped within
Folders(e.g.,Character.Body.LeftArm). - Assign a
Healthattribute (Number) to the specific limb Folders you want to be destructible. - 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:
- Proximity Damage: Instead of hitting a specific part, the system strikes a
Vector3position in space. It calculates the magnitude from the impact point to the closest part inside a limb folder, applying a distance multiplier to the base damage. - Armor Mathematics: Armor penetration is calculated via subtraction:
Difference = ArmorLevel - ArmorPenetration.- If the difference is
≥ 2, the attack is fully blocked (0 damage). - If the difference is
== 1, the attack deals 50% damage. - If the difference is
≤ 0, the attack deals 100% damage.
- If the difference is
- Visual Degradation: As a limb loses health, the parts within its folder seamlessly interpolate their color toward a bright, heated orange. Upon reaching critical health, the material forces a switch to Neon.
Server Methods
Interact with the AD system through these public module functions.
AD.InitCharacter(character)
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)
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)
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