Quest System Help

So like what script would I do to make a repeatable quest to kill 5 zombies and earn about 200 Candy (My currency, I alr have a leaderstats with candy and money) but you walk up to him and use the in-game dialogue and after selecting ChoiceA which is yes it activates a quest to kill about 5-10 zombies then you earn a badge and 200 candy?

1 Like

I believe this could work?

local Npc = workspace.NPC
local Players = game:GetService("Players")
Players.PlayerAdded:Connect(function(plr)
	local CurrentQuest = Instance.new("StringValue")
	CurrentQuest.Name = "Quest"
	CurrentQuest.Value = ""
	CurrentQuest.Parent = plr
end)

Npc.ClickDetector.MouseClick:Connect(function(plr)
	if plr.Quest.Value ~= "Kill 5 Zombies" then
		plr.Quest.Value = "Kill 5 Zombies"
		local ZombiesKilled = Instance.new("IntValue")
		ZombiesKilled.Value = 0
		ZombiesKilled.Name = "ZombiesKilled"
		ZombiesKilled.Parent = plr
		plr.PlayerGui.DialogueGui.Enabled = true -- you would prob have another script to handle this, and also one to handle the quest gui
		
		while wait(0.01) do
			if ZombiesKilled.Value >= 5 then -- you're gonna need a script inside the zombies to increase this value when it dies
				ZombiesKilled:Destroy()
				plr.leaderstats.candy.Value += 200
				plr.PlayerGui.QuestGui:Destroy()
				break
			end
		end
	end
end)
5 Likes

what would the other scripts be and how would I customize it? to work with like a interaction or whatever and where would I place it

uh…you put it in the npc and add a click detector in the npc too

ok! i’ll try it out. thank you!

Also how would I make this quest repeatable? and what about the Dialogue gui?

@Robo_Supernova
You probably don’t want to do this, the constant polling would really slow the game down. You’d want to connect an event to the value instead.

You’d probably want to create a ModuleScript for this, which I just made!

Code:

--!strict

local Players = game:GetService("Players")
local PlayerQuests = {} -- This stores all the players' progresses and active quests.
local Quests = { -- This stores all the quest information, this is what you'll most likely want to edit
	["KillZombies"] = {
		Goal = 5;
		Reward = 200;
		RewardType = "Candy";
	}	
}
local QuestModule = {}

local Settings = {
	AutorunComplete = true; -- Automatically runs QuestCompleted when a quest is finished
	AddQuestIfNotPresent = false; -- Automatically add quest if attempting to increment nonexistent quest
}

function QuestModule.AddPlayer(Player)
	PlayerQuests[Player] = {}
end

function QuestModule.RemovePlayer(Player)
	PlayerQuests[Player] = nil
end

-- Use this to give a player a quest
function QuestModule.IssueQuest(Player,QuestIdentifier: string)

	local Quest = Quests[QuestIdentifier]
	if Quest then
		local PlayerTable = PlayerQuests[Player] -- Aforementioned table that stores quests for players
		if PlayerTable and not PlayerTable[QuestIdentifier] then -- Make sure they don't have this quest
			PlayerTable[QuestIdentifier] = {
				Progress = 0;
				--QuestReference = Quest;	// Doesn't currently use this, but could be useful later
			}
		end
	end
end

-- Runs automatically when a player finishes a quest
function QuestModule.QuestCompleted(Player, QuestIdentifier: string): boolean

	local Quest = Quests[QuestIdentifier] -- Gets server info on quest
	local PlayerTable = PlayerQuests[Player]
	local PlayerStats = Player:FindFirstChild("leaderstats")
	if Quest and PlayerTable and PlayerTable[QuestIdentifier] and PlayerStats then

		local Reward = Quest["Reward"]
		local RewardType = Quest["RewardType"]
		local StatIncrement = PlayerStats:FindFirstChild(RewardType)

		if Reward and StatIncrement and typeof(StatIncrement.Value) == "number" then
			StatIncrement.Value += Reward
			PlayerTable[QuestIdentifier] = nil
			return true
		else
			warn("Attempted to complete quest "..QuestIdentifier.." for "..tostring(Player)..", but either the reward or the increment was invalid!")
		end
	else
		warn("Attempted to complete quest "..QuestIdentifier.." for "..tostring(Player)..", but either the quest was not found, or the player does not have leaderstats!")
	end
	return false
end

