[8 Effects!] Thunder's Status Effect System | Apply unique effects to deal damage over time, restrict movement, alter behavior and more!

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.


📦 Roblox Model | ✨ Showcase game

:page_facing_up: Asset file: Thunder’s Status Effect System.rbxm (39.3 KB)

Introduction

Hi, it’s me, that one modeler again! Thunder

Last time I made this very cool FreezeModule FrostyMan & I revamped Roblox’s TagHumanoid function

So 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:
  1. 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.
  2. 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.
  3. 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
  4. 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 (8 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.
  • :fire: Fire: Burns to deal DoT and melt defense, can set damage and tick, can set color with variant, target turns to coal on death

  • :ice_cube: Ice: Freezes target to reduces defense, stops any movements or animations while retaining physics, creates a thin ice layer on the character. Utilizes my FreezeModule

  • :zap: 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

  • :skull: Poison: Deals DoT while bypassing defense, damage depends on the MaxHealth of the target, can set tick and variant

  • :test_tube: Acid: Corrodes to deal DoT and melt defense, can set damage, tick and variant, target fades away on death.

  • :radioactive: Radiation: Deals DoT, also melts and ignores defense, can set damage, tick and variant, target breaks into pieces on death

  • :question: Confusion: If the target is a Player, their control is inverted (all devices), otherwise, the movement of the target is reversed

  • :milky_way: Void: Halves the damage that the target can deal when applied

  • :link: Love-Link: (Will be added soon after Valentines): Electrocuts targets near the main target who is being affected by this effect, lightning strikes will link the affected targets together

  • :snowflake: Chill: (Will be added on Xmas): Slows down chilled targets

  • :boom: Explosion: (Will be added on New Year Eve): Instantly kills the target and then creates an explosion afterwards

  • :drop_of_blood: Bleed: (Will be added on ??? event): Deals DoT while bypassing defense, damage depends on the remaining Health of the target, can set tick. Effect color depends on the color of the target

  • :nazar_amulet: Teleportation: (Will be added on ??? event): Randomly teleports the target to a random location at random time

Explanation:
  1. DoT: Damage over time. Damage is applied every Tick

  2. 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

  3. 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)

  4. 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

  5. Tick: The rate at which the effect deals damage or activates. Basically firerate

  6. 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

  7. 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

  8. Variant: Customize an effect or give it a different mechanic. This is because you can’t send info to the CollectionService

Examples:
  1. You apply :fire: 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.
  2. You apply :ice_cube: Ice for 2 seconds to a Snowman that has IceImmunity = 100, it will not be frozen at all.
  3. You apply :skull: 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
  4. You apply :test_tube: 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.
  5. You apply :biohazard: 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
  6. You apply :question: 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)
  7. You apply :sob: 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

