public class Packet {
	private static final Logger log = LoggerFactory.getLogger(Packet.class);

	public static final int HEADER_SIZE = 4 + 4; // type + size

	protected static final CharsetDecoder stringDecoder;

	static {
		stringDecoder = Charset.forName("UTF-8").newDecoder();
	}

	// X_SEND are packets from the client -> server
	// X_RESPONSE are from the server -> client
	public enum Type {
		LOGIN_SEND,				LOGIN_RESPONSE,
		PING_SEND,
		CHAT_SEND,				CHAT_RESPONSE,
								MESSAGE_RESPONSE,
		USE_ITEM_SEND,
								FRIEND_LOGIN_RESPONSE,
		FRIEND_ADD_SEND,		FRIEND_ADD_RESPONSE,
		FRIEND_MESSAGE_SEND,	FRIEND_MESSAGE_RESPONSE,
		FRIEND_REMOVE_SEND,		FRIEND_REMOVE_RESPONSE,
								PLAYERS_ADD_RESPONSE,
								PLAYERS_REMOVE_RESPONSE,
								PLAYERS_UPDATE_RESPONSE,
								INVENTORY_ADD_RESPONSE,
								INVENTORY_REMOVE_RESPONSE,
								INVENTORY_UPDATE_RESPONSE,
		WALK_TO_SEND,
		STAT_UPDATE_SEND,
	}

	protected final Type type;
	protected IoBuffer payload;
	protected final IoSession session;

	public Packet(Type type, IoBuffer payload, IoSession session) {
		this.type = type;
		this.payload = payload;
		this.session = session;
	}

	public IoSession getSession() {
		return session;
	}

	public Type getType() {
		return type;
	}

	protected byte[] getBytes() {
		byte[] bytes = new byte[this.size()];

		payload.rewind();
		payload.get(bytes, 0, bytes.length);

		return bytes;
	}

	protected void setBytes(byte[] bytes) {
		payload = IoBuffer.wrap(bytes).asReadOnlyBuffer();
	}

