How to make a quest for each NPC

So, I am making an NPC quest system. I have finished the basic stuff, like NPC talking to you—basically, the chat function with the NPC is finished. But the problem is: How do I make the quest functionality work?

I want each NPC to have a quest, like:

  • “Oh, go bring some boxes.”
  • “Oh, go talk to the other NPC.”

So, I have three main scripts:

  • A server script that sets up a ProximityPrompt for each character in a folder and initializes quest data for each player.
  • When a player triggers the ProximityPrompt, the server script sends a remote event to a local script.
-- here is the server script 
local Characters = game.Workspace:WaitForChild("Characters")
local Promixty = script:WaitForChild("ProximityPrompt")
local Event = game.ReplicatedStorage:WaitForChild("NpcTalk")
local Players = game.Players


Players.PlayerAdded:Connect(function(Player)
	local Folder = Instance.new("Folder",Player)
	Folder.Name = "Quest_Data"
	local Quest = Instance.new("StringValue",Folder)
	Quest.Name = "QUEST"
	Quest.Value = ""
	local QuestStage = Instance.new("IntValue",Folder)
	QuestStage.Name = "QuestStage"
	local QuestFinished = Instance.new("BoolValue",Folder)
	QuestFinished.Name = "QuestFinshed"
	QuestFinished.Value = false
end)


for _,Character in ipairs(Characters:GetChildren()) do
	if Character:IsA("Model") then
		local CLonedPromixty = Promixty:Clone()
		CLonedPromixty.Parent = Character
		CLonedPromixty.Triggered:Connect(function(Player)
			Event:FireClient(Player,Character)
		end)
	end
end






And the client simply calls a function in a module script when the event is fired.

-- here is the local script
local MoudleQuest=  require(game.ReplicatedStorage:WaitForChild("ModuleScript"))
local ScreenUI = script.Parent
local Event = game.ReplicatedStorage:WaitForChild("NpcTalk")
local Player  = game.Players.LocalPlayer
local PlayerCharacter:CharacterAppearance = Player.Character or Player.CharacterAdded
local CharactersFolder = game.Workspace.Characters 
local PlayerHumanoid:Humanoid = PlayerCharacter:WaitForChild("Humanoid")
local function TurnOffAllPromixty(Folder,TurnOffOn)
	local onOff = if TurnOffOn then "On" else "Off"
	print("turn"..onOff.." the Promixty ")
	for _,Character:Model in ipairs(Folder:GetChildren()) do
		local prompet = Character:FindFirstChildOfClass("ProximityPrompt")
		if Character:IsA("Model") and prompet  then
			prompet.Enabled = TurnOffOn
		end 
	end
end


Event.OnClientEvent:Connect(function(Character:Model)
	local Promixty = Character:FindFirstChildOfClass("ProximityPrompt")
	TurnOffAllPromixty(CharactersFolder,false)
	local a = MoudleQuest:SetUpQuest(ScreenUI,Character,Player)
	TurnOffAllPromixty(CharactersFolder,true)

end)


PlayerHumanoid.Died:Connect(function()
	TurnOffAllPromixty(CharactersFolder,true)
end)


Here is the main module script that handles the conversation between the player and the NPC.

-- model script

local sound = script:WaitForChild("استاذ فرقان صوت")
local Tweenservice = game:GetService("TweenService")
local tweeninfo = TweenInfo.new(0.5,Enum.EasingStyle.Back,Enum.EasingDirection.In)
local continueBool = false


