Making a quests system

Hello developers! IAmPinleon here.

I am wondering how I can make a quests system, preferably the part where you script what to check when checking if a quest is complete. For example, collecting 5 apples. But it won’t just be collecting apples (this is an example, not actually my game), it could be where you need to collect apples, AND kill monsters, AND get a certain amount of cash. I don’t really know of a way to script that.

I do not have a script to show as I am wanting a bit of info on how I can script that one part.

If you need any more information please tell me! I did try to search but nothing came up. :slight_smile:

6 Likes

This is just what I would do. I would have the quest info in a module with all the quests and there requirements and every time the player interacts with something quest-related I would run checks to see if they have a quest activated and if quest requirements are being met. and ofc u need a datastore system that stores & edits tables.

8 Likes

QuestDemo.rbxl (53.4 KB)

Here’s a quick demo game of what you requested.

local AppleCount = script.Parent.AppleCount
local CoinCount = script.Parent.CoinCount
local QuestStatus = script.Parent.QuestDisplay

function TrackQuest ()
	local Connections

	-- Check the quest progress everytime the apple or coin value changes

	local function CheckProgress ()
		if AppleCount.Count.Value == 5 and CoinCount.Count.Value == 3 then
			print("Quest completed!")
			QuestStatus.Text = "Quest: completed!"
			
			for _, connection in Connections do
				connection:Disconnect()
			end
		end
	end

	--Set up your listeners

	Connections = {
		AppleCount.Count.Changed:Connect(CheckProgress),
		CoinCount.Count.Changed:Connect(CheckProgress)
	}
end

-- Apple found event

game.ReplicatedStorage.AppleEvent.OnClientEvent:Connect(function()
	print('Apple Found!')
	AppleCount.Count.Value += 1
	AppleCount.Text = "Apples: ".. AppleCount.Count.Value
end)

-- Coin found event

game.ReplicatedStorage.CoinEvent.OnClientEvent:Connect(function()
	print('Coin Found!')
	CoinCount.Count.Value += 1
	CoinCount.Text = "Coins: ".. CoinCount.Count.Value
end)

TrackQuest()

Play around with it a little bit to understand how it works a bit better. Every time your apple or coin value is changed, it performs a quick check to see if the completion values have been met. Once they have been, it will stop listening to those specific values and free up a bit of memory. Hope this helps :slight_smile:

13 Likes

I’m gonna try that. Would you have the checking done in the module? For example, when you get one apple, your quest progress goes up, and the checking is player.leaderstats.Apples.Changed:Connect(function() --code end)? How could I implement that into the module and in the server script, use the module part to check it?

3 Likes

I wouldn’t use Numbervals.Is what I meant when I said this.

ofc u need a datastore system that stores & edits tables.

But… I would probably have an entire module dedicated for quests functions to do the checking if a quest is activated to fetch its requirements etc. so that anytime I update the inventory datastore I would just a run function from my quest module or Anytime I were to experience any data changes related to quest would run questmodule functions with a function that will check datastore and compare changed data

In your server script, you most likely would have a remote for adding data into the datastore I would just add if statements/checking there

the leader stats method is nice and easy and all but its a lot to set up down the line but the alternative method I have in mind takes even more thinking.

Ill try to compile an example because it seems like just text wont be helpful.

2 Likes

Almost finished making the example just debugging and cleaning up. Experimenting bugs with one of the demos after i fix it ill show

I didn’t manage to patch the bug with gold not adding so ignore the gold falling from the sky :cloud:

The Test place https://www.roblox.com/games/6786571483/Quest-Test (Uncopylocked Place Verison)|
Quest_Test.rbxl (56.9 KB) - Just View output log to see what is actually happening

You will need to access API services to test because the test utilizes datastore’s but the datastores will wipe everytime a player joins repeat what happened

Please note: These are just an example of just how to start this isn’t a full-fledge system to take and use willy nilly :worried:

