diff --git a/dGame/dComponents/RocketLaunchpadControlComponent.cpp b/dGame/dComponents/RocketLaunchpadControlComponent.cpp index 3b8a5024a..3ebc7a145 100644 --- a/dGame/dComponents/RocketLaunchpadControlComponent.cpp +++ b/dGame/dComponents/RocketLaunchpadControlComponent.cpp @@ -48,6 +48,23 @@ void RocketLaunchpadControlComponent::LoadDataFromTemplate() { result.finalize(); } +// The level's keys. LWORocketLaunchComponentCommon::LoadConfigData (0x00c55150) reads them into the fields the row +// would fill, and the row replaces them only while rocketLaunchUseDBDefaults is true, its default (0x00c12ea0). A pad +// that turns it off is ruled by its level: 31 pads in the 1.10.64 maps, whose zones and scenes are their rows' but +// whose launch preconditions only the level has (the Venture Explorer's and Avant Gardens' pads). +void RocketLaunchpadControlComponent::LoadConfigData() { + if (!m_Parent->HasVar(u"rocketLaunchUseDBDefaults") || m_Parent->GetVarAs(u"rocketLaunchUseDBDefaults")) return; + + if (m_Parent->HasVar(u"targetZone")) m_TargetZone = m_Parent->GetVarAs(u"targetZone"); + m_DefaultZone = m_Parent->GetVarAs(u"propertyDefaultTargetZone"); + m_TargetScene = m_Parent->GetVarAsString(u"targetScene"); + m_GMLevel = m_Parent->GetVarAs(u"GMLevel"); + m_LaunchPrecondition = m_Parent->GetVarAs(u"rocketUseLaunchPrecondition") ? m_Parent->GetVarAsString(u"rocketLaunchPreCondition") : ""; + m_AltLandingScene = m_Parent->GetVarAsString(u"rocketAltLandingSpawnName"); + delete m_AltPrecondition; + m_AltPrecondition = new PreconditionExpression(m_Parent->GetVarAsString(u"rocketAltLandingPrecondition")); +} + RocketLaunchpadControlComponent::~RocketLaunchpadControlComponent() { delete m_AltPrecondition; } diff --git a/dGame/dComponents/RocketLaunchpadControlComponent.h b/dGame/dComponents/RocketLaunchpadControlComponent.h index 13ddf856b..a4e72b81a 100644 --- a/dGame/dComponents/RocketLaunchpadControlComponent.h +++ b/dGame/dComponents/RocketLaunchpadControlComponent.h @@ -28,6 +28,9 @@ public: ~RocketLaunchpadControlComponent() override; void LoadDataFromTemplate() override; + void LoadConfigData() override; + + const std::string& GetTargetScene() const { return m_TargetScene; } /** * Whether the player may launch from this pad: LWORocketLaunchComponentCommon refuses a player under the pad's GM @@ -146,12 +149,13 @@ private: PreconditionExpression* m_AltPrecondition{}; /** - * The GM level a player needs to launch from this pad (the row's gmLevel) + * The GM level a player needs to launch from this pad (the row's gmLevel, or the level's GMLevel) */ int32_t m_GMLevel{}; /** - * The row's launchPrecondition, when its useLaunchPrecondition is set: what a player has to pass to launch + * The row's launchPrecondition, when its useLaunchPrecondition is set: what a player has to pass to launch. For a pad + * whose level turns rocketLaunchUseDBDefaults off, the level's rocketLaunchPreCondition and rocketUseLaunchPrecondition. */ std::string m_LaunchPrecondition; diff --git a/tests/dGameTests/dComponentsTests/RocketLaunchTests.cpp b/tests/dGameTests/dComponentsTests/RocketLaunchTests.cpp index b5f3a87c2..a0aa844d9 100644 --- a/tests/dGameTests/dComponentsTests/RocketLaunchTests.cpp +++ b/tests/dGameTests/dComponentsTests/RocketLaunchTests.cpp @@ -73,3 +73,85 @@ TEST_F(RocketLaunchTests, APadWithAGmLevelLaunchesOnlyThoseAtIt) { player.SetGMLevel(eGameMasterLevel::OPERATOR); EXPECT_TRUE(gmOnly->MayLaunch(player)); } + +namespace { + void MakePadTables() { + CDClientDatabase::Connect(":memory:"); + CDClientDatabase::ExecuteDML("CREATE TABLE RocketLaunchpadControlComponent (id INTEGER, targetZone INTEGER, defaultZoneID INTEGER, targetScene TEXT, gmLevel INTEGER, useLaunchPrecondition INTEGER, launchPrecondition TEXT, useAltLandingPrecondition INTEGER, altLandingPrecondition TEXT, altLandingSpawnPointName TEXT);"); + // Row 3 is the Venture Explorer pad's (LOT 14214): no precondition in the table + // No precondition is known: one that is asked for fails for a player without components + CDClientDatabase::ExecuteDML("CREATE TABLE Preconditions (id INTEGER, type INTEGER, targetLOT TEXT, targetGroup TEXT, targetCount INTEGER, iconID INTEGER, localize INTEGER, validContexts INTEGER);"); + CDClientDatabase::ExecuteDML("INSERT INTO RocketLaunchpadControlComponent VALUES (3, 1100, 0, 'AGLanding', 0, 0, NULL, 0, NULL, NULL);"); + // Row 16: a Nimbus Station pad with the precondition in the table + CDClientDatabase::ExecuteDML("INSERT INTO RocketLaunchpadControlComponent VALUES (16, 1300, 0, 'GFLanding', 0, 1, '0', 0, '', '');"); + } +} + +// A pad whose level sets rocketLaunchUseDBDefaults to false is ruled by the level's keys, not its row: +// LWORocketLaunchComponentCommon::LoadConfigData (0x00c55150) reads them and the row is only loaded while the key is +// true (0x00c12ea0). The Venture Explorer's pads: the row has no precondition, the level has +// "237;8;127|128;6|55;107;282" with rocketUseLaunchPrecondition. +TEST_F(RocketLaunchTests, APadThatDoesNotUseTheTableIsRuledByItsLevel) { + MakePadTables(); + Entity pad(PAD, info); + pad.SetVar(u"rocketLaunchUseDBDefaults", false); + pad.SetVar(u"targetZone", 1100); + pad.SetVar(u"targetScene", u"AGLanding"); + pad.SetVar(u"GMLevel", 0); + pad.SetVar(u"rocketUseLaunchPrecondition", true); + pad.SetVar(u"rocketLaunchPreCondition", u"0"); // an empty precondition table: nobody passes "0" + // AddComponent loads the row, then the level's keys + auto* const component = pad.AddComponent(3); + + Entity player(PLAYER, info); + // The row alone asks for nothing + Entity rowPad(PAD + 2, info); + EXPECT_TRUE(rowPad.AddComponent(3)->MayLaunch(player)); + EXPECT_EQ(component->GetTargetZone(), 1100); + EXPECT_EQ(component->GetTargetScene(), "AGLanding"); + EXPECT_FALSE(component->MayLaunch(player)); + + // The level's GM level counts as well (the test pads to the Gnarled Forest and Forbidden Valley properties: 9) + Entity gmPad(PAD + 1, info); + gmPad.SetVar(u"rocketLaunchUseDBDefaults", false); + gmPad.SetVar(u"targetZone", 1100); + gmPad.SetVar(u"GMLevel", 9); + auto* const gmOnly = gmPad.AddComponent(3); + EXPECT_FALSE(gmOnly->MayLaunch(player)); + player.SetGMLevel(eGameMasterLevel::OPERATOR); + EXPECT_TRUE(gmOnly->MayLaunch(player)); +} + +// The level's precondition is only used with the level's rocketUseLaunchPrecondition (Nimbus Station's pad to +// Forbidden Valley's property has "115" without it) +TEST_F(RocketLaunchTests, ALevelPreconditionThatIsNotInUseIsNotChecked) { + MakePadTables(); + Entity pad(PAD, info); + pad.SetVar(u"rocketLaunchUseDBDefaults", false); + pad.SetVar(u"targetZone", 1450); + pad.SetVar(u"rocketUseLaunchPrecondition", false); + pad.SetVar(u"rocketLaunchPreCondition", u"0"); + auto* const component = pad.AddComponent(3); + Entity player(PLAYER, info); + EXPECT_TRUE(component->MayLaunch(player)); + EXPECT_EQ(component->GetTargetZone(), 1450); +} + +// With rocketLaunchUseDBDefaults true (the client's default, and 12 pads in the maps say so) the row rules and the +// level's keys are not looked at +TEST_F(RocketLaunchTests, APadThatUsesTheTableIgnoresItsLevelKeys) { + MakePadTables(); + for (const bool withKey : { true, false }) { + Entity pad(PAD, info); + if (withKey) pad.SetVar(u"rocketLaunchUseDBDefaults", true); + pad.SetVar(u"targetZone", 1400); + pad.SetVar(u"targetScene", u"FVGate"); + pad.SetVar(u"GMLevel", 9); + pad.SetVar(u"rocketUseLaunchPrecondition", false); + auto* const component = pad.AddComponent(16); + EXPECT_EQ(component->GetTargetZone(), 1300); + EXPECT_EQ(component->GetTargetScene(), "GFLanding"); + Entity player(PLAYER, info); + EXPECT_FALSE(component->MayLaunch(player)); // the row's precondition, not the level's "none" + } +}