diff options
author | Mattes D <github@xoft.cz> | 2014-12-25 00:31:56 +0100 |
---|---|---|
committer | Mattes D <github@xoft.cz> | 2014-12-25 00:31:56 +0100 |
commit | 1d593134da55317e3ec09c56463d4a0a92128b81 (patch) | |
tree | 220066e191e85bb3fafd4bccbcfbe62ed737f2f6 | |
parent | ByteBuffer: SingleThreadAccessChecker is request-only. (diff) | |
parent | APIDump: Updated the player block placement documentation. (diff) | |
download | cuberite-1d593134da55317e3ec09c56463d4a0a92128b81.tar cuberite-1d593134da55317e3ec09c56463d4a0a92128b81.tar.gz cuberite-1d593134da55317e3ec09c56463d4a0a92128b81.tar.bz2 cuberite-1d593134da55317e3ec09c56463d4a0a92128b81.tar.lz cuberite-1d593134da55317e3ec09c56463d4a0a92128b81.tar.xz cuberite-1d593134da55317e3ec09c56463d4a0a92128b81.tar.zst cuberite-1d593134da55317e3ec09c56463d4a0a92128b81.zip |
Diffstat (limited to '')
48 files changed, 1489 insertions, 786 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.</p> <p> See also the {{OnPlayerPlacingBlock|HOOK_PLAYER_PLACING_BLOCK}} hook for a similar hook called - before the placement. + before the placement.</p> + <p> + 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.</p> <p> See also the {{OnPlayerPlacedBlock|HOOK_PLAYER_PLACED_BLOCK}} hook for a similar hook called after - the placement. + the placement.</p> + <p> + 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/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); - /// <summary>Similar to DoesIgnoreBuildCollision(void), but is used for cases where block meta/player item-in-hand is needed to determine collision (thin snow)</summary> + /** 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(); } - /// <summary>Returns if this block drops if it gets destroyed by an unsuitable situation. Default: true</summary> + /** 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); /// <summary>Rotates a given block meta counter-clockwise. Default: no change</summary> 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<cMobHeadEntity*>(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<cMobHeadEntity*>(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<eMobHeadType>(m_OldBlockMeta)); - MobHeadEntity->SetRotation(static_cast<eMobHeadRotation>(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<NIBBLETYPE>(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..2bfa2949c 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<sSetBlock> sSetBlockList; @@ -385,6 +399,17 @@ public: typedef std::list<cChunkCoords> cChunkCoordsList; typedef std::vector<cChunkCoords> 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<cChunkCoords, ...> */ +class cChunkCoordsHash +{ +public: + size_t operator () (const cChunkCoords & a_Coords) const + { + return (static_cast<size_t>(a_Coords.m_ChunkX) << 16) ^ static_cast<size_t>(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..1d5cc6554 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 <unordered_map> #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<cChunkCoords, sSetBlockVector, cChunkCoordsHash> 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..b94d2659e 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -440,8 +440,27 @@ 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 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 + 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<int>(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<BLOCKTYPE>(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<NIBBLETYPE>(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<cMobHeadEntity *>(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<eMobHeadRotation>(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<eMobHeadType>(a_EquippedItem.m_ItemType), static_cast<NIBBLETYPE>(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<double>(BlockX) + 0.5, a_PlacedHeadY - 2, static_cast<double>(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<float>(a_BlockX), static_cast<float>(a_BlockY), static_cast<float>(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<double>(a_BlockX) + 0.5, a_BlockY - 2, static_cast<double>(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<double>(a_BlockX) + 0.5, a_BlockY - 2, static_cast<double>(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/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; + } } ; 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/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; 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); |