Custom RBXScriptConnection help

Hello, I need help with this EffectReplicator module, I will post it and its children here.

EffectReplicator module:

local ReplicatedStorage = game:GetService('ReplicatedStorage');
local RunService = game:GetService('RunService');
local Players = game:GetService('Players')

local Player = Players.LocalPlayer;
local EffectReplication = ReplicatedStorage.Requests:WaitForChild('EffectReplication');

local Effect = require(script.Effect);
local Signal = require(script.Signal);

local isServer = RunService:IsServer();

local EffectReplicator = {};
EffectReplicator.__index = EffectReplicator;

function EffectReplicator.new(player)
	local self = setmetatable({}, EffectReplicator);

	self.EffectAdded = Signal.new();
	self.EffectRemoving = Signal.new();

	self.Container = nil;
	self.Effects = {};

	if (not isServer) then	
		EffectReplication._update.OnClientEvent:Connect(function(...)
			self:_handleClientUpdate(...);
		end);
	end;

	return self;
end;

function EffectReplicator:GetEffect(effectData)
	local effectId = typeof(effectData) == 'table' and effectData.ID or effectData;

	return self.Effects[effectId];
end;

function EffectReplicator:FindEffect(class, ignoreDisabled)
	for _, effect in next, self.Effects do
		if (effect.Class == class and (not effect.Disabled or ignoreDisabled)) then
			return effect;
		end;
	end;
end;

function EffectReplicator:FindServerEffect(class, ignoreDisabled)
	for _, effect in next, self.Effects do
		if (effect.Class == class and effect.Domain == 'Server' and (not effect.Disabled or ignoreDisabled)) then
			return effect;
		end;
	end;
end;

function EffectReplicator:FindEffectWithTag(tag, ignoreDisabled)
	for _, effect in next, self.Effects do
		if (effect.Tags[tag] and (not effect.Disabled or ignoreDisabled)) then
			return effect;
		end;
	end;
end;

function EffectReplicator:FindEffectWithValue(class, value, ignoreDisabled)
	for _, effect in next, self.Effects do
		if (effect.Class == class and effect.Value == value and (not effect.Disabled or ignoreDisabled)) then
			return effect;
		end;
	end;
end;

function EffectReplicator:CreateEffect(name, effectData, customDebrisTime)
	effectData = effectData or {};
	effectData.Class = name;
	
	local effect = Effect.new(self, effectData, isServer, customDebrisTime);

	self.Effects[effect.ID] = effect;
	self.EffectAdded:Fire(effect);

	return effect;
end;

function EffectReplicator:GetEffects()
	local effectsArray = {};

	for _, effect in next, self.Effects do
		table.insert(effectsArray, effect);
	end;

	return effectsArray;
end;

function EffectReplicator:GetEffectsOfClass(class)
	local effectsArray = {};

	for _, effect in next, self.Effects do
		if (effect.Class == class) then
			table.insert(effectsArray, effect);
		end;
	end;

	return effectsArray;
end;

function EffectReplicator:GetEffectsWithTag(tag)
	local effectsArray = {};

	for _, effect in next, self.Effects do
		if (effect.Tags[tag]) then
			table.insert(effectsArray, effect);
		end;
	end;

	return effectsArray;
end;

function EffectReplicator:GetEffectsWithValue(class, value)
	local effectsArray = {};

	for _, effect in next, self.Effects do
		if (effect.Class == class and effect.Value == value) then
			table.insert(effectsArray, effect);
		end;
	end;

	return effectsArray;
end;

function EffectReplicator:ParseEffects()
	local effectParsed = {};

	for i, v in next, self.Effects do
		table.insert(effectParsed, v:ParseEffect());
	end;

	return effectParsed;
end;

function EffectReplicator:GetEffectsHash()
	local effectsHash = {};

	for _, effect in next, self.Effects do
		effectsHash[effect.Class] = true;
	end;

	return effectsHash;
end;

function EffectReplicator:_clearEffects()
	for i, v in next, self:GetEffects() do
		v:Destroy();
	end;
end;

function EffectReplicator:_handleClientUpdate(replicationData)
	local updateType = replicationData.updateType;
	local sum = replicationData.sum;

	if (updateType == 'updatecontainer') then
		if (sum ~= self.Container) then
			self:_clearEffects();
			self.Container = sum;
		end;
	elseif (updateType == 'remove') then
		local effect = self.Effects[sum];

		if (effect) then
			effect:Destroy(true);
		end;
	elseif (updateType == 'clear') then
		self:_clearEffects();
	elseif (updateType == 'update') then
		for i, v in next, sum do
			local effect = self.Effects[v.ID] or self:CreateEffect(v.Class, v);

			if (v.Tags ~= nil) then
				effect.Tags = v.Tags;
			end;

			if (v.Value ~= nil) then
				effect.Value = v.Value;
			end;

			if (v.Disabled ~= nil) then
				effect.Disabled = v.Disabled;
			end;
			
			if (v.DebrisTime ~= nil) then
				effect.DebrisTime = v.DebrisTime
			end

			self.Effects[v.ID] = effect;
		end;
	end;
