mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
Live pushed a mail NotificationResponse (NewMail, the unread count) right after the respawn checkpoint in loads where the player had unread mail, without the client asking (e.g. a new character's first load: ServerDoneLoadingAllObjects, PlayerReachedRespawnCheckpoint, NotifyMissionTask, MAIL). DLU had that call commented out, so the mailbox icon only lit up once the client asked or new mail arrived. It is sent only when there is unread mail (inferred: the 223 captured loads without one are loads without unread mail; only 3 of 226 loads carry it). Check: send a character mail, log it out and back in without opening the mailbox: the new-mail indicator shows right after loading; a character with no unread mail shows none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
81 lines
3.9 KiB
C++
81 lines
3.9 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]));
|
|
}
|