Controller module handling UI and cooldown PROBLEM!

You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve? Keep it simple and clear!

I want to achieve a module where I could create Tool Classes In where I could call to create UI/Switch Visibility and Bind it to the preferred input like in Blox Fruit by the way here is the UI:

  1. What is the issue? Include screenshots / videos if possible!

Everything works fine like for everything but when the player dies it goes bonkers nothing errors but nothing works like the Tweening of UI and Binding Input. Note: when not firing replica everything works but when i fire the replica in the function sended to bindPC it doesnt work!

  1. What solutions have you tried so far? Did you look for solutions on the Creator Hub?

I have tried looking into different implementations through many documentations and AI :smile:

After that, you should include more details if you have any. Try to make your topic as descriptive as possible, so that it’s easier for people to help you!

Here is what happens when the player resets and yes i am clicking after i died like left mouse clicking and the inputs specified on the UI:

This is the Module Script [Element Controller]:

----!strict

local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

local Trove = require(ReplicatedStorage.Packages.Trove)
local t = require(ReplicatedStorage.Packages.t)

local CooldownHandler = require(ReplicatedStorage.Modules.CooldownHandler)

type Controller = {
	__index: Controller,
	new: (Name: string, Player: Player, Module: ModuleScript) -> Class,
	
	UIPC: (self: Class, show: boolean) -> (),
	BindPC: (self: Class, functionT1:()->(), functionT1:()->(), functionT1:()->(), functionM1:()->()) -> (),
}

type Class = typeof(setmetatable({} :: {
	
	Name: string,
	Player: Player,
	Module: typeof(require(ReplicatedStorage.Modules.Configurations.Static)),
	
	UI: CanvasGroup?,
	Thread: thread?,
	
	FirstTime: boolean,
	Equip: boolean,
	Hold: boolean,
	
	_trove: typeof(Trove.new())
	
}, {} :: Controller))

function Tweenrow(UI: CanvasGroup, Row: string, Duration: number, self)
	if not UI or not UI.Parent then
		warn("TweenRow: UI is missing or destroyed for;", self.Name)
		return
	end
	
	local holder = UI:FindFirstChild("Holder")
	if not holder then warn("Holder not found in CanvasGroup") return end
	
	
	local rowFrame = holder:FindFirstChild(Row)
	if not rowFrame then warn(Row .. " not found in UI Holder!") return end
	
	local bar = rowFrame:FindFirstChild("Bar") :: Frame
	if not (bar and bar:IsA("Frame")) then
		warn("TweenRow: Missing Bar for", Row)
		return
	end
	
	local info = TweenInfo.new(Duration, Enum.EasingStyle.Linear, Enum.EasingDirection.Out)
	local tween = game:GetService("TweenService"):Create(bar, info, {Size = UDim2.new(1, 0, 1, 0)})
	tween:Play()
	tween.Completed:Connect(function()
		bar.Size = UDim2.new(0, 0, 1, 0)
	end)		
end

local Controller : Controller = {} :: Controller
Controller.__index = Controller

function Controller.new(Name, Player, Module)
	local self = setmetatable({}, Controller)
	
	self.Name = Name
	self.Player = Player
	self.Module = Module
	
	self.UI = nil
	self.Thread = nil
	
	self.FirstTime = true
	self.Equip = false
	self.Hold = false
	
	self._trove = Trove.new()
	
	return self
end

function Controller:UIPC(show)
	if UserInputService.PreferredInput ~= Enum.PreferredInput.KeyboardAndMouse then return end
	
	if self.FirstTime then
		local CanvasGroup = script.CanvasGroup:Clone()
		CanvasGroup.Name = self.Name
		CanvasGroup.DisplayName.Text = self.Name
		CanvasGroup.Parent = self.Player.PlayerGui:WaitForChild("Tool_UI")
		
		for _, v in pairs(self.Module) do
			local Row = script.Template_PC:Clone()
			Row.Name = v.Name
			Row.DisplayName.Text = v.Name
			Row.Key.Text = v.RepresentKey
			Row.Parent = CanvasGroup.Holder
		end
		
		if show then
			CanvasGroup.Visible = true
			CanvasGroup.Active = true
			self.Equip = true
			self.UI = CanvasGroup
			self.FirstTime = false
			self._trove:Add(CanvasGroup)
			self._trove:Add(function()
				print("TROVE CLEANED UP!")
			end)
		end
		
		self.UI = CanvasGroup
		self._trove:Add(CanvasGroup)
		
	end
	
	if not self.FirstTime then
		if show and self.UI then
			self.UI.Visible = true
			self.UI.Active = true
			self.Equip = true
		elseif not show and self.UI then

			self.UI.Visible = false
			self.UI.Active = false
			self.Equip = false
		end
	end
end