-- Increases quest progress by Increment (Also runs QuestCompleted unless disabled)
function QuestModule.UpdateQuestProgress(Player, QuestIdentifier: string, Increment: number): boolean
	local Quest = Quests[QuestIdentifier]
	local PlayerTable = PlayerQuests[Player]
	local PlrQuest = PlayerTable and PlayerTable[QuestIdentifier]

	if Quest then
		if PlrQuest and typeof(PlrQuest) == "table" then
			local Goal = Quest["Goal"]
			local Progress = PlrQuest["Progress"]
			local UpdatedProgress = (Progress + Increment)
			print(Goal, UpdatedProgress)
			if UpdatedProgress >= Goal then
				print("Quest "..QuestIdentifier.." has been completed by "..tostring(Player))
				if Settings.AutorunComplete then
					QuestModule.QuestCompleted(Player, QuestIdentifier)
				end
				return true
			else
				PlrQuest["Progress"] = UpdatedProgress
				return false
			end
		else
			if Settings.AddQuestIfNotPresent then
				QuestModule.IssueQuest(Player, QuestIdentifier)
			else
				warn("Attempted to increment quest progress for "..tostring(Player).." on quest "..QuestIdentifier..", but player does not have such quest!")
			end
		end
	else
		warn("Attempted to increment quest progress for "..tostring(Player).." on quest "..QuestIdentifier..", but no such quest was found!")
	end
	return false
end

Players.PlayerAdded:Connect(QuestModule.AddPlayer)
Players.PlayerRemoving:Connect(QuestModule.RemovePlayer)

for i,v in pairs(Players:GetPlayers()) do -- Eliminates race condition
	QuestModule.AddPlayer(v)
end

return QuestModule
3 Likes

Solution: ModuleScript. You have to make it server sided so ,presumably, put it in serverstorage. The UI part should be handled by a LocalScript. To make it more organized, i hav 2 module scripts, one for quest data and another called QuestService.

QuestData:

local Module = {}

Module.Quests = {
    ["Kill Zombie"] = {
        RequiredKills = 3, --Amount Of zombies to kill to get reward

        CurrentKills = 0,

        UI = {
            Title = {
                Text = "smth",
                TextColor3 = Color3.fromRGB(0,0,0)
            },

            Desc = {
                Text = "smth",
                TextColor3 = Color3.fromRGB(0,0,0)
            },
        },

        Reward = function(player)
        player.leaderstats.Coins.Value += 200 --Reward, modify it to ur needs.
        end
    }
}

return Module

The Text and Desc is made for handling the GUI and u mite notice that their values match the properties of a TextLabel.

QuestsService:

local QuestData = require(script.QuestData) -- path to above file.

local Module = {}

function Module:AddQuest(datastore, quest)
    for questName, questData in pairs(QuestData.Quests) do
        if questName == quest and not datastore.Quests[questName] then
            table.insert(datastore.Quests, {questName, Kills = 0, RequiredKills = questData.RequiredKills})
            break
        end
    end
end

function Module:AddKills(datastore, questName, killAmount, player)
    for index, quest in ipairs(datastore.Quests) do
        if table.find(quest, questName) then
            quest.Kills += killAmount
            
            if quest.RequiredKills <= quest.Kills then
                QuestData[questName].Reward(player)
                
                table.remove(datastore.Quests, index)

With the above script, ahem module script, adding quests and kills are extremely easy. This was implemented with datastores in mind, and u shud use it too to save quests, and it automatically removes the quest if the currentKills matches the requiredKills. You can use this as a base to create further quests and more efficient systems. If u have any questions, feel free to ask them here.

Have a nice day!

ModuleScripts are usually supposed to go in ServerScriptService rather than ServerStorage.

Also, I’m confused about the QuestService part of your module. How is the datastore section supposed to work? Also, why not just use a dictionary instead of a ton of table.insert, table.find, and looping through the quests constantly?

how would I make the quest be activated? if you know things about the in-game dialogue a want the quest to activate after the players choses ChoiceA which is accept the quest.

As for the location of modulescripts, i use a optimized heirarchy, where I place the modules inside serverstorage and use a custom Loader to require each of them from the server. As for the datastore issye, i agree its not ideal, and thts because its just created from scratch with no testing in studio(i wrote this in mobile), so it is only intended to b a base for a full-on Quests System.

To activate a quest, you require the module and use Module.IssueQuest. For example:

local QuestModule = require(game:GetService("ServerScriptService").QuestModule)

-- to give a quest
QuestModule.IssueQuest(Player, QuestIdentifier)

QuestIdentifier would be a string corresponding to a ​quest, such as "KillZombies"

there are many great youtube videos on this topic, i recommend you try them before coming here as this place sometimes can be of no help.

1 Like

Ok! i’ll try one of the youtube videos and if that doesn’t work then i’ll try these.

1 Like

the videos didn’t work but I changed the quest so basically you interact using a proximity prompt and hold for 2 seconds then a gui would pop up and say “Find the Witches Cat (0/1)” then once you find the cat and click it using a click detector it changes to “You found the witches cat! (1/1)” then it ends and you get a badge and 150 candy? how do I make it do that and what would the script be?

Wdym? Spooks video on it was amazing, i recommend u check this out.

1 Like

Nevermind this! I found a working video.