diff --git a/dGame/dComponents/CharacterComponent.cpp b/dGame/dComponents/CharacterComponent.cpp index d8d07bb67..6d7eb5fe9 100644 --- a/dGame/dComponents/CharacterComponent.cpp +++ b/dGame/dComponents/CharacterComponent.cpp @@ -13,6 +13,7 @@ #include "HavokVehiclePhysicsComponent.h" #include "GameMessages.h" #include "EffectsMessages.h" +#include "PlayerMessages.h" #include "Item.h" #include "Amf3.h" #include "eGameMasterLevel.h" @@ -550,6 +551,16 @@ void CharacterComponent::TrackMissionCompletion(bool isAchievement) { } } +void CharacterComponent::SendZoneStatisticIncrement(const std::u16string& statName) const { + GameMessages::ModifyPlayerZoneStatistic statistic; + statistic.target = m_Parent->GetObjectID(); + statistic.bSet = false; + statistic.statName = statName; + statistic.statValue = 1; + statistic.zoneID = Game::zoneManager->GetZoneID().GetMapID(); + statistic.SendToClient(m_Parent->GetSystemAddress()); +} + void CharacterComponent::TrackLOTCollection(LOT lot) { const auto statistic = GetPowerUpStatistic(lot); if (statistic) UpdatePlayerStatistic(statistic.value()); diff --git a/dGame/dComponents/CharacterComponent.h b/dGame/dComponents/CharacterComponent.h index 05e5b7f4b..78afb19ad 100644 --- a/dGame/dComponents/CharacterComponent.h +++ b/dGame/dComponents/CharacterComponent.h @@ -194,6 +194,13 @@ public: */ void TrackMissionCompletion(bool isAchievement); + /** + * Tells this player's client one more of a zone statistic in the current zone, as live did for + * "QuickBuildsCompleted" and "AchievementsCompleted" (ModifyPlayerZoneStatistic: not set, value 1, this map). + * The client counts enemies, coins and bricks itself. + */ + void SendZoneStatisticIncrement(const std::u16string& statName) const; + /** * Handles statistics related to collecting heart flags and imagination bricks * @param lot the lot of the object that was collected diff --git a/dGame/dComponents/QuickBuildComponent.cpp b/dGame/dComponents/QuickBuildComponent.cpp index e05811bf1..ab5813c5c 100644 --- a/dGame/dComponents/QuickBuildComponent.cpp +++ b/dGame/dComponents/QuickBuildComponent.cpp @@ -477,6 +477,7 @@ void QuickBuildComponent::CompleteQuickBuild(Entity* const user) { enableRebuild.fDuration = 0.0f; enableRebuild.user = user->GetObjectID(); enableRebuild.Send(UNASSIGNED_SYSTEM_ADDRESS); + characterComponent->SendZoneStatisticIncrement(u"QuickBuildsCompleted"); GameMessages::TerminateInteraction(user->GetObjectID(), eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID()).Send(UNASSIGNED_SYSTEM_ADDRESS); diff --git a/dGame/dMission/Mission.cpp b/dGame/dMission/Mission.cpp index ea853702c..c9e3e2c96 100644 --- a/dGame/dMission/Mission.cpp +++ b/dGame/dMission/Mission.cpp @@ -341,6 +341,11 @@ void Mission::Complete(const bool yieldRewards) { missionComponent->Progress(eMissionTaskType::RACING, info.id, static_cast(eRacingTaskParam::COMPLETE_TRACK_TASKS)); + // Live sent this after the completion and the achievement tasks it progressed (186 in the live captures). + if (characterComponent != nullptr && !info.isMission) { + characterComponent->SendZoneStatisticIncrement(u"AchievementsCompleted"); + } + auto* missionEmailTable = CDClientManager::GetTable(); const auto missionId = GetMissionId(); diff --git a/tests/dGameTests/dComponentsTests/QuickBuildCompleteTests.cpp b/tests/dGameTests/dComponentsTests/QuickBuildCompleteTests.cpp index 7cfad2153..67306007e 100644 --- a/tests/dGameTests/dComponentsTests/QuickBuildCompleteTests.cpp +++ b/tests/dGameTests/dComponentsTests/QuickBuildCompleteTests.cpp @@ -5,10 +5,13 @@ #include "CharacterComponent.h" #include "CDClientDatabase.h" #include "Entity.h" +#include "PlayerMessages.h" +#include "dZoneManager.h" #include "QuickBuildComponent.h" #include "QuickBuildMessages.h" #include "eQuickBuildState.h" +#include #include #include @@ -37,6 +40,7 @@ protected: void SetUp() override { SetUpDependencies(); ConnectEmptyCDClient(); + Game::zoneManager->LoadZone(LWOZONEID(1101, 0, 0)); // the zone of the live samples below auto playerInfo = info; playerInfo.lot = 1; player = std::make_unique(0x1000000000000001LL, playerInfo); @@ -88,3 +92,34 @@ TEST_F(QuickBuildCompleteTests, SuccessHasNoDuration) { EXPECT_EQ(enables[0].fDuration, 0.0f); EXPECT_EQ(enables[0].user, player->GetObjectID()); } + +// Live sends the builder one more QuickBuildsCompleted for this zone after the EnableRebuild. Bytes: a live +// ModifyPlayerZoneStatistic from zone 1101 with the player ID replaced. +TEST_F(QuickBuildCompleteTests, ZoneStatisticFollowsEnableRebuild) { + const auto sent = Build(); + const auto ids = SentGameMessageIds(sent); + const auto enable = std::find(ids.begin(), ids.end(), MessageType::Game::ENABLE_REBUILD); + const auto statistic = std::find(ids.begin(), ids.end(), MessageType::Game::MODIFY_PLAYER_ZONE_STATISTIC); + ASSERT_NE(statistic, ids.end()); + EXPECT_LT(enable, statistic); + + std::vector statistics; + for (const auto& packet : sent) { + if (!SentGameMessages({ packet }).empty()) statistics.push_back(packet); + } + ASSERT_EQ(statistics.size(), 1u); + EXPECT_EQ(statistics[0].sysAddr, ClientAddress()); + EXPECT_FALSE(statistics[0].broadcast); + EXPECT_PACKET_EQ(FromHex( + "53 05 00 0c 00 00 00 00 01 00 00 00 00 00 00 10 16 04 0a 00 00 00 28 80 3a 80 34 80 31 80 35 80 21 00 3a 80 34 80 " + "36 00 32 00 39 80 21 80 37 80 36 80 38 00 36 00 32 80 3a 00 32 80 32 00 40 40 00 00 29 a0 80", 547), FromCapture(statistics[0])); +} + +// The same message for a completed achievement (sent from Mission::Complete). +TEST_F(QuickBuildCompleteTests, AchievementZoneStatisticMatchesLive) { + const auto sent = Capture([&] { player->GetComponent()->SendZoneStatisticIncrement(u"AchievementsCompleted"); }); + ASSERT_EQ(sent.size(), 1u); + EXPECT_PACKET_EQ(FromHex( + "53 05 00 0c 00 00 00 00 01 00 00 00 00 00 00 10 16 04 0a 80 00 00 20 80 31 80 34 00 34 80 32 80 3b 00 32 80 36 80 " + "32 80 37 00 3a 00 39 80 21 80 37 80 36 80 38 00 36 00 32 80 3a 00 32 80 32 00 40 40 00 00 29 a0 80", 563), FromCapture(sent[0])); +}