#include "stdafx.h"
#include "PlayerChunkMap.h"
#include "PlayerConnection.h"
#include "ServerLevel.h"
#include "ServerChunkCache.h"
#include "ServerPlayer.h"
#include "MinecraftServer.h"
#include "..\Minecraft.World\net.minecraft.network.packet.h"
#include "..\Minecraft.World\net.minecraft.world.level.h"
#include "..\Minecraft.World\net.minecraft.world.level.tile.h"
#include "..\Minecraft.World\ArrayWithLength.h"
#include "..\Minecraft.World\System.h"
#include "PlayerList.h"

PlayerChunkMap::PlayerChunk::PlayerChunk(int x, int z, PlayerChunkMap *pcm) : pos(x,z)
{
	// 4J - added initialisers
	changes = 0;
	changedTiles = shortArray(MAX_CHANGES_BEFORE_RESEND);
	xChangeMin = xChangeMax = 0;
	yChangeMin = yChangeMax = 0;
	zChangeMin = zChangeMax = 0;
	parent = pcm;	// 4J added
	ticksToNextRegionUpdate = 0;	// 4J added
	prioritised = false;	// 4J added

    parent->getLevel()->cache->create(x, z);
	// 4J - added make sure our lights are up to date as soon as we make it. This is of particular concern for local clients, who have their data
	// shared as soon as the chunkvisibilitypacket is sent, and so could potentially create render data for this chunk before it has been properly lit.
	while( parent->getLevel()->updateLights() )
		;
}

PlayerChunkMap::PlayerChunk::~PlayerChunk()
{
	delete changedTiles.data;
}

// 4J added - construct an an array of flags that indicate which entities are still waiting to have network packets sent out to say that they have been removed
// If there aren't any entities to be flagged, this function does nothing. If there *are* entities to be added, uses the removedFound as an input to
// determine if the flag array has already been initialised at all - if it has been, then just adds flags to it; if it hasn't, then memsets the output
// flag array and adds to it for this ServerPlayer.
void PlayerChunkMap::flagEntitiesToBeRemoved(unsigned int *flags, bool *flagToBeRemoved)
{
	for(AUTO_VAR(it,players.begin()); it != players.end(); it++)
	{
		shared_ptr<ServerPlayer> serverPlayer = *it;
		serverPlayer->flagEntitiesToBeRemoved(flags, flagToBeRemoved);
	}
}

void PlayerChunkMap::PlayerChunk::add(shared_ptr<ServerPlayer> player, bool sendPacket /*= true*/)
{
	//app.DebugPrintf("--- Adding player to chunk x=%d\tz=%d\n",x, z);
    if (find(players.begin(),players.end(),player) != players.end())
	{
		// 4J-PB - At the start of the game, lots of chunks are added, and we can then move into an area that is outside the diameter of our starting area, 
		// but is inside the area loaded at the start.
		app.DebugPrintf("--- Adding player to chunk x=%d\t z=%d, but they are already in there!\n",pos.x, pos.z);
		return;

		//assert(false);
// 4J - was			throw new IllegalStateException("Failed to add player. " + player + " already is in chunk " + x + ", " + z);
	}

    player->seenChunks.insert(pos);

	// 4J Added the sendPacket check. See PlayerChunkMap::add for the usage
	if( sendPacket ) player->connection->send( shared_ptr<ChunkVisibilityPacket>( new ChunkVisibilityPacket(pos.x, pos.z, true) ) );

	players.push_back(player);
	
	player->chunksToSend.push_back(pos);

#ifdef _LARGE_WORLDS
	parent->getLevel()->cache->dontDrop(pos.x, pos.z); // 4J Added;
#endif
}

