Exemple #1
0
void cBlockArea::MirrorYZ(void)
{
	if (!HasBlockTypes())
	{
		LOGWARNING("cBlockArea: Cannot mirror meta without blocktypes!");
		return;
	}

	if (!HasBlockMetas())
	{
		// There are no blockmetas to mirror, just use the NoMeta function
		MirrorYZNoMeta();
		return;
	}

	// We are guaranteed that both blocktypes and blockmetas exist; mirror both at the same time:
	int HalfX = m_SizeX / 2;
	int MaxX = m_SizeX - 1;
	for (int y = 0; y < m_SizeY; y++)
	{
		for (int z = 0; z < m_SizeZ; z++)
		{
			for (int x = 0; x < HalfX; x++)
			{
				int Idx1 = MakeIndex(x, y, z);
				int Idx2 = MakeIndex(MaxX - x, y, z);
				std::swap(m_BlockTypes[Idx1], m_BlockTypes[Idx2]);
				NIBBLETYPE Meta1 = BlockHandler(m_BlockTypes[Idx2])->MetaMirrorYZ(m_BlockMetas[Idx1]);
				NIBBLETYPE Meta2 = BlockHandler(m_BlockTypes[Idx1])->MetaMirrorYZ(m_BlockMetas[Idx2]);
				m_BlockMetas[Idx1] = Meta2;
				m_BlockMetas[Idx2] = Meta1;
			}  // for x
		}  // for z
	}  // for y
}
Exemple #2
0
void cBlockPistonHandler::ExtendPiston(int pistx, int pisty, int pistz)
{
	BLOCKTYPE pistonBlock;
	NIBBLETYPE pistonMeta;
	m_World->GetBlockTypeMeta(pistx, pisty, pistz, pistonBlock, pistonMeta);
	
	if (IsExtended(pistonMeta))
	{
		// Already extended, bail out
		return;
	}

	int dist = FirstPassthroughBlock(pistx, pisty, pistz, pistonMeta);
	if (dist < 0)
	{
		// FirstPassthroughBlock says piston can't push anything, bail out
		return;
	}

	m_World->BroadcastBlockAction(pistx, pisty, pistz, 0, pistonMeta, pistonBlock);
	m_World->BroadcastSoundEffect("tile.piston.out", pistx * 8, pisty * 8, pistz * 8, 0.5f, 0.7f);	
	
	// Drop the breakable block in the line, if appropriate:
	AddPistonDir(pistx, pisty, pistz, pistonMeta, dist + 1);  // "pist" now at the breakable / empty block
	BLOCKTYPE currBlock;
	NIBBLETYPE currMeta;
	m_World->GetBlockTypeMeta(pistx, pisty, pistz, currBlock, currMeta);
	if (currBlock != E_BLOCK_AIR)
	{
		cBlockHandler * Handler = BlockHandler(currBlock);
		if (Handler->DoesDropOnUnsuitable())
		{
			cChunkInterface ChunkInterface(m_World->GetChunkMap());
			cBlockInServerPluginInterface PluginInterface(*m_World);
			Handler->DropBlock(ChunkInterface, *m_World, PluginInterface, NULL, pistx, pisty, pistz);
		}
	}
	
	// Push blocks, from the furthest to the nearest:
	int oldx = pistx, oldy = pisty, oldz = pistz;
	NIBBLETYPE currBlockMeta;
	for (int i = dist + 1; i > 1; i--)
	{
		AddPistonDir(pistx, pisty, pistz, pistonMeta, -1);
		m_World->GetBlockTypeMeta(pistx, pisty, pistz, currBlock, currBlockMeta);
		m_World->QueueSetBlock( oldx, oldy, oldz, currBlock, currBlockMeta, PISTON_TICK_DELAY);
		oldx = pistx;
		oldy = pisty;
		oldz = pistz;
	}

	int extx = pistx;
	int exty = pisty;
	int extz = pistz;
	AddPistonDir(pistx, pisty, pistz, pistonMeta, -1);
	// "pist" now at piston body, "ext" at future extension
	
	m_World->SetBlock(pistx, pisty, pistz, pistonBlock, pistonMeta | 0x8);
	m_World->QueueSetBlock(extx, exty, extz, E_BLOCK_PISTON_EXTENSION, pistonMeta | (IsSticky(pistonBlock) ? 8 : 0), PISTON_TICK_DELAY);
}
Exemple #3
0
void cBlockPistonHandler::PushBlocks(
	const Vector3iSet & a_BlocksToPush,
	cWorld * a_World, const Vector3i & a_PushDir
)
{
	// Sort blocks to move the blocks first, which are farest away from the piston
	// This prevents the overwriting of existing blocks
	std::vector<Vector3i> sortedBlocks(a_BlocksToPush.begin(), a_BlocksToPush.end());
	std::sort(sortedBlocks.begin(), sortedBlocks.end(), [a_PushDir](const Vector3i & a, const Vector3i & b)
	{
		return a.Dot(a_PushDir) > b.Dot(a_PushDir);
	});

	// Move every block
	BLOCKTYPE moveBlock;
	NIBBLETYPE moveMeta;
	for (auto & moveBlockPos : sortedBlocks)
	{
		a_World->GetBlockTypeMeta(moveBlockPos.x, moveBlockPos.y, moveBlockPos.z, moveBlock, moveMeta);
		a_World->SetBlock(moveBlockPos.x, moveBlockPos.y, moveBlockPos.z, E_BLOCK_AIR, 0);

		moveBlockPos += a_PushDir;
		if (cBlockInfo::IsPistonBreakable(moveBlock))
		{
			// Block is breakable, drop it
			cBlockHandler * Handler = BlockHandler(moveBlock);
			if (Handler->DoesDropOnUnsuitable())
			{
				cChunkInterface ChunkInterface(a_World->GetChunkMap());
				cBlockInServerPluginInterface PluginInterface(*a_World);
				Handler->DropBlock(ChunkInterface, *a_World, PluginInterface, nullptr,
					moveBlockPos.x, moveBlockPos.y, moveBlockPos.z
				);
			}
		} else
		{
			// Not breakable, just move it
			a_World->SetBlock(moveBlockPos.x, moveBlockPos.y, moveBlockPos.z, moveBlock, moveMeta);
		}
	}
}
Exemple #4
0
void cBlockArea::RotateCW(void)
{
	if (!HasBlockTypes())
	{
		LOGWARNING("cBlockArea: Cannot rotate blockmeta without blocktypes!");
		return;
	}

	if (!HasBlockMetas())
	{
		// There are no blockmetas to rotate, just use the NoMeta function
		RotateCWNoMeta();
		return;
	}
	
	// We are guaranteed that both blocktypes and blockmetas exist; rotate both at the same time:
	BLOCKTYPE * NewTypes = new BLOCKTYPE[m_SizeX * m_SizeY * m_SizeZ];
	NIBBLETYPE * NewMetas = new NIBBLETYPE[m_SizeX * m_SizeY * m_SizeZ];
	for (int x = 0; x < m_SizeX; x++)
	{
		int NewZ = x;
		for (int z = 0; z < m_SizeZ; z++)
		{
			int NewX = m_SizeZ - z - 1;
			for (int y = 0; y < m_SizeY; y++)
			{
				int NewIdx = NewX + NewZ * m_SizeX + y * m_SizeX * m_SizeZ;
				int OldIdx = MakeIndex(x, y, z);
				NewTypes[NewIdx] = m_BlockTypes[OldIdx];
				NewMetas[NewIdx] = BlockHandler(m_BlockTypes[OldIdx])->MetaRotateCW(m_BlockMetas[OldIdx]);
			}  // for y
		}  // for z
	}  // for x
	std::swap(m_BlockTypes, NewTypes);
	std::swap(m_BlockMetas, NewMetas);
	delete[] NewTypes;
	delete[] NewMetas;

	std::swap(m_SizeX, m_SizeZ);
}
Exemple #5
0
bool cItemHandler::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
)
{
	ASSERT(m_ItemType < 256);  // Items with IDs above 255 should all be handled by specific handlers
	
	if (m_ItemType >= 256)
	{
		LOGERROR("%s: Item %d is not eligible for direct block placement!", __FUNCTION__, m_ItemType);
		return false;
	}
	
	cBlockHandler * BlockH = BlockHandler((BLOCKTYPE)m_ItemType);
	cChunkInterface ChunkInterface(a_World->GetChunkMap());
	return BlockH->GetPlacementBlockTypeMeta(
		ChunkInterface, a_Player,
		a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, 
		a_CursorX, a_CursorY, a_CursorZ,
		a_BlockType, a_BlockMeta
	);
}
Exemple #6
0
int
WaitForSomething(int *pClientsReady)
{
    struct timeval *wt,
               waittime;
    fd_set      clientsReadable;
    fd_set      clientsWriteable;
    long        curclient;
    int         selecterr;
    long        current_time = 0;
    long        timeout;
    int         nready,
                i;

    while (1) {
        /* handle the work Q */
        if (workQueue)
            ProcessWorkQueue();

        if (XFD_ANYSET(&ClientsWithInput)) {
            XFD_COPYSET(&ClientsWithInput, &clientsReadable);
            break;
        }
        /*
         * deal with KeepAlive timeouts.  if this seems to costly, SIGALRM
         * could be used, but its more dangerous since some it could catch us
         * at an inopportune moment (like inside un-reentrant malloc()).
         */
        current_time = GetTimeInMillis();
        timeout = current_time - LastReapTime;
        if (timeout > ReapClientTime) {
            ReapAnyOldClients();
            LastReapTime = current_time;
            timeout = ReapClientTime;
        }
        timeout = ReapClientTime - timeout;
        waittime.tv_sec = timeout / MILLI_PER_SECOND;
        waittime.tv_usec = (timeout % MILLI_PER_SECOND) *
                           (1000000 / MILLI_PER_SECOND);
        wt = &waittime;

        XFD_COPYSET(&AllSockets, &LastSelectMask);

        BlockHandler(&wt, (pointer) &LastSelectMask);
        if (NewOutputPending)
            FlushAllOutput();

        if (AnyClientsWriteBlocked) {
            XFD_COPYSET(&ClientsWriteBlocked, &clientsWriteable);
            i = Select(MAXSOCKS, &LastSelectMask, &clientsWriteable, NULL, wt);
        } else {
            i = Select(MAXSOCKS, &LastSelectMask, NULL, NULL, wt);
        }
        selecterr = errno;

        WakeupHandler(i, (unsigned long *) &LastSelectMask);
        if (i <= 0) {		/* error or timeout */
            FD_ZERO(&clientsWriteable);
            if (i < 0) {
                if (selecterr == EBADF) {	/* somebody disconnected */
                    CheckConnections();
                } else if (selecterr != EINTR) {
                    ErrorF("WaitForSomething: select(): errno %d\n", selecterr);
                } else {
                    /*
                     * must have been broken by a signal.  go deal with any
                     * exception flags
                     */
                    return 0;
                }
            } else {		/* must have timed out */
                ReapAnyOldClients();
                LastReapTime = GetTimeInMillis();
            }
        } else {
            if (AnyClientsWriteBlocked && XFD_ANYSET(&clientsWriteable)) {
                NewOutputPending = TRUE;
                XFD_ORSET(&OutputPending, &clientsWriteable, &OutputPending);
                XFD_UNSET(&ClientsWriteBlocked, &clientsWriteable);
                if (!XFD_ANYSET(&ClientsWriteBlocked))
                    AnyClientsWriteBlocked = FALSE;
            }
            XFD_ANDSET(&clientsReadable, &LastSelectMask, &AllClients);
            if (LastSelectMask.fds_bits[0] & WellKnownConnections.fds_bits[0])
                MakeNewConnections();
            if (XFD_ANYSET(&clientsReadable))
                break;

        }
    }
    nready = 0;

    if (XFD_ANYSET(&clientsReadable)) {
        ClientPtr   client;
        int         conn;

        if (current_time)	/* may not have been set */
            current_time = GetTimeInMillis();
        for (i = 0; i < howmany(XFD_SETSIZE, NFDBITS); i++) {
            while (clientsReadable.fds_bits[i]) {
                curclient = xfd_ffs(clientsReadable.fds_bits[i]) - 1;
                conn = ConnectionTranslation[curclient + (i * (sizeof(fd_mask) * 8))];
                clientsReadable.fds_bits[i] &= ~(((fd_mask)1L) << curclient);
                client = clients[conn];
                if (!client)
                    continue;
                pClientsReady[nready++] = conn;
                client->last_request_time = current_time;
                client->clientGone = CLIENT_ALIVE;

                if (nready >= MaxClients) {
                    /* pClientsReady buffer has no more room, get the
                       rest on the next time through select() loop */
                    return nready;
                }
            }
        }
    }
    return nready;
}
void cFloodyFluidSimulator::SpreadToNeighbor(cChunk * a_NearChunk, int a_RelX, int a_RelY, int a_RelZ, NIBBLETYPE a_NewMeta)
{
	ASSERT(a_NewMeta <= 8);  // Invalid meta values
	ASSERT(a_NewMeta > 0);  // Source blocks aren't spread

	a_NearChunk = a_NearChunk->GetRelNeighborChunkAdjustCoords(a_RelX, a_RelZ);
	if ((a_NearChunk == NULL) || (!a_NearChunk->IsValid()))
	{
		// Chunk not available
		return;
	}

	const int BlockX = a_NearChunk->GetPosX() * cChunkDef::Width + a_RelX;
	const int BlockZ = a_NearChunk->GetPosZ() * cChunkDef::Width + a_RelZ;
	
	BLOCKTYPE BlockType;
	NIBBLETYPE BlockMeta;
	a_NearChunk->GetBlockTypeMeta(a_RelX, a_RelY, a_RelZ, BlockType, BlockMeta);
	
	if (IsAllowedBlock(BlockType))
	{
		if ((BlockMeta == a_NewMeta) || IsHigherMeta(BlockMeta, a_NewMeta))
		{
			// Don't spread there, there's already a higher or same level there
			return;
		}
	}

	// Check water - lava interaction:
	if (m_FluidBlock == E_BLOCK_LAVA)
	{
		if (IsBlockWater(BlockType))
		{
			// Lava flowing into water, change to stone / cobblestone based on direction:
			BLOCKTYPE NewBlock = (a_NewMeta == 8) ? E_BLOCK_STONE : E_BLOCK_COBBLESTONE;
			FLOG("  Lava flowing into water, turning water at rel {%d, %d, %d} into stone", 
				a_RelX, a_RelY, a_RelZ,
				ItemTypeToString(NewBlock).c_str()
			);
			a_NearChunk->SetBlock(a_RelX, a_RelY, a_RelZ, NewBlock, 0);

			a_NearChunk->BroadcastSoundEffect("random.fizz", BlockX * 8, a_RelY * 8, BlockZ * 8, 0.5f, 1.5f);
			return;
		}
	}
	else if (m_FluidBlock == E_BLOCK_WATER)
	{
		if (IsBlockLava(BlockType))
		{
			// Water flowing into lava, change to cobblestone / obsidian based on dest block:
			BLOCKTYPE NewBlock = (BlockMeta == 0) ? E_BLOCK_OBSIDIAN : E_BLOCK_COBBLESTONE;
			FLOG("  Water flowing into lava, turning lava at rel {%d, %d, %d} into %s", 
				a_RelX, a_RelY, a_RelZ, ItemTypeToString(NewBlock).c_str()
			);
			a_NearChunk->SetBlock(a_RelX, a_RelY, a_RelZ, NewBlock, 0);

			a_NearChunk->BroadcastSoundEffect("random.fizz", BlockX * 8, a_RelY * 8, BlockZ * 8, 0.5f, 1.5f);
			return;
		}
	}
	else
	{
		ASSERT(!"Unknown fluid!");
	}
	
	if (!IsPassableForFluid(BlockType))
	{
		// Can't spread there
		return;
	}
	
	// Wash away the block there, if possible:
	if (CanWashAway(BlockType))
	{
		cBlockHandler * Handler = BlockHandler(BlockType);
		if (Handler->DoesDropOnUnsuitable())
		{
			cChunkInterface ChunkInterface(m_World.GetChunkMap());
			cBlockInServerPluginInterface PluginInterface(m_World);
			Handler->DropBlock(
				ChunkInterface,
				m_World,
				PluginInterface,
				NULL, 
				BlockX, 
				a_RelY,
				BlockZ
			);
		}
	}  // if (CanWashAway)

	// Spread:
	FLOG("  Spreading to {%d, %d, %d} with meta %d", BlockX, a_RelY, BlockZ, a_NewMeta);
	a_NearChunk->SetBlock(a_RelX, a_RelY, a_RelZ, m_FluidBlock, a_NewMeta);
	m_World.GetSimulatorManager()->WakeUp(BlockX, a_RelY, BlockZ, a_NearChunk);

	HardenBlock(a_NearChunk, a_RelX, a_RelY, a_RelZ, m_FluidBlock, a_NewMeta);
}
Exemple #8
0
void cBlockPistonHandler::ExtendPiston(int a_BlockX, int a_BlockY, int a_BlockZ, cWorld * a_World)
{
	BLOCKTYPE pistonBlock;
	NIBBLETYPE pistonMeta;
	a_World->GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, pistonBlock, pistonMeta);

	if (IsExtended(pistonMeta))
	{
		// Already extended, bail out
		return;
	}

	int dist = FirstPassthroughBlock(a_BlockX, a_BlockY, a_BlockZ, pistonMeta, a_World);
	if (dist < 0)
	{
		// FirstPassthroughBlock says piston can't push anything, bail out
		return;
	}

	a_World->BroadcastBlockAction(a_BlockX, a_BlockY, a_BlockZ, 0, pistonMeta, pistonBlock);
	a_World->BroadcastSoundEffect("tile.piston.out", (double)a_BlockX, (double)a_BlockY, (double)a_BlockZ, 0.5f, 0.7f);

	// Drop the breakable block in the line, if appropriate:
	AddPistonDir(a_BlockX, a_BlockY, a_BlockZ, pistonMeta, dist + 1);  // "a_Block" now at the breakable / empty block
	BLOCKTYPE currBlock;
	NIBBLETYPE currMeta;
	a_World->GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, currBlock, currMeta);
	if (currBlock != E_BLOCK_AIR)
	{
		cBlockHandler * Handler = BlockHandler(currBlock);
		if (Handler->DoesDropOnUnsuitable())
		{
			cChunkInterface ChunkInterface(a_World->GetChunkMap());
			cBlockInServerPluginInterface PluginInterface(*a_World);
			Handler->DropBlock(ChunkInterface, *a_World, PluginInterface, NULL, a_BlockX, a_BlockY, a_BlockZ);
		}
	}

	// Push blocks, from the furthest to the nearest:
	int oldx = a_BlockX, oldy = a_BlockY, oldz = a_BlockZ;
	NIBBLETYPE currBlockMeta;
	std::vector<Vector3i> ScheduledBlocks;
	ScheduledBlocks.reserve(PISTON_MAX_PUSH_DISTANCE);

	for (int i = dist + 1; i > 1; i--)
	{
		AddPistonDir(a_BlockX, a_BlockY, a_BlockZ, pistonMeta, -1);
		a_World->GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, currBlock, currBlockMeta);
		a_World->SetBlock(oldx, oldy, oldz, currBlock, currBlockMeta, false);
		ScheduledBlocks.push_back(Vector3i(oldx, oldy, oldz));
		oldx = a_BlockX;
		oldy = a_BlockY;
		oldz = a_BlockZ;
	}

	int extx = a_BlockX;
	int exty = a_BlockY;
	int extz = a_BlockZ;
	ScheduledBlocks.push_back(Vector3i(extx, exty, extz));
	AddPistonDir(a_BlockX, a_BlockY, a_BlockZ, pistonMeta, -1);
	// "a_Block" now at piston body, "ext" at future extension

	a_World->SetBlock(a_BlockX, a_BlockY, a_BlockZ, pistonBlock, pistonMeta | 0x8);
	a_World->SetBlock(extx, exty, extz, E_BLOCK_PISTON_EXTENSION, pistonMeta | (IsSticky(pistonBlock) ? 8 : 0), false);
	a_World->ScheduleTask(PISTON_TICK_DELAY, new cWorld::cTaskSendBlockToAllPlayers(ScheduledBlocks));
}
Exemple #9
0
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;
}
Exemple #10
0
bool cMap::UpdatePixel(unsigned int a_X, unsigned int a_Z)
{
	int BlockX = m_CenterX + static_cast<int>((a_X - m_Width  / 2) * GetPixelWidth());
	int BlockZ = m_CenterZ + static_cast<int>((a_Z - m_Height / 2) * GetPixelWidth());

	int ChunkX, ChunkZ;
	cChunkDef::BlockToChunk(BlockX, BlockZ, ChunkX, ChunkZ);

	int RelX = BlockX - (ChunkX * cChunkDef::Width);
	int RelZ = BlockZ - (ChunkZ * cChunkDef::Width);

	ASSERT(m_World != nullptr);

	ColorID PixelData;
	m_World->DoWithChunk(ChunkX, ChunkZ, [&](cChunk & a_Chunk)
		{
			if (!a_Chunk.IsValid())
			{
				return false;
			}

			if (GetDimension() == dimNether)
			{
				// TODO 2014-02-22 xdot: Nether maps

				return false;
			}

			static const std::array<unsigned char, 4> BrightnessID = { { 3, 0, 1, 2 } };  // Darkest to lightest
			BLOCKTYPE TargetBlock;
			NIBBLETYPE TargetMeta;

			auto Height = a_Chunk.GetHeight(RelX, RelZ);
			auto ChunkHeight = cChunkDef::Height;
			a_Chunk.GetBlockTypeMeta(RelX, Height, RelZ, TargetBlock, TargetMeta);
			auto ColourID = BlockHandler(TargetBlock)->GetMapBaseColourID(TargetMeta);

			if (IsBlockWater(TargetBlock))
			{
				ChunkHeight /= 4;
				while (((--Height) != -1) && IsBlockWater(a_Chunk.GetBlock(RelX, Height, RelZ)))
				{
					continue;
				}
			}
			else if (ColourID == 0)
			{
				while (((--Height) != -1) && ((ColourID = BlockHandler(a_Chunk.GetBlock(RelX, Height, RelZ))->GetMapBaseColourID(a_Chunk.GetMeta(RelX, Height, RelZ))) == 0))
				{
					continue;
				}
			}

			// Multiply base color ID by 4 and add brightness ID
			const int BrightnessIDSize = static_cast<int>(BrightnessID.size());
			PixelData = ColourID * 4 + BrightnessID[static_cast<size_t>(Clamp<int>((BrightnessIDSize * Height) / ChunkHeight, 0, BrightnessIDSize - 1))];
			return false;
		}
	);

	SetPixel(a_X, a_Z, PixelData);

	return true;
}
void cBlockPistonHandler::ExtendPiston(int a_BlockX, int a_BlockY, int a_BlockZ, cWorld * a_World)
{
	BLOCKTYPE pistonBlock;
	NIBBLETYPE pistonMeta;
	a_World->GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, pistonBlock, pistonMeta);

	if (IsExtended(pistonMeta))
	{
		// Already extended, bail out
		return;
	}

	int dist = FirstPassthroughBlock(a_BlockX, a_BlockY, a_BlockZ, pistonMeta, a_World);
	if (dist < 0)
	{
		// FirstPassthroughBlock says piston can't push anything, bail out
		return;
	}

	a_World->BroadcastBlockAction(a_BlockX, a_BlockY, a_BlockZ, 0, pistonMeta, pistonBlock);
	a_World->BroadcastSoundEffect("tile.piston.out", static_cast<double>(a_BlockX), static_cast<double>(a_BlockY), static_cast<double>(a_BlockZ), 0.5f, 0.7f);

	// Drop the breakable block in the line, if appropriate:
	AddPistonDir(a_BlockX, a_BlockY, a_BlockZ, pistonMeta, dist + 1);  // "a_Block" now at the breakable / empty block
	BLOCKTYPE currBlock;
	NIBBLETYPE currMeta;
	a_World->GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, currBlock, currMeta);
	if (currBlock != E_BLOCK_AIR)
	{
		cBlockHandler * Handler = BlockHandler(currBlock);
		if (Handler->DoesDropOnUnsuitable())
		{
			cChunkInterface ChunkInterface(a_World->GetChunkMap());
			cBlockInServerPluginInterface PluginInterface(*a_World);
			Handler->DropBlock(ChunkInterface, *a_World, PluginInterface, nullptr, a_BlockX, a_BlockY, a_BlockZ);
		}
	}

	// Push blocks, from the furthest to the nearest:
	int oldx = a_BlockX, oldy = a_BlockY, oldz = a_BlockZ;
	NIBBLETYPE currBlockMeta;

	for (int i = dist + 1; i > 1; i--)
	{
		AddPistonDir(a_BlockX, a_BlockY, a_BlockZ, pistonMeta, -1);
		a_World->GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, currBlock, currBlockMeta);
		a_World->SetBlock(oldx, oldy, oldz, currBlock, currBlockMeta);
		oldx = a_BlockX;
		oldy = a_BlockY;
		oldz = a_BlockZ;
	}

	int extx = a_BlockX;
	int exty = a_BlockY;
	int extz = a_BlockZ;
	AddPistonDir(a_BlockX, a_BlockY, a_BlockZ, pistonMeta, -1);
	// "a_Block" now at piston body, "ext" at future extension

	a_World->SetBlock(a_BlockX, a_BlockY, a_BlockZ, pistonBlock, pistonMeta | 0x8);
	a_World->SetBlock(extx, exty, extz, E_BLOCK_PISTON_EXTENSION, pistonMeta | (IsSticky(pistonBlock) ? 8 : 0));
}