function Controller:BindPC(F1,F2,F3,M1)
	assert(t.callback(F1))
	assert(t.callback(F2))
	assert(t.callback(F3))
	assert(t.callback(M1))
	
	if not self._trove then warn("Trove non-existance can't be called for use!") return end
	local functionArray = {F1,F2,F3,M1}
	
	for i, v in self.Module do
		
		local keyAllocations = {}
		keyAllocations[v.Key] = {
			FUNCTION = functionArray[i];
			NAME = v.Name;
			DURATION = v.Duration;
		}
		
		self._trove:Add(UserInputService.InputBegan:Connect(function(Input, Gpe)
			if not self.Equip then return end
			if Gpe then return end
			if Input.KeyCode ~= v.Key then return end
			
			if keyAllocations[v.Key] then
				local key = keyAllocations[v.Key]
				if CooldownHandler.Check(self.Player.Name, key.NAME) then
					CooldownHandler.Add(self.Player.Name, key.NAME, key.DURATION)
					key.FUNCTION(self._trove)
					if self.UI then
						Tweenrow(self.UI, key.NAME, key.DURATION, self)
					else
						warn("Unable to tweenrow due to ui set to nil!")
					end
				end
			end
		end))
	end
	
	self._trove:Add(UserInputService.InputBegan:Connect(function(Input, Gpe)
		if not self.Equip then return end
		if Gpe then return end
		if Input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end
		
		self.Hold = true
		
		if self.Thread then
			self._trove:Remove(self.Thread)
			self.Thread = nil
		end

		self.Thread = self._trove:Add(task.spawn(function()
			while self.Hold do
				if not self.Equip then return end
				if CooldownHandler.Check(self.Player.Name, string.format("%s: M1", self.Name)) then
					CooldownHandler.Add(self.Player.Name, string.format("%s: M1", self.Name), 0.1)
					if self._trove then
						M1(self._trove)
					end
				end
				
				RunService.Heartbeat:Wait()
			end
		end))
		
	end))
	
	self._trove:Add(UserInputService.InputEnded:Connect(function(Input, Gpe)
		if not self.Equip then return end
		if Gpe then return end
		if Input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end

		self.Hold = false

		if self.Thread then
			self._trove:Remove(self.Thread)
			self.Thread = nil
		end

	end))
	
	local Character = self.Player.Character :: Model
	local Humanoid = Character:WaitForChild("Humanoid") :: Humanoid

	self._trove:Add(Humanoid.Died:Connect(function()

		local storedFunctions = {F1, F2, F3, M1}

		if self._trove then
			self._trove:Destroy()
		end
		
		self.Hold = false
		self.Equip = false
		
		self.UI = nil
		self.Thread = nil
		self.FirstTime = true
		
		self._trove = Trove.new()
		
		local newCharacter = self.Player.Character :: Model
		local Humanoid = newCharacter:WaitForChild("Humanoid") :: Humanoid
		
		local success, errorMessage = pcall(function()
			self:BindPC(storedFunctions[1], storedFunctions[2], storedFunctions[3], storedFunctions[4])
		end)

		if success then
			print("Successfully rebound after death!")
		else
			warn("Failed to rebind after death:", errorMessage)
		end
	end))
end

return Controller
Here is the Local Script under a tool name Static which is using the replicaController
To send data to the server with the functions!
local newElementController = require(game.ReplicatedStorage.Shared.Controllers.NewElementController)
local static = require(game.ReplicatedStorage.Modules.Configurations.Static)
local ReplicaController = require(game.ReplicatedStorage.Network.ReplicaController)

local player = game.Players.LocalPlayer

local tool = script.Parent

local newClass = newElementController.new("Static", player, static)

ReplicaController.RequestData()

tool.Equipped:Connect(function()
	newClass:UIPC(true)
end)

tool.Unequipped:Connect(function()
	newClass:UIPC(false)
end)

print("=== SCRIPT RUNNING ===")

local replica = ReplicaController.GetReplicaById("Static")
if replica then
	local function Move1()
		replica:FireServer(1)
	end

	local function Move2()
		replica:FireServer(2)
	end

	local function Move3()
		replica:FireServer(3)
	end
	local function M1()
		replica:FireServer(4)
	end

	newClass:BindPC(Move1, Move2, Move3, M1)
	
else
	replica = ReplicaController.ReplicaOfClassCreated("Static", function(replica)
		local function Move1()
			replica:FireServer(1)
		end

		local function Move2()
			replica:FireServer(2)
		end

		local function Move3()
			replica:FireServer(3)
		end
		local function M1()
			replica:FireServer(4)
		end

		newClass:BindPC(Move1, Move2, Move3, M1)
	end)
end

Here is the server if you are wondering:

--!strict

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

local ReplicaService = require(ServerScriptService.Shared.Modules.ReplicaService)

local newReplica = ReplicaService.NewReplica({
	ClassToken = ReplicaService.NewClassToken("Static"),
	Data = {},
	Replication = "All"
})

