Appearance
Server Functions
Server-side PlayerState API for data management and persistence.
Server Only - Security
Server scripts only. No built-in RemoteEvents - create your own with validation.
Never trust client data!
Initialization Functions
Init()
Init(player, existingData?)
Initializes PlayerState for a player. Must be called when a player joins.
Parameters:
player: Player- The player to initializeexistingData?: PlayerData- Optional existing data (not currently supported)
Returns: boolean - Success status
Example
lua
local success = PlayerState.Init(player)
if success then
print("Player data loaded successfully")
else
warn("Failed to load player data")
-- Player will be kicked automatically
endNotes:
- Automatically loads data from ProfileStore and creates Replica for client sync
- Handles ProfileStore session management with automatic leaderstats integration
- Enhanced data validation and cleanup
- Player will be kicked if data fails to load
Basic Data Functions
Set()
Set(player, key, value) → boolean
Sets a top-level data value with enhanced validation.
Parameters: player: Player, key: string, value: any
Returns: boolean - Success status (false if leaderstats modification attempted)
Example
lua
local success = PlayerState.Set(player, "Coins", 1000)
if success then
print("Coins updated successfully")
else
warn("Failed to update coins")
endNote
Use SetPath() for nested paths instead of dot notation
Get()
Get(player, key) → any
Gets a top-level data value with validation. Automatically clones tables when AutoCloneTables is enabled in configuration.
Parameters: player: Player, key: string
Returns: any - The value, or nil if not found. Tables are cloned if AutoCloneTables is enabled.
Example
lua
local coins = PlayerState.Get(player, "Coins")
local level = PlayerState.Get(player, "Level")
print(`Player {player.Name} has {coins or 0} coins and is level {level or 1}`)
-- With AutoCloneTables enabled, tables are automatically cloned
local inventory = PlayerState.Get(player, "Inventory") -- Safe to modify
table.insert(inventory, newItem) -- Won't affect original if AutoCloneTables is trueNote
- When
AutoCloneTablesis enabled in config, table values are automatically deep cloned - Prevents accidental modification of internal data structures
- Configure in
PlayerStateConfig.Server.AutoCloneTables
Path-based Functions
SetPath()
SetPath(player, path, value) → boolean
Sets a nested data value using dot notation with enhanced validation.
Parameters: player: Player, path: ValuePath, value: any
Returns: boolean - Success status
Example
lua
local success = PlayerState.SetPath(player, "Plot.Likes", 50)
if success then
print("Plot likes updated")
end
PlayerState.SetPath(player, "Settings.MusicEnabled", false)
PlayerState.SetPath(player, "Stats.HighScore", 1200)Note
Returns success status with optimized path parsing and caching
GetPath()
GetPath(player, path) → any
Gets a nested data value using dot notation with optimized performance. Automatically clones tables when AutoCloneTables is enabled in configuration.
Parameters: player: Player, path: ValuePath
Returns: any - The value, or nil if not found. Tables are cloned if AutoCloneTables is enabled.
Example
lua
local likes = PlayerState.GetPath(player, "Plot.Likes")
local musicEnabled = PlayerState.GetPath(player, "Settings.MusicEnabled")
local highScore = PlayerState.GetPath(player, "Stats.HighScore")
-- With AutoCloneTables enabled, tables are automatically cloned
local settings = PlayerState.GetPath(player, "Settings") -- Safe to modify
settings.NewOption = true -- Won't affect original if AutoCloneTables is trueNote
- Optimized nested value retrieval with enhanced path caching system
- When
AutoCloneTablesis enabled in config, table values are automatically deep cloned - Prevents accidental modification of internal data structures
- Configure in
PlayerStateConfig.Server.AutoCloneTables
Numeric Operation Functions
Increment()
Increment(player, key, amount?) → boolean
Increments a numeric value with validation. Works with both top-level keys and nested paths.
Parameters: player: Player, key: string, amount: number? (defaults to 1)
Returns: boolean - Success status
Example
lua
-- Increment top-level values
local success = PlayerState.Increment(player, "Coins", 100)
if success then
print("Coins increased by 100")
end
-- Increment by 1 (default)
PlayerState.Increment(player, "Level")
-- Increment nested values using paths
PlayerState.Increment(player, "Plot.Likes", 5)
PlayerState.Increment(player, "Stats.HighScore", 250)
PlayerState.Increment(player, "Stats.GamesPlayed") -- defaults to +1Note
Only works with numeric values - warns if value is not a number
Numeric Values Only
Increment() only works with numeric values. Attempting to increment a non-numeric value will return false and show a warning.
Decrement()
Decrement(player, key, amount?) → boolean
Decrements a numeric value with validation. Works with both top-level keys and nested paths.
Parameters: player: Player, key: string, amount: number? (defaults to 1)
Returns: boolean - Success status
Example
lua
-- Spend coins (top-level)
local success = PlayerState.Decrement(player, "Coins", 50)
if success then
print("Coins decreased by 50")
else
warn("Failed to spend coins")
end
-- Decrement by 1 (default)
PlayerState.Decrement(player, "Lives")
-- Decrement nested values using paths
PlayerState.Decrement(player, "Plot.Health", 10)
PlayerState.Decrement(player, "Stats.Attempts", 1)Note
Internally uses Increment() with negative amounts
Performance & Safety
Use Increment() and Decrement() for numeric operations instead of Get() + Set() patterns. They're safer and more efficient!
lua
-- ❌ Less efficient and less safe
local coins = PlayerState.Get(player, "Coins")
PlayerState.Set(player, "Coins", coins + 100)
-- ✅ Better approach
PlayerState.Increment(player, "Coins", 100)Dictionary Operations: SetInDict() and RemoveFromDict() now use efficient nested path updates to prevent race conditions and minimize network traffic.
Batch Operation Functions
SetValues()
SetValues(player, values) → boolean
Sets multiple top-level values at once with leaderstats protection.
Parameters: player: Player, values: {[string]: any}
Returns: boolean - Success status
Example
lua
local success = PlayerState.SetValues(player, {
Coins = 1000,
Level = 5,
Experience = 750
})
if success then
print("Batch update successful")
endNote
Returns success status and prevents leaderstats modification in batch
Performance
Use SetValues() instead of multiple Set() calls for better performance!
BatchSetValues()
BatchSetValues(player, operations) → boolean
Queues multiple path-based operations for optimized batch processing.
Parameters: player: Player, operations: {BatchOperation}
Returns: boolean - Success status
lua
local success = PlayerState.BatchSetValues(player, {
{path = "Coins", value = 1000},
{path = "Level", value = 5},
{path = "Plot.Likes", value = 50}
})lua
local operations = {
{path = "Coins", value = 2000},
{path = "Experience", value = 500},
{path = "Plot.Likes", value = 100},
{path = "Settings.MusicEnabled", value = true},
{path = "Stats.GamesPlayed", value = 25}
}
PlayerState.BatchSetValues(player, operations)Notes:
- High-performance batch processing system with automatic optimization
- Configurable batch delay and size limits with auto-flush
- Prevents leaderstats modification
Batch Performance
- Operations are grouped by path structure for optimal performance
- Root-level operations are batched together
- Nested operations are intelligently grouped
- Auto-flush occurs at 20 operations or 0.03s delay
BatchUpdateValues()
BatchUpdateValues(player, operations) → boolean
Applies multiple increment/decrement operations at path-based locations in a single batch. Validates that each target value is numeric, then commits all updates via BatchSetValues().
Parameters: player: Player, operations: {BatchUpdateOperation}
Returns: boolean - Success status (false if validation fails or any path is non-numeric)
Each BatchUpdateOperation has:
path: string- Dot-notation path to a numeric valueoperation: "Increment" | "Decrement"amount: number?- Optional; defaults to1
Example
lua
local success = PlayerState.BatchUpdateValues(player, {
{path = "Coins", operation = "Increment", amount = 100},
{path = "Plot.Likes", operation = "Increment", amount = 5},
{path = "Lives", operation = "Decrement"}, -- amount defaults to 1
{path = "Stats.GamesPlayed", operation = "Increment"}
})
if success then
print("Batch update applied")
else
warn("BatchUpdateValues failed (invalid player, missing replica, or non-numeric path)")
endNumeric paths only
If any path points to a non-numeric value, the function warns and returns false; no changes are applied.
FlushBatch()
FlushBatch(player) → boolean
Immediately processes all queued batch operations for a player.
Parameters: player: Player
Returns: boolean - Success status
Example
lua
-- Queue operations
PlayerState.BatchSetValues(player, {
{path = "Coins", value = 1000},
{path = "Level", value = 5}
})
-- Force immediate processing
PlayerState.FlushBatch(player)Note
Manual batch processing control for critical operations
Array Operation Functions
AddToArray()
AddToArray(player, arrayPath, item) → boolean
Adds an item to the end of an array with validation.
Parameters: player: Player, arrayPath: ArrayPath, item: any
Returns: boolean - Success status
Example
lua
-- Add item to inventory
local success = PlayerState.AddToArray(player, "Inventory", {
Id = "sword_001",
Name = "Iron Sword",
Rarity = "Common"
})
if success then
print("Item added to inventory")
end
-- Add achievement
PlayerState.AddToArray(player, "Achievements", "first_kill")Note
Enhanced array operation with success return and validation
Array Indexing
Arrays are 1-indexed in Lua. Index 1 is the first item!
RemoveFromArray()
RemoveFromArray(player, arrayPath, index) → boolean
Removes an item from an array by index with bounds checking.
Parameters: player: Player, arrayPath: ArrayPath, index: number (1-based)
Returns: boolean - Success status
Example
lua
-- Remove first item from inventory
local success = PlayerState.RemoveFromArray(player, "Inventory", 1)
if not success then
warn("Failed to remove item")
end
-- Remove specific achievement
PlayerState.RemoveFromArray(player, "Achievements", 3)Note
Safe index-based removal with bounds checking and validation
UpdateArrayItem()
UpdateArrayItem(player, arrayPath, index, newItem) → boolean
Updates an item in an array by index with validation.
Parameters: player: Player, arrayPath: ArrayPath, index: number (1-based), newItem: any
Returns: boolean - Success status
Example
lua
-- Update inventory item
local success = PlayerState.UpdateArrayItem(player, "Inventory", 1, {
Id = "sword_002",
Name = "Steel Sword",
Rarity = "Rare",
Enchantments = {"Sharpness", "Durability"}
})
if success then
print("Item upgraded successfully")
endNote
Safe array item modification with index validation and bounds checking
Dictionary Operation Functions
SetInDict()
SetInDict(player, dictPath, key, value) → boolean
Sets a value in a dictionary/table with efficient nested path updates and automatic dictionary creation.
Parameters: player: Player, dictPath: DictPath, key: string, value: any
Returns: boolean - Success status
Example
lua
-- Set building data (creates dictionary if needed)
local success = PlayerState.SetInDict(player, "Plot.Buildings", "House", {
Level = 2,
Material = "Wood",
Position = Vector3.new(10, 0, 10)
})
-- Set game setting
PlayerState.SetInDict(player, "Settings", "GraphicsQuality", "High")
-- Set player stat
PlayerState.SetInDict(player, "Stats", "BestTime", 120.5)Note
Efficient nested path updates with automatic dictionary creation and race condition prevention
GetFromDict()
GetFromDict(player, dictPath, key) → any
Gets a value from a dictionary using a key with automatic type conversion. Automatically clones tables when AutoCloneTables is enabled in configuration.
Parameters: player: Player, dictPath: DictPath, key: string | number
Returns: any - The value, or nil if not found. Tables are cloned if AutoCloneTables is enabled.
Example
lua
-- Get building data
local houseData = PlayerState.GetFromDict(player, "Plot.Buildings", "House")
if houseData then
print("House level:", houseData.Level)
print("House material:", houseData.Material)
end
-- Get with numeric key (automatically converted to string)
local item = PlayerState.GetFromDict(player, "Inventory.Equipped", 1)
-- Get setting
local quality = PlayerState.GetFromDict(player, "Settings", "GraphicsQuality")
print("Graphics quality:", quality or "Default")Note
- Automatically converts number keys to strings for consistency
- When
AutoCloneTablesis enabled in config, table values are automatically deep cloned - Prevents accidental modification of internal data structures
RemoveFromDict()
RemoveFromDict(player, dictPath, key) → boolean
Removes a key from a dictionary using efficient nested path updates.
Parameters: player: Player, dictPath: DictPath, key: string
Returns: boolean - Success status
Example
lua
-- Remove building
local success = PlayerState.RemoveFromDict(player, "Plot.Buildings", "OldHouse")
if success then
print("Building removed")
end
-- Remove obsolete setting
PlayerState.RemoveFromDict(player, "Settings", "ObsoleteOption")Note
Efficient nested path removal with race condition prevention and minimal network traffic
Cache Management Functions
ClearPathCache()
ClearPathCache() → void
Clears the global path parsing cache for memory management.
Parameters: None
Returns: Nothing
Example
lua
-- Clear path cache during server maintenance
PlayerState.ClearPathCache()
-- Useful for debugging path-related issuesNote
Global cache management for path parsing with automatic size management (max 1000 entries)
Events
Data Manipulation Guidelines
BeforeSave & ProfileLoaded: Use PlayerState functions for data changes (recommended) as they provide validation, leaderstats sync, and proper error handling. Direct data manipulation via the data parameter bypasses these safeguards.
ProfileUnloaded: PlayerState functions are disabled. Only use direct data access via the data parameter for read-only operations or external system synchronization.
BeforeSave()
BeforeSave:Connect(listener)
Fired before player data is saved, allowing for final data modifications.
Parameters: listener: (player: Player, data: PlayerData) -> ()
Returns: Connection - Event connection for disconnecting
Example
lua
PlayerState.BeforeSave:Connect(function(player, data)
-- Use PlayerState functions for data changes (recommended)
PlayerState.Set(player, "LastSaveTime", os.time())
PlayerState.SetPath(player, "Stats.TotalPlayTime", data.Stats.PlayTime or 0)
-- Direct data manipulation bypasses validation (use sparingly)
data.FinalCoins = data.Coins -- Direct access for read-only operations
print(`Saving data for {player.Name}`)
end)Note
Fired before automatic saves with PlayerState functions working normally. Use PlayerState functions for data changes (recommended) as they provide validation and leaderstats sync.
ProfileLoaded()
ProfileLoaded:Connect(listener)
Fired when a player's profile data has been loaded and is ready for use.
Parameters: listener: (player: Player, data: PlayerData) -> ()
Returns: Connection - Event connection for disconnecting
Example
lua
PlayerState.ProfileLoaded:Connect(function(player, data)
-- Use PlayerState functions for data changes (recommended)
PlayerState.Set(player, "JoinTime", os.time())
PlayerState.SetPath(player, "Stats.Logins", (data.Stats.Logins or 0) + 1)
-- Read data for initialization logic
print(`{player.Name}'s data loaded with {data.Coins} coins`)
-- Setup player-specific features
setupPlayerUI(player)
end)Note
Fired after PlayerState.Init() completes successfully. PlayerState functions work normally - use them for data changes (recommended) for validation and proper sync.
ProfileUnloaded()
ProfileUnloaded:Connect(listener)
Fired when a player's profile session ends and data is being unloaded.
Parameters: listener: (player: Player, data: PlayerData) -> ()
Returns: Connection - Event connection for disconnecting
Example
lua
PlayerState.ProfileUnloaded:Connect(function(player, data)
-- Cleanup player-specific systems
print(`{player.Name}'s profile unloaded`)
-- Direct data access only - PlayerState functions don't work here
local finalCoins = data.Coins
local playTime = data.Stats.PlayTime or 0
-- Save to external systems if needed
saveToAnalytics(player.UserId, finalCoins, playTime)
end)Note
Fired when profile session ends. PlayerState functions do NOT work during this event - only direct data access via the data parameter is available for read-only operations.
Utility Functions
GetAll()
GetAll(player) → PlayerData?
Gets all player data with validation.
Parameters: player: Player
Returns: PlayerData? - Complete data table, or nil if not found
Example
lua
local allData = PlayerState.GetAll(player)
if allData then
print("Player has", allData.Coins, "coins")
-- Safely iterate through data
for key, value in pairs(allData) do
print(`{key}: {value}`)
end
endNote
Returns reference to actual data (be careful with modifications)
GetReplica()
GetReplica(player) → ReplicaInstance?
Gets the raw Replica instance for advanced operations.
Parameters: player: Player
Returns: ReplicaInstance? - Replica instance, or nil if not found
Example
lua
local replica = PlayerState.GetReplica(player)
if replica then
-- Advanced Replica operations
replica:OnChange(function(action, path, ...)
print(`Data changed: {action} at {table.concat(path, ".")}`)
end)
-- Check replica status
print("Replica is active:", replica:IsActive())
endNote
Enhanced validation ensures replica is active
GetProfile()
GetProfile(player) → ProfileStoreProfile?
Gets the raw ProfileStore profile for advanced operations.
Parameters: player: Player
Returns: ProfileStoreProfile? - Profile instance, or nil if not found
Example
lua
local profile = PlayerState.GetProfile(player)
if profile then
-- Advanced ProfileStore operations
print("Profile is active:", profile:IsActive())
-- Access profile metadata
print("Profile tags:", profile.Tags)
endNote
Enhanced validation ensures profile is active
Clone()
Clone(value, deep?) → any
Clones a value, with optional deep cloning for tables. Useful for safely copying data without modifying the original.
Parameters: value: any, deep: boolean? (defaults to false)
Returns: any - Cloned value (tables are cloned, other types returned as-is)
Example
lua
-- Shallow clone (default)
local originalTable = {coins = 100, items = {sword = true}}
local clonedTable = PlayerState.Clone(originalTable)
clonedTable.coins = 200 -- Only modifies clone
clonedTable.items.sword = false -- Modifies original too (shallow clone)
-- Deep clone (nested tables are also cloned)
local deepCloned = PlayerState.Clone(originalTable, true)
deepCloned.coins = 300 -- Only modifies clone
deepCloned.items.sword = false -- Only modifies clone (deep clone)
-- Clone non-table values (returns as-is)
local number = PlayerState.Clone(100) -- Returns 100
local string = PlayerState.Clone("test") -- Returns "test"Note
- Non-table values are returned unchanged
- Shallow clone (default) uses
table.clone()for performance - Deep clone recursively clones all nested tables
- Use deep clone when you need to modify nested structures safely
GetOfflineData()
GetOfflineData(userId) → PlayerData?
Retrieves player data for offline users from ProfileStore. Automatically checks if the player is currently in the server first for better performance.
Parameters: userId: number
Returns: PlayerData? - Player data, or nil if not found or invalid
Example
lua
local offlineData = PlayerState.GetOfflineData(123456789)
if offlineData then
print("Offline player has", offlineData.Coins, "coins")
print("Level:", offlineData.Level)
else
print("No offline data found for user")
endNote
- Automatically checks if player is in server first (uses live data if available)
- Falls back to ProfileStore for offline players
- More efficient than previous implementation
SetOfflineData()
SetOfflineData(userId, path, value) → boolean
Sets data for offline users in ProfileStore. Automatically checks if the player is currently in the server first and uses live data operations when available.
Parameters: userId: number, path: string, value: any
Returns: boolean - Success status
Example
lua
-- Set top-level data
local success = PlayerState.SetOfflineData(123456789, "Coins", 5000)
if success then
print("Offline coins updated successfully")
end
-- Set nested data using paths
PlayerState.SetOfflineData(123456789, "Plot.Likes", 100)
PlayerState.SetOfflineData(123456789, "Settings.MusicEnabled", false)Note
- Automatically checks if player is in server first (uses live PlayerState functions if available)
- Falls back to ProfileStore messaging for offline players with active sessions
- Creates new session if player is completely offline
- More efficient handling of online vs offline players
SaveData()
SaveData(player) → boolean
Forces data save validation check.
Parameters: player: Player
Returns: boolean - Profile active status
Example
lua
-- Check if save will succeed before critical operation
local canSave = PlayerState.SaveData(player)
if canSave then
-- Proceed with critical operation
PlayerState.Set(player, "ImportantData", value)
else
warn("Cannot save data - profile inactive")
endNote
Returns profile active status for validation
WipePlayerData()
WipePlayerData(player) → boolean
Wipes all player data and resets it to the template values.
Parameters: player: Player
Returns: boolean - Success status
Example
lua
-- Reset player data to template values
local success = PlayerState.WipePlayerData(player)
if success then
print(`{player.Name}'s data has been reset`)
else
warn(`Failed to reset {player.Name}'s data`)
endNote
Resets all data to CONFIG.Profile.Template values and kicks player
WipeOfflinePlayerData()
WipeOfflinePlayerData(userId) → boolean
Wipes data for offline players and resets it to template values.
Parameters: userId: number
Returns: boolean - Success status
Example
lua
-- Reset offline player data
local success = PlayerState.WipeOfflinePlayerData(123456789)
if success then
print("Offline player data reset successfully")
else
warn("Failed to reset offline player data")
endNote
Works with offline players not currently in the game
🏆 Global Leaderboard
Configuration Required & Runtime Validation
Leaderboard functionality requires proper configuration in PlayerStateConfig!
Make sure your PlayerStateConfig.Server.Leaderboard section is properly configured:
lua
Server = {
Leaderboard = {
Enabled = true,
DataStoreName = "PlayerState_Leaderboards",
TrackedStats = {"Coins", "Level", "HighScore"},
SyncInterval = 60,
UpdateOnPlayerLeave = true,
},
}Runtime Validation: Functions now validate configuration at runtime and will warn/return appropriate values if misconfigured:
GetLeaderboard()returns empty array{}GetPlayerRank()returnsnilUpdateLeaderboard()returnsfalse
Client-synced rank/score (for UI)
When leaderboards are enabled, PlayerState syncs each player’s current leaderboard display info to the client via a replicated _LeaderboardRanks table.
What this gives you:
- Reliable UI data: the client can read
{rank, score}for tracked stats without re-deriving it. - Instant-feeling score: when a tracked stat changes (via
Set,SetPath,SetValues, etc.), the syncedscoreupdates right away. - Rank on refresh:
rankupdates on the normal server refresh cycle (based onSyncInterval).
Important:
_LeaderboardRanksis temporary display data and is treated as non-persistent (it will not be saved into player data).- On the client, use
PlayerStateClient.GetLeaderboardInfo(statName)to read this value.
GetLeaderboard()
GetLeaderboard(statName, topCount) → {LeaderboardEntry}
Retrieves the top players for a specific leaderboard statistic.
Parameters: statName: string, topCount: number
Returns: {LeaderboardEntry} - Array of leaderboard entries sorted by score (highest first), or empty array if configuration issues
Example
lua
-- Get top 10 coin leaders
local coinLeaderboard = PlayerState.GetLeaderboard("Coins", 10)
for i, entry in ipairs(coinLeaderboard) do
print(`#{i}: User {entry.userId} - {entry.score} coins`)
end
-- Get top 100 high scores
local highScores = PlayerState.GetLeaderboard("HighScore", 100)
if #highScores > 0 then
print(`Highest score: {highScores[1].score} by User {highScores[1].userId}`)
endNote
Results are cached for 15 seconds to improve performance.
Configuration Validation
This function validates configuration at runtime:
- Returns empty array
{}and warns ifCONFIG.Leaderboard.Enabled = false - Returns empty array
{}and warns if thestatNameis not inCONFIG.Leaderboard.TrackedStats - Requires the stat name to be properly configured before calling
GetPlayerRank()
GetPlayerRank(player, statName) → number?
Gets a player's current rank on a specific leaderboard.
Parameters: player: Player, statName: string
Returns: number? - Player's rank (1-based), or nil if not ranked, invalid player, or configuration issues
Example
lua
-- Get player's coin rank
local coinRank = PlayerState.GetPlayerRank(player, "Coins")
if coinRank then
print(`You're ranked #{coinRank} for coins!`)
else
print("You're not yet ranked for coins")
end
-- Check multiple ranks
local levelRank = PlayerState.GetPlayerRank(player, "Level")
local scoreRank = PlayerState.GetPlayerRank(player, "HighScore")
if levelRank and scoreRank then
print(`Level Rank: #{levelRank}, Score Rank: #{scoreRank}`)
endNote
Returns nil if player has no score for this stat or if the player is invalid.
Configuration Validation
This function validates configuration at runtime:
- Returns
niland warns ifCONFIG.Leaderboard.Enabled = false - Returns
niland warns if thestatNameis not inCONFIG.Leaderboard.TrackedStats - Returns
nilif the player is invalid or not found - Requires the stat name to be properly configured before calling
UpdateLeaderboard()
UpdateLeaderboard(player, statName, score) → boolean
Manually updates a player's leaderboard entry for a specific statistic.
Parameters: player: Player, statName: string, score: number
Returns: boolean - Success status, or false if configuration or validation issues
Example
lua
-- Update player's coin leaderboard
local success = PlayerState.UpdateLeaderboard(player, "Coins", 5000)
if success then
print("Leaderboard updated successfully")
else
warn("Failed to update leaderboard")
end
-- Update multiple stats
PlayerState.UpdateLeaderboard(player, "Level", 25)
PlayerState.UpdateLeaderboard(player, "HighScore", 1200)Note
This function immediately updates the leaderboard. Most stats are automatically updated when data changes, but you can use this for manual updates.
Configuration Validation
This function validates configuration at runtime:
- Returns
falseand warns ifCONFIG.Leaderboard.Enabled = false - Returns
falseand warns if thestatNameis not inCONFIG.Leaderboard.TrackedStats - Returns
falseif the player is invalid - Requires the stat name to be properly configured before calling
- Clears the cache for the updated stat to ensure fresh data
Leaderstats Integration
🏆 Automatic Leaderstats
PlayerState now automatically creates and manages leaderstats based on your data configuration:
lua
-- In your DefaultData module, add leaderstats configuration:
local DefaultData = {
Coins = 0,
Level = 1,
Experience = 0,
Plot = {
Likes = 0
},
-- Leaderstats configuration
leaderstats = {
["💰 Coins"] = "Coins",
["⭐ Level"] = "Level",
["👍 Likes"] = "Plot.Likes"
}
}Features:
- Automatic Creation: Leaderstats are created when player data loads
- Real-time Sync: Updates automatically when data changes
- Type-Safe: Numbers use IntValue, others use StringValue
- Path Support: Supports nested paths like
"Plot.Likes"
Leaderstats Protection
Direct modification of leaderstats paths is prevented. Change the source data instead!
lua
-- ❌ This will fail
PlayerState.Set(player, "leaderstats", {...})
-- ✅ This updates the leaderstat automatically
PlayerState.Set(player, "Coins", 1000)Usage Examples
💰 Currency Management with Validation
Example
lua
-- Award coins (validation handled internally)
local function awardCoins(player, amount, source)
local currentCoins = PlayerState.Get(player, "Coins") or 0
local success = PlayerState.Set(player, "Coins", currentCoins + amount)
if success then
print(`Awarded {amount} coins to {player.Name} from {source}`)
return true
else
warn(`Failed to award coins to {player.Name}`)
return false
end
end
-- Spend coins with validation
local function spendCoins(player, amount, item)
local currentCoins = PlayerState.Get(player, "Coins") or 0
if currentCoins < amount then
return false, "Insufficient coins"
end
local success = PlayerState.Set(player, "Coins", currentCoins - amount)
if success then
print(`{player.Name} spent {amount} coins on {item}`)
return true, "Purchase successful"
else
return false, "Transaction failed"
end
end📈 Level System with Batch Operations
Example
lua
-- Level up player with batch operations
local function levelUp(player)
local currentLevel = PlayerState.Get(player, "Level") or 1
local currentCoins = PlayerState.Get(player, "Coins") or 0
local bonusCoins = currentLevel * 100
local success = PlayerState.BatchSetValues(player, {
{path = "Level", value = currentLevel + 1},
{path = "Experience", value = 0},
{path = "Coins", value = currentCoins + bonusCoins}
})
if success then
print(`{player.Name} leveled up to {currentLevel + 1}!`)
return true
end
return false
end
-- Add experience with level up check
local function addExperience(player, amount)
local currentExp = PlayerState.Get(player, "Experience") or 0
local currentLevel = PlayerState.Get(player, "Level") or 1
local expNeeded = currentLevel * 100
local newExp = currentExp + amount
if newExp >= expNeeded then
-- Level up
levelUp(player)
else
-- Just add experience
PlayerState.Set(player, "Experience", newExp)
end
end🎒 Enhanced Inventory Management
Example
lua
local MAX_INVENTORY_SIZE = 50
-- Add item with validation and limits
local function addItem(player, item)
local inventory = PlayerState.GetPath(player, "Inventory") or {}
if #inventory >= MAX_INVENTORY_SIZE then
return false, "Inventory full"
end
-- Add timestamp to item
item.AcquiredTime = os.time()
local success = PlayerState.AddToArray(player, "Inventory", item)
if success then
print(`Added {item.Name} to {player.Name}'s inventory`)
return true, "Item added"
end
return false, "Failed to add item"
end
-- Remove item by ID
local function removeItemById(player, itemId)
local inventory = PlayerState.GetPath(player, "Inventory") or {}
for index, item in ipairs(inventory) do
if item.Id == itemId then
local success = PlayerState.RemoveFromArray(player, "Inventory", index)
if success then
print(`Removed {item.Name} from {player.Name}'s inventory`)
return true
end
break
end
end
return false
end
-- Upgrade item in inventory
local function upgradeItem(player, itemIndex, newStats)
local inventory = PlayerState.GetPath(player, "Inventory") or {}
local item = inventory[itemIndex]
if not item then
return false, "Item not found"
end
-- Merge new stats
for key, value in pairs(newStats) do
item[key] = value
end
-- Update upgraded time
item.UpgradedTime = os.time()
local success = PlayerState.UpdateArrayItem(player, "Inventory", itemIndex, item)
if success then
print(`Upgraded {item.Name} for {player.Name}`)
return true
end
return false, "Upgrade failed"
end🏆 Leaderboard Management
Example
lua
-- Server: Comprehensive leaderboard system with validation
local function setupLeaderboardSystem()
-- Configuration reminder
-- Make sure PlayerStateConfig.Server.Leaderboard is properly configured!
-- ✅ Get top 10 players for coins (works if configured)
local topCoins = PlayerState.GetLeaderboard("Coins", 10)
if #topCoins > 0 then
for rank, entry in ipairs(topCoins) do
print(`#{rank}: User {entry.userId} - {entry.score} coins`)
end
else
print("No coin leaderboard data available (check configuration)")
end
-- ✅ Get player's current rank (returns nil if not ranked)
local playerRank = PlayerState.GetPlayerRank(player, "Coins")
if playerRank then
print(`You're currently ranked #{playerRank} for coins!`)
else
print("You're not yet ranked for coins")
end
-- ✅ Manual leaderboard update (for special cases)
local success = PlayerState.UpdateLeaderboard(player, "HighScore", 2500)
if success then
print("High score leaderboard updated!")
else
print("Failed to update leaderboard (check configuration)")
end
end
-- Safe leaderboard usage with error handling
local function safeLeaderboardUsage()
-- Always check if leaderboard is available before heavy usage
local testBoard = PlayerState.GetLeaderboard("Coins", 1)
if #testBoard == 0 then
warn("Leaderboard not configured properly - check PlayerStateConfig")
return false
end
-- Proceed with leaderboard operations
local playerRank = PlayerState.GetPlayerRank(player, "Coins")
return PlayerState.UpdateLeaderboard(player, "Coins", 100)
end
-- Get multiple leaderboard stats at once
local function getPlayerStatsSummary(player)
local stats = {
coinRank = PlayerState.GetPlayerRank(player, "Coins"),
levelRank = PlayerState.GetPlayerRank(player, "Level"),
scoreRank = PlayerState.GetPlayerRank(player, "HighScore"),
}
-- Display player's rankings
for statName, rank in pairs(stats) do
if rank then
print(`{statName}: Rank #{rank}`)
else
print(`{statName}: Not ranked yet`)
end
end
return stats
end
-- Leaderboard UI data
local function getLeaderboardUIData(statName, maxEntries)
local leaderboard = PlayerState.GetLeaderboard(statName, maxEntries or 50)
local uiData = {}
for i, entry in ipairs(leaderboard) do
table.insert(uiData, {
rank = entry.rank,
userId = entry.userId,
score = entry.score,
displayName = "Loading...", -- You'll need to get this from Player service
})
end
return uiData
end
-- Periodic leaderboard announcements
local function announceTopPlayers()
local topPlayers = PlayerState.GetLeaderboard("Coins", 3)
if #topPlayers >= 3 then
print("🏆 TOP 3 RICHEST PLAYERS 🏆")
for i, entry in ipairs(topPlayers) do
local medal = i == 1 and "🥇" or i == 2 and "🥈" or "🥉"
print(`{medal} #{i}: User {entry.userId} - {entry.score} coins`)
end
end
end🏠 Plot Building System
Example
lua
-- Build structure on plot (efficient nested updates)
local function buildStructure(player, buildingType, position)
local buildingData = {
Type = buildingType,
Level = 1,
Position = position,
BuiltTime = os.time(),
Health = 100
}
local buildingId = `{buildingType}_{os.time()}`
local success = PlayerState.SetInDict(player, "Plot.Buildings", buildingId, buildingData)
if success then
print(`{player.Name} built {buildingType} at {position}`)
return true, buildingId
end
return false, "Build failed"
end
-- Upgrade building (efficient single-key update)
local function upgradeBuilding(player, buildingId)
local building = PlayerState.GetPath(player, `Plot.Buildings.{buildingId}`)
if not building then
return false, "Building not found"
end
building.Level = building.Level + 1
building.UpgradedTime = os.time()
local success = PlayerState.SetInDict(player, "Plot.Buildings", buildingId, building)
if success then
print(`{player.Name} upgraded {building.Type} to level {building.Level}`)
return true
end
return false, "Upgrade failed"
end
-- Remove building (efficient key removal)
local function removeBuilding(player, buildingId)
local success = PlayerState.RemoveFromDict(player, "Plot.Buildings", buildingId)
if success then
print(`{player.Name} removed building {buildingId}`)
return true
end
return false, "Removal failed"
end🔄 Event-Driven Data Management
Example
lua
-- Setup event handlers for comprehensive data management
local function setupPlayerStateEvents()
-- When player data loads
PlayerState.ProfileLoaded:Connect(function(player, data)
-- Welcome back message
local loginCount = (data.Stats.Logins or 0) + 1
PlayerState.SetPath(player, "Stats.Logins", loginCount)
PlayerState.Set(player, "LastJoinTime", os.time())
-- Check for daily rewards
local lastReward = data.Stats.LastDailyReward or 0
local currentDay = math.floor(os.time() / 86400) -- Days since epoch
if currentDay > lastReward then
PlayerState.SetPath(player, "Stats.LastDailyReward", currentDay)
PlayerState.Increment(player, "Coins", 100)
print(`{player.Name} received daily reward!`)
end
-- Initialize player systems
setupPlayerUI(player)
setupPlayerEffects(player)
end)
-- Before data saves
PlayerState.BeforeSave:Connect(function(player, data)
-- Final cleanup and validation
data.LastSaveTime = os.time()
-- Calculate session playtime
local joinTime = data.LastJoinTime or os.time()
local sessionTime = os.time() - joinTime
local totalPlayTime = (data.Stats.TotalPlayTime or 0) + sessionTime
PlayerState.SetPath(player, "Stats.TotalPlayTime", totalPlayTime)
PlayerState.SetPath(player, "Stats.LastSessionTime", sessionTime)
-- Validate data integrity
if data.Coins and data.Coins < 0 then
warn(`{player.Name} had negative coins, resetting to 0`)
PlayerState.Set(player, "Coins", 0)
end
print(`Saving {player.Name}'s data - Session: {sessionTime}s, Total: {totalPlayTime}s`)
end)
-- When profile unloads
PlayerState.ProfileUnloaded:Connect(function(player, data)
-- Analytics and external system updates
local finalStats = {
userId = player.UserId,
coins = data.Coins or 0,
level = data.Level or 1,
totalPlayTime = data.Stats.TotalPlayTime or 0,
logins = data.Stats.Logins or 0
}
-- Send to analytics (direct data access only)
sendToAnalytics(finalStats)
-- Cleanup player-specific systems
cleanupPlayerUI(player)
cleanupPlayerEffects(player)
print(`{player.Name} profile unloaded - Final coins: {finalStats.coins}`)
end)
end
-- Call this during server startup
setupPlayerStateEvents()Performance Tips
Optimization
Batch Operations: Use BatchSetValues() for multiple changes instead of individual calls Cache Management: Call ClearPathCache() periodically for memory optimization Return Values: Always check success returns for error handling Path Reuse: Reuse path strings when possible for better performance
Best Practices
Automatic Validation: Functions validate automatically - no manual checks needed Leaderstats Protection: Change source data instead of direct leaderstats modification Error Handling: Use proper error handling and provide user feedback Cache Monitoring: Monitor cache performance and clear when necessary