Loading...
Loading...
Recipes for common systems built on server authority - currency service and leaderstats, XP/levels, round loops with intermission and teams, validated melee/ranged combat, inventory and tools, soft-currency shops, obby checkpoints, daily rewards, pets. Use when implementing or reviewing gameplay systems such as rounds, combat, weapons, inventories, shops, currencies, or progression.
npx skill4agent add el4cteo/roblox-skills roblox-game-systemsroblox-data-storesroblox-securityroblox-networkingConfigService| System | Reference |
|---|---|
| Leaderstats, currency service, XP/levels, daily rewards, obby checkpoints | references/progression.md |
| Round loop (intermission → match → results), teams, spawning | references/round-system.md |
| Melee and ranged combat with server validation, cooldowns, damage attribution | references/combat.md |
| Inventory, equipping tools, shop purchases (soft currency), pets that follow | references/inventory-shop.md |
--!strict
-- ServerScriptService/Server/Services/CurrencyService.luau
local Players = game:GetService("Players")
type Balances = { [Player]: number }
local MAX_BALANCE = 1e12
local CurrencyService = {}
local balances: Balances = {}
local function publish(player: Player)
player:SetAttribute("Coins", balances[player]) -- replicated to every client; UI listens
end
function CurrencyService.load(player: Player, saved: number?)
balances[player] = math.clamp(saved or 0, 0, MAX_BALANCE)
publish(player)
end
function CurrencyService.get(player: Player): number
return balances[player] or 0
end
-- Positive amount only; returns the new balance.
function CurrencyService.add(player: Player, amount: number, _source: string): number?
local balance = balances[player]
if balance == nil or not math.isfinite(amount) or amount <= 0 then
return nil
end
balances[player] = math.min(balance + math.floor(amount), MAX_BALANCE)
publish(player)
return balances[player]
end
-- Atomic check-and-spend; false if insufficient.
function CurrencyService.spend(player: Player, amount: number, _sink: string): boolean
local balance = balances[player]
if balance == nil or not math.isfinite(amount) or amount <= 0 or balance < amount then
return false
end
balances[player] = balance - math.floor(amount)
publish(player)
return true
end
Players.PlayerRemoving:Connect(function(player)
balances[player] = nil -- after the data layer has saved it
end)
return CurrencyServiceroblox-analytics-liveopsprofile.Data.coinsplayer:GetAttribute("Coins")GetAttributeChangedSignal("Coins")leaderstatsIntValueroblox-architectureroblox-securityroblox-data-storesroblox-networkingroblox-characters-animationroblox-monetizationroblox-ui