I want to be able to update quest value in quest system

  1. I want to be able to update my quest value without relying on fireclient so that whenever the player completes the quest the have to go back to the npc to trigger the completion dialogue instead of the dialogue popping up once the player completes the quest.

  2. I am sure that the main problem is that since the value is on a local script and when I change it on a server script it will not work.

Heres my scripts:
Local script

local rs = game:GetService("ReplicatedStorage")
local stg = game:GetService("StarterGui")
local plr = game.Players.LocalPlayer

rs.RemoteEvents.quest1.OnClientEvent:Connect(function(state, newquest)
	print("Fired")
	local maxcount = 3
	local uihandler = require(rs:FindFirstChild("modules").UIColorhandler)
	local dialogoptions = require(rs:FindFirstChild("modules").QuestData)
	local questhandler = require(rs:FindFirstChild("modules").QuestHandler)
	print(questhandler.savedvalues[plr.UserId])
	local quest2 = rs:FindFirstChild("Quests")["Quest#2"]:Clone()
	print(quest2.Value)
	local textFrame = plr.PlayerGui.QuestGui:FindFirstChild("Quest#1"):FindFirstChild("DialogFrame")
	local UI = {
	 	questframe2 = plr.PlayerGui.QuestGui:FindFirstChild("Quest#2").QuestFrame,
		dialogtxtbutton = textFrame.Dialog,
		txtlabel = textFrame.TextLabel,
		no = textFrame.Nah,
		yes = textFrame.Yes
	}
	
	print(UI.no)
	
	quest2.Changed:Connect(function()
		print('Changed')
	end)

	local color1 = Color3.fromRGB(67, 123, 255)
	local color2 = Color3.fromRGB(45, 83, 172)
	
	print(dialogoptions["Quest#2"])
	print(quest2.Value)
	
	if state == "NewValue"  then
		print("EVERYONES IS A BAD GUYYY")
		uihandler.UiHandler(plr, textFrame, color1, color2)
		questhandler.QuestHandler(plr,newquest, UI.no, UI.yes, UI.dialogtxtbutton, dialogoptions["Quest#2"].Kate, dialogoptions["Quest#2"].player, maxcount)
		return
	end
	
	if stg.QuestGui.Enabled then
		uihandler.UiHandler(plr, textFrame, color1, color2)
		questhandler.QuestHandler(plr,quest2, UI.no, UI.yes, UI.dialogtxtbutton, dialogoptions["Quest#2"].Kate, dialogoptions["Quest#2"].player, maxcount)
	else
		print("didnt work")
	end
	
	
end)

--[[rs.RemoteEvents.questfinish.OnClientEvent:Connect(function(state, newquest)
	local maxcount = 3
	local uihandler = require(rs:FindFirstChild("modules").UIColorhandler)
	local dialogoptions = require(rs:FindFirstChild("modules").QuestData)
	local questhandler = require(rs:FindFirstChild("modules").QuestHandler)
	local quest2 = rs:FindFirstChild("Quests")["Quest#2"]:Clone()
	local textFrame = plr.PlayerGui.QuestGui:FindFirstChild("Quest#1"):FindFirstChild("DialogFrame")
	local UI = {
		questframe2 = plr.PlayerGui.QuestGui:FindFirstChild("Quest#2").QuestFrame,
		dialogtxtbutton = textFrame.Dialog,
		txtlabel = textFrame.TextLabel,
		no = textFrame.Nah,
		yes = textFrame.Yes
	}
	
	print(plr, state, newquest)
	
	local color1 = Color3.fromRGB(67, 123, 255)
	local color2 = Color3.fromRGB(45, 83, 172)
	
	if state == "NewValue" then
		uihandler.UiHandler(plr, textFrame, color1, color2)
		questhandler.QuestHandler(plr,newquest, UI.no, UI.yes, UI.dialogtxtbutton, dialogoptions["Quest#2"].Kate, dialogoptions["Quest#2"].player, maxcount)
	end
end)--]]

QuestHandleModule

local module = {}

local rs = game:GetService("ReplicatedStorage")

module.savedvalues = {}