for _, v in pairs(workspace:GetChildren()) do
	if v:IsA("Model") and v:FindFirstChild("HumanoidRootPart") then
		FunctionBank:AddEffect(v, Effects[math.random(1, #Effects)], 10, 5, script.Parent, nil) -- You don't have to put nil unless if you want to reach the next argument
	end
end

Source code

Script 1 (All available status effects):
-- By ThunderDaNub. More info in the Notes script

local CS = game:GetService("CollectionService")
local SS = game:GetService("ServerStorage")
local SSS = game:GetService("ServerScriptService")
local Players = game:GetService("Players")

local FireEffect = script.Fire
local PoisonEffect = script.Poison
local ElectricityEffect = script.Electricity
local HealEffect = script.Healing.HealFx
local AcidEffect = script.Acid
local RadiationEffect = script.Radiation
local VoidEffect = script.Void
local ConfusionEffect = script.Confusion

local FunctionBank = require(script.Parent.FunctionBank)
--local DeathEffects = require(SSS.Main.Game.DeathEffects) -- Custom death effect, not ready for this system yet wait for me to update

local function CheckForEffectConfig(Target, Effect)
	for i, v in ipairs(Target:GetChildren()) do
		if v.Name == Effect and v:IsA("Configuration") then
			return v
		end
	end
end

local function DealDamage(Damage, Effect, Creator, Target, Humanoid, IgnoreDefense, IgnoreImmunity, DeathEffect)
	if Humanoid.Health > Damage then
		if Creator and Players:FindFirstChild(Creator) and Players:FindFirstChild(Creator):IsA("Player") then
			local Player = Players:FindFirstChild(Creator)
			if Player and Player:IsA("Player") then
				FunctionBank:Damage(Player, Target, math.round(Damage), IgnoreDefense, IgnoreImmunity, Effect)
			end
		else
			Humanoid.Health = Humanoid.Health - math.round(Damage)
		end
	else
		if Creator and Players:FindFirstChild(Creator) and Players:FindFirstChild(Creator):IsA("Player") then
			local Player = Players:FindFirstChild(Creator)
			if Player and Player:IsA("Player") then
				FunctionBank:Damage(Player, Target, Humanoid.Health, IgnoreDefense, IgnoreImmunity, Effect)
			end
		else
			Humanoid.Health = 0
		end

		--DeathEffects.Play(Target, DeathEffect) -- Play a custom death effect, not ready for this system yet wait for me to update
	end
end

CS:GetInstanceAddedSignal("Stun"):Connect(function(Character)
	local Effect = "Stun"
	local Humanoid = Character:FindFirstChildOfClass("Humanoid")
	local Root = Character:FindFirstChild("HumanoidRootPart")
	local EffectConfig = CheckForEffectConfig(Character, Effect)

	if Root and Humanoid and EffectConfig then
		local VisualEffect = ElectricityEffect:Clone()
		VisualEffect.Parent = Root
		
		local Damage = EffectConfig:GetAttribute("Damage") or 0
		local Tick = EffectConfig:GetAttribute("Tick") or 0.1
		local Creator = EffectConfig:GetAttribute("Creator")
		local Variant = EffectConfig:GetAttribute("Variant")
		local Force = Vector3.one * 1e9
		local AnimationSpeed = 0.2

		if Character:GetAttribute(Effect .. "Immunity") then
			Damage = Damage - Damage * (Character:GetAttribute(Effect .. "Immunity") / 100)
			Tick = Tick + Tick * (Character:GetAttribute(Effect .. "Immunity") / 100)
			Force = Force / Force * (Character:GetAttribute(Effect .. "Immunity") / 100)
			AnimationSpeed = AnimationSpeed + AnimationSpeed * (Character:GetAttribute(Effect .. "Immunity") / 100)

			if Tick <= 0.05 then
				Tick = 0.05
			end
			if AnimationSpeed <= 0.05 then
				AnimationSpeed = 0.05
			end
		end

		if Variant then
			if typeof(Variant) == "Color3" then
				VisualEffect.Color = ColorSequence.new{
					ColorSequenceKeypoint.new(0, Variant),
					ColorSequenceKeypoint.new(1, Variant)
				}
				Character:SetAttribute(Effect .. "Variant", nil)
			elseif Variant == "Paralyze" then
				Humanoid:ChangeState(Enum.HumanoidStateType.Ragdoll)
			elseif Variant == "Shock" then
				Humanoid:ChangeState(Enum.HumanoidStateType.FallingDown)
			end
		end

		task.spawn(function()
			local BV
			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
				VisualEffect:Emit(1)
				
				if BV then
					BV.Velocity = Root.Position.Unit * 0 + Vector3.new(0, -0.1, 0)
					
					if Character:GetAttribute(Effect .. "Immunity") then
						if Character:GetAttribute(Effect .. "Immunity") < 100 then
							FunctionBank:MeltDefense(Character, "Lightning", 1)
						end
					else
						FunctionBank:MeltDefense(Character, "Lightning", 1)
					end
				end
				
				if Humanoid and Humanoid:FindFirstChildOfClass("Animator") then
					local Animator = Humanoid:FindFirstChildOfClass("Animator")
					for i, v in pairs(Animator:GetPlayingAnimationTracks()) do
						v:AdjustSpeed(AnimationSpeed)
					end
				end
				
				task.wait(Tick)
			until not CS:HasTag(Character, "Stun")

			if Humanoid:FindFirstChildOfClass("Animator") then
				local Animator = Humanoid:FindFirstChildOfClass("Animator")
				for i, v in pairs(Animator:GetPlayingAnimationTracks()) do
					v:AdjustSpeed(1)
				end
			end
			Humanoid:ChangeState(Enum.HumanoidStateType.Freefall)
			if BV then
				BV:Destroy()
				BV = nil
			end
			if VisualEffect then
				VisualEffect:Destroy()
				VisualEffect = nil
			end
			Creator = nil
		end)
	end
end)

CS:GetInstanceAddedSignal("Fire"):Connect(function(Character)
	local Effect = "Fire"
	local Humanoid = Character:FindFirstChildOfClass("Humanoid")
	local Root = Character:FindFirstChild("HumanoidRootPart")
	local EffectConfig = CheckForEffectConfig(Character, Effect)

	if Root and EffectConfig then
		local VisualEffect = FireEffect:Clone()
		VisualEffect.Parent = Root
		VisualEffect.Enabled = true
		
		local Damage = EffectConfig:GetAttribute("Damage") or 5
		local Tick = EffectConfig:GetAttribute("Tick") or 1
		local Creator = EffectConfig:GetAttribute("Creator")
		local Variant = EffectConfig:GetAttribute("Variant")
		
		if Variant 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))
				}
			end
		end
		
		task.spawn(function()
			repeat
				if Humanoid and Humanoid.Health > 0 then
					DealDamage(Damage, Effect, Creator, Character, Humanoid, false, false, "Burn")
					
					FunctionBank:MeltDefense(Character, Effect, 2)
				else
					break
				end
				task.wait(Tick)
			until not CS:HasTag(Character, Effect)
			
			if VisualEffect then
				VisualEffect:Destroy()
				VisualEffect = nil
			end
			Creator = nil
		end)
	end
end)

