Custom Tool System: Equipping and Unequipping not working as intended

  1. What do you want to achieve? Keep it simple and clear!
    I want to make a Custom Tool System similar to Decaying Winter, where you have a limited amount of inventory slot, and if you equip an empty slot you will have a fallback tool, for example: Fists.

  2. What is the issue? Include screenshots / videos if possible!
    The issue I am coming across is if I equip an empty slot (Fists) while already equipping another empty slot (Fists again) the bindable events for equipping and unequipping will fire correctly (first unequipping then equipping) the “equipped” value will stay as false. Mind the fact then when you equip an empty slot (Fists) the first time it equips them just fine.
    image
    In the image above you can see that the first equipped is fired correctly and the value is true.
    But the second time equipping after unequipping the item, you can see that the value is false.

  3. What solutions have you tried so far? Did you look for solutions on the Developer Hub?
    I have tried looking for solutions on the web and also adding a delay before sending the equipped event, which while working had to be a significantly long time which made equipping not very responsive.

.
.
Item code:

script.Parent.UnequipEvent.Event:Connect(function()
	equipped = false
	print("Unequipping "..script.Parent.Name)
end)

script.Parent.EquipEvent.Event:Connect(function()
	equipped = true
	print("Equipping "..script.Parent.Name)
end)

inputService.InputBegan:Connect(function(input,chatted)
	print("Tool used! Equipped = "..tostring(equipped))
end)

.
.

Inventory code:

--|| SERVICES ||--
local players = game:GetService("Players")
local repStorage = game:GetService("ReplicatedStorage")
local inputService = game:GetService("UserInputService")

--|| PLAYER VARIABLES ||--
local player = players.LocalPlayer

--|| VARIABLES ||--
local currentItem = nil
local currentItemId = 0

local inventoryTable = {
	[1] = {nil,"Melee"};
	[2] = {nil,"Gun"};
	[3] = {nil,"Slot"};
	[4] = {nil,"Slot"};
}

local physicalInventory = player:WaitForChild("Inventory")
local physicalFallback = player:WaitForChild("FallbackItem")
local fallbackItem = physicalFallback:WaitForChild("Fists")
--|| UI VARIABLES ||--
local inventoryUI = script.Parent:WaitForChild("InventoryUI")
local itemFrame = inventoryUI:WaitForChild("ItemFrame")

local templateFolder = repStorage:WaitForChild("UITemplates")
local slotTemplate = templateFolder:WaitForChild("InventorySlot")

--|| EVENTS VARIABLES ||--
local eventsFolder = repStorage:WaitForChild("Events")
local inventoryHandler = eventsFolder:WaitForChild("InventoryHandler")

local equipItemEvent = inventoryHandler:WaitForChild("Equip")
local unequipItemEvent = inventoryHandler:WaitForChild("Unequip")

--|| FUNCTIONS ||--
function equipItem(id)
	if inventoryTable[id][1] then
		--Equip from physical inventory
	else
		--Equip fallback item
		currentItem = fallbackItem
		currentItemId = id
		currentItem.Parent = player.Character
		equipItemEvent:FireServer(currentItem)
		
		currentItem.EquipEvent:Fire()

		itemFrame["SLOT"..id].Item.Text.Text = "Fists"
		itemFrame["SLOT"..id].Item.BackgroundColor3 = Color3.fromRGB(50,50,50)
		itemFrame["SLOT"..id].Slot.BackgroundColor3 = Color3.fromRGB(50,50,50)

		if currentItem:FindFirstChild("JointGrip") then
			local visualGrip = currentItem:Clone()
			visualGrip.Parent = workspace.CurrentCamera.Arms
			visualGrip.Name = "Item"

			for i,v in pairs(visualGrip:GetChildren()) do
				if not v:IsA("Model") and not v:IsA("BasePart") then
					v:Destroy()
				end
			end

			local newJoint = Instance.new("Motor6D",visualGrip.JointGrip)
			newJoint.Name = "GripJoint"
			newJoint.Part0 = workspace.CurrentCamera.Arms.RightUpperArm
			newJoint.Part1 = visualGrip.JointGrip
			newJoint.C0 = CFrame.new(0,0,-1.25)
		end
	end
