package dangerzone.threads;
/*
 * This code is copyright Richard H. Clark, TheyCallMeDanger, OreSpawn, 2015-2020.
 * You may use this code for reference for modding the DangerZone game program,
 * and are perfectly welcome to cut'n'paste portions for your mod as well.
 * DO NOT USE THIS CODE FOR ANY PURPOSE OTHER THAN MODDING FOR THE DANGERZONE GAME.
 * DO NOT REDISTRIBUTE THIS CODE. 
 * 
 * This copyright remains in effect until January 1st, 2021. 
 * At that time, this code becomes public domain.
 * 
 * WARNING: There are bugs. Big bugs. Little bugs. Every size in-between bugs.
 * This code is NOT suitable for use in anything other than this particular game. 
 * NO GUARANTEES of any sort are given, either express or implied, and Richard H. Clark, 
 * TheyCallMeDanger, OreSpawn are not responsible for any damages, direct, indirect, or otherwise. 
 * You should have made backups. It's your own fault for not making them.
 * 
 * NO ATTEMPT AT SECURITY IS MADE. This code is USE AT YOUR OWN RISK.
 * Regardless of what you may think, the reality is, that the moment you 
 * connected your computer to the Internet, Uncle Sam, among many others, hacked it.
 * DO NOT KEEP VALUABLE INFORMATION ON INTERNET-CONNECTED COMPUTERS.
 * Or your phone...
 * 
 */

import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.imageio.ImageIO;

import dangerzone.Chunk;
import dangerzone.CommandHandlers;
import dangerzone.Coords;
import dangerzone.CustomPackets;
import dangerzone.DangerZone;
import dangerzone.Dimensions;
import dangerzone.Effects;
import dangerzone.InventoryContainer;
import dangerzone.ItemAttribute;
import dangerzone.NetworkInputBuffer;
import dangerzone.NetworkOutputBuffer;
import dangerzone.PacketTypes;
import dangerzone.Player;
import dangerzone.PlayerPrivs;
import dangerzone.Utils;
import dangerzone.blocks.Blocks;
import dangerzone.blocks.ColoringBlock;
import dangerzone.entities.Entities;
import dangerzone.entities.Entity;
import dangerzone.entities.EntityBlockItem;
import dangerzone.entities.EntityExp;
import dangerzone.items.Item;
import dangerzone.items.Items;


public class ServerThread implements Runnable {
	
	public Player p;
	
	//ObjectOutputStream objectOutput = null;
	//BufferedOutputStream bufobjectOutput = null;
	NetworkOutputBuffer objectOutput = null;
	//BufferedInputStream bufobjectInput = null;
	//ObjectInputStream objectInput = null;
	NetworkInputBuffer objectInput = null;
	private Lock lock = new ReentrantLock(); //send lock so we don't intertwine packets!
	private Lock lightinglock = new ReentrantLock();
	public volatile int fatal_error = 0;
	private volatile int ready = 0;
	private volatile boolean lightready = false;
	private volatile float lightvalue = 0.0f;
	private int save_ticker = 0;
	private ChunkSender chunker = null;
	private long timechecktime;
	private long averagetime;
	private int averagefps;
	
	
	ServerThread(Player pl){
		p = pl;
		p.server_thread = this;
	}
	