CS:GetInstanceAddedSignal("Acid"):Connect(function(Character)
	local Effect = "Acid"
	local Humanoid = Character:FindFirstChildOfClass("Humanoid")
	local Root = Character:FindFirstChild("HumanoidRootPart")
	local EffectConfig = CheckForEffectConfig(Character, Effect)

	if Root and EffectConfig then
		local VisualEffect = AcidEffect:Clone()
		VisualEffect.Parent = Root
		VisualEffect.Enabled = true

		local Damage = EffectConfig:GetAttribute("Damage") or 5
		local Tick = EffectConfig:GetAttribute("Tick") or 1
		local Creator = EffectConfig:GetAttribute("Creator")
		local Variant = EffectConfig:GetAttribute("Variant")

		if Variant then
			if typeof(Variant) == "Color3" then
				VisualEffect.Color = ColorSequence.new{
					ColorSequenceKeypoint.new(0, Variant),
					ColorSequenceKeypoint.new(1, Variant)
				}
			end
		end

		task.spawn(function()
			repeat
				if Humanoid and Humanoid.Health > 0 then
					DealDamage(Damage, Effect, Creator, Character, Humanoid, false, false, "Fade")

					FunctionBank:MeltDefense(Character, Effect, 2)
				else
					break
				end
				task.wait(Tick)
			until not CS:HasTag(Character, Effect)

			if VisualEffect then
				VisualEffect:Destroy()
				VisualEffect = nil
			end
			Creator = nil
		end)
	end
end)

CS:GetInstanceAddedSignal("Radiation"):Connect(function(Character)
	local Effect = "Radiation"
	local Humanoid = Character:FindFirstChildOfClass("Humanoid")
	local Root = Character:FindFirstChild("HumanoidRootPart")
	local EffectConfig = CheckForEffectConfig(Character, Effect)

	if Root and EffectConfig then
		local VisualEffect = RadiationEffect:Clone()
		VisualEffect.Parent = Root
		VisualEffect.Enabled = true

		local Damage = EffectConfig:GetAttribute("Damage") or 5
		local Tick = EffectConfig:GetAttribute("Tick") or 1
		local Creator = EffectConfig:GetAttribute("Creator")
		local Variant = EffectConfig:GetAttribute("Variant")
		
		if Variant then
			if typeof(Variant) == "Color3" then
				VisualEffect.Color = ColorSequence.new{
					ColorSequenceKeypoint.new(0, Variant),
					ColorSequenceKeypoint.new(1, Variant)
				}
			end
		end

		task.spawn(function()
			repeat
				if Humanoid and Humanoid.Health > 0 then
					DealDamage(Damage, Effect, Creator, Character, Humanoid, true, false, "JointBreak")

					FunctionBank:MeltDefense(Character, Effect, 2)
				else
					break
				end
				task.wait(Tick)
			until not CS:HasTag(Character, Effect)

			if VisualEffect then
				VisualEffect:Destroy()
				VisualEffect = nil
			end
			Creator = nil
		end)
	end
end)

