There is, however, one more problem;
I basically have a few quest values in the ReplicatedStorage that look like this:
The way that they work is that they get added to the player folder called ‘Quests’ (this is also the case for a few other values and folders), afterwards, the ‘objective value’ is manipulated while the player aims to complete the quest.
@sniper74will suggested that I should save player data by cloning tools, quests, codex entries, manuals ect. into the player’s folders when they join the game. However, this wouldn’t work with the quest values, as the objective would be ‘reset’ when the player joins a game.
Could someone please help me? I’m still new to datastores, so I hardly have a clue on how to approach this.
Any help would be greatly appreciated
This is what sniper74will’s suggested script looks like:
local dss = game:GetService("DataStoreService")
local toolsDS = dss:GetDataStore("WeaponsData")
local toolsFolder = game.ServerStorage.ToolsFolder
game.Players.PlayerAdded:Connect(function(plr)
local savedTools = toolsDS:GetAsync(plr.UserId) or {}
for i, savedTool in pairs(savedTools) do
if toolsFolder:FindFirstChild(savedTool) then
toolsFolder[savedTool]:Clone().Parent = plr:WaitForChild("OwnedWeapons")
end
end
end)
game.Players.PlayerRemoving:Connect(function(plr)
local savedTools = {}
for i, savedTool in pairs(plr.OwnedWeapons:GetChildren()) do
table.insert(savedTools, savedTool.Name)
end
local success, errormsg = pcall(function()
toolsDS:SetAsync(plr.UserId, savedTools)
end)
if errormsg then warn(errormsg) end
end)
I’m not a master of LUA, but I know the way of thinking that’s required, therefore I can’t actually show you mistakes or anything in your code…
For the saving, I would give every existing quest an id, which you can save into a local, save it with datastoring, and load the local on player join, using the ID to get the correct quest
I would say, make a remoteevent on player removing, which will look which quest is active, and give it the correct ID, (for example, if its the 12th quest the player has to do in the game, assign a local with the value 12 to it)
Try using a module script for storing the player data:
Add the player’s data in when they join:
local module = require(PATH)
local starterData ={
{"Toasted!", "Cook some toast", false} --> if your quests have more parameters add them as well
-- order of this is [NAME], [DESC], [ALREADY COMPLETED]
}
game.Players.PlayerAdded:Connect(function(plr)
local data = load(plr) -- returns data, write this function for yourself
if not module[plr.UserId] then
module[plr.UserId] = data or starterData
end
end)
Here’s a referencing example:
local module = require(PATH)
for v, quest in pairs(module[player.UserId]) do
-- "v" IS THE QUEST ID
-- "quest[1]" IS THE QUEST TITLE
-- "quest[2]" IS THE QUEST DESCRIPTION
-- "quest[3]" IS TRUE/FALSE (HAS THE QUEST BEEN COMPLETED?)
if v == 1 and points >= 70 and not quest[3] then
-- player finished the quest that they have not finished yet
end
end
Do pardon me, but I’m rather confused on how this system would work, probably because I’m very unfamiliar with module scripts. How would the ‘PATH’ look like? Do I add all of the quests in starterData? And what would the function look like?
This may look alright at first, but there’s still the case of DataStore errors which you might want to handle manually/separately as well - since it’s an unpredictable sort of matter, Roblox DataStores could be down any moment.
--// very slight expansion on that line
local success, savedTools = pcall(function() return toolsDS:GetAsync(plr.UserId) end)
savedTools = (success and savedTools) or {} -- if there's no error leave the data as is; if there's an error or data doesn't exist, set the data to a blank table
--// you could retry for failures too which would be better than giving up after one failure, DataStore wrappers like DataStore2 do much of the work for you so consider using an alternative to simplify things
There’s a number of things you can change here:
firstly it doesn’t look like you’re iterating through a non-array so use ipairs instead, I’ve already made several replies in various topics explaining the reason so check them out if you want, though that won’t change the output here.
plr:WaitForChild("OwnedWeapons")
instead of calling WaitForChild on the same object in an iteration potentially multiple times you might just want to call it once in the beginning and index it normally afterwards - though it won’t make much difference since a WaitForChild thread resumes without delay after being added to the queue.
You decide that, just substitute a reference to the ModuleScript object.
That aside I don’t really understand the issue here, further explanation might help but I can’t guarantee I’ll respond.
Cheers for the fixes, they’re bound to help alot, I’m sure!
The issue is this; the quests from the ReplicatedStorage get added to the player folder once they’re accepted from a quest giver in-game. Afterwards, the objective of the quest will change inside the player’s folder. However, using the script above, I think, would only copy the initial quests inside the ReplicatedStorage back to the player folder, therefore having the player lose all progress on the quest.
I think this should be a sufficient explanation - if it isn’t, feel free to ask for more clarification
Is there with the way your system is designed a limitation due to which you can’t just save completed quests in a dictionary to a store indicating completion of quests?
-- pseudo
Store:SetAsync(userId, {
quest1 = true; -- here the quest's name is "quest1", this table indicates completed quests
quest2 = true;
});
--// player leaves and rejoins
local data = Store:GetAsync(key); -- remember I'm not handling a variety of possible cases here since this is merely an outline
--// clone every quest BoolValue object and set the values
for _, quest in ipairs(ReplicatedStorage.QuestObjects:GetChildren()) do -- to see what the data would look like go above where it's saved
local clone = quest:Clone()
clone.Value = data[clone.Name] -- if the index doesn't exist it's nil, setting a BoolValue's value to nil sets it to false
clone.Parent = player
end
Basically, the way the game detects whether a quest is finished or not, and what the current objective is is via the value of the ‘objective’ StringValue. An example:
In the ‘Lumberjacking’ quest, the objective will turn into “Retrieve 10 logs of wood to Tobias the Farmer” as soon as enough wood logs have been collected.
When the player talks to the quest giver to finish the quest, the game checks for the current objective, and if it, indeed, is “Retrieve 10 logs of wood to Tobias the Farmer”, then a remote event will fire and the quest objective will be set to “Congratulations! You’ve completed this quest!” and the main quest value will turn to true, aswell as the quest rewards being given.
That’s how the system works. Basically, what I need to do is detect if the player has accepted the quest, and check for the current objective, aswell as if the quest value is true or not.