How to data-store multiple values easily?

I’m making a game in which the “playable characters are cards” is the easiest way to explain it. I need ways to save the cards and their respective properties like this:

CARD B
Power: 1
Owned: false
Dodge: 3
Speed: 2

So yeah, the only way i can think of how to do it would take hundreds of lines and wouldn’t be a very good method, if anyone could help me out i’d appreciate it, thanks!

Also please don’t suggest ProfileService, i can’t use it.

1 Like

You could represent each card using tables:

data = {
--//example of how it might look
cards = {

{
power = 1;
owned = false;
dodge = 3;
speed = 2;
};

}
}

However if they don’t own the card at all, you shouldn’t save it. You should only save items the player should actually need access to. Otherwise, it could go in a module.

The current system i’m using makes use of HttpService, but it’s very glitchy and not very good so i’m looking for something else i can switch it.

The cards are arranged like this:

{
	cardName = "Card1",
	Owned = false,
	Rarity = "Common",
	Level = 1,
	Strength = 2,
	Resistence = 2,
},

I still would like all the values to be there as i need them as control values for the base level, because they can be upgraded.

Still, my issue is more on the saving and datastore part, do you have any clue?

I was the one the recommended ProfileService since it is really useful. I will show you how to use it:

First download the module and put into ServerStorage or as a child of the data script.

Next make the data I would create a table called data

local Data = {
   Cards = {},
   Money = 0,
   -- Other Values 
}

That is really all you need for the data.

Now for the main script. This is what it would look like:

--// Services
local Players = game:GetService("Players")

--// Profile
local ProfileService = require(Where.You.Saved.The.Module)

-- For this you want a key and the data template. The key were using is "PlayerData" and the Data will be the table we made 
local ProfileStore = ProfileService.GetProfileStore("PlayerData", Data)

local Profiles = {} -- This will the store the players session data

local function PlayerAdded(player) -- This function will run when player gets added
   local profile = ProfileStore:LoadProfileAsync("Player_" .. player.UserId) -- Get's the players data
   if profile then -- If profile exist 
        profile:AddUserId(player.UserId) -- GDPR compliance
        profile:Reconcile() -- This is optional but it will fill in the missing variables. If you update data when players already have data it will update their data to fill in the gaps
        profile:ListenToRelease(function() -- This is needed due to the way profileService works
            Profiles[player] = nil
            -- The profile could've been loaded on another Roblox server:
            player:Kick()
        end)
        if player:IsDescendantOf(Players) then
            Profiles[player] = profile
            -- A profile has been successfully loaded:
            -- Their Data has loaded and now you can do what you want with it
        else
            -- Player left before the profile loaded:
            profile:Release()
        end
   else
      player:Kick("Couldn't load profile")
   end
end

-- In case Players have joined the server earlier than this script ran:
for _, player in ipairs(Players:GetPlayers()) do
    task.spawn(PlayerAdded, player)
end

----- Connections -----

Players.PlayerAdded:Connect(PlayerAdded)

Players.PlayerRemoving:Connect(function(player)
    local profile = Profiles[player]
    if profile then
        profile:Release()
    end
end)

As you can see this is how the basic script would look like, once you understand it, it will be a lot easier to add stuff to it. Now the best part about this is you can really use this in any game simply by copy and pasting since everything is set the only thing you will need to update is the data.

Now that we have that we want to actually save the cards. For this we will have a function.

local function AddCardToData(player: Player)
   local Profile = Profiles[player]
    
   local CardToAdd = {
       Name = "Card Name Here",
       Rarity = "Common",
       Strength = 2,
       Exp = 100, -- If you have EXP there's no need for level really
       Resistence = 2,
       -- There is no need for the owned variable since if it's in their data they already own it
   }
   
   -- Add it to the players Cards data 
   table.insert(Profile.Data.Cards, CardToAdd)
end

As you can see that is a function that will add the cards to the player data and you can already see some issues. Whenever this function is called it will always add the same card. So for this we can add ... to the function and for this you will have to send the variables in order so, Name, Rarity, Strength, Exp, Resistence

