package world;
import java.util.HashMap;
import java.util.HashSet;
import datatypes.XZasDouble;
import packets.SPacket;
import packets.s2cpackets.S0x32;
import packets.s2cpackets.S0x33;
import entities.Player;
import exceptions.MCConnectionException;
import tools.ArrayMerge;
import worldmap.MapColumn;
import worldmap.MapColumnChunk;
import worldmap.WorldMap;
/**
* The mapmanager will keep track of the state of the map.
*
* It monitors players and send players new map-parts if they need them.
* It send map updates to players when they need them.
* It will request clients to unload map-parts if they should do that.
*
*/
public final class MapManager extends WorldStateMonitor {
private WorldMap worldmap = new WorldMap();
private HashMap<Player, HashSet<Long>> playerLoadedColumns = new HashMap<Player, HashSet<Long>>();
public MapManager(WorldState state) {
super(state);
}
private void setPlayerHasLoadedColumn(Player player, int x, int z) {
long coordinate = XZasDouble.toLong(x, z);
if(!playerLoadedColumns.containsKey(player)) {
playerLoadedColumns.put(player, new HashSet<Long>());
}
playerLoadedColumns.get(player).add(coordinate);
}
private void unsetPlayerHasLoadedColumn(Player player, int x, int z) {
long coordinate = XZasDouble.toLong(x, z);
try {
playerLoadedColumns.get(player).remove(coordinate);
} catch(Exception e) { } //nothing to do on catch
}
private boolean playerHasLoadedColumn(Player player, int x, int z) {
long coordinate = XZasDouble.toLong(x, z);
try {
return playerLoadedColumns.get(player).contains(coordinate);
} catch(Exception e) { } //nothing to do on catch
return false ;
}
/**
* Sends the initial area of 7x7 columns to the player.
* This is always send before spawing or re-spawning.
*
* @param player
* @throws MCConnectionException
*/
public void sendInitialMap(Player player) throws MCConnectionException {
//first send needed init-chunks
// in order to calculate the correct column, divide the location by 16
int px = (int) player.getLocation().x / 16;
int pz = (int) player.getLocation().z / 16;
for(int x = px-3; x <= px+3; x++) {
for(int z = pz-3; z <= pz+3; z++) {
sendInitColumn(x, z, player);
}
}
for(int x = px-3; x <= px+3; x++) {
for(int z = pz-3; z <= pz+3; z++) {
sendColumn(x, z, player);
}
}
System.out.println("ALL DONE");
//start a thread which will start sending new mapchunks when needed.
//new Thread(new MapsendThread(player)).start();
}
/**
* Requests the client to initialize a new column. Note that this should
* sent before the column data itself.
*
* @param x
* @param z
* @param player
* @throws MCConnectionException
*/
private void sendInitColumn(int x, int z, Player player) throws MCConnectionException {
SPacket p = new S0x32(x, z, true);
state.connectionManager().sendPacketTo(player, p);
setPlayerHasLoadedColumn(player, x, z);
}
/**
* Send all nececairy data to the so it can build up the column for the given
* coordinates.
*
* @param x
* @param z
* @param player
* @throws MCConnectionException
*/
private void sendColumn(int x, int z, Player player) throws MCConnectionException {
MapColumn column = worldmap.getColumn(x, z);
short maskerbit = 1;
//sending 16 chunks from bottom up to top
short bitmask = 0;
short bitmaskaddarray = 0;
byte[] block_ids= null;
byte[] block_metadata = null;
byte[] block_light = null;
byte[] block_skylight= null;
//TODO
byte[] biome_addarray = new byte[16*16];
for(int i = 0; i<biome_addarray.length; i++) {
biome_addarray[i] = 0x09;
}
//TODO?
byte[] block_addarray = new byte[0];
for(byte y = 0; y < 16; y++) {
if(!column.isAir(y)) {
bitmask |= maskerbit;
MapColumnChunk chunk = column.getChunk(y);
if(block_ids == null) {
block_ids = chunk.getBlocksIds();
block_metadata = chunk.getBlocksMetadata();
block_light = chunk.getBlocksLight();
block_skylight = chunk.getBlocksSkylight();
} else {
block_ids = ArrayMerge.mergeBytes(block_ids, chunk.getBlocksIds());
block_metadata = ArrayMerge.mergeBytes(block_metadata, chunk.getBlocksMetadata());
block_light = ArrayMerge.mergeBytes(block_light, chunk.getBlocksLight());
block_skylight = ArrayMerge.mergeBytes(block_skylight, chunk.getBlocksSkylight());
}
}
maskerbit <<= maskerbit;
}
byte[] r = ArrayMerge.mergeBytes(block_ids, block_metadata);
r = ArrayMerge.mergeBytes(r, block_light);
r = ArrayMerge.mergeBytes(r, block_skylight);
r = ArrayMerge.mergeBytes(r, block_addarray);
r = ArrayMerge.mergeBytes(r, biome_addarray);
SPacket p = new S0x33(x, z, true, bitmask, bitmaskaddarray, r);
System.out.println("L: "+r.length);
state.connectionManager().sendPacketTo(player, p);
}
class AllSentException extends Exception {
private static final long serialVersionUID = 1085415294421705987L;
}
/**
* Thread wich will ping a client for a undefined amount of time.
* Thread stops automagically and silently when a connection is
* closed or an error occurs.
*/
class MapsendThread implements Runnable {
private Player player;
public MapsendThread(Player player) {
this.player = player;
}
private void sendColumn(int x, int z) {
try {
MapManager.this.sendInitColumn(x, z, player);
MapManager.this.sendColumn(x, z, player);
} catch (MCConnectionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return;
}
}
@Override
public void run() {
System.out.println("Starting the mapsendthread.");
while(true) {
try {
sendNextColumn();
} catch (AllSentException e) {
try {
Thread.sleep(100);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
}
/**
* Decide which column has to be sent to the client.
* Then send it.
* @throws AllSentException
*/
private void sendNextColumn() throws AllSentException {
int cx = (int) (player.getLocation().x / 16);
int cz = (int) (player.getLocation().z / 16);
//fill a radius_max grid around the player with columns
int radius_max = 15;
for(int radius = 0; radius < radius_max; radius++) {
int z_upper = cz + radius;
int z_lower = cz - radius;
for(int x = cx - radius; x < cx + radius; x++) {
if(!playerHasLoadedColumn(player, x, z_upper)) {
sendColumn(x, z_upper);
return;
}
if(!playerHasLoadedColumn(player, x, z_lower)) {
sendColumn(x, z_lower);
return;
}
}
int x_upper = cx + radius;
int x_lower = cx - radius;
for(int z = cz - radius; z < cz + radius; z++) {
if(!playerHasLoadedColumn(player, x_lower, z)) {
sendColumn(x_lower, z);
return;
}
if(!playerHasLoadedColumn(player, x_upper, z)) {
sendColumn(x_upper, z);
return;
}
}
}
throw new AllSentException();
}
}
}