QuestBuilder Library

I’ve been building some quests and making tools to use with AI and NPCs to tell stories. I recently compiled a neat library of all the functions I use for building quests. What do you think? First, here’s a quest I’m working on using the library.

local function warehousepart1()
   q.quicktime(Warehouse,nil,nil,function(player)
   	q.message(nil,nil,player,Warehouse,"Hey! Do you have any experience getting rid of bee hives?")
   	q.message(nil,nil,player,player.Character,"Beehives huh?")
   	q.registerevent(player,Warehouse,{yes=function()
   		q.message(nil,nil,player,Warehouse,"Alright, You're hired! You take out these bee hives and I'll give you 2000 gold. What do you say shake on it?")
   		q.quicktime(Warehouse,"shake hands",nil,function()
   			q.message(nil,nil,player,player.Character,"You got yourself a deal!")
   			q.message(nil,nil,player,Warehouse,"Excellent!")
   			q.defeatenemys(player,script.Parent.BeeHives,{[1]=function()
   				q.message(nil,nil,player,Warehouse,"Look's like you got one!")
   				q.message(nil,nil,player,player.Character,"One down, one more to go.")
   			end,[0]=function()
   				q.message(nil,nil,player,Warehouse,"Great work! Here's your 2000 gold.")
   				q.statmodifier(player,"Gold",2000)
   				
   				q.message(nil,nil,player,Warehouse,"So, my guys who do the warehouse ran out of here for the day due to the bees. Would you move these boxes to the pallets in the shipping bay?")
   				q.registerevent(player,Warehouse,{yes=function()
   					q.message(nil,nil,player,Warehouse,"Amazing! Just get that done and I pay you another 3000 gold.")
   					q.boxtouchpad(script.Parent.ActivationPads,script.Parent.ActivationPads,3,function()
   						q.statmodifier(player,"Gold",3000)
   						q.message(nil,nil,player,Warehouse,"You sure are a hard worker! I can't believe you lifted those boxes like that! Here's your 3000 gold. I appreciate all your help. Stop by again sometime.")	
   					end)
   					
   				end,
   				no=function()
   					q.message(nil,nil,player,Warehouse,"No? well that's too bad...")
   				end,})
   			end,})
   		end)	
   	end,
   	no=function()
   		q.message(nil,nil,player,Warehouse,"No? well that's too bad...")
   	end,})
   end,nil,"talk")
end

The nil,nil in the message is for voiceline timings. The actual module itself is this lightweight masterpiece. All the modules are individual functions that are either doing specific things or firing nodes handling different things abstractions on the message function are huge. It displays text, plays animations.