local function AddCardToData(player: Player, ...)
   local Profile = Profiles[player]
   local Args = {...} -- This will look something like this:
   -- Args = {[1] = "Card Name", [2] = "Rare", [3] = 5, etc}
       
   local CardToAdd = {
       Name = Args[1],
       Rarity = Args[2],
       Strength = Args[3],
       Exp = Args[4], -- If you have EXP there's no need for level really
       Resistence = Args[5],
       -- There is no need for the owned variable since if it's in their data they already own it
   }
   
   -- Add it to the players Cards data 
   table.insert(Profile.Data.Cards, CardToAdd)
end

-- Now whenever we want to add a card to the players table all we need to is
AddCardToData(player, "Card 2", "Common", 3, 500, 6)
-- Name: Card 2
-- Rarity: Common
-- Strength : 3
-- Exp : 500
-- Resistence : 6

Quite simple, sorry I couldn’t explain it with normal datastore since I haven’t used it in a while but let me know what you think. This is the whole script and you can simple just copy and paste it and it should work as long as you put the module in place. I haven’t tested it.

TL:DR: This is the whole script using profileservice. Hope this helps.

local Data = {
   Cards = {},
   Money = 0,
   -- Other Values 
}

--// Services
local Players = game:GetService("Players")

--// Profile
local ProfileService = require(Where.You.Saved.The.Module)

-- For this you want a key and the data template. The key were using is "PlayerData" and the Data will be the table we made 
local ProfileStore = ProfileService.GetProfileStore("PlayerData", Data)

local Profiles = {} -- This will the store the players session data

local function PlayerAdded(player) -- This function will run when player gets added
   local profile = ProfileStore:LoadProfileAsync("Player_" .. player.UserId) -- Get's the players data
   if profile then -- If profile exist 
        profile:AddUserId(player.UserId) -- GDPR compliance
        profile:Reconcile() -- This is optional but it will fill in the missing variables. If you update data when players already have data it will update their data to fill in the gaps
        profile:ListenToRelease(function() -- This is needed due to the way profileService works
            Profiles[player] = nil
            -- The profile could've been loaded on another Roblox server:
            player:Kick()
        end)
        if player:IsDescendantOf(Players) then
            Profiles[player] = profile
            -- A profile has been successfully loaded:
            -- Their Data has loaded and now you can do what you want with it
        else
            -- Player left before the profile loaded:
            profile:Release()
        end
   else
      player:Kick("Couldn't load profile")
   end
end

local function AddCardToData(player: Player, ...)
   local Profile = Profiles[player]
   local Args = {...} -- This will look something like this:
   -- Args = {[1] = "Card Name", [2] = "Rare", [3] = 5, etc}
       
   local CardToAdd = {
       Name = Args[1],
       Rarity = Args[2],
       Strength = Args[3],
       Exp = Args[4], -- If you have EXP there's no need for level really
       Resistence = Args[5],
       -- There is no need for the owned variable since if it's in their data they already own it
   }
   
   -- Add it to the players Cards data 
   table.insert(Profile.Data.Cards, CardToAdd)
end

-- In case Players have joined the server earlier than this script ran:
for _, player in ipairs(Players:GetPlayers()) do
    task.spawn(PlayerAdded, player)
end

----- Connections -----

Players.PlayerAdded:Connect(PlayerAdded)

Players.PlayerRemoving:Connect(function(player)
    local profile = Profiles[player]
    if profile then
        profile:Release()
    end
end)

I really hope this helps it did take some time to write this so hopefully, it is useful. Let me know if you need any help and also try not to make multiple posts on the same subject instead just use one until you find a solution. Have a good day!

2 Likes

Yes, that helps, though i still have some questions.

  1. What does GDPR mean in “GDPR compliance”? (Just curious)
  2. Is the main script a normal script or a module script? (It’s a module, right? And also shouldn’t the local function be just funcion?)
  3. Does it work cross-places? (Like different places inside a same game)
  4. How can i check the player’s “inventory”? (See the player’s Profile)

That was mainly it, if i have further questions may i contact you through the devforums?

PS: After reading it a couple times i’m starting to understand it better, thanks a lot for the help.

