Stats Handler System
☆⸻●⸻☆
A flexible and efficient stat handler system for characters. Supports flat and additive or multiplicative stacking. Easily apply, remove, or clear modifiers by source or type. Perfect for applying and stacking buffs, debuffs and status effects. Has a lot of examples within the module
Basically allows stacking buffs/debuffs on players/NPCs with many active buffs at once. New buffs won't overlap/override old buffs. Removing a buff will not reset the stat and will maintain running buffs
Introduction
Introduction
Hi, it’s me, that one modeler again!
One day, I came across this post by Lightlimn which could fix overlapped walkspeed issue
It worked perfectly, but there weren’t many features and you could only modify walkspeed so I decided to improve the original work
I’m quite surprised that a lot of people stumble upon this same problem but all they choose is a short-term solution lol
This module is released to prepare for my upcoming Status Effects
I hope this module will help you!
How this system works:
-
When a stat modifier is applied to a character, it’s inserted into a table
-
Stats like MaxHealth, WalkSpeed or JumpPower will be applied to the corresponding Humanoid property
-
Otherwise, they are stored as Attributes on the character
-
The same stat can be stacked as many times as you want as long as the sources (ModifierName argument) are unique, or else it will just override with the new value. You can also choose to add, subtract, multiply or divide, just take a look at the examples, I showed the calculations as well
-
How it calculates the final stat: First it runs a loop through all the active modifiers in the table we just mentioned (Or you can call them buffs) to get the buff amount and add them up. Flat value and Multiplier value are separated. Then it’s calculated like below:
-
Flat = Flat1 + Flat2 + FlatX
-
Multiplier = Multiplier1 + Multiplier2 + MultiplierX (MultiplyMode setting = false)
-
Multiplier = Multiplier1 x Multiplier2 x MultiplierX (MultiplyMode setting = true)
-
Final number = (Base/Default + Flat value) x Multiplier
-
Confusing? Again, check examples there are detailed calculations
-
Character: The target that will receive the stat change (Model with Humanoid obviously)
-
Type: The stat name. For instance, WalkSpeed, JumpPower, MaxHealth, Defense, RegenerateRate, RegeneratePercent, DamageBoost, FireImmunity, StunImmunity, etc…
-
ModifierName: The source of the modifier like “Tool” or “Armor” or “Boost”, basically a unique string to help identify where it’s located from. If you put the same mod name it will override/reapply on the already existing stat mod of that same mod name (Hope this makes sense)
-
Amount: The value to change, can be a negative (-) number if you want to subtract for a debuff
-
Multiplier: The percentage (%) to multiply a stat by, can be negative to decrease a stat by % amount
How To Use
How To Use
-
Get the model, insert it and move the module to ServerScriptService or any places you want
-
In your script, require this module
-
Use it!
-
There are examples within the module as well
Source Code
Source Code
Full script:
--[[
Original module by @Lightlimn (WalkSpeedHandler)
Improved by @ThunderDaNub (Multiple stats, various modes, memory leak fixed + cleanups, more functions, etc...)
]]
local Players = game:GetService("Players")
local MultiplyMode = false
-- false = Additive multipliers: Each multiplier is added together before being applied
-- Example:
-- Base = 10
-- +25% and +50% --> (1 + 0.25 + 0.5) = 1.75
-- Final = 10 * 1.75 = 17.5
-- true = Multiplicative multipliers: Each multiplier is applied one after another
-- Example:
-- Base = 10
-- +25% and +50% --> (1 + 0.25) * (1 + 0.5) = 1.25 * 1.5 = 1.875
-- Final = 10 * 1.875 = 18.75
-- The amount can be a negative number to decrease the final number as well
local Module = {}
local Modifiers = {}
-- This table defines which stats directly affect the Humanoid object
-- If a modified stat is listed here, its value will be applied to the corresponding Humanoid property (Ex: Humanoid.WalkSpeed = Stat)
-- For all other stats, values will be stored as attributes on the Character instead (Change the attribute location if you want)
local HumanoidProperties = {
MaxHealth = true,
WalkSpeed = true,
JumpPower = true,
}
-- This is just an example, I put [""] because it looks cooler this way
-- For stats that are set as attributes on the characters you gotta implement your own code to use them
local DefaultStats = {
["MaxHealth"] = 100,
["WalkSpeed"] = 16,
["JumpPower"] = 50,
["RegenerateRate"] = 1,
["RegeneratePercent"] = 1,
["Defense"] = 0,
["DamageBoost"] = 0,
["FireImmunity"] = 0,
["IQ"] = 100,
}
function Module.UpdateModifiers(Character: Model, Type: "StatName")
local Humanoid = Character:FindFirstChildOfClass("Humanoid")
local Stat = DefaultStats[Type]
assert(Stat, "Stat named " .. Type .. " is not included in DefaultStats table")
if not Players:GetPlayerFromCharacter(Character) and Humanoid and HumanoidProperties[Type] then
local Initial = Character:GetAttribute("Initial_" .. Type)
if not Initial then
Character:SetAttribute("Initial_" .. Type, Humanoid[Type])
else
Stat = Initial
end
end
local TotalFlat = 0
local TotalMultiplier = MultiplyMode and 1 or 0
local Mods = Modifiers[Character] and Modifiers[Character][Type]
if Mods then
for _, Mod in pairs(Mods) do
TotalFlat += Mod.Flat or 0
if Mod.Multiplier then
if MultiplyMode then
TotalMultiplier *= (1 + Mod.Multiplier)
else
TotalMultiplier += Mod.Multiplier
end
end
end
end
local FinalStat = (Stat + TotalFlat) * TotalMultiplier
local FinalStat = (Stat + TotalFlat) * (MultiplyMode and TotalMultiplier or (1 + TotalMultiplier))
if Humanoid and HumanoidProperties[Type] then
Humanoid[Type] = FinalStat
if Type == "MaxHealth" and Humanoid.Health > Humanoid.MaxHealth then
Humanoid.Health = Humanoid.MaxHealth
elseif Type == "JumpPower" and Humanoid.UseJumpPower == false then
Humanoid.UseJumpPower = true
end
elseif Mods then
Character:SetAttribute(Type, FinalStat)
else
Character:SetAttribute(Type, nil)
end
end
--[[
You can use this in two ways:
local Handler = require(game.Path_To_This_Module)
local Character = Your_Target
1. Pass a single stat name and an amount:
Handler.AddModifier(Character, "WalkSpeed", "Wow", 16)
2. Pass a table:
Handler.AddModifier(Character, {
["WalkSpeed"] = {Flat = -10, Multiplier = -0.25}, -- (Optional) Specify if it's incremental or multiplicative
["JumpPower"] = -25, -- Or simply put a number unlike above, for Flat value by default
["RegeneratePercent"] = -1, -- This one will be set as an attribute because it isn't a Humanoid property (More info above)
}, "DebuffEffect")
This function below will automatically check for the type you send
--]]
function Module.AddModifier(Character: Model, Type: "StatName/StatTable", ModifierName: "Source", Amount: number?, Multiplier: number?)
if typeof(Type) == "table" then
for Stat, Value in pairs(Type) do
if typeof(Value) == "table" then
Module.AddModifier(Character, Stat, ModifierName, Value.Flat or 0, Value.Multiplier or 0)
else
Module.AddModifier(Character, Stat, ModifierName, Value, 0)
end
end
return
end
if not Modifiers[Character] then
Modifiers[Character] = {}
local DestroyingConnection, AncestryChangedConnection
AncestryChangedConnection = Character.AncestryChanged:Connect(function()
if not Character:IsDescendantOf(game) then
DestroyingConnection:Disconnect()
AncestryChangedConnection:Disconnect()
Modifiers[Character] = nil
end
end)
DestroyingConnection = Character.Destroying:Once(function()
AncestryChangedConnection:Disconnect()
Modifiers[Character] = nil
end)
end
if not Modifiers[Character][Type] then
Modifiers[Character][Type] = {}
end
Modifiers[Character][Type][ModifierName] = {
Flat = Amount or 0,
Multiplier = Multiplier or 0,
}
Module.UpdateModifiers(Character, Type)
end
-- Use when you want to remove only one source of modification (Ex: A speed boost from a specific tool)
function Module.RemoveModifier(Character: Model, Type: "StatName", ModifierName: "Source")
if Modifiers[Character] and Modifiers[Character][Type] then
Modifiers[Character][Type][ModifierName] = nil
end
Module.UpdateModifiers(Character, Type)
end
-- Use for bulk removal, you can clear:
-- - All modifiers affecting a character by passing just the Character
-- - Type: All modifiers of a specific stat Type (Pass "WalkSpeed", 3 active WalkSpeed buffs --> None, keep other stats)
-- - ModifierName: A specific modifier source (Pass "Armor" to remove only mods from "Armor", keep the rest)
-- - In short, pass only Character, or Character & Type, or Character & ModifierName, or all 3 for different cases
function Module.ClearModifiers(Character: Model, Type: "StatName", ModifierName: "Source")
if not Modifiers[Character] then return end
if ModifierName and not Type then
for Stat, Sources in pairs(Modifiers[Character]) do
Sources[ModifierName] = nil
if not next(Sources) then
Modifiers[Character][Stat] = nil
end
Module.UpdateModifiers(Character, Stat)
end
if not next(Modifiers[Character]) then
Modifiers[Character] = nil
end
elseif Type then
if Modifiers[Character][Type] then
if ModifierName then
Modifiers[Character][Type][ModifierName] = nil
if not next(Modifiers[Character][Type]) then
Modifiers[Character][Type] = nil
end
else
Modifiers[Character][Type] = nil
end
Module.UpdateModifiers(Character, Type)
end
else
Modifiers[Character] = nil
for stat in pairs(DefaultStats) do
Module.UpdateModifiers(Character, stat)
end
end
end
return Module
Code examples from the model:
-- Put this in a Part or anything that can use Touched event and modify it to what you want
local StatsHandler = require(game:GetService("ServerScriptService").StatsHandler)
script.Parent.Touched:Connect(function(Hit)
if Hit and Hit.Parent and Hit.Parent:IsA("Model") then
local Character = Hit.Parent
if Character:FindFirstChildOfClass("Humanoid") then
StatsHandler.AddModifier(Character, "WalkSpeed", "Boost", 32)
StatsHandler.AddModifier(Character, "JumpPower", "Boost", 100)
-- This doesn't stack btw, to do so you need unique ModifierNames
--[[
Too many function calls? Use a table
StatsHandler.AddModifier(Character, {
WalkSpeed = 32,
JumpPower = 100,
Defense = 50,
IQ = -199,
}, "Boost")
Want to remove?
StatsHandler.RemoveModifier(Character, "WalkSpeed", "Boost")
StatsHandler.RemoveModifier(Character, "JumpPower", "Boost")
Or simply do this
To remove all mods granted by this "Boost" modifier source
StatsHandler.ClearModifiers(Character, nil, "Boost")
Don't wanna do it like others?
This will remove ALL active WalkSpeed and JumpPower boosts no matter the sources
StatsHandler.RemoveModifier(Character, "WalkSpeed", nil)
StatsHandler.RemoveModifier(Character, "JumpPower", nil)
]]
end
end
end)
local StatsHandler = require(game:GetService("ServerScriptService").StatsHandler)
while task.wait(10) do
for _, v in pairs(workspace:GetChildren()) do
if v:IsA("Model") and v:FindFirstChildOfClass("Humanoid") then
task.spawn(function()
StatsHandler.AddModifier(v, {
WalkSpeed = 32,
JumpPower = 100,
Defense = 50,
IQ = -199, -- This is not a Humanoid property so it will be set as an attribute (Info in module)
}, "Boost2")
task.wait(2)
StatsHandler.AddModifier(v, "WalkSpeed", "Boost2", 64) -- Change the current WalkSpeed boost
task.wait(2)
StatsHandler.AddModifier(v, "WalkSpeed", "Boost3", 20) -- Add 1 extra WalkSpeed boost
StatsHandler.RemoveModifier(v, "JumpPower", "Boost2") -- Remove JumpPower boost
task.wait(3)
StatsHandler.ClearModifiers(v, nil, "Boost2") -- You can either use Remove like above
StatsHandler.ClearModifiers(v, nil, "Boost3") -- or use Clear to remove in bulk
end)
end
end
end
local StatsHandler = require(game:GetService("ServerScriptService").StatsHandler)
local Character = nil -- Choose a target to test
-- MultiplyMode is a setting in the main module (More info there)
StatsHandler.AddModifier(Character, "WalkSpeed", "Boost", 10)
-- Default = 16
-- Flat = +10
-- Multiplier = 1
-- Final = (16 + 10) * 1 = 26
-- MultiplyMode = false
StatsHandler.AddModifier(Character, "WalkSpeed", "Boost", 0, 0.25)
-- Default = 16
-- Flat = 0
-- Multiplier = 1 + 0.25 = 1.25
-- Final = 16 * 1.25 = 20
-- MultiplyMode = true
StatsHandler.AddModifier(Character, "WalkSpeed", "Boost", 0, 0.25)
StatsHandler.AddModifier(Character, "WalkSpeed", "Boost2", 0, 0.5)
-- Default = 16
-- Flat = 0
-- Multipliers = (1 + 0.25) * (1 + 0.5) = 1.25 * 1.5 = 1.875
-- Final = 16 * 1.875 = 30
-- MultiplyMode = false
StatsHandler.AddModifier(Character, "WalkSpeed", "Boost", 10)
StatsHandler.AddModifier(Character, "WalkSpeed", "Boost2", 0, 0.5)
-- Default = 16
-- Flat = +10 → 26
-- Multiplier = 1 + 0.5 = 1.5
-- Final = 26 * 1.5 = 39
-- MultiplyMode = true
StatsHandler.AddModifier(Character, "WalkSpeed", "Boost", 10)
StatsHandler.AddModifier(Character, "WalkSpeed", "Boost2", 0, 0.5)
-- Default = 16
-- Flat = +10 → 26
-- Multiplier = 1.5
-- Final = 26 * 1.5 = 39
StatsHandler.AddModifier(Character, {
["WalkSpeed"] = {Flat = -10, Multiplier = -0.25}, -- (Optional) Specify if it's incremental or multiplicative
["JumpPower"] = -25, -- Or simply put a number unlike above, for Flat value by default
["RegeneratePercent"] = -1, -- This one will be set as an attribute because it isn't a Humanoid property (More info above)
}, "DebuffEffect")
local StatsHandler = require(game:GetService("ServerScriptService").StatsHandler)
local Tool = script.Parent
local Character
Tool.Equipped:Connect(function()
Character = Tool.Parent
local Humanoid:Humanoid = Character and Character:FindFirstChildOfClass("Humanoid")
if Humanoid ~= nil and Humanoid.Health > 0 and Humanoid.RootPart ~= nil then
StatsHandler.AddModifier(Character, "WalkSpeed", Tool.Name, Tool:GetAttribute("SpeedBoost"), Tool:GetAttribute("SpeedMulti"))
end
end)
Tool.Unequipped:Connect(function()
StatsHandler.RemoveModifier(Character, "WalkSpeed", Tool.Name)
end)
Features
Features
- Performance: Only runs once when a function is called
- Automatically cleans up when characters are destroyed
- Support stacking, all rig types, either player or NPC is fine
- Active modifiers don’t overlap with each other. Adding or removing stat mods won’t affect the other mods, just the removed ones, final values are correctly calculated
- Explanations and examples so detailed you won’t be reading allat
Update Logs
Update Logs
- Cricket noises
Credits
Credits
@Lightlimn - Thanks to the original creator! Here’s the post