	public void decrypt(PrivateKey key) {
		try {
			Cipher cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.DECRYPT_MODE, key);

			byte[] encrypted = this.getBytes();
			byte[] decrypted = cipher.doFinal(encrypted);

			this.setBytes(decrypted);
		}
		catch (Exception e) {
			log.error("Error decrpyting packet: " + e.getMessage());
		}
	}

	public <E extends Enum<E>> E getEnum(Class<E> enumClass) {
		return payload.getEnum(enumClass);
	}

	public String getString() {
		try {
			return payload.getString(stringDecoder);
		}
		catch (CharacterCodingException e) {
			return null;
		}
	}

	public Hash getHash() {
		byte[] data = new byte[Hash.LENGTH];
		payload.get(data, 0, Hash.LENGTH);

		return Hash.fromBytes(data);
	}

	public Point getPoint() {
		int x = this.getShort();
		int y = this.getShort();

		return new Point(x, y);
	}

	public Stat getStat() {
		Stat.Type type = this.getEnum(Stat.Type.class);
		long exp = this.getLong();
		int current = this.getShort();

		return new Stat(type, exp, current);
	}

	public Item getItem() {
		int id = this.getShort();
		long amount = this.getLong();
		boolean equiped = this.getBoolean();

		return new Item(id, amount, equiped);
	}

	public Friend getFriend() {
		Hash id = this.getHash();
		String username = this.getString();
		boolean online = this.getBoolean();

		return new Friend(id, username, online);
	}

	public Date getDate() {
		long time = this.getLong();

		return new Date(time);
	}

	public Path getPath() {
		Path path = new Path();

		int stepCount = this.getShort();
		for (int i = 0;i < stepCount;i++)
			path.append(this.getPoint());

		return path;
	}

	public boolean getBoolean() {
		return payload.get() == 1;
	}

	public byte getByte() {
		return payload.get();
	}

	public short getShort() {
		return payload.getShort();
	}

	public int getInt() {
		return payload.getInt();
	}

	public long getLong() {
		return payload.getLong();
	}

	public void rewind() {
		payload.rewind();
	}

	public int size() {
		return payload.limit();
	}

	@Override
	public String toString() {
		return "packet[" + type + "].length = " + this.size();
	}
} public class Item implements Serializable {
	private static final Logger log = LoggerFactory.getLogger(Item.class);
	private static final long serialVersionUID = 1L;

	protected static final Map<Integer, Definition> definitions;

	static {
		definitions = Item.loadDefinitions();

		if (log.isDebugEnabled())
			log.debug("Loaded " + definitions.size() + " item definitions");
	}

	public static Map<Integer, Definition> getDefinitions() {
		return definitions;
	}

	// do nothing - static {} does the work
	public static void load() { }

	@SuppressWarnings("unchecked")
	private static Map<Integer, Definition> loadDefinitions() {
		URL path = Item.class.getResource("items.xml");
		if (path == null) {
			log.error("Unable to find items.xml resource");
			System.exit(1); // fatal error
			return null;
		}

		return (Map<Integer, Definition>) PersistenceManager.load(path);
	}

	public static class Definition {
		public static final Definition DEFAULT_DEFINITION;

		static {
			DEFAULT_DEFINITION = new Definition();
			DEFAULT_DEFINITION.name = "Unknown Item";
			DEFAULT_DEFINITION.stackable = false;
			DEFAULT_DEFINITION.equipable = Equipable.NOT_EQUIPABLE;
			DEFAULT_DEFINITION.edible = 0;
			DEFAULT_DEFINITION.description = "An unknown item.";
		}

		public enum Equipable {
			NOT_EQUIPABLE,
			HEAD,
		};

		public String name;
		public boolean stackable;
		public Equipable equipable;
		public byte edible;
		public String description;
	}

	protected int id;
	protected long amount;
	protected boolean equiped;
	protected transient Definition definition;

	public Item(int id) {
		this (id, 1, false);
	}

	public Item(int id, long amount, boolean equiped) {
		this.setID(id);
		this.setAmount(amount);
		this.setEquiped(equiped);
	}

	@SuppressWarnings("unused")
	private Item() { } // for hibernate

	// for hibernate
	private void setID(int id) {
		this.id = id;

		definition = definitions.get(id);
		if (definition == null)
			definition = Definition.DEFAULT_DEFINITION;
	}

	public int getID() {
		return id;
	}

	public long getAmount() {
		return amount;
	}

	public void setAmount(long amount) {
		// If the item isn't stackable it's amount should always be 1
		if (amount != 1 && !this.isStackable()) {
			log.error("Attempted to set amount of non-stackable item.");
			return;
		}

		this.amount = amount;
	}

	public boolean isEquiped() {
		return equiped;
	}

	public void setEquiped(boolean equiped) {
		// If the item isn't equipable it should never be equiped
		if (equiped && !this.isEquipable()) {
			log.error("Attempted to equip non-equipable item.");
			return;
		}

		this.equiped = equiped;
	}

	public boolean isEquipable() {
		return definition.equipable != Definition.Equipable.NOT_EQUIPABLE;
	}

	public Definition.Equipable getEquipable() {
		return definition.equipable;
	}

	public boolean isEdible() {
		return definition.edible > 0;
	}

	public int getEdible() {
		return definition.edible;
	}

	public String getName() {
		return definition.name;
	}

	public String getDescription() {
		return definition.description;
	}

	public boolean isStackable() {
		return definition.stackable;
	}

	@Override
	public String toString() {
		return "item[id = " + id + ", name = '" + definition.name + "', amount = " + amount + ", equiped = " + equiped + "]";
	}

	private void writeObject(ObjectOutputStream out) throws IOException {
		out.defaultWriteObject();
	}

	private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
		in.defaultReadObject();
		this.setID(id); // Re-set the ID, this will set our definition up
	}
} public class PersistenceManager {
	private static final Logger log = LoggerFactory.getLogger(PersistenceManager.class);

	public class PacketHandler {
		public Packet.Type[] types;
		public Class<?> handler;
	}

	public class ItemHandler {
		public int[] ids;
		public Class<?> handler;
	}

	protected static final XStream xstream;

	static {
		xstream = new XStream();

		xstream.alias("type", Packet.Type.class);

		xstream.alias("PacketHandler", PacketHandler.class);
		xstream.alias("ItemHandler", ItemHandler.class);

		xstream.alias("ItemDef", Item.Definition.class);
		xstream.alias("TileDef", Tile.Definition.class);
	}

	public static void alias(String name, Class<?> type) {
		xstream.alias(name, type);
	}

	public static Object load(URL path) {
		try {
			InputStream in = path.openStream();
			Object o = xstream.fromXML(in);
			in.close();

			return o;
		}
		catch (IOException ioe) {
			log.error("Error loading object: " + ioe.getMessage());
			System.exit(1); // fatal error

			return null;
		}
	}

	public static void save(Object o, File f) throws IOException {
		OutputStream out = new FileOutputStream(f);
		xstream.toXML(o, out);
		out.close();
	}
} public class Server implements IoHandler, Runnable {
	private static final Logger log = LoggerFactory.getLogger(Server.class);

	public static final int DEFAULT_PORT = 36954;
	public static final int LOOP_DELAY = 100;

	protected static final Options options;

	static {
		options = new Options();

		options.addOption("h", "help", false, "print this help.");
		options.addOption("p", "port", true, "port number to listen on, default " + DEFAULT_PORT + ".");
		options.addOption("b", "bind", true, "IP address to bind to, default all.");
	}

	public static void main(String[] args) {
		try {
			CommandLineParser parser = new PosixParser();
			CommandLine config = parser.parse(options, args);

			if (config.hasOption("h")) {
				HelpFormatter help = new HelpFormatter();
				help.printHelp("java " + Server.class.getSimpleName(), options);
				return;
			}

			Server server = new Server(config);
			server.start();
		}
		catch (ParseException e) {
			log.error("Error parsing command line options: " + e);
		}
		catch (RuntimeException e) {
			log.error(e.getMessage());
		}
	}

	protected final CommandLine config;
	protected final NioSocketAcceptor acceptor;
	protected final Map<Packet.Type, PacketHandler> packetHandlers;
	protected final Database db;
	protected final WorldManager world;
	protected final PrivateKey privateKey;
	protected final Queue<Packet> packets;
	protected boolean running;
	protected long lastPacketUpdate;

	private Server(CommandLine config) {
		this.config = config;

		packetHandlers = this.loadPacketHandlers();

		// Connection to the MySQL database
		File dbConfig = new File("database.conf.xml");
		if (!dbConfig.exists()) {
			// fatal error
			throw new RuntimeException("Unable to load database config file: " + dbConfig.getAbsolutePath());
		}

		privateKey = (PrivateKey) PersistenceManager.load(Server.class.getResource("privatekey.xml"));

		// Pre-load the item definitions
		Item.load();

		db = new Database(dbConfig, this);

		acceptor = new NioSocketAcceptor();
		acceptor.setReuseAddress(true);

		acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new PacketCodecFactory()));

		// Set the idle timeout to 5 seconds - once a client has logged in this gets increased
		acceptor.getSessionConfig().setIdleTime(IdleStatus.READER_IDLE, 5);
		acceptor.setHandler(this);

		world = new WorldManager(this);

		packets = new LinkedList<Packet>();

		running = false;
		lastPacketUpdate = 0;
	}

	private Map<Packet.Type, PacketHandler> loadPacketHandlers() {
		Map<Packet.Type, PacketHandler> handlers = new HashMap<Packet.Type, PacketHandler>();
		URL path = PacketHandler.class.getResource("packethandlers.xml");
		if (path == null) {
			// fatal error
			throw new RuntimeException("Unable to find packethandlers.xml resource");
		}

		PersistenceManager.PacketHandler[] definitions = (PersistenceManager.PacketHandler[]) PersistenceManager.load(path);
		for (PersistenceManager.PacketHandler definition : definitions) {
			try {
				PacketHandler handler = (PacketHandler) definition.handler.newInstance();
				for (Packet.Type type : definition.types)
					handlers.put(type, handler);
			}
			catch (Exception e) {
				// fatal error
				throw new RuntimeException("Error loading packet handlers: " + e.getMessage());
			}
		}

		if (log.isDebugEnabled())
			log.debug("Loaded " + handlers.size() + " packet handlers");

		return handlers;
	}

	public Database getDatabase() {
		return db;
	}

	public WorldManager getWorldManager() {
		return world;
	}

	@Override
	public void run() {
		while (running) {
			long start = System.currentTimeMillis();
			this.update(start);

			int duration = (int) (System.currentTimeMillis() - start);
			if (duration < LOOP_DELAY) {
				try { Thread.sleep(LOOP_DELAY - duration); } catch (InterruptedException e) { }
			}
		}

		// TODO: Shut down the server
	}

	private void update(long now) {
		// Process the queued packets
		synchronized (packets) {
			for (Packet message : packets)
				this.processPacket(message);

			packets.clear();
		}

		// Update the world
		world.update(now);
	}

	private boolean processPacket(Packet message) {
		IoSession session = message.getSession();
		// Confirm the client is still connected and valid
		if (!session.isConnected() || (!session.containsAttribute("client") && !session.containsAttribute("pending")))
			return false;

		Player client = (Player) session.getAttribute("client");

		PacketHandler handler = packetHandlers.get(message.getType());
		// If there's no handler then close the session (forcefully)
		if (handler == null) {
			log.warn("Unhandled packet from: " + client);
			session.close(true);
			return false;
		}

		try {
			handler.handlePacket(this, world, client, message);
			return true;
		}
		// Something went wrong (malformed packet?), close the session (forcefully)
		catch (Exception e) {
			log.warn("Error decoding packet from: " + client);
			e.printStackTrace();
			session.close(true);
			return false;
		}
	}

	private void start() {
		if (running)
			return;

		int port = DEFAULT_PORT;
		if (config.hasOption("p")) {
			try {
				port = Integer.parseInt(config.getOptionValue("p"));
			}
			catch (NumberFormatException e) {
				// fatal error
				throw new RuntimeException("Invalid port number: " + config.getOptionValue("p"));
			}
		}

		InetSocketAddress listen = null;
		if (config.hasOption("b"))
			listen = new InetSocketAddress(config.getOptionValue("b"), port);
		else
			listen = new InetSocketAddress(port);

		running = true;
		new Thread(this).start();

		try {
			acceptor.bind(listen);
			System.out.println("Server listening on: " + listen.getHostName() + ":" + listen.getPort());
		}
		catch (IOException e) {
			// fatal error
			throw new RuntimeException("Unable to bind to: " + listen.getHostName() + ":" + listen.getPort());
		}
	}

	public void stop() {
		running = false;
	}

	@Override
	public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
		log.warn("Error from " + (session.containsAttribute("client") ? session.getAttribute("client") : "new") + " connection: " + cause.getMessage());

		// Close the session (forcefully)
		session.close(true);
	}

	@Override
	public void messageReceived(IoSession session, Object o) throws Exception {
		Packet message = (Packet) o;
		// We should only process 1 packet from a session at a time
		synchronized (session) {
			// If there is a client, queue the packet for processing
			if (session.containsAttribute("client") || session.containsAttribute("pending")) {
				synchronized (packets) {
					packets.add(message);
				}
			}
			// If there isn't a client attached then this must be the login request
			else if (message.getType() == Packet.Type.LOGIN_SEND) {
				// Decrypt the login request
				message.decrypt(privateKey);

				// Mark this session as pending login
				session.setAttribute("pending");

				// Queue the packet
				synchronized (packets) {
					packets.add(message);
				}
			}
			// Otherwise this packet shouldn't be here!
			else {
				log.warn("Client isn't logged in, but sent a packet");
				session.close(true);
			}
		}
	}

	@Override
	public void messageSent(IoSession session, Object o) throws Exception { }

	@Override
	public void sessionClosed(IoSession session) throws Exception {
		synchronized (packets) {
			// If the session has a client attached, call the session closed method
			if (session.containsAttribute("client")) {
				Player client = (Player) session.getAttribute("client");
				world.removePlayer(client);
			}
			// Otherwise they haven't logged in yet, so who cares just drop them
		}
	}

	@Override
	public void sessionCreated(IoSession session) throws Exception { }

	@Override
	public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
		// When a session becomes idle, close it (gracefully)
		session.close(false);
	}

	@Override
	public void sessionOpened(IoSession session) throws Exception { }
} public class WorldManager {
	public static final int PLAYER_UPDATE_DELAY = 200;

	protected final Server server;
	protected final WorldMap map;
	protected final EntityList<Player> players;
	protected long lastPlayerUpdate;

	public WorldManager(Server server) {
		this.server = server;

		map = WorldMap.load();
		players = new EntityList<Player>();

		lastPlayerUpdate = 0;
	}

	public WorldMap getMap() {
		return map;
	}

	public void update(long now) {
		if (now - lastPlayerUpdate > PLAYER_UPDATE_DELAY) {
			lastPlayerUpdate = now;

			synchronized (players) {
				// Update all the players
				for (Player player : players.allEntities())
					player.update(now, players);

				Collection<Player> newPlayers = players.newEntities();
				Collection<Player> removedPlayers = players.removedEntities();

				// Alert all the players of any logins/logouts
				for (Player player : players.allEntities()) {
					// Alert this player if any of the new players are their friends
					for (Player newPlayer : newPlayers) {
						if (player.getFriends().contains(newPlayer.getID()))
							player.sendFriendLogin(newPlayer.getID(), true);
					}

					// Alert this player is any of the removed players are their friends
					for (Player removedPlayer : removedPlayers) {
						if (player.getFriends().contains(removedPlayer.getID()))
							player.sendFriendLogin(removedPlayer.getID(), false);
					}
				}

				players.reset();
			}
		}
	}

	public Collection<Player> getPlayers() {
		return players.allEntities();
	}

	public Player getPlayer(Hash id) {
		return players.get(id);
	}

	public void addPlayer(Player player) {
		synchronized (players) {
			player.onSessionStarted();
			players.add(player);
		}
	}

	public void removePlayer(Player player) {
		synchronized (players) {
			players.remove(player.getID());
			player.onSessionEnded();
		}
	}
} public interface ItemHandler {

	public void handleItem(Server server, WorldManager world, Player player, Item item, int index);

} public interface PacketHandler {

	public void handlePacket(Server server, WorldManager world, Player player, Packet packet) throws Exception;

} public class EdibleHandler implements ItemHandler {

	@Override
	public void handleItem(Server server, WorldManager world, Player player, Item item, int index) {
		Stat hitpoints = player.getStats().get(Stat.Type.HITPOINTS);
		String message = "You eat the " + item.getName().toLowerCase();

		player.getInventory().remove(index);
		player.sendInventoryRemove(index);

		// Our health isn't full so increase it
		int health = hitpoints.getCurrent();
		if (health < hitpoints.getLevel()) {
			// Increase the health by the correct amount, but not over full
			health += item.getEdible();
			if (health > hitpoints.getLevel())
				health = hitpoints.getLevel();

			hitpoints.setCurrent(health);

			message += ", it heals some health";
		}

		player.sendMessage(message + ".");
	}
} public class EquipableHandler implements ItemHandler {

	@Override
	public void handleItem(Server server, WorldManager world, Player player, Item item, int index) {
		boolean equiped = !item.isEquiped();
		// If we are equiping this item, un-equip anything else that uses the same equipable position
		if (equiped)
			this.removeEquipedItem(player, item.getEquipable());

		item.setEquiped(equiped);
		player.sendInventoryUpdate(index, item);
	}

	private void removeEquipedItem(Player player, Item.Definition.Equipable position) {
		Inventory inventory = player.getInventory();

		// Un-equip anything else that is equiped in this position
		for (int index = 0;index < inventory.size();index++) {
			Item item = inventory.get(index);
			if (item.isEquiped() && item.getEquipable() == position) {
				item.setEquiped(false);
				player.sendInventoryUpdate(index, item);
				break;
			}
		}
	}
} public class Player extends Entity implements Observer {
	private static final Logger log = LoggerFactory.getLogger(Player.class);

	public static final int VIEW_DISTANCE = 64;

	protected final Server server;
	protected final WorldManager world;
	protected final IoSession session;
	protected final PlayerProfile profile;
	protected final EntityList<Player> knownPlayers;
	protected Path path;

	public Player(Server server, IoSession session, PlayerProfile profile) {
		this.server = server;
		this.session = session;
		this.profile = profile;

		for (Stat stat : profile.stats)
			stat.addObserver(this);

		path = null;
		world = server.getWorldManager();
		knownPlayers = new EntityList<Player>();

		// Once a client is logged in, we can relax the idle time to 60 seconds
		session.getConfig().setIdleTime(IdleStatus.READER_IDLE, 60);
	}

	public void setPath(Path path) {
		this.path = path;
	}

	public void update(long now, EntityList<Player> allPlayers) {
		// If we have a path set, move to the next step along
		if (path != null) {
			Point step = path.removeNext();
			if (!path.hasNext())
				path = null;

			// The next step is next to us and a valid step
			if (world.getMap().isValidStep(profile.location, step)) {
				this.setLocation(step);
			}
			// The next step isn't next to us, we have an invalid path!
			else {
				// TODO: This can happen if the player is walking and they generate a new path, can we someone handle this nicely?
				log.warn("Received invalid path from: " + this);
				path = null;
			}
		}

		// For each player we know about, check they are still within our view area and logged in
		for (Iterator<Player> it = knownPlayers.iterator();it.hasNext();) {
			Player player = it.next();
			double distance = this.getLocation().distanceTo(player.getLocation());
			if (distance > VIEW_DISTANCE || !allPlayers.contains(player))
				it.remove();
		}

		// For each player in our view area, check they are in our known players list
		for (Player player : allPlayers) {
			double distance = this.getLocation().distanceTo(player.getLocation());
			if (distance > VIEW_DISTANCE)
				continue;

			knownPlayers.add(player);
		}

		// Alert this player of new players
		if (knownPlayers.hasNewEntities())
			this.sendAddPlayers(knownPlayers.newEntities());

		// Alert this player of removed players
		if (knownPlayers.hasRemovedEntities())
			this.sendRemovePlayers(knownPlayers.removedEntities());

		// Alert this player of updated players
		if (knownPlayers.hasUpdatedEntities())
			this.sendUpdatePlayers(knownPlayers.updatedEntities());

		// Reset the new and removed list ready for next time
		knownPlayers.reset();
	}

	public Collection<Player> getKnownPlayers() {
		return knownPlayers.allEntities();
	}

	public StatList getStats() {
		return profile.stats;
	}

	public FriendList getFriends() {
		return profile.friends;
	}

	public Inventory getInventory() {
		return profile.inventory;
	}

	public void onSessionStarted() {
		log.info("Client connected: " + this);
		WorldTile tile = world.getMap().getTile(profile.location);
		if (tile == null) {
			log.error("Attempted to add client to non-existant tile: " + this);

			session.close(false);
			return;
		}

		tile.add(this);
	}

	@Override
	public Point getLocation() {
		return profile.location;
	}

	@Override
	public void setLocation(Point p) {
		// If we haven't moved then don't bother updating tiles
		if (profile.location.equals(p))
			return;

		// Remove ourselves from the old tile
		world.getMap().getTile(profile.location).remove(this);

		WorldTile tile = world.getMap().getTile(p);
		if (tile == null) {
			log.error("Attempted to add client to non-existant tile: " + this);

			session.close(false);
			return;
		}

		// Update our location
		profile.location.set(p);

		// Add ourselves to the new tile
		tile.add(this);

		// Mark us as changed
		super.setChanged();
		super.notifyObservers();
	}

	public String getUsername() {
		return profile.username;
	}

	@Override
	public Hash getID() {
		return profile.id;
	}

	public void write(PacketBuilder packet) {
		session.write(packet);
	}

	public void onSessionEnded() {
		// Remove ourselves from the map
		world.getMap().getTile(profile.location).remove(this);

		// Update our last session time and save our profile
		profile.lastSession.setTime(System.currentTimeMillis());
		server.getDatabase().save(profile);

		log.info("Client disconnected: " + this);
	}

	public void sendChat(Player from, String message) {
		PacketBuilder packet = new PacketBuilder(Packet.Type.CHAT_RESPONSE);

		packet.putHash(from.getID());
		packet.putString(message);

		this.write(packet);
	}

	public void sendMessage(String message) {
		PacketBuilder packet = new PacketBuilder(Packet.Type.MESSAGE_RESPONSE);

		packet.putString(message);

		this.write(packet);
	}

	public void sendInventoryAdd(Item item) {
		PacketBuilder packet = new PacketBuilder(Packet.Type.INVENTORY_ADD_RESPONSE);

		packet.putItem(item);

		this.write(packet);
	}

	public void sendInventoryRemove(int index) {
		PacketBuilder packet = new PacketBuilder(Packet.Type.INVENTORY_REMOVE_RESPONSE);

		packet.putByte((byte) index);

		this.write(packet);
	}

	public void sendInventoryUpdate(int index, Item item) {
		PacketBuilder packet = new PacketBuilder(Packet.Type.INVENTORY_UPDATE_RESPONSE);

		packet.putByte((byte) index);
		packet.putLong(item.getAmount());
		packet.putBoolean(item.isEquiped());

		this.write(packet);
	}

	public void sendAddFriend(Friend friend) {
		PacketBuilder packet = new PacketBuilder(Packet.Type.FRIEND_ADD_RESPONSE);

		packet.putFriend(friend);

		this.write(packet);
	}

	public void sendRemoveFriend(Friend friend) {
		PacketBuilder packet = new PacketBuilder(Packet.Type.FRIEND_REMOVE_RESPONSE);

		packet.putHash(friend.getID());

		this.write(packet);
	}

	public void sendFriendMessage(String username, String message) {
		PacketBuilder packet = new PacketBuilder(Packet.Type.FRIEND_MESSAGE_RESPONSE);

		packet.putString(username);
		packet.putString(message);

		this.write(packet);
	}

	public void sendFriendLogin(Hash id, boolean in) {
		PacketBuilder packet = new PacketBuilder(Packet.Type.FRIEND_LOGIN_RESPONSE);

		packet.putHash(id);
		packet.putBoolean(in);

		this.write(packet);
	}

	public void sendAddPlayers(Collection<Player> players) {
		// Remove ourself, we already know where we are!
		int playerCount = players.size();
		if (players.contains(this))
			playerCount--;

		// If there's no players then we have nothing to send
		if (playerCount == 0)
			return;

		PacketBuilder packet = new PacketBuilder(Packet.Type.PLAYERS_ADD_RESPONSE);

		packet.putShort((short) playerCount);
		for (Player player : players) {
			if (player.equals(this))
				continue;

			packet.putHash(player.getID());
			packet.putString(player.getUsername());
			packet.putPoint(player.getLocation());
		}

		this.write(packet);
	}

	public void sendRemovePlayers(Collection<Player> players) {
		// Remove ourself, we already know where we are!
		int playerCount = players.size();
		if (players.contains(this))
			playerCount--;

		// If there's no players then we have nothing to send
		if (playerCount == 0)
			return;

		PacketBuilder packet = new PacketBuilder(Packet.Type.PLAYERS_REMOVE_RESPONSE);

		packet.putShort((short) playerCount);
		for (Player player : players) {
			if (player.equals(this))
				continue;

			packet.putHash(player.getID());
		}

		this.write(packet);
	}

	public void sendUpdatePlayers(Collection<Player> players) {
		int playerCount = players.size();
		// If there's no players then we have nothing to send
		if (playerCount == 0)
			return;

		PacketBuilder packet = new PacketBuilder(Packet.Type.PLAYERS_UPDATE_RESPONSE);

		packet.putShort((short) playerCount);
		for (Player player : players) {
			packet.putHash(player.getID());
			packet.putPoint(player.getLocation());
		}

		this.write(packet);
	}

	public void sendUpdateStat(Stat stat) {
		PacketBuilder packet = new PacketBuilder(Packet.Type.STAT_UPDATE_SEND);

		packet.putStat(stat);

		this.write(packet);
	}

	@Override
	public int hashCode() {
		return profile.hashCode();
	}

	@Override
	public boolean equals(Object o) {
		if (!(o instanceof Player))
			return false;

		Player c = (Player) o;
		return this.getID().equals(c.getID());
	}

	@Override
	public String toString() {
		return "player[id = " + this.getID() + ", username = '" + profile.username + "']";
	}

	@Override
	public void update(Observable o, Object arg) {
		// A stat was updated, send the update to the client
		if (o instanceof Stat) {
			Stat stat = (Stat) o;
			boolean levelNotification = (Boolean) arg;
			if (levelNotification)
				this.sendMessage("You increased a " + stat.getType().name + " level!");
			else
				this.sendUpdateStat(stat);
		}
	}
}
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.game.common.codec.Packet;
import com.game.common.model.Item;
import com.game.common.util.PersistenceManager;
import com.game.server.Server;
import com.game.server.WorldManager;
import com.game.server.handlers.ItemHandler;
import com.game.server.handlers.PacketHandler;
import com.game.server.handlers.item.EdibleHandler;
import com.game.server.handlers.item.EquipableHandler;
import com.game.server.model.Player;
package com.game.server.handlers.packet;




