What is the best practice for a centralized tool handler script on the client?

If there are many different tools that do essentially the same thing except have different stats, rather than copying the same client script into every one (which is terrible and not DRY) I could have a single script in StarterCharacterScripts that handles them:

local Char, Name = script.Parent, script.Name
Char.ChildAdded:Connect(function(Tool)
    if not Tool:HasTag(Name) return end
    Tool.RemoveTag(Name)
    Tool.Activated:Connect(function()
        -- Handle activated
    end)
end)

However, there are a few pitfalls with this approach, like if the player drops the tool and another picks up the tool, I am not sure if the original player will received the other player Activated events or not. And if they drop the tool, reset, pickup the same tool, the tool will be non-functional, since the Activated event would have been disconnected by the script being destroyed but the client side removal of the tag would persist. So rather than working around these manually with extra checks, is there a ā€œbest practiceā€ for what I am going for here?

1 Like

Ok, so, basically, I am doing a fighting game, and that is exactly what I am wondering. I have found some solutions. However, they are not the best, but they work at least.
Firstly, I made a dictionary like this:

{
  ["Sword of Infinite Wisdom"] = {
    Damage = 10,
    SourceToolHandle = PATH.TO.HANDLE -- Note that here is the basepart that will be used for the tool
    
    OnCreate = function(self)
      -- Create a Tool and parent the clone self.SourceToolHandle to it
    end)
  }
}

Essentially, this would work. My game however don’t use tools, but for you this should be fine :smiley:
Pretty sure you can code the rest, but I can help you with some parts :wink:

My game basically has every weapon described as data. I have an animation folder that is the attacks (in a tree representing combos). I have a hitbox folder with named parts that are welded transparent and cancolide can query false that I use to build my hitbox items with. I then have the actual tool with the handle, extra data and my sound effects for the most part are under the attacks in the tree (so my attack handler knows which sound to play).

Each weapon then has a single script.

require(game.ReplicatedStorage.Modules.WeaponBase).new(script.Parent))

Then each weapon uses the data inside it to set it up for each player that uses it, and when it’s unequipped or discarded, I destroy the weapon. It gets rebuilt every time it’s equipped (but the build does very little as it’s optimized for this).

If I need to carry data about it to another player equipping (I don’t, the weapon can be built the same way every time) I would just set attributes that I also load on create that handles any changes, and push those changes as attributes as well.

So for example, on equip I bind all my input connections, I load or retrieve the animations and load or retrieve them through the animation object. I build my hitboxs which just gets the data needed so I can activate/deactivate them as well as handle the hits when active. I build the combo system by just looking at the animation tree, but I only look at the tree and progress down it based on inputs and reset it back to folder head when you stop doing a combo.

1 Like

I’m not 100% sure about my ideas, but here’s my two cents. FYI, I’m assuming what you’re looking for is a way to add the same connection to every tool w/ a single local script, alongside managing what happens when the player drops the tool, has it picked up by someone else, or drops it, resets it, and picks it up?

#1 - Your concerns

  1. If you drop the tool and someone else picks it up, alongside activating the tool, it won’t trigger for your client since ā€˜.activated’ in this context is local to each client. If someone else activates the tool, only they will get the .activated event for this local script.
  2. I’m kind of not sure what you need the tags for. Are you checking for a specific tool name? I thought they all worked similarly, so the tool name wouldn’t matter much. Just store attributes in each tool if you want to track stats, right? I don’t have enough information to understand.

#2 - How to do what you’re asking, if I get it correctly?

Tweak your local script to have a table, which will store two things about any new tool added in your backpack: The instance, and the .activated connection.

This is so that we can easily check if this tool is removed from your backpack later, and also to disconnect the .activated event in case the tool is dropped and handed to someone else (no point in leaving your .activated connection in that case? dunno, garbage collection confuses me).

Next, tweak the local script so it’s a starterplayerscript, and change ChildAdded event to instead check when a child is added to the player’s backpack. When this happens, perform the checks for this tool to check if it has the tag you’re talking about (not too familiar with collectionservice but it seems cool), and then store the literal instance as a key inside your table, with its reference being the .activated event which will do your generic stuff.

