Script destruction makes threads die, causing status effects to break

I’m working on a status effect system for our game, but when an object that applies an effect gets destroyed while an effect is running, the threads the status effect system creates are cancelled too. Is it possible to make these threads independent of the scripts in the objects in Workspace or do I need to make a new server script to fire BindableEvents at?

The code in question:


-- Return an effect's ModuleScript
function module.LookupEffect(effectName)
	return script.Effects:FindFirstChild(effectName)
end

-- Apply an effect to a player. If this effect is stackable,
-- it will add the base duration of the effect on top of the currently
-- running effect.
function module:ApplyEffectTo(effectName, plr)
	if not effects[plr] then
		-- Create this player's effect subtable if it doesn't already exist
		effects[plr] = {}
	end
	
	-- Create a new instance of the status effect
	local toApply = require(module.LookupEffect(effectName)).New()
	
	if not effects[plr][toApply.Name] then -- If this effect isn't already applied,
		task.spawn(function() --!! This was just something I tried that didn't work
			effects[plr][toApply.Name] = toApply
			coroutine.wrap(toApply.Apply)(plr) -- Start the effect
			if db then print(toApply.Name.." applied") end -- db refers to a debug mode variable
			RE:FireClient(plr, effects[plr][toApply.Name])
			toApply.Ended:Connect(function() -- Remove it when it finishes
				if db then
					print(toApply.Name.." ended")
				end
				effects[plr][toApply.Name] = nil
			end)
		end)
	else
		if toApply["Stackable"] == false then 
			if db then --!! Problem area, the thread is terminated before the Ended signal fires, leaving a dead entry in the effects list
				print("Not applying because this isn't stackable")
				print(effects)
			end
			return
		end -- Return if it's not stackable
		effects[plr][toApply.Name].addDuration(toApply.Duration) -- Add duration if it is
		if db then
			print("Applied additonal duration to effect")
		end
		RE:FireClient(plr, effects[plr][toApply.Name])
	end
end

Structure of an effect:

local effect = {}
local RS = game:GetService("RunService")
local Signal = require(game.ReplicatedStorage.Libraries.Signal)

function effect.New(effect)
	local self = {}
	setmetatable(self, effect)
	
	self.Name = effect["Name"]
	self.Description = effect["Description"]
	self.Image = effect["Image"]
	self.Duration = effect["Duration"]
	self.Sound = effect["Sound"] or 0
	self.Stackable = if effect.Stackable == false then false else true
	
	self.Apply = nil
	
	self.applyTime = tick()
	
	self.Ended = Signal.new()
	
	RS.Heartbeat:Connect(function()
		if tick() >= self.applyTime + self.Duration then
			self.Revoke()
			self.Ended:Fire()
			self.Ended:DisconnectAll()
		end
	end)
	
	self.addDuration = function(addTime)
		self.Duration += addTime
	end
	
	self.Revoke = function()
		-- We need to make sure that Ended is fired no matter how this is called
		print("Revoked~!")
		self.Ended:Fire()
		effect.Revoke()
	end
	
	return self
end

return effect
2 Likes

Call an event that’s connected in a script that doesn’t get removed, which in turn spawns a thread with the effect?

2 Likes

i think this might be the only solution, a bit more jank than i was hoping but it works

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.