module.QuestHandler = function(plr,quest, branch1, branch2, questframe, npcdialogueoptions, playerdialogueoptions, maxcount)
	local questdata = require(script.Parent.QuestData)
	local slow = 0.1
	local state = module.savedvalues[plr.UserId] or false
	module.savedvalues[plr.UserId] = state
	print(module.savedvalues[plr.UserId])
	
	
	
	questframe.MouseButton1Click:Connect(function()
		slow = 0
		if questframe.Text == npcdialogueoptions.Unfinished or questframe.Text == npcdialogueoptions.Completed then
			questframe.Parent.Visible = false
		end
	end)
	local function disableButtons()
		branch1.Visible = false
		branch2.Visible = false
	end
	
	local function typewrite(obj, text)
		obj.Text = ""
		for i = 1, #text do
			task.wait(slow)
			obj.Text = string.sub(text, 1, i)
		end
	end
	
	local function wipe()
		for i, v in pairs(questframe.Parent:GetChildren()) do
			if v:IsA("TextButton") then
				v.Text = ""
				slow = 0.1
			end
		end
	end
	
	
	
		print(quest.Value)
		quest.Changed:Connect(function()
			print('dah daha ahaha')
		end)
	
	print(quest.Count.Value)
		if state and not quest.Value then 
			print('UNFINISHED')
			print(quest.Value)
			disableButtons() 
			typewrite(questframe, npcdialogueoptions.Unfinished)
			return
		elseif quest.Value and state then
			print("DONE!")
			disableButtons()
			typewrite(questframe, npcdialogueoptions.Completed)
			return
		end

	
	
	typewrite(questframe, npcdialogueoptions.text.Question)
	
	branch1.MouseButton1Click:Once(function()
		questframe.Parent.Visible = false
	end)
	branch2.MouseButton1Click:Once(function()
		wipe()
		typewrite(questframe, npcdialogueoptions.text.Mission)
		if npcdialogueoptions.text.Mission then
			typewrite(branch1, playerdialogueoptions.Decline[2])
			typewrite(branch2, playerdialogueoptions.Accept)
			if branch2.Text == playerdialogueoptions.Accept then
				branch2.MouseButton1Click:Once(function()
					questframe.Parent.Visible = false
					state = true
					module.savedvalues[plr.UserId] = state
					rs.RemoteEvents.quest1:FireServer("QuestActivated", state)
				end)
			end
		end
	end)
	
	
	if npcdialogueoptions.text.Question then
		typewrite(branch1, playerdialogueoptions.Decline[1])
		typewrite(branch2, playerdialogueoptions.Next)
	
	end
	print(module.savedvalues[plr.UserId])
	
end

return module

server script

local rs = game:GetService("ReplicatedStorage")
local questremote = rs:FindFirstChild("RemoteEvents").quest1
local stg = game:GetService("StarterGui")

script.Parent.Triggered:Connect(function(plr)

	questremote:FireClient(plr)


	rs.RemoteEvents.quest1.OnServerEvent:Connect(function(plr, state, boolean)

		if state == "QuestActivated" then
			print("HAJIME STARTOOOO")

			local mxcount = 3
			local accept = require(rs:FindFirstChild("modules").QuestAccept)
			local quest2 = rs:FindFirstChild("Quests")["Quest#2"]:Clone()
			local textFrame = plr.PlayerGui.QuestGui:FindFirstChild("Quest#1"):FindFirstChild("DialogFrame")
			local UI = {
				questframe2 = plr.PlayerGui.QuestGui:FindFirstChild("Quest#2").QuestFrame,
				dialogtxtbutton = textFrame.Dialog,
				txtlabel = textFrame.TextLabel,
				no = textFrame.Nah,
				yes = textFrame.Yes
			}
			print(boolean)
			quest2.Parent = plr

			accept.Accept(plr, UI.questframe2, textFrame, quest2, mxcount, boolean)

			print(quest2.Parent)

		elseif state == "NewValue" then
			print("NEWWWWW")

		end
	end)
end) 

quest accept module

local module = {}
local rs = game:GetService("ReplicatedStorage")


module.Accept = function(plr,objectiveframe, questframe, quest, maxcount, state)
	local savedvalues = require(script.Parent.QuestHandler)
	local fired = rs.RemoteEvents.quest1
	print("YOSHAAA")
	objectiveframe.Visible = true
	print(savedvalues.savedvalues)

	quest.Count:GetPropertyChangedSignal("Value"):Connect(function()
		local newcount = quest.Count.Value
		objectiveframe.Objective.Text = "• Kill 3 zombies: "..newcount.."/"..3
		if quest.Count.Value == maxcount then
			objectiveframe.Visible = false
			quest.Value = true
			state = false

			savedvalues.savedvalues = state


			fired:FireClient(plr,"NewValue", quest)

		end
	end)




