Help Saving Multiple Values With Datastore in Specific Script

Hey! I have been working on a game which requires the use of checkpoints and money, and of course I would like both to save with datastore considering the players leaving and rejoining.

function OnPlayerAdded(plr)

	plr.CharacterAdded:Connect(OnCharacterAdded)

	local stats = Instance.new("Folder")
	stats.Name = "leaderstats"
	stats.Parent = plr

	local stage = Instance.new("IntValue")
	stage.Name = "Stage"
	stage.Parent = stats
	
	local money = Instance.new("IntValue")
	money.Name = "Money"
	money.Parent = stats
	money.Value = 0

The issue is that I have not been able to do so when it comes to saving the money. I have found the perfect checkpoints and I cannot save both money and stages (checkpoints) without messing them up. Right now I don’t have the money value in any datastore, well because I haven’t been able to do so, and so I want to see if anyone knows a way to modify the script for both the checkpoints to be working and the money and stages value to save to the datastore:

local DS = game:GetService("DataStoreService"):GetDataStore("Shut")
local key = "id_" .. plr.userId

	local data = DS:GetAsync(key)

	if data then
		stage.Value = data
		TeleStage.Value = stage.Value
	else
		DS:GetAsync(key, stage)
	end

end

game.Players.PlayerAdded:Connect(OnPlayerAdded)

function OnPlayerRemoved(plr)
	local key = "id_" .. plr.userId
	local data = plr.leaderstats.Stage.Value
	DS:SetAsync(key, data)
end

game.Players.PlayerRemoving:Connect(OnPlayerRemoved)


local RS = game:GetService("ReplicatedStorage")

repeat wait() until script.Main ~= nil

local main = require(script.Main)

function recived(plr, direction)
	if direction == "up" then
		main.up(plr, direction)
	elseif direction == "down" then
		main.down(plr, direction)
	elseif plr.Stages > main.TOTAL_STAGES_IN_GAME then
		print("Player is in an older version of the game!")
	end
end

RS.StageTransfer.OnServerEvent:Connect(recived)

I have been trying to figure out a way to make it so that this can all work for about 3-4 months now :open_mouth: and I have spent countless hours watching tutorials and even trying to do it on my own (which didn’t work so well because I’m a beginner)

If anyone is willing to help, and would need more of the script or anything else let me know.

1 Like
  • Hi, I advise saving values inside tables.

I decided to recode this portion of the original code:

local Players = game:GetService("Players");
local DataStorage = game:GetService("DataStoreService");

