diff --git a/platforms/macos/projects/Minecraft/Minecraft.xcodeproj/project.pbxproj b/platforms/macos/projects/Minecraft/Minecraft.xcodeproj/project.pbxproj index 8fce391a5..de35c4949 100644 --- a/platforms/macos/projects/Minecraft/Minecraft.xcodeproj/project.pbxproj +++ b/platforms/macos/projects/Minecraft/Minecraft.xcodeproj/project.pbxproj @@ -520,6 +520,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 */; }; 84BF631C2AF18631008A9995 /* TileItem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 840DD67A2AC810620006A435 /* TileItem.cpp */; }; @@ -812,6 +813,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 */; }; @@ -1190,6 +1192,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 */; }; @@ -1639,6 +1643,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 = ""; }; @@ -2452,6 +2458,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 = ""; }; @@ -2828,6 +2836,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 = ""; }; @@ -3342,6 +3352,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 */, @@ -3569,6 +3581,8 @@ 84E1C9E22E7FDC72007D2F5D /* ClothItem.hpp */, 840DD6722AC810620006A435 /* DoorItem.cpp */, 840DD6732AC810620006A435 /* DoorItem.hpp */, + 275AF7A1723F44EE9B9C0DA7 /* BedItem.cpp */, + 6CE4834EE148437C93D73250 /* BedItem.hpp */, 84E023022F2A0255003C8FFE /* DyeColor.cpp */, 84E023032F2A0255003C8FFE /* DyeColor.hpp */, 840DD6742AC810620006A435 /* Inventory.cpp */, @@ -3788,6 +3802,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 */, @@ -5248,6 +5264,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 */, @@ -6245,6 +6262,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 */, @@ -6520,6 +6538,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 */, @@ -6575,6 +6594,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 */, diff --git a/platforms/windows/projects/World/World.vcxproj b/platforms/windows/projects/World/World.vcxproj index df137ba50..6d27f0544 100644 --- a/platforms/windows/projects/World/World.vcxproj +++ b/platforms/windows/projects/World/World.vcxproj @@ -199,6 +199,7 @@ + @@ -353,6 +354,7 @@ + diff --git a/platforms/windows/projects/World/World.vcxproj.filters b/platforms/windows/projects/World/World.vcxproj.filters index 99660d47e..1705f8245 100644 --- a/platforms/windows/projects/World/World.vcxproj.filters +++ b/platforms/windows/projects/World/World.vcxproj.filters @@ -566,6 +566,9 @@ Source Files\Level\LevelGen\Chunk + + Source Files\Item + Source Files @@ -1024,6 +1027,9 @@ Header Files\Level + + Header Files\Item + Header Files diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 24ebc1bb5..449d64bb6 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -186,6 +186,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 @@ -325,6 +326,7 @@ add_library(reminecraftpe-core STATIC world/inventory/SimpleContainer.cpp world/CompoundContainer.cpp world/item/DoorItem.cpp + world/item/BedItem.cpp world/item/ItemStack.cpp world/item/RocketItem.cpp world/item/Item.cpp @@ -388,6 +390,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/client/app/Minecraft.cpp b/source/client/app/Minecraft.cpp index 46ebd3e21..588b5fd45 100644 --- a/source/client/app/Minecraft.cpp +++ b/source/client/app/Minecraft.cpp @@ -37,6 +37,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" @@ -150,11 +151,33 @@ void Minecraft::_levelGenerated() void Minecraft::_resetPlayer(Player* player) { m_pLevel->validateSpawn(); + + TilePos spawnPos; + + if (player->m_bHasRespawnPos) + { + spawnPos = player->getRespawnPosition(); + + if (m_pLevel->getTile(spawnPos) == Tile::bed->m_ID) + { + 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)); + } + + spawnPos = m_pLevel->getSharedSpawnPos(); player->reset(); - - TilePos pos = m_pLevel->getSharedSpawnPos(); - player->setPos(pos); - player->resetPos(); + player->moveTo(Vec3(spawnPos.x + 0.5f, float(spawnPos.y), spawnPos.z + 0.5f)); + player->resetPos(true); } GameMode* Minecraft::_createGameMode(GameType gameType, Level& level) @@ -465,6 +488,12 @@ void Minecraft::handleBuildAction(const BuildActionIntention& action) if (item.isEmpty() || !item.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; @@ -1122,6 +1151,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->stopSleepInBed(false, true, true); + return true; + } + if (!isOnline()) { // Actually pause the game, because fuck bedrock edition diff --git a/source/client/multiplayer/MultiplayerLocalPlayer.cpp b/source/client/multiplayer/MultiplayerLocalPlayer.cpp index 090b04e02..ba661c42e 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,25 @@ void MultiplayerLocalPlayer::hurtTo(int newHealth) m_flashOnSetHealth = true; } } + +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); + + // Send wake notification to server + if (m_pLevel && m_pLevel->m_pRakNetInstance) + { + 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, + 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..80140088d 100644 --- a/source/client/multiplayer/MultiplayerLocalPlayer.hpp +++ b/source/client/multiplayer/MultiplayerLocalPlayer.hpp @@ -15,6 +15,9 @@ class MultiplayerLocalPlayer : public LocalPlayer void heal(int health) override; //void drop() override; void hurtTo(int newHealth) override; + + BedSleepingProblem startSleepInBed(const TilePos& pos) override; + 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 032546ce7..3f6540f47 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 @@ -40,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; @@ -311,14 +329,25 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, MovePla { if (!m_pLevel) return; - Entity* pEntity = m_pLevel->getEntity(packet->m_id); + Entity* pEntity = _getEntityOrLocalPlayer(packet->m_id); + if (!pEntity) { LOG_E("MovePlayerPacket: No player with id %d", packet->m_id); 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) @@ -562,6 +591,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->setRespawnPos(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); + Facing::Name dir = BedTile::getDirectionFromData(data); + pPlayer->updateSleepingPos(dir); + } + + m_pLevel->updateSleeping(); + } +} + void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, SetEntityDataPacket* pkt) { puts_ignorable("SetEntityDataPacket"); @@ -595,7 +658,8 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, Animate if (!m_pLevel) return; - Entity* pEntity = m_pLevel->getEntity(pkt->m_entityId); + Entity* pEntity = _getEntityOrLocalPlayer(pkt->m_entityId); + if (!pEntity) return; @@ -615,6 +679,20 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, Animate pEntity->animateHurt(); break; } + case AnimatePacket::WAKE_UP: + { + 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->stopSleepInBed(false, false, false); + } + break; + } default: { LOG_W("Received unkown action in AnimatePacket: %d, EntityType: %s", pkt->m_actionId, pEntity->getDescriptor().getEntityType().getName().c_str()); @@ -630,6 +708,7 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, RespawnPac if (!m_pLevel) return; + // Call base handler to perform the respawn NetEventCallback::handle(*m_pLevel, guid, packet); } diff --git a/source/client/network/ClientSideNetworkHandler.hpp b/source/client/network/ClientSideNetworkHandler.hpp index 81230686c..872c1921f 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; @@ -66,6 +67,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; diff --git a/source/client/player/LocalPlayer.cpp b/source/client/player/LocalPlayer.cpp index 33627ab61..c0545ab6f 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; @@ -324,3 +325,32 @@ void LocalPlayer::sendPosition() m_lastSentRot = m_rot; } } + +Player::BedSleepingProblem LocalPlayer::startSleepInBed(const TilePos& pos) +{ + Player::BedSleepingProblem result = Player::startSleepInBed(pos); + + // 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 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 actual player position after startSleepInBed positioned them + m_pLevel->m_pRakNetInstance->send(new MovePlayerPacket(m_EntityID, m_pos, m_rot)); + } + + return result; +} + +void LocalPlayer::stopSleepInBed(bool resetCounter, bool update, bool 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) + { + m_pLevel->m_pRakNetInstance->send(new AnimatePacket(m_EntityID, AnimatePacket::WAKE_UP)); + } +} \ No newline at end of file diff --git a/source/client/player/LocalPlayer.hpp b/source/client/player/LocalPlayer.hpp index c836c0e26..d221c2e30 100644 --- a/source/client/player/LocalPlayer.hpp +++ b/source/client/player/LocalPlayer.hpp @@ -38,6 +38,8 @@ class LocalPlayer : public Player bool interpolateOnly() const override { return false; } void setPlayerGameType(GameType gameType) override; void swing() 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/client/renderer/GameRenderer.cpp b/source/client/renderer/GameRenderer.cpp index daccbe1a8..63ec1f193 100644 --- a/source/client/renderer/GameRenderer.cpp +++ b/source/client/renderer/GameRenderer.cpp @@ -308,6 +308,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_bHasRespawnPos) + { + 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_respawnPos); + 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) { @@ -582,7 +611,7 @@ void GameRenderer::render(float f) { if (m_pMinecraft->m_pLocalPlayer && m_pMinecraft->m_bGrabbedMouse) { - Minecraft *pMC = m_pMinecraft; + Minecraft* pMC = m_pMinecraft; pMC->m_mouseHandler.poll(); float multPitch = -1.0f; @@ -637,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/source/client/renderer/TileRenderer.cpp b/source/client/renderer/TileRenderer.cpp index 037b0e802..953b3272f 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,192 @@ 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); + + // 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 direction = BedTile::getDirectionFromData(data); + Facing::Name flippedFace = Facing::WEST; + switch (direction) { + case Facing::SOUTH: + flippedFace = Facing::EAST; + break; + case Facing::WEST: + flippedFace = Facing::SOUTH; + break; + case Facing::NORTH: + flippedFace = Facing::WEST; + break; + case Facing::EAST: + flippedFace = Facing::NORTH; + break; + default: + flippedFace = Facing::WEST; + 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) + int dirIndex = BedTile::getDirectionIndex(data); + Facing::Name hiddenFace = BedTile::hiddenFace[dirIndex]; + if (BedTile::isHead(data)) { + hiddenFace = BedTile::hiddenFace[BedTile::hiddenFaceIndex[dirIndex]]; + } + + 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 +1750,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 7710612c7..06d8d64c2 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 diff --git a/source/client/renderer/entity/HumanoidMobRenderer.cpp b/source/client/renderer/entity/HumanoidMobRenderer.cpp index b3397d2c9..3778f9f23 100644 --- a/source/client/renderer/entity/HumanoidMobRenderer.cpp +++ b/source/client/renderer/entity/HumanoidMobRenderer.cpp @@ -122,6 +122,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); diff --git a/source/common/Utils.hpp b/source/common/Utils.hpp index 5bfdd89cf..c745b9c92 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/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.cpp b/source/network/NetEventCallback.cpp index 724c0bed1..d85810aa2 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,9 @@ 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); + TilePos 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/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 d6388c790..85699e790 100644 --- a/source/network/packets/AnimatePacket.hpp +++ b/source/network/packets/AnimatePacket.hpp @@ -9,7 +9,8 @@ class AnimatePacket : public Packet { NONE, SWING, - HURT + HURT, + 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 3dec41d31..988ea583c 100644 --- a/source/server/ServerPlayer.cpp +++ b/source/server/ServerPlayer.cpp @@ -1,8 +1,12 @@ #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/packets/InteractionPacket.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 +31,52 @@ void ServerPlayer::take(Entity* pEnt, int count) Player::take(pEnt, count); } + +Player::BedSleepingProblem ServerPlayer::startSleepInBed(const TilePos& pos) +{ + Player::BedSleepingProblem result = Player::startSleepInBed(pos); + + // Broadcast sleep state to all clients if successful + if (result == BED_SLEEPING_OK && m_pLevel && m_pLevel->m_pRakNetInstance) + { + // 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 actual player position after startSleepInBed positioned them + m_pLevel->m_pRakNetInstance->send(new MovePlayerPacket(m_EntityID, m_pos, m_rot)); + } + + return result; +} + +void ServerPlayer::stopSleepInBed(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_bHasRespawnPos && m_pLevel && m_pLevel->getTile(m_respawnPos) == Tile::bed->m_ID) { + BedTile::setBedOccupied(m_pLevel, m_respawnPos, false); + } + + if (setSpawn && m_bHasRespawnPos) { + setRespawnPos(m_respawnPos); + } + + // 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_UP)); + } +} diff --git a/source/server/ServerPlayer.hpp b/source/server/ServerPlayer.hpp index c69ea2210..d7d06a9eb 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 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 83bbec528..6c69ed037 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,6 +526,12 @@ void ServerSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, AnimatePac pPlayer->animateHurt(); break; } + case AnimatePacket::WAKE_UP: + { + // Client is waking up - call wake on server player + pPlayer->stopSleepInBed(false, true, true); + 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; 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 b33d9599e..ee60acd73 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 f9d5ea840..e329ee3fb 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,8 @@ void Player::_init() m_bob = 0.0f; m_dimension = 0; m_destroyingBlock = false; + m_bSleeping = false; + m_sleepTimer = 0; } Player::Player(Level* pLevel, GameType playerGameType) : Mob(pLevel) @@ -174,6 +177,33 @@ void Player::aiStep() heal(1); } + // 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(); + } + + return; + } else if (m_sleepTimer > 0) { + ++m_sleepTimer; + if (m_sleepTimer >= 110) { + m_sleepTimer = 0; + } + } + #ifdef ENH_GUI_ITEM_POP m_pInventory->tick(); #endif @@ -264,15 +294,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); @@ -389,6 +410,146 @@ void Player::setRespawnPos(const TilePos& pos) m_respawnPos = pos; } +void Player::updateSleepingPos(Facing::Name direction) +{ + m_sleepingPos.x = 0.0f; + m_sleepingPos.y = 0.0f; + m_sleepingPos.z = 0.0f; + switch (direction) { + case Facing::SOUTH: // +Z facing + m_sleepingPos.z = -1.8f; + break; + case Facing::WEST: // -X facing + m_sleepingPos.x = 1.8f; + break; + case Facing::NORTH: // -Z facing + m_sleepingPos.z = 1.8f; + break; + default: // +X facing + m_sleepingPos.x = -1.8f; + break; + } +} + +Player::BedSleepingProblem Player::startSleepInBed(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; + + // 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; + + if (!m_pLevel->isEmptyTile(pos)) { + TileData data = m_pLevel->getData(pos); + Facing::Name dir = BedTile::getDirectionFromData(data); + float xOff = 0.5f; + float zOff = 0.5f; + switch (dir) { + case Facing::SOUTH: + zOff = 0.9f; + break; + case Facing::WEST: + xOff = 0.1f; + break; + case Facing::NORTH: + zOff = 0.1f; + break; + case Facing::EAST: + xOff = 0.9f; + break; + default: + break; + } + updateSleepingPos(dir); + setPos(Vec3(pos.x + xOff, pos.y + 1.1f, pos.z + zOff)); + } + else { + setPos(Vec3(pos.x + 0.5f, pos.y + 1.1f, pos.z + 0.5f)); + } + + m_bSleeping = true; + m_sleepTimer = 0; + + setRespawnPos(pos); + m_vel = Vec3::ZERO; + + m_pLevel->updateSleeping(); + + return BED_SLEEPING_OK; +} + +void Player::stopSleepInBed(bool resetCounter, bool update, bool setSpawn) +{ + setSize(0.6f, 1.8f); + setDefaultHeadHeight(); + + 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_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; + } else { + m_sleepTimer = 100; + } +} + +float Player::getBedSleepRot() const +{ + if (!m_pLevel || !m_bHasRespawnPos) + return 0.0f; + + if (m_respawnPos.y < 0 || m_respawnPos.y >= 128) + return 0.0f; + + TileData data = m_pLevel->getData(m_respawnPos); + Facing::Name dir = BedTile::getDirectionFromData(data); + + switch (dir) { + case Facing::SOUTH: + return 90.0f; + case Facing::WEST: + return 0.0f; + case Facing::NORTH: + return 270.0f; + case Facing::EAST: + return 180.0f; + default: + return 0.0f; + } +} + +bool Player::checkBed() const +{ + return m_pLevel->getTile(m_respawnPos) == Tile::bed->m_ID; +} + /*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 11f088424..86a7bc129 100644 --- a/source/world/entity/Player.hpp +++ b/source/world/entity/Player.hpp @@ -19,6 +19,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; @@ -49,7 +59,7 @@ class Player : public Mob void aiStep() override; void tick() override; const ItemStack& 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; @@ -82,6 +92,15 @@ class Player : public Mob void setDefaultHeadHeight(); void setRespawnPos(const TilePos& pos); + // Sleeping + 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; + bool checkBed() const; + void touch(Entity* pEnt); GameType getPlayerGameType() const { return _playerGameType; } virtual void setPlayerGameType(GameType playerGameType) { _playerGameType = playerGameType; } @@ -109,11 +128,14 @@ 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; + + // Sleeping + bool m_bSleeping; + int m_sleepTimer; + Vec3 m_sleepingPos; }; diff --git a/source/world/gamemode/GameMode.cpp b/source/world/gamemode/GameMode.cpp index 29cb8dc33..55af314b9 100644 --- a/source/world/gamemode/GameMode.cpp +++ b/source/world/gamemode/GameMode.cpp @@ -158,19 +158,23 @@ bool GameMode::useItemOn(Player* player, Level* level, ItemStack& item, const Ti return false; bool success = false; + bool isBed = (tile > 0 && Tile::tiles[tile] == Tile::bed); + bool isMultiplayerClient = level->m_bIsClientSide; if (tile > 0 && Tile::tiles[tile]->use(level, pos, player)) { success = true; } - else if (!item.isEmpty()) + else if (!item.isEmpty() && !(isMultiplayerClient && isBed)) { success = item.useOn(player, level, pos, face); } - if (success) + if (success || (isMultiplayerClient && isBed)) { _level.m_pRakNetInstance->send(new UseItemPacket(pos, face, player->m_EntityID, item)); + if (isBed) + return true; } return success; diff --git a/source/world/item/BedItem.cpp b/source/world/item/BedItem.cpp new file mode 100644 index 000000000..fd87ead1d --- /dev/null +++ b/source/world/item/BedItem.cpp @@ -0,0 +1,48 @@ +#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..88ee86966 --- /dev/null +++ b/source/world/item/BedItem.hpp @@ -0,0 +1,11 @@ +#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 c5aa77dc1..9b3cea538 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" @@ -514,7 +515,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/level/Level.cpp b/source/world/level/Level.cpp index e34cfe0e7..b9c1fd9ed 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" @@ -1939,3 +1940,61 @@ 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); + + // Update sky brightness and notify listeners (triggers chunk relighting) + updateSkyDarken(); + + // Wake all players + for (size_t i = 0; i < m_players.size(); i++) { + Player* player = m_players[i]; + if (player && player->isSleeping()) { + player->stopSleepInBed(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); diff --git a/source/world/tile/BedTile.cpp b/source/world/tile/BedTile.cpp new file mode 100644 index 000000000..1bc7688ae --- /dev/null +++ b/source/world/tile/BedTile.cpp @@ -0,0 +1,183 @@ +#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 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}}; + +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 = 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)); + } + 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 = getDirectionIndex(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 = getDirectionIndex(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 = getDirectionIndex(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_bHasRespawnPos) { + if (p->m_respawnPos == tp) { + // Bed is occupied by another player + return true; + } + } + } + setBedOccupied(level, tp, false); + } + + Player::BedSleepingProblem result = player->startSleepInBed(tp); + 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; + } + } +} + +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 = getDirectionIndex(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..a916533cc --- /dev/null +++ b/source/world/tile/BedTile.hpp @@ -0,0 +1,52 @@ +#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 Facing::Name hiddenFace[4]; + static const int hiddenFaceIndex[4]; + static const int bedDirection[4][6]; + + // 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; + } + + 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 eafaad6ad..09ea4a1d4 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;