§ How to · with Picoo
How to make a Roblox quest system (with AI)
By Sametcan Tasgiran, Founder & Developer·Published ·Updated
A Roblox quest system needs four pieces: quest definitions, progress the server tracks, a quest log where players accept and claim, and saves that survive a rejoin. Ask Picoo for a quest system in a new or nearly empty place and its quest recipe builds all four, with three starter quests and a log that opens on Q.
9 files · 1219 lines · one recipe call. 852 of those lines are PicooFeedback, the shared module behind the reward popups.
Quests in one module
ReplicatedStorage.Quests lists each quest's title, objective, goal and reward. The starter set is First Steps (walk 100, 50 Coins), Slayer (kill 10, 200 Coins) and Collector (collect 25, 150 Coins).
Server-only progress
Progress moves only when server code calls _G.QuestServer.report(player, objective, amount). The one remote the server listens to carries accept and claim requests, nothing else.
Quest log on Q
A panel on the right of the screen lists every quest with its progress and one button: Accept, In progress, Claim, or Done. ResetOnSpawn is off, so the log keeps working after the player dies.
Claims pay once
A claim marks the quest claimed before the reward is added, and a per-player lock drops a second request that arrives mid-claim, so a double click cannot pay twice. After a failed load on a live server, claims are refused for the session, so a quest claimed earlier cannot pay again; in a Studio play test quests work but are not saved.
Rewards and feedback
Claiming adds the reward to the Coins IntValue in the player's leaderstats and shows a '+200 Coins' popup. Reaching a goal plays a 'SLAYER COMPLETE' popup with a sound and a burst of sparks.
Saved per player
Progress and the accepted and claimed flags save to the Quests_v1 DataStore when a player leaves and when the server shuts down, unless that player's load failed. On join, saved rows are checked against the current quest list.
Files Picoo ships for this prompt
9 files · 1219 lines · one recipe call
ReplicatedStorage/Quests
ModuleScript with the quest list: title, objective, goal and reward for each quest.
29 lines
ServerScriptService/QuestServer
Per-player quest state, accept and claim handling, DataStore load and save, and the QuestServer.report function.
246 lines
StarterPlayer/StarterPlayerScripts/QuestClient
Draws the quest log from the server's snapshot, sends Accept and Claim clicks, and toggles the log with Q.
79 lines
StarterGui/QuestGui
ScreenGui with the log panel, a scrolling list and a row template (10 instances).
instance
ReplicatedStorage/Remotes
Folder for the quest remotes.
instance
ReplicatedStorage/Remotes/QuestSync
Server → client quest snapshot.
instance
ReplicatedStorage/Remotes/QuestAction
Client → server accept and claim requests.
instance
ReplicatedStorage/PicooFeedback
Shared game-feel module behind the reward popup and the quest-complete effect.
852 lines
StarterPlayer/StarterPlayerScripts/PicooFeedbackClient
Loads the client half of PicooFeedback for every player.
13 lines
Sample output: ServerScriptService/QuestServer
--------------------------------------------------------------------
-- SERVER API — this is the ONLY way progress moves.
--------------------------------------------------------------------
local QuestServer = {}
--- Call from server game logic: QuestServer.report(player, "kill", 1)
function QuestServer.report(player: Player, objective: string, amount: number)
local s = state[player.UserId]
-- No progress after a failed load on a live server: it could never be saved or claimed.
if not s or not (loaded[player.UserId] or playTest[player.UserId]) then return end
local changed = false
for id, def in Quests.Defs do
if def.objective == objective then
local e = entry(player, id)
if e and e.accepted and not e.claimed and e.progress < def.goal then
e.progress = math.clamp(e.progress + math.floor(amount), 0, def.goal)
changed = true
if e.progress >= def.goal then
-- The moment the objective is MET, not the claim button that
-- follows it. Target is nil on purpose: the only part we could
-- pass is the player's own root, and a reveal resizes what it is
-- given.
Feedback.reveal(nil, "epic", {
player = player,
label = def.title .. " COMPLETE",
position = originOf(player),
color = Color3.fromRGB(255, 214, 110),
})
end
end
end
end
if changed then push(player) end
end
_G.QuestServer = QuestServerBuilding a Roblox quest system
Quest systems are the backbone of RPG-style Roblox games, and the usual way they break is that the client decides how far along a quest is. A RemoteEvent that accepts "I killed 5 zombies" works as a quest-complete button for anyone running an exploit. Picoo's quest recipe keeps every count on the server.
It writes three quest scripts. ReplicatedStorage.Quests is the data: each quest has a title, an objective name, a goal and a reward. ServerScriptService.QuestServer owns every player's quest state and exposes one function for game logic, _G.QuestServer.report(player, objective, amount). QuestClient draws the log from snapshots the server sends and keeps no counts of its own.
Players open the log with Q. Each row shows the quest's progress and one button: Accept, In progress, Claim, or Done. Claiming is where double payouts usually happen, because two requests in the same frame can both see an unclaimed quest. QuestServer marks the quest claimed before it adds the reward and holds a per-player lock while it works, so the second request is dropped.
The reward goes into the player's leaderstats Coins with a '+200 Coins' popup, and reaching a goal plays a quest-complete popup with a sound and sparks. State saves to the Quests_v1 DataStore when a player leaves and when the server shuts down. On join, saved progress is checked against the current quest list, so a quest you delete from the module does not linger in the log. If the load fails, the session is never saved, so a DataStore outage cannot replace a player's saved quests with an empty log, and on a live server the log shows a rejoin message instead of Accept and Claim buttons, so a quest claimed in an earlier session cannot pay twice. In a Studio play test of an unpublished place (or with Studio API access off), where every load fails, quests work but are not saved.
The part you add is the wiring: a report call wherever an objective happens, such as an enemy death in your combat script or a pickup in your collection script. NPC quest givers and quest chains are follow-up prompts built on the same QuestServer; daily quests with a reset timer come from a separate add-on with its own save.
See more on the Luau generator, the AI Luau script writer, the 3D model generator and the game builder, read how Picoo compares with ChatGPT, or browse the full blog.
Frequently asked
How do I add a new quest?+
Add an entry to Quests.Defs in ReplicatedStorage.Quests with a title, an objective name, a goal and a reward. QuestServer and the log both read that table, so no other script changes. You can also list your quests in the prompt, up to 30, and the recipe writes them in.
How does a quest's progress go up?+
Server code calls _G.QuestServer.report(player, objective, amount), for example _G.QuestServer.report(player, 'kill', 1) when an enemy dies. Every accepted, unclaimed quest with that objective moves up, capped at its goal. Add those calls where each objective happens in your game, or ask Picoo for them in a follow-up prompt.
Can players cheat quest progress?+
Progress lives in a table on the server. The only remote the server listens to, QuestAction, carries 'accept' or 'claim' and a quest id. A claim is checked against the server's own count and pays only when the quest is accepted, complete and not yet claimed.
How is progress saved?+
QuestServer saves each player's progress with SetAsync to the Quests_v1 DataStore when they leave, and for every player when the server shuts down. A failed save logs a warning. On join, saved progress is clamped to each quest's goal, and quests no longer in the list are dropped. If that load fails, a warning is logged and the player's quests are not saved for the rest of the session, so an empty log never overwrites their saved progress. On a live server the log tells them to rejoin, and accept and claim do nothing until they do, so a quest they already claimed cannot pay a second time. In a Studio play test of an unpublished place (or with Studio API access off), where every load fails, quests work but are not saved.
Where do the Coins come from?+
Rewards go into an IntValue named Coins in the player's leaderstats. Picoo's coin-save recipe creates that value and saves it, so asking Picoo to save player coins sets it up. The currency name is a parameter if your game uses another one.
Can quests have NPC givers, chains or daily resets?+
Ask for them in a follow-up prompt. In the recipe every quest is available from the log from the start; an NPC who hands out a quest or a quest that unlocks another is written for your game on top of QuestServer. Daily quests with a reset timer come from a separate add-on that keeps its own save and does not use QuestServer.
Related Picoo prompts
Roblox dialogue system
8 files · 237 lines · one recipe call. A starter NPC named Sage with a three-node tree; the server checks every reply.
Roblox daily rewards
Example output: 5 files · 170 lines. A 7-day cycle in this example; the model writes the code for your request, so the files and cycle can differ.
How to use Roblox DataStore the right way
1 file · 155 lines · one recipe call. Saves one number per player; name the stat, its starting value and the autosave interval in the prompt.