Files
DarkflameServer/tests/dGameTests/dComponentsTests/CollectibleComponentTests.cpp
Aaron Kimbrell 8dd279cb45 fix: write a collectible's id on construction only
LWOCollectibleComponent::Deserialize (0x00c45ec0) reads the 16-bit id
on construction and nothing on an update. DLU wrote the id on every
serialization, so the client read 16 stray bits as the flag bits of the
components after it. The replica decoder follows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 14:26:46 -06:00

109 lines
4.0 KiB
C++

// CollectibleComponent.requirement_mission (a server-only table): collectibles of a mission to accept only count while
// the player has that mission.
#include "GameDependencies.h"
#include "BitStream.h"
#include "GameMessages.h"
#include "CDClientDatabase.h"
#include "CDClientManager.h"
#include "CDCollectibleComponentTable.h"
#include "CDMissionsTable.h"
#include "CollectibleComponent.h"
#include "Entity.h"
#include "MissionComponent.h"
#include "tinyxml2.h"
#include <gtest/gtest.h>
class CollectibleComponentTests : public GameDependenciesTest {
protected:
// Rows as in the 1.10.64 CDClient: 78 is the Ice Dragon Relic (requirement 2040, a mission to accept), 12 the Pirate
// Flag (requirement 12, an achievement), 45 a test item (66666666, no such mission), 1 none
void SetUp() override {
SetUpDependencies();
CDClientDatabase::Connect(":memory:"); // MissionComponent counts the achievements
CDClientDatabase::ExecuteDML("CREATE TABLE Missions (id INTEGER, isMission INTEGER);");
auto& collectibles = CDClientManager::GetEntriesMutable<CDCollectibleComponentTable>();
collectibles[78] = { 78, 2040 };
collectibles[12] = { 12, 12 };
collectibles[45] = { 45, 66666666 };
collectibles[1] = { 1, -1 };
auto& missions = CDClientManager::GetEntriesMutable<CDMissionsTable>();
CDMissions relics{};
relics.id = 2040;
relics.isMission = true;
missions.push_back(relics);
CDMissions flags{};
flags.id = 12;
flags.isMission = false;
missions.push_back(flags);
}
void TearDown() override {
CDClientManager::GetEntriesMutable<CDCollectibleComponentTable>().clear();
CDClientManager::GetEntriesMutable<CDMissionsTable>().clear();
TearDownDependencies();
}
static void GiveMission(MissionComponent& missions, const char* state) {
tinyxml2::XMLDocument doc;
const std::string xml = std::string(R"(<obj v="1"><mis><done/><cur><m id="2040" state=")") + state + R"("/></cur></mis></obj>)";
ASSERT_EQ(doc.Parse(xml.c_str()), tinyxml2::XML_SUCCESS);
missions.LoadFromSaveData(doc);
}
};
TEST_F(CollectibleComponentTests, RequirementMissionIsRead) {
Entity object(40, info);
EXPECT_EQ(object.AddComponent<CollectibleComponent>(78, 5)->GetRequirementMission(), 2040);
Entity other(41, info);
EXPECT_EQ(other.AddComponent<CollectibleComponent>(999, 5)->GetRequirementMission(), -1); // no row
}
// LWOCollectibleComponent::Deserialize reads the 16-bit id on construction and nothing on an update. DLU wrote the id
// on updates too, which the client read as the next components' flag bits.
TEST_F(CollectibleComponentTests, TheIdIsWrittenOnConstructionOnly) {
Entity object(22, info);
auto* const collectible = object.AddComponent<CollectibleComponent>(78, 5);
RakNet::BitStream construction;
collectible->Serialize(construction, true);
ASSERT_EQ(construction.GetNumberOfBitsUsed(), 16u);
int16_t id{};
construction.Read(id);
EXPECT_EQ(id, 5);
RakNet::BitStream update;
collectible->Serialize(update, false);
EXPECT_EQ(update.GetNumberOfBitsUsed(), 0u);
}
TEST_F(CollectibleComponentTests, AchievementsAndMissingMissionsAlwaysCount) {
Entity player(42, info);
player.AddComponent<MissionComponent>(-1);
for (const int32_t component : { 12, 45, 1, 999 }) {
Entity object(43, info);
EXPECT_TRUE(object.AddComponent<CollectibleComponent>(component, 5)->CountsFor(player)) << component;
}
}
TEST_F(CollectibleComponentTests, MissionCollectiblesCountOnlyWhileTheMissionIsAccepted) {
Entity relic(44, info);
const auto* const collectible = relic.AddComponent<CollectibleComponent>(78, 5);
Entity player(45, info);
auto* const missions = player.AddComponent<MissionComponent>(-1);
EXPECT_FALSE(collectible->CountsFor(player)); // never offered
GiveMission(*missions, "2"); // ACTIVE
EXPECT_TRUE(collectible->CountsFor(player));
GiveMission(*missions, "10"); // COMPLETE_ACTIVE (a repeat)
EXPECT_TRUE(collectible->CountsFor(player));
GiveMission(*missions, "1"); // AVAILABLE: offered, not accepted
EXPECT_FALSE(collectible->CountsFor(player));
Entity noMissions(46, info);
EXPECT_FALSE(collectible->CountsFor(noMissions));
}