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