QuestManager Module
local QuestModule = {}
local DataManager = require(game.ServerScriptService.DataManagers.DataManager) -- // A module that has been set up to use ProfileService by Loleris
local QuestDetails = require(game.ServerScriptService.InformationModules.QuestDetails) --// Quest Info Module

function QuestModule.StartQuest(Player,Quest_ID)
	local Profile = DataManager.GetStatisticProfile(Player)
	local Data = Profile.Data
	local Quests = Data.Quests
	
	for QuestIndex,Quest in pairs(Quests) do
		print(QuestIndex,Quest)
		if Quest and Quest.ID ~= Quest_ID  then --// Checking if already has quest even if its completed or not // not allowing any copies of the same quest
		else
			warn(Player.Name.. " Already has this quest")
			return
		end
	end
	
	
	local QuestAmount = table.getn(Quests) --// Amount of Quests
	local Quest = {} --// Table being stored in Datastore
		Quest.ID = Quest_ID --// Storing the ID of quest
		Quest.Prog = {} --// Table of Quest Progress // Can be shorten to just P if desired 
		Quest.TS = os.time() --// TimeStamp of when quest has begun
		
	table.insert(Quests,QuestAmount+ 1,Quest)--//Adding Quests to datastore // ProfileService will do all the work Datastores
end

function QuestModule.ClearQuest(Player,Quest_ID) --// Clears Quest when its complete setting prog table to true // to save space in the  datastore
	local Profile = DataManager.GetStatisticProfile(Player)
	local Data = Profile.Data
	local Quests = Data.Quests

	for QuestIndex,Quest in pairs(Quests) do print(QuestIndex,Quest)
		if Quest.ID == Quest_ID and Quest.Prog ~= true then --// Checking if QuestID Matchs
			Quest.Prog = true --// Setting progress to tree meaning quest is completed // Wont be the same if ur not using profile service
			
			local QuestInfo = QuestDetails.Get_Quest_FromID(Quest.ID) --// Fetches Info
			print("CONGRATS!!! | ", "Quest Completed: " ..QuestInfo.Quest_Name.. " | Rewarding ",QuestInfo.QuestRewards)
			
			--// You can add code to give out rewards for completed quest within this if statement
		end
	end	
	print(Quests)
end


function QuestModule.Check(Player,KeyName,Value)
local Profile = DataManager.GetStatisticProfile(Player)	
	if not Profile then return end

	local Data = Profile.Data
	local Quests = Data.Quests
	
	if #Quests == 0 then print(Player.Name .. " has no quests")
	else		
		for QuestIndex,Quest in pairs(Quests) do
		if Quest and Quest.Prog ~= true then 
				local QuestInfo = QuestDetails.Get_Quest_FromID(Quest.ID) --// Fetches Requirements
				
				for K, V in pairs(QuestInfo.QuestRequirments) do
					
					if K == KeyName and QuestInfo.QuestRequirments[KeyName]  then --// Checking if Data  being changed is accurate to Requirements KeyName
						
						
						if V == Value then --// Quest requirements meet // Clearing quest // This can use more work 
							QuestModule.ClearQuest(Player,Quest.ID)
						else 
							Quest.Prog[KeyName] = Value
						end
					end
					
				end
			end
		end	
	end
end

return QuestModule

I attempted to comment as much as possible

QuestDetails Module
local QuestDetails = {}

QuestDetails.Q1 = {
	Quest_Name = "The Quest of Time / 40 Seconds",
	Quest_ID = 1,
	QuestRequirments = {
		SecondsPlayed = 25
	},
	QuestRewards = {"Goodies","Trophy"}
}

QuestDetails.Q2 = {
	Quest_Name = "The Quest of Time / 50 Seconds",
	Quest_ID = 1,
	QuestRequirments = {
		SecondsPlayed = 50
	},
	QuestRewards = {"Goodiesx2","Trophyx2"}
} 

