mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
Live sent, right before TRANSFER_TO_WORLD on every rocket launch (131 captured): NotifyClientFlagChange(32, true), TransferToZone (check_transfer_allowed, the target map, the landing spawn point, the clone only for properties, no position or rotation), TransferToZoneCheckedIM (no queue) and NotifyClientFlagChange(32, false). DLU went straight to TRANSFER_TO_WORLD. In the client TransferToZone runs the civilian INVALIDMAPTRANSFERLIST check, and TransferToZoneCheckedIM pauses the player's controls, starts the target zone's loading screen and leaves the gameplay state (LWOCharacterComponent, cases TransferToZone and TransferToZoneCheckedIM). They are sent from the transfer callback, so a failed zone request never leaves the client paused. Instance exits through a message box used TransferToLastNonInstance in live instead and are not changed here. Check: launch rockets to Nimbus Station, Gnarled Forest and your property: controls lock and the destination's loading screen shows as the rocket leaves, and you land at the usual spawn point. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
118 lines
6.0 KiB
C++
118 lines
6.0 KiB
C++
#include "GameDependencies.h"
|
|
#include "GameMessageTestUtils.h"
|
|
|
|
#include "Mail.h"
|
|
#include "MovementMessages.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]));
|
|
}
|
|
|
|
// Live: ServerDoneLoadingAllObjects, then right after it the respawn checkpoint with a rotation. Bytes: a live pair
|
|
// from Avant Gardens Survival (a first load, so the spawn point) with the player ID replaced.
|
|
TEST_F(LoadSequenceTests, RespawnCheckpointFollowsDoneLoading) {
|
|
const NiPoint3 spawn(35.218f, 365.7804f, -201.3283f);
|
|
const NiQuaternion facing(0.7015f, 0.0f, -0.7126f, 0.0f);
|
|
const auto sent = Capture([&] { GameMessages::SendDoneLoading(PLAYER, NiPoint3Constant::ZERO, spawn, facing, 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 6a 06"), FromCapture(sent[0]));
|
|
EXPECT_PACKET_EQ(FromHex(
|
|
"53 05 00 0c 00 00 00 00 01 00 00 00 00 00 00 10 10 05 3b df 0c 42 e4 e3 b6 43 0b 54 49 c3 c0 ca 99 9f 80 00 00 00 "
|
|
"7a 36 1b 5f 80 00 00 00 00", 369), FromCapture(sent[1]));
|
|
for (const auto& packet : sent) {
|
|
EXPECT_EQ(packet.sysAddr, ClientAddress());
|
|
EXPECT_FALSE(packet.broadcast);
|
|
}
|
|
}
|
|
|
|
// A saved checkpoint wins over where the player loaded in; DLU keeps no rotation for it.
|
|
TEST_F(LoadSequenceTests, SavedCheckpointIsSentUnrotated) {
|
|
const NiPoint3 saved(100.0f, 200.0f, 300.0f);
|
|
const auto sent = Capture([&] { GameMessages::SendDoneLoading(PLAYER, saved, NiPoint3(1.0f, 2.0f, 3.0f), NiQuaternion(0.7015f, 0.0f, -0.7126f, 0.0f), ClientAddress()); });
|
|
const auto checkpoints = SentGameMessages<GameMessages::PlayerReachedRespawnCheckpoint>(sent);
|
|
ASSERT_EQ(checkpoints.size(), 1u);
|
|
EXPECT_EQ(checkpoints[0].pos, saved);
|
|
EXPECT_EQ(checkpoints[0].rot, QuatUtils::IDENTITY);
|
|
}
|
|
|
|
// Unread mail is announced during the load without the client asking; nothing is sent without unread mail.
|
|
// Live's packet (56 bytes) also fills the three fields DLU leaves 0 with object IDs the client does not use here.
|
|
TEST_F(LoadSequenceTests, UnreadMailIsAnnouncedOnLoad) {
|
|
auto sent = Capture([&] { Mail::NotifyUnreadMailOnLoad(0, ClientAddress()); });
|
|
EXPECT_TRUE(sent.empty());
|
|
|
|
sent = Capture([&] { Mail::NotifyUnreadMailOnLoad(1, ClientAddress()); });
|
|
ASSERT_EQ(sent.size(), 1u);
|
|
EXPECT_EQ(sent[0].sysAddr, ClientAddress());
|
|
EXPECT_PACKET_EQ(FromHex(
|
|
"53 05 00 31 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 "
|
|
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00"), FromCapture(sent[0]));
|
|
}
|
|
|
|
// Live, right before TRANSFER_TO_WORLD on a rocket launch to Nimbus Station (spawn point MedPropLand): flag 32 on,
|
|
// TransferToZone, TransferToZoneCheckedIM, flag 32 off, all to the launching client. Bytes: live with the player ID
|
|
// replaced (the live UpdatePlayerStatistic between the first two is left out).
|
|
TEST_F(LoadSequenceTests, ZoneTransferNoticeMatchesLive) {
|
|
const auto sent = Capture([&] { GameMessages::SendZoneTransferNotice(PLAYER, 1200, 0, u"MedPropLand", ClientAddress()); });
|
|
ASSERT_EQ(sent.size(), 4u);
|
|
EXPECT_PACKET_EQ(FromHex("53 05 00 0c 00 00 00 00 01 00 00 00 00 00 00 10 d8 01 90 00 00 00 00", 177), FromCapture(sent[0]));
|
|
EXPECT_PACKET_EQ(FromHex(
|
|
"53 05 00 0c 00 00 00 00 01 00 00 00 00 00 00 10 04 02 80 05 80 00 00 26 80 32 80 32 00 28 00 39 00 37 80 38 00 "
|
|
"26 00 30 80 37 00 32 00 00 6c 01 00", 386), FromCapture(sent[1]));
|
|
EXPECT_PACKET_EQ(FromHex(
|
|
"53 05 00 0c 00 00 00 00 01 00 00 00 00 00 00 10 05 02 00 05 80 00 00 26 80 32 80 32 00 28 00 39 00 37 80 38 00 "
|
|
"26 00 30 80 37 00 32 00 00 6c 01 00", 386), FromCapture(sent[2]));
|
|
EXPECT_PACKET_EQ(FromHex("53 05 00 0c 00 00 00 00 01 00 00 00 00 00 00 10 d8 01 10 00 00 00 00", 177), FromCapture(sent[3]));
|
|
for (const auto& packet : sent) {
|
|
EXPECT_EQ(packet.sysAddr, ClientAddress());
|
|
EXPECT_FALSE(packet.broadcast);
|
|
}
|
|
}
|
|
|
|
// A property launch carries the clone; the messages read back to what was sent.
|
|
TEST_F(LoadSequenceTests, ZoneTransferCarriesThePropertyClone) {
|
|
const auto sent = Capture([&] { GameMessages::SendZoneTransferNotice(PLAYER, 1250, 545173, u"", ClientAddress()); });
|
|
const auto transfers = SentGameMessages<GameMessages::TransferToZone>(sent);
|
|
const auto checked = SentGameMessages<GameMessages::TransferToZoneCheckedIM>(sent);
|
|
ASSERT_EQ(transfers.size(), 1u);
|
|
ASSERT_EQ(checked.size(), 1u);
|
|
EXPECT_TRUE(transfers[0].bCheckTransferAllowed);
|
|
EXPECT_EQ(transfers[0].cloneID, 545173u);
|
|
EXPECT_EQ(transfers[0].zoneID, 1250);
|
|
EXPECT_FALSE(checked[0].bIsThereaQueue);
|
|
EXPECT_EQ(checked[0].cloneID, 545173u);
|
|
RoundTrip(transfers[0]);
|
|
RoundTrip(checked[0]);
|
|
ExpectTruncatedFails(transfers[0]);
|
|
}
|