Files
DarkflameServer/tests/dGameTests/dGameMessagesTests/ClientMessagesTests.cpp
Aaron Kimbrell 8d4422fa5c feat(character): keep the rocket build the client reports (SetLastCustomBuild)
The client sends SetLastCustomBuild (890, a u32-length wide string;
other references) with its rocket build, e.g. "1:14454;1:4714;1:4715;", when it
assembles the rocket the player carries: after EquipInventory at a
launchpad and on load when landing (202 live packets). It stores it as
lastUsedRocketInfo (LWOCharacterComponent::SendMessage 0x00d34330) and
reads it back from char@lcbp on load (0x00ca4910). Live saved lcbp in the
same format on every character.

DLU dropped the message and only set lcbp when the server equipped the
rocket. The handler now keeps the client's build as the rocket config
(saved as lcbp). It does not change whether the player lands on the next
load: DLU uses an empty config to skip the landing after non-rocket
transfers, and that still happens when the server clears it.

Check in game: build a new rocket in the rocket builder, launch from a
launchpad and land: the rocket shown on landing and the builder's default
next time are the new build. Teleport to another world without a rocket
(e.g. from a property): no landing animation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 03:44:06 -05:00

85 lines
3.5 KiB
C++

// Client -> server game messages DLU used to drop (docs/CaptureUnknowns.md section 4). Packets are from 2011/2012
// live captures with the object ID replaced.
#include "GameDependencies.h"
#include "GameMessageTestUtils.h"
#include "CharacterComponent.h"
#include "Entity.h"
#include "PlayerMessages.h"
#include <memory>
#include <gtest/gtest.h>
using namespace GameMessageTestUtils;
namespace {
// A synthetic player object ID, as the object ID of the live packets below
constexpr const char* PLAYER_HEADER = "53040005000000000100000000000010";
}
class ClientMessagesTests : public GameDependenciesTest {
protected:
std::unique_ptr<Entity> player;
std::unique_ptr<Character> character;
void SetUp() override {
SetUpDependencies();
player = std::make_unique<Entity>(0x1000000000000001LL, info);
character = std::make_unique<Character>(1, nullptr);
player->AddComponent<CharacterComponent>(-1, character.get(), UNASSIGNED_SYSTEM_ADDRESS);
}
void TearDown() override {
player.reset();
character.reset();
TearDownDependencies();
}
};
// SetTooltipFlag (469): bFlag, then the tooltip. Both live samples flagged tooltip 24; live saved ttip="16777216".
TEST_F(ClientMessagesTests, SetTooltipFlagMatchesLiveCapture) {
auto msg = FromLiveClientCapture<GameMessages::SetTooltipFlag>(std::string(PLAYER_HEADER) + "d501" + "8c00000000");
EXPECT_TRUE(msg.bFlag);
EXPECT_EQ(msg.iToolTip, 24);
EXPECT_EQ(RoundTrip(msg).iToolTip, 24);
ExpectTruncatedFails(msg);
auto* const characterComponent = player->GetComponent<CharacterComponent>();
msg.Handle(*player, UNASSIGNED_SYSTEM_ADDRESS);
EXPECT_EQ(characterComponent->GetTooltipFlags(), 16777216u);
}
// The client's bit operations: set shifts 1 by the tooltip; clear masks with ~1 shifted by the tooltip (clearing
// every lower bit too); shifts of 64 or more give 0; tooltips above 127 are ignored.
TEST_F(ClientMessagesTests, SetTooltipFlagFollowsTheClient) {
auto* const characterComponent = player->GetComponent<CharacterComponent>();
characterComponent->SetTooltipFlag(0, true);
characterComponent->SetTooltipFlag(3, true);
characterComponent->SetTooltipFlag(63, true);
EXPECT_EQ(characterComponent->GetTooltipFlags(), 0x8000000000000009ULL);
characterComponent->SetTooltipFlag(64, true);
EXPECT_EQ(characterComponent->GetTooltipFlags(), 0x8000000000000009ULL);
characterComponent->SetTooltipFlag(128, false);
EXPECT_EQ(characterComponent->GetTooltipFlags(), 0x8000000000000009ULL);
characterComponent->SetTooltipFlag(3, false);
EXPECT_EQ(characterComponent->GetTooltipFlags(), 0x8000000000000000ULL);
characterComponent->SetTooltipFlag(100, false);
EXPECT_EQ(characterComponent->GetTooltipFlags(), 0u);
}
// SetLastCustomBuild (890): a u32-length wide string. 202 live packets, sent when the carried rocket is assembled.
TEST_F(ClientMessagesTests, SetLastCustomBuildMatchesLiveCapture) {
auto msg = FromLiveClientCapture<GameMessages::SetLastCustomBuild>(std::string(PLAYER_HEADER) + "7a03" +
"1600000031003a00310034003400350034003b0031003a0034003700310034003b0031003a0034003700310035003b00");
EXPECT_EQ(msg.tokenizedLOTList, u"1:14454;1:4714;1:4715;");
EXPECT_EQ(RoundTrip(msg).tokenizedLOTList, msg.tokenizedLOTList);
ExpectTruncatedFails(msg);
// Kept as the rocket config (saved as char@lcbp) without turning on the landing
auto* const characterComponent = player->GetComponent<CharacterComponent>();
msg.Handle(*player, UNASSIGNED_SYSTEM_ADDRESS);
EXPECT_EQ(characterComponent->GetLastRocketConfig(), u"1:14454;1:4714;1:4715;");
EXPECT_FALSE(characterComponent->GetIsLanding());
}