From 075b19c7cbf5d6e45dbd14e8fab5740d7000bf0f Mon Sep 17 00:00:00 2001 From: Mattes D Date: Wed, 24 Dec 2014 06:43:28 +0100 Subject: Added Vector3::TurnCW() and Vector3::TurnCCW() --- src/Vector3.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Vector3.h b/src/Vector3.h index 1e4a1f5d9..1f3f6b955 100644 --- a/src/Vector3.h +++ b/src/Vector3.h @@ -307,6 +307,22 @@ public: return (a_X - x) / (a_OtherEnd.x - x); } + /** Rotates the vector 90 degrees clockwise around the vertical axis. + Note that this is specific to minecraft's axis ordering, which is X+ left, Z+ down. */ + inline void TurnCW(void) + { + std::swap(x, z); + x = -x; + } + + /** Rotates the vector 90 degrees counterclockwise around the vertical axis. + Note that this is specific to minecraft's axis ordering, which is X+ left, Z+ down. */ + inline void TurnCCW(void) + { + std::swap(x, z); + z = -z; + } + /** The max difference between two coords for which the coords are assumed equal. */ static const double EPS; -- cgit v1.2.3 From ccdf03daaf880dd0c89a03b50c11eb083ee1cfb0 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Wed, 24 Dec 2014 07:20:17 +0100 Subject: Refactored all player block placing to go through hooks. Fixes #1618. --- src/Bindings/Plugin.h | 4 +- src/Bindings/PluginLua.cpp | 18 ++- src/Bindings/PluginLua.h | 4 +- src/Bindings/PluginManager.cpp | 8 +- src/Bindings/PluginManager.h | 6 +- src/Blocks/BlockBed.cpp | 20 +--- src/Blocks/BlockBed.h | 1 - src/Blocks/BlockBigFlower.h | 12 -- src/Blocks/BlockChest.h | 65 ++-------- src/Blocks/BlockDoor.cpp | 40 ++----- src/Blocks/BlockDoor.h | 39 ++++-- src/Blocks/BlockHandler.cpp | 2 +- src/Blocks/BlockHandler.h | 21 ++-- src/Blocks/BlockMobHead.h | 204 +------------------------------- src/Blocks/BlockPumpkin.h | 64 ---------- src/Blocks/BlockSignPost.h | 11 -- src/Blocks/BlockWallSign.h | 11 -- src/Chunk.cpp | 16 +-- src/ChunkDef.h | 48 ++++++-- src/ChunkMap.cpp | 103 ++++++++++------ src/ChunkMap.h | 23 +++- src/ClientHandle.cpp | 158 ++----------------------- src/ClientHandle.h | 3 - src/Entities/Player.cpp | 96 +++++++++++++++ src/Entities/Player.h | 18 +++ src/Generating/StructGen.cpp | 14 +-- src/Generating/Trees.cpp | 14 +-- src/Items/CMakeLists.txt | 7 +- src/Items/ItemBed.h | 28 +++-- src/Items/ItemBigFlower.h | 56 +++++++++ src/Items/ItemChest.h | 167 ++++++++++++++++++++++++++ src/Items/ItemDoor.h | 96 +++++++++++---- src/Items/ItemDye.h | 25 ++-- src/Items/ItemHandler.cpp | 199 ++++++++++++++++++++++++------- src/Items/ItemHandler.h | 39 ++++-- src/Items/ItemMobHead.h | 261 +++++++++++++++++++++++++++++++++++++++++ src/Items/ItemPumpkin.h | 156 ++++++++++++++++++++++++ src/Items/ItemSign.h | 22 +++- src/Items/ItemSlab.h | 93 +++++++++++++++ src/Protocol/Protocol17x.cpp | 4 +- src/Protocol/Protocol18x.cpp | 4 +- src/World.cpp | 13 +- src/World.h | 5 + 43 files changed, 1423 insertions(+), 775 deletions(-) create mode 100644 src/Items/ItemBigFlower.h create mode 100644 src/Items/ItemChest.h create mode 100644 src/Items/ItemPumpkin.h create mode 100644 src/Items/ItemSlab.h diff --git a/src/Bindings/Plugin.h b/src/Bindings/Plugin.h index 08677553c..b5944e9cb 100644 --- a/src/Bindings/Plugin.h +++ b/src/Bindings/Plugin.h @@ -76,8 +76,8 @@ public: virtual bool OnPlayerJoined (cPlayer & a_Player) = 0; virtual bool OnPlayerLeftClick (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, char a_Status) = 0; virtual bool OnPlayerMoving (cPlayer & a_Player, const Vector3d & a_OldPosition, const Vector3d & a_NewPosition) = 0; - virtual bool OnPlayerPlacedBlock (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) = 0; - virtual bool OnPlayerPlacingBlock (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) = 0; + virtual bool OnPlayerPlacedBlock (cPlayer & a_Player, const sSetBlock & a_BlockChange) = 0; + virtual bool OnPlayerPlacingBlock (cPlayer & a_Player, const sSetBlock & a_BlockChange) = 0; virtual bool OnPlayerRightClick (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ) = 0; virtual bool OnPlayerRightClickingEntity(cPlayer & a_Player, cEntity & a_Entity) = 0; virtual bool OnPlayerShooting (cPlayer & a_Player) = 0; diff --git a/src/Bindings/PluginLua.cpp b/src/Bindings/PluginLua.cpp index ea782ea3f..cc3146610 100644 --- a/src/Bindings/PluginLua.cpp +++ b/src/Bindings/PluginLua.cpp @@ -857,14 +857,19 @@ bool cPluginLua::OnPlayerMoving(cPlayer & a_Player, const Vector3d & a_OldPositi -bool cPluginLua::OnPlayerPlacedBlock(cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) +bool cPluginLua::OnPlayerPlacedBlock(cPlayer & a_Player, const sSetBlock & a_BlockChange) { cCSLock Lock(m_CriticalSection); bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_PLACED_BLOCK]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) { - m_LuaState.Call((int)(**itr), &a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, a_BlockType, a_BlockMeta, cLuaState::Return, res); + m_LuaState.Call((int)(**itr), &a_Player, + a_BlockChange.GetX(), a_BlockChange.GetY(), a_BlockChange.GetZ(), + a_BlockChange.m_BlockType, a_BlockChange.m_BlockMeta, + cLuaState::Return, + res + ); if (res) { return true; @@ -877,14 +882,19 @@ bool cPluginLua::OnPlayerPlacedBlock(cPlayer & a_Player, int a_BlockX, int a_Blo -bool cPluginLua::OnPlayerPlacingBlock(cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) +bool cPluginLua::OnPlayerPlacingBlock(cPlayer & a_Player, const sSetBlock & a_BlockChange) { cCSLock Lock(m_CriticalSection); bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_PLACING_BLOCK]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) { - m_LuaState.Call((int)(**itr), &a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, a_BlockType, a_BlockMeta, cLuaState::Return, res); + m_LuaState.Call((int)(**itr), &a_Player, + a_BlockChange.GetX(), a_BlockChange.GetY(), a_BlockChange.GetZ(), + a_BlockChange.m_BlockType, a_BlockChange.m_BlockMeta, + cLuaState::Return, + res + ); if (res) { return true; diff --git a/src/Bindings/PluginLua.h b/src/Bindings/PluginLua.h index 7de5ffec4..ad3f82b42 100644 --- a/src/Bindings/PluginLua.h +++ b/src/Bindings/PluginLua.h @@ -100,8 +100,8 @@ public: virtual bool OnPlayerJoined (cPlayer & a_Player) override; virtual bool OnPlayerLeftClick (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, char a_Status) override; virtual bool OnPlayerMoving (cPlayer & a_Player, const Vector3d & a_OldPosition, const Vector3d & a_NewPosition) override; - virtual bool OnPlayerPlacedBlock (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override; - virtual bool OnPlayerPlacingBlock (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override; + virtual bool OnPlayerPlacedBlock (cPlayer & a_Player, const sSetBlock & a_BlockChange) override; + virtual bool OnPlayerPlacingBlock (cPlayer & a_Player, const sSetBlock & a_BlockChange) override; virtual bool OnPlayerRightClick (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ) override; virtual bool OnPlayerRightClickingEntity(cPlayer & a_Player, cEntity & a_Entity) override; virtual bool OnPlayerShooting (cPlayer & a_Player) override; diff --git a/src/Bindings/PluginManager.cpp b/src/Bindings/PluginManager.cpp index 406a540f4..fad0a36d2 100644 --- a/src/Bindings/PluginManager.cpp +++ b/src/Bindings/PluginManager.cpp @@ -866,14 +866,14 @@ bool cPluginManager::CallHookPlayerMoving(cPlayer & a_Player, const Vector3d a_O -bool cPluginManager::CallHookPlayerPlacedBlock(cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) +bool cPluginManager::CallHookPlayerPlacedBlock(cPlayer & a_Player, const sSetBlock & a_BlockChange) { FIND_HOOK(HOOK_PLAYER_PLACED_BLOCK); VERIFY_HOOK; for (PluginList::iterator itr = Plugins->second.begin(); itr != Plugins->second.end(); ++itr) { - if ((*itr)->OnPlayerPlacedBlock(a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, a_BlockType, a_BlockMeta)) + if ((*itr)->OnPlayerPlacedBlock(a_Player, a_BlockChange)) { return true; } @@ -885,14 +885,14 @@ bool cPluginManager::CallHookPlayerPlacedBlock(cPlayer & a_Player, int a_BlockX, -bool cPluginManager::CallHookPlayerPlacingBlock(cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) +bool cPluginManager::CallHookPlayerPlacingBlock(cPlayer & a_Player, const sSetBlock & a_BlockChange) { FIND_HOOK(HOOK_PLAYER_PLACING_BLOCK); VERIFY_HOOK; for (PluginList::iterator itr = Plugins->second.begin(); itr != Plugins->second.end(); ++itr) { - if ((*itr)->OnPlayerPlacingBlock(a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, a_BlockType, a_BlockMeta)) + if ((*itr)->OnPlayerPlacingBlock(a_Player, a_BlockChange)) { return true; } diff --git a/src/Bindings/PluginManager.h b/src/Bindings/PluginManager.h index 3a2aecc92..d4b82376a 100644 --- a/src/Bindings/PluginManager.h +++ b/src/Bindings/PluginManager.h @@ -207,10 +207,10 @@ public: bool CallHookPlayerFishing (cPlayer & a_Player, cItems a_Reward); bool CallHookPlayerFoodLevelChange (cPlayer & a_Player, int a_NewFoodLevel); bool CallHookPlayerJoined (cPlayer & a_Player); - bool CallHookPlayerMoving (cPlayer & a_Player, const Vector3d a_OldPosition, const Vector3d a_NewPosition); bool CallHookPlayerLeftClick (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, char a_Status); - bool CallHookPlayerPlacedBlock (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); - bool CallHookPlayerPlacingBlock (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); + bool CallHookPlayerMoving (cPlayer & a_Player, const Vector3d a_OldPosition, const Vector3d a_NewPosition); + bool CallHookPlayerPlacedBlock (cPlayer & a_Player, const sSetBlock & a_BlockChange); + bool CallHookPlayerPlacingBlock (cPlayer & a_Player, const sSetBlock & a_BlockChange); bool CallHookPlayerRightClick (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ); bool CallHookPlayerRightClickingEntity(cPlayer & a_Player, cEntity & a_Entity); bool CallHookPlayerShooting (cPlayer & a_Player); diff --git a/src/Blocks/BlockBed.cpp b/src/Blocks/BlockBed.cpp index 3b6328b38..57b9855d0 100644 --- a/src/Blocks/BlockBed.cpp +++ b/src/Blocks/BlockBed.cpp @@ -14,24 +14,6 @@ -void cBlockBedHandler::OnPlacedByPlayer( - cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, - int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, - int a_CursorX, int a_CursorY, int a_CursorZ, - BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta -) -{ - if (a_BlockMeta < 8) - { - Vector3i Direction = MetaDataToDirection(a_BlockMeta); - a_ChunkInterface.SetBlock(a_BlockX + Direction.x, a_BlockY, a_BlockZ + Direction.z, E_BLOCK_BED, a_BlockMeta | 0x8); - } -} - - - - - void cBlockBedHandler::OnDestroyed(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, int a_BlockX, int a_BlockY, int a_BlockZ) { NIBBLETYPE OldMeta = a_ChunkInterface.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ); @@ -151,7 +133,7 @@ void cBlockBedHandler::OnUse(cChunkInterface & a_ChunkInterface, cWorldInterface cPlayerBedStateUnsetter Unsetter(Vector3i(a_BlockX + PillowDirection.x, a_BlockY, a_BlockZ + PillowDirection.z), a_WorldInterface); a_WorldInterface.ForEachPlayer(Unsetter); a_WorldInterface.SetTimeOfDay(0); - a_ChunkInterface.SetBlockMeta(a_BlockX, a_BlockY, a_BlockZ, Meta & 0xB); // Where 0xB = 1011, and zero is to make sure 'occupied' bit is always unset + a_ChunkInterface.SetBlockMeta(a_BlockX, a_BlockY, a_BlockZ, Meta & 0x0b); // Clear the "occupied" bit of the bed's block } } } diff --git a/src/Blocks/BlockBed.h b/src/Blocks/BlockBed.h index a8b5be899..5b746110a 100644 --- a/src/Blocks/BlockBed.h +++ b/src/Blocks/BlockBed.h @@ -23,7 +23,6 @@ public: } - virtual void OnPlacedByPlayer(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override; virtual void OnDestroyed(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, int a_BlockX, int a_BlockY, int a_BlockZ) override; virtual void OnUse(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ) override; diff --git a/src/Blocks/BlockBigFlower.h b/src/Blocks/BlockBigFlower.h index 3577bdd40..5240ddf53 100644 --- a/src/Blocks/BlockBigFlower.h +++ b/src/Blocks/BlockBigFlower.h @@ -85,18 +85,6 @@ public: } - virtual void OnPlacedByPlayer( - cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, - int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, - int a_CursorX, int a_CursorY, int a_CursorZ, - BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta - ) override - { - int Meta = (((int)floor(a_Player->GetYaw() * 4.0 / 360.0 + 0.5) & 0x3) + 2) % 4; - a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY + 1, a_BlockZ, m_BlockType, 0x8 | Meta); - } - - virtual void OnDestroyed(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, int a_BlockX, int a_BlockY, int a_BlockZ) override { NIBBLETYPE OldMeta = a_ChunkInterface.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ); diff --git a/src/Blocks/BlockChest.h b/src/Blocks/BlockChest.h index 201f2309b..01fec7f8b 100644 --- a/src/Blocks/BlockChest.h +++ b/src/Blocks/BlockChest.h @@ -62,50 +62,11 @@ public: } // Single chest, get meta from rotation only - a_BlockMeta = RotationToMetaData(yaw); + a_BlockMeta = PlayerYawToMetaData(yaw); return true; } - virtual void OnPlacedByPlayer( - cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, - int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, - int a_CursorX, int a_CursorY, int a_CursorZ, - BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta - ) override - { - // Check if this forms a doublechest, if so, need to adjust the meta: - cBlockArea Area; - if (!Area.Read(&a_ChunkInterface, a_BlockX - 1, a_BlockX + 1, a_BlockY, a_BlockY, a_BlockZ - 1, a_BlockZ + 1)) - { - return; - } - - double rot = a_Player->GetYaw(); // FIXME: Rename rot to yaw - // Choose meta from player rotation, choose only between 2 or 3 - NIBBLETYPE NewMeta = ((rot >= -90) && (rot < 90)) ? 2 : 3; - if ( - CheckAndAdjustNeighbor(a_ChunkInterface, Area, 0, 1, NewMeta) || - CheckAndAdjustNeighbor(a_ChunkInterface, Area, 2, 1, NewMeta) - ) - { - // Forming a double chest in the X direction - return; - } - // Choose meta from player rotation, choose only between 4 or 5 - NewMeta = (rot < 0) ? 4 : 5; - if ( - CheckAndAdjustNeighbor(a_ChunkInterface, Area, 1, 0, NewMeta) || - CheckAndAdjustNeighbor(a_ChunkInterface, Area, 2, 2, NewMeta) - ) - { - // Forming a double chest in the Z direction - return; - } - - // Single chest, no further processing needed - } - virtual bool CanBeAt(cChunkInterface & a_ChunkInterface, int a_RelX, int a_RelY, int a_RelZ, const cChunk & a_Chunk) override { int BlockX = a_RelX + a_Chunk.GetPosX() * cChunkDef::Width; @@ -180,30 +141,30 @@ public: } - /// Translates player rotation when placing a chest into the chest block metadata. Valid for single chests only - static NIBBLETYPE RotationToMetaData(double a_Rotation) + /** Translates player yaw when placing a chest into the chest block metadata. Valid for single chests only */ + static NIBBLETYPE PlayerYawToMetaData(double a_Yaw) { - a_Rotation += 90 + 45; // So its not aligned with axis + a_Yaw += 90 + 45; // So its not aligned with axis - if (a_Rotation > 360.f) + if (a_Yaw > 360.f) { - a_Rotation -= 360.f; + a_Yaw -= 360.f; } - if ((a_Rotation >= 0.f) && (a_Rotation < 90.f)) + if ((a_Yaw >= 0.f) && (a_Yaw < 90.f)) { - return 0x4; + return 0x04; } - else if ((a_Rotation >= 180) && (a_Rotation < 270)) + else if ((a_Yaw >= 180) && (a_Yaw < 270)) { - return 0x5; + return 0x05; } - else if ((a_Rotation >= 90) && (a_Rotation < 180)) + else if ((a_Yaw >= 90) && (a_Yaw < 180)) { - return 0x2; + return 0x02; } else { - return 0x3; + return 0x03; } } diff --git a/src/Blocks/BlockDoor.cpp b/src/Blocks/BlockDoor.cpp index 90b7b15c2..d2bf180be 100644 --- a/src/Blocks/BlockDoor.cpp +++ b/src/Blocks/BlockDoor.cpp @@ -23,7 +23,7 @@ void cBlockDoorHandler::OnDestroyed(cChunkInterface & a_ChunkInterface, cWorldIn if (OldMeta & 8) { // Was upper part of door - if (IsDoor(a_ChunkInterface.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ))) + if (IsDoorBlockType(a_ChunkInterface.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ))) { a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0); } @@ -31,7 +31,7 @@ void cBlockDoorHandler::OnDestroyed(cChunkInterface & a_ChunkInterface, cWorldIn else { // Was lower part - if (IsDoor(a_ChunkInterface.GetBlock(a_BlockX, a_BlockY + 1, a_BlockZ))) + if (IsDoorBlockType(a_ChunkInterface.GetBlock(a_BlockX, a_BlockY + 1, a_BlockZ))) { a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY + 1, a_BlockZ, E_BLOCK_AIR, 0); } @@ -84,52 +84,34 @@ void cBlockDoorHandler::OnCancelRightClick(cChunkInterface & a_ChunkInterface, c -void cBlockDoorHandler::OnPlacedByPlayer( - cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, - int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, - int a_CursorX, int a_CursorY, int a_CursorZ, - BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta -) -{ - NIBBLETYPE a_TopBlockMeta = 8; - if ( - ((a_BlockMeta == 0) && (a_ChunkInterface.GetBlock(a_BlockX, a_BlockY, a_BlockZ - 1) == m_BlockType)) || - ((a_BlockMeta == 1) && (a_ChunkInterface.GetBlock(a_BlockX + 1, a_BlockY, a_BlockZ) == m_BlockType)) || - ((a_BlockMeta == 2) && (a_ChunkInterface.GetBlock(a_BlockX, a_BlockY, a_BlockZ + 1) == m_BlockType)) || - ((a_BlockMeta == 3) && (a_ChunkInterface.GetBlock(a_BlockX - 1, a_BlockY, a_BlockZ) == m_BlockType)) - ) - { - a_TopBlockMeta = 9; - } - a_ChunkInterface.SetBlock(a_BlockX, a_BlockY + 1, a_BlockZ, m_BlockType, a_TopBlockMeta); -} - - - - - NIBBLETYPE cBlockDoorHandler::MetaRotateCCW(NIBBLETYPE a_Meta) { if (a_Meta & 0x08) { + // The meta doesn't change for the top block return a_Meta; } else { + // Rotate the bottom block return super::MetaRotateCCW(a_Meta); } } + + NIBBLETYPE cBlockDoorHandler::MetaRotateCW(NIBBLETYPE a_Meta) { if (a_Meta & 0x08) { + // The meta doesn't change for the top block return a_Meta; } else { + // Rotate the bottom block return super::MetaRotateCW(a_Meta); } } @@ -138,8 +120,10 @@ NIBBLETYPE cBlockDoorHandler::MetaRotateCW(NIBBLETYPE a_Meta) NIBBLETYPE cBlockDoorHandler::MetaMirrorXY(NIBBLETYPE a_Meta) { - // Top bit (0x08) contains door panel type (Top/Bottom panel) Only Bottom panels contain position data - // Return a_Meta if panel is a top panel (0x08 bit is set to 1) + /* + Top bit (0x08) contains door block position (Top / Bottom). Only Bottom blocks contain position data + Return a_Meta if panel is a top panel (0x08 bit is set to 1) + */ // Note: Currently, you can not properly mirror the hinges on a double door. The orientation of the door is stored // in only the bottom tile while the hinge position is in the top tile. This function only operates on one tile at a time, diff --git a/src/Blocks/BlockDoor.h b/src/Blocks/BlockDoor.h index 92ad8da12..334519077 100644 --- a/src/Blocks/BlockDoor.h +++ b/src/Blocks/BlockDoor.h @@ -101,14 +101,6 @@ public: } - virtual void OnPlacedByPlayer( - cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, - int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, - int a_CursorX, int a_CursorY, int a_CursorZ, - BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta - ) override; - - virtual bool IsUseable(void) override { return true; @@ -117,11 +109,19 @@ public: virtual bool CanBeAt(cChunkInterface & a_ChunkInterface, int a_RelX, int a_RelY, int a_RelZ, const cChunk & a_Chunk) override { - return ((a_RelY > 0) && (a_Chunk.GetBlock(a_RelX, a_RelY - 1, a_RelZ) != E_BLOCK_AIR)); + return ((a_RelY > 0) && CanBeOn(a_Chunk.GetBlock(a_RelX, a_RelY - 1, a_RelZ))); + } + + + /** Returns true if door can be placed on the specified block type. */ + static bool CanBeOn(BLOCKTYPE a_BlockType) + { + // Vanilla refuses to place doors on transparent blocks + return !cBlockInfo::IsTransparent(a_BlockType); } - bool CanReplaceBlock(BLOCKTYPE a_BlockType) + static bool CanReplaceBlock(BLOCKTYPE a_BlockType) { switch (a_BlockType) { @@ -170,8 +170,21 @@ public: } + /** Returns a vector pointing one block in the direction the door is facing (where the outside is). */ + inline static Vector3i GetRelativeDirectionToOutside(NIBBLETYPE a_BlockMeta) + { + switch (a_BlockMeta & 0x03) + { + case 0: return Vector3i(-1, 0, 0); // Facing West / XM + case 1: return Vector3i( 0, 0, -1); // Facing North / ZM + case 2: return Vector3i( 1, 0, 0); // Facing East / XP + default: return Vector3i( 0, 0, 1); // Facing South / ZP + } + } + + /** Returns true if the specified blocktype is any kind of door */ - inline static bool IsDoor(BLOCKTYPE a_Block) + inline static bool IsDoorBlockType(BLOCKTYPE a_Block) { switch (a_Block) { @@ -193,6 +206,8 @@ public: } + /** Returns true iff the door at the specified coords is open. + The coords may point to either the top part or the bottom part of the door. */ static NIBBLETYPE IsOpen(cChunkInterface & a_ChunkInterface, int a_BlockX, int a_BlockY, int a_BlockZ) { NIBBLETYPE Meta = GetCompleteDoorMeta(a_ChunkInterface, a_BlockX, a_BlockY, a_BlockZ); @@ -237,7 +252,7 @@ public: static void SetOpen(cChunkInterface & a_ChunkInterface, int a_BlockX, int a_BlockY, int a_BlockZ, bool a_Open) { BLOCKTYPE Block = a_ChunkInterface.GetBlock(a_BlockX, a_BlockY, a_BlockZ); - if (!IsDoor(Block)) + if (!IsDoorBlockType(Block)) { return; } diff --git a/src/Blocks/BlockHandler.cpp b/src/Blocks/BlockHandler.cpp index d532aa1dc..2de4a3e4c 100644 --- a/src/Blocks/BlockHandler.cpp +++ b/src/Blocks/BlockHandler.cpp @@ -369,7 +369,7 @@ void cBlockHandler::OnUpdate(cChunkInterface & cChunkInterface, cWorldInterface -void cBlockHandler::OnPlacedByPlayer(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) +void cBlockHandler::OnPlacedByPlayer(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, const sSetBlock & a_BlockChange) { } diff --git a/src/Blocks/BlockHandler.h b/src/Blocks/BlockHandler.h index f2298afb5..4dec0dc95 100644 --- a/src/Blocks/BlockHandler.h +++ b/src/Blocks/BlockHandler.h @@ -42,15 +42,12 @@ public: BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta ); - /// Called by cWorld::SetBlock() after the block has been set + /** Called by cWorld::SetBlock() after the block has been set */ virtual void OnPlaced(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); - /// Called by cClientHandle::HandlePlaceBlock() after the player has placed a new block. Called after OnPlaced(). + /** Called by cPlayer::PlaceBlocks() for each block after it has been set to the world. Called after OnPlaced(). */ virtual void OnPlacedByPlayer( - cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, - int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, - int a_CursorX, int a_CursorY, int a_CursorZ, - BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta + cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, const sSetBlock & a_BlockChange ); /// Called before the player has destroyed a block @@ -96,7 +93,8 @@ public: */ // virtual bool CanBePlacedAt(cWorld * a_World, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Dir); - /// Called to check whether this block supports a rclk action. If it returns true, OnUse() is called + /** Called to check whether this block supports a rclk action. + If it returns true, OnUse() is called */ virtual bool IsUseable(void); /** Indicates whether the client will click through this block. @@ -109,20 +107,21 @@ public: */ virtual bool DoesIgnoreBuildCollision(void); - /// Similar to DoesIgnoreBuildCollision(void), but is used for cases where block meta/player item-in-hand is needed to determine collision (thin snow) + /** Similar to DoesIgnoreBuildCollision(void), but is used for cases where block's meta or + player's item-in-hand is needed to determine collision (thin snow) */ virtual bool DoesIgnoreBuildCollision(cPlayer *, NIBBLETYPE a_Meta) { UNUSED(a_Meta); return DoesIgnoreBuildCollision(); } - /// Returns if this block drops if it gets destroyed by an unsuitable situation. Default: true + /** Returns if this block drops if it gets destroyed by an unsuitable situation. + Default: true */ virtual bool DoesDropOnUnsuitable(void); /** Called when one of the neighbors gets set; equivalent to MC block update. By default drops if position no more suitable (CanBeAt(), DoesDropOnUnsuitable(), Drop()), - and wakes up all simulators on the block. - */ + and wakes up all simulators on the block. */ virtual void Check(cChunkInterface & ChunkInterface, cBlockPluginInterface & a_PluginInterface, int a_RelX, int a_RelY, int a_RelZ, cChunk & a_Chunk); /// Rotates a given block meta counter-clockwise. Default: no change diff --git a/src/Blocks/BlockMobHead.h b/src/Blocks/BlockMobHead.h index e21e42334..cb8143749 100644 --- a/src/Blocks/BlockMobHead.h +++ b/src/Blocks/BlockMobHead.h @@ -12,16 +12,18 @@ class cBlockMobHeadHandler : public cBlockEntityHandler { public: - cBlockMobHeadHandler(BLOCKTYPE a_BlockType) - : cBlockEntityHandler(a_BlockType) + cBlockMobHeadHandler(BLOCKTYPE a_BlockType): + cBlockEntityHandler(a_BlockType) { } + virtual void ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_BlockMeta) override { - // The drop spawn is in OnDestroyed method + // The drop spawn is in the OnDestroyedByPlayer method } + virtual void OnDestroyedByPlayer(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, int a_BlockX, int a_BlockY, int a_BlockZ) override { if (a_Player->IsGameModeCreative()) @@ -61,202 +63,6 @@ public: a_WorldInterface.DoWithBlockEntityAt(a_BlockX, a_BlockY, a_BlockZ, Callback); } - - bool TrySpawnWither(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, int a_BlockX, int a_BlockY, int a_BlockZ) - { - if (a_BlockY < 2) - { - return false; - } - - class cCallback : public cBlockEntityCallback - { - bool m_IsWither; - - virtual bool Item(cBlockEntity * a_BlockEntity) - { - if (a_BlockEntity->GetBlockType() != E_BLOCK_HEAD) - { - return false; - } - cMobHeadEntity * MobHeadEntity = static_cast(a_BlockEntity); - - m_IsWither = (MobHeadEntity->GetType() == SKULL_TYPE_WITHER); - return false; - } - - public: - cCallback () : m_IsWither(false) {} - - bool IsWither(void) const { return m_IsWither; } - - void Reset(void) { m_IsWither = false; } - - } CallbackA, CallbackB; - - class cPlayerCallback : public cPlayerListCallback - { - Vector3f m_Pos; - - virtual bool Item(cPlayer * a_Player) - { - // TODO 2014-05-21 xdot: Vanilla minecraft uses an AABB check instead of a radius one - double Dist = (a_Player->GetPosition() - m_Pos).Length(); - if (Dist < 50.0) - { - // If player is close, award achievement - a_Player->AwardAchievement(achSpawnWither); - } - return false; - } - - public: - cPlayerCallback(const Vector3f & a_Pos) : m_Pos(a_Pos) {} - - } PlayerCallback(Vector3f((float)a_BlockX, (float)a_BlockY, (float)a_BlockZ)); - - a_WorldInterface.DoWithBlockEntityAt(a_BlockX, a_BlockY, a_BlockZ, CallbackA); - - if (!CallbackA.IsWither()) - { - return false; - } - - CallbackA.Reset(); - - BLOCKTYPE BlockY1 = a_ChunkInterface.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ); - BLOCKTYPE BlockY2 = a_ChunkInterface.GetBlock(a_BlockX, a_BlockY - 2, a_BlockZ); - - if ((BlockY1 != E_BLOCK_SOULSAND) || (BlockY2 != E_BLOCK_SOULSAND)) - { - return false; - } - - a_WorldInterface.DoWithBlockEntityAt(a_BlockX - 1, a_BlockY, a_BlockZ, CallbackA); - a_WorldInterface.DoWithBlockEntityAt(a_BlockX + 1, a_BlockY, a_BlockZ, CallbackB); - - BLOCKTYPE Block1 = a_ChunkInterface.GetBlock(a_BlockX - 1, a_BlockY - 1, a_BlockZ); - BLOCKTYPE Block2 = a_ChunkInterface.GetBlock(a_BlockX + 1, a_BlockY - 1, a_BlockZ); - - if ((Block1 == E_BLOCK_SOULSAND) && (Block2 == E_BLOCK_SOULSAND) && CallbackA.IsWither() && CallbackB.IsWither()) - { - a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0); - a_ChunkInterface.FastSetBlock(a_BlockX + 1, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0); - a_ChunkInterface.FastSetBlock(a_BlockX - 1, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0); - a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 2, a_BlockZ, E_BLOCK_AIR, 0); - - // Block entities - a_ChunkInterface.SetBlock(a_BlockX + 1, a_BlockY, a_BlockZ, E_BLOCK_AIR, 0); - a_ChunkInterface.SetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_AIR, 0); - a_ChunkInterface.SetBlock(a_BlockX - 1, a_BlockY, a_BlockZ, E_BLOCK_AIR, 0); - - // Spawn the wither: - a_WorldInterface.SpawnMob(a_BlockX + 0.5, a_BlockY - 2, a_BlockZ + 0.5, mtWither); - - // Award Achievement - a_WorldInterface.ForEachPlayer(PlayerCallback); - - return true; - } - - CallbackA.Reset(); - CallbackB.Reset(); - - a_WorldInterface.DoWithBlockEntityAt(a_BlockX, a_BlockY, a_BlockZ - 1, CallbackA); - a_WorldInterface.DoWithBlockEntityAt(a_BlockX, a_BlockY, a_BlockZ + 1, CallbackB); - - Block1 = a_ChunkInterface.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ - 1); - Block2 = a_ChunkInterface.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ + 1); - - if ((Block1 == E_BLOCK_SOULSAND) && (Block2 == E_BLOCK_SOULSAND) && CallbackA.IsWither() && CallbackB.IsWither()) - { - a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0); - a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 1, a_BlockZ + 1, E_BLOCK_AIR, 0); - a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 1, a_BlockZ - 1, E_BLOCK_AIR, 0); - a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 2, a_BlockZ, E_BLOCK_AIR, 0); - - // Block entities - a_ChunkInterface.SetBlock(a_BlockX, a_BlockY, a_BlockZ + 1, E_BLOCK_AIR, 0); - a_ChunkInterface.SetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_AIR, 0); - a_ChunkInterface.SetBlock(a_BlockX, a_BlockY, a_BlockZ - 1, E_BLOCK_AIR, 0); - - // Spawn the wither: - a_WorldInterface.SpawnMob(a_BlockX + 0.5, a_BlockY - 2, a_BlockZ + 0.5, mtWither); - - // Award Achievement - a_WorldInterface.ForEachPlayer(PlayerCallback); - - return true; - } - - return false; - } - - virtual void OnPlacedByPlayer( - cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, - int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, - int a_CursorX, int a_CursorY, int a_CursorZ, - BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta - ) override - { - class cCallback : public cBlockEntityCallback - { - cPlayer * m_Player; - NIBBLETYPE m_OldBlockMeta; - NIBBLETYPE m_NewBlockMeta; - - virtual bool Item(cBlockEntity * a_BlockEntity) - { - if (a_BlockEntity->GetBlockType() != E_BLOCK_HEAD) - { - return false; - } - cMobHeadEntity * MobHeadEntity = static_cast(a_BlockEntity); - - int Rotation = 0; - if (m_NewBlockMeta == 1) - { - Rotation = (int) floor(m_Player->GetYaw() * 16.0F / 360.0F + 0.5) & 0xF; - } - - MobHeadEntity->SetType(static_cast(m_OldBlockMeta)); - MobHeadEntity->SetRotation(static_cast(Rotation)); - MobHeadEntity->GetWorld()->BroadcastBlockEntity(MobHeadEntity->GetPosX(), MobHeadEntity->GetPosY(), MobHeadEntity->GetPosZ()); - return false; - } - - public: - cCallback (cPlayer * a_CBPlayer, NIBBLETYPE a_OldBlockMeta, NIBBLETYPE a_NewBlockMeta) : - m_Player(a_CBPlayer), - m_OldBlockMeta(a_OldBlockMeta), - m_NewBlockMeta(a_NewBlockMeta) - {} - }; - cCallback Callback(a_Player, a_BlockMeta, static_cast(a_BlockFace)); - - a_BlockMeta = (NIBBLETYPE)a_BlockFace; - a_WorldInterface.DoWithBlockEntityAt(a_BlockX, a_BlockY, a_BlockZ, Callback); - a_ChunkInterface.SetBlockMeta(a_BlockX, a_BlockY, a_BlockZ, a_BlockMeta); - - if (a_BlockMeta == SKULL_TYPE_WITHER) - { - static const Vector3i Coords[] = - { - Vector3i( 0, 0, 0), - Vector3i( 1, 0, 0), - Vector3i(-1, 0, 0), - Vector3i( 0, 0, 1), - Vector3i( 0, 0, -1), - }; - for (size_t i = 0; i < ARRAYCOUNT(Coords); ++i) - { - if (TrySpawnWither(a_ChunkInterface, a_WorldInterface, a_BlockX + Coords[i].x, a_BlockY, a_BlockZ + Coords[i].z)) - { - break; - } - } // for i - Coords[] - } - } } ; diff --git a/src/Blocks/BlockPumpkin.h b/src/Blocks/BlockPumpkin.h index 275d1422a..af00fbe8e 100644 --- a/src/Blocks/BlockPumpkin.h +++ b/src/Blocks/BlockPumpkin.h @@ -17,70 +17,6 @@ public: } - virtual void OnPlacedByPlayer(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override - { - // Check whether the pumpkin is a part of a golem or a snowman - - if (a_BlockY < 2) - { - // The pumpkin is too low for a golem / snowman - return; - } - - BLOCKTYPE BlockY1 = a_ChunkInterface.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ); - BLOCKTYPE BlockY2 = a_ChunkInterface.GetBlock(a_BlockX, a_BlockY - 2, a_BlockZ); - - // Check for a snow golem: - if ((BlockY1 == E_BLOCK_SNOW_BLOCK) && (BlockY2 == E_BLOCK_SNOW_BLOCK)) - { - a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_AIR, 0); - a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0); - a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 2, a_BlockZ, E_BLOCK_AIR, 0); - a_WorldInterface.SpawnMob(a_BlockX + 0.5, a_BlockY - 2, a_BlockZ + 0.5, mtSnowGolem); - return; - } - - // Check for an iron golem. First check only the body and legs, since those are the same for both orientations: - if ((BlockY1 != E_BLOCK_IRON_BLOCK) || (BlockY2 != E_BLOCK_IRON_BLOCK)) - { - // One of the blocks is not an iron, no chance of a golem here - return; - } - - // Now check both orientations for hands: - if ( - (a_ChunkInterface.GetBlock(a_BlockX + 1, a_BlockY - 1, a_BlockZ) == E_BLOCK_IRON_BLOCK) && - (a_ChunkInterface.GetBlock(a_BlockX - 1, a_BlockY - 1, a_BlockZ) == E_BLOCK_IRON_BLOCK) - ) - { - // Remove the iron blocks: - a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_AIR, 0); - a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0); - a_ChunkInterface.FastSetBlock(a_BlockX + 1, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0); - a_ChunkInterface.FastSetBlock(a_BlockX - 1, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0); - a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 2, a_BlockZ, E_BLOCK_AIR, 0); - - // Spawn the golem: - a_WorldInterface.SpawnMob(a_BlockX + 0.5, a_BlockY - 2, a_BlockZ + 0.5, mtIronGolem); - } - else if ( - (a_ChunkInterface.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ + 1) == E_BLOCK_IRON_BLOCK) && - (a_ChunkInterface.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ - 1) == E_BLOCK_IRON_BLOCK) - ) - { - // Remove the iron blocks: - a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_AIR, 0); - a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0); - a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 1, a_BlockZ + 1, E_BLOCK_AIR, 0); - a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 1, a_BlockZ - 1, E_BLOCK_AIR, 0); - a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY - 2, a_BlockZ, E_BLOCK_AIR, 0); - - // Spawn the golem: - a_WorldInterface.SpawnMob(a_BlockX + 0.5, a_BlockY - 2, a_BlockZ + 0.5, mtIronGolem); - } - } - - virtual bool GetPlacementBlockTypeMeta( cChunkInterface & a_ChunkInterface, cPlayer * a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, diff --git a/src/Blocks/BlockSignPost.h b/src/Blocks/BlockSignPost.h index d97501651..99c000633 100644 --- a/src/Blocks/BlockSignPost.h +++ b/src/Blocks/BlockSignPost.h @@ -53,17 +53,6 @@ public: } - virtual void OnPlacedByPlayer( - cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, - int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, - int a_CursorX, int a_CursorY, int a_CursorZ, - BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta - ) override - { - a_Player->GetClientHandle()->SendEditSign(a_BlockX, a_BlockY, a_BlockZ); - } - - virtual NIBBLETYPE MetaRotateCW(NIBBLETYPE a_Meta) override { return (a_Meta + 4) & 0x0f; diff --git a/src/Blocks/BlockWallSign.h b/src/Blocks/BlockWallSign.h index 0abe9c52c..b6599d033 100644 --- a/src/Blocks/BlockWallSign.h +++ b/src/Blocks/BlockWallSign.h @@ -27,17 +27,6 @@ public: } - virtual void OnPlacedByPlayer( - cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, - int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, - int a_CursorX, int a_CursorY, int a_CursorZ, - BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta - ) override - { - a_Player->GetClientHandle()->SendEditSign(a_BlockX, a_BlockY, a_BlockZ); - } - - virtual bool CanBeAt(cChunkInterface & a_ChunkInterface, int a_RelX, int a_RelY, int a_RelZ, const cChunk & a_Chunk) override { int BlockX = (a_Chunk.GetPosX() * cChunkDef::Width) + a_RelX; diff --git a/src/Chunk.cpp b/src/Chunk.cpp index 555e85015..b06eed316 100644 --- a/src/Chunk.cpp +++ b/src/Chunk.cpp @@ -49,14 +49,14 @@ //////////////////////////////////////////////////////////////////////////////// // sSetBlock: -sSetBlock::sSetBlock( int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) // absolute block position - : x( a_BlockX) - , y( a_BlockY) - , z( a_BlockZ) - , BlockType( a_BlockType) - , BlockMeta( a_BlockMeta) -{ - cChunkDef::AbsoluteToRelative(x, y, z, ChunkX, ChunkZ); +sSetBlock::sSetBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta): + m_RelX(a_BlockX), + m_RelY(a_BlockY), + m_RelZ(a_BlockZ), + m_BlockType(a_BlockType), + m_BlockMeta(a_BlockMeta) +{ + cChunkDef::AbsoluteToRelative(m_RelX, m_RelY, m_RelZ, m_ChunkX, m_ChunkZ); } diff --git a/src/ChunkDef.h b/src/ChunkDef.h index 8f1d416ad..b62656a58 100644 --- a/src/ChunkDef.h +++ b/src/ChunkDef.h @@ -347,18 +347,32 @@ public: struct sSetBlock { - int x, y, z; - int ChunkX, ChunkZ; - BLOCKTYPE BlockType; - NIBBLETYPE BlockMeta; - - sSetBlock( int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); // absolute block position - sSetBlock(int a_ChunkX, int a_ChunkZ, int a_X, int a_Y, int a_Z, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) : - x(a_X), y(a_Y), z(a_Z), - ChunkX(a_ChunkX), ChunkZ(a_ChunkZ), - BlockType(a_BlockType), - BlockMeta(a_BlockMeta) - {} + int m_RelX, m_RelY, m_RelZ; + int m_ChunkX, m_ChunkZ; + BLOCKTYPE m_BlockType; + NIBBLETYPE m_BlockMeta; + + sSetBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); + + sSetBlock(int a_ChunkX, int a_ChunkZ, int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) : + m_RelX(a_RelX), m_RelY(a_RelY), m_RelZ(a_RelZ), + m_ChunkX(a_ChunkX), m_ChunkZ(a_ChunkZ), + m_BlockType(a_BlockType), + m_BlockMeta(a_BlockMeta) + { + ASSERT((a_RelX >= 0) && (a_RelX < cChunkDef::Width)); + ASSERT((a_RelZ >= 0) && (a_RelZ < cChunkDef::Width)); + } + + /** Returns the absolute X coord of the stored block. */ + int GetX(void) const { return m_RelX + cChunkDef::Width * m_ChunkX; } + + /** Returns the absolute Y coord of the stored block. + Is the same as relative Y coords, because there's no Y relativization. */ + int GetY(void) const { return m_RelY; } + + /** Returns the absolute Z coord of the stored block. */ + int GetZ(void) const { return m_RelZ + cChunkDef::Width * m_ChunkZ; } }; typedef std::list sSetBlockList; @@ -385,6 +399,16 @@ public: typedef std::list cChunkCoordsList; typedef std::vector cChunkCoordsVector; +/** A simple hash function for chunk coords, we assume that chunk coords won't use more than 16 bits, so the hash is almost an identity. +Used for std::unordered_map */ +template<> struct std::hash +{ + size_t operator ()(const cChunkCoords & a_Coords) + { + return (static_cast(a_Coords.m_ChunkX) << 16) ^ static_cast(a_Coords.m_ChunkZ); + } +}; + diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp index 6aff3a754..c8f5aa673 100644 --- a/src/ChunkMap.cpp +++ b/src/ChunkMap.cpp @@ -1101,17 +1101,17 @@ void cChunkMap::FastSetBlocks(sSetBlockList & a_BlockList) // Process all items from a_BlockList, either successfully or by placing into Failed while (!a_BlockList.empty()) { - int ChunkX = a_BlockList.front().ChunkX; - int ChunkZ = a_BlockList.front().ChunkZ; + int ChunkX = a_BlockList.front().m_ChunkX; + int ChunkZ = a_BlockList.front().m_ChunkZ; cCSLock Lock(m_CSLayers); cChunkPtr Chunk = GetChunkNoGen(ChunkX, ChunkZ); if ((Chunk != nullptr) && Chunk->IsValid()) { for (sSetBlockList::iterator itr = a_BlockList.begin(); itr != a_BlockList.end();) { - if ((itr->ChunkX == ChunkX) && (itr->ChunkZ == ChunkZ)) + if ((itr->m_ChunkX == ChunkX) && (itr->m_ChunkZ == ChunkZ)) { - Chunk->FastSetBlock(itr->x, itr->y, itr->z, itr->BlockType, itr->BlockMeta); + Chunk->FastSetBlock(itr->m_RelX, itr->m_RelY, itr->m_RelZ, itr->m_BlockType, itr->m_BlockMeta); itr = a_BlockList.erase(itr); } else @@ -1125,7 +1125,7 @@ void cChunkMap::FastSetBlocks(sSetBlockList & a_BlockList) // The chunk is not valid, move all blocks within this chunk to Failed for (sSetBlockList::iterator itr = a_BlockList.begin(); itr != a_BlockList.end();) { - if ((itr->ChunkX == ChunkX) && (itr->ChunkZ == ChunkZ)) + if ((itr->m_ChunkX == ChunkX) && (itr->m_ChunkZ == ChunkZ)) { Failed.push_back(*itr); itr = a_BlockList.erase(itr); @@ -1146,6 +1146,34 @@ void cChunkMap::FastSetBlocks(sSetBlockList & a_BlockList) +void cChunkMap::SetBlocks(const sSetBlockVector & a_Blocks) +{ + cCSLock lock(m_CSLayers); + cChunkPtr chunk = nullptr; + int lastChunkX = 0x7fffffff; // Bogus coords so that chunk is updated on first pass + int lastChunkZ = 0x7fffffff; + for (auto block: a_Blocks) + { + // Update the chunk, if different from last time: + if ((block.m_ChunkX != lastChunkX) || (block.m_ChunkZ != lastChunkZ)) + { + lastChunkX = block.m_ChunkX; + lastChunkZ = block.m_ChunkZ; + chunk = GetChunk(lastChunkX, lastChunkZ); + } + + // If the chunk is valid, set the block: + if (chunk != nullptr) + { + chunk->SetBlock(block.m_RelX, block.m_RelY, block.m_RelZ, block.m_BlockType, block.m_BlockMeta); + } + } // for block - a_Blocks[] +} + + + + + void cChunkMap::CollectPickupsByPlayer(cPlayer & a_Player) { int BlockX = (int)(a_Player.GetPosX()); // Truncating doesn't matter much; we're scanning entire chunks anyway @@ -1175,28 +1203,28 @@ void cChunkMap::CollectPickupsByPlayer(cPlayer & a_Player) BLOCKTYPE cChunkMap::GetBlock(int a_BlockX, int a_BlockY, int a_BlockZ) { + int X = a_BlockX, Y = a_BlockY, Z = a_BlockZ; + int ChunkX, ChunkZ; + cChunkDef::AbsoluteToRelative(X, Y, Z, ChunkX, ChunkZ); + // First check if it isn't queued in the m_FastSetBlockQueue: { - int X = a_BlockX, Y = a_BlockY, Z = a_BlockZ; - int ChunkX, ChunkZ; - cChunkDef::AbsoluteToRelative(X, Y, Z, ChunkX, ChunkZ); cCSLock Lock(m_CSFastSetBlock); for (sSetBlockList::iterator itr = m_FastSetBlockQueue.begin(); itr != m_FastSetBlockQueue.end(); ++itr) { - if ((itr->x == X) && (itr->y == Y) && (itr->z == Z) && (itr->ChunkX == ChunkX) && (itr->ChunkZ == ChunkZ)) + if ((itr->m_RelX == X) && (itr->m_RelY == Y) && (itr->m_RelZ == Z) && (itr->m_ChunkX == ChunkX) && (itr->m_ChunkZ == ChunkZ)) { - return itr->BlockType; + return itr->m_BlockType; } } // for itr - m_FastSetBlockQueue[] } - int ChunkX, ChunkZ; - cChunkDef::AbsoluteToRelative(a_BlockX, a_BlockY, a_BlockZ, ChunkX, ChunkZ); - + + // Not in the queue, query the chunk, if loaded: cCSLock Lock(m_CSLayers); cChunkPtr Chunk = GetChunk(ChunkX, ChunkZ); if ((Chunk != nullptr) && Chunk->IsValid()) { - return Chunk->GetBlock(a_BlockX, a_BlockY, a_BlockZ); + return Chunk->GetBlock(X, Y, Z); } return 0; } @@ -1207,25 +1235,28 @@ BLOCKTYPE cChunkMap::GetBlock(int a_BlockX, int a_BlockY, int a_BlockZ) NIBBLETYPE cChunkMap::GetBlockMeta(int a_BlockX, int a_BlockY, int a_BlockZ) { + int X = a_BlockX, Y = a_BlockY, Z = a_BlockZ; + int ChunkX, ChunkZ; + cChunkDef::AbsoluteToRelative(X, Y, Z, ChunkX, ChunkZ); + // First check if it isn't queued in the m_FastSetBlockQueue: { cCSLock Lock(m_CSFastSetBlock); for (sSetBlockList::iterator itr = m_FastSetBlockQueue.begin(); itr != m_FastSetBlockQueue.end(); ++itr) { - if ((itr->x == a_BlockX) && (itr->y == a_BlockY) && (itr->z == a_BlockZ)) + if ((itr->m_RelX == X) && (itr->m_RelY == Y) && (itr->m_RelZ == Z) && (itr->m_ChunkX == ChunkX) && (itr->m_ChunkZ == ChunkZ)) { - return itr->BlockMeta; + return itr->m_BlockMeta; } } // for itr - m_FastSetBlockQueue[] } - int ChunkX, ChunkZ; - cChunkDef::AbsoluteToRelative(a_BlockX, a_BlockY, a_BlockZ, ChunkX, ChunkZ); - + + // Not in the queue, query the chunk, if loaded: cCSLock Lock(m_CSLayers); - cChunkPtr Chunk = GetChunk( ChunkX, ChunkZ); + cChunkPtr Chunk = GetChunk(ChunkX, ChunkZ); if ((Chunk != nullptr) && Chunk->IsValid()) { - return Chunk->GetMeta(a_BlockX, a_BlockY, a_BlockZ); + return Chunk->GetMeta(X, Y, Z); } return 0; } @@ -1373,14 +1404,14 @@ void cChunkMap::ReplaceBlocks(const sSetBlockVector & a_Blocks, BLOCKTYPE a_Filt cCSLock Lock(m_CSLayers); for (sSetBlockVector::const_iterator itr = a_Blocks.begin(); itr != a_Blocks.end(); ++itr) { - cChunkPtr Chunk = GetChunk(itr->ChunkX, itr->ChunkZ); + cChunkPtr Chunk = GetChunk(itr->m_ChunkX, itr->m_ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { continue; } - if (Chunk->GetBlock(itr->x, itr->y, itr->z) == a_FilterBlockType) + if (Chunk->GetBlock(itr->m_RelX, itr->m_RelY, itr->m_RelZ) == a_FilterBlockType) { - Chunk->SetBlock(itr->x, itr->y, itr->z, itr->BlockType, itr->BlockMeta); + Chunk->SetBlock(itr->m_RelX, itr->m_RelY, itr->m_RelZ, itr->m_BlockType, itr->m_BlockMeta); } } } @@ -1394,24 +1425,24 @@ void cChunkMap::ReplaceTreeBlocks(const sSetBlockVector & a_Blocks) cCSLock Lock(m_CSLayers); for (sSetBlockVector::const_iterator itr = a_Blocks.begin(); itr != a_Blocks.end(); ++itr) { - cChunkPtr Chunk = GetChunk(itr->ChunkX, itr->ChunkZ); + cChunkPtr Chunk = GetChunk(itr->m_ChunkX, itr->m_ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { continue; } - switch (Chunk->GetBlock(itr->x, itr->y, itr->z)) + switch (Chunk->GetBlock(itr->m_RelX, itr->m_RelY, itr->m_RelZ)) { CASE_TREE_OVERWRITTEN_BLOCKS: { - Chunk->SetBlock(itr->x, itr->y, itr->z, itr->BlockType, itr->BlockMeta); + Chunk->SetBlock(itr->m_RelX, itr->m_RelY, itr->m_RelZ, itr->m_BlockType, itr->m_BlockMeta); break; } case E_BLOCK_LEAVES: case E_BLOCK_NEW_LEAVES: { - if ((itr->BlockType == E_BLOCK_LOG) || (itr->BlockType == E_BLOCK_NEW_LOG)) + if ((itr->m_BlockType == E_BLOCK_LOG) || (itr->m_BlockType == E_BLOCK_NEW_LOG)) { - Chunk->SetBlock(itr->x, itr->y, itr->z, itr->BlockType, itr->BlockMeta); + Chunk->SetBlock(itr->m_RelX, itr->m_RelY, itr->m_RelZ, itr->m_BlockType, itr->m_BlockMeta); } break; } @@ -1507,7 +1538,7 @@ bool cChunkMap::GetBlocks(sSetBlockVector & a_Blocks, bool a_ContinueOnFailure) cCSLock Lock(m_CSLayers); for (sSetBlockVector::iterator itr = a_Blocks.begin(); itr != a_Blocks.end(); ++itr) { - cChunkPtr Chunk = GetChunk(itr->ChunkX, itr->ChunkZ); + cChunkPtr Chunk = GetChunk(itr->m_ChunkX, itr->m_ChunkZ); if ((Chunk == nullptr) || !Chunk->IsValid()) { if (!a_ContinueOnFailure) @@ -1517,8 +1548,8 @@ bool cChunkMap::GetBlocks(sSetBlockVector & a_Blocks, bool a_ContinueOnFailure) res = false; continue; } - itr->BlockType = Chunk->GetBlock(itr->x, itr->y, itr->z); - itr->BlockMeta = Chunk->GetMeta(itr->x, itr->y, itr->z); + itr->m_BlockType = Chunk->GetBlock(itr->m_RelX, itr->m_RelY, itr->m_RelZ); + itr->m_BlockMeta = Chunk->GetMeta(itr->m_RelX, itr->m_RelY, itr->m_RelZ); } return res; } @@ -1527,11 +1558,11 @@ bool cChunkMap::GetBlocks(sSetBlockVector & a_Blocks, bool a_ContinueOnFailure) -bool cChunkMap::DigBlock(int a_X, int a_Y, int a_Z) +bool cChunkMap::DigBlock(int a_BlockX, int a_BlockY, int a_BlockZ) { - int PosX = a_X, PosY = a_Y, PosZ = a_Z, ChunkX, ChunkZ; + int PosX = a_BlockX, PosY = a_BlockY, PosZ = a_BlockZ, ChunkX, ChunkZ; - cChunkDef::AbsoluteToRelative( PosX, PosY, PosZ, ChunkX, ChunkZ); + cChunkDef::AbsoluteToRelative(PosX, PosY, PosZ, ChunkX, ChunkZ); { cCSLock Lock(m_CSLayers); @@ -1542,7 +1573,7 @@ bool cChunkMap::DigBlock(int a_X, int a_Y, int a_Z) } DestChunk->SetBlock(PosX, PosY, PosZ, E_BLOCK_AIR, 0); - m_World->GetSimulatorManager()->WakeUp(a_X, a_Y, a_Z, DestChunk); + m_World->GetSimulatorManager()->WakeUp(a_BlockX, a_BlockY, a_BlockZ, DestChunk); } return true; diff --git a/src/ChunkMap.h b/src/ChunkMap.h index 6a858b06d..fe0bb3733 100644 --- a/src/ChunkMap.h +++ b/src/ChunkMap.h @@ -143,7 +143,13 @@ public: void FastSetBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); void FastSetQueuedBlocks(); - void FastSetBlocks (sSetBlockList & a_BlockList); + void FastSetBlocks(sSetBlockList & a_BlockList); + + /** Performs the specified single-block set operations simultaneously, as if SetBlock() was called for each item. + Is more efficient than calling SetBlock() multiple times. + If the chunk for any of the blocks is not loaded, the set operation is ignored silently. */ + void SetBlocks(const sSetBlockVector & a_Blocks); + void CollectPickupsByPlayer(cPlayer & a_Player); BLOCKTYPE GetBlock (int a_BlockX, int a_BlockY, int a_BlockZ); @@ -173,11 +179,20 @@ public: (Re)sends the chunks to their relevant clients if successful. */ bool SetAreaBiome(int a_MinX, int a_MaxX, int a_MinZ, int a_MaxZ, EMCSBiome a_Biome); - /** Retrieves block types of the specified blocks. If a chunk is not loaded, doesn't modify the block. Returns true if all blocks were read. */ + /** Retrieves block types and metas of the specified blocks. + If a chunk is not loaded, doesn't modify the block and consults a_ContinueOnFailure whether to process the rest of the array. + Returns true if all blocks were read, false if any one failed. */ bool GetBlocks(sSetBlockVector & a_Blocks, bool a_ContinueOnFailure); - bool DigBlock (int a_X, int a_Y, int a_Z); - void SendBlockTo(int a_X, int a_Y, int a_Z, cPlayer * a_Player); + /** Removes the block at the specified coords and wakes up simulators. + Returns false if the chunk is not loaded (and the block is not dug). + Returns true if successful. */ + bool DigBlock(int a_BlockX, int a_BlockY, int a_BlockZ); + + /** Sends the block at the specified coords to the specified player. + Uses a blockchange packet to send the block. + If the relevant chunk isn't loaded, doesn't do anything. */ + void SendBlockTo(int a_BlockX, int a_BlockY, int a_BlockZ, cPlayer * a_Player); /** Compares clients of two chunks, calls the callback accordingly */ void CompareChunkClients(int a_ChunkX1, int a_ChunkZ1, int a_ChunkX2, int a_ChunkZ2, cClientDiffCallback & a_Callback); diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index cb9d34c84..6fe6e99fa 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -1346,12 +1346,19 @@ void cClientHandle::HandleRightClick(int a_BlockX, int a_BlockY, int a_BlockZ, e if (ItemHandler->IsPlaceable() && (a_BlockFace != BLOCK_FACE_NONE)) { - HandlePlaceBlock(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, *ItemHandler); + if (!ItemHandler->OnPlayerPlace(*World, *m_Player, Equipped, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ)) + { + // Placement failed, bail out + return; + } } else if ((ItemHandler->IsFood() || ItemHandler->IsDrinkable(EquippedDamage))) { - if ((m_Player->IsSatiated() || m_Player->IsGameModeCreative()) && - ItemHandler->IsFood() && (Equipped.m_ItemType != E_ITEM_GOLDEN_APPLE)) + if ( + (m_Player->IsSatiated() || m_Player->IsGameModeCreative()) && // Only creative or hungry players can eat + ItemHandler->IsFood() && + (Equipped.m_ItemType != E_ITEM_GOLDEN_APPLE) // Golden apple is a special case, it is used instead of eaten + ) { // The player is satiated or in creative, and trying to eat return; @@ -1379,151 +1386,6 @@ void cClientHandle::HandleRightClick(int a_BlockX, int a_BlockY, int a_BlockZ, e -void cClientHandle::HandlePlaceBlock(int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, cItemHandler & a_ItemHandler) -{ - BLOCKTYPE EquippedBlock = (BLOCKTYPE)(m_Player->GetEquippedItem().m_ItemType); - if (a_BlockFace < 0) - { - // Clicked in air - return; - } - - cWorld * World = m_Player->GetWorld(); - - BLOCKTYPE ClickedBlock; - NIBBLETYPE ClickedBlockMeta; - NIBBLETYPE EquippedBlockDamage = (NIBBLETYPE)(m_Player->GetEquippedItem().m_ItemDamage); - - if ((a_BlockY < 0) || (a_BlockY >= cChunkDef::Height)) - { - // The block is being placed outside the world, ignore this packet altogether (#128) - return; - } - - World->GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, ClickedBlock, ClickedBlockMeta); - - // Special slab handling - placing a slab onto another slab produces a dblslab instead: - if ( - cBlockSlabHandler::IsAnySlabType(ClickedBlock) && // Is there a slab already? - cBlockSlabHandler::IsAnySlabType(EquippedBlock) && // Is the player placing another slab? - ((ClickedBlockMeta & 0x07) == EquippedBlockDamage) && // Is it the same slab type? - ( - (a_BlockFace == BLOCK_FACE_TOP) || // Clicking the top of a bottom slab - (a_BlockFace == BLOCK_FACE_BOTTOM) // Clicking the bottom of a top slab - ) - ) - { - // Coordinates at clicked block, which was an eligible slab, and either top or bottom faces were clicked - // If clicked top face and slab occupies the top voxel, we want a slab to be placed above it (therefore increment Y) - // Else if clicked bottom face and slab occupies the bottom voxel, decrement Y for the same reason - // Don't touch coordinates if anything else because a dblslab opportunity is present - if ((ClickedBlockMeta & 0x08) && (a_BlockFace == BLOCK_FACE_TOP)) - { - ++a_BlockY; - } - else if (!(ClickedBlockMeta & 0x08) && (a_BlockFace == BLOCK_FACE_BOTTOM)) - { - --a_BlockY; - } - World->GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, ClickedBlock, ClickedBlockMeta); - } - else - { - // Check if the block ignores build collision (water, grass etc.): - if ( - BlockHandler(ClickedBlock)->DoesIgnoreBuildCollision() || - BlockHandler(ClickedBlock)->DoesIgnoreBuildCollision(m_Player, ClickedBlockMeta) - ) - { - cChunkInterface ChunkInterface(World->GetChunkMap()); - BlockHandler(ClickedBlock)->OnDestroyedByPlayer(ChunkInterface, *World, m_Player, a_BlockX, a_BlockY, a_BlockZ); - } - else - { - AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); - - if ((a_BlockY < 0) || (a_BlockY >= cChunkDef::Height)) - { - // The block is being placed outside the world, ignore this packet altogether (#128) - return; - } - - NIBBLETYPE PlaceMeta; - BLOCKTYPE PlaceBlock; - World->GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, PlaceBlock, PlaceMeta); - - // Clicked on side of block, make sure that placement won't be cancelled if there is a slab able to be double slabbed. - // No need to do combinability (dblslab) checks, client will do that here. - if (cBlockSlabHandler::IsAnySlabType(PlaceBlock)) - { - // It's a slab, don't do checks and proceed to double-slabbing - } - else - { - if ( - !BlockHandler(PlaceBlock)->DoesIgnoreBuildCollision() && - !BlockHandler(PlaceBlock)->DoesIgnoreBuildCollision(m_Player, PlaceMeta) - ) - { - // Tried to place a block *into* another? - // Happens when you place a block aiming at side of block with a torch on it or stem beside it - return; - } - } - } - } - - BLOCKTYPE BlockType; - NIBBLETYPE BlockMeta; - if (!a_ItemHandler.GetPlacementBlockTypeMeta(World, m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta)) - { - // Handler refused the placement, send that information back to the client: - World->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, m_Player); - m_Player->GetInventory().SendEquippedSlot(); - return; - } - - cBlockHandler * NewBlock = BlockHandler(BlockType); - - if (cRoot::Get()->GetPluginManager()->CallHookPlayerPlacingBlock(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta)) - { - // A plugin doesn't agree with placing the block, revert the block on the client: - World->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, m_Player); - m_Player->GetInventory().SendEquippedSlot(); - return; - } - - // The actual block placement: - World->SetBlock(a_BlockX, a_BlockY, a_BlockZ, BlockType, BlockMeta); - if (!m_Player->IsGameModeCreative()) - { - m_Player->GetInventory().RemoveOneEquippedItem(); - } - - cChunkInterface ChunkInterface(World->GetChunkMap()); - NewBlock->OnPlacedByPlayer(ChunkInterface, *World, m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta); - - AString PlaceSound = cBlockInfo::GetPlaceSound(BlockType); - float Volume = 1.0f, Pitch = 0.8f; - if (PlaceSound == "dig.metal") - { - Pitch = 1.2f; - PlaceSound = "dig.stone"; - } - else if (PlaceSound == "random.anvil_land") - { - Volume = 0.65f; - } - - World->BroadcastSoundEffect(PlaceSound, a_BlockX + 0.5, a_BlockY + 0.5, a_BlockZ + 0.5, Volume, Pitch); - - cRoot::Get()->GetPluginManager()->CallHookPlayerPlacedBlock(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta); -} - - - - - void cClientHandle::HandleChat(const AString & a_Message) { // We no longer need to postpone message processing, because the messages already arrive in the Tick thread diff --git a/src/ClientHandle.h b/src/ClientHandle.h index 25dd250d9..5e10bb52f 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -459,9 +459,6 @@ private: UInt32 m_ProtocolVersion; - /** Handles the block placing packet when it is a real block placement (not block-using, item-using or eating) */ - void HandlePlaceBlock(int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, cItemHandler & a_ItemHandler); - /** Returns true if the rate block interactions is within a reasonable limit (bot protection) */ bool CheckBlockInteractionsRate(void); diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 15920d6cf..7bdd1c6f7 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -2,6 +2,7 @@ #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "Player.h" +#include #include "../ChatColor.h" #include "../Server.h" #include "../UI/Window.h" @@ -19,6 +20,10 @@ #include "../WorldStorage/StatSerializer.h" #include "../CompositeChat.h" +#include "../Blocks/BlockHandler.h" +#include "../Blocks/BlockSlab.h" +#include "../Blocks/ChunkInterface.h" + #include "../IniFile.h" #include "json/json.h" @@ -2168,6 +2173,97 @@ void cPlayer::LoadRank(void) +bool cPlayer::PlaceBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) +{ + sSetBlockVector blk{{a_BlockX, a_BlockY, a_BlockZ, a_BlockType, a_BlockMeta}}; + return PlaceBlocks(blk); +} + + + + + +void cPlayer::SendBlocksAround(int a_BlockX, int a_BlockY, int a_BlockZ, int a_Range) +{ + // Collect the coords of all the blocks to send: + sSetBlockVector blks; + for (int y = a_BlockY - a_Range + 1; y < a_BlockY + a_Range; y++) + { + for (int z = a_BlockZ - a_Range + 1; z < a_BlockZ + a_Range; z++) + { + for (int x = a_BlockX - a_Range + 1; x < a_BlockX + a_Range; x++) + { + blks.emplace_back(x, y, z, E_BLOCK_AIR, 0); // Use fake blocktype, it will get set later on. + }; + }; + } // for y + + // Get the values of all the blocks: + if (!m_World->GetBlocks(blks, false)) + { + LOGD("%s: Cannot query all blocks, not sending an update", __FUNCTION__); + return; + } + + // Divide the block changes by their respective chunks: + std::unordered_map Changes; + for (const auto & blk: blks) + { + Changes[cChunkCoords(blk.m_ChunkX, blk.m_ChunkZ)].push_back(blk); + } // for blk - blks[] + blks.clear(); + + // Send the blocks for each affected chunk: + for (auto itr = Changes.cbegin(), end = Changes.cend(); itr != end; ++itr) + { + m_ClientHandle->SendBlockChanges(itr->first.m_ChunkX, itr->first.m_ChunkZ, itr->second); + } +} + + + + + +bool cPlayer::PlaceBlocks(const sSetBlockVector & a_Blocks) +{ + // Call the "placing" hooks; if any fail, abort: + cPluginManager * pm = cPluginManager::Get(); + for (auto blk: a_Blocks) + { + if (pm->CallHookPlayerPlacingBlock(*this, blk)) + { + // Abort - re-send all the current blocks in the a_Blocks' coords to the client: + for (auto blk2: a_Blocks) + { + m_World->SendBlockTo(blk2.GetX(), blk2.GetY(), blk2.GetZ(), this); + } + return false; + } + } // for blk - a_Blocks[] + + // Set the blocks: + m_World->SetBlocks(a_Blocks); + + // Notify the blockhandlers: + cChunkInterface ChunkInterface(m_World->GetChunkMap()); + for (auto blk: a_Blocks) + { + cBlockHandler * newBlock = BlockHandler(blk.m_BlockType); + newBlock->OnPlacedByPlayer(ChunkInterface, *m_World, this, blk); + } + + // Call the "placed" hooks: + for (auto blk: a_Blocks) + { + pm->CallHookPlayerPlacedBlock(*this, blk); + } + return true; +} + + + + + void cPlayer::Detach() { super::Detach(); diff --git a/src/Entities/Player.h b/src/Entities/Player.h index c643aaa8e..33ab5293c 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -440,8 +440,26 @@ public: Loads the m_Rank, m_Permissions, m_MsgPrefix, m_MsgSuffix and m_MsgNameColorCode members. */ void LoadRank(void); + /** Calls the block-placement hook and places the block in the world, unless refused by the hook. + If the hook prevents the placement, sends the current block at the specified coords back to the client. + Assumes that all the blocks are in currently loaded chunks. */ + bool PlaceBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); + + /** Sends the block in the specified range around the specified coord to the client + as a block change packet. + The blocks in range (a_BlockX - a_Range, a_BlockX + a_Range) are sent (NY-metric). */ + void SendBlocksAround(int a_BlockX, int a_BlockY, int a_BlockZ, int a_Range = 1); + // tolua_end + /** Calls the block placement hooks and places the blocks in the world. + First the "placing" hooks for all the blocks are called, then the blocks are placed, and finally + the "placed" hooks are called. + If the any of the "placing" hooks aborts, none of the blocks are placed and the function returns false. + Returns true if all the blocks are placed. + Assumes that all the blocks are in currently loaded chunks. */ + bool PlaceBlocks(const sSetBlockVector & a_Blocks); + // cEntity overrides: virtual bool IsCrouched (void) const { return m_IsCrouched; } virtual bool IsSprinting(void) const { return m_IsSprinting; } diff --git a/src/Generating/StructGen.cpp b/src/Generating/StructGen.cpp index 2f685c808..2d5a73739 100644 --- a/src/Generating/StructGen.cpp +++ b/src/Generating/StructGen.cpp @@ -123,18 +123,18 @@ void cStructGenTrees::GenerateSingleTree( // Check if the generated image fits the terrain. Only the logs are checked: for (sSetBlockVector::const_iterator itr = TreeLogs.begin(); itr != TreeLogs.end(); ++itr) { - if ((itr->ChunkX != a_ChunkX) || (itr->ChunkZ != a_ChunkZ)) + if ((itr->m_ChunkX != a_ChunkX) || (itr->m_ChunkZ != a_ChunkZ)) { // Outside the chunk continue; } - if (itr->y >= cChunkDef::Height) + if (itr->m_RelY >= cChunkDef::Height) { // Above the chunk, cut off (this shouldn't happen too often, we're limiting trees to y < 230) continue; } - BLOCKTYPE Block = a_ChunkDesc.GetBlockType(itr->x, itr->y, itr->z); + BLOCKTYPE Block = a_ChunkDesc.GetBlockType(itr->m_RelX, itr->m_RelY, itr->m_RelZ); switch (Block) { CASE_TREE_ALLOWED_BLOCKS: @@ -167,14 +167,14 @@ void cStructGenTrees::ApplyTreeImage( // Put the generated image into a_BlockTypes, push things outside this chunk into a_Blocks for (sSetBlockVector::const_iterator itr = a_Image.begin(), end = a_Image.end(); itr != end; ++itr) { - if ((itr->ChunkX == a_ChunkX) && (itr->ChunkZ == a_ChunkZ) && (itr->y < cChunkDef::Height)) + if ((itr->m_ChunkX == a_ChunkX) && (itr->m_ChunkZ == a_ChunkZ) && (itr->m_RelY < cChunkDef::Height)) { // Inside this chunk, integrate into a_ChunkDesc: - switch (a_ChunkDesc.GetBlockType(itr->x, itr->y, itr->z)) + switch (a_ChunkDesc.GetBlockType(itr->m_RelX, itr->m_RelY, itr->m_RelZ)) { case E_BLOCK_LEAVES: { - if (itr->BlockType != E_BLOCK_LOG) + if (itr->m_BlockType != E_BLOCK_LOG) { break; } @@ -182,7 +182,7 @@ void cStructGenTrees::ApplyTreeImage( } CASE_TREE_OVERWRITTEN_BLOCKS: { - a_ChunkDesc.SetBlockTypeMeta(itr->x, itr->y, itr->z, itr->BlockType, itr->BlockMeta); + a_ChunkDesc.SetBlockTypeMeta(itr->m_RelX, itr->m_RelY, itr->m_RelZ, itr->m_BlockType, itr->m_BlockMeta); break; } diff --git a/src/Generating/Trees.cpp b/src/Generating/Trees.cpp index be8b0cd6b..a10e0f4f1 100644 --- a/src/Generating/Trees.cpp +++ b/src/Generating/Trees.cpp @@ -403,17 +403,17 @@ void GetLargeAppleTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a for (auto itr : a_LogBlocks) { // Get the log's X and Z coordinates - int X = itr.ChunkX * 16 + itr.x; - int Z = itr.ChunkZ * 16 + itr.z; + int X = itr.GetX(); + int Z = itr.GetZ(); - a_OtherBlocks.push_back(sSetBlock(X, itr.y - 2, Z, E_BLOCK_LEAVES, E_META_LEAVES_APPLE)); - PushCoordBlocks(X, itr.y - 2, Z, a_OtherBlocks, BigO1, ARRAYCOUNT(BigO1), E_BLOCK_LEAVES, E_META_LEAVES_APPLE); + a_OtherBlocks.push_back(sSetBlock(X, itr.m_RelY - 2, Z, E_BLOCK_LEAVES, E_META_LEAVES_APPLE)); + PushCoordBlocks(X, itr.m_RelY - 2, Z, a_OtherBlocks, BigO1, ARRAYCOUNT(BigO1), E_BLOCK_LEAVES, E_META_LEAVES_APPLE); for (int y = -1; y <= 1; y++) { - PushCoordBlocks (X, itr.y + y, Z, a_OtherBlocks, BigO2, ARRAYCOUNT(BigO2), E_BLOCK_LEAVES, E_META_LEAVES_APPLE); + PushCoordBlocks (X, itr.m_RelY + y, Z, a_OtherBlocks, BigO2, ARRAYCOUNT(BigO2), E_BLOCK_LEAVES, E_META_LEAVES_APPLE); } - PushCoordBlocks(X, itr.y + 2, Z, a_OtherBlocks, BigO1, ARRAYCOUNT(BigO1), E_BLOCK_LEAVES, E_META_LEAVES_APPLE); - a_OtherBlocks.push_back(sSetBlock(X, itr.y + 2, Z, E_BLOCK_LEAVES, E_META_LEAVES_APPLE)); + PushCoordBlocks(X, itr.m_RelY + 2, Z, a_OtherBlocks, BigO1, ARRAYCOUNT(BigO1), E_BLOCK_LEAVES, E_META_LEAVES_APPLE); + a_OtherBlocks.push_back(sSetBlock(X, itr.m_RelY + 2, Z, E_BLOCK_LEAVES, E_META_LEAVES_APPLE)); } // Trunk: diff --git a/src/Items/CMakeLists.txt b/src/Items/CMakeLists.txt index 12a467672..c50ddb372 100644 --- a/src/Items/CMakeLists.txt +++ b/src/Items/CMakeLists.txt @@ -10,12 +10,14 @@ SET (SRCS SET (HDRS ItemArmor.h ItemBed.h + ItemBigFlower.h ItemBoat.h ItemBow.h ItemBrewingStand.h ItemBucket.h ItemCake.h ItemCauldron.h + ItemChest.h ItemCloth.h ItemComparator.h ItemDoor.h @@ -38,18 +40,21 @@ SET (HDRS ItemPainting.h ItemPickaxe.h ItemPotion.h + ItemPumpkin.h ItemRedstoneDust.h ItemRedstoneRepeater.h ItemSapling.h ItemSeeds.h ItemShears.h ItemShovel.h + ItemSlab.h ItemSign.h ItemSpawnEgg.h ItemString.h ItemSugarcane.h ItemSword.h - ItemThrowable.h) + ItemThrowable.h +) if(NOT MSVC) add_library(Items ${SRCS} ${HDRS}) diff --git a/src/Items/ItemBed.h b/src/Items/ItemBed.h index 94a14cf16..77d51d744 100644 --- a/src/Items/ItemBed.h +++ b/src/Items/ItemBed.h @@ -24,30 +24,36 @@ public: return true; } - virtual bool GetPlacementBlockTypeMeta( - cWorld * a_World, cPlayer * a_Player, + + virtual bool OnPlayerPlace( + cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, - int a_CursorX, int a_CursorY, int a_CursorZ, - BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta + int a_CursorX, int a_CursorY, int a_CursorZ ) override { + // Can only be placed on the floor: if (a_BlockFace != BLOCK_FACE_TOP) { - // Can only be placed on the floor return false; } + AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); - a_BlockMeta = cBlockBedHandler::RotationToMetaData(a_Player->GetYaw()); + // The "foot" block: + sSetBlockVector blks; + NIBBLETYPE BlockMeta = cBlockBedHandler::RotationToMetaData(a_Player.GetYaw()); + blks.emplace_back(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_BED, BlockMeta); - // Check if there is empty space for the foot section: - Vector3i Direction = cBlockBedHandler::MetaDataToDirection(a_BlockMeta); - if (a_World->GetBlock(a_BlockX + Direction.x, a_BlockY, a_BlockZ + Direction.z) != E_BLOCK_AIR) + // Check if there is empty space for the "head" block: + // (Vanilla only allows beds to be placed into air) + Vector3i Direction = cBlockBedHandler::MetaDataToDirection(BlockMeta); + if (a_World.GetBlock(a_BlockX + Direction.x, a_BlockY, a_BlockZ + Direction.z) != E_BLOCK_AIR) { return false; } + blks.emplace_back(a_BlockX + Direction.x, a_BlockY, a_BlockZ + Direction.z, E_BLOCK_BED, BlockMeta | 0x08); - a_BlockType = E_BLOCK_BED; - return true; + // Place both bed blocks: + return a_Player.PlaceBlocks(blks); } } ; diff --git a/src/Items/ItemBigFlower.h b/src/Items/ItemBigFlower.h new file mode 100644 index 000000000..4341a1a17 --- /dev/null +++ b/src/Items/ItemBigFlower.h @@ -0,0 +1,56 @@ + +// ItemBigFlower.h + +// Declares the cItemBigFlower class representing the cItemHandler for big flowers + + + + + +#pragma once + +#include "ItemHandler.h" + + + + + +class cItemBigFlowerHandler: + public cItemHandler +{ + typedef cItemHandler super; + +public: + cItemBigFlowerHandler(void): + super(E_BLOCK_BIG_FLOWER) + { + } + + + virtual bool OnPlayerPlace( + cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, + int a_CursorX, int a_CursorY, int a_CursorZ + ) override + { + // Can only be placed on the floor: + if (a_BlockFace != BLOCK_FACE_TOP) + { + return false; + } + AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); + + // Place both blocks atomically: + sSetBlockVector blks; + blks.emplace_back(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_BIG_FLOWER, a_EquippedItem.m_ItemDamage & 0x07); + if (a_BlockY < cChunkDef::Height - 1) + { + blks.emplace_back(a_BlockX, a_BlockY + 1, a_BlockZ, E_BLOCK_BIG_FLOWER, (a_EquippedItem.m_ItemDamage & 0x07) | 0x08); + } + return a_Player.PlaceBlocks(blks); + } +}; + + + + diff --git a/src/Items/ItemChest.h b/src/Items/ItemChest.h new file mode 100644 index 000000000..b6579c423 --- /dev/null +++ b/src/Items/ItemChest.h @@ -0,0 +1,167 @@ + +// ItemChest.h + +// Declares the cItemChestHandler class representing the cItemHandler descendant responsible for chests + + + + + +#pragma once + +#include "ItemHandler.h" +#include "../Blocks/BlockChest.h" + + + + + +class cItemChestHandler: + public cItemHandler +{ + typedef cItemHandler super; +public: + cItemChestHandler(int a_ItemType): + super(a_ItemType) + { + } + + + virtual bool OnPlayerPlace( + cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, + int a_CursorX, int a_CursorY, int a_CursorZ + ) override + { + if (a_BlockFace < 0) + { + // Clicked in air + return false; + } + + if ((a_BlockY < 0) || (a_BlockY >= cChunkDef::Height)) + { + // The clicked block is outside the world, ignore this call altogether (#128) + return false; + } + + // Check if the block ignores build collision (water, grass etc.): + BLOCKTYPE ClickedBlock; + NIBBLETYPE ClickedBlockMeta; + a_World.GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, ClickedBlock, ClickedBlockMeta); + if ( + BlockHandler(ClickedBlock)->DoesIgnoreBuildCollision() || + BlockHandler(ClickedBlock)->DoesIgnoreBuildCollision(&a_Player, ClickedBlockMeta) + ) + { + cChunkInterface ChunkInterface(a_World.GetChunkMap()); + BlockHandler(ClickedBlock)->OnDestroyedByPlayer(ChunkInterface, a_World, &a_Player, a_BlockX, a_BlockY, a_BlockZ); + } + else + { + AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); + + if ((a_BlockY < 0) || (a_BlockY >= cChunkDef::Height)) + { + // The block is being placed outside the world, ignore this packet altogether (#128) + return false; + } + + NIBBLETYPE PlaceMeta; + BLOCKTYPE PlaceBlock; + a_World.GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, PlaceBlock, PlaceMeta); + + // Clicked on side of block, make sure that placement won't be cancelled if there is a slab able to be double slabbed. + // No need to do combinability (dblslab) checks, client will do that here. + if ( + !BlockHandler(PlaceBlock)->DoesIgnoreBuildCollision() && + !BlockHandler(PlaceBlock)->DoesIgnoreBuildCollision(&a_Player, PlaceMeta) + ) + { + // Tried to place a block *into* another? + // Happens when you place a block aiming at side of block with a torch on it or stem beside it + return false; + } + } + + // Check that there is at most one single neighbor of the same chest type: + static const Vector3i CrossCoords[] = + { + {-1, 0, 0}, + { 0, 0, -1}, + { 1, 0, 0}, + { 0, 0, 1}, + }; + int NeighborIdx = -1; + for (size_t i = 0; i < ARRAYCOUNT(CrossCoords); i++) + { + if (a_World.GetBlock(a_BlockX + CrossCoords[i].x, a_BlockY, a_BlockZ + CrossCoords[i].z) != m_ItemType) + { + continue; + } + if (NeighborIdx >= 0) + { + // Can't place here, there are already two neighbors, this would form a 3-block chest + return false; + } + NeighborIdx = static_cast(i); + + // Check that this neighbor is a single chest: + int bx = a_BlockX + CrossCoords[i].x; + int bz = a_BlockZ + CrossCoords[i].z; + for (size_t j = 0; j < ARRAYCOUNT(CrossCoords); j++) + { + if (a_World.GetBlock(bx + CrossCoords[j].x, a_BlockY, bz + CrossCoords[j].z) == m_ItemType) + { + return false; + } + } // for j + } // for i + + // If there's no chest neighbor, place the single block chest and bail out: + BLOCKTYPE ChestBlockType = static_cast(m_ItemType); + if (NeighborIdx < 0) + { + NIBBLETYPE Meta = cBlockChestHandler::PlayerYawToMetaData(a_Player.GetYaw()); + return a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, ChestBlockType, Meta); + } + + // There is a neighbor to which we need to adjust + double yaw = a_Player.GetYaw(); + if ((NeighborIdx == 0) || (NeighborIdx == 2)) + { + // The neighbor is in the X axis, form a X-axis-aligned dblchest: + NIBBLETYPE Meta = ((yaw >= -90) && (yaw < 90)) ? E_META_CHEST_FACING_ZM : E_META_CHEST_FACING_ZP; + + // Place the new chest: + if (!a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, ChestBlockType, Meta)) + { + return false; + } + + // Adjust the existing chest: + a_World.FastSetBlock(a_BlockX + CrossCoords[NeighborIdx].x, a_BlockY, a_BlockZ + CrossCoords[NeighborIdx].z, ChestBlockType, Meta); + return true; + } + + // The neighbor is in the Z axis, form a Z-axis-aligned dblchest: + NIBBLETYPE Meta = (yaw < 0) ? E_META_CHEST_FACING_XM : E_META_CHEST_FACING_XP; + + // Place the new chest: + if (!a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, ChestBlockType, Meta)) + { + return false; + } + + // Adjust the existing chest: + a_World.FastSetBlock(a_BlockX + CrossCoords[NeighborIdx].x, a_BlockY, a_BlockZ + CrossCoords[NeighborIdx].z, ChestBlockType, Meta); + return true; + } + +private: + cItemChestHandler(const cItemChestHandler &) = delete; +}; + + + + diff --git a/src/Items/ItemDoor.h b/src/Items/ItemDoor.h index cd5baf44f..dbba26728 100644 --- a/src/Items/ItemDoor.h +++ b/src/Items/ItemDoor.h @@ -3,6 +3,7 @@ #include "ItemHandler.h" #include "../World.h" +#include "../Blocks/BlockDoor.h" @@ -18,27 +19,43 @@ public: } - virtual bool IsPlaceable(void) override - { - return true; - } - virtual bool GetPlacementBlockTypeMeta( - cWorld * a_World, cPlayer * a_Player, + virtual bool OnPlayerPlace( + cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, - int a_CursorX, int a_CursorY, int a_CursorZ, - BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta + int a_CursorX, int a_CursorY, int a_CursorZ ) override { + // Vanilla only allows door placement while clicking on the top face of the block below the door: + if (a_BlockFace != BLOCK_FACE_NONE) + { + return false; + } + AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); + + // Door (bottom block) can be placed in Y range of [1, 254]: + if ((a_BlockY < 1) || (a_BlockY + 2 >= cChunkDef::Height)) + { + return false; + } + + // The door needs a compatible block below it: + if ((a_BlockY > 0) && cBlockDoorHandler::CanBeOn(a_World.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ))) + { + return false; + } + + // Get the block type of the door to place: + BLOCKTYPE BlockType; switch (m_ItemType) { - case E_ITEM_WOODEN_DOOR: a_BlockType = E_BLOCK_WOODEN_DOOR; break; - case E_ITEM_IRON_DOOR: a_BlockType = E_BLOCK_IRON_DOOR; break; - case E_ITEM_SPRUCE_DOOR: a_BlockType = E_BLOCK_SPRUCE_DOOR; break; - case E_ITEM_BIRCH_DOOR: a_BlockType = E_BLOCK_BIRCH_DOOR; break; - case E_ITEM_JUNGLE_DOOR: a_BlockType = E_BLOCK_JUNGLE_DOOR; break; - case E_ITEM_DARK_OAK_DOOR: a_BlockType = E_BLOCK_DARK_OAK_DOOR; break; - case E_ITEM_ACACIA_DOOR: a_BlockType = E_BLOCK_ACACIA_DOOR; break; + case E_ITEM_WOODEN_DOOR: BlockType = E_BLOCK_WOODEN_DOOR; break; + case E_ITEM_IRON_DOOR: BlockType = E_BLOCK_IRON_DOOR; break; + case E_ITEM_SPRUCE_DOOR: BlockType = E_BLOCK_SPRUCE_DOOR; break; + case E_ITEM_BIRCH_DOOR: BlockType = E_BLOCK_BIRCH_DOOR; break; + case E_ITEM_JUNGLE_DOOR: BlockType = E_BLOCK_JUNGLE_DOOR; break; + case E_ITEM_DARK_OAK_DOOR: BlockType = E_BLOCK_DARK_OAK_DOOR; break; + case E_ITEM_ACACIA_DOOR: BlockType = E_BLOCK_ACACIA_DOOR; break; default: { ASSERT(!"Unhandled door type"); @@ -46,14 +63,47 @@ public: } } - cChunkInterface ChunkInterface(a_World->GetChunkMap()); - bool Meta = BlockHandler(a_BlockType)->GetPlacementBlockTypeMeta( - ChunkInterface, a_Player, - a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, - a_CursorX, a_CursorY, a_CursorZ, - a_BlockType, a_BlockMeta - ); - return Meta; + // Check the two blocks that will get replaced by the door: + BLOCKTYPE LowerBlockType = a_World.GetBlock(a_BlockX, a_BlockY + 1, a_BlockZ); + BLOCKTYPE UpperBlockType = a_World.GetBlock(a_BlockX, a_BlockY + 2, a_BlockZ); + if ( + !cBlockDoorHandler::CanReplaceBlock(LowerBlockType) || + !cBlockDoorHandler::CanReplaceBlock(UpperBlockType)) + { + return false; + } + + // Get the coords of the neighboring blocks: + NIBBLETYPE LowerBlockMeta = cBlockDoorHandler::PlayerYawToMetaData(a_Player.GetYaw()); + Vector3i RelDirToOutside = cBlockDoorHandler::GetRelativeDirectionToOutside(LowerBlockMeta); + Vector3i LeftNeighborPos = RelDirToOutside; + LeftNeighborPos.TurnCCW(); + LeftNeighborPos.Move(a_BlockX, a_BlockY, a_BlockZ); + Vector3i RightNeighborPos = RelDirToOutside; + RightNeighborPos.TurnCW(); + RightNeighborPos.Move(a_BlockX, a_BlockY, a_BlockZ); + + // Decide whether the hinge is on the left (default) or on the right: + NIBBLETYPE UpperBlockMeta = 0x08; + if ( + cBlockDoorHandler::IsDoorBlockType(a_World.GetBlock(LeftNeighborPos)) || // The block to the left is a door block + cBlockInfo::IsSolid(a_World.GetBlock(RightNeighborPos)) // The block to the right is solid + ) + { + UpperBlockMeta = 0x09; // Upper block | hinge on right + } + + // Set the blocks: + sSetBlockVector blks; + blks.emplace_back(a_BlockX, a_BlockY, a_BlockZ, BlockType, LowerBlockMeta); + blks.emplace_back(a_BlockX, a_BlockY + 1, a_BlockZ, BlockType, UpperBlockMeta); + return a_Player.PlaceBlocks(blks); + } + + + virtual bool IsPlaceable(void) override + { + return true; } } ; diff --git a/src/Items/ItemDye.h b/src/Items/ItemDye.h index da978040d..bfcd0bac4 100644 --- a/src/Items/ItemDye.h +++ b/src/Items/ItemDye.h @@ -55,25 +55,16 @@ public: return false; } - // Check plugins - if (cRoot::Get()->GetPluginManager()->CallHookPlayerPlacingBlock(*a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, 0, 0, 0, E_BLOCK_COCOA_POD, BlockMeta)) + // Place the cocoa pod: + if (a_Player->PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_COCOA_POD, BlockMeta)) { - a_World->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, a_Player); - a_Player->GetInventory().SendEquippedSlot(); - return false; - } - - // Set block and broadcast place sound - a_World->SetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_COCOA_POD, BlockMeta); - a_World->BroadcastSoundEffect("dig.stone", a_BlockX + 0.5, a_BlockY + 0.5, a_BlockZ + 0.5, 1.0f, 0.8f); - - // Remove one cocoa pod from the inventory - if (!a_Player->IsGameModeCreative()) - { - a_Player->GetInventory().RemoveOneEquippedItem(); + a_World->BroadcastSoundEffect("dig.stone", a_BlockX + 0.5, a_BlockY + 0.5, a_BlockZ + 0.5, 1.0f, 0.8f); + if (a_Player->IsGameModeSurvival()) + { + a_Player->GetInventory().RemoveOneEquippedItem(); + } + return true; } - cRoot::Get()->GetPluginManager()->CallHookPlayerPlacedBlock(*a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, 0, 0, 0, E_BLOCK_COCOA_POD, BlockMeta); - return true; } return false; } diff --git a/src/Items/ItemHandler.cpp b/src/Items/ItemHandler.cpp index 9272a723d..92c55ec62 100644 --- a/src/Items/ItemHandler.cpp +++ b/src/Items/ItemHandler.cpp @@ -6,39 +6,43 @@ #include "../Entities/Player.h" #include "../FastRandom.h" #include "../BlockInServerPluginInterface.h" +#include "../Chunk.h" // Handlers: #include "ItemArmor.h" #include "ItemBed.h" +#include "ItemBigFlower.h" #include "ItemBoat.h" #include "ItemBow.h" #include "ItemBrewingStand.h" #include "ItemBucket.h" #include "ItemCake.h" #include "ItemCauldron.h" +#include "ItemChest.h" #include "ItemCloth.h" #include "ItemComparator.h" #include "ItemDoor.h" -#include "ItemMilk.h" #include "ItemDye.h" #include "ItemEmptyMap.h" #include "ItemFishingRod.h" #include "ItemFlowerPot.h" #include "ItemFood.h" #include "ItemGoldenApple.h" -#include "ItemItemFrame.h" #include "ItemHoe.h" +#include "ItemItemFrame.h" #include "ItemLeaves.h" #include "ItemLighter.h" #include "ItemLilypad.h" #include "ItemMap.h" +#include "ItemMilk.h" #include "ItemMinecart.h" +#include "ItemMobHead.h" #include "ItemMushroomSoup.h" #include "ItemNetherWart.h" #include "ItemPainting.h" #include "ItemPickaxe.h" #include "ItemPotion.h" -#include "ItemThrowable.h" +#include "ItemPumpkin.h" #include "ItemRedstoneDust.h" #include "ItemRedstoneRepeater.h" #include "ItemSapling.h" @@ -46,11 +50,12 @@ #include "ItemShears.h" #include "ItemShovel.h" #include "ItemSign.h" -#include "ItemMobHead.h" +#include "ItemSlab.h" #include "ItemSpawnEgg.h" #include "ItemString.h" #include "ItemSugarcane.h" #include "ItemSword.h" +#include "ItemThrowable.h" #include "../Blocks/BlockHandler.h" @@ -94,52 +99,58 @@ cItemHandler * cItemHandler::GetItemHandler(int a_ItemType) -cItemHandler *cItemHandler::CreateItemHandler(int a_ItemType) +cItemHandler * cItemHandler::CreateItemHandler(int a_ItemType) { switch (a_ItemType) { default: return new cItemHandler(a_ItemType); // Single item per handler, alphabetically sorted: - case E_BLOCK_LEAVES: return new cItemLeavesHandler(a_ItemType); - case E_BLOCK_NEW_LEAVES: return new cItemLeavesHandler(a_ItemType); - case E_BLOCK_SAPLING: return new cItemSaplingHandler(a_ItemType); - case E_BLOCK_WOOL: return new cItemClothHandler(a_ItemType); - case E_ITEM_BED: return new cItemBedHandler(a_ItemType); - case E_ITEM_BOAT: return new cItemBoatHandler(a_ItemType); + case E_BLOCK_CHEST: return new cItemChestHandler(a_ItemType); + case E_BLOCK_LEAVES: return new cItemLeavesHandler(a_ItemType); + case E_BLOCK_HEAD: return new cItemMobHeadHandler(a_ItemType); + case E_BLOCK_NEW_LEAVES: return new cItemLeavesHandler(a_ItemType); + case E_BLOCK_PUMPKIN: return new cItemPumpkinHandler; + case E_BLOCK_SAPLING: return new cItemSaplingHandler(a_ItemType); + case E_BLOCK_STONE_SLAB: return new cItemSlabHandler(E_BLOCK_STONE_SLAB, E_BLOCK_DOUBLE_STONE_SLAB); + case E_BLOCK_TRAPPED_CHEST: return new cItemChestHandler(a_ItemType); + case E_BLOCK_WOODEN_SLAB: return new cItemSlabHandler(E_BLOCK_WOODEN_SLAB, E_BLOCK_DOUBLE_WOODEN_SLAB); + case E_BLOCK_WOOL: return new cItemClothHandler(a_ItemType); + case E_ITEM_BED: return new cItemBedHandler(a_ItemType); + case E_ITEM_BOAT: return new cItemBoatHandler(a_ItemType); case E_ITEM_BOTTLE_O_ENCHANTING: return new cItemBottleOEnchantingHandler(); - case E_ITEM_BOW: return new cItemBowHandler(); - case E_ITEM_BREWING_STAND: return new cItemBrewingStandHandler(a_ItemType); - case E_ITEM_CAKE: return new cItemCakeHandler(a_ItemType); - case E_ITEM_CAULDRON: return new cItemCauldronHandler(a_ItemType); - case E_ITEM_COMPARATOR: return new cItemComparatorHandler(a_ItemType); - case E_ITEM_DYE: return new cItemDyeHandler(a_ItemType); - case E_ITEM_EGG: return new cItemEggHandler(); - case E_ITEM_EMPTY_MAP: return new cItemEmptyMapHandler(); - case E_ITEM_ENDER_PEARL: return new cItemEnderPearlHandler(); - case E_ITEM_FIRE_CHARGE: return new cItemLighterHandler(a_ItemType); - case E_ITEM_FIREWORK_ROCKET: return new cItemFireworkHandler(); - case E_ITEM_FISHING_ROD: return new cItemFishingRodHandler(a_ItemType); - case E_ITEM_FLINT_AND_STEEL: return new cItemLighterHandler(a_ItemType); - case E_ITEM_FLOWER_POT: return new cItemFlowerPotHandler(a_ItemType); - case E_ITEM_GOLDEN_APPLE: return new cItemGoldenAppleHandler(); - case E_BLOCK_LILY_PAD: return new cItemLilypadHandler(a_ItemType); - case E_ITEM_MAP: return new cItemMapHandler(); - case E_ITEM_MILK: return new cItemMilkHandler(); - case E_ITEM_MUSHROOM_SOUP: return new cItemMushroomSoupHandler(a_ItemType); - case E_ITEM_ITEM_FRAME: return new cItemItemFrameHandler(a_ItemType); - case E_ITEM_NETHER_WART: return new cItemNetherWartHandler(a_ItemType); - case E_ITEM_PAINTING: return new cItemPaintingHandler(a_ItemType); - case E_ITEM_POTIONS: return new cItemPotionHandler(); - case E_ITEM_REDSTONE_DUST: return new cItemRedstoneDustHandler(a_ItemType); - case E_ITEM_REDSTONE_REPEATER: return new cItemRedstoneRepeaterHandler(a_ItemType); - case E_ITEM_SHEARS: return new cItemShearsHandler(a_ItemType); - case E_ITEM_SIGN: return new cItemSignHandler(a_ItemType); - case E_ITEM_HEAD: return new cItemMobHeadHandler(a_ItemType); - case E_ITEM_SNOWBALL: return new cItemSnowballHandler(); - case E_ITEM_SPAWN_EGG: return new cItemSpawnEggHandler(a_ItemType); - case E_ITEM_STRING: return new cItemStringHandler(a_ItemType); - case E_ITEM_SUGARCANE: return new cItemSugarcaneHandler(a_ItemType); + case E_ITEM_BOW: return new cItemBowHandler(); + case E_ITEM_BREWING_STAND: return new cItemBrewingStandHandler(a_ItemType); + case E_ITEM_CAKE: return new cItemCakeHandler(a_ItemType); + case E_ITEM_CAULDRON: return new cItemCauldronHandler(a_ItemType); + case E_ITEM_COMPARATOR: return new cItemComparatorHandler(a_ItemType); + case E_ITEM_DYE: return new cItemDyeHandler(a_ItemType); + case E_ITEM_EGG: return new cItemEggHandler(); + case E_ITEM_EMPTY_MAP: return new cItemEmptyMapHandler(); + case E_ITEM_ENDER_PEARL: return new cItemEnderPearlHandler(); + case E_ITEM_FIRE_CHARGE: return new cItemLighterHandler(a_ItemType); + case E_ITEM_FIREWORK_ROCKET: return new cItemFireworkHandler(); + case E_ITEM_FISHING_ROD: return new cItemFishingRodHandler(a_ItemType); + case E_ITEM_FLINT_AND_STEEL: return new cItemLighterHandler(a_ItemType); + case E_ITEM_FLOWER_POT: return new cItemFlowerPotHandler(a_ItemType); + case E_ITEM_GOLDEN_APPLE: return new cItemGoldenAppleHandler(); + case E_BLOCK_LILY_PAD: return new cItemLilypadHandler(a_ItemType); + case E_ITEM_MAP: return new cItemMapHandler(); + case E_ITEM_MILK: return new cItemMilkHandler(); + case E_ITEM_MUSHROOM_SOUP: return new cItemMushroomSoupHandler(a_ItemType); + case E_ITEM_ITEM_FRAME: return new cItemItemFrameHandler(a_ItemType); + case E_ITEM_NETHER_WART: return new cItemNetherWartHandler(a_ItemType); + case E_ITEM_PAINTING: return new cItemPaintingHandler(a_ItemType); + case E_ITEM_POTIONS: return new cItemPotionHandler(); + case E_ITEM_REDSTONE_DUST: return new cItemRedstoneDustHandler(a_ItemType); + case E_ITEM_REDSTONE_REPEATER: return new cItemRedstoneRepeaterHandler(a_ItemType); + case E_ITEM_SHEARS: return new cItemShearsHandler(a_ItemType); + case E_ITEM_SIGN: return new cItemSignHandler(a_ItemType); + case E_ITEM_HEAD: return new cItemMobHeadHandler(a_ItemType); + case E_ITEM_SNOWBALL: return new cItemSnowballHandler(); + case E_ITEM_SPAWN_EGG: return new cItemSpawnEggHandler(a_ItemType); + case E_ITEM_STRING: return new cItemStringHandler(a_ItemType); + case E_ITEM_SUGARCANE: return new cItemSugarcaneHandler(a_ItemType); case E_ITEM_WOODEN_HOE: case E_ITEM_STONE_HOE: @@ -297,6 +308,108 @@ cItemHandler::cItemHandler(int a_ItemType) +bool cItemHandler::OnPlayerPlace( + cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, + int a_CursorX, int a_CursorY, int a_CursorZ +) +{ + if (a_BlockFace < 0) + { + // Clicked in air + return false; + } + + if ((a_BlockY < 0) || (a_BlockY >= cChunkDef::Height)) + { + // The clicked block is outside the world, ignore this call altogether (#128) + return false; + } + + BLOCKTYPE ClickedBlock; + NIBBLETYPE ClickedBlockMeta; + + a_World.GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, ClickedBlock, ClickedBlockMeta); + + // Check if the block ignores build collision (water, grass etc.): + if ( + BlockHandler(ClickedBlock)->DoesIgnoreBuildCollision() || + BlockHandler(ClickedBlock)->DoesIgnoreBuildCollision(&a_Player, ClickedBlockMeta) + ) + { + cChunkInterface ChunkInterface(a_World.GetChunkMap()); + BlockHandler(ClickedBlock)->OnDestroyedByPlayer(ChunkInterface, a_World, &a_Player, a_BlockX, a_BlockY, a_BlockZ); + } + else + { + AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); + + if ((a_BlockY < 0) || (a_BlockY >= cChunkDef::Height)) + { + // The block is being placed outside the world, ignore this packet altogether (#128) + return false; + } + + NIBBLETYPE PlaceMeta; + BLOCKTYPE PlaceBlock; + a_World.GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, PlaceBlock, PlaceMeta); + + // Clicked on side of block, make sure that placement won't be cancelled if there is a slab able to be double slabbed. + // No need to do combinability (dblslab) checks, client will do that here. + if ( + !BlockHandler(PlaceBlock)->DoesIgnoreBuildCollision() && + !BlockHandler(PlaceBlock)->DoesIgnoreBuildCollision(&a_Player, PlaceMeta) + ) + { + // Tried to place a block *into* another? + // Happens when you place a block aiming at side of block with a torch on it or stem beside it + return false; + } + } + + BLOCKTYPE BlockType; + NIBBLETYPE BlockMeta; + if (!GetPlacementBlockTypeMeta(&a_World, &a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta)) + { + // Handler refused the placement, send that information back to the client: + a_World.SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, &a_Player); + a_Player.GetInventory().SendEquippedSlot(); + return false; + } + + if (!a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, BlockType, BlockMeta)) + { + // The placement failed, the block has already been re-sent, re-send inventory: + a_Player.GetInventory().SendEquippedSlot(); + return false; + } + + AString PlaceSound = cBlockInfo::GetPlaceSound(BlockType); + float Volume = 1.0f, Pitch = 0.8f; + if (PlaceSound == "dig.metal") + { + Pitch = 1.2f; + PlaceSound = "dig.stone"; + } + else if (PlaceSound == "random.anvil_land") + { + Volume = 0.65f; + } + + a_World.BroadcastSoundEffect(PlaceSound, a_BlockX + 0.5, a_BlockY + 0.5, a_BlockZ + 0.5, Volume, Pitch); + + // Remove the "placed" item: + if (a_Player.IsGameModeSurvival()) + { + a_Player.GetInventory().RemoveOneEquippedItem(); + } + return true; +} + + + + + bool cItemHandler::OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir) { UNUSED(a_World); diff --git a/src/Items/ItemHandler.h b/src/Items/ItemHandler.h index 67c250a97..3ac664798 100644 --- a/src/Items/ItemHandler.h +++ b/src/Items/ItemHandler.h @@ -31,10 +31,35 @@ public: /** Force virtual destructor */ virtual ~cItemHandler() {} + + + /** Called when the player tries to place the item (right mouse button, IsPlaceable() == true). + The default handler uses GetPlacementBlockTypeMeta and places the returned block. + Override this function for advanced behavior such as placing multiple blocks. + If the block placement is refused inside this call, it will automatically revert the client-side changes. + Returns true if the placement succeeded, false if the placement was aborted for any reason. */ + virtual bool OnPlayerPlace( + cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, + int a_CursorX, int a_CursorY, int a_CursorZ + ); + + /** Called when the player right-clicks with this item and IsPlaceable() == true, and OnPlace() is not overridden. + This function should provide the block type and meta for the placed block, or refuse the placement. + Returns true to allow placement, false to refuse. */ + virtual bool GetPlacementBlockTypeMeta( + cWorld * a_World, cPlayer * a_Player, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, + int a_CursorX, int a_CursorY, int a_CursorZ, + BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta + ); + + /** Called when the player tries to use the item (right mouse button). Return false to make the item unusable. DEFAULT: False */ virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir); + /** Called when the client sends the SHOOT status in the lclk packet */ virtual void OnItemShoot(cPlayer *, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace) { @@ -106,18 +131,8 @@ public: /** Can the anvil repair this item, when a_Item is the second input? */ virtual bool CanRepairWithRawMaterial(short a_ItemType); - /** Called before a block is placed into a world. - The handler should return true to allow placement, false to refuse. - Also, the handler should set a_BlockType and a_BlockMeta to correct values for the newly placed block. - */ - virtual bool GetPlacementBlockTypeMeta( - cWorld * a_World, cPlayer * a_Player, - int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, - int a_CursorX, int a_CursorY, int a_CursorZ, - BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta - ); - - /** Returns whether this tool/item can harvest a specific block (e.g. wooden pickaxe can harvest stone, but wood can't) DEFAULT: False */ + /** Returns whether this tool / item can harvest a specific block (e.g. iron pickaxe can harvest diamond ore, but wooden one can't). + Defaults to false unless overridden. */ virtual bool CanHarvestBlock(BLOCKTYPE a_BlockType); static cItemHandler * GetItemHandler(int a_ItemType); diff --git a/src/Items/ItemMobHead.h b/src/Items/ItemMobHead.h index 4c36fe8d8..d962dabae 100644 --- a/src/Items/ItemMobHead.h +++ b/src/Items/ItemMobHead.h @@ -3,6 +3,7 @@ #include "ItemHandler.h" #include "../World.h" +#include "../BlockEntities/MobHeadEntity.h" @@ -18,6 +19,266 @@ public: } + virtual bool OnPlayerPlace( + cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, + int a_CursorX, int a_CursorY, int a_CursorZ + ) override + { + // Cannot place a head at "no face" and from the bottom: + if ((a_BlockFace == BLOCK_FACE_NONE) || (a_BlockFace == BLOCK_FACE_BOTTOM)) + { + return true; + } + AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); + + // If the placed head is a wither, try to spawn the wither first: + if (a_EquippedItem.m_ItemDamage == E_META_HEAD_WITHER) + { + if (TrySpawnWitherAround(a_World, a_Player, a_BlockX, a_BlockY, a_BlockZ)) + { + return true; + } + // Wither not created, proceed with regular head placement + } + + return PlaceRegularHead(a_World, a_Player, a_EquippedItem, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); + } + + + /** Places a regular head block with no mob spawning checking. */ + bool PlaceRegularHead( + cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace + ) + { + // Place the block: + if (!a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_HEAD, static_cast(a_EquippedItem.m_ItemType))) + { + return false; + } + + // Use a callback to set the properties of the mob head block entity: + class cCallback : public cBlockEntityCallback + { + cPlayer & m_Player; + eMobHeadType m_HeadType; + NIBBLETYPE m_BlockMeta; + + virtual bool Item(cBlockEntity * a_BlockEntity) + { + if (a_BlockEntity->GetBlockType() != E_BLOCK_HEAD) + { + return false; + } + cMobHeadEntity * MobHeadEntity = static_cast(a_BlockEntity); + + int Rotation = 0; + if (m_BlockMeta == 1) + { + Rotation = FloorC(m_Player.GetYaw() * 16.0f / 360.0f + 0.5f) & 0x0f; + } + + MobHeadEntity->SetType(m_HeadType); + MobHeadEntity->SetRotation(static_cast(Rotation)); + MobHeadEntity->GetWorld()->BroadcastBlockEntity(MobHeadEntity->GetPosX(), MobHeadEntity->GetPosY(), MobHeadEntity->GetPosZ()); + return false; + } + + public: + cCallback (cPlayer & a_CBPlayer, eMobHeadType a_HeadType, NIBBLETYPE a_BlockMeta) : + m_Player(a_CBPlayer), + m_HeadType(a_HeadType), + m_BlockMeta(a_BlockMeta) + {} + }; + cCallback Callback(a_Player, static_cast(a_EquippedItem.m_ItemType), static_cast(a_BlockFace)); + a_World.DoWithBlockEntityAt(a_BlockX, a_BlockY, a_BlockZ, Callback); + return true; + } + + + /** Spawns a wither if the wither skull placed at the specified coords completes wither's spawning formula. + Returns true if the wither was created. */ + bool TrySpawnWitherAround( + cWorld & a_World, cPlayer & a_Player, + int a_BlockX, int a_BlockY, int a_BlockZ + ) + { + // No wither can be created at Y < 2 - not enough space for the formula: + if (a_BlockY < 2) + { + return false; + } + + // Check for all relevant wither locations: + static const Vector3i RelCoords[] = + { + { 0, 0, 0}, + { 1, 0, 0}, + {-1, 0, 0}, + { 0, 0, 1}, + { 0, 0, -1}, + }; + for (size_t i = 0; i < ARRAYCOUNT(RelCoords); ++i) + { + if (TrySpawnWitherAt( + a_World, a_Player, + a_BlockX, a_BlockY, a_BlockZ, + RelCoords[i].x, RelCoords[i].z + )) + { + return true; + } + } // for i - Coords[] + + return false; + } + + + /** Tries to spawn a wither at the specified offset from the placed head block. + PlacedHead coords are used to override the block query - at those coords the block is not queried from the world, + but assumed to be a head instead. + Offset is used to shift the image around the X and Z axis. + Returns true iff the wither was created successfully. */ + bool TrySpawnWitherAt( + cWorld & a_World, cPlayer & a_Player, + int a_PlacedHeadX, int a_PlacedHeadY, int a_PlacedHeadZ, + int a_OffsetX, int a_OffsetZ + ) + { + // Image for the wither at the X axis: + static const sSetBlock ImageWitherX[] = + { + {-1, 0, 0, E_BLOCK_HEAD, E_META_HEAD_WITHER}, + { 0, 0, 0, E_BLOCK_HEAD, E_META_HEAD_WITHER}, + { 1, 0, 0, E_BLOCK_HEAD, E_META_HEAD_WITHER}, + {-1, -1, 0, E_BLOCK_SOULSAND, 0}, + { 0, -1, 0, E_BLOCK_SOULSAND, 0}, + { 1, -1, 0, E_BLOCK_SOULSAND, 0}, + {-1, -2, 0, E_BLOCK_AIR, 0}, + { 0, -2, 0, E_BLOCK_SOULSAND, 0}, + { 1, -2, 0, E_BLOCK_AIR, 0}, + }; + + // Image for the wither at the Z axis: + static const sSetBlock ImageWitherZ[] = + { + { 0, 0, -1, E_BLOCK_HEAD, E_META_HEAD_WITHER}, + { 0, 0, 0, E_BLOCK_HEAD, E_META_HEAD_WITHER}, + { 0, 0, 1, E_BLOCK_HEAD, E_META_HEAD_WITHER}, + { 0, -1, -1, E_BLOCK_SOULSAND, 0}, + { 0, -1, 0, E_BLOCK_SOULSAND, 0}, + { 0, -1, 1, E_BLOCK_SOULSAND, 0}, + { 0, -2, -1, E_BLOCK_AIR, 0}, + { 0, -2, 0, E_BLOCK_SOULSAND, 0}, + { 0, -2, 1, E_BLOCK_AIR, 0}, + }; + + // Try to spawn the wither from each image: + return ( + TrySpawnWitherFromImage( + a_World, a_Player, ImageWitherX, ARRAYCOUNT(ImageWitherX), + a_PlacedHeadX, a_PlacedHeadY, a_PlacedHeadZ, + a_OffsetX, a_OffsetZ + ) || + TrySpawnWitherFromImage( + a_World, a_Player, ImageWitherZ, ARRAYCOUNT(ImageWitherZ), + a_PlacedHeadX, a_PlacedHeadY, a_PlacedHeadZ, + a_OffsetX, a_OffsetZ + ) + ); + } + + + /** Tries to spawn a wither from the specified image at the specified offset from the placed head block. + PlacedHead coords are used to override the block query - at those coords the block is not queried from the world, + but assumed to be a head instead. + Offset is used to shift the image around the X and Z axis. + Returns true iff the wither was created successfully. */ + bool TrySpawnWitherFromImage( + cWorld & a_World, cPlayer & a_Player, const sSetBlock * a_Image, size_t a_ImageCount, + int a_PlacedHeadX, int a_PlacedHeadY, int a_PlacedHeadZ, + int a_OffsetX, int a_OffsetZ + ) + { + // Check each block individually; simultaneously build the SetBlockVector for clearing the blocks: + sSetBlockVector AirBlocks; + AirBlocks.reserve(a_ImageCount); + for (size_t i = 0; i < a_ImageCount; i++) + { + // Get the absolute coords of the image: + int BlockX = a_PlacedHeadX + a_OffsetX + a_Image[i].m_RelX; + int BlockY = a_PlacedHeadY + a_Image[i].m_RelY; + int BlockZ = a_PlacedHeadZ + a_OffsetZ + a_Image[i].m_RelZ; + + // If the query is for the placed head, short-circuit-evaluate it: + if ((BlockX == a_PlacedHeadX) && (BlockY == a_PlacedHeadY) && (BlockZ == a_PlacedHeadZ)) + { + if ((a_Image[i].m_BlockType != E_BLOCK_HEAD) || (a_Image[i].m_BlockMeta != E_META_HEAD_WITHER)) + { + return false; // Didn't match + } + continue; // Matched, continue checking the rest of the image + } + + // Query the world block: + BLOCKTYPE BlockType; + NIBBLETYPE BlockMeta; + if (!a_World.GetBlockTypeMeta(BlockX, BlockY, BlockZ, BlockType, BlockMeta)) + { + // Cannot query block, assume unloaded chunk, fail to spawn the wither + return false; + } + + // Compare the world block: + if ((BlockType != a_Image[i].m_BlockType) || (BlockMeta != a_Image[i].m_BlockMeta)) + { + return false; // Didn't match + } + // Matched, continue checking + } // for i - a_Image + + // All image blocks matched, try place the wither: + if (!a_Player.PlaceBlocks(AirBlocks)) + { + return false; + } + + // Spawn the wither: + int BlockX = a_PlacedHeadX + a_OffsetX; + int BlockZ = a_PlacedHeadZ + a_OffsetZ; + a_World.SpawnMob(static_cast(BlockX) + 0.5, a_PlacedHeadY - 2, static_cast(BlockZ) + 0.5, mtWither); + AwardSpawnWitherAchievement(a_World, BlockX, a_PlacedHeadY - 2, BlockZ); + return true; + } + + + /** Awards the achievement to all players close to the specified point. */ + void AwardSpawnWitherAchievement(cWorld & a_World, int a_BlockX, int a_BlockY, int a_BlockZ) + { + class cPlayerCallback : public cPlayerListCallback + { + Vector3f m_Pos; + + virtual bool Item(cPlayer * a_Player) + { + // If player is close, award achievement: + double Dist = (a_Player->GetPosition() - m_Pos).Length(); + if (Dist < 50.0) + { + a_Player->AwardAchievement(achSpawnWither); + } + return false; + } + + public: + cPlayerCallback(const Vector3f & a_Pos) : m_Pos(a_Pos) {} + } PlayerCallback(Vector3f(static_cast(a_BlockX), static_cast(a_BlockY), static_cast(a_BlockZ))); + a_World.ForEachPlayer(PlayerCallback); + } + + virtual bool IsPlaceable(void) override { return true; diff --git a/src/Items/ItemPumpkin.h b/src/Items/ItemPumpkin.h new file mode 100644 index 000000000..fa00179d3 --- /dev/null +++ b/src/Items/ItemPumpkin.h @@ -0,0 +1,156 @@ + +// ItemPumpkin.h + +// Declares the cItemPumpkinHandler class representing the pumpkin block in its item form + + + + + +#pragma once + +#include "ItemHandler.h" + + + + + +class cItemPumpkinHandler: + public cItemHandler +{ + typedef cItemHandler super; + +public: + cItemPumpkinHandler(void): + super(E_BLOCK_PUMPKIN) + { + } + + + virtual bool OnPlayerPlace( + cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, + int a_CursorX, int a_CursorY, int a_CursorZ + ) override + { + // First try spawning a snow golem or an iron golem: + int PlacedBlockX = a_BlockX; + int PlacedBlockY = a_BlockY; + int PlacedBlockZ = a_BlockZ; + AddFaceDirection(PlacedBlockX, PlacedBlockY, PlacedBlockZ, a_BlockFace); + if (TrySpawnGolem(a_World, a_Player, PlacedBlockX, PlacedBlockY, PlacedBlockZ)) + { + // The client thinks that they placed the pumpkin, let them know it's been replaced: + a_Player.SendBlocksAround(PlacedBlockX, PlacedBlockY, PlacedBlockZ); + return true; + } + + // No golem at these coords, place the block normally: + return super::OnPlayerPlace(a_World, a_Player, a_EquippedItem, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ); + } + + + /** Spawns a snow / iron golem if the shape matches the recipe, supposing that the block placed at the specified coords is a pumpkin. + Returns true if the golem blocks are removed (for spawning), false if the recipe is not matched. */ + bool TrySpawnGolem(cWorld & a_World, cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ) + { + // A golem can't form with a pumpkin below level 2 or above level 255 + if ((a_BlockY < 2) || (a_BlockY >= cChunkDef::Height)) + { + return false; + } + + // Decide which golem to try spawning based on the block below the placed pumpkin: + switch (a_World.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ)) + { + case E_BLOCK_SNOW_BLOCK: return TrySpawnSnowGolem(a_World, a_Player, a_BlockX, a_BlockY, a_BlockZ); + case E_BLOCK_IRON_BLOCK: return TrySpawnIronGolem(a_World, a_Player, a_BlockX, a_BlockY, a_BlockZ); + default: + { + // No golem here + return false; + } + } + } + + + /** Spawns a snow golem if the shape matches the recipe, supposing that the block placed at the specified coords is a pumpkin. + Returns true if the golem blocks are removed (for spawning), false if the recipe is not matched. + Assumes that the block below the specified block has already been checked and is a snow block. */ + bool TrySpawnSnowGolem(cWorld & a_World, cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ) + { + // Need one more snow block 2 blocks below the pumpkin: + if (a_World.GetBlock(a_BlockX, a_BlockY - 2, a_BlockZ) != E_BLOCK_SNOW_BLOCK) + { + return false; + } + + // Try to place air blocks where the original recipe blocks were: + sSetBlockVector AirBlocks; + AirBlocks.emplace_back(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_AIR, 0); // Head + AirBlocks.emplace_back(a_BlockX, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0); // Torso + AirBlocks.emplace_back(a_BlockX, a_BlockY - 2, a_BlockZ, E_BLOCK_AIR, 0); // Legs + if (!a_Player.PlaceBlocks(AirBlocks)) + { + return false; + } + + // Spawn the golem: + a_World.SpawnMob(static_cast(a_BlockX) + 0.5, a_BlockY - 2, static_cast(a_BlockZ) + 0.5, mtSnowGolem); + return true; + } + + + /** Spawns an iron golem if the shape matches the recipe, supposing that the block placed at the specified coords is a pumpkin. + Returns true if the golem blocks are removed (for spawning), false if the recipe is not matched. + Assumes that the block below the specified block has already been checked and is an iron block. */ + bool TrySpawnIronGolem(cWorld & a_World, cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ) + { + // Need one more iron block 2 blocks below the pumpkin: + if (a_World.GetBlock(a_BlockX, a_BlockY - 2, a_BlockZ) != E_BLOCK_IRON_BLOCK) + { + return false; + } + + // Check the two arm directions (X, Z) using a loop over two sets of offset vectors: + static const Vector3i ArmOffsets[] = + { + {1, 0, 0}, + {0, 0, 1}, + }; + for (size_t i = 0; i < ARRAYCOUNT(ArmOffsets); i++) + { + // If the arm blocks don't match, bail out of this loop repetition: + if ( + (a_World.GetBlock(a_BlockX + ArmOffsets[i].x, a_BlockY - 1, a_BlockZ + ArmOffsets[i].z) != E_BLOCK_IRON_BLOCK) || + (a_World.GetBlock(a_BlockX - ArmOffsets[i].x, a_BlockY - 1, a_BlockZ - ArmOffsets[i].z) != E_BLOCK_IRON_BLOCK) + ) + { + continue; + } + + // Try to place air blocks where the original recipe blocks were: + sSetBlockVector AirBlocks; + AirBlocks.emplace_back(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_AIR, 0); // Head + AirBlocks.emplace_back(a_BlockX, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0); // Torso + AirBlocks.emplace_back(a_BlockX, a_BlockY - 2, a_BlockZ, E_BLOCK_AIR, 0); // Legs + AirBlocks.emplace_back(a_BlockX + ArmOffsets[i].x, a_BlockY - 1, a_BlockZ + ArmOffsets[i].z, E_BLOCK_AIR, 0); // Arm + AirBlocks.emplace_back(a_BlockX - ArmOffsets[i].x, a_BlockY - 1, a_BlockZ - ArmOffsets[i].z, E_BLOCK_AIR, 0); // Arm + if (!a_Player.PlaceBlocks(AirBlocks)) + { + return false; + } + + // Spawn the golem: + a_World.SpawnMob(static_cast(a_BlockX) + 0.5, a_BlockY - 2, static_cast(a_BlockZ) + 0.5, mtIronGolem); + return true; + } // for i - ArmOffsets[] + + // Neither arm offset matched, this thing is not a complete golem + return false; + } +}; + + + + diff --git a/src/Items/ItemSign.h b/src/Items/ItemSign.h index 0fa0fa0be..dabbdbba1 100644 --- a/src/Items/ItemSign.h +++ b/src/Items/ItemSign.h @@ -13,13 +13,33 @@ class cItemSignHandler : public cItemHandler { + typedef cItemHandler super; public: cItemSignHandler(int a_ItemType) : - cItemHandler(a_ItemType) + super(a_ItemType) { } + virtual bool OnPlayerPlace( + cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, + int a_CursorX, int a_CursorY, int a_CursorZ + ) + { + // If the regular placement doesn't work, do no further processing: + if (!super::OnPlayerPlace(a_World, a_Player, a_EquippedItem, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ)) + { + return false; + } + + // After successfully placing the sign, open the sign editor for the player: + AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); + a_Player.GetClientHandle()->SendEditSign(a_BlockX, a_BlockY, a_BlockZ); + return true; + } + + virtual bool IsPlaceable(void) override { return true; diff --git a/src/Items/ItemSlab.h b/src/Items/ItemSlab.h new file mode 100644 index 000000000..1b68b9d0c --- /dev/null +++ b/src/Items/ItemSlab.h @@ -0,0 +1,93 @@ + +// ItemSlab.h + +// Declares the cItemSlabHandler responsible for handling slabs, when in their item form. + + + + + +#pragma once + +#include "ItemHandler.h" +#include "../Blocks/BlockSlab.h" + + + + + +class cItemSlabHandler: + public cItemHandler +{ + typedef cItemHandler super; + +public: + + /** Creates a new handler for the specified slab item type. + Sets the handler to use the specified doubleslab block type for combining self into doubleslabs. */ + cItemSlabHandler(int a_ItemType, BLOCKTYPE a_DoubleSlabBlockType): + super(a_ItemType), + m_DoubleSlabBlockType(a_DoubleSlabBlockType) + { + } + + + // cItemHandler overrides: + virtual bool OnPlayerPlace( + cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, + int a_CursorX, int a_CursorY, int a_CursorZ + ) override + { + // Special slab handling - placing a slab onto another slab produces a dblslab instead: + BLOCKTYPE ClickedBlockType; + NIBBLETYPE ClickedBlockMeta; + a_World.GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, ClickedBlockType, ClickedBlockMeta); + if ( + (ClickedBlockType == m_ItemType) && // Placing the same slab material + (ClickedBlockMeta == a_EquippedItem.m_ItemDamage) // Placing the same slab sub-kind (and existing slab is single) + ) + { + // If clicking the top side of a bottom-half slab, combine into a doubleslab: + if ( + (a_BlockFace == BLOCK_FACE_TOP) && + ((ClickedBlockMeta & 0x08) == 0) + ) + { + return a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, m_DoubleSlabBlockType, ClickedBlockMeta & 0x07); + } + + // If clicking the bottom side of a top-half slab, combine into a doubleslab: + if ( + (a_BlockFace == BLOCK_FACE_BOTTOM) && + ((ClickedBlockMeta & 0x08) != 0) + ) + { + return a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, m_DoubleSlabBlockType, ClickedBlockMeta & 0x07); + } + } + + // The slabs didn't combine, use the default handler to place the slab: + bool res = super::OnPlayerPlace(a_World, a_Player, a_EquippedItem, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ); + + /* + The client has a bug when a slab replaces snow and there's a slab above it. + The client then combines the slab above, rather than replacing the snow. + We send the block above the currently placed block back to the client to fix the bug. + Ref.: http://forum.mc-server.org/showthread.php?tid=434&pid=17388#pid17388 + */ + if ((a_BlockFace == BLOCK_FACE_TOP) && (a_BlockY < cChunkDef::Height - 1)) + { + a_Player.SendBlocksAround(a_BlockX, a_BlockY + 1, a_BlockZ, 1); + } + return res; + } + +protected: + /** The block type to use when the slab combines into a doubleslab block. */ + BLOCKTYPE m_DoubleSlabBlockType; +}; + + + + diff --git a/src/Protocol/Protocol17x.cpp b/src/Protocol/Protocol17x.cpp index 1e33ec433..34103ea5a 100644 --- a/src/Protocol/Protocol17x.cpp +++ b/src/Protocol/Protocol17x.cpp @@ -228,8 +228,8 @@ void cProtocol172::SendBlockChanges(int a_ChunkX, int a_ChunkZ, const sSetBlockV Pkt.WriteInt((int)a_Changes.size() * 4); for (sSetBlockVector::const_iterator itr = a_Changes.begin(), end = a_Changes.end(); itr != end; ++itr) { - unsigned int Coords = itr->y | (itr->z << 8) | (itr->x << 12); - unsigned int Blocks = itr->BlockMeta | (itr->BlockType << 4); + unsigned int Coords = itr->m_RelY | (itr->m_RelZ << 8) | (itr->m_RelX << 12); + unsigned int Blocks = itr->m_BlockMeta | (itr->m_BlockType << 4); Pkt.WriteInt((Coords << 16) | Blocks); } // for itr - a_Changes[] } diff --git a/src/Protocol/Protocol18x.cpp b/src/Protocol/Protocol18x.cpp index 1a13f4f7c..72827ac47 100644 --- a/src/Protocol/Protocol18x.cpp +++ b/src/Protocol/Protocol18x.cpp @@ -207,9 +207,9 @@ void cProtocol180::SendBlockChanges(int a_ChunkX, int a_ChunkZ, const sSetBlockV Pkt.WriteVarInt((UInt32)a_Changes.size()); for (sSetBlockVector::const_iterator itr = a_Changes.begin(), end = a_Changes.end(); itr != end; ++itr) { - short Coords = (short) (itr->y | (itr->z << 8) | (itr->x << 12)); + short Coords = (short) (itr->m_RelY | (itr->m_RelZ << 8) | (itr->m_RelX << 12)); Pkt.WriteShort(Coords); - Pkt.WriteVarInt((itr->BlockType & 0xFFF) << 4 | (itr->BlockMeta & 0xF)); + Pkt.WriteVarInt((itr->m_BlockType & 0xFFF) << 4 | (itr->m_BlockMeta & 0xF)); } // for itr - a_Changes[] } diff --git a/src/World.cpp b/src/World.cpp index ae739a2c3..c08b44f77 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -1463,7 +1463,7 @@ void cWorld::GrowTreeImage(const sSetBlockVector & a_Blocks) sSetBlockVector b2; for (sSetBlockVector::const_iterator itr = a_Blocks.begin(); itr != a_Blocks.end(); ++itr) { - if (itr->BlockType == E_BLOCK_LOG) + if (itr->m_BlockType == E_BLOCK_LOG) { b2.push_back(*itr); } @@ -1478,7 +1478,7 @@ void cWorld::GrowTreeImage(const sSetBlockVector & a_Blocks) // Check that at each log's coord there's an block allowed to be overwritten: for (sSetBlockVector::const_iterator itr = b2.begin(); itr != b2.end(); ++itr) { - switch (itr->BlockType) + switch (itr->m_BlockType) { CASE_TREE_ALLOWED_BLOCKS: { @@ -1926,6 +1926,15 @@ void cWorld::SpawnPrimedTNT(double a_X, double a_Y, double a_Z, int a_FuseTicks, +void cWorld::SetBlocks(const sSetBlockVector & a_Blocks) +{ + m_ChunkMap->SetBlocks(a_Blocks); +} + + + + + void cWorld::ReplaceBlocks(const sSetBlockVector & a_Blocks, BLOCKTYPE a_FilterBlockType) { m_ChunkMap->ReplaceBlocks(a_Blocks, a_FilterBlockType); diff --git a/src/World.h b/src/World.h index 4f24280a4..2ee33375f 100644 --- a/src/World.h +++ b/src/World.h @@ -491,6 +491,11 @@ public: // tolua_end + /** Performs the specified single-block set operations simultaneously, as if SetBlock() was called for each item. + Is more efficient than calling SetBlock() multiple times. + If the chunk for any of the blocks is not loaded, the set operation is ignored silently. */ + void SetBlocks(const sSetBlockVector & a_Blocks); + /** Replaces world blocks with a_Blocks, if they are of type a_FilterBlockType */ void ReplaceBlocks(const sSetBlockVector & a_Blocks, BLOCKTYPE a_FilterBlockType); -- cgit v1.2.3 From 9c5463be1e090ff65acdc0c0571788f60ad76478 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Wed, 24 Dec 2014 07:32:31 +0100 Subject: gcc compilation fix. --- src/ChunkDef.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ChunkDef.h b/src/ChunkDef.h index b62656a58..959841ecc 100644 --- a/src/ChunkDef.h +++ b/src/ChunkDef.h @@ -399,9 +399,12 @@ public: typedef std::list cChunkCoordsList; typedef std::vector cChunkCoordsVector; +namespace std +{ + /** A simple hash function for chunk coords, we assume that chunk coords won't use more than 16 bits, so the hash is almost an identity. Used for std::unordered_map */ -template<> struct std::hash +template<> struct hash { size_t operator ()(const cChunkCoords & a_Coords) { @@ -409,6 +412,8 @@ template<> struct std::hash } }; +} // namespace std + -- cgit v1.2.3 From 63de5f8a555e3bd4b9309792e3a9c0dcb9e8f4b6 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Wed, 24 Dec 2014 08:38:37 +0100 Subject: Replaced a std::hash specialization with explicit type. std::hash is problematic in gcc / clang, one has a class, the other a struct. --- src/ChunkDef.h | 10 +++------- src/Entities/Player.cpp | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/ChunkDef.h b/src/ChunkDef.h index 959841ecc..2bfa2949c 100644 --- a/src/ChunkDef.h +++ b/src/ChunkDef.h @@ -399,21 +399,17 @@ public: typedef std::list cChunkCoordsList; typedef std::vector cChunkCoordsVector; -namespace std -{ - /** A simple hash function for chunk coords, we assume that chunk coords won't use more than 16 bits, so the hash is almost an identity. Used for std::unordered_map */ -template<> struct hash +class cChunkCoordsHash { - size_t operator ()(const cChunkCoords & a_Coords) +public: + size_t operator () (const cChunkCoords & a_Coords) const { return (static_cast(a_Coords.m_ChunkX) << 16) ^ static_cast(a_Coords.m_ChunkZ); } }; -} // namespace std - diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 7bdd1c6f7..1d5cc6554 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -2206,7 +2206,7 @@ void cPlayer::SendBlocksAround(int a_BlockX, int a_BlockY, int a_BlockZ, int a_R } // Divide the block changes by their respective chunks: - std::unordered_map Changes; + std::unordered_map Changes; for (const auto & blk: blks) { Changes[cChunkCoords(blk.m_ChunkX, blk.m_ChunkZ)].push_back(blk); -- cgit v1.2.3 From f07784b92f9e402706afce148ecc50430832ae16 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Wed, 24 Dec 2014 19:44:15 +0100 Subject: Fixed redstone dust placement on upside-down slabs. --- src/Items/ItemRedstoneDust.h | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/Items/ItemRedstoneDust.h b/src/Items/ItemRedstoneDust.h index a2289239c..6d5fb521f 100644 --- a/src/Items/ItemRedstoneDust.h +++ b/src/Items/ItemRedstoneDust.h @@ -27,7 +27,20 @@ public: BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta ) override { - if (!cBlockInfo::FullyOccupiesVoxel(a_World->GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ))) // Some solid blocks, such as cocoa beans, are not suitable for dust + // Check if coords are out of range: + if ((a_BlockY <= 0) || (a_BlockY >= cChunkDef::Height)) + { + return false; + } + + // Check the block below, if it supports dust on top of it: + BLOCKTYPE BlockType; + NIBBLETYPE BlockMeta; + if (!a_World->GetBlockTypeMeta(a_BlockX, a_BlockY - 1, a_BlockZ, BlockType, BlockMeta)) + { + return false; + } + if (!IsBlockTypeUnderSuitable(BlockType, BlockMeta)) { return false; } @@ -36,6 +49,28 @@ public: a_BlockMeta = 0; return true; } + + + /** Returns true if the specified block type / meta is suitable to have redstone dust on top of it. */ + static bool IsBlockTypeUnderSuitable(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) + { + if (cBlockInfo::FullyOccupiesVoxel(a_BlockType)) + { + return true; + } + + switch (a_BlockType) + { + case E_BLOCK_NEW_STONE_SLAB: + case E_BLOCK_WOODEN_SLAB: + case E_BLOCK_STONE_SLAB: + { + // Slabs can support redstone if they're upside down: + return ((a_BlockMeta & 0x08) != 0); + } + } + return false; + } } ; -- cgit v1.2.3 From 5609d76ed7d8026b3bcaeb02fb42bd9ba2f27c96 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Wed, 24 Dec 2014 20:02:51 +0100 Subject: APIDump: Updated the player block placement documentation. The hooks now have fewer parameters but are called on all player-placed blocks (#1618). --- MCServer/Plugins/APIDump/APIDesc.lua | 2 ++ MCServer/Plugins/APIDump/Hooks/OnPlayerPlacedBlock.lua | 10 +++++----- MCServer/Plugins/APIDump/Hooks/OnPlayerPlacingBlock.lua | 10 +++++----- src/Entities/Player.h | 3 ++- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/MCServer/Plugins/APIDump/APIDesc.lua b/MCServer/Plugins/APIDump/APIDesc.lua index ba3763724..b2c7108e9 100644 --- a/MCServer/Plugins/APIDump/APIDesc.lua +++ b/MCServer/Plugins/APIDump/APIDesc.lua @@ -1840,7 +1840,9 @@ a_Player:OpenWindow(Window); MoveToWorld = { Params = "WorldName", Return = "bool", Return = "Moves the player to the specified world. Returns true if successful." }, OpenWindow = { Params = "{{cWindow|Window}}", Return = "", Notes = "Opens the specified UI window for the player." }, PermissionMatches = { Params = "Permission, Template", Return = "bool", Notes = "(STATIC) Returns true if the specified permission matches the specified template. The template may contain wildcards." }, + PlaceBlock = { Params = "BlockX, BlockY, BlockZ, BlockType, BlockMeta", Return = "bool", Notes = "Places a block while impersonating the player. The {{OnPlayerPlacingBlock|HOOK_PLAYER_PLACING_BLOCK}} hook is called before the placement, and if it succeeds, the block is placed and the {{OnPlayerPlacedBlock|HOOK_PLAYER_PLACED_BLOCK}} hook is called. Returns true iff the block is successfully placed. Assumes that the block is in a currently loaded chunk." }, Respawn = { Params = "", Return = "", Notes = "Restores the health, extinguishes fire, makes visible and sends the Respawn packet." }, + SendBlocksAround = { Params = "BlockX, BlockY, BlockZ, [Range]", Return = "", Notes = "Sends all the world's blocks in Range from the specified coords to the player, as a BlockChange packet. Range defaults to 1 (only one block sent)." }, SendMessage = { Params = "Message", Return = "", Notes = "Sends the specified message to the player." }, SendMessageFailure = { Params = "Message", Return = "", Notes = "Prepends Rose [INFO] / colours entire text (depending on ShouldUseChatPrefixes()) and sends message to player. For a command that failed to run because of insufficient permissions, etc." }, SendMessageFatal = { Params = "Message", Return = "", Notes = "Prepends Red [FATAL] / colours entire text (depending on ShouldUseChatPrefixes()) and sends message to player. For something serious, such as a plugin crash, etc." }, diff --git a/MCServer/Plugins/APIDump/Hooks/OnPlayerPlacedBlock.lua b/MCServer/Plugins/APIDump/Hooks/OnPlayerPlacedBlock.lua index 54888a6db..6445a76b4 100644 --- a/MCServer/Plugins/APIDump/Hooks/OnPlayerPlacedBlock.lua +++ b/MCServer/Plugins/APIDump/Hooks/OnPlayerPlacedBlock.lua @@ -12,7 +12,11 @@ return Use the {{cPlayer}}:GetWorld() function to get the world to which the block belongs.