QuestDetails.Q3 = {
	Quest_Name = "The Quest for GOLD",
	Quest_ID = 1,
	QuestRequirments = {Gold = 5},
	QuestRewards = {"Gold Trophy","Rewards"}
}


function QuestDetails.Get_Quest_FromID(ID)
	local ID = "Q"..ID
	for QuestID,Quest in pairs(QuestDetails) do
	
		local ID_Prefix = string.find(QuestID,'Q')
		if ID_Prefix == 1 then 				
			if type(Quest)== "table" and QuestID == ID then
				return Quest
			end 
		end
	end
end

function QuestDetails.Get_Quest_FromName(Name)
	for QuestID,Quest in pairs(QuestDetails) do
		if type(Quest)== "table" and Name == Quest.Quest_Name then
			return Quest
		end
	end
end

return QuestDetails
How I used QuestManager

In my StatManager module I added QuestManger.Check() This will check if the value of Stat being changed and see if it matches the quest requirements value. This will NOT be a fix-all solution because sometimes quest requirements will be met and the values will not match.

Here is an example of using QuestManger.Check() after setting a value

	QuestModule.Check(Player,"SecondsPlayed",SecondsPlayed)--// Checking After setting Data
			

Every time SecondsPlayed was changed I ran QuestManger.Check() to see if it met quest requirements.

Important part of QuestModule.Check()
if #Quests == 0 then 
		print(Player.Name .. " has no quests") --// Checks if there are any quest active 
	else		
		for QuestIndex,Quest in pairs(Quests) do --// Looping through Quests to find Quests Inprogress
			
		if Quest and Quest.Prog ~= true then 
				local QuestInfo = QuestDetails.Get_Quest_FromID(Quest.ID) --// Fetches Requirements
				
				for K, V in pairs(QuestInfo.QuestRequirments) do --// Looping Through Quest Requirements to compare values
					
					if K == KeyName and QuestInfo.QuestRequirments[KeyName]  then --// Checking if Data  being changed is accurate to Requirements KeyName
						
						
						if V == Value then --// Quest requirements meet // Clearing quest // This can use more work 
							
							QuestModule.ClearQuest(Player,Quest.ID) --// Req are met so Clearing quest and Granting rewards 
						else 
							Quest.Prog[KeyName] = Value --// Setting Quest Values to store progress made 
						end
					end
					
				end
			end
		end	
	end

To add quests I made QuestManager.StartQuest(Player,QuestID)
For example how of I added a quest 1-3

local QuestManager = require(game.ServerScriptService.DataManagers.QuestModule)

Players.PlayerAdded:Connect(function(Player)	
	QuestManager.StartQuest(Player,1) --// Added quest via ID QuestManager.StartQuest(Player,QuestID)
	QuestManager.StartQuest(Player,2)
	QuestManager.StartQuest(Player,3)
end)
How can I tell if it WORKS?!?!

https://gyazo.com/862b93c8588756f159d7d1c20a07fe1c.gif - Gif of what occurs
As you can see it counts to 25

QuestDetails.Q1 = {
	Quest_Name = "The Quest of Time / 40 Seconds",
	Quest_ID = 1,
	QuestRequirments = {
		SecondsPlayed = 25
	},
	QuestRewards = {"Goodies","Trophy"}
}

Quest one requirement is 25 seconds

image

  • You can see that name of quest is printed as well as a table of the rewards

  • Below that you can see that the progress is set to true meaning the quest is completed

  • Below that you can see quest two with a progress of 24 seconds will soon reach the goal of 50 seconds

QuestDetails.Q2 = {
	Quest_Name = "The Quest of Time / 50 Seconds",
	Quest_ID = 1,
	QuestRequirments = {
		SecondsPlayed = 50
	},
	QuestRewards = {"Goodiesx2","Trophyx2"}
} 

You may be asking How can it see the quest info?

A function in QuestDetails module called QuestDetails.Get_Quest_FromID(Quest.ID) returns the questinfo table

