How to stop purchase limits resetting when players rejoin

Weekly stock and daily caps kept in a script table reset every time a player rejoins. How to save per-player limits in the profile instead.

warningLuaudit check: memory-only-limits

What it looks like

The game caps how much one player can buy or earn: a shop with three of each item per player, a weekly merchant, a daily cap on a reward, limited stock bought with a paid restock. The count of what each player has used is kept in a script table on the server and thrown away when they leave, so the cap starts again whenever they rejoin or move to another server.

Luau
local boughtThisWeek: { [Player]: number } = {}

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

What an exploiter could do

They hit the limit, rejoin or hop to another server, and buy or earn the full amount again. Limited items stop being limited, weekly stock becomes endless, and a paid restock is pointless because leaving does the same thing for free.

How to fix it

  • Save the count in the player's profile with the period it belongs to, such as the week number, and reset it on the server when the period changes.
  • Check and update the saved count in the same step that grants the item.
  • Keep short cooldowns in memory. Only limits meant to outlast a session need saving.

Vulnerable example

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

local price = 900
local dailyCap = 5
local opened: { [Player]: number } = {}

ReplicatedStorage.Remotes.BuyMysteryBox.OnServerEvent:Connect(function(player: Player)
	local profile = PlayerData.get(player)
	local count = opened[player] or 0
	if not profile or count >= dailyCap or profile.Coins < price then
		return
	end
	opened[player] = count + 1
	profile.Coins -= price
	table.insert(profile.Inventory, "MysteryBox")
end)

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

Fixed example

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

local price = 900
local dailyCap = 5

ReplicatedStorage.Remotes.BuyMysteryBox.OnServerEvent:Connect(function(player: Player)
	local profile = PlayerData.get(player)
	if not profile then
		return
	end
	local today = os.time() // 86400
	if profile.Progress.MysteryBoxDay ~= today then
		profile.Progress.MysteryBoxDay = today
		profile.Progress.MysteryBoxes = 0
	end
	local count = profile.Progress.MysteryBoxes or 0
	if count >= dailyCap or profile.Coins < price then
		return
	end
	profile.Progress.MysteryBoxes = count + 1
	profile.Coins -= price
	table.insert(profile.Inventory, "MysteryBox")
end)

How to spot this in your own scripts, and the cases that only look like it, are part of the Luaudit check your AI runs.