local FreezeModule = require(script.FreezeModuleModified)

CS:GetInstanceAddedSignal("Ice"):Connect(function(Character)
	local Effect = "Ice"
	if Character:GetAttribute(Effect .. "Immunity") and Character:GetAttribute(Effect .. "Immunity") >= 100 then
		return
	end

	if Character and Character:FindFirstChildOfClass("Humanoid") then
		FreezeModule.Freeze(Character)
		Character:SetAttribute("Frozen", true)
		FunctionBank:MeltDefense(Character, Effect, 5)
	end
end)

CS:GetInstanceRemovedSignal("Ice"):Connect(function(Character)
	if Character and Character:FindFirstChildOfClass("Humanoid") then
		FreezeModule.Unfreeze(Character)
		Character:SetAttribute("Frozen", nil)
	end
end)

CS:GetInstanceAddedSignal("Void"):Connect(function(Character)
	local Effect = "Void"
	if Character:GetAttribute(Effect .. "Immunity") and Character:GetAttribute(Effect .. "Immunity") >= 100 then
		return
	end
	local Humanoid = Character:FindFirstChildOfClass("Humanoid")
	local Root = Character:FindFirstChild("HumanoidRootPart")

	if Root then
		local VisualEffect = VoidEffect:Clone()
		VisualEffect.Parent = Root
		VisualEffect.Enabled = true

		task.spawn(function()
			repeat
				if Humanoid and Humanoid.Health > 0 then
					if not Character:GetAttribute("Voided") then
						Character:SetAttribute("Voided", true)
					end
					FunctionBank:MeltDefense(Character, Effect, 5)
				else
					break
				end
				task.wait(1)
			until not CS:HasTag(Character, Effect)
			
			if Character and Character:GetAttribute("Voided") then
				Character:SetAttribute("Voided", nil)
			end
			if VisualEffect then
				VisualEffect:Destroy()
				VisualEffect = nil
			end
		end)
	end
end)

CS:GetInstanceAddedSignal("Poison"):Connect(function(Character)
	local Effect = "Poison"
	local Humanoid = Character:FindFirstChildOfClass("Humanoid")
	local Root = Character:FindFirstChild("HumanoidRootPart")
	local EffectConfig = CheckForEffectConfig(Character, Effect)

	if Root and EffectConfig then
		local VisualEffect = PoisonEffect:Clone()
		VisualEffect.Parent = Root
		VisualEffect.Enabled = true
		
		local Damage = math.round(Humanoid.MaxHealth * EffectConfig:GetAttribute("Damage")) or math.round(Humanoid.MaxHealth * 0.025)
		local Tick = EffectConfig:GetAttribute("Tick") or 0.5
		local Creator = EffectConfig:GetAttribute("Creator")
		local Variant = EffectConfig:GetAttribute("Variant")

		if Variant then
			if typeof(Variant) == "Color3" then
				VisualEffect.Color = ColorSequence.new{
					ColorSequenceKeypoint.new(0, Variant),
					ColorSequenceKeypoint.new(1, Variant)
				}
			end
		end

		task.spawn(function()
			repeat
				if Humanoid and Humanoid.Health > 0 then
					DealDamage(Damage, Effect, Creator, Character, Humanoid, true, false, nil)
				else
					break
				end
				task.wait(Tick)
			until not CS:HasTag(Character, Effect)

			if VisualEffect then
				VisualEffect:Destroy()
				VisualEffect = nil
			end
			Creator = nil
		end)
	end
end)

