diff --git a/dGame/dComponents/RocketLaunchpadControlComponent.cpp b/dGame/dComponents/RocketLaunchpadControlComponent.cpp index f5148d3a2..3b8a5024a 100644 --- a/dGame/dComponents/RocketLaunchpadControlComponent.cpp +++ b/dGame/dComponents/RocketLaunchpadControlComponent.cpp @@ -30,7 +30,7 @@ RocketLaunchpadControlComponent::RocketLaunchpadControlComponent(Entity* parent, // The RocketLaunchpadControlComponent row void RocketLaunchpadControlComponent::LoadDataFromTemplate() { auto query = CDClientDatabase::CreatePreppedStmt( - "SELECT targetZone, defaultZoneID, targetScene, altLandingPrecondition, altLandingSpawnPointName FROM RocketLaunchpadControlComponent WHERE id = ?;"); + "SELECT targetZone, defaultZoneID, targetScene, altLandingPrecondition, altLandingSpawnPointName, gmLevel, useLaunchPrecondition, launchPrecondition FROM RocketLaunchpadControlComponent WHERE id = ?;"); query.bind(1, m_ComponentID); auto result = query.execQuery(); @@ -41,6 +41,8 @@ void RocketLaunchpadControlComponent::LoadDataFromTemplate() { m_TargetScene = result.getStringField("targetScene"); m_AltPrecondition = new PreconditionExpression(result.getStringField("altLandingPrecondition")); m_AltLandingScene = result.getStringField("altLandingSpawnPointName"); + m_GMLevel = result.getIntField("gmLevel", 0); + if (result.getIntField("useLaunchPrecondition", 0) != 0) m_LaunchPrecondition = result.getStringField("launchPrecondition", ""); } result.finalize(); @@ -57,6 +59,11 @@ GameMessages::FireEventClientSide RocketLaunchpadControlComponent::MakeRocketEqu return rocketEquipped; } +bool RocketLaunchpadControlComponent::MayLaunch(Entity& player) const { + if (static_cast(player.GetGMLevel()) < m_GMLevel) return false; + return m_LaunchPrecondition.empty() || PreconditionExpression(m_LaunchPrecondition).Check(&player); +} + void RocketLaunchpadControlComponent::Launch(Entity* originator, LWOMAPID mapId, LWOCLONEID cloneId, int32_t worldIndex) { auto zone = mapId == LWOMAPID_INVALID ? m_TargetZone : mapId; @@ -64,6 +71,8 @@ void RocketLaunchpadControlComponent::Launch(Entity* originator, LWOMAPID mapId, return; } + if (!MayLaunch(*originator)) return; + // This also gets triggered by a proximity monitor + item equip, I will set that up when havok is ready auto* characterComponent = originator->GetComponent(); auto* character = originator->GetCharacter(); diff --git a/dGame/dComponents/RocketLaunchpadControlComponent.h b/dGame/dComponents/RocketLaunchpadControlComponent.h index c2e63ee36..13ddf856b 100644 --- a/dGame/dComponents/RocketLaunchpadControlComponent.h +++ b/dGame/dComponents/RocketLaunchpadControlComponent.h @@ -29,6 +29,13 @@ public: void LoadDataFromTemplate() override; + /** + * Whether the player may launch from this pad: LWORocketLaunchComponentCommon refuses a player under the pad's GM + * level (msgFireEventClientSide 0x00cb78b0) or that fails its launch precondition (msgCheckUseRequirements + * 0x00c7d0d0), so a client does not ask + */ + bool MayLaunch(Entity& player) const; + /** * Launches some entity to another world * @param originator the entity to launch @@ -136,7 +143,17 @@ private: /** * Some precondition that needs to be met to trigger the alternative landing scene */ - PreconditionExpression* m_AltPrecondition; + PreconditionExpression* m_AltPrecondition{}; + + /** + * The GM level a player needs to launch from this pad (the row's gmLevel) + */ + int32_t m_GMLevel{}; + + /** + * The row's launchPrecondition, when its useLaunchPrecondition is set: what a player has to pass to launch + */ + std::string m_LaunchPrecondition; /** * Notifies the master server to prepare some world for a player to be able to travel to it diff --git a/tests/dGameTests/dComponentsTests/RocketLaunchTests.cpp b/tests/dGameTests/dComponentsTests/RocketLaunchTests.cpp index 45f0ff75b..b5f3a87c2 100644 --- a/tests/dGameTests/dComponentsTests/RocketLaunchTests.cpp +++ b/tests/dGameTests/dComponentsTests/RocketLaunchTests.cpp @@ -1,6 +1,8 @@ #include "GameDependencies.h" #include "dGameMessagesTests/GameMessageTestUtils.h" +#include "CDClientDatabase.h" +#include "Entity.h" #include "ObjectMessages.h" #include "RocketLaunchpadControlComponent.h" @@ -49,3 +51,25 @@ TEST_F(RocketLaunchTests, RocketEquippedIsBroadcast) { ASSERT_EQ(sent.size(), 1u); EXPECT_TRUE(sent[0].broadcast); } + +// A pad's row can ask for a GM level (the AG test pad: 9); LWORocketLaunchComponentCommon::msgFireEventClientSide +// (0x00cb78b0) does not launch a player under it +TEST_F(RocketLaunchTests, APadWithAGmLevelLaunchesOnlyThoseAtIt) { + 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);"); + CDClientDatabase::ExecuteDML("INSERT INTO RocketLaunchpadControlComponent VALUES (1, 1100, 0, 'AGLaunch', 9, 0, '', 0, '', '');"); + CDClientDatabase::ExecuteDML("INSERT INTO RocketLaunchpadControlComponent VALUES (2, 1200, 0, 'NS_NT', 0, 0, '47', 0, '', '');"); + + Entity gmPad(PAD, info); + auto* const gmOnly = gmPad.AddComponent(1); + gmOnly->LoadDataFromTemplate(); + Entity openPad(PAD + 1, info); + auto* const open = openPad.AddComponent(2); + open->LoadDataFromTemplate(); + + Entity player(PLAYER, info); + EXPECT_FALSE(gmOnly->MayLaunch(player)); + EXPECT_TRUE(open->MayLaunch(player)); // its precondition is not in use + player.SetGMLevel(eGameMasterLevel::OPERATOR); + EXPECT_TRUE(gmOnly->MayLaunch(player)); +}