void PlayerChunkMap::PlayerChunk::remove(shared_ptr<ServerPlayer> player)
{
	PlayerChunkMap::PlayerChunk *toDelete = NULL;

	//app.DebugPrintf("--- PlayerChunkMap::PlayerChunk::remove x=%d\tz=%d\n",x,z);
	AUTO_VAR(it, find(players.begin(),players.end(),player));
	if ( it == players.end())
	{
		app.DebugPrintf("--- INFO - Removing player from chunk x=%d\t z=%d, but they are not in that chunk!\n",pos.x, pos.z);

		return;
	}

    players.erase(it);
    if (players.size() == 0)
	{
        __int64 id = (pos.x + 0x7fffffffLL) | ((pos.z + 0x7fffffffLL) << 32);
		AUTO_VAR(it, parent->chunks.find(id));
		if( it != parent->chunks.end() )
		{
			toDelete = it->second;	// Don't delete until the end of the function, as this might be this instance
			parent->chunks.erase(it);
		}
        if (changes > 0)
		{
			AUTO_VAR(it, find(parent->changedChunks.begin(),parent->changedChunks.end(),this));
			parent->changedChunks.erase(it);
        }
        parent->getLevel()->cache->drop(pos.x, pos.z);
    }

    player->chunksToSend.remove(pos);
	// 4J - I don't think there's any point sending these anymore, as we don't need to unload chunks with fixed sized maps
	// 4J - We do need to send these to unload entities in chunks when players are dead. If we do not and the entity is removed
	// while they are dead, that entity will remain in the clients world
    if (player->connection != NULL && player->seenChunks.find(pos) != player->seenChunks.end())
	{
		INetworkPlayer *thisNetPlayer = player->connection->getNetworkPlayer();
		bool noOtherPlayersFound = true;
		
		if( thisNetPlayer != NULL )
		{
			for( AUTO_VAR(it, players.begin()); it < players.end(); ++it )
			{
				shared_ptr<ServerPlayer> currPlayer = *it;
				INetworkPlayer *currNetPlayer = currPlayer->connection->getNetworkPlayer();
				if( currNetPlayer != NULL && currNetPlayer->IsSameSystem( thisNetPlayer ) && currPlayer->seenChunks.find(pos) != currPlayer->seenChunks.end() )
				{
					noOtherPlayersFound = false;
					break;
				}
			}
			if(noOtherPlayersFound)
			{
				//wprintf(L"Sending ChunkVisiblity packet false for chunk (%d,%d) to player %ls\n", x, z, player->name.c_str() );
				player->connection->send( shared_ptr<ChunkVisibilityPacket>( new ChunkVisibilityPacket(pos.x, pos.z, false) ) );
			}
		}
		else
		{
			//app.DebugPrintf("PlayerChunkMap::PlayerChunk::remove - QNetPlayer is NULL\n");
		}
    }

	delete toDelete;
}

void PlayerChunkMap::PlayerChunk::tileChanged(int x, int y, int z)
{
    if (changes == 0)
	{
        parent->changedChunks.push_back(this);
        xChangeMin = xChangeMax = x;
        yChangeMin = yChangeMax = y;
        zChangeMin = zChangeMax = z;
    }
    if (xChangeMin > x) xChangeMin = x;
    if (xChangeMax < x) xChangeMax = x;

    if (yChangeMin > y) yChangeMin = y;
    if (yChangeMax < y) yChangeMax = y;

    if (zChangeMin > z) zChangeMin = z;
    if (zChangeMax < z) zChangeMax = z;

    if (changes < MAX_CHANGES_BEFORE_RESEND)
	{
        short id = (short) ((x << 12) | (z << 8) | (y));

        for (int i = 0; i < changes; i++)
		{
            if (changedTiles[i] == id) return;
        }

        changedTiles[changes++] = id;
    }
}

// 4J added - make sure that any tile updates for the chunk at this location get prioritised for sending
void PlayerChunkMap::PlayerChunk::prioritiseTileChanges()
{
	prioritised = true;
}

