mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-14 07:47:13 +00:00
fix(world): a player's client reloading the zone gets its character and the world again
A client that loads the zone again while its player is in the world (the Happy Flower editor reloads the zone in place) sent LEVEL_LOAD_COMPLETE a second time; LoadPlayer returned at once because the player existed, so the client waited forever. Now the server resends the character (CreateCharacter, ServerStates), forgets what it had sent that client, constructs every object again and sends the done-loading sequence at the player's current place. A first load is unchanged. The Happy Flower editor messages (GetEditorLevel, the HF_* messages) are logged when they arrive, until the server handles them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,9 @@ public:
|
||||
|
||||
void GhostEntity(const LWOOBJID id);
|
||||
|
||||
// The client dropped every object it had (it reloaded the zone in place): nothing counts as sent to it any more
|
||||
void ForgetObservedEntities() { m_ObservedEntities.clear(); m_LimboConstructions.clear(); }
|
||||
|
||||
bool OnToggleGMInvis(GameMessages::ToggleGMInvisEvent& event);
|
||||
|
||||
bool OnGetGMInvis(GameMessages::GetGMInvis& msg);
|
||||
|
||||
@@ -268,7 +268,11 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Received Unknown GM with ID: %4i, %s", messageID, StringifiedEnum::ToString(messageID).data());
|
||||
// The Happy Flower editor's messages, logged always until the server handles them (what a client sends while editing)
|
||||
const bool isHappyFlower = messageID == MessageType::Game::GET_EDITOR_LEVEL
|
||||
|| (messageID >= MessageType::Game::HF_ATTRIBUTES_PUSH && messageID <= MessageType::Game::HF_GETS_WANT_PATH);
|
||||
if (isHappyFlower) LOG("Received unhandled Happy Flower GM: %4i, %s", messageID, StringifiedEnum::ToString(messageID).data());
|
||||
else LOG_DEBUG("Received Unknown GM with ID: %4i, %s", messageID, StringifiedEnum::ToString(messageID).data());
|
||||
}
|
||||
|
||||
std::unique_ptr<GameMessages::NetGameMsg> GameMessageHandler::CreateReceived(MessageType::Game messageID) {
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
#include "ZoneInstanceManager.h"
|
||||
#include "dChatFilter.h"
|
||||
#include "ClientPackets.h"
|
||||
#include "GhostComponent.h"
|
||||
#include "CharacterComponent.h"
|
||||
|
||||
#include "EntityManager.h"
|
||||
@@ -219,6 +220,7 @@ void HandleMasterPacket(Packet* packet);
|
||||
void HandlePacket(Packet* packet);
|
||||
void CleanupDisconnectedUser(const SystemAddress& sysAddr);
|
||||
void LoadPlayer(const SystemAddress& sysAddr);
|
||||
void ReloadPlayerInPlace(const SystemAddress& sysAddr, Entity& player, Character& character);
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
const auto curTimeStr = std::to_string(time(nullptr));
|
||||
@@ -1018,13 +1020,45 @@ void HandleMasterPacket(Packet* packet) {
|
||||
|
||||
// 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
|
||||
// object again and the done-loading sequence, as on a first load. Nothing is created or restarted on the server.
|
||||
void ReloadPlayerInPlace(const SystemAddress& sysAddr, Entity& player, Character& character) {
|
||||
LOG("Reloading %s (%llu) in place: its client loaded the zone again", character.GetName().c_str(), player.GetObjectID());
|
||||
auto* const characterComponent = player.GetComponent<CharacterComponent>();
|
||||
if (!characterComponent) return;
|
||||
|
||||
character.SaveXMLToDatabase();
|
||||
ClientPackets::CreateCharacter createCharacter;
|
||||
createCharacter.objectID = player.GetObjectID();
|
||||
createCharacter.xmlData = character.GetXMLData();
|
||||
createCharacter.name = GeneralUtils::ASCIIToUTF16(character.GetName());
|
||||
createCharacter.gmLevel = character.GetGMLevel();
|
||||
createCharacter.chatMode = static_cast<int32_t>(character.GetGMLevel());
|
||||
createCharacter.reputation = characterComponent->GetReputation();
|
||||
createCharacter.propertyCloneID = character.GetPropertyCloneID();
|
||||
createCharacter.Send(sysAddr);
|
||||
ClientPackets::ServerStates serverStates;
|
||||
serverStates.Send(sysAddr);
|
||||
|
||||
if (auto* const ghostComponent = player.GetComponent<GhostComponent>()) ghostComponent->ForgetObservedEntities();
|
||||
Game::entityManager->ConstructAllEntities(sysAddr);
|
||||
|
||||
const auto respawnPoint = character.GetRespawnPoint(Game::zoneManager->GetZone()->GetWorldID());
|
||||
GameMessages::SendDoneLoading(player.GetObjectID(), respawnPoint, player.GetPosition(), player.GetRotation(), sysAddr);
|
||||
}
|
||||
|
||||
void LoadPlayer(const SystemAddress& sysAddr) {
|
||||
Profiler::Scope scope("LoadPlayer");
|
||||
User* user = UserManager::Instance()->GetUser(sysAddr);
|
||||
if (user) {
|
||||
Character* c = user->GetLastUsedChar();
|
||||
if (c != nullptr) {
|
||||
if (Game::entityManager->GetEntity(c->GetObjectID())) return;
|
||||
// The client loaded the zone again while its player is here (the Happy Flower editor reloads the zone in
|
||||
// place): it dropped every object, so send its character and the world again
|
||||
if (auto* const existing = Game::entityManager->GetEntity(c->GetObjectID())) {
|
||||
ReloadPlayerInPlace(sysAddr, *existing, *c);
|
||||
return;
|
||||
}
|
||||
std::u16string username = GeneralUtils::ASCIIToUTF16(c->GetName());
|
||||
Game::server->GetReplicaManager()->AddParticipant(sysAddr);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user