end

function unequipItem()
	currentItem.UnequipEvent:Fire()
	unequipItemEvent:FireServer(currentItem)
	if currentItem ~= fallbackItem then
		currentItem.Parent = physicalInventory
	else
		itemFrame["SLOT"..currentItemId].Item.Text.Text = "Empty"
		currentItem.Parent = physicalFallback
	end
	itemFrame["SLOT"..currentItemId].Item.BackgroundColor3 = Color3.fromRGB(255,255,255)
	itemFrame["SLOT"..currentItemId].Slot.BackgroundColor3 = Color3.fromRGB(255,255,255)
	
	if workspace.CurrentCamera.Arms:FindFirstChild("Item") then
		workspace.CurrentCamera.Arms.Item:Destroy()
	end
	currentItem = nil
end

function onInputBegan(input,chatted)
	if chatted then return end
	local number = tonumber(inputService:GetStringForKeyCode(input.KeyCode))
	if number and number < #inventoryTable then
		if not currentItem then
			equipItem(number)
		elseif (currentItem ~= inventoryTable[number][1]) then
			if currentItem then
				unequipItem()
			end
			equipItem(number)
		end
	end
end

function initializeUI()
	for i,item in pairs(inventoryTable) do
		local newSlot = slotTemplate:Clone()
		newSlot.Name = "SLOT"..i
		newSlot.Parent = itemFrame
		
		newSlot.Slot.Text.Text = item[2].." - "..i
		
	end
end

--|| CONNECTIONS ||--
inputService.InputBegan:Connect(onInputBegan)
--|| INITIALIZING ||--
initializeUI()
equipItem(1)
1 Like

Extra note:
image

the equipped value seems to be set correctly when looking at it before equipping and before unequipping (the print is at the start of the equip and unequip functions), but it just seems like it doesn’t want to set the equipped value?

1 Like

Are the events sending multiple times per equip/unequip event? Why it is printing 4 times and 2 times?

The tool used print is printed every time an input is detected

How can the tool be equipped 4 times in a row before being unequipped?

“Tool used! equipped = true” just says in which state the equipped value is in, when I use the tool (when an input is detected) it doesn’t set the equipped value.

1 Like

These prints don’t match the code shown. Has the code been updated since? The item code does not appear to be printing from the bindable events.

Edit: I see the original output shown does print what is in the item code, nvm that question.

I’m at a loss. Does the equipped Boolean get edited anywhere else in the script?

No, the equipped boolean isn’t edited anywhere else.

Okay so, I used an auto clicker to force an update every frame and I noticed that after equipping there is like 1 - 2 frames where the equipped value is set to true but after that it goes back to false.
Edit: Also, if I set a task.wait() in a for i loop for the amount of frames that the equipped value is set to true, it will equip no problem.

Can you show the whole item script? This is very odd behavior.

local inputService = game:GetService("UserInputService")

local animations = script.Parent:WaitForChild("Animations")

local equipAnimation = nil
local idleAnimation = nil
local lightCombo1Animation = nil
local lightCombo2Animation = nil
local lightCombo3Animation = nil
local heavyAttackAnimation = nil
local blockAnimation = nil
local blockHitAnimation = nil
local shoveAnimation = nil
local equipped = false

local equipTick = tick()

local comboSpeed = 0.5
local heavySpeed = 2

local attacking = false
local canPerform = true

script.Parent.UnequipEvent.Event:Connect(function()
	equipped = false
	print("Unequipped")
	if equipAnimation then
		equipAnimation:Stop()
	end
	if idleAnimation then
		idleAnimation:Stop()
	end
	if lightCombo1Animation then
		lightCombo1Animation:Stop()
	end
	if lightCombo2Animation then
		lightCombo2Animation:Stop()
	end
	if lightCombo3Animation then
		lightCombo3Animation:Stop()
	end
	if heavyAttackAnimation then
		heavyAttackAnimation:Stop()
	end
	if blockAnimation then
		blockAnimation:Stop()
	end
	if blockHitAnimation then
		blockHitAnimation:Stop()
	end
	if shoveAnimation then
		shoveAnimation:Stop()
	end
end)