local QuestModules = {
	-- 📦 Utility
	checkinvent   = game.ReplicatedStorage.GlobalSpells.Questing.Util.checkinvent, -- (player,item,take,reward)
	giveitem      = game.ReplicatedStorage.GlobalSpells.Questing.Util.giveitem,
	rewards     = game.ReplicatedStorage.GlobalSpells.Questing.Util.rewards, -- (keywords,exactword)
	nearestplayer = game.ReplicatedStorage.GlobalSpells.Questing.EFX.nearestplayer, -- (position,distance)
	disappear = game.ReplicatedStorage.GlobalSpells.Disappear,
	touched= game.ReplicatedStorage.GlobalSpells.Questing.Util.Touched,--(Trigger,payloadfunction,singleuse)
	boxtouchpad = game.ReplicatedStorage.GlobalSpells.Questing.Util.MoveBoxActivationPads,--(Blocks,core,activationthreshold,payload) --if duration is nil then return puddle release.
	chopminegoal = game.ReplicatedStorage.GlobalSpells.Questing.Util.ChopDownTreeGoal,--(tree,payloadfunction)
	
	-- 🎵 Dialogue & Atmosphere
	message         = game.ReplicatedStorage.GlobalSpells.Questing.Util.Message2,
	playsound     = game.ReplicatedStorage.GlobalSpells.Questing.Util.playsound,
	music         = game.ReplicatedStorage.GlobalSpells.Questing.Util.PlayAtmosphere, -- (player,keyword)
	cutscene      = game.ReplicatedStorage.GlobalSpells.Questing.Util.Cutscene, -- (player,CutsceneData,ScenePrimaryPart)
	weather       = game.ReplicatedStorage.GlobalSpells.Questing.Util.weather, -- (player,weatherstate)

	-- ⚡ Special Effects
	
	dropitem      = game.ReplicatedStorage.GlobalSpells.Questing.Util.dropitem, -- (player,reward:string,cframe)
	droprandomitem= game.ReplicatedStorage.GlobalSpells.Questing.Util.droprandomitem,
	lootarea      = game.ReplicatedStorage.GlobalSpells.Questing.Util.lootarea, -- (loottable,centerobject,radius)	
	energyball    = game.ReplicatedStorage.GlobalSpells.Questing.EFX.Energyballflash, -- (script)
	statmodifier  = game.ReplicatedStorage.GlobalSpells.Questing.EFX.StatModifier, -- (player,key,value)
	damage        = game.ReplicatedStorage.GlobalSpells.Questing.EFX.Damage, -- (hit:BasePart,Damage:number,Element:string,CombatStyle:number,Attacker:Instance,Humanoid)
	damageBrick   = game.ReplicatedStorage.GlobalSpells.Questing.EFX.DamageBrick, -- (part:BasePart,MaxDamage:number,Persistent:bool,Element:string,CombatStyle:number,Attacker:Instance)
	dropspellcard = game.ServerStorage.ServerModules.DungeonModules.SpellCardSpawn,--(position,specific)
	drippuddle = game.ReplicatedStorage.GlobalSpells.Questing.Util.PuddleUtil,--(Adornee,PuddleKey or "Blood",Duration)--if duration is nil then return puddle release.
	
	-- AI Effects
	faceoff       = game.ReplicatedStorage.GlobalSpells.Questing.Util.FaceOff, -- (target,both,Player,Character)
	path          = game.ReplicatedStorage.GlobalSpells.Questing.Util.pathing,
	simplepath    = game.ReplicatedStorage.GlobalSpells.Questing.Util.SimplePath, -- (subject:model, position)
	follow        = game.ReplicatedStorage.GlobalSpells.Questing.Util.FollowMe, -- (model,part)
	interacttool  = game.ReplicatedStorage.GlobalSpells.Questing.Util.ToolUser, -- (model,object,key,objectprimarypart or nil)
	interact      = game.ReplicatedStorage.GlobalSpells.Questing.Util.interact,	
	inventory =  game.ReplicatedStorage.GlobalSpells.Questing.Util.InventInterface,--(character,ItemString,player:Optional)
	seat =  game.ReplicatedStorage.GlobalSpells.Questing.EFX.InteractWithSeat,--(Seat,Character,ProximityPrompt,player)
	readbook = game.ReplicatedStorage.GlobalSpells.Questing.EFX.ReadBook,--(player,character,wikiresult)--if duration is nil then return puddle release.
	transform = game.ReplicatedStorage.GlobalSpells.Questing.EFX.TransformWrapper,-- (character,formlist)
	enemyai = game.ServerStorage.ServerModules.BasicAISetup,--(model)
	emote = game.ReplicatedStorage.GlobalSpells.Questing.Util.PlayAnimation,--(character,animation,duration,looped)
	-- 🎯 Events & Registration
	registerevent      = game.ReplicatedStorage.GlobalSpells.Questing.Util.registerevent,
	registerchoiceevent= game.ReplicatedStorage.GlobalSpells.Questing.Util.registerchoiceevent,	
	quicktime     = game.ReplicatedStorage.GlobalSpells.Questing.EFX.QuicktimeEvent, -- (model:Instance,key:String,timer:Number,payload:function)

	-- 🗡️ Quest Interactions
	
	battle = game.ServerStorage.GlobalSpells.Questing.Util.Battle,--(model)
	askmathquestion= game.ReplicatedStorage.GlobalSpells.Questing.Util.mathquestion, -- ({player,model},Level)
	findquest          = game.ReplicatedStorage.GlobalSpells.Questing.Util.FindQuest, -- (ProximityPrompt, model, script,playscript,rewards)
	protectquest  = game.ReplicatedStorage.GlobalSpells.Questing.Util.DarknessArise, -- (player,centerPos,Minion,Target,Radius,SpawnAmnt,MaxScale,dialogues)
	chasesequence = game.ReplicatedStorage.GlobalSpells.Questing.Util.ChaseSequence, -- (player,TombRaider,paths,dialogues)
	bossbattle    = game.ReplicatedStorage.GlobalSpells.Questing.Util.BossBattle, -- (player,Goddess,dialogues,custombattlephases)
	defeatenemys  = game.ReplicatedStorage.GlobalSpells.Questing.Util.DefeatEnemys, -- (player,enemys:array)
	flychasesequence = game.ServerStorage.GlobalSpells.Questing.Util.FlyingChase,--(model)
	questgen      = game.ReplicatedStorage.GlobalSpells.Questing.Util.QuestController, -- (player,model,ProximityPrompt,QuestType,ObjectClass,Objective,PersonalityModule,VoicelineObject)
}
local environments={
	envir = game.ReplicatedStorage.GlobalSpells.EnvirMod,
}
local LoadedModules = {}
local QuestFunctions = {}

for i,v in QuestModules do 
	QuestFunctions[i] = function(...)
		if not LoadedModules[i] then
			LoadedModules[i] = require(v)
		end
		return LoadedModules[i](...)
	end
end
for i,v in environments do 
	QuestFunctions[i]=v
end
QuestFunctions.spell=function(key,arguments)
	game.ReplicatedStorage.GlobalSpells.Generate:Invoke(key,arguments)