newReplica:ConnectOnServerEvent(function(plr, num)
	
	print("Received on [server]: of function of ".. num)
	
end)

Please do not ask people to write entire scripts or design entire systems for you. If you can’t answer the three questions above, you should probably pick a different category.

I think of UI is litreally redoned

I can think of feature ResetonSpawn

1 Like

Hey there. Perhaps try replacing the very last part of your script—this section:

	self._trove:Add(Humanoid.Died:Connect(function()

		local storedFunctions = {F1, F2, F3, M1}

		if self._trove then
			self._trove:Destroy()
		end
		
		self.Hold = false
		self.Equip = false
		
		self.UI = nil
		self.Thread = nil
		self.FirstTime = true
		
		self._trove = Trove.new()
		
		local newCharacter = self.Player.Character :: Model
		local Humanoid = newCharacter:WaitForChild("Humanoid") :: Humanoid
		
		local success, errorMessage = pcall(function()
			self:BindPC(storedFunctions[1], storedFunctions[2], storedFunctions[3], storedFunctions[4])
		end)

		if success then
			print("Successfully rebound after death!")
		else
			warn("Failed to rebind after death:", errorMessage)
		end
	end))

with this code:

	self._trove:Add(self.Player.CharacterAdded:Connect(function(newCharacter)

		local storedFunctions = {F1, F2, F3, M1}

		if self._trove then
			self._trove:Destroy()
		end
		
		self.Hold = false
		self.Equip = false
		
		self.UI = nil
		self.Thread = nil
		self.FirstTime = true
		
		self._trove = Trove.new()
		
		local Humanoid = newCharacter:WaitForChild("Humanoid") :: Humanoid
		
		local success, errorMessage = pcall(function()
			self:BindPC(storedFunctions[1], storedFunctions[2], storedFunctions[3], storedFunctions[4])
		end)

		if success then
			print("Successfully rebound after death!")
		else
			warn("Failed to rebind after death:", errorMessage)
		end
	end))

I simply switched the

Humanoid.Died

event with

self.Player.CharacterAdded
1 Like

Thank you , I tried your suggestion but it still didn’t work. I think the Died property of the humanoid is fine because i did test of this without using ReplicaService and ReplicaController with is my network used to fire / create events, and it worked ! everything like the UI tweening and binding works but when i add replicaService into the functions it doesnt work like replica:FireServer()

If you did not modify ReplicaService in anyway, then GetReplicaById() would return nil, as the ClassToken (“Static”) for the Replica is not it’s Id. Each Id is a number by default:

-- Defined before NewReplica:
local ReplicaIndex = 0

-- Snippet from ReplicaService.NewReplica
function ReplicaService.NewReplica(replica_params) --> [Replica]
	-- ...
	ReplicaIndex = ReplicaIndex + 1 -- Increments the index for the new replica
	
	local replica = {
		Id = ReplicaIndex -- This would be Replica.Id
	}
	
	Replicas[ReplicaIndex] = replica -- Adds replica to Replicas table with the key as ReplicaIndex
	
	return replica -- Returns the new replica
end

-- Snippet from ReplicaController.GetReplicaById
function ReplicaController.GetReplicaById(replica_id)
	return Replicas[replica_id] -- << replica_id should be a number, as seen above
end

Therefore, when you respawn, your conditional statement is not true:

local replica = ReplicaController.GetReplicaById("Static")
if replica then -- << This will never be true, so this block of code is skipped
	local function Move1()
		replica:FireServer(1)
	end

	local function Move2()
		replica:FireServer(2)
	end

	local function Move3()
		replica:FireServer(3)
	end
	local function M1()
		replica:FireServer(4)
	end

	newClass:BindPC(Move1, Move2, Move3, M1)
	
else -- << This only works ONCE (when the player first joins) since you only create the class on the server once.
    -- This will never run since ReplicaOfClassCreated will not be fired unless
    -- you create the class on the server again (which you can't even do)
	replica = ReplicaController.ReplicaOfClassCreated("Static", function(replica)
		local function Move1()
			replica:FireServer(1)
		end

		local function Move2()
			replica:FireServer(2)
		end

		local function Move3()
			replica:FireServer(3)
		end
		local function M1()
			replica:FireServer(4)
		end

		newClass:BindPC(Move1, Move2, Move3, M1)
	end)
end

Another thing worth mentioning is your Died event. Although you attempt to rebind the keys after death, it will not work – since the LocalScript gets removed when the player’s old character model does. Once the LocalScript is no longer a descendant of the current character, it stops running, and any connections or event handlers it created will no longer function.

This behavior on top of what I mentioned previously is why the input does not rebind upon respawning. You should either modify ReplicaService to store Replicas using the Class as the key or move all related logic into a LocalScript that does not have the possibility of being removed during runtime.

1 Like

Thank you so much bro, i found the solution like what you mentioned i thiink replica is very messy and i think it yields so i switched to packets and changed my module onDied event :smiley:

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