void PlayerChunkMap::PlayerChunk::broadcast(shared_ptr<Packet> packet)
{
	vector< shared_ptr<ServerPlayer> > sentTo;
    for (unsigned int i = 0; i < players.size(); i++)
	{
        shared_ptr<ServerPlayer> player = players[i];

		// 4J - don't send to a player we've already sent this data to that shares the same machine. TileUpdatePacket,
		// ChunkTilesUpdatePacket and SignUpdatePacket all used to limit themselves to sending once to each machine
		// by only sending to the primary player on each machine. This was causing trouble for split screen
		// as updates were only coming in for the region round this one player. Now these packets can be sent to any
		// player, but we try to restrict the network impact this has by not resending to the one machine
		bool dontSend = false;
		if( sentTo.size() )
		{
			INetworkPlayer *thisPlayer = player->connection->getNetworkPlayer();
			if( thisPlayer == NULL )
			{
				dontSend = true;
			}
			else
			{
				for(unsigned int j = 0; j < sentTo.size(); j++ )	
				{
					shared_ptr<ServerPlayer> player2 = sentTo[j];
					INetworkPlayer *otherPlayer = player2->connection->getNetworkPlayer();
					if( otherPlayer != NULL && thisPlayer->IsSameSystem(otherPlayer) )
					{
						dontSend = true;
					}
				}
			}
		}
		if( dontSend )
		{
			continue;
		}

		// 4J Changed to get the flag index for the player before we send a packet. This flag is updated when we queue
		// for send the first BlockRegionUpdatePacket for this chunk to that player/players system. Therefore there is no need to
		// send tile updates or other updates until that has been sent
		int flagIndex = ServerPlayer::getFlagIndexForChunk(pos, parent->dimension);
        if (player->seenChunks.find(pos) != player->seenChunks.end() && (player->connection->isLocal() || g_NetworkManager.SystemFlagGet(player->connection->getNetworkPlayer(),flagIndex) ))
		{
            player->connection->send(packet);
			sentTo.push_back(player);
        }
    }
	// Now also check round all the players that are involved in this game. We also want to send the packet
	// to them if their system hasn't received it already, but they have received the first BlockRegionUpdatePacket for this
	// chunk

	// Make sure we are only doing this for BlockRegionUpdatePacket, ChunkTilesUpdatePacket and TileUpdatePacket.
	// We'll be potentially sending to players who aren't on the same level as this packet is intended for,
	// and only these 3 packets have so far been updated to be able to encode the level so they are robust
	// enough to cope with this
	if(!( ( packet->getId() == 51 ) || ( packet->getId() == 52 ) || ( packet->getId() == 53 ) ) )
	{
		return;
	}

	for( int i = 0; i < parent->level->getServer()->getPlayers()->players.size(); i++ )
	{
		shared_ptr<ServerPlayer> player = parent->level->getServer()->getPlayers()->players[i];
		// Don't worry about local players, they get all their updates through sharing level with the server anyway
		if ( player->connection == NULL ) continue;
		if( player->connection->isLocal() ) continue;

		// Don't worry about this player if they haven't had this chunk yet (this flag will be the
		// same for all players on the same system)
		int flagIndex = ServerPlayer::getFlagIndexForChunk(pos,parent->dimension);
		if(!g_NetworkManager.SystemFlagGet(player->connection->getNetworkPlayer(),flagIndex)) continue;

		// From here on the same rules as in the loop above - don't send it if we've already sent to the same system
		bool dontSend = false;
		if( sentTo.size() )
		{
			INetworkPlayer *thisPlayer = player->connection->getNetworkPlayer();
			if( thisPlayer == NULL )
			{
				dontSend = true;
			}
			else
			{
				for(unsigned int j = 0; j < sentTo.size(); j++ )	
				{
					shared_ptr<ServerPlayer> player2 = sentTo[j];
					INetworkPlayer *otherPlayer = player2->connection->getNetworkPlayer();
					if( otherPlayer != NULL && thisPlayer->IsSameSystem(otherPlayer) )
					{
						dontSend = true;
					}
				}
			}
		}
		if( !dontSend )
		{
            player->connection->send(packet);
			sentTo.push_back(player);
		}
	}
}