local tweeninfos = {
	["ShowUpTween"] = TweenInfo.new(0.5,Enum.EasingStyle.Exponential,Enum.EasingDirection.Out),
	["HideUpTween"] = TweenInfo.new(0.4,Enum.EasingStyle.Exponential,Enum.EasingDirection.In)

}
local CharactersFolder = game.Workspace:WaitForChild("Characters") -- aka where is the character at 
local WorkingTween = false
local module = {
	Characters = {
		["Fox"] = {
			QuestTalkLines = {"Hello there would u like to help me please?","Well there is boxes over there can u bring them to me ?","Testoo:"},
			FinshQuestLine = "Thank here a reward for u",
			WaitingForQuestToFinsh = "soo are u bringing those boxes or not?",
			Decline_Line = "Whatever u are too weak anyway to do it "
		},
		["Leeyo"] = {
			QuestTalkLines = {"Ya how u doin","i only want food 😭","HAHAHAHAH"},
			FinshQuestLine = "Thank here a reward for u",
			WaitingForQuestToFinsh = "soo are u bringing those boxes or not?",
			Decline_Line = "u wont help i hopeless old man >:) "

		},
		["mxxitixx"] = {
			QuestTalkLines = {"Sub my name is mobse","well can u talk to turkish for me?","so do we have a deal"},
			FinshQuestLine = "Thank here a reward for u",
			WaitingForQuestToFinsh = "are you going to talk to him?",
			Decline_Line = "I guess i am going to talk to him "

		},
	}
	,
		
	CharacterSounds = 11,
	SplitTextFunction = function(Text:string,voiceid,TextLabe:TextLabel)
		if typeof(Text) == "string" then

			local SplitText = Text:split("")
			local ReText = ""
			for _,Letter in ipairs(SplitText) do
				if continueBool then
					break
				end
				sound:Play()
				ReText = ReText..Letter
				TextLabe.Text = ReText
				task.wait(0.07)
				sound:stop()

			end
		else
			print("text is not a string ,",typeof(string))
		end
		
	end,
	ShowHideUpQuest = function(Ui:ScreenGui,ShowOrHide:string)
		if ShowOrHide == "show"  and not WorkingTween then
			local Frame:Frame = Ui:FindFirstChild("Frame")
			if Frame then
				local targetPositioin = Frame.Position
				Frame.Position = targetPositioin + UDim2.new(0,0,1,0)
				Ui.Enabled = true
				local Tween = Tweenservice:Create(Frame,tweeninfos["ShowUpTween"],{Position = targetPositioin})
				WorkingTween = true

				Tween:Play()
				Tween.Completed:Wait()
				WorkingTween = false
				print("updated workning tween to false")


				
			end
		elseif ShowOrHide == "hide" and not WorkingTween then
			local Frame:Frame = Ui:FindFirstChild("Frame")
			if Frame then
				WorkingTween = true

				local FrameOrignallPosition = Frame.Position
				local targetPose = Frame.Position + UDim2.new(0,0,1,0)
				local Tween = Tweenservice:Create(Frame,tweeninfos["ShowUpTween"],{Position = targetPose})
				Tween:Play()
				Tween.Completed:Wait()
				Ui.Enabled = false
				Frame.Position = FrameOrignallPosition
				WorkingTween = false


			end
		
		else
			print("error couldn't Know what is ,",ShowOrHide,"it might be cuz there is tween ",WorkingTween)
		end
	end,
	TurnOffAllPromixty =  function(Folder,TurnOffOn)
		local onOff = if TurnOffOn then "On" else "Off"
		print("turn"..onOff.." the Promixty ")
		for _,Character:Model in ipairs(Folder:GetChildren()) do
			local prompet = Character:FindFirstChildOfClass("ProximityPrompt")
			if Character:IsA("Model") and prompet  then
				prompet.Enabled = TurnOffOn
			end 
		end
	end


	,
	ClearQuest = function(Folder:Folder,a,b)
		for _,SomethingQuest in ipairs(Folder:GetChildren()) do
			if SomethingQuest:IsA("StringValue") then
				SomethingQuest.Value = ""
			elseif SomethingQuest:IsA("NumberValue") then
				SomethingQuest.Value = 0
			elseif SomethingQuest:IsA("BoolValue") then
				SomethingQuest.Value = false
			else
				print("something went wrong doing Clear Quest ")
			end
		end
		if a and b then
			print("disconnected a  and b ")
			a:Disconnect()
			b:Disconnect()
		end
	end,
}

function module:SetViewPortFrame(ViewportFrame:ViewportFrame,Character:Model)
	if ViewportFrame and Character then
		print(ViewportFrame,Character)
		for _,Thing in ViewportFrame:GetChildren() do
			if Thing:IsA("Model") or Thing:IsA("Camera") then
				Thing:Destroy()
			end
		end
		local CharacterClone = Character:Clone()
		CharacterClone:PivotTo(CFrame.new(0,0,0))
		local newCamerea = Instance.new("Camera")
		newCamerea.Parent = ViewportFrame
		newCamerea.CFrame = CFrame.new(-0.151978925, 4.86038303, -2.51316714, -0.996948421, 0.0077367234, -0.0776794329, -0, 0.995076835, 0.0991077423, 0.0780637637, 0.0988053083, -0.992040157)
		ViewportFrame.CurrentCamera = newCamerea
		CharacterClone.Parent = ViewportFrame
	else
		print("something is not vaild either viewport frame or the character , Character :",Character," View Port Frame :",ViewportFrame)
	end
end