See also the {{OnPlayerPlacingBlock|HOOK_PLAYER_PLACING_BLOCK}} hook for a similar hook called - before the placement. + before the placement.

+

+ If the client action results in multiple blocks being placed (such as a bed or a door), each separate + block is reported through this hook. All the blocks are already present in the world before the first + instance of this hook is called. ]], Params = { @@ -20,10 +24,6 @@ return { Name = "BlockX", Type = "number", Notes = "X-coord of the block" }, { Name = "BlockY", Type = "number", Notes = "Y-coord of the block" }, { Name = "BlockZ", Type = "number", Notes = "Z-coord of the block" }, - { Name = "BlockFace", Type = "number", Notes = "Face of the existing block upon which the player interacted. One of the BLOCK_FACE_ constants" }, - { Name = "CursorX", Type = "number", Notes = "X-coord of the cursor within the block face (0 .. 15)" }, - { Name = "CursorY", Type = "number", Notes = "Y-coord of the cursor within the block face (0 .. 15)" }, - { Name = "CursorZ", Type = "number", Notes = "Z-coord of the cursor within the block face (0 .. 15)" }, { Name = "BlockType", Type = "BLOCKTYPE", Notes = "The block type of the block" }, { Name = "BlockMeta", Type = "NIBBLETYPE", Notes = "The block meta of the block" }, }, diff --git a/MCServer/Plugins/APIDump/Hooks/OnPlayerPlacingBlock.lua b/MCServer/Plugins/APIDump/Hooks/OnPlayerPlacingBlock.lua index 2a928390b..4241a09aa 100644 --- a/MCServer/Plugins/APIDump/Hooks/OnPlayerPlacingBlock.lua +++ b/MCServer/Plugins/APIDump/Hooks/OnPlayerPlacingBlock.lua @@ -15,7 +15,11 @@ return Use the {{cPlayer}}:GetWorld() function to get the world to which the block belongs.