CS:GetInstanceAddedSignal("Heal"):Connect(function(Character)
	local Effect = "Heal"
	local Humanoid = Character:FindFirstChildOfClass("Humanoid")
	local Root = Character:FindFirstChild("HumanoidRootPart")
	local EffectConfig = CheckForEffectConfig(Character, Effect)

	if Root and EffectConfig then
		local VisualEffect = HealEffect:Clone()
		VisualEffect.Parent = Root
		for _, v in pairs(VisualEffect:GetChildren()) do
			if v:IsA("ParticleEmitter") then
				v.Enabled = true
			end
		end

		local Damage = EffectConfig:GetAttribute("Damage") or 5
		local Tick = EffectConfig:GetAttribute("Tick") or 0.5
		local Variant = EffectConfig:GetAttribute("Variant")

		if Variant then
			if typeof(Variant) == "Color3" then
				VisualEffect.Color = ColorSequence.new{
					ColorSequenceKeypoint.new(0, Variant),
					ColorSequenceKeypoint.new(1, Variant)
				}
			end
		end
		
		task.spawn(function()
			repeat
				if 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
				else
					break
				end
				task.wait(Tick)
			until not CS:HasTag(Character, Effect)

			if VisualEffect then
				VisualEffect:Destroy()
				VisualEffect = nil
			end
		end)
	end
end)

CS:GetInstanceAddedSignal("Confusion"):Connect(function(Character)
	local Effect = "Confusion"
	if Character:GetAttribute(Effect .. "Immunity") and Character:GetAttribute(Effect .. "Immunity") >= 100 then
		return
	end
	local Humanoid = Character:FindFirstChildOfClass("Humanoid")
	local Root = Character:FindFirstChild("HumanoidRootPart")
	local EffectConfig = CheckForEffectConfig(Character, Effect)

	if Root and EffectConfig then
		local VisualEffect = ConfusionEffect:Clone()
		VisualEffect.Parent = Root
		VisualEffect.Enabled = true
		
		local Tick = EffectConfig:GetAttribute("Tick") or 0.01
		local Creator = EffectConfig:GetAttribute("Creator")
		local Variant = EffectConfig:GetAttribute("Variant")

		if Variant then
			if typeof(Variant) == "Color3" then
				VisualEffect.Color = ColorSequence.new{
					ColorSequenceKeypoint.new(0, Variant),
					ColorSequenceKeypoint.new(1, Variant)
				}
			end
		end

		task.spawn(function()
			local InvertControls = nil
			local BV = nil

			if Character and Humanoid then
				if Players:GetPlayerFromCharacter(Character) and not Character:FindFirstChild("InvertControls") then
					InvertControls = script.InvertControls:Clone()
					InvertControls.Parent = Character
					InvertControls.Enabled = true
				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 * 1e6
							BV.Velocity = Root.Velocity * -1
							BV.Parent = Root
						end
						BV.Velocity = Root.Velocity * -1
					end

					task.wait(Tick)
				until not CS:HasTag(Character, Effect)
			end
			
			if BV then
				BV:Destroy()
				BV = nil
			end
			if VisualEffect then
				VisualEffect:Destroy()
				VisualEffect = nil
			end
			if InvertControls then
				InvertControls:Destroy()
				InvertControls = nil

				local Reset = script.Reset:Clone()
				Reset.Parent = Character
				Reset.Enabled = true
				task.delay(1, function()
					if Reset then
						Reset:Destroy()
						Reset = nil
					end
				end)
			end
			Creator = nil
		end)
	end
end)
Script 2 (StatusEffectHandler):
-- By ThunderDaNub. More info in the Notes script

local RS = game:GetService("ReplicatedStorage")
local CS = game:GetService("CollectionService")
local SS = game:GetService("ServerStorage")
local SSS = game:GetService("ServerScriptService")
local Running = game:GetService("RunService")

local AddEffect = script:FindFirstChild("AddEffect")

local FunctionBank = require(script.Parent.FunctionBank)
local Effects = FunctionBank.Effects

local ActiveEffects = {}

local function TableFind(Table, Value)
	for i, v in ipairs(Table) do
		if string.match(v, Value) then
			return v
		end
	end
end

local function IncreaseEffectDuration(Target, Effect, ExtraDuration)
	for _, v in ipairs(ActiveEffects) do
		if v.Target == Target and v.Effect == Effect then
			v.Duration = v.Duration + ExtraDuration
			return true
		end
	end
	return false
