How to make Pseudotools

NEW VERSION HERE:

Hey there! This is my first tutorial on the devforum, please point out errors / typos if you see any.
This tutorial is basic / intermediate level, you will need to know about these topics.

  1. modules and metatables
  2. welding
  3. joint editing

Images coming soon :’(

PLUGINS USED
I used this plugin to edit welds, you will need it for this tutorial
https://create.roblox.com/marketplace/asset/245496280/Joint-Edit?pageNumber=1&pagePosition=0&keyword=joint+edit

Alright, time to start for real now!

1.0 Setting up
Insert 3 folders into ReplicatedStorage, and name them “Functions”, “Tools”, and “Modules”. We will be using these later

Grab a tool model from the toolbox(If it’s a tool, remove all scripts and only keep the parts), or make one (I’m bad at building so I got one from the toolbox lol)

If the tool you got from the toolbox / made doesn’t have an invisible part named handle, make one, and position it near the handle of the tool. Make sure the entire model is UNACHORED AND CANCOLLIDE IS CHECKED OFF. Now, make the primary part of the model the handle part and inside, add a motor6d
Name the motor6d something like “torso”

Set the Part1 of the motor6D to the handle part, and your tool is set for joint editing!

This tutorial will now be continued based on if you want to implement it using r15 or r6

1.1 Multiple part model
If you are using a model with multiple parts, WELD THE TOOL TOGETHER USING WeldConstraints TO THE HANDLE BEFORE FOLLOWING THE NEXT STEPS!!!

1.1 r15
Insert an r15 rig into workspace
Drag your tool into the rig.
Look for the motor6D inside of the handle part in the tool, and make Part0 UpperTorso or LowerTorso(Your choice lol)

INSTALL THE PLUGIN I LINKED AT THE TOP IF YOU HAVEN’T ALREADY!!

In the explorer, select the motor6D in the part named handle, and open the Joint Editor plugin.
Use the rotation and movement tools to edit the weld
After you are satisfied with the result, click save and click on the Joint Editor plugin in the plugins tab to close it.

MAKE THE PART0 OF THE MOTOR6D NIL

Drag the tool in ReplicatedStorage > Tools(One of three folders we made.)

1.2 R6
Do the exact same thing except instead of changing the Part0 of the Motor6D to UpperTorso or LowerTorso, change it to Torso

2.0 Modules!
Insert a module script into ReplicatedStorage > modules, and name it whatever you want, in this case, I named it combat (Because I am using a sword as my model)
Type this out in the module(It’s ok if you don’t understand, I never understood it before too)

local module = {}
local meta = {__index = module}

function module.new(toolName)
    local self = {} -- creating a table to hold all our values
    self.tool = toolName -- tool name
    self.equipped = false -- if its equipped, you will be needing this if you want the tool to do stuff
    self.canUse = false -- debounce

    return setmetatable(self, meta) -- metatable stuff (makes it so you can access "self" from any function in the module
end

function module:equip()
    if not self.tool and not self.equipped and not self.canUse then return end -- checks
    local tool = game.ReplicatedStorage.Tools:WaitForChild(self.tool) 
    local check = game.ReplicatedStorage.Functions.equip:InvokeServer(tool)

    if check.returning then -- if the tool was equipped
        self.equipped = true
        self.canUse = true
        warn(check.error)
    else -- if the tool wasn't equipped and an error occured
        warn(check.error)
    end
end

return module

2.1 Server sided code
Insert a script into ServerScriptService and type this
R15 VERSION

local equipFunc = game.ReplicatedStorage.Functions.equip

equipFunc.OnServerInvoke = function(plr, tool)
    local char = plr.Character or plr.CharacterAdded:Wait() -- character of player
    if not char then return  {returning = false, error = "Could not find character"} -- if no character, returns an error table
    tool = tool:Clone()
    local main = tool.PrimaryPart
    local motor = main:FindFirstChild("torso") or main:FindFirstChildWhichIsA("Motor6D")
    
    if not main and motor then return {returning = false, error = "Could not find tool handle and/or torso motor"} -- error table 2
    if not char["UpperTorso"] then return {returning = false, error = "Could not find character torso"} -- error table 3
    motor.Part1 = main
    motor.Part0 = char["UpperTorso"] -- OR char["LowerTorso"]! -- connects tool to body
    return  {returning = true, error = "successful!"} -- if succesful
end

R6 VERSION

local equipFunc = game.ReplicatedStorage.Functions.equip

equipFunc.OnServerInvoke = function(plr, tool)
    local char = plr.Character or plr.CharacterAdded:Wait()
    if not char then return  {returning = false, error = "Could not find character"} -- if no character, sends back error table
    tool = tool:Clone()
    local main = tool.PrimaryPart
    local motor = main:FindFirstChild("torso") or main:FindFirstChildWhichIsA("Motor6D")
    
    if not main and motor then return  {returning = false, error = "Could not find tool handle and/or torso motor"} -- error table 2
    if not char["Torso"] then return {returning = false, error = "Could not find character torso"} -- error table 3
    motor.Part1 = main
    motor.Part0 = char["Torso"] -- connects tool to body
    return {returning = true, error = "successful!"} -- if succesful
end

Now, you may be wondering, why aren’t you just calling the RemoteFunction from a separate script instead of using a module?
Good question!
The reason for this is code management
If we use modules, we can store any other related functions into the module, for a easy and secure place for them to be kept.

2.2 Local sided code and mobile support
Add a LocalScript into StarterPlayer > StarterCharacterScripts
Now type this code

local plr = game.Players.LocalPlayer
local char = plr.Character or plr.CharacterAdded:Wait()

local ContextActionService = game:GetService("ContextActionService")

local combatModule = require(game.ReplicatedStorage.Modules.combat) -- module we just made

local sword = combatModule.new("sword")-- your weapon name here
--[[Multiple tools
local tools = {
    [1] = combatModule.new("sword"),
    [2] = combatModule.new("flashlight")
}

local keycodes = {
    [1] = Enum.KeyCode.One, -- keyboard button 1
    [2] = Enum.KeyCode.Two, -- keyboard button 2
    [3] = Enum.KeyCode.Three, -- keyboard button 3
}
]]

local function equip()
    sword:equip()
end -- don't know if you can just call sword:equip() through ContextActionService so i made a new function :/
--[[Multiple tools
local function equip(WepName)
   tools[WepName]:equip()
end
]]

ContextActionService:BindAction("EquipSword", equip, true, Enum.KeyCode.One) -- binds the equip function to keyboard button 1
--[[Multiple weps(again)
for i, v in pairs(tools) do
    ContextActionService:BindAction("Equip"..v, equip(v), true, keycodes[i]) -- binds multiple equip functions with keyboard buttons 1, 2, and 3
end
]]

That should be enough code to make it equip nicely. This should work in mobile, too.

2.3 EXTRAS
Unequipping is easy, just destroy the weapon in the server side using a RemoteFunction, and if it is usable, unbind the action using ContextActionService:UnbindAction(ActionName). Make a function in the module like module:unequip(), then fire a RemoteFunction from there to make it easier on you

To make it usable, add some code in the module, bind another action using ContextActionService, and call the action from the module, OR make a function like this

local function use()
    BlahTool:doSomething()
end

For switching, just add more binding and unequip the first tool, equip the second.

Maybe legacy moon animation suite can be used to edit welds instead of Joint Edit, I haven’t tested it yet though, but I know Joint Edit works

2.4 CONCLUSION
Alrighty, I hope you guys enjoyed this tutorial. The best part about the module is its flexibility. You can really just add more to the module for animations and stuff

Remember though, damage HAS TO BE HANDLED IN THE SERVER
Alright, thanks for making it this far and trusting in me. Please point out errors. Thanks and see you hopefully next time

Edit: BETTER VERSION OUT NOW!!!

13 Likes

How come you are using remotefunctions but not returning anything?

edit: (what i’m saying is, why not just use remoteevents, or use the remote function to return errors if the character or character’s hand doesn’t exist etc)

1 Like

I suggest putting the Motor6D in the Torso, because it gives you more free reign on animating. Im going to slowly re animate the few animations in my game because of the mistake of putting a Motor6D in the right arm which made it harder to animate the shield in my game too. Selecting both the arm and the tool is much better than the Motor6D moving the tool with the arm when you don’t want it to. The shield in my game was harder to animate because of how it had to be held in front of you. Well, when I wanted to do an adjustment to the arms, I’d have to move the shield all the way back into place.

3 Likes

Perhaps it’s to yield the script until the motor6d has been created?

1 Like

W but I suggest trying to use Knit by Sleitnick, it’s useful to get around the remote functions thing.

1 Like

Yup! That’s why I used RemoteFunctions

1 Like

Thanks for the recommendation! Will check it out

Thanks for the feedback! I’ll edit it now

Are there any use cases for making a tools this way compared to making tools with the Tool Object?

1 Like

Not really, but in my opinion this is more flexible and it’s easier to implement mobile support, and it’s a lot easier to handle multiple tools with this method. If you don’t want a tool to be unequippable, I feel this method is easier, as you just don’t make a function for unequipping, instead of adding some code to make the Tool object unequippable

Perhaps it would be great if you explain in detail how the code works by adding a comment next to the code. It would help beginners understand your code and the logic behind it better.

1 Like

thanks for the suggestion! I’ll be sure to edit and add comments on most lines later

I added some code and now it has a reason to use RemoteFunctions instead of RemoteEvents!

1 Like

I added comments on crucial lines to help people understand the code!

Can you add a video for the result u are making? i dont understand well whats the title says sorry

1 Like

Yeah, images are coming out soon!