summaryrefslogtreecommitdiffstats
path: root/src/Items
diff options
context:
space:
mode:
Diffstat (limited to 'src/Items')
-rw-r--r--src/Items/CMakeLists.txt7
-rw-r--r--src/Items/ItemBed.h28
-rw-r--r--src/Items/ItemBigFlower.h56
-rw-r--r--src/Items/ItemChest.h167
-rw-r--r--src/Items/ItemDoor.h96
-rw-r--r--src/Items/ItemDye.h25
-rw-r--r--src/Items/ItemHandler.cpp199
-rw-r--r--src/Items/ItemHandler.h39
-rw-r--r--src/Items/ItemMobHead.h261
-rw-r--r--src/Items/ItemPumpkin.h156
-rw-r--r--src/Items/ItemRedstoneDust.h37
-rw-r--r--src/Items/ItemSign.h22
-rw-r--r--src/Items/ItemSlab.h93
13 files changed, 1077 insertions, 109 deletions
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;
+};
+
+
+
+