end

local Signal
local function StartEffectLoop()
	if Signal then return end
	
	Signal = Running.Heartbeat:Connect(function()
		local t = 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 (t - v.Tick) >= v.Duration or (EffectConfig and (not EffectConfig.Parent or not EffectConfig.Parent.Parent)) then
					if 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
			Signal:Disconnect()
			Signal = nil
		end
	end)
end

local function AddNewEffect(Arg)
	table.insert(ActiveEffects, Arg)
	StartEffectLoop()
end

AddEffect.Event:Connect(function(Target, Effect, Duration, Damage, Creator, Tick, Variant, Stackable)
	if Target and Effect and Duration then
		for i, v in ipairs(Target:GetChildren()) do
			if v.Name == Effect and v:IsA("Configuration") then
				if Stackable then
					IncreaseEffectDuration(Target, Effect, Duration)
				end
				return
			end
		end

		local Effect = TableFind(Effects, Effect)
		if Effect then
			local EffectConfig = script.EffectTemplate:Clone()
			EffectConfig.Name = Effect
			if Damage and tonumber(Damage) ~= nil then
				EffectConfig:SetAttribute("Damage", tonumber(Damage))
			end
			if Tick and tonumber(Tick) ~= nil then
				EffectConfig:SetAttribute("Tick", tonumber(Tick))
			end
			if Creator then
				if typeof(Creator) == "string" then
					EffectConfig:SetAttribute("Creator", Creator)
				else
					EffectConfig:SetAttribute("Creator", Creator.Name)
				end
			end
			if Variant then
				EffectConfig:SetAttribute("Variant", Variant)
			end
			Duration = tonumber(Duration)
			if Target:GetAttribute(Effect .. "Immunity") then
				if Target:GetAttribute(Effect .. "Immunity") <= 100 then
					Duration = Duration - Duration * (Target:GetAttribute(Effect .. "Immunity") / 100)
				end
			end
			task.defer(function()
				if Duration > 0 then
					AddNewEffect({Target = Target, Effect = Effect, EffectConfig = EffectConfig, Tick = os.clock(), Duration = Duration})
					
					EffectConfig.Parent = Target
					if not CS:HasTag(Target, Effect) then
						CS:AddTag(Target, Effect)
					end
				else
					EffectConfig:Destroy()
					EffectConfig = nil
				end
			end)
		end
	end
end)
Module script (Configuration/settings and other functions):
-- By ThunderDaNub. More info in the Notes script

local Module = {}

local Players = game:GetService("Players")

Module.DefaultStats = {
	["MaxHealth"] = 100,
	["WalkSpeed"] = 16,
	["JumpPower"] = 50,
}

Module.Effects = {"Stun", "Fire", "Poison", "Heal", "Ice", "Acid", "Radiation", "Confusion", "Void"}

Module.Elements = {
	["Fire"] = {Limit = 15},
	["Ice"] = {Limit = 15},
	["Lightning"] = {Limit = 15},
	["Acid"] = {Limit = 15},
	["Radiation"] = {Limit = 15},
	["Void"] = {Limit = 15},
}

Module.Immunities = {
	"FireImmunity",
	"IceImmunity",
	"PoisonImmunity",
	"StunImmunity",
	"AquaImmunity",
	"ExplosiveImmunity",
	"AcidImmunity",
	"RadiationImmunity",
}

local AddEffect = script.Parent.StatusEffectHandler:FindFirstChild("AddEffect")

function Module:AddEffect(Target, Effect, Duration, Damage, Creator, Tick, Variant, Stackable)
	AddEffect:Fire(Target, Effect, Duration, Damage, Creator, Tick, Variant, Stackable)
end

