diff options
Diffstat (limited to '')
-rw-r--r-- | src/Entities/Entity.cpp | 86 | ||||
-rw-r--r-- | src/Entities/Entity.h | 12 | ||||
-rw-r--r-- | src/Entities/Player.cpp | 458 | ||||
-rw-r--r-- | src/Entities/Player.h | 55 |
4 files changed, 244 insertions, 367 deletions
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<cPlayer *>(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<cPlayer *>(this)->AwardAchievement(Statistic::AchPortal); - } - - static_cast<cPlayer *>(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<cPlayer *>(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<cPlayer *>(this)->AwardAchievement(Statistic::AchTheEnd); - } - static_cast<cPlayer *>(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<int>(World->GetSpawnX()), static_cast<int>(World->GetSpawnY()), static_cast<int>(World->GetSpawnZ())), World); - SetWorld(World); // Use default world - m_EnchantmentSeed = GetRandomProvider().RandInt<unsigned int>(); // 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<float>(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<cPlayer>(static_cast<cPlayer *>(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<void *>(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<void *>(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<UInt32> & 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<int>(eGameMode_NotSet); - } - else - { - root["gamemode"] = static_cast<int>(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<int>(eGameMode_NotSet); - } + root["world"] = m_CurrentWorldName; + root["gamemode"] = static_cast<int>(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<UInt32> & 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<cItem, cItem::sItemCompare> 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; |