local QuestInfo = QuestDetails.Get_Quest_FromID(Quest.ID) --// Fetches Info
	print("CONGRATS!!! | ", "Quest Completed: " ..QuestInfo.Quest_Name.. " | Rewarding ",QuestInfo.QuestRewards)

4 Likes

This is a very late reply, I apologize for it.

I’ve been looking through the scripts and I’m very confused on one thing. How do I make a custom quest? You have the one where you get the gold. I want to make myself some other quest, like doing an obby or something. Just list me the steps and I’ll tell you if I need more information.

Also, will this work where one person can give multiple quests? If it was just a person that gave random quests, would I do it through the script where the person is talking? In the script it would pick a random quest and start it.

Not just random quests, also in order. It goes through quest 1, then quest 2, then quest 3, and not randomly like quest 2, quest 1, quest 3, quest 1.

Hey, You’ve asked a lot of really great questions here. And by asking these questions you actually come up with new ways and functionality that you can add to a script.

The whole idea of being a programmer is that you get to ask yourself these questions, and then come up with solutions to how you can implement them. Like when you asked, “Will this work where one person can give multiple quests”. The joy of programming is that you can make it work so that one person can give multiple quests.

You’ve also asked for a list of steps. However, being a developer is about coming up with a list of steps yourself, or doing independent research so that you can get the idea of what you’d like. There’s no blueprint for something that doesn’t exist yet. Therefore there are many solutions to a problem.

In this case, a custom quest is anything you decide it to be. It can be a table or an object/class. It can be a folder in the workspace. Since the system doesn’t exist, it’s all about how you decide to build it. @Extrenious gave you an example of how this could work. If you need more information, you can try checking out how other games have handled their quest systems. This includes games on or off the platform. There’s always some sort of post mortem or online resource about game mechanics.

1 Like

You have a lot of question and to a lot of them i am confused and concerned.

This was just something to show you how you can start maybe get an idea of how to make your own i dont believe its polished or good enough to just use.

give or receive mutiple quests?
If you meant receive quests yes they can receive quests as much as the data store will allow

Not sure what you meant person talking about being an NPC? … Yes u would just require your quest module and fire start quest

QuestManager.StartQuest(Player,2) --//Player receiving quest / Second Parameter is QuestID

Not sure what you mean here…

If you are referring to what was printed in the output. That is being printed because each quest has an ID that is stored in the datastore so that the quest requirements can be identified via Get quest from ID.

The order isn’t something to pay attention to the ID can be strings values not just number values if dislike numbers being ID’s to quest just give em names. I choose numbers to preserve space within the datastore.

Making a obby quest

You asked how do you make a custom quest you would rewrite QuestManager.Check() and add new checks for Quest requirements for the scenario of you making an obby as a quest you would just make a quest like this

QuestDetails.Q4 = {
	Quest_Name = "Obby",
	Quest_ID = 4,
	QuestRequirments = {OC= true}, --// OC being ObbyCompleted
	QuestRewards = {"Rewards"}
}

Storing a bool named OC as the quest requirement And then when obby is complete you would insert into the quest Progress table OC = true meaning ObbyCompleted = true and then Quest.Manager.Check() would see that it matches quest requirements

I’d like to state this again what I made was just an Example There are plenty of ways you can make a quest system this was just my take.

Looking back I can even spot little errors I’ve made I don’t recommend using this example unless you modify it or refractor it almost completely (but… to do that u need to understand what the code does)

1 Like

Hi could you take a look at my Quest post?

1 Like

– get cash quest (in gui)

local player = game.Players.LocalPlayer
local cash = player.leaderstats.Cash
local button = script.Parent

local cashNeed = 100
local winCash = 20
local Done = false

button.mousebutton1click:connect(function(plr)

if Done = false then
if cash.Value >= cashNeed then
print(“the player have the cash!”)
cash.Value += winCash
Done = true

else
print(“the player not have the cash yeet!”)
end
else
print(“The player has already performed it”)
end

end)