script.Parent.EquipEvent.Event:Connect(function()
	equipped = true
	print("Equipped")
	equipTick = tick()
	if not equipAnimation then
		equipAnimation = workspace.CurrentCamera.Arms.Animator:LoadAnimation(animations:WaitForChild("Equip"))
	end
	if not idleAnimation then
		idleAnimation = workspace.CurrentCamera.Arms.Animator:LoadAnimation(animations:WaitForChild("Idle"))
	end
	if not lightCombo1Animation then
		lightCombo1Animation = workspace.CurrentCamera.Arms.Animator:LoadAnimation(animations:WaitForChild("LightCombo1"))
	end
	if not lightCombo2Animation then
		lightCombo2Animation = workspace.CurrentCamera.Arms.Animator:LoadAnimation(animations:WaitForChild("LightCombo2"))
	end
	if not lightCombo3Animation then
		lightCombo3Animation = workspace.CurrentCamera.Arms.Animator:LoadAnimation(animations:WaitForChild("LightCombo3"))
	end
	if not heavyAttackAnimation then
		heavyAttackAnimation = workspace.CurrentCamera.Arms.Animator:LoadAnimation(animations:WaitForChild("HeavyAttack"))
	end
	if not blockAnimation then
		blockAnimation = workspace.CurrentCamera.Arms.Animator:LoadAnimation(animations:WaitForChild("Block"))
	end
	if not blockHitAnimation then
		blockHitAnimation = workspace.CurrentCamera.Arms.Animator:LoadAnimation(animations:WaitForChild("BlockHit"))
	end
	if not shoveAnimation then
		shoveAnimation = workspace.CurrentCamera.Arms.Animator:LoadAnimation(animations:WaitForChild("Shove"))
	end
	idleAnimation:Play()
	equipAnimation:Play()
	equipAnimation:AdjustWeight(1,0)
end)

inputService.InputBegan:Connect(function(input,chatted)
	print("Tool used! Equipped = "..tostring(equipped))
	if not equipped or chatted or not canPerform or tick() - equipTick < 1 then return end
	if input.UserInputType == Enum.UserInputType.MouseButton1 then
		attacking = true
		canPerform = false
		lightCombo1Animation:Play()
		task.wait(comboSpeed)
		if attacking and equipped then
			lightCombo2Animation:Play()
			task.wait(comboSpeed)
			if attacking and equipped then
				lightCombo3Animation:Play()
				task.wait(comboSpeed)
			end
		end
		attacking = false
		canPerform = true
	elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
		attacking = true
		canPerform = false
		heavyAttackAnimation:Play()
		task.wait(heavySpeed)
		attacking = false
		canPerform = true
	elseif input.KeyCode == Enum.KeyCode.Q then
		canPerform = false
		shoveAnimation:Play()
		shoveAnimation:AdjustWeight(1,0)
		task.wait(1)
		canPerform = true
	elseif input.KeyCode == Enum.KeyCode.R then
		canPerform = false
		blockAnimation:Play()
		spawn(function()
			local RNG = math.random(1,2)
			if RNG == 1 then
				task.wait(0.3)
				blockHitAnimation:Play()
				blockHitAnimation:AdjustWeight(1,0)
			end
		end)
		task.wait(1)
		canPerform = true
	end
end)
inputService.InputEnded:Connect(function(input,chatted)
	if chatted then return end
	if input.UserInputType == Enum.UserInputType.MouseButton1 then
		attacking = false
	end
end)

Here you go

Is there one of these in each tool or only one item script total?

I got an update: I removed the events for unequipping and equipping for the server and the equipping fixed itself.

in each tool, but currently there is only 1 tool rn

So it’s fixed? That is odd behavior as well :joy:

Edit: are you using a RemoteEvent or a RemoteFunction? RemoteFunction will yield the code until it returns, RemoteEvent does not:

Not really, cause I need the calls to the server so the tool appears to other players too

See above edit
adding text for chr req

I use RemoteEvent, never even used RemoteFunction in my life :thinking:

I’ve noticed that in the equip function, you fire the RemoteEvent before the bindable Event, but in the unequip function it is the opposite order.

Is this a necessary distinction and could be causing any strange behavior?