Tool info table gets into all tools, help!

Howdy, so, in my game, i have multiple weapon tools, such as an axe, a knife, and others, so i tried to avoid exploiders by setting all the important stats into a server script inside serverScriptService.

The script haves inside a table with all the info, such as Cooldown, Damage, and the AnimationsFolder, each tool should have their own space inside the table:

--=============================[[ SERVICE ]]==============================--

local ReplicatedStorage = game:GetService("ReplicatedStorage")

--=============================[[ FOLDERS ]]==============================--

local Remotes = ReplicatedStorage:WaitForChild("Remotes")
local Animations = ReplicatedStorage:WaitForChild("Animations").Weapons

local ToolInfoRemote = Remotes:WaitForChild("ToolInfo")

--=============================[[ TABLES ]]==============================--

local ToolInfo = {
	["FireAxe"] = {
		Cooldown = 2,
		Damage = 15,
		AnimationsFolder = Animations:WaitForChild("FireAxe")
	},
	["Knife"] = {
		Cooldown = 1,
		Damage = 15,
		AnimationsFolder = Animations:WaitForChild("Knife")
	},
}

--=============================[[ FUNCTIONS ]]==============================--

ToolInfoRemote.OnServerEvent:Connect(function(player, Tool) -- Tool, is tool.name from the client

	ToolInfoRemote:FireClient(player, ToolInfo[Tool])

end)

here is the client side script

local InfoRemote= ReplicatedStorage.Remotes:WaitForChild("ToolInfo")

local CoolDownTime = nil
local Damage = nil
local AnimFolder = nil

InfoRemote:FireServer(Tool.Name)

InfoRemote.OnClientEvent:Connect(function(toolData)

	CoolDownTime = toolData.Cooldown
	Damage = toolData.Damage
	AnimFolder = toolData.AnimationsFolder
	
	if AnimFolder then
		EquipAnim = AnimFolder:WaitForChild("Equip")
		IdleAnim = AnimFolder:WaitForChild("Idle")
		Slash1Anim = AnimFolder:WaitForChild("Slash1")
		Slash2Anim = AnimFolder:WaitForChild("Slash2")
		CleanAnim = AnimFolder:WaitForChild("Clean")
	end
	
end)
--=============================[[ ANIMATIONS ]]==============================--

local AnimEquip
local AnimIdle
local AnimSlash1
local AnimSlash2
local AnimClean


local function LoadAnimations ()
	AnimEquip = Humanoid.Animator:LoadAnimation(EquipAnim)
	AnimIdle = Humanoid.Animator:LoadAnimation(IdleAnim)
	AnimSlash1 = Humanoid.Animator:LoadAnimation(Slash1Anim)
	AnimSlash2 = Humanoid.Animator:LoadAnimation(Slash2Anim)
	AnimClean = Humanoid.Animator:LoadAnimation(CleanAnim)
	if TestMode then
		print(Tool.name.." Animations Loadded")
	end
end

local function StopAnimations ()
	if AnimIdle then AnimIdle:Stop() end
	if AnimEquip then AnimEquip:Stop() end
	if AnimSlash1 then AnimSlash1:Stop() end
	if AnimSlash2 then AnimSlash2:Stop() end
	if AnimClean then AnimClean:Stop() end

	if TestMode then
		print(Tool.name.." Animations Stopped")
	end

end

so, the issue, when calling the InfoRemote inside the client, every single tool gets the same info and stats from the table.

So, knife haves Axe Animations, CoolDown, and Damage, i want this to apply individualy for each tool, (every tool having what their own stats).

I tried calling InfoRemote in a different order.
I tried to change the structure of the table, but honestly idk much about tables on scripting.
My best attemp was calling InfoRemote when equipping the tool, but this makes so i need to equip it twice to get their own Stats

Tool.Equipped:Connect(function()
	InfoRemote:FireServer(Tool.Name)
end)

so, i just got more confused, i can provide the rest of the code if needed

Currently, each player is sharing the same ToolInfo table, so to solve this problem, you’ll need to clone the ToolInfo table:

--=============================[[ SERVICE ]]==============================--

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

