Devlog Help 2: Showing String Values

WELCOME TO DEVLOG 2 OF ME CREATING DEVELOPER DREAMSCAPE!

MY ISSUE:

The script below saves the data in the leaderstats:

local DataStoreService = game:GetService("DataStoreService")
local HttpService = game:GetService("HttpService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local runService = game:GetService("RunService")

local DataStore = DataStoreService:GetDataStore("UnofficialDataStorage")

local leaderstatValues = {
	"ForHire",
	"Hiring",
	"Posting",
	"Public"
}

for i, valueName in leaderstatValues do
	local remoteEvent = Instance.new("RemoteEvent")
	remoteEvent.Name = valueName
	remoteEvent.Parent = ReplicatedStorage

	remoteEvent.OnServerEvent:Connect(function(player, value)
		if not value or type(value) ~= "boolean" then
			return
		end

		player.leaderstats[valueName].Value = value and "Yes" or "No"
	end)
end

-- Function to load player data
local function loadPlayerData(player)
	local key = player.UserId

	local success, errorMessage = pcall(function()
		return DataStore:GetAsync(key)
	end)

	if success then --no errors
		print("Player data loaded successfully for", player.Name)
		return errorMessage -- error message only if failed, return value if it didn't
	elseif not success and errorMessage then
		warn("Failed to load data for player " .. player.Name .. ": " .. errorMessage)
	end
end
-- Function to save player data
local function savePlayerData(player, data)
	local key = player.UserId

	local success, errorMessage = pcall(function()
		DataStore:SetAsync(key, data)
	end)

	if success then
		print("Player data saved successfully for", player.Name)
	else
		warn("Failed to save data for player " .. player.Name .. ": " .. errorMessage)
	end
end

-- Create leaderstats and set initial values
local function setupLeaderstats(player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"

	for i, valueName in leaderstatValues do
		local stringValue = Instance.new("StringValue")
		stringValue.Name = valueName
		stringValue.Value = "No"
		stringValue.Parent = leaderstats
	end

	leaderstats.Parent = player
end

-- Connect the function to player added event
Players.PlayerAdded:Connect(function(player)
	local playerData = loadPlayerData(player)

	setupLeaderstats(player)

	if playerData then
		player:WaitForChild("leaderstats").ForHire.Value = playerData.ForHire;
		player:WaitForChild("leaderstats").Hiring.Value = playerData.Hiring;
		player:WaitForChild("leaderstats").Posting.Value = playerData.Posting;
		player:WaitForChild("leaderstats").Public.Value = playerData.Public;
	end
end)

Players.PlayerRemoving:Connect(function(player)
	local playerData = {
		ForHire = player:WaitForChild("leaderstats").ForHire.Value,
		Hiring = player:WaitForChild("leaderstats").Hiring.Value,
		Posting = player:WaitForChild("leaderstats").Posting.Value,
		Public = player:WaitForChild("leaderstats").Public.Value
	}

	savePlayerData(player, playerData)
end)

game:BindToClose(function()
	if runService:IsStudio() then task.wait(3) return end

	for _, player in Players:GetPlayers() do
		local playerData = {
			ForHire = player:WaitForChild("leaderstats").ForHire.Value,
			Hiring = player:WaitForChild("leaderstats").Hiring.Value,
			Posting = player:WaitForChild("leaderstats").Posting.Value,
			Public = player:WaitForChild("leaderstats").Public.Value
		}

		savePlayerData(player, playerData)
	end

	task.wait(3)
	print("Server shutting down. Player data saved.")
end)

And all of this works correctly. But next is with these pictures…


Capture



My Issue is that when the save script is fired, it saves solely in the leaderstats. And not in these values.

Here are my other scripts:
No ... - Updates First Photo And Second Photo Values
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RemoteEvent = ReplicatedStorage:WaitForChild("ForHire")

script.Parent.MouseButton1Click:Connect(function()
	script.Parent.TextColor3 = Color3.fromRGB(255,67,67)
	script.Parent.Parent.YesButton.TextColor3 = Color3.fromRGB(173, 173, 173)
	script.Parent.Parent.Parent.Parent.Parent.Stats.ForHire.ForHireLabel.Text = "NO"
	script.Parent.Parent.Parent.Parent.Parent.Stats.ForHire.ForHireLabel.TextColor3 = Color3.fromRGB(255,67,67)
	RemoteEvent:FireServer(false)
end)
Yes ... - Updates First Photo And Second Photo Values
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RemoteEvent = ReplicatedStorage:WaitForChild("ForHire")

script.Parent.MouseButton1Click:Connect(function()
	script.Parent.TextColor3 = Color3.fromRGB(85, 255, 127)
	script.Parent.Parent.NoButton.TextColor3 = Color3.fromRGB(173, 173, 173)
	script.Parent.Parent.Parent.Parent.Parent.Stats.ForHire.ForHireLabel.Text = "YES"
	script.Parent.Parent.Parent.Parent.Parent.Stats.ForHire.ForHireLabel.TextColor3 = Color3.fromRGB(85,255,127)
	RemoteEvent:FireServer(true)
end)

WHAT I WANT TO HAPPEN:

I want the leaderstats AND all of these values to save when the player joins, leaves, and rejoins.


VIDEO:

Devlog Help. - YouTube


DEVLOG HISTORY:

  1. Devlog Help 1: Saving String Values - Look At Post.

SOLUTIONS LEADERBOARD:

1st: @FlipDip32 - 1 Post(s) Solved - Solved Post(s) 1

2nd TO BE DETERMINED

3rd TO BE DETERMINED

Created: 2 . 04. 2024 | 4:44 P.M. EST (:us:)

1 Like

What prints to the console when you change the values and then rejoin? I see print and warn statements, which is nice, but no output.

1 Like

When I join… This prints:

1 Like

Tell me when you have something.

1 Like

do you have the code for the OnServerEvent function for the ForHire remote event?
i’m not sure if i can see it in the post, but the problem should be inside of that function.

1 Like

Yeah, here

remoteEvent.OnServerEvent:Connect(function(player, value)
		if not value or type(value) ~= "boolean" then
			return
		end

I think you just need to add the code into here.

1 Like
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RemoteEvent = ReplicatedStorage:WaitForChild("ChangeValue") --new remote event
local key = script.Parent.Name -- change this to the value's name in leaderstats.
local bool = true -- set this to the value the button will change the stat to. for example, TRUE if "yes".

script.Parent.MouseButton1Click:Connect(function()
	script.Parent.TextColor3 = Color3.fromRGB(85, 255, 127)
	script.Parent.Parent.NoButton.TextColor3 = Color3.fromRGB(173, 173, 173)
	script.Parent.Parent.Parent.Parent.Parent.Stats.ForHire.ForHireLabel.Text = "YES"
	script.Parent.Parent.Parent.Parent.Parent.Stats.ForHire.ForHireLabel.TextColor3 = Color3.fromRGB(85,255,127)
	RemoteEvent:FireServer(key, bool)
end)

for the buttons, include this code within them and change the “bool” variable to what their text is, for example true if the text is “Yes”.
also, make sure to change the button’s name to what key they’re supposed to be for, for example if there was a key called “ForHire” then change the button’s name to “ForHire”.

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RemoteEvent = ReplicatedStorage:WaitForChild("ChangeValue")

RemoteEvent.OnServerEvent:Connect(function(player, key, value)
	if player:FindFirstChild("leaderstats") then
		if player.leaderstats:FindFirstChild(key) then
			player.leaderstats:FindFirstChild(key).Value = value
			return
		end
	end
end)

here’s the serversided part.
this will just receive the player, key and value and save the player’s data based on what the stat is and what the value it’s being changed to.

hopefully this works for you!

(also, add the “ChangeValue” remote event into replicatedstorage if it makes an error)

1 Like

Alright well, can you do it without changing the names of the remote events please?

1 Like

It’d be called the names of each of the values under leaderstats (e.g. ForHire, Posting)

1 Like

If not possible can you tell and/or show me where I should put these?

1 Like

(I’m on mobile now so sorry if I make any mistakes)
Sure, it’s possible to use multiple remote events if you wish.
For the server, duplicate the function for each remote event. This will make the function work for all of them.
For the buttons in the client, change the remote event to what value they’re changing and make sure to add the bool variable and the variable for what stat it is (for the remote event being fired).

1 Like

However it’s also better to just use a remote event called “ChangeValue” which will just search in the player’s leaderstats folder for the stat.
You just need to add one function which will be responsible for handling the remote event on the server side (which will change the stat value) and a script (including the clicked function) in each of the buttons which will fire the “ChangeValue” remote event with the stat that the button is changing and the value that its setting to.

I added a single warn statement under Players.PlayerAdded. Could you run this code with the output open and post the result of the output here? Provide a video if you would like.

Update: I lied. I ran the code myself, as it was pretty self-contained. Kudos to that.

This is a picture of my output after I rejoined, and it saved perfectly fine. Can you check to make sure “Player data saved successfully for {player.Name}” is printing?

Edit: Try kicking yourself from the command-line to get the data to save. For some reason, ending the play testing session in studio is not enough to trigger it.

game.Players.OfficialPlatinumMan:Kick("Test")

I might not understand the required goal, so my best assumption is that the User Interface does not correctly match the Leaderstats.

I’ll be modifying the Yes script for now. First, you can place the button formatting system into its own function.

local formatButton(value)
	if not value then return end -- Not for hire
	script.Parent.TextColor3 = Color3.fromRGB(85, 255, 127)
	script.Parent.Parent.NoButton.TextColor3 = Color3.fromRGB(173, 173, 173)
	script.Parent.Parent.Parent.Parent.Parent.Stats.ForHire.ForHireLabel.Text = "YES"
	script.Parent.Parent.Parent.Parent.Parent.Stats.ForHire.ForHireLabel.TextColor3 = Color3.fromRGB(85,255,127)
end

Then, we could wait for the leaderstats folder to appear using :WaitForChild

local Players = game:GetService("Players")
local Player = Players.LocalPLayer
local Leaderstats = Player:WaitForChild("Leaderstats")

Now, we can add :GetPropertyChangedSignal to detect if the ForHire’s Value property in the leaderstats folder changed. And we execute the formatButton function with the current value, but only once. This event is here in case the value is edited after the Localscript starts executing.

local ForHireStat = leaderstats:FindFirstChild("ForHire")

ForHireStat:GetPropertyChangedSignal("Value"):Once(function()
	formatButton(ForHireStat.Value)
end

When you click the Yes button, it should also have a formatButton function.

Now, there are certain cases where the data is set before the Localscript executes. So we add a formatButton function by itself.

local Players = game:GetService("Players")
local Player = Players.LocalPLayer
local Leaderstats = Player:WaitForChild("Leaderstats")

local ForHireStat = leaderstats:FindFirstChild("ForHire")

formatButton(ForHireStat.Value) -- Just in case the ForHire value is set already

Alright so lemme show you:

What I want to happen is with these values:



When YES is clicked, the NO Button because gray and YES becomes green and vice versa.

But when I leave and rejoin, the LEADERSTATS save, But I want these values to save. Like if the value in the leaderstats for “Do you want others to hire you”(ForHire in leaderstats) equals to YES, I want the values to corelate to that. You can use my scripts above.

And I also want these to save aswell.
Capture

IF YOU NEED ANY ADDITIONAL INFORMATION, ASK ME!

If anybody has any suggestions, i’d love to hear it.

We can use the 4 values that have been saved to match it.

Assuming that your hirearchy contains all 4 questions in an object in the first picture, and another object that contains the 4 stats in the second picture, we can loop through each one as they are almost the same instead of creating a Localscript for each.

for _, question in pairs(questionsFrame:GetChildren()) do
	
end

Unless each question object has a name separate to the RemoteEvent, we can use Attributes to define where the RemoteEvent should go to.

for _, question in pairs(questionsFrame:GetChildren()) do
	local RemoteEvent = ReplicatedStorage:WaitForChild(question:GetAttribute("FireEvent"))
end

This is a fine solution. However, exploiters are able to easily change the attributes and trick the script into firing the wrong event. So we can make each event Hard-coded, which means that events will be inside the script. The key will be the name of the question, and the value will be the RemoteEvent.

local Events = {
	["question1"] = event1,
	["question2"] = event2,
	["question3"] = event3,
	["question4"] = event4
}

Then, we can set the RemoteEvent value to the event.

Now, for each question, we can add a click event for the Yes and No buttons.

for _, question in pairs(questionsFrame:GetChildren()) do
	local RemoteEvent = Events[question.Name]
	if not RemoteEvent then -- Event does not exist
		warn(`Event for {question.Name} does not exist.`)
		continue
	end

	question.YesButton.MouseButton1Click:Connect(function()
		RemoteEvent:FireServer(true)
	end)

	question.NoButton.MouseButton1Click:Connect(function()
		RemoteEvent:FireServer(false)
	end)
end

Now, we need to add a function that formats both the Yes and No buttons, and the result.

local function formatQuestion(question, statsItem, value)
	local yesColor = Color3.fromRGB(255, 67, 67)
	local noColor = Color3.fromRGB(173, 173, 173)
	local greyColor = Color3.fromRGB(173, 173, 173)

	question.YesButton.TextColor3 = value and yesColor or greyColor
	question.NoButton.TextColor3 = value and noColor or greyColor

	statsItem.Label.Text = value and "YES" or "NO"
	statsItem.Label.TextColor3 = value and yesColor or noColor
end

Then, we can place that function in the yes and no buttons.

for _, question in pairs(questionsFrame:GetChildren()) do
	local RemoteEvent = Events[question.Name]
	if not RemoteEvent then
		warn(`Event for {question.Name} does not exist.`)
		continue
	end

	local statText = Stats:FindFirstChild(question.Name)

	question.YesButton.MouseButton1Click:Connect(function()
		formatQuestion(question, statText, true)
		RemoteEvent:FireServer(true)
	end)

	question.NoButton.MouseButton1Click:Connect(function()
		formatQuestion(question, statText, false)
		RemoteEvent:FireServer(false)
	end)
end

So, the first and second pictures should be exactly the same. But now for showing the Yes and No values whenever the player joins.

We can first wait for the player’s leaderstats to appear.

local Players = game:GetService("Players")
local Player = Players.LocalPlayer

local Leaderstats = Player:WaitForChild("Leaderstats")

Then, by looping through each question, get the value of the leaderstats, then run the formatQuestion function with the value being said.

for _, question in pairs(questionsFrame:GetChildren()) do
	local RemoteEvent = Events[question.Name]
	if not RemoteEvent then
		warn(`Event for {question.Name} does not exist.`)
		continue
	end

	local stat = Leaderstats:FindFirstChild(question.Name)
	if not stat then -- Stat does not exist
		warn(`Stat for {question.Name} does not exist.`)
		continue
	end

	local statText = Stats:FindFirstChild(question.Name)

	formatQuestion(question, statText, stat.Value == "Yes")
	...

Now, there can be a scenario where that Localscript happens faster than the leaderstats values being changed. Therefore it completely misses the value, defaulting it to No. To fix this, we can include a :GetPropertyChangedSignal to detect if the value was changed. But only once.

for _, question in pairs(questionsFrame:GetChildren()) do
	local RemoteEvent = Events[question.Name]
	if not RemoteEvent then
		warn(`Event for {question.Name} does not exist.`)
		continue
	end

	local stat = Leaderstats:FindFirstChild(question.Name)
	if not stat then
		warn(`Stat for {question.Name} does not exist.`)
		continue
	end

	local statText = Stats:FindFirstChild(question.Name)

	formatQuestion(question, statText, stat.Value == "Yes")
	stat:GetPropertyChangedSignal("Value"):Once(function()
		formatQuestion(question, statText, stat.Value == "Yes")
	end)
	...

Full code:

local Players = game:GetService("Players")
local Player = Players.LocalPlayer

local Leaderstats = Player:WaitForChild("Leaderstats")

local Stats = nil -- Replace this
local questionFrame = nil -- Replace this as well

local Events = {
	["question1"] = event1,
	["question2"] = event2,
	["question3"] = event3,
	["question4"] = event4
}

local function formatQuestion(question, statsItem, value)
	local yesColor = Color3.fromRGB(255, 67, 67)
	local noColor = Color3.fromRGB(173, 173, 173)
	local greyColor = Color3.fromRGB(173, 173, 173)

	question.YesButton.TextColor3 = value and yesColor or greyColor
	question.NoButton.TextColor3 = value and noColor or greyColor

	statsItem.Label.Text = value and "YES" or "NO"
	statsItem.Label.TextColor3 = value and yesColor or noColor
end

for _, question in pairs(questionsFrame:GetChildren()) do
	if not question:IsA("Frame") then continue end -- If the questions use a different object, replace this

	local RemoteEvent = Events[question.Name]
	if not RemoteEvent then
		warn(`Event for {question.Name} does not exist.`)
		continue
	end

	local stat = Leaderstats:FindFirstChild(question.Name)
	if not stat then
		warn(`Stat for {question.Name} does not exist.`)
		continue
	end

	local statText = Stats:FindFirstChild(question.Name)

	formatQuestion(question, statText, stat.Value == "Yes")
	stat:GetPropertyChangedSignal("Value"):Once(function()
		formatQuestion(question, statText, stat.Value == "Yes")
	end)

	question.YesButton.MouseButton1Click:Connect(function()
		formatQuestion(question, statText, true)
		RemoteEvent:FireServer(true)
	end)

	question.NoButton.MouseButton1Click:Connect(function()
		formatQuestion(question, statText, false)
		RemoteEvent:FireServer(false)
	end)
end

where should this script go? I need to know that

It can go anywhere unless you change the two variables. But I recommend placing it under the frame that contains all 4 questions. I’m going to update the script to allow it.