Hold-Click Tools

Hi there! Hope your having a great day! I’m currently making a simulator and would like some help. Currently the system I have makes it so you have to repeatedly click to gain “points”. How would I make it so that it would be like mining sim’s tools where you hold down the mouse? I’m just looking for ways I can do so.

My current client code which is in the tool itself

local equipped = false
local tool = script.Parent
local plr = game.Players.LocalPlayer
local mouse = plr:GetMouse()

tool.Equipped:Connect(function()
	equipped = true
end)

tool.Unequipped:Connect(function()
	equipped = false
end)

mouse.Button1Down:Connect(function()
	if equipped then
		game.ReplicatedStorage.Events.EatDonut:FireServer()
	end
end)

Server Code

local LastEat = {}

function Eat(player)
	local stat = 100
	if LastEat[player.UserId] == nil then
		LastEat[player.UserId] = os.time()
	end
	
	if (os.time() - LastEat[player.UserId] >= 1) then
		if player.Stats.CurrentAmount.Value < player.Stats.MaxAmount.Value then
			player.Stats.CurrentAmount.Value = player.Stats.CurrentAmount.Value + 1
			LastEat[player.UserId] = os.time()
		end
	end
	
	if (os.time() - LastEat[player.UserId] >= 1) then
		if player.Stats.CurrentAmount.Value >= player.Stats.MaxAmount.Value then
			player.Stats.CurrentAmount.Value = player.Stats.MaxAmount.Value
		end
	end
end

game.ReplicatedStorage.Events.EatDonut.OnServerEvent:Connect(function(player)
	Eat(player)
	
end)
6 Likes

You’ll need a variable to store if the player is holding their click or not, similar to your equipped variable.

Then with the Tool, you can use the Activated and Deactivated events to toggle that variable. Activated is ran when the player clicks while the Tool is equipped, and Deactivated is ran when the player releases that click.

If you are constantly performing an action while the player is holding down their click, you’ll need a loop to help there. I don’t recommend spamming your RemoteEvent within a loop, so you can pass a variable to it and have that handle the check.

https://developer.roblox.com/en-us/api-reference/event/Tool/Activated
https://developer.roblox.com/en-us/api-reference/event/Tool/Deactivated

2 Likes

There is probably a better solution, but what typically works for me is a while loop that causes the tool’s action to happen when a certain value is true.

Here is what I would suggest:

local tool = script.Parent
local plr = game.Players.LocalPlayer
local mouse = plr:GetMouse()

mouseDown = false

mouse.Button1Down:connect(function()
    mouseDown = true
end)

mouse.Button1Up:connect(function()
    mouseDown = false
end)

while true do
    wait()
    if mouseDown then
    	game.ReplicatedStorage.Events.EatDonut:FireServer()
    end
end

Edit: My bad, I completely forgot to put in a check to see if the player has the tool equipped or not (thank you, ChasingSpace). Make sure that’s added; the code you had to check that to begin with should work.

4 Likes

Close, but this would run regardless if the player has the tool equipped or not. Also, I don’t think it’s a great idea to spam fire a RemoteEvent like that. The server code should have it’s own loop, and the client code should only tell the server when the player clicks and when they release that click.

4 Likes

To prevent spamming remote event calls:

local lastsent = tick() --initialize to something between negative infinity and tick()
local increment = 0.1 --fire the remote event 10 times per second
while true do
  wait()
    if tick() >= lastsent + increment then
         if mousedown then
               lastsent = tick()
               --fire server via remote event
         end
    end
end
1 Like

To completely avoid having the remote events spammed, you could instead, send a signal to the server to communicate the mouse down and up event, and let the server run a loop to give the player points.

The client code can be changed to:

local equipped = false
local mouseDown = false
local lastClick = 0
local tool = script.Parent
local plr = game.Players.LocalPlayer
local inputServ = game:GetService("UserInputService")

tool.Equipped:Connect(function()
	equipped = true
end)

tool.Unequipped:Connect(function()
	equipped = false
end)

inputServ.InputBegan:Connect(function(input, processed)
    if processed then
        return
    end
    if equipped and input.UserInputType == Enum.UserInputType.MouseButton1 then
        if tick()-lastClick >= 1 then
            lastClick = tick()
            mouseDown = true
		    game.ReplicatedStorage.Events.EatDonut:FireServer(true)
        end
	end
end)

inputServ.InputEnded:Connect(function(input, processed)
	if mouseDown and input.UserInputType == Enum.UserInputType.MouseButton1 then
        mouseDown = false
		game.ReplicatedStorage.Events.EatDonut:FireServer(false)
	end
end)

I’ve updated the mouse events to use UserInputService as is recommended on the developer hub.

And the server code:

local eating = {}

function Eat(player)
	local stats = player.Stats
    local current = stats.CurrentAmount
    local maximum = stats.MaxAmount
    
    -- While they're still eating, and not at maximum capacity
    while eating[player.UserId] and current.Value < maximum.Value do
        -- Add 1 point per second
	    current.Value = current.Value + 1
        wait(1)
	end
end

game.ReplicatedStorage.Events.EatDonut.OnServerEvent:Connect(function(player, mode)
    if mode then -- Mouse down
        eating[player.UserId] = true
	    Eat(player)
    else -- Mouse up
        eating[player.UserId] = false
    end
end)

No matter what method you chose to use, be mindful of possible exploiters who may send invalid remote requests to the server. You could combat this by making sure they’re at least holding the tool on the server.
Hope this is what you needed, let me know if you face any problems as I haven’t tested the code!

1 Like

Okay, you make a good point. I forgot about spam clicking, but my method will still make less remote event calls than yours while the mouse is being held down. If 10 players are holding the mouse for 10 seconds using your method, the remote event will recieve 1000 calls in those 10 seconds. Using my method, each player only sends an event at the start and end of their clicking, meaning the event will recieve just 20 calls in those 10 seconds.

I’ll update mine now to prevent spam clicking, that was an oversight when I coded it - more of a problem than something I intended to miss out.

This is what I use for automatic guns:

local mouse = player:GetMouse()
local mousedown = false
local canshoot = true -- debounce
local cooldown = 0.5

local function shoot()
if canshoot == true then
canshoot = false
--do something
wait(cooldown)
canshoot = true
end
end

mouse.Button1Down:connect(function()
mousedown = true
if canshoot == true then
repeat
shoot()
until mousedown == false
end
end)

mouse.Button1Up:connect(function()
mousedown = false
end)

essentially its just debounce + repeated functions

4 Likes

But it would also be better to do on the server if spam clicking is a concern. Exploiters could completely overwrite the local script on the client and send that event as fast as they can.

edit: sorry, replied to wrong person

Include mouse.Button1Up.

30chars