bool PlayerChunkMap::PlayerChunk::broadcastChanges(bool allowRegionUpdate)
{
	bool didRegionUpdate = false;
    ServerLevel *level = parent->getLevel();
	if( ticksToNextRegionUpdate > 0 ) ticksToNextRegionUpdate--;
    if (changes == 0)
	{
		prioritised = false;
		return false;
	}
    if (changes == 1)
	{
        int x = pos.x * 16 + xChangeMin;
        int y = yChangeMin;
        int z = pos.z * 16 + zChangeMin;
        broadcast( shared_ptr<TileUpdatePacket>( new TileUpdatePacket(x, y, z, level) ) );
        if (level->isEntityTile(x, y, z))
		{
            broadcast(level->getTileEntity(x, y, z));
        }
    }
	else if (changes == MAX_CHANGES_BEFORE_RESEND)
	{
		// 4J added, to allow limiting of region update packets created
		if( !prioritised )
		{
			if( !allowRegionUpdate || ( ticksToNextRegionUpdate > 0 ) )
			{
				return false;
			}
		}

        yChangeMin = yChangeMin / 2 * 2;
        yChangeMax = (yChangeMax / 2 + 1) * 2;
        int xp = xChangeMin + pos.x * 16;
        int yp = yChangeMin;
        int zp = zChangeMin + pos.z * 16;
        int xs = xChangeMax - xChangeMin + 1;
        int ys = yChangeMax - yChangeMin + 2;
        int zs = zChangeMax - zChangeMin + 1;

		// Fix for buf #95007 : TCR #001 BAS Game Stability: TU12: Code: Compliance: More than 192 dropped items causes game to freeze or crash.
		// Block region update packets can only encode ys in a range of 1 - 256
		if( ys > 256 ) ys = 256;		

        broadcast( shared_ptr<BlockRegionUpdatePacket>( new BlockRegionUpdatePacket(xp, yp, zp, xs, ys, zs, level) ) );
        vector<shared_ptr<TileEntity> > *tes = level->getTileEntitiesInRegion(xp, yp, zp, xp + xs, yp + ys, zp + zs);
        for (unsigned int i = 0; i < tes->size(); i++)
		{
            broadcast(tes->at(i));
        }
		delete tes;
		ticksToNextRegionUpdate = MIN_TICKS_BETWEEN_REGION_UPDATE;
		didRegionUpdate = true;
    }
	else
	{
		// 4J As we only get here if changes is less than MAX_CHANGES_BEFORE_RESEND (10) we only need to send a byte value in the packet
        broadcast( shared_ptr<ChunkTilesUpdatePacket>( new ChunkTilesUpdatePacket(pos.x, pos.z, changedTiles, (byte)changes, level) ) );
        for (int i = 0; i < changes; i++)
		{
            int x = pos.x * 16 + ((changedTiles[i] >> 12) & 15);
            int y = ((changedTiles[i]) & 255);
            int z = pos.z * 16 + ((changedTiles[i] >> 8) & 15);

            if (level->isEntityTile(x, y, z))
			{
//                    System.out.println("Sending!");
                broadcast(level->getTileEntity(x, y, z));
            }
        }
    }
    changes = 0;
	prioritised = false;
	return didRegionUpdate;
}

void PlayerChunkMap::PlayerChunk::broadcast(shared_ptr<TileEntity> te)
{
    if (te != NULL)
	{
        shared_ptr<Packet> p = te->getUpdatePacket();
        if (p != NULL)
		{
            broadcast(p);
        }
    }
}

PlayerChunkMap::PlayerChunkMap(ServerLevel *level, int dimension, int radius)
{
	assert(radius <= MAX_VIEW_DISTANCE);
	assert(radius >= MIN_VIEW_DISTANCE);
    this->radius = radius;
    this->level = level;
	this->dimension = dimension;
}

PlayerChunkMap::~PlayerChunkMap()
{
	for( AUTO_VAR(it, chunks.begin()); it != chunks.end(); it++ )
	{
		delete it->second;
	}
}

ServerLevel *PlayerChunkMap::getLevel()
{
	return level;
}