See also the {{OnPlayerPlacedBlock|HOOK_PLAYER_PLACED_BLOCK}} hook for a similar hook called after - the placement. + the placement.

+

+ If the client action results in multiple blocks being placed (such as a bed or a door), each separate + block is reported through this hook and only if all of them succeed, all the blocks are placed. If + any one of the calls are refused by the plugin, all the blocks are refused and reverted on the client. ]], Params = { @@ -23,10 +27,6 @@ return { Name = "BlockX", Type = "number", Notes = "X-coord of the block" }, { Name = "BlockY", Type = "number", Notes = "Y-coord of the block" }, { Name = "BlockZ", Type = "number", Notes = "Z-coord of the block" }, - { Name = "BlockFace", Type = "number", Notes = "Face of the existing block upon which the player is interacting. One of the BLOCK_FACE_ constants" }, - { Name = "CursorX", Type = "number", Notes = "X-coord of the cursor within the block face (0 .. 15)" }, - { Name = "CursorY", Type = "number", Notes = "Y-coord of the cursor within the block face (0 .. 15)" }, - { Name = "CursorZ", Type = "number", Notes = "Z-coord of the cursor within the block face (0 .. 15)" }, { Name = "BlockType", Type = "BLOCKTYPE", Notes = "The block type of the block" }, { Name = "BlockMeta", Type = "NIBBLETYPE", Notes = "The block meta of the block" }, }, diff --git a/src/Entities/Player.h b/src/Entities/Player.h index 33ab5293c..b94d2659e 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -442,7 +442,8 @@ public: /** Calls the block-placement hook and places the block in the world, unless refused by the hook. If the hook prevents the placement, sends the current block at the specified coords back to the client. - Assumes that all the blocks are in currently loaded chunks. */ + Assumes that the block is in a currently loaded chunk. + Returns true if the block is successfully placed. */ bool PlaceBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); /** Sends the block in the specified range around the specified coord to the client -- cgit v1.2.3