Skip to content

Endeavours

Jay Malhotra edited this page Nov 15, 2023 · 2 revisions

This page will attempt to explain how the endeavour system works and how to add new ones.

Note that from here onwards, 'endeavours' will be referred to as 'missions'. This is the internal name that the game uses and the Dawnshard codebase has kept this convention.

Data

Missions are stored in the PlayerMissions table. This table holds all of a player's missions that they have started. Missions have to be started before they can be progressed -- for example:

  • changing to a compendium event which you haven't played before will start those missions
  • progressing through a group in the Royal Regimen will start the next group's missions
  • reaching certain points in the main campaign will start a set of story-blocking progression missions

The most important fields for a mission are:

  • Id, corresponding to that from the relevant mission type asset in the master asset
  • Type e.g. Normal/Daily/MemoryEvent
  • State e.g. InProgress/Completed/Claimed
  • Progress indicating the amount of progress towards a mission

Initial progress

When a mission is started, a check is performed to see how much progress has been made against that mission. For example, it is often possible to complete part of a Royal Regimen group before it is started due to actions like clearing a quest for the first time. It is also important to determine this for mission conditions which cannot be completed twice, such as buying a wyrmprint.

This is implemented in MissionInitialProgressService.GetInitialMissionProgress with a switch statement based on the completion type.

Progressing a mission

Missions are progressed via the MissionProgressionService. This implements a queue of MissionEvents which can be fired from anywhere in the code. When the update data list is processed and database changes are saved, it processes these events against defined progression info to work out if any were relevant to the player's open missions and progresses/completes any applicable missions.

Progression events

Mission progression events are structured as

private record MissionEvent(
    MissionCompleteType Type,
    int Value,
    int TotalValue,
    int? Parameter = null,
    int? Parameter2 = null,
    int? Parameter3 = null,
    int? Parameter4 = null
);

Here the MissionCompleteType refers to the progression type, such as clearing a quest or placing a building. Value indicates the value for this event. The TotalValue indicates the total value that the player is currently at, which is only applicable for some mission events. For example, when levelling up a building, an event is fired with a Value of 1, indicating one level up, but a TotalValue of the player's new total fort level. This allows this event to progress two kinds of missions, those that are phrased as 'Level up Halidom Buildings X Times' but also 'Reach a Total Fort Level of Y'.

The optional parameters after these required arguments define properties of the event which may make it only apply to specific missions. For example, when reading a story, an event will be fired with MissionCompleteType.QuestStoryCleared and a Parameter equal to the story ID that was read.

The check to see if an event applies to a mission is as follows:

List<(MissionType Type, int Id)> affectedMissions = MasterAsset
    .MissionProgressionInfo
    .Enumerable
    .Where(x => x.CompleteType == evt.Type)
    .Where(
        x =>
            (x.Parameter is null || x.Parameter == evt.Parameter)
            && (x.Parameter2 is null || x.Parameter2 == evt.Parameter2)
            && (x.Parameter3 is null || x.Parameter3 == evt.Parameter3)
            && (x.Parameter4 is null || x.Parameter4 == evt.Parameter4)
    )
    .Select(x => (x.MissionType, x.MissionId))
    .ToList();

This checks for missions of the same complete type and either matching or more general parameters, so for instance a QuestStoryCleared event with a Parameter of a specific quest story ID can match a mission to read a specific quest story with that ID, and also a mission with no Parameter that just stipulates any quest story has to be read.

Defining progression information

Progression information must be defined manually because this cannot be data-mined from the client, but the amount required to complete a mission can be -- so total values do not have to be specified.

If a player has started a mission in the backend but it has no progression info associated with it, it will not be sent to the client.

The way progression info is added is via a file called MissionProgressionInfo.json. A project called DragaliaAPI.MissionDesigner exists in the solution to make it easier to define missions here with access to strongly typed Parameters. When this is run, it generates a JSON file containing the new progression info based on the lists defined in the project, which can be then copied over MissionProgressionInfo.json in DragaliaAPI.Shared/Resources/Missions. This copying can also be automated with a launchSettings.json task or similar equivalent in your IDE.

The project works by using runtime reflection on startup to iterate through the types defined in its assembly and searches for those with particular attributes. First it checks for classes with [ContainsMissionList] then for the first List<Mission> property with [MissionList]. A mission type can be specified as an argument to [MissionList] which sets the mission category type for all of the missions in the list to avoid repetition.

When adding new missions, it is recommended to add a new class extending Mission with appropriately named and typed properties that logically represent the mission.

Clone this wiki locally