void PlayerChunkMap::tick()
{
	// 4J - some changes here so that we only send one region update per tick. The chunks themselves also
	// limit their resend rate to once every MIN_TICKS_BETWEEN_REGION_UPDATE ticks
	bool regionUpdateSent = false;
    for (unsigned int i = 0; i < changedChunks.size();)
	{
        regionUpdateSent |= changedChunks[i]->broadcastChanges(!regionUpdateSent);
		// Changes will be 0 if the chunk actually sent something, in which case we can delete it from this array
		if( changedChunks[i]->changes == 0 )
		{
			changedChunks[i] = changedChunks.back();
			changedChunks.pop_back();
		}
		else
		{
			// Limiting of some kind means we didn't send this chunk so move onto the next
			i++;
		}
    }

	for( unsigned int i = 0; i < players.size(); i++ )
	{
		tickAddRequests(players[i]);
	}

	// 4J Stu - Added 1.1 but not relevant to us as we never no 0 players anyway, and don't think we should be dropping stuff
	//if (players.isEmpty()) {
	//	ServerLevel level = server.getLevel(this.dimension);
	//	Dimension dimension = level.dimension;
	//	if (!dimension.mayRespawn()) {
	//		level.cache.dropAll();
	//	}
	//}
}

bool PlayerChunkMap::hasChunk(int x, int z)
{
    __int64 id = (x + 0x7fffffffLL) | ((z + 0x7fffffffLL) << 32);
	return chunks.find(id) != chunks.end();
}

PlayerChunkMap::PlayerChunk *PlayerChunkMap::getChunk(int x, int z, bool create)
{
    __int64 id = (x + 0x7fffffffLL) | ((z + 0x7fffffffLL) << 32);
	AUTO_VAR(it, chunks.find(id));

	PlayerChunk *chunk = NULL;
	if( it != chunks.end() )
	{
		chunk = it->second;
	}
	else if ( create)
	{
        chunk = new PlayerChunk(x, z, this);
        chunks[id] = chunk;
    }

	return chunk;
}

// 4J - added. If a chunk exists, add a player to it straight away. If it doesn't exist,
// queue a request for it to be created.
void PlayerChunkMap::getChunkAndAddPlayer(int x, int z, shared_ptr<ServerPlayer> player)
{
    __int64 id = (x + 0x7fffffffLL) | ((z + 0x7fffffffLL) << 32);
	AUTO_VAR(it, chunks.find(id));

	if( it != chunks.end() )
	{
		it->second->add(player);
	}
	else
	{
		addRequests.push_back(PlayerChunkAddRequest(x,z,player));
	}
}

// 4J - added. If the chunk and player are in the queue to be added, remove from there. Otherwise
// attempt to remove from main chunk map.
void PlayerChunkMap::getChunkAndRemovePlayer(int x, int z, shared_ptr<ServerPlayer> player)
{
	for( AUTO_VAR(it, addRequests.begin()); it != addRequests.end(); it++ )
	{
		if( ( it->x == x ) &&
			( it->z == z ) &&
			( it->player == player ) )
		{
			addRequests.erase(it);
			return;
		}
	}
    __int64 id = (x + 0x7fffffffLL) | ((z + 0x7fffffffLL) << 32);
	AUTO_VAR(it, chunks.find(id));

	if( it != chunks.end() )
	{
		it->second->remove(player);
	}
}

// 4J - added - actually create & add player to a playerchunk, if there is one queued for this player.
void PlayerChunkMap::tickAddRequests(shared_ptr<ServerPlayer> player)
{
	if( addRequests.size() )
	{
		// Find the nearest chunk request to the player
		int px = (int)player->x;
		int pz = (int)player->z;
		int minDistSq = -1;
		
		AUTO_VAR(itNearest, addRequests.end());
		for( AUTO_VAR(it, addRequests.begin()); it != addRequests.end(); it++ )
		{
			if( it->player == player )
			{
				int xm = ( it->x  * 16 ) + 8;
				int zm = ( it->z  * 16 ) + 8;
				int distSq = (xm - px) * (xm - px) +
							 (zm - pz) * (zm - pz);
				if( ( minDistSq == -1 ) || ( distSq < minDistSq ) )
				{
					minDistSq = distSq;
					itNearest = it;
				}
			}
		}

		// If we found one at all, then do this one
		if( itNearest != addRequests.end() )
		{
			getChunk(itNearest->x, itNearest->z, true)->add(itNearest->player);
			addRequests.erase(itNearest);
			return;
		}
	}
}

void PlayerChunkMap::broadcastTileUpdate(shared_ptr<Packet> packet, int x, int y, int z)
{
    int xc = x >> 4;
    int zc = z >> 4;
    PlayerChunk *chunk = getChunk(xc, zc, false);
    if (chunk != NULL)
	{
        chunk->broadcast(packet);
    }
}

