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
- Basic knowledge in modules and meta tables
- Joint Edit Plugin (https://create.roblox.com/marketplace/asset/245496280?viewFromStudio=true&keyword=joint&searchId=65405040-0920-4607-a803-5ffc9ac74b95)
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
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
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
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!
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 :\