end

return module

1 Like

Store the quest data in the players leaderstats or PlayerValues on the Server, and use ObjectValues or a BoolValue inside Player instead of storing values in the table: savedvalues

So you’d want to update the value on the server so the player must return to the NPC to complete the quest.

Server Script:

local rs = game:GetService("ReplicatedStorage")
local questremote = rs:FindFirstChild("RemoteEvents").quest1
local stg = game:GetService("StarterGui")

script.Parent.Triggered:Connect(function(plr)
	questremote:FireClient(plr) -- this keeps the interaction active but doesnt update the quest
	
	rs.RemoteEvents.quest1.OnServerEvent:Connect(function(plr, state, boolean)
		if state == "QuestActivated" then
			print("HAJIME STARTOOOO")

			local mxcount = 3
			local accept = require(rs:FindFirstChild("modules").QuestAccept)
			local quest2 = rs:FindFirstChild("Quests")["Quest#2"]:Clone()

			-- store the questp progress as a boolvalue inside the Player
			if not plr:FindFirstChild("QuestProgress") then
				local questProgress = Instance.new("BoolValue")
				questProgress.Name = "QuestProgress"
				questProgress.Value = false
				questProgress.Parent = plr
			end

			local textFrame = plr.PlayerGui.QuestGui:FindFirstChild("Quest#1"):FindFirstChild("DialogFrame")
			local UI = {
				questframe2 = plr.PlayerGui.QuestGui:FindFirstChild("Quest#2").QuestFrame,
				dialogtxtbutton = textFrame.Dialog,
				txtlabel = textFrame.TextLabel,
				no = textFrame.Nah,
				yes = textFrame.Yes
			}

			quest2.Parent = plr
			accept.Accept(plr, UI.questframe2, textFrame, quest2, mxcount, boolean)
			print("Quest started for " .. plr.Name)

		elseif state == "NewValue" then
			print("NEWWWWW")
		end
	end)
end)

Quest Module:

local module = {}
local rs = game:GetService("ReplicatedStorage")

module.Accept = function(plr,objectiveframe, questframe, quest, maxcount, state)
	local fired = rs.RemoteEvents.quest1
	print("YOSHAAA")
	objectiveframe.Visible = true

	quest.Count:GetPropertyChangedSignal("Value"):Connect(function()
		local newcount = quest.Count.Value
		objectiveframe.Objective.Text = "• Kill 3 zombies: "..newcount.."/"..maxcount

		if newcount == maxcount then
			objectiveframe.Visible = false
			quest.Value = true
			state = false
			
			-- update the server side quest progress
			local questProgress = plr:FindFirstChild("QuestProgress")
			if questProgress then
				questProgress.Value = true
			end

			print("Quest completed for " .. plr.Name)
		end
	end)
end

return module

Local Script:

local rs = game:GetService("ReplicatedStorage")
local stg = game:GetService("StarterGui")
local plr = game.Players.LocalPlayer

local function checkQuestCompletion()
	local questProgress = plr:FindFirstChild("QuestProgress")

	if questProgress and questProgress.Value == true then
		print("Quest is complete! Player needs to return to NPC.")
	else
		print("Quest is not yet complete.")
	end
end

