Skip to content

Missions (Achievements and Quests) System

Tom-Strooper edited this page Oct 3, 2023 · 17 revisions

MissionManager

The MissionManager is a class which manages and tracks all in-game Mission progress. It stores an array of all Achievements, a list of active Quests* (Quests that the player is currently attempting to complete), and a list of selectable Quests (Quests that the player is able to accept, although has not started).

* Note: Completed quests are to be deleted once their reward is collected

The MissionManager provides the following publicly accessible methods which all classes can use to interact with the system:

  • MissionManager() (constructor) - calls registerMission() on all achievements in the array. Also sets up listening for hourly events with the TimeService and adds the hourUpdate() method as the event handler for this event.
  • addQuest() - adds a Quest to the list of selectable Quests
  • acceptQuest() - accepts a Quest by removing it from the selectableQuests (if in selectableQuests), adding the Quest to the activeQuests, and by calling its registerMission() method
  • getEvents() - returns a reference to the MissionManager’s EventHandler
  • getSelectableQuests() - returns the List of selectable Quests
  • getActiveQuests() - returns the List of active Quests

The MissionManager will update the time to expiry for all active Quests via the private updateActiveQuestTimes() method. This method will only update the expiry for all active Quests, using the Quests’ updateExpiry() methods. This method also checks if any mandatory Quests have expired, and call a game over if so.

Additionally, when a Quest is added to the MissionManager’s List of selectable Quests (the event triggered is given by MissionManager.MissionEvent.NEW_QUEST.name()). The MissionManager.MissionEvent.QUEST_EXPIRED.name() event is triggered when a Quest expires.

The Mission class