end;

function EffectReplicator:WaitForContainer()
	while (not self.Container) do
		task.wait();
	end;
end;

return isServer and EffectReplicator or EffectReplicator.new();

Effect (child of EffectReplicator):

local HttpService = game:GetService('HttpService');
local Signal = require(script.Parent.Signal);

local Effect = {};
Effect.__index = function(self, p)
	local prop = rawget(self._props, p);

	if (prop == nil) then
		prop = rawget(self, p);
	end;
	
	if (prop == nil) then
		prop = rawget(Effect, p);
	end;

	return prop;
end;

Effect.__metatable = 'Effect';

local readOnlyProperties = {'Class', 'ID', 'Domain'};

function Effect:__tostring()
	return string.format('Effect: %s [%s] (%s|%s) [%s]', tostring(self.ID), string.upper(self.Domain), tostring(self.Class), tostring(self.Value), self.Disabled and 'X' or '\226\156\147');
end;

function Effect:__newindex(p, v)
	if (table.find(readOnlyProperties, p)) then
		return error(string.format('Attempt to change %s of effect', p));
	end;

	rawset(self._props, p, v);

	if (rawget(self, 'Shadow')) then
		self.Shadow:Fire(p, v);
	end;
end;

function Effect.new(effectReplicator, data, isServer, customDebrisTime)
	local props = {};
	
	props.Domain = data.Domain or isServer and 'Server' or 'Client';
	props.ID = data.ID or HttpService:GenerateGUID(false);	
	props.Class = data.Class;
	props.Disabled = data.Disabled or false;
	props.Value = data.Value ~= nil and data.Value or '???';
	props.Parent = effectReplicator;
	props.Tags = data.Tags or {};
	props.DebrisTime = customDebrisTime or 0
	
	local self = setmetatable({_props = props}, Effect);
	
	rawset(self, 'TagAdded', Signal.new());
	rawset(self, 'TagRemoving', Signal.new());
	
	return self;
end;

function Effect:ParseEffect()
	return {
		Class = self.Class,
		Disabled = self.Disabled,
		Tags = self.Tags,
		Domain = self.Domain,
		ID = self.ID,
		Value = self.Value,
		DebrisTime = self.DebrisTime,
	}
end;

function Effect:Debris(debrisTime)
	task.delay(debrisTime, self.Destroy, self);
end;

function Effect:Connect(f)
	if (not self.Shadow) then
		rawset(self, 'Shadow', Signal.new());
	end;

	self.Shadow:Connect(f);
end;

Effect.connect = Effect.Connect;

function Effect:AddTag(name)
	self.Tags[name] = true;
	self.TagAdded:Fire(name);
end;

function Effect:RemoveTag(name)
	self.Tags[name] = nil;
	self.TagRemoving:Fire(name);
end;

function Effect:HasTag(name)
	return self.Tags[name] == true;
end;

function Effect:Destroy()
	if (self.Destroyed) then
		return;
	end;

	self.Destroyed = true;
	
	if self.Parent and self.Parent.Effects then
		self.Parent.Effects[self.ID] = nil;
		self.Parent.EffectRemoving:Fire(self);
	end
	
	self.TagAdded:Destroy();
	self.TagRemoving:Destroy();
	
	if (self.Shadow) then
		self.Shadow:Destroy();
		self.Shadow = nil;
	end;
	
	self.TagAdded = nil;
	self.TagRemoving = nil;
	self.Parent = nil;
end;

Effect.Remove = Effect.Destroy;
return Effect;

Signal (child of EffectReplicator):

--- Lua-side duplication of the API of events on Roblox objects.
-- Signals are needed for to ensure that for local events objects are passed by
-- reference rather than by value where possible, as the BindableEvent objects
-- always pass signal arguments by value, meaning tables will be deep copied.
-- Roblox's deep copy method parses to a non-lua table compatable format.
-- @classmod Signal

local Signal = {}
Signal.__index = Signal
Signal.ClassName = "Signal"

--- Constructs a new signal.
-- @constructor Signal.new()
-- @treturn Signal
function Signal.new()
	local self = setmetatable({}, Signal)

	self._bindableEvent = Instance.new("BindableEvent")
	self._argData = nil
	self._argCount = nil -- Prevent edge case of :Fire("A", nil) --> "A" instead of "A", nil

	return self
end

