How can I use the ProfileService module to store save slots?

You can write your topic however you want, but you need to answer these questions:
I’m using the ProfileService module, this is my first time using it. How could I save save slots to the profile service and each save slot takes you into a new game, I’d just like a brief summary if possible please!

I’ve created the data template but I don’t have an idea on how to actually save the saves for each save slot in the main template. I can’t find any exact solutions, some people have told me to save them as separate tables, I’m not sure how to.

this is in a modulescript that holds the data template alone

local Data = {
	FirstJoin = false,
	SaveSlots = {} --?? this is the part confusing me
}

return Data
4 Likes

This can get fairly complicated depending on what data you want to store but here’s a general rundown.

First, you should determine what information you want to to save for each slot. I’m not sure what your game is about but for this example I want to save three pieces of data:

  • Slot creation timestamp (number)
  • Slot last accessed timestamp (number)
  • Dictionary of all the placed furniture in the slot
    • If you’re doing this type of thing in a real game the structure will not look like this but for my example I’m going to use it

Given those three things I want to store, we can create a system which can create, save and load different slots and make sure all of them stay in sync with the player’s default data template. The best way of doing this is making some type of Data Handler module, similar to what I’ve created below.

-- Data Handler Module
---> services
local players = game:GetService("Players")
local serverScriptService = game:GetService("ServerScriptService")
local httpService = game:GetService("HttpService")

---> modules
local profileService = require(serverScriptService.Modules.ProfileService)

---> variables
local dataModule = {}

local dataTemplate = {
    firstJoin = false,
    saveSlots = {},
}

local profileStore = profileService.GetProfileStore("DataKey", dataTemplate)
local loadedProfiles = {}

---> functions
local function playerAdded(player: Player)
    local profile = profileStore:LoadProfileAsync("Player_" .. player.UserId) -- change to your own data key format
    if profile then
        profile:AddUserId(player.UserId)
        profile:Reconcile() -- fill in missing values from the data template

        profile:ListenToRelease(function()
            loadedProfiles[player] = nil
            player:Kick()
        end)

        if player:IsDescendantOf(players) then
            profiles[player] = profile
        else
            profile:Release()
        end
    else
        player:Kick("Failed to load your data!")
   end
end

function dataModule.getProfile(player: Player)
    if not loadedProfiles[player] then return nil end
    return loadedProfiles[player].Data -- easier access to the Data part of the profile that we actually want
end

function dataModule.getSaveSlotFromId(player: Player, id: string)
    local playerProfile = dataModule.getProfile(player)
	if not playerProfile then return nil end

     local slotToLoad
	 for _, savedSlot in playerProfile.saveSlots do
		if savedSlot.id == id then
			slotToLoad = savedSlot
			break
		end
	end

    return slotToLoad
end

function dataModule.newSlot(player: Player)
    local playerProfile = dataModule.getProfile(player)
    if not playerProfile then return end
    
    local timeNow = os.time()
    local newSlotData = {
        id = httpService:GenerateGUID(false), -- generate a unique ID for the slot so we can reference it later
        createdAt = timeNow, -- store the time this slot was created at (now!)
        lastAccessed = timeNow, -- when the slot was last accessed (now as it was just created)
        placedFurniture = {}, -- empty table for now as there is no furniture placed
    }

    -- insert the new slot into the saved slot data (this will mean it is saved to the player's actual data)
    table.insert(playerProfile.saveSlots, newSlotData)
end

function dataModule.loadSlot(player: Player, id: string)
	local playerProfile = dataModule.getProfile(player)
	if not playerProfile then return end

	-- make sure player does not already have that slot loaded
	if player:GetAttribute("loadedSlot") == id then return end

	-- check if the slot exists based on its ID
	local slotToLoad = dataModule.getSaveSlotFromId(id)
	if not slotToLoad then
		warn(`Slot with ID {id} does not exist for {player.Name}!`)
		return
	end

    -- make sure to update the last accessed time
    slotToLoad.lastAccessed = os.time()

	-- do some logic to load in the slot
	-- you would probably want to clear up the things related to the old slot and load this one in instead
	
    -- store in some way that this is the slot that is currently loaded (doesn't have to be with an attribute but in this case it is)
    player:SetAttribute("loadedSlot", id)
end

function dataModule.placeFurniture(player: Player, furnitureName: string)
    -- this is an unrealistic example because you probably wouldn't handle 
    -- placement of furniture in the data script but for this example it's okay

    local playerProfile = dataModule.getProfile(player)
	if not playerProfile then return end

	-- get the current save slot
	local currentSlotId = player:GetAttribute("loadedSlot")
	if not currentSlotId then return end

	local slotTable = dataModule.getSaveSlotFromId(currentSlotId)
	if not slotTable then return end

	-- increment placed furniture in the data table
	if slotTable.placedFurniture[furnitureName] then
		slotTable.placedFurniture[furnitureName] += 1
	else
		slotTable.placedFurniture[furnitureName] = 1
	end

	-- here you'd probably do some stuff to actually place the item
end

---> main
players.PlayerAdded:Connect(playerAdded)

players.PlayerRemoving:Connect(function(player: Player)
    local profile = loadedProfiles[player]
    if profile then
        profile:Release()
    end
end)

return dataModule

You’ll notice with the code above that any updates that we make to any slot are automatically saved to the profile and we don’t have to do anything - only update the profile we have. We don’t have a dedicated save function; only a function that creates a slot and one that will edit some of the data in it. The only thing we have to do is store some generic data when the player creates a new slot and be able to interpret the data that is saved when we come to load a slot.

The module above is very simple and can be easily edited/expanded to fit other needs. It’s probably in your interest to prevent people from creating too many slots, etc, and you could add some checks in the relevant functions to fix this. It also probably isn’t already your design to have attributes to store the currently loaded slot, but you can adapt to whatever you already have.

2 Likes

This is helpful, thank you. I’m guessing the module is designed to be used in local scripts and server scripts? And could I ask the difference between the getSaveSlotFromId and getProfile functions?

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.