	public void run()  {
		int d, x, y, z, id, iid, meta, pd;
		double px, py, pz; 
		float pitch, yaw, roll, mx, my, mz, vol, freq;
		String name = null;
		int which, slot, count, bid, what, uses;
		InventoryContainer ic;
		boolean forceall = false;
		int packettype = 0;
		boolean do_respawn = false;
		boolean save_player = true;
		
		
		
		averagetime = 0;
		averagefps = 100; //start out nicely!
		timechecktime = System.currentTimeMillis(); //don't really need to here... just habit!
		
		lock.lock();
		
		try {
			p.toClient.setReceiveBufferSize(1024*128);
		} catch (SocketException e2) {
			e2.printStackTrace();
		}
		
		try {
			p.toClient.setSendBufferSize(1024*1024*2);
		} catch (SocketException e2) {
			e2.printStackTrace();
		}
		
		try {
			//Shorter timeout if real server... longer if single-player...
			if(!DangerZone.start_client){
				p.toClient.setSoTimeout(15000); //15 seconds is enough. Then fail and give up! Some machines can be SLOW to load textures...
			}else{
				p.toClient.setSoTimeout(60000);
			}
		} catch (SocketException e2) {
			e2.printStackTrace();
		} 
		
		
		//we do our own buffering. It's much better to send immediately!
		try {
			p.toClient.setTcpNoDelay(true);
		} catch (SocketException e2) {
			// TODO Auto-generated catch block
			e2.printStackTrace();
		}
		
		
		//System.out.printf("Got connection!\n");
		try {
			//bufobjectInput = new BufferedInputStream(p.toClient.getInputStream(), 1024*128);
			//objectInput = new ObjectInputStream(bufobjectInput);	
			objectInput = new NetworkInputBuffer(p.toClient.getInputStream(), 1024*64);
			packettype = objectInput.readInt();	
			if(packettype != PacketTypes.CONNECT){
				lock.unlock();
				objectInput.close();
				try {
					p.toClient.close();
				} catch (IOException e) {

				}
				DangerZone.server.removeMe(this);
				return;
			}
			
			//bufobjectOutput = new BufferedOutputStream(p.toClient.getOutputStream(), 1024*256);
			//objectOutput = new ObjectOutputStream(bufobjectOutput);
			objectOutput = new NetworkOutputBuffer(p.toClient.getOutputStream(), 1024*128);
			
			
			//version check!!!
			String clientversion = (String) objectInput.readString();
			
			//System.out.printf("Server read version string: %s\n", clientversion);
			
			if(clientversion != null && clientversion.equals(DangerZone.versionstring)){
				//System.out.printf("Server write version string: %s\n", DangerZone.versionstring);
				objectOutput.writeString(DangerZone.versionstring);
				//System.out.printf("Server flush\n");
				objectOutput.flush();
				//System.out.printf("Server wait sync\n");
				packettype = objectInput.readInt();	//fetch whether clients wants to sync up...
				String playername = (String) objectInput.readString();	//get my name!
				String playerpassword = (String) objectInput.readString();	//get my password!
				if(packettype != 0){
					//Now send back the items, blocks, and dimension IDs we are using!	
					//System.out.printf("Server get skin\n");
					getSkin();
					//System.out.printf("Server send mod names\n");
					sendModNames();
					objectOutput.flush();
					//System.out.printf("Server send server ids\n");
					sendServerIDs();
					objectOutput.flush();
					//Now find a new name for this player if we need to!
					if(playername.equals("Player") || DangerZone.server.entityManager.findPlayerByName(playername)!= null){
						save_player = false;
						while(true){
							String newname = String.format("Player %d", DangerZone.rand.nextInt(1000));
							if(DangerZone.server.entityManager.findPlayerByName(newname)==null){
								playername = newname;
								break;
							}
						}
					}
					//System.out.printf("Server send back player name\n");
					objectOutput.writeString(playername);
					objectOutput.flush();				
				}
				
				p.myname = playername;
				p.setPetName(playername);
				do_respawn = DangerZone.server.loadPlayer(p);
				p.myname = playername;
				p.setPetName(playername);
				
				//see if passwords are required, and if this one matches!
				if(!verify_playername(p, playername, playerpassword)){
					packettype = 1;
					objectOutput.writeInt(packettype);
					objectOutput.flush();
					fatal_error = 1;
				}
				if(fatal_error == 0){
					if(DangerZone.server.isBanned(playername)){
						packettype = 2;
						objectOutput.writeInt(packettype);
						objectOutput.flush();
						fatal_error = 1;
					}else{
						packettype = 0;
						objectOutput.writeInt(packettype);
						objectOutput.flush();
					}
				}
				
				if(fatal_error == 0){
					//System.out.printf("Player load\n");
					p.world = DangerZone.server_world;
					p.init();
					if(p.getHealth() <= 0){
						//System.out.printf("player was dead.\n");
						//p.setHealth(p.getMaxHealth()); //deadlocks because updates when shouldn't during this init!
						p.setVarFloat(1, p.getMaxHealth());
						p.setExperience(0);
						for(int iw=0;iw<p.maxinv;iw++){
							p.setVarInventory(iw, null); //just to make sure!
						}
						do_respawn = true;
					}


					//Send FULL player info back to client!
					sendPlayerToPlayer(p);

					//System.out.printf("player sent.\n");

					id = DangerZone.server.entityManager.addEntity(p); //get an entity ID to respond with!
					if(id <= 0){
						fatal_error = 1;
					}else{
						objectOutput.writeInt(p.entityID);
						objectOutput.flush();
					}
				}
			}else{
				objectOutput.writeString(DangerZone.versionstring);
				objectOutput.flush();
				fatal_error = 1;
			}
		} catch (IOException e) {
			//System.out.printf("ServerThread client connection failed...\n");
			fatal_error = 1;
		}

		//Fire up a chunk sender!
		if(fatal_error == 0){
			//System.out.printf("start chunk sender.\n");
			chunker = new ChunkSender(p, this);
			Thread cnk = new Thread(chunker);
			cnk.start();	
			//DangerZone.server.sendChatToAllExcept(String.format("Player: %s connected.\n", p.myname), p);
			//System.out.printf("Player: %s connected.\n", p.myname);
			
		}else{
			lock.unlock();
			if(objectInput != null)objectInput.close();
			if(objectOutput != null)objectOutput.close();
			try {
				p.toClient.close();
			} catch (IOException e) {

			}
			if(p.entityID > 0){
				DangerZone.server.entityManager.removeEntityByID(p.entityID);
			}
			DangerZone.server.removeMe(this);
			return;
		}
		
		ready = 1;
		
		lock.unlock();
		
		
		if(fatal_error == 0 && do_respawn){
			//System.out.printf("player respawn.\n");
			p.posx += (DangerZone.server_world.rand.nextFloat()-DangerZone.server_world.rand.nextFloat())*64f;
			p.posz += (DangerZone.server_world.rand.nextFloat()-DangerZone.server_world.rand.nextFloat())*64f;
			Dimensions.DimensionArray[p.dimension].teleportToDimension(p, DangerZone.server_world, p.dimension, (int)p.posx, (int)p.posy, (int)p.posz);
			sendTeleportToPlayer(p.dimension, p.posx, p.posy, p.posz); //Let him know he's been moved!
		}
		
		//System.out.printf("player running...\n");
		DangerZone.server.sendChatToAllExcept(String.format("Player: %s connected.\n", p.myname), p);
		
		while(DangerZone.gameover == 0 && fatal_error == 0){
			
			if(objectOutput.errorOccurred())break;
			if(objectInput.errorOccurred())break;

			//System.out.printf("Waiting for an int...\n");

			packettype = objectInput.readInt();

			//-------------------------------------------------------------------------------------
			if(packettype == PacketTypes.GETCHUNK){ //Wants a decorated chunk!

				d = objectInput.readInt();
				x = objectInput.readInt();
				y = objectInput.readInt();
				z = objectInput.readInt();
				
				//Send chunk request off to the thread that handles them...
				//No reason to clog up and delay the network here!
				Coords cl = new Coords();
				cl.d = d;
				cl.x = x >> 4;
				cl.z = z >> 4;
				chunker.addCoords(cl);
				continue;
			}
			
			//-------------------------------------------------------------------------------------
			if(packettype == PacketTypes.BLOCK){ //Telling us a block changed
				d = objectInput.readInt();
				x = objectInput.readInt();
				y = objectInput.readInt();
				z = objectInput.readInt();
				id = objectInput.readInt();
				meta = objectInput.readInt();
				//System.out.printf("Block changed %d,  %d, %d : %d\n", x>>4, y, z>>4, id);
				DangerZone.server_world.setblockandmeta(d, x, y, z, id, meta);
				DangerZone.server.sendBlockToAllExcept(this, d, x, y, z, id, meta);
				continue;
			}
						
			//-------------------------------------------------------------------------------------
			if(packettype == PacketTypes.DISCONNECT){
				//bye!
				break;
			}
			//-------------------------------------------------------------------------------------
			if(packettype == PacketTypes.LIGHTINGREQUEST){ //Response to a lighting request
				lightvalue = objectInput.readFloat();
				lightready = true;
				continue;
			}
			
			//-------------------------------------------------------------------------------------
			if(packettype == PacketTypes.TIMERESPONSE){ //Response to a time message
				d = objectInput.readInt(); //fps
				long mylongtime = System.currentTimeMillis(); 
				mylongtime -= timechecktime;
				averagetime = ((averagetime*3)+mylongtime)/4;
				averagefps = ((averagefps*3)+d)/4;
				if(DangerZone.show_server_stats){
					System.out.printf("player: %s, averagetime = %d, fps = %d\n", p.myname, averagetime, averagefps);
				}
				continue;
			}
			
			//-------------------------------------------------------------------------------------
			if(packettype == PacketTypes.GAMEMODECHANGE){ //Response to a gamemode change request
				d = objectInput.readInt();
				if((p.player_privs & PlayerPrivs.GAMEMODE) != 0x00 || (p.player_privs & PlayerPrivs.OPERATOR) != 0x00){
					sendNewGameMode(d);
					p.setGameMode(d);
				}
				continue;
			}

			//-------------------------------------------------------------------------------------
			if(packettype == PacketTypes.INVENTORYUPDATE){ //Telling us inventory changed , for THIS player only.
				which = objectInput.readInt();
				slot = objectInput.readInt();
				bid = objectInput.readInt();
				iid = objectInput.readInt();
				count = objectInput.readInt();
				uses = objectInput.readInt();
				String inmeta = objectInput.readString();
				if(count == 0){
					ic = null;
				}else{
					ic = new InventoryContainer();
					ic.bid = bid;
					ic.iid = iid;
					ic.count = count;
					ic.currentuses = uses;
					ic.icmeta = inmeta;
					if(ic.count == 1 && ic.bid == 0 && ic.iid != 0){
						int atcount = objectInput.readInt();
						if(atcount > 0){
							int i, attype, atval;
							for(i=0;i<atcount;i++){
								attype = objectInput.readInt();
								atval = objectInput.readInt();
								ic.addAttribute(attype, atval);
							}
						}
					}
				}
				if(which == 0){
					p.setHotbar(slot, ic);
				}
				if(which == 1){
					p.setInventory(slot, ic);
				}
				if(which == 2){
					p.setArmor(slot, ic);
				}
				continue;
			}
			
			//-------------------------------------------------------------------------------------
			if(packettype == PacketTypes.ENTITYINVENTORYUPDATE){ //Telling us inventory changed for arbitrary entity
				int eid = objectInput.readInt();
				which = objectInput.readInt();
				slot = objectInput.readInt();
				bid = objectInput.readInt();
				iid = objectInput.readInt();
				count = objectInput.readInt();
				uses = objectInput.readInt();
				String inmeta = objectInput.readString();
				if(count == 0){
					ic = null;
				}else{
					ic = new InventoryContainer();
					ic.bid = bid;
					ic.iid = iid;
					ic.count = count;
					ic.currentuses = uses;
					ic.icmeta = inmeta;
					if(ic.count == 1 && ic.bid == 0 && ic.iid != 0){
						int atcount = objectInput.readInt();
						if(atcount > 0){
							int i, attype, atval;
							for(i=0;i<atcount;i++){
								attype = objectInput.readInt();
								atval = objectInput.readInt();
								ic.addAttribute(attype, atval);
							}
						}
					}
				}
				Entity ent = DangerZone.server.entityManager.findEntityByID(eid);
				if(ent != null){
					if(which == 0){
						ent.setHotbar(slot, ic);
					}
					if(which == 1){
						ent.setInventory(slot, ic);
					}
					if(which == 2){
						ent.setArmor(slot, ic);
					}
				}
				continue;
			}
			
			//-------------------------------------------------------------------------------------
			if(packettype == PacketTypes.PLAYERACTION){ //Telling player did something
				which = objectInput.readInt();
				what = objectInput.readInt();
				d = objectInput.readInt(); //entityID
				if(which == 0){ //Player click
					DangerZone.server.sendPlayerActionToAllExcept(p, which, what);
					if(what == 0)p.leftclick(DangerZone.server_world, 0, 0, 0, 0, d); //Do it! (see if we hit an ENTITY)
					if(what == 1)p.rightclick(DangerZone.server_world, 0, 0, 0, 0, d); //Do it! (see if we hit an ENTITY)
					if(what == 2){
						Item itm = Items.getItem(d);
						if(itm != null){
							itm.onFoodEaten(p);
						}
					}
				}	
				continue;
			}
			
			//------------------------------------------------------------------------------------------
			if(packettype == PacketTypes.CHATMESSAGE){ //Chat!
				String s = (String) objectInput.readString();
				DangerZone.server.sendChatToAll(s); //got from single client, send out to all
				continue;
			}
			
			//------------------------------------------------------------------------------------------
			if(packettype == PacketTypes.PETNAME){ //name a pet
				int whichpet = objectInput.readInt();
				int whichindex = objectInput.readInt();
				String s = (String) objectInput.readString();
				Entity ent = DangerZone.server.entityManager.findEntityByID(whichpet);
				if(ent != null){
					if(whichindex == 1){
						ent.setPetName(s); //its 1 anyway...
					}else{
						ent.setVarString(whichindex, s);
					}
				}							
				continue;
			}
			
			//------------------------------------------------------------------------------------------
			if(packettype == PacketTypes.VARINTUPDATE){ //name a pet
				int whichpet = objectInput.readInt();
				int whichindex = objectInput.readInt();
				int val = objectInput.readInt();
				Entity ent = DangerZone.server.entityManager.findEntityByID(whichpet);
				if(ent != null){
					ent.setVarInt(whichindex, val);
				}							
				continue;
			}
			
			//------------------------------------------------------------------------------------------
			if(packettype == PacketTypes.COMMANDMESSAGE){ //Chat!
				String s = (String) objectInput.readString();
				
				//DO COMMAND!
				CommandHandlers.parseCommand(p, s);					
				DangerZone.server.sendCommandToAll(s); //got from single client, send out to all
				continue;
			}
			
			//------------------------------------------------------------------------------------------
			if(packettype == PacketTypes.SPAWNPARTICLES){ //Chat!
				String s = (String) objectInput.readString();
				which = objectInput.readInt(); //really how many!
				d = objectInput.readInt(); //dimension
				px = objectInput.readDouble();
				py = objectInput.readDouble();
				pz = objectInput.readDouble();	
				id = objectInput.readInt();
				DangerZone.server.sendSpawnParticleToAllExcept(p, s, which, d, px, py, pz); //got from single client, send out to all except self
				continue;
			}
			
			//------------------------------------------------------------------------------------------
			if(packettype == PacketTypes.PLAYERTELEPORT){ 
				d = objectInput.readInt(); //dimension
				px = objectInput.readDouble();
				py = objectInput.readDouble();
				pz = objectInput.readDouble();	
				Utils.doTeleport(p, d, px, py, pz);				
				continue;
			}
			
			//-------------------------------------------------------------------------------------
			//SHOULD ONLY BE RECEIVING PLAYER...
			if(packettype == PacketTypes.ENTITYUPDATE){ //Yay!
				d = objectInput.readInt(); //throw away - because it should ONLY BE ME!
				//Entity ex = DangerZone.server.entityManager.findEntityByID(d);
				//if(ex != null){
				//	if(!(ex instanceof Player))System.out.printf("WTF NOT player?\n");
				//}
				d = objectInput.readInt(); //If dimension changes, we have to tell everyone!!!
				forceall = false;
				if(d != p.dimension)forceall = true;
				p.dimension = d;					
				p.posx = objectInput.readDouble();
				p.posy = objectInput.readDouble();
				p.posz = objectInput.readDouble();
				p.motionx = objectInput.readFloat();
				p.motiony = objectInput.readFloat();
				p.motionz = objectInput.readFloat();
				p.rotation_pitch = objectInput.readFloat();
				p.rotation_yaw = objectInput.readFloat();
				p.rotation_roll = objectInput.readFloat();
				p.rotation_pitch_head = objectInput.readFloat();
				p.rotation_yaw_head = objectInput.readFloat();
				p.rotation_roll_head = objectInput.readFloat();
				p.rotation_pitch_motion = objectInput.readFloat();
				p.rotation_yaw_motion = objectInput.readFloat();
				p.rotation_roll_motion = objectInput.readFloat();
				d = objectInput.readInt();
				if(d != 0){
					p.deadflag = true;
				}else{
					p.deadflag = false;
				}
				readVarsIntoEntity(p);
				//System.out.printf("Server fire = %d,  %d, %d\n", p.getOnFire(), p.changed, p.changes[9]);
				
				save_ticker++;
				if(save_player && save_ticker > 600){ //about once a minute
					DangerZone.server.saveThisPlayer(p);
					save_ticker = 0;
				}

				//Tell all the other players!
				DangerZone.server.sendPlayerUpdateToAllExcept(p, p, forceall);
				continue;
			}
			
			//-------------------------------------------------------------------------------------
			if(packettype == PacketTypes.SPAWNENTITY){ //			
				name = (String) objectInput.readString();
				pd = objectInput.readInt();
				px = objectInput.readDouble();
				py = objectInput.readDouble();
				pz = objectInput.readDouble();					
				pitch = objectInput.readFloat();
				yaw = objectInput.readFloat();
				roll = objectInput.readFloat();
				mx = objectInput.readFloat();
				my = objectInput.readFloat();
				mz = objectInput.readFloat();
				Entity e = doSpawnEntity(name, pd, px, py, pz, pitch, yaw, roll, mx, my, mz);
				if(e != null){
					readVarsIntoEntity(e);
					e.init();
					if(DangerZone.server.entityManager.addEntity(e) > 0){
						DangerZone.server.sendSpawnEntityToAll(e);
					}
				}else{
					readVarsIntoNull();
				}
				continue;
			}
			
			//-------------------------------------------------------------------------------------
			if(packettype == PacketTypes.PLAYSOUND){ //						
				name = (String) objectInput.readString();					
				pd = objectInput.readInt();
				px = objectInput.readDouble();
				py = objectInput.readDouble();
				pz = objectInput.readDouble();					
				vol = objectInput.readFloat();
				freq = objectInput.readFloat();
				DangerZone.server.sendSoundToAllExcept(p, name, pd, px, py, pz, vol, freq); //client will play his own!		
				continue;
			}
			
			//-------------------------------------------------------------------------------------
			if(packettype == PacketTypes.WHATISTHISENTITY){ //								
				id = objectInput.readInt(); //Client is asking what this is!				
				Entity e = DangerZone.server.entityManager.findEntityByID(id);
				if(e != null){
					sendSpawnEntityToPlayer(e); //Respond with a spawn command.
					if(e instanceof Player){
						sendSkinToPlayer(e);
					}
				}
				continue;
			}				
			
			//-------------------------------------------------------------------------------------
			if(packettype == PacketTypes.KILLME){ //								
				id = objectInput.readInt(); //Kill this thing...	
				Entity e = DangerZone.server.entityManager.findEntityByID(id);
				if(e != null){
					int ieid = DangerZone.server.entityManager.removeEntityByID(id);
					e.deadflag = true;
					if(ieid == id){
						DangerZone.server.sendEntityDeathToAll(e);
					}
				}
				continue;
			}
			
			//-------------------------------------------------------------------------------------
			if(packettype == PacketTypes.COLORINGBLOCK){ //			
				iid = objectInput.readInt(); //really bid
				d = objectInput.readInt();
				x = objectInput.readInt();
				y = objectInput.readInt();
				z = objectInput.readInt();
				float colordata[][][] = new float[16][16][4];
				for(int i=0;i<16;i++){
					for(int j=0;j<16;j++){
						for(int k=0;k<4;k++){
							colordata[i][j][k] = objectInput.readFloat();
						}
					}
				}
				doSaveColoringBlock(iid, d, x, y, z, colordata);					
				continue;
			}
			
			//-------------------------------------------------------------------------------------
			//hopefully it's a custom packet!
			CustomPackets.messageFromClient(packettype, this.p, objectInput);
			
		}
		
		//let the chunker know we are leaving!
		fatal_error = 1;
		
		if(save_player)DangerZone.server.saveThisPlayer(p);
		
		DangerZone.server.sendEntityRemoveToAllExcept(this.p, this.p);
		DangerZone.server.entityManager.removeEntityByID(this.p.entityID);		
		DangerZone.server.removeMe(this);
		
		//System.out.printf("Player being removed!\n");
		
		//try {
		//	if(bufobjectInput != null)bufobjectInput.close();
		//} catch (IOException e) {
//
		//}
//		try {
//			if(bufobjectOutput != null)bufobjectOutput.close();
//		} catch (IOException e) {
//
//		}
		
		if(objectInput != null)objectInput.close();
		if(objectOutput != null)objectOutput.close();
//		try {
//			if(objectOutput != null)objectOutput.close();
//		} catch (IOException e) {
//
//		}
		try {
			p.toClient.close();
		} catch (IOException e) {

		}
		

		
	}
	
