From b033655e90e110e36f38c851075a3db93deefcc3 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Tue, 10 Nov 2026 15:14:44 -0600 Subject: [PATCH] fix: a rail that is off or whose preconditions fail is not ridden LWORailActivatorComponent::msgCheckUseRequirements (0x00c2c5b0) refuses a use, after the quick build check, when the row's preconditions fail or rail_activator_active is off, so a client does not ask. The server started the rail on any use request. The rows ask for an equipped item (44) or a completed mission (283 to 286). Co-Authored-By: Claude Opus 5.5 --- dGame/dComponents/RailActivatorComponent.cpp | 7 ++++++ dGame/dComponents/RailActivatorComponent.h | 8 +++++++ .../RailActivatorComponentTests.cpp | 22 +++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/dGame/dComponents/RailActivatorComponent.cpp b/dGame/dComponents/RailActivatorComponent.cpp index bd2066c4d..0c07e06e8 100644 --- a/dGame/dComponents/RailActivatorComponent.cpp +++ b/dGame/dComponents/RailActivatorComponent.cpp @@ -9,6 +9,7 @@ #include "CombatMessages.h" #include "MovementMessages.h" #include "QuickBuildComponent.h" +#include "Preconditions.h" #include "Game.h" #include "Logger.h" #include "RenderComponent.h" @@ -42,6 +43,7 @@ void RailActivatorComponent::LoadDataFromTemplate() { m_ShowNameBillboard = tableData.showNameBillboard; m_CameraLocked = tableData.cameraLocked; m_CollisionEnabled = tableData.playerCollision; + m_Preconditions = tableData.preconditions; } // The level config: a key, when there, replaces the row's value, as StartRailMovement's flags replace the row's in @@ -71,6 +73,11 @@ bool RailActivatorComponent::OnRequestUse(GameMessages::RequestUseEvent& event) if (quickBuildComponent != nullptr && quickBuildComponent->GetState() != eQuickBuildState::COMPLETED) return true; + // LWORailActivatorComponent::msgCheckUseRequirements (0x00c2c5b0): after the quick build, the row's preconditions + // have to pass and the rail has to be on; the client does not send a use otherwise ("-1": the table has none) + if (!m_Active) return true; + if (!m_Preconditions.empty() && m_Preconditions != "-1" && !PreconditionExpression(m_Preconditions).Check(originator)) return true; + if (quickBuildComponent != nullptr) { // Don't want it to be destroyed while a player is using it quickBuildComponent->SetResetTime(quickBuildComponent->GetResetTime() + 10.0f); diff --git a/dGame/dComponents/RailActivatorComponent.h b/dGame/dComponents/RailActivatorComponent.h index 7be3f5a07..5c0b88ef6 100644 --- a/dGame/dComponents/RailActivatorComponent.h +++ b/dGame/dComponents/RailActivatorComponent.h @@ -46,6 +46,9 @@ public: * 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; } + + // The players that used the rail and have not left it yet + const std::vector& GetEntitiesOnRail() const { return m_EntitiesOnRail; } bool GetNoAggro() const { return m_NoAggro; } bool GetShowNameBillboard() const { return m_ShowNameBillboard; } private: @@ -131,6 +134,11 @@ private: */ bool m_UseDB; + /** + * The row's preconditions a player has to pass to use the rail + */ + std::string m_Preconditions; + /** * Client flag */ diff --git a/tests/dGameTests/dComponentsTests/RailActivatorComponentTests.cpp b/tests/dGameTests/dComponentsTests/RailActivatorComponentTests.cpp index a1138f3f2..a81c3b47a 100644 --- a/tests/dGameTests/dComponentsTests/RailActivatorComponentTests.cpp +++ b/tests/dGameTests/dComponentsTests/RailActivatorComponentTests.cpp @@ -5,6 +5,7 @@ #include "CDRailActivatorComponent.h" #include "Entity.h" #include "MovementMessages.h" +#include "ObjectMessages.h" #include "dGameMessagesTests/GameMessageTestUtils.h" #include "RailActivatorComponent.h" @@ -82,3 +83,24 @@ TEST_F(RailActivatorComponentTests, InactiveRailIsReportedInactive) { notify.bActive = false; EXPECT_FALSE(RoundTrip(notify).bActive); } + +// A rail that is off is not ridden: LWORailActivatorComponent::msgCheckUseRequirements (0x00c2c5b0) refuses the use, so a +// client never asks for it +TEST_F(RailActivatorComponentTests, ARailThatIsOffIsNotUsed) { + entity->SetVar(u"rail_activator_active", false); + auto* const rail = entity->AddComponent(COMPONENT_ID); + Entity user(77, info); + GameMessages::RequestUse use; + GameMessages::RequestUseEvent event(use, user); + EXPECT_TRUE(rail->OnRequestUse(event)); + EXPECT_TRUE(rail->GetEntitiesOnRail().empty()); +} + +TEST_F(RailActivatorComponentTests, ARailThatIsOnIsUsed) { + auto* const rail = entity->AddComponent(COMPONENT_ID); + Entity user(78, info); + GameMessages::RequestUse use; + GameMessages::RequestUseEvent event(use, user); + EXPECT_TRUE(rail->OnRequestUse(event)); + EXPECT_EQ(rail->GetEntitiesOnRail(), std::vector{ 78 }); +}