Thunder's Status Effect System
☆⸻●⸻☆
Apply status effects such as 🔥 Fire, 🧊 Ice, ⚡ Stun, ❓ Confusion, etc... to targets and have them affected by various conditions. These effects can deal damage over time, restrict movement, alter behavior, reduce defense or apply other unique mechanics depending on the effect type.
Introduction
Introduction
Hi, it’s me, that one modeler again!
Last time I made this very cool FreezeModule
, I also revamped Roblox’s TagHumanoid function and made this Stats Handler systemSo this time I combined the both of them in order to make a system that lets you apply certain elemental status effects on characters (Players/NPCs). I hope this might help you learn something new even if you don’t use it!
Besides, this system allows you to easily set Defense and Immunities for characters using Attributes. You can add an effect from a script whose parent might be destroyed later, doing so does not stop the ongoing effect.
I’ll provide updates once in a while to fix bugs or improve the system (Any help would be appreciated!), especially during special occasions like Halloween or Christmas, you can expect to see even more premade status effects themed around those events, which are free for use (Basically similar to game events)
How this system works:
- When an effect is applied to a target, the said target is tagged using CollectionService and some additional Attributes are set on a Configuration object, which is parented in the target.
- In the main script, when there's a new target added to to the CollectionService, it checks for the Humanoid object, HumanoidRootPart, type of status effect, damage, tick, variant as well as the defense amount and immunities of the target before applying the effect.
- The status effect is applied with visual effects using a repeat until loop until the duration runs out, at which point the target is untagged. Duration is check every frame using a loop, this allows us to change the duration while an effect is active, unlike my previous method using Debris Service and ChildAdded() event
- If a target has Defense attribute, it will receive less damage from an effect, same goes for its Immunities, but an immunity can also shorten the effective duration. Ex: You apply 🔥 Fire with 100 damage per tick for 5 seconds to a Noob who has Defense = 50(%), FireImmunity = 50, he will only take 25 damage every tick for 2.5 seconds.
Free Status Effect Examples (10 Presets/Templates)
Free Status Effect Examples (10 Presets/Templates)
Free status effects:
I tried making each effect as unique as possible, take a look here to see what they do. These examples are placed within the folder or one of the main scripts.-
Stun: Stuns and stops the target from moving with a strong force. For heavy targets or those with Stun Immunity, the target is not stunned but rather slowed down along with their animation speed. Can set tick, can set stunning type (Zap/Shock/Paralyze) or color with variant. Effect emits once every tick. Tick, Force and AnimationSpeed factor are affected by Stun Immunity -
Fire: Burns to deal DoT and melt defense -
Acid: Corrodes to deal DoT and melt defense -
Radiation: Deals DoT, also melts and ignores defense -
Poison: Deals DoT while bypassing defense, damage (%) depends on the MaxHealth of the target -
Bleed: Deals DoT, damage grows after each tick until the limit is reached, also reduces the WalkSpeed and JumpPower of the target based on the % of Health remaining -
Heal: Heals target -
Ice: Freezes the target to reduces defense, stops any movements or animations while retaining physics, creates a thin ice layer on the character. Utilizes my FreezeModule. If the target is 100% immune to this then
Chill will be applied instead -
Chill: Slows down chilled targets, freeze amount depends on FreezeAmount setting and the IceImmunity of the target -
Confusion: If the target is a Player, their control is inverted (all devices), otherwise, the movement of the target is reversed -
Void: Halves the damage that the target can deal when applied -
Lifesteal: Takes HP from the target to heal the attacker. Lifesteal amount is affected by Defense, Void immunity and is multiplied by LifestealBoost stat. Must provide Creator in the settings -
Love-Link: (Will be added one day): Electrocuts targets near the main target who is being affected by this effect, lightning strikes will link the affected targets together -
Explosion: (Will be added on New Year Eve): Instantly kills the target and then creates an explosion afterwards -
Teleportation: (Will be added on ??? event): Randomly teleports the target to a random location at random time
Explanation:
-
DoT: Damage over time. Damage is applied every Tick
-
Defense: How many % of damage that a character can block. The effective damage dealt towards a character is decreased if its defense amount is higher than 0. However, Poison & Radiation can both bypass/ignore defense
-
Immunity:: How many % of damage that a character can block from the same type of effect they are immune to, can stack with Defense (Full Damage → Reduced by Defense → Reduced by Immunity)
-
Defense-melting: Reduce defense, each type of elemental status effect can only drop up to a certain amount of defense and does not stack with the same type of status effect
-
Tick: The rate at which the effect deals damage or activates. Basically firerate
-
Duration: How long the effect will be applied for. This timespan can be shorten by an immunity (0%-100%). If the immunity is higher than 100%, then the duration is not changed. Else if the immunity is lower than 0%, then the duration is increased. Duration is also stackable if an effect allows it
-
Creator: The Player instance or the character of a player (Module auto checks), is used to tag targets and recognize total damage contributed and the final hit dealer. Does not tag if the Creator is not a player
-
Variant: Customize an effect or give it a different mechanic. This is because you can’t send info to the CollectionService
Examples:
- You apply
Fire with 100 damage per tick for 5 seconds to a Noob who has Defense = 50(%), FireImmunity = 50, he will only take 25 damage every tick for 2.5 seconds. - You apply
Ice for 2 seconds to a Snowman that has IceImmunity = 100, it will not be frozen at all. - You apply
Poison with 1% MaxHealth as damage per tick for 10 seconds to a Zombie who has Defense = 90, PoisonImmunity = 150, it will not take any damage, but heal up by 1.5% of its MaxHealth per tick for 10 seconds. Poison ignores Defense - You apply
Acid with 100 damage per tick for 5 seconds to a Robot that has Defense = 50, AcidImmunity = -50, it will take 75 damage every tick for 7.5 seconds. - You apply
Radiation with 100 damage per tick for 5 seconds to a Golem that has Defense = 100, no RadiationImmunity, it will take 100 damage every tick for 5 seconds. Radiation ignores Defense - You apply
Confusion for 5 seconds to a Player without ConfusionImmunity, their control will be inverted during that time (WASD → SDWA on PC, opposite movement direction on all devices) - You apply
Failure with infinite emotional damage per tick to a Player forever, they will not be happy and will most likely dislike your game
Code examples from the model:
local FunctionBank = require(script.Parent.Parent.FunctionBank)
local Effects = FunctionBank.Effects
local Effects = {}
for Effect in pairs(FunctionBank.Effects) do
table.insert(Effects, Effect)
end
for i, v in pairs(workspace:GetChildren()) do
if v:IsA("Model") and v:FindFirstChild("HumanoidRootPart") then
local RandomEffect = Effects[math.random(1, #Effects)]
FunctionBank.AddEffect(v, RandomEffect, 10, {
Damage = 5, -- Some settings are not needed. But you must give it a valid Duration (Above ⬆)
Creator = script.Parent,
})
end
end
--[[
-- There are 2 ways to add an effect as shown below
-- #1
-- Arguments in this order:
-- Target, Effect, Duration, {Settings: {Damage: number?, Creator: Instance | string, Tick: number?, Variant: any?, Stackable: boolean?, MaxStack: number?, StackBehavior: string?}}
-- If settings like Damage or Tick are not included, then the default values in the Effects script will be used instead
FunctionBank.AddEffect(Target, "Fire", 5, {
Damage = 5,
Tick = 1,
Creator = Creator,
Variant = Color3.fromRGB(85, 0, 255),
Stackable = true,
MaxStack = 5,
StackBehavior = "Overlap"
})
-- #2
local EffectEvent = script.Parent.Parent.Handler:FindFirstChild("EffectEvent")
EffectEvent:Fire(Target, "Ice", 5, {
Creator = Creator,
})
]]
-- An example from the Status Effect system
local FunctionBank = require(game:GetService("ServerScriptService")["Thunder's Status Effect System"]:FindFirstChild("FunctionBank"))
local Effects = {}
for Effect in pairs(FunctionBank.Effects) do
table.insert(Effects, Effect)
end
local Debounce = {}
script.Parent.Touched:Connect(function(Hit)
if Hit and Hit.Parent and Hit.Parent:IsA("Model") then
local Target = Hit.Parent
if Debounce[Target] then return end
local RandomEffect = Effects[math.random(1, #Effects)]
local Duration = math.random(2, 8)
if Target:FindFirstChildOfClass("Humanoid") then
Debounce[Target] = true
FunctionBank.AddEffect(Target, RandomEffect, Duration, {
Damage = 5,
Creator = script.Parent,
})
task.delay(Duration, function()
Debounce[Target] = nil
end)
end
end
end)
Source code
Source code
Script 1 (All available status effects):
--!strict
local CS = game:GetService("CollectionService")
local Players = game:GetService("Players")
local EffectFolder = script:FindFirstChild("EffectFolder")
local Utility = script:FindFirstChild("Utility")
local FunctionBank = require(script.Parent.FunctionBank)
local Setup = require(Utility.Setup)
local StatsHandler = require(Utility.StatsHandler)
local FreezeModule = require(Utility.FreezeModuleModified)
local Effects = FunctionBank.Effects
local function CheckForEffectConfig(Target: Instance, Effect: string): Configuration
local Config
for i, v in ipairs(Target:GetChildren()) do
if v.Name == Effect and v:IsA("Configuration") then
Config = v
break
end
end
return Config
end
local function GetAttributeOrValue(Config: Instance, Key: string): any?
local Value = Config:GetAttribute(Key)
if Value ~= nil then
return Value
end
local ObjectValue = Config:FindFirstChild(Key)
if ObjectValue and ObjectValue:IsA("ObjectValue") then
return ObjectValue.Value
end
return nil
end
local function Emit(VisualEffect: any?)
if VisualEffect then
if VisualEffect:IsA("Attachment") then
for i, v in ipairs(VisualEffect:GetChildren()) do
if v:IsA("ParticleEmitter") then
v:Emit(v:GetAttribute("EmitCount") or 1)
end
end
elseif VisualEffect:IsA("ParticleEmitter") then
VisualEffect:Emit(VisualEffect:GetAttribute("EmitCount") or 1)
end
end
end
local function PlaySound(Sound: Sound?)
if Sound then
Sound.TimePosition = 0
Sound:Play()
end
end
Setup.Setup({
Effect = "Stun",
VisualTemplate = EffectFolder:FindFirstChild("Electricity"),
OnTickFunction = function(Character, EffectConfig, VisualEffect, Sound)
local Effect = "Stun"
local Immunity = Character:GetAttribute(Effect .. "Immunity") or 0 :: number
local Humanoid = Character:FindFirstChildOfClass("Humanoid") :: Humanoid
local Root = Character:FindFirstChild("HumanoidRootPart") :: BasePart
local Damage = EffectConfig:GetAttribute("Damage") or 0
local Tick = EffectConfig:GetAttribute("Tick") or 0.1
local Creator = GetAttributeOrValue(EffectConfig, "Creator")
local Force = Vector3.one * (1e7 - 1e7 * (Immunity / 100))
local MinAnimationSpeed = 0.2 + (1 - 0.2) * (Immunity / 100)
Tick = math.max(Tick + Tick * (Immunity / 100), 0.05)
local BV: BodyVelocity
if not Root:FindFirstChild("Stunned") then
BV = Instance.new("BodyVelocity")
BV.Name = "Stunned"
BV.MaxForce = Force
BV.Velocity = Root.Position.Unit * 0 + Vector3.new(0, -0.1, 0)
BV.Parent = Root
end
repeat
if Character and Character.Parent and Humanoid and Humanoid.Health > 0 then
--FunctionBank:Damage(Creator, Character, Damage, false, false, Effect, nil, nil)
Emit(VisualEffect)
PlaySound(Sound)
if BV then
BV.Velocity = Root.Position.Unit * 0 + Vector3.new(0, -0.1, 0)
end
if Immunity < 100 then
FunctionBank:MeltDefense(Character, Effect, 1)
end
local Animator = Humanoid:FindFirstChildOfClass("Animator")
if Humanoid and Animator then
for i, v in ipairs(Animator:GetPlayingAnimationTracks()) do
v:AdjustSpeed(MinAnimationSpeed)
end
end
task.wait(Tick)
else
break
end
until not CS:HasTag(Character, Effect) or not EffectConfig.Parent
local Animator = Humanoid:FindFirstChildOfClass("Animator")
if Humanoid and Animator then
for i, v in ipairs(Animator:GetPlayingAnimationTracks()) do
v:AdjustSpeed(1)
end
end
Humanoid:ChangeState(Enum.HumanoidStateType.Freefall)
if BV then
BV:Destroy()
end
end
})
Setup.Setup({
Effect = "Fire",
VisualTemplate = EffectFolder:FindFirstChild("Fire"),
OnTickFunction = function(Character, EffectConfig, VisualEffect)
local Effect = "Fire"
local Immunity = Character:GetAttribute(Effect .. "Immunity") or 0 :: number
local Humanoid = Character:FindFirstChildOfClass("Humanoid") :: Humanoid
local Root = Character:FindFirstChild("HumanoidRootPart") :: BasePart
local Damage = EffectConfig:GetAttribute("Damage") or 5
local Tick = EffectConfig:GetAttribute("Tick") or 1
local Creator = GetAttributeOrValue(EffectConfig, "Creator")
repeat
if Character and Character.Parent and Humanoid and Humanoid.Health > 0 then
FunctionBank:Damage(Creator, Character, Damage, Effects[Effect].IgnoreDefense, Effects[Effect].IgnoreImmunity, Effect, nil, "Burn")
FunctionBank:MeltDefense(Character, Effect, 2)
task.wait(Tick)
else
break
end
until not CS:HasTag(Character, Effect) or not EffectConfig.Parent
end
})
Setup.Setup({
Effect = "Acid",
VisualTemplate = EffectFolder:FindFirstChild("Acid"),
OnTickFunction = function(Character, EffectConfig, VisualEffect)
local Effect = "Acid"
local Immunity = Character:GetAttribute(Effect .. "Immunity") or 0 :: number
local Humanoid = Character:FindFirstChildOfClass("Humanoid") :: Humanoid
local Root = Character:FindFirstChild("HumanoidRootPart") :: BasePart
local Damage = EffectConfig:GetAttribute("Damage") or 5
local Tick = EffectConfig:GetAttribute("Tick") or 1
local Creator = GetAttributeOrValue(EffectConfig, "Creator")
repeat
if Character and Character.Parent and Humanoid and Humanoid.Health > 0 then
FunctionBank:Damage(Creator, Character, Damage, Effects[Effect].IgnoreDefense, Effects[Effect].IgnoreImmunity, Effect, nil, "Fade")
FunctionBank:MeltDefense(Character, Effect, 2)
task.wait(Tick)
else
break
end
until not CS:HasTag(Character, Effect) or not EffectConfig.Parent
end
})
Setup.Setup({
Effect = "Radiation",
VisualTemplate = EffectFolder:FindFirstChild("Radiation"),
OnTickFunction = function(Character, EffectConfig, VisualEffect)
local Effect = "Radiation"
local Immunity = Character:GetAttribute(Effect .. "Immunity") or 0 :: number
local Humanoid = Character:FindFirstChildOfClass("Humanoid") :: Humanoid
local Root = Character:FindFirstChild("HumanoidRootPart") :: BasePart
local Damage = EffectConfig:GetAttribute("Damage") or 5
local Tick = EffectConfig:GetAttribute("Tick") or 1
local Creator = GetAttributeOrValue(EffectConfig, "Creator")
repeat
if Character and Character.Parent and Humanoid and Humanoid.Health > 0 then
FunctionBank:Damage(Creator, Character, Damage, Effects[Effect].IgnoreDefense, Effects[Effect].IgnoreImmunity, Effect, nil, "JointBreak")
FunctionBank:MeltDefense(Character, Effect, 2)
task.wait(Tick)
else
break
end
until not CS:HasTag(Character, Effect) or not EffectConfig.Parent
end
})
Setup.Setup({
Effect = "Poison",
VisualTemplate = EffectFolder:FindFirstChild("Poison"),
OnTickFunction = function(Character, EffectConfig, VisualEffect)
local Effect = "Poison"
local Immunity = Character:GetAttribute(Effect .. "Immunity") or 0 :: number
local Humanoid = Character:FindFirstChildOfClass("Humanoid") :: Humanoid
local Root = Character:FindFirstChild("HumanoidRootPart") :: BasePart
local Damage = EffectConfig:GetAttribute("Damage")
Damage = math.round((Damage and Humanoid.MaxHealth * (Damage / 100)) or (Humanoid.MaxHealth * (2.5 / 100)))
local Tick = EffectConfig:GetAttribute("Tick") or 0.5
local Creator = GetAttributeOrValue(EffectConfig, "Creator")
repeat
if Character and Character.Parent and Humanoid and Humanoid.Health > 0 then
FunctionBank:Damage(Creator, Character, Damage, Effects[Effect].IgnoreDefense, Effects[Effect].IgnoreImmunity, Effect, nil, nil)
task.wait(Tick)
else
break
end
until not CS:HasTag(Character, Effect) or not EffectConfig.Parent
end
})
Setup.Setup({
Effect = "Bleed",
VisualTemplate = EffectFolder:FindFirstChild("Bleed"),
OnTickFunction = function(Character, EffectConfig, VisualEffect, Sound)
local Effect = "Bleed"
local Immunity = Character:GetAttribute(Effect .. "Immunity") or 0 :: number
local Humanoid = Character:FindFirstChildOfClass("Humanoid") :: Humanoid
local Root = Character:FindFirstChild("HumanoidRootPart") :: BasePart
local Damage = EffectConfig:GetAttribute("Damage") or 5
local Tick = EffectConfig:GetAttribute("Tick") or 1
local Creator = GetAttributeOrValue(EffectConfig, "Creator")
local TickCount = 0
local MaxDebuff = EffectConfig:GetAttribute("MaxDebuff") or 0.5
local DamageGrowth = EffectConfig:GetAttribute("DamageGrowth") or 0.5
local MaxDamage = EffectConfig:GetAttribute("MaxDamage") or Damage * 10
repeat
if Character and Character.Parent and Humanoid and Humanoid.Health > 0 then
FunctionBank:Damage(Creator, Character, math.min(Damage + (TickCount^1.5) * DamageGrowth, MaxDamage), Effects[Effect].IgnoreDefense, Effects[Effect].IgnoreImmunity, Effect, nil, nil)
TickCount += 1
local DebuffMultiplier = -(1 - Humanoid.Health / Humanoid.MaxHealth) ^ 2 * MaxDebuff
StatsHandler.AddModifier(Character, {
WalkSpeed = {Multiplier = DebuffMultiplier},
JumpPower = {Multiplier = DebuffMultiplier},
RegenerateRate = 1,
RegeneratePercent = -1,
}, "BleedEffect")
Emit(VisualEffect)
PlaySound(Sound)
task.wait(Tick)
else
break
end
until not CS:HasTag(Character, Effect) or not EffectConfig.Parent
StatsHandler.ClearModifiers(Character, nil, "BleedEffect")
end
})
Setup.Setup({
Effect = "Heal",
VisualTemplate = EffectFolder:FindFirstChild("Heal"),
OnTickFunction = function(Character, EffectConfig, VisualEffect)
local Effect = "Heal"
local Immunity = Character:GetAttribute(Effect .. "Immunity") or 0 :: number
local Humanoid = Character:FindFirstChildOfClass("Humanoid") :: Humanoid
local Root = Character:FindFirstChild("HumanoidRootPart") :: BasePart
local Damage = EffectConfig:GetAttribute("Damage") or 5
local Tick = EffectConfig:GetAttribute("Tick") or 0.5
local Creator = GetAttributeOrValue(EffectConfig, "Creator")
repeat
if Character and Character.Parent and Humanoid and Humanoid.Health > 0 then
if Humanoid.Health < Humanoid.MaxHealth then
Humanoid.Health += Damage
elseif Humanoid.Health > Humanoid.MaxHealth then
Humanoid.Health = Humanoid.MaxHealth
end
task.wait(Tick)
else
break
end
until not CS:HasTag(Character, Effect) or not EffectConfig.Parent
end
})
Setup.Setup({
Effect = "Ice",
VisualTemplate = EffectFolder:FindFirstChild("Chill"),
OnTickFunction = function(Character, EffectConfig, VisualEffect)
local Effect = "Ice"
local Immunity = Character:GetAttribute(Effect .. "Immunity") or 0 :: number
local Humanoid = Character:FindFirstChildOfClass("Humanoid") :: Humanoid
local Root = Character:FindFirstChild("HumanoidRootPart") :: BasePart
if Immunity >= 100 then
if Humanoid then
local FreezeAmount = EffectConfig and EffectConfig:GetAttribute("Damage") or 100
local ResistanceFactor = 1 / (1 + (Immunity / 100))
local DebuffAmount = (FreezeAmount / 100) * ResistanceFactor
local DebuffMultiplier = -math.clamp((FreezeAmount / 100) * ResistanceFactor, 0, 1)
local Animator = Humanoid:FindFirstChildOfClass("Animator")
if Humanoid and Animator then
local DebuffPercent = math.clamp(Humanoid.WalkSpeed * DebuffAmount / Humanoid.WalkSpeed, 0, 1)
local AnimationSpeed = 1 - (DebuffPercent)
for i, v in ipairs(Animator:GetPlayingAnimationTracks()) do
v:AdjustSpeed(AnimationSpeed)
end
end
StatsHandler.AddModifier(Character, {
WalkSpeed = {Multiplier = DebuffMultiplier},
JumpPower = {Multiplier = DebuffMultiplier},
}, "ChillEffect")
end
return
end
if Character and Character:FindFirstChildOfClass("Humanoid") then
FreezeModule.Freeze(Character)
Character:SetAttribute("Frozen", true)
FunctionBank:MeltDefense(Character, Effect, 5)
end
end,
OnRemoveFunction = function(Character)
local Effect = "Ice" :: string
local Immunity = Character:GetAttribute(Effect .. "Immunity") or 0 :: number
local Humanoid = Character:FindFirstChildOfClass("Humanoid") :: Humanoid
local Root = Character:FindFirstChild("HumanoidRootPart") :: BasePart
if Character and Humanoid then
if Immunity >= 100 then
StatsHandler.ClearModifiers(Character, nil, "ChillEffect")
local Animator = Humanoid:FindFirstChildOfClass("Animator")
if Humanoid and Animator then
for i, v in ipairs(Animator:GetPlayingAnimationTracks()) do
v:AdjustSpeed(1)
end
end
end
for i, v in ipairs(Character:GetDescendants()) do
if v:GetAttribute("EffectName") and v:GetAttribute("EffectName") == Effect then
v:Destroy()
end
end
FreezeModule.Unfreeze(Character)
Character:SetAttribute("Frozen", nil)
end
end,
})
Setup.Setup({
Effect = "Void",
VisualTemplate = EffectFolder:FindFirstChild("Void"),
OnTickFunction = function(Character, EffectConfig, VisualEffect)
local Effect = "Void"
local Immunity = Character:GetAttribute(Effect .. "Immunity") or 0 :: number
if Immunity >= 100 then
return
end
local Humanoid = Character:FindFirstChildOfClass("Humanoid") :: Humanoid
local Root = Character:FindFirstChild("HumanoidRootPart") :: BasePart
local Tick = EffectConfig:GetAttribute("Tick") or 1
repeat
if Character and Character.Parent and Humanoid and Humanoid.Health > 0 then
if not Character:GetAttribute("Voided") then
Character:SetAttribute("Voided", true)
end
FunctionBank:MeltDefense(Character, Effect, 5)
task.wait(Tick)
else
break
end
until not CS:HasTag(Character, Effect) or not EffectConfig.Parent
if Character and Character:GetAttribute("Voided") then
Character:SetAttribute("Voided", nil)
end
end
})
Setup.Setup({
Effect = "Confusion",
VisualTemplate = EffectFolder:FindFirstChild("Confusion"),
OnTickFunction = function(Character, EffectConfig, VisualEffect)
local Effect = "Confusion"
local Immunity = Character:GetAttribute(Effect .. "Immunity") or 0 :: number
if Immunity >= 100 then
return
end
local Humanoid = Character:FindFirstChildOfClass("Humanoid") :: Humanoid
local Root = Character:FindFirstChild("HumanoidRootPart") :: BasePart
local Tick = EffectConfig:GetAttribute("Tick") or 0.01
local Creator = GetAttributeOrValue(EffectConfig, "Creator")
local InvertControls = nil
local BV = nil
if Character and Humanoid then
local Player = Players:GetPlayerFromCharacter(Character)
if Player and not Character:FindFirstChild("InvertControls") then
InvertControls = Utility.InvertControls:Clone()
InvertControls.Parent = Character
InvertControls.Enabled = true
local Reset = Utility.Reset:Clone()
Reset.Parent = InvertControls
Reset.Enabled = false
end
repeat
if not Players:GetPlayerFromCharacter(Character) and Root and Humanoid and Humanoid.Health > 0 then
if not Root:FindFirstChild("Reverse") then
BV = Instance.new("BodyVelocity")
BV.Name = "Reverse"
BV.MaxForce = Vector3.one * 1e7
BV.Velocity = Root.AssemblyLinearVelocity * -1
BV.Parent = Root
end
BV.Velocity = Root.AssemblyLinearVelocity * -1
end
task.wait(Tick)
until not CS:HasTag(Character, Effect)
end
if BV then
BV:Destroy()
end
if VisualEffect then
VisualEffect:Destroy()
end
if InvertControls then
InvertControls:Destroy()
local Reset = Utility.Reset:Clone()
Reset.Parent = Character
Reset.Enabled = true
end
end
})
Script 2 (Handler):
--!strict
local CS = game:GetService("CollectionService")
local Running = game:GetService("RunService")
local EffectEvent = script:FindFirstChild("EffectEvent")
local FunctionBank = require(script.Parent.FunctionBank)
local Effects = FunctionBank.Effects
type ActiveEffect = {
Target: Instance,
Effect: string,
EffectConfig: Configuration,
StartTime: number,
Duration: number,
}
local ActiveEffects: {ActiveEffect} = {}
local Signal: RBXScriptConnection?
local function StartEffectLoop()
if Signal then return end
Signal = Running.Heartbeat:Connect(function()
local CurrentTime = os.clock()
for i = #ActiveEffects, 1, -1 do
local v = ActiveEffects[i]
if v then
local EffectConfig = v.EffectConfig
local Target = v.Target
local Effect = v.Effect
if (CurrentTime - v.StartTime) >= v.Duration or (EffectConfig and (not EffectConfig.Parent or not EffectConfig.Parent.Parent)) then
local stillHasStacks = false
for j = 1, #ActiveEffects do
local other = ActiveEffects[j]
if other and other ~= v and other.Target == Target and other.Effect == Effect then
stillHasStacks = true
break
end
end
if not stillHasStacks and CS:HasTag(Target, Effect) then
CS:RemoveTag(Target, Effect)
end
if EffectConfig then
EffectConfig:Destroy()
end
table.remove(ActiveEffects, i)
end
end
end
-- Stop the loop if no effects remain
if #ActiveEffects == 0 then
if Signal then
Signal:Disconnect()
Signal = nil
end
end
end)
end
local function AddNewEffect(Arg)
table.insert(ActiveEffects, Arg)
StartEffectLoop()
end
local function CountActiveStacks(Target: Instance, Effect: string): number
local Count = 0
for i, v in ipairs(ActiveEffects) do
if v.Target == Target and v.Effect == Effect then
Count += 1
end
end
return Count
end
local function GetNumber(Value: any): number?
return typeof(Value) == "number" and Value or tonumber(Value)
end
local function HandleImmunity(Target: Instance, Effect: string, Duration: number?): number?
if not Duration then
return nil
end
local Immunity = Target:GetAttribute(Effect .. "Immunity")
if not Immunity then
return Duration
end
if Effect == "Ice" then
local MinFactor = 0.1
local ResistanceFactor = 1 / (1 + (Immunity / 100))
return Duration * math.max(ResistanceFactor, MinFactor)
else
if Immunity <= 100 then
return Duration - Duration * (Immunity / 100)
else
return 0
end
end
end
local function CreateEffectConfig(Effect: string, Settings, Damage: number?, Creator: Instance | string, Tick: number?, Variant: any?)
local EffectConfig = script.EffectTemplate:Clone()
EffectConfig.Name = Effect
if Damage then
EffectConfig:SetAttribute("Damage", Damage)
end
if Tick then
EffectConfig:SetAttribute("Tick", Tick)
end
if Creator then
if typeof(Creator) == "string" then
EffectConfig:SetAttribute("Creator", Creator)
else
local Tag = Instance.new("ObjectValue")
Tag.Name = "Creator"
Tag.Value = Creator
Tag.Parent = EffectConfig
end
end
if Variant then
EffectConfig:SetAttribute("Variant", Variant)
end
if Effects[Effect].CustomSettings then
for i, Setting in ipairs(Effects[Effect].CustomSettings) do
local Value = Settings and Settings[Setting]
if Value then
EffectConfig:SetAttribute(Setting, Value)
end
end
end
return EffectConfig
end
local function ClearExistingConfigs(EffectConfigs: {Configuration})
for i, v in ipairs(EffectConfigs) do
v:Destroy()
end
table.clear(EffectConfigs)
end
EffectEvent.Event:Connect(function(Target: Instance, Effect: string, Duration: number?, Settings: {Damage: number?, Creator: Instance | string, Tick: number?, Variant: any?, Stackable: boolean?, MaxStack: number?, StackBehavior: string?})
if Target and Effect and Duration and typeof(Settings) == "table" then
if not Effects[Effect] then return end
Duration = GetNumber(Duration)
Duration = HandleImmunity(Target, Effect, Duration)
if not Duration or Duration <= 0 then return end
local Damage = GetNumber(Settings.Damage)
local Tick = GetNumber(Settings.Tick)
local MaxStack = GetNumber(Settings.MaxStack)
local Stackable = Settings.Stackable
local StackBehavior = Settings.StackBehavior
local Creator = Settings.Creator
local Variant = Settings.Variant
local StackCount = CountActiveStacks(Target, Effect)
local ExistingConfigs = {}
for i, v in ipairs(Target:GetChildren()) do
if v:IsA("Configuration") and v.Name == Effect then
table.insert(ExistingConfigs, v)
end
end
local function AddEffectStack()
local EffectConfig = CreateEffectConfig(Effect, Settings, Damage, Creator, Tick, Variant)
AddNewEffect({Target = Target, Effect = Effect, EffectConfig = EffectConfig, StartTime = os.clock(), Duration = Duration})
EffectConfig.Parent = Target
end
if Stackable or Effects[Effect].Stackable then
if StackBehavior == "Replace" then
ClearExistingConfigs(ExistingConfigs)
AddEffectStack()
elseif StackBehavior == "Overlap" then
if not MaxStack or StackCount < MaxStack then
AddEffectStack()
end
elseif StackBehavior == "RemoveAll" then
ClearExistingConfigs(ExistingConfigs)
return
elseif StackBehavior == "ResetAll" then
local StackCount = 0
for i, v in ipairs(ActiveEffects) do
if v.Target == Target and v.Effect == Effect then
v.StartTime = os.clock()
v.Duration = Duration
StackCount += 1
end
end
if not MaxStack or StackCount < MaxStack then
AddEffectStack()
end
elseif StackBehavior == "OverwriteAll" then
for i, v in ipairs(ActiveEffects) do
if v.Target == Target and v.Effect == Effect then
v.StartTime = os.clock()
v.Duration = Duration
if Damage then
v.EffectConfig:SetAttribute("Damage", Damage)
end
if Tick then
v.EffectConfig:SetAttribute("Tick", Tick)
end
if Variant then
v.EffectConfig:SetAttribute("Variant", Variant)
end
if Creator then
if typeof(Creator) == "string" then
v.EffectConfig:SetAttribute("Creator", Creator)
else
local OldTag = v.EffectConfig:FindFirstChild("Creator")
if OldTag then
OldTag:Destroy()
end
local Tag = Instance.new("ObjectValue")
Tag.Name = "Creator"
Tag.Value = Creator
Tag.Parent = v.EffectConfig
end
end
return
end
end
AddEffectStack()
elseif StackBehavior == "Increase" then
for i, v in ipairs(ActiveEffects) do
if v.Target == Target and v.Effect == Effect then
v.Duration += Duration
local Attribute = v.EffectConfig:GetAttribute("Damage")
if Damage and Attribute then
v.EffectConfig:SetAttribute("Damage", Attribute + Damage)
end
end
end
elseif StackBehavior == "ReplaceOldest" then
if not MaxStack or StackCount < MaxStack then
AddEffectStack()
else
local Oldest, OldestIndex, OldestTime = nil, nil, math.huge
for i, v in ipairs(ActiveEffects) do
if v.Target == Target and v.Effect == Effect and v.StartTime < OldestTime then
Oldest = v
OldestIndex = i
OldestTime = v.StartTime
end
end
if Oldest and Oldest.EffectConfig then
Oldest.EffectConfig:Destroy()
table.remove(ActiveEffects, OldestIndex)
end
AddEffectStack()
end
elseif StackBehavior == "Queue" then
while CountActiveStacks(Target, Effect) > 0 do
task.wait(0.1)
end
AddEffectStack()
else
AddEffectStack()
end
else
if #ExistingConfigs == 0 then
AddEffectStack()
end
end
if not CS:HasTag(Target, Effect) then
CS:AddTag(Target, Effect)
end
end
end)
Module script (Setup):
--!strict
local RS = game:GetService("ReplicatedStorage")
local CS = game:GetService("CollectionService")
local FunctionBank = require(script.Parent.Parent.Parent.FunctionBank)
local Effects = FunctionBank.Effects
local Sounds = script.Parent.Parent:FindFirstChild("Sounds")
local EffectHandler = {}
function EffectHandler.Setup(Params: {
Effect: string,
VisualTemplate: any?,
OnTickFunction: (Character: Instance, EffectConfig: Configuration, VisualEffect: any?, Sound: Sound?) -> (),
OnRemoveFunction: ((Character: Instance) -> ())?
})
local Effect = Params.Effect
local VisualTemplate = Params.VisualTemplate
local OnTickFunction = Params.OnTickFunction
local OnRemoveFunction = Params.OnRemoveFunction
local List = {"ParticleEmitter", "Fire", "Smoke", "Sparkles"}
CS:GetInstanceAddedSignal(Effect):Connect(function(Character: Instance)
local Humanoid = Character:FindFirstChildOfClass("Humanoid") :: Humanoid
local Root = Character:FindFirstChild("HumanoidRootPart") :: BasePart
if not Humanoid then return end
local VisualEffect = nil
if VisualTemplate then
if Effects[Effect].SpreadEffect then
local Ignore = false
local Immunity = Character:GetAttribute(Effect .. "Immunity") or 0
if Effect == "Ice" and Immunity < 100 then
Ignore = true
end
if not Ignore then
for i, Part in ipairs(Character:GetChildren()) do
if Part:IsA("BasePart") then
for i, v in ipairs(VisualTemplate:GetChildren()) do
if table.find(List, v.ClassName) then
local VisualEffect = v:Clone()
VisualEffect.Parent = Part
VisualEffect.Enabled = true
VisualEffect:SetAttribute("EffectName", Effect)
end
end
end
end
end
else
if not Root then return end
VisualEffect = VisualTemplate:Clone()
if Effect ~= "Stun" and Effect ~= "Bleed" then
if table.find(List, VisualEffect.ClassName) then
VisualEffect.Enabled = true
VisualEffect.Parent = Root
else
if VisualEffect:IsA("BasePart") then
local Attachment = VisualEffect:FindFirstChildOfClass("Attachment")
if Attachment then
for i, v in ipairs(Attachment:GetChildren()) do
if table.find(List, v.ClassName) then
v.Enabled = true
end
end
Attachment.Parent = Root
Attachment:SetAttribute("EffectName", Effect)
else
for i, v in ipairs(VisualEffect:GetChildren()) do
if table.find(List, v.ClassName) then
v.Enabled = true
v.Parent = Root
v:SetAttribute("EffectName", Effect)
end
end
VisualEffect:Destroy()
end
end
end
else
if table.find(List, VisualEffect.ClassName) then
VisualEffect.Parent = Root
elseif VisualEffect:IsA("BasePart") then
local Visual = VisualEffect:FindFirstChildOfClass("Attachment") or VisualEffect:FindFirstChildOfClass("ParticleEmitter")
if Visual then
VisualEffect = Visual:Clone()
VisualEffect.Parent = Root
end
end
end
end
end
local Sound = Sounds:FindFirstChild(Effect)
if Sound and Sound:IsA("Sound") then
Sound = Sound:Clone()
if Effect ~= "Stun" and Effect ~= "Bleed" and Effect ~= "Void" and Effect ~= "Confusion" then
Sound.Looped = true
end
Sound.Parent = Root
Sound:Play()
end
local function HandleConfig(EffectConfig: Configuration)
if EffectConfig.Name ~= Effect then return end
local Variant = EffectConfig:GetAttribute("Variant")
if Variant and VisualEffect then
if typeof(Variant) == "Color3" then
VisualEffect.Color = ColorSequence.new{
ColorSequenceKeypoint.new(0, Variant),
ColorSequenceKeypoint.new(1, Variant)
}
elseif Variant == "OverseerBurn" then
VisualEffect.Color = ColorSequence.new{
ColorSequenceKeypoint.new(0, Color3.fromRGB(0, 85, 0)),
ColorSequenceKeypoint.new(1, Color3.fromRGB(0, 170, 0))
}
elseif Variant == "Paralyze" then
VisualEffect = script.Parent.Parent.EffectFolder:FindFirstChild("Stun")
if VisualEffect then
local Attachment = VisualEffect:FindFirstChildOfClass("Attachment")
if Attachment then
VisualEffect = Attachment:Clone()
VisualEffect.Parent = Root
end
end
Humanoid:ChangeState(Enum.HumanoidStateType.Ragdoll)
elseif Variant == "Shock" then
Humanoid:ChangeState(Enum.HumanoidStateType.FallingDown)
elseif Variant == "TargetColor" and Root then
VisualEffect.Color = ColorSequence.new{
ColorSequenceKeypoint.new(0, Root.Color),
ColorSequenceKeypoint.new(1, Root.Color)
}
end
end
task.spawn(function()
OnTickFunction(Character, EffectConfig, VisualEffect, Sound)
local StillActive = false
for _, v in ipairs(Character:GetChildren()) do
if v:IsA("Configuration") and v.Name == Effect then
StillActive = true
break
end
end
if not StillActive then
if VisualEffect then
VisualEffect:Destroy()
end
if Sound then
Sound:Destroy()
end
for i, v in ipairs(Character:GetDescendants()) do
if v:GetAttribute("EffectName") and v:GetAttribute("EffectName") == Effect then
v:Destroy()
end
end
end
end)
end
for _, v in ipairs(Character:GetChildren()) do
if v:IsA("Configuration") and v.Name == Effect then
HandleConfig(v)
end
end
local AddedConnection, RemovedConnection
AddedConnection = Character.ChildAdded:Connect(function(Object)
if Object:IsA("Configuration") and Object.Name == Effect then
HandleConfig(Object)
end
end)
RemovedConnection = CS:GetInstanceRemovedSignal(Effect):Connect(function(Object)
if Object == Character then
if OnRemoveFunction then
OnRemoveFunction(Character)
end
if VisualEffect then
VisualEffect:Destroy()
end
if Sound then
Sound:Destroy()
end
if AddedConnection then
AddedConnection:Disconnect()
end
if RemovedConnection then
RemovedConnection:Disconnect()
end
end
end)
end)
end
return EffectHandler
Features
Features
-
Many free presets to choose from (Literally everything you will ever need)
-
Any targets can have any amount of status effects being applied at once
-
Status effects are stackable with various stacking options
-
The same type of effect may look or function differently by setting Variant (Green fire, ragdoll stun, etc…)
Limitations
Limitations
-
I’m a modeler, so my coding skill is limited. HOWEVER, the models I make are also not really good (Jack of
all trades, master of none moment) -
No support for duration-stacking (Using Debris service)- Actually, I just found out an alternative way using BindableEvent as I was making this post -
No colored text formatting here

Story
Story
One day while looking through the code in free models to see if I could learn something interesting, I came across CollectionService and learnt how to use it
As I was developing my game, Mega Boss Battles, I made this open-sourced FreezeModule for everyone to use with my little scripting knowledge
After a while, I realized there was a problem. If I called this function to freeze players from a projectile script or any tools but they get destroyed before the effect is over, they would be stuck. That’s why I decided to spend months on making and improving this system
Update Logs
Update Logs
24/02/2025
- Confusion effect will be reverted correctly upon dying
- Removed the asset file download option from the post, please use the model and update it instead
19/02/2025
Fixed a mistake of mine
It’s supposed to check for the immunity of the target, not the player dealing damage, sorry
I updated this model and the other damage module as well
Support Me - Check the game below to see this system in effect!
No puns intended
Mega Boss Battles (Showcase) | Clothing Store |
Youtube Channel
Support Me - Check the game below to see this system in effect!
No puns intended



