How would I go about making a Questing System?

Hello fellow developers! I have a question I need some answers on… QUESTS! I know this has already been posted many many times but I’ve read through the posted ones and none have answered my questions specifically. So, I’ll be listing them down here!

I am in a midst of making a quest system similar to the one in Bee Swarm simulator. I have the dialogues ready but I have encountered some problems with the quest giving, processing and rewarding.

  1. After a player gets a quest, how can I keep track that the player has finished/ not finished the quest? For example, Collect 5 Apples, I thought of doing a check to see if the player has >5 apples then he/she gets rewarded but then I realised that that won’t work if the player already has 10 apples, or anything more than 5. The quest would be completed instantly. So, how do I track that a player has collected the certain amount required no matter how much of the item the player already has collected before?

  2. Giving quests. I am able to give a quest if its just 1. But I do not know how to give it in progression, like Quest 1 then Quest 2 then Quest 3, and also how would I give out quests in random order? Like once the player talks to the NPC he/she gets Quest 10, then completes it and talks to the NPC again and now gets Quest 3?

  3. How do daily quests work? I’ve tried to wrap my head around os.time for quite some time and I always cannot do something successful with it.

  4. Lastly, how do I prevent a player from taking another quest if the player already has taken a quest from that same NPC?

I will be providing some of the tries I did trying to resolve these issues. (Problems 1,2 and 4 only)

Code for Problem 1: (Tracking Quests)

local Players = game:GetService("Players") -- My attempt at tracking quests

local Player = Players.LocalPlayer

local QuestModule = require(game.ReplicatedStorage.Modules.QuestModule)

local Apple = Player:WaitForChild("Stats"):WaitForChild("Apple")

local QuestLabel = script.Parent.Bar.AmountText
local QuestProgress = script.Parent.Bar.AmountNeeded

local onAppleChanged
onAppleChanged = Apple:GetPropertyChangedSignal("Value"):Connect(function()
	if Apple.Value < 200 then
		QuestLabel.Text = "Collect 200 Apples"
	else
		QuestLabel.Text = "Collect 200 Apples completed!"
		local QuestComplete = game.ReplicatedStorage.Remotes.QuestComplete
		QuestComplete:FireServer("RewardToGive")
	end
end)

Code for Problem 2: (Giving Quests)

QuestServer.OnServerEvent:Connect(function(Player, State)
	if State == "Quest1" then -- My attempt at giving a single quest
		print("Success!")
		local OneTaskQuest = script.QuestTemplateOne:Clone()
		OneTaskQuest.Parent = Player:WaitForChild("PlayerGui"):WaitForChild("UIs").QuestFrame.ActualFrame.Frames.ConcurrentQuestFrame
		Player:FindFirstChild("Mage").Value = "Quest1Taken"
		game.Workspace.Quest1.Mage.ProximityPrompt.Enabled = true
	end
	if State == "RandomQuest" then -- My attempt at giving random quests
		for i,v in pairs(script.RQuests:GetChildren()) do
			local Number = math.random(1,3)
			wait()
			if Number == 1 then
				if v:FindFirstChild("Type").Value == "1" then
					local OneTaskQuest = v:Clone()
					OneTaskQuest.Parent = Player:WaitForChild("PlayerGui"):WaitForChild("UIs").QuestFrame.ActualFrame.Frames.ConcurrentQuestFrame
					OneTaskQuest.Visible = true
				end
			elseif Number == 2 then
				if v:FindFirstChild("Type").Value == "2" then
					local OneTaskQuest = v:Clone()
					OneTaskQuest.Parent = Player:WaitForChild("PlayerGui"):WaitForChild("UIs").QuestFrame.ActualFrame.Frames.ConcurrentQuestFrame
					OneTaskQuest.Visible = true
				end
			elseif Number == 3 then
				if v:FindFirstChild("Type").Value == "3" then
					local OneTaskQuest = v:Clone()
					OneTaskQuest.Parent = Player:WaitForChild("PlayerGui"):WaitForChild("UIs").QuestFrame.ActualFrame.Frames.ConcurrentQuestFrame
					OneTaskQuest.Visible = true
				end
			end
		end
	end
end)

Code for Problem 4: (Preventing Players from taking quests repeatedly)

local Debris = game:GetService("Debris") 

