feat(load): PlayerReady also goes to the zone control object

Live answered PlayerLoaded with PlayerReady to the player and then a
second PlayerReady to the zone control object (0x3FFFFFFFFFFE), both
to the loading client: 235 of 237 captured loads carry exactly that
pair. DLU sent only the first. The zone control object's client-side
zone scripts are what can react to it (inferred: the client's
component handlers only act on the player's own PlayerReady).

Check: load into Avant Gardens, Nimbus Station, a property and an
instance (a survival or dragon battle): the zone plays its intro,
music and UI as before and nothing is stuck waiting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-29 03:25:56 -05:00
parent e2d6c4124b
commit a25eb34d66
4 changed files with 54 additions and 4 deletions

View File

@@ -17,7 +17,8 @@ SET(DGAMEMESSAGES_TESTS
"LegacyGameMessageTests.cpp"
"GameMessageDecoderTests.cpp"
"HotPropertyMessageTests.cpp"
"ClientMessagesTests.cpp")
"ClientMessagesTests.cpp"
"LoadSequenceTests.cpp")
# Get the folder name and prepend it to the files above
get_filename_component(thisFolderName ${CMAKE_CURRENT_SOURCE_DIR} NAME)

View File

@@ -0,0 +1,37 @@
#include "GameDependencies.h"
#include "GameMessageTestUtils.h"
#include "ZoneMessages.h"
#include <gtest/gtest.h>
using namespace GameMessageTestUtils;
// What the world sends while a player loads into a zone, compared with the live captures.
class LoadSequenceTests : public GameDependenciesTest {
protected:
void SetUp() override { SetUpDependencies(); }
void TearDown() override { TearDownDependencies(); }
static constexpr LWOOBJID PLAYER = 0x1000000000000001LL;
static constexpr LWOOBJID ZONE_CONTROL = 0x3FFF'FFFFFFFELL;
};
// Live: PlayerReady to the player, then to the zone control object, both to the loading client only.
// Bytes: the live pair with the player ID replaced.
TEST_F(LoadSequenceTests, PlayerReadyGoesToThePlayerThenTheZoneControl) {
const auto sent = Capture([&] { GameMessages::SendPlayerReady(PLAYER, ZONE_CONTROL, ClientAddress()); });
ASSERT_EQ(sent.size(), 2u);
EXPECT_PACKET_EQ(FromHex("53 05 00 0c 00 00 00 00 01 00 00 00 00 00 00 10 fd 01"), FromCapture(sent[0]));
EXPECT_PACKET_EQ(FromHex("53 05 00 0c 00 00 00 00 fe ff ff ff ff 3f 00 00 fd 01"), FromCapture(sent[1]));
for (const auto& packet : sent) {
EXPECT_EQ(packet.sysAddr, ClientAddress());
EXPECT_FALSE(packet.broadcast);
}
}
TEST_F(LoadSequenceTests, PlayerReadyWithoutZoneControlGoesToThePlayerOnly) {
const auto sent = Capture([&] { GameMessages::SendPlayerReady(PLAYER, LWOOBJID_EMPTY, ClientAddress()); });
ASSERT_EQ(sent.size(), 1u);
EXPECT_PACKET_EQ(FromHex("53 05 00 0c 00 00 00 00 01 00 00 00 00 00 00 10 fd 01"), FromCapture(sent[0]));
}