void PlayerChunkMap::tileChanged(int x, int y, int z)
{
    int xc = x >> 4;
    int zc = z >> 4;
    PlayerChunk *chunk = getChunk(xc, zc, false);
    if (chunk != NULL)
	{
        chunk->tileChanged(x & 15, y, z & 15);
    }
}

bool PlayerChunkMap::isTrackingTile(int x, int y, int z)
{
    int xc = x >> 4;
    int zc = z >> 4;
    PlayerChunk *chunk = getChunk(xc, zc, false);
	if( chunk ) return true;
	return false;
}

// 4J added - make sure that any tile updates for the chunk at this location get prioritised for sending
void PlayerChunkMap::prioritiseTileChanges(int x, int y, int z)
{
    int xc = x >> 4;
    int zc = z >> 4;
    PlayerChunk *chunk = getChunk(xc, zc, false);
    if (chunk != NULL)
	{
        chunk->prioritiseTileChanges();
    }
}

void PlayerChunkMap::add(shared_ptr<ServerPlayer> player)
{
	static int direction[4][2] = { { 1, 0 }, { 0, 1 }, { -1, 0 }, {0, -1} };

	int xc = (int) player->x >> 4;
	int zc = (int) player->z >> 4;

    player->lastMoveX = player->x;
    player->lastMoveZ = player->z;

//        for (int x = xc - radius; x <= xc + radius; x++)
//            for (int z = zc - radius; z <= zc + radius; z++) {
//                getChunk(x, z, true).add(player);
//            }

    // CraftBukkit start
    int facing = 0;
    int size = radius;
    int dx = 0;
    int dz = 0;

    // Origin
    getChunk(xc, zc, true)->add(player, false);

	// 4J Added so we send an area packet rather than one visibility packet per chunk
	int minX, maxX, minZ, maxZ;
	minX = maxX = xc;
	minZ = maxZ = zc;

    // All but the last leg
    for (int legSize = 1; legSize <= size * 2; legSize++)
	{
        for (int leg = 0; leg < 2; leg++)
		{
            int *dir = direction[facing++ % 4];

            for (int k = 0; k < legSize; k++)
			{
                dx += dir[0];
                dz += dir[1];

				int targetX, targetZ;
				targetX = xc + dx;
				targetZ = zc + dz;
				if( targetX > maxX ) maxX = targetX;
				if( targetX < minX ) minX = targetX;
				if( targetZ > maxZ ) maxZ = targetZ;
				if( targetZ < minZ ) minZ = targetZ;

                getChunk(targetX, targetZ, true)->add(player, false);
            }
        }
    }

    // Final leg
    facing %= 4;
    for (int k = 0; k < size * 2; k++)
	{
        dx += direction[facing][0];
        dz += direction[facing][1];

		int targetX, targetZ;
		targetX = xc + dx;
		targetZ = zc + dz;
		if( targetX > maxX ) maxX = targetX;
		if( targetX < minX ) minX = targetX;
		if( targetZ > maxZ ) maxZ = targetZ;
		if( targetZ < minZ ) minZ = targetZ;

        getChunk(targetX, targetZ, true)->add(player, false);
    }
    // CraftBukkit end

	player->connection->send( shared_ptr<ChunkVisibilityAreaPacket>( new ChunkVisibilityAreaPacket(minX, maxX, minZ, maxZ) ) );

#ifdef _LARGE_WORLDS
	getLevel()->cache->dontDrop(xc,zc);
#endif

	players.push_back(player);

}

void PlayerChunkMap::remove(shared_ptr<ServerPlayer> player)
{
    int xc = ((int) player->lastMoveX) >> 4;
    int zc = ((int) player->lastMoveZ) >> 4;

    for (int x = xc - radius; x <= xc + radius; x++)
        for (int z = zc - radius; z <= zc + radius; z++)
		{
            PlayerChunk *playerChunk = getChunk(x, z, false);
            if (playerChunk != NULL) playerChunk->remove(player);
        }

	AUTO_VAR(it, find(players.begin(),players.end(),player));
	if( players.size() > 0 && it != players.end() )
		players.erase(find(players.begin(),players.end(),player));

	// 4J - added - also remove any queued requests to be added to playerchunks here
	for( AUTO_VAR(it, addRequests.begin()); it != addRequests.end(); )
	{
		if( it->player == player )
		{
			it = addRequests.erase(it);
		}
		else
		{
			++it;
		}
	}

}

