From 9400308c461574a4071f3c85295a14c820223347 Mon Sep 17 00:00:00 2001 From: danqzq Date: Mon, 12 Jan 2026 01:21:29 -0500 Subject: [PATCH 01/23] Add BedTile and BedItem classes --- source/CMakeLists.txt | 2 + source/common/Utils.hpp | 19 +++- source/world/item/BedItem.cpp | 56 ++++++++++ source/world/item/BedItem.hpp | 19 ++++ source/world/item/Item.cpp | 3 +- source/world/tile/BedTile.cpp | 192 ++++++++++++++++++++++++++++++++++ source/world/tile/BedTile.hpp | 48 +++++++++ source/world/tile/Tile.cpp | 10 +- source/world/tile/Tile.hpp | 3 +- 9 files changed, 348 insertions(+), 4 deletions(-) create mode 100644 source/world/item/BedItem.cpp create mode 100644 source/world/item/BedItem.hpp create mode 100644 source/world/tile/BedTile.cpp create mode 100644 source/world/tile/BedTile.hpp diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 33e075227..cc67f8741 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -301,6 +301,7 @@ add_library(reminecraftpe-core STATIC world/item/TileItem.cpp world/item/Inventory.cpp world/item/DoorItem.cpp + world/item/BedItem.cpp world/item/ItemInstance.cpp world/item/RocketItem.cpp world/item/Item.cpp @@ -363,6 +364,7 @@ add_library(reminecraftpe-core STATIC world/tile/GlowstoneTile.cpp world/tile/Web.cpp world/tile/FenceTile.cpp + world/tile/BedTile.cpp renderer/GL/GL.cpp renderer/Attribute.cpp renderer/ConstantBufferMetaData.cpp diff --git a/source/common/Utils.hpp b/source/common/Utils.hpp index f230c02b7..c4628f0e6 100644 --- a/source/common/Utils.hpp +++ b/source/common/Utils.hpp @@ -493,6 +493,22 @@ enum // Textures TEXTURE_NONE125, TEXTURE_NONE126, TEXTURE_NONE127, + TEXTURE_RAIL = 128, + TEXTURE_CLOTH_129, + TEXTURE_CLOTH_130, + TEXTURE_NONE131, + TEXTURE_NONE132, + TEXTURE_NONE133, + TEXTURE_BED_FOOT_TOP, + TEXTURE_BED_HEAD_TOP, + TEXTURE_MELON_SIDE, + TEXTURE_MELON_TOP, + TEXTURE_NONE138, + TEXTURE_NONE139, + TEXTURE_NONE140, + TEXTURE_NONE141, + TEXTURE_NONE142, + TEXTURE_NONE143, TEXTURE_LAPIS = 144, TEXTURE_ORE_LAPIS = 160, @@ -526,7 +542,8 @@ enum eRenderShape SHAPE_STAIRS, SHAPE_FENCE, SHAPE_CACTUS, - SHAPE_RANDOM_CROSS + SHAPE_RANDOM_CROSS, + SHAPE_BED }; typedef uint8_t TileID; diff --git a/source/world/item/BedItem.cpp b/source/world/item/BedItem.cpp new file mode 100644 index 000000000..3955de964 --- /dev/null +++ b/source/world/item/BedItem.cpp @@ -0,0 +1,56 @@ +/******************************************************************** + Minecraft: Pocket Edition - Decompilation Project + Copyright (C) 2023 iProgramInCpp + + The following code is licensed under the BSD 1 clause license. + SPDX-License-Identifier: BSD-1-Clause + ********************************************************************/ + +#include "BedItem.hpp" +#include "world/level/Level.hpp" +#include "world/entity/Player.hpp" +#include "world/tile/Tile.hpp" +#include "world/tile/BedTile.hpp" + +BedItem::BedItem(int id) : Item(id) +{ + m_maxStackSize = 1; +} + +bool BedItem::useOn(ItemInstance* inst, Player* player, Level* level, const TilePos& pos, Facing::Name face) const +{ + if (face != Facing::UP) { + return false; + } + + TilePos tp = pos.above(); + Tile* bedTile = Tile::bed; + int dir = Mth::floor((player->m_rot.x * 4.0f / 360.0f) + 0.5f) & 3; + TilePos offTp(tp); + if (dir == 0) { + offTp.z += 1; + } + + if (dir == 1) { + offTp.x -= 1; + } + + if (dir == 2) { + offTp.z -= 1; + } + + if (dir == 3) { + offTp.x += 1; + } + + if (level->isEmptyTile(tp) && level->isEmptyTile(offTp) && + level->isSolidTile(tp.below()) && level->isSolidTile(offTp.below())) { + + level->setTileAndData(tp, bedTile->m_ID, dir); + level->setTileAndData(offTp, bedTile->m_ID, dir + 8); + --inst->m_count; + return true; + } + + return false; +} diff --git a/source/world/item/BedItem.hpp b/source/world/item/BedItem.hpp new file mode 100644 index 000000000..b6fa1e722 --- /dev/null +++ b/source/world/item/BedItem.hpp @@ -0,0 +1,19 @@ +/******************************************************************** + Minecraft: Pocket Edition - Decompilation Project + Copyright (C) 2023 iProgramInCpp + + The following code is licensed under the BSD 1 clause license. + SPDX-License-Identifier: BSD-1-Clause + ********************************************************************/ + +#pragma once + +#include "Item.hpp" + +class BedItem : public Item +{ +public: + BedItem(int id); + + bool useOn(ItemInstance*, Player*, Level*, const TilePos& pos, Facing::Name face) const override; +}; diff --git a/source/world/item/Item.cpp b/source/world/item/Item.cpp index 15e8f7944..a15bd6d10 100644 --- a/source/world/item/Item.cpp +++ b/source/world/item/Item.cpp @@ -12,6 +12,7 @@ #include "CameraItem.hpp" #include "DoorItem.hpp" +#include "BedItem.hpp" #include "TileItem.hpp" #include "TilePlanterItem.hpp" #include "RocketItem.hpp" @@ -504,7 +505,7 @@ void Item::initItems() ->setMaxStackSize(1) ->setDescriptionId("cake"); - Item::bed = NEW_ITEM(ITEM_BED) + Item::bed = NEW_X_ITEMN(BedItem, ITEM_BED) ->setIcon(13, 2) ->setDescriptionId("bed"); diff --git a/source/world/tile/BedTile.cpp b/source/world/tile/BedTile.cpp new file mode 100644 index 000000000..29b5a69e8 --- /dev/null +++ b/source/world/tile/BedTile.cpp @@ -0,0 +1,192 @@ +/******************************************************************** + Minecraft: Pocket Edition - Decompilation Project + Copyright (C) 2023 iProgramInCpp + + The following code is licensed under the BSD 1 clause license. + SPDX-License-Identifier: BSD-1-Clause + ********************************************************************/ + +#include "BedTile.hpp" +#include "world/level/Level.hpp" +#include "world/entity/Player.hpp" +#include "world/item/Item.hpp" + +const int BedTile::headBlockToFootBlockMap[4][2] = {{0, 1}, {-1, 0}, {0, -1}, {1, 0}}; +const int BedTile::hiddenFace[4] = {3, 4, 2, 5}; +const int BedTile::hiddenFaceIndex[4] = {2, 3, 0, 1}; +const int BedTile::bedDirection[4][6] = {{1, 0, 3, 2, 5, 4}, {1, 0, 5, 4, 2, 3}, {1, 0, 2, 3, 4, 5}, {1, 0, 4, 5, 3, 2}}; + +BedTile::BedTile(TileID id, int texture) : Tile(id, texture, Material::cloth) +{ + m_renderLayer = RENDER_LAYER_ALPHATEST; + updateDefaultShape(); +} + +int BedTile::getTexture(Facing::Name face, TileData data) const +{ + if (face != Facing::DOWN && face != Facing::UP) { + int var3 = getDirectionFromData(data); + int var4 = bedDirection[var3][face]; + return isHead(data) ? (var4 == 2 ? m_TextureFrame + 2 + 16 : (var4 != 5 && var4 != 4 ? m_TextureFrame + 1 : m_TextureFrame + 1 + 16)) : (var4 == 3 ? m_TextureFrame - 1 + 16 : (var4 != 5 && var4 != 4 ? m_TextureFrame : m_TextureFrame + 16)); + } + else { + return isHead(data) ? m_TextureFrame + 1 : m_TextureFrame; + } +} + +void BedTile::updateShape(const LevelSource* level, const TilePos& pos) +{ + updateDefaultShape(); +} + +void BedTile::updateDefaultShape() +{ + setShape(0, 0, 0, 1, 9.0f / 16.0f, 1); +} + +bool BedTile::isSolidRender() const +{ + return false; +} + +bool BedTile::isCubeShaped() const +{ + return false; +} + +eRenderShape BedTile::getRenderShape() const +{ + return SHAPE_BED; +} + +void BedTile::neighborChanged(Level* level, const TilePos& pos, TileID tile) +{ + TileData data = level->getData(pos); + int dir = getDirectionFromData(data); + + if (isHead(data)) { + TilePos footPos(pos.x - headBlockToFootBlockMap[dir][0], pos.y, pos.z - headBlockToFootBlockMap[dir][1]); + if (level->getTile(footPos) != m_ID) { + level->setTile(pos, TILE_AIR); + } + } + else { + TilePos headPos(pos.x + headBlockToFootBlockMap[dir][0], pos.y, pos.z + headBlockToFootBlockMap[dir][1]); + if (level->getTile(headPos) != m_ID) { + level->setTile(pos, TILE_AIR); + if (!level->m_bIsClientSide) { + Tile::spawnResources(level, pos, data); + } + } + } +} + +bool BedTile::use(Level* level, const TilePos& pos, Player* player) +{ + if (level->m_bIsClientSide) { + return true; + } + + TileData data = level->getData(pos); + TilePos tp(pos); + + if (!isHead(data)) { + int dir = getDirectionFromData(data); + tp.x += headBlockToFootBlockMap[dir][0]; + tp.z += headBlockToFootBlockMap[dir][1]; + if (level->getTile(tp) != m_ID) { + return true; + } + data = level->getData(tp); + } + + if (!level->m_pDimension->mayRespawn()) { + level->setTile(tp, TILE_AIR); + int dir = getDirectionFromData(data); + TilePos footPos(tp); + footPos.x -= headBlockToFootBlockMap[dir][0]; + footPos.z -= headBlockToFootBlockMap[dir][1]; + if (level->getTile(footPos) == m_ID) { + level->setTile(footPos, TILE_AIR); + } + + Vec3 explodePos(tp.x + 0.5f, tp.y + 0.5f, tp.z + 0.5f); + level->explode(NULL, explodePos, 5.0f, true); + return true; + } + else { + if (isBedOccupied(data)) { + // Check if there's a player sleeping in this bed + for (size_t i = 0; i < level->m_players.size(); i++) { + Player* p = level->m_players[i]; + if (p->isSleeping() && p->m_bHasBedSleepPos) { + if (p->m_bedSleepPos == tp) { + player->displayClientMessage("tile.bed.occupied"); + return true; + } + } + } + setBedOccupied(level, tp, false); + } + + Player::BedSleepingProblem result = player->sleep(tp); + if (result == Player::BED_SLEEPING_OK) { + setBedOccupied(level, tp, true); + return true; + } + else { + if (result == Player::BED_SLEEPING_NOT_POSSIBLE_NOW) + player->displayClientMessage("tile.bed.noSleep"); + + return true; + } + } +} + +int BedTile::getResource(TileData data, Random* random) const +{ + return TILE_AIR; // Bed should not drop any items +} + +void BedTile::spawnResources(Level* level, const TilePos& pos, TileData data, float chance) +{ + // Bed should not drop any items +} + +void BedTile::setBedOccupied(Level* level, const TilePos& pos, bool occupied) +{ + TileData data = level->getData(pos); + if (occupied) { + data |= 4; + } + else { + data &= ~4; + } + level->setData(pos, data); +} + +TilePos BedTile::getRespawnTilePos(const Level* level, const TilePos& pos, int steps) +{ + TileData data = level->getData(pos); + int dir = getDirectionFromData(data); + + for (int i = 0; i <= 1; ++i) { + int startX = pos.x - headBlockToFootBlockMap[dir][0] * i - 1; + int startZ = pos.z - headBlockToFootBlockMap[dir][1] * i - 1; + + TilePos tp(pos); + for (tp.x = startX; tp.x <= startX + 2; ++tp.x) { + for (tp.z = startZ; tp.z <= startZ + 2; ++tp.z) { + TilePos belowPos = tp.below(); + Material* mat = level->getMaterial(belowPos); + if (mat && mat->isSolid() && level->isEmptyTile(tp) && level->isEmptyTile(tp.above())) { + if (steps <= 0) + return tp; + --steps; + } + } + } + } + + return pos; +} diff --git a/source/world/tile/BedTile.hpp b/source/world/tile/BedTile.hpp new file mode 100644 index 000000000..2d24613d0 --- /dev/null +++ b/source/world/tile/BedTile.hpp @@ -0,0 +1,48 @@ +/******************************************************************** + Minecraft: Pocket Edition - Decompilation Project + Copyright (C) 2023 iProgramInCpp + + The following code is licensed under the BSD 1 clause license. + SPDX-License-Identifier: BSD-1-Clause + ********************************************************************/ + +#pragma once + +#include "Tile.hpp" + +class BedTile : public Tile +{ +public: + BedTile(TileID id, int texture); + + int getTexture(Facing::Name face, TileData data) const override; + bool isSolidRender() const override; + bool isCubeShaped() const override; + eRenderShape getRenderShape() const override; + void updateShape(const LevelSource* level, const TilePos& pos) override; + void updateDefaultShape() override; + void neighborChanged(Level* level, const TilePos& pos, TileID tile) override; + bool use(Level*, const TilePos& pos, Player*) override; + int getResource(TileData data, Random* random) const override; + void spawnResources(Level*, const TilePos& pos, TileData, float) override; + + static const int headBlockToFootBlockMap[4][2]; + static const int hiddenFace[4]; + static const int hiddenFaceIndex[4]; + static const int bedDirection[4][6]; + + static int getDirectionFromData(TileData meta) { + return meta & 3; + } + + static bool isHead(TileData meta) { + return (meta & 8) != 0; + } + + static bool isBedOccupied(TileData meta) { + return (meta & 4) != 0; + } + + static void setBedOccupied(Level* level, const TilePos& pos, bool occupied); + static TilePos getRespawnTilePos(const Level* level, const TilePos& pos, int steps); +}; diff --git a/source/world/tile/Tile.cpp b/source/world/tile/Tile.cpp index 9d5f0987b..30849211a 100644 --- a/source/world/tile/Tile.cpp +++ b/source/world/tile/Tile.cpp @@ -64,6 +64,7 @@ #include "SoulSandTile.hpp" #include "GlowstoneTile.hpp" #include "FenceTile.hpp" +#include "BedTile.hpp" //#include "BedTile.hpp" //#include "CropsTile.hpp" #include "Web.hpp" @@ -676,6 +677,12 @@ void Tile::initTiles() ->setSoundType(Tile::SOUND_CLOTH) ->setDescriptionId("sponge"); + Tile::bed = (new BedTile(TILE_BED, TEXTURE_BED_FOOT_TOP)) + ->init() + ->setDestroyTime(0.2f) + ->setSoundType(Tile::SOUND_CLOTH) + ->setDescriptionId("bed"); + Tile::cryingObsidian = (new Tile(TILE_OBSIDIAN_CRYING, TEXTURE_OBSIDIAN_CRYING, Material::stone)) ->init() ->setDestroyTime(10.0f) @@ -1224,4 +1231,5 @@ Tile *Tile::soulSand, *Tile::glowstone, *Tile::web, - *Tile::fence; + *Tile::fence, + *Tile::bed; diff --git a/source/world/tile/Tile.hpp b/source/world/tile/Tile.hpp index 01690bd16..4b3f1071a 100644 --- a/source/world/tile/Tile.hpp +++ b/source/world/tile/Tile.hpp @@ -234,7 +234,8 @@ class Tile * soulSand, * glowstone, * web, - * fence; + * fence, + * bed; public: int m_TextureFrame; From 049d45ab31cb9ad1abd4cdddf641c55307d049d1 Mon Sep 17 00:00:00 2001 From: danqzq Date: Mon, 12 Jan 2026 01:22:07 -0500 Subject: [PATCH 02/23] Add bed tile rendering --- source/client/renderer/TileRenderer.cpp | 187 +++++++++++++++++++++++- source/client/renderer/TileRenderer.hpp | 7 + 2 files changed, 193 insertions(+), 1 deletion(-) diff --git a/source/client/renderer/TileRenderer.cpp b/source/client/renderer/TileRenderer.cpp index 5b104ad4d..9dd5ca6b2 100644 --- a/source/client/renderer/TileRenderer.cpp +++ b/source/client/renderer/TileRenderer.cpp @@ -12,6 +12,7 @@ #include "client/renderer/GrassColor.hpp" #include "client/renderer/FoliageColor.hpp" #include "client/renderer/renderer/RenderMaterialGroup.hpp" +#include "world/tile/BedTile.hpp" #include "world/tile/FireTile.hpp" #include "world/tile/LiquidTile.hpp" #include "GameMods.hpp" @@ -153,7 +154,7 @@ float TileRenderer::getWaterHeight(const TilePos& pos, const Material* pCheckMtl bool TileRenderer::canRender(int renderShape) { - return renderShape == SHAPE_SOLID || renderShape == SHAPE_STAIRS || renderShape == SHAPE_FENCE || renderShape == SHAPE_CACTUS; + return renderShape == SHAPE_SOLID || renderShape == SHAPE_STAIRS || renderShape == SHAPE_FENCE || renderShape == SHAPE_CACTUS || renderShape == SHAPE_BED; } // @NOTE: This sucks! Very badly! But it's how they did it. @@ -1198,6 +1199,188 @@ bool TileRenderer::tesselateDoorInWorld(Tile* tile, const TilePos& pos) return true; } +bool TileRenderer::tesselateBedInWorld(Tile* tile, const TilePos& pos) +{ + static constexpr float C_RATIO = 1.0f / 256.0f; + + Tesselator& t = Tesselator::instance; + TileData data = m_pTileSource->getData(pos); + int direction = BedTile::getDirectionFromData(data); + + // Brightness factors for faces + float fBrightDown = 0.5f; + float fBrightUp = 1.0f; + float fBrightNS = 0.8f; + float fBrightEW = 0.6f; + + float fLightHere = tile->getBrightness(m_pTileSource, pos); + + bool bDrewAnything = false; + + // Only render wood underside if not using texture override (for item rendering) + if (m_fixedTexture < 0) { + t.color(fBrightDown * fLightHere, fBrightDown * fLightHere, fBrightDown * fLightHere); + int woodTexture = Tile::wood->m_TextureFrame; + int woodTexX = (woodTexture & 15) << 4; + int woodTexY = woodTexture & 240; + float woodU1 = float(woodTexX) * C_RATIO; + float woodU2 = (float(woodTexX) + 16.0f - 0.01f) * C_RATIO; + float woodV1 = float(woodTexY) * C_RATIO; + float woodV2 = (float(woodTexY) + 16.0f - 0.01f) * C_RATIO; + + float woodMinX = pos.x + tile->m_aabb.min.x; + float woodMaxX = pos.x + tile->m_aabb.max.x; + float woodY = pos.y + tile->m_aabb.min.y + 0.1875f; + float woodMinZ = pos.z + tile->m_aabb.min.z; + float woodMaxZ = pos.z + tile->m_aabb.max.z; + + t.vertexUV(woodMinX, woodY, woodMaxZ, woodU1, woodV2); + t.vertexUV(woodMinX, woodY, woodMinZ, woodU1, woodV1); + t.vertexUV(woodMaxX, woodY, woodMinZ, woodU2, woodV1); + t.vertexUV(woodMaxX, woodY, woodMaxZ, woodU2, woodV2); + bDrewAnything = true; + } + + Facing::Name flippedFace = Facing::WEST; + switch (direction) { + case 0: // +Z facing + flippedFace = Facing::EAST; + break; + case 1: // -X facing + flippedFace = Facing::SOUTH; + break; + case 2: // -Z facing + // flippedFace = WEST + break; + case 3: // +X facing + flippedFace = Facing::NORTH; + break; + } + + float fBright; + int texture; + + // Render UP face (top of bed) with rotation based on bed direction + if (m_bNoCulling || tile->shouldRenderFace(m_pTileSource, pos.above(), Facing::UP)) + { + float fBrightAbove = tile->getBrightness(m_pTileSource, pos.above()); + if (tile->m_aabb.max.y != 1.0f && !tile->m_pMaterial->isLiquid()) + fBrightAbove = fLightHere; + + int upTexture = tile->getTexture(m_pTileSource, pos, Facing::UP); + if (m_fixedTexture >= 0) + upTexture = m_fixedTexture; + + int texX = (upTexture & 15) << 4; + int texY = upTexture & 240; + float u1 = float(texX) * C_RATIO; + float u2 = (float(texX) + 16.0f - 0.01f) * C_RATIO; + float v1 = float(texY) * C_RATIO; + float v2 = (float(texY) + 16.0f - 0.01f) * C_RATIO; + + float upY = pos.y + tile->m_aabb.max.y; + float minX = pos.x + tile->m_aabb.min.x; + float maxX = pos.x + tile->m_aabb.max.x; + float minZ = pos.z + tile->m_aabb.min.z; + float maxZ = pos.z + tile->m_aabb.max.z; + + t.color(fBrightUp * fBrightAbove, fBrightUp * fBrightAbove, fBrightUp * fBrightAbove); + + // Apply UV rotation based on upRot + // The bed top texture has: pillow at low V (top), blanket at high V (bottom) + // We rotate to align pillow with the head direction + switch (flippedFace) { + case Facing::NORTH: + t.vertexUV(maxX, upY, maxZ, u2, v1); + t.vertexUV(maxX, upY, minZ, u2, v2); + t.vertexUV(minX, upY, minZ, u1, v2); + t.vertexUV(minX, upY, maxZ, u1, v1); + break; + case Facing::EAST: + t.vertexUV(maxX, upY, maxZ, u2, v1); + t.vertexUV(maxX, upY, minZ, u1, v1); + t.vertexUV(minX, upY, minZ, u1, v2); + t.vertexUV(minX, upY, maxZ, u2, v2); + break; + case Facing::WEST: + t.vertexUV(maxX, upY, maxZ, u1, v1); + t.vertexUV(maxX, upY, minZ, u2, v1); + t.vertexUV(minX, upY, minZ, u2, v2); + t.vertexUV(minX, upY, maxZ, u1, v2); + break; + case Facing::SOUTH: + t.vertexUV(maxX, upY, maxZ, u1, v2); + t.vertexUV(maxX, upY, minZ, u1, v1); + t.vertexUV(minX, upY, minZ, u2, v1); + t.vertexUV(minX, upY, maxZ, u2, v2); + break; + default: + break; + } + bDrewAnything = true; + } + + struct FaceData { + Facing::Name face; + TilePos adjPos; + float aabbCheck; + bool checkMin; + void (TileRenderer::*renderFunc)(Tile*, const Vec3&, int); + }; + + FaceData faces[4]; + faces[0].face = Facing::NORTH; + faces[0].adjPos = pos.north(); + faces[0].aabbCheck = tile->m_aabb.min.z; + faces[0].checkMin = true; + faces[0].renderFunc = &TileRenderer::renderNorth; + + faces[1].face = Facing::SOUTH; + faces[1].adjPos = pos.south(); + faces[1].aabbCheck = tile->m_aabb.max.z; + faces[1].checkMin = false; + faces[1].renderFunc = &TileRenderer::renderSouth; + + faces[2].face = Facing::WEST; + faces[2].adjPos = pos.west(); + faces[2].aabbCheck = tile->m_aabb.min.x; + faces[2].checkMin = true; + faces[2].renderFunc = &TileRenderer::renderWest; + + faces[3].face = Facing::EAST; + faces[3].adjPos = pos.east(); + faces[3].aabbCheck = tile->m_aabb.max.x; + faces[3].checkMin = false; + faces[3].renderFunc = &TileRenderer::renderEast; + + // Determine which face to hide (the connecting face between head and foot) + Facing::Name hiddenFace = (Facing::Name)BedTile::hiddenFace[direction]; + if (BedTile::isHead(data)) { + hiddenFace = (Facing::Name)BedTile::hiddenFace[BedTile::hiddenFaceIndex[direction]]; + } + + for (int i = 0; i < 4; i++) { + FaceData& fd = faces[i]; + if (hiddenFace == fd.face || (!m_bNoCulling && !tile->shouldRenderFace(m_pTileSource, fd.adjPos, fd.face))) { + continue; + } + fBright = tile->getBrightness(m_pTileSource, fd.adjPos); + if ((fd.checkMin && fd.aabbCheck > 0.0f) || (!fd.checkMin && fd.aabbCheck < 1.0f)) { + fBright = fLightHere; + } + float brightness = i < 2 ? fBrightNS : fBrightEW; + t.color(brightness * fBright, brightness * fBright, brightness * fBright); + texture = tile->getTexture(m_pTileSource, pos, fd.face); + if (flippedFace == fd.face) { + m_bXFlipTexture = true; + } + (this->*fd.renderFunc)(tile, pos, texture); + m_bXFlipTexture = false; + } + + return bDrewAnything; +} + void TileRenderer::tesselateTorch(Tile* tile, const Vec3& pos, float a, float b) { constexpr float C_RATIO = 1.0f / 256.0f; @@ -1563,6 +1746,8 @@ bool TileRenderer::tesselateInWorld(Tile* tile, const TilePos& pos) return tesselateFenceInWorld(tile, pos); case SHAPE_CACTUS: return tesselateBlockInWorld(tile, pos); + case SHAPE_BED: + return tesselateBedInWorld(tile, pos); } return false; diff --git a/source/client/renderer/TileRenderer.hpp b/source/client/renderer/TileRenderer.hpp index e1670d8e5..df3936631 100644 --- a/source/client/renderer/TileRenderer.hpp +++ b/source/client/renderer/TileRenderer.hpp @@ -48,6 +48,12 @@ class TileRenderer void renderNorth(Tile*, const Vec3& pos, int texture); void renderFaceDown(Tile*, const Vec3& pos, int texture); void renderFaceUp(Tile*, const Vec3& pos, int texture); + + // Double-sided rendering (reversed winding order) + void renderEastReversed(Tile*, const Vec3& pos, int texture); + void renderWestReversed(Tile*, const Vec3& pos, int texture); + void renderSouthReversed(Tile*, const Vec3& pos, int texture); + void renderNorthReversed(Tile*, const Vec3& pos, int texture); void tesselateCrossTexture(const FullTile& tile, const Vec3& pos, bool simple = false); void tesselateTorch(Tile*, const Vec3& pos, float a, float b); @@ -61,6 +67,7 @@ class TileRenderer bool tesselateLadderInWorld(Tile*, const TilePos& pos); bool tesselateTorchInWorld(Tile*, const TilePos& pos); bool tesselateDoorInWorld(Tile*, const TilePos& pos); + bool tesselateBedInWorld(Tile*, const TilePos& pos); #ifndef ORIGINAL_CODE bool tesselateFireInWorld(Tile*, const TilePos& pos); #endif From 80e662b4b53701ac78fcabf4508dd8ffbdf69c53 Mon Sep 17 00:00:00 2001 From: danqzq Date: Mon, 12 Jan 2026 01:22:29 -0500 Subject: [PATCH 03/23] Add player sleeping logic --- source/world/Facing.hpp | 10 +++ source/world/entity/Mob.hpp | 1 + source/world/entity/Player.cpp | 144 +++++++++++++++++++++++++++++++++ source/world/entity/Player.hpp | 28 ++++++- source/world/level/Level.cpp | 55 +++++++++++++ source/world/level/Level.hpp | 1 + 6 files changed, 238 insertions(+), 1 deletion(-) diff --git a/source/world/Facing.hpp b/source/world/Facing.hpp index 740f1ce37..cb6d2a4c2 100644 --- a/source/world/Facing.hpp +++ b/source/world/Facing.hpp @@ -12,4 +12,14 @@ class Facing WEST, // -X EAST // +X }; + + static bool isHorizontal(Name face) + { + return face != DOWN && face != UP; + } + + static bool isVertical(Name face) + { + return face == DOWN || face == UP; + } }; diff --git a/source/world/entity/Mob.hpp b/source/world/entity/Mob.hpp index 931e44718..17b5b7604 100644 --- a/source/world/entity/Mob.hpp +++ b/source/world/entity/Mob.hpp @@ -76,6 +76,7 @@ class Mob : public Entity virtual int getDeathLoot() const { return 0; } virtual void dropDeathLoot(); virtual bool isImmobile() const { return m_health <= 0; } + virtual bool isSleeping() const { return false; } virtual void jumpFromGround(); virtual void updateAi(); virtual int getMaxHeadXRot() const { return 10; } diff --git a/source/world/entity/Player.cpp b/source/world/entity/Player.cpp index e12e7e45e..8acabef4c 100644 --- a/source/world/entity/Player.cpp +++ b/source/world/entity/Player.cpp @@ -8,6 +8,7 @@ #include "Player.hpp" #include "world/level/Level.hpp" +#include "world/tile/BedTile.hpp" #include "nbt/CompoundTag.hpp" void Player::_init() @@ -18,6 +19,9 @@ void Player::_init() m_bob = 0.0f; m_dimension = 0; m_destroyingBlock = false; + m_bSleeping = false; + m_sleepTimer = 0; + m_bHasBedSleepPos = false; } Player::Player(Level* pLevel, GameType playerGameType) : Mob(pLevel) @@ -167,6 +171,16 @@ void Player::aiStep() heal(1); } + // Handle sleeping + if (m_bSleeping) { + ++m_sleepTimer; + if (m_sleepTimer >= 100) { + m_pLevel->updateSleeping(); + } + // Don't do normal movement while sleeping + return; + } + #ifdef ENH_GUI_ITEM_POP m_pInventory->tick(); #endif @@ -371,6 +385,136 @@ void Player::setRespawnPos(const TilePos& pos) m_respawnPos = pos; } +void Player::setBedSleepPos(const TilePos& pos) +{ + m_bHasBedSleepPos = true; + m_bedSleepPos = pos; +} + +void Player::updateSleepingPos(int direction) +{ + m_sleepingPos.x = 0.0f; + m_sleepingPos.y = 0.0f; + m_sleepingPos.z = 0.0f; + switch (direction) { + case 0: // +Z facing + m_sleepingPos.z = -1.8f; + break; + case 1: // -X facing + m_sleepingPos.x = 1.8f; + break; + case 2: // -Z facing + m_sleepingPos.z = 1.8f; + break; + case 3: // +X facing + m_sleepingPos.x = -1.8f; + break; + } +} + +Player::BedSleepingProblem Player::sleep(const TilePos& pos) +{ + if (isSleeping() || !isAlive()) + return BED_SLEEPING_OTHER_PROBLEM; + + if (m_pLevel->m_pDimension->m_bFoggy) + return BED_SLEEPING_NOT_POSSIBLE_HERE; + + if (m_pLevel->isDay()) + return BED_SLEEPING_NOT_POSSIBLE_NOW; + + if (Mth::abs(m_pos.x - pos.x) > 3.0f || Mth::abs(m_pos.y - pos.y) > 2.0f || Mth::abs(m_pos.z - pos.z) > 3.0f) + return BED_SLEEPING_TOO_FAR_AWAY; + + setSize(0.2f, 0.2f); + m_heightOffset = 0.2f; + + if (!m_pLevel->isEmptyTile(pos)) { + TileData data = m_pLevel->getData(pos); + int dir = BedTile::getDirectionFromData(data); + float xOff = 0.5f; + float zOff = 0.5f; + switch (dir) { + case 0: + zOff = 0.9f; + break; + case 1: + xOff = 0.1f; + break; + case 2: + zOff = 0.1f; + break; + case 3: + xOff = 0.9f; + break; + } + updateSleepingPos(dir); + setPos(Vec3(pos.x + xOff, pos.y + 15.0f / 16.0f, pos.z + zOff)); + } + else { + setPos(Vec3(pos.x + 0.5f, pos.y + 15.0f / 16.0f, pos.z + 0.5f)); + } + + m_bSleeping = true; + m_sleepTimer = 0; + setBedSleepPos(pos); + m_vel = Vec3::ZERO; + + m_pLevel->updateSleeping(); + + return BED_SLEEPING_OK; +} + +void Player::wake(bool resetCounter, bool update, bool setSpawn) +{ + setSize(0.6f, 1.8f); + setDefaultHeadHeight(); + + TilePos checkBedPos = m_bedSleepPos; + if (m_bHasBedSleepPos && m_pLevel->getTile(checkBedPos) == Tile::bed->m_ID) { + BedTile::setBedOccupied(m_pLevel, checkBedPos, false); + checkBedPos = BedTile::getRespawnTilePos(m_pLevel, checkBedPos, 0); + if (checkBedPos == m_bedSleepPos) + checkBedPos = checkBedPos.above(); + + setPos(Vec3(checkBedPos.x + 0.5f, checkBedPos.y + m_heightOffset + 0.1f, checkBedPos.z + 0.5f)); + } + + m_bSleeping = false; + if (resetCounter) { + m_sleepTimer = 0; + } + + if (setSpawn && m_bHasBedSleepPos) { + setRespawnPos(m_bedSleepPos); + } +} + +float Player::getBedSleepRot() const +{ + if (!m_pLevel || !m_bHasBedSleepPos) + return 0.0f; + + if (m_bedSleepPos.y < 0 || m_bedSleepPos.y >= 128) + return 0.0f; + + TileData data = m_pLevel->getData(m_bedSleepPos); + int dir = BedTile::getDirectionFromData(data); + + switch (dir) { + case 0: + return 90.0f; + case 1: + return 0.0f; + case 2: + return 270.0f; + case 3: + return 180.0f; + default: + return 0.0f; + } +} + /*void Player::drop() { // From b1.2_02, doesn't exist in PE diff --git a/source/world/entity/Player.hpp b/source/world/entity/Player.hpp index a261e0c41..4d142919b 100644 --- a/source/world/entity/Player.hpp +++ b/source/world/entity/Player.hpp @@ -18,6 +18,16 @@ class Inventory; // in case we're included from Inventory.hpp class Player : public Mob { +public: + enum BedSleepingProblem + { + BED_SLEEPING_OK, + BED_SLEEPING_NOT_POSSIBLE_HERE, + BED_SLEEPING_NOT_POSSIBLE_NOW, + BED_SLEEPING_TOO_FAR_AWAY, + BED_SLEEPING_OTHER_PROBLEM + }; + private: GameType _playerGameType; @@ -47,7 +57,7 @@ class Player : public Mob void die(Entity* pCulprit) override; void aiStep() override; ItemInstance* getCarriedItem() const override; - bool isImmobile() const override { return m_health <= 0; } + bool isImmobile() const override { return m_health <= 0 || m_bSleeping; } void updateAi() override; void addAdditionalSaveData(CompoundTag& tag) const override; void readAdditionalSaveData(const CompoundTag& tag) override; @@ -80,6 +90,15 @@ class Player : public Mob void setDefaultHeadHeight(); void setRespawnPos(const TilePos& pos); + // Sleeping + void setBedSleepPos(const TilePos& pos); + void updateSleepingPos(int direction); + virtual BedSleepingProblem sleep(const TilePos& pos); + virtual void wake(bool resetCounter, bool update, bool setSpawn); + bool isSleeping() const override { return m_bSleeping; } + bool isSleepingLongEnough() const { return m_bSleeping && m_sleepTimer >= 100; } + float getBedSleepRot() const; + void touch(Entity* pEnt); GameType getPlayerGameType() const { return _playerGameType; } virtual void setPlayerGameType(GameType playerGameType) { _playerGameType = playerGameType; } @@ -111,5 +130,12 @@ class Player : public Mob bool m_bHasRespawnPos; //TODO bool m_destroyingBlock; + + // Sleeping + bool m_bSleeping; + int m_sleepTimer; + TilePos m_bedSleepPos; + bool m_bHasBedSleepPos; + Vec3 m_sleepingPos; }; diff --git a/source/world/level/Level.cpp b/source/world/level/Level.cpp index ada020483..60e5a0d4f 100644 --- a/source/world/level/Level.cpp +++ b/source/world/level/Level.cpp @@ -1939,3 +1939,58 @@ float Level::getSunAngle(float f) const { return (float(M_PI) * getTimeOfDay(f)) * 2; } + +void Level::updateSleeping() +{ + // Only the host/server should manage sleep time skipping + if (m_bIsClientSide) + return; + + // Check if all players are sleeping + bool allSleeping = true; + bool anyPlayerExists = false; + + for (size_t i = 0; i < m_players.size(); i++) { + Player* player = m_players[i]; + if (player && !player->m_bRemoved) { + anyPlayerExists = true; + if (!player->isSleeping()) { + allSleeping = false; + break; + } + } + } + + // If all players are sleeping long enough, skip to morning + if (anyPlayerExists && allSleeping) { + bool allSleepingLongEnough = true; + for (size_t i = 0; i < m_players.size(); i++) { + Player* player = m_players[i]; + if (player && !player->m_bRemoved) { + if (!player->isSleepingLongEnough()) { + allSleepingLongEnough = false; + break; + } + } + } + + if (allSleepingLongEnough) { + // Skip to morning (time 0 is dawn, 24000 is a full day) + int32_t currentTime = getTime(); + int32_t timeOfDay = currentTime % 24000; + int32_t timeToMorning = (24000 - timeOfDay) % 24000; + if (timeToMorning == 0) + timeToMorning = 24000; // Full day if already morning + + setTime(currentTime + timeToMorning); + + // Wake all players + for (size_t i = 0; i < m_players.size(); i++) { + Player* player = m_players[i]; + if (player && player->isSleeping()) { + player->wake(false, true, true); + } + } + } + } +} diff --git a/source/world/level/Level.hpp b/source/world/level/Level.hpp index 62c5554d0..dee0768e1 100644 --- a/source/world/level/Level.hpp +++ b/source/world/level/Level.hpp @@ -149,6 +149,7 @@ class Level : public LevelSource void tickPendingTicks(bool b); void tickTiles(); void tickEntities(); + void updateSleeping(); void addToTickNextTick(const TilePos& tilePos, int, int); void takePicture(TripodCamera* pCamera, Entity* pOwner); void addParticle(const std::string& name, const Vec3& pos, const Vec3& dir = Vec3::ZERO); From aeaa1a04530211b007c8062489ea77e2a3c9ca4a Mon Sep 17 00:00:00 2001 From: danqzq Date: Mon, 12 Jan 2026 01:22:49 -0500 Subject: [PATCH 04/23] Add sleeping player rendering --- .../renderer/entity/HumanoidMobRenderer.cpp | 40 +++++++++++++++++++ .../renderer/entity/HumanoidMobRenderer.hpp | 2 + 2 files changed, 42 insertions(+) diff --git a/source/client/renderer/entity/HumanoidMobRenderer.cpp b/source/client/renderer/entity/HumanoidMobRenderer.cpp index aa948c413..c60c19cfb 100644 --- a/source/client/renderer/entity/HumanoidMobRenderer.cpp +++ b/source/client/renderer/entity/HumanoidMobRenderer.cpp @@ -101,6 +101,46 @@ void HumanoidMobRenderer::onGraphicsReset() m_pHumanoidModel->onGraphicsReset(); } +void HumanoidMobRenderer::setupPosition(const Entity& entity, const Vec3& pos, Matrix& matrix) +{ + if (entity.isAlive() && entity.isMob()) + { + const Mob& mob = (const Mob&)entity; + if (mob.isSleeping() && entity.isPlayer()) + { + const Player& player = (const Player&)entity; + Vec3 sleepPos( + pos.x + player.m_sleepingPos.x, + pos.y, + pos.z + player.m_sleepingPos.z + ); + MobRenderer::setupPosition(entity, sleepPos, matrix); + return; + } + } + MobRenderer::setupPosition(entity, pos, matrix); +} + +void HumanoidMobRenderer::setupRotations(const Entity& entity, float bob, float bodyRot, Matrix& matrix, float a) +{ + if (entity.isAlive() && entity.isMob()) + { + const Mob& mob = (const Mob&)entity; + if (mob.isSleeping() && entity.isPlayer()) + { + const Player& player = (const Player&)entity; + if (player.m_pLevel) + { + matrix.rotate(player.getBedSleepRot(), Vec3::UNIT_Y); + matrix.rotate(getFlipDegrees(mob), Vec3::UNIT_Z); + matrix.rotate(270.0f, Vec3::UNIT_Y); + return; + } + } + } + MobRenderer::setupRotations(entity, bob, bodyRot, matrix, a); +} + void HumanoidMobRenderer::renderHand(const Entity& entity, float a) { m_pHumanoidModel->field_4 = 0; diff --git a/source/client/renderer/entity/HumanoidMobRenderer.hpp b/source/client/renderer/entity/HumanoidMobRenderer.hpp index fc7f3dfdb..7210fe6f8 100644 --- a/source/client/renderer/entity/HumanoidMobRenderer.hpp +++ b/source/client/renderer/entity/HumanoidMobRenderer.hpp @@ -18,6 +18,8 @@ class HumanoidMobRenderer : public MobRenderer virtual void additionalRendering(const Mob& mob, float) override; virtual void render(const Entity& entity, const Vec3&, float, float) override; virtual void onGraphicsReset() override; + virtual void setupPosition(const Entity& entity, const Vec3& pos, Matrix& matrix) override; + virtual void setupRotations(const Entity& entity, float bob, float bodyRot, Matrix& matrix, float a) override; void renderHand(const Entity& entity, float a); From a83ff4cb2e762014998ec8a2f30d2b1a6053671f Mon Sep 17 00:00:00 2001 From: danqzq Date: Mon, 12 Jan 2026 01:23:17 -0500 Subject: [PATCH 05/23] Add sleeping camera/view behavior --- source/client/app/Minecraft.cpp | 33 +++++- source/client/renderer/GameRenderer.cpp | 134 +++++++++++++++--------- 2 files changed, 116 insertions(+), 51 deletions(-) diff --git a/source/client/app/Minecraft.cpp b/source/client/app/Minecraft.cpp index 28bbdbbe5..bd545b378 100644 --- a/source/client/app/Minecraft.cpp +++ b/source/client/app/Minecraft.cpp @@ -36,6 +36,7 @@ #include "client/player/input/Multitouch.hpp" #include "world/tile/SandTile.hpp" +#include "world/tile/BedTile.hpp" #include "client/renderer/GrassColor.hpp" #include "client/renderer/FoliageColor.hpp" @@ -147,7 +148,24 @@ void Minecraft::_resetPlayer(Player* player) m_pLevel->validateSpawn(); player->reset(); - TilePos pos = m_pLevel->getSharedSpawnPos(); + // Use player's personal respawn position (set by sleeping in bed) if available + // Otherwise fall back to the level's shared spawn position + TilePos pos; + if (player->m_bHasRespawnPos) + { + pos = player->m_respawnPos; + // Check if the bed still exists + if (m_pLevel->getTile(pos) == Tile::bed->m_ID) + { + // Find a valid position near the bed to spawn + pos = BedTile::getRespawnTilePos(m_pLevel, pos, 0); + } + } + else + { + pos = m_pLevel->getSharedSpawnPos(); + } + player->setPos(pos); player->resetPos(); } @@ -453,6 +471,12 @@ void Minecraft::handleBuildAction(const BuildActionIntention& action) if (ItemInstance::isNull(pItem) || !pItem->getTile()) return; + // Don't send PlaceBlockPacket if we just interacted with a bed + // The bed interaction is handled via UseItemPacket + TileID interactedTile = m_pLevel->getTile(m_hitResult.m_tilePos); + if (interactedTile == Tile::bed->m_ID) + return; + TilePos tp(m_hitResult.m_tilePos); Facing::Name hitSide = m_hitResult.m_hitSide; @@ -1083,6 +1107,13 @@ bool Minecraft::pauseGame() { if (isGamePaused() || m_pScreen) return false; + // If player is sleeping, wake them up instead of pausing + if (m_pLocalPlayer && m_pLocalPlayer->isSleeping()) + { + m_pLocalPlayer->wake(false, true, true); + return true; + } + if (!isOnline()) { // Actually pause the game, because fuck bedrock edition diff --git a/source/client/renderer/GameRenderer.cpp b/source/client/renderer/GameRenderer.cpp index 0c495f4f2..4e63058bd 100644 --- a/source/client/renderer/GameRenderer.cpp +++ b/source/client/renderer/GameRenderer.cpp @@ -289,6 +289,35 @@ void GameRenderer::moveCameraToPlayer(Matrix& matrix, float f) float posZ = Mth::Lerp(pMob->m_oPos.z, pMob->m_pos.z, f); matrix.rotate(field_5C + f * (field_58 - field_5C), Vec3::UNIT_Z); + + // Adjust camera for sleeping player + if (pMob->isPlayer()) + { + Player* player = (Player*)pMob; + if (player->isSleeping()) + { + if (!m_pMinecraft->getOptions()->field_241) + { + // Get bed direction for camera orientation + float bedRot = 0.0f; + if (player->m_bHasBedSleepPos) + { + TileID bedTile = m_pMinecraft->m_pLevel->getTile(player->m_bedSleepPos); + if (bedTile == Tile::bed->m_ID) + { + int data = m_pMinecraft->m_pLevel->getData(player->m_bedSleepPos); + int direction = data & 3; + bedRot = direction * 90.0f; + } + } + + // Position camera at bed height looking up + matrix.translate(Vec3(0.0f, 0.2f, 0.0f)); + matrix.rotate(bedRot, Vec3::UNIT_Y); + } + return; + } + } if (m_pMinecraft->getOptions()->m_bThirdPerson) { @@ -564,66 +593,71 @@ void GameRenderer::render(float f) if (m_pMinecraft->m_pLocalPlayer && m_pMinecraft->m_bGrabbedMouse) { Minecraft *pMC = m_pMinecraft; - pMC->m_mouseHandler.poll(); + + // Don't allow camera rotation while sleeping + if (!pMC->m_pLocalPlayer->isSleeping()) + { + pMC->m_mouseHandler.poll(); - float multPitch = -1.0f; - float diff_field_84; + float multPitch = -1.0f; + float diff_field_84; - if (pMC->getOptions()->m_bInvertMouse) - multPitch = 1.0f; + if (pMC->getOptions()->m_bInvertMouse) + multPitch = 1.0f; - if (pMC->m_mouseHandler.smoothTurning()) - { - float mult1 = 2.0f * (0.2f + pMC->getOptions()->m_fSensitivity * 0.6f); - mult1 = pow(mult1, 3); + if (pMC->m_mouseHandler.smoothTurning()) + { + float mult1 = 2.0f * (0.2f + pMC->getOptions()->m_fSensitivity * 0.6f); + mult1 = pow(mult1, 3); - float xd = 4.0f * mult1 * pMC->m_mouseHandler.m_delta.x; - float yd = 4.0f * mult1 * pMC->m_mouseHandler.m_delta.y; + float xd = 4.0f * mult1 * pMC->m_mouseHandler.m_delta.x; + float yd = 4.0f * mult1 * pMC->m_mouseHandler.m_delta.y; - float old_field_84 = field_84; - field_84 = float(field_C) + f; - diff_field_84 = field_84 - old_field_84; - field_74 += xd; - field_78 += yd; + float old_field_84 = field_84; + field_84 = float(field_C) + f; + diff_field_84 = field_84 - old_field_84; + field_74 += xd; + field_78 += yd; - if (diff_field_84 > 3.0f) - diff_field_84 = 3.0f; + if (diff_field_84 > 3.0f) + diff_field_84 = 3.0f; - if (!pMC->getOptions()->field_240) + if (!pMC->getOptions()->field_240) + { + // @TODO: untangle this code + float v17 = xd + field_14; + float v18 = field_18; + float v19 = field_1C; + field_14 = v17; + float v20 = mult1 * 0.25f * (v17 - v18); + float v21 = v19 + (v20 - v19) * 0.5f; + field_1C = v21; + if ((v20 <= 0.0 || v20 <= v21) && (v20 >= 0.0 || v20 >= v21)) + v21 = mult1 * 0.25f * (v17 - v18); + float v22 = yd + field_20; + field_18 = v18 + v21; + float v23 = field_24; + field_20 = v22; + float v24 = mult1 * 0.15f * (v22 - v23); + float v25 = field_28 + (v24 - field_28) * 0.5f; + field_28 = v25; + if ((v24 <= 0.0 || v24 <= v25) && (v24 >= 0.0 || v24 >= v25)) + v25 = v24; + field_24 = v23 + v25; + } + } + else { - // @TODO: untangle this code - float v17 = xd + field_14; - float v18 = field_18; - float v19 = field_1C; - field_14 = v17; - float v20 = mult1 * 0.25f * (v17 - v18); - float v21 = v19 + (v20 - v19) * 0.5f; - field_1C = v21; - if ((v20 <= 0.0 || v20 <= v21) && (v20 >= 0.0 || v20 >= v21)) - v21 = mult1 * 0.25f * (v17 - v18); - float v22 = yd + field_20; - field_18 = v18 + v21; - float v23 = field_24; - field_20 = v22; - float v24 = mult1 * 0.15f * (v22 - v23); - float v25 = field_28 + (v24 - field_28) * 0.5f; - field_28 = v25; - if ((v24 <= 0.0 || v24 <= v25) && (v24 >= 0.0 || v24 >= v25)) - v25 = v24; - field_24 = v23 + v25; + diff_field_84 = 1.0f; + field_7C = pMC->m_mouseHandler.m_delta.x; + field_80 = pMC->m_mouseHandler.m_delta.y; } - } - else - { - diff_field_84 = 1.0f; - field_7C = pMC->m_mouseHandler.m_delta.x; - field_80 = pMC->m_mouseHandler.m_delta.y; - } - Vec2 rot(field_7C * diff_field_84, - field_80 * diff_field_84 * multPitch); - m_pItemInHandRenderer->turn(rot); - pMC->m_pLocalPlayer->turn(rot); + Vec2 rot(field_7C * diff_field_84, + field_80 * diff_field_84 * multPitch); + m_pItemInHandRenderer->turn(rot); + pMC->m_pLocalPlayer->turn(rot); + } } int mouseX = int(Mouse::getX() * Gui::InvGuiScale); From 3c288307e40bd3169ac4cea89b96dbf0ec3317ad Mon Sep 17 00:00:00 2001 From: danqzq Date: Mon, 12 Jan 2026 01:23:27 -0500 Subject: [PATCH 06/23] Add multiplayer sleeping support --- .../multiplayer/MultiplayerLocalPlayer.cpp | 17 +++ .../multiplayer/MultiplayerLocalPlayer.hpp | 4 + .../network/ClientSideNetworkHandler.cpp | 100 ++++++++++++++++++ source/client/player/LocalPlayer.cpp | 34 ++++++ source/client/player/LocalPlayer.hpp | 2 + source/network/NetEventCallback.cpp | 22 +++- source/network/packets/AnimatePacket.hpp | 4 +- source/server/ServerPlayer.cpp | 52 +++++++++ source/server/ServerPlayer.hpp | 2 + source/server/ServerSideNetworkHandler.cpp | 12 +++ source/world/gamemode/GameMode.cpp | 8 +- 11 files changed, 252 insertions(+), 5 deletions(-) diff --git a/source/client/multiplayer/MultiplayerLocalPlayer.cpp b/source/client/multiplayer/MultiplayerLocalPlayer.cpp index 090b04e02..a481f2693 100644 --- a/source/client/multiplayer/MultiplayerLocalPlayer.cpp +++ b/source/client/multiplayer/MultiplayerLocalPlayer.cpp @@ -1,5 +1,7 @@ #include "MultiplayerLocalPlayer.hpp" #include "network/RakNetInstance.hpp" +#include "network/packets/AnimatePacket.hpp" +#include "network/packets/MovePlayerPacket.hpp" #include "world/level/Level.hpp" MultiplayerLocalPlayer::MultiplayerLocalPlayer(Minecraft* pMinecraft, Level* pLevel, User* pUser, GameType gameType, int dimensionId) @@ -85,3 +87,18 @@ void MultiplayerLocalPlayer::hurtTo(int newHealth) m_flashOnSetHealth = true; } } + +void MultiplayerLocalPlayer::wake(bool resetCounter, bool update, bool setSpawn) +{ + Player::wake(resetCounter, update, setSpawn); + + // Send wake notification to server + if (m_pLevel && m_pLevel->m_pRakNetInstance) + { + m_pLevel->m_pRakNetInstance->send(new AnimatePacket(m_EntityID, AnimatePacket::WAKE)); + + // Also send position update so server knows where we are after waking + m_pLevel->m_pRakNetInstance->send(new MovePlayerPacket(m_EntityID, + Vec3(m_pos.x, m_pos.y - m_heightOffset, m_pos.z), m_rot)); + } +} diff --git a/source/client/multiplayer/MultiplayerLocalPlayer.hpp b/source/client/multiplayer/MultiplayerLocalPlayer.hpp index f564abd8f..1aea21cbf 100644 --- a/source/client/multiplayer/MultiplayerLocalPlayer.hpp +++ b/source/client/multiplayer/MultiplayerLocalPlayer.hpp @@ -15,6 +15,10 @@ class MultiplayerLocalPlayer : public LocalPlayer void heal(int health) override; //void drop() override; void hurtTo(int newHealth) override; + + // Client doesn't perform sleep validation - server handles it and sends AnimatePacket + BedSleepingProblem sleep(const TilePos& pos) override { return BED_SLEEPING_OK; } + void wake(bool resetCounter, bool update, bool setSpawn) override; private: bool m_flashOnSetHealth; diff --git a/source/client/network/ClientSideNetworkHandler.cpp b/source/client/network/ClientSideNetworkHandler.cpp index 39728a45f..867e6b295 100644 --- a/source/client/network/ClientSideNetworkHandler.cpp +++ b/source/client/network/ClientSideNetworkHandler.cpp @@ -15,6 +15,7 @@ #include "client/multiplayer/MultiplayerLocalPlayer.hpp" #include "network/MinecraftPackets.hpp" #include "world/entity/MobFactory.hpp" +#include "world/tile/BedTile.hpp" // This lets you make the client shut up and not log events in the debug console. //#define VERBOSE_CLIENT @@ -317,6 +318,14 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, MovePla if (!m_pLevel) return; Entity* pEntity = m_pLevel->getEntity(packet->m_id); + + // Check if this is the local player (they may not be in the entity list on multiplayer clients) + if (!pEntity && m_pMinecraft && m_pMinecraft->m_pLocalPlayer && + m_pMinecraft->m_pLocalPlayer->m_EntityID == packet->m_id) + { + pEntity = m_pMinecraft->m_pLocalPlayer; + } + if (!pEntity) { LOG_E("MovePlayerPacket: No player with id %d", packet->m_id); @@ -601,6 +610,14 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, Animate return; Entity* pEntity = m_pLevel->getEntity(pkt->m_entityId); + + // Check if this is the local player (they may not be in the entity list on multiplayer clients) + if (!pEntity && m_pMinecraft && m_pMinecraft->m_pLocalPlayer && + m_pMinecraft->m_pLocalPlayer->m_EntityID == pkt->m_entityId) + { + pEntity = m_pMinecraft->m_pLocalPlayer; + } + if (!pEntity) return; @@ -620,6 +637,89 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, Animate pEntity->animateHurt(); break; } + case AnimatePacket::WAKE: + { + if (pEntity->isPlayer()) + { + Player* pPlayer = (Player*)pEntity; + // Skip if this is our local player AND they're not sleeping + // (means they already woke themselves up) + // But if they ARE sleeping, the server is waking them (e.g., time skip) + if (pPlayer->isLocalPlayer() && !pPlayer->isSleeping()) + break; + pPlayer->wake(false, false, false); + } + break; + } + case AnimatePacket::SLEEP: + { + if (pEntity->isPlayer()) + { + Player* pPlayer = (Player*)pEntity; + + // Find the bed tile at the player's position + if (m_pLevel && Tile::bed) + { + // Use lerp target position if available (MovePlayerPacket contains exact bed position) + // The server sends bedPos + 0.5, so floor gives us exact tile coords + Vec3 searchPos = pPlayer->m_pos; + if (pPlayer->m_lSteps > 0) + { + searchPos = pPlayer->m_lPos; + searchPos.y -= pPlayer->m_heightOffset; + } + + // The server sends the bed center (tile + 0.5), so floor to get exact tile + TilePos bedPos(Mth::floor(searchPos.x), Mth::floor(searchPos.y), Mth::floor(searchPos.z)); + + // First check the exact position - this is where the server said the bed is + if (bedPos.y >= 0 && bedPos.y < 128 && + m_pLevel->getTile(bedPos) == Tile::bed->m_ID) + { + TileData data = m_pLevel->getData(bedPos); + int dir = BedTile::getDirectionFromData(data); + + float xOff = 0.5f; + float zOff = 0.5f; + switch (dir) { + case 0: + zOff = 0.9f; + break; + case 1: + xOff = 0.1f; + break; + case 2: + zOff = 0.1f; + break; + case 3: + xOff = 0.9f; + break; + } + + pPlayer->setSize(0.2f, 0.2f); + pPlayer->m_heightOffset = 0.2f; + pPlayer->updateSleepingPos(dir); + pPlayer->setPos(Vec3(bedPos.x + xOff, bedPos.y + 15.0f / 16.0f, bedPos.z + zOff)); + pPlayer->m_bSleeping = true; + pPlayer->m_sleepTimer = 0; + pPlayer->setBedSleepPos(bedPos); + pPlayer->m_vel = Vec3::ZERO; + pPlayer->m_lSteps = 0; + } + else + { + pPlayer->m_bSleeping = true; + pPlayer->m_sleepTimer = 0; + } + } + else + { + pPlayer->m_bSleeping = true; + pPlayer->m_sleepTimer = 0; + } + } + break; + } default: { LOG_W("Received unkown action in AnimatePacket: %d, EntityType: %s", pkt->m_actionId, pEntity->getDescriptor().getEntityType().getName().c_str()); diff --git a/source/client/player/LocalPlayer.cpp b/source/client/player/LocalPlayer.cpp index d3a41f082..809214b21 100644 --- a/source/client/player/LocalPlayer.cpp +++ b/source/client/player/LocalPlayer.cpp @@ -11,6 +11,7 @@ #include "nbt/CompoundTag.hpp" #include "network/packets/MovePlayerPacket.hpp" #include "network/packets/PlayerEquipmentPacket.hpp" +#include "network/packets/AnimatePacket.hpp" int dword_250ADC, dword_250AE0; @@ -327,3 +328,36 @@ void LocalPlayer::sendPosition() m_lastSentRot = m_rot; } } + +Player::BedSleepingProblem LocalPlayer::sleep(const TilePos& pos) +{ + Player::BedSleepingProblem result = Player::sleep(pos); + + // Broadcast position and sleep state to all clients if in multiplayer (hosting) + if (result == BED_SLEEPING_OK && m_pLevel && m_pLevel->m_pRakNetInstance && m_pLevel->m_pRakNetInstance->m_bIsHost) + { + // Send bed position so remote clients know exactly where the bed is + // Use the actual bed position, not interpolated player position + Vec3 bedPos(float(pos.x) + 0.5f, float(pos.y) + 0.5f, float(pos.z) + 0.5f); + MovePlayerPacket movePacket(m_EntityID, bedPos, m_rot); + RakNet::BitStream bs; + movePacket.write(bs); + m_pLevel->m_pRakNetInstance->getPeer()->Send(&bs, HIGH_PRIORITY, RELIABLE_ORDERED, 0, m_guid, true); + + // Then send sleep animation to everyone + m_pLevel->m_pRakNetInstance->send(new AnimatePacket(m_EntityID, AnimatePacket::SLEEP)); + } + + return result; +} + +void LocalPlayer::wake(bool resetCounter, bool update, bool setSpawn) +{ + Player::wake(resetCounter, update, setSpawn); + + // Broadcast wake animation to all clients if in multiplayer (hosting) + if (m_pLevel && m_pLevel->m_pRakNetInstance && m_pLevel->m_pRakNetInstance->m_bIsHost) + { + m_pLevel->m_pRakNetInstance->send(new AnimatePacket(m_EntityID, AnimatePacket::WAKE)); + } +} \ No newline at end of file diff --git a/source/client/player/LocalPlayer.hpp b/source/client/player/LocalPlayer.hpp index 1742da082..98bfdbe7b 100644 --- a/source/client/player/LocalPlayer.hpp +++ b/source/client/player/LocalPlayer.hpp @@ -39,6 +39,8 @@ class LocalPlayer : public Player bool interpolateOnly() const override { return false; } void setPlayerGameType(GameType gameType) override; void swing() override; + BedSleepingProblem sleep(const TilePos& pos) override; + void wake(bool resetCounter, bool update, bool setSpawn) override; virtual void hurtTo(int newHealth); diff --git a/source/network/NetEventCallback.cpp b/source/network/NetEventCallback.cpp index 724c0bed1..11bbd160d 100644 --- a/source/network/NetEventCallback.cpp +++ b/source/network/NetEventCallback.cpp @@ -1,6 +1,7 @@ #include "NetEventCallback.hpp" #include "common/Logger.hpp" #include "world/level/Level.hpp" +#include "world/tile/BedTile.hpp" Player* NetEventCallback::_findPlayer(Level& level, Entity::ID entityId, const RakNet::RakNetGUID* guid) { @@ -36,8 +37,25 @@ void NetEventCallback::handle(Level& level, const RakNet::RakNetGUID& guid, Resp return; } - // @TODO: on server, ignore client's requested coords, and teleport them to their server-determined spawn - pPlayer->moveTo(pkt->m_pos); + // Use player's personal respawn position (set by sleeping in bed) if available + // Otherwise fall back to the level's shared spawn position + TilePos spawnPos; + if (pPlayer->m_bHasRespawnPos) + { + spawnPos = pPlayer->m_respawnPos; + // Check if the bed still exists + if (level.getTile(spawnPos) == Tile::bed->m_ID) + { + // Find a valid position near the bed to spawn + spawnPos = BedTile::getRespawnTilePos(&level, spawnPos, 0); + } + } + else + { + spawnPos = level.getSharedSpawnPos(); + } + + pPlayer->moveTo(Vec3(spawnPos.x + 0.5f, float(spawnPos.y), spawnPos.z + 0.5f)); pPlayer->reset(); pPlayer->resetPos(true); } diff --git a/source/network/packets/AnimatePacket.hpp b/source/network/packets/AnimatePacket.hpp index d6388c790..62d19afb5 100644 --- a/source/network/packets/AnimatePacket.hpp +++ b/source/network/packets/AnimatePacket.hpp @@ -9,7 +9,9 @@ class AnimatePacket : public Packet { NONE, SWING, - HURT + HURT, + WAKE, + SLEEP }; public: diff --git a/source/server/ServerPlayer.cpp b/source/server/ServerPlayer.cpp index 3dec41d31..30718df17 100644 --- a/source/server/ServerPlayer.cpp +++ b/source/server/ServerPlayer.cpp @@ -1,8 +1,11 @@ #include "ServerPlayer.hpp" #include "network/packets/SetHealthPacket.hpp" #include "network/packets/TakeItemEntityPacket.hpp" +#include "network/packets/AnimatePacket.hpp" +#include "network/packets/MovePlayerPacket.hpp" #include "network/RakNetInstance.hpp" #include "world/level/Level.hpp" +#include "world/tile/BedTile.hpp" ServerPlayer::ServerPlayer(Level* pLevel, GameType playerGameType) : Player(pLevel, playerGameType) @@ -27,3 +30,52 @@ void ServerPlayer::take(Entity* pEnt, int count) Player::take(pEnt, count); } + +Player::BedSleepingProblem ServerPlayer::sleep(const TilePos& pos) +{ + Player::BedSleepingProblem result = Player::sleep(pos); + + // Broadcast position and sleep state to all clients if successful + if (result == BED_SLEEPING_OK && m_pLevel && m_pLevel->m_pRakNetInstance) + { + // Send position update to ALL clients including self so they can find the bed + // The position is the bed tile center + m_pLevel->m_pRakNetInstance->send(new MovePlayerPacket(m_EntityID, Vec3(m_pos.x, m_pos.y - m_heightOffset, m_pos.z), m_rot)); + + // Then send sleep animation to everyone + m_pLevel->m_pRakNetInstance->send(new AnimatePacket(m_EntityID, AnimatePacket::SLEEP)); + } + + return result; +} + +void ServerPlayer::wake(bool resetCounter, bool update, bool setSpawn) +{ + // Clear sleeping state FIRST so isImmobile() returns false + m_bSleeping = false; + if (resetCounter) { + m_sleepTimer = 0; + } + + // Reset size and height + setSize(0.6f, 1.8f); + setDefaultHeadHeight(); + + // Clear bed occupancy + if (m_bHasBedSleepPos && m_pLevel && m_pLevel->getTile(m_bedSleepPos) == Tile::bed->m_ID) { + BedTile::setBedOccupied(m_pLevel, m_bedSleepPos, false); + } + + if (setSpawn && m_bHasBedSleepPos) { + setRespawnPos(m_bedSleepPos); + } + + // Reset lerp steps so position updates work immediately + m_lSteps = 0; + + // Broadcast wake animation to all clients + if (m_pLevel && m_pLevel->m_pRakNetInstance) + { + m_pLevel->m_pRakNetInstance->send(new AnimatePacket(m_EntityID, AnimatePacket::WAKE)); + } +} diff --git a/source/server/ServerPlayer.hpp b/source/server/ServerPlayer.hpp index c69ea2210..8d3bcf8c8 100644 --- a/source/server/ServerPlayer.hpp +++ b/source/server/ServerPlayer.hpp @@ -8,6 +8,8 @@ class ServerPlayer : public Player public: void tick() override; void take(Entity* pEnt, int count) override; + BedSleepingProblem sleep(const TilePos& pos) override; + void wake(bool resetCounter, bool update, bool setSpawn) override; private: int m_lastHealth; diff --git a/source/server/ServerSideNetworkHandler.cpp b/source/server/ServerSideNetworkHandler.cpp index 8fcd8a12e..299513c87 100644 --- a/source/server/ServerSideNetworkHandler.cpp +++ b/source/server/ServerSideNetworkHandler.cpp @@ -518,6 +518,18 @@ void ServerSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, AnimatePac pPlayer->animateHurt(); break; } + case AnimatePacket::WAKE: + { + // Client is waking up - call wake on server player + pPlayer->wake(false, true, true); + break; + } + case AnimatePacket::SLEEP: + { + // Client sleeping is handled via UseItemPacket on bed tile + // This is just for forwarding to other clients + break; + } default: { LOG_W("Received unkown action in AnimatePacket: %d, EntityType: %s", packet->m_actionId, pEntity->getDescriptor().getEntityType().getName().c_str()); diff --git a/source/world/gamemode/GameMode.cpp b/source/world/gamemode/GameMode.cpp index 4e896140d..eabe1f3ec 100644 --- a/source/world/gamemode/GameMode.cpp +++ b/source/world/gamemode/GameMode.cpp @@ -152,19 +152,23 @@ bool GameMode::useItemOn(Player* player, Level* level, ItemInstance* instance, c return false; bool success = false; + bool isBed = (tile > 0 && Tile::tiles[tile] == Tile::bed); + bool isMultiplayerClient = (level->m_pRakNetInstance != nullptr && !level->m_pRakNetInstance->m_bIsHost); if (tile > 0 && Tile::tiles[tile]->use(level, pos, player)) { success = true; } - else if (instance) + else if (instance && !(isMultiplayerClient && isBed)) { success = instance->useOn(player, level, pos, face); } - if (success) + if (success || (isMultiplayerClient && isBed)) { _level.m_pRakNetInstance->send(new UseItemPacket(pos, face, player->m_EntityID, instance)); + if (isBed) + return true; } return success; From 219205524f5906dc6b8812a57de39945edd726f3 Mon Sep 17 00:00:00 2001 From: danqzq Date: Mon, 12 Jan 2026 02:07:42 -0500 Subject: [PATCH 07/23] Add BedItem and BedTile source files to Xcode project for macOS build --- .../Minecraft/Minecraft.xcodeproj/project.pbxproj | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/platforms/macos/projects/Minecraft/Minecraft.xcodeproj/project.pbxproj b/platforms/macos/projects/Minecraft/Minecraft.xcodeproj/project.pbxproj index 526d2fbc4..5ca7d823e 100644 --- a/platforms/macos/projects/Minecraft/Minecraft.xcodeproj/project.pbxproj +++ b/platforms/macos/projects/Minecraft/Minecraft.xcodeproj/project.pbxproj @@ -522,6 +522,7 @@ 84BF63162AF18631008A9995 /* SurvivalMode.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 840DD66D2AC810620006A435 /* SurvivalMode.cpp */; }; 84BF63172AF18631008A9995 /* CameraItem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 840DD6702AC810620006A435 /* CameraItem.cpp */; }; 84BF63182AF18631008A9995 /* DoorItem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 840DD6722AC810620006A435 /* DoorItem.cpp */; }; + 59B35F78C33D4247A5F2FB80 /* BedItem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 275AF7A1723F44EE9B9C0DA7 /* BedItem.cpp */; }; 84BF63192AF18631008A9995 /* Inventory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 840DD6742AC810620006A435 /* Inventory.cpp */; }; 84BF631A2AF18631008A9995 /* Item.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 840DD6762AC810620006A435 /* Item.cpp */; }; 84BF631B2AF18631008A9995 /* ItemInstance.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 840DD6782AC810620006A435 /* ItemInstance.cpp */; }; @@ -800,6 +801,7 @@ 84E1C9D02E7FDC26007D2F5D /* CactusTile.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 84E1C9C02E7FDC26007D2F5D /* CactusTile.hpp */; }; 84E1C9D12E7FDC26007D2F5D /* DeadBush.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 84E1C9C12E7FDC26007D2F5D /* DeadBush.cpp */; }; 84E1C9D22E7FDC26007D2F5D /* DeadBush.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 84E1C9C22E7FDC26007D2F5D /* DeadBush.hpp */; }; + D025F1B21908432AAB13CCAB /* BedTile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D025F1B21908432AAB13CCAC /* BedTile.cpp */; }; 84E1C9D32E7FDC26007D2F5D /* FenceTile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 84E1C9C32E7FDC26007D2F5D /* FenceTile.cpp */; }; 84E1C9D42E7FDC26007D2F5D /* FenceTile.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 84E1C9C42E7FDC26007D2F5D /* FenceTile.hpp */; }; 84E1C9D52E7FDC26007D2F5D /* GlowstoneTile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 84E1C9C52E7FDC26007D2F5D /* GlowstoneTile.cpp */; }; @@ -1586,6 +1588,8 @@ 840DD6712AC810620006A435 /* CameraItem.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CameraItem.hpp; sourceTree = ""; }; 840DD6722AC810620006A435 /* DoorItem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DoorItem.cpp; sourceTree = ""; }; 840DD6732AC810620006A435 /* DoorItem.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DoorItem.hpp; sourceTree = ""; }; + 275AF7A1723F44EE9B9C0DA7 /* BedItem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BedItem.cpp; sourceTree = ""; }; + 6CE4834EE148437C93D73250 /* BedItem.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = BedItem.hpp; sourceTree = ""; }; 840DD6742AC810620006A435 /* Inventory.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Inventory.cpp; sourceTree = ""; }; 840DD6752AC810620006A435 /* Inventory.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Inventory.hpp; sourceTree = ""; }; 840DD6762AC810620006A435 /* Item.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Item.cpp; sourceTree = ""; }; @@ -2385,6 +2389,8 @@ 84E1C9C02E7FDC26007D2F5D /* CactusTile.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CactusTile.hpp; sourceTree = ""; }; 84E1C9C12E7FDC26007D2F5D /* DeadBush.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DeadBush.cpp; sourceTree = ""; }; 84E1C9C22E7FDC26007D2F5D /* DeadBush.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DeadBush.hpp; sourceTree = ""; }; + D025F1B21908432AAB13CCAC /* BedTile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BedTile.cpp; sourceTree = ""; }; + D025F1B21908432AAB13CCAD /* BedTile.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = BedTile.hpp; sourceTree = ""; }; 84E1C9C32E7FDC26007D2F5D /* FenceTile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FenceTile.cpp; sourceTree = ""; }; 84E1C9C42E7FDC26007D2F5D /* FenceTile.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FenceTile.hpp; sourceTree = ""; }; 84E1C9C52E7FDC26007D2F5D /* GlowstoneTile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GlowstoneTile.cpp; sourceTree = ""; }; @@ -3442,6 +3448,8 @@ 84E1C9E22E7FDC72007D2F5D /* ClothItem.hpp */, 840DD6722AC810620006A435 /* DoorItem.cpp */, 840DD6732AC810620006A435 /* DoorItem.hpp */, + 275AF7A1723F44EE9B9C0DA7 /* BedItem.cpp */, + 6CE4834EE148437C93D73250 /* BedItem.hpp */, 840DD6742AC810620006A435 /* Inventory.cpp */, 840DD6752AC810620006A435 /* Inventory.hpp */, 840DD6762AC810620006A435 /* Item.cpp */, @@ -3659,6 +3667,8 @@ 840DD6ED2AC810620006A435 /* DoorTile.hpp */, 840DD6EE2AC810620006A435 /* FarmTile.cpp */, 840DD6EF2AC810620006A435 /* FarmTile.hpp */, + D025F1B21908432AAB13CCAC /* BedTile.cpp */, + D025F1B21908432AAB13CCAD /* BedTile.hpp */, 84E1C9C32E7FDC26007D2F5D /* FenceTile.cpp */, 84E1C9C42E7FDC26007D2F5D /* FenceTile.hpp */, 840DD6F02AC810620006A435 /* FireTile.cpp */, @@ -6277,6 +6287,7 @@ 84E1C9E52E7FDC72007D2F5D /* AuxTileItem.cpp in Sources */, 84E1C9D52E7FDC26007D2F5D /* GlowstoneTile.cpp in Sources */, 84BF63182AF18631008A9995 /* DoorItem.cpp in Sources */, + 59B35F78C33D4247A5F2FB80 /* BedItem.cpp in Sources */, 84BF63192AF18631008A9995 /* Inventory.cpp in Sources */, 8445E7A22D769329008DC834 /* EntityType.cpp in Sources */, 84BF631A2AF18631008A9995 /* Item.cpp in Sources */, @@ -6330,6 +6341,7 @@ 84BF633F2AF18631008A9995 /* LevelSource.cpp in Sources */, 84BF63402AF18631008A9995 /* LevelStorage.cpp in Sources */, 84BF63412AF18631008A9995 /* LevelStorageSource.cpp in Sources */, + D025F1B21908432AAB13CCAB /* BedTile.cpp in Sources */, 84E1C9D32E7FDC26007D2F5D /* FenceTile.cpp in Sources */, 8477B3BA2C4DC42E004E1AC5 /* Vec2.cpp in Sources */, 84BF63422AF18631008A9995 /* MemoryChunkStorage.cpp in Sources */, From e8d2b6040c5cda90a5145cd39b50032f013c1bec Mon Sep 17 00:00:00 2001 From: danqzq Date: Mon, 12 Jan 2026 18:36:50 -0500 Subject: [PATCH 08/23] Add helper function to retrieve entity or local player in ClientSideNetworkHandler --- .../network/ClientSideNetworkHandler.cpp | 35 ++++++++++--------- .../network/ClientSideNetworkHandler.hpp | 4 +++ 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/source/client/network/ClientSideNetworkHandler.cpp b/source/client/network/ClientSideNetworkHandler.cpp index 867e6b295..2f8261776 100644 --- a/source/client/network/ClientSideNetworkHandler.cpp +++ b/source/client/network/ClientSideNetworkHandler.cpp @@ -41,6 +41,23 @@ ClientSideNetworkHandler::ClientSideNetworkHandler(Minecraft* pMinecraft, RakNet clearChunksLoaded(); } +Entity* ClientSideNetworkHandler::getEntityOrLocalPlayer(int entityId) +{ + if (!m_pLevel) + return nullptr; + + Entity* pEntity = m_pLevel->getEntity(entityId); + + // Check if this is the local player (they may not be in the entity list on multiplayer clients) + if (!pEntity && m_pMinecraft && m_pMinecraft->m_pLocalPlayer && + m_pMinecraft->m_pLocalPlayer->m_EntityID == entityId) + { + pEntity = m_pMinecraft->m_pLocalPlayer; + } + + return pEntity; +} + void ClientSideNetworkHandler::levelGenerated(Level* level) { m_pLevel = (MultiPlayerLevel*)level; @@ -317,14 +334,7 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, MovePla { if (!m_pLevel) return; - Entity* pEntity = m_pLevel->getEntity(packet->m_id); - - // Check if this is the local player (they may not be in the entity list on multiplayer clients) - if (!pEntity && m_pMinecraft && m_pMinecraft->m_pLocalPlayer && - m_pMinecraft->m_pLocalPlayer->m_EntityID == packet->m_id) - { - pEntity = m_pMinecraft->m_pLocalPlayer; - } + Entity* pEntity = getEntityOrLocalPlayer(packet->m_id); if (!pEntity) { @@ -609,14 +619,7 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, Animate if (!m_pLevel) return; - Entity* pEntity = m_pLevel->getEntity(pkt->m_entityId); - - // Check if this is the local player (they may not be in the entity list on multiplayer clients) - if (!pEntity && m_pMinecraft && m_pMinecraft->m_pLocalPlayer && - m_pMinecraft->m_pLocalPlayer->m_EntityID == pkt->m_entityId) - { - pEntity = m_pMinecraft->m_pLocalPlayer; - } + Entity* pEntity = getEntityOrLocalPlayer(pkt->m_entityId); if (!pEntity) return; diff --git a/source/client/network/ClientSideNetworkHandler.hpp b/source/client/network/ClientSideNetworkHandler.hpp index 81230686c..1b90e3b75 100644 --- a/source/client/network/ClientSideNetworkHandler.hpp +++ b/source/client/network/ClientSideNetworkHandler.hpp @@ -66,6 +66,10 @@ class ClientSideNetworkHandler : public NetEventCallback void flushAllBufferedUpdates(); // inlined void handleBlockUpdate(const BlockUpdate& u); +protected: + // Helper function to get entity, with fallback to local player if not in entity list + Entity* getEntityOrLocalPlayer(int entityId); + private: Minecraft* m_pMinecraft; MultiPlayerLevel* m_pLevel; From c64477c92db6e66cf1595da8a24bed635bc5d911 Mon Sep 17 00:00:00 2001 From: danqzq Date: Mon, 12 Jan 2026 20:15:03 -0500 Subject: [PATCH 09/23] Implement proper bed respawn validation and messaging for players --- source/client/app/Minecraft.cpp | 22 ++++++++------- .../network/ClientSideNetworkHandler.cpp | 28 +++++++++++++++++++ source/network/NetEventCallback.cpp | 21 ++++++-------- source/world/entity/Player.cpp | 19 +++++++++++++ source/world/entity/Player.hpp | 1 + 5 files changed, 69 insertions(+), 22 deletions(-) diff --git a/source/client/app/Minecraft.cpp b/source/client/app/Minecraft.cpp index bd545b378..8c048d12e 100644 --- a/source/client/app/Minecraft.cpp +++ b/source/client/app/Minecraft.cpp @@ -150,20 +150,22 @@ void Minecraft::_resetPlayer(Player* player) // Use player's personal respawn position (set by sleeping in bed) if available // Otherwise fall back to the level's shared spawn position - TilePos pos; + TilePos pos = m_pLevel->getSharedSpawnPos(); + if (player->m_bHasRespawnPos) { - pos = player->m_respawnPos; - // Check if the bed still exists - if (m_pLevel->getTile(pos) == Tile::bed->m_ID) + TilePos respawnPos = player->m_respawnPos; + TilePos checkedPos = Player::checkRespawnPos(m_pLevel, respawnPos); + + // If checkedPos != respawnPos, bed is valid and we found a spawn position + if (checkedPos != respawnPos) { - // Find a valid position near the bed to spawn - pos = BedTile::getRespawnTilePos(m_pLevel, pos, 0); + pos = checkedPos; + } + else + { + m_pGui->addMessage("Your home bed was missing or obstructed"); } - } - else - { - pos = m_pLevel->getSharedSpawnPos(); } player->setPos(pos); diff --git a/source/client/network/ClientSideNetworkHandler.cpp b/source/client/network/ClientSideNetworkHandler.cpp index 2f8261776..71059b32d 100644 --- a/source/client/network/ClientSideNetworkHandler.cpp +++ b/source/client/network/ClientSideNetworkHandler.cpp @@ -738,7 +738,35 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, RespawnPac if (!m_pLevel) return; + // Find the player to check their bed spawn status + Player* pPlayer = nullptr; + if (packet->m_entityId != -1) + { + Entity* pEntity = m_pLevel->getEntity(packet->m_entityId); + if (pEntity && pEntity->isPlayer()) + pPlayer = (Player*)pEntity; + } + + // Check if bed is valid + bool hadBedSpawn = pPlayer && pPlayer->m_bHasRespawnPos; + TilePos respawnPos, checkedPos; + bool bedValid = false; + + if (hadBedSpawn) + { + respawnPos = pPlayer->m_respawnPos; + checkedPos = Player::checkRespawnPos(m_pLevel, respawnPos); + bedValid = (checkedPos != respawnPos); + } + + // Call base handler to perform the respawn NetEventCallback::handle(*m_pLevel, guid, packet); + + // Show message if bed spawn failed and this is the local player + if (hadBedSpawn && !bedValid && pPlayer && pPlayer->isLocalPlayer()) + { + m_pMinecraft->m_pGui->addMessage("Your home bed was missing or obstructed"); + } } void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, LevelDataPacket* packet) diff --git a/source/network/NetEventCallback.cpp b/source/network/NetEventCallback.cpp index 11bbd160d..0aa2149ef 100644 --- a/source/network/NetEventCallback.cpp +++ b/source/network/NetEventCallback.cpp @@ -39,20 +39,17 @@ void NetEventCallback::handle(Level& level, const RakNet::RakNetGUID& guid, Resp // Use player's personal respawn position (set by sleeping in bed) if available // Otherwise fall back to the level's shared spawn position - TilePos spawnPos; + TilePos spawnPos = level.getSharedSpawnPos(); + if (pPlayer->m_bHasRespawnPos) { - spawnPos = pPlayer->m_respawnPos; - // Check if the bed still exists - if (level.getTile(spawnPos) == Tile::bed->m_ID) - { - // Find a valid position near the bed to spawn - spawnPos = BedTile::getRespawnTilePos(&level, spawnPos, 0); - } - } - else - { - spawnPos = level.getSharedSpawnPos(); + TilePos respawnPos = pPlayer->m_respawnPos; + TilePos checkedPos = Player::checkRespawnPos(&level, respawnPos); + + // If checkedPos != respawnPos, bed is valid and we found a spawn position + if (checkedPos != respawnPos) + spawnPos = checkedPos; + // else: fall back to world spawn (message shown in ClientSideNetworkHandler) } pPlayer->moveTo(Vec3(spawnPos.x + 0.5f, float(spawnPos.y), spawnPos.z + 0.5f)); diff --git a/source/world/entity/Player.cpp b/source/world/entity/Player.cpp index 8acabef4c..d9eae6b1d 100644 --- a/source/world/entity/Player.cpp +++ b/source/world/entity/Player.cpp @@ -515,6 +515,25 @@ float Player::getBedSleepRot() const } } +TilePos Player::checkRespawnPos(Level* level, const TilePos& pos) +{ + // Load nearby chunks to ensure bed data is available + ChunkPos ch(pos); + level->getChunkSource()->getChunk(ChunkPos(ch.x - 1, ch.z)); + level->getChunkSource()->getChunk(ChunkPos(ch.x + 1, ch.z - 1)); + level->getChunkSource()->getChunk(ChunkPos(ch.x - 1, ch.z + 1)); + level->getChunkSource()->getChunk(ChunkPos(ch.x + 1, ch.z)); + + // Check if bed still exists + if (level->getTile(pos) != Tile::bed->m_ID) { + // Bed missing - return original pos as failure indicator + return pos; + } + + // Find valid spawn position near bed + return BedTile::getRespawnTilePos(level, pos, 0); +} + /*void Player::drop() { // From b1.2_02, doesn't exist in PE diff --git a/source/world/entity/Player.hpp b/source/world/entity/Player.hpp index 4d142919b..5cf40932d 100644 --- a/source/world/entity/Player.hpp +++ b/source/world/entity/Player.hpp @@ -98,6 +98,7 @@ class Player : public Mob bool isSleeping() const override { return m_bSleeping; } bool isSleepingLongEnough() const { return m_bSleeping && m_sleepTimer >= 100; } float getBedSleepRot() const; + static TilePos checkRespawnPos(Level* level, const TilePos& pos); void touch(Entity* pEnt); GameType getPlayerGameType() const { return _playerGameType; } From ae895eec7f0a90973fb77e3e882908a6bd048fb9 Mon Sep 17 00:00:00 2001 From: danqzq Date: Mon, 12 Jan 2026 20:37:13 -0500 Subject: [PATCH 10/23] Add bed obstruction check to sleep and respawn position validation --- source/world/entity/Player.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/source/world/entity/Player.cpp b/source/world/entity/Player.cpp index d9eae6b1d..3fc55cba6 100644 --- a/source/world/entity/Player.cpp +++ b/source/world/entity/Player.cpp @@ -426,6 +426,11 @@ Player::BedSleepingProblem Player::sleep(const TilePos& pos) if (Mth::abs(m_pos.x - pos.x) > 3.0f || Mth::abs(m_pos.y - pos.y) > 2.0f || Mth::abs(m_pos.z - pos.z) > 3.0f) return BED_SLEEPING_TOO_FAR_AWAY; + // Check if bed is obstructed (has blocks on top) + TilePos aboveBed = pos.above(); + if (!m_pLevel->isEmptyTile(aboveBed)) + return BED_SLEEPING_OTHER_PROBLEM; + setSize(0.2f, 0.2f); m_heightOffset = 0.2f; @@ -525,12 +530,15 @@ TilePos Player::checkRespawnPos(Level* level, const TilePos& pos) level->getChunkSource()->getChunk(ChunkPos(ch.x + 1, ch.z)); // Check if bed still exists - if (level->getTile(pos) != Tile::bed->m_ID) { - // Bed missing - return original pos as failure indicator + if (level->getTile(pos) != Tile::bed->m_ID) return pos; - } - // Find valid spawn position near bed + // Check if bed is obstructed (has blocks directly on top) + if (!level->isEmptyTile(pos.above())) + return pos; + + // Find valid spawn position near bed (searches 3x3 area for valid spawn) + // Returns original position if no valid spawn found return BedTile::getRespawnTilePos(level, pos, 0); } From 1a770bc2465d1c16f95ed48a118db70feec50ce5 Mon Sep 17 00:00:00 2001 From: danqzq Date: Mon, 12 Jan 2026 20:39:12 -0500 Subject: [PATCH 11/23] Rename sleep -> startSleepInBed and wake -> stopSleepInBed --- source/client/app/Minecraft.cpp | 2 +- source/client/multiplayer/MultiplayerLocalPlayer.cpp | 4 ++-- source/client/multiplayer/MultiplayerLocalPlayer.hpp | 4 ++-- source/client/network/ClientSideNetworkHandler.cpp | 2 +- source/client/player/LocalPlayer.cpp | 8 ++++---- source/client/player/LocalPlayer.hpp | 4 ++-- source/server/ServerPlayer.cpp | 6 +++--- source/server/ServerPlayer.hpp | 4 ++-- source/server/ServerSideNetworkHandler.cpp | 2 +- source/world/entity/Player.cpp | 4 ++-- source/world/entity/Player.hpp | 4 ++-- source/world/level/Level.cpp | 2 +- source/world/tile/BedTile.cpp | 2 +- 13 files changed, 24 insertions(+), 24 deletions(-) diff --git a/source/client/app/Minecraft.cpp b/source/client/app/Minecraft.cpp index 8c048d12e..2b7291de2 100644 --- a/source/client/app/Minecraft.cpp +++ b/source/client/app/Minecraft.cpp @@ -1112,7 +1112,7 @@ bool Minecraft::pauseGame() // If player is sleeping, wake them up instead of pausing if (m_pLocalPlayer && m_pLocalPlayer->isSleeping()) { - m_pLocalPlayer->wake(false, true, true); + m_pLocalPlayer->stopSleepInBed(false, true, true); return true; } diff --git a/source/client/multiplayer/MultiplayerLocalPlayer.cpp b/source/client/multiplayer/MultiplayerLocalPlayer.cpp index a481f2693..55a073c70 100644 --- a/source/client/multiplayer/MultiplayerLocalPlayer.cpp +++ b/source/client/multiplayer/MultiplayerLocalPlayer.cpp @@ -88,9 +88,9 @@ void MultiplayerLocalPlayer::hurtTo(int newHealth) } } -void MultiplayerLocalPlayer::wake(bool resetCounter, bool update, bool setSpawn) +void MultiplayerLocalPlayer::stopSleepInBed(bool resetCounter, bool update, bool setSpawn) { - Player::wake(resetCounter, update, setSpawn); + Player::stopSleepInBed(resetCounter, update, setSpawn); // Send wake notification to server if (m_pLevel && m_pLevel->m_pRakNetInstance) diff --git a/source/client/multiplayer/MultiplayerLocalPlayer.hpp b/source/client/multiplayer/MultiplayerLocalPlayer.hpp index 1aea21cbf..c19f12f82 100644 --- a/source/client/multiplayer/MultiplayerLocalPlayer.hpp +++ b/source/client/multiplayer/MultiplayerLocalPlayer.hpp @@ -17,8 +17,8 @@ class MultiplayerLocalPlayer : public LocalPlayer void hurtTo(int newHealth) override; // Client doesn't perform sleep validation - server handles it and sends AnimatePacket - BedSleepingProblem sleep(const TilePos& pos) override { return BED_SLEEPING_OK; } - void wake(bool resetCounter, bool update, bool setSpawn) override; + BedSleepingProblem startSleepInBed(const TilePos& pos) override { return BED_SLEEPING_OK; } + void stopSleepInBed(bool resetCounter, bool update, bool setSpawn) override; private: bool m_flashOnSetHealth; diff --git a/source/client/network/ClientSideNetworkHandler.cpp b/source/client/network/ClientSideNetworkHandler.cpp index 71059b32d..7472d4797 100644 --- a/source/client/network/ClientSideNetworkHandler.cpp +++ b/source/client/network/ClientSideNetworkHandler.cpp @@ -650,7 +650,7 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, Animate // But if they ARE sleeping, the server is waking them (e.g., time skip) if (pPlayer->isLocalPlayer() && !pPlayer->isSleeping()) break; - pPlayer->wake(false, false, false); + pPlayer->stopSleepInBed(false, false, false); } break; } diff --git a/source/client/player/LocalPlayer.cpp b/source/client/player/LocalPlayer.cpp index 809214b21..d7f114167 100644 --- a/source/client/player/LocalPlayer.cpp +++ b/source/client/player/LocalPlayer.cpp @@ -329,9 +329,9 @@ void LocalPlayer::sendPosition() } } -Player::BedSleepingProblem LocalPlayer::sleep(const TilePos& pos) +Player::BedSleepingProblem LocalPlayer::startSleepInBed(const TilePos& pos) { - Player::BedSleepingProblem result = Player::sleep(pos); + Player::BedSleepingProblem result = Player::startSleepInBed(pos); // Broadcast position and sleep state to all clients if in multiplayer (hosting) if (result == BED_SLEEPING_OK && m_pLevel && m_pLevel->m_pRakNetInstance && m_pLevel->m_pRakNetInstance->m_bIsHost) @@ -351,9 +351,9 @@ Player::BedSleepingProblem LocalPlayer::sleep(const TilePos& pos) return result; } -void LocalPlayer::wake(bool resetCounter, bool update, bool setSpawn) +void LocalPlayer::stopSleepInBed(bool resetCounter, bool update, bool setSpawn) { - Player::wake(resetCounter, update, setSpawn); + Player::stopSleepInBed(resetCounter, update, setSpawn); // Broadcast wake animation to all clients if in multiplayer (hosting) if (m_pLevel && m_pLevel->m_pRakNetInstance && m_pLevel->m_pRakNetInstance->m_bIsHost) diff --git a/source/client/player/LocalPlayer.hpp b/source/client/player/LocalPlayer.hpp index 98bfdbe7b..b5ea7b18a 100644 --- a/source/client/player/LocalPlayer.hpp +++ b/source/client/player/LocalPlayer.hpp @@ -39,8 +39,8 @@ class LocalPlayer : public Player bool interpolateOnly() const override { return false; } void setPlayerGameType(GameType gameType) override; void swing() override; - BedSleepingProblem sleep(const TilePos& pos) override; - void wake(bool resetCounter, bool update, bool setSpawn) override; + BedSleepingProblem startSleepInBed(const TilePos& pos) override; + void stopSleepInBed(bool resetCounter, bool update, bool setSpawn) override; virtual void hurtTo(int newHealth); diff --git a/source/server/ServerPlayer.cpp b/source/server/ServerPlayer.cpp index 30718df17..38726a303 100644 --- a/source/server/ServerPlayer.cpp +++ b/source/server/ServerPlayer.cpp @@ -31,9 +31,9 @@ void ServerPlayer::take(Entity* pEnt, int count) Player::take(pEnt, count); } -Player::BedSleepingProblem ServerPlayer::sleep(const TilePos& pos) +Player::BedSleepingProblem ServerPlayer::startSleepInBed(const TilePos& pos) { - Player::BedSleepingProblem result = Player::sleep(pos); + Player::BedSleepingProblem result = Player::startSleepInBed(pos); // Broadcast position and sleep state to all clients if successful if (result == BED_SLEEPING_OK && m_pLevel && m_pLevel->m_pRakNetInstance) @@ -49,7 +49,7 @@ Player::BedSleepingProblem ServerPlayer::sleep(const TilePos& pos) return result; } -void ServerPlayer::wake(bool resetCounter, bool update, bool setSpawn) +void ServerPlayer::stopSleepInBed(bool resetCounter, bool update, bool setSpawn) { // Clear sleeping state FIRST so isImmobile() returns false m_bSleeping = false; diff --git a/source/server/ServerPlayer.hpp b/source/server/ServerPlayer.hpp index 8d3bcf8c8..d7d06a9eb 100644 --- a/source/server/ServerPlayer.hpp +++ b/source/server/ServerPlayer.hpp @@ -8,8 +8,8 @@ class ServerPlayer : public Player public: void tick() override; void take(Entity* pEnt, int count) override; - BedSleepingProblem sleep(const TilePos& pos) override; - void wake(bool resetCounter, bool update, bool setSpawn) override; + BedSleepingProblem startSleepInBed(const TilePos& pos) override; + void stopSleepInBed(bool resetCounter, bool update, bool setSpawn) override; private: int m_lastHealth; diff --git a/source/server/ServerSideNetworkHandler.cpp b/source/server/ServerSideNetworkHandler.cpp index 299513c87..819dabc88 100644 --- a/source/server/ServerSideNetworkHandler.cpp +++ b/source/server/ServerSideNetworkHandler.cpp @@ -521,7 +521,7 @@ void ServerSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, AnimatePac case AnimatePacket::WAKE: { // Client is waking up - call wake on server player - pPlayer->wake(false, true, true); + pPlayer->stopSleepInBed(false, true, true); break; } case AnimatePacket::SLEEP: diff --git a/source/world/entity/Player.cpp b/source/world/entity/Player.cpp index 3fc55cba6..ad4b83af3 100644 --- a/source/world/entity/Player.cpp +++ b/source/world/entity/Player.cpp @@ -412,7 +412,7 @@ void Player::updateSleepingPos(int direction) } } -Player::BedSleepingProblem Player::sleep(const TilePos& pos) +Player::BedSleepingProblem Player::startSleepInBed(const TilePos& pos) { if (isSleeping() || !isAlive()) return BED_SLEEPING_OTHER_PROBLEM; @@ -470,7 +470,7 @@ Player::BedSleepingProblem Player::sleep(const TilePos& pos) return BED_SLEEPING_OK; } -void Player::wake(bool resetCounter, bool update, bool setSpawn) +void Player::stopSleepInBed(bool resetCounter, bool update, bool setSpawn) { setSize(0.6f, 1.8f); setDefaultHeadHeight(); diff --git a/source/world/entity/Player.hpp b/source/world/entity/Player.hpp index 5cf40932d..e64b539f2 100644 --- a/source/world/entity/Player.hpp +++ b/source/world/entity/Player.hpp @@ -93,8 +93,8 @@ class Player : public Mob // Sleeping void setBedSleepPos(const TilePos& pos); void updateSleepingPos(int direction); - virtual BedSleepingProblem sleep(const TilePos& pos); - virtual void wake(bool resetCounter, bool update, bool setSpawn); + virtual BedSleepingProblem startSleepInBed(const TilePos& pos); + virtual void stopSleepInBed(bool resetCounter, bool update, bool setSpawn); bool isSleeping() const override { return m_bSleeping; } bool isSleepingLongEnough() const { return m_bSleeping && m_sleepTimer >= 100; } float getBedSleepRot() const; diff --git a/source/world/level/Level.cpp b/source/world/level/Level.cpp index 60e5a0d4f..fae9c18f5 100644 --- a/source/world/level/Level.cpp +++ b/source/world/level/Level.cpp @@ -1988,7 +1988,7 @@ void Level::updateSleeping() for (size_t i = 0; i < m_players.size(); i++) { Player* player = m_players[i]; if (player && player->isSleeping()) { - player->wake(false, true, true); + player->stopSleepInBed(false, true, true); } } } diff --git a/source/world/tile/BedTile.cpp b/source/world/tile/BedTile.cpp index 29b5a69e8..cca648f1a 100644 --- a/source/world/tile/BedTile.cpp +++ b/source/world/tile/BedTile.cpp @@ -129,7 +129,7 @@ bool BedTile::use(Level* level, const TilePos& pos, Player* player) setBedOccupied(level, tp, false); } - Player::BedSleepingProblem result = player->sleep(tp); + Player::BedSleepingProblem result = player->startSleepInBed(tp); if (result == Player::BED_SLEEPING_OK) { setBedOccupied(level, tp, true); return true; From 5a277af31e294435a45753a694f434a3e01ffaf1 Mon Sep 17 00:00:00 2001 From: danqzq Date: Mon, 12 Jan 2026 21:50:54 -0500 Subject: [PATCH 12/23] Replace AnimatePacket::SLEEP with InteractionPacket for handling player interactions and sleeping state synchronization --- .../Minecraft.xcodeproj/project.pbxproj | 8 ++ source/CMakeLists.txt | 1 + .../multiplayer/MultiplayerLocalPlayer.cpp | 9 +- .../multiplayer/MultiplayerLocalPlayer.hpp | 3 +- .../network/ClientSideNetworkHandler.cpp | 117 +++++++----------- .../network/ClientSideNetworkHandler.hpp | 1 + source/client/player/LocalPlayer.cpp | 18 ++- source/network/MinecraftPackets.cpp | 2 + source/network/MinecraftPackets.hpp | 1 + source/network/NetEventCallback.hpp | 1 + source/network/Packet.hpp | 1 + source/network/packets/AnimatePacket.hpp | 3 +- source/network/packets/InteractionPacket.cpp | 33 +++++ source/network/packets/InteractionPacket.hpp | 31 +++++ source/server/ServerPlayer.cpp | 15 +-- source/server/ServerSideNetworkHandler.cpp | 16 +-- source/server/ServerSideNetworkHandler.hpp | 1 + 17 files changed, 160 insertions(+), 101 deletions(-) create mode 100644 source/network/packets/InteractionPacket.cpp create mode 100644 source/network/packets/InteractionPacket.hpp diff --git a/platforms/macos/projects/Minecraft/Minecraft.xcodeproj/project.pbxproj b/platforms/macos/projects/Minecraft/Minecraft.xcodeproj/project.pbxproj index 5ca7d823e..509ab516f 100644 --- a/platforms/macos/projects/Minecraft/Minecraft.xcodeproj/project.pbxproj +++ b/platforms/macos/projects/Minecraft/Minecraft.xcodeproj/project.pbxproj @@ -1147,6 +1147,8 @@ 84F77A402EA1C01C0045C907 /* EntityEventPacket.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 84F77A162EA1C01B0045C907 /* EntityEventPacket.hpp */; }; 84F77A412EA1C01C0045C907 /* InteractPacket.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 84F77A172EA1C01C0045C907 /* InteractPacket.cpp */; }; 84F77A422EA1C01C0045C907 /* InteractPacket.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 84F77A182EA1C01C0045C907 /* InteractPacket.hpp */; }; + 84F77AA12EA1C01C0045C907 /* InteractionPacket.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 84F77AA32EA1C01C0045C907 /* InteractionPacket.cpp */; }; + 84F77AA22EA1C01C0045C907 /* InteractionPacket.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 84F77AA42EA1C01C0045C907 /* InteractionPacket.hpp */; }; 84F77A432EA1C01C0045C907 /* LevelDataPacket.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 84F77A192EA1C01C0045C907 /* LevelDataPacket.hpp */; }; 84F77A442EA1C01C0045C907 /* LevelEventPacket.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 84F77A1A2EA1C01C0045C907 /* LevelEventPacket.cpp */; }; 84F77A452EA1C01C0045C907 /* LevelEventPacket.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 84F77A1B2EA1C01C0045C907 /* LevelEventPacket.hpp */; }; @@ -2734,6 +2736,8 @@ 84F77A162EA1C01B0045C907 /* EntityEventPacket.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = EntityEventPacket.hpp; sourceTree = ""; }; 84F77A172EA1C01C0045C907 /* InteractPacket.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InteractPacket.cpp; sourceTree = ""; }; 84F77A182EA1C01C0045C907 /* InteractPacket.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = InteractPacket.hpp; sourceTree = ""; }; + 84F77AA32EA1C01C0045C907 /* InteractionPacket.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InteractionPacket.cpp; sourceTree = ""; }; + 84F77AA42EA1C01C0045C907 /* InteractionPacket.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = InteractionPacket.hpp; sourceTree = ""; }; 84F77A192EA1C01C0045C907 /* LevelDataPacket.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LevelDataPacket.hpp; sourceTree = ""; }; 84F77A1A2EA1C01C0045C907 /* LevelEventPacket.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LevelEventPacket.cpp; sourceTree = ""; }; 84F77A1B2EA1C01C0045C907 /* LevelEventPacket.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LevelEventPacket.hpp; sourceTree = ""; }; @@ -3227,6 +3231,8 @@ 84F77A162EA1C01B0045C907 /* EntityEventPacket.hpp */, 84F77A172EA1C01C0045C907 /* InteractPacket.cpp */, 84F77A182EA1C01C0045C907 /* InteractPacket.hpp */, + 84F77AA32EA1C01C0045C907 /* InteractionPacket.cpp */, + 84F77AA42EA1C01C0045C907 /* InteractionPacket.hpp */, 840DD6422AC810620006A435 /* LevelDataPacket.cpp */, 84F77A192EA1C01C0045C907 /* LevelDataPacket.hpp */, 84F77A1A2EA1C01C0045C907 /* LevelEventPacket.cpp */, @@ -5060,6 +5066,7 @@ 84F77A522EA1C01C0045C907 /* RemoveEntityPacket.hpp in Headers */, 84F77A5A2EA1C01C0045C907 /* SetTimePacket.hpp in Headers */, 84F77A422EA1C01C0045C907 /* InteractPacket.hpp in Headers */, + 84F77AA22EA1C01C0045C907 /* InteractionPacket.hpp in Headers */, 84F77A472EA1C01C0045C907 /* LoginStatusPacket.hpp in Headers */, 84F77A502EA1C01C0045C907 /* ReadyPacket.hpp in Headers */, 84F77A512EA1C01C0045C907 /* RemoveBlockPacket.hpp in Headers */, @@ -6028,6 +6035,7 @@ 84F77A3C2EA1C01C0045C907 /* AnimatePacket.cpp in Sources */, 84F77A392EA1C01C0045C907 /* AddMobPacket.cpp in Sources */, 84F77A412EA1C01C0045C907 /* InteractPacket.cpp in Sources */, + 84F77AA12EA1C01C0045C907 /* InteractionPacket.cpp in Sources */, 842610702AE989720065905F /* NetEventCallback.cpp in Sources */, 84F77A562EA1C01C0045C907 /* SetEntityDataPacket.cpp in Sources */, 842610712AE989720065905F /* AddPlayerPacket.cpp in Sources */, diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index cc67f8741..b494028f6 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -173,6 +173,7 @@ add_library(reminecraftpe-core STATIC network/packets/RequestChunkPacket.cpp network/packets/PlayerEquipmentPacket.cpp network/packets/InteractPacket.cpp + network/packets/InteractionPacket.cpp network/packets/UseItemPacket.cpp network/packets/SetEntityDataPacket.cpp network/packets/SetHealthPacket.cpp diff --git a/source/client/multiplayer/MultiplayerLocalPlayer.cpp b/source/client/multiplayer/MultiplayerLocalPlayer.cpp index 55a073c70..ba661c42e 100644 --- a/source/client/multiplayer/MultiplayerLocalPlayer.cpp +++ b/source/client/multiplayer/MultiplayerLocalPlayer.cpp @@ -88,6 +88,13 @@ void MultiplayerLocalPlayer::hurtTo(int newHealth) } } +Player::BedSleepingProblem MultiplayerLocalPlayer::startSleepInBed(const TilePos& pos) +{ + // Client players receive sleep command from server via InteractionPacket + // Just apply the sleep state locally - position will be set by MovePlayerPacket + return Player::startSleepInBed(pos); +} + void MultiplayerLocalPlayer::stopSleepInBed(bool resetCounter, bool update, bool setSpawn) { Player::stopSleepInBed(resetCounter, update, setSpawn); @@ -95,7 +102,7 @@ void MultiplayerLocalPlayer::stopSleepInBed(bool resetCounter, bool update, bool // Send wake notification to server if (m_pLevel && m_pLevel->m_pRakNetInstance) { - m_pLevel->m_pRakNetInstance->send(new AnimatePacket(m_EntityID, AnimatePacket::WAKE)); + m_pLevel->m_pRakNetInstance->send(new AnimatePacket(m_EntityID, AnimatePacket::WAKE_UP)); // Also send position update so server knows where we are after waking m_pLevel->m_pRakNetInstance->send(new MovePlayerPacket(m_EntityID, diff --git a/source/client/multiplayer/MultiplayerLocalPlayer.hpp b/source/client/multiplayer/MultiplayerLocalPlayer.hpp index c19f12f82..80140088d 100644 --- a/source/client/multiplayer/MultiplayerLocalPlayer.hpp +++ b/source/client/multiplayer/MultiplayerLocalPlayer.hpp @@ -16,8 +16,7 @@ class MultiplayerLocalPlayer : public LocalPlayer //void drop() override; void hurtTo(int newHealth) override; - // Client doesn't perform sleep validation - server handles it and sends AnimatePacket - BedSleepingProblem startSleepInBed(const TilePos& pos) override { return BED_SLEEPING_OK; } + BedSleepingProblem startSleepInBed(const TilePos& pos) override; void stopSleepInBed(bool resetCounter, bool update, bool setSpawn) override; private: diff --git a/source/client/network/ClientSideNetworkHandler.cpp b/source/client/network/ClientSideNetworkHandler.cpp index 7472d4797..09550320c 100644 --- a/source/client/network/ClientSideNetworkHandler.cpp +++ b/source/client/network/ClientSideNetworkHandler.cpp @@ -342,7 +342,17 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, MovePla return; } - pEntity->lerpTo(packet->m_pos, packet->m_rot); + if (pEntity->isPlayer() && ((Player*)pEntity)->isSleeping()) + { + pEntity->setPos(packet->m_pos); + pEntity->m_rot = packet->m_rot; + pEntity->m_oPos = packet->m_pos; + pEntity->m_posPrev = packet->m_pos; + } + else + { + pEntity->lerpTo(packet->m_pos, packet->m_rot); + } } void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, PlaceBlockPacket* pPlaceBlockPkt) @@ -586,6 +596,40 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, Interac } } +void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, InteractionPacket* pkt) +{ + puts_ignorable("InteractionPacket"); + + if (!m_pLevel) + return; + + Entity* pEntity = m_pLevel->getEntity(pkt->m_entityId); + if (!pEntity || !pEntity->isPlayer()) + return; + + Player* pPlayer = (Player*)pEntity; + + if (pkt->m_actionType == InteractionPacket::SLEEP) + { + pPlayer->m_bSleeping = true; + pPlayer->m_sleepTimer = 0; + pPlayer->setBedSleepPos(pkt->m_pos); + pPlayer->m_vel = Vec3::ZERO; + pPlayer->setSize(0.2f, 0.2f); + pPlayer->m_heightOffset = 0.2f; + + // Update bed direction for rendering + if (!m_pLevel->isEmptyTile(pkt->m_pos)) + { + TileData data = m_pLevel->getData(pkt->m_pos); + int dir = BedTile::getDirectionFromData(data); + pPlayer->updateSleepingPos(dir); + } + + m_pLevel->updateSleeping(); + } +} + void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, SetEntityDataPacket* pkt) { puts_ignorable("SetEntityDataPacket"); @@ -640,7 +684,7 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, Animate pEntity->animateHurt(); break; } - case AnimatePacket::WAKE: + case AnimatePacket::WAKE_UP: { if (pEntity->isPlayer()) { @@ -654,75 +698,6 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, Animate } break; } - case AnimatePacket::SLEEP: - { - if (pEntity->isPlayer()) - { - Player* pPlayer = (Player*)pEntity; - - // Find the bed tile at the player's position - if (m_pLevel && Tile::bed) - { - // Use lerp target position if available (MovePlayerPacket contains exact bed position) - // The server sends bedPos + 0.5, so floor gives us exact tile coords - Vec3 searchPos = pPlayer->m_pos; - if (pPlayer->m_lSteps > 0) - { - searchPos = pPlayer->m_lPos; - searchPos.y -= pPlayer->m_heightOffset; - } - - // The server sends the bed center (tile + 0.5), so floor to get exact tile - TilePos bedPos(Mth::floor(searchPos.x), Mth::floor(searchPos.y), Mth::floor(searchPos.z)); - - // First check the exact position - this is where the server said the bed is - if (bedPos.y >= 0 && bedPos.y < 128 && - m_pLevel->getTile(bedPos) == Tile::bed->m_ID) - { - TileData data = m_pLevel->getData(bedPos); - int dir = BedTile::getDirectionFromData(data); - - float xOff = 0.5f; - float zOff = 0.5f; - switch (dir) { - case 0: - zOff = 0.9f; - break; - case 1: - xOff = 0.1f; - break; - case 2: - zOff = 0.1f; - break; - case 3: - xOff = 0.9f; - break; - } - - pPlayer->setSize(0.2f, 0.2f); - pPlayer->m_heightOffset = 0.2f; - pPlayer->updateSleepingPos(dir); - pPlayer->setPos(Vec3(bedPos.x + xOff, bedPos.y + 15.0f / 16.0f, bedPos.z + zOff)); - pPlayer->m_bSleeping = true; - pPlayer->m_sleepTimer = 0; - pPlayer->setBedSleepPos(bedPos); - pPlayer->m_vel = Vec3::ZERO; - pPlayer->m_lSteps = 0; - } - else - { - pPlayer->m_bSleeping = true; - pPlayer->m_sleepTimer = 0; - } - } - else - { - pPlayer->m_bSleeping = true; - pPlayer->m_sleepTimer = 0; - } - } - break; - } default: { LOG_W("Received unkown action in AnimatePacket: %d, EntityType: %s", pkt->m_actionId, pEntity->getDescriptor().getEntityType().getName().c_str()); diff --git a/source/client/network/ClientSideNetworkHandler.hpp b/source/client/network/ClientSideNetworkHandler.hpp index 1b90e3b75..2c1ff1d0c 100644 --- a/source/client/network/ClientSideNetworkHandler.hpp +++ b/source/client/network/ClientSideNetworkHandler.hpp @@ -52,6 +52,7 @@ class ClientSideNetworkHandler : public NetEventCallback void handle(const RakNet::RakNetGUID&, ChunkDataPacket*) override; void handle(const RakNet::RakNetGUID&, PlayerEquipmentPacket*) override; void handle(const RakNet::RakNetGUID&, InteractPacket*) override; + void handle(const RakNet::RakNetGUID&, InteractionPacket*) override; void handle(const RakNet::RakNetGUID&, SetEntityDataPacket*) override; void handle(const RakNet::RakNetGUID&, SetHealthPacket*) override; void handle(const RakNet::RakNetGUID&, AnimatePacket*) override; diff --git a/source/client/player/LocalPlayer.cpp b/source/client/player/LocalPlayer.cpp index d7f114167..0a6782332 100644 --- a/source/client/player/LocalPlayer.cpp +++ b/source/client/player/LocalPlayer.cpp @@ -333,19 +333,15 @@ Player::BedSleepingProblem LocalPlayer::startSleepInBed(const TilePos& pos) { Player::BedSleepingProblem result = Player::startSleepInBed(pos); - // Broadcast position and sleep state to all clients if in multiplayer (hosting) + // Broadcast sleep state to all clients if in multiplayer (hosting) if (result == BED_SLEEPING_OK && m_pLevel && m_pLevel->m_pRakNetInstance && m_pLevel->m_pRakNetInstance->m_bIsHost) { - // Send bed position so remote clients know exactly where the bed is - // Use the actual bed position, not interpolated player position - Vec3 bedPos(float(pos.x) + 0.5f, float(pos.y) + 0.5f, float(pos.z) + 0.5f); - MovePlayerPacket movePacket(m_EntityID, bedPos, m_rot); - RakNet::BitStream bs; - movePacket.write(bs); - m_pLevel->m_pRakNetInstance->getPeer()->Send(&bs, HIGH_PRIORITY, RELIABLE_ORDERED, 0, m_guid, true); + // Send sleep interaction with the bed tile position + // Remote clients will handle positioning based on this + m_pLevel->m_pRakNetInstance->send(new InteractionPacket(m_EntityID, 0, pos)); - // Then send sleep animation to everyone - m_pLevel->m_pRakNetInstance->send(new AnimatePacket(m_EntityID, AnimatePacket::SLEEP)); + // Then send actual player position after startSleepInBed positioned them + m_pLevel->m_pRakNetInstance->send(new MovePlayerPacket(m_EntityID, m_pos, m_rot)); } return result; @@ -358,6 +354,6 @@ void LocalPlayer::stopSleepInBed(bool resetCounter, bool update, bool setSpawn) // Broadcast wake animation to all clients if in multiplayer (hosting) if (m_pLevel && m_pLevel->m_pRakNetInstance && m_pLevel->m_pRakNetInstance->m_bIsHost) { - m_pLevel->m_pRakNetInstance->send(new AnimatePacket(m_EntityID, AnimatePacket::WAKE)); + m_pLevel->m_pRakNetInstance->send(new AnimatePacket(m_EntityID, AnimatePacket::WAKE_UP)); } } \ No newline at end of file diff --git a/source/network/MinecraftPackets.cpp b/source/network/MinecraftPackets.cpp index 411dcce72..bf0d1fd4f 100644 --- a/source/network/MinecraftPackets.cpp +++ b/source/network/MinecraftPackets.cpp @@ -66,6 +66,8 @@ Packet* MinecraftPackets::createPacket(int type) return new SetHealthPacket; case PACKET_ANIMATE: return new AnimatePacket; + case PACKET_INTERACTION: + return new InteractionPacket; case PACKET_RESPAWN: return new RespawnPacket; diff --git a/source/network/MinecraftPackets.hpp b/source/network/MinecraftPackets.hpp index 600b8d187..ff0756298 100644 --- a/source/network/MinecraftPackets.hpp +++ b/source/network/MinecraftPackets.hpp @@ -32,6 +32,7 @@ #include "packets/ChunkDataPacket.hpp" #include "packets/PlayerEquipmentPacket.hpp" #include "packets/InteractPacket.hpp" +#include "packets/InteractionPacket.hpp" #include "packets/UseItemPacket.hpp" #include "packets/SetEntityDataPacket.hpp" #include "packets/SetHealthPacket.hpp" diff --git a/source/network/NetEventCallback.hpp b/source/network/NetEventCallback.hpp index b3af86a68..7678567b1 100644 --- a/source/network/NetEventCallback.hpp +++ b/source/network/NetEventCallback.hpp @@ -50,6 +50,7 @@ class NetEventCallback virtual void handle(const RakNet::RakNetGUID&, ChunkDataPacket*) {} virtual void handle(const RakNet::RakNetGUID&, PlayerEquipmentPacket*) {} virtual void handle(const RakNet::RakNetGUID&, InteractPacket*) {} + virtual void handle(const RakNet::RakNetGUID&, InteractionPacket*) {} virtual void handle(const RakNet::RakNetGUID&, UseItemPacket*) {} virtual void handle(const RakNet::RakNetGUID&, SetEntityDataPacket*) {} virtual void handle(const RakNet::RakNetGUID&, SetHealthPacket*) {} diff --git a/source/network/Packet.hpp b/source/network/Packet.hpp index e49913bdb..3e2de5288 100644 --- a/source/network/Packet.hpp +++ b/source/network/Packet.hpp @@ -92,6 +92,7 @@ enum ePacketType PACKET_SET_ENTITY_DATA, PACKET_SET_HEALTH, PACKET_ANIMATE, + PACKET_INTERACTION, PACKET_RESPAWN, PACKET_LEVEL_DATA = 200 diff --git a/source/network/packets/AnimatePacket.hpp b/source/network/packets/AnimatePacket.hpp index 62d19afb5..85699e790 100644 --- a/source/network/packets/AnimatePacket.hpp +++ b/source/network/packets/AnimatePacket.hpp @@ -10,8 +10,7 @@ class AnimatePacket : public Packet NONE, SWING, HURT, - WAKE, - SLEEP + WAKE_UP }; public: diff --git a/source/network/packets/InteractionPacket.cpp b/source/network/packets/InteractionPacket.cpp new file mode 100644 index 000000000..c50213cdf --- /dev/null +++ b/source/network/packets/InteractionPacket.cpp @@ -0,0 +1,33 @@ +#include "InteractionPacket.hpp" +#include "../NetEventCallback.hpp" + +InteractionPacket::InteractionPacket(int32_t entityId, int8_t actionType, const TilePos& pos) +{ + m_entityId = entityId; + m_actionType = actionType; + m_pos = pos; +} + +void InteractionPacket::handle(const RakNet::RakNetGUID& guid, NetEventCallback& callback) +{ + callback.handle(guid, this); +} + +void InteractionPacket::write(RakNet::BitStream& bs) +{ + bs.Write((unsigned char)PACKET_INTERACTION); + bs.Write(m_entityId); + bs.Write(m_pos.x); + bs.Write(m_pos.z); + bs.Write(m_pos.y); + bs.Write(m_actionType); +} + +void InteractionPacket::read(RakNet::BitStream& bs) +{ + bs.Read(m_entityId); + bs.Read(m_pos.x); + bs.Read(m_pos.z); + bs.Read(m_pos.y); + bs.Read(m_actionType); +} diff --git a/source/network/packets/InteractionPacket.hpp b/source/network/packets/InteractionPacket.hpp new file mode 100644 index 000000000..9ca601b47 --- /dev/null +++ b/source/network/packets/InteractionPacket.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "../Packet.hpp" +#include "world/phys/Vec3.hpp" +#include "world/tile/Tile.hpp" + +class InteractionPacket : public Packet +{ +public: + enum ActionType + { + SLEEP = 0 + }; + +public: + InteractionPacket() + { + m_entityId = 0; + m_actionType = SLEEP; + m_pos = TilePos(0, 0, 0); + } + InteractionPacket(int32_t entityId, int8_t actionType, const TilePos& pos); + + void handle(const RakNet::RakNetGUID&, NetEventCallback& callback) override; + void write(RakNet::BitStream&) override; + void read(RakNet::BitStream&) override; +public: + int32_t m_entityId; + int8_t m_actionType; + TilePos m_pos; +}; diff --git a/source/server/ServerPlayer.cpp b/source/server/ServerPlayer.cpp index 38726a303..dc0911141 100644 --- a/source/server/ServerPlayer.cpp +++ b/source/server/ServerPlayer.cpp @@ -3,6 +3,7 @@ #include "network/packets/TakeItemEntityPacket.hpp" #include "network/packets/AnimatePacket.hpp" #include "network/packets/MovePlayerPacket.hpp" +#include "network/packets/InteractionPacket.hpp" #include "network/RakNetInstance.hpp" #include "world/level/Level.hpp" #include "world/tile/BedTile.hpp" @@ -35,15 +36,15 @@ Player::BedSleepingProblem ServerPlayer::startSleepInBed(const TilePos& pos) { Player::BedSleepingProblem result = Player::startSleepInBed(pos); - // Broadcast position and sleep state to all clients if successful + // Broadcast sleep state to all clients if successful if (result == BED_SLEEPING_OK && m_pLevel && m_pLevel->m_pRakNetInstance) { - // Send position update to ALL clients including self so they can find the bed - // The position is the bed tile center - m_pLevel->m_pRakNetInstance->send(new MovePlayerPacket(m_EntityID, Vec3(m_pos.x, m_pos.y - m_heightOffset, m_pos.z), m_rot)); + // Send sleep interaction with the bed tile position + // Remote clients will handle positioning based on this + m_pLevel->m_pRakNetInstance->send(new InteractionPacket(m_EntityID, 0, pos)); - // Then send sleep animation to everyone - m_pLevel->m_pRakNetInstance->send(new AnimatePacket(m_EntityID, AnimatePacket::SLEEP)); + // Then send actual player position after startSleepInBed positioned them + m_pLevel->m_pRakNetInstance->send(new MovePlayerPacket(m_EntityID, m_pos, m_rot)); } return result; @@ -76,6 +77,6 @@ void ServerPlayer::stopSleepInBed(bool resetCounter, bool update, bool setSpawn) // Broadcast wake animation to all clients if (m_pLevel && m_pLevel->m_pRakNetInstance) { - m_pLevel->m_pRakNetInstance->send(new AnimatePacket(m_EntityID, AnimatePacket::WAKE)); + m_pLevel->m_pRakNetInstance->send(new AnimatePacket(m_EntityID, AnimatePacket::WAKE_UP)); } } diff --git a/source/server/ServerSideNetworkHandler.cpp b/source/server/ServerSideNetworkHandler.cpp index 819dabc88..fa44b32c1 100644 --- a/source/server/ServerSideNetworkHandler.cpp +++ b/source/server/ServerSideNetworkHandler.cpp @@ -432,6 +432,14 @@ void ServerSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, InteractPa redistributePacket(packet, guid); } +void ServerSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, InteractionPacket* packet) +{ + // InteractionPacket is broadcast-only, used to synchronize actions like sleeping + // The actual action is triggered via UseItemPacket on the server + // We just redistribute it to other clients + redistributePacket(packet, guid); +} + void ServerSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, UseItemPacket* packet) { //puts_ignorable("UseItemPacket"); @@ -518,18 +526,12 @@ void ServerSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, AnimatePac pPlayer->animateHurt(); break; } - case AnimatePacket::WAKE: + case AnimatePacket::WAKE_UP: { // Client is waking up - call wake on server player pPlayer->stopSleepInBed(false, true, true); break; } - case AnimatePacket::SLEEP: - { - // Client sleeping is handled via UseItemPacket on bed tile - // This is just for forwarding to other clients - break; - } default: { LOG_W("Received unkown action in AnimatePacket: %d, EntityType: %s", packet->m_actionId, pEntity->getDescriptor().getEntityType().getName().c_str()); diff --git a/source/server/ServerSideNetworkHandler.hpp b/source/server/ServerSideNetworkHandler.hpp index 09319b6c1..bb1e8a0fc 100644 --- a/source/server/ServerSideNetworkHandler.hpp +++ b/source/server/ServerSideNetworkHandler.hpp @@ -56,6 +56,7 @@ class ServerSideNetworkHandler : public NetEventCallback, public LevelListener void handle(const RakNet::RakNetGUID&, RemoveBlockPacket*) override; void handle(const RakNet::RakNetGUID&, PlayerEquipmentPacket*) override; void handle(const RakNet::RakNetGUID&, InteractPacket*) override; + void handle(const RakNet::RakNetGUID&, InteractionPacket*) override; void handle(const RakNet::RakNetGUID&, UseItemPacket*) override; void handle(const RakNet::RakNetGUID&, RequestChunkPacket*) override; void handle(const RakNet::RakNetGUID&, AnimatePacket*) override; From 1ec5a638841cb3b2d1331bc50bfda559d901b3e4 Mon Sep 17 00:00:00 2001 From: danqzq Date: Mon, 12 Jan 2026 21:56:40 -0500 Subject: [PATCH 13/23] Add bed destruction handling to wake sleeping players --- source/world/level/Level.cpp | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/source/world/level/Level.cpp b/source/world/level/Level.cpp index fae9c18f5..19732df8e 100644 --- a/source/world/level/Level.cpp +++ b/source/world/level/Level.cpp @@ -16,6 +16,7 @@ #include "network/packets/EntityEventPacket.hpp" #include "network/packets/SetEntityDataPacket.hpp" #include "world/level/levelgen/chunk/ChunkCache.hpp" +#include "world/tile/BedTile.hpp" #include "Explosion.hpp" #include "Region.hpp" @@ -689,6 +690,41 @@ bool Level::setTileAndData(const TilePos& pos, TileID tile, TileData data, TileC if (change.isUpdateNeighbors()) oldTile = pChunk->getTile(pos); + // Check if a bed is being destroyed - wake up any sleeping players + if (oldTile == Tile::bed->m_ID && tile != Tile::bed->m_ID) + { + TilePos bedHeadPos = pos; + TileData bedData = getData(pos); + if (BedTile::isHead(bedData)) + { + bedHeadPos = pos; + } + else + { + // This is the foot part, find the head + int dir = BedTile::getDirectionFromData(bedData); + switch (dir) + { + case 0: bedHeadPos = pos.south(); break; + case 1: bedHeadPos = pos.west(); break; + case 2: bedHeadPos = pos.north(); break; + case 3: bedHeadPos = pos.east(); break; + } + } + + for (size_t i = 0; i < m_players.size(); i++) + { + Player* player = m_players[i]; + if (player && player->isSleeping() && player->m_bHasBedSleepPos) + { + if (player->m_bedSleepPos == pos || player->m_bedSleepPos == bedHeadPos) + { + player->stopSleepInBed(false, true, false); + } + } + } + } + bool result = pChunk->setTileAndData(pos, tile, data); if (result) { From ad17095e2d4058c94077eb7070852b9680c56eda Mon Sep 17 00:00:00 2001 From: danqzq Date: Mon, 12 Jan 2026 22:38:38 -0500 Subject: [PATCH 14/23] Refactor bed direction handling to use Facing::Name enum for improved clarity and consistency --- .../network/ClientSideNetworkHandler.cpp | 2 +- source/client/renderer/TileRenderer.cpp | 20 ++++++----- source/world/entity/Player.cpp | 36 ++++++++++--------- source/world/entity/Player.hpp | 2 +- source/world/level/Level.cpp | 11 +++--- source/world/tile/BedTile.cpp | 10 +++--- source/world/tile/BedTile.hpp | 14 +++++++- 7 files changed, 57 insertions(+), 38 deletions(-) diff --git a/source/client/network/ClientSideNetworkHandler.cpp b/source/client/network/ClientSideNetworkHandler.cpp index 09550320c..4d4aa5b5b 100644 --- a/source/client/network/ClientSideNetworkHandler.cpp +++ b/source/client/network/ClientSideNetworkHandler.cpp @@ -622,7 +622,7 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, Interac if (!m_pLevel->isEmptyTile(pkt->m_pos)) { TileData data = m_pLevel->getData(pkt->m_pos); - int dir = BedTile::getDirectionFromData(data); + Facing::Name dir = BedTile::getDirectionFromData(data); pPlayer->updateSleepingPos(dir); } diff --git a/source/client/renderer/TileRenderer.cpp b/source/client/renderer/TileRenderer.cpp index 9dd5ca6b2..08672572e 100644 --- a/source/client/renderer/TileRenderer.cpp +++ b/source/client/renderer/TileRenderer.cpp @@ -1205,7 +1205,6 @@ bool TileRenderer::tesselateBedInWorld(Tile* tile, const TilePos& pos) Tesselator& t = Tesselator::instance; TileData data = m_pTileSource->getData(pos); - int direction = BedTile::getDirectionFromData(data); // Brightness factors for faces float fBrightDown = 0.5f; @@ -1241,20 +1240,24 @@ bool TileRenderer::tesselateBedInWorld(Tile* tile, const TilePos& pos) bDrewAnything = true; } + Facing::Name direction = BedTile::getDirectionFromData(data); Facing::Name flippedFace = Facing::WEST; switch (direction) { - case 0: // +Z facing + case Facing::SOUTH: flippedFace = Facing::EAST; break; - case 1: // -X facing + case Facing::WEST: flippedFace = Facing::SOUTH; break; - case 2: // -Z facing - // flippedFace = WEST + case Facing::NORTH: + flippedFace = Facing::WEST; break; - case 3: // +X facing + case Facing::EAST: flippedFace = Facing::NORTH; break; + default: + flippedFace = Facing::WEST; + break; } float fBright; @@ -1354,9 +1357,10 @@ bool TileRenderer::tesselateBedInWorld(Tile* tile, const TilePos& pos) faces[3].renderFunc = &TileRenderer::renderEast; // Determine which face to hide (the connecting face between head and foot) - Facing::Name hiddenFace = (Facing::Name)BedTile::hiddenFace[direction]; + int dirIndex = BedTile::getDirectionIndex(data); + Facing::Name hiddenFace = (Facing::Name)BedTile::hiddenFace[dirIndex]; if (BedTile::isHead(data)) { - hiddenFace = (Facing::Name)BedTile::hiddenFace[BedTile::hiddenFaceIndex[direction]]; + hiddenFace = (Facing::Name)BedTile::hiddenFace[BedTile::hiddenFaceIndex[dirIndex]]; } for (int i = 0; i < 4; i++) { diff --git a/source/world/entity/Player.cpp b/source/world/entity/Player.cpp index ad4b83af3..39f6cdf96 100644 --- a/source/world/entity/Player.cpp +++ b/source/world/entity/Player.cpp @@ -391,22 +391,22 @@ void Player::setBedSleepPos(const TilePos& pos) m_bedSleepPos = pos; } -void Player::updateSleepingPos(int direction) +void Player::updateSleepingPos(Facing::Name direction) { m_sleepingPos.x = 0.0f; m_sleepingPos.y = 0.0f; m_sleepingPos.z = 0.0f; switch (direction) { - case 0: // +Z facing + case Facing::SOUTH: // +Z facing m_sleepingPos.z = -1.8f; break; - case 1: // -X facing + case Facing::WEST: // -X facing m_sleepingPos.x = 1.8f; break; - case 2: // -Z facing + case Facing::NORTH: // -Z facing m_sleepingPos.z = 1.8f; break; - case 3: // +X facing + default: // +X facing m_sleepingPos.x = -1.8f; break; } @@ -436,28 +436,30 @@ Player::BedSleepingProblem Player::startSleepInBed(const TilePos& pos) if (!m_pLevel->isEmptyTile(pos)) { TileData data = m_pLevel->getData(pos); - int dir = BedTile::getDirectionFromData(data); + Facing::Name dir = BedTile::getDirectionFromData(data); float xOff = 0.5f; float zOff = 0.5f; switch (dir) { - case 0: + case Facing::SOUTH: zOff = 0.9f; break; - case 1: + case Facing::WEST: xOff = 0.1f; break; - case 2: + case Facing::NORTH: zOff = 0.1f; break; - case 3: + case Facing::EAST: xOff = 0.9f; break; + default: + break; } updateSleepingPos(dir); - setPos(Vec3(pos.x + xOff, pos.y + 15.0f / 16.0f, pos.z + zOff)); + setPos(Vec3(pos.x + xOff, pos.y + 1.1f, pos.z + zOff)); } else { - setPos(Vec3(pos.x + 0.5f, pos.y + 15.0f / 16.0f, pos.z + 0.5f)); + setPos(Vec3(pos.x + 0.5f, pos.y + 1.1f, pos.z + 0.5f)); } m_bSleeping = true; @@ -504,16 +506,16 @@ float Player::getBedSleepRot() const return 0.0f; TileData data = m_pLevel->getData(m_bedSleepPos); - int dir = BedTile::getDirectionFromData(data); + Facing::Name dir = BedTile::getDirectionFromData(data); switch (dir) { - case 0: + case Facing::SOUTH: return 90.0f; - case 1: + case Facing::WEST: return 0.0f; - case 2: + case Facing::NORTH: return 270.0f; - case 3: + case Facing::EAST: return 180.0f; default: return 0.0f; diff --git a/source/world/entity/Player.hpp b/source/world/entity/Player.hpp index e64b539f2..f070428d6 100644 --- a/source/world/entity/Player.hpp +++ b/source/world/entity/Player.hpp @@ -92,7 +92,7 @@ class Player : public Mob // Sleeping void setBedSleepPos(const TilePos& pos); - void updateSleepingPos(int direction); + void updateSleepingPos(Facing::Name direction); virtual BedSleepingProblem startSleepInBed(const TilePos& pos); virtual void stopSleepInBed(bool resetCounter, bool update, bool setSpawn); bool isSleeping() const override { return m_bSleeping; } diff --git a/source/world/level/Level.cpp b/source/world/level/Level.cpp index 19732df8e..fc3d37646 100644 --- a/source/world/level/Level.cpp +++ b/source/world/level/Level.cpp @@ -702,13 +702,14 @@ bool Level::setTileAndData(const TilePos& pos, TileID tile, TileData data, TileC else { // This is the foot part, find the head - int dir = BedTile::getDirectionFromData(bedData); + Facing::Name dir = BedTile::getDirectionFromData(bedData); switch (dir) { - case 0: bedHeadPos = pos.south(); break; - case 1: bedHeadPos = pos.west(); break; - case 2: bedHeadPos = pos.north(); break; - case 3: bedHeadPos = pos.east(); break; + case Facing::SOUTH: bedHeadPos = pos.south(); break; + case Facing::WEST: bedHeadPos = pos.west(); break; + case Facing::NORTH: bedHeadPos = pos.north(); break; + case Facing::EAST: bedHeadPos = pos.east(); break; + default: bedHeadPos = pos.east(); break; } } diff --git a/source/world/tile/BedTile.cpp b/source/world/tile/BedTile.cpp index cca648f1a..3a76b32e2 100644 --- a/source/world/tile/BedTile.cpp +++ b/source/world/tile/BedTile.cpp @@ -25,7 +25,7 @@ BedTile::BedTile(TileID id, int texture) : Tile(id, texture, Material::cloth) int BedTile::getTexture(Facing::Name face, TileData data) const { if (face != Facing::DOWN && face != Facing::UP) { - int var3 = getDirectionFromData(data); + int var3 = getDirectionIndex(data); int var4 = bedDirection[var3][face]; return isHead(data) ? (var4 == 2 ? m_TextureFrame + 2 + 16 : (var4 != 5 && var4 != 4 ? m_TextureFrame + 1 : m_TextureFrame + 1 + 16)) : (var4 == 3 ? m_TextureFrame - 1 + 16 : (var4 != 5 && var4 != 4 ? m_TextureFrame : m_TextureFrame + 16)); } @@ -62,7 +62,7 @@ eRenderShape BedTile::getRenderShape() const void BedTile::neighborChanged(Level* level, const TilePos& pos, TileID tile) { TileData data = level->getData(pos); - int dir = getDirectionFromData(data); + int dir = getDirectionIndex(data); if (isHead(data)) { TilePos footPos(pos.x - headBlockToFootBlockMap[dir][0], pos.y, pos.z - headBlockToFootBlockMap[dir][1]); @@ -91,7 +91,7 @@ bool BedTile::use(Level* level, const TilePos& pos, Player* player) TilePos tp(pos); if (!isHead(data)) { - int dir = getDirectionFromData(data); + int dir = getDirectionIndex(data); tp.x += headBlockToFootBlockMap[dir][0]; tp.z += headBlockToFootBlockMap[dir][1]; if (level->getTile(tp) != m_ID) { @@ -102,7 +102,7 @@ bool BedTile::use(Level* level, const TilePos& pos, Player* player) if (!level->m_pDimension->mayRespawn()) { level->setTile(tp, TILE_AIR); - int dir = getDirectionFromData(data); + int dir = getDirectionIndex(data); TilePos footPos(tp); footPos.x -= headBlockToFootBlockMap[dir][0]; footPos.z -= headBlockToFootBlockMap[dir][1]; @@ -168,7 +168,7 @@ void BedTile::setBedOccupied(Level* level, const TilePos& pos, bool occupied) TilePos BedTile::getRespawnTilePos(const Level* level, const TilePos& pos, int steps) { TileData data = level->getData(pos); - int dir = getDirectionFromData(data); + int dir = getDirectionIndex(data); for (int i = 0; i <= 1; ++i) { int startX = pos.x - headBlockToFootBlockMap[dir][0] * i - 1; diff --git a/source/world/tile/BedTile.hpp b/source/world/tile/BedTile.hpp index 2d24613d0..4c26b978a 100644 --- a/source/world/tile/BedTile.hpp +++ b/source/world/tile/BedTile.hpp @@ -31,10 +31,22 @@ class BedTile : public Tile static const int hiddenFaceIndex[4]; static const int bedDirection[4][6]; - static int getDirectionFromData(TileData meta) { + // Returns the direction index (0-3) for array lookups + static int getDirectionIndex(TileData meta) { return meta & 3; } + // Returns the semantic direction as Facing::Name enum + static Facing::Name getDirectionFromData(TileData meta) { + switch (meta & 3) { + case 0: return Facing::SOUTH; + case 1: return Facing::WEST; + case 2: return Facing::NORTH; + case 3: return Facing::EAST; + default: return Facing::SOUTH; + } + } + static bool isHead(TileData meta) { return (meta & 8) != 0; } From 8fb2a12f55099ed0368e06005baddb0e16d3f67d Mon Sep 17 00:00:00 2001 From: danqzq Date: Mon, 12 Jan 2026 22:40:32 -0500 Subject: [PATCH 15/23] Update multiplayer check to use m_bIsClientSide --- source/world/gamemode/GameMode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/world/gamemode/GameMode.cpp b/source/world/gamemode/GameMode.cpp index eabe1f3ec..e75ed1423 100644 --- a/source/world/gamemode/GameMode.cpp +++ b/source/world/gamemode/GameMode.cpp @@ -153,7 +153,7 @@ bool GameMode::useItemOn(Player* player, Level* level, ItemInstance* instance, c bool success = false; bool isBed = (tile > 0 && Tile::tiles[tile] == Tile::bed); - bool isMultiplayerClient = (level->m_pRakNetInstance != nullptr && !level->m_pRakNetInstance->m_bIsHost); + bool isMultiplayerClient = level->m_bIsClientSide; if (tile > 0 && Tile::tiles[tile]->use(level, pos, player)) { From 1ba53fdde9eeaf378107ca902eb7a7a4fe13d10d Mon Sep 17 00:00:00 2001 From: danqzq Date: Mon, 12 Jan 2026 22:44:35 -0500 Subject: [PATCH 16/23] Remove displayClientMessage usage in BedTile.cpp - no localization at the moment --- source/world/tile/BedTile.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/source/world/tile/BedTile.cpp b/source/world/tile/BedTile.cpp index 3a76b32e2..007e1c31f 100644 --- a/source/world/tile/BedTile.cpp +++ b/source/world/tile/BedTile.cpp @@ -121,7 +121,7 @@ bool BedTile::use(Level* level, const TilePos& pos, Player* player) Player* p = level->m_players[i]; if (p->isSleeping() && p->m_bHasBedSleepPos) { if (p->m_bedSleepPos == tp) { - player->displayClientMessage("tile.bed.occupied"); + // Bed is occupied by another player return true; } } @@ -135,8 +135,10 @@ bool BedTile::use(Level* level, const TilePos& pos, Player* player) return true; } else { - if (result == Player::BED_SLEEPING_NOT_POSSIBLE_NOW) - player->displayClientMessage("tile.bed.noSleep"); + if (result == Player::BED_SLEEPING_NOT_POSSIBLE_NOW) { + // Localization is to be implemented + // player->displayClientMessage("tile.bed.noSleep"); + } return true; } From fa519f7baf195e7bdf280399a87950231d30698e Mon Sep 17 00:00:00 2001 From: danqzq Date: Mon, 12 Jan 2026 22:45:56 -0500 Subject: [PATCH 17/23] Remove license header comments from BedItem and BedTile files --- source/world/item/BedItem.cpp | 8 -------- source/world/item/BedItem.hpp | 8 -------- source/world/tile/BedTile.cpp | 8 -------- source/world/tile/BedTile.hpp | 8 -------- 4 files changed, 32 deletions(-) diff --git a/source/world/item/BedItem.cpp b/source/world/item/BedItem.cpp index 3955de964..fd87ead1d 100644 --- a/source/world/item/BedItem.cpp +++ b/source/world/item/BedItem.cpp @@ -1,11 +1,3 @@ -/******************************************************************** - Minecraft: Pocket Edition - Decompilation Project - Copyright (C) 2023 iProgramInCpp - - The following code is licensed under the BSD 1 clause license. - SPDX-License-Identifier: BSD-1-Clause - ********************************************************************/ - #include "BedItem.hpp" #include "world/level/Level.hpp" #include "world/entity/Player.hpp" diff --git a/source/world/item/BedItem.hpp b/source/world/item/BedItem.hpp index b6fa1e722..88ee86966 100644 --- a/source/world/item/BedItem.hpp +++ b/source/world/item/BedItem.hpp @@ -1,11 +1,3 @@ -/******************************************************************** - Minecraft: Pocket Edition - Decompilation Project - Copyright (C) 2023 iProgramInCpp - - The following code is licensed under the BSD 1 clause license. - SPDX-License-Identifier: BSD-1-Clause - ********************************************************************/ - #pragma once #include "Item.hpp" diff --git a/source/world/tile/BedTile.cpp b/source/world/tile/BedTile.cpp index 007e1c31f..88267b534 100644 --- a/source/world/tile/BedTile.cpp +++ b/source/world/tile/BedTile.cpp @@ -1,11 +1,3 @@ -/******************************************************************** - Minecraft: Pocket Edition - Decompilation Project - Copyright (C) 2023 iProgramInCpp - - The following code is licensed under the BSD 1 clause license. - SPDX-License-Identifier: BSD-1-Clause - ********************************************************************/ - #include "BedTile.hpp" #include "world/level/Level.hpp" #include "world/entity/Player.hpp" diff --git a/source/world/tile/BedTile.hpp b/source/world/tile/BedTile.hpp index 4c26b978a..7d68abde5 100644 --- a/source/world/tile/BedTile.hpp +++ b/source/world/tile/BedTile.hpp @@ -1,11 +1,3 @@ -/******************************************************************** - Minecraft: Pocket Edition - Decompilation Project - Copyright (C) 2023 iProgramInCpp - - The following code is licensed under the BSD 1 clause license. - SPDX-License-Identifier: BSD-1-Clause - ********************************************************************/ - #pragma once #include "Tile.hpp" From 931a1cc728c667f9b042e4d642ba2c39711f1b8e Mon Sep 17 00:00:00 2001 From: danqzq Date: Tue, 13 Jan 2026 00:06:39 -0500 Subject: [PATCH 18/23] Remove client-side bed spawn validation --- .../network/ClientSideNetworkHandler.cpp | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/source/client/network/ClientSideNetworkHandler.cpp b/source/client/network/ClientSideNetworkHandler.cpp index 4d4aa5b5b..7249a014d 100644 --- a/source/client/network/ClientSideNetworkHandler.cpp +++ b/source/client/network/ClientSideNetworkHandler.cpp @@ -713,35 +713,8 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, RespawnPac if (!m_pLevel) return; - // Find the player to check their bed spawn status - Player* pPlayer = nullptr; - if (packet->m_entityId != -1) - { - Entity* pEntity = m_pLevel->getEntity(packet->m_entityId); - if (pEntity && pEntity->isPlayer()) - pPlayer = (Player*)pEntity; - } - - // Check if bed is valid - bool hadBedSpawn = pPlayer && pPlayer->m_bHasRespawnPos; - TilePos respawnPos, checkedPos; - bool bedValid = false; - - if (hadBedSpawn) - { - respawnPos = pPlayer->m_respawnPos; - checkedPos = Player::checkRespawnPos(m_pLevel, respawnPos); - bedValid = (checkedPos != respawnPos); - } - // Call base handler to perform the respawn NetEventCallback::handle(*m_pLevel, guid, packet); - - // Show message if bed spawn failed and this is the local player - if (hadBedSpawn && !bedValid && pPlayer && pPlayer->isLocalPlayer()) - { - m_pMinecraft->m_pGui->addMessage("Your home bed was missing or obstructed"); - } } void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, LevelDataPacket* packet) From 0322c307b471654f1152929e879e6ce5cda16866 Mon Sep 17 00:00:00 2001 From: danqzq Date: Tue, 13 Jan 2026 00:10:46 -0500 Subject: [PATCH 19/23] Rename getEntityOrLocalPlayer to _getEntityOrLocalPlayer --- source/client/network/ClientSideNetworkHandler.cpp | 6 +++--- source/client/network/ClientSideNetworkHandler.hpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/client/network/ClientSideNetworkHandler.cpp b/source/client/network/ClientSideNetworkHandler.cpp index 7249a014d..328021e52 100644 --- a/source/client/network/ClientSideNetworkHandler.cpp +++ b/source/client/network/ClientSideNetworkHandler.cpp @@ -41,7 +41,7 @@ ClientSideNetworkHandler::ClientSideNetworkHandler(Minecraft* pMinecraft, RakNet clearChunksLoaded(); } -Entity* ClientSideNetworkHandler::getEntityOrLocalPlayer(int entityId) +Entity* ClientSideNetworkHandler::_getEntityOrLocalPlayer(int entityId) { if (!m_pLevel) return nullptr; @@ -334,7 +334,7 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, MovePla { if (!m_pLevel) return; - Entity* pEntity = getEntityOrLocalPlayer(packet->m_id); + Entity* pEntity = _getEntityOrLocalPlayer(packet->m_id); if (!pEntity) { @@ -663,7 +663,7 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, Animate if (!m_pLevel) return; - Entity* pEntity = getEntityOrLocalPlayer(pkt->m_entityId); + Entity* pEntity = _getEntityOrLocalPlayer(pkt->m_entityId); if (!pEntity) return; diff --git a/source/client/network/ClientSideNetworkHandler.hpp b/source/client/network/ClientSideNetworkHandler.hpp index 2c1ff1d0c..872c1921f 100644 --- a/source/client/network/ClientSideNetworkHandler.hpp +++ b/source/client/network/ClientSideNetworkHandler.hpp @@ -69,7 +69,7 @@ class ClientSideNetworkHandler : public NetEventCallback protected: // Helper function to get entity, with fallback to local player if not in entity list - Entity* getEntityOrLocalPlayer(int entityId); + Entity* _getEntityOrLocalPlayer(int entityId); private: Minecraft* m_pMinecraft; From be00a99c9b3f7a0073070084bf415409b0009e4e Mon Sep 17 00:00:00 2001 From: danqzq Date: Tue, 13 Jan 2026 16:20:56 -0500 Subject: [PATCH 20/23] Refactor BedTile hiddenFace to use Facing::Name type for better clarity --- source/client/renderer/TileRenderer.cpp | 4 ++-- source/world/tile/BedTile.cpp | 2 +- source/world/tile/BedTile.hpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/client/renderer/TileRenderer.cpp b/source/client/renderer/TileRenderer.cpp index 08672572e..72ab7f95d 100644 --- a/source/client/renderer/TileRenderer.cpp +++ b/source/client/renderer/TileRenderer.cpp @@ -1358,9 +1358,9 @@ bool TileRenderer::tesselateBedInWorld(Tile* tile, const TilePos& pos) // Determine which face to hide (the connecting face between head and foot) int dirIndex = BedTile::getDirectionIndex(data); - Facing::Name hiddenFace = (Facing::Name)BedTile::hiddenFace[dirIndex]; + Facing::Name hiddenFace = BedTile::hiddenFace[dirIndex]; if (BedTile::isHead(data)) { - hiddenFace = (Facing::Name)BedTile::hiddenFace[BedTile::hiddenFaceIndex[dirIndex]]; + hiddenFace = BedTile::hiddenFace[BedTile::hiddenFaceIndex[dirIndex]]; } for (int i = 0; i < 4; i++) { diff --git a/source/world/tile/BedTile.cpp b/source/world/tile/BedTile.cpp index 88267b534..bc3c4258b 100644 --- a/source/world/tile/BedTile.cpp +++ b/source/world/tile/BedTile.cpp @@ -4,7 +4,7 @@ #include "world/item/Item.hpp" const int BedTile::headBlockToFootBlockMap[4][2] = {{0, 1}, {-1, 0}, {0, -1}, {1, 0}}; -const int BedTile::hiddenFace[4] = {3, 4, 2, 5}; +const Facing::Name BedTile::hiddenFace[4] = {Facing::SOUTH, Facing::WEST, Facing::NORTH, Facing::EAST}; const int BedTile::hiddenFaceIndex[4] = {2, 3, 0, 1}; const int BedTile::bedDirection[4][6] = {{1, 0, 3, 2, 5, 4}, {1, 0, 5, 4, 2, 3}, {1, 0, 2, 3, 4, 5}, {1, 0, 4, 5, 3, 2}}; diff --git a/source/world/tile/BedTile.hpp b/source/world/tile/BedTile.hpp index 7d68abde5..a916533cc 100644 --- a/source/world/tile/BedTile.hpp +++ b/source/world/tile/BedTile.hpp @@ -19,7 +19,7 @@ class BedTile : public Tile void spawnResources(Level*, const TilePos& pos, TileData, float) override; static const int headBlockToFootBlockMap[4][2]; - static const int hiddenFace[4]; + static const Facing::Name hiddenFace[4]; static const int hiddenFaceIndex[4]; static const int bedDirection[4][6]; From 7513b80e429a848e7a029d059efca2f28b235495 Mon Sep 17 00:00:00 2001 From: danqzq Date: Tue, 13 Jan 2026 16:24:42 -0500 Subject: [PATCH 21/23] Refactor bed respawn handling to align with b1.3 logic --- source/client/app/Minecraft.cpp | 35 ++++---- .../network/ClientSideNetworkHandler.cpp | 2 +- source/client/renderer/GameRenderer.cpp | 6 +- source/network/NetEventCallback.cpp | 13 --- source/server/ServerPlayer.cpp | 8 +- source/world/entity/Player.cpp | 82 ++++++++----------- source/world/entity/Player.hpp | 7 +- source/world/level/Level.cpp | 39 +-------- source/world/tile/BedTile.cpp | 23 +++--- 9 files changed, 76 insertions(+), 139 deletions(-) diff --git a/source/client/app/Minecraft.cpp b/source/client/app/Minecraft.cpp index 2b7291de2..7a26b510a 100644 --- a/source/client/app/Minecraft.cpp +++ b/source/client/app/Minecraft.cpp @@ -146,30 +146,33 @@ void Minecraft::_levelGenerated() void Minecraft::_resetPlayer(Player* player) { m_pLevel->validateSpawn(); - player->reset(); - - // Use player's personal respawn position (set by sleeping in bed) if available - // Otherwise fall back to the level's shared spawn position - TilePos pos = m_pLevel->getSharedSpawnPos(); + + TilePos spawnPos; if (player->m_bHasRespawnPos) { - TilePos respawnPos = player->m_respawnPos; - TilePos checkedPos = Player::checkRespawnPos(m_pLevel, respawnPos); + spawnPos = player->getRespawnPosition(); - // If checkedPos != respawnPos, bed is valid and we found a spawn position - if (checkedPos != respawnPos) + if (m_pLevel->getTile(spawnPos) == Tile::bed->m_ID) { - pos = checkedPos; - } - else - { - m_pGui->addMessage("Your home bed was missing or obstructed"); + TilePos safePos = BedTile::getRespawnTilePos(m_pLevel, spawnPos, 0); + if (safePos == spawnPos) + safePos = safePos.above(); + + player->reset(); + player->moveTo(Vec3(safePos.x + 0.5f, float(safePos.y), safePos.z + 0.5f)); + player->resetPos(true); + return; } + + // If bed was destroyed, clear the respawn position and fall through to world spawn + player->setRespawnPos(TilePos(-1, -1, -1)); } - player->setPos(pos); - player->resetPos(); + spawnPos = m_pLevel->getSharedSpawnPos(); + player->reset(); + player->moveTo(Vec3(spawnPos.x + 0.5f, float(spawnPos.y), spawnPos.z + 0.5f)); + player->resetPos(true); } GameMode* Minecraft::_createGameMode(GameType gameType, Level& level) diff --git a/source/client/network/ClientSideNetworkHandler.cpp b/source/client/network/ClientSideNetworkHandler.cpp index 328021e52..d1f2e328e 100644 --- a/source/client/network/ClientSideNetworkHandler.cpp +++ b/source/client/network/ClientSideNetworkHandler.cpp @@ -613,7 +613,7 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, Interac { pPlayer->m_bSleeping = true; pPlayer->m_sleepTimer = 0; - pPlayer->setBedSleepPos(pkt->m_pos); + pPlayer->setRespawnPos(pkt->m_pos); pPlayer->m_vel = Vec3::ZERO; pPlayer->setSize(0.2f, 0.2f); pPlayer->m_heightOffset = 0.2f; diff --git a/source/client/renderer/GameRenderer.cpp b/source/client/renderer/GameRenderer.cpp index 4e63058bd..6c1bfc15f 100644 --- a/source/client/renderer/GameRenderer.cpp +++ b/source/client/renderer/GameRenderer.cpp @@ -300,12 +300,12 @@ void GameRenderer::moveCameraToPlayer(Matrix& matrix, float f) { // Get bed direction for camera orientation float bedRot = 0.0f; - if (player->m_bHasBedSleepPos) + if (player->m_bHasRespawnPos) { - TileID bedTile = m_pMinecraft->m_pLevel->getTile(player->m_bedSleepPos); + TileID bedTile = m_pMinecraft->m_pLevel->getTile(player->m_respawnPos); if (bedTile == Tile::bed->m_ID) { - int data = m_pMinecraft->m_pLevel->getData(player->m_bedSleepPos); + int data = m_pMinecraft->m_pLevel->getData(player->m_respawnPos); int direction = data & 3; bedRot = direction * 90.0f; } diff --git a/source/network/NetEventCallback.cpp b/source/network/NetEventCallback.cpp index 0aa2149ef..d85810aa2 100644 --- a/source/network/NetEventCallback.cpp +++ b/source/network/NetEventCallback.cpp @@ -37,21 +37,8 @@ void NetEventCallback::handle(Level& level, const RakNet::RakNetGUID& guid, Resp return; } - // Use player's personal respawn position (set by sleeping in bed) if available - // Otherwise fall back to the level's shared spawn position TilePos spawnPos = level.getSharedSpawnPos(); - if (pPlayer->m_bHasRespawnPos) - { - TilePos respawnPos = pPlayer->m_respawnPos; - TilePos checkedPos = Player::checkRespawnPos(&level, respawnPos); - - // If checkedPos != respawnPos, bed is valid and we found a spawn position - if (checkedPos != respawnPos) - spawnPos = checkedPos; - // else: fall back to world spawn (message shown in ClientSideNetworkHandler) - } - pPlayer->moveTo(Vec3(spawnPos.x + 0.5f, float(spawnPos.y), spawnPos.z + 0.5f)); pPlayer->reset(); pPlayer->resetPos(true); diff --git a/source/server/ServerPlayer.cpp b/source/server/ServerPlayer.cpp index dc0911141..988ea583c 100644 --- a/source/server/ServerPlayer.cpp +++ b/source/server/ServerPlayer.cpp @@ -63,12 +63,12 @@ void ServerPlayer::stopSleepInBed(bool resetCounter, bool update, bool setSpawn) setDefaultHeadHeight(); // Clear bed occupancy - if (m_bHasBedSleepPos && m_pLevel && m_pLevel->getTile(m_bedSleepPos) == Tile::bed->m_ID) { - BedTile::setBedOccupied(m_pLevel, m_bedSleepPos, false); + if (m_bHasRespawnPos && m_pLevel && m_pLevel->getTile(m_respawnPos) == Tile::bed->m_ID) { + BedTile::setBedOccupied(m_pLevel, m_respawnPos, false); } - if (setSpawn && m_bHasBedSleepPos) { - setRespawnPos(m_bedSleepPos); + if (setSpawn && m_bHasRespawnPos) { + setRespawnPos(m_respawnPos); } // Reset lerp steps so position updates work immediately diff --git a/source/world/entity/Player.cpp b/source/world/entity/Player.cpp index 39f6cdf96..330921e3a 100644 --- a/source/world/entity/Player.cpp +++ b/source/world/entity/Player.cpp @@ -21,7 +21,6 @@ void Player::_init() m_destroyingBlock = false; m_bSleeping = false; m_sleepTimer = 0; - m_bHasBedSleepPos = false; } Player::Player(Level* pLevel, GameType playerGameType) : Mob(pLevel) @@ -174,11 +173,28 @@ void Player::aiStep() // Handle sleeping if (m_bSleeping) { ++m_sleepTimer; + if (m_sleepTimer > 100) { + m_sleepTimer = 100; + } + + if (!checkBed()) { + stopSleepInBed(true, true, false); + } + else if (!m_pLevel->m_bIsClientSide && m_pLevel->isDay()) { + stopSleepInBed(false, true, false); + } + + // Check if all players are sleeping to skip time to morning if (m_sleepTimer >= 100) { m_pLevel->updateSleeping(); } - // Don't do normal movement while sleeping + return; + } else if (m_sleepTimer > 0) { + ++m_sleepTimer; + if (m_sleepTimer >= 110) { + m_sleepTimer = 0; + } } #ifdef ENH_GUI_ITEM_POP @@ -260,15 +276,6 @@ void Player::addAdditionalSaveData(CompoundTag& tag) const tag.putInt32("playerGameType", getPlayerGameType()); tag.putInt32("Dimension", m_dimension); - // Why would we save the player's sleep state? If they leave the game, just wake them up. - /*tag.putBoolean("Sleeping", m_bSleeping); - tag.putShort("SleepTimer", m_sleepTimer); - if (m_bSleeping) - { - setBedSleepPos(m_pos); - wake(true, true, false); - }*/ - if (m_bHasRespawnPos) { tag.putInt32("SpawnX", m_respawnPos.x); @@ -385,12 +392,6 @@ void Player::setRespawnPos(const TilePos& pos) m_respawnPos = pos; } -void Player::setBedSleepPos(const TilePos& pos) -{ - m_bHasBedSleepPos = true; - m_bedSleepPos = pos; -} - void Player::updateSleepingPos(Facing::Name direction) { m_sleepingPos.x = 0.0f; @@ -464,7 +465,8 @@ Player::BedSleepingProblem Player::startSleepInBed(const TilePos& pos) m_bSleeping = true; m_sleepTimer = 0; - setBedSleepPos(pos); + + setRespawnPos(pos); m_vel = Vec3::ZERO; m_pLevel->updateSleeping(); @@ -477,35 +479,38 @@ void Player::stopSleepInBed(bool resetCounter, bool update, bool setSpawn) setSize(0.6f, 1.8f); setDefaultHeadHeight(); - TilePos checkBedPos = m_bedSleepPos; - if (m_bHasBedSleepPos && m_pLevel->getTile(checkBedPos) == Tile::bed->m_ID) { + TilePos checkBedPos = m_respawnPos; + if (m_bHasRespawnPos && m_pLevel->getTile(checkBedPos) == Tile::bed->m_ID) { BedTile::setBedOccupied(m_pLevel, checkBedPos, false); checkBedPos = BedTile::getRespawnTilePos(m_pLevel, checkBedPos, 0); - if (checkBedPos == m_bedSleepPos) + if (checkBedPos == m_respawnPos) checkBedPos = checkBedPos.above(); setPos(Vec3(checkBedPos.x + 0.5f, checkBedPos.y + m_heightOffset + 0.1f, checkBedPos.z + 0.5f)); } m_bSleeping = false; + + if (!m_pLevel->m_bIsClientSide && update) { + m_pLevel->updateSleeping(); + } + if (resetCounter) { m_sleepTimer = 0; - } - - if (setSpawn && m_bHasBedSleepPos) { - setRespawnPos(m_bedSleepPos); + } else { + m_sleepTimer = 100; } } float Player::getBedSleepRot() const { - if (!m_pLevel || !m_bHasBedSleepPos) + if (!m_pLevel || !m_bHasRespawnPos) return 0.0f; - if (m_bedSleepPos.y < 0 || m_bedSleepPos.y >= 128) + if (m_respawnPos.y < 0 || m_respawnPos.y >= 128) return 0.0f; - TileData data = m_pLevel->getData(m_bedSleepPos); + TileData data = m_pLevel->getData(m_respawnPos); Facing::Name dir = BedTile::getDirectionFromData(data); switch (dir) { @@ -522,26 +527,9 @@ float Player::getBedSleepRot() const } } -TilePos Player::checkRespawnPos(Level* level, const TilePos& pos) +bool Player::checkBed() const { - // Load nearby chunks to ensure bed data is available - ChunkPos ch(pos); - level->getChunkSource()->getChunk(ChunkPos(ch.x - 1, ch.z)); - level->getChunkSource()->getChunk(ChunkPos(ch.x + 1, ch.z - 1)); - level->getChunkSource()->getChunk(ChunkPos(ch.x - 1, ch.z + 1)); - level->getChunkSource()->getChunk(ChunkPos(ch.x + 1, ch.z)); - - // Check if bed still exists - if (level->getTile(pos) != Tile::bed->m_ID) - return pos; - - // Check if bed is obstructed (has blocks directly on top) - if (!level->isEmptyTile(pos.above())) - return pos; - - // Find valid spawn position near bed (searches 3x3 area for valid spawn) - // Returns original position if no valid spawn found - return BedTile::getRespawnTilePos(level, pos, 0); + return m_pLevel->getTile(m_respawnPos) == Tile::bed->m_ID; } /*void Player::drop() diff --git a/source/world/entity/Player.hpp b/source/world/entity/Player.hpp index f070428d6..1f4738202 100644 --- a/source/world/entity/Player.hpp +++ b/source/world/entity/Player.hpp @@ -91,14 +91,13 @@ class Player : public Mob void setRespawnPos(const TilePos& pos); // Sleeping - void setBedSleepPos(const TilePos& pos); void updateSleepingPos(Facing::Name direction); virtual BedSleepingProblem startSleepInBed(const TilePos& pos); virtual void stopSleepInBed(bool resetCounter, bool update, bool setSpawn); bool isSleeping() const override { return m_bSleeping; } bool isSleepingLongEnough() const { return m_bSleeping && m_sleepTimer >= 100; } float getBedSleepRot() const; - static TilePos checkRespawnPos(Level* level, const TilePos& pos); + bool checkBed() const; void touch(Entity* pEnt); GameType getPlayerGameType() const { return _playerGameType; } @@ -125,9 +124,7 @@ class Player : public Mob std::string m_name; int m_dimension; RakNet::RakNetGUID m_guid; - //TODO TilePos m_respawnPos; - //TODO bool m_bHasRespawnPos; //TODO bool m_destroyingBlock; @@ -135,8 +132,6 @@ class Player : public Mob // Sleeping bool m_bSleeping; int m_sleepTimer; - TilePos m_bedSleepPos; - bool m_bHasBedSleepPos; Vec3 m_sleepingPos; }; diff --git a/source/world/level/Level.cpp b/source/world/level/Level.cpp index fc3d37646..975c472ed 100644 --- a/source/world/level/Level.cpp +++ b/source/world/level/Level.cpp @@ -690,42 +690,6 @@ bool Level::setTileAndData(const TilePos& pos, TileID tile, TileData data, TileC if (change.isUpdateNeighbors()) oldTile = pChunk->getTile(pos); - // Check if a bed is being destroyed - wake up any sleeping players - if (oldTile == Tile::bed->m_ID && tile != Tile::bed->m_ID) - { - TilePos bedHeadPos = pos; - TileData bedData = getData(pos); - if (BedTile::isHead(bedData)) - { - bedHeadPos = pos; - } - else - { - // This is the foot part, find the head - Facing::Name dir = BedTile::getDirectionFromData(bedData); - switch (dir) - { - case Facing::SOUTH: bedHeadPos = pos.south(); break; - case Facing::WEST: bedHeadPos = pos.west(); break; - case Facing::NORTH: bedHeadPos = pos.north(); break; - case Facing::EAST: bedHeadPos = pos.east(); break; - default: bedHeadPos = pos.east(); break; - } - } - - for (size_t i = 0; i < m_players.size(); i++) - { - Player* player = m_players[i]; - if (player && player->isSleeping() && player->m_bHasBedSleepPos) - { - if (player->m_bedSleepPos == pos || player->m_bedSleepPos == bedHeadPos) - { - player->stopSleepInBed(false, true, false); - } - } - } - } - bool result = pChunk->setTileAndData(pos, tile, data); if (result) { @@ -2020,6 +1984,9 @@ void Level::updateSleeping() timeToMorning = 24000; // Full day if already morning setTime(currentTime + timeToMorning); + + // Update sky brightness and notify listeners (triggers chunk relighting) + updateSkyDarken(); // Wake all players for (size_t i = 0; i < m_players.size(); i++) { diff --git a/source/world/tile/BedTile.cpp b/source/world/tile/BedTile.cpp index bc3c4258b..1bc7688ae 100644 --- a/source/world/tile/BedTile.cpp +++ b/source/world/tile/BedTile.cpp @@ -111,8 +111,8 @@ bool BedTile::use(Level* level, const TilePos& pos, Player* player) // Check if there's a player sleeping in this bed for (size_t i = 0; i < level->m_players.size(); i++) { Player* p = level->m_players[i]; - if (p->isSleeping() && p->m_bHasBedSleepPos) { - if (p->m_bedSleepPos == tp) { + if (p->isSleeping() && p->m_bHasRespawnPos) { + if (p->m_respawnPos == tp) { // Bed is occupied by another player return true; } @@ -122,17 +122,14 @@ bool BedTile::use(Level* level, const TilePos& pos, Player* player) } Player::BedSleepingProblem result = player->startSleepInBed(tp); - if (result == Player::BED_SLEEPING_OK) { - setBedOccupied(level, tp, true); - return true; - } - else { - if (result == Player::BED_SLEEPING_NOT_POSSIBLE_NOW) { - // Localization is to be implemented - // player->displayClientMessage("tile.bed.noSleep"); - } - - return true; + switch (result) { + case Player::BED_SLEEPING_OK: + setBedOccupied(level, tp, true); + return true; + case Player::BED_SLEEPING_NOT_POSSIBLE_NOW: + return true; + default: + return true; } } } From 6948adf5b70571a36ca600c4ebfa9b6d06297328 Mon Sep 17 00:00:00 2001 From: Brent Da Mage Date: Mon, 26 Jan 2026 22:54:05 -0600 Subject: [PATCH 22/23] Fixed bad merge (I love git) --- source/client/renderer/GameRenderer.cpp | 56 +++++++------------------ thirdparty/stb_image/include | 2 +- 2 files changed, 15 insertions(+), 43 deletions(-) diff --git a/source/client/renderer/GameRenderer.cpp b/source/client/renderer/GameRenderer.cpp index c57f1c88d..d64994469 100644 --- a/source/client/renderer/GameRenderer.cpp +++ b/source/client/renderer/GameRenderer.cpp @@ -611,23 +611,19 @@ void GameRenderer::render(float f) { if (m_pMinecraft->m_pLocalPlayer && m_pMinecraft->m_bGrabbedMouse) { - Minecraft *pMC = m_pMinecraft; - - // Don't allow camera rotation while sleeping - if (!pMC->m_pLocalPlayer->isSleeping()) - { - pMC->m_mouseHandler.poll(); + Minecraft* pMC = m_pMinecraft; + pMC->m_mouseHandler.poll(); - float multPitch = -1.0f; - float diff_field_84; + float multPitch = -1.0f; + float diff_field_84; - if (pMC->getOptions()->m_bInvertMouse) - multPitch = 1.0f; + if (pMC->getOptions()->m_bInvertMouse) + multPitch = 1.0f; - if (pMC->m_mouseHandler.smoothTurning()) - { - float mult1 = 2.0f * (0.2f + pMC->getOptions()->m_fSensitivity * 0.6f); - mult1 = pow(mult1, 3); + if (pMC->m_mouseHandler.smoothTurning()) + { + float mult1 = 2.0f * (0.2f + pMC->getOptions()->m_fSensitivity * 0.6f); + mult1 = pow(mult1, 3); Vec2 d = pMC->m_mouseHandler.m_delta * (4.0f * mult1); @@ -636,34 +632,10 @@ void GameRenderer::render(float f) diff_field_84 = field_84 - old_field_84; m_smoothTurnDelta += d; - if (diff_field_84 > 3.0f) - diff_field_84 = 3.0f; + if (diff_field_84 > 3.0f) + diff_field_84 = 3.0f; - if (!pMC->getOptions()->field_240) - { - // @TODO: untangle this code - float v17 = xd + field_14; - float v18 = field_18; - float v19 = field_1C; - field_14 = v17; - float v20 = mult1 * 0.25f * (v17 - v18); - float v21 = v19 + (v20 - v19) * 0.5f; - field_1C = v21; - if ((v20 <= 0.0 || v20 <= v21) && (v20 >= 0.0 || v20 >= v21)) - v21 = mult1 * 0.25f * (v17 - v18); - float v22 = yd + field_20; - field_18 = v18 + v21; - float v23 = field_24; - field_20 = v22; - float v24 = mult1 * 0.15f * (v22 - v23); - float v25 = field_28 + (v24 - field_28) * 0.5f; - field_28 = v25; - if ((v24 <= 0.0 || v24 <= v25) && (v24 >= 0.0 || v24 >= v25)) - v25 = v24; - field_24 = v23 + v25; - } - } - else + if (!pMC->getOptions()->field_240) { // @TODO: untangle this code float v17 = d.x + field_14; @@ -694,7 +666,7 @@ void GameRenderer::render(float f) } Vec2 rot(m_turnDelta.x * diff_field_84, - m_turnDelta.y * diff_field_84 * multPitch); + m_turnDelta.y * diff_field_84 * multPitch); m_pItemInHandRenderer->turn(rot); pMC->m_pLocalPlayer->turn(rot); } diff --git a/thirdparty/stb_image/include b/thirdparty/stb_image/include index f7f20f39f..beebb24b9 160000 --- a/thirdparty/stb_image/include +++ b/thirdparty/stb_image/include @@ -1 +1 @@ -Subproject commit f7f20f39fe4f206c6f19e26ebfef7b261ee59ee4 +Subproject commit beebb24b945efdea3b9bba23affb8eb3ba8982e7 From 51d47c798e4fa53617870fbba2c922f10e30e8a5 Mon Sep 17 00:00:00 2001 From: Brent Da Mage Date: Tue, 27 Jan 2026 03:12:25 -0600 Subject: [PATCH 23/23] Added BedItem to VS World Project --- platforms/windows/projects/World/World.vcxproj | 2 ++ platforms/windows/projects/World/World.vcxproj.filters | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/platforms/windows/projects/World/World.vcxproj b/platforms/windows/projects/World/World.vcxproj index c807d42fe..8e3dbf92b 100644 --- a/platforms/windows/projects/World/World.vcxproj +++ b/platforms/windows/projects/World/World.vcxproj @@ -199,6 +199,7 @@ + @@ -341,6 +342,7 @@ + diff --git a/platforms/windows/projects/World/World.vcxproj.filters b/platforms/windows/projects/World/World.vcxproj.filters index 54805eb8e..91b40c43c 100644 --- a/platforms/windows/projects/World/World.vcxproj.filters +++ b/platforms/windows/projects/World/World.vcxproj.filters @@ -560,6 +560,9 @@ Source Files\Level\LevelGen\Chunk + + Source Files\Item + @@ -982,5 +985,8 @@ Header Files\Level + + Header Files\Item + \ No newline at end of file