How to make Pseudotools (UPDATED)

Hello! This is my second pseudotool tutorial, because since that last tutorial, I’ve noticed some flaws cough cough server security. This is an updated version that should have better server security! With images this time woo hoo!

0.1 Requirements

1. Setup
Firstly, get some folders in ReplicatedStorage, we will need these.

Now, you are going to want to grab a weapon, or build one. I’m going to use a sword as an example. I’m a horrible builder, so I got one from the toolbox

Screen Shot 2023-02-08 at 7.08.11 PM

Alright, we’re going to do a few things with. Unanchor all parts of the model, and set CanCollide to false, Name a random part to handle, and set the model’s PrimaryPart to the handle.

Now, time for some welding!
Weld all parts of the model to the handle, and for moving parts, Motor6D them together. Then, throw the model into ReplicatedStorage > weapons

1.1 Inputs
Ok, for the inputs, we will be using ContextActionService, as its easy to implement mobile support with it. Insert a LocalScript into StarterPlayer > StarterCharacterScripts.

Also add a 3 RemoteFunctions called “new”, “equip”, and “unequip” into ReplicatedStorage > events

Screen Shot 2023-02-08 at 7.32.05 PM

Let’s get the inputHandler coded.

--Note, this is StarterPlayer > StarterCharacterScripts

local ContextActionService = game:GetService("ContextActionService")
local handler = require(game.ReplicatedStorage.modules.psuedotool) -- module will be coded soon

local weps = game.ReplicatedStorage.events.new:InvokeServer()

local weapon = handler.new(weps)

local enumBinds = { -- enum bind table to loop through to set up contextactionservice
	[1] = Enum.KeyCode.One,
	[2] = Enum.KeyCode.Two,
	[3] = Enum.KeyCode.Three -- Add more for more weps equippable!
}

local lastInput = nil
local cooldown = 0.5 -- cooldown for weapon spam
local cooling = false

