-
-
Notifications
You must be signed in to change notification settings - Fork 120
Quests
Quests consist of a series of tasks (stages) that the player must complete in order to finish the quest. A completed quest can grant multiple rewards, such as items, ability to access new areas (through dynamic regions), skill experience, new spells/abilities (coming soon), or access to an item. The quests consist of two files, the quest JSON, and the quest TypeScript file. The JSON contains all the data for the quest, such as the name displayed to the player, the description, the requirements, stages, etc. The TypeScript file of the quest is a subclass of the Quest
class, which contains the code logic for handling quests. Functions within the subclass can be overriden from special scenarios and situations (e.g. we want doors to have a different message when they cannot be accessed).
The following is the absolute bare minimum needed to add a quest. Note, the stages dictionary is empty, resulting in the quest being marked as completed when implemented into the game.
Firstly, create a new JSON file (name should be all lowercase) in packages/server/data/quests/
.
testquest.json
{
"name": "Test Quest",
"description": "This is a demo short description.|This is a demo long description.",
"rewards": ["A test reward", "420 Smithing experience"],
"stages": {}
}
Secondly, create a TypeScript file for the quest in /packages/server/src/game/entity/character/player/quest/impl
.
import Quest from '../quest';
import Data from '../../../../../../../data/quests/testquest.json';
export default class TestQuest extends Quest {
public constructor(key: string) {
super(key, Data);
}
}
Lastly, you must add the key to the index.ts
in the same directory you're in.
...
import testquest from './testquest';
export default {
...
testquest
}
That's basically it. That will initialize an empty quest, and when you log in, the quest will display as completed (because there are no stages).
For this section we'll be inspecting the Miner's Quest II quest. Note that this quest doesn't contain all the possible values, however we will go over everything not included in this quest below.
{
"name": "Miner's Quest II",
"description": "The miner is at it again, this time he doesn't just need ores.|The miner requires your help once again, this time his sketch creations include bars of metals and stuff like that. You should probably go see what he's up to and whatnot.",
"rewards": ["Access to the mining cave"],
"skillRequirements": {
"mining": 30
},
"questRequirements": ["minersquest"],
"stages": {
"0": {
"task": "talk",
"npc": "miner",
"text": [
"Oh it's you again, seems you didn't learn your lesson last time.",
"Anyway, what do you say you help me out again, this time I need",
"to do something with the ores I've collected.",
"But I'm not gonna give them to you, you have to actually go",
"and collect ores again and this time smelt them.",
"Doesn't that sound like an amazing deal where only I benefit?",
"Tell you what, you help me out and I will give you access to",
"my special secret cave where only miners are allowed.",
"Okay so anyway, I need you to bring me 5 tin bars, 5 copper bars."
],
"completedText": ["Please hurry, the time is ticking!", "Bring me 5 tin bars and 5 copper bars."]
},
"1": {
"task": "talk",
"npc": "miner",
"hasItemText": [
"Oh wonderful, but unfortunately it's not enough.",
"I want you to bring me 5 bronze bars."
],
"completedText": ["Please hurry, the time is ticking!", "Bring me 5 bronze bars."],
"itemRequirements": [
{
"key": "tinbar",
"count": 5
},
{
"key": "copperbar",
"count": 5
}
]
},
"2": {
"task": "talk",
"npc": "miner",
"hasItemText": [
"Okay fine, I think I'm done sending you on errands.",
"For now that is, who knows maybe I'll need you in",
"the future for redundant tasks like these.",
"Thank you for your help, I guess you can use my cave now."
],
"itemRequirements": [
{
"key": "bronzebar",
"count": 5
}
],
"popup": {
"title": "Quest completed!",
"text": "@green@You have just finished @crimson@Miner's Quest II@green@. You've been awarded @crimson@Access to the mining cave@green@!",
"colour": "#33cc33"
}
}
}
}
Using the Miner's Quest II, the keys serve the following functionalities:
-
name
- This is the name of the quest that the player sees, as such, make sure it is properly formatted. -
description
- The description component contains both the short and long description. The first half of the | delimiter is the short description, and the latter is the long description. -
rewards
- These are rewards we want to show to the players (as seen in the quest log). -
skillRequirements
- A dictionary containing the skills the player must have before starting the quest. If the player doesn't fulfill these requirements there is no quest prompt. The key is the name of the skill in lower case (seemodules.ts
) for an enum of all skills, and the value is the level required. -
questRequirements
- An array containing the keys of the quests needed to start the current quest. This must correspond with the key of the quest as per the TypeScript file. -
stages
- A dictionary containing the stages of the quest. The key is the number of the stage (0, 1, 2), and the value is an object containing data for each stage (see below).
The following are values that are not present in the quest example but can be used:
-
hideNPCs
- A dictionary of NPC keys that we want to hide. The key is the key of the NPC we're hiding (seenpcs.json
), and the value can be eitherafter
/before
. If set to after, the NPC with the respective key will be hidden after the quest is completed, and vice versa forbefore
. -
difficulty
- Parameter used for organizing quests in the quest log by difficulty.
Each stage within the list of stages can contain a plethora of variables. Within the stage object a task
key and value must be provided, this determines the necessary task to progress through the quest stage. There are currently 4 available tasks that you can choose, these are talk
, kill
, door
, and tree
. Modifying the quest.ts
and including a quest checking in the handler for an action is a way to add additional stages. However that is outside the scope of this document.
-
talk
- The talk task requires that the player communicate with an NPC to progress the quest. -
kill
- The kill task requires that the player kill a certain amount of mobs to progress. -
door
- The door task requires that the player goes through a door to progress. The door MUST have a quest property and a stage property that belongs to the quest (in the Tiled map editor). -
tree
- The door task requires that a player cuts a specific tree and a certain amount in order to progress.
Depending on the task specified, different variables may be specified.
Looking at the example above with the Miner's Quest, let's go through the available properties for the talking task and what they each mean:
talk
task:
-
npc
- The key of the NPC that the player must interact with (seenpcs.json
for a list of keys). -
text
- This is the array of text that the player sees from the NPC when the stage has not been started. The player must go through every index in the array, and once they have exhausted the dialogue, the stage will progress (if they fulfill the requirements of the stage). -
completedText
- This is the array of text that the NPC will repeat when the player attempts to talk to them after completing the stage. In the event that the NPC requires the player bring them an item and they do not have that item, this is the fallback text when they player talks to the NPC again. -
hasItemText
- The array of text that will be displayed by the NPC if the player has the requirements required for the stage. -
itemRequirements
- An array of item dictionaries that represents the items the player must have in their inventory in order to progress to the next stage (each dictionary object must contain a key and count). In the above example, the player must have 5 tin bars, and 5 copper bars, the key for items can be found initems.json
. -
popup
- The popup is displayed when the player finishes the quest. It is recommended to just copy and paste the format and use other quests as examples.
Additional parameters that are not specified:
-
itemRewards
- Same formatting asitemRequirements
. These are items that are granted to the player upon completion of the stage. If the player does not have enough space in their inventory to store these items, they will not be able to complete the quest.
kill
task:
-
mob
- Used to specify the key for a mob if the task is a kill task. -
mobCountRequirement
- The number of mobs with the key specified that the player must kill to complete the stage. The progression is done automatically once they've defeated the required mobs.
Example of a mob requirement stage:
"3": {
"task": "kill",
"mob": ["santa"],
"mobCountRequirement": 1
},
tree
task:
-
tree
- The key of the tree that the player must cut in order to progress to the next stage (see trees.json). -
treeCount
- The amount of trees the player must cut in order to progress to the next stage.
For a door task, you must only specify the task as a door, and set the door object in the Tiled map editor to contain the quest key and stage required. Here is an example:
In the above example the player must be at stage 1 for the quest tutorial
in order to be able to pass through the door. If the player does not meet the requirements, they will not be able to pass through. If the player is at stage 1, they will be able to pass through the door and the quest will progress to stage 2.
Additionally, the following is an example of overriding the door messages from evilsanta.ts
:
protected override handleDoor(door: ProcessedDoor, player: Player): void {
log.debug(`[${this.name}] Door: ${door.x}-${door.y} - stage: ${this.stage}.`);
if (this.stage === 0) return player.notify(`misc:WHY_GO_THERE`);
// If the player is not on the correct stage, don't let them through.
if (this.stage < door.stage) return player.notify(`misc:DONT_THINK_GO_IN`);
// Handle door requiring an item to proceed (and remove the item from the player's inventory).
if (door.reqItem) {
let count = door.reqItemCount || 1;
if (!player.inventory.hasItem(door.reqItem, count))
return player.notify('misc:NO_KEY_DOOR');
player.inventory.removeItem(door.reqItem, count);
player.notify(`misc:DOOR_KEY_CRUMBLES`);
}
// Let the super class handle the rest.
super.handleDoor(door, player);
}
The default handleDoor
message only checks the stage requirements and returns a default text. In this example, we override the function to substitute our own messages under certain conditions, such as, if the player is at stage 0 (i.e. they did not start the quest) we will send a notification "Why would I go in there?" when they attempt to pass through the door prematurely. In the event that the quest has started, but the player is not at the correct stage, we will send a notification "I don't think I should go in there just yet..." Lastly, we have added a reqItem
and reqItemCount
check (both specified in the door object in a similar fashion as above). These check that the player has the required item and amount in his inventory in order to be able to pass through the door. Note that these two variables can be applied to non-quest doors as well, this is just an override to display a different message.