bool PlayerChunkMap::chunkInRange(int x, int z, int xc, int zc)
{
	// If the distance between x and xc
    int xd = x - xc;
    int zd = z - zc;
    if (xd < -radius || xd > radius) return false;
    if (zd < -radius || zd > radius) return false;
    return true;
}

// 4J - have changed this so that we queue requests to add the player to chunks if they
// need to be created, so that we aren't creating potentially 20 chunks per player per tick
void PlayerChunkMap::move(shared_ptr<ServerPlayer> player)
{
    int xc = ((int) player->x) >> 4;
    int zc = ((int) player->z) >> 4;

    double _xd = player->lastMoveX - player->x;
    double _zd = player->lastMoveZ - player->z;
    double dist = _xd * _xd + _zd * _zd;
    if (dist < 8 * 8) return;

    int last_xc = ((int) player->lastMoveX) >> 4;
    int last_zc = ((int) player->lastMoveZ) >> 4;

    int xd = xc - last_xc;
    int zd = zc - last_zc;
    if (xd == 0 && zd == 0) return;

	for (int x = xc - radius; x <= xc + radius; x++)
        for (int z = zc - radius; z <= zc + radius; z++)
		{ 
			if (!chunkInRange(x, z, last_xc, last_zc))
			{
				// 4J - changed from separate getChunk & add so we can wrap these operations up and queue
				getChunkAndAddPlayer(x, z, player);
			}

			if (!chunkInRange(x - xd, z - zd, xc, zc))
			{
				// 4J - changed from separate getChunk & remove so we can wrap these operations up and queue
				getChunkAndRemovePlayer(x - xd, z - zd, player);
			}
        }

    player->lastMoveX = player->x;
    player->lastMoveZ = player->z;
}

int PlayerChunkMap::getMaxRange()
{
	 return radius * 16 - 16;
}

bool PlayerChunkMap::isPlayerIn(shared_ptr<ServerPlayer> player, int xChunk, int zChunk)
{
	PlayerChunk *chunk = getChunk(xChunk, zChunk, false);

	if(chunk == NULL)
	{
		return false;
	}
	else
	{
		AUTO_VAR(it1, find(chunk->players.begin(), chunk->players.end(), player));
		AUTO_VAR(it2, find(player->chunksToSend.begin(), player->chunksToSend.end(), chunk->pos));
		return it1 != chunk->players.end() && it2 == player->chunksToSend.end();
	}

	//return chunk == NULL ? false : chunk->players->contains(player) && !player->chunksToSend->contains(chunk->pos);
}

int PlayerChunkMap::convertChunkRangeToBlock(int radius)
{
	return radius * 16 - 16;
}

// AP added for Vita so the range can be increased once the level starts
void PlayerChunkMap::setRadius(int newRadius)
{
	if( radius != newRadius )
	{
		PlayerList* players = level->getServer()->getPlayerList();
		for( int i = 0;i < players->players.size();i += 1 )
		{
			shared_ptr<ServerPlayer> player = players->players[i];
			if( player->level == level )
			{
				int xc = ((int) player->x) >> 4;
				int zc = ((int) player->z) >> 4;

				for (int x = xc - newRadius; x <= xc + newRadius; x++)
					for (int z = zc - newRadius; z <= zc + newRadius; z++)
					{ 
						// check if this chunk is outside the old radius area
						if ( x < xc - radius || x > xc + radius || z < zc - radius || z > zc + radius )
						{
							getChunkAndAddPlayer(x, z, player);
						}
					}
			}
		}

		assert(radius <= MAX_VIEW_DISTANCE);
		assert(radius >= MIN_VIEW_DISTANCE);
		this->radius = newRadius;
	}
}