From 054a89dd9e5d6819adede9d7ba781b69f98ff2f4 Mon Sep 17 00:00:00 2001 From: Tiger Wang Date: Wed, 6 Jan 2021 00:35:42 +0000 Subject: Clarify cClientHandle, cPlayer ownership semantics + A cPlayer, once created, has a strong pointer to the cClientHandle. The player ticks the clienthandle. If he finds the handle destroyed, he destroys himself in turn. Nothing else can kill the player. * The client handle has a pointer to the player. Once a player is created, the client handle never outlasts the player, nor does it manage the player's lifetime. The pointer is always safe to use after FinishAuthenticate, which is also the point where cProtocol is put into the Game state that allows player manipulation. + Entities are once again never lost by constructing a chunk when they try to move into one that doesn't exist. * Fixed a forgotten Super invocation in cPlayer::OnRemoveFromWorld. * Fix SaveToDisk usage in destructor by only saving things cPlayer owns, instead of accessing cWorld. --- src/Chunk.cpp | 26 +- src/ChunkMap.cpp | 17 +- src/ChunkSender.cpp | 75 +++--- src/ChunkSender.h | 10 +- src/CircularBufferCompressor.h | 6 + src/ClientHandle.cpp | 359 +++++++-------------------- src/ClientHandle.h | 35 +-- src/Entities/Entity.cpp | 86 ++----- src/Entities/Entity.h | 12 +- src/Entities/Player.cpp | 458 ++++++++++++++++------------------- src/Entities/Player.h | 55 ++--- src/Mobs/Monster.cpp | 14 -- src/Mobs/Monster.h | 2 - src/Protocol/ChunkDataSerializer.cpp | 4 +- src/Protocol/ChunkDataSerializer.h | 4 +- src/Protocol/Protocol_1_8.cpp | 17 +- src/Root.cpp | 2 +- src/Server.cpp | 10 +- src/StringCompression.h | 2 - src/World.cpp | 367 +++++----------------------- src/World.h | 56 +---- 21 files changed, 498 insertions(+), 1119 deletions(-) (limited to 'src') diff --git a/src/Chunk.cpp b/src/Chunk.cpp index 4140d4f85..0dc333468 100644 --- a/src/Chunk.cpp +++ b/src/Chunk.cpp @@ -668,8 +668,10 @@ void cChunk::SpawnMobs(cMobSpawner & a_MobSpawner) void cChunk::Tick(std::chrono::milliseconds a_Dt) { + const auto ShouldTick = ShouldBeTicked(); + // If we are not valid, tick players and bailout - if (!IsValid()) + if (!ShouldTick) { for (const auto & Entity : m_Entities) { @@ -681,11 +683,6 @@ void cChunk::Tick(std::chrono::milliseconds a_Dt) return; } - CheckBlocks(); - - // Tick simulators: - m_World->GetSimulatorManager()->SimulateChunk(a_Dt, m_PosX, m_PosZ, this); - TickBlocks(); // Tick all block entities in this chunk: @@ -745,6 +742,13 @@ void cChunk::Tick(std::chrono::milliseconds a_Dt) ApplyWeatherToTop(); + // Tick simulators: + m_World->GetSimulatorManager()->SimulateChunk(a_Dt, m_PosX, m_PosZ, this); + + // Check blocks after everything else to apply at least one round of queued ticks (i.e. cBlockHandler::Check) this tick: + CheckBlocks(); + + // Finally, tell the client about all block changes: BroadcastPendingBlockChanges(); } @@ -768,9 +772,11 @@ void cChunk::MoveEntityToNewChunk(OwnedEntity a_Entity) cChunk * Neighbor = GetNeighborChunk(a_Entity->GetChunkX() * cChunkDef::Width, a_Entity->GetChunkZ() * cChunkDef::Width); if (Neighbor == nullptr) { - LOGWARNING("%s: Failed to move entity, destination chunk unreachable. Entity lost", __FUNCTION__); - a_Entity->OnRemoveFromWorld(*m_World); - return; + LOGWARNING("%s: Entity at %p (%s, ID %d) moving to a non-existent chunk.", + __FUNCTION__, static_cast(a_Entity.get()), a_Entity->GetClass(), a_Entity->GetUniqueID() + ); + + Neighbor = &m_ChunkMap->ConstructChunk(a_Entity->GetChunkX(), a_Entity->GetChunkZ()); } ASSERT(Neighbor != this); // Moving into the same chunk? wtf? @@ -1480,7 +1486,7 @@ cBlockEntity * cChunk::GetBlockEntityRel(Vector3i a_RelPos) bool cChunk::ShouldBeTicked(void) const { - return (HasAnyClients() || (m_AlwaysTicked > 0)); + return IsValid() && (HasAnyClients() || (m_AlwaysTicked > 0)); } diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp index c5798260b..c1d9b78ab 100644 --- a/src/ChunkMap.cpp +++ b/src/ChunkMap.cpp @@ -940,19 +940,18 @@ void cChunkMap::RemoveClientFromChunks(cClientHandle * a_Client) void cChunkMap::AddEntity(OwnedEntity a_Entity) { cCSLock Lock(m_CSChunks); - const auto Chunk = FindChunk(a_Entity->GetChunkX(), a_Entity->GetChunkZ()); - if (Chunk == nullptr) + if (FindChunk(a_Entity->GetChunkX(), a_Entity->GetChunkZ()) == nullptr) { - LOGWARNING("Entity at %p (%s, ID %d) spawning in a non-existent chunk, the entity is lost.", - static_cast(a_Entity.get()), a_Entity->GetClass(), a_Entity->GetUniqueID() + LOGWARNING("%s: Entity at %p (%s, ID %d) spawning in a non-existent chunk.", + __FUNCTION__, static_cast(a_Entity.get()), a_Entity->GetClass(), a_Entity->GetUniqueID() ); - return; } const auto EntityPtr = a_Entity.get(); ASSERT(EntityPtr->GetWorld() == m_World); - Chunk->AddEntity(std::move(a_Entity)); + auto & Chunk = ConstructChunk(a_Entity->GetChunkX(), a_Entity->GetChunkZ()); + Chunk.AddEntity(std::move(a_Entity)); EntityPtr->OnAddToWorld(*m_World); ASSERT(!EntityPtr->IsTicking()); @@ -1738,11 +1737,7 @@ void cChunkMap::Tick(std::chrono::milliseconds a_Dt) cCSLock Lock(m_CSChunks); for (auto & Chunk : m_Chunks) { - // Only tick chunks that are valid and should be ticked: - if (Chunk.second.IsValid() && Chunk.second.ShouldBeTicked()) - { - Chunk.second.Tick(a_Dt); - } + Chunk.second.Tick(a_Dt); } } diff --git a/src/ChunkSender.cpp b/src/ChunkSender.cpp index c93a764b2..0a7f58bc7 100644 --- a/src/ChunkSender.cpp +++ b/src/ChunkSender.cpp @@ -104,13 +104,13 @@ void cChunkSender::QueueSendChunkTo(int a_ChunkX, int a_ChunkZ, Priority a_Prior m_SendChunks.push(sChunkQueue{a_Priority, Chunk}); info.m_Priority = a_Priority; } - info.m_Clients.insert(a_Client); + info.m_Clients.insert(a_Client->shared_from_this()); } else { m_SendChunks.push(sChunkQueue{a_Priority, Chunk}); auto info = sSendChunk{Chunk, a_Priority}; - info.m_Clients.insert(a_Client); + info.m_Clients.insert(a_Client->shared_from_this()); m_ChunkInfo.emplace(Chunk, info); } } @@ -135,13 +135,19 @@ void cChunkSender::QueueSendChunkTo(int a_ChunkX, int a_ChunkZ, Priority a_Prior m_SendChunks.push(sChunkQueue{a_Priority, Chunk}); info.m_Priority = a_Priority; } - info.m_Clients.insert(a_Clients.begin(), a_Clients.end()); + for (const auto & Client : a_Clients) + { + info.m_Clients.insert(Client->shared_from_this()); + } } else { m_SendChunks.push(sChunkQueue{a_Priority, Chunk}); auto info = sSendChunk{Chunk, a_Priority}; - info.m_Clients.insert(a_Clients.begin(), a_Clients.end()); + for (const auto & Client : a_Clients) + { + info.m_Clients.insert(Client->shared_from_this()); + } m_ChunkInfo.emplace(Chunk, info); } } @@ -152,24 +158,6 @@ void cChunkSender::QueueSendChunkTo(int a_ChunkX, int a_ChunkZ, Priority a_Prior -void cChunkSender::RemoveClient(cClientHandle * a_Client) -{ - { - cCSLock Lock(m_CS); - for (auto && pair : m_ChunkInfo) - { - auto && clients = pair.second.m_Clients; - clients.erase(a_Client); // nop for sets that do not contain a_Client - } - } - m_evtQueue.Set(); - m_evtRemoved.Wait(); // Wait for all remaining instances of a_Client to be processed (Execute() makes a copy of m_ChunkInfo) -} - - - - - void cChunkSender::Execute(void) { while (!m_ShouldTerminate) @@ -189,16 +177,13 @@ void cChunkSender::Execute(void) continue; } - std::unordered_set clients; - std::swap(itr->second.m_Clients, clients); + auto clients = std::move(itr->second.m_Clients); m_ChunkInfo.erase(itr); cCSUnlock Unlock(Lock); SendChunk(Chunk.m_ChunkX, Chunk.m_ChunkZ, clients); } } - - m_evtRemoved.SetAll(); // Notify all waiting threads that all clients are processed and thus safe to destroy } // while (!m_ShouldTerminate) } @@ -206,21 +191,27 @@ void cChunkSender::Execute(void) -void cChunkSender::SendChunk(int a_ChunkX, int a_ChunkZ, std::unordered_set a_Clients) +void cChunkSender::SendChunk(int a_ChunkX, int a_ChunkZ, const WeakClients & a_Clients) { + // Contains strong pointers to clienthandles. + std::vector> Clients; + // Ask the client if it still wants the chunk: - for (auto itr = a_Clients.begin(); itr != a_Clients.end();) + for (const auto & WeakClient : a_Clients) { - if (!(*itr)->WantsSendChunk(a_ChunkX, a_ChunkZ)) + auto Client = WeakClient.lock(); + if ((Client != nullptr) && Client->WantsSendChunk(a_ChunkX, a_ChunkZ)) { - itr = a_Clients.erase(itr); - } - else - { - itr++; + Clients.push_back(std::move(Client)); } } + // Bail early if every requester disconnected: + if (Clients.empty()) + { + return; + } + // If the chunk has no clients, no need to packetize it: if (!m_World.HasChunkAnyClients(a_ChunkX, a_ChunkZ)) { @@ -247,9 +238,9 @@ void cChunkSender::SendChunk(int a_ChunkX, int a_ChunkZ, std::unordered_setGetUsername().c_str() ); */ - a_Entity.SpawnOn(*Client); + + /* This check looks highly suspect. + Its purpose is to check the client still has a valid player object associated, + since the player destroys itself when the client is destroyed. + It's done within the world lock to ensure correctness. + A better way involves fixing chunk sending (GH #3696) to obviate calling SpawnOn from this thread in the first place. */ + if (!Client->IsDestroyed()) + { + a_Entity.SpawnOn(*Client); + } + return true; }); } diff --git a/src/ChunkSender.h b/src/ChunkSender.h index be7b55b16..43246ad64 100644 --- a/src/ChunkSender.h +++ b/src/ChunkSender.h @@ -74,11 +74,10 @@ public: void QueueSendChunkTo(int a_ChunkX, int a_ChunkZ, Priority a_Priority, cClientHandle * a_Client); void QueueSendChunkTo(int a_ChunkX, int a_ChunkZ, Priority a_Priority, cChunkClientHandles a_Client); - /** Removes the a_Client from all waiting chunk send operations */ - void RemoveClient(cClientHandle * a_Client); - protected: + using WeakClients = std::set, std::owner_less>>; + struct sChunkQueue { Priority m_Priority; @@ -96,7 +95,7 @@ protected: struct sSendChunk { cChunkCoords m_Chunk; - std::unordered_set m_Clients; + WeakClients m_Clients; Priority m_Priority; sSendChunk(cChunkCoords a_Chunk, Priority a_Priority) : m_Chunk(a_Chunk), @@ -114,7 +113,6 @@ protected: std::priority_queue m_SendChunks; std::unordered_map m_ChunkInfo; cEvent m_evtQueue; // Set when anything is added to m_ChunksReady - cEvent m_evtRemoved; // Set when removed clients are safe to be deleted // Data about the chunk that is being sent: // NOTE that m_BlockData[] is inherited from the cChunkDataCollector @@ -132,7 +130,7 @@ protected: virtual void BlockEntity (cBlockEntity * a_Entity) override; /** Sends the specified chunk to all the specified clients */ - void SendChunk(int a_ChunkX, int a_ChunkZ, std::unordered_set a_Clients); + void SendChunk(int a_ChunkX, int a_ChunkZ, const WeakClients & a_Clients); } ; diff --git a/src/CircularBufferCompressor.h b/src/CircularBufferCompressor.h index 12c708baf..53c96bd15 100644 --- a/src/CircularBufferCompressor.h +++ b/src/CircularBufferCompressor.h @@ -7,6 +7,12 @@ +class cByteBuffer; + + + + + class CircularBufferCompressor { public: diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index 4ec01744b..ffb968a0c 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -68,13 +68,12 @@ float cClientHandle::FASTBREAK_PERCENTAGE; // cClientHandle: cClientHandle::cClientHandle(const AString & a_IPString, int a_ViewDistance) : - m_LastSentDimension(dimNotSet), m_ForgeHandshake(this), m_CurrentViewDistance(a_ViewDistance), m_RequestedViewDistance(a_ViewDistance), m_IPString(a_IPString), m_Player(nullptr), - m_CachedSentChunk(0, 0), + m_CachedSentChunk(0x7fffffff, 0x7fffffff), m_HasSentDC(false), m_LastStreamedChunkX(0x7fffffff), // bogus chunk coords to force streaming upon login m_LastStreamedChunkZ(0x7fffffff), @@ -116,26 +115,6 @@ cClientHandle::~cClientHandle() LOGD("Deleting client \"%s\" at %p", GetUsername().c_str(), static_cast(this)); - { - cCSLock Lock(m_CSChunkLists); - m_LoadedChunks.clear(); - m_ChunksToSend.clear(); - } - - if (m_Player != nullptr) - { - cWorld * World = m_Player->GetWorld(); - if (World != nullptr) - { - m_Player->GetWorld()->RemoveClientFromChunkSender(this); - } - // Send the Offline PlayerList packet: - cRoot::Get()->BroadcastPlayerListsRemovePlayer(*m_Player); - - m_PlayerPtr.reset(); - m_Player = nullptr; - } - LOGD("ClientHandle at %p deleted", static_cast(this)); } @@ -145,12 +124,7 @@ cClientHandle::~cClientHandle() void cClientHandle::Destroy(void) { - { - cCSLock Lock(m_CSOutgoingData); - m_Link.reset(); - } - - if (!SetState(csDestroying)) + if (!SetState(csDestroyed)) { // Already called LOGD("%s: client %p, \"%s\" already destroyed, bailing out", __FUNCTION__, static_cast(this), m_Username.c_str()); @@ -158,41 +132,12 @@ void cClientHandle::Destroy(void) } LOGD("%s: destroying client %p, \"%s\" @ %s", __FUNCTION__, static_cast(this), m_Username.c_str(), m_IPString.c_str()); - auto player = m_Player; - auto Self = std::move(m_Self); // Keep ourself alive for at least as long as this function - SetState(csDestroyed); - - if (player == nullptr) - { - return; - } - - // Atomically decrement player count (in world or server thread) - cRoot::Get()->GetServer()->PlayerDestroyed(); - auto world = player->GetWorld(); - if (world != nullptr) { - player->StopEveryoneFromTargetingMe(); - player->SetIsTicking(false); - - if (!m_PlayerPtr) - { - // If our own smart pointer is unset, player has been transferred to world - ASSERT(world->IsPlayerReferencedInWorldOrChunk(*player)); - - m_PlayerPtr = world->RemovePlayer(*player); - - // And RemovePlayer should have returned a valid smart pointer - ASSERT(m_PlayerPtr); - } - else - { - // If ownership was not transferred, our own smart pointer should be valid and RemovePlayer's should not - ASSERT(!world->IsPlayerReferencedInWorldOrChunk(*player)); - } + cCSLock Lock(m_CSOutgoingData); + m_Link->Shutdown(); // Cleanly close the connection + m_Link.reset(); // Release the strong reference cTCPLink holds to ourself } - player->RemoveClientHandle(); } @@ -301,9 +246,6 @@ void cClientHandle::Kick(const AString & a_Reason) void cClientHandle::Authenticate(const AString & a_Name, const cUUID & a_UUID, const Json::Value & a_Properties) { - // Atomically increment player count (in server thread) - cRoot::Get()->GetServer()->PlayerCreated(); - { cCSLock lock(m_CSState); /* @@ -351,95 +293,67 @@ void cClientHandle::Authenticate(const AString & a_Name, const cUUID & a_UUID, c void cClientHandle::FinishAuthenticate(const AString & a_Name, const cUUID & a_UUID, const Json::Value & a_Properties) { - cWorld * World; - { - // Spawn player (only serversided, so data is loaded) - m_PlayerPtr = std::make_unique(m_Self, GetUsername()); - m_Player = m_PlayerPtr.get(); - /* - LOGD("Created a new cPlayer object at %p for client %s @ %s (%p)", - static_cast(m_Player), - m_Username.c_str(), m_IPString.c_str(), static_cast(this) - ); - //*/ - InvalidateCachedSentChunk(); - m_Self.reset(); + // Serverside spawned player (so data are loaded). + auto Player = std::make_unique(shared_from_this()); + m_Player = Player.get(); + /* + LOGD("Created a new cPlayer object at %p for client %s @ %s (%p)", + static_cast(m_Player), + m_Username.c_str(), m_IPString.c_str(), static_cast(this) + ); + //*/ - // New player use default world - // Player who can load from disk, use loaded world - if (m_Player->GetWorld() == nullptr) - { - World = cRoot::Get()->GetWorld(m_Player->GetLoadedWorldName()); - if (World == nullptr) - { - World = cRoot::Get()->GetDefaultWorld(); - m_Player->SetPosition(World->GetSpawnX(), World->GetSpawnY(), World->GetSpawnZ()); - } - m_Player->SetWorld(World); - } - else - { - World = m_Player->GetWorld(); - } - - m_Player->SetIP (m_IPString); + cWorld * World = cRoot::Get()->GetWorld(m_Player->GetLoadedWorldName()); + if (World == nullptr) + { + World = cRoot::Get()->GetDefaultWorld(); + } - if (!cRoot::Get()->GetPluginManager()->CallHookPlayerJoined(*m_Player)) - { - cRoot::Get()->BroadcastChatJoin(Printf("%s has joined the game", GetUsername().c_str())); - LOGINFO("Player %s has joined the game", m_Username.c_str()); - } + // Atomically increment player count (in server thread): + cRoot::Get()->GetServer()->PlayerCreated(); - m_ConfirmPosition = m_Player->GetPosition(); + if (!cRoot::Get()->GetPluginManager()->CallHookPlayerJoined(*m_Player)) + { + cRoot::Get()->BroadcastChatJoin(Printf("%s has joined the game", a_Name.c_str())); + LOGINFO("Player %s has joined the game", a_Name.c_str()); + } - // Return a server login packet - m_Protocol->SendLogin(*m_Player, *World); - m_LastSentDimension = World->GetDimension(); + // TODO: this accesses the world spawn from the authenticator thread + // World spawn should be sent in OnAddedToWorld. + // Return a server login packet: + m_Protocol->SendLogin(*m_Player, *World); - // Send Weather if raining: - if ((World->GetWeather() == 1) || (World->GetWeather() == 2)) + if (m_Player->GetKnownRecipes().empty()) + { + SendInitRecipes(0); + } + else + { + for (const auto KnownRecipe : m_Player->GetKnownRecipes()) { - m_Protocol->SendWeather(World->GetWeather()); + SendInitRecipes(KnownRecipe); } - - // Send time: - m_Protocol->SendTimeUpdate(World->GetWorldAge(), World->GetTimeOfDay(), World->IsDaylightCycleEnabled()); - - // Send contents of the inventory window - m_Protocol->SendWholeInventory(*m_Player->GetWindow()); - - // Send health - m_Player->SendHealth(); - - // Send experience - m_Player->SendExperience(); - - // Send hotbar active slot - m_Player->SendHotbarActiveSlot(); - - // Send player list items - SendPlayerListAddPlayer(*m_Player); - cRoot::Get()->BroadcastPlayerListsAddPlayer(*m_Player); - cRoot::Get()->SendPlayerLists(m_Player); - - SetState(csAuthenticated); } - // Query player team - m_Player->UpdateTeam(); - - // Send scoreboard data - World->GetScoreBoard().SendTo(*this); + // Send player list items: + SendPlayerListAddPlayer(*m_Player); // Add ourself + cRoot::Get()->BroadcastPlayerListsAddPlayer(*m_Player); // Add ourself to everyone else + cRoot::Get()->SendPlayerLists(m_Player); // Add everyone else to ourself - // Send statistics + // Send statistics: SendStatistics(m_Player->GetStatManager()); // Delay the first ping until the client "settles down" // This should fix #889, "BadCast exception, cannot convert bit to fm" error in client m_PingStartTime = std::chrono::steady_clock::now() + std::chrono::seconds(3); // Send the first KeepAlive packet in 3 seconds - cRoot::Get()->GetPluginManager()->CallHookPlayerSpawned(*m_Player); + // Remove the client handle from the server, it will be ticked from its cPlayer object from now on: + cRoot::Get()->GetServer()->ClientMovedToWorld(this); + + SetState(csDownloadingWorld); + m_Player->Initialize(std::move(Player), *World); + // LOGD("Client %s @ %s (%p) has been fully authenticated", m_Username.c_str(), m_IPString.c_str(), static_cast(this)); } @@ -449,10 +363,6 @@ void cClientHandle::FinishAuthenticate(const AString & a_Name, const cUUID & a_U bool cClientHandle::StreamNextChunk(void) { - if ((m_State < csAuthenticated) || (m_State >= csDestroying)) - { - return true; - } ASSERT(m_Player != nullptr); int ChunkPosX = m_Player->GetChunkX(); @@ -609,12 +519,6 @@ void cClientHandle::UnloadOutOfRangeChunks(void) void cClientHandle::StreamChunk(int a_ChunkX, int a_ChunkZ, cChunkSender::Priority a_Priority) { - if (m_State >= csDestroying) - { - // Don't stream chunks to clients that are being destroyed - return; - } - cWorld * World = m_Player->GetWorld(); ASSERT(World != nullptr); @@ -820,12 +724,6 @@ void cClientHandle::HandlePlayerAbilities(bool a_IsFlying, float FlyingSpeed, fl void cClientHandle::HandlePlayerPos(double a_PosX, double a_PosY, double a_PosZ, double a_Stance, bool a_IsOnGround) { - if ((m_Player == nullptr) || (m_State != csPlaying)) - { - // The client hasn't been spawned yet and sends nonsense, we know better - return; - } - if (m_Player->IsFrozen()) { // Ignore client-side updates if the player is frozen @@ -1580,11 +1478,6 @@ void cClientHandle::HandleChat(const AString & a_Message) void cClientHandle::HandlePlayerLook(float a_Rotation, float a_Pitch, bool a_IsOnGround) { - if ((m_Player == nullptr) || (m_State != csPlaying)) - { - return; - } - m_Player->SetYaw (a_Rotation); m_Player->SetHeadYaw (a_Rotation); m_Player->SetPitch (a_Pitch); @@ -1838,11 +1731,12 @@ void cClientHandle::HandleUseItem(eHand a_Hand) void cClientHandle::HandleRespawn(void) { - if (m_Player == nullptr) + if (m_Player->GetHealth() > 0) { - Destroy(); + Kick("What is not dead may not live again. Hacked client?"); return; } + m_Player->Respawn(); cRoot::Get()->GetPluginManager()->CallHookPlayerSpawned(*m_Player); } @@ -1961,10 +1855,6 @@ void cClientHandle::HandleEntitySprinting(UInt32 a_EntityID, bool a_IsSprinting) void cClientHandle::HandleUnmount(void) { - if (m_Player == nullptr) - { - return; - } m_Player->Detach(); } @@ -2009,8 +1899,14 @@ void cClientHandle::SendData(const ContiguousByteBufferView a_Data) return; } - cCSLock Lock(m_CSOutgoingData); - m_OutgoingData += a_Data; + // Due to cTCPLink's design of holding a strong pointer to ourself, we need to explicitly reset m_Link. + // This means we need to check it's not nullptr before trying to send, but also capture the link, + // to prevent it being reset between the null check and the Send: + if (auto Link = m_Link; Link != nullptr) + { + cCSLock Lock(m_CSOutgoingData); + Link->Send(a_Data.data(), a_Data.size()); + } } @@ -2087,28 +1983,15 @@ void cClientHandle::Tick(float a_Dt) try { - ProcessProtocolInOut(); + ProcessProtocolIn(); } catch (const std::exception & Oops) { Kick(Oops.what()); - return; // Return early to give a chance to send the kick packet before link shutdown } - // If player has been kicked, terminate the connection: - if (m_State == csKicked) + if (IsDestroyed()) { - m_Link->Shutdown(); - } - - // If destruction is queued, destroy now: - if (m_State == csQueuedForDestruction) - { - LOGD("Client %s @ %s (%p) has been queued for destruction, destroying now.", - m_Username.c_str(), m_IPString.c_str(), static_cast(this) - ); - GetPlayer()->GetStatManager().AddValue(Statistic::LeaveGame); - Destroy(); return; } @@ -2119,12 +2002,6 @@ void cClientHandle::Tick(float a_Dt) return; } - // Only process further if the player object is valid: - if (m_Player == nullptr) - { - return; - } - // Freeze the player if they are standing in a chunk not yet sent to the client m_HasSentPlayerChunk = false; if (m_Player->GetParentChunk() != nullptr) @@ -2160,6 +2037,12 @@ void cClientHandle::Tick(float a_Dt) { m_Protocol->SendPlayerMoveLook(); m_State = csPlaying; + + // Send resource pack (after a MoveLook, because sending it before the initial MoveLook cancels the download screen): + if (const auto & ResourcePackUrl = cRoot::Get()->GetServer()->GetResourcePackUrl(); !ResourcePackUrl.empty()) + { + SendResourcePack(ResourcePackUrl); + } } } // lock(m_CSState) @@ -2174,24 +2057,21 @@ void cClientHandle::Tick(float a_Dt) } } - if ((m_State >= csAuthenticated) && (m_State < csQueuedForDestruction)) + // Stream 4 chunks per tick + for (int i = 0; i < 4; i++) { - // Stream 4 chunks per tick - for (int i = 0; i < 4; i++) + // Stream the next chunk + if (StreamNextChunk()) { - // Stream the next chunk - if (StreamNextChunk()) - { - // Streaming finished. All chunks are loaded. - break; - } + // Streaming finished. All chunks are loaded. + break; } + } - // Unload all chunks that are out of the view distance (every 5 seconds) - if ((m_Player->GetWorld()->GetWorldAge() % 100) == 0) - { - UnloadOutOfRangeChunks(); - } + // Unload all chunks that are out of the view distance (every 5 seconds) + if ((m_Player->GetWorld()->GetWorldAge() % 100) == 0) + { + UnloadOutOfRangeChunks(); } // Handle block break animation: @@ -2220,33 +2100,7 @@ void cClientHandle::Tick(float a_Dt) void cClientHandle::ServerTick(float a_Dt) { - ProcessProtocolInOut(); - - // If destruction is queued, destroy now: - if (m_State == csQueuedForDestruction) - { - LOGD("Client %s @ %s (%p) has been queued for destruction, destroying now.", - m_Username.c_str(), m_IPString.c_str(), static_cast(this) - ); - Destroy(); - return; - } - - { - cCSLock lock(m_CSState); - if (m_State == csAuthenticated) - { - StreamNextChunk(); - - // Remove the client handle from the server, it will be ticked from its cPlayer object from now on - cRoot::Get()->GetServer()->ClientMovedToWorld(this); - - // Add the player to the world (start ticking from there): - m_State = csDownloadingWorld; - m_Player->Initialize(std::move(m_PlayerPtr), *(m_Player->GetWorld())); - return; - } - } // lock(m_CSState) + ProcessProtocolIn(); m_TicksSinceLastPacket += 1; if (m_TicksSinceLastPacket > 600) // 30 seconds @@ -2538,10 +2392,7 @@ void cClientHandle::SendDisconnect(const AString & a_Reason) LOGD("Sending a DC: \"%s\"", StripColorCodes(a_Reason).c_str()); m_Protocol.SendDisconnect(*this, a_Reason); m_HasSentDC = true; - // csKicked means m_Link will be shut down on the next tick. The - // disconnect packet data is sent in the tick thread so the connection - // is closed there after the data is sent. - SetState(csKicked); + Destroy(); } } @@ -2906,19 +2757,18 @@ void cClientHandle::SendResetTitle() void cClientHandle::SendRespawn(eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) { - if ((!a_ShouldIgnoreDimensionChecks) && (a_Dimension == m_LastSentDimension)) + if (!a_ShouldIgnoreDimensionChecks && (a_Dimension == m_Player->GetWorld()->GetDimension())) { // The client goes crazy if we send a respawn packet with the dimension of the current world // So we send a temporary one first. // This is not needed when the player dies, hence the a_ShouldIgnoreDimensionChecks flag. - // a_ShouldIgnoreDimensionChecks is true only at cPlayer::respawn, which is called after + // a_ShouldIgnoreDimensionChecks is true only at cPlayer::Respawn, which is called after // the player dies. eDimension TemporaryDimension = (a_Dimension == dimOverworld) ? dimNether : dimOverworld; m_Protocol->SendRespawn(TemporaryDimension); } + m_Protocol->SendRespawn(a_Dimension); - m_Protocol->SendExperience(); - m_LastSentDimension = a_Dimension; } @@ -3285,7 +3135,7 @@ bool cClientHandle::HasPluginChannel(const AString & a_PluginChannel) bool cClientHandle::WantsSendChunk(int a_ChunkX, int a_ChunkZ) { - if (m_State >= csQueuedForDestruction) + if (m_State >= csDestroyed) { return false; } @@ -3300,7 +3150,7 @@ bool cClientHandle::WantsSendChunk(int a_ChunkX, int a_ChunkZ) void cClientHandle::AddWantedChunk(int a_ChunkX, int a_ChunkZ) { - if (m_State >= csQueuedForDestruction) + if (m_State >= csDestroyed) { return; } @@ -3368,17 +3218,7 @@ void cClientHandle::SocketClosed(void) } // Queue self for destruction: - SetState(csQueuedForDestruction); -} - - - - - -void cClientHandle::SetSelf(cClientHandlePtr a_Self) -{ - ASSERT(m_Self == nullptr); - m_Self = std::move(a_Self); + Destroy(); } @@ -3400,7 +3240,7 @@ bool cClientHandle::SetState(eState a_NewState) -void cClientHandle::ProcessProtocolInOut(void) +void cClientHandle::ProcessProtocolIn(void) { // Process received network data: AString IncomingData; @@ -3413,21 +3253,6 @@ void cClientHandle::ProcessProtocolInOut(void) { m_Protocol.HandleIncomingData(*this, IncomingData); } - - // Send any queued outgoing data: - ContiguousByteBuffer OutgoingData; - { - cCSLock Lock(m_CSOutgoingData); - std::swap(OutgoingData, m_OutgoingData); - } - - // Capture the link to prevent it being reset between the null check and the Send: - auto Link = m_Link; - - if ((Link != nullptr) && !OutgoingData.empty()) - { - Link->Send(OutgoingData.data(), OutgoingData.size()); - } } @@ -3464,10 +3289,6 @@ void cClientHandle::OnRemoteClosed(void) m_Username.c_str(), m_IPString.c_str() ); //*/ - { - cCSLock Lock(m_CSOutgoingData); - m_Link.reset(); - } SocketClosed(); } @@ -3480,9 +3301,5 @@ void cClientHandle::OnError(int a_ErrorCode, const AString & a_ErrorMsg) LOGD("An error has occurred on client link for %s @ %s: %d (%s). Client disconnected.", m_Username.c_str(), m_IPString.c_str(), a_ErrorCode, a_ErrorMsg.c_str() ); - { - cCSLock Lock(m_CSOutgoingData); - m_Link.reset(); - } SocketClosed(); } diff --git a/src/ClientHandle.h b/src/ClientHandle.h index 555c4396a..2662f20d6 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -45,7 +45,7 @@ typedef std::shared_ptr cClientHandlePtr; class cClientHandle // tolua_export - : public cTCPLink::cCallbacks + : public cTCPLink::cCallbacks, public std::enable_shared_from_this { // tolua_export public: // tolua_export @@ -136,7 +136,6 @@ public: // tolua_export bool IsPlaying (void) const { return (m_State == csPlaying); } bool IsDestroyed (void) const { return (m_State == csDestroyed); } - bool IsDestroying(void) const { return (m_State == csDestroying); } // The following functions send the various packets: // (Please keep these alpha-sorted) @@ -197,7 +196,7 @@ public: // tolua_export void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID); void SendResourcePack (const AString & a_ResourcePackUrl); void SendResetTitle (void); // tolua_export - void SendRespawn (eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks = false); + void SendRespawn (eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks); void SendScoreUpdate (const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode); void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode); void SendSetSubTitle (const cCompositeChat & a_SubTitle); // tolua_export @@ -401,9 +400,6 @@ public: // tolua_export bool IsPlayerChunkSent(); private: - /** The dimension that was last sent to a player in a Respawn or Login packet. - Used to avoid Respawning into the same dimension, which confuses the client. */ - eDimension m_LastSentDimension; friend class cServer; // Needs access to SetSelf() @@ -447,17 +443,12 @@ private: /** Protects m_OutgoingData against multithreaded access. */ cCriticalSection m_CSOutgoingData; - /** Buffer for storing outgoing data from any thread; will get sent in Tick() (to prevent deadlocks). - Protected by m_CSOutgoingData. */ - ContiguousByteBuffer m_OutgoingData; - - Vector3d m_ConfirmPosition; - + /** A pointer to a World-owned player object, created in FinishAuthenticate when authentication succeeds. + The player should only be accessed from the tick thread of the World that owns him. + After the player object is handed off to the World, lifetime is managed automatically, guaranteed to outlast this client handle. + The player self-destructs some time after the client handle enters the Destroyed state. */ cPlayer * m_Player; - // Temporary (#3115-will-fix): maintain temporary ownership of created cPlayer objects while they are in limbo - std::unique_ptr m_PlayerPtr; - /** This is an optimization which saves you an iteration of m_SentChunks if you just want to know whether or not the player is standing at a sent chunk. If this is equal to the coordinates of the chunk the player is currrently standing at, then this must be a sent chunk @@ -500,12 +491,8 @@ private: { csConnected, ///< The client has just connected, waiting for their handshake / login csAuthenticating, ///< The client has logged in, waiting for external authentication - csAuthenticated, ///< The client has been authenticated, will start streaming chunks in the next tick csDownloadingWorld, ///< The client is waiting for chunks, we're waiting for the loader to provide and send them csPlaying, ///< Normal gameplay - csKicked, ///< Disconnect packet sent, awaiting connection closure - csQueuedForDestruction, ///< The client will be destroyed in the next tick (flag set when socket closed) - csDestroying, ///< The client is being destroyed, don't queue any more packets / don't add to chunks csDestroyed, ///< The client has been destroyed, the destructor is to be called from the owner thread } ; @@ -555,9 +542,6 @@ private: m_CSOutgoingData is used to synchronize access for sending data. */ cTCPLinkPtr m_Link; - /** Shared pointer to self, so that this instance can keep itself alive when needed. */ - cClientHandlePtr m_Self; - /** The fraction between 0 and 1 (or above), of how far through mining the currently mined block is. 0 for just started, 1 and above for broken. Used for anti-cheat. */ float m_BreakProgress; @@ -592,16 +576,13 @@ private: /** Called when the network socket has been closed. */ void SocketClosed(void); - /** Called right after the instance is created to store its SharedPtr inside. */ - void SetSelf(cClientHandlePtr a_Self); - /** Called to update m_State. Only succeeds if a_NewState > m_State, otherwise returns false. */ bool SetState(eState a_NewState); - /** Processes the data in the network input and output buffers. + /** Processes the data in the network input buffer. Called by both Tick() and ServerTick(). */ - void ProcessProtocolInOut(void); + void ProcessProtocolIn(void); // cTCPLink::cCallbacks overrides: virtual void OnLinkCreated(cTCPLinkPtr a_Link) override; diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index f07eab415..a185b8f69 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -1183,12 +1183,6 @@ void cEntity::ApplyFriction(Vector3d & a_Speed, double a_SlowdownMultiplier, flo void cEntity::TickBurning(cChunk & a_Chunk) { - // If we're about to change worlds, then we can't accurately determine whether we're in lava (#3939) - if (IsWorldChangeScheduled()) - { - return; - } - // Remember the current burning state: bool HasBeenBurning = (m_TicksLeftBurning > 0); @@ -1359,12 +1353,6 @@ void cEntity::DetectMagma(void) bool cEntity::DetectPortal() { - // If somebody scheduled a world change, do nothing. - if (IsWorldChangeScheduled()) - { - return true; - } - if (GetWorld()->GetDimension() == dimOverworld) { if (GetWorld()->GetLinkedNetherWorldName().empty() && GetWorld()->GetLinkedEndWorldName().empty()) @@ -1380,7 +1368,7 @@ bool cEntity::DetectPortal() } int X = POSX_TOINT, Y = POSY_TOINT, Z = POSZ_TOINT; - if ((Y > 0) && (Y < cChunkDef::Height)) + if (cChunkDef::IsValidHeight(Y)) { switch (GetWorld()->GetBlock(X, Y, Z)) { @@ -1413,24 +1401,16 @@ bool cEntity::DetectPortal() { return false; } - cWorld * DestinationWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName()); - eDimension DestionationDim = DestinationWorld->GetDimension(); m_PortalCooldownData.m_ShouldPreventTeleportation = true; // Stop portals from working on respawn - if (IsPlayer()) - { - // Send a respawn packet before world is loaded / generated so the client isn't left in limbo - (static_cast(this))->GetClientHandle()->SendRespawn(DestionationDim); - } - Vector3d TargetPos = GetPosition(); TargetPos.x *= 8.0; TargetPos.z *= 8.0; cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName()); ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start() - LOGD("Jumping %s -> %s", DimensionToString(dimNether).c_str(), DimensionToString(DestionationDim).c_str()); + LOGD("Jumping %s -> %s", DimensionToString(dimNether).c_str(), DimensionToString(TargetWorld->GetDimension()).c_str()); new cNetherPortalScanner(*this, *TargetWorld, TargetPos, cChunkDef::Height); return true; } @@ -1441,28 +1421,16 @@ bool cEntity::DetectPortal() { return false; } - cWorld * DestinationWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedNetherWorldName()); - eDimension DestionationDim = DestinationWorld->GetDimension(); m_PortalCooldownData.m_ShouldPreventTeleportation = true; - if (IsPlayer()) - { - if (DestionationDim == dimNether) - { - static_cast(this)->AwardAchievement(Statistic::AchPortal); - } - - static_cast(this)->GetClientHandle()->SendRespawn(DestionationDim); - } - Vector3d TargetPos = GetPosition(); TargetPos.x /= 8.0; TargetPos.z /= 8.0; cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedNetherWorldName()); ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start() - LOGD("Jumping %s -> %s", DimensionToString(dimOverworld).c_str(), DimensionToString(DestionationDim).c_str()); + LOGD("Jumping %s -> %s", DimensionToString(dimOverworld).c_str(), DimensionToString(TargetWorld->GetDimension()).c_str()); new cNetherPortalScanner(*this, *TargetWorld, TargetPos, (cChunkDef::Height / 2)); return true; } @@ -1483,34 +1451,26 @@ bool cEntity::DetectPortal() // End portal in the end if (GetWorld()->GetDimension() == dimEnd) { - if (GetWorld()->GetLinkedOverworldName().empty()) { return false; } - cWorld * DestinationWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName()); - eDimension DestionationDim = DestinationWorld->GetDimension(); - m_PortalCooldownData.m_ShouldPreventTeleportation = true; + cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName()); + ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start() + LOGD("Jumping %s -> %s", DimensionToString(dimEnd).c_str(), DimensionToString(TargetWorld->GetDimension()).c_str()); + if (IsPlayer()) { cPlayer * Player = static_cast(this); - if (Player->GetBedWorld() == DestinationWorld) - { - Player->TeleportToCoords(Player->GetLastBedPos().x, Player->GetLastBedPos().y, Player->GetLastBedPos().z); - } - else + if (Player->GetBedWorld() == TargetWorld) { - Player->TeleportToCoords(DestinationWorld->GetSpawnX(), DestinationWorld->GetSpawnY(), DestinationWorld->GetSpawnZ()); + return MoveToWorld(*TargetWorld, Player->GetLastBedPos()); } - Player->GetClientHandle()->SendRespawn(DestionationDim); } - cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName()); - ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start() - LOGD("Jumping %s -> %s", DimensionToString(dimEnd).c_str(), DimensionToString(DestionationDim).c_str()); return MoveToWorld(*TargetWorld, false); } // End portal in the overworld @@ -1520,23 +1480,12 @@ bool cEntity::DetectPortal() { return false; } - cWorld * DestinationWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedEndWorldName()); - eDimension DestionationDim = DestinationWorld->GetDimension(); m_PortalCooldownData.m_ShouldPreventTeleportation = true; - if (IsPlayer()) - { - if (DestionationDim == dimEnd) - { - static_cast(this)->AwardAchievement(Statistic::AchTheEnd); - } - static_cast(this)->GetClientHandle()->SendRespawn(DestionationDim); - } - cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedEndWorldName()); ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start() - LOGD("Jumping %s -> %s", DimensionToString(dimOverworld).c_str(), DimensionToString(DestionationDim).c_str()); + LOGD("Jumping %s -> %s", DimensionToString(dimOverworld).c_str(), DimensionToString(TargetWorld->GetDimension()).c_str()); return MoveToWorld(*TargetWorld, false); } @@ -1559,13 +1508,14 @@ void cEntity::DoMoveToWorld(const sWorldChangeInfo & a_WorldChangeInfo) { ASSERT(a_WorldChangeInfo.m_NewWorld != nullptr); + // Reset portal cooldown: if (a_WorldChangeInfo.m_SetPortalCooldown) { m_PortalCooldownData.m_TicksDelayed = 0; m_PortalCooldownData.m_ShouldPreventTeleportation = true; } - if (GetWorld() == a_WorldChangeInfo.m_NewWorld) + if (m_World == a_WorldChangeInfo.m_NewWorld) { // Moving to same world, don't need to remove from world SetPosition(a_WorldChangeInfo.m_NewPosition); @@ -1578,23 +1528,19 @@ void cEntity::DoMoveToWorld(const sWorldChangeInfo & a_WorldChangeInfo) GetChunkX(), GetChunkZ() ); - // If entity is attached to another entity, detach, to prevent client side effects - Detach(); - // Stop ticking, in preperation for detaching from this world. SetIsTicking(false); // Remove from the old world + const auto OldWorld = m_World; auto Self = m_World->RemoveEntity(*this); - // Update entity before calling hook + // Update entity: ResetPosition(a_WorldChangeInfo.m_NewPosition); SetWorld(a_WorldChangeInfo.m_NewWorld); - cRoot::Get()->GetPluginManager()->CallHookEntityChangedWorld(*this, *m_World); - // Don't do anything after adding as the old world's CS no longer protects us - a_WorldChangeInfo.m_NewWorld->AddEntity(std::move(Self)); + a_WorldChangeInfo.m_NewWorld->AddEntity(std::move(Self), OldWorld); } @@ -1614,7 +1560,7 @@ bool cEntity::MoveToWorld(cWorld & a_World, Vector3d a_NewPosition, bool a_SetPo // Create new world change info // (The last warp command always takes precedence) - m_WorldChangeInfo = { &a_World, a_NewPosition, a_SetPortalCooldown, a_ShouldSendRespawn }; + m_WorldChangeInfo = { &a_World, a_NewPosition, a_SetPortalCooldown }; if (OldWorld != nullptr) { diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index 85cf35661..cbefc764c 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -81,7 +81,6 @@ protected: cWorld * m_NewWorld; Vector3d m_NewPosition; bool m_SetPortalCooldown; - bool m_SendRespawn; }; public: @@ -173,7 +172,7 @@ public: /** Spawns the entity in the world; returns true if spawned, false if not (plugin disallowed). Adds the entity to the world. */ - virtual bool Initialize(OwnedEntity a_Self, cWorld & a_EntityWorld); + bool Initialize(OwnedEntity a_Self, cWorld & a_EntityWorld); /** Called when the entity is added to a world. e.g after first spawning or after successfuly moving between worlds. @@ -469,13 +468,6 @@ public: /** Teleports to the coordinates specified */ virtual void TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ); - /** Schedules a MoveToWorld call to occur on the next Tick of the entity */ - [[deprecated]] void ScheduleMoveToWorld(cWorld & a_World, Vector3d a_NewPosition, bool a_ShouldSetPortalCooldown = false, bool a_ShouldSendRespawn = true) - { - LOGWARNING("ScheduleMoveToWorld is deprecated, use MoveToWorld instead"); - MoveToWorld(a_World, a_NewPosition, a_ShouldSetPortalCooldown, a_ShouldSendRespawn); - } - bool MoveToWorld(cWorld & a_World, Vector3d a_NewPosition, bool a_ShouldSetPortalCooldown = false, bool a_ShouldSendRespawn = true); bool MoveToWorld(cWorld & a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition) @@ -718,7 +710,7 @@ protected: /** Handles the moving of this entity between worlds. Should handle degenerate cases such as moving to the same world. */ - virtual void DoMoveToWorld(const sWorldChangeInfo & a_WorldChangeInfo); + void DoMoveToWorld(const sWorldChangeInfo & a_WorldChangeInfo); /** Applies friction to an entity @param a_Speed The speed vector to apply changes to diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 391a5ce71..d20795643 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -86,7 +86,7 @@ const int cPlayer::EATING_TICKS = 30; -cPlayer::cPlayer(const cClientHandlePtr & a_Client, const AString & a_PlayerName) : +cPlayer::cPlayer(const cClientHandlePtr & a_Client) : Super(etPlayer, 0.6, 1.8), m_bVisible(true), m_FoodLevel(MAX_FOOD_LEVEL), @@ -96,10 +96,8 @@ cPlayer::cPlayer(const cClientHandlePtr & a_Client, const AString & a_PlayerName m_Stance(0.0), m_Inventory(*this), m_EnderChestContents(9, 3), - m_CurrentWindow(nullptr), - m_InventoryWindow(nullptr), + m_DefaultWorldPath(cRoot::Get()->GetDefaultWorld()->GetDataPath()), m_GameMode(eGameMode_NotSet), - m_IP(""), m_ClientHandle(a_Client), m_IsFrozen(false), m_NormalMaxSpeed(1.0), @@ -113,7 +111,6 @@ cPlayer::cPlayer(const cClientHandlePtr & a_Client, const AString & a_PlayerName m_EatingFinishTick(-1), m_LifetimeTotalXp(0), m_CurrentXp(0), - m_bDirtyExperience(false), m_IsChargingBow(false), m_BowCharge(0), m_FloaterID(cEntity::INVALID_ID), @@ -122,11 +119,10 @@ cPlayer::cPlayer(const cClientHandlePtr & a_Client, const AString & a_PlayerName m_TicksUntilNextSave(PLAYER_INVENTORY_SAVE_INTERVAL), m_bIsTeleporting(false), m_UUID((a_Client != nullptr) ? a_Client->GetUUID() : cUUID{}), - m_CustomName(""), m_SkinParts(0), m_MainHand(mhRight) { - ASSERT(a_PlayerName.length() <= 16); // Otherwise this player could crash many clients... + ASSERT(GetName().length() <= 16); // Otherwise this player could crash many clients... m_InventoryWindow = new cInventoryWindow(*this); m_CurrentWindow = m_InventoryWindow; @@ -136,7 +132,6 @@ cPlayer::cPlayer(const cClientHandlePtr & a_Client, const AString & a_PlayerName m_Health = MAX_HEALTH; m_LastPlayerListTime = std::chrono::steady_clock::now(); - m_PlayerName = a_PlayerName; cWorld * World = nullptr; if (!LoadFromDisk(World)) @@ -149,19 +144,16 @@ cPlayer::cPlayer(const cClientHandlePtr & a_Client, const AString & a_PlayerName // This is a new player. Set the player spawn point to the spawn point of the default world SetBedPos(Vector3i(static_cast(World->GetSpawnX()), static_cast(World->GetSpawnY()), static_cast(World->GetSpawnZ())), World); - SetWorld(World); // Use default world - m_EnchantmentSeed = GetRandomProvider().RandInt(); // Use a random number to seed the enchantment generator FLOGD("Player \"{0}\" is connecting for the first time, spawning at default world spawn {1:.2f}", - a_PlayerName, GetPosition() + GetName(), GetPosition() ); } m_LastGroundHeight = static_cast(GetPosY()); m_Stance = GetPosY() + 1.62; - if (m_GameMode == gmNotSet) { if (World->IsGameModeCreative()) @@ -187,34 +179,6 @@ cPlayer::cPlayer(const cClientHandlePtr & a_Client, const AString & a_PlayerName -bool cPlayer::Initialize(OwnedEntity a_Self, cWorld & a_World) -{ - UNUSED(a_World); - ASSERT(GetWorld() != nullptr); - ASSERT(GetParentChunk() == nullptr); - GetWorld()->AddPlayer(std::unique_ptr(static_cast(a_Self.release()))); - - cPluginManager::Get()->CallHookSpawnedEntity(*GetWorld(), *this); - - if (m_KnownRecipes.empty()) - { - m_ClientHandle->SendInitRecipes(0); - } - else - { - for (const auto KnownRecipe : m_KnownRecipes) - { - m_ClientHandle->SendInitRecipes(KnownRecipe); - } - } - - return true; -} - - - - - void cPlayer::AddKnownItem(const cItem & a_Item) { if (a_Item.m_ItemType < 0) @@ -258,20 +222,14 @@ void cPlayer::AddKnownRecipe(UInt32 a_RecipeId) cPlayer::~cPlayer(void) { - if (!cRoot::Get()->GetPluginManager()->CallHookPlayerDestroyed(*this)) - { - cRoot::Get()->BroadcastChatLeave(Printf("%s has left the game", GetName().c_str())); - LOGINFO("Player %s has left the game", GetName().c_str()); - } - LOGD("Deleting cPlayer \"%s\" at %p, ID %d", GetName().c_str(), static_cast(this), GetUniqueID()); - SaveToDisk(); + // "Times ragequit": + m_Stats.AddValue(Statistic::LeaveGame); - m_ClientHandle = nullptr; + SaveToDisk(); delete m_InventoryWindow; - m_InventoryWindow = nullptr; LOGD("Player %p deleted", static_cast(this)); } @@ -280,9 +238,105 @@ cPlayer::~cPlayer(void) +void cPlayer::OnAddToWorld(cWorld & a_World) +{ + Super::OnAddToWorld(a_World); + + // Update world name tracking: + m_CurrentWorldName = m_World->GetName(); + + // Fix to stop the player falling through the world, until we get serversided collision detection: + FreezeInternal(GetPosition(), false); + + // Set capabilities based on new world: + SetCapabilities(); + + // Send contents of the inventory window: + m_ClientHandle->SendWholeInventory(*m_CurrentWindow); + + // Send health (the respawn packet, which understandably resets health, is also used for world travel...): + m_ClientHandle->SendHealth(); + + // Send experience, similar story with the respawn packet: + m_ClientHandle->SendExperience(); + + // Send hotbar active slot (also reset by respawn): + m_ClientHandle->SendHeldItemChange(m_Inventory.GetEquippedSlotNum()); + + // Update player team: + UpdateTeam(); + + // Send scoreboard data: + m_World->GetScoreBoard().SendTo(*m_ClientHandle); + + // Update the view distance: + m_ClientHandle->SetViewDistance(m_ClientHandle->GetRequestedViewDistance()); + + // Send current weather of target world: + m_ClientHandle->SendWeather(a_World.GetWeather()); + + // Send time: + m_ClientHandle->SendTimeUpdate(a_World.GetWorldAge(), a_World.GetTimeOfDay(), a_World.IsDaylightCycleEnabled()); + + // Finally, deliver the notification hook: + cRoot::Get()->GetPluginManager()->CallHookPlayerSpawned(*this); +} + + + + + void cPlayer::OnRemoveFromWorld(cWorld & a_World) { + Super::OnRemoveFromWorld(a_World); + + // Remove any references to this player pointer by windows in the old world: CloseWindow(false); + + // Remove the client handle from the world: + m_World->RemoveClientFromChunks(m_ClientHandle.get()); + + if (m_ClientHandle->IsDestroyed()) // Note: checking IsWorldChangeScheduled not appropriate here since we can disconnecting while having a scheduled warp + { + // Disconnecting, do the necessary cleanup. + // This isn't in the destructor to avoid crashing accessing destroyed objects during shutdown. + + if (!cRoot::Get()->GetPluginManager()->CallHookPlayerDestroyed(*this)) + { + cRoot::Get()->BroadcastChatLeave(Printf("%s has left the game", GetName().c_str())); + LOGINFO("Player %s has left the game", GetName().c_str()); + } + + // Remove ourself from everyone's lists: + cRoot::Get()->BroadcastPlayerListsRemovePlayer(*this); + + // Atomically decrement player count (in world thread): + cRoot::Get()->GetServer()->PlayerDestroyed(); + + // We're just disconnecting. The remaining code deals with going through portals, so bail: + return; + } + + const auto DestinationDimension = m_WorldChangeInfo.m_NewWorld->GetDimension(); + + // Award relevant achievements: + if (DestinationDimension == dimEnd) + { + AwardAchievement(Statistic::AchTheEnd); + } + else if (DestinationDimension == dimNether) + { + AwardAchievement(Statistic::AchPortal); + } + + // Clear sent chunk lists from the clienthandle: + m_ClientHandle->RemoveFromWorld(); + + // The clienthandle caches the coords of the chunk we're standing at. Invalidate this. + m_ClientHandle->InvalidateCachedSentChunk(); + + // Clientside warp start: + m_ClientHandle->SendRespawn(DestinationDimension, false); } @@ -313,30 +367,23 @@ void cPlayer::SpawnOn(cClientHandle & a_Client) void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { - if (m_ClientHandle != nullptr) - { - if (m_ClientHandle->IsDestroyed()) - { - // This should not happen, because destroying a client will remove it from the world, but just in case - ASSERT(!"Player ticked whilst in the process of destruction!"); - m_ClientHandle = nullptr; - return; - } + m_ClientHandle->Tick(a_Dt.count()); - if (!m_ClientHandle->IsPlaying()) - { - // We're not yet in the game, ignore everything - return; - } - } - else + if (m_ClientHandle->IsDestroyed()) { - ASSERT(!"Player ticked whilst in the process of destruction!"); + Destroy(); + return; } + if (!m_ClientHandle->IsPlaying()) + { + // We're not yet in the game, ignore everything: + return; + } m_Stats.AddValue(Statistic::PlayOneMinute); m_Stats.AddValue(Statistic::TimeSinceDeath); + if (IsCrouched()) { m_Stats.AddValue(Statistic::SneakTime); @@ -356,16 +403,27 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) Detach(); } - // Handle a frozen player - TickFreezeCode(); - if (m_IsFrozen) + if (!a_Chunk.IsValid()) { + // Players are ticked even if the parent chunk is invalid. + // We've processed as much as we can, bail: return; } - ASSERT((GetParentChunk() != nullptr) && (GetParentChunk()->IsValid())); + ASSERT((GetParentChunk() != nullptr) && (GetParentChunk()->IsValid())); ASSERT(a_Chunk.IsValid()); + // Handle a frozen player: + TickFreezeCode(); + + if ( + m_IsFrozen || // Don't do Tick updates if frozen + IsWorldChangeScheduled() // If we're about to change worlds (e.g. respawn), abort processing all world interactions (GH #3939) + ) + { + return; + } + Super::Tick(a_Dt, a_Chunk); // Handle charging the bow: @@ -374,12 +432,6 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) m_BowCharge += 1; } - // Handle updating experience - if (m_bDirtyExperience) - { - SendExperience(); - } - BroadcastMovementUpdate(m_ClientHandle.get()); if (m_Health > 0) // make sure player is alive @@ -504,6 +556,15 @@ int cPlayer::CalcLevelFromXp(int a_XpTotal) +const std::set & cPlayer::GetKnownRecipes() const +{ + return m_KnownRecipes; +} + + + + + int cPlayer::XpForLevel(int a_Level) { // level 0 to 15 @@ -558,8 +619,8 @@ bool cPlayer::SetCurrentExperience(int a_CurrentXp) m_CurrentXp = a_CurrentXp; - // Set experience to be updated - m_bDirtyExperience = true; + // Update experience: + m_ClientHandle->SendExperience(); return true; } @@ -591,8 +652,8 @@ int cPlayer::DeltaExperience(int a_Xp_delta) LOGD("Player \"%s\" gained / lost %d experience, total is now: %d", GetName().c_str(), a_Xp_delta, m_CurrentXp); - // Set experience to be updated - m_bDirtyExperience = true; + // Set experience to be updated: + m_ClientHandle->SendExperience(); return m_CurrentXp; } @@ -661,7 +722,7 @@ void cPlayer::SetTouchGround(bool a_bTouchGround) void cPlayer::Heal(int a_Health) { Super::Heal(a_Health); - SendHealth(); + m_ClientHandle->SendHealth(); } @@ -679,7 +740,7 @@ void cPlayer::SetFoodLevel(int a_FoodLevel) } m_FoodLevel = FoodLevel; - SendHealth(); + m_ClientHandle->SendHealth(); } @@ -807,43 +868,6 @@ void cPlayer::AbortEating(void) -void cPlayer::SendHealth(void) -{ - if (m_ClientHandle != nullptr) - { - m_ClientHandle->SendHealth(); - } -} - - - - - -void cPlayer::SendHotbarActiveSlot(void) -{ - if (m_ClientHandle != nullptr) - { - m_ClientHandle->SendHeldItemChange(m_Inventory.GetEquippedSlotNum()); - } -} - - - - - -void cPlayer::SendExperience(void) -{ - if (m_ClientHandle != nullptr) - { - m_ClientHandle->SendExperience(); - m_bDirtyExperience = false; - } -} - - - - - void cPlayer::ClearInventoryPaintSlots(void) { // Clear the list of slots that are being inventory-painted. Used by cWindow only @@ -1007,7 +1031,7 @@ void cPlayer::SetCustomName(const AString & a_CustomName) } m_World->BroadcastPlayerListAddPlayer(*this); - m_World->BroadcastSpawnEntity(*this, GetClientHandle()); + m_World->BroadcastSpawnEntity(*this, m_ClientHandle.get()); } @@ -1017,7 +1041,7 @@ void cPlayer::SetCustomName(const AString & a_CustomName) void cPlayer::SetBedPos(const Vector3i & a_Pos) { m_LastBedPos = a_Pos; - m_SpawnWorld = m_World; + m_SpawnWorldName = m_World->GetName(); } @@ -1028,7 +1052,7 @@ void cPlayer::SetBedPos(const Vector3i & a_Pos, cWorld * a_World) { m_LastBedPos = a_Pos; ASSERT(a_World != nullptr); - m_SpawnWorld = a_World; + m_SpawnWorldName = a_World->GetName(); } @@ -1037,7 +1061,12 @@ void cPlayer::SetBedPos(const Vector3i & a_Pos, cWorld * a_World) cWorld * cPlayer::GetBedWorld() { - return m_SpawnWorld; + if (const auto World = cRoot::Get()->GetWorld(m_SpawnWorldName); World != nullptr) + { + return World; + } + + return cRoot::Get()->GetDefaultWorld(); } @@ -1106,7 +1135,7 @@ bool cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI) { // Any kind of damage adds food exhaustion AddFoodExhaustion(0.3f); - SendHealth(); + m_ClientHandle->SendHealth(); // Tell the wolves if (a_TDI.Attacker != nullptr) @@ -1296,17 +1325,16 @@ void cPlayer::Respawn(void) m_LifetimeTotalXp = 0; // ToDo: send score to client? How? - m_ClientHandle->SendRespawn(m_SpawnWorld->GetDimension(), true); - // Extinguish the fire: StopBurning(); - if (GetWorld() != m_SpawnWorld) + if (const auto BedWorld = GetBedWorld(); m_World != BedWorld) { - MoveToWorld(*m_SpawnWorld, GetLastBedPos(), false, false); + MoveToWorld(*BedWorld, GetLastBedPos(), false, false); } else { + m_ClientHandle->SendRespawn(m_World->GetDimension(), true); TeleportToCoords(GetLastBedPos().x, GetLastBedPos().y, GetLastBedPos().z); } @@ -1380,6 +1408,15 @@ bool cPlayer::CanMobsTarget(void) const +AString cPlayer::GetIP(void) const +{ + return m_ClientHandle->GetIPString(); +} + + + + + void cPlayer::SetTeam(cTeam * a_Team) { if (m_Team == a_Team) @@ -1600,6 +1637,15 @@ void cPlayer::SendAboveActionBarMessage(const cCompositeChat & a_Message) +const AString & cPlayer::GetName(void) const +{ + return m_ClientHandle->GetUsername(); +} + + + + + void cPlayer::SetGameMode(eGameMode a_GameMode) { if ((a_GameMode < gmMin) || (a_GameMode >= gmMax)) @@ -1671,15 +1717,6 @@ void cPlayer::SetCapabilities() -void cPlayer::SetIP(const AString & a_IP) -{ - m_IP = a_IP; -} - - - - - void cPlayer::AwardAchievement(const Statistic a_Ach) { // Check if the prerequisites are met: @@ -1753,7 +1790,7 @@ void cPlayer::Unfreeze() GetClientHandle()->SendPlayerMaxSpeed(); m_IsFrozen = false; - BroadcastMovementUpdate(GetClientHandle()); + BroadcastMovementUpdate(m_ClientHandle.get()); GetClientHandle()->SendPlayerPosition(); } @@ -1819,6 +1856,29 @@ Vector3d cPlayer::GetThrowSpeed(double a_SpeedCoeff) const +eGameMode cPlayer::GetEffectiveGameMode(void) const +{ + // Since entities' m_World aren't set until Initialize, but cClientHandle sends the player's gamemode early + // the below block deals with m_World being nullptr when called. + + auto World = m_World; + + if (World == nullptr) + { + World = cRoot::Get()->GetDefaultWorld(); + } + else if (IsWorldChangeScheduled()) + { + World = m_WorldChangeInfo.m_NewWorld; + } + + return (m_GameMode == gmNotSet) ? World->GetGameMode() : m_GameMode; +} + + + + + void cPlayer::ForceSetSpeed(const Vector3d & a_Speed) { SetSpeed(a_Speed); @@ -2099,79 +2159,6 @@ void cPlayer::TossPickup(const cItem & a_Item) -void cPlayer::DoMoveToWorld(const cEntity::sWorldChangeInfo & a_WorldChangeInfo) -{ - ASSERT(a_WorldChangeInfo.m_NewWorld != nullptr); - - // Reset portal cooldown - if (a_WorldChangeInfo.m_SetPortalCooldown) - { - m_PortalCooldownData.m_TicksDelayed = 0; - m_PortalCooldownData.m_ShouldPreventTeleportation = true; - } - - if (m_World == a_WorldChangeInfo.m_NewWorld) - { - // Moving to same world, don't need to remove from world - SetPosition(a_WorldChangeInfo.m_NewPosition); - return; - } - - LOGD("Warping player \"%s\" from world \"%s\" to \"%s\". Source chunk: (%d, %d) ", - GetName(), GetWorld()->GetName(), a_WorldChangeInfo.m_NewWorld->GetName(), - GetChunkX(), GetChunkZ() - ); - - // Stop all mobs from targeting this player - StopEveryoneFromTargetingMe(); - - // If player is attached to entity, detach, to prevent client side effects - Detach(); - - // Prevent further ticking in this world - SetIsTicking(false); - - // Remove from the old world - auto & OldWorld = *GetWorld(); - auto Self = OldWorld.RemovePlayer(*this); - - ResetPosition(a_WorldChangeInfo.m_NewPosition); - FreezeInternal(a_WorldChangeInfo.m_NewPosition, false); - SetWorld(a_WorldChangeInfo.m_NewWorld); // Chunks may be streamed before cWorld::AddPlayer() sets the world to the new value - - // Set capabilities based on new world - SetCapabilities(); - - cClientHandle * ch = GetClientHandle(); - if (ch != nullptr) - { - // The clienthandle caches the coords of the chunk we're standing at. Invalidate this. - ch->InvalidateCachedSentChunk(); - - // Send the respawn packet: - if (a_WorldChangeInfo.m_SendRespawn) - { - ch->SendRespawn(a_WorldChangeInfo.m_NewWorld->GetDimension()); - } - - // Update the view distance. - ch->SetViewDistance(ch->GetRequestedViewDistance()); - - // Send current weather of target world to player - if (a_WorldChangeInfo.m_NewWorld->GetDimension() == dimOverworld) - { - ch->SendWeather(a_WorldChangeInfo.m_NewWorld->GetWeather()); - } - } - - // New world will take over and announce client at its next tick - a_WorldChangeInfo.m_NewWorld->AddPlayer(std::move(Self), &OldWorld); -} - - - - - bool cPlayer::LoadFromDisk(cWorldPtr & a_World) { LoadRank(); @@ -2334,7 +2321,7 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World) cEnderChestEntity::LoadFromJson(root["enderchestinventory"], m_EnderChestContents); - m_LoadedWorldName = root.get("world", "world").asString(); + m_CurrentWorldName = root.get("world", "world").asString(); a_World = cRoot::Get()->GetWorld(GetLoadedWorldName()); if (a_World == nullptr) { @@ -2345,18 +2332,13 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World) m_LastBedPos.x = root.get("SpawnX", a_World->GetSpawnX()).asInt(); m_LastBedPos.y = root.get("SpawnY", a_World->GetSpawnY()).asInt(); m_LastBedPos.z = root.get("SpawnZ", a_World->GetSpawnZ()).asInt(); - AString SpawnWorldName = root.get("SpawnWorld", cRoot::Get()->GetDefaultWorld()->GetName()).asString(); - m_SpawnWorld = cRoot::Get()->GetWorld(SpawnWorldName); - if (m_SpawnWorld == nullptr) - { - m_SpawnWorld = cRoot::Get()->GetDefaultWorld(); - } + m_SpawnWorldName = root.get("SpawnWorld", cRoot::Get()->GetDefaultWorld()->GetName()).asString(); try { // Load the player stats. // We use the default world name (like bukkit) because stats are shared between dimensions / worlds. - StatSerializer::Load(m_Stats, cRoot::Get()->GetDefaultWorld()->GetDataPath(), GetUUID().ToLongString()); + StatSerializer::Load(m_Stats, m_DefaultWorldPath, GetUUID().ToLongString()); } catch (...) { @@ -2460,27 +2442,10 @@ bool cPlayer::SaveToDisk() root["SpawnX"] = GetLastBedPos().x; root["SpawnY"] = GetLastBedPos().y; root["SpawnZ"] = GetLastBedPos().z; - root["SpawnWorld"] = m_SpawnWorld->GetName(); + root["SpawnWorld"] = m_SpawnWorldName; root["enchantmentSeed"] = m_EnchantmentSeed; - - if (m_World != nullptr) - { - root["world"] = m_World->GetName(); - if (m_GameMode == m_World->GetGameMode()) - { - root["gamemode"] = static_cast(eGameMode_NotSet); - } - else - { - root["gamemode"] = static_cast(m_GameMode); - } - } - else - { - // This happens if the player is saved to new format after loading from the old format - root["world"] = m_LoadedWorldName; - root["gamemode"] = static_cast(eGameMode_NotSet); - } + root["world"] = m_CurrentWorldName; + root["gamemode"] = static_cast(m_GameMode); auto JsonData = JsonUtils::WriteStyledString(root); AString SourceFile = GetUUIDFileName(m_UUID); @@ -2505,7 +2470,8 @@ bool cPlayer::SaveToDisk() { // Save the player stats. // We use the default world name (like bukkit) because stats are shared between dimensions / worlds. - StatSerializer::Save(m_Stats, cRoot::Get()->GetDefaultWorld()->GetDataPath(), GetUUID().ToLongString()); + // TODO: save together with player.dat, not in some other place. + StatSerializer::Save(m_Stats, m_DefaultWorldPath, GetUUID().ToLongString()); } catch (...) { @@ -2787,7 +2753,7 @@ void cPlayer::LoadRank(void) else { // Update the name: - RankMgr->UpdatePlayerName(m_UUID, m_PlayerName); + RankMgr->UpdatePlayerName(m_UUID, GetName()); } m_Permissions = RankMgr->GetPlayerPermissions(m_UUID); m_Restrictions = RankMgr->GetPlayerRestrictions(m_UUID); @@ -3062,16 +3028,6 @@ void cPlayer::Detach() -void cPlayer::RemoveClientHandle(void) -{ - ASSERT(m_ClientHandle != nullptr); - m_ClientHandle.reset(); -} - - - - - AString cPlayer::GetUUIDFileName(const cUUID & a_UUID) { AString UUID = a_UUID.ToLongString(); diff --git a/src/Entities/Player.h b/src/Entities/Player.h index 1e7a17e4f..ba3c345ed 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -48,12 +48,11 @@ public: CLASS_PROTODEF(cPlayer) - cPlayer(const cClientHandlePtr & a_Client, const AString & a_PlayerName); - - virtual bool Initialize(OwnedEntity a_Self, cWorld & a_World) override; + cPlayer(const cClientHandlePtr & a_Client); virtual ~cPlayer() override; + virtual void OnAddToWorld(cWorld & a_World) override; virtual void OnRemoveFromWorld(cWorld & a_World) override; virtual void SpawnOn(cClientHandle & a_Client) override; @@ -123,6 +122,9 @@ public: // tolua_end + /** Gets the set of IDs for recipes this player has discovered. */ + const std::set & GetKnownRecipes() const; + /** Starts charging the equipped bow */ void StartChargingBow(void); @@ -187,7 +189,7 @@ public: eGameMode GetGameMode(void) const { return m_GameMode; } /** Returns the current effective gamemode (inherited gamemode is resolved before returning) */ - eGameMode GetEffectiveGameMode(void) const { return (m_GameMode == gmNotSet) ? m_World->GetGameMode() : m_GameMode; } + eGameMode GetEffectiveGameMode(void) const; /** Sets the gamemode for the player. The gamemode may be gmNotSet, in that case the player inherits the world's gamemode. @@ -219,7 +221,7 @@ public: /** Returns true if the player can be targeted by Mobs */ bool CanMobsTarget(void) const; - AString GetIP(void) const { return m_IP; } // tolua_export + AString GetIP(void) const; // tolua_export /** Returns the associated team, nullptr if none */ cTeam * GetTeam(void) { return m_Team; } // tolua_export @@ -243,8 +245,6 @@ public: If the achievement has been already awarded to the player, this method will just increment the stat counter. */ void AwardAchievement(Statistic a_Ach); - void SetIP(const AString & a_IP); - /** Forces the player to move in the given direction. @deprecated Use SetSpeed instead. */ void ForceSetSpeed(const Vector3d & a_Speed); // tolua_export @@ -263,7 +263,6 @@ public: /** Closes the current window if it matches the specified ID, resets current window to m_InventoryWindow */ void CloseWindowIfID(char a_WindowID, bool a_CanRefuse = true); - /** Returns the raw client handle associated with the player. */ cClientHandle * GetClientHandle(void) const { return m_ClientHandle.get(); } // tolua_end @@ -275,9 +274,6 @@ public: /** Permute the seed for enchanting related PRNGs, don't use this for other purposes. */ void PermuteEnchantmentSeed(); - /** Returns the SharedPtr to client handle associated with the player. */ - cClientHandlePtr GetClientHandlePtr(void) const { return m_ClientHandle; } - // tolua_begin void SendMessage (const AString & a_Message); @@ -294,8 +290,7 @@ public: void SendSystemMessage (const cCompositeChat & a_Message); void SendAboveActionBarMessage(const cCompositeChat & a_Message); - const AString & GetName(void) const { return m_PlayerName; } - void SetName(const AString & a_Name) { m_PlayerName = a_Name; } + const AString & GetName(void) const; // tolua_end @@ -434,7 +429,7 @@ public: */ bool LoadFromFile(const AString & a_FileName, cWorldPtr & a_World); - const AString & GetLoadedWorldName() { return m_LoadedWorldName; } + const AString & GetLoadedWorldName() const { return m_CurrentWorldName; } /** Opens the inventory of any tame horse the player is riding. If the player is not riding a horse or if the horse is untamed, does nothing. */ @@ -452,13 +447,6 @@ public: equipped item is enchanted. */ void UseItem(int a_SlotNumber, short a_Damage = 1); - void SendHealth(void); - - // Send current active hotbar slot - void SendHotbarActiveSlot(void); - - void SendExperience(void); - /** In UI windows, get the item that the player is dragging */ cItem & GetDraggingItem(void) {return m_DraggingItem; } // tolua_export @@ -598,10 +586,6 @@ public: virtual void AttachTo(cEntity * a_AttachTo) override; virtual void Detach(void) override; - /** Called by cClientHandle when the client is being destroyed. - The player removes its m_ClientHandle ownership so that the ClientHandle gets deleted. */ - void RemoveClientHandle(void); - /** Returns the progress mined per tick for the block a_Block as a fraction (1 would be completely mined) Depends on hardness values so check those are correct. @@ -647,9 +631,6 @@ protected: AString m_MsgPrefix, m_MsgSuffix; AString m_MsgNameColorCode; - AString m_PlayerName; - AString m_LoadedWorldName; - /** Xp Level stuff */ enum { @@ -687,11 +668,18 @@ protected: /** The player's last saved bed position */ Vector3i m_LastBedPos; - /** The world which the player respawns in upon death */ - cWorld * m_SpawnWorld; + /** The name of the world which the player respawns in upon death. + This is stored as a string to enable SaveToDisk to not touch cRoot, and thus can be safely called in the player's destructor. */ + std::string m_SpawnWorldName; + + /** The name of the world which the player currently resides in. + Stored in addition to m_World to allow SaveToDisk to be safely called in the player's destructor. */ + std::string m_CurrentWorldName; + + /** The save path of the default world. */ + std::string m_DefaultWorldPath; eGameMode m_GameMode; - AString m_IP; /** The item being dragged by the cursor while in a UI window */ cItem m_DraggingItem; @@ -738,9 +726,6 @@ protected: int m_CurrentXp; unsigned int m_EnchantmentSeed; - // flag saying we need to send a xp update to client - bool m_bDirtyExperience; - bool m_IsChargingBow; int m_BowCharge; @@ -781,8 +766,6 @@ protected: /** List of known items as Ids */ std::set m_KnownItems; - virtual void DoMoveToWorld(const cEntity::sWorldChangeInfo & a_WorldChangeInfo) override; - /** Sets the speed and sends it to the client, so that they are forced to move so. */ virtual void DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ) override; diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index 52b3f8454..b115b241a 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -598,20 +598,6 @@ bool cMonster::DoTakeDamage(TakeDamageInfo & a_TDI) -void cMonster::DoMoveToWorld(const cEntity::sWorldChangeInfo & a_WorldChangeInfo) -{ - // Stop all mobs from targeting this entity - // Stop this entity from targeting other mobs - SetTarget(nullptr); - StopEveryoneFromTargetingMe(); - - Super::DoMoveToWorld(a_WorldChangeInfo); -} - - - - - void cMonster::KilledBy(TakeDamageInfo & a_TDI) { Super::KilledBy(a_TDI); diff --git a/src/Mobs/Monster.h b/src/Mobs/Monster.h index eb8905ae3..830919c22 100644 --- a/src/Mobs/Monster.h +++ b/src/Mobs/Monster.h @@ -358,8 +358,6 @@ protected: /** Adds weapon that is equipped with the chance saved in m_DropChance[...] (this will be greter than 1 if picked up or 0.085 + (0.01 per LootingLevel) if born with) to the drop */ void AddRandomWeaponDropItem(cItems & a_Drops, unsigned int a_LootingLevel); - virtual void DoMoveToWorld(const cEntity::sWorldChangeInfo & a_WorldChangeInfo) override; - /* The breeding processing */ /** The monster's breeding partner. */ diff --git a/src/Protocol/ChunkDataSerializer.cpp b/src/Protocol/ChunkDataSerializer.cpp index a16cce02b..2c45eea58 100644 --- a/src/Protocol/ChunkDataSerializer.cpp +++ b/src/Protocol/ChunkDataSerializer.cpp @@ -74,7 +74,7 @@ cChunkDataSerializer::cChunkDataSerializer(const eDimension a_Dimension) : void cChunkDataSerializer::SendToClients(const int a_ChunkX, const int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData, const ClientHandles & a_SendTo) { - for (const auto Client : a_SendTo) + for (const auto & Client : a_SendTo) { switch (static_cast(Client->GetProtocolVersion())) { @@ -133,7 +133,7 @@ void cChunkDataSerializer::SendToClients(const int a_ChunkX, const int a_ChunkZ, -inline void cChunkDataSerializer::Serialize(cClientHandle * a_Client, const int a_ChunkX, const int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData, const CacheVersion a_CacheVersion) +inline void cChunkDataSerializer::Serialize(const ClientHandles::value_type & a_Client, const int a_ChunkX, const int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData, const CacheVersion a_CacheVersion) { auto & Cache = m_Cache[static_cast(a_CacheVersion)]; if (Cache.Engaged) diff --git a/src/Protocol/ChunkDataSerializer.h b/src/Protocol/ChunkDataSerializer.h index ea0b0be11..47d92e2ee 100644 --- a/src/Protocol/ChunkDataSerializer.h +++ b/src/Protocol/ChunkDataSerializer.h @@ -21,7 +21,7 @@ Caches the serialized data for as long as this object lives, so that the same da other clients using the same protocol. */ class cChunkDataSerializer { - using ClientHandles = std::unordered_set; + using ClientHandles = std::vector>; /** Enum to collapse protocol versions into a contiguous index. */ enum class CacheVersion @@ -55,7 +55,7 @@ private: /** Serialises the given chunk, storing the result into the given cache entry, and sends the data. If the cache entry is already present, simply re-uses it. */ - inline void Serialize(cClientHandle * a_Client, int a_ChunkX, int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData, CacheVersion a_CacheVersion); + inline void Serialize(const ClientHandles::value_type & a_Client, int a_ChunkX, int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData, CacheVersion a_CacheVersion); inline void Serialize47 (int a_ChunkX, int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData); // Release 1.8 inline void Serialize107(int a_ChunkX, int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData); // Release 1.9 diff --git a/src/Protocol/Protocol_1_8.cpp b/src/Protocol/Protocol_1_8.cpp index 12e2d441c..be855678e 100644 --- a/src/Protocol/Protocol_1_8.cpp +++ b/src/Protocol/Protocol_1_8.cpp @@ -670,8 +670,7 @@ void cProtocol_1_8_0::SendHeldItemChange(int a_ItemIndex) ASSERT((a_ItemIndex >= 0) && (a_ItemIndex <= 8)); // Valid check cPacketizer Pkt(*this, pktHeldItemChange); - cPlayer * Player = m_Client->GetPlayer(); - Pkt.WriteBEInt8(static_cast(Player->GetInventory().GetEquippedSlotNum())); + Pkt.WriteBEInt8(static_cast(a_ItemIndex)); } @@ -1021,15 +1020,11 @@ void cProtocol_1_8_0::SendPlayerListUpdatePing(const cPlayer & a_Player) { ASSERT(m_State == 3); // In game mode? - auto ClientHandle = a_Player.GetClientHandlePtr(); - if (ClientHandle != nullptr) - { - cPacketizer Pkt(*this, pktPlayerList); - Pkt.WriteVarInt32(2); - Pkt.WriteVarInt32(1); - Pkt.WriteUUID(a_Player.GetUUID()); - Pkt.WriteVarInt32(static_cast(ClientHandle->GetPing())); - } + cPacketizer Pkt(*this, pktPlayerList); + Pkt.WriteVarInt32(2); + Pkt.WriteVarInt32(1); + Pkt.WriteUUID(a_Player.GetUUID()); + Pkt.WriteVarInt32(static_cast(a_Player.GetClientHandle()->GetPing())); } diff --git a/src/Root.cpp b/src/Root.cpp index 9ccb18729..57782fbb4 100644 --- a/src/Root.cpp +++ b/src/Root.cpp @@ -500,7 +500,7 @@ void cRoot::QueueExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCall cRoot::Get()->ForEachPlayer( [&](cPlayer & a_Player) { - a_Player.GetClientHandlePtr()->Kick(m_Server->GetShutdownMessage()); + a_Player.GetClientHandle()->Kick(m_Server->GetShutdownMessage()); SentDisconnect = true; return false; } diff --git a/src/Server.cpp b/src/Server.cpp index eb8b3de36..cd44e948f 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -319,7 +319,6 @@ cTCPLink::cCallbacksPtr cServer::OnConnectionAccepted(const AString & a_RemoteIP { LOGD("Client \"%s\" connected!", a_RemoteIPAddress.c_str()); cClientHandlePtr NewHandle = std::make_shared(a_RemoteIPAddress, m_ClientViewDistance); - NewHandle->SetSelf(NewHandle); cCSLock Lock(m_CSClients); m_Clients.push_back(NewHandle); return std::move(NewHandle); @@ -368,14 +367,17 @@ void cServer::TickClients(float a_Dt) // Tick the remaining clients, take out those that have been destroyed into RemoveClients for (auto itr = m_Clients.begin(); itr != m_Clients.end();) { - if ((*itr)->IsDestroyed()) + auto & Client = *itr; + + Client->ServerTick(a_Dt); + if (Client->IsDestroyed()) { // Delete the client later, when CS is not held, to avoid deadlock: https://forum.cuberite.org/thread-374.html - RemoveClients.push_back(*itr); + RemoveClients.push_back(std::move(Client)); itr = m_Clients.erase(itr); continue; } - (*itr)->ServerTick(a_Dt); + ++itr; } // for itr - m_Clients[] } diff --git a/src/StringCompression.h b/src/StringCompression.h index c091c27a0..862cf7a62 100644 --- a/src/StringCompression.h +++ b/src/StringCompression.h @@ -9,8 +9,6 @@ -class cByteBuffer; - struct libdeflate_compressor; struct libdeflate_decompressor; diff --git a/src/World.cpp b/src/World.cpp index 6b224b634..7f6fa0d10 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -234,8 +234,7 @@ cWorld::cWorld( Serializer.Load(); // Track the CSs used by this world in the deadlock detector: - a_DeadlockDetect.TrackCriticalSection(m_CSClients, Printf("World %s clients", m_WorldName.c_str())); - a_DeadlockDetect.TrackCriticalSection(m_CSTasks, Printf("World %s tasks", m_WorldName.c_str())); + a_DeadlockDetect.TrackCriticalSection(m_CSTasks, Printf("World %s tasks", m_WorldName.c_str())); // Load world settings from the ini file cIniFile IniFile; @@ -914,16 +913,6 @@ void cWorld::InitializeAndLoadMobSpawningValues(cIniFile & a_IniFile) void cWorld::Stop(cDeadlockDetect & a_DeadlockDetect) { - // Delete the clients that have been in this world: - { - cCSLock Lock(m_CSClients); - for (auto itr = m_Clients.begin(); itr != m_Clients.end(); ++itr) - { - (*itr)->Destroy(); - } // for itr - m_Clients[] - m_Clients.clear(); - } - // Write settings to file; these are all plugin changeable values - keep updated! cIniFile IniFile; IniFile.ReadFile(m_IniFileName); @@ -951,7 +940,6 @@ void cWorld::Stop(cDeadlockDetect & a_DeadlockDetect) m_ChunkSender.Stop(); m_Storage.Stop(); // Waits for thread to finish - a_DeadlockDetect.UntrackCriticalSection(m_CSClients); a_DeadlockDetect.UntrackCriticalSection(m_CSTasks); m_ChunkMap.UntrackInDeadlockDetect(a_DeadlockDetect); @@ -1009,26 +997,10 @@ void cWorld::Tick(std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_La } } - // Add entities waiting in the queue to be added: - cEntityList EntitiesToAdd; - { - // Don't access chunkmap while holding lock - cCSLock Lock(m_CSEntitiesToAdd); - std::swap(EntitiesToAdd, m_EntitiesToAdd); - } - for (auto & Entity : EntitiesToAdd) - { - m_ChunkMap.AddEntity(std::move(Entity)); - } - EntitiesToAdd.clear(); - - // Add players waiting in the queue to be added: - AddQueuedPlayers(); - - TickClients(static_cast(a_Dt.count())); TickQueuedBlocks(); - m_ChunkMap.Tick(a_Dt); // Tick chunk after clients to apply at least one round of queued ticks (e.g. cBlockHandler::Check) this tick + m_ChunkMap.Tick(a_Dt); TickMobs(a_Dt); + TickQueuedEntityAdditions(); m_MapManager.TickMaps(); TickQueuedTasks(); @@ -1179,6 +1151,45 @@ void cWorld::TickMobs(std::chrono::milliseconds a_Dt) +void cWorld::TickQueuedEntityAdditions(void) +{ + decltype(m_EntitiesToAdd) EntitiesToAdd; + { + cCSLock Lock(m_CSEntitiesToAdd); + EntitiesToAdd = std::move(m_EntitiesToAdd); + } + + // Ensures m_Players manipulation happens under the chunkmap lock. + cLock Lock(*this); + + // Add entities waiting in the queue to be added: + for (auto & Item: EntitiesToAdd) + { + const auto Entity = Item.first.get(); + + if (Entity->IsPlayer()) + { + const auto Player = static_cast(Entity); + + LOGD("Adding player %s to world \"%s\".", Player->GetName().c_str(), m_WorldName.c_str()); + ASSERT(std::find(m_Players.begin(), m_Players.end(), Player) == m_Players.end()); // Is it already in the list? HOW? + + m_Players.push_back(Player); + } + + m_ChunkMap.AddEntity(std::move(Item.first)); + + if (const auto OldWorld = Item.second; OldWorld != nullptr) + { + cRoot::Get()->GetPluginManager()->CallHookEntityChangedWorld(*Entity, *OldWorld); + } + } +} + + + + + void cWorld::TickQueuedTasks(void) { // Move the tasks to be executed to a seperate vector to avoid deadlocks on accessing m_Tasks @@ -1218,57 +1229,6 @@ void cWorld::TickQueuedTasks(void) -void cWorld::TickClients(float a_Dt) -{ - cClientHandlePtrs RemoveClients; - { - cCSLock Lock(m_CSClients); - - // Remove clients scheduled for removal: - for (auto itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr) - { - for (auto itrC = m_Clients.begin(), endC = m_Clients.end(); itrC != endC; ++itrC) - { - if (itrC->get() == *itr) - { - m_Clients.erase(itrC); - break; - } - } - } // for itr - m_ClientsToRemove[] - m_ClientsToRemove.clear(); - - // Add clients scheduled for adding: - for (auto itr = m_ClientsToAdd.begin(), end = m_ClientsToAdd.end(); itr != end; ++itr) - { - ASSERT(std::find(m_Clients.begin(), m_Clients.end(), *itr) == m_Clients.end()); - m_Clients.push_back(*itr); - } // for itr - m_ClientsToRemove[] - m_ClientsToAdd.clear(); - - // Tick the clients, take out those that have been destroyed into RemoveClients - for (auto itr = m_Clients.begin(); itr != m_Clients.end();) - { - if ((*itr)->IsDestroyed()) - { - // Remove the client later, when CS is not held, to avoid deadlock - RemoveClients.push_back(*itr); - itr = m_Clients.erase(itr); - continue; - } - (*itr)->Tick(a_Dt); - ++itr; - } // for itr - m_Clients[] - } - - // Delete the clients queued for removal: - RemoveClients.clear(); -} - - - - - void cWorld::UpdateSkyDarkness(void) { int TempTime = std::chrono::duration_cast(m_TimeOfDay).count(); @@ -2421,123 +2381,6 @@ void cWorld::CollectPickupsByPlayer(cPlayer & a_Player) -void cWorld::AddPlayer(std::unique_ptr a_Player, cWorld * a_OldWorld) -{ - cCSLock Lock(m_CSPlayersToAdd); - m_PlayersToAdd.emplace_back(std::move(a_Player), a_OldWorld); -} - - - - - -std::unique_ptr cWorld::RemovePlayer(cPlayer & a_Player) -{ - // Check the chunkmap - std::unique_ptr PlayerPtr(static_cast(m_ChunkMap.RemoveEntity(a_Player).release())); - - if (PlayerPtr != nullptr) - { - // Player found in the world, tell it it's being removed - PlayerPtr->OnRemoveFromWorld(*this); - } - else // Check the awaiting players list - { - cCSLock Lock(m_CSPlayersToAdd); - auto itr = std::find_if(m_PlayersToAdd.begin(), m_PlayersToAdd.end(), - [&](const decltype(m_PlayersToAdd)::value_type & value) - { - return (value.first.get() == &a_Player); - } - ); - - if (itr != m_PlayersToAdd.end()) - { - PlayerPtr = std::move(itr->first); - m_PlayersToAdd.erase(itr); - } - } - - // Remove from the player list - { - cLock Lock(*this); - LOGD("Removing player %s from world \"%s\"", a_Player.GetName().c_str(), m_WorldName.c_str()); - m_Players.remove(&a_Player); - } - - // Remove the player's client from the list of clients to be ticked: - cClientHandle * Client = a_Player.GetClientHandle(); - if (Client != nullptr) - { - Client->RemoveFromWorld(); - m_ChunkMap.RemoveClientFromChunks(Client); - cCSLock Lock(m_CSClients); - m_ClientsToRemove.push_back(Client); - } - - return PlayerPtr; -} - - - - - -#ifdef _DEBUG -bool cWorld::IsPlayerReferencedInWorldOrChunk(cPlayer & a_Player) -{ - { - cLock lock(*this); - auto * Chunk = a_Player.GetParentChunk(); - if (Chunk && Chunk->HasEntity(a_Player.GetUniqueID())) - { - return true; - } - } - - { - cCSLock Lock(m_CSPlayersToAdd); - if (std::find_if( - m_PlayersToAdd.begin(), m_PlayersToAdd.end(), - [&a_Player](const cAwaitingPlayerList::value_type & Item) { return Item.first.get() == &a_Player; }) != m_PlayersToAdd.end() - ) - { - return true; - } - } - - { - cLock Lock(*this); - if (std::find(m_Players.begin(), m_Players.end(), &a_Player) != m_Players.end()) - { - return true; - } - } - - { - cCSLock Lock(m_CSEntitiesToAdd); - if (std::find(m_ClientsToAdd.begin(), m_ClientsToAdd.end(), a_Player.GetClientHandlePtr()) != m_ClientsToAdd.end()) - { - return true; - } - } - - { - cCSLock Lock(m_CSClients); - if (std::find(m_Clients.begin(), m_Clients.end(), a_Player.GetClientHandlePtr()) != m_Clients.end()) - { - return true; - } - } - - // Assume OK if in ClientsToRemove or PlayersToRemove - return false; -} -#endif - - - - - bool cWorld::ForEachPlayer(cPlayerListCallback a_Callback) { // Calls the callback for each player in the list @@ -2686,12 +2529,11 @@ void cWorld::SendPlayerList(cPlayer * a_DestPlayer) { // Sends the playerlist to a_DestPlayer cLock Lock(*this); - for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + for (const auto & Player : m_Players) { - cClientHandle * ch = (*itr)->GetClientHandle(); - if ((ch != nullptr) && !ch->IsDestroyed()) + if (!Player->GetClientHandle()->IsDestroyed()) { - a_DestPlayer->GetClientHandle()->SendPlayerListAddPlayer(*(*itr)); + a_DestPlayer->GetClientHandle()->SendPlayerListAddPlayer(*Player); } } } @@ -2732,11 +2574,11 @@ bool cWorld::DoWithEntityByID(UInt32 a_UniqueID, cEntityCallback a_Callback) // First check the entities-to-add: { cCSLock Lock(m_CSEntitiesToAdd); - for (const auto & ent: m_EntitiesToAdd) + for (const auto & Item : m_EntitiesToAdd) { - if (ent->GetUniqueID() == a_UniqueID) + if (Item.first->GetUniqueID() == a_UniqueID) { - a_Callback(*ent); + a_Callback(*Item.first); return true; } } // for ent - m_EntitiesToAdd[] @@ -2805,15 +2647,6 @@ void cWorld::ForceSendChunkTo(int a_ChunkX, int a_ChunkZ, cChunkSender::Priority -void cWorld::RemoveClientFromChunkSender(cClientHandle * a_Client) -{ - m_ChunkSender.RemoveClient(a_Client); -} - - - - - void cWorld::PrepareChunk(int a_ChunkX, int a_ChunkZ, std::unique_ptr a_CallAfter) { m_ChunkMap.PrepareChunk(a_ChunkX, a_ChunkZ, std::move(a_CallAfter)); @@ -3014,10 +2847,10 @@ void cWorld::ScheduleTask(int a_DelayTicks, std::function a_Tas -void cWorld::AddEntity(OwnedEntity a_Entity) +void cWorld::AddEntity(OwnedEntity a_Entity, cWorld * a_OldWorld) { cCSLock Lock(m_CSEntitiesToAdd); - m_EntitiesToAdd.emplace_back(std::move(a_Entity)); + m_EntitiesToAdd.emplace_back(std::move(a_Entity), a_OldWorld); } @@ -3026,6 +2859,15 @@ void cWorld::AddEntity(OwnedEntity a_Entity) OwnedEntity cWorld::RemoveEntity(cEntity & a_Entity) { + // Remove players from the player list: + if (a_Entity.IsPlayer()) + { + cLock Lock(*this); + const auto Player = static_cast(&a_Entity); + LOGD("Removing player %s from world \"%s\"", Player->GetName().c_str(), m_WorldName.c_str()); + m_Players.remove(Player); + } + // Check if the entity is in the chunkmap: auto Entity = m_ChunkMap.RemoveEntity(a_Entity); if (Entity != nullptr) @@ -3037,17 +2879,18 @@ OwnedEntity cWorld::RemoveEntity(cEntity & a_Entity) // Check if the entity is in the queue to be added to the world: cCSLock Lock(m_CSEntitiesToAdd); auto itr = std::find_if(m_EntitiesToAdd.begin(), m_EntitiesToAdd.end(), - [&a_Entity](const OwnedEntity & a_OwnedEntity) + [&a_Entity](const auto & Item) { - return (a_OwnedEntity.get() == &a_Entity); + return (Item.first.get() == &a_Entity); } ); if (itr != m_EntitiesToAdd.end()) { - Entity = std::move(*itr); + Entity = std::move(itr->first); m_EntitiesToAdd.erase(itr); } + return Entity; } @@ -3400,90 +3243,6 @@ cFluidSimulator * cWorld::InitializeFluidSimulator(cIniFile & a_IniFile, const c -void cWorld::AddQueuedPlayers(void) -{ - ASSERT(m_TickThread.IsCurrentThread()); - - // Grab the list of players to add, it has to be locked to access it: - cAwaitingPlayerList PlayersToAdd; - { - cCSLock Lock(m_CSPlayersToAdd); - std::swap(PlayersToAdd, m_PlayersToAdd); - } - - // Temporary (#3115-will-fix): store pointers to player objects after ownership transferral - std::vector> AddedPlayerPtrs; - AddedPlayerPtrs.reserve(PlayersToAdd.size()); - - // Add all the players in the grabbed list: - { - cLock Lock(*this); - for (auto & AwaitingPlayer : PlayersToAdd) - { - auto & Player = AwaitingPlayer.first; - ASSERT(std::find(m_Players.begin(), m_Players.end(), Player.get()) == m_Players.end()); // Is it already in the list? HOW? - LOGD("Adding player %s to world \"%s\".", Player->GetName().c_str(), m_WorldName.c_str()); - - m_Players.push_back(Player.get()); - Player->SetWorld(this); - - // Add to chunkmap, if not already there (Spawn vs MoveToWorld): - auto PlayerPtr = Player.get(); - m_ChunkMap.AddPlayer(std::move(Player)); - PlayerPtr->OnAddToWorld(*this); - ASSERT(!PlayerPtr->IsTicking()); - PlayerPtr->SetIsTicking(true); - AddedPlayerPtrs.emplace_back(PlayerPtr, AwaitingPlayer.second); - } // for itr - PlayersToAdd[] - } // cLock(*this) - - // Add all the players' clienthandles: - { - cCSLock Lock(m_CSClients); - for (auto & AwaitingPlayer : AddedPlayerPtrs) - { - auto & Player = AwaitingPlayer.first; - cClientHandlePtr Client = Player->GetClientHandlePtr(); - if (Client != nullptr) - { - m_Clients.push_back(Client); - } - } // for itr - PlayersToAdd[] - } // Lock(m_CSClients) - - // Stream chunks to all eligible clients: - for (auto & AwaitingPlayer : AddedPlayerPtrs) - { - auto & Player = AwaitingPlayer.first; - cClientHandle * Client = Player->GetClientHandle(); - if (Client != nullptr) - { - Client->SendHealth(); - Client->SendWholeInventory(*Player->GetWindow()); - - // Send resource pack - auto ResourcePackUrl = cRoot::Get()->GetServer()->GetResourcePackUrl(); - if (!ResourcePackUrl.empty()) - { - Client->SendResourcePack(ResourcePackUrl); - } - } - } // for itr - PlayersToAdd[] - - // Call EntityChangedWorld callback on all eligible clients - for (auto & AwaitingPlayer : AddedPlayerPtrs) - { - if (AwaitingPlayer.second != nullptr) - { - cRoot::Get()->GetPluginManager()->CallHookEntityChangedWorld(*(static_cast (AwaitingPlayer.first)), *AwaitingPlayer.second); - } - } -} - - - - - //////////////////////////////////////////////////////////////////////////////// // cWorld::cChunkGeneratorCallbacks: diff --git a/src/World.h b/src/World.h index ba4e5ee58..2fc5040b8 100644 --- a/src/World.h +++ b/src/World.h @@ -54,7 +54,6 @@ class cDeadlockDetect; class cUUID; typedef std::list< cPlayer * > cPlayerList; -typedef std::list< std::pair< std::unique_ptr, cWorld * > > cAwaitingPlayerList; typedef std::unique_ptr cSetChunkDataPtr; typedef std::vector cSetChunkDataPtrs; @@ -269,22 +268,6 @@ public: void CollectPickupsByPlayer(cPlayer & a_Player); - /** Adds the player to the world. - Uses a queue to store the player object until the Tick thread processes the addition event. - Also adds the player as an entity in the chunkmap, and the player's ClientHandle, if any, for ticking. - If a_OldWorld is provided, a corresponding ENTITY_CHANGED_WORLD event is triggerred after the addition. */ - void AddPlayer(std::unique_ptr a_Player, cWorld * a_OldWorld = nullptr); - - /** Removes the player from the world. - Removes the player from the addition queue, too, if appropriate. - If the player has a ClientHandle, the ClientHandle is removed from all chunks in the world and will not be ticked by this world anymore. - @return An owning reference to the given player. */ - std::unique_ptr RemovePlayer(cPlayer & a_Player); - -#ifdef _DEBUG - bool IsPlayerReferencedInWorldOrChunk(cPlayer & a_Player); -#endif - /** Calls the callback for each player in the list; returns true if all players processed, false if the callback aborted by returning true */ virtual bool ForEachPlayer(cPlayerListCallback a_Callback) override; // >> EXPORTED IN MANUALBINDINGS << @@ -304,8 +287,9 @@ public: void SendPlayerList(cPlayer * a_DestPlayer); // Sends playerlist to the player /** Adds the entity into its appropriate chunk; takes ownership of the entity ptr. - The entity is added lazily - this function only puts it in a queue that is then processed by the Tick thread. */ - void AddEntity(OwnedEntity a_Entity); + The entity is added lazily - this function only puts it in a queue that is then processed by the Tick thread. + If a_OldWorld is provided, a corresponding ENTITY_CHANGED_WORLD event is triggerred after the addition. */ + void AddEntity(OwnedEntity a_Entity, cWorld * a_OldWorld = nullptr); /** Removes the entity from the world. Returns an owning reference to the found entity. */ @@ -346,9 +330,6 @@ public: If the chunk's not valid, the request is postponed (ChunkSender will send that chunk when it becomes valid + lighted). */ void ForceSendChunkTo(int a_ChunkX, int a_ChunkZ, cChunkSender::Priority a_Priority, cClientHandle * a_Client); - /** Removes client from ChunkSender's queue of chunks to be sent */ - void RemoveClientFromChunkSender(cClientHandle * a_Client); - /** Queues the chunk for preparing - making sure that it's generated and lit. The specified chunk is queued to be loaded or generated, and lit if needed. The specified callback is called after the chunk has been prepared. If there's no preparation to do, only the callback is called. @@ -1271,29 +1252,11 @@ private: /** Tasks that have been queued onto the tick thread, possibly to be executed at target tick in the future; guarded by m_CSTasks */ std::vector>> m_Tasks; - /** Guards m_Clients */ - cCriticalSection m_CSClients; - - /** List of clients in this world, these will be ticked by this world */ - cClientHandlePtrs m_Clients; - - /** Clients that are scheduled for removal (ticked in another world), waiting for TickClients() to remove them */ - cClientHandles m_ClientsToRemove; - - /** Clients that are scheduled for adding, waiting for TickClients to add them */ - cClientHandlePtrs m_ClientsToAdd; - /** Guards m_EntitiesToAdd */ cCriticalSection m_CSEntitiesToAdd; /** List of entities that are scheduled for adding, waiting for the Tick thread to add them. */ - cEntityList m_EntitiesToAdd; - - /** Guards m_PlayersToAdd */ - cCriticalSection m_CSPlayersToAdd; - - /** List of players that are scheduled for adding, waiting for the Tick thread to add them. */ - cAwaitingPlayerList m_PlayersToAdd; + std::vector> m_EntitiesToAdd; /** CS protecting m_SetChunkDataQueue. */ cCriticalSection m_CSSetChunkDataQueue; @@ -1309,12 +1272,13 @@ private: /** Handles the mob spawning / moving / destroying each tick */ void TickMobs(std::chrono::milliseconds a_Dt); + /** Adds the entities queued in the m_EntitiesToAdd queue into their chunk. + If the entity was a player, he is also added to the m_Players list. */ + void TickQueuedEntityAdditions(void); + /** Executes all tasks queued onto the tick thread */ void TickQueuedTasks(void); - /** Ticks all clients that are in this world */ - void TickClients(float a_Dt); - /** Unloads all chunks immediately. */ void UnloadUnusedChunks(void); @@ -1339,10 +1303,6 @@ private: /** Creates a new redstone simulator. */ cRedstoneSimulator * InitializeRedstoneSimulator(cIniFile & a_IniFile); - /** Adds the players queued in the m_PlayersToAdd queue into the m_Players list. - Assumes it is called from the Tick thread. */ - void AddQueuedPlayers(void); - /** Sets mob spawning values if nonexistant to their dimension specific defaults */ void InitializeAndLoadMobSpawningValues(cIniFile & a_IniFile); -- cgit v1.2.3