-- [[ Your data key, for DataStoreService;
local DataKey = DataStorage:GetDataStore("leaderstats");
local DefaultValues = {
	["Stage"] = 0;
	["Money"] = 0;
};

-- [[ Saves the data to a player, using tables.
function Save(ObjPlayer: Player)
	local PlayerLeaderstats = ObjPlayer:FindFirstChild("leaderstats");

	local Data = {
		["Stage"] = PlayerLeaderstats:FindFirstChild("Stage").Value;
		["Money"] = PlayerLeaderstats:FindFirstChild("Money").Value;
	};

	local _, na = pcall(function()
		DataKey:SetAsync(ObjPlayer.UserId, Data);
	end);

	if na then
		warn(na, ObjPlayer); -- This will run if an error occurs whilst loading or saving data.
	end
end;

-- [[ Unpacks the data saved to a player.
function Load(ObjPlayer: Player)
	local Data;
	local _, na = pcall(function()
		Data = DataKey:GetAsync(ObjPlayer.UserId);
	end);

	if Data == nil and type(Data) ~= "table" then
		Data = DefaultValues; -- Adds the default values, to new players that have not been added to the database.
	end; print(Data)

	local stats = Instance.new("Folder");
	stats.Name = "leaderstats";
	stats.Parent = ObjPlayer;

	local Stage = Instance.new("IntValue", stats);
	Stage.Name = "Stage";
	Stage.Value = Data[Stage.Name];

	local Money = Instance.new("IntValue", stats);
	Money.Name = "Money";
	Money.Value = Data[Money.Name];

	if na then
		warn(na, ObjPlayer); -- This will run if an error occurs whilst loading or saving data.
	end
end;

Players.PlayerAdded:Connect(Load)
Players.PlayerRemoving:Connect(Save)
  • Also, make sure you have API enabled for data storage in the first place.

The datastore works great, but how would I make it work with the checkpoints, when I tried to replace the old datastore with the new one, it doesn’t work I think I might be doing something wrong

It basically spawns me off the the checkpoints but with the stage value saved as 3

I put this part at the end of the script and I think I did this wrong because might need to add something there where it allows me to spawn on the checkpoints.

for _, v in pairs(workspace.Checkpoints:GetChildren()) do
	v.Touched:Connect(function(hit)
		if v ~= nil then
			if v.Parent == workspace.Checkpoints then
				local player = game:GetService("Players"):GetPlayerFromCharacter(hit.Parent)
				if player then
					if player.leaderstats.Stage.Value == tonumber(v.Name) - 1 then
						player.leaderstats.Stage.Value += 1
						player.TeleportedStage.Value = player.leaderstats.Stage.Value
					end
				end
			end
		end
	end)
end

So you want to save checkpoints?

  • And spawn accordingly to the stage value?
  • If so, this can be achieved by making your own spawning system. And spawn the player according to their stage number.

  • First, a folder with stages [spawn-points]

  • Make sure you name them accordingly to their value, if the player’s value exceeds the number of stages it will always go for the highest one.

  • Second, create a module inside the script that I gave you last time.

  • Make sure, it is only a one-way function and not a table function. [Not really important]

  • Third, make a CharacterAdded connection to the player & change the load function accordingly to this:

function Load(ObjPlayer: Player)
	ObjPlayer.CharacterAdded:Connect(function(ObjModel: Model)
		require(script.SpawnModule)(ObjPlayer, ObjModel);
	end);
	
	local Data;
	local _, na = pcall(function()
		Data = DataKey:GetAsync(ObjPlayer.UserId);
	end);

	if Data == nil and type(Data) ~= "table" then
		Data = DefaultValues; -- Adds the default values, to new players that have not been added to the database.
	end; print(Data)

	local stats = Instance.new("Folder");
	stats.Name = "leaderstats";
	stats.Parent = ObjPlayer;

	local Stage = Instance.new("IntValue", stats);
	Stage.Name = "Stage";
	Stage.Value = Data[Stage.Name];

	local Money = Instance.new("IntValue", stats);
	Money.Name = "Money";
	Money.Value = Data[Money.Name];

	if na then
		warn(na, ObjPlayer); -- This will run if an error occurs whilst loading or saving data.
	end
end;
  • Now, inside the spawning module there you will be able to set it to do whatever you so see fit.
    For example, how I would do it is:
local Stages = workspace.Stages:GetChildren();
local function GetStage(StageNumber: number): Instance
	local ToStage, HighestNumber = nil, 0;
	
	for _, Inst in pairs(Stages) do if tonumber(Inst.Name) > HighestNumber then
			HighestNumber = tonumber(Inst.Name);
		end; -- Get the highest number, in table.
		
		if tonumber(Inst.Name) == StageNumber then
			ToStage = Inst;
		end; -- Checks if a stage number matches the players stage number.
	end
	
	local Result;
	if not ToStage and StageNumber > HighestNumber then
		Result = Stages[HighestNumber];
	elseif ToStage then
		Result = ToStage;
	end
	
	return Result;
end;

return function(ObjPlayer: Player, ObjModel: Model)
	local LeaderStats = ObjPlayer:WaitForChild("leaderstats", 1);
	local StageNumber = LeaderStats:FindFirstChild("Stage").Value; -- Yes, I could've added another parameter with the players stats with.
	
	local GetStage = GetStage(StageNumber);
	local _, e1 = pcall(function()
		task.spawn(function()
			task.wait(1);
			ObjModel:SetPrimaryPartCFrame(GetStage.CFrame * CFrame.new(Vector3.new(0, 5, 0)));
		end);
	end);
	
	if e1 then
		warn(ObjPlayer, e1);
	end
end;
2 Likes

Thanks so much, It all works great :smiley:
Everything saves and the checkpoints work