fix(rails): rider flags default to the RailActivatorComponent row

StartRailMovement's bDamageImmune, bNoAggro and bShowNameBillboard came
only from the level keys rail_activator_damage_immune, rail_no_aggro and
rail_show_name_billboard, which 23 of the 80 level rails do not have, so
those rails sent false. The client reads the same flags from the
RailActivatorComponent row (DamageImmune, NoAggro, ShowNameBillboard; all
11 rows are 1) in LWOPlayerForcedMovementComponent::LoadRailData
(0x00c85dc0), and the message's values replace the row's unless bUseDB
(msgStartRailMovement 0x00ccd9c0). The server now takes the row's value
and lets a level key replace it when the key is there. Wire format is
unchanged; only the flag values sent for the 23 rails change (to true,
matching the 5 live samples).

In game: ride the 23 rails without the keys, all in the Ninjago earth gauntlet
(earthgauntlet levels: dart spinners, 8 spinners, millstones, boss) near enemies: you are not
damaged or targeted while riding, and name billboards behave as on the
other rails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-28 23:34:42 -05:00
parent 54cd9abd7a
commit 7440e5744d
4 changed files with 73 additions and 3 deletions

View File

@@ -2,6 +2,7 @@ set(DCOMPONENTS_TESTS
"DeletionRestrictionTests.cpp"
"InventorySaveTests.cpp"
"DestroyableLevelConfigTests.cpp"
"RailActivatorComponentTests.cpp"
"VendorBuybackTests.cpp"
"RacingWrongWayTests.cpp"
"DestroyableComponentTests.cpp"

View File

@@ -0,0 +1,59 @@
#include "GameDependencies.h"
#include "CDClientManager.h"
#include "CDComponentsRegistryTable.h"
#include "CDRailActivatorComponent.h"
#include "Entity.h"
#include "RailActivatorComponent.h"
#include <gtest/gtest.h>
// The rider flags come from the RailActivatorComponent row; a level key, when there, replaces the row's value.
class RailActivatorComponentTests : public GameDependenciesTest {
protected:
static constexpr int32_t COMPONENT_ID = 9999;
std::unique_ptr<Entity> entity;
void SetUp() override {
SetUpDependencies();
CDRailActivatorComponent row{};
row.id = COMPONENT_ID;
row.damageImmune = true;
row.noAggro = true;
row.showNameBillboard = true;
CDClientManager::GetEntriesMutable<CDRailActivatorComponentTable>().push_back(row);
// The entity's own LOT has no components, so it doesn't look anything up in a database
CDClientManager::GetEntriesMutable<CDComponentsRegistryTable>().insert_or_assign(static_cast<uint64_t>(info.lot), 0);
entity = std::make_unique<Entity>(1, info);
}
void TearDown() override {
entity.reset();
CDClientManager::GetEntriesMutable<CDRailActivatorComponentTable>().clear();
TearDownDependencies();
}
};
TEST_F(RailActivatorComponentTests, WithoutLevelKeysTheRowIsUsed) {
const auto* const rail = entity->AddComponent<RailActivatorComponent>(COMPONENT_ID);
EXPECT_TRUE(rail->GetDamageImmune());
EXPECT_TRUE(rail->GetNoAggro());
EXPECT_TRUE(rail->GetShowNameBillboard());
}
TEST_F(RailActivatorComponentTests, LevelKeysReplaceTheRow) {
entity->SetVar<bool>(u"rail_activator_damage_immune", false);
entity->SetVar<bool>(u"rail_no_aggro", false);
entity->SetVar<bool>(u"rail_show_name_billboard", false);
const auto* const rail = entity->AddComponent<RailActivatorComponent>(COMPONENT_ID);
EXPECT_FALSE(rail->GetDamageImmune());
EXPECT_FALSE(rail->GetNoAggro());
EXPECT_FALSE(rail->GetShowNameBillboard());
}
TEST_F(RailActivatorComponentTests, NoRowAndNoKeysIsNotImmune) {
const auto* const rail = entity->AddComponent<RailActivatorComponent>(COMPONENT_ID + 1);
EXPECT_FALSE(rail->GetDamageImmune());
EXPECT_FALSE(rail->GetNoAggro());
}