From 2fc86476ae04ea2f11a41215f783cdbc5d924579 Mon Sep 17 00:00:00 2001 From: Tiger Wang Date: Fri, 9 Apr 2021 23:18:09 +0100 Subject: Improve bed handling robustness + Boot the player out if the bed was destroyed --- src/Blocks/BlockBed.cpp | 109 ++++++++++++++++++++++-------------------------- src/Blocks/BlockBed.h | 45 ++++++++++++-------- src/ClientHandle.cpp | 3 +- src/Entities/Player.cpp | 11 ++++- 4 files changed, 88 insertions(+), 80 deletions(-) diff --git a/src/Blocks/BlockBed.cpp b/src/Blocks/BlockBed.cpp index 4fe22dffd..68f45b518 100644 --- a/src/Blocks/BlockBed.cpp +++ b/src/Blocks/BlockBed.cpp @@ -60,11 +60,13 @@ bool cBlockBedHandler::OnUse( cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer & a_Player, - const Vector3i a_BlockPos, + Vector3i a_BlockPos, eBlockFace a_BlockFace, const Vector3i a_CursorPos ) const { + // Source: https://minecraft.gamepedia.com/Bed#Sleeping + // Sleeping in bed only allowed in Overworld, beds explode elsewhere: if (a_WorldInterface.GetDimension() != dimOverworld) { @@ -73,82 +75,82 @@ bool cBlockBedHandler::OnUse( return true; } + auto Meta = a_ChunkInterface.GetBlockMeta(a_BlockPos); + + if ((Meta & 0x8) == 0) + { + // Clicked on the foot of the bed, adjust to the head: + a_BlockPos += MetaDataToDirection(Meta & 0x03); + + BLOCKTYPE Type; + a_ChunkInterface.GetBlockTypeMeta(a_BlockPos, Type, Meta); + if (Type != E_BLOCK_BED) + { + // Bed was incomplete, bail: + return true; + } + } + + // Set the bed position to the pillow block: + a_Player.SetBedPos(a_BlockPos); + // Sleeping is allowed only during night and thunderstorms: if ( !(((a_WorldInterface.GetTimeOfDay() > 12541_tick) && (a_WorldInterface.GetTimeOfDay() < 23458_tick)) || (a_Player.GetWorld()->GetWeather() == wThunderstorm)) - ) // Source: https://minecraft.gamepedia.com/Bed#Sleeping + ) { a_Player.SendAboveActionBarMessage("You can only sleep at night and during thunderstorms"); - - // Try to set home position anyway: - SetBedPos(a_Player, a_BlockPos); return true; } // Check if the bed is occupied: - auto Meta = a_ChunkInterface.GetBlockMeta(a_BlockPos); if ((Meta & 0x04) == 0x04) { - a_Player.SendMessageFailure("This bed is occupied"); + a_Player.SendAboveActionBarMessage("This bed is occupied"); return true; } // Cannot sleep if there are hostile mobs nearby: - auto FindMobs = [](cEntity & a_Entity) - { - return ( - (a_Entity.GetEntityType() == cEntity::etMonster) && - (static_cast(a_Entity).GetMobFamily() == cMonster::mfHostile) - ); - }; - if (!a_Player.GetWorld()->ForEachEntityInBox(cBoundingBox(a_Player.GetPosition() - Vector3i(0, 5, 0), 8, 10), FindMobs)) + if ( + !a_Player.GetWorld()->ForEachEntityInBox({ a_Player.GetPosition().addedY(-5), 8, 10 }, [](cEntity & a_Entity) + { + return a_Entity.IsMob() && (static_cast(a_Entity).GetMobFamily() == cMonster::mfHostile); + }) + ) { a_Player.SendAboveActionBarMessage("You may not rest now, there are monsters nearby"); return true; } - // Broadcast the "Use bed" for the pillow block: - if ((Meta & 0x8) == 0x8) - { - // Is pillow - a_WorldInterface.GetBroadcastManager().BroadcastUseBed(a_Player, a_BlockPos); - } - else - { - // Is foot end - VERIFY((Meta & 0x04) != 0x04); // Occupied flag should never be set, else our compilator (intended) is broken + // This will broadcast "use bed" for the pillow block, if the player can sleep: + a_Player.SetIsInBed(true); - auto PillowPos = a_BlockPos + MetaDataToDirection(Meta & 0x03); - if (a_ChunkInterface.GetBlock(PillowPos) == E_BLOCK_BED) // Must always use pillow location for sleeping - { - a_WorldInterface.GetBroadcastManager().BroadcastUseBed(a_Player, PillowPos); - } + // Check sleeping was successful, if not, bail: + if (!a_Player.IsInBed()) + { + return true; } - // Occupy the bed: - SetBedPos(a_Player, a_BlockPos); - SetBedOccupationState(a_ChunkInterface, a_Player.GetLastBedPos(), true); - a_Player.SetIsInBed(true); + // Occupy the bed, where 0x4 = occupied bit: + a_ChunkInterface.SetBlockMeta(a_BlockPos, Meta | 0x04); a_Player.GetStatManager().AddValue(Statistic::SleepInBed); + // When sleeping, the player's bounding box moves to approximately where his head is. + // Set the player's position to somewhere close to the edge of the pillow block: + a_Player.SetPosition(Vector3f(0.4f, 1, 0.4f) * MetaDataToDirection(Meta & 0x03) + Vector3f(0.5f, 0.6875, 0.5f) + a_BlockPos); + // Fast-forward the time if all players in the world are in their beds: - auto TimeFastForwardTester = [](cPlayer & a_OtherPlayer) - { - return !a_OtherPlayer.IsInBed(); - }; - if (a_WorldInterface.ForEachPlayer(TimeFastForwardTester)) + if (a_WorldInterface.ForEachPlayer([](cPlayer & a_OtherPlayer) { return !a_OtherPlayer.IsInBed(); })) { - a_WorldInterface.ForEachPlayer([&](cPlayer & a_OtherPlayer) - { - cBlockBedHandler::SetBedOccupationState(a_ChunkInterface, a_OtherPlayer.GetLastBedPos(), false); - a_OtherPlayer.SetIsInBed(false); - return false; - } - ); + a_WorldInterface.ForEachPlayer([&a_ChunkInterface](cPlayer & a_OtherPlayer) + { + VacateBed(a_ChunkInterface, a_OtherPlayer); + return false; + }); a_WorldInterface.SetTimeOfDay(0_tick); - a_ChunkInterface.SetBlockMeta(a_BlockPos, Meta & 0x0b); // Clear the "occupied" bit of the bed's block } + return true; } @@ -175,16 +177,3 @@ cItems cBlockBedHandler::ConvertToPickups(const NIBBLETYPE a_BlockMeta, const cI // Drops handled by the block entity: return {}; } - - - - - -void cBlockBedHandler::SetBedPos(cPlayer & a_Player, const Vector3i a_BedPosition) -{ - if (a_Player.GetLastBedPos() != a_BedPosition) - { - a_Player.SetBedPos(a_BedPosition); - a_Player.SendMessageSuccess("Home position set successfully"); - } -} diff --git a/src/Blocks/BlockBed.h b/src/Blocks/BlockBed.h index 1765773d6..7da6740bd 100644 --- a/src/Blocks/BlockBed.h +++ b/src/Blocks/BlockBed.h @@ -36,19 +36,40 @@ public: return Vector3i(); } - static void SetBedOccupationState(cChunkInterface & a_ChunkInterface, Vector3i a_BedPosition, bool a_IsOccupied) + static void VacateBed(cChunkInterface & a_ChunkInterface, cPlayer & a_Player) { - auto Meta = a_ChunkInterface.GetBlockMeta(a_BedPosition); - if (a_IsOccupied) + auto BedPosition = a_Player.GetLastBedPos(); + + BLOCKTYPE Type; + NIBBLETYPE Meta; + a_ChunkInterface.GetBlockTypeMeta(BedPosition, Type, Meta); + + if (Type != E_BLOCK_BED) { - Meta |= 0x04; // Where 0x4 = occupied bit + // Bed was incomplete, just wake: + a_Player.SetIsInBed(false); + return; } - else + + if ((Meta & 0x8) == 0) { - Meta &= 0x0b; // Clear the "occupied" bit of the bed's block + // BedPosition is the foot of the bed, adjust to the head: + BedPosition += MetaDataToDirection(Meta & 0x03); + + a_ChunkInterface.GetBlockTypeMeta(BedPosition, Type, Meta); + if (Type != E_BLOCK_BED) + { + // Bed was incomplete, just wake: + a_Player.SetIsInBed(false); + return; + } } - a_ChunkInterface.SetBlockMeta(a_BedPosition, Meta); + // Clear the "occupied" bit of the bed's pillow block: + a_ChunkInterface.SetBlockMeta(BedPosition, Meta & 0x0b); + + // Wake the player: + a_Player.SetIsInBed(false); } private: @@ -79,19 +100,9 @@ private: - static void SetBedPos(cPlayer & a_Player, const Vector3i a_BedPosition); - - - - - virtual ColourID GetMapBaseColourID(NIBBLETYPE a_Meta) const override { UNUSED(a_Meta); return 28; } } ; - - - - diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index d356b524a..1a99fa300 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -1857,8 +1857,7 @@ bool cClientHandle::HandleHandshake(const AString & a_Username) void cClientHandle::HandleLeaveBed() { cChunkInterface Interface(m_Player->GetWorld()->GetChunkMap()); - cBlockBedHandler::SetBedOccupationState(Interface, m_Player->GetLastBedPos(), false); - m_Player->SetIsInBed(false); + cBlockBedHandler::VacateBed(Interface, *m_Player); } diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 563a1cb97..f5752ffb4 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -481,11 +481,12 @@ void cPlayer::SetIsInBed(const bool a_GoToBed) if (a_GoToBed && IsStanding()) { m_BodyStance = BodyStanceSleeping(*this); + m_World->BroadcastEntityAnimation(*this, EntityAnimation::PlayerEntersBed); } else if (!a_GoToBed && IsInBed()) { m_BodyStance = BodyStanceStanding(*this); - GetWorld()->BroadcastEntityAnimation(*this, 2); + m_World->BroadcastEntityAnimation(*this, EntityAnimation::PlayerLeavesBed); } } @@ -3252,6 +3253,14 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) SetElytraFlight(false); } } + else if (IsInBed()) + { + // Check if sleeping is still possible: + if ((GetPosition().Floor() != m_LastBedPos) || (m_World->GetBlock(m_LastBedPos) != E_BLOCK_BED)) + { + m_ClientHandle->HandleLeaveBed(); + } + } BroadcastMovementUpdate(m_ClientHandle.get()); -- cgit v1.2.3