From be3111d133cf01fe266d1e267acdb9f22f98f513 Mon Sep 17 00:00:00 2001 From: JK2K Date: Sat, 2 Oct 2021 22:28:24 +0200 Subject: [PATCH] Perform bed checks upon respawn (#5300) Co-authored-by: Tiger Wang --- Server/Plugins/APIDump/APIDesc.lua | 28 +++++++++++++-- Server/Plugins/Core | 2 +- src/Entities/Entity.cpp | 2 +- src/Entities/Player.cpp | 56 +++++++++++++++++++++++------- src/Entities/Player.h | 36 ++++++++++++------- 5 files changed, 95 insertions(+), 29 deletions(-) diff --git a/Server/Plugins/APIDump/APIDesc.lua b/Server/Plugins/APIDump/APIDesc.lua index 2f1d66648..db06612d9 100644 --- a/Server/Plugins/APIDump/APIDesc.lua +++ b/Server/Plugins/APIDump/APIDesc.lua @@ -10463,7 +10463,7 @@ a_Player:OpenWindow(Window); Type = "Vector3i", }, }, - Notes = "Returns the position of the last bed the player has slept in, or the world's spawn if no such position was recorded.", + Notes = "Returns the player's respawn position. The player is guaranteed to respawn from death here if {{cPlayer}}:IsRespawnPointForced is true or if a bed exists at this position.", }, GetMaxSpeed = { @@ -10827,6 +10827,15 @@ a_Player:OpenWindow(Window); }, Notes = "Returns true if the player's left hand is dominant.", }, + IsRespawnPointForced = { + Returns = + { + { + Type = "boolean", + }, + }, + Notes = "Returns true if the player unconditionally respawns from death at the position given by {{cPlayer}}:GetLastBedPos with no bed checks performed.", + }, IsSatiated = { Returns = @@ -11098,7 +11107,7 @@ a_Player:OpenWindow(Window); IsOptional = true, }, }, - Notes = "Sets the position and world of the player's respawn point, which is also known as the bed position. The player will respawn at this position and world upon death. If the world is not specified, it is set to the player's current world.", + Notes = "Sets the position and world of the player's bed. If the world is not specified, it is set to the player's current world. The player will respawn at this position and world upon death if there is a bed there.", }, SetCanFly = { @@ -11298,6 +11307,21 @@ a_Player:OpenWindow(Window); }, Notes = "Sets the normal (walking) maximum speed, relative to the game default speed. The default value is 1. Sends the updated speed to the client, if appropriate.", }, + SetRespawnPosition = + { + Params = + { + { + Name = "Position", + Type = "Vector3i", + }, + { + Name = "World", + Type = "cWorld", + }, + }, + Notes = "Sets the position and world of the player's respawn point. The player will respawn at this position and world upon death.", + }, SetSprint = { Params = diff --git a/Server/Plugins/Core b/Server/Plugins/Core index 189690e30..9913cece5 160000 --- a/Server/Plugins/Core +++ b/Server/Plugins/Core @@ -1 +1 @@ -Subproject commit 189690e30d3c9175843d5ed27c447b8c12054f3e +Subproject commit 9913cece597eed01177086a620ad304a68a4693c diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index 74b184c54..e275d03da 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -1500,7 +1500,7 @@ bool cEntity::DetectPortal() if (IsPlayer()) { cPlayer * Player = static_cast(this); - if (Player->GetBedWorld() == TargetWorld) + if (Player->GetRespawnWorld() == TargetWorld) { return MoveToWorld(*TargetWorld, Player->GetLastBedPos()); } diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index b4d806c4f..b99c0227a 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -786,8 +786,7 @@ void cPlayer::SetCustomName(const AString & a_CustomName) void cPlayer::SetBedPos(const Vector3i a_Position) { - m_LastBedPos = a_Position; - m_SpawnWorldName = m_World->GetName(); + SetBedPos(a_Position, *m_World); } @@ -796,7 +795,8 @@ void cPlayer::SetBedPos(const Vector3i a_Position) void cPlayer::SetBedPos(const Vector3i a_Position, const cWorld & a_World) { - m_LastBedPos = a_Position; + m_RespawnPosition = a_Position; + m_IsRespawnPointForced = false; m_SpawnWorldName = a_World.GetName(); } @@ -804,7 +804,18 @@ void cPlayer::SetBedPos(const Vector3i a_Position, const cWorld & a_World) -cWorld * cPlayer::GetBedWorld() +void cPlayer::SetRespawnPosition(const Vector3i a_Position, const cWorld & a_World) +{ + m_RespawnPosition = a_Position; + m_IsRespawnPointForced = true; + m_SpawnWorldName = a_World.GetName(); +} + + + + + +cWorld * cPlayer::GetRespawnWorld() { if (const auto World = cRoot::Get()->GetWorld(m_SpawnWorldName); World != nullptr) { @@ -943,14 +954,33 @@ void cPlayer::Respawn(void) // Extinguish the fire: StopBurning(); - if (const auto BedWorld = GetBedWorld(); m_World != BedWorld) + // Disable flying: + SetFlying(false); + + if (!m_IsRespawnPointForced) { - MoveToWorld(*BedWorld, GetLastBedPos(), false, false); + // Check if the bed is still present: + if (GetRespawnWorld()->GetBlock(m_RespawnPosition) != E_BLOCK_BED) + { + const auto & DefaultWorld = *cRoot::Get()->GetDefaultWorld(); + + // If not, reset spawn to default and inform: + SetRespawnPosition(Vector3d(DefaultWorld.GetSpawnX(), DefaultWorld.GetSpawnY(), DefaultWorld.GetSpawnZ()), DefaultWorld); + SendAboveActionBarMessage("Your home bed was missing or obstructed"); + } + + // TODO: bed obstruction check here + } + + + if (const auto RespawnWorld = GetRespawnWorld(); m_World != RespawnWorld) + { + MoveToWorld(*RespawnWorld, m_RespawnPosition, false, false); } else { m_ClientHandle->SendRespawn(m_World->GetDimension(), true); - TeleportToCoords(GetLastBedPos().x, GetLastBedPos().y, GetLastBedPos().z); + TeleportToCoords(m_RespawnPosition.x, m_RespawnPosition.y, m_RespawnPosition.z); } SetVisible(true); @@ -1785,7 +1815,7 @@ void cPlayer::LoadFromDisk() const Vector3i WorldSpawn(static_cast(m_World->GetSpawnX()), static_cast(m_World->GetSpawnY()), static_cast(m_World->GetSpawnZ())); SetPosition(WorldSpawn); - SetBedPos(WorldSpawn, *m_World); + SetRespawnPosition(WorldSpawn, *m_World); m_Inventory.Clear(); m_EnchantmentSeed = GetRandomProvider().RandInt(); // Use a random number to seed the enchantment generator @@ -1892,9 +1922,10 @@ bool cPlayer::LoadFromFile(const AString & a_FileName) m_World = cRoot::Get()->GetDefaultWorld(); } - m_LastBedPos.x = Root.get("SpawnX", m_World->GetSpawnX()).asInt(); - m_LastBedPos.y = Root.get("SpawnY", m_World->GetSpawnY()).asInt(); - m_LastBedPos.z = Root.get("SpawnZ", m_World->GetSpawnZ()).asInt(); + m_RespawnPosition.x = Root.get("SpawnX", m_World->GetSpawnX()).asInt(); + m_RespawnPosition.y = Root.get("SpawnY", m_World->GetSpawnY()).asInt(); + m_RespawnPosition.z = Root.get("SpawnZ", m_World->GetSpawnZ()).asInt(); + m_IsRespawnPointForced = Root.get("SpawnForced", true).asBool(); m_SpawnWorldName = Root.get("SpawnWorld", cRoot::Get()->GetDefaultWorld()->GetName()).asString(); try @@ -2006,6 +2037,7 @@ void cPlayer::SaveToDisk() root["SpawnX"] = GetLastBedPos().x; root["SpawnY"] = GetLastBedPos().y; root["SpawnZ"] = GetLastBedPos().z; + root["SpawnForced"] = m_IsRespawnPointForced; root["SpawnWorld"] = m_SpawnWorldName; root["enchantmentSeed"] = m_EnchantmentSeed; root["world"] = m_CurrentWorldName; @@ -3236,7 +3268,7 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) else if (IsInBed()) { // Check if sleeping is still possible: - if ((GetPosition().Floor() != m_LastBedPos) || (m_World->GetBlock(m_LastBedPos) != E_BLOCK_BED)) + if ((GetPosition().Floor() != m_RespawnPosition) || (m_World->GetBlock(m_RespawnPosition) != E_BLOCK_BED)) { m_ClientHandle->HandleLeaveBed(); } diff --git a/src/Entities/Player.h b/src/Entities/Player.h index d71fedec5..d1dfffa0b 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -513,22 +513,28 @@ public: The custom name will be used in the tab-list, in the player nametag and in the tab-completion. */ void SetCustomName(const AString & a_CustomName); - /** Gets the last position that the player slept in - This is initialised to the world spawn point if the player has not slept in a bed as of yet - */ - Vector3i GetLastBedPos(void) const { return m_LastBedPos; } + /** Gets the player's potential respawn position (named LastBedPos for compatibility reasons). */ + Vector3i GetLastBedPos(void) const { return m_RespawnPosition; } - /** Sets the player's bed (home / respawn) position to the specified position. - Sets the respawn world to the player's world. */ - void SetBedPos(const Vector3i a_Position); + /** Returns if the respawn point is unconditionally used. */ + bool IsRespawnPointForced(void) const { return m_IsRespawnPointForced; } - /** Sets the player's bed (home / respawn) position and respawn world to the specified parameters. */ - void SetBedPos(const Vector3i a_Position, const cWorld & a_World); + /** Sets the player's bed position to the specified position. + Sets the respawn world to the player's world and unforces the respawn point. + The given position will be used subject to bed checks when respawning. */ + void SetBedPos(Vector3i a_Position); + + /** Sets the player's bed position to the specified position. + The spawn point is unforced. The given position will be used subject to bed checks when respawning. */ + void SetBedPos(Vector3i a_Position, const cWorld & a_World); + + /** Sets the player's forced respawn position and world. */ + void SetRespawnPosition(Vector3i a_Position, const cWorld & a_World); // tolua_end - // TODO lua export GetBedPos and GetBedWorld - cWorld * GetBedWorld(); + // TODO lua export GetRespawnWorld + cWorld * GetRespawnWorld(); /** Update movement-related statistics. */ void UpdateMovementStats(const Vector3d & a_DeltaPos, bool a_PreviousIsOnGround); @@ -652,8 +658,9 @@ private: cWindow * m_CurrentWindow; cWindow * m_InventoryWindow; - /** The player's last saved bed position */ - Vector3i m_LastBedPos; + /** The player's potential respawn position, initialised to world spawn by default. + During player respawn from death, if m_IsRespawnPointForced is false and no bed exists here, it will be reset to world spawn. */ + Vector3i m_RespawnPosition; /** 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. */ @@ -707,6 +714,9 @@ private: /** Was the player frozen manually by a plugin or automatically by the server? */ bool m_IsManuallyFrozen; + /** Whether we unconditionally respawn to m_RespawnPosition, or check if a bed is unobstructed and available first. */ + bool m_IsRespawnPointForced; + /** Flag used by food handling system to determine whether a teleport has just happened. Will not apply food penalties if found to be true; will set to false after processing. */ bool m_IsTeleporting;