From a253fd6ee9ee80ca5b475d9c6f0af87ecc6b6cf3 Mon Sep 17 00:00:00 2001 From: capyb2222 Date: Sat, 12 Sep 2026 19:37:52 -0700 Subject: [PATCH] fix(game): only report missions the client has --- src/data/excel.cpp | 13 ++++++++++++- src/data/excel.h | 2 ++ src/game/scene.cpp | 2 ++ tests/test_game.cpp | 8 ++++++++ 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/data/excel.cpp b/src/data/excel.cpp index bfa38e1..2d3a63a 100644 --- a/src/data/excel.cpp +++ b/src/data/excel.cpp @@ -611,7 +611,17 @@ bool Tables::load(const std::vector& sources) { if (std::find(into.begin(), into.end(), id) == into.end()) into.push_back(id); } }; - ids("MainMission.json", "MainMissionID", mainMissions_); + // The client logs "can't find MainMissionRow" for a mission its own table lacks, and + // the beta table is the client's, so the last source with this table wins. + const std::vector& missionRows = table("MainMission.json"); + if (!missionRows.empty()) mainMissions_.clear(); + for (const json* row : missionRows) { + uint32_t id = u32(*row, "MainMissionID"); + if (id == 0) continue; + if (std::find(mainMissions_.begin(), mainMissions_.end(), id) == mainMissions_.end()) { + mainMissions_.push_back(id); + } + } ids("TutorialData.json", "TutorialID", tutorials_); ids("TutorialGuideGroup.json", "GroupID", tutorialGuides_); ids("QuestData.json", "QuestID", quests_); @@ -869,6 +879,7 @@ bool Tables::load(const std::vector& sources) { floor.battleTargetIds = extra->second.battleTargetIds; } challengeExtras_.clear(); + mainMissionIds_.insert(mainMissions_.begin(), mainMissions_.end()); loaded_ = !avatars_.empty() && !stages_.empty(); if (!loaded_) { diff --git a/src/data/excel.h b/src/data/excel.h index ff3a791..bfc2b01 100644 --- a/src/data/excel.h +++ b/src/data/excel.h @@ -279,6 +279,7 @@ public: // Reported wholesale as already done, which is what keeps the client out of the // prologue -- with nothing finished it waits for the intro to drive it. const std::vector& mainMissions() const { return mainMissions_; } + bool knowsMainMission(uint32_t id) const { return mainMissionIds_.count(id) != 0; } const std::vector& tutorials() const { return tutorials_; } const std::vector& tutorialGuides() const { return tutorialGuides_; } const std::vector& quests() const { return quests_; } @@ -332,6 +333,7 @@ private: std::unordered_set battlePassRewards_; std::vector globalMazeBuffs_; std::vector mainMissions_; + std::unordered_set mainMissionIds_; std::vector tutorials_; std::vector tutorialGuides_; std::vector quests_; diff --git a/src/game/scene.cpp b/src/game/scene.cpp index 376050f..91a6292 100644 --- a/src/game/scene.cpp +++ b/src/game/scene.cpp @@ -291,6 +291,8 @@ bool load(Player& player, uint32_t entryId, uint32_t teleportId, bool commit, const core::GameplayConfig& gameplay = core::Config::get().gameplay; for (uint32_t mission : group.finishedMainMissions) { if (gameplay.missionSkipped(mission)) continue; + // The dump comes from production data, which has missions this client does not. + if (!data::Tables::get().knowsMainMission(mission)) continue; missions.finished_main_mission_id_list.push_back(mission); } for (uint32_t mission : group.finishedSubMissions) { diff --git a/tests/test_game.cpp b/tests/test_game.cpp index 787fce8..f7c348d 100644 --- a/tests/test_game.cpp +++ b/tests/test_game.cpp @@ -188,6 +188,14 @@ void testTables() { check(tables.entrance(1000001) != nullptr, "map entrance 1000001 is loaded"); check(tables.plane(10000) != nullptr, "plane 10000 is loaded"); + + // Main missions come from the client's own (beta) table, which lacks 2031101. + const std::vector& missions = tables.mainMissions(); + check(!tables.knowsMainMission(2031101) && + std::find(missions.begin(), missions.end(), 2031101u) == missions.end(), + "a mission the client lacks is not reported"); + check(tables.knowsMainMission(1054600) && tables.knowsMainMission(1000101), + "beta and production missions both are"); } bool contains(const std::vector& ids, uint32_t id) {