diff --git a/dGame/dComponents/Character/MissionComponent.cpp b/dGame/dComponents/Character/MissionComponent.cpp index 3f29927f2..b7bbf119b 100644 --- a/dGame/dComponents/Character/MissionComponent.cpp +++ b/dGame/dComponents/Character/MissionComponent.cpp @@ -3,6 +3,8 @@ * Copyright 2019 */ +#include +#include #include #include #include @@ -73,6 +75,38 @@ eMissionState MissionComponent::GetMissionState(const uint32_t missionId) const } +int32_t MissionComponent::GetClientMissionState(const uint32_t missionId) const { + // A mission ID below 1 is no mission + if (missionId == 0 || missionId > static_cast(std::numeric_limits::max())) return 0; + + const auto* const mission = GetMission(missionId); + const bool done = mission && (mission->GetCompletions() > 0 || mission->IsComplete()); + int32_t state = done ? static_cast(eMissionState::COMPLETE) : 0; + + // In progress: its own state + if (mission && !mission->IsComplete()) return state | static_cast(mission->GetMissionState()); + + CDMissions info{}; + if (!Mission::IsValidMission(missionId, info)) return 0; + + if ((!done || info.repeatable) && MissionPrerequisites::CheckPrerequisites(missionId, m_Missions)) { + state |= static_cast(eMissionState::AVAILABLE); + } + + return state; +} + +bool MissionComponent::IsCooldownRunning(const int64_t completedAt, const int64_t cooldownMinutes, const int64_t now) { + return cooldownMinutes > 0 && (now - completedAt) / 60 < cooldownMinutes; +} + +bool MissionComponent::IsCooldownRunning(const uint32_t missionId) const { + const auto* const mission = GetMission(missionId); + if (!mission || (mission->GetCompletions() == 0 && !mission->IsComplete())) return false; + + return IsCooldownRunning(mission->GetTimestamp(), mission->GetClientInfo().cooldownTime, std::time(nullptr)); +} + const std::unordered_map& MissionComponent::GetMissions() const { return m_Missions; } @@ -720,8 +754,19 @@ bool MissionComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& return true; } +// LWOMissionComponent::msgGetMissionState (0x00d176e0): the client's mission state bit mask; with cooldown info +// requested (0x00cd7770), the cooldown is only checked for a repeat in progress (state 9 or 10) and otherwise finished bool MissionComponent::OnGetMissionState(GameMessages::GetMissionState& getMissionState) { - getMissionState.missionState = GetMissionState(getMissionState.missionID); + const auto missionID = static_cast(getMissionState.missionID); + const auto state = getMissionState.missionID < 1 ? 0 : GetClientMissionState(missionID); + getMissionState.missionState = static_cast(state); + + if (getMissionState.cooldownInfoRequested) { + const auto* const mission = GetMission(missionID); + const bool inProgress = mission && !mission->IsComplete(); + const bool repeat = state == static_cast(eMissionState::COMPLETE_AVAILABLE) || state == static_cast(eMissionState::COMPLETE_ACTIVE); + getMissionState.cooldownFinished = !(inProgress && repeat && IsCooldownRunning(missionID)); + } return true; } diff --git a/dGame/dComponents/Character/MissionComponent.h b/dGame/dComponents/Character/MissionComponent.h index f5ca72a41..3e0d60756 100644 --- a/dGame/dComponents/Character/MissionComponent.h +++ b/dGame/dComponents/Character/MissionComponent.h @@ -56,6 +56,22 @@ public: */ 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 diff --git a/dGame/dMission/MissionPrerequisites.h b/dGame/dMission/MissionPrerequisites.h index 13b8e903a..422c3093c 100644 --- a/dGame/dMission/MissionPrerequisites.h +++ b/dGame/dMission/MissionPrerequisites.h @@ -41,6 +41,14 @@ public: * @return whether or not the mission identified by the specified ID can be accepted */ static bool CanAccept(uint32_t missionId, const std::unordered_map& missions); + + /** + * Checks the prerequisites for a mission + * @param missionId the mission ID to check prerequisites for + * @param missions the mission inventory to check the prerequisites against + * @return whether or not the prerequisites of the mission identified by the specified ID are met + */ + static bool CheckPrerequisites(uint32_t missionId, const std::unordered_map& missions); private: /** @@ -48,11 +56,4 @@ private: */ static std::unordered_map expressions; - /** - * Checks the prerequisites for a mission - * @param missionId the mission ID to check prerequisites for - * @param missions the mission inventory to check the prerequisites against - * @return whether or not the mission identified by the specified ID can be accepted - */ - static bool CheckPrerequisites(uint32_t missionId, const std::unordered_map& missions); };