--- Fire the event with the given arguments. All handlers will be invoked. Handlers follow
-- Roblox signal conventions.
-- @param ... Variable arguments to pass to handler
-- @treturn nil
function Signal:Fire(...)
	self._argData = {...}
	self._argCount = select("#", ...)
	self._bindableEvent:Fire()
	self._argData = nil
	self._argCount = nil
end

--- Connect a new handler to the event. Returns a connection object that can be disconnected.
-- @tparam function handler Function handler called with arguments passed when `:Fire(...)` is called
-- @treturn Connection Connection object that can be disconnected
function Signal:Connect(handler)
	if not (type(handler) == "function") then
		error(("connect(%s)"):format(typeof(handler)), 2)
	end

	return self._bindableEvent.Event:Connect(function()
		if not self._argData then return end;
		handler(unpack(self._argData, 1, self._argCount))
	end)
end

--- Wait for fire to be called, and return the arguments it was given.
-- @treturn ... Variable arguments from connection
function Signal:Wait()
	self._bindableEvent.Event:Wait()
	assert(self._argData, "Missing arg data, likely due to :TweenSize/Position corrupting threadrefs.")
	return unpack(self._argData, 1, self._argCount)
end

--- Disconnects all connected events to the signal. Voids the signal as unusable.
-- @treturn nil
function Signal:Destroy()
	if self._bindableEvent then
		self._bindableEvent:Destroy()
		self._bindableEvent = nil
	end

	self._argData = nil
	self._argCount = nil
end

Signal.connect = Signal.Connect;

return Signal

This is how I’m calling the EffectReplicator module:
(serversided code)

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")


local ServerReplicator = {};
local EffectReplicator, Replication = require(ReplicatedStorage.EffectReplicator), ReplicatedStorage.Requests.EffectReplication
local PlayerEffects = {}
local UpdateRemote = Replication._update
local PlayerContainers = {}

function shared.GetReplicator(Player)
	return PlayerEffects[Player];
end

function EffectReplicator:Destroy()
	UpdateRemote:FireClient(self.Player, {
		updateType = "clear"
	})
end

function shared.CreateReplicator(Player, isNPC)
	local oldEffectReplicator = PlayerEffects[Player]

	if oldEffectReplicator then
		oldEffectReplicator:Destroy()
	end

	local newPlayerContainer = {};
	local effectReplicator = EffectReplicator.new();

	effectReplicator.Player = Player;

	if not isNPC then

		effectReplicator.EffectAdded:Connect(function(effect, DebrisTime)
			warn('effect added server')
			task.wait();
			if (effect.Destroyed) then return end;

			if (effect.Domain == "Server") then
				warn('effect added server:', effect.ID)
				UpdateRemote:FireClient(Player, {
					updateType = "update";
					sum = effectReplicator:ParseEffects()
				});

				for i,v in next, {effect, effect.TagAdded, effect.TagRemoving} do
					v:Connect(function()
						warn('tag added')
						UpdateRemote:FireClient(Player, {
							updateType = "update";
							sum = {effect:ParseEffect()};
						});
					end)
				end
			end;
		end)


		effectReplicator.EffectRemoving:Connect(function(effect)
			warn('removing effect')
			UpdateRemote:FireClient(Player, {
				updateType = "remove";
				sum = effect.ID;
			});
		end);
	end

	PlayerEffects[Player] = effectReplicator;
	PlayerContainers[Player] = newPlayerContainer;

	if not isNPC then
		UpdateRemote:FireClient(Player, {
			updateType = "updatecontainer";
			sum = newPlayerContainer;
		});
	end
	return effectReplicator
end

Players.PlayerAdded:Connect(function(Player)
	Player.CharacterAdded:Connect(function(character: Model)
		shared.CreateReplicator(Player)
	end)
end)

The issue I’m facing is the .EffectAdded and .EffectRemoving connections do not work for some reason. I’ve tried everything I could think of but nothing works. This is how I’m creating the effect (serversided)

shared.GetReplicator(game.Players.AcidSkillz):CreateEffect('testeffect')

I’ve added my own EffectAdded connections in other scripts but it still doesn’t work, such as

shared.GetReplicator(game.Players.AcidSkillz).EffectAdded:Connect(function(effect)
    warn(effect.Class)
end)


as you can see, nothing is outputted when I add an effect after I create the connection with EffectAdded. If anyone knows how to fix this please help.

im having an issue with the client not being updated with the effects.

i would recommend for you to make the shared.GetReplicator a definition and then use :CreateEffect

1 Like

I fixed everything already, including the client not receiving the effects. The signal module was faulty (offloading the arguments before returning anything), and the reason the client wasn’t receiving the signal was due to the server taking priority of the .EffectAdded signals, I can send you the fully updated module with everything fixed if you’d like. I’m not home at the moment but I’ll send you an example place with everything fixed.

1 Like

if you can, please send it. ive been struggling for a whole day figuring out what the issue is.
add xyphergg on discord when u can send

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