local ProximityPrompts = script["Proximity Prompts"]
local Settings = script.Settings

local proximityPrompts = {}
local connections = {}

local screenGuis = Settings.ScreenGuis:GetChildren()
local timeUntilDelete = Settings.Time.Value

local function getProximityPrompts()
	for index, child in pairs(ProximityPrompts:GetChildren()) do
		if child:IsA("ObjectValue") then
			local prompt = child.Value
			if prompt:IsA("ProximityPrompt") then
				table.insert(proximityPrompts, prompt)
			end
		end
	end
end

local function onTriggered(player)
	local playerGui = player:WaitForChild("PlayerGui")
	if not playerGui then return end
	if player:FindFirstChild("Mage").Value == "Quest1" then
		script.Parent.Trevor.ProximityPrompt.Enabled = false -- my attempt on preventing repeated quests
		print("Newcomer!")
		for index, screen in pairs(screenGuis) do
			for index, child in pairs(playerGui:GetChildren()) do
				local childCloneSource = child:FindFirstChild("CloneSource")
				if childCloneSource then
					if childCloneSource.Value == screen then
						child:Destroy()
					end
				end
			end
			local newScreen = screen:Clone()
			local cloneSource = Instance.new("ObjectValue")
			cloneSource.Name = "CloneSource"
			cloneSource.Value = screen
			cloneSource.Parent = newScreen
			newScreen.Parent = playerGui
			--Debris:AddItem(newScreen, timeUntilDelete)
	    end
	elseif player:FindFirstChild("Mage").Value == "Quest1Taken" then -- my attempt on preventing repeated quests
		print("Already taken!")
		if player:WaitForChild("PlayerGui"):WaitForChild("UIs").QuestFrame.ActualFrame.Frames.ConcurrentQuestFrame:FindFirstChild("QuestTemplateOne"):FindFirstChild("Completed").Value == false then
			print("Complete first!")
		else
			player:FindFirstChild("Mage").Value = "Quest2" -- giving player next quest
			print("Congrats!")
			player:WaitForChild("PlayerGui"):WaitForChild("UIs").QuestFrame.ActualFrame.Frames.ConcurrentQuestFrame:FindFirstChild("QuestTemplateOne"):Destroy()
		end
end

-- Code --
getProximityPrompts()
for index, prompt in ipairs(proximityPrompts) do
	local connection = prompt.Triggered:Connect(onTriggered)
	table.insert(connections, connection)
end

I really really hope I can get the problems resolved with your help. Thank you ALL!

~ I also have another post I need help on a Boosting/Bonus system you can check out!

2 Likes

For the example of the apples, i would do the following:

  1. once the player get the quest, giving him a IntValue
  2. when the player collect an apple, do AppleValue += 1
  3. To finish the quest, you can let the player talk to an npc and check the value or just use a script inside the AppleValue which verify the Value every time it does changes
2 Likes

For the random gives quest, just do a randomized choice quest. Once the player talk to the npc, just do math.random() to a tables of keys

I can’t answer the question 3 about daily quest but for the question 4:
If you use the Value system, you can check if the player has the AppleValue. If he does, then do not gives him the quest.

1 Like

Hi, it would be great if you could provide code examples for both of your replies. It helps to visualize it better!

Since the dialogue system is broken and i am too lazy to do one by myself, i am using a ClickDetector. With it, here is my example:

  1. Click the npc
  2. Do a random choice among a table of keys
  3. give the quest with the name of the choice
  4. For example with the apple collect, i would give linked with the quest a IntValue. Just instance one and parent it to the character

As script example, here is one

local click = script.Parent
local servstor = game.ServerStorage

click.MouseClick:Connect(function(plr)
local Items = {
		{"quest1"},
        {"quest2"},
        {"quest3"},
	}
	local TotalWeight = 0

	for _,ItemData in pairs(Items) do
		TotalWeight = TotalWeight + ItemData[2]
	end

local function chooseRandomItem()
		local Chance = math.random(1,TotalWeight)
		local Counter = 0
		for _,ItemData in pairs(Items) do
			Counter = Counter + ItemData[2]
			if Chance <= Counter then
				return ItemData[1]
			end
		end
	end
	servstor.ITEMS:FindFirstChild(chooseRandomItem()):Clone().Parent = plr.Character
end)
1 Like

This will work for an infinite amount of times right?

