diff --git a/dGame/dComponents/Character/GhostComponent.h b/dGame/dComponents/Character/GhostComponent.h index c66916b59..d61893e19 100644 --- a/dGame/dComponents/Character/GhostComponent.h +++ b/dGame/dComponents/Character/GhostComponent.h @@ -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); diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index 6f3d16a03..5caa13ef9 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -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 GameMessageHandler::CreateReceived(MessageType::Game messageID) { diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index f23f57056..427552fdb 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -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(); + 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(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->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);