--=============================[[ FOLDERS ]]==============================--

local Remotes = ReplicatedStorage:WaitForChild("Remotes")
local Animations = ReplicatedStorage:WaitForChild("Animations").Weapons

local ToolInfoRemote = Remotes:WaitForChild("ToolInfo")

--=============================[[ TABLES ]]==============================--

local ToolInfo = {
	["FireAxe"] = {
		Cooldown = 2,
		Damage = 15,
		AnimationsFolder = Animations:WaitForChild("FireAxe")
	},
	["Knife"] = {
		Cooldown = 1,
		Damage = 15,
		AnimationsFolder = Animations:WaitForChild("Knife")
	},
}

local ToolInfoCache = {} -- Storage for each player's unique ToolInfo table

--=============================[[ FUNCTIONS ]]==============================--

Players.PlayerAdded:Connect(function(player)

	ToolInfoCache[player.UserId] = table.clone(ToolInfo)

end)

Players.PlayerRemoving:Connect(function(player)

	ToolInfoCache[player.UserId] = nil -- The player is leaving the game, so remove their table from memory

end)

ToolInfoRemote.OnServerEvent:Connect(function(player, Tool) -- Tool, is tool.name from the client

	local ToolInfo = ToolInfoCache[player.UserId]

	if ToolInfo then -- Check to make sure that the player's ToolInfo is not nil before using it
		ToolInfoRemote:FireClient(player, ToolInfo[Tool])
	end

end)

Do note that if you plan on storing other tables within ToolInfo, you’ll need to use a deep copy function rather than table.clone, otherwise values may be lost

What exactly are you avoiding from exploiters by storing that data on the server?

Data sent through remotes are essentially copies of that data, their “identities” are not preserved.
No changes on the server or client will replicate to the server or other clients unless done so explicitly.

1 Like

I’m aware of that, so hopefully this will clear any misunderstandings:

@stirkman Making any changes to the ToolInfo table on the client side won’t make it replicate to the ToolInfo table that’s stored on the server side, due to what @nowodev correctly stated (essentially, the client receives a copy of the table). You shouldn’t really trust the client to make any changes in the first place due to the fact that exploiters exist and can modify the stats to their benefit, so you should still do something similar to my original suggestion and create a unique copy of the ToolInfo table on the server side for each player, and making sure to edit the player’s copy on the server side whenever appropriate

If you’re allowing clients to make changes to data, you should perform sanity checks, or avoid allowing them in the first place if you are able to handle everything on the server side (such as upgrades, don’t allow them to just specify what stats they want!).

I don’t see anything in OP’s code that would make such changes anyways, and that’s not their issue.

Again, I’m well aware of how important it is to protect code against exploiters, and the steps required to do so. You might not understand what I’m trying to suggest, but hopefully the OP does, and it is relevant to their issue:

I’m pretty sure they’re saying that all requests return the same data when they should be something else.

Please keep in mind exploiters can modify pretty much anything on their client.

This includes hooking remotes and functions and modifying their results. If your code relies on values sent from clients (such as damage), then that is fundamentally flawed and you should change that.
If not, then your stats are safe to store in a location that can be accessed by both the client and server, and you don’t need to go through this custom remote replication.

Which, to be honest, is due to the fact that they’re always indexing the same exact ToolInfo table. If the OP wants each individual tool to have unique stats, then they need a unique copy of the ToolInfo table for each player, due to the fact that each player gets a unique copy of the tools that are stored in StarterPack. If the player is able to have multiple copies of one particular type of tool, then you’ll need to use a system that’s much more complicated in-order for every single tool to have unique stats. I’m able to write it if need be, but it will take time since I’m currently on mobile

I don’t think they’re changing the values per player, their data seems to just be constant stats for the tools.

If that’s the case, then their original code should already work without any issues, unless they expect the client to be able to modify the stats. In all honesty, we both need to wait for more information from the OP in-order to be able to provide better suggestions :sweat_smile:

1 Like

You need to use remote functions to request and receive data (client-server-client)

okay, i think any missundertandings are my fault, im honestly bad at explaining myself.