Yes, actually, it doesn’t gives a limited amount of quests

1 Like

Here is a script which gives a bounty if you have less than 3 bounties with you:

local btn = script.Parent
local count = 0
btn.MouseClick:Connect(function(plr)
	for i, v in pairs(plr.Backpack:GetChildren()) do
		if v.Name == btn.Parent.Name.."'s bounty" then
			count = count + 1
		end
	end
	if count < 3 then
		game.ServerStorage.BOUNTIES:FindFirstChild(btn.Parent.Name.."'s bounty"):Clone().Parent = plr.Character
	end
	count = 0
end)

So yeah maybe it isn’t optimized but i did it some times ago

As you could see, i loop inside the Backpack to see if there is a bounty with the same name. If the script find one, it does count += 1. At the end, if the count is smaller than 3, then it gives a bounty. Else, it doesn’t

Hmm… what does this relate to?

Might be a good idea to break up these four questions into four posts next time, otherwise the discussion will get confused and out of hand.

Anyways, I’ll give some ideas for the “daily rewards question”. This isn’t the exact same problem, but it’s very similar:

1 Like

Reviewing your code I see a lot of code for visuals and UI but I don’t see code interacting with datastores editing tables and information you stored for quests (After some time reading your code I come to realize you may be using folders and number values it isn’t bad it just takes away a lot of possible things you can do )

I would need more snippets of your code specifically where you add onto values like apple to make a suggestion. While I was making a suggestion… I realize how you stored your data. or assumed atleast.

What i would do (but wouldnt be helpful)

Why did i say “(but wouldnt be helpful)”? - Because the way you store data at the moment doesn’t allow editing data table aside from storing your number values (I’m assuming your datastore system is similar to using leader stats)

I would store a new table of two values (this would require a whole new system of storing data)

  1. First Value would be ID or name of Quest so u can identify the second value
  2. Second Value would be the progress value (could be a number or bool or whatever u want)

Hmm. I currently use Datastore2 to store my values. I am able to update them

Can I see how you edit your data tables? While viewing your apple changed event it seemed like something similar to a leader stats system.

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local players = game:GetService("Players")
local DataStore2 = require(game.ReplicatedStorage.MainModule)
--# Requiring The Module
DataStore2.Combine("Key","Apple")

--# Player Join Function:
game.Players.PlayerAdded:Connect(function(plr)
	--[Defining The Different Major Datastores]--
	local AppleDatastore = DataStore2("Apple",plr)
	local Stats = Instance.new("Folder",plr)
	Stats.Name = "Stats"

	local Apples= Instance.new("IntValue",Stats)
	Apples.Name = "Apple"

	if AppleDatastore:Get() ~= nil then
		Apple.Value = AppleDatastore:Get()
		print("[Debug]" .. "AppleAmount: " .. Apple.Value)
	else
		Apple.Value = 0
	end
	Apple.Changed:Connect(function()
		AppleDatastore:Set(Apple.Value)
	end)

This is how I store for all my items

Its mostly what I suspected. I may be able to help but… I see that you set up a datastore for Apple. I am concerned moving forward are going to setup a datastore for each number value you need? (Just asking because i wouldnt recommend it)

Well… I do not know of any better ways so if you do I would like to learn it

I would suggest learning how to create and edit tables then I would recommend you learn how to use modules. So that you can make functions to edit your data tables within your module.

Once you know how to make datastore modules you can slowly add functionality for quest and various other systems like boosters/bonus like you wanted.

Oh I already am using modules, just simple ones to retrieve data

Possible if you could share some code on the basics on what I need to do?

If you have a module for retrieving data I would recommend you add functions for editing that data within that module unless you want to make a separate module to call the module retrieving data and edit the data

In my case I usually have
image

ProfileService is an open-source data auto-saving module I use like how you use Datastore2

DataStoreManager Retrieves all the player’s data when they join and stores it in a table
All my data modules require a datastore manager to retrieve data to edit

InventoryManager and StatsManager are modules i have functions in to edit data they both require Datastore manager to retrieve data before they edit like so

function StatsManager:GetData(Player)
	local Profile = DataManager:GetStatisticProfile(Player)
	local Data = Profile.Data
		return Data	
end

I used to have modules that utilized datastore2 I would have to find them to show you an example if you’d like to see (Looking for it rn)