Files
DarkflameServer/tests/dGameTests/dComponentsTests/PropertyVendorComponentTests.cpp
Aaron Kimbrell baf8b24ad1 fix: a property is rented only with the level's required mission
LWOPropertyVendorComponent::onCheckUseRequirements (0x00c2e210) lets a
player use the property guard only when the level's
property_required_mission (LoadConfigData 0x00c2e550) is finished. The
server opened the vendor and rented the property on any request.

The guard is now not opened without the mission, and a rent request
without it is answered with PropertyRentalResponse code 9, which
onPropertyRentalResponse (0x00c56d70) shows as "you failed to rent" with
UGG_PROPRENT_Dont_have_achievement. A mission that was finished and is
being repeated counts, as in the client's list of finished missions. A
guard without the key asks for nothing.

The six property maps name missions 768, 872, 873, 874, 1293 and 1322.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 15:28:18 -06:00

114 lines
4.3 KiB
C++

// The property guard's mission: LWOPropertyVendorComponent::onCheckUseRequirements (0x00c2e210) lets a player use the
// guard only with the level's property_required_mission finished.
#include "GameDependencies.h"
#include "dGameMessagesTests/GameMessageTestUtils.h"
#include "CDClientDatabase.h"
#include "CDClientManager.h"
#include "CDMissionsTable.h"
#include "Entity.h"
#include "MissionComponent.h"
#include "PropertyMessages.h"
#include "PropertyVendorComponent.h"
#include "tinyxml2.h"
#include <gtest/gtest.h>
using namespace GameMessageTestUtils;
namespace {
// Mission IDs only these tests use
constexpr uint32_t REQUIRED = 993001;
constexpr uint32_t REPEATED = 993002;
}
class PropertyVendorComponentTests : public GameDependenciesTest {
protected:
void SetUp() override {
SetUpDependencies();
CDClientDatabase::Connect(":memory:"); // MissionComponent counts the achievements
CDClientDatabase::ExecuteDML("CREATE TABLE Missions (id INTEGER, isMission INTEGER);");
auto& missions = CDClientManager::GetEntriesMutable<CDMissionsTable>();
for (const auto id : { REQUIRED, REPEATED }) {
CDMissions row{};
row.id = id;
row.isMission = true;
row.repeatable = id == REPEATED;
missions.push_back(row);
}
guard = std::make_unique<Entity>(GUARD, info);
vendor = guard->AddComponent<PropertyVendorComponent>(-1);
player = std::make_unique<Entity>(PLAYER, info);
missions_ = player->AddComponent<MissionComponent>(-1);
}
void TearDown() override {
player.reset();
guard.reset();
CDClientManager::GetEntriesMutable<CDMissionsTable>().clear();
TearDownDependencies();
}
void Load(const std::string& done, const std::string& cur) {
tinyxml2::XMLDocument doc;
const auto xml = R"(<obj v="1"><mis><done>)" + done + "</done><cur>" + cur + "</cur></mis></obj>";
ASSERT_EQ(doc.Parse(xml.c_str()), tinyxml2::XML_SUCCESS);
missions_->LoadFromSaveData(doc);
}
static constexpr LWOOBJID GUARD = 0x0000400000112233LL;
static constexpr LWOOBJID PLAYER = 0x1000000000000001LL;
std::unique_ptr<Entity> guard;
std::unique_ptr<Entity> player;
PropertyVendorComponent* vendor = nullptr;
MissionComponent* missions_ = nullptr;
};
TEST_F(PropertyVendorComponentTests, AGuardWithoutAMissionAsksForNone) {
EXPECT_EQ(vendor->GetRequiredMission(), 0u);
EXPECT_TRUE(vendor->MayRent(*player));
}
TEST_F(PropertyVendorComponentTests, TheMissionMustBeFinished) {
guard->SetVar<uint32_t>(u"property_required_mission", REQUIRED);
EXPECT_EQ(vendor->GetRequiredMission(), REQUIRED);
EXPECT_FALSE(vendor->MayRent(*player)); // never taken
Load("", R"(<m id="993001" state="2"/>)");
EXPECT_FALSE(vendor->MayRent(*player)); // in progress
}
TEST_F(PropertyVendorComponentTests, AFinishedMissionRents) {
guard->SetVar<uint32_t>(u"property_required_mission", REQUIRED);
Load(R"(<m id="993001" cct="1" cts="1000"/>)", "");
EXPECT_TRUE(vendor->MayRent(*player));
}
// The client's list of finished missions keeps a mission that is being repeated
TEST_F(PropertyVendorComponentTests, AFinishedMissionThatIsRepeatedRents) {
guard->SetVar<uint32_t>(u"property_required_mission", REPEATED);
Load(R"(<m id="993002" cct="1" cts="1000"/>)", R"(<m id="993002" state="10"/>)");
EXPECT_TRUE(vendor->MayRent(*player));
}
// A rent request without the mission is answered with code 9, which the client shows as "you failed to rent" and
// UGG_PROPRENT_Dont_have_achievement (LWOPropertyVendorComponent::onPropertyRentalResponse 0x00c56d70):
// clone u32 0, code u32 9, property u64 0, rent due i64 0
TEST_F(PropertyVendorComponentTests, RentingWithoutTheMissionIsRefused) {
guard->SetVar<uint32_t>(u"property_required_mission", REQUIRED);
const auto packets = Capture([&] { vendor->OnBuyFromVendor(player.get(), true, 0, 1); });
ASSERT_EQ(packets.size(), 1u);
EXPECT_PACKET_EQ(FromHex(
"53 05 00 0c 00 00 00 00 33 22 11 00 00 40 00 00 ee 02 "
"00 00 00 00 09 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", 336), FromCapture(packets[0]));
}
// With the mission, the request goes on to the property (none in this test: nothing is sent, nothing refused)
TEST_F(PropertyVendorComponentTests, RentingWithTheMissionIsNotRefused) {
guard->SetVar<uint32_t>(u"property_required_mission", REQUIRED);
Load(R"(<m id="993001" cct="1" cts="1000"/>)", "");
const auto packets = Capture([&] { vendor->OnBuyFromVendor(player.get(), true, 0, 1); });
EXPECT_TRUE(packets.empty());
}