If you want to make the activation events different for different tools, create a function to identify the type of tool you have (using tags assigned with collection service, for example, or just storing a string value inside your tool), and then you can use that to assign a different function to activate inside of the .activated event for your tool depending on the type of tool you identified (such as swords, spears, hammers, axes).

#3 - This is an example I made. Probably way longer than it needs to be???

 local player = game.Players.LocalPlayer -- References the player.
 -- References the backpack for our ChildAdded/Removed purposes.

local toolConnections = {} -- How we'll store references to tools in our inventory and event connections we setup.



local function setupBackpack(backpack)
	backpack.ChildAdded:Connect(function(child) -- Just tweaked your code to check for backpack's children instead of character.
		-- Your original code seemed to have the HasTag check to prevent a script from being misidentified.
		-- I think :IsA("Tool") is enough to prevent non-tool items from being checked.
		-- If you want to check a tool's specific type or something, you can add that by adding tool attributes.
		
		if child:IsA("Tool") and not toolConnections[child] then -- Check the item's type.
			toolConnections[child] = {} -- This adds your tool to the toolConnections array as a key w/ {} as its value.
			
			local toolReference = toolConnections[child] -- Using a variable here for readability.

			-- We are creating a key inside the table that corresponds to our tool key. This key has an event as its value.
			toolReference.ActivatedConnection = child.Activated:Connect(function() -- Connects a function to the tool's Activated event.
				-- Your code here for tool activation.
				print("Tool activated.")
			end)
		end
	end)

	-- Let's add a childRemoved event to disconnect the event when you drop the tool.
	-- BTW, .activated event WON'T fire for your client if someone else activates the tool - .activated is a local event.
	backpack.ChildRemoved:Connect(function(child) -- Just the opposite of a childAdded event.
		if child:IsA("Tool") and child.Parent ~= player.Character then -- Check if you dropped tool out of inventory.
			
			if toolConnections[child] then
				local toolReference = toolConnections[child]
				-- Disconnect the .activated event. I don't think it's necessary, but better to be safe than sorry, right?
				toolReference.ActivatedConnection:Disconnect() 
				toolConnections[child] = nil -- Remove the reference to this tool in the table.
			end
		end
	end)
	
	player.CharacterAdded:Connect(function()  -- Literally the same code as above but for characters dropping tools.
		
		local char = player.Character
		char.ChildRemoved:Connect(function(child)
			if child:IsA("Tool") and child.Parent ~= player.Backpack then
				
				if toolConnections[child] then
					local toolReference = toolConnections[child]

					toolReference.ActivatedConnection:Disconnect() 
					toolConnections[child] = nil
				end
			end
		end)
	end)
		
	
end

-- Sets up your initial backpack and character connection. This gets automatically disconnected when you respawn.
setupBackpack(player.Backpack)

LMK if you have any questions/concerns. There’s probably a simpler solution? My longer solution is good for tracking both tools in backpack and character, disconnecting .activated events for when you drop the tool but you don’t reset, and my script gives you a way to track per-tool values like cooldowns and so on/so forth.

1 Like

I’m kind of not sure what you need the tags for. Are you checking for a specific tool name? I thought they all worked similarly, so the tool name wouldn’t matter much. Just store attributes in each tool if you want to track stats, right? I don’t have enough information to understand.

if not Tool:HasTag(Name) return end
Tool.RemoveTag(Name)

That ensures 2 things, that a) the tool is not registered (Activated:Connect()) multiple times, and b) that the tool is meant to be handled by this script and not handled elsewhere. So if the tool is already handled we remove the tag so the tool is not handled multiple times. I use script.Name as the tag name so the tag name matches the script name that handles the tag, and I am not hardcoding any particular name.

With this script that is handled automatically. Each tool is connected once, and the connection resets upon you dying so it can be reapplied later. The tool can avoid being handled elsewhere by instead using a modulescript for this on the client so you can track across all scripts if there is a current tool being handled in the table, though I don’t think you need to do that with this in a localscript in startercharacterscripts anyways.

Anything that you still need resolved?

1 Like

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