rs.RemoteEvents.quest1.OnClientEvent:Connect(function(state, newquest)
	print("Fired")
	local maxcount = 3
	local uihandler = require(rs:FindFirstChild("modules").UIColorhandler)
	local dialogoptions = require(rs:FindFirstChild("modules").QuestData")
	local questhandler = require(rs:FindFirstChild("modules").QuestHandler")
	
	local quest2 = rs:FindFirstChild("Quests")["Quest#2"]:Clone()
	local textFrame = plr.PlayerGui.QuestGui:FindFirstChild("Quest#1"):FindFirstChild("DialogFrame")
	local UI = {
		questframe2 = plr.PlayerGui.QuestGui:FindFirstChild("Quest#2").QuestFrame,
		dialogtxtbutton = textFrame.Dialog,
		txtlabel = textFrame.TextLabel,
		no = textFrame.Nah,
		yes = textFrame.Yes
	}

	local color1 = Color3.fromRGB(67, 123, 255)
	local color2 = Color3.fromRGB(45, 83, 172)

	if state == "NewValue" then
		uihandler.UiHandler(plr, textFrame, color1, color2)
		questhandler.QuestHandler(plr,newquest, UI.no, UI.yes, UI.dialogtxtbutton, dialogoptions["Quest#2"].Kate, dialogoptions["Quest#2"].player, maxcount)
		return
	end

	if stg.QuestGui.Enabled then
		uihandler.UiHandler(plr, textFrame, color1, color2)
		questhandler.QuestHandler(plr, quest2, UI.no, UI.yes, UI.dialogtxtbutton, dialogoptions["Quest#2"].Kate, dialogoptions["Quest#2"].player, maxcount)
	else
		print("Quest UI not enabled")
	end

	checkQuestCompletion() -- everytime a player opens the UI it checks the quest progress
end)

Hope this helps :slight_smile:

1 Like

I added a few tweaks and implemented some of the code to areas I needed it to work but overall it worked! Thank you so much also if you don’t mind can you explain how questprogress works? I want to be able to make more advanced quest systems and this topic seems to be really pushing me on my mastery on coding.

1 Like

Alright so the server knows the players quest state at all times since the value is stored there.
Also players can’t fake quest progress since it’s stored securely on the server, so it’s not exploitable.
You can also track multiple quests, objectives, and progress levels.

So how the Quest Progress works is when we store the BoolValue we’ll be modifying it’s property based on the quest progress so It’ll create a BoolValue in the player if it doesn’t exist. We set it to false by default meaning the quest isn’t completed and it changes to true when completed.

Now you mentioned how you can make more advanced quest systems you can be using IntValue and have three states like this:

if not plr:FindFirstChild("QuestProgress") then
    local questProgress = Instance.new("IntValue")
    questProgress.Name = "QuestProgress"
    questProgress.Value = 0 -- 0 = not started, 1 = in progress, 2 = completed
    questProgress.Parent = plr
end

and to be able to check the quest progress when using an IntValue you’d do:

local questProgress = plr:FindFirstChild("QuestProgress")
if questProgress and questProgress.Value == 1 then
    print("quest is in progress")
elseif questProgress and questProgress.Value == 2 then
    print("quest is already completed")
end

Now let’s say you want to track multiple quests you can create a folder inside the player that holds multiple quest progress values heres a way to achieve this:

if not plr:FindFirstChild("Quests") then
    local questsFolder = Instance.new("Folder")
    questsFolder.Name = "Quests"
    questsFolder.Parent = plr
end

-- so here is a quest progress tracker for EACH quest
local function addQuest(plr, questName)
    if not plr.Quests:FindFirstChild(questName) then
        local questProgress = Instance.new("IntValue")
        questProgress.Name = questName
        questProgress.Value = 0 -- 0 = not started, 1 = in progress, 2 = completed
        questProgress.Parent = plr.Quests
    end
end

Now if you want to check one of the multiple quests here is an example:

local questProgress = plr.Quests:FindFirstChild("ZombieSlayer") 
if questProgress then
    if questProgress.Value == 1 then
        print("zombie Slayer quest is active")
    elseif questProgress.Value == 2 then
        print("zombie Slayer quest is completed")
    end
end

And if you want to update a quest to completed the method is easy:

local zombieQuest = plr.Quests:FindFirstChild("ZombieSlayer")
if zombieQuest then
    zombieQuest.Value = 2 -- 2 is the state of completed
end

If you want to make this more advanced you can use a datastore to save the players data within leaderstats also if you want to store additional data like (XPReward, ObjectivesCompleted, ItemsCollected) you can use NumberValue or a StringValue.

1 Like

Alright thank you very much so the reason why my original script didn’t work was because it didn’t have a seperate instance to track if the quest was done or not compared to using quest.Value?

Yes that’s correct, your original script only relied on quest.Value, which was a local instance and not tracked on the server so in other words it only existed on the players client. So when you tried modifying the value on the client, it didn’t update because client and server don’t automatically sync object properties (like .Value).

1 Like

Alright thanks a lot. I got a lot out of this.

1 Like