
// 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;
};




