AFK system not working as intended

Hey, currently I’m trying to make an afk system, which makes the player’s character material to forcefield, and gives the player a forcefield when the player has clicked away from the screen OR if the player has not been doing any inputs on their device for 60 seconds+. Ive done most of it, when the player clicks away from the roblox tab it works and all, but basically when i do inputs it doesnt reset back the timer (should go back to 60).

this is my script, i can show in-game aswell:

local ReplicatedStorage = game:GetService("ReplicatedStorage").Events
local UserInputService = game:GetService("UserInputService")

local Focused = ReplicatedStorage:WaitForChild("Focused")
local UnFocused = ReplicatedStorage:WaitForChild("Unfocused")

local AFKTimeThreshold = 60 -- in seconds
local forceFieldCreated = {} -- Track if force field is already created for each player
local lastActivityTime = {} -- Track the last activity time for each player

local function CreateForceField(player)
	if not forceFieldCreated[player] then
		if not player.Character:FindFirstChild("ForceField") then
			local forceField = Instance.new("ForceField")
			forceField.Visible = true
			forceField.Parent = player.Character
			for _,v in ipairs(player.Character:GetDescendants()) do
				if v:IsA("MeshPart") then 
					v.Material = Enum.Material.ForceField
				end
			end
			forceFieldCreated[player] = true
			print(player.Name .. " force field created at: ", tick())
		end
	end
end

local function RemoveForceField(player)
	if forceFieldCreated[player] then
		local forceField = player.Character:FindFirstChild("ForceField")
		if forceField then
			forceField:Destroy()
			for _,v in ipairs(player.Character:GetDescendants()) do 
				if v:IsA("MeshPart") then 
					v.Material = Enum.Material.Plastic
				end
			end
			forceFieldCreated[player] = nil
			print(player.Name .. " force field removed at: ", tick())
		end
	end
end

local function IsPlayerAFK(player)
	return (tick() - lastActivityTime[player]) > AFKTimeThreshold
end

Focused.OnServerEvent:Connect(function(Player)
	RemoveForceField(Player)
end)

UnFocused.OnServerEvent:Connect(function(Player)
	CreateForceField(Player)
end)

game.Players.PlayerAdded:Connect(function(player)
	lastActivityTime[player] = tick() -- Initialize last activity time for each player

	while wait(1) do
		local timeLeft = math.max(0, AFKTimeThreshold - (tick() - lastActivityTime[player]))
		print(player.Name .. " timer at: " .. timeLeft .. " seconds")

		if IsPlayerAFK(player) then
			print(player.Name .. " is AFK")
			CreateForceField(player)
		else
			print(player.Name .. " is not AFK")
			RemoveForceField(player)
		end
	end
end)

local function UpdateLastActivityTime(player)
	lastActivityTime[player] = tick()
end

UserInputService.InputBegan:Connect(function(input)
	local player = game.Players:GetPlayerFromCharacter(input.UserInputType)
	if player then
		UpdateLastActivityTime(player)
		print(player.Name .. " input detected at: ", tick())
	end
end)

UserInputService.InputChanged:Connect(function(input)
	local player = game.Players:GetPlayerFromCharacter(input.UserInputType)
	if player then
		UpdateLastActivityTime(player)
		print(player.Name .. " input changed at: ", tick())
	end
end)
2 Likes

When you get the player at the bottom, you attempt to get their character from the user input type. User input type is for example, keyboard, touch, etc. Instead, just set the player to game.Players.localplayer.

If this isn’t a local script, UIS won’t work anyway and you would need to fire a remote event when ever there is input from the client side.

It seems you have mixed up server and client scripts?

Also this line makes no sense, UserInputType is not a character…

local player = game.Players:GetPlayerFromCharacter(input.UserInputType)

Also your solution is way too complicated imho. Allow me to simplify it.

I would say all the local script needs to do is to fire server when there is an input, and make the server start a timer.

local ActiveEvent = game.ReplicatedStorage:WaitForChild('ActiveEvent')

UserInputService.InputBegan:Connect(function(input)

	ActiveEvent:FireServer()

end)

Server Script will keep track of active players:

local ActiveEvent = Instance.new("RemoteEvent")
ActiveEvent.Name = "ActiveEvent"
ActiveEvent.Parent = game.RepicatedStorage

local TimeOutTicks = {}