local function equip(actionName, inputState)
	local num = tonumber(string.sub(actionName, string.len(actionName))) -- mobile support is confusing :(
	--[[ 
		So basically, once the ContextActionService fires, it returns the name of the action, and the input state
		State can be begin, end, etc.
		If we click "1", the actionName will return "equip1"
		So we get the last character of the name, or the number, using string.sub(), and we turn it into a number using tonumber()
		we get the input by finding the index in the "enumBinds" table
		so enumBinds[1] would be Enum.KeyCode.1
	]]
	
	if inputState == Enum.UserInputState.Begin then
		if cooling then return end
		
		cooling = true
		local input = enumBinds[num]
		--lots of checks
		if weapon.equipped and lastInput == input then
			weapon:unequip()
		elseif weapon.equipped and lastInput ~= input then
			weapon:unequip()
			weapon:equip(table.find(enumBinds, input, 1))
		elseif not weapon.equipped then
			weapon:equip(table.find(enumBinds, input, 1))
		end

		lastInput = input
		task.wait(cooldown)
		cooling = false
	end
end

for i, v in pairs(weps) do
	ContextActionService:BindAction("equip"..i, equip, true, enumBinds[i]) -- binding to the keyboard and adding mobile buttons.
end

That should equip nicely, and there should be a button on mobile, too

Now, time to get the module coded!

local module = {}
module.__index = module -- metatable stuff

function module.new(weps)
	local self = {}
	
	self.loadout = weps
	
	return setmetatable(self, module) -- Now you can use the weapon variable in the local script and in any function in this module!
end

function module:equip(keybind)
	if #self.loadout < keybind then return end -- so you dont press 9 and it errors.
	
	local wep = game.ReplicatedStorage.weapons:FindFirstChild(self.loadout[keybind])
	if not wep then return end
	
	self.curWep = wep
	
	local check = game.ReplicatedStorage.events.equip:InvokeServer(wep) -- making sure the server can equip then weapons
	if not check then self:unequip() self.curWep = nil return end -- server can't equip, neither can client
	
	self.equipped = true -- used for checks.
end

function module:unequip()
	if not self.equipped and self.curWep then return end
	
	local check = game.ReplicatedStorage.events.unequip:InvokeServer() -- making sure the weapon is also equipped on the server
	if not check then return end -- server cant unequip, neither can client
	
	self.equipped = false -- cannot use weapon anymore...
	self.curWep = false
end

return module

Before we proceed, we have to make a few changes to the weapon. Add a Motor6D called “equip” or something, and set Part1 to handle, and set part0 to either torso, lower torso, or right arm, then edit the weld using the plugin I linked above by clicking on the plugin button and selecting the Motor6D

For the sake of easy weld editing, I will be using the “Right Arm” as the Part0 of my Motor6D, but the flaw is it makes animating annoying unless you are using procedural animations

Screen Shot 2023-02-08 at 7.39.11 PM

Make sure after editing weapons to remove the Motor6D’s Part0!!!

Now, insert a Script in ServerScriptService, and let’s get this coded

-- serverscriptservice
local events = game:GetService("ReplicatedStorage"):WaitForChild("events")
local weps = game:GetService("ReplicatedStorage"):WaitForChild("weapons")

local dss = game:GetService("DataStoreService") -- make sure API services are enabled if you do use data stores

local ds = dss:GetDataStore("jdsndjdchfb")

local players = {}
local default = {
	[1] = "Sword", -- your weapon name here
}

events:WaitForChild("new").OnServerInvoke = function(plr)
	if players[plr.Name] then return end
	
	players[plr.Name] = {} -- inspired from BlackShibe's fps tutorial!!!
	
	local data = players[plr.Name]
	
	players[plr.Name].loadout = ds:GetAsync("plr"..plr.UserId.."-loadout") or default -- if saved data, will not be showed in this tutorial
	
	return players[plr.Name].loadout
end

events:WaitForChild("equip").OnServerInvoke = function(plr, wepName)
	if players[plr.Name].curWep then return end -- if weapon is equipped
	if players[plr.Name].equipped then return end
	
	if not plr.Character then return end -- We need the character!
	
	local wep = weps:FindFirstChild(wepName)
	if not wep then return end -- if weapon is nonexistent
	
	wep = wep:Clone()
	
	local motor = wep.PrimaryPart:FindFirstChild("equip") -- if motor6d is existent
	if not motor then return end
	
	wep.Parent = plr.Character -- almost forgot this
	
	motor.Part0 = plr.Character['Right Arm'] --changeable to LowerTorso, UpperTorso, Torso, Right Arm, etc. attaches weapon to arm
	motor.Part1 = wep.PrimaryPart
	
	players[plr.Name].curWep = wep
	players[plr.Name].equipped = true
	
	return true -- client can now use
end

events:WaitForChild("unequip").OnServerInvoke = function(plr)
	-- pretty straightforward function
	if not players[plr.Name].curWep then return end
	if not players[plr.Name].equipped then return end -- make sure weapon is equipped
	
	if not plr.Character then return end
	
	players[plr.Name].curWep:Destroy()
	players[plr.Name].curWep = nil
	
	players[plr.Name].equipped = false
	
	return true -- client can now NOT use
end

Final result:


There, the weapons equips and unequips, with a cooldown. This works on mobile, too

Thanks for sticking until the end, and I hope to see you around the DevForum! :wink:

FAQ

  • To make the tool actually do stuff, just make a module:swing() function, and code it to work.

  • Animations can be added easily

Place File (Uncopylocked incase your code errors):

The creator is different because this is my devforum account.

Edit 1: Fixed mobile support because it was broken :\

15 Likes

2024… Are pseudotools more performant than tool instances?

tools themself are instances, this is pretty much the data blocks of a tool, without the instance

I imagine performance with pseudo tools is greater, because managing connections becomes much viable, and a lot more events become usable, such as characteremoving?

Just dropping in here:

  • Psuedotools will probably only be more performant in large numbers, but they are easier to manage, and are easily compatible with custom backpacks. It also makes it easier for external use, such as welding onto a character, animations, etc, due to it being a model. For me, at least, pseudotools are easier to manage in large numbers, and are not too advanced, which is why I use them

  • Tools are much simpler to use, however the current backpack UI is a bit outdated in my opinion, so if you are looking to create a custom backpack UI, then tools could work for you.

Edit: Tools are also less in your control than pseudotools are

Here’s a post regarding the up’s and downs of both:

Pseudotools or tools is honestly just based off of preference, so choose what you see fit between these two.

2 Likes