function Module:MeltDefense(Target, Element, Amount)
	if Target and Target:GetAttribute("Defense") and Target:GetAttribute("Defense") > 0 then
		local MeltedAmount = Target:GetAttribute(Element .. "DefenseMelting")

		if MeltedAmount then	
			local Limit = Module.Elements[Element].Limit

			if MeltedAmount < Limit then
				if Limit - MeltedAmount >= Amount then
					Target:SetAttribute(Element .. "DefenseMelting", MeltedAmount + Amount)
					Target:SetAttribute("Defense", Target:GetAttribute("Defense") - Amount)
				else
					Target:SetAttribute(Element .. "DefenseMelting", Limit)
					Target:SetAttribute("Defense", Target:GetAttribute("Defense") - (Limit - MeltedAmount))
				end
			end
		else
			Target:SetAttribute(Element .. "DefenseMelting", Amount)
			Target:SetAttribute("Defense", Target:GetAttribute("Defense") - Amount)
		end
	end
end

function Module:TagForDamage(Player: Player, Target, Damage: number)
	local Humanoid = Target:FindFirstChildOfClass("Humanoid")
	if not Humanoid or Humanoid.Health <= 0 then return end

	local PlayerTags = Target:FindFirstChild("PlayerTags")
	if not PlayerTags then
		PlayerTags = Instance.new("Configuration")
		PlayerTags.Name = "PlayerTags"
		PlayerTags.Parent = Target
	end

	local ExistingTag = PlayerTags:GetAttribute(Player.UserId)
	if ExistingTag then
		PlayerTags:SetAttribute(Player.UserId, ExistingTag + Damage)
	else
		PlayerTags:SetAttribute(Player.UserId, Damage)
	end

	if Damage >= Humanoid.Health then
		PlayerTags:SetAttribute("LastHit", Player.UserId)
	end
end

function Module:Damage(Player: Player, Target, Damage, IgnoreDefense, IgnoreImmunity, AttackType, Cooldown)
	if Player and Target and Target:FindFirstChildOfClass("Humanoid") and tonumber(Damage) ~= nil then
		local TargetHum = Target:FindFirstChildOfClass("Humanoid")
		if TargetHum.Health <= 0 then return end

		local Character = Player
		if Player:IsA("Player") then
			Character = Player.Character
		else
			Player = Players:GetPlayerFromCharacter(Player)
			Character = Player.Character
		end

		if Cooldown then
			if Target:GetAttribute(Player.UserId) then return end
			Target:SetAttribute(Player.UserId, true)
			task.delay(Cooldown, function()
				Target:SetAttribute(Player.UserId, nil)
			end)
		end

		if Character and Character:GetAttribute("Voided") then
			Damage = Damage / 2
		end
		if not IgnoreDefense and Target:GetAttribute("Defense") then
			Damage = Damage - (Damage * Target:GetAttribute("Defense") / 100)
		end
		if not IgnoreImmunity and AttackType and Target:GetAttribute(AttackType .. "Immunity") then
			Damage = Damage - Damage * (Target:GetAttribute(AttackType .. "Immunity") / 100)
		end
		Damage = math.clamp(Damage, 0, TargetHum.Health)

		Module:TagForDamage(Player, Target, Damage)
		TargetHum:TakeDamage(Damage)

		return Damage
	end
end

return Module

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 effect duration is stackable

  • The same type of effect may look or function differently by setting Variant (Green fire, ragdoll stun, etc…)


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 :sob:


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

cricket noises


Support Me - Check the game below to see this system in effect!
No puns intended

Mega Boss Battles (Showcase) | Clothing Store | Youtube Channel

:package: Roblox Model
:page_facing_up: Asset file: Thunder’s Status Effect System.rbxm (39.3 KB)

7 Likes

I’ll add GIF and video examples in the next few days.
In the meanwhile you can play the game I linked in the post to see what you can use these effects are. Note: You have to get certain gears, or get attacked by certain bosses with elemental attacks!

If you have any feedback, ideas or suggestions to improve this please tell me, I appreciate that!

1 Like

Heya! long time no see, you made a very unique new stuff beside your freezing module :slight_smile:

1 Like

Hi! Thanks, It’s been a while lol my scripting skill improved so much since then

1 Like

I was searching up a little good effects for my RPG i’m currently developing and found it :smiley:

1 Like

I added a simple showcase video, might make a more detailed GIF with all types of effects and targets with different attributes later
Edit: Omg typos :sob:

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

if not IgnoreImmunity and AttackType and Target:GetAttribute(AttackType .. "Immunity") then
			Damage = Damage - Damage * (Target:GetAttribute(AttackType .. "Immunity") / 100)
		end