As soon as you’re able to respond to those questions please get it back to me, thanks!

GDPR means General Data Protection Regulation. Learn more about it here.

So what I usually do is have one Main script which handles players leaving and joining and then create a module as a child of the main script so instead of PlayerAdded I have CreateProfile and RemoveProfile. In the example I gave it can be done in a normal script but if you do use it in a module I would edit it a bit since for me personally, I like to have one PlayedAdded and PlayerRemoving in one script so it’s easier to manage. I would use local since the function is a local function but you can use function if you want.

This script specifically doesn’t work with cross-places since I never added that functionality to the script but looking at the API you would use :ListenToHopReady() once you read the API it will make more sense on how to use it.

So to check a players inventory first make sure to have an inventory table in their data and once the players profile has loaded you can do print(profile.Data.Inventory) or to simply view all data you can do print(profile.Data). So whenever a player joins I would just have a function which creates a card and gives it to them for example something similar to the AddCardToData function.

local function CreateCardsForPlayer(player)
    local Profile = Profiles[player] -- Get their profile
    local CardData = Profile.Data.Cards -- Get their card data
    if #CardData == 0 then return end -- They have nothing so no point carrying on
    for Index, Value in ipairs(CardData) do
       local Card = -- is it a tool? If so you can clone a template of it  

       -- Assigning it's values
       Card.Name = Value.Name
       Card:SetAttribute("Rarity", Value.Rarity) -- Using attributes here
       Card:SetAttribute("Strength", Value.Strength)
       Card:SetAttribute("Exp", Value.Exp)
       -- You can also add your level here just add a way to get the level using exp
       Card:SetAttribute("Resistence", Value.Resistence)

       Card.Parent = player.BackPack
    end
end

As you can see in this function it’s very simple, all it’s doing is looping through the cards table in their data and just giving them a card with those attributes. Hope this helps and gives you a better understanding of how to use it. If you have any more questions feel free to ask!

1 Like

If the main script is a normal script, how would i “inherit” it’s functions outside of it? That’s why i though it was a module.

In the API documentation it says "In many cases ProfileService will be fast enough when loading and releasing profiles as the player teleports between places belonging to the same universe / game.", doesn’t that mean it already works cross-places if i use the same ‘data key’?

So if this is a normal script you would use either Bindable Events (server-to-sever) or Remote Events (client-to-server)

Yeah I believe so, you can do some testing to see how it goes it, if you need to add the function for teleport you can add it, but if not then it should be fine.

1 Like

Great, thanks a lot for all the help!

Just something i didn’t quite understand, how do i get the player’s profile?

You said to do print(profile.Data.Inventory) but how do i get the profile itself on a script different than the main one?

You could have a function that returns the profile something like this:

local function GetProfile(player)
   if Profiles[player] then
      return Profiles[player].Data
    end
end
1 Like

This is well Variable.

For Ease what you could do is just store them in a table. BUT the table is Gonna Take time to Fill. Instead i will suggest you how i do it.

If you are good with plugin making then you can do this or i will provide an alternative.

Plugin Route: What you do is Take a Basic Instance Like let’s say a IntValue.but what we will be doing is Using OOP with it.this is useful for workflow.

We take the int value and we will just add attributes to it in Form of Properties. Next we create a module script. If you are familiar with OOP then we will be creating new methods for that IntValue.this is Helpful as we will have a Physical Instance instead of a Tabular one. Now we will make a Method named Init(). This will first add all the properties and Add a Tag on it using CS. Now you can call :GetTagged() to get all the Cards in a Table! Next thing will be making a function called ReturnProperties()

It will return all attributes and make a dictionary of it. Then the only thing you have to do is compile those tables using for loop and Save it DataStores.

Now the plugin thing is optional but it can help to create cards more efficiently.you just code a bunch of buttons that set properties to your desire and then it creates a Class for you.

I will provide code later as I’m a bit low on Battery :stuck_out_tongue:

1 Like

The script identifies ‘Profiles’ as an unknown variable.

image

This is has to be in the same script as your data and you will have to use a remote event or bindable to get the profile.

1 Like