The Mission class is an abstract class, which can be extended to specify an in-game mission (including an Achievement, Quest or MandatoryQuest. It stores all information related to the mission, and contains methods to allow the MissionManager to determine whether the player has completed the mission.

The following methods have a default implementation:

  • getName() - returns the name of the Mission
  • notifyUpdate() - triggers the MissionManager.MissionEvent.MISSION_COMPLETE.name() event on the MissionManager’s EventHandler if the Mission is completed (as determined by the implementation of isCompleted()). This should be called any time a Mission’s state is updated

Below are descriptions of a variety of methods which should be overridden by anyone who would like to create their own in-game Missions.

registerMission()

The registerMission() method is called when the Mission is registered to the MissionManager. In this method, you should add all of the event listeners to the MissionManager which your Mission will listen to (see the Event listeners section for more details), using an enum from the MissionEvent enum class.

Say, for instance, you would like your Mission to listen to the “plant” event (triggered when a plant is planted, and given a reference to the type of plant planted), then you would add the following line to your Mission’s registerMission() method:

ServiceLocator.getMissionManager().getEventHandler().addListener(MissionEvent.PLANT);

Event listeners

When an Mission is registered to the MissionManager, it should begin listening to events on the MissionManager’s EventHandler. The idea is that certain interactions and occurrences in game may cause events to be triggered. Internally, the Mission should have methods which update the state of the tracked stats when certain key events are triggered (e.g., the planting of a corn crop causes an Mission to increment an int numCornCropsPlanted property through some private method).

isCompleted()

Returns a boolean value, representing whether an Mission has been attained by the player. This should be overridden by any Mission class to specify the criteria for which an Mission has been reached.

getDescription()

Returns a string description of the Mission. This should be overridden by any Mission class. If you would like the Mission to display information about the player’s progression through the Mission, this should be included in the text description returned by this method.

getShortDescription()

Similar to getDescription(), although should return a shorter description of at most 50 characters, to be displayed in the mission NPC’s UI before the player chooses to view a Mission in its entirety.

getProgress()

This method returns an object which holds information about the progress of the mission (any stats tracked by the Mission which change overtime).

The returned object should be easily serialized by libGDX's Json serialiser. The best options are returning primitive types (e.g., int, boolean), Strings, or Arrays of these primatives. Basic classes packaging these serialisable types are also easily serialised by the Json serialiser. For more information, read the libGDX wiki.

readProgress()

This should read a JsonValue storing the progress of the Mission. The precise JsonValue.ValueType of this JsonValue is given by the type of object returned in getProgress() (e.g., if you returned an Integer in getProgress(), you can safely call jsonValue.asInt() and expect no errors to occur). Read the javadocs for JsonValue for more information on how to read from a JsonValue (you should also use this to inform how you implement getProgress).

Many examples of how these 2 methods should be implemented now exist in the quests package, so look there (or ping team 7 in discord) if you need more help.

Implementations

Achievements

Achievements are abstract missions which are tracked statically throughout the course of the game. Once a player completes an achievement, it is completed permanently.

It also contains a write() method for save/load purposes. See the save/load wiki page for more details.

Quests

Quests are abstract missions with the addition of a Reward class instance property representing the reward that one gets from completing the Quest. Quests, also have a time limit, and store an int duration representing the number of hours the Quest should active before it expires (the actual amount of time left before the Quest expires is stored in timeToExpiry).

As of Sprint 3, a secondary constructor was added to the Quest class. This constructor does not have the parameters int expiryDuration or boolean isMandatory. Quests made with this constructor never expire, and are made to be non-mandatory. This constructor is currently used for all story-quests which aren't MainQuests. We recommend you do not create Quests with this constructor unless it is a story Quest, since non-mandatory Quests should probably have an expiry duration as per the original vision of the mission system.

It provides the following method implementations by default:

  • isExpired() - returns a boolean value, representing whether this Quest has expired. If this Quest can't expire it will always return false.
  • isMandatory() - returns a boolean value, representing whether this Quest is mandatory. Mandatory Quests will result in a game over if not completed before expiry
  • collectReward() - calls collect() on this Quest’s reward instance if the Reward is uncollected and the Quest is completed
  • updateExpiry() - decrements the expiry time by 1 hour if this Quest can expire and is not completed (otherwise it will do nothing)
  • resetExpiry() - resets the expiry time for the Quest, but resetting the timeToExpiry variable, and calling the resetState() method
  • resetState() - an abstract method which, when implemented, should reset the internal state of the Quest’s stats (for instance, when an expired Quest gets reactivated)
  • isRewardCollected() - returns a boolean value representing whether this Quest's Reward has been collected.

This class also provides an override for notifyUpdate(), so if the reward of a Quest has been collected, the Alien NPC is no longer notified of Quest completion (otherwise completed Quests whose Reward has been collected would continually notify the Alien NPC that a new Quest has been completed).

The methods read() and write() also exist for save/load purposes. Refer to the save/load wiki page for more information.

Any Quest class implementation created by members of the studio should be instantiated by a method in the QuestFactory (found in the quests package). You can call these methods in other methods in QuestFactory. That is, you should make your Quests be Rewards (using the QuestReward class) for completing various story Quests in game. We recommend introducing your non-story Quests in Act II or Act III of the game.

MissionEvent enum

The MissionEvent enum is an enum class which stores all events handled by the MissionManagerService’s EventHandler. Each enum corresponds to a string representation of the event. This decision was made to reduce the chance that an event listener or trigger is mis-typed, and allows the precise string representation of these events to change easily.

Reward class

The Reward class is an abstract class representing an in-game reward which can be collected as a result of completing an Mission. The public isCollected() method is provided by default, and returns true iff the reward’s collect() method has been called.

collect()

This method executes the logic associated with collecting this Reward. This may include adding some items to the player’s inventory, permanently boosting a player’s stat, or enabling some in-game progression.

UML Diagram

Below is UML overview of the core classes in the system (ignoring specific implementations).

missionSystemUML

Here is a UML diagram showing the core Quest classes implemented for the main storyline of the game (that is, the MainQuest, AutoQuest and the QuestReward which triggers them). See the storyline wiki page explaining how the storyline is told in the game. For more information about all of the specific Quest implementations for the storyline of the game, please read the JavaDocs

questsUML

Here is a UML diagram showing the Reward classes implemented for the main storyline of the game. See the storyline wiki page explaining how the storyline is told in the game. For more information about all of the specific Reward implementations for the storyline of the game, please read the JavaDocs

rewardsUML

Testing

The classes MissionManager, Mission, Achievement, Quest and Reward all have 100% testing code coverage except (as of Sprint 3) methods made for save/load purposes (read/write methods). Quest class implementations made for the storyline of the game (i.e., implemented by team 7 for Sprint 3) have 100% coverage. Most Rewards made for the storyline of the game (i.e., implemented by team 7 for Sprint 3) also have 100% coverage, except for TriggerHostilesReward and DialogueReward, which contain portions of logic implemented by other teams. These classes will be covered by tests early in sprint 4, once the other teams have added their logic.

Studio members are welcome to and are encouraged to add JUnit5 testing for any Achievements, Quests or Rewards they add. In-game testing is also necessary for these Missions.

Past Designs & Design Process

Milestones

In a previous iteration of the Missions System design, the way through which Quests would offer Rewards to the player was different. Instead of a Quest simply being complete or incomplete (and only allowing the player the ability to receive a reward upon the completion of the entire Quest), the team considered implementing Quest completion through the use of Milestones.

Milestones were an abstract class which had names, descriptions, and rewards. They would define a subset of the Quest that needed to be completed to attain their reward, meaning the player could receive multiple rewards for a single Quest.

This idea was scrapped, as it would require studio members creating a Quest to implement and create a string of Milestones for each Quest, which would not only reduce the flexibility of the system, but also introduce additional complexity to the process of adding Quests.

Clone this wiki locally