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/Entities/Entity.cpp | 86 ++------- src/Entities/Entity.h | 12 +- src/Entities/Player.cpp | 458 ++++++++++++++++++++++-------------------------- src/Entities/Player.h | 55 ++---- 4 files changed, 244 insertions(+), 367 deletions(-) (limited to 'src/Entities') 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; -- cgit v1.2.3