How can I better organize my status effect system?

The status effect system works like this:

  1. Create an OOP object (StatusEffect) – the constructor needs the effect name, the target, and an optional table containing attributes for the effect (e.g. {["Duration"] = 10}), such as a custom duration or damage per tick.

  2. Set the table of status effect attributes in the StatusEffect. Compare the parameters we passed into a giant dictionary of default status effect parameters stored in another ModuleScript. Add any missing attributes (e.g. if we only passed in a table containing a custom duration for a status effect that also had a damage per tick attribute) to a table that is then set to the StatusEffect’s Params.

  3. CollectionService tags the target with the effect name. If this tag is removed before the effect times out, remove the effect (managed by the function in Step 4)

  4. Call the function. After a few checks, call the function with the same name that we set the StatusEffect’s name to. All the possible functions are stored in the same ModuleScript as the dictionary of status effects.

The issue is that I am repeating myself a tremendous amount. About 80% of the code in the other specific functions is the same, and with only 10 status effects, the ModuleScript is already over 1000 lines long. How should I change this? Make a separate OOP Module for each status effect?

Here is an example function:

--[[
BURNING:
]]--
function statusEffectList.burning(player, params, giver)
	-- Apply the particles and other visuals
	local damagePerTick, tickTime, duration = params["Damage"], params["Tick"], params["Duration"]
	
	-- Timer  --
	local currentTime = 0
	task.spawn(function()
		while currentTime <= duration and CollectionService:HasTag(player, "Burning") do
			currentTime += 0.1
			task.wait(0.1)
		end
	end)
	
	-- Reset duration if burning is applied again to the same player --
    -- This section is also really bad because of the if statements
	local resetDurationConnection;
	resetDurationConnection = StatusEffectResetDuration.Event:Connect(function(newPlayer, newEffectName, newParams, newGiver)
		
		if newPlayer == player and newEffectName == "Burning" then
			
			if typeof(newParams) ~= "table" then
				warn("The new params aren't in a table")
				return
			end
			
			currentTime = 0 -- Reset our timer
			
			if newParams["Tick"] < tickTime then -- Change the damage tick if it's lower
				tickTime = newParams["Tick"]
			end
			
			if newParams["Damage"] > damagePerTick then
				damagePerTick = newParams["Damage"]
			end
			
			if newParams["Duration"] ~= duration then -- If the new duration is different, set it as the new one
				duration = newParams["Duration"]
			end
			
			if newGiver then
				if newGiver ~= giver then -- Replace the previous player who applied the effect
					giver = newGiver
				end
			end
			
		end
	end)
	
	-- Deal damage --
    -- Yields the script until the effect times out or gets removed
	while currentTime <= duration and CollectionService:HasTag(player, "Burning") do
		if player then
			-- Code for damage
		end

		task.wait(tickTime)
	end

	StatusEffectEnded:Fire(player, "Burning") -- Signals to the StatusEffect object to remove CollectionService tags

	-- Remove effects and visuals
	-- Disconnect the listener once we're done -- 
	if resetDurationConnection then
		resetDurationConnection:Disconnect()
	end
end

If you are making more than 1 effect then do create a base OOP module for you effects, then make a module and inside there it would look something like this:

local BaseEffect = require(path_here)

local TestEffect = BaseEffect.new(args_here)

TestEffect.DoRandomFunctionHere = function()
    --Stuff here
end

return TestEffect

or just do this in whatever script:

local BaseEffect = require(path_here)

local TestEffect = BaseEffect.new(args_here)

TestEffect:Run(workspace.Part)

This will depend on your base effect module.
You will need to return the functions in your module though.

Hope this works out!(Again as I said you may need to change some things in your base module)

I’m not sure if this is what you need, but I’d do something similar to this:
–[[

Dictionary of status effect attributes:

Key: StatusEffectName
Value: Table containing attributes for the effect

]]–
local StatusEffectAttributes = {
–[[
StatusEffectName = {
Duration = <value>,
DamagePerTick = <value>
}
]]–
[“Burning”] = {
Duration = 5,
DamagePerTick = 5
},
[“Frozen”] = {
Duration = 5,
DamagePerTick = 5
}
}

local StatusEffects = {}

local function StatusEffect(EffectName, Target, Params)

local self = {}

local function StatusEffectRemoval()
    -- Remove effects and visuals
    -- Disconnect the listener once we're done -- 
    if self.ResetDurationConnection then
        self.ResetDurationConnection:Disconnect()
    end
end

local function StatusEffectResetDuration(NewPlayer, NewEffectName, NewParams, NewGiver)
    -- Reset duration if burning is applied again to the same player --
    -- This section is also really bad because of the if statements

    if NewPlayer == self.Player and NewEffectName == self.EffectName then

        if typeof(NewParams) ~= "table" then
            warn("The new params aren't in a table")
            return
        end

        self.CurrentTime = 0 -- Reset our timer

        if NewParams["Tick"] &lt; self.TickTime then -- Change the damage tick if it's lower
            self.TickTime = NewParams["Tick"]
        end

        if NewParams["Damage"] &gt; self.DamagePerTick then
            self.DamagePerTick = NewParams["Damage"]
        end

        if NewParams["Duration"] ~= self.Duration then -- If the new duration is different, set it as the new one
            self.Duration = NewParams["Duration"]