diff --git a/dGame/dComponents/RailActivatorComponent.cpp b/dGame/dComponents/RailActivatorComponent.cpp index d3aef96d4..0377a30b5 100644 --- a/dGame/dComponents/RailActivatorComponent.cpp +++ b/dGame/dComponents/RailActivatorComponent.cpp @@ -44,6 +44,7 @@ RailActivatorComponent::RailActivatorComponent(Entity* parent, const int32_t com m_NotifyArrived = parent->GetVar(u"rail_notify_activator_arrived"); m_ShowNameBillboard = levelOr(u"rail_show_name_billboard", tableData.showNameBillboard); m_UseDB = parent->GetVar(u"rail_use_db"); + m_Active = levelOr(u"rail_activator_active", true); m_CameraLocked = tableData.cameraLocked; m_CollisionEnabled = tableData.playerCollision; } diff --git a/dGame/dComponents/RailActivatorComponent.h b/dGame/dComponents/RailActivatorComponent.h index a94342766..6b9b224fe 100644 --- a/dGame/dComponents/RailActivatorComponent.h +++ b/dGame/dComponents/RailActivatorComponent.h @@ -38,6 +38,11 @@ public: void OnCancelRailMovement(Entity* originator); bool GetDamageImmune() const { return m_DamageImmune; } + + /** + * Whether the rail can be used (level key rail_activator_active; every rail in the live levels has it set) + */ + bool GetActive() const { return m_Active; } bool GetNoAggro() const { return m_NoAggro; } bool GetShowNameBillboard() const { return m_ShowNameBillboard; } private: @@ -111,6 +116,8 @@ private: */ bool m_DamageImmune; + bool m_Active{ true }; + /** * Client flag */ diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index 9813c4b4a..1d87239f5 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -215,6 +215,7 @@ namespace { { CLIENT_RAIL_MOVEMENT_READY, []() { return std::make_unique(); } }, { CANCEL_RAIL_MOVEMENT, []() { return std::make_unique(); } }, { PLAYER_RAIL_ARRIVED_NOTIFICATION, []() { return std::make_unique(); } }, + { REQUEST_RAIL_ACTIVATOR_STATE, []() { return std::make_unique(); } }, { DISMOUNT_COMPLETE, []() { return std::make_unique(); } }, { ACKNOWLEDGE_POSSESSION, []() { return std::make_unique(); } }, { TOGGLE_GHOST_REFERENCE_OVERRIDE, []() { return std::make_unique(); } }, diff --git a/dGame/dGameMessages/MovementMessages.cpp b/dGame/dGameMessages/MovementMessages.cpp index d50153321..509fd7442 100644 --- a/dGame/dGameMessages/MovementMessages.cpp +++ b/dGame/dGameMessages/MovementMessages.cpp @@ -295,6 +295,24 @@ namespace GameMessages { } } + void RequestRailActivatorState::Handle(Entity& entity, const SystemAddress& sysAddr) { + const auto* const rail = entity.GetComponent(); + if (!rail) return; + NotifyRailActivatorStateChange notify; + notify.target = entity.GetObjectID(); + notify.bActive = rail->GetActive(); + notify.SendToClient(sysAddr); + } + + void NotifyRailActivatorStateChange::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bActive); + } + + bool NotifyRailActivatorStateChange::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bActive)); + return true; + } + void PlayerRailArrivedNotification::Serialize(RakNet::BitStream& bitStream) const { BitStreamUtils::WriteLengthPrefixed(bitStream, pathName); bitStream.Write(waypointNumber); diff --git a/dGame/dGameMessages/MovementMessages.h b/dGame/dGameMessages/MovementMessages.h index efcc51b39..fbd94b030 100644 --- a/dGame/dGameMessages/MovementMessages.h +++ b/dGame/dGameMessages/MovementMessages.h @@ -197,6 +197,22 @@ namespace GameMessages { bool bImmediate{}; }; + // Client -> server, no payload. The client's rail activator asks for its state when it is added to the world + // (LWORailActivatorComponent::SendMessage 0x00c00da0); live answered NotifyRailActivatorStateChange to that client. + struct RequestRailActivatorState : public NetGameMsg { + RequestRailActivatorState() : NetGameMsg(MessageType::Game::REQUEST_RAIL_ACTIVATOR_STATE) {} + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + }; + + // Server -> client. The client sets rail_activator_active and updates the rail's pick type (usable or not). + struct NotifyRailActivatorStateChange : public NetGameMsg { + NotifyRailActivatorStateChange() : NetGameMsg(MessageType::Game::NOTIFY_RAIL_ACTOVATOR_STATE_CHANGE) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + bool bActive{ true }; + }; + // Client -> server. struct PlayerRailArrivedNotification : public NetGameMsg { PlayerRailArrivedNotification() : NetGameMsg(MessageType::Game::PLAYER_RAIL_ARRIVED_NOTIFICATION) {} diff --git a/tests/dGameTests/dComponentsTests/RailActivatorComponentTests.cpp b/tests/dGameTests/dComponentsTests/RailActivatorComponentTests.cpp index 00cdc93fc..a1138f3f2 100644 --- a/tests/dGameTests/dComponentsTests/RailActivatorComponentTests.cpp +++ b/tests/dGameTests/dComponentsTests/RailActivatorComponentTests.cpp @@ -4,6 +4,8 @@ #include "CDComponentsRegistryTable.h" #include "CDRailActivatorComponent.h" #include "Entity.h" +#include "MovementMessages.h" +#include "dGameMessagesTests/GameMessageTestUtils.h" #include "RailActivatorComponent.h" #include @@ -57,3 +59,26 @@ TEST_F(RailActivatorComponentTests, NoRowAndNoKeysIsNotImmune) { EXPECT_FALSE(rail->GetDamageImmune()); EXPECT_FALSE(rail->GetNoAggro()); } + +// Live answered the client's RequestRailActivatorState (1479, no payload; 152 packets) with +// NotifyRailActivatorStateChange (1478) to that client: every one of the 413 live packets was bActive = true (80). +TEST_F(RailActivatorComponentTests, RequestRailActivatorStateIsAnswered) { + using namespace GameMessageTestUtils; + entity->AddComponent(COMPONENT_ID); + GameMessages::RequestRailActivatorState request; + const auto packets = Capture([&] { request.Handle(*entity, ClientAddress()); }); + ASSERT_EQ(packets.size(), 1u); + EXPECT_EQ(packets[0].sysAddr, ClientAddress()); + EXPECT_FALSE(packets[0].broadcast); + EXPECT_PACKET_EQ(FromHex("53 05 00 0c 00 00 00 00 01 00 00 00 00 00 00 00 c6 05 80", 145), FromCapture(packets[0])); +} + +TEST_F(RailActivatorComponentTests, InactiveRailIsReportedInactive) { + using namespace GameMessageTestUtils; + entity->SetVar(u"rail_activator_active", false); + const auto* const rail = entity->AddComponent(COMPONENT_ID); + EXPECT_FALSE(rail->GetActive()); + GameMessages::NotifyRailActivatorStateChange notify; + notify.bActive = false; + EXPECT_FALSE(RoundTrip(notify).bActive); +}