From 38fe226e6fe0c41fd29213288066610b3bbdbed7 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Tue, 10 Nov 2026 15:14:44 -0600 Subject: [PATCH] fix: a proximity monitor row lists proximity types, not radii LWOProximityMonitorComponent::LoadDataFromTemplate (0x00c2dfb0) reads the row's Proximities as ids of ProximityTypes rows, and SendMessage (0x00cbb930) makes, on AddedToWorld, a proximity under the type's name and radius (30 when missing) for each type loaded on its side. The ids were read as two radii: every launch pad ("7,8", the client's RocketSign and WorldLauncher types) got a rocketSmall sphere of 7 and a rocketLarge one of 8 that nothing used. Now the row's and the type's LoadOnServer decide; of the 40 rows only a test object's has one. Co-Authored-By: Claude Opus 5.5 --- .../dComponents/ProximityMonitorComponent.cpp | 28 ++++++--- .../dComponentsTests/CMakeLists.txt | 1 + .../ProximityMonitorComponentTests.cpp | 59 +++++++++++++++++++ 3 files changed, 79 insertions(+), 9 deletions(-) create mode 100644 tests/dGameTests/dComponentsTests/ProximityMonitorComponentTests.cpp diff --git a/dGame/dComponents/ProximityMonitorComponent.cpp b/dGame/dComponents/ProximityMonitorComponent.cpp index 112fc2429..6a7fb696a 100644 --- a/dGame/dComponents/ProximityMonitorComponent.cpp +++ b/dGame/dComponents/ProximityMonitorComponent.cpp @@ -1,5 +1,6 @@ #include "ProximityMonitorComponent.h" #include "CDClientManager.h" +#include "CDClientDatabase.h" #include "CDProximityMonitorComponentTable.h" #include "GameMessages.h" #include "dZoneManager.h" @@ -19,20 +20,29 @@ ProximityMonitorComponent::ProximityMonitorComponent(Entity* parent, const int32 RegisterMsg(&ProximityMonitorComponent::OnGetObjectReportInfo); } -// The ProximityMonitorComponent row's radii, "small,large" (one value, as LOT 9833's "4", is both): the rocket -// launch radii +// The ProximityMonitorComponent row lists ProximityTypes ids ("7,8": RocketSign and WorldLauncher), not radii. Each +// type has a name, a radius and where it is loaded: LWOProximityMonitorComponent::LoadDataFromTemplate (0x00c2dfb0) +// keeps the ids and SendMessage (0x00cbb930), on AddedToWorld, sets a proximity up under the type's name for each one +// that has LoadOnClient. The server's are the ones with LoadOnServer, of a row that has it too. void ProximityMonitorComponent::LoadDataFromTemplate() { if (m_ComponentID <= 0) return; const auto componentID = m_ComponentID; const auto proxCompData = CDClientManager::GetTable()->Query([componentID](const CDProximityMonitorComponent& entry) { return (entry.id == componentID); }); - if (proxCompData.empty()) return; + if (proxCompData.empty() || !proxCompData[0].LoadOnServer) return; - const auto radii = GeneralUtils::SplitString(proxCompData[0].Proximities, ','); - const auto small = radii.empty() ? std::nullopt : GeneralUtils::TryParse(radii[0]); - const auto large = radii.size() > 1 ? GeneralUtils::TryParse(radii[1]) : small; - if (!small || !large) return; - SetProximityRadius(*small, "rocketSmall"); - SetProximityRadius(*large, "rocketLarge"); + for (const auto& typeString : GeneralUtils::SplitString(proxCompData[0].Proximities, ',')) { + const auto typeID = GeneralUtils::TryParse(typeString); + if (!typeID) continue; + auto query = CDClientDatabase::CreatePreppedStmt("SELECT Name, Radius, LoadOnServer FROM ProximityTypes WHERE id = ?;"); + query.bind(1, *typeID); + auto result = query.execQuery(); + if (result.eof() || result.getIntField("LoadOnServer", 0) == 0) continue; + const std::string name = result.getStringField("Name", ""); + if (name.empty()) continue; + // A missing or non-positive radius is 30, as the client takes it + const auto radius = result.getIntField("Radius", 30); + SetProximityRadius(static_cast(radius > 0 ? radius : 30), name); + } } ProximityMonitorComponent::~ProximityMonitorComponent() { diff --git a/tests/dGameTests/dComponentsTests/CMakeLists.txt b/tests/dGameTests/dComponentsTests/CMakeLists.txt index efc31b5aa..98c6bc021 100644 --- a/tests/dGameTests/dComponentsTests/CMakeLists.txt +++ b/tests/dGameTests/dComponentsTests/CMakeLists.txt @@ -4,6 +4,7 @@ set(DCOMPONENTS_TESTS "InventorySaveTests.cpp" "EquipMessagesTests.cpp" "DestroyableLevelConfigTests.cpp" + "ProximityMonitorComponentTests.cpp" "RailActivatorComponentTests.cpp" "VendorBuybackTests.cpp" "RacingWrongWayTests.cpp" diff --git a/tests/dGameTests/dComponentsTests/ProximityMonitorComponentTests.cpp b/tests/dGameTests/dComponentsTests/ProximityMonitorComponentTests.cpp new file mode 100644 index 000000000..919d140cd --- /dev/null +++ b/tests/dGameTests/dComponentsTests/ProximityMonitorComponentTests.cpp @@ -0,0 +1,59 @@ +#include "GameDependencies.h" + +#include "CDClientDatabase.h" +#include "CDClientManager.h" +#include "CDProximityMonitorComponentTable.h" +#include "Entity.h" +#include "GameMessages.h" +#include "ProximityMonitorComponent.h" +#include "dpEntity.h" + +#include + +// A ProximityMonitorComponent row lists ProximityTypes ids, as LWOProximityMonitorComponent::LoadDataFromTemplate +// (0x00c2dfb0) reads it; the types say their name, radius and where they are loaded. +class ProximityMonitorComponentTests : public GameDependenciesTest { +protected: + void SetUp() override { + SetUpDependencies(); + CDClientDatabase::Connect(":memory:"); + CDClientDatabase::ExecuteDML("CREATE TABLE ProximityTypes (id INTEGER, Name TEXT, Radius INTEGER, CollisionGroup INTEGER, PassiveChecks INTEGER, IconID INTEGER, LoadOnClient INTEGER, LoadOnServer INTEGER);"); + CDClientDatabase::ExecuteDML("INSERT INTO ProximityTypes VALUES (2, 'Test Proximity', 10, 1, 1, 1, 1, 0);"); + CDClientDatabase::ExecuteDML("INSERT INTO ProximityTypes VALUES (3, 'Test Prox Large', 20, 1, 1, 3, 1, 1);"); + CDClientDatabase::ExecuteDML("INSERT INTO ProximityTypes VALUES (7, 'RocketSign', 30, 1, 1, -1, 1, 0);"); + CDClientDatabase::ExecuteDML("INSERT INTO ProximityTypes VALUES (8, 'WorldLauncher', 80, 1, 1, 74, 1, 0);"); + CDClientDatabase::ExecuteDML("INSERT INTO ProximityTypes VALUES (11, 'NoRadius', 0, 1, 1, 0, 1, 1);"); + auto& rows = CDClientManager::GetEntriesMutable(); + rows.push_back({ 3, "2,3", true, true }); + rows.push_back({ 6, "7,8", true, false }); + rows.push_back({ 50, "3,11,99", true, true }); + } + + void TearDown() override { + TearDownDependencies(); + } +}; + +// A rocket launch pad's "7,8" are the client's sign and launch proximities, not radii of 7 and 8: nothing for the server +TEST_F(ProximityMonitorComponentTests, ClientOnlyProximitiesAreNotMadeOnTheServer) { + Entity launchpad(1, info); + auto* const proximity = launchpad.AddComponent(6); + proximity->LoadDataFromTemplate(); + EXPECT_TRUE(proximity->GetProximitiesData().empty()); +} + +TEST_F(ProximityMonitorComponentTests, ServerProximitiesTakeTheTypesNameAndRadius) { + Entity object(2, info); + auto* const proximity = object.AddComponent(3); + proximity->LoadDataFromTemplate(); + const auto& made = proximity->GetProximitiesData(); + ASSERT_EQ(made.size(), 1u); + ASSERT_TRUE(made.contains("Test Prox Large")); + + // A radius of 0 is 30, and a type that does not exist is skipped + Entity other(3, info); + auto* const otherProximity = other.AddComponent(50); + otherProximity->LoadDataFromTemplate(); + EXPECT_EQ(otherProximity->GetProximitiesData().size(), 2u); + EXPECT_TRUE(otherProximity->GetProximitiesData().contains("NoRadius")); +}