	private boolean checkReady(){
		while(fatal_error == 0 && DangerZone.gameover == 0){
			if(ready != 0)return true;
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
				fatal_error = 1;
			}
		}
		return false;
	}
	
	public void sendSpawnEntityToPlayer(Entity e){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();	
		int pt = PacketTypes.SPAWNENTITY;
		int i;
		objectOutput.writeInt(pt);
		objectOutput.writeString(e.uniquename);
		objectOutput.writeInt(e.entityID);
		objectOutput.writeInt(e.dimension);
		objectOutput.writeDouble(e.posx);
		objectOutput.writeDouble(e.posy);
		objectOutput.writeDouble(e.posz);
		objectOutput.writeFloat(e.rotation_pitch);
		objectOutput.writeFloat(e.rotation_yaw);
		objectOutput.writeFloat(e.rotation_roll);
		objectOutput.writeFloat(e.rotation_pitch_head);
		objectOutput.writeFloat(e.rotation_yaw_head);
		objectOutput.writeFloat(e.rotation_roll_head);
		objectOutput.writeFloat(e.motionx);
		objectOutput.writeFloat(e.motiony);
		objectOutput.writeFloat(e.motionz);
		
		//bang out ints
		for(i=0;i<e.maxvars;i++){
			if(e.entity_ints[i] != 0){
				objectOutput.writeInt(i);
				objectOutput.writeInt(e.entity_ints[i]);						
			}
		}
		pt = -1; //packet separator
		objectOutput.writeInt(pt);

		//bang out floats
		for(i=0;i<e.maxvars;i++){
			if(e.entity_floats[i] != 0){
				objectOutput.writeInt(i);
				objectOutput.writeFloat(e.entity_floats[i]);						
			}
		}
		pt = -1; //packet separator
		objectOutput.writeInt(pt);

		//bang out strings
		for(i=0;i<e.maxvars;i++){
			if(e.entity_strings[i] != null){
				objectOutput.writeInt(i);
				objectOutput.writeString(e.entity_strings[i]);						
			}
		}
		pt = -1; //packet separator
		objectOutput.writeInt(pt);	
		
		InventoryContainer ic = null;
		for(i=0;i<e.maxinv;i++){
			ic = e.getVarInventory(i);
			if(ic != null){
				objectOutput.writeInt(i);
				objectOutput.writeInt(ic.bid);
				objectOutput.writeInt(ic.iid);
				objectOutput.writeInt(ic.count);
				objectOutput.writeInt(ic.currentuses);
				objectOutput.writeString(ic.icmeta);
				if(ic.count == 1 && ic.bid == 0 && ic.iid != 0){
					int atcount = 0;
					if(ic.attributes != null)atcount = ic.attributes.size();
					objectOutput.writeInt(atcount);
					if(atcount > 0){
						ItemAttribute ia = null;
						for(int k=0;k<atcount;k++){
							ia = ic.attributes.get(k);
							objectOutput.writeInt(ia.type);
							objectOutput.writeInt(ia.value);
						}
					}
				}
			}				
		}				
		pt = -1; //packet separator
		objectOutput.writeInt(pt);
		
		pt = -2; //packet terminator!
		objectOutput.writeInt(pt);
		
		if(e.effect_list != null){
			Effects ef = null;
			for(i=0;i<e.effect_list.size();i++){
				ef = e.effect_list.get(i);
				if(ef != null){
					objectOutput.writeInt(ef.effect);
					objectOutput.writeFloat(ef.amplitude);
					objectOutput.writeInt(ef.duration);
					objectOutput.writeInt(ef.duration_counter);
				}				
			}
		}
		
		pt = -3; //packet terminator!
		objectOutput.writeInt(pt);
		
		//flushSendLocked(); 
		lock.unlock();
	}
	
	public void sendChunkToPlayer(Chunk c){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		DangerZone.chunks_per_second++;
		lock.lock();

		int pt = PacketTypes.CHUNK;
		short curval;
		short curcount;
		int indx;

		int separator = -1;
		int i;
		objectOutput.writeInt(pt); //packet type
		objectOutput.writeInt(c.chunkX);
		objectOutput.writeInt(c.chunkZ);
		objectOutput.writeInt(c.dimension);
		objectOutput.writeInt(c.isDecorated);
		objectOutput.writeInt(c.isChanged);
		objectOutput.writeInt(c.isValid);
		objectOutput.writeInt(c.must_be_written);
		
		objectOutput.writeInt(separator);
		
		for(i=0;i<256;i++){
			if(c.blockdata[i] != null){
				objectOutput.writeInt(i); //current index
				//old way
				//sending a 50*50 chunk cache of the world is the equivalent of sending about 100MB of data. (2500 *40K)
				//not fast
				//objectOutput.writeObject(c.blockdata[i]);
				//
				//new way
				//cheap and dirty run-length encoding
				//very fast
				//
				curval = c.blockdata[i][0];
				curcount = 0;
				for(indx=0;indx<256;indx++){
					if(c.blockdata[i][indx] == curval){
						curcount++;
					}else{
						objectOutput.writeShort(curcount);
						objectOutput.writeShort(curval);
						curcount = 1;
						curval = c.blockdata[i][indx];
					}						
				}					
				objectOutput.writeShort(curcount);
				objectOutput.writeShort(curval);
				
			}
		}
		
		objectOutput.writeInt(separator);
		
		for(i=0;i<256;i++){
			if(c.metadata[i] != null){
				objectOutput.writeInt(i);
				objectOutput.writeShortArray(c.metadata[i], c.metadata[i].length);
			}
		}
		
		objectOutput.writeInt(separator);

		lock.unlock();
	}
	

	public void sendBlockToPlayer(int d, int x, int y, int z, int id, int meta){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();
		//System.out.printf("Sending block\n");
		
		int pt = PacketTypes.BLOCK;
		objectOutput.writeInt(pt);
		objectOutput.writeInt(d);
		objectOutput.writeInt(x);
		objectOutput.writeInt(y);
		objectOutput.writeInt(z);
		objectOutput.writeInt(id);
		objectOutput.writeInt(meta);

		lock.unlock();
	}
	
	public void sendEntityDeathToPlayer(int eid){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();
		int pt = PacketTypes.ENTITYDEATH;
		objectOutput.writeInt(pt);
		objectOutput.writeInt(eid);
//		flushSendLocked();
		lock.unlock();
	}
	
	public void sendChatToPlayer(String s){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();
		int pt = PacketTypes.CHATMESSAGE;
		objectOutput.writeInt(pt);
		objectOutput.writeString(s);
		flushSendLocked();
		lock.unlock();
	}
	
	public void sendSongToPlayer(String s){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();
		int pt = PacketTypes.PLAYSONG;
		objectOutput.writeInt(pt);
		objectOutput.writeString(s);
		flushSendLocked();
		lock.unlock();
	}
	
	public void sendCommandToPlayer(String s){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();
		int pt = PacketTypes.COMMANDMESSAGE;
		objectOutput.writeInt(pt);
		objectOutput.writeString(s);
		flushSendLocked();
		lock.unlock();
	}
	
	public void sendSpawnParticleToPlayer(String s, int hm, int d, double x, double y, double z){
		sendSpawnParticleToPlayer( s,  hm,  d,  x,  y,  z, 0);
	}
	public void sendSpawnParticleToPlayer(String s, int hm, int d, double x, double y, double z, int bid){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		
		if(averagetime > 500){			
			if(DangerZone.rand.nextBoolean())return;
			if(averagetime > 1000)if(DangerZone.rand.nextBoolean())return;
			if(averagetime > 2000)if(DangerZone.rand.nextBoolean())return;
			if(averagetime > 3000)if(DangerZone.rand.nextBoolean())return;
			if(averagetime > 4000)if(DangerZone.rand.nextBoolean())return;
		}
		
		lock.lock();

		int pt = PacketTypes.SPAWNPARTICLES;
		objectOutput.writeInt(pt);
		objectOutput.writeString(s);
		objectOutput.writeInt(hm);
		objectOutput.writeInt(d);
		objectOutput.writeDouble(x);
		objectOutput.writeDouble(y);
		objectOutput.writeDouble(z);
		objectOutput.writeInt(bid);
		//let them stack together a little....   //flushSendLocked();
		lock.unlock();
	}
	
	public void sendVelocityUpdateToPlayer(float newx, float newy, float newz){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();
		int pt = PacketTypes.PLAYERVELOCITY;
		objectOutput.writeInt(pt);
		objectOutput.writeFloat(newx);
		objectOutput.writeFloat(newy);
		objectOutput.writeFloat(newz);
//		flushSendLocked();
		lock.unlock();
	}
	
	public void sendPositionAndVelocityUpdateToPlayer(Player p){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();
		int pt = PacketTypes.PLAYERPOSITIONVELOCITY;
		objectOutput.writeInt(pt);
		objectOutput.writeDouble(p.posx);
		objectOutput.writeDouble(p.posy);
		objectOutput.writeDouble(p.posz);
		objectOutput.writeFloat(p.motionx);
		objectOutput.writeFloat(p.motiony);
		objectOutput.writeFloat(p.motionz);
//		flushSendLocked();
		lock.unlock();
	}
	
	public void sendTeleportToPlayer(int dim, double newx, double newy, double newz){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();
		int pt = PacketTypes.PLAYERTELEPORT;
		objectOutput.writeInt(pt);
		objectOutput.writeInt(dim);
		objectOutput.writeDouble(newx);
		objectOutput.writeDouble(newy);
		objectOutput.writeDouble(newz);
		flushSendLocked();
		lock.unlock();
	}
	
	public void sendEntityRemoveToPlayer(int eid){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();
		int pt = PacketTypes.ENTITYREMOVE;
		objectOutput.writeInt(pt);
		objectOutput.writeInt(eid);
//		flushSendLocked();
		lock.unlock();
	}
	
	public float doLightingRequest(int d, int x, int y, int z){
		if(!checkReady())return 0;
		DangerZone.packets_per_second++;
		//System.out.printf("trying for light\n");
		lightinglock.lock();
		lightready = false;
		lightvalue = DangerZone.server_world.rand.nextFloat();
		lock.lock();
		int pt = PacketTypes.LIGHTINGREQUEST;
		objectOutput.writeInt(pt);
		objectOutput.writeInt(d);
		objectOutput.writeInt(x);
		objectOutput.writeInt(y);
		objectOutput.writeInt(z);
//		flushSendLocked();
		lock.unlock();
		
		int tries = 100; //Don't try too hard!!!
		while(tries > 0 && !lightready){
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			tries--;
		}
		//System.out.printf("got %f after %d tries\n",  lightvalue, 2000-tries);
		
		//these don't happen very frequently, or I'd have to do something less kludgy...
		float lv = lightvalue;		
		lightready = false; //done!
		lightinglock.unlock();
		return lv;
	}
	
	public void sendEntityHitToPlayer(int eid, float newhealth){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();
		int pt = PacketTypes.ENTITYHIT;
		objectOutput.writeInt(pt);
		objectOutput.writeInt(eid);
		objectOutput.writeFloat(newhealth);
//		flushSendLocked();
		lock.unlock();
	}
	
	public void sendPlayerAction(Player p, int which, int what){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();		
		int pt = PacketTypes.PLAYERACTION;
		objectOutput.writeInt(pt);
		objectOutput.writeInt(p.entityID);
		objectOutput.writeInt(which);
		objectOutput.writeInt(what);
//		flushSendLocked(); 
		lock.unlock();
	}
	
	public void sendExpAdjust(int what){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();		
		int pt = PacketTypes.ADJUSTEXP;
		objectOutput.writeInt(pt);
		objectOutput.writeInt(what);
//		flushSendLocked(); 
		lock.unlock();
	}
	
	public void sendNewEffect(Effects ef){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();		
		int pt = PacketTypes.NEWEFFECT;
		objectOutput.writeInt(pt);
		objectOutput.writeInt(ef.effect);
		objectOutput.writeInt(ef.duration);
		objectOutput.writeFloat(ef.amplitude);
//		flushSendLocked(); 
		lock.unlock();
	}
	
	public void sendVarIntUpdate(int which, int val){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();		
		int pt = PacketTypes.VARINTUPDATE;
		objectOutput.writeInt(pt);
		objectOutput.writeInt(which);
		objectOutput.writeInt(val);
//		flushSendLocked(); 
		lock.unlock();
	}
	
	public void sendVarFloatUpdate(int which, float val){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();		
		int pt = PacketTypes.VARFLOATUPDATE;
		objectOutput.writeInt(pt);
		objectOutput.writeInt(which);
		objectOutput.writeFloat(val);
//		flushSendLocked(); 
		lock.unlock();
	}
	
	public void sendVarStringUpdate(int which, String val){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();		
		int pt = PacketTypes.VARSTRINGUPDATE;
		objectOutput.writeInt(pt);
		objectOutput.writeInt(which);
		objectOutput.writeString(val);
//		flushSendLocked(); 
		lock.unlock();
	}
	
	public void sendMountCommand(int which){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();		
		int pt = PacketTypes.MOUNTENTITY;
		objectOutput.writeInt(pt);
		objectOutput.writeInt(which);
//		flushSendLocked(); 
		lock.unlock();
	}
	
	public void sendInventoryUpdateToPlayer(int which, int slot, InventoryContainer ic){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();
		int pt = PacketTypes.INVENTORYUPDATE;
		objectOutput.writeInt(pt);
		objectOutput.writeInt(which);
		objectOutput.writeInt(slot);
		if(ic != null){
			objectOutput.writeInt(ic.bid);
			objectOutput.writeInt(ic.iid);
			objectOutput.writeInt(ic.count);
			objectOutput.writeInt(ic.currentuses);
			objectOutput.writeString(ic.icmeta);
			if(ic.count == 1 && ic.bid == 0 && ic.iid != 0){
				int atcount = 0;
				if(ic.attributes != null)atcount = ic.attributes.size();
				objectOutput.writeInt(atcount);
				if(atcount > 0){
					ItemAttribute ia = null;
					for(int k=0;k<atcount;k++){
						ia = ic.attributes.get(k);
						objectOutput.writeInt(ia.type);
						objectOutput.writeInt(ia.value);
					}
				}
			}
		}else{
			pt = 0;
			objectOutput.writeInt(pt);
			objectOutput.writeInt(pt);
			objectOutput.writeInt(pt);
			objectOutput.writeInt(pt);
			objectOutput.writeString(null);
		}
		flushSendLocked();
		lock.unlock();
	}
	
	public void sendEntityUpdateToPlayer(Entity e){
		if(!checkReady())return;
		if(e.entityID == p.entityID)return; //NOT self!!!
				
		//start getting harsh!
		if(averagetime > 500 && (e instanceof EntityBlockItem || e instanceof EntityExp)){			
			if(DangerZone.rand.nextBoolean())return;
			if(averagetime > 1000)if(DangerZone.rand.nextBoolean())return;
			if(averagetime > 1500)if(DangerZone.rand.nextBoolean())return;
			if(averagetime > 2000)if(DangerZone.rand.nextBoolean())return;
			if(averagetime > 2500)if(DangerZone.rand.nextBoolean())return;
			if(averagetime > 3000)if(DangerZone.rand.nextBoolean())return;
			if(averagetime > 3500)if(DangerZone.rand.nextBoolean())return;
		}
		
		DangerZone.packets_per_second++;
		lock.lock();
				
		
		int pt = PacketTypes.ENTITYUPDATE;
		int i;
		objectOutput.writeInt(pt);
		objectOutput.writeInt(e.entityID);
		objectOutput.writeInt(e.dimension);
		objectOutput.writeDouble(e.posx);
		objectOutput.writeDouble(e.posy);
		objectOutput.writeDouble(e.posz);
		objectOutput.writeFloat(e.motionx);
		objectOutput.writeFloat(e.motiony);
		objectOutput.writeFloat(e.motionz);
		objectOutput.writeFloat(e.rotation_pitch);
		objectOutput.writeFloat(e.rotation_yaw);
		objectOutput.writeFloat(e.rotation_roll);
		objectOutput.writeFloat(e.rotation_pitch_head);
		objectOutput.writeFloat(e.rotation_yaw_head);
		objectOutput.writeFloat(e.rotation_roll_head);
		objectOutput.writeFloat(e.rotation_pitch_motion);
		objectOutput.writeFloat(e.rotation_yaw_motion);
		objectOutput.writeFloat(e.rotation_roll_motion);
		if(e.deadflag){
			pt = 1;
			objectOutput.writeInt(pt);
		}else{
			pt = 0;
			objectOutput.writeInt(pt);
		}
		
		//The *ToAll* routine will clear the changed flags when it is done.
		if(e.changed != 0){
			//bang out ints that changed
			for(i=0;i<e.maxvars;i++){
				if((e.changes[i]&0x01) == 0x01){
					objectOutput.writeInt(i);
					objectOutput.writeInt(e.entity_ints[i]);
					//if(i == 3)System.out.printf("%s changed %d to %d\n",  e.uniquename, i, e.entity_ints[i]);
				}
			}
			pt = -1; //packet separator
			objectOutput.writeInt(pt);
			
			//bang out floats that changed
			for(i=0;i<e.maxvars;i++){
				if((e.changes[i]&0x02) == 0x02){
					objectOutput.writeInt(i);
					objectOutput.writeFloat(e.entity_floats[i]);						
				}
			}
			pt = -1; //packet separator
			objectOutput.writeInt(pt);
			
			//bang out strings that changed
			for(i=0;i<e.maxvars;i++){
				if((e.changes[i]&0x04) == 0x04){
					objectOutput.writeInt(i);
					objectOutput.writeString(e.entity_strings[i]);						
				}
			}
			pt = -1; //packet separator
			objectOutput.writeInt(pt);
			
			InventoryContainer ic = null;
			for(i=0;i<e.maxinv;i++){
				if((e.changes[i]&0x08) == 0x08){
					objectOutput.writeInt(i);
					ic = e.getVarInventory(i);
					if(ic != null && ic.count > 0){
						objectOutput.writeInt(ic.bid);
						objectOutput.writeInt(ic.iid);
						objectOutput.writeInt(ic.count);
						objectOutput.writeInt(ic.currentuses);
						objectOutput.writeString(ic.icmeta);
						if(ic.count == 1 && ic.bid == 0 && ic.iid != 0){
							int atcount = 0;
							if(ic.attributes != null)atcount = ic.attributes.size();
							objectOutput.writeInt(atcount);
							if(atcount > 0){
								ItemAttribute ia = null;
								for(int k=0;k<atcount;k++){
									ia = ic.attributes.get(k);
									objectOutput.writeInt(ia.type);
									objectOutput.writeInt(ia.value);
								}
							}
						}
					}else{
						pt = 0;
						objectOutput.writeInt(pt);
						objectOutput.writeInt(pt);
						objectOutput.writeInt(pt);
						objectOutput.writeInt(pt);
						objectOutput.writeString(null);
					}
				}
			}				
			pt = -1; //packet separator
			objectOutput.writeInt(pt);
						
		}
		
		pt = -2; //packet terminator!
		objectOutput.writeInt(pt);
		
		if(e.effect_list != null){
			Effects ef = null;
			for(i=0;i<e.effect_list.size();i++){
				ef = e.effect_list.get(i);
				if(ef != null && ef.effect > 0){
					objectOutput.writeInt(ef.effect);
					objectOutput.writeFloat(ef.amplitude);
					objectOutput.writeInt(ef.duration);
					objectOutput.writeInt(ef.duration_counter);
				}				
			}
		}
		
		pt = -3; //packet terminator!
		objectOutput.writeInt(pt);
		
		
		lock.unlock();

	}
	
	//Server just read/created player. Send complete info back to them.
	public void sendPlayerToPlayer(Player e){

		int pt = 0;
		
		
		objectOutput.writeInt(e.dimension);
		objectOutput.writeDouble(e.posx);
		objectOutput.writeDouble(e.posy);
		objectOutput.writeDouble(e.posz);
		objectOutput.writeFloat(e.motionx);
		objectOutput.writeFloat(e.motiony);
		objectOutput.writeFloat(e.motionz);
		objectOutput.writeFloat(e.rotation_pitch);
		objectOutput.writeFloat(e.rotation_yaw);
		objectOutput.writeFloat(e.rotation_roll);
		objectOutput.writeFloat(e.rotation_pitch_head);
		objectOutput.writeFloat(e.rotation_yaw_head);
		objectOutput.writeFloat(e.rotation_roll_head);
		objectOutput.writeFloat(e.rotation_pitch_motion);
		objectOutput.writeFloat(e.rotation_yaw_motion);
		objectOutput.writeFloat(e.rotation_roll_motion);

		if(e.deadflag){
			pt = 1;
			objectOutput.writeInt(pt);
		}else{
			pt = 0;
			objectOutput.writeInt(pt);
		}
		
		//if(e.changed != 0){
			//bang out ints that changed
			for(int i=0;i<e.maxvars;i++){
				//if((e.changes[i]&0x01) == 0x01){
					objectOutput.writeInt(i);
					objectOutput.writeInt(e.entity_ints[i]);						
				//}
			}
			pt = -1; //packet separator
			objectOutput.writeInt(pt);
			
			//bang out floats that changed
			for(int i=0;i<e.maxvars;i++){
				//if((e.changes[i]&0x02) == 0x02){
					objectOutput.writeInt(i);
					objectOutput.writeFloat(e.entity_floats[i]);						
				//}
			}
			pt = -1; //packet separator
			objectOutput.writeInt(pt);
			
			//bang out strings that changed
			for(int i=0;i<e.maxvars;i++){
				//if((e.changes[i]&0x04) == 0x04){
					objectOutput.writeInt(i);
					objectOutput.writeString(e.entity_strings[i]);						
				//}
			}
			pt = -1; //packet separator
			objectOutput.writeInt(pt);
			
			InventoryContainer ic = null;
			for(int i=0;i<e.maxinv;i++){
				//if((e.changes[i]&0x08) == 0x08){
					objectOutput.writeInt(i);
					ic = e.getVarInventory(i);
					if(ic != null){
						objectOutput.writeInt(ic.bid);
						objectOutput.writeInt(ic.iid);
						objectOutput.writeInt(ic.count);
						objectOutput.writeInt(ic.currentuses);
						objectOutput.writeString(ic.icmeta);
						if(ic.count == 1 && ic.bid == 0 && ic.iid != 0){
							int atcount = 0;
							if(ic.attributes != null)atcount = ic.attributes.size();
							objectOutput.writeInt(atcount);
							if(atcount > 0){
								ItemAttribute ia = null;
								for(int k=0;k<atcount;k++){
									ia = ic.attributes.get(k);
									objectOutput.writeInt(ia.type);
									objectOutput.writeInt(ia.value);
								}
							}
						}
					}else{
						pt = 0;
						objectOutput.writeInt(pt);
						objectOutput.writeInt(pt);
						objectOutput.writeInt(pt);
						objectOutput.writeInt(pt);
						objectOutput.writeString(null);
					}
				//}
			}				
			pt = -1; //packet separator
			objectOutput.writeInt(pt);
						
		//}
		
		pt = -2; //packet terminator!
		objectOutput.writeInt(pt);
		
		if(e.effect_list != null){
			Effects ef = null;
			for(int i=0;i<e.effect_list.size();i++){
				ef = e.effect_list.get(i);
				if(ef != null){
					objectOutput.writeInt(ef.effect);
					objectOutput.writeFloat(ef.amplitude);
					objectOutput.writeInt(ef.duration);
					objectOutput.writeInt(ef.duration_counter);
				}				
			}
		}
		
		pt = -3; //packet terminator!
		objectOutput.writeInt(pt);

	}
	
	public void sendTimeToPlayer(int t, int l){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();
		timechecktime = System.currentTimeMillis(); //record when this was sent!
		int pt = PacketTypes.TIME;
		objectOutput.writeInt(pt);
		objectOutput.writeInt(t);
		objectOutput.writeInt(l);
		flushSendLocked();
		lock.unlock();
	}
	
	public void sendNewGameMode(int t){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();
		int pt = PacketTypes.GAMEMODECHANGE;
		objectOutput.writeInt(pt);
		objectOutput.writeInt(t);
		flushSendLocked();
		lock.unlock();
	}
	
	//Called from outside
	public void flushSend(){
		if(!checkReady())return;
		lock.lock();
		if(objectOutput != null)objectOutput.flush();
		lock.unlock();
	}
	
	//internal
	public void flushSendLocked(){
		objectOutput.flush();
	}
	
	
	public Entity doSpawnEntity(String name, int pd, double px, double py, double pz, float pitch, float yaw, float roll, float mx, float my, float mz){
		Entity e = Entities.spawnEntityByName(name, DangerZone.server_world);
		if(e != null){
			e.posx = px;
			e.posy = py;
			e.posz = pz;
			e.rotation_pitch = pitch;
			e.rotation_yaw = yaw;
			e.rotation_roll = roll;
			e.rotation_pitch_head = pitch;
			e.rotation_yaw_head = yaw;
			e.rotation_roll_head = roll;
			e.motionx = mx;
			e.motiony = my;
			e.motionz = mz;
			e.dimension = pd;
			//e.init();
			//if(DangerZone.server.entityManager.addEntity(e) <= 0)return null; //oops! Nevermind!
		}
		return e;
	}
	
	//track changes in (player) so that they are forwarded to all!
	private void readVarsIntoEntity(Entity ent){
		int index;
		index = objectInput.readInt();
		if(index != -2){ //contains vars!
			while(index >= 0){
				if(index < ent.maxvars){
					ent.entity_ints[index] = objectInput.readInt();					
					ent.changed = 1;
					ent.changes[index] |= 0x01;
				}
				index = objectInput.readInt();
			}
			//floats that changed
			index = objectInput.readInt();
			while(index >= 0){
				if(index < ent.maxvars){
					ent.entity_floats[index] = objectInput.readFloat();					
					ent.changed = 1;
					ent.changes[index] |= 0x02;
				}
				index = objectInput.readInt();
			}
			//strings that changed
			index = objectInput.readInt();
			while(index >= 0){
				if(index < ent.maxvars){
					ent.entity_strings[index] = (String) objectInput.readString();						
					ent.changed = 1;
					ent.changes[index] |= 0x04;
				}
				index = objectInput.readInt();
			}
			index = objectInput.readInt();
			
			InventoryContainer ic;
			int bid, iid, count, uses;
			String inmeta = null;
			while(index >= 0){
				bid = objectInput.readInt();
				iid = objectInput.readInt();
				count = objectInput.readInt();
				uses = objectInput.readInt();
				inmeta = objectInput.readString();
				if(count == 0){
					ic = null;
				}else{
					ic = new InventoryContainer();
					ic.bid = bid;
					ic.iid = iid;
					ic.count = count;
					ic.currentuses = uses;
					ic.icmeta = inmeta;
					if(ic.count == 1 && ic.bid == 0 && ic.iid != 0){
						int atcount = objectInput.readInt();
						if(atcount > 0){
							int i, attype, atval;
							for(i=0;i<atcount;i++){
								attype = objectInput.readInt();
								atval = objectInput.readInt();
								ic.addAttribute(attype, atval);
							}
						}
					}
				}
				ent.setVarInventory(index, ic);
				ent.setVarInventoryChanged(index);
				
				index = objectInput.readInt();
			}
			index = objectInput.readInt();
		}
		if(index != -2){
			//System.out.printf("packet terminator failure on enitity update!\n");
			fatal_error = 1;
		}
		
		index = objectInput.readInt();
		if(index > 0){
			List<Effects> new_effect_list = new ArrayList <Effects>();		
			while(index > 0){
				Effects ef = new Effects();
				ef.effect = index;
				ef.amplitude = objectInput.readFloat();
				ef.duration = objectInput.readInt();
				ef.duration_counter = objectInput.readInt();
				new_effect_list.add(ef);
				index = objectInput.readInt();
			}
			ent.effect_list = new_effect_list;
		}else{
			if(ent.effect_list.size() > 0){
				ent.effect_list =  new ArrayList <Effects>();
			}
		}
		
		if(index != -3){
			System.out.printf("packet terminator failure on server enitity update!\n");
			fatal_error = 1;
		}
	}
	
	private void readVarsIntoNull(){
		int index = 0;
		int bid, iid, count;
		index = objectInput.readInt();
		if(index != -2){ //contains vars!
			while(index >= 0){
				objectInput.readInt();
				index = objectInput.readInt();
			}
			//floats that changed
			index = objectInput.readInt();
			while(index >= 0){
				objectInput.readFloat();
				index = objectInput.readInt();
			}
			//strings that changed
			index = objectInput.readInt();
			while(index >= 0){
				objectInput.readString();
				index = objectInput.readInt();
			}
			index = objectInput.readInt();
			while(index >= 0){
				bid = objectInput.readInt();
				iid = objectInput.readInt();
				count = objectInput.readInt();
				objectInput.readInt();				
				if(count == 1 && bid == 0 && iid != 0){
					int atcount = objectInput.readInt();
					if(atcount > 0){							
						for(int i=0;i<atcount;i++){
							objectInput.readInt();
							objectInput.readInt();
						}
					}
				}
				index = objectInput.readInt();
			}
			index = objectInput.readInt();
		}
		if(index != -2){
			//System.out.printf("packet terminator failure on enitity update!\n");
			fatal_error = 1;
		}	
		index = objectInput.readInt();
		if(index > 0){
			while(index > 0){
				objectInput.readFloat();
				objectInput.readInt();
				objectInput.readInt();
				index = objectInput.readInt();
			}
		}
		if(index != -3){
			System.out.printf("packet terminator failure on server enititynull update!\n");
			fatal_error = 1;
		}		
	}
	
	public void sendSound(String name, int pd, double px, double py, double pz, float vol, float freq){
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();	
		int pt = PacketTypes.PLAYSOUND;
		objectOutput.writeInt(pt);
		objectOutput.writeString(name);
		objectOutput.writeInt(pd);
		objectOutput.writeDouble(px);
		objectOutput.writeDouble(py);
		objectOutput.writeDouble(pz);
		objectOutput.writeFloat(vol);
		objectOutput.writeFloat(freq);
		
//		flushSendLocked(); 
		lock.unlock();
	
	}
	
	/*
	 * Makes sure Client agrees with our IDs!
	 */
	public void sendServerIDs(){
		int i, j;

		try {
			for(i=1;i<Items.itemsMAX;i++){
				if(Items.ItemArray[i] != null){
					objectOutput.writeInt(i);
					objectOutput.writeString(Items.ItemArray[i].uniquename);
				}
			}
			i = -1;
			objectOutput.writeInt(i); //separator
			
			for(i=1;i<Blocks.blocksMAX;i++){
				if(Blocks.BlockArray[i] != null){
					objectOutput.writeInt(i);
					objectOutput.writeString(Blocks.BlockArray[i].uniquename);
				}
			}
			i = -1;
			objectOutput.writeInt(i); //separator
			
			for(i=1;i<Dimensions.dimensionsMAX;i++){
				if(Dimensions.DimensionArray[i] != null){
					objectOutput.writeInt(i);
					objectOutput.writeString(Dimensions.DimensionArray[i].uniquename);
				}
			}
			i = -1;
			objectOutput.writeInt(i); //separator		
			
			for(i=1;i<CustomPackets.CustomPacketsMAX;i++){
				if(CustomPackets.CustomPacketsArray[i] != null){
					objectOutput.writeInt(i);
					objectOutput.writeString(CustomPackets.CustomPacketsArray[i].uniquename);
				}
			}
			i = -1;
			objectOutput.writeInt(i); //separator	
			
			//Send the coloring blocks!
			String name;
			int bid;
			float colordata[][][] = new float[16][16][4];
			for(bid=1;bid<Blocks.blocksMAX;bid++){
				name = Blocks.getUniqueName(bid);
				if(name != null && name.startsWith("DangerZone:Coloring Block ")){ //notice the space!
					BufferedImage bi = ImageIO.read(new File(Blocks.BlockArray[bid].texturepath));
					if(bi != null){
						int r, g, b, color;
						for(j=0; j<16;j++){
							for(i=0;i<16;i++){
								color = bi.getRGB(i, j);
								r = (color >> 16) & 0xff;
								g = (color >> 8) & 0xff;
								b = (color) & 0xff;								
								colordata[i][15-j][0] = r; 								
								colordata[i][15-j][1] = g; 				
								colordata[i][15-j][2] = b; 
								colordata[i][15-j][3] = 255f;			
							}
						}								
						sendColoringBlock(name, bid, colordata, false);
					}
				}				
			}
			
			i = -1;
			objectOutput.writeInt(i); //separator	
			
			objectOutput.flush();			
		} catch (IOException err) {
			//System.out.printf("Server sendsound send failed.\n");
			fatal_error = 1;
		} 

	}
	
	public void sendModNames(){
		int i = DangerZone.all_the_mods.size();

		objectOutput.writeInt(i); //write number of mod names
		for(int k=0;k<i;k++){
			String s = DangerZone.all_the_mods.get(k).modname;				
			objectOutput.writeString(s);
			//System.out.printf("Sent mod name %s\n", s);
		}
		objectOutput.writeString(DangerZone.worldname);
		objectOutput.flush(); 

	}
	
	private void getSkin(){
		int seq;
		byte b[];
		
		seq = objectInput.readInt();
		if(seq != -3){
			fatal_error = 1;
			return;
		}
		
		b = (byte[]) objectInput.readByteArray();  //FIX ME! I CRASH!!!!!!
		
		//System.out.printf("Got skin len = %d\n", len);
		if(b == null || b.length < 8192){
			fatal_error = 1;
			return;
		}
		p.tdata = b; //got it! save it!!!
	}
	
	private void sendSkinToPlayer(Entity e){
		Player pe = (Player)e;
		
		if(!checkReady())return;
		DangerZone.packets_per_second++;
		lock.lock();	
		objectOutput.writeByteArray(pe.tdata, pe.tdata.length);			
		flushSendLocked(); 
		lock.unlock();
		
		
	}
	
	public NetworkOutputBuffer getOutputStream(){
		if(!checkReady())return null;
		DangerZone.packets_per_second++;		
		lock.lock();
		return objectOutput;
	}
	
	public void releaseOutputStream(){
		//objectOutput.flush();	
		lock.unlock();
	}
	


	
	public void sendColoringBlock(String name,  int bid, float colordata[][][], boolean check){
		if(check){
			if(!checkReady())return;
		}
		DangerZone.packets_per_second++;
		lock.lock();	
		int pt = PacketTypes.COLORINGBLOCK;
		objectOutput.writeInt(pt);
		objectOutput.writeString(name);
		objectOutput.writeInt(bid);
		for(int i=0;i<16;i++){
			for(int j=0;j<16;j++){
				for(int k=0;k<4;k++){
					objectOutput.writeFloat(colordata[i][j][k]);
				}
			}
		}
		
		flushSendLocked(); 
		lock.unlock();	
	}
	
	private void doSaveColoringBlock(int bid, int dim, int px, int py, int pz, float colordata[][][]){
		int newbid = bid;
		String blkname = Blocks.getUniqueName(bid);
		if(blkname == null)return;
		if(!blkname.equals("DangerZone:Coloring Block")){ //existing block!
			//Overwrite existing block. Hopefully a coloring block! Doh!
			String fp = Blocks.BlockArray[bid].texturepath;
			if(fp == null)return;
			File file = new File(fp);	
			int width = 16;
			int height = 16;
			String format = "PNG"; 
			BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
			   
			for(int x = 0; x < width; x++){
			    for(int y = 0; y < height; y++){
			        int r = ((int)colordata[x][y][0]) & 0xFF;
			        int g = ((int)colordata[x][y][1]) & 0xFF;
			        int b = ((int)colordata[x][y][2]) & 0xFF;
			        image.setRGB(x, height - (y + 1), (0xFF << 24) | (r << 16) | (g << 8) | b);
			    }
			}
			   
			try {
			    ImageIO.write(image, format, file);
			} catch (IOException e) { e.printStackTrace(); }
			image.flush();
			DangerZone.server.sendColoringBlockToAll(blkname, newbid, colordata);
			return;
		}
		//Have to make a new one.
		int index = 0;
		int blkindex = 0;
		boolean ok = false;
		boolean found = false;
		for(index = 0;index < 100;index++){
			blkname = String.format("DangerZone:Coloring Block %2d", index);
			found = false;
			for(blkindex=1;blkindex<Blocks.blocksMAX;blkindex++){
				if(Blocks.BlockArray[blkindex] != null){
					if(blkname.equals(Blocks.getUniqueName(blkindex))){
						found = true;
						break;
					}
				}
			}			
			if(found)continue;
			ok = true;
			for(blkindex=1;blkindex<Blocks.blocksMAX;blkindex++){
				if(Blocks.BlockArray[blkindex] == null)break;
			}
			break;			
		}
		if(!ok)return; //too many!
		//index = color name
		//blkindex = bid
		String fp = String.format("worlds/%s/coloring/coloringblock%2d.png", DangerZone.worldname, index);
		File file = new File(fp);	
		int width = 16;
		int height = 16;
		String format = "PNG"; 
		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		file.mkdirs();
		   
		for(int x = 0; x < width; x++){
		    for(int y = 0; y < height; y++){
		        int r = ((int)colordata[x][y][0]) & 0xFF;
		        int g = ((int)colordata[x][y][1]) & 0xFF;
		        int b = ((int)colordata[x][y][2]) & 0xFF;
		        image.setRGB(x, height - (y + 1), (0xFF << 24) | (r << 16) | (g << 8) | b);
		    }
		}
		   
		try {
		    ImageIO.write(image, format, file);
		} catch (IOException e) { e.printStackTrace(); }
		image.flush();
		
		ColoringBlock cb = new ColoringBlock(blkname, "");
		cb.texturepath = file.getAbsolutePath();
		cb.blockID = blkindex;
		Blocks.BlockArray[blkindex] = cb;		//Hope there wasn't anything there! :)
		Blocks.addthis(blkname, blkindex);
		Blocks.save(); //Save it so we can find it when we start again next time.
		
		DangerZone.server.sendColoringBlockToAll(blkname, blkindex, colordata);
		DangerZone.server_world.setblock(dim, px, py, pz, blkindex);
	}
	
	private boolean verify_playername(Player p, String pln, String pa){

		if(!DangerZone.start_client){
			p.player_privs = DangerZone.default_privs;
			
			if(DangerZone.require_valid_passwords){
				if(pln == null || pa == null)return false;
				if(pln.length() < 4)return false;
				if(pln.length() > 16)return false;
				if(pa.length() != 32)return false;
				
				Socket ns_sock = null;
				ObjectInputStream ns_objectInput = null;
				ObjectOutputStream ns_objectOutput = null;
				BufferedOutputStream ns_bufobjectOutput = null;
				BufferedInputStream ns_bufobjectInput = null;
				int packettype = 0;
				boolean is_valid = false;
				
				try {
					//System.out.printf("connecting to %s:%d\n", LauncherMain.nsserver_address, LauncherMain.nsserver_port);
					ns_sock = new Socket(DangerZone.nsserver_address, DangerZone.nsserver_port);
					ns_bufobjectOutput = new BufferedOutputStream(ns_sock.getOutputStream());
					ns_objectOutput = new ObjectOutputStream(ns_bufobjectOutput);
					packettype = -1; //dummy just to connect...
					ns_objectOutput.writeInt(packettype);
					ns_objectOutput.flush();
					ns_bufobjectInput = new BufferedInputStream(ns_sock.getInputStream());
					ns_objectInput = new ObjectInputStream(ns_bufobjectInput);
					packettype = ns_objectInput.readInt(); //dummy just to connect...
					packettype = 0;
				} catch (UnknownHostException e) {
					//System.out.printf("Client failed to make socket 1\n");

				} catch (IOException e) {
					//System.out.printf("Client failed to make socket 2\n");

				}
				
				//got a connection and something to do!
				try {

						packettype = 0;
						ns_objectOutput.writeInt(packettype);
						ns_objectOutput.writeObject(pln);
						ns_objectOutput.writeObject(pa);
						ns_objectOutput.flush();
						int response = ns_objectInput.readInt();
						if(response == 0){
							is_valid = false;
						}else{
							is_valid = true;
						}

				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
				
				try {
					if(ns_bufobjectInput != null)ns_bufobjectInput.close();
				} catch (IOException e) {

				}
				try {
					if(ns_bufobjectOutput != null)ns_bufobjectOutput.close();
				} catch (IOException e) {

				}
				
				try {
					if(ns_objectInput != null)ns_objectInput.close();
				} catch (IOException e) {

				}
				try {
					if(ns_objectOutput != null)ns_objectOutput.close();
				} catch (IOException e) {

				}
				
				try {
					if(ns_sock != null)ns_sock.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
				if(!is_valid)return false;
				
			}
			
			p.player_privs = DangerZone.server.find_privs(pln);
			
			return true;
		}
			

		//single player
		p.player_privs = 0xffffffff;
		return true;
	}

}