ActiveEvent.OnServerEvent:Connect(function(player)
	RemoveForceField(player)
	if not TimeOutTicks[player] then
		TimeOutTicks[player] = 0
	end
	TimeOutTicks[player] += 1
	local thisTick = TimeOutTicks[player]

	task.wait(AFKTimeThreshold)

	if player.Parent and thisTick == TimeOutTicks[player] then
		CreateForceField(player)
	end
end)

Be aware though. This script is super exploitable, I would also add special function that removes force field when server detects character movement or any other player input based remote event.

Adding on, the server sided script can also detect changes in position.

I guess that would be very recommended to add this as an extra. It could also be the alternative to my solution. It really depends on the game.

Can the player still affect the game without moving their character? If not, then local script could be scrapped, and the server could rely on avatar movement only.

1 Like

sorry for not replying earlier, was @ school. this is my game setup, ill try to make it compatible with the things you provided me with, but im pretty new to scripting, i doubt ill be able to make it myself:

this is the script I provided, which is a server script:
image

and this is a local script from StarterPlayerScripts:

actually i kind of figured it out by myself surprisingly, and it works perfectly on my part! :slight_smile: can someone check it out and let me know how i could improve the script, perchance?

local ReplicatedStorage = game:GetService("ReplicatedStorage").Events
local ActiveEvent = ReplicatedStorage:WaitForChild("ActiveEvent")
local Focused = ReplicatedStorage:WaitForChild("Focused")
local UnFocused = ReplicatedStorage:WaitForChild("Unfocused")

local AFKTimeThreshold = 60 -- in seconds
local forceFieldCreated = {} -- Track if force field is already created for each player
local lastActivityTime = {} -- Track the last activity time for each player

local function CreateForceField(player)
	if not forceFieldCreated[player] then
		if not player.Character:FindFirstChild("ForceField") then
			local forceField = Instance.new("ForceField")
			forceField.Visible = true
			forceField.Parent = player.Character
			for _,v in ipairs(player.Character:GetDescendants()) do
				if v:IsA("MeshPart") then 
					v.Material = Enum.Material.ForceField
				end
			end
			forceFieldCreated[player] = true
			print(player.Name .. " force field created at: ", tick())
		end
	end
end

local function RemoveForceField(player)
	if forceFieldCreated[player] then
		local forceField = player.Character:FindFirstChild("ForceField")
		if forceField then
			forceField:Destroy()
			for _,v in ipairs(player.Character:GetDescendants()) do 
				if v:IsA("MeshPart") then 
					v.Material = Enum.Material.Plastic
				end
			end
			forceFieldCreated[player] = nil
			print(player.Name .. " force field removed at: ", tick())
		end
	end
end

local function IsPlayerAFK(player)
	return (tick() - lastActivityTime[player]) > AFKTimeThreshold
end

ActiveEvent.OnServerEvent:Connect(function(player)
	lastActivityTime[player] = tick() -- Update last activity time for the player

	if IsPlayerAFK(player) then
		CreateForceField(player)
	else
		RemoveForceField(player)
	end
end)

Focused.OnServerEvent:Connect(function(player)
	lastActivityTime[player] = tick() -- Update last activity time for the player
end)

UnFocused.OnServerEvent:Connect(function(player)
	if IsPlayerAFK(player) then
		CreateForceField(player)
	else
		RemoveForceField(player)
	end
end)

local function UpdateLastActivityTime(player)
	lastActivityTime[player] = tick()
end

-- Detect movement
local function OnCharacterMove(character)
	local player = game.Players:GetPlayerFromCharacter(character)
	if player then
		UpdateLastActivityTime(player)
		print(player.Name .. " movement detected at: ", tick())
	end
end

game.Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		character:WaitForChild("HumanoidRootPart").Changed:Connect(function()
			OnCharacterMove(character)
		end)
	end)
end)

-- Check AFK state every second and apply/remove force field accordingly
while true do
	wait(1)
	for player, lastActivityTick in pairs(lastActivityTime) do
		local remainingTime = math.max(0, AFKTimeThreshold - (tick() - lastActivityTick))
		print(player.Name .. " timer at: " .. remainingTime .. " seconds")
		if remainingTime == 0 then
			if IsPlayerAFK(player) then
				CreateForceField(player)
			else
				RemoveForceField(player)
			end
		end
	end
end