mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-04 02:47:14 +00:00
fix(load): the respawn checkpoint follows ServerDoneLoadingAllObjects on every load
Live sent PlayerReachedRespawnCheckpoint right after ServerDoneLoadingAllObjects on 143 of 157 captured loads, with a rotation: the spawn point the player arrived at, or a checkpoint they had reached before. DLU sent it before constructing the zone's objects, only when a checkpoint was saved for the zone, and always unrotated. It now goes right after ServerDoneLoadingAllObjects on every load: the saved checkpoint when there is one (DLU saves no rotation for it, so that one stays unrotated), otherwise the position and facing the player loaded in at. Inferred: that the loaded-in spot matches live's spawn point choice (live's is the spawn point object, a few tenths of a unit from the player's start). Check: log in fresh to a zone, die before touching any checkpoint and respawn: you come back where you arrived, facing the same way; reach a checkpoint, change zones and come back, die: you respawn at that checkpoint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
#include "ZoneMessages.h"
|
#include "ZoneMessages.h"
|
||||||
|
#include "MovementMessages.h"
|
||||||
|
|
||||||
#include "BitStreamUtils.h"
|
#include "BitStreamUtils.h"
|
||||||
#include "CDClientManager.h"
|
#include "CDClientManager.h"
|
||||||
@@ -35,6 +36,19 @@ namespace GameMessages {
|
|||||||
return bitStream.Read(playerID);
|
return bitStream.Read(playerID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SendDoneLoading(const LWOOBJID player, const NiPoint3& savedCheckpoint, const NiPoint3& spawnPosition, const NiQuaternion& spawnRotation, const SystemAddress& sysAddr) {
|
||||||
|
ServerDoneLoadingAllObjects doneLoading;
|
||||||
|
doneLoading.target = player;
|
||||||
|
doneLoading.SendToClient(sysAddr);
|
||||||
|
|
||||||
|
PlayerReachedRespawnCheckpoint checkpoint;
|
||||||
|
checkpoint.target = player;
|
||||||
|
const bool hasSavedCheckpoint = savedCheckpoint != NiPoint3Constant::ZERO;
|
||||||
|
checkpoint.pos = hasSavedCheckpoint ? savedCheckpoint : spawnPosition;
|
||||||
|
checkpoint.rot = hasSavedCheckpoint ? QuatUtils::IDENTITY : spawnRotation;
|
||||||
|
checkpoint.SendToClient(sysAddr);
|
||||||
|
}
|
||||||
|
|
||||||
void SendPlayerReady(const LWOOBJID player, const LWOOBJID zoneControl, const SystemAddress& sysAddr) {
|
void SendPlayerReady(const LWOOBJID player, const LWOOBJID zoneControl, const SystemAddress& sysAddr) {
|
||||||
PlayerReady playerReady;
|
PlayerReady playerReady;
|
||||||
playerReady.target = player;
|
playerReady.target = player;
|
||||||
|
|||||||
@@ -47,6 +47,14 @@ namespace GameMessages {
|
|||||||
ServerDoneLoadingAllObjects() : NetGameMsg(MessageType::Game::SERVER_DONE_LOADING_ALL_OBJECTS) {}
|
ServerDoneLoadingAllObjects() : NetGameMsg(MessageType::Game::SERVER_DONE_LOADING_ALL_OBJECTS) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ends a player's load the way live did: ServerDoneLoadingAllObjects, then PlayerReachedRespawnCheckpoint with where
|
||||||
|
* the player respawns in this zone (143 of 157 captured loads send the pair back to back, with a rotation).
|
||||||
|
* That is their saved checkpoint for the zone when they have one (DLU saves no rotation for it, so it goes
|
||||||
|
* unrotated), otherwise the spot and facing they loaded in at.
|
||||||
|
*/
|
||||||
|
void SendDoneLoading(LWOOBJID player, const NiPoint3& savedCheckpoint, const NiPoint3& spawnPosition, const NiQuaternion& spawnRotation, const SystemAddress& sysAddr);
|
||||||
|
|
||||||
// Server -> client, to one client.
|
// Server -> client, to one client.
|
||||||
struct InvalidZoneTransferList : public NetGameMsg {
|
struct InvalidZoneTransferList : public NetGameMsg {
|
||||||
InvalidZoneTransferList() : NetGameMsg(MessageType::Game::INVALID_ZONE_TRANSFER_LIST) {}
|
InvalidZoneTransferList() : NetGameMsg(MessageType::Game::INVALID_ZONE_TRANSFER_LIST) {}
|
||||||
|
|||||||
@@ -1051,17 +1051,11 @@ void LoadPlayer(const SystemAddress& sysAddr) {
|
|||||||
serverStates.Send(sysAddr);
|
serverStates.Send(sysAddr);
|
||||||
|
|
||||||
const auto respawnPoint = player->GetCharacter()->GetRespawnPoint(Game::zoneManager->GetZone()->GetWorldID());
|
const auto respawnPoint = player->GetCharacter()->GetRespawnPoint(Game::zoneManager->GetZone()->GetWorldID());
|
||||||
|
const auto spawnPosition = player->GetPosition();
|
||||||
|
const auto spawnRotation = player->GetRotation();
|
||||||
|
|
||||||
Game::entityManager->ConstructEntity(player, UNASSIGNED_SYSTEM_ADDRESS);
|
Game::entityManager->ConstructEntity(player, UNASSIGNED_SYSTEM_ADDRESS);
|
||||||
|
|
||||||
if (respawnPoint != NiPoint3Constant::ZERO) {
|
|
||||||
GameMessages::PlayerReachedRespawnCheckpoint respawnCheckpoint;
|
|
||||||
respawnCheckpoint.target = player->GetObjectID();
|
|
||||||
respawnCheckpoint.pos = respawnPoint;
|
|
||||||
respawnCheckpoint.rot = QuatUtils::IDENTITY;
|
|
||||||
respawnCheckpoint.SendToClient(player->GetSystemAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Before the models are constructed: the served meshes' checksums, so a client whose cached checksum is its own
|
// Before the models are constructed: the served meshes' checksums, so a client whose cached checksum is its own
|
||||||
// build's downloads the served mesh again (UgcManifest, docs/UgcServer.md)
|
// build's downloads the served mesh again (UgcManifest, docs/UgcServer.md)
|
||||||
if (g_CloneID != 0 && UgcManifest::ServesModels()) {
|
if (g_CloneID != 0 && UgcManifest::ServesModels()) {
|
||||||
@@ -1141,9 +1135,7 @@ void LoadPlayer(const SystemAddress& sysAddr) {
|
|||||||
invalidTransferList.bCustomerFeedbackOnExit = false;
|
invalidTransferList.bCustomerFeedbackOnExit = false;
|
||||||
invalidTransferList.bCustomerFeedbackOnInvalidMapTransfer = false;
|
invalidTransferList.bCustomerFeedbackOnInvalidMapTransfer = false;
|
||||||
invalidTransferList.SendToClient(sysAddr);
|
invalidTransferList.SendToClient(sysAddr);
|
||||||
GameMessages::ServerDoneLoadingAllObjects doneLoading;
|
GameMessages::SendDoneLoading(player->GetObjectID(), respawnPoint, spawnPosition, spawnRotation, sysAddr);
|
||||||
doneLoading.target = player->GetObjectID();
|
|
||||||
doneLoading.SendToClient(sysAddr);
|
|
||||||
|
|
||||||
//Send the player it's mail count:
|
//Send the player it's mail count:
|
||||||
//update: this might not be needed so im going to try disabling this here.
|
//update: this might not be needed so im going to try disabling this here.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "GameDependencies.h"
|
#include "GameDependencies.h"
|
||||||
#include "GameMessageTestUtils.h"
|
#include "GameMessageTestUtils.h"
|
||||||
|
|
||||||
|
#include "MovementMessages.h"
|
||||||
#include "ZoneMessages.h"
|
#include "ZoneMessages.h"
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
@@ -35,3 +36,30 @@ TEST_F(LoadSequenceTests, PlayerReadyWithoutZoneControlGoesToThePlayerOnly) {
|
|||||||
ASSERT_EQ(sent.size(), 1u);
|
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]));
|
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);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user