end
local LoadedModules={}
local QuestFunctions={}
for i,v in QuestModules do 
	QuestFunctions[i]=function(...)
		if LoadedModules[i]==nil then
			LoadedModules[i]=require(QuestModules[i])
		end
		return LoadedModules[i](...)
	end
end
--[[
local q=require(game.ReplicatedStorage.GlobalSpells.Questing.QuestBuilder)

local debounce={}
local envir=require(q.envir)
local player=q.nearplayer(Vector3.new(1,1,1),1000)
q.message(nil,nil,player,player.Character,"What's happening?!")
local Minion=envir.GetNPC("darkness"):Clone()
q.defeatenemys(player,{Minion},{[1]=function()
	q.message(nil,nil,player,player.Character,"Hmm.. I wonder what that was all about.")
	local Minion2=envir.GetNPC("darkness")
	q.protectquest(player,player.Character.PrimaryPart.CFrame,Minion2,player.Character.PrimaryPart,10,4,nil,{
			Opening=function()
				q.message(nil,nil,player,player.Character,"More? What are these things?")
			end,
			--suggested if you have a target
			Protected=function()
				q.message(nil,nil,player,player.Character,"They are not that tough.")
			end,		
		}
	)
end,})
]]
return QuestFunctions

This organized setup is such a blast.

4 Likes

It’s a very functional approach and definitely impressive. I’d probably prefer something data-oriented, using a tree of tables instead of a tree of functions. With that said, this probably has a speed advantage, but it’s immutable and can’t be serialized easily if at all.

The nil,nil,player boilerplate before each line is pretty bad. Why not have the first two arguments placed at the end? As for player, an object-oriented approach would genuinely be ideal here, as you could spawn a quest “instance” that stores a reference to player and other relevant data. You might need to change your functions to be function(context) and indirectly get player from there, but it’d be cleaner—everything would act off the shared context and data can be deduplicated, you could even have methods dynamically generated to quickly message as a certain speaker if you wanted.

I notice there doesn’t seem to be any capability for jumping. You have two identical “no” options, and although they aren’t that bad and you could re-use the function, being able to re-use the same node in the tree would be better.

Is it trolling?
@1kaelen1 I think you have a concurrent; your monopoly stands no chance :scream: DEFEND IT :crown:

Holy ChatGPT :skull: you could’ve at least cached variable :sob:

OH, THIS COMBUSTIBLE LEMONS
Why don’t you cache services at least :fearful:

What in Skibidi Toilet is that? :scientist: :thinking:
Is it the real sigma? :flexed_biceps:

Module Loader in big 2025 :wilted_flower:

Anyway, jokes aside, is it really that bad.
You should learn everything from the start
Here is a good way to understand bytecode:

1 Like

The nil,nil That’s actually for the voiceline timings. I’ve definitely been considering caching the player but, it’s also important to have a dynamic player. A lot of these functions fire nodes and just use specifc methods and the function is for reducing the amount of time needed to write when building quests. I posted it because it’s great. But the choice events, one is a two choice event and the other can handle up to 6 choices. These are all just modular functions I’ve used and use for streamlining creation of quests.
The huge benefits of modularization being that if you upgrade or bug fix the method, all the quests get the update!
I’ve been making a lot of quests so making this was just to reduce the headache. I posted about it cause I was having fun with it.

It’s very performant because the big stuff is in a node that get fired. the message function is designed that way for my personal preference when writing voiceline timings.
But I just wrote an algorithm that makes a tournament

This was difficult. But this bad boy is about to go right in my quest builder library. It’s mwah chef’s kiss. Could be a bit better thought maybe reduce the x scale of the brackets.
Here’s a incomprehensible snippet.

local TournamentWinner=nil
	for index=1, #roundvariables do 	
		local rounds=roundvariables[index]
		--1st round of the tournament
		local winners={}
		for match,contestants in rounds do 
			local winner=nil
			--1st battle of the tournament
			--testing
			winner=contestants.contestants[math.random(1,2)]		
			print(winner.Name)
			table.insert(winners,winner)
		end
		local nextround=roundvariables[index+1]
		if #winners==1 or #winners==0 then
			TournamentWinner=winners[1] or nil
		return winners
		elseif nextround then
		local nextroundcontestants={}
		for i = 1, #winners, 2 do
			table.insert(nextroundcontestants, {winners[i], winners[i+1]})
		end
		for i,v in nextroundcontestants do 
			roundvariables[index+1][i]["gui"][1].Text=v[1].Name
			roundvariables[index+1][i]["gui"][2].Text=v[2].Name
			roundvariables[index+1][i]["contestants"]=v
		end
			
		end
	end

	return TournamentWinner

I was considering posting the full script in community resources. What do you think?

Also it works because, again they are all lightweight modules. Mainly firing bindable function nodes in my game. This is just designed to make it take less brain cells to harness all of the glorious functions. So I don’t have to pick the function out of a basket and require everytime i make a new quest scene