From ccdf03daaf880dd0c89a03b50c11eb083ee1cfb0 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Wed, 24 Dec 2014 07:20:17 +0100 Subject: Refactored all player block placing to go through hooks. Fixes #1618. --- src/Items/CMakeLists.txt | 7 +- src/Items/ItemBed.h | 28 +++-- src/Items/ItemBigFlower.h | 56 ++++++++++ src/Items/ItemChest.h | 167 +++++++++++++++++++++++++++++ src/Items/ItemDoor.h | 96 +++++++++++++---- src/Items/ItemDye.h | 25 ++--- src/Items/ItemHandler.cpp | 199 +++++++++++++++++++++++++++-------- src/Items/ItemHandler.h | 39 ++++--- src/Items/ItemMobHead.h | 261 ++++++++++++++++++++++++++++++++++++++++++++++ src/Items/ItemPumpkin.h | 156 +++++++++++++++++++++++++++ src/Items/ItemSign.h | 22 +++- src/Items/ItemSlab.h | 93 +++++++++++++++++ 12 files changed, 1041 insertions(+), 108 deletions(-) create mode 100644 src/Items/ItemBigFlower.h create mode 100644 src/Items/ItemChest.h create mode 100644 src/Items/ItemPumpkin.h create mode 100644 src/Items/ItemSlab.h (limited to 'src/Items') diff --git a/src/Items/CMakeLists.txt b/src/Items/CMakeLists.txt index 12a467672..c50ddb372 100644 --- a/src/Items/CMakeLists.txt +++ b/src/Items/CMakeLists.txt @@ -10,12 +10,14 @@ SET (SRCS SET (HDRS ItemArmor.h ItemBed.h + ItemBigFlower.h ItemBoat.h ItemBow.h ItemBrewingStand.h ItemBucket.h ItemCake.h ItemCauldron.h + ItemChest.h ItemCloth.h ItemComparator.h ItemDoor.h @@ -38,18 +40,21 @@ SET (HDRS ItemPainting.h ItemPickaxe.h ItemPotion.h + ItemPumpkin.h ItemRedstoneDust.h ItemRedstoneRepeater.h ItemSapling.h ItemSeeds.h ItemShears.h ItemShovel.h + ItemSlab.h ItemSign.h ItemSpawnEgg.h ItemString.h ItemSugarcane.h ItemSword.h - ItemThrowable.h) + ItemThrowable.h +) if(NOT MSVC) add_library(Items ${SRCS} ${HDRS}) diff --git a/src/Items/ItemBed.h b/src/Items/ItemBed.h index 94a14cf16..77d51d744 100644 --- a/src/Items/ItemBed.h +++ b/src/Items/ItemBed.h @@ -24,30 +24,36 @@ public: return true; } - virtual bool GetPlacementBlockTypeMeta( - cWorld * a_World, cPlayer * a_Player, + + virtual bool OnPlayerPlace( + cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, - int a_CursorX, int a_CursorY, int a_CursorZ, - BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta + int a_CursorX, int a_CursorY, int a_CursorZ ) override { + // Can only be placed on the floor: if (a_BlockFace != BLOCK_FACE_TOP) { - // Can only be placed on the floor return false; } + AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); - a_BlockMeta = cBlockBedHandler::RotationToMetaData(a_Player->GetYaw()); + // The "foot" block: + sSetBlockVector blks; + NIBBLETYPE BlockMeta = cBlockBedHandler::RotationToMetaData(a_Player.GetYaw()); + blks.emplace_back(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_BED, BlockMeta); - // Check if there is empty space for the foot section: - Vector3i Direction = cBlockBedHandler::MetaDataToDirection(a_BlockMeta); - if (a_World->GetBlock(a_BlockX + Direction.x, a_BlockY, a_BlockZ + Direction.z) != E_BLOCK_AIR) + // Check if there is empty space for the "head" block: + // (Vanilla only allows beds to be placed into air) + Vector3i Direction = cBlockBedHandler::MetaDataToDirection(BlockMeta); + if (a_World.GetBlock(a_BlockX + Direction.x, a_BlockY, a_BlockZ + Direction.z) != E_BLOCK_AIR) { return false; } + blks.emplace_back(a_BlockX + Direction.x, a_BlockY, a_BlockZ + Direction.z, E_BLOCK_BED, BlockMeta | 0x08); - a_BlockType = E_BLOCK_BED; - return true; + // Place both bed blocks: + return a_Player.PlaceBlocks(blks); } } ; diff --git a/src/Items/ItemBigFlower.h b/src/Items/ItemBigFlower.h new file mode 100644 index 000000000..4341a1a17 --- /dev/null +++ b/src/Items/ItemBigFlower.h @@ -0,0 +1,56 @@ + +// ItemBigFlower.h + +// Declares the cItemBigFlower class representing the cItemHandler for big flowers + + + + + +#pragma once + +#include "ItemHandler.h" + + + + + +class cItemBigFlowerHandler: + public cItemHandler +{ + typedef cItemHandler super; + +public: + cItemBigFlowerHandler(void): + super(E_BLOCK_BIG_FLOWER) + { + } + + + virtual bool OnPlayerPlace( + cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, + int a_CursorX, int a_CursorY, int a_CursorZ + ) override + { + // Can only be placed on the floor: + if (a_BlockFace != BLOCK_FACE_TOP) + { + return false; + } + AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); + + // Place both blocks atomically: + sSetBlockVector blks; + blks.emplace_back(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_BIG_FLOWER, a_EquippedItem.m_ItemDamage & 0x07); + if (a_BlockY < cChunkDef::Height - 1) + { + blks.emplace_back(a_BlockX, a_BlockY + 1, a_BlockZ, E_BLOCK_BIG_FLOWER, (a_EquippedItem.m_ItemDamage & 0x07) | 0x08); + } + return a_Player.PlaceBlocks(blks); + } +}; + + + + diff --git a/src/Items/ItemChest.h b/src/Items/ItemChest.h new file mode 100644 index 000000000..b6579c423 --- /dev/null +++ b/src/Items/ItemChest.h @@ -0,0 +1,167 @@ + +// ItemChest.h + +// Declares the cItemChestHandler class representing the cItemHandler descendant responsible for chests + + + + + +#pragma once + +#include "ItemHandler.h" +#include "../Blocks/BlockChest.h" + + + + + +class cItemChestHandler: + public cItemHandler +{ + typedef cItemHandler super; +public: + cItemChestHandler(int a_ItemType): + super(a_ItemType) + { + } + + + virtual bool OnPlayerPlace( + cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, + int a_CursorX, int a_CursorY, int a_CursorZ + ) override + { + if (a_BlockFace < 0) + { + // Clicked in air + return false; + } + + if ((a_BlockY < 0) || (a_BlockY >= cChunkDef::Height)) + { + // The clicked block is outside the world, ignore this call altogether (#128) + return false; + } + + // Check if the block ignores build collision (water, grass etc.): + BLOCKTYPE ClickedBlock; + NIBBLETYPE ClickedBlockMeta; + a_World.GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, ClickedBlock, ClickedBlockMeta); + if ( + BlockHandler(ClickedBlock)->DoesIgnoreBuildCollision() || + BlockHandler(ClickedBlock)->DoesIgnoreBuildCollision(&a_Player, ClickedBlockMeta) + ) + { + cChunkInterface ChunkInterface(a_World.GetChunkMap()); + BlockHandler(ClickedBlock)->OnDestroyedByPlayer(ChunkInterface, a_World, &a_Player, a_BlockX, a_BlockY, a_BlockZ); + } + else + { + AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); + + if ((a_BlockY < 0) || (a_BlockY >= cChunkDef::Height)) + { + // The block is being placed outside the world, ignore this packet altogether (#128) + return false; + } + + NIBBLETYPE PlaceMeta; + BLOCKTYPE PlaceBlock; + a_World.GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, PlaceBlock, PlaceMeta); + + // Clicked on side of block, make sure that placement won't be cancelled if there is a slab able to be double slabbed. + // No need to do combinability (dblslab) checks, client will do that here. + if ( + !BlockHandler(PlaceBlock)->DoesIgnoreBuildCollision() && + !BlockHandler(PlaceBlock)->DoesIgnoreBuildCollision(&a_Player, PlaceMeta) + ) + { + // Tried to place a block *into* another? + // Happens when you place a block aiming at side of block with a torch on it or stem beside it + return false; + } + } + + // Check that there is at most one single neighbor of the same chest type: + static const Vector3i CrossCoords[] = + { + {-1, 0, 0}, + { 0, 0, -1}, + { 1, 0, 0}, + { 0, 0, 1}, + }; + int NeighborIdx = -1; + for (size_t i = 0; i < ARRAYCOUNT(CrossCoords); i++) + { + if (a_World.GetBlock(a_BlockX + CrossCoords[i].x, a_BlockY, a_BlockZ + CrossCoords[i].z) != m_ItemType) + { + continue; + } + if (NeighborIdx >= 0) + { + // Can't place here, there are already two neighbors, this would form a 3-block chest + return false; + } + NeighborIdx = static_cast(i); + + // Check that this neighbor is a single chest: + int bx = a_BlockX + CrossCoords[i].x; + int bz = a_BlockZ + CrossCoords[i].z; + for (size_t j = 0; j < ARRAYCOUNT(CrossCoords); j++) + { + if (a_World.GetBlock(bx + CrossCoords[j].x, a_BlockY, bz + CrossCoords[j].z) == m_ItemType) + { + return false; + } + } // for j + } // for i + + // If there's no chest neighbor, place the single block chest and bail out: + BLOCKTYPE ChestBlockType = static_cast(m_ItemType); + if (NeighborIdx < 0) + { + NIBBLETYPE Meta = cBlockChestHandler::PlayerYawToMetaData(a_Player.GetYaw()); + return a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, ChestBlockType, Meta); + } + + // There is a neighbor to which we need to adjust + double yaw = a_Player.GetYaw(); + if ((NeighborIdx == 0) || (NeighborIdx == 2)) + { + // The neighbor is in the X axis, form a X-axis-aligned dblchest: + NIBBLETYPE Meta = ((yaw >= -90) && (yaw < 90)) ? E_META_CHEST_FACING_ZM : E_META_CHEST_FACING_ZP; + + // Place the new chest: + if (!a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, ChestBlockType, Meta)) + { + return false; + } + + // Adjust the existing chest: + a_World.FastSetBlock(a_BlockX + CrossCoords[NeighborIdx].x, a_BlockY, a_BlockZ + CrossCoords[NeighborIdx].z, ChestBlockType, Meta); + return true; + } + + // The neighbor is in the Z axis, form a Z-axis-aligned dblchest: + NIBBLETYPE Meta = (yaw < 0) ? E_META_CHEST_FACING_XM : E_META_CHEST_FACING_XP; + + // Place the new chest: + if (!a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, ChestBlockType, Meta)) + { + return false; + } + + // Adjust the existing chest: + a_World.FastSetBlock(a_BlockX + CrossCoords[NeighborIdx].x, a_BlockY, a_BlockZ + CrossCoords[NeighborIdx].z, ChestBlockType, Meta); + return true; + } + +private: + cItemChestHandler(const cItemChestHandler &) = delete; +}; + + + + diff --git a/src/Items/ItemDoor.h b/src/Items/ItemDoor.h index cd5baf44f..dbba26728 100644 --- a/src/Items/ItemDoor.h +++ b/src/Items/ItemDoor.h @@ -3,6 +3,7 @@ #include "ItemHandler.h" #include "../World.h" +#include "../Blocks/BlockDoor.h" @@ -18,27 +19,43 @@ public: } - virtual bool IsPlaceable(void) override - { - return true; - } - virtual bool GetPlacementBlockTypeMeta( - cWorld * a_World, cPlayer * a_Player, + virtual bool OnPlayerPlace( + cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, - int a_CursorX, int a_CursorY, int a_CursorZ, - BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta + int a_CursorX, int a_CursorY, int a_CursorZ ) override { + // Vanilla only allows door placement while clicking on the top face of the block below the door: + if (a_BlockFace != BLOCK_FACE_NONE) + { + return false; + } + AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); + + // Door (bottom block) can be placed in Y range of [1, 254]: + if ((a_BlockY < 1) || (a_BlockY + 2 >= cChunkDef::Height)) + { + return false; + } + + // The door needs a compatible block below it: + if ((a_BlockY > 0) && cBlockDoorHandler::CanBeOn(a_World.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ))) + { + return false; + } + + // Get the block type of the door to place: + BLOCKTYPE BlockType; switch (m_ItemType) { - case E_ITEM_WOODEN_DOOR: a_BlockType = E_BLOCK_WOODEN_DOOR; break; - case E_ITEM_IRON_DOOR: a_BlockType = E_BLOCK_IRON_DOOR; break; - case E_ITEM_SPRUCE_DOOR: a_BlockType = E_BLOCK_SPRUCE_DOOR; break; - case E_ITEM_BIRCH_DOOR: a_BlockType = E_BLOCK_BIRCH_DOOR; break; - case E_ITEM_JUNGLE_DOOR: a_BlockType = E_BLOCK_JUNGLE_DOOR; break; - case E_ITEM_DARK_OAK_DOOR: a_BlockType = E_BLOCK_DARK_OAK_DOOR; break; - case E_ITEM_ACACIA_DOOR: a_BlockType = E_BLOCK_ACACIA_DOOR; break; + case E_ITEM_WOODEN_DOOR: BlockType = E_BLOCK_WOODEN_DOOR; break; + case E_ITEM_IRON_DOOR: BlockType = E_BLOCK_IRON_DOOR; break; + case E_ITEM_SPRUCE_DOOR: BlockType = E_BLOCK_SPRUCE_DOOR; break; + case E_ITEM_BIRCH_DOOR: BlockType = E_BLOCK_BIRCH_DOOR; break; + case E_ITEM_JUNGLE_DOOR: BlockType = E_BLOCK_JUNGLE_DOOR; break; + case E_ITEM_DARK_OAK_DOOR: BlockType = E_BLOCK_DARK_OAK_DOOR; break; + case E_ITEM_ACACIA_DOOR: BlockType = E_BLOCK_ACACIA_DOOR; break; default: { ASSERT(!"Unhandled door type"); @@ -46,14 +63,47 @@ public: } } - cChunkInterface ChunkInterface(a_World->GetChunkMap()); - bool Meta = BlockHandler(a_BlockType)->GetPlacementBlockTypeMeta( - ChunkInterface, a_Player, - a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, - a_CursorX, a_CursorY, a_CursorZ, - a_BlockType, a_BlockMeta - ); - return Meta; + // Check the two blocks that will get replaced by the door: + BLOCKTYPE LowerBlockType = a_World.GetBlock(a_BlockX, a_BlockY + 1, a_BlockZ); + BLOCKTYPE UpperBlockType = a_World.GetBlock(a_BlockX, a_BlockY + 2, a_BlockZ); + if ( + !cBlockDoorHandler::CanReplaceBlock(LowerBlockType) || + !cBlockDoorHandler::CanReplaceBlock(UpperBlockType)) + { + return false; + } + + // Get the coords of the neighboring blocks: + NIBBLETYPE LowerBlockMeta = cBlockDoorHandler::PlayerYawToMetaData(a_Player.GetYaw()); + Vector3i RelDirToOutside = cBlockDoorHandler::GetRelativeDirectionToOutside(LowerBlockMeta); + Vector3i LeftNeighborPos = RelDirToOutside; + LeftNeighborPos.TurnCCW(); + LeftNeighborPos.Move(a_BlockX, a_BlockY, a_BlockZ); + Vector3i RightNeighborPos = RelDirToOutside; + RightNeighborPos.TurnCW(); + RightNeighborPos.Move(a_BlockX, a_BlockY, a_BlockZ); + + // Decide whether the hinge is on the left (default) or on the right: + NIBBLETYPE UpperBlockMeta = 0x08; + if ( + cBlockDoorHandler::IsDoorBlockType(a_World.GetBlock(LeftNeighborPos)) || // The block to the left is a door block + cBlockInfo::IsSolid(a_World.GetBlock(RightNeighborPos)) // The block to the right is solid + ) + { + UpperBlockMeta = 0x09; // Upper block | hinge on right + } + + // Set the blocks: + sSetBlockVector blks; + blks.emplace_back(a_BlockX, a_BlockY, a_BlockZ, BlockType, LowerBlockMeta); + blks.emplace_back(a_BlockX, a_BlockY + 1, a_BlockZ, BlockType, UpperBlockMeta); + return a_Player.PlaceBlocks(blks); + } + + + virtual bool IsPlaceable(void) override + { + return true; } } ; diff --git a/src/Items/ItemDye.h b/src/Items/ItemDye.h index da978040d..bfcd0bac4 100644 --- a/src/Items/ItemDye.h +++ b/src/Items/ItemDye.h @@ -55,25 +55,16 @@ public: return false; } - // Check plugins - if (cRoot::Get()->GetPluginManager()->CallHookPlayerPlacingBlock(*a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, 0, 0, 0, E_BLOCK_COCOA_POD, BlockMeta)) + // Place the cocoa pod: + if (a_Player->PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_COCOA_POD, BlockMeta)) { - a_World->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, a_Player); - a_Player->GetInventory().SendEquippedSlot(); - return false; - } - - // Set block and broadcast place sound - a_World->SetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_COCOA_POD, BlockMeta); - a_World->BroadcastSoundEffect("dig.stone", a_BlockX + 0.5, a_BlockY + 0.5, a_BlockZ + 0.5, 1.0f, 0.8f); - - // Remove one cocoa pod from the inventory - if (!a_Player->IsGameModeCreative()) - { - a_Player->GetInventory().RemoveOneEquippedItem(); + a_World->BroadcastSoundEffect("dig.stone", a_BlockX + 0.5, a_BlockY + 0.5, a_BlockZ + 0.5, 1.0f, 0.8f); + if (a_Player->IsGameModeSurvival()) + { + a_Player->GetInventory().RemoveOneEquippedItem(); + } + return true; } - cRoot::Get()->GetPluginManager()->CallHookPlayerPlacedBlock(*a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, 0, 0, 0, E_BLOCK_COCOA_POD, BlockMeta); - return true; } return false; } diff --git a/src/Items/ItemHandler.cpp b/src/Items/ItemHandler.cpp index 9272a723d..92c55ec62 100644 --- a/src/Items/ItemHandler.cpp +++ b/src/Items/ItemHandler.cpp @@ -6,39 +6,43 @@ #include "../Entities/Player.h" #include "../FastRandom.h" #include "../BlockInServerPluginInterface.h" +#include "../Chunk.h" // Handlers: #include "ItemArmor.h" #include "ItemBed.h" +#include "ItemBigFlower.h" #include "ItemBoat.h" #include "ItemBow.h" #include "ItemBrewingStand.h" #include "ItemBucket.h" #include "ItemCake.h" #include "ItemCauldron.h" +#include "ItemChest.h" #include "ItemCloth.h" #include "ItemComparator.h" #include "ItemDoor.h" -#include "ItemMilk.h" #include "ItemDye.h" #include "ItemEmptyMap.h" #include "ItemFishingRod.h" #include "ItemFlowerPot.h" #include "ItemFood.h" #include "ItemGoldenApple.h" -#include "ItemItemFrame.h" #include "ItemHoe.h" +#include "ItemItemFrame.h" #include "ItemLeaves.h" #include "ItemLighter.h" #include "ItemLilypad.h" #include "ItemMap.h" +#include "ItemMilk.h" #include "ItemMinecart.h" +#include "ItemMobHead.h" #include "ItemMushroomSoup.h" #include "ItemNetherWart.h" #include "ItemPainting.h" #include "ItemPickaxe.h" #include "ItemPotion.h" -#include "ItemThrowable.h" +#include "ItemPumpkin.h" #include "ItemRedstoneDust.h" #include "ItemRedstoneRepeater.h" #include "ItemSapling.h" @@ -46,11 +50,12 @@ #include "ItemShears.h" #include "ItemShovel.h" #include "ItemSign.h" -#include "ItemMobHead.h" +#include "ItemSlab.h" #include "ItemSpawnEgg.h" #include "ItemString.h" #include "ItemSugarcane.h" #include "ItemSword.h" +#include "ItemThrowable.h" #include "../Blocks/BlockHandler.h" @@ -94,52 +99,58 @@ cItemHandler * cItemHandler::GetItemHandler(int a_ItemType) -cItemHandler *cItemHandler::CreateItemHandler(int a_ItemType) +cItemHandler * cItemHandler::CreateItemHandler(int a_ItemType) { switch (a_ItemType) { default: return new cItemHandler(a_ItemType); // Single item per handler, alphabetically sorted: - case E_BLOCK_LEAVES: return new cItemLeavesHandler(a_ItemType); - case E_BLOCK_NEW_LEAVES: return new cItemLeavesHandler(a_ItemType); - case E_BLOCK_SAPLING: return new cItemSaplingHandler(a_ItemType); - case E_BLOCK_WOOL: return new cItemClothHandler(a_ItemType); - case E_ITEM_BED: return new cItemBedHandler(a_ItemType); - case E_ITEM_BOAT: return new cItemBoatHandler(a_ItemType); + case E_BLOCK_CHEST: return new cItemChestHandler(a_ItemType); + case E_BLOCK_LEAVES: return new cItemLeavesHandler(a_ItemType); + case E_BLOCK_HEAD: return new cItemMobHeadHandler(a_ItemType); + case E_BLOCK_NEW_LEAVES: return new cItemLeavesHandler(a_ItemType); + case E_BLOCK_PUMPKIN: return new cItemPumpkinHandler; + case E_BLOCK_SAPLING: return new cItemSaplingHandler(a_ItemType); + case E_BLOCK_STONE_SLAB: return new cItemSlabHandler(E_BLOCK_STONE_SLAB, E_BLOCK_DOUBLE_STONE_SLAB); + case E_BLOCK_TRAPPED_CHEST: return new cItemChestHandler(a_ItemType); + case E_BLOCK_WOODEN_SLAB: return new cItemSlabHandler(E_BLOCK_WOODEN_SLAB, E_BLOCK_DOUBLE_WOODEN_SLAB); + case E_BLOCK_WOOL: return new cItemClothHandler(a_ItemType); + case E_ITEM_BED: return new cItemBedHandler(a_ItemType); + case E_ITEM_BOAT: return new cItemBoatHandler(a_ItemType); case E_ITEM_BOTTLE_O_ENCHANTING: return new cItemBottleOEnchantingHandler(); - case E_ITEM_BOW: return new cItemBowHandler(); - case E_ITEM_BREWING_STAND: return new cItemBrewingStandHandler(a_ItemType); - case E_ITEM_CAKE: return new cItemCakeHandler(a_ItemType); - case E_ITEM_CAULDRON: return new cItemCauldronHandler(a_ItemType); - case E_ITEM_COMPARATOR: return new cItemComparatorHandler(a_ItemType); - case E_ITEM_DYE: return new cItemDyeHandler(a_ItemType); - case E_ITEM_EGG: return new cItemEggHandler(); - case E_ITEM_EMPTY_MAP: return new cItemEmptyMapHandler(); - case E_ITEM_ENDER_PEARL: return new cItemEnderPearlHandler(); - case E_ITEM_FIRE_CHARGE: return new cItemLighterHandler(a_ItemType); - case E_ITEM_FIREWORK_ROCKET: return new cItemFireworkHandler(); - case E_ITEM_FISHING_ROD: return new cItemFishingRodHandler(a_ItemType); - case E_ITEM_FLINT_AND_STEEL: return new cItemLighterHandler(a_ItemType); - case E_ITEM_FLOWER_POT: return new cItemFlowerPotHandler(a_ItemType); - case E_ITEM_GOLDEN_APPLE: return new cItemGoldenAppleHandler(); - case E_BLOCK_LILY_PAD: return new cItemLilypadHandler(a_ItemType); - case E_ITEM_MAP: return new cItemMapHandler(); - case E_ITEM_MILK: return new cItemMilkHandler(); - case E_ITEM_MUSHROOM_SOUP: return new cItemMushroomSoupHandler(a_ItemType); - case E_ITEM_ITEM_FRAME: return new cItemItemFrameHandler(a_ItemType); - case E_ITEM_NETHER_WART: return new cItemNetherWartHandler(a_ItemType); - case E_ITEM_PAINTING: return new cItemPaintingHandler(a_ItemType); - case E_ITEM_POTIONS: return new cItemPotionHandler(); - case E_ITEM_REDSTONE_DUST: return new cItemRedstoneDustHandler(a_ItemType); - case E_ITEM_REDSTONE_REPEATER: return new cItemRedstoneRepeaterHandler(a_ItemType); - case E_ITEM_SHEARS: return new cItemShearsHandler(a_ItemType); - case E_ITEM_SIGN: return new cItemSignHandler(a_ItemType); - case E_ITEM_HEAD: return new cItemMobHeadHandler(a_ItemType); - case E_ITEM_SNOWBALL: return new cItemSnowballHandler(); - case E_ITEM_SPAWN_EGG: return new cItemSpawnEggHandler(a_ItemType); - case E_ITEM_STRING: return new cItemStringHandler(a_ItemType); - case E_ITEM_SUGARCANE: return new cItemSugarcaneHandler(a_ItemType); + case E_ITEM_BOW: return new cItemBowHandler(); + case E_ITEM_BREWING_STAND: return new cItemBrewingStandHandler(a_ItemType); + case E_ITEM_CAKE: return new cItemCakeHandler(a_ItemType); + case E_ITEM_CAULDRON: return new cItemCauldronHandler(a_ItemType); + case E_ITEM_COMPARATOR: return new cItemComparatorHandler(a_ItemType); + case E_ITEM_DYE: return new cItemDyeHandler(a_ItemType); + case E_ITEM_EGG: return new cItemEggHandler(); + case E_ITEM_EMPTY_MAP: return new cItemEmptyMapHandler(); + case E_ITEM_ENDER_PEARL: return new cItemEnderPearlHandler(); + case E_ITEM_FIRE_CHARGE: return new cItemLighterHandler(a_ItemType); + case E_ITEM_FIREWORK_ROCKET: return new cItemFireworkHandler(); + case E_ITEM_FISHING_ROD: return new cItemFishingRodHandler(a_ItemType); + case E_ITEM_FLINT_AND_STEEL: return new cItemLighterHandler(a_ItemType); + case E_ITEM_FLOWER_POT: return new cItemFlowerPotHandler(a_ItemType); + case E_ITEM_GOLDEN_APPLE: return new cItemGoldenAppleHandler(); + case E_BLOCK_LILY_PAD: return new cItemLilypadHandler(a_ItemType); + case E_ITEM_MAP: return new cItemMapHandler(); + case E_ITEM_MILK: return new cItemMilkHandler(); + case E_ITEM_MUSHROOM_SOUP: return new cItemMushroomSoupHandler(a_ItemType); + case E_ITEM_ITEM_FRAME: return new cItemItemFrameHandler(a_ItemType); + case E_ITEM_NETHER_WART: return new cItemNetherWartHandler(a_ItemType); + case E_ITEM_PAINTING: return new cItemPaintingHandler(a_ItemType); + case E_ITEM_POTIONS: return new cItemPotionHandler(); + case E_ITEM_REDSTONE_DUST: return new cItemRedstoneDustHandler(a_ItemType); + case E_ITEM_REDSTONE_REPEATER: return new cItemRedstoneRepeaterHandler(a_ItemType); + case E_ITEM_SHEARS: return new cItemShearsHandler(a_ItemType); + case E_ITEM_SIGN: return new cItemSignHandler(a_ItemType); + case E_ITEM_HEAD: return new cItemMobHeadHandler(a_ItemType); + case E_ITEM_SNOWBALL: return new cItemSnowballHandler(); + case E_ITEM_SPAWN_EGG: return new cItemSpawnEggHandler(a_ItemType); + case E_ITEM_STRING: return new cItemStringHandler(a_ItemType); + case E_ITEM_SUGARCANE: return new cItemSugarcaneHandler(a_ItemType); case E_ITEM_WOODEN_HOE: case E_ITEM_STONE_HOE: @@ -297,6 +308,108 @@ cItemHandler::cItemHandler(int a_ItemType) +bool cItemHandler::OnPlayerPlace( + cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, + int a_CursorX, int a_CursorY, int a_CursorZ +) +{ + if (a_BlockFace < 0) + { + // Clicked in air + return false; + } + + if ((a_BlockY < 0) || (a_BlockY >= cChunkDef::Height)) + { + // The clicked block is outside the world, ignore this call altogether (#128) + return false; + } + + BLOCKTYPE ClickedBlock; + NIBBLETYPE ClickedBlockMeta; + + a_World.GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, ClickedBlock, ClickedBlockMeta); + + // Check if the block ignores build collision (water, grass etc.): + if ( + BlockHandler(ClickedBlock)->DoesIgnoreBuildCollision() || + BlockHandler(ClickedBlock)->DoesIgnoreBuildCollision(&a_Player, ClickedBlockMeta) + ) + { + cChunkInterface ChunkInterface(a_World.GetChunkMap()); + BlockHandler(ClickedBlock)->OnDestroyedByPlayer(ChunkInterface, a_World, &a_Player, a_BlockX, a_BlockY, a_BlockZ); + } + else + { + AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); + + if ((a_BlockY < 0) || (a_BlockY >= cChunkDef::Height)) + { + // The block is being placed outside the world, ignore this packet altogether (#128) + return false; + } + + NIBBLETYPE PlaceMeta; + BLOCKTYPE PlaceBlock; + a_World.GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, PlaceBlock, PlaceMeta); + + // Clicked on side of block, make sure that placement won't be cancelled if there is a slab able to be double slabbed. + // No need to do combinability (dblslab) checks, client will do that here. + if ( + !BlockHandler(PlaceBlock)->DoesIgnoreBuildCollision() && + !BlockHandler(PlaceBlock)->DoesIgnoreBuildCollision(&a_Player, PlaceMeta) + ) + { + // Tried to place a block *into* another? + // Happens when you place a block aiming at side of block with a torch on it or stem beside it + return false; + } + } + + BLOCKTYPE BlockType; + NIBBLETYPE BlockMeta; + if (!GetPlacementBlockTypeMeta(&a_World, &a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta)) + { + // Handler refused the placement, send that information back to the client: + a_World.SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, &a_Player); + a_Player.GetInventory().SendEquippedSlot(); + return false; + } + + if (!a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, BlockType, BlockMeta)) + { + // The placement failed, the block has already been re-sent, re-send inventory: + a_Player.GetInventory().SendEquippedSlot(); + return false; + } + + AString PlaceSound = cBlockInfo::GetPlaceSound(BlockType); + float Volume = 1.0f, Pitch = 0.8f; + if (PlaceSound == "dig.metal") + { + Pitch = 1.2f; + PlaceSound = "dig.stone"; + } + else if (PlaceSound == "random.anvil_land") + { + Volume = 0.65f; + } + + a_World.BroadcastSoundEffect(PlaceSound, a_BlockX + 0.5, a_BlockY + 0.5, a_BlockZ + 0.5, Volume, Pitch); + + // Remove the "placed" item: + if (a_Player.IsGameModeSurvival()) + { + a_Player.GetInventory().RemoveOneEquippedItem(); + } + return true; +} + + + + + bool cItemHandler::OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir) { UNUSED(a_World); diff --git a/src/Items/ItemHandler.h b/src/Items/ItemHandler.h index 67c250a97..3ac664798 100644 --- a/src/Items/ItemHandler.h +++ b/src/Items/ItemHandler.h @@ -31,10 +31,35 @@ public: /** Force virtual destructor */ virtual ~cItemHandler() {} + + + /** Called when the player tries to place the item (right mouse button, IsPlaceable() == true). + The default handler uses GetPlacementBlockTypeMeta and places the returned block. + Override this function for advanced behavior such as placing multiple blocks. + If the block placement is refused inside this call, it will automatically revert the client-side changes. + Returns true if the placement succeeded, false if the placement was aborted for any reason. */ + virtual bool OnPlayerPlace( + cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, + int a_CursorX, int a_CursorY, int a_CursorZ + ); + + /** Called when the player right-clicks with this item and IsPlaceable() == true, and OnPlace() is not overridden. + This function should provide the block type and meta for the placed block, or refuse the placement. + Returns true to allow placement, false to refuse. */ + virtual bool GetPlacementBlockTypeMeta( + cWorld * a_World, cPlayer * a_Player, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, + int a_CursorX, int a_CursorY, int a_CursorZ, + BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta + ); + + /** Called when the player tries to use the item (right mouse button). Return false to make the item unusable. DEFAULT: False */ virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir); + /** Called when the client sends the SHOOT status in the lclk packet */ virtual void OnItemShoot(cPlayer *, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace) { @@ -106,18 +131,8 @@ public: /** Can the anvil repair this item, when a_Item is the second input? */ virtual bool CanRepairWithRawMaterial(short a_ItemType); - /** Called before a block is placed into a world. - The handler should return true to allow placement, false to refuse. - Also, the handler should set a_BlockType and a_BlockMeta to correct values for the newly placed block. - */ - virtual bool GetPlacementBlockTypeMeta( - cWorld * a_World, cPlayer * a_Player, - int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, - int a_CursorX, int a_CursorY, int a_CursorZ, - BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta - ); - - /** Returns whether this tool/item can harvest a specific block (e.g. wooden pickaxe can harvest stone, but wood can't) DEFAULT: False */ + /** Returns whether this tool / item can harvest a specific block (e.g. iron pickaxe can harvest diamond ore, but wooden one can't). + Defaults to false unless overridden. */ virtual bool CanHarvestBlock(BLOCKTYPE a_BlockType); static cItemHandler * GetItemHandler(int a_ItemType); diff --git a/src/Items/ItemMobHead.h b/src/Items/ItemMobHead.h index 4c36fe8d8..d962dabae 100644 --- a/src/Items/ItemMobHead.h +++ b/src/Items/ItemMobHead.h @@ -3,6 +3,7 @@ #include "ItemHandler.h" #include "../World.h" +#include "../BlockEntities/MobHeadEntity.h" @@ -18,6 +19,266 @@ public: } + virtual bool OnPlayerPlace( + cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, + int a_CursorX, int a_CursorY, int a_CursorZ + ) override + { + // Cannot place a head at "no face" and from the bottom: + if ((a_BlockFace == BLOCK_FACE_NONE) || (a_BlockFace == BLOCK_FACE_BOTTOM)) + { + return true; + } + AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); + + // If the placed head is a wither, try to spawn the wither first: + if (a_EquippedItem.m_ItemDamage == E_META_HEAD_WITHER) + { + if (TrySpawnWitherAround(a_World, a_Player, a_BlockX, a_BlockY, a_BlockZ)) + { + return true; + } + // Wither not created, proceed with regular head placement + } + + return PlaceRegularHead(a_World, a_Player, a_EquippedItem, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); + } + + + /** Places a regular head block with no mob spawning checking. */ + bool PlaceRegularHead( + cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace + ) + { + // Place the block: + if (!a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_HEAD, static_cast(a_EquippedItem.m_ItemType))) + { + return false; + } + + // Use a callback to set the properties of the mob head block entity: + class cCallback : public cBlockEntityCallback + { + cPlayer & m_Player; + eMobHeadType m_HeadType; + NIBBLETYPE m_BlockMeta; + + virtual bool Item(cBlockEntity * a_BlockEntity) + { + if (a_BlockEntity->GetBlockType() != E_BLOCK_HEAD) + { + return false; + } + cMobHeadEntity * MobHeadEntity = static_cast(a_BlockEntity); + + int Rotation = 0; + if (m_BlockMeta == 1) + { + Rotation = FloorC(m_Player.GetYaw() * 16.0f / 360.0f + 0.5f) & 0x0f; + } + + MobHeadEntity->SetType(m_HeadType); + MobHeadEntity->SetRotation(static_cast(Rotation)); + MobHeadEntity->GetWorld()->BroadcastBlockEntity(MobHeadEntity->GetPosX(), MobHeadEntity->GetPosY(), MobHeadEntity->GetPosZ()); + return false; + } + + public: + cCallback (cPlayer & a_CBPlayer, eMobHeadType a_HeadType, NIBBLETYPE a_BlockMeta) : + m_Player(a_CBPlayer), + m_HeadType(a_HeadType), + m_BlockMeta(a_BlockMeta) + {} + }; + cCallback Callback(a_Player, static_cast(a_EquippedItem.m_ItemType), static_cast(a_BlockFace)); + a_World.DoWithBlockEntityAt(a_BlockX, a_BlockY, a_BlockZ, Callback); + return true; + } + + + /** Spawns a wither if the wither skull placed at the specified coords completes wither's spawning formula. + Returns true if the wither was created. */ + bool TrySpawnWitherAround( + cWorld & a_World, cPlayer & a_Player, + int a_BlockX, int a_BlockY, int a_BlockZ + ) + { + // No wither can be created at Y < 2 - not enough space for the formula: + if (a_BlockY < 2) + { + return false; + } + + // Check for all relevant wither locations: + static const Vector3i RelCoords[] = + { + { 0, 0, 0}, + { 1, 0, 0}, + {-1, 0, 0}, + { 0, 0, 1}, + { 0, 0, -1}, + }; + for (size_t i = 0; i < ARRAYCOUNT(RelCoords); ++i) + { + if (TrySpawnWitherAt( + a_World, a_Player, + a_BlockX, a_BlockY, a_BlockZ, + RelCoords[i].x, RelCoords[i].z + )) + { + return true; + } + } // for i - Coords[] + + return false; + } + + + /** Tries to spawn a wither at the specified offset from the placed head block. + PlacedHead coords are used to override the block query - at those coords the block is not queried from the world, + but assumed to be a head instead. + Offset is used to shift the image around the X and Z axis. + Returns true iff the wither was created successfully. */ + bool TrySpawnWitherAt( + cWorld & a_World, cPlayer & a_Player, + int a_PlacedHeadX, int a_PlacedHeadY, int a_PlacedHeadZ, + int a_OffsetX, int a_OffsetZ + ) + { + // Image for the wither at the X axis: + static const sSetBlock ImageWitherX[] = + { + {-1, 0, 0, E_BLOCK_HEAD, E_META_HEAD_WITHER}, + { 0, 0, 0, E_BLOCK_HEAD, E_META_HEAD_WITHER}, + { 1, 0, 0, E_BLOCK_HEAD, E_META_HEAD_WITHER}, + {-1, -1, 0, E_BLOCK_SOULSAND, 0}, + { 0, -1, 0, E_BLOCK_SOULSAND, 0}, + { 1, -1, 0, E_BLOCK_SOULSAND, 0}, + {-1, -2, 0, E_BLOCK_AIR, 0}, + { 0, -2, 0, E_BLOCK_SOULSAND, 0}, + { 1, -2, 0, E_BLOCK_AIR, 0}, + }; + + // Image for the wither at the Z axis: + static const sSetBlock ImageWitherZ[] = + { + { 0, 0, -1, E_BLOCK_HEAD, E_META_HEAD_WITHER}, + { 0, 0, 0, E_BLOCK_HEAD, E_META_HEAD_WITHER}, + { 0, 0, 1, E_BLOCK_HEAD, E_META_HEAD_WITHER}, + { 0, -1, -1, E_BLOCK_SOULSAND, 0}, + { 0, -1, 0, E_BLOCK_SOULSAND, 0}, + { 0, -1, 1, E_BLOCK_SOULSAND, 0}, + { 0, -2, -1, E_BLOCK_AIR, 0}, + { 0, -2, 0, E_BLOCK_SOULSAND, 0}, + { 0, -2, 1, E_BLOCK_AIR, 0}, + }; + + // Try to spawn the wither from each image: + return ( + TrySpawnWitherFromImage( + a_World, a_Player, ImageWitherX, ARRAYCOUNT(ImageWitherX), + a_PlacedHeadX, a_PlacedHeadY, a_PlacedHeadZ, + a_OffsetX, a_OffsetZ + ) || + TrySpawnWitherFromImage( + a_World, a_Player, ImageWitherZ, ARRAYCOUNT(ImageWitherZ), + a_PlacedHeadX, a_PlacedHeadY, a_PlacedHeadZ, + a_OffsetX, a_OffsetZ + ) + ); + } + + + /** Tries to spawn a wither from the specified image at the specified offset from the placed head block. + PlacedHead coords are used to override the block query - at those coords the block is not queried from the world, + but assumed to be a head instead. + Offset is used to shift the image around the X and Z axis. + Returns true iff the wither was created successfully. */ + bool TrySpawnWitherFromImage( + cWorld & a_World, cPlayer & a_Player, const sSetBlock * a_Image, size_t a_ImageCount, + int a_PlacedHeadX, int a_PlacedHeadY, int a_PlacedHeadZ, + int a_OffsetX, int a_OffsetZ + ) + { + // Check each block individually; simultaneously build the SetBlockVector for clearing the blocks: + sSetBlockVector AirBlocks; + AirBlocks.reserve(a_ImageCount); + for (size_t i = 0; i < a_ImageCount; i++) + { + // Get the absolute coords of the image: + int BlockX = a_PlacedHeadX + a_OffsetX + a_Image[i].m_RelX; + int BlockY = a_PlacedHeadY + a_Image[i].m_RelY; + int BlockZ = a_PlacedHeadZ + a_OffsetZ + a_Image[i].m_RelZ; + + // If the query is for the placed head, short-circuit-evaluate it: + if ((BlockX == a_PlacedHeadX) && (BlockY == a_PlacedHeadY) && (BlockZ == a_PlacedHeadZ)) + { + if ((a_Image[i].m_BlockType != E_BLOCK_HEAD) || (a_Image[i].m_BlockMeta != E_META_HEAD_WITHER)) + { + return false; // Didn't match + } + continue; // Matched, continue checking the rest of the image + } + + // Query the world block: + BLOCKTYPE BlockType; + NIBBLETYPE BlockMeta; + if (!a_World.GetBlockTypeMeta(BlockX, BlockY, BlockZ, BlockType, BlockMeta)) + { + // Cannot query block, assume unloaded chunk, fail to spawn the wither + return false; + } + + // Compare the world block: + if ((BlockType != a_Image[i].m_BlockType) || (BlockMeta != a_Image[i].m_BlockMeta)) + { + return false; // Didn't match + } + // Matched, continue checking + } // for i - a_Image + + // All image blocks matched, try place the wither: + if (!a_Player.PlaceBlocks(AirBlocks)) + { + return false; + } + + // Spawn the wither: + int BlockX = a_PlacedHeadX + a_OffsetX; + int BlockZ = a_PlacedHeadZ + a_OffsetZ; + a_World.SpawnMob(static_cast(BlockX) + 0.5, a_PlacedHeadY - 2, static_cast(BlockZ) + 0.5, mtWither); + AwardSpawnWitherAchievement(a_World, BlockX, a_PlacedHeadY - 2, BlockZ); + return true; + } + + + /** Awards the achievement to all players close to the specified point. */ + void AwardSpawnWitherAchievement(cWorld & a_World, int a_BlockX, int a_BlockY, int a_BlockZ) + { + class cPlayerCallback : public cPlayerListCallback + { + Vector3f m_Pos; + + virtual bool Item(cPlayer * a_Player) + { + // If player is close, award achievement: + double Dist = (a_Player->GetPosition() - m_Pos).Length(); + if (Dist < 50.0) + { + a_Player->AwardAchievement(achSpawnWither); + } + return false; + } + + public: + cPlayerCallback(const Vector3f & a_Pos) : m_Pos(a_Pos) {} + } PlayerCallback(Vector3f(static_cast(a_BlockX), static_cast(a_BlockY), static_cast(a_BlockZ))); + a_World.ForEachPlayer(PlayerCallback); + } + + virtual bool IsPlaceable(void) override { return true; diff --git a/src/Items/ItemPumpkin.h b/src/Items/ItemPumpkin.h new file mode 100644 index 000000000..fa00179d3 --- /dev/null +++ b/src/Items/ItemPumpkin.h @@ -0,0 +1,156 @@ + +// ItemPumpkin.h + +// Declares the cItemPumpkinHandler class representing the pumpkin block in its item form + + + + + +#pragma once + +#include "ItemHandler.h" + + + + + +class cItemPumpkinHandler: + public cItemHandler +{ + typedef cItemHandler super; + +public: + cItemPumpkinHandler(void): + super(E_BLOCK_PUMPKIN) + { + } + + + virtual bool OnPlayerPlace( + cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, + int a_CursorX, int a_CursorY, int a_CursorZ + ) override + { + // First try spawning a snow golem or an iron golem: + int PlacedBlockX = a_BlockX; + int PlacedBlockY = a_BlockY; + int PlacedBlockZ = a_BlockZ; + AddFaceDirection(PlacedBlockX, PlacedBlockY, PlacedBlockZ, a_BlockFace); + if (TrySpawnGolem(a_World, a_Player, PlacedBlockX, PlacedBlockY, PlacedBlockZ)) + { + // The client thinks that they placed the pumpkin, let them know it's been replaced: + a_Player.SendBlocksAround(PlacedBlockX, PlacedBlockY, PlacedBlockZ); + return true; + } + + // No golem at these coords, place the block normally: + return super::OnPlayerPlace(a_World, a_Player, a_EquippedItem, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ); + } + + + /** Spawns a snow / iron golem if the shape matches the recipe, supposing that the block placed at the specified coords is a pumpkin. + Returns true if the golem blocks are removed (for spawning), false if the recipe is not matched. */ + bool TrySpawnGolem(cWorld & a_World, cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ) + { + // A golem can't form with a pumpkin below level 2 or above level 255 + if ((a_BlockY < 2) || (a_BlockY >= cChunkDef::Height)) + { + return false; + } + + // Decide which golem to try spawning based on the block below the placed pumpkin: + switch (a_World.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ)) + { + case E_BLOCK_SNOW_BLOCK: return TrySpawnSnowGolem(a_World, a_Player, a_BlockX, a_BlockY, a_BlockZ); + case E_BLOCK_IRON_BLOCK: return TrySpawnIronGolem(a_World, a_Player, a_BlockX, a_BlockY, a_BlockZ); + default: + { + // No golem here + return false; + } + } + } + + + /** Spawns a snow golem if the shape matches the recipe, supposing that the block placed at the specified coords is a pumpkin. + Returns true if the golem blocks are removed (for spawning), false if the recipe is not matched. + Assumes that the block below the specified block has already been checked and is a snow block. */ + bool TrySpawnSnowGolem(cWorld & a_World, cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ) + { + // Need one more snow block 2 blocks below the pumpkin: + if (a_World.GetBlock(a_BlockX, a_BlockY - 2, a_BlockZ) != E_BLOCK_SNOW_BLOCK) + { + return false; + } + + // Try to place air blocks where the original recipe blocks were: + sSetBlockVector AirBlocks; + AirBlocks.emplace_back(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_AIR, 0); // Head + AirBlocks.emplace_back(a_BlockX, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0); // Torso + AirBlocks.emplace_back(a_BlockX, a_BlockY - 2, a_BlockZ, E_BLOCK_AIR, 0); // Legs + if (!a_Player.PlaceBlocks(AirBlocks)) + { + return false; + } + + // Spawn the golem: + a_World.SpawnMob(static_cast(a_BlockX) + 0.5, a_BlockY - 2, static_cast(a_BlockZ) + 0.5, mtSnowGolem); + return true; + } + + + /** Spawns an iron golem if the shape matches the recipe, supposing that the block placed at the specified coords is a pumpkin. + Returns true if the golem blocks are removed (for spawning), false if the recipe is not matched. + Assumes that the block below the specified block has already been checked and is an iron block. */ + bool TrySpawnIronGolem(cWorld & a_World, cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ) + { + // Need one more iron block 2 blocks below the pumpkin: + if (a_World.GetBlock(a_BlockX, a_BlockY - 2, a_BlockZ) != E_BLOCK_IRON_BLOCK) + { + return false; + } + + // Check the two arm directions (X, Z) using a loop over two sets of offset vectors: + static const Vector3i ArmOffsets[] = + { + {1, 0, 0}, + {0, 0, 1}, + }; + for (size_t i = 0; i < ARRAYCOUNT(ArmOffsets); i++) + { + // If the arm blocks don't match, bail out of this loop repetition: + if ( + (a_World.GetBlock(a_BlockX + ArmOffsets[i].x, a_BlockY - 1, a_BlockZ + ArmOffsets[i].z) != E_BLOCK_IRON_BLOCK) || + (a_World.GetBlock(a_BlockX - ArmOffsets[i].x, a_BlockY - 1, a_BlockZ - ArmOffsets[i].z) != E_BLOCK_IRON_BLOCK) + ) + { + continue; + } + + // Try to place air blocks where the original recipe blocks were: + sSetBlockVector AirBlocks; + AirBlocks.emplace_back(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_AIR, 0); // Head + AirBlocks.emplace_back(a_BlockX, a_BlockY - 1, a_BlockZ, E_BLOCK_AIR, 0); // Torso + AirBlocks.emplace_back(a_BlockX, a_BlockY - 2, a_BlockZ, E_BLOCK_AIR, 0); // Legs + AirBlocks.emplace_back(a_BlockX + ArmOffsets[i].x, a_BlockY - 1, a_BlockZ + ArmOffsets[i].z, E_BLOCK_AIR, 0); // Arm + AirBlocks.emplace_back(a_BlockX - ArmOffsets[i].x, a_BlockY - 1, a_BlockZ - ArmOffsets[i].z, E_BLOCK_AIR, 0); // Arm + if (!a_Player.PlaceBlocks(AirBlocks)) + { + return false; + } + + // Spawn the golem: + a_World.SpawnMob(static_cast(a_BlockX) + 0.5, a_BlockY - 2, static_cast(a_BlockZ) + 0.5, mtIronGolem); + return true; + } // for i - ArmOffsets[] + + // Neither arm offset matched, this thing is not a complete golem + return false; + } +}; + + + + diff --git a/src/Items/ItemSign.h b/src/Items/ItemSign.h index 0fa0fa0be..dabbdbba1 100644 --- a/src/Items/ItemSign.h +++ b/src/Items/ItemSign.h @@ -13,13 +13,33 @@ class cItemSignHandler : public cItemHandler { + typedef cItemHandler super; public: cItemSignHandler(int a_ItemType) : - cItemHandler(a_ItemType) + super(a_ItemType) { } + virtual bool OnPlayerPlace( + cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, + int a_CursorX, int a_CursorY, int a_CursorZ + ) + { + // If the regular placement doesn't work, do no further processing: + if (!super::OnPlayerPlace(a_World, a_Player, a_EquippedItem, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ)) + { + return false; + } + + // After successfully placing the sign, open the sign editor for the player: + AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); + a_Player.GetClientHandle()->SendEditSign(a_BlockX, a_BlockY, a_BlockZ); + return true; + } + + virtual bool IsPlaceable(void) override { return true; diff --git a/src/Items/ItemSlab.h b/src/Items/ItemSlab.h new file mode 100644 index 000000000..1b68b9d0c --- /dev/null +++ b/src/Items/ItemSlab.h @@ -0,0 +1,93 @@ + +// ItemSlab.h + +// Declares the cItemSlabHandler responsible for handling slabs, when in their item form. + + + + + +#pragma once + +#include "ItemHandler.h" +#include "../Blocks/BlockSlab.h" + + + + + +class cItemSlabHandler: + public cItemHandler +{ + typedef cItemHandler super; + +public: + + /** Creates a new handler for the specified slab item type. + Sets the handler to use the specified doubleslab block type for combining self into doubleslabs. */ + cItemSlabHandler(int a_ItemType, BLOCKTYPE a_DoubleSlabBlockType): + super(a_ItemType), + m_DoubleSlabBlockType(a_DoubleSlabBlockType) + { + } + + + // cItemHandler overrides: + virtual bool OnPlayerPlace( + cWorld & a_World, cPlayer & a_Player, const cItem & a_EquippedItem, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, + int a_CursorX, int a_CursorY, int a_CursorZ + ) override + { + // Special slab handling - placing a slab onto another slab produces a dblslab instead: + BLOCKTYPE ClickedBlockType; + NIBBLETYPE ClickedBlockMeta; + a_World.GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, ClickedBlockType, ClickedBlockMeta); + if ( + (ClickedBlockType == m_ItemType) && // Placing the same slab material + (ClickedBlockMeta == a_EquippedItem.m_ItemDamage) // Placing the same slab sub-kind (and existing slab is single) + ) + { + // If clicking the top side of a bottom-half slab, combine into a doubleslab: + if ( + (a_BlockFace == BLOCK_FACE_TOP) && + ((ClickedBlockMeta & 0x08) == 0) + ) + { + return a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, m_DoubleSlabBlockType, ClickedBlockMeta & 0x07); + } + + // If clicking the bottom side of a top-half slab, combine into a doubleslab: + if ( + (a_BlockFace == BLOCK_FACE_BOTTOM) && + ((ClickedBlockMeta & 0x08) != 0) + ) + { + return a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, m_DoubleSlabBlockType, ClickedBlockMeta & 0x07); + } + } + + // The slabs didn't combine, use the default handler to place the slab: + bool res = super::OnPlayerPlace(a_World, a_Player, a_EquippedItem, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ); + + /* + The client has a bug when a slab replaces snow and there's a slab above it. + The client then combines the slab above, rather than replacing the snow. + We send the block above the currently placed block back to the client to fix the bug. + Ref.: http://forum.mc-server.org/showthread.php?tid=434&pid=17388#pid17388 + */ + if ((a_BlockFace == BLOCK_FACE_TOP) && (a_BlockY < cChunkDef::Height - 1)) + { + a_Player.SendBlocksAround(a_BlockX, a_BlockY + 1, a_BlockZ, 1); + } + return res; + } + +protected: + /** The block type to use when the slab combines into a doubleslab block. */ + BLOCKTYPE m_DoubleSlabBlockType; +}; + + + + -- cgit v1.2.3