Quest System
Contents
- 1 The vanilla quest system
- 1.1 Interface
- 1.2 Quest Task Actions
- 1.2.1 ConsumeItems
- 1.2.2 ObjVar
- 1.2.3 SetObjVar
- 1.2.4 Resource
- 1.2.5 ObjVarValue
- 1.2.6 SetObjVar
- 1.2.7 Dialog
- 1.2.8 NPCDialog
- 1.2.9 PlayEffect
- 1.2.10 CreateItem
- 1.2.11 RandomItem
- 1.2.12 CreateItems
- 1.2.13 ConsumeItemsWithObjVar
- 1.2.14 CreateObjects
- 1.2.15 SendMessageToNPC
- 1.2.16 SendMessageToPlayer
- 1.2.17 Hint
- 1.3 Quest Callbacks
- 1.3.1 NPCInteraction
- 1.3.2 HaveFaction
- 1.3.3 HaveItemWithObjVar
- 1.3.4 HasItemInList
- 1.3.5 HasPet
- 1.3.6 KnowRecipe
- 1.3.7 Items
- 1.3.8 ObjVar
- 1.3.9 ObjVarValue
- 1.3.10 ObjVarValueMin
- 1.3.11 Resource
- 1.3.12 ExitRegion
- 1.3.13 EnterRegion
- 1.3.14 EnterRegions
- 1.3.15 CompleteOnEnter
- 1.3.16 HaveSkillLevel
- 1.3.17 HasCombatStance
- 1.3.18 GainSkillLevel
- 1.4 Making a quest: The Questtable Structure
The vanilla quest system
File: base_quest_sys.lua Version: 0.3.3
The quest system allows you to create a series of tasks the player has to follow.
It is possible to jump to any given task at any given time.
Advancing from task to task can be done by fullfilling the completion conditions for a task or externally by sending messages.
Interface
The interface consists of messages being sent to the player. There are 4 important messages:
- StartQuest
- AdvanceQuest
- UpdateQuestUI
- FinishQuest
Starting a New Quest
- Assigns a new quest to the player.
- By chosing the starting task different entry points into the assigned quest are possible
player:SendMessage("StartQuest",questName,startingTask)
example from E:\ShardsServer\PublicServer\Build\base\scripts\ai_npc_alchemist.lua line 229:
user:SendMessage("StartQuest","MageIntroQuest")
Advancing a quest
- Will make the quest jump to any given task.
- Very powerful, can create branching quests, quests with multiple endings, etc.
- If prerequisiteTask is set, the user must be on this task in order to advance
player:SendMessage("AdvanceQuest",questName,questTaskName,prerequisiteTask)
example from Build\base\scripts\ai_catacombs_stranger_worshiper.lua line 382
user:SendMessage("AdvanceQuest","CatacombsStartQuest","TalkToSomeone","Investigate")
(UNUSED) Set the active (tracked) quest in the Quest Tracker UI
- Changes the quest tracker to the Quest "questName" and updates the Tracker
player:SendMessage("SetActiveQuest",questName)
Example: This Message is never used in Vanilla code, but the handler function is called from other handlers.
(BARELY USED) Refresh Quest Tracker UI with last active quest
- This one just calls SetActiveQuest with the last active quest from ObjVar
player:SendMessage("RefreshQuestUI")
example: build\base\scripts\player.lua line 1137
this:SendMessage("RefreshQuestUI")
This also is the only use inside the Vanilla code - so it's just for login handling.
Update Quest Tracker UI
- Redraws the Tracker with the given Quest "questName"
- When "isTask" is true the given Quest is handled as task. Must be set correctly or bad stuff will happen.
player:SendMessage("UpdateQuestUI",questName,isTask)
example from build\base\scripts\base_quest_sys.lua line 551
this:SendMessage("UpdateQuestUI",questName)
- This is used inside the function which initiates a new quest of name <questName>.
- This message can be used to to update the QuestTracker.
(UNUSED) Fail (=delete) a quest
- This simply removes the quest from the players quest table.
Caveat: Just removing the quest data from the table does not guarantee proper undoing of a quest. There might be side effects from OBjVars Set, Quest Item given, etc.
player:SendMessage("FailQuest",questName)
- example: none in the entire Vanilla Codebase.
- Use FinishQuest below to end a quest
Finish Quest
- Finishes a quest and runs finishing Actions if "runFinishActions" is true
player:SendMessage("FinishQuest",questName,runFinishActions)
- example #1 from Build\base\scripts\ai_npc_catacombs_priest.lua line 425:
user:SendMessage("FinishQuest","CatacombsStartQuest")
- example #2 from Build\base\scripts\ai_npc_catacombs_fruit_vendor.lua line 188:
user:SendMessage("FinishQuest","FruitQuest",true)
Here the "true" for "runFinishActions" is used.
Quest Task Actions
QuestTaskActions = {}
Functions that get called when a task is finished or initiated in
- StartActions or
- FinishActions
ConsumeItems
ConsumeItems = function (user,task) backpackObj:ConsumeItemsInContainer(task.TaskConsumeItemList,task.TaskForceConsumeItems)
ObjVar
ObjVar = function (user,task) if task.TaskDeleteObjVar ~= nil and task.TaskDeleteObjVar ~= false then user:DelObjVar(task.TaskObjVarName)
SetObjVar
SetObjVar = function (user,task) user:SetObjVar(task.TaskSetObjVar,task.TaskSetObjVarValue)
Resource
Resource = function (user,task) user:RequestConsumeResource(task.ResourceRequired,task.ResourceAmount,"QuestConsumeResource",this)
ObjVarValue
ObjVarValue = ObjVar,
SetObjVar
SetObjVar = function (user,task) user:SetObjVar(task.TaskSetObjVar,task.TaskSetObjVarValue)
Dialog
Dialog = function (user,task) --throw up dialog NPCInteraction(task.DialogText,nil,user,"Responses",response,task.DialogTitle)
NPCDialog
--THIS IS NOT GUARENTEED TO THROW A DIALOG UP IF YOU'RE OUTSIDE OF RANGE --throw up npc dialog
NPCDialog = function (user,task)--throw up dialog local response = task.DialogResponses or Template:Text="Ok.",handle="Ok" local npc = FindObject(SearchMulti({ SearchMobileInRange(5), SearchTemplate(task.NPCTemplate), })) NPCInteraction(task.DialogText,npc,user,"Responses",response,nil,NPC_SEARCH_RANGE)
PlayEffect
--play an effect
PlayEffect = function(user,task)
this:PlayEffect(task.EffectToPlay) -- No EffectDuration Given !!!!
CreateItem
--AS A RULE OF THUMB, ONLY CALL THIS IF YOU KNOW YOU WILL BE IN PROXIMITY TO A GIVEN NPC --create object lists
CreateItem = function(user,task) local creationTemplate = task.Reward or task.RewardItem or task.TaskItem or task.Item ... give item ... this:SystemMessage("[D7D700]You have received a "..name,"event")
RandomItem
RandomItem = function(user,task) local creationTemplate = task.RewardChoices[math.random(1,#task.RewardChoices)] ... give random item ... this:SystemMessage("[D7D700]You have received a "..name,"event")
CreateItems
--AS A RULE OF THUMB, ONLY CALL THIS IF YOU KNOW YOU WILL BE IN PROXIMITY TO A GIVEN NPC
CreateItems = function (user,task) for i,j in pairs (task.RewardItems) do ... give stuff ...
ConsumeItemsWithObjVar
--AS A RULE OF THUMB, ONLY CALL THIS IF YOU KNOW YOU WILL BE IN PROXIMITY TO A GIVEN NPC
ConsumeItemsWithObjVar = function (user,task) local items = FindObjects(SearchMulti({SearchContainer(backpackObj), SearchHasObjVar(task.ItemObjVarName)} )) ... Destroy All items ...
CreateObjects
--create random mobiles or other random objects CreateObjects = function(user,task)
for i=0,task.SpawnAmount,1 do
CreateObj(task.SpawnTemplates[math.random(1,#task.SpawnTemplates)], task.SpawnLocations[math.random(1,#task.SpawnLocations)],"created")
SendMessageToNPC
--send a message to an object
SendMessageToNPC = function(user,task) local npc = FindObject(SearchMulti({SearchTemplate(task.NPCTemplate)})) npc:SendMessage(task.TaskMessage,task.TaskMessageArgs)
SendMessageToPlayer
--send a message to a player
SendMessageToPlayer = function(user,task) user:SendMessage(task.TaskMessage,task.TaskMessageArgs)
Hint
--show a hint to a player
Hint = function(user,task) user:SendMessage("ShowHint",task.Hint)
Quest Callbacks
QuestCallbacks = { ... callback function list ...}
- Callbacks have <user>,<task> as parameters and additional optional parameters.
- They are used to check Quest/Task entry/exit conditions.
- They return true or false.
NPCInteraction
True when interacting with an NPC made from a given template
NPCInteraction = function(user,task,templateId) if templateId == task.NPCTemplate then return true ....
HaveFaction
True when having a minimum reputation with a given faction
HaveFaction = function (user,task) if (GetFaction(user,task.FactionName) >= task.FactionAmount) then return true ....
HaveItemWithObjVar
True when having an item with a certain ObjVar set. ("Flagged Item")
HaveItemWithObjVar = function (user,task) ... local items = FindObjects(SearchMulti({{ SearchContainer(backpackObj), SearchHasObjVar(task.ItemObjVarName)}} ))
HasItemInList
True when having an item with a template from a given list of item templates.
HasItemInList = function (user,task) ... for i,j in pairs(items) do for i,itemId in pairs(task.ItemList) do .... if (j:GetCreationTemplateId() == itemId) then return true
HasPet
True when having any pet.
HasPet = function (user,task) if (user:HasObjVar("Minions")) then return true
KnowRecipe
True when knowing a given recipe.
KnowRecipe = function(user,task) if (HasRecipe(user,task.TaskRecipe)) then return true
Items
True when having the required amount of a list of items. This allows doing complex collector quests.
Items = function (user,task) return user:GetEquippedObject("Backpack"):HasItemsInContainer(task.TaskItemList)
ObjVar
True if the user has a given ObjVar (ObjVar basically is a flag)
ObjVar = function (user,task) if (user:HasObjVar(task.TaskObjVarName)) then return true
ObjVarValue
True if the user has a given ObjVar with a given value (ObjVar basically is a flag)
ObjVarValue = function (user,task) if (user:GetObjVar(task.TaskObjVarName) == task.TaskObjVarValue) then return true
ObjVarValueMin
True if the value of a given ObjVar is at least as big as the given threshold
ObjVarValueMin = function (user,task) if (user:GetObjVar(task.TaskObjVarName) >= task.TaskObjVarValue) then return true
Resource
True when the player has the required amount of a given resource.
Resource = function (user,task) if user:GetEquippedObject("Backpack"):CountResourcesInContainer(task.ResourceRequired) >= task.ResourceCount then return true
ExitRegion
True when the player is NOT in a given region. It is not actually leaving ...
ExitRegion = function(user,task)--left region if (not user:IsInRegion(task.TaskRegion)) then return true
EnterRegion
True when the player is in a given region. It is not actually entering ...
EnterRegion = function(user,task)--entered region if (user:IsInRegion(task.TaskRegion)) then return true
EnterRegions
True when the player is in ANY of the given region. It is not actually entering ...
EnterRegions = function(user,task) --left region for i,j in pairs(task.TaskRegions) do if (user:IsInRegion(j)) then return true
CompleteOnEnter
Always returns true ... WTF ???
CompleteOnEnter = function(user,task) return true
HaveSkillLevel
True if the player has reached a minimum skill level in a given skill.
HaveSkillLevel = function (user,task) if (this:GetSkillLevel(task.TaskSkillType) >= task.TaskSkillLevel) then return true
HasCombatStance
True if the player is in a given combat stance.
HasCombatStance = function (user,task) if (this:GetSharedObjectProperty("CombatStance") == task.StanceRequired) then return true
GainSkillLevel
True if the player has levelled in a given skill.
GainSkillLevel = function(user,task) local lastSkillLevel = mLastSkillLevel or this:GetSkillLevel(task.TaskSkillType) mLastSkillLevel = this:GetSkillLevel(task.TaskSkillType) if (lastSkillLevel < mLastSkillLevel) then return true
Making a quest: The Questtable Structure
File: data_quests.lua Version: 0.3.3
The Quests Main Table:
AllQuests = { HomoSapiensQuest={... QuestData ...}, Quest_02={... QuestData ...}, Quest_03={... QuestData ...}, Quest_04={... QuestData ...}, Quest_05={... QuestData ...}, Quest_06={... QuestData ...}, }
A quest inside the Main Quests Table
Here defined outside the main quests table, using the dot notation. That's entirely subject to taste.
AllQuests.HomoSapiensQuest = { QuestName = "HomoSapiensQuest ", StartConditions = {"EnterRegion"}, -- OPTIONAL - See below StartConditionArgs = {TaskRegion = "FollowerHubRegion"}, -- OPTIONAL - See below Description = "This is an example description for a silly quest", StartingTask = "TalkToTheQuestgiverApe", QuestDisplayName = "[ECD905]Do the most stupid quest ever[-]", QuestPlayerScript = "quest_player_starting", -- THIS IS ONLY USED IN THE STARTING QUEST. NO SUCH SCRIPT EXISTS !!! Notify = false, -- THIS IS ONLY USED IN THE STARTING QUEST. Prolly good for nuffin' ~Yorlik Repeatable = true, -- OPTIONAL (makes quest repeatable) QuestCompleteMessage = "You have proven to be a braindead individual which is stupidly following arbitrary instructions. You must be of the species homo sapiens.", Tasks = {... tasklist ...} }
Fields of the Quest Structure
- QuestName = String. Name of the Quest. Is the same like the handle of the Quest inside the Quests table.
- Description = String. Description of the quest. Used in the quest window as description.
- StartingTask = String. Handle of the starting task in the tasklist.
- QuestDisplayName = String. Quest Title shown in the quest tracker.
- Repeatable(optional) = Bool (true/false). Make a quest repeatable if true.
- QuestCompleteMessage = String. When the Quest is completed this text is displayed in the Message popping up.
- StartConditions(optional) = {"EnterRegion"} Callback to use for checking the conditional. See Quest Callbacks above.
- StartConditionArgs(optional) = {TaskRegion = "FollowerHubRegion"} - Given as arguments to the callback. See Quest Callbacks above.
- Tasks = A table holding the individual steps (=tasks) of a quest
The tasklist inside the HomoSapiensQuest Table
Here defined outside the main quest table, using the dot notation:
AllQuests.HomoSapiensQuest.Tasks= { TalkToTheQuestgiverApe = {... Task Data ...}, TalkToTheQuestgiverApeAgain = {... Task Data ...}, TalkToTheQuestgiverApeAgainAndAgain = {... Task Data ...}, TalkToTheQuestgiverApeALastTime = {... Task Data ...}, }
A single task Structure
Here defined outside the tasklist table, using the dot notation:
AllQuests.HomoSapiensQuest.Tasks.TalkToTheQuestgiverApe ={ TaskName = "TalkToTheQuestgiverApe", Description = "Talk to the ale drinking ape in the Tavern", Hint = "Be a Homo Sapiens and behave like one.", DialogText = "You feel unsecure about being a machine or a living being. You should speak to a monkey. Preferably a drunken one.", DialogTitle = "Passing the Turing test...", NextTask = "TalkToTheQuestgiverApeAgain", IsQuestEnding = false, StartActions= {}, -- Starting and Finishing Actions - See Quest Task Actions doc above FinishActions={}, CompletionConditions = { "ObjVar", }, TaskObjVarName = "Intro|TheDrinkingApe", }
Fields inside the task structure
- TaskName = Name of the Task. Same as the handle in the tasklist.
- Description = Short description of the task shown in the quest tracker.
- NextTask = Next task after the current task is finished. Using the "AdvanceQuest" message in the AI/Dialogue System this can be overridden
- IsQuestEnding = (optional) Bool (true/false). Is this task ending the quest?
- CompletionConditions = table. Conditions defining the end of the task. Depends on the Quest Callbacks above.
- TaskObjVarName = Parameter for the callback "ObjVar" in the CompletionConditions. See Callback documentation above.
- Hint = Hint shown by the "Hint" Quest Task Action in Start of Finish Actions
- DialogText = Dialog text for the "Dialog" or "NpcDialog" Quest Task Actions
- DialogTitle = Dialog title for the "Dialog" or "NpcDialog" Quest Task Actions
- StartActions = Starting Actions for the task. See Quest Task Action doc above
example with 2 actions :
StartActions = {"Hint", "SetObjVar"}
- FinishActions = "" Like StartActions, just for for exit ...