I’ve been working on a system to manage player data using ProfileStore by loleris, and I’d love some feedback on it. The system handles things like saving/loading inventory data and managing player sessions. It works, but I feel like there’s room for improvement, and I want to make sure I’m doing things the right way.
What I Need Help With:
Better Practices: Am I using ProfileStore the way it’s meant to be used?
General Feedback: Anything else you see that could be improved or cleaned up?
--<Services>--
local runService = game:GetService("RunService")
local players = game:GetService("Players")
--<Modules>--
local profileStoreModule = require(script.Parent:FindFirstChild("ProfileStore"))
local defaultData = require(script.Parent.Default_Data)
--<Variables>--
local playersInventoryFolder = script.Parent.Parent.Parent.Players_Inventories
local inventoryTemplate = playersInventoryFolder:FindFirstChild("Inventory_Temp")
local key = "MainKey"
if runService:IsStudio() then
key = "StudioKey"
end
local profileStore = profileStoreModule.New(key, defaultData)
local Profiles: {[player]: typeof(playerStore:StartSessionAsync())} = {}
local Player = {}
Player.__index = Player
function Player.new(newPlayer: Player)
local self = setmetatable({}, Player)
self.Player = newPlayer
self.Character = newPlayer.Character
self.Player.CharacterAdded:Connect(function(character: Model)
self.Character = character
end)
--Extra variables
self.CurrentlySaving = false
self.CurrentlyLoading = false
self.UnLoading = false
self.UserId = self.Player.UserId
self:CreateProfile(self.Player)
return self
end
function Player:CreateProfile(player: Player)
print("Data key name: "..profileStore.Name)
local profile = profileStore:StartSessionAsync(`{player.UserId}`, {
Cancel = function()
return player.Parent ~= players
end,
})
if profile ~= nil then
profile:AddUserId(player.UserId)
profile:Reconcile()
profile.OnSessionEnd:Connect(function()
Profiles[player] = nil
player:Kick("Profile session end - Please rejoin")
end)
if player.Parent == players then
Profiles[player] = profile
print(`Profile loaded for {player.DisplayName}!`)
self:CloneDefaultInventory()
task.wait()
self:LoadData(player)
else
profile:EndSession()
end
else
player:Kick("Profile load fail - Please rejoin")
end
end
function Player:LoadData(player: Player)
local playerInventoryModule = require(playersInventoryFolder:FindFirstChild(player.UserId))
if not playerInventoryModule then
return false
end
local profile = Profiles[player]
if profile and profile.Data then
local success, errorMsg = pcall(function()
playerInventoryModule.SendInventoryData(profile.Data.Inventory)
end)
if success then
print(profile.Data)
--task.wait(3)
--for _, player in ipairs(players:GetPlayers()) do
-- playerInventoryModule.AddItem("Wooden Axe")
-- print(playerInventoryModule["Inventory"])
--end
else
warn("Failed to save data for " .. player.DisplayName .. ": " .. errorMsg)
end
else
warn("No profile found for "..player.DisplayName)
end
end
function Player:SaveData(player: Player)
local profile = Profiles[player]
if profile ~= nil then
local success, errorMsg = pcall(function()
profile.Data.Inventory = self:GetPlayerInventory(player)
end)
if success then
profile:EndSession()
print(player.DisplayName .. "'s data successfully saved!")
end
end
end
function Player:CloneDefaultInventory()
if not inventoryTemplate then
return
end
local defaultInventory = inventoryTemplate:Clone()
defaultInventory.Name = self.UserId
defaultInventory.Parent = playersInventoryFolder
end
function Player:GetPlayerInventory(player: Player)
local playerInventoryModule = require(playersInventoryFolder:FindFirstChild(player.UserId))
if not playerInventoryModule then
return
end
return playerInventoryModule.GetInventoryData()
end
return Player
I appreciate any tips or suggestions you can provide—I’m continually learning and eager to improve, so your insights would be incredibly valuable.
you could have your player data class object just be standalone and create a module script that contain your loadprofile or saveprofile or anyhing like that, also i would try separating really other logic from the module you are working on.
Depending on how big your game is gonna be if you have stuff like inventory and add items, clearitems etc in a module named playerdata it can get quite confusing. i would try making different seperate modules for like inventory and equipment or any other things and their own logic, so instead of having the GetPlayerInventory function in playerdata you can have that in a own modulescript for the inventory.
Thank you for your recommendation and feedback. I had already implemented an improved version that follows this approach, separating the player class and profile class. If you’re interested, I’ve shared the code below.
Player Class: Manages all player/character-related mechanics. (Work in progress)
local serverScriptService = game:GetService("ServerScriptService")
local playersInventoryFolder = serverScriptService.Players_Inventory
local Player_Class = {}
Player_Class.__index = Player_Class
local MAX_WEIGHT = 200
local SLOW_SPEED = 8
local WALK_SPEED = 16
local RUN_SPEED = 32
local runMechanic
local profileClass
------------------------
--< Local Functions >--
------------------------
local function ShiftRun(player, eventName, speed)
if eventName ~= "RunMechanic" or typeof(speed) ~= "number" or speed > RUN_SPEED or speed < 0 then
return
end
player:SetAttribute("RunSpeed", speed)
end
----------------------
--< Main Fumctions >--
----------------------
function Player_Class.new(newPlayer)
local self = setmetatable({}, Player_Class)
self.Player = newPlayer
self.Character = newPlayer.Character or newPlayer.CharacterAdded:wait()
self.UserId = newPlayer.UserId
self.Player.CharacterAdded:Connect(function(character)
self.Character = character
end)
self:SetUpPlayer()
self:MonitorChanges()
return self
end
--< Set Players Attributes >--
function Player_Class:SetUpPlayer()
local playerInventory = playersInventoryFolder.Inventory_Template:Clone()
playerInventory.Name = tostring(self.UserId)
playerInventory.Parent = playersInventoryFolder
profileClass.new(self.Player)
end
--< Recieve Damage >--
function Player_Class:RecieveDamage(damage: number)
local humanoid = self.Character:FindFirstChildOfClass("Humanoid")
if humanoid then
humanoid:TakeDamage(damage)
else
warn("No humanoid found in player")
end
end
--< Live Connections >--
function Player_Class:MonitorChanges()
self.Player:GetAttributeChangedSignal("Weight"):Connect(function()
self:UpdateSpeed()
end)
self.Player:GetAttributeChangedSignal("RunSpeed"):Connect(function()
self:UpdateSpeed()
end)
self.Player:GetAttributeChangedSignal("Health"):Connect(function()
self:UpdateHealth()
end)
self.Player:GetAttributeChangedSignal("Banned"):Connect(function()
self:ApplyBan()
end)
end
--< Apply Ban >--
function Player_Class:ApplyBan()
local banAttribute = self.Player:GetAttribute("Banned")
if banAttribute then
self.Player:Kick("You are banned from this game")
end
end
(Other functions)...
function Player_Class.Init(modules, remotes)
profileClass = modules.Profile_Class
runMechanic = remotes.Run_Mechanic
runMechanic.OnServerEvent:Connect(ShiftRun)
game:GetService("Players").PlayerAdded:Connect(function(player)
local playerObject = Player_Class.new(player)
end)
end
return Player_Class
Profile Class: Handles data loading and saving.
local players = game:GetService("Players")
local RunService = game:GetService("RunService")
local ServerScriptService = game:GetService("ServerScriptService")
local playersInventoryFolder = ServerScriptService.Players_Inventory
local key = "MainKey"
if RunService:IsStudio() then
key = "StudioKey1"
end
local Profile_Class = {}
Profile_Class.__index = Profile_Class
local profileStoreModule
local defaultData
local profileStore
local Profiles: {[player]: typeof(playerStore:StartSessionAsync())} = {}
-----------------------
--< Local Functions >--
-----------------------
local function CheckPlayerBan(player)
local banAttribute = player:GetAttribute("Banned")
if not banAttribute then
print(player.Name, " is not banned")
else
print(player.Name, " is banned")
player:Kick("You are banned from this game")
end
end
local function GetPlayerInventoryModule(playerUserId)
local moduleName = tostring(playerUserId)
local moduleScript = playersInventoryFolder:FindFirstChild(moduleName)
if not moduleScript then
warn("Didn't found the module: " .. moduleName)
return nil
end
local success, result = pcall(require, moduleScript)
if not success then
warn("Error to required the module: " .. result)
return nil
end
return result
end
----------------------
--< Main Fumctions >--
----------------------
function Profile_Class.new(player)
local self = setmetatable({}, Profile_Class)
self.Player = player
self.UserId = player.UserId
profileStore = profileStoreModule.New(key, defaultData)
self:CreatePlayerProfile()
return self
end
--< Create Player Profile >--
function Profile_Class:CreatePlayerProfile()
print("Profile Store Key: ", key)
local profile = profileStore:StartSessionAsync(`{self.UserId}`, {
Cancel = function()
return self.Player.Parent ~= players
end,
})
if profile ~= nil then
profile:AddUserId(self.UserId)
profile:Reconcile()
profile.OnSessionEnd:Connect(function()
Profiles[self.Player] = nil
self.Player:Kick("Profile session end - Please rejoin")
end)
if self.Player.Parent == players then
Profiles[self.Player] = profile
self.Player:SetAttribute("ProfileLoaded", true)
print(`Profile loaded for {self.Player.DisplayName}!`)
self:LoadData()
else
profile:EndSession()
end
else
self.Player:Kick("Profile load fail - Please rejoin")
end
end
--< Loading Data >--
function Profile_Class:LoadData()
local profile = Profiles[self.Player]
if not profile then
return
end
if profile.Data then
local success, errorMsg = pcall(function()
-- Load Player Attributes
for name, values in pairs(profile.Data.Attributes) do
self.Player:SetAttribute(name, values)
end
-- Load Player Inventory
local playerInventoryModule = GetPlayerInventoryModule(self.UserId)
if playerInventoryModule then
playerInventoryModule:SendInventoryTable(profile.Data.Inventory)
end
CheckPlayerBan(self.Player)
print("Loaded Data: ", profile.Data)
end)
if not success then
warn("Failed to load data for " .. self.Player.DisplayName .. ": " .. errorMsg)
end
else
warn("No profile data found for "..self.Player.DisplayName)
end
end
--< Saving Data >--
function Profile_Class:SaveData(player)
local profile = Profiles[player]
if not profile or not player:GetAttribute("ProfileLoaded") then
return
end
local success, errorMsg = pcall(function()
-- Saves Player Attributes
local atributes = player:GetAttributes()
for name, values in pairs(atributes) do
if name ~= "Health" then
profile.Data.Attributes[name] = values
end
end
-- Saves Player Inventory
local playerInventoryModule = GetPlayerInventoryModule(player.UserId)
if playerInventoryModule then
profile.Data.Inventory = playerInventoryModule:GetInventoryTable()
end
end)
if success then
print("New Saved Data: ", profile.Data)
profile:EndSession()
print(player.DisplayName .. "'s data successfully saved!")
else
warn("Failed to save data for " .. player.DisplayName .. ": " .. errorMsg)
end
end
function Profile_Class.Init(modules, remotes)
profileStoreModule = modules.ProfileStore
defaultData = modules.Default_Data
players.PlayerRemoving:Connect(function(player)
if Profiles[player] then
Profile_Class:SaveData(player)
end
end)
end
return Profile_Class