function module:SetUpQuest(Ui:ScreenGui,Character,Player:Player)
	local CharacterLines = self.Characters
	local CharacterLine = CharacterLines[Character.Name]
	local Frame:Frame = Ui:WaitForChild("Frame")
	local NPCIMAGe = Frame:WaitForChild("NpcImage")

	local NPCLINE = Frame:WaitForChild("NpcLine")
	local continueBtn:TextButton = Frame:WaitForChild("continue")
	local CloseBtn:TextButton = Frame:WaitForChild("CloseBtn")
	local viewPortFrame:ViewportFrame = NPCIMAGe:WaitForChild("ViewportFrame")

	local QuestDataFolder = Player:WaitForChild("Quest_Data")
	local CurrentQuest =  QuestDataFolder:WaitForChild("QUEST")
	self.ShowHideUpQuest(Ui,"show")
	if CurrentQuest.Value == Character.Name.."Quest" then
		if CharacterLine then
			CloseBtn.Text = "Remove Quest "
			continueBtn.Text = "I Will "

			--local event = continueBtn.MouseButton1Click or CloseBtn.MouseButton1Click
				
			self.SplitTextFunction(CharacterLine["WaitingForQuestToFinsh"],nil,NPCLINE)
			self.TurnOffAllPromixty(CharactersFolder,true)
			local a = nil
			local b = nil
			a = continueBtn.MouseButton1Click:Connect(function()
				self.ShowHideUpQuest(Ui,"hide")
				print("Disconnect A event")
				return

			end)
			b = CloseBtn.MouseButton1Click:Connect(function()
				self.ShowHideUpQuest(Ui,"hide")
				CloseBtn.Text = "Shh man"
				self.ClearQuest(QuestDataFolder,a,b)
				print("connected")
				return
			end)
			return
		end
	
	elseif CurrentQuest.Value ~= "" then
		CloseBtn.Text = "Remove Quest"
		continueBtn.Text = "I do "
		self.SetViewPortFrame(viewPortFrame,Character)
		self.SplitTextFunction("Dont you have a quest to Finsh?",nil,NPCLINE)
		local a = nil
		local b = nil
		a = continueBtn.MouseButton1Click:Connect(function()
			self.ShowHideUpQuest(Ui,"hide")
			print("Disconnect A event")
			return

		end)
		b = CloseBtn.MouseButton1Click:Connect(function()
			self.ShowHideUpQuest(Ui,"hide")
			CloseBtn.Text = "Shh man"
			self.ClearQuest(QuestDataFolder,a,b)
			print("connected")
			return
		end)
		return
	elseif CurrentQuest.Value == "" then
		print("Player dont have a quest to do ")
	else
		print("Error something went wrong with the quest ,removel or  if the player talked to the same character after getting the quest")

	end
	
	if CharacterLines then
		local AConnecting :RBXScriptConnection = nil
		local BConnecting :RBXScriptConnection = nil


		self:SetViewPortFrame(viewPortFrame,Character)
		AConnecting = continueBtn.MouseButton1Click:Connect(function()
			if continueBtn.Text == "Accept" then
				self.ShowHideUpQuest(Ui,"hide")
				print("hide ui ")
				print("connected")

			end
			continueBool = true

		end)
		BConnecting = CloseBtn.MouseButton1Click:Connect(function()
			self.SplitTextFunction(CharacterLine["Decline_Line"],nil,NPCLINE)
			self.ShowHideUpQuest(Ui,"hide")
			print("connected")

			return
		end)
		for _,NormalLine in ipairs(CharacterLine.QuestTalkLines) do
			if _ == #CharacterLine.QuestTalkLines then
				continueBtn.Text = "Accept"
				
			end
			self.SplitTextFunction(NormalLine,nil,NPCLINE,continueBool)
			while not  continueBool  do
				task.wait()
			end
			continueBool = false
		end
		

		if CurrentQuest then
			CurrentQuest.Value = Character.Name.."Quest"
			print("set up quest")
			if AConnecting and BConnecting then
				AConnecting:Disconnect()
				BConnecting:Disconnect()
				
			end
		end
		
		return true
	end

end
return module
2 Likes

i really need help with that i am stuck sos :sob:

Its could be complicated but I can give you some framework idea:

you can create a module script for each quest, in the module script (which is called by server), you pass in a player, and just call the function, (preferebly add quest ID to database, so when player rejoins, the quest is there and the module is initialized automatically)
in the module script, when you initialize th quest, you can handle each connection or each event solely inside the module script, for example, talk to another npc… what you can do is set up a conenction that when player promp triggers the target NPC, the quest is complete, give out the rewards and clean up the module(remove connections etc). thats the basic framework I can think of at the moment

2 Likes

thank you so much . i might try this <3