ok, so, the tables are indeed constant, so im not making any changes on them, im trying to get the table contents by firing the remote event.

All tools have the same client script inside, they all call the same remote event to get their variables, like the cooldown and so on, but for some reason this makes that if ToolA gets their respective table variables, ToolB will also get them, replacing their current variables, i dont want that.

I also tried your last aproach, but i dont think it worked for the issue im trying to fix tho
again, please if you need more information and have any questions, just ask me!

here is an example of how the issue looks inside my game, in the vid the Knife animations at first work normally, but then they apply to the Axe, after equipping the Axe, its animations apply to the knife. i probably should have provided this first-

1 Like

With the help of this new information, I’ve just realized what the true cause of the problem is: Each time a tool is equipped, it’s calling to get its ToolInfo from the RemoteEvent, and since every tool is connected to the same exact event, their ToolInfo is overwritten with the equipped tool’s info. The best way to solve this problem is to store the ToolInfo table inside of a module in ReplicatedStorage, and each tool’s LocalScript requires it to fetch the necessary data. If a client modifies the ToolInfo table, it won’t replicate to the server or other players, so you can still use a server script to verify that their tool is working as intended. The reason why this is the best solution is because creating a unique RemoteEvent for each different type of tool would also work to fix the issue, but it’s not a very practical solution

@stirkman You can easily verify the above by printing the toolData parameter of the RemoteEvent’s OnClientEvent connection inside of each tool’s LocalScript. If my suspicion is correct, each tool will print the same value no matter what their actual type is, which will be the type of the tool that was previously equipped :slight_smile::+1:

You should also consider rewriting logic; Becouse its insanely flawwed and could be source of issue to begin with

I also don’t understand what the point gatekeeping info from client?
Why can’t you straight up provide this information for a client?

1 Like

Could it be that updating the Animation variables and playing them are NOT sequential?, causing them to play the previous animation variables before them being updated? since you’re calling almost 3 remote events just to get the tool’s data and update the animation variables it could be delayed, and we don’t know how or when you’re playing the animations.

if that is the case then you can use RemoteFunctions and request the server for the tool’s data when equipped and then load the animations and play them inside the same block of code to ensure they’re sequential.

Thx for the feedback
Im honestly new to all this about avoiding exploiders and this is the best idea i had, plus im a bit paranoid about finishing a game just for it to be filled with exploids-

so, quick replies to your questions:

so im trying to avoid that important stuff from being modified by exploiders, idk if its actually posible, but i heard this a lot “Never trust the client”.

So, even if try to call all the animations from the client, the rest of the content from the tables will be filled incorrectly, and i do have a working version from just the client, but i dont think it would be safe to use it.

you missunderstood, Im using a single remote event, and that is InfoRemote , im not using 2 nor 3, and when i tried to use a RemoteFunction, it just straith up didnt work, not even an error output, but probably my fault, ill try again after researching how to use them.

Ok, so, after meesing around with other stuff from my game, the solution just popped out of my head-

So, the error was that the client didnt confirm if the tool was getting the correct information from the remote event, so all i had to do was updating FireClient and OnClientEvent

On the server:

ToolInfoRemote.OnServerEvent:Connect(function(player, Tool)

	ToolInfoRemote:FireClient(player, Tool, ToolInfo[Tool])
	print(Tool, ToolInfo)
end)

And on the client:

InfoRemote.OnClientEvent:Connect(function(Toolname, toolData)
	if Toolname == Tool.Name then
		CoolDownTime = toolData.Cooldown
		Damage = toolData.Damage
		AnimFolder = toolData.AnimationsFolder

		if AnimFolder then
			EquipAnim = AnimFolder:WaitForChild("Equip")
			IdleAnim = AnimFolder:WaitForChild("Idle")
			Slash1Anim = AnimFolder:WaitForChild("Slash1")
			Slash2Anim = AnimFolder:WaitForChild("Slash2")
			CleanAnim = AnimFolder:WaitForChild("Clean")
		end
	end
end)

Either way i thank you all for trying to help me!, i actually learned from this.
Also, if you wish please provide more feedback about how make this in a better of more efficent way, again, if you wish.