How to stop players farming kill rewards with alt accounts

Paying for every kill lets two accounts trade kills for currency. How to skip freshly spawned victims and limit repeat kills of the same player.

warningLuaudit check: farmable-kill-rewards

What it looks like

The game pays currency, experience or items for every kill, with nothing to stop the same pair of players trading kills, or one player killing the same victim the moment they respawn. Two accounts, or a player and their friend, can farm rewards without playing.

Luau
humanoid.Died:Connect(function()
	local killer = lastHitBy[victim]
	if killer then
		reward(killer, 100)
	end
end)

How to detect it

  • Find where kills are credited: Humanoid.Died handlers, creator tags, last-hit tables, and leaderstats or profile fields for kills.
  • Look at what a kill pays. If it pays anything that can be spent or saved, check what limits it.
  • Roblox's defensive design guide uses this case: bots repeatedly killed for easy points. It recommends reducing or removing rewards for kills on newly spawned players. Look for a minimum time alive before a victim is worth anything.
  • Look for limits on repeat victims, such as diminishing rewards for killing the same player again within a few minutes, or a cap on rewards per victim per session.
  • A kill where the killer and victim are on the same team, or where the victim died to their own hazard, shouldn't pay.
  • Rewards a player can earn alone, such as for NPC kills, are judged by how fast NPCs spawn, not by this check.

What an exploiter could do

They don't need an exploit, only a second account. Two accounts stand together and kill each other as fast as the weapons allow, or one account kills the other at its spawn over and over. Currency and experience climb far faster than any real player can manage, and leaderboards fill with farmed scores.

How to fix it

  • Pay nothing for a victim who has been alive for less than a few seconds.
  • Reduce the reward for each repeat kill of the same victim within a window, down to nothing.
  • Credit kills only between opponents, and only when the killer's last hit was recent.

Vulnerable example

Vulnerable
local Players = game:GetService("Players")
local PlayerData = require(script.Parent.PlayerData)

local reward = 25
local lastHitBy: { [Player]: Player } = {}

local function watch(victim: Player, character: Model)
	local humanoid = character:WaitForChild("Humanoid") :: Humanoid
	humanoid.Died:Connect(function()
		local killer = lastHitBy[victim]
		lastHitBy[victim] = nil
		local profile = killer and PlayerData.get(killer)
		if profile and killer ~= victim then
			profile.Coins += reward
		end
	end)
end

Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		watch(player, character)
	end)
end)

Players.PlayerRemoving:Connect(function(player)
	lastHitBy[player] = nil
end)

Fixed example

Fixed
local Players = game:GetService("Players")
local PlayerData = require(script.Parent.PlayerData)

local reward = 25
local minimumAlive = 10
local repeatWindow = 300
local lastHitBy: { [Player]: Player } = {}
local spawnedAt: { [Player]: number } = {}
local recentKills: { [Player]: { [Player]: number } } = {}

local function watch(victim: Player, character: Model)
	spawnedAt[victim] = os.clock()
	local humanoid = character:WaitForChild("Humanoid") :: Humanoid
	humanoid.Died:Connect(function()
		local killer = lastHitBy[victim]
		lastHitBy[victim] = nil
		local profile = killer and PlayerData.get(killer)
		if not killer or not profile or killer == victim then
			return
		end
		local now = os.clock()
		if now - (spawnedAt[victim] or now) < minimumAlive then
			return
		end
		local victims = recentKills[killer] or {}
		recentKills[killer] = victims
		if now - (victims[victim] or -math.huge) < repeatWindow then
			return
		end
		victims[victim] = now
		profile.Coins += reward
	end)
end

Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		watch(player, character)
	end)
end)

Players.PlayerRemoving:Connect(function(player)
	lastHitBy[player] = nil
	spawnedAt[player] = nil
	recentKills[player] = nil
	for _, victims in recentKills do
		victims[player] = nil
	end
end)

Common false positives

  • Kills that pay only a score that resets each round, with no saved currency or leaderboard, give farmers nothing to keep.
  • Games where every match is server-made with random teams, such as matchmade rounds, make it hard to pair two accounts. Say so rather than reporting it at full weight.
  • Rewards already limited per victim and by time alive, like the fixed example, are the fix.