fix: racing meta missions and undefined behavior in mission progression

Tested that missions can still be completed
Tested that racing meta tasks are now actually possible
TODO: Tested that old characters with incomplete meta missions are brought up to speed with their current progress
This commit is contained in:
David Markowitz
2025-11-19 22:18:57 -08:00
parent a713216540
commit 4a9971d182
7 changed files with 66 additions and 14 deletions

View File

@@ -20,7 +20,8 @@ enum class eCharacterVersion : uint32_t {
NJ_JAYMISSIONS, NJ_JAYMISSIONS,
NEXUS_FORCE_EXPLORER, // Fixes pet ids in player inventories NEXUS_FORCE_EXPLORER, // Fixes pet ids in player inventories
PET_IDS, // Fixes pet ids in player inventories PET_IDS, // Fixes pet ids in player inventories
UP_TO_DATE, // will become INVENTORY_PERSISTENT_IDS INVENTORY_PERSISTENT_IDS, // Fixes racing meta missions
UP_TO_DATE, // will become RACING_META_MISSIONS
}; };
#endif //!__ECHARACTERVERSION__H__ #endif //!__ECHARACTERVERSION__H__

View File

@@ -3,6 +3,7 @@
* Copyright 2019 * Copyright 2019
*/ */
#include <ranges>
#include <sstream> #include <sstream>
#include <string> #include <string>
@@ -71,7 +72,7 @@ eMissionState MissionComponent::GetMissionState(const uint32_t missionId) const
} }
const std::unordered_map<uint32_t, Mission*>& MissionComponent::GetMissions() const { const std::map<uint32_t, Mission*>& MissionComponent::GetMissions() const {
return m_Missions; return m_Missions;
} }
@@ -149,7 +150,7 @@ void MissionComponent::Progress(eMissionTaskType type, int32_t value, LWOOBJID a
} }
for (const auto& [id, mission] : m_Missions) { for (const auto& [id, mission] : m_Missions) {
if (!mission || std::find(acceptedAchievements.begin(), acceptedAchievements.end(), mission->GetMissionId()) != acceptedAchievements.end()) continue; if (!mission || std::ranges::find(acceptedAchievements, mission->GetMissionId()) != acceptedAchievements.end()) continue;
if (mission->IsAchievement() && ignoreAchievements) continue; if (mission->IsAchievement() && ignoreAchievements) continue;
@@ -747,3 +748,30 @@ bool MissionComponent::OnMissionNeedsLot(GameMessages::GameMsg& msg) {
const auto& needMsg = static_cast<GameMessages::MissionNeedsLot&>(msg); const auto& needMsg = static_cast<GameMessages::MissionNeedsLot&>(msg);
return RequiresItem(needMsg.item); return RequiresItem(needMsg.item);
} }
void MissionComponent::FixRacingMetaMissions() {
for (const auto mission : m_Missions | std::views::values) {
if (!mission || mission->IsComplete()) continue;
for (const auto task : mission->GetTasks()) {
if (!task) continue;
// has to be a racing meta mission and have a taskparam1 of 4
if (task->GetType() != eMissionTaskType::RACING || !task->GetClientInfo().taskParam1.starts_with("4")) continue;
// Each target is racing mission that needs to be completed.
// If its completed, progress the meta task by 1.
uint32_t progress = 0;
for (const auto& target : task->GetAllTargets()) {
if (target == 0) continue;
auto* racingMission = GetMission(target);
if (racingMission && racingMission->IsComplete()) {
progress++;
}
}
task->SetProgress(progress);
}
// in case the mission is actually complete, give them the rewards
mission->CheckCompletion();
}
}

View File

@@ -38,7 +38,7 @@ public:
* Returns all the missions for this entity, mapped by mission ID * Returns all the missions for this entity, mapped by mission ID
* @return 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; const std::map<uint32_t, Mission*>& GetMissions() const;
/** /**
* Returns the mission for the given mission ID, if it exists * Returns the mission for the given mission ID, if it exists
@@ -170,6 +170,8 @@ public:
bool HasMission(uint32_t missionId); bool HasMission(uint32_t missionId);
void ResetMission(const int32_t missionId); void ResetMission(const int32_t missionId);
void FixRacingMetaMissions();
private: private:
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg); bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
bool OnGetMissionState(GameMessages::GameMsg& msg); bool OnGetMissionState(GameMessages::GameMsg& msg);
@@ -177,7 +179,7 @@ private:
/** /**
* All the missions owned by this entity, mapped by mission ID * All the missions owned by this entity, mapped by mission ID
*/ */
std::unordered_map<uint32_t, Mission*> m_Missions; std::map<uint32_t, Mission*> m_Missions;
/** /**
* All the collectibles currently collected by the entity * All the collectibles currently collected by the entity

View File

@@ -407,9 +407,7 @@ void Mission::Catchup() {
task->Progress(target); task->Progress(target);
} }
} }
} } else if (type == eMissionTaskType::PLAYER_FLAG) {
if (type == eMissionTaskType::PLAYER_FLAG) {
for (int32_t target : task->GetAllTargets()) { for (int32_t target : task->GetAllTargets()) {
const auto flag = GetCharacter()->GetPlayerFlag(target); const auto flag = GetCharacter()->GetPlayerFlag(target);
@@ -423,6 +421,24 @@ void Mission::Catchup() {
break; break;
} }
} }
} else if (type == eMissionTaskType::RACING) {
// check if its a racing meta task ("4") and then set its progress to the current completed missions in all tasks
const auto& clientInfo = task->GetClientInfo();
if (clientInfo.taskParam1.starts_with("4")) {
// Each target is racing mission that needs to be completed.
// If its completed, progress the meta task by 1.
// at the end set the task progress to avoid sending excess msgs across the wire
uint32_t progress = 0;
for (const auto& target : task->GetAllTargets()) {
if (target == 0) continue;
auto* racingMission = m_MissionComponent->GetMission(target);
if (racingMission != nullptr && racingMission->IsComplete()) {
progress++;
}
}
task->SetProgress(progress);
}
} }
} }
} }

View File

@@ -92,7 +92,7 @@ PrerequisiteExpression::PrerequisiteExpression(const std::string& str) {
} }
bool PrerequisiteExpression::Execute(const std::unordered_map<uint32_t, Mission*>& missions) const { bool PrerequisiteExpression::Execute(const std::map<uint32_t, Mission*>& missions) const {
auto a = this->a == 0; auto a = this->a == 0;
auto b = this->b == nullptr; auto b = this->b == nullptr;
@@ -129,7 +129,7 @@ PrerequisiteExpression::~PrerequisiteExpression() {
} }
bool MissionPrerequisites::CanAccept(const uint32_t missionId, const std::unordered_map<uint32_t, Mission*>& missions) { bool MissionPrerequisites::CanAccept(const uint32_t missionId, const std::map<uint32_t, Mission*>& missions) {
const auto& missionIndex = missions.find(missionId); const auto& missionIndex = missions.find(missionId);
if (missionIndex != missions.end()) { if (missionIndex != missions.end()) {
@@ -155,7 +155,7 @@ bool MissionPrerequisites::CanAccept(const uint32_t missionId, const std::unorde
return CheckPrerequisites(missionId, missions); return CheckPrerequisites(missionId, missions);
} }
bool MissionPrerequisites::CheckPrerequisites(uint32_t missionId, const std::unordered_map<uint32_t, Mission*>& missions) { bool MissionPrerequisites::CheckPrerequisites(uint32_t missionId, const std::map<uint32_t, Mission*>& missions) {
const auto& index = expressions.find(missionId); const auto& index = expressions.find(missionId);
if (index != expressions.end()) { if (index != expressions.end()) {
return index->second->Execute(missions); return index->second->Execute(missions);

View File

@@ -21,7 +21,7 @@ public:
* @param missions the list of missions to check the prerequisites against (f.e. whether they're completed) * @param missions the list of missions to check the prerequisites against (f.e. whether they're completed)
* @return whether or not all the prerequisites are met * @return whether or not all the prerequisites are met
*/ */
bool Execute(const std::unordered_map<uint32_t, Mission*>& missions) const; bool Execute(const std::map<uint32_t, Mission*>& missions) const;
explicit PrerequisiteExpression(const std::string& str); explicit PrerequisiteExpression(const std::string& str);
~PrerequisiteExpression(); ~PrerequisiteExpression();
@@ -40,7 +40,7 @@ public:
* @param missions the mission inventory to check the prerequisites against * @param missions the mission inventory to check the prerequisites against
* @return whether or not the mission identified by the specified ID can be accepted * @return whether or not the mission identified by the specified ID can be accepted
*/ */
static bool CanAccept(uint32_t missionId, const std::unordered_map<uint32_t, Mission*>& missions); static bool CanAccept(uint32_t missionId, const std::map<uint32_t, Mission*>& missions);
private: private:
/** /**
@@ -54,5 +54,5 @@ private:
* @param missions the mission inventory to check the prerequisites against * @param missions the mission inventory to check the prerequisites against
* @return whether or not the mission identified by the specified ID can be accepted * @return whether or not the mission identified by the specified ID can be accepted
*/ */
static bool CheckPrerequisites(uint32_t missionId, const std::unordered_map<uint32_t, Mission*>& missions); static bool CheckPrerequisites(uint32_t missionId, const std::map<uint32_t, Mission*>& missions);
}; };

View File

@@ -1118,6 +1118,11 @@ void HandlePacket(Packet* packet) {
levelComponent->SetCharacterVersion(eCharacterVersion::UP_TO_DATE); levelComponent->SetCharacterVersion(eCharacterVersion::UP_TO_DATE);
[[fallthrough]]; [[fallthrough]];
} }
case eCharacterVersion::INVENTORY_PERSISTENT_IDS: {
LOG("Fixing racing meta missions");
missionComponent->FixRacingMetaMissions();
[[fallthrough]];
}
case eCharacterVersion::UP_TO_DATE: case eCharacterVersion::UP_TO_DATE:
break; break;
} }