public class UseItemHandler implements PacketHandler {
	private static final Logger log = LoggerFactory.getLogger(UseItemHandler.class);

	protected final Map<Integer, ItemHandler> itemHandlers;
	protected final ItemHandler equipableHandler;
	protected final ItemHandler edibleHandler;

	public UseItemHandler() {
		itemHandlers = this.loadItemHandlers();
		equipableHandler = new EquipableHandler();
		edibleHandler = new EdibleHandler();
	}

	private Map<Integer, ItemHandler> loadItemHandlers() {
		Map<Integer, ItemHandler> handlers = new HashMap<Integer, ItemHandler>();
		URL path = ItemHandler.class.getResource("itemhandlers.xml");
		if (path == null) {
			// fatal error
			throw new RuntimeException("Unable to find itemhandlers.xml resource");
		}

		PersistenceManager.ItemHandler[] definitions = (PersistenceManager.ItemHandler[]) PersistenceManager.load(path);
		for (PersistenceManager.ItemHandler definition : definitions) {
			try {
				ItemHandler handler = (ItemHandler) definition.handler.newInstance();
				for (int id : definition.ids)
					handlers.put(id, handler);
			}
			catch (Exception e) {
				// fatal error
				throw new RuntimeException("Error loading item handlers: " + e.getMessage());
			}
		}

		if (log.isDebugEnabled())
			log.debug("Loaded " + handlers.size() + " item handlers");

		return handlers;
	}

	@Override
	public void handlePacket(Server server, WorldManager world, Player player, Packet packet) throws Exception {