Repository navigation
Quest
There's 4 components to the quest system, the static Quest class, the QuestGiverComponent, the QuestDisplay component and the different types of quest classes themselves. The static Quest class is responsible for keeping track of all the QuestGiverComponent components in the game through the uniqueNPCID arrayList, keeping track of all the quests that are in the game through the XJumpTracker arraylist (where X represents that quest-type in particular) and keeping track of wheither a QuestGiverComponent has already been allocated a quest.
Keep in mind that at present, a QuestGiverComponent component can only have 1 quest assigned to it at a time.
Firstly, a NPC Entity is given a QuestGiverComponent, which in its constructor, sends a function call to the static Quest class and is given a uniqueNPCID and given space in questActiveForNPCID, uniqueNPCID and XQuestTracker (where X represents all of the quest-types). This uniqueNPCID is what identifies this QuestGiverComponent to the static Quest. Here's how this plays out in code:
public class QuestGiverComponent extends Component {
public int uniqueNPCID;
public QuestGiverComponent(){
uniqueNPCID = Quest.giveOutUniqueNPCID();
}
...
Which makes a call to Quest which looks like:
public class Quest {
private static ArrayList<Integer> uniqueNPCID = new ArrayList<Integer>();
//The unique NPCID lets us know if the NPC has a quest is active.
private static ArrayList<Boolean> questActiveForNPCID = new ArrayList<Boolean>();
//Quest trackers
private static ArrayList<JumpQuest> jumpQuestTracker = new ArrayList<JumpQuest>();
...
//Statistics tracker
private static int globalJumps = 1;
...
public static int giveOutUniqueNPCID(){
int uniqueID = uniqueNPCID.size();
uniqueNPCID.add(uniqueID);
questActiveForNPCID.add(false);
//Add space for a possible jump quest later on.
jumpQuestTracker.add(uniqueID, null);
...
return uniqueID;
}
To log a quest, the QuestGiverComponent calls logXQuest (where X is the quest-type) which calls the static Quest class which allocates a quest.
Here's how this looks in code (we'll be using the JumpQuest as an example of a quest-type:
public class QuestGiverComponent extends Component {
...
public boolean logJumpQuest(String Reward, int jumpsToDo){
if(Quest.logJumpQuest(uniqueNPCID, jumpsToDo)){
return true;
}else{
return false;
}
}
Note: Reward is not included right now and will be included later on. Here's what Quest.logJumpQuest(uniqueNPCID, jumpsToDo) makes reference to in the code.
public class Quest {
...
public static boolean logJumpQuest(int npcID, int jumpsToDo){
if(questActiveForNPCID.get(npcID)){
return false;
}else {
jumpQuestTracker.set(npcID, new JumpQuest(globalJumps,jumpsToDo));
questActiveForNPCID.set(npcID, true);
return true;
}
}
...
As we can see, the static Quest class check if the QuestGiverComponent as has a quest active and if not, allocates them a quest through the jumpQuestTracker using their uniqueNPCID.
Here's a UML sequence diagram to help you understand better:

What happens here is that the QuestGiverComponent makes a call to the static Quest class and that class makes a call to the QuestGiverComponent's allocated quest which recursively returns the progress amount as a percentage. Here's how that looks in code (taking the JumpQuest as a example).
public int checkJumpQuestComplete(){
if(Quest.checkJumpQuestComplete(uniqueNPCID)==-1){
throw new NullPointerException("A QuestGiver component tried to call checkJumpQuestComplete when " +
"there isn't a quest to check the progress of i.e. it returned null");
}else{
return Quest.checkJumpQuestComplete(uniqueNPCID);
}
}
And here's what's happening with Quest.checkJumpQuestComplete(uniqueNPCID) in Quest:
public static int checkJumpQuestComplete(int npcID){
if(jumpQuestTracker.get(npcID)!= null) {
return jumpQuestTracker.get(npcID).checkQuestProgress();
}else{
//There is no jumpQuest to check because it wasn't set up
return -1;
}
}
The static class Quest makes a call to the JumpQuest instance that is accessed by using the uniqueNPCID of the QuestGiverComponent. Here's what the function jumpQuestTracker.get(NPCId).checkQuestProgress() looks like from the perspective of the JumpQuest object.
public class JumpQuest {
...
//Gives out the quest progress in a percentage
public int checkQuestProgress(){
if((globalJumps/jumpsToDoTotal)*100 >100){
//To avoid giving out over 100%
return 100;
}
return (int) ((globalJumps/jumpsToDoTotal)*100);
}
...
}
As we can see, the quest does some mathematics to turn the progress into a percentage and returns it recursively to the QuestGiverComponent.
This requires the QuestGiverComponent to call the static Quest class to clear a specific quest type. Here's what it looks like in code, keeping in mind that this is looking at JumpQuest specifically in this example.
public class QuestGiverComponent extends Component {
...
public void clearJumpQuest(){
Quest.clearJumpQuest(uniqueNPCID);
}
And how this looks in the static Quest class is:
public static void clearJumpQuest(int npcID){
jumpQuestTracker.set(npcID,null);
questActiveForNPCID.set(npcID,false);
}
The QuestGiverComponent is now able to take on a new quest because it's old one has been cleared.
The QuestDisplay component creates the UI background through the refreshQuestTable function while the component checks every update for new quests to display and draw onto the box. Here's how that looks in code.
/** Updates the quest list and handles the quest menu toggle. */
@Override
public void update() {
jumpQuestsToDisplay = Quest.getJumpQuests();
enemiesKilledQuestsToDisplay = Quest.getEnemiesKilledQuests();
goldSpentQuestsToDisplay = Quest.getGoldSpentQuests();
shieldsCollectedQuestsToDisplay = Quest.getShieldsCollectedQuests();
refreshQuestTable();
}
/** Refreshes the completed and remaining quest sections. */
private void refreshQuestTable() {
questTable.clear();
Label title = new Label("QUEST", skin, "subtitle");
title.setAlignment(Align.center);
questTable.add(title).expandX().fillX().padBottom(10f).row();
// Completed quests section
Label completedTitle = new Label("COMPLETED", skin);
questTable.add(completedTitle).expandX().fillX().left().padBottom(5f).row();
if (PerkService.getPerk(TIME_LORD).isUnlocked()) {
Label questToDisplay = new Label("• Find the tortoise!", skin);
questTable.add(questToDisplay).expandX().fillX().left().row();
}
if (!completedJumpQuests.isEmpty()) {
for (JumpQuest quest : completedJumpQuests) {
Label completedQuest = new Label("✓ Jump Quest", skin);
questTable.add(completedQuest).expandX().fillX().left().row();
}
}
if (!completedEnemiesKilledQuest.isEmpty()) {
for (EnemiesKilledQuest quest : completedEnemiesKilledQuest) {
Label completedQuest = new Label("✓ Enemies Killed Quest", skin);
questTable.add(completedQuest).left().row();
}
}
if (!completedGoldSpentQuest.isEmpty()) {
for (GoldSpentQuest quest : completedGoldSpentQuest) {
Label completedQuest = new Label("✓ Gold Spent Quest", skin);
questTable.add(completedQuest).left().row();
}
}
if (!completedShieldsCollectedQuest.isEmpty()) {
for (ShieldsCollectedQuest quest : completedShieldsCollectedQuest) {
Label completedQuest = new Label("✓ Shields Collected Quest", skin);
questTable.add(completedQuest).left().row();
}
}
if (!PerkService.getPerk(TIME_LORD).isUnlocked()
&& completedEnemiesKilledQuest.isEmpty()
&& completedJumpQuests.isEmpty()
&& completedGoldSpentQuest.isEmpty()
&& completedShieldsCollectedQuest.isEmpty()) {
Label noCompleted = new Label("No completed quests", skin);
questTable.add(noCompleted).expandX().fillX().left().row();
}
// Remaining quests section
Label remainingTitle = new Label("REMAINING", skin);
questTable.add(remainingTitle).expandX().fillX().left().padTop(10f).padBottom(5f).row();
boolean hasRemainingQuests = false;
if (!PerkService.getPerk(TIME_LORD).isUnlocked()) {
Label questToDisplay = new Label("• Find the tortoise!", skin);
questTable.add(questToDisplay).expandX().fillX().left().row();
}
if (jumpQuestsToDisplay != null) {
for (JumpQuest quest : jumpQuestsToDisplay) {
if (quest != null) {
int progress = quest.checkQuestProgress();
if (progress >= 100) {
completedJumpQuests.add(quest);
} else {
hasRemainingQuests = true;
Label questToDisplay = new Label("• Jump Quest - " + progress + "%", skin);
questTable.add(questToDisplay).expandX().fillX().left().row();
}
}
}
}
if (enemiesKilledQuestsToDisplay != null) {
for (EnemiesKilledQuest quest : enemiesKilledQuestsToDisplay) {
if (quest != null) {
int progress = quest.checkQuestProgress();
if (progress >= 100) {
completedEnemiesKilledQuest.add(quest);
} else {
hasRemainingQuests = true;
Label questToDisplay = new Label("• Enemies Killed Quest - " + progress + "%", skin);
questTable.add(questToDisplay).left().row();
}
}
}
}
if (goldSpentQuestsToDisplay != null) {
for (GoldSpentQuest quest : goldSpentQuestsToDisplay) {
if (quest != null) {
int progress = quest.checkQuestProgress();
if (progress >= 100) {
completedGoldSpentQuest.add(quest);
} else {
hasRemainingQuests = true;
Label questToDisplay = new Label("• Gold Spent Quest - " + progress + "%", skin);
questTable.add(questToDisplay).left().row();
}
}
}
}
if (shieldsCollectedQuestsToDisplay != null) {
for (ShieldsCollectedQuest quest : shieldsCollectedQuestsToDisplay) {
if (quest != null) {
int progress = quest.checkQuestProgress();
if (progress >= 100) {
completedShieldsCollectedQuest.add(quest);
} else {
hasRemainingQuests = true;
Label questToDisplay = new Label("• Shields Collected Quest - " + progress + "%", skin);
questTable.add(questToDisplay).left().row();
}
}
}
}
if (!hasRemainingQuests && PerkService.getPerk(TIME_LORD).isUnlocked()) {
Label noRemaining = new Label("No remaining quests", skin);
questTable.add(noRemaining).expandX().fillX().left().row();
}
}
As you can see, it continuously checks with Quest to see if there's any new quests to display or get rid of and it then draws them.
Here's how to importantly create your own quest-type to make sure that it works well with the pre-existing system in place.
If you want to track any statistics for your quest (enemies killed, steps walked, etc...), please put them in under the heading //Statistics tracker in the static Quest class. It should be around the private static int globalJumps = 1; already in place.
public class Quest {
...
//Statistics tracker
private static int globalJumps = 1;
...
Put your statistics above in the code! ^^^
Also make sure to create a function (or keep the statistic variable public) so that you can increment/decrement it where-ever in the code you need to do so.
Please put your quest-type class in the following path source/core/src/main/com/csse3200/game/Quests ideally so that it's easier to keep track of all the quest-types.
There should be functions to check the progress of the quest and a way to check whatever statistics you put into the static Quests class.
You should add an Arraylist<(Your quest-type here)> to the Quest Trackers section so that the Quest class can keep track of the quest-types going on. Here's the section in particular:
public class Quest {
...
//Quest trackers
private static ArrayList<JumpQuest> jumpQuestTracker = new ArrayList<JumpQuest>();
...
Put your QuestTracker above! ^^^
Also make sure to add your Arraylist to the giveOutUniqueNPCID() function as (quest-type)QuestTracker.add(uniqueID, null); so that it's initalized for logging a quest there later.
##Adding in quest-type specific functions
Make sure to add in functions in the static class Quest that are able to log the quest-type, check the progress of the quest type get quest-type ArrayList and clear the quest-type from the ArrayList <(quest-type)> you created.
In the function to log the quest type, what you need to do is check if the NPCID already has a class allocated to them and if so then return false. Otherwise set the place in the quest-type ArrayList given by the NPCId to a new object of your quest-type. Also make sure to set the NPCID indexed place in questActiveForNPCID to true.
In the function to clear the quest type, make sure to set the (quest-type)QuestTracker at the NPCID as the index, to null as to get rid of the quest. Also make sure to set questActiveForNPCID at the NPCID index to false.
For checking the progress of the quest, it should just make sure that the (quest-type)QuestTracker at index NPCID isn't null and then call the quest-type function for checking the progress.
You will need to add an ArrayList of quests to display and a Set of completed quests.
private ArrayList<JumpQuest> jumpQuestsToDisplay;
private ArrayList<EnemiesKilledQuest> enemiesKilledQuestsToDisplay;
private ArrayList<GoldSpentQuest> goldSpentQuestsToDisplay;
private ArrayList<ShieldsCollectedQuest> shieldsCollectedQuestsToDisplay;
//Your quest ArrayList goes here
private final Set<JumpQuest> completedJumpQuests = new HashSet<>();
private final Set<EnemiesKilledQuest> completedEnemiesKilledQuest = new HashSet<>();
private final Set<GoldSpentQuest> completedGoldSpentQuest = new HashSet<>();
private final Set<ShieldsCollectedQuest> completedShieldsCollectedQuest = new HashSet<>();
//Your quest Set goes here
In the create() function, set your ArrayList there:
@Override
public void create() {
super.create();
jumpQuestsToDisplay = Quest.getJumpQuests();
enemiesKilledQuestsToDisplay = Quest.getEnemiesKilledQuests();
goldSpentQuestsToDisplay = Quest.getGoldSpentQuests();
shieldsCollectedQuestsToDisplay = Quest.getShieldsCollectedQuests();
//Your ArrayList goes here
Do the same in the update() function.
/** Updates the quest list and handles the quest menu toggle. */
@Override
public void update() {
jumpQuestsToDisplay = Quest.getJumpQuests();
enemiesKilledQuestsToDisplay = Quest.getEnemiesKilledQuests();
goldSpentQuestsToDisplay = Quest.getGoldSpentQuests();
shieldsCollectedQuestsToDisplay = Quest.getShieldsCollectedQuests();
//Put your ArrayList here
In the refreshQuestList function, add in this for your completedXQuests at the top to display completed quests.
if (!completedXQuests.isEmpty()) {
for (XQuests quest : completedXQuests) {
Label completedQuest = new Label("✓ X Quest", skin);
questTable.add(completedQuest).left().row();
}
}
Later on in the function, add in this for your X in-progress quests.
if (XQuestsToDisplay != null) {
for (XQuest quest : XQuestsToDisplay) {
if (quest != null) {
int progress = quest.checkQuestProgress();
if (progress >= 100) {
completedXQuests.add(quest);
} else {
hasRemainingQuests = true;
Label questToDisplay = new Label("• X Quest - " + progress + "%", skin);
questTable.add(questToDisplay).left().row();
}
}
}
}
In DialogueComponent, make sure to implement your quest into the logCorrospondingQuest, clearCorrospondingQuest and checkCorrospondingQuest functions so that the quest can be logged, cleared and checked by the NPC.
If you're having trouble following this (which is genuinely understandable) then please look towards how the JumpQuest is implemented in the game and use that as a reference for creating your new quest-type.