Files
DarkflameServer/dGame/dComponents/Character/MissionComponent.h
Aaron Kimbrell 8564e293fa fix(mission): GetMissionState answers the client's state bit mask
LWOMissionComponent::getMissionState (0x00cd7400) returns a mask: COMPLETE
once the mission was completed, ORed with the state of the mission in
progress; a mission not in progress gets AVAILABLE when it was never
completed or is repeatable and its prerequisites are met, else 0. DLU's
handler answered UNKNOWN (-1, every bit set) for a mission that cannot be
taken and COMPLETE for a completed repeatable one, and ignored
cooldownInfoRequested. With cooldown info requested (0x00cd7770) the
client only checks the cooldown of a repeat in progress (state 9 or 10):
fewer whole minutes since the last completion than Missions.cooldownTime.

MissionComponent::GetMissionState, used across the server, is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:43:08 -05:00

258 lines
11 KiB
C++

/*
* Darkflame Universe
* Copyright 2019
*/
#ifndef MISSIONCOMPONENT_H
#define MISSIONCOMPONENT_H
#include <map>
#include <string>
#include <unordered_map>
#include <vector>
#include "dCommonVars.h"
#include "RakNetTypes.h"
#include "Entity.h"
#include "Mission.h"
#include "tinyxml2.h"
#include "CDClientManager.h"
#include "CDMissionsTable.h"
#include "Component.h"
#include "eReplicaComponentType.h"
class AchievementCacheKey;
/**
* The mission inventory of an entity. Tracks mission state for each mission that can be accepted and allows for
* progression of each of the mission task types (see eMissionTaskType).
*/
class MissionComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MISSION;
explicit MissionComponent(Entity* parent, const int32_t componentID);
~MissionComponent() override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate, unsigned int& flags);
void LoadFromSaveData(const tinyxml2::XMLDocument& doc) override;
void WriteSaveData(tinyxml2::XMLDocument& doc) override;
/**
* Returns all the missions for this entity, mapped by mission ID
* @return the missions for this entity, mapped by mission ID
*/
const std::unordered_map<uint32_t, Mission*>& GetMissions() const;
/**
* Returns the mission for the given mission ID, if it exists
* @param missionId the id of the mission to get
* @return the mission for the given mission ID
*/
Mission* GetMission(uint32_t missionId) const;
/**
* Returns the current state of the entities progression for the mission of the specified ID
* @param missionId the ID of the mission to get the mission state for
* @return the mission state of the mission specified by the ID
*/
eMissionState GetMissionState(uint32_t missionId) const;
/**
* The mission state as the client works it out (LWOMissionComponent::getMissionState 0x00cd7400): a bit mask,
* COMPLETE (8) once the mission was ever completed, ORed with the state of the mission in progress; a mission not in
* progress gets AVAILABLE (1) when it was never completed or is repeatable and its prerequisites are met. 0 for an
* unknown mission.
*/
int32_t GetClientMissionState(uint32_t missionId) const;
/**
* Whether the cooldown (Missions.cooldownTime, minutes) of a completed mission is still running, as the client
* checks it: only for a mission completed before, a cooldown above 0, and while fewer whole minutes than the
* cooldown have passed since the last completion.
*/
bool IsCooldownRunning(uint32_t missionId) const;
static bool IsCooldownRunning(int64_t completedAt, int64_t cooldownMinutes, int64_t now);
/**
* Checks if the entity has all the requirements for accepting the mission specified by the ID.
* @param missionId the mission ID to check for if the character may accept it
* @return whether this entity can accept the mission represented by the given mission ID
*/
bool CanAccept(uint32_t missionId) const;
/**
* Accepts the mission specified by the ID, if the entity may accept it. Also stores it in the mission inventory.
* @param missionId the ID of the mission to accept
* @param skipChecks skips the checks for the mission prerequisites
*/
void AcceptMission(uint32_t missionId, bool skipChecks = false);
/**
* Completes the mission specified by the given ID, if the entity has fulfilled all progress requirements.
* @param missionId the ID of the mission to complete
* @param skipChecks skips the checks for having completed all of the mission tasks
* @param yieldRewards whether to yield mission rewards, currently unused
*/
void CompleteMission(uint32_t missionId, bool skipChecks = false, bool yieldRewards = true);
/**
* Removes the mission from the entities' mission chain. Not used for normal gameplay but useful for debugging.
* @param missionId the ID of the mission to remove
*/
void RemoveMission(uint32_t missionId);
/**
* Attempts to progress mission tasks for a given type using parameters to progress. Note that this function is
* very abstract and different mission tasks require different combinations of parameters. This also progresses
* achievements, which are basically just missions with the isMission flag set to false.
* @param type the type of the mission task to try and progress with
* @param value the value to progress with, could be a LOT for item collection for example
* @param associate an associated entity for the progression, might be an activity host for example
* @param targets optionally multiple target values that could exist for the mission that we wish to progress
* @param count the number to progress by, for example the number of items
* @param ignoreAchievements do not progress achievements
*/
void Progress(eMissionTaskType type, int32_t value, LWOOBJID associate = 0, const std::string& targets = "", int32_t count = 1, bool ignoreAchievements = false);
/**
* Forces progression for a mission and task, ignoring checks
* @param missionId the mission ID to try and progress
* @param taskId the task ID of the task belonging to the mission trying to progress
* @param value the value to progress with
* @param acceptMission accept the mission if it was not already accepted
*/
void ForceProgress(uint32_t missionId, uint32_t taskId, int32_t value, bool acceptMission = true);
/**
* Forces progress for all tasks of a certain type that belong to the same mission
* @param missionId the mission to progress
* @param taskType the task tyoe to progress
* @param value the value to progress with
* @param acceptMission accept the mission if it wasn't already
*/
void ForceProgressTaskType(uint32_t missionId, uint32_t taskType, int32_t value, bool acceptMission = true);
/**
* Force progresses by checking the value and progressing by 1
* @param missionId the mission to progress
* @param taskType the task to progress for
* @param value the value to check the mission values before progressing
* @param acceptMission accept the mission if it wasn't already
*/
void ForceProgressValue(uint32_t missionId, uint32_t taskType, int32_t value, bool acceptMission = true);
/**
* Returns client database mission information for a mission
* @param missionId the ID of the mission to get the info for
* @param result the result to store the information in
* @return true if the information was succesfully retrieved, false otherwise
*/
bool GetMissionInfo(uint32_t missionId, CDMissions& result);
/**
* Checks if there's any achievements we might be able to accept for the given parameters
* @param type the task type for tasks in the achievement that we wish to progress
* @param value the value to progress by
* @param associate optional associate related to mission progression
* @param targets optional multiple targets related to mission progression
* @param count the number of values to progress by (differs by task type)
* @return true if a achievement was accepted, false otherwise
*/
const std::vector<uint32_t> LookForAchievements(eMissionTaskType type, int32_t value, LWOOBJID associate = LWOOBJID_EMPTY, const std::string& targets = "", int32_t count = 1);
/**
* Checks if there's a mission active that requires the collection of the specified LOT
* @param lot the LOT to check for
* @return if there's a mission active that requires the collection of the specified LOT
*/
bool RequiresItem(LOT lot);
/**
* Collects a collectable for the entity, unrendering it for the entity
* @param collectibleID the ID of the collectable to add
*/
void AddCollectible(int32_t collectibleID);
/**
* Checks if the entity already has a collectible of the specified ID
* @param collectibleID the ID of the collectible to check
* @return if the entity already has a collectible of the specified ID
*/
bool HasCollectible(int32_t collectibleID);
/**
* Checks if the entity has a certain mission in its inventory
* @param missionId the ID of the mission to check
* @return if the entity has a certain mission in its inventory
*/
bool HasMission(uint32_t missionId);
void ResetMission(const int32_t missionId);
void FixRacingMetaMissions();
// The journal state of a mission type and subtype (Missions.defined_type / defined_subtype), as the client sets it
// with SetMissionTypeState. Saved in the charxml: <mis><ts><type v="type"><st sub="subtype" val="state"/></type></ts>
using MissionTypeStates = std::map<std::string, std::map<std::string, eMissionLockState>>;
void SetMissionTypeState(const std::string& type, const std::string& subtype, eMissionLockState state);
const MissionTypeStates& GetMissionTypeStates() const { return m_MissionTypeStates; }
private:
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
bool OnGetMissionState(GameMessages::GetMissionState& getMissionState);
bool OnMissionNeedsLot(GameMessages::MissionNeedsLot& missionNeedsLot);
/**
* All the missions owned by this entity, mapped by mission ID
*/
std::unordered_map<uint32_t, Mission*> m_Missions;
/**
* All the collectibles currently collected by the entity
*/
std::vector<int32_t> m_Collectibles;
MissionTypeStates m_MissionTypeStates;
/**
* For the given parameters, finds the mission IDs of the achievements that may be unlcoked
* @param type the mission task type to try and progress
* @param value the value to try and progress with
* @param targets optional targets to progress with
* @return list of mission IDs (achievements) that can be progressed for the given parameters
*/
static const std::vector<uint32_t>& QueryAchievements(eMissionTaskType type, int32_t value, const std::string targets);
/**
* As achievements can be hard to query, we here store a list of all the mission IDs that can be unlocked for a
* combination of tasks and values, so that they can be easily re-queried later
*/
static std::unordered_map<AchievementCacheKey, std::vector<uint32_t>> m_AchievementCache;
/**
* Order of missions in the UI. This value is incremented by 1
* for each mission the Entity that owns this component accepts.
* In live this value started at 745.
*/
uint32_t m_LastUsedMissionOrderUID = 746U;
/**
* Holds arguments for a Progress() call that arrived re-entrantly while m_Missions was
* being iterated, so it can be replayed after the active loop finishes.
*/
struct PendingProgress {
eMissionTaskType type{};
int32_t value{};
LWOOBJID associate{};
std::string targets;
int32_t count{};
bool ignoreAchievements{};
};
/// True while the m_Missions range-for loop in Progress() is executing.
bool m_IsProgressing = false;
/// Re-entrant Progress() calls deferred here for replay after the active loop finishes.
std::vector<PendingProgress> m_PendingProgress;
};
#endif // MISSIONCOMPONENT_H