mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-14 07:47:13 +00:00
fix: the character data packet carries the world keys live sent
Every live character data packet (451 of 451) had legoclub=1, freetrial=0, requiresrename=0, levelid and position.x/y/z + rotation.x/y/z/w next to the keys already sent. LWOCharacterComponent::LoadConfigData (0x00c70f00) reads requiresrename, legoclub and freetrial. requiresrename is the character's refused-name state, levelid the zone id (map, instance << 16, clone << 32) and the position and rotation are where the player is put in the world. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -251,6 +251,19 @@ namespace ClientPackets {
|
||||
ldfData.Insert<int64_t>(u"reputation", reputation);
|
||||
ldfData.Insert<int32_t>(u"propertycloneid", propertyCloneID);
|
||||
if (txfring) ldfData.Insert<bool>(u"txfring", true);
|
||||
if (sendWorldKeys) {
|
||||
ldfData.Insert<bool>(u"legoclub", legoClub);
|
||||
ldfData.Insert<bool>(u"freetrial", freeTrial);
|
||||
ldfData.Insert<bool>(u"requiresrename", requiresRename);
|
||||
ldfData.Insert<uint64_t>(u"levelid", levelID);
|
||||
ldfData.Insert<float>(u"position.x", position.x);
|
||||
ldfData.Insert<float>(u"position.y", position.y);
|
||||
ldfData.Insert<float>(u"position.z", position.z);
|
||||
ldfData.Insert<float>(u"rotation.x", rotation.x);
|
||||
ldfData.Insert<float>(u"rotation.y", rotation.y);
|
||||
ldfData.Insert<float>(u"rotation.z", rotation.z);
|
||||
ldfData.Insert<float>(u"rotation.w", rotation.w);
|
||||
}
|
||||
|
||||
data.Write<uint32_t>(ldfData.values.size());
|
||||
for (const auto& toSerialize : ldfData | std::views::values) toSerialize->WriteToPacket(data);
|
||||
@@ -309,6 +322,8 @@ namespace ClientPackets {
|
||||
|
||||
RakNet::BitStream data(uncompressed.data(), uncompressed.size(), false);
|
||||
txfring = false; // the client's value without the key
|
||||
sendWorldKeys = false;
|
||||
legoClub = false;
|
||||
uint32_t count{};
|
||||
VALIDATE_READ(data.Read(count));
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
@@ -349,6 +364,27 @@ namespace ClientPackets {
|
||||
uint8_t value{};
|
||||
VALIDATE_READ(data.Read(value));
|
||||
if (key == u"txfring") txfring = value != 0;
|
||||
else if (key == u"legoclub") legoClub = value != 0;
|
||||
else if (key == u"freetrial") freeTrial = value != 0;
|
||||
else if (key == u"requiresrename") requiresRename = value != 0;
|
||||
break;
|
||||
}
|
||||
case LDF_TYPE_U64: {
|
||||
uint64_t value{};
|
||||
VALIDATE_READ(data.Read(value));
|
||||
if (key == u"levelid") { levelID = value; sendWorldKeys = true; }
|
||||
break;
|
||||
}
|
||||
case LDF_TYPE_FLOAT: {
|
||||
float value{};
|
||||
VALIDATE_READ(data.Read(value));
|
||||
if (key == u"position.x") position.x = value;
|
||||
else if (key == u"position.y") position.y = value;
|
||||
else if (key == u"position.z") position.z = value;
|
||||
else if (key == u"rotation.x") rotation.x = value;
|
||||
else if (key == u"rotation.y") rotation.y = value;
|
||||
else if (key == u"rotation.z") rotation.z = value;
|
||||
else if (key == u"rotation.w") rotation.w = value;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "MessageType/Client.h"
|
||||
#include "MessageType/Game.h"
|
||||
#include "NiPoint3.h"
|
||||
#include "NiQuaternion.h"
|
||||
#include "Stamps.h"
|
||||
|
||||
enum class eCharacterCreationResponse : uint8_t;
|
||||
@@ -189,6 +190,15 @@ namespace ClientPackets {
|
||||
// packet, and with it false the client shows inventory-full tips on every world load. Not written when false
|
||||
// (the client's default).
|
||||
bool txfring = true;
|
||||
// The keys below were in every live character data packet. LWOCharacterComponent::LoadConfigData (0x00c70f00)
|
||||
// reads requiresrename, legoclub and freetrial. Not written when sendWorldKeys is false.
|
||||
bool sendWorldKeys = true;
|
||||
bool legoClub = true;
|
||||
bool freeTrial = false;
|
||||
bool requiresRename = false; // The name was refused and a new one is asked for
|
||||
uint64_t levelID{}; // map id, instance id << 16, clone id << 32
|
||||
NiPoint3 position{};
|
||||
NiQuaternion rotation = QuatUtils::IDENTITY;
|
||||
|
||||
CreateCharacter() : LUBitStream(ServiceType::CLIENT, MessageType::Client::CREATE_CHARACTER) {}
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
|
||||
@@ -640,6 +640,13 @@ json ToJson(const ClientPackets::CreateCharacter& m) {
|
||||
j["reputation"] = ToJson(m.reputation);
|
||||
j["propertyCloneID"] = ToJson(m.propertyCloneID);
|
||||
j["txfring"] = ToJson(m.txfring);
|
||||
j["sendWorldKeys"] = ToJson(m.sendWorldKeys);
|
||||
j["legoClub"] = ToJson(m.legoClub);
|
||||
j["freeTrial"] = ToJson(m.freeTrial);
|
||||
j["requiresRename"] = ToJson(m.requiresRename);
|
||||
j["levelID"] = ToJson(m.levelID);
|
||||
j["position"] = ToJson(m.position);
|
||||
j["rotation"] = ToJson(m.rotation);
|
||||
return j;
|
||||
}
|
||||
json ToJson(const ClientPackets::ChatModerationString& m) {
|
||||
|
||||
@@ -1018,6 +1018,17 @@ void HandleMasterPacket(Packet* packet) {
|
||||
}
|
||||
}
|
||||
|
||||
// What every live character data packet also carried: the world the player loads into, where it stands there and
|
||||
// whether its name was refused (LWOCharacterComponent::LoadConfigData 0x00c70f00 reads requiresrename)
|
||||
void SetWorldKeys(ClientPackets::CreateCharacter& createCharacter, const Entity& player, const Character& character) {
|
||||
const auto& zoneID = Game::zoneManager->GetZoneID();
|
||||
createCharacter.levelID = static_cast<uint64_t>(zoneID.GetMapID()) | static_cast<uint64_t>(zoneID.GetInstanceID()) << 16 |
|
||||
static_cast<uint64_t>(zoneID.GetCloneID()) << 32;
|
||||
createCharacter.position = player.GetPosition();
|
||||
createCharacter.rotation = player.GetRotation();
|
||||
createCharacter.requiresRename = character.GetNameRejected();
|
||||
}
|
||||
|
||||
// Creates the player's entity and sends the client everything in the world: after the client loaded the zone
|
||||
// (LEVEL_LOAD_COMPLETE), or at once when it kept its scene (an experimental seamless migration)
|
||||
// A player already in the world whose client reloaded the zone: its character again (from its current save), every
|
||||
@@ -1036,6 +1047,7 @@ void ReloadPlayerInPlace(const SystemAddress& sysAddr, Entity& player, Character
|
||||
createCharacter.chatMode = static_cast<int32_t>(character.GetGMLevel());
|
||||
createCharacter.reputation = characterComponent->GetReputation();
|
||||
createCharacter.propertyCloneID = character.GetPropertyCloneID();
|
||||
SetWorldKeys(createCharacter, player, character);
|
||||
createCharacter.Send(sysAddr);
|
||||
ClientPackets::ServerStates serverStates;
|
||||
serverStates.Send(sysAddr);
|
||||
@@ -1193,6 +1205,7 @@ void LoadPlayer(const SystemAddress& sysAddr) {
|
||||
createCharacter.chatMode = static_cast<int32_t>(c->GetGMLevel());
|
||||
createCharacter.reputation = characterComponent->GetReputation();
|
||||
createCharacter.propertyCloneID = c->GetPropertyCloneID();
|
||||
SetWorldKeys(createCharacter, *player, *c);
|
||||
createCharacter.Send(sysAddr);
|
||||
LOG("Sent CreateCharacter for ID: %llu", player->GetObjectID());
|
||||
ClientPackets::ServerStates serverStates;
|
||||
|
||||
@@ -310,6 +310,7 @@ TEST_F(WorldPacketsTests, CreateCharacterMatchesLegacy) {
|
||||
packet.reputation = reputation;
|
||||
packet.propertyCloneID = cloneID;
|
||||
packet.txfring = false; // the legacy sender never wrote it
|
||||
packet.sendWorldKeys = false; // nor the world keys
|
||||
ExpectSameSend([&](const SystemAddress& a) { LegacyWorldPackets::SendCreateCharacter(a, reputation, objectID, xml, name, gm, cloneID); }, packet);
|
||||
|
||||
const auto copy = RoundTrip(packet);
|
||||
@@ -340,6 +341,30 @@ TEST_F(WorldPacketsTests, CreateCharacterSendsTxfring) {
|
||||
EXPECT_FALSE(RoundTrip(packet).txfring);
|
||||
}
|
||||
|
||||
// Live sent legoclub=1, freetrial=0, requiresrename=0, levelid and position / rotation in 451 of 451 character data packets
|
||||
TEST_F(WorldPacketsTests, CreateCharacterSendsTheWorldKeys) {
|
||||
ClientPackets::CreateCharacter packet;
|
||||
packet.xmlData = "<obj/>";
|
||||
packet.requiresRename = true;
|
||||
packet.levelID = 1200ULL | 7ULL << 16 | 99ULL << 32;
|
||||
packet.position = NiPoint3(1.5f, -2.0f, 3.25f);
|
||||
packet.rotation = NiQuaternion(0.5f, 0.1f, 0.2f, 0.3f);
|
||||
const auto copy = RoundTrip(packet);
|
||||
EXPECT_TRUE(copy.sendWorldKeys);
|
||||
EXPECT_TRUE(copy.legoClub);
|
||||
EXPECT_FALSE(copy.freeTrial);
|
||||
EXPECT_TRUE(copy.requiresRename);
|
||||
EXPECT_EQ(copy.levelID, packet.levelID);
|
||||
EXPECT_EQ(copy.position, packet.position);
|
||||
EXPECT_EQ(copy.rotation, packet.rotation);
|
||||
|
||||
packet.sendWorldKeys = false;
|
||||
const auto without = RoundTrip(packet);
|
||||
EXPECT_FALSE(without.sendWorldKeys);
|
||||
EXPECT_FALSE(without.legoClub); // the client's value without the key
|
||||
EXPECT_FALSE(without.requiresRename);
|
||||
}
|
||||
|
||||
TEST_F(WorldPacketsTests, CharacterListMatchesLegacy) {
|
||||
FakeCharacter first;
|
||||
FakeCharacter second;
|
||||
|
||||
Reference in New Issue
Block a user