public class Location {

	public final static int VIEW_RANGE = 18;
	public final int xCord, yCord;
	public final Map map;
	public final int direction;
	
	/**
	 * Construct a new Location in this Map
	 * @param map
	 * @param xCord
	 * @param yCord
	 */
	public Location(Map map, int xCord, int yCord) {
		this(map, xCord, yCord, 0);
	}

	/**
	 * Construct a new Location in this Map
	 * @param map
	 * @param xCord
	 * @param yCord
	 * @param direction
	 */
	public Location(Map map, int xCord, int yCord, int direction) {
		this.xCord = xCord;
		this.yCord = yCord;
		this.map = map;
		this.direction = direction % 8;
	}

	/**
	 * @return the Map for this Location
	 */
	public Map getMap() {
		return map;
	}
	
	/**
	 * @return the x-coordinate for this Location
	 */
	public int getxCord() {
		return xCord;
	}
	
	/**
	 * @return the direction
	 */
	public int getDirection() {
		return direction;
	}
	
	/**
	 * @return the y-coordinate for this Location
	 */
	public int getyCord() {
		return yCord;
	}
	
	/**
	 * @param x offset
	 * @param y offset
	 * @return a new Location in this name with a given offset
	 * from this Location
	 */
	public Location atOffset(int x, int y) {
		return new Location(map, xCord + x, yCord + y, direction);
	}
	
	/**
	 * @param direction
	 * @return the next point in the given direction
	 */
	public Location inDirection(int direction) {
		switch (direction%8) {
        case 0:
            return new Location(map, xCord, yCord+1, direction);
        case 1:
            return new Location(map, xCord-1, yCord+1, direction);
        case 2:
            return new Location(map, xCord-1, yCord, direction);
        case 3:
            return new Location(map, xCord-1, yCord-1, direction);
        case 4:
            return new Location(map, xCord, yCord-1, direction);
        case 5:
            return new Location(map, xCord+1, yCord-1, direction);
        case 6:
            return new Location(map, xCord+1, yCord, direction);
        default:
            return new Location(map, xCord+1, yCord+1, direction);
		}
	}
	
	/**
	 * @param direction
	 * @return Create a new Location with only a changed direction
	 */
	public Location switchDirection(int direction) {
		return new Location(map, xCord, yCord, direction);
	}
	
	/**
	 * @param location
	 * @return true if an entity is within the default sight of
	 * the entity at this location
	 */
	public boolean inView(Location location) {
		return location.getMap().equals(getMap())
				&& Math.sqrt(Math.pow(location.xCord - xCord, 2) + Math.pow(location.yCord - yCord, 2)) < VIEW_RANGE;
		//		&& Math.abs(location.xCord - xCord) < VIEW_RANGE
		//		&& Math.abs(location.yCord - yCord) < VIEW_RANGE;
	}
	
	
	@Override
	public String toString() {
		return map.toString() + "(" + xCord + "; " + yCord + ")";
	}

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof Location) {
			Location other = (Location) obj;
			return other.map.equals(map)
					&& other.xCord == xCord
					&& other.yCord == yCord;
		}
		return false;
	}

} public class Player extends Entity {
	
	private static final long serialVersionUID = 8357201473468302184L;

	private AbstractClient client;
	//private NPC_Dialog activeDialog;
	
	private int gold = 0, cps = 0,
			experience = 0, pkPoints = 0, rebornCount = 0, stamina = 100, action = 0,
			strength = 1, dexterity = 1, vitality = 1, spirit = 1, xpRing;
	
	private boolean xpON; 
	
	private long blessTime = 0; 
	
	private Player prayerHost;
	
	private Profession profession; 
			
	protected Guild guild = null;	                
	//protected GuildRank guildRank = GuildRank.NONE;  // TO be replaced with guild member v
	protected GuildMember guildMember; // 
	private String spouse;
	
	public final Inventory inventory = new Inventory();	
	private final HashMap<WeaponType, WeaponProficiency> proficiencies = new HashMap<WeaponType, WeaponProficiency>();
	private final HashMap<Skill, SkillProficiency> skills = new HashMap<Skill, SkillProficiency>();

	private long lastMovement = 0;

	public Player(Long identity, String name, Location location, int HP) {
		super(identity, 223, 315, name, location, HP);
	}
	


	public GuildMember getGuildMember() {
		return guildMember;
	}
	
	public Player getPrayerHost() {
		return prayerHost;
	}



	public void setPrayerHost(Player prayerHost) {
		this.prayerHost = prayerHost;
	}



	public boolean isBlessing() {
		return hasFlag(Flag.LUCKYTIME);
	}

	public boolean isPraying() {
		return hasFlag(Flag.PRAY);
	}

	public long getBlessTime() {
		return blessTime;
	}


	public void setBlessTime(long blessTime) {
		this.blessTime = blessTime;
	}


	/**
	 * @param guildMember the guildMember to set
	 */
	public void setGuildMember(GuildMember guildMember) {
		this.guildMember = guildMember;
	}
	
	public boolean isXPON() {
		return xpON;
	}
	
	public void setXPON(boolean xpON) {
		this.xpON = xpON;
	}


	public void setClient(AbstractClient client) {
		this.client = client;
	}

	public int getGold() {
		return gold;
	}

	public void setGold(int gold) {
		this.gold = gold;
	}

	public int getCps() {
		return cps;
	}
	
	public int getSkillLevel(Skill skill) {
		return skills.get(skill).level;
	}

	public void setCps(int cps) {
		this.cps = cps;
	}

	public Guild getGuild() {
		return guildMember.getGuild();
	}

	public void setGuild(Guild guild) {
		this.guild = guild;
	}
	
	public int getExperience() {
		return experience;
	}

	public void setExperience(int experience) {
		this.experience = experience;
	}

	public int getStrength() {
		return strength;
	}

	public void setStrength(int strength) {
		this.strength = strength;
	}

	public int getDexterity() {
		return dexterity;
	}

	public void setDexterity(int dexterity) {
		this.dexterity = dexterity;
	}

	public int getVitality() {
		return vitality;
	}

	public void setVitality(int vitality) {
		this.vitality = vitality;
	}

	public int getSpirit() {
		return spirit;
	}

	public void setSpirit(int spirit) {
		this.spirit = spirit;
	}
	
	

	/**
	 * @return the xpRing
	 */
	public int getXpRing() {
		return xpRing;
	}


	/**
	 * @param xpRing the xpRing to set
	 */
	public void setXpRing(int xpRing) {
		this.xpRing = xpRing;
	}


	/**
	 * @return the amount of remaining attribute points.
	 * Attribute points are calculated based on your reborn path
	 * and current level. 
	 */
	public int getAttributePoints() {
		// Reborn characters start at level 15 and level attribute
		// point assignment starts at level 15 as well
		if ( level < 15 )
			return 0;
		// 52 Attribute points by default and 3 attribute points per level after level 15
		int ap = 52 + 3 * (level - 15);
		if(rebornCount > 1) {
			// After rebirth, players will receive 30 attribute points
			ap += 30;
			// In addition, more bonus attribute points (55 at most) will
			// be awarded if you were reborn at higher level.
			int fstRbLvl = 130;
			boolean fstRbWater = false;
			if (fstRbWater) {
				// Water taoists can reborn at level 110 and therefore
				// have different stats
				ap += ((fstRbLvl - 110) / 2);
			} else {
				ap += fstRbLvl - 120;
			}
			// Second reborn additional attribute points
			if(rebornCount > 2) {
				int secRbLvl = 130;
				boolean secRbWater = false;
				if (secRbWater) {
					// Water taoists can reborn at level 110 and therefore
					// have different stats
					ap += ((secRbLvl - 110) / 2);
				} else {
					ap += secRbLvl - 120;
				}
			}
		}
		// Remove the assigned points
		ap -= strength + dexterity + vitality + spirit;
		return ap;
	}

	public int getPkPoints() {
		return pkPoints;
	}

	public void setPkPoints(int pkPoints) {
		this.pkPoints = pkPoints;
	}

	public Profession getProfession() {
		return profession;
	}

	public void setProfession(Profession profession) {
		this.profession = profession;
	}
	
	/**
	 * Player profession will be increased with 1, if the player wasn't promoted to 'Master' yet.
	 */
	public void promote()	{
		this.profession = (profession.value%10 != 5) ? Profession.valueOf(profession.value + 1) : profession;
	}

	public int getRebornCount() {
		return rebornCount;
	}

	public void setRebornCount(int rebornCount) {
		this.rebornCount = rebornCount;
	}

	public String getSpouse() {
		return spouse == null ? "None" : spouse;
	}

	public void setSpouse(String spouse) {
		this.spouse = spouse;
	}

	public int getStamina() {
		return stamina;
	}

	public void setStamina(int stamina) {
		this.stamina = stamina;
	}
	
	public void setAction(int action) {
		this.action = action;
	}
	
	public int getAction() {
		return action;
	}

	@Override
	public int getMaxHP() {
		int hp = vitality * 24;
		hp += strength * 3;
		hp += dexterity * 3;
		hp += spirit * 3;
		
		if(profession.value >= 10 && profession.value <= 15) {
			if ( level >= 110 ) {
				hp *= 1.15;
			} else if ( level >= 100 ) {
				hp *= 1.12;
			} else if ( level >= 70 ) {
				hp *= 1.1;
			} else if ( level >= 40 ) {
				hp *= 1.08;
			} else if ( level >= 15 ) {
				hp *= 1.05;
			}
		}
		
		for ( EquipmentInstance eq : inventory.getEquipments()) {
			hp += eq.enchant;
		}
		
		return hp;
	}
	
	@Override
	public int getMaxMana() {
		int mana = spirit * 5;
		if(profession.value >= 100) {
			if ( level >= 110 ) { 
				mana *= 6;
			} else if ( level >= 100 ) {
				mana *= 5;
			} else if ( level >= 70 ) {
				mana *= 4;
			} else if ( level >= 40 ) {
				mana *= 3;
			}
		}
		return mana;
	}
	
	/**
	 * @return the lastMovement
	 */
	public long getLastMovement() {
		return lastMovement;
	}

	/**
	 * @param l the lastMovement to set
	 */
	public void setLastMovement(long l) {
		this.lastMovement = l;
	}

	/**
	 * @return the {@code Client} instance for this {@code User},
	 * or null if the player is not online 
	 */
	public AbstractClient getClient() {
		return client;
	}
	
	public void setProficiency(WeaponType t, int level, long exp) {
		if ( level > 0 || exp > 0 )
			proficiencies.put(t, new WeaponProficiency(this, t, level, exp));
	}
	
	public HashMap<WeaponType, WeaponProficiency> getProficiencies() {
		return proficiencies;
	}

	public void setSkill(Skill s, int level, long exp) {
		if ( level >= 0 || exp >= 0 )
			skills.put(s, new SkillProficiency(this, s, level, exp));
	}
	
	
	
	/**
	 * @return the skills
	 */
	public HashMap<Skill, SkillProficiency> getSkills() {
		return skills;
	}


	public SkillProficiency getSkillProficiency(Skill skill) {
		return skills.get(skill);
	}
	
	public WeaponProficiency getWeaponProficiency(WeaponType t) {
		return proficiencies.get(t);
	}

	public class Inventory {
		
		private static final int CAPACITY = 40;
		public static final int INVENTORY = 0;
		public static final int HELM = 1;
		public static final int NECKLACE = 2;
		public static final int ARMOR = 3;
		public static final int RIGHT_HAND = 4;
		public static final int LEFT_HAND = 5;
		public static final int RING = 6;
		public static final int TALISMAN = 7;
		public static final int BOOTS = 8;
		public static final int GARMENT = 9;
		// public static final int ATTACK_TALISMAN = 10;
		// public static final int DEFENSE_TALISMAN = 11;
		// public static final int STEED = 12;	
		private int position = 0;
		private final ItemInstance[] items = new ItemInstance[CAPACITY];
		private final EquipmentInstance[] equipments = new EquipmentInstance[10];
		/**
		 * Add an item to the Players inventory and update the client through
		 * an ItemInformation Packet
		 * @param item to be added to the inventory
		 * @return true if the item is successfully added to the inventory,
		 * false if the inventory was full
		 */
		public boolean addItem(ItemInstance item) {
			if(position < CAPACITY) {
				items[position++] = item;
				// Send a packet to the client to add the item to the inventory
				if(sent) item.new ItemInformationPacket(Mode.DEFAULT, INVENTORY).send(client);
				return true;
			}
			return false;
		}
		
		/**
		 * Remove an item from the Players inventory and update the client through
		 * an ItemUsage packet
		 * @param item to be removed from the inventory
		 * @return true if the item is successfully removed from the inventory,
		 * false if the inventory did not contain the item
		 */
		public boolean removeItem(ItemInstance item) {
			for ( int i = 0; i < position; i++ ) {
				if (items[i] == item ) {
					for ( int j = i + 1; j < position; j++ ) {
						items[j-1] = items[j];
					}
					position--;
					// Send a packet to the client to remove the item from the inventory
					if(sent) new ItemUsage(item.uniqueIdentifier, INVENTORY, ItemUsage.Mode.RemoveInventory).build().send(client);
					return true;
				}
			}
			return false;
		}
		
		/**
		 * Equip an item. If the inventory contains the item, remove it from the
		 * inventory first. The item which used the equipment slot before this
		 * equip interaction, is transfered to the inventory. 
		 * Send a ItemInformation packet such that the newly equipped item
		 * appears in the client
		 * @param slot
		 * @param equipment
		 * @return true if successfully equipped the item, or false if not.
		 */
		public boolean equip(int slot, EquipmentInstance equipment) {
			if ( slot > 0 && slot < 11 ) {
				if ( contains(equipment) ) {
					// Remove the item from the inventory
					removeItem(equipment);
				}
				unequip(slot);
				equipments[slot] = equipment;
				// Send a packet to the client to equip the item at the equipment slot
				if(sent) equipment.new ItemInformationPacket(Mode.DEFAULT, slot).send(client);
				return true;
			} else {
				// Item can't be equipped
				return false;
			}
		}
	
		/**
		 * Unequip an item, and transfer the item to the inventory.
		 * @param slot
		 * @return true if the inventory is not full
		 */
		public boolean unequip(int slot) {
			// Slot should be an equipment slot and inventory should not be full
			if ( slot > 0 && slot < 11 && !isFull()) {
				EquipmentInstance oldItem = equipments[slot];
				if ( oldItem != null ) {
					addItem(oldItem);
					equipments[slot] = null;
					// Send a packet to the client to remove the item from the equipment slot
					if(sent) new ItemUsage(oldItem.uniqueIdentifier, slot, ItemUsage.Mode.RemoveEquipment).build().send(client);
				}
				return true;
			}
			return false;
		}
	
		/**
		 * @param item
		 * @return true if the inventory contains the given item
		 */
		public boolean contains(ItemInstance item) {
			for ( int i = 0; i < position; i++ )
				if ( items[i] == item )
					return true;
			return false;
		}
		
		/**
		 * @return true if the inventory is empty
		 */
		public boolean isEmpty() {
			return position == 0;
		}
		
		/**
		 * @return true if the inventory is full
		 */
		public boolean isFull() {
			return position == CAPACITY;
		}
		
		/**
		 * @return an array of items in the inventory
		 */
		public ItemInstance[] getItems() {
			ItemInstance[] result = new ItemInstance[position];
			for ( int i = 0; i < position; i++ )
				result[i] = items[i];
			return result;
		}
		
		/**
		 * @return the equipments for this Player
		 */
		public EquipmentInstance[] getEquipments() {
			int amount = 0;
			for ( int i = 0; i < equipments.length; i++ )
				if ( equipments[i] != null )
					amount++;
			EquipmentInstance[] result = new EquipmentInstance[amount];
			for ( int i = 0, k = 0; i < equipments.length; i++ )
				if ( equipments[i] != null )
					result[k++] = equipments[i];
			return result;
		}
		
		/**
		 * @param slot
		 * @return the SID for the equipment in a given slot, or 0.
		 */
		public long getEquipmentSID(int slot) {
			if(equipments[slot] != null)
				return equipments[slot].itemPrototype.identifier;
			return 0;
		}
		
		private boolean sent = false;

		/**
		 * Send the equipments and inventory contents to the client
		 */
		public void send() {
			if(!sent){
				for ( int i = 0; i < position; i++ )
					items[i].new ItemInformationPacket(Mode.DEFAULT, INVENTORY).send(client);
				for ( int i = 1; i < equipments.length; i++ )
					if ( equipments[i] != null )
						equipments[i].new ItemInformationPacket(Mode.DEFAULT, i).send(client);
				sent = true;
			}
		}
		
	}
	
	public PacketWriter characterInformation() {
		String spouseName = getSpouse();
		int size = 71 + this.name.length() + spouseName.length();
		
		return new PacketWriter(PacketType.CHAR_INFO_PACKET, size)
		.putUnsignedInteger(this.identity)
		.putUnsignedInteger(this.mesh)
		.putUnsignedShort(this.hairstyle)
		.putUnsignedInteger(this.gold)
		.putUnsignedInteger(this.cps)
		.putUnsignedInteger(this.experience)
		.setOffset(46)
		.putUnsignedShort(this.strength)
		.putUnsignedShort(this.dexterity)
		.putUnsignedShort(this.vitality)
		.putUnsignedShort(this.spirit)
		.putUnsignedShort(this.getAttributePoints())
		.putUnsignedShort(this.HP)
		.putUnsignedShort(this.mana)
		.putUnsignedShort(this.pkPoints)
		.putUnsignedByte(this.level)
		.putUnsignedByte(this.profession.value)
		.setOffset(65)
		.putUnsignedByte(this.rebornCount)
		.putBoolean(true) // Display names
		.putUnsignedByte(2) // String count
		.putUnsignedByte(this.name.length())
		.putString(this.name)
		.putUnsignedByte(spouseName.length())
		.putString(spouseName);
	}

	public void sendProficiencies() {
		for ( WeaponProficiency wp : proficiencies.values())
			wp.sendSkill();
	}
	
	public void sendSkills(){
		for ( SkillProficiency skill : skills.values() )
		{
			System.out.println(skill.getSkill());
			skill.sendSkill();
		}
		
	}
	
	public void sendStamina() {
		new UpdatePacket(client.getPlayer()).setAttribute(UpdatePacket.Mode.Stamina, (long) getStamina()).build().send(client);
	}

	@Override
	public void notify(PacketWriter writer) {
		writer.send(client);
	}
	
	public boolean isPromotable()
	{
		if(level > 15 && profession.value < 11)
			return true;
		if(level > 40 && profession.value < 12)
			return true;
		if(level > 70 && profession.value < 13)
			return true;
		if(level > 100 && profession.value < 14)
			return true;
		if(level > 110 && profession.value < 15)
			return true;
		return false; 
	}
	
	
	public enum Profession
	{
		InternTrojan                  (10),
		Trojan                        (11),
		VeteranTrojan                 (12),
		TigerTrojan                   (13),
		DragonTrojan                  (14),
		TrojanMaster                  (15),
		InternWarrior                 (20),
		Warrior                       (21),
		BrassWarrior                  (22),
		SilverWarrior                 (23),
		GoldWarrior                   (24),
		WarriorMaster                 (25),
		InternArcher                  (40),
		Archer                        (41),
		EagleArcher                   (42),
		TigerArcher                   (43),
		DragonArcher                  (44),
		ArcherMaster                  (45),
		InternTaoist                 (100),
		Taoist                       (101),
		WaterTaoist                  (132),
		WaterWizard                  (133),
		WaterMaster                  (134),
		WaterSaint                   (135),
		FireTaoist                   (142),
		FireWizard                   (143),
		FireMaster                   (144),
		FireSaint                    (145);
		
		public int value; 
		
		private Profession(int value)
		{
			this.value = value;
		}
		
		public static Profession valueOf(int value){
			for ( Profession prof : Profession.values() ) {
				if ( prof.value == value )
					return prof;
			}
			return null;
		}
		
		/** 
		 * Computes the profession of the player regardless of promotions, and see whether it is
		 * the profession number of an interntrojan. 
		 */
		public boolean isTrojan(){
			return (value - (value%10)) == InternTrojan.value;
		}
		
		/** 
		 * Computes the profession of the player regardless of promotions, and see whether it is
		 * the profession number of an internwarrior. 
		 */
		public boolean isWarrior(){
			return (value - (value%10)) == InternWarrior.value;
		}
		
		/** 
		 * Computes the profession of the player regardless of promotions, and see whether it is
		 * the profession number of an internarcher. 
		 */
		public boolean isArcher(){
			return (value - (value%10)) == InternArcher.value;
		}
		
		/** 
		 * Checks whether the person is a taoist or not. 
		 */
		public boolean isTaoist(){
			switch(this)
			{
				case InternTaoist:
				case Taoist:
				case WaterTaoist:
				case WaterWizard:
				case WaterMaster:
				case WaterSaint:
				case FireTaoist:
				case FireWizard:
				case FireMaster:
				case FireSaint: 
					return true;
				default:
					return false;
			}
		}

	}
	
} public class GuildMember {
	
	private Player player = null; // The player object could be usefull for guild chat purposes etc.
	private String name = null;  // if player is not online, there is no player object, else there is. 
	private GuildRank rank; // e.g. Guild Leader 
	private int donation; 
	private Guild guild;
	private String bulletin; 
	
	/** Not entirely sure if this one is actually ever needed. */ 
	public GuildMember(Guild guild, Player player, GuildRank rank, int donation)
	{
		this.guild = guild;
		this.player = player;
		this.rank = rank;
		this.donation = donation; 
	}
	
	public GuildMember(Guild guild, String name, GuildRank rank, int donation)
	{
		this.guild = guild; 
		this.name = name;
		this.rank = rank;
		this.donation = donation; 
	}
	
	/**
	 *  Can be used as a check to see whether you should use getPlayer() or getName(). 
	 *  @return
	 */
	public boolean isOnline()
	{
		return (player != null); // if there is a player object, the player ought to be online. 
	}
	
	public void setPlayer(Player p)
	{
		// no check for p.getName() == name, cause we will do it at a higher level.
		this.player = p; 
		this.name = null; 
	}
	
	public Player getPlayer(){
		return player;
	}
	
	// returns player.getName() if there is a player instance, else it returns the name. 
	public String getName(){
		return (player == null) ? name : player.getName(); 
	}
	
	public void donate(int amount){
		donation += amount;
	}
	
	public void goOffline(){
		name = player.getName();
		player = null;
	}

	/**
	 * @return the rank
	 */
	public GuildRank getRank() {
		return rank;
	}

	/**
	 * @return the donation
	 */
	public int getDonation() {
		return donation;
	}
	
	public Guild getGuild(){
		return guild; 
	}
} public abstract class AbstractPacketHandler implements PacketHandler {
	protected Packet packet; 
	
	public AbstractPacketHandler(Packet packet) { 
		this.packet = packet; 
	}
	
	@Override
	public PacketWriter build() {
		return new PacketBuilder(packet).build(); 
	}
} public class GeneralData implements PacketHandler {
	
	private final SubType subType;
	private final long timestamp;
	private long identity;
	
	private long dwParam;
	private int wParam1;
	private int wParam2;
	private int wParam3;
	
	private IncomingPacket ip;
	
	/**
	 * Construct an GeneralData packet filled with data from
	 * an Incoming packet
	 * @param ip
	 */
	public GeneralData(IncomingPacket ip) {
		this.timestamp = ip.readUnsignedInt(4);
		this.identity = ip.readUnsignedInt(8);
		this.subType = SubType.valueOf(ip.readUnsignedShort(22));
		this.dwParam = ip.readUnsignedInt(12);
		this.wParam1 = ip.readUnsignedShort(16);
		this.wParam2 = ip.readUnsignedShort(18);
		this.wParam3 = ip.readUnsignedShort(20);
		this.ip = ip;
	}
	
	/**
	 * Construct a GeneralData packet with given data
	 * @param subType
	 * @param entity
	 */
	public GeneralData(SubType subType, Entity entity) {
		this.timestamp = System.currentTimeMillis() & 0xFFFFFFFF;
		this.subType = subType;
		this.identity = entity.getIdentity();
	}
	
	/**
	 * @return the identity
	 */
	public long getIdentity() {
		return identity;
	}

	/**
	 * @param identity the identity to set
	 * @return this GeneralData instance (builder pattern)
	 */
	public GeneralData setIdentity(long identity) {
		this.identity = identity;
		return this;
	}

	/**
	 * @return the dwParam
	 */
	public long getDwParam() {
		return dwParam;
	}

	/**
	 * @param dwParam the dwParam to set
	 * @return this GeneralData instance (builder pattern)
	 */
	public GeneralData setDwParam(long dwParam) {
		this.dwParam = dwParam;
		return this;
	}

	/**
	 * @return the wParam1
	 */
	public int getwParam1() {
		return wParam1;
	}

	/**
	 * @param wParam1 the wParam1 to set
	 * @return this GeneralData instance (builder pattern)
	 */
	public GeneralData setwParam1(int wParam1) {
		this.wParam1 = wParam1;
		return this;
	}

	/**
	 * @return the wParam2
	 */
	public int getwParam2() {
		return wParam2;
	}

	/**
	 * @param wParam2 the wParam2 to set
	 * @return this GeneralData instance (builder pattern)
	 */
	public GeneralData setwParam2(int wParam2) {
		this.wParam2 = wParam2;
		return this;
	}

	/**
	 * @return the wParam3
	 */
	public int getwParam3() {
		return wParam3;
	}

	/**
	 * @param wParam3 the wParam3 to set
	 * @return this GeneralData instance (builder pattern)
	 */
	public GeneralData setwParam3(int wParam3) {
		this.wParam3 = wParam3;
		return this;
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "GeneralData [subType=" + subType + ", timestamp=" + timestamp
				+ ", identity=" + identity + ", dwParam=" + dwParam
				+ ", wParam1=" + wParam1 + ", wParam2=" + wParam2
				+ ", wParam3=" + wParam3 + "]";
	}

	@Override
	public void handle(GameServerClient client) {
		Player hero = client.getPlayer();
		switch(subType){
		case GET_SURROUNDINGS:
			hero.spawn();
			break;
		case JUMP:
			if(System.currentTimeMillis() - client.getPlayer().getLastMovement() > 550) 
			{
				hero.jump((int) (dwParam & 0xFFFF), (int) (dwParam >> 16), wParam3, ip);
				client.getPlayer().setLastMovement(System.currentTimeMillis());
			}
			else
			{
                new PacketSerializer(
			    	new MessagePacket(MessagePacket.SYSTEM, client.getPlayer().getName(), "Sorry, you can't jump yet.")
				    	.setMessageType(MessageType.TOPLEFT)
					    .setARGB(0xFFFF0000)
                ).serialize().send(client);
//				new GeneralData(SubType.LOCATION, hero)
//					.setDwParam(hero.getLocation().getMap().getMapID())
//					.setwParam1(hero.getLocation().getxCord())
//					.setwParam2(hero.getLocation().getyCord())
//					.build().send(client);
				
				new PacketBuilder(new GeneralDataPacket(SubType.LOCATION, hero)
						.setDwParam(hero.getLocation().getMap().getMapID())
						.setwParam1(hero.getLocation().getxCord())
						.setwParam2(hero.getLocation().getyCord())
				).build().send(client);
			}
			break;
		case LOCATION:
			{ // Update location
				Location location = hero.getLocation();
				
				new GeneralData(SubType.LOCATION, hero)
					.setDwParam(location.getMap().getMapID())
					.setwParam1(location.getxCord())
					.setwParam2(location.getyCord())
					.build().send(client);
				
				/*
				 * This packet is a bit unknown for now, but when sent, the
				 * map name is shown in the center of the screen and appears in the minimap
				 */
				new PacketWriter(PacketType.MAP_STATUS, 24)
					.putUnsignedInteger(System.currentTimeMillis() & 0xFFFFFFFF)
					.putUnsignedInteger(hero.getIdentity())
					.putUnsignedInteger(0xFFFFFFFFl)
					.putUnsignedShort(location.getxCord())
					.putUnsignedShort(location.getyCord())
					.setOffset(22).putUnsignedByte(0x68).send(client);
				
				// Retrieve map status TODO : Status from map
				// Sound plays
				new PacketWriter(PacketType.MAP_STATUS, 16)
					.putUnsignedInteger(location.getMap().getMapID()) // Map ID
					.putUnsignedInteger(location.getMap().getMapID()) // Map unique ID
					.putUnsignedInteger(0x0000) // Map status
				.send(client);
			}
		
			new UpdatePacket(hero)
				.setAttribute(UpdatePacket.Mode.RaiseFlag, hero.getFlags())
				.setAttribute(UpdatePacket.Mode.Stamina, (long) hero.getStamina())
				.setAttribute(UpdatePacket.Mode.HP, (long) hero.getHP())
				.setAttribute(UpdatePacket.Mode.Mana, (long) hero.getMana())
				.setAttribute(UpdatePacket.Mode.MaxHP, (long) hero.getMaxHP())
				.setAttribute(UpdatePacket.Mode.MaxMana, (long) hero.getMaxMana())
				.setAttribute(UpdatePacket.Mode.XPCircle, 0L)
				.setAttribute(UpdatePacket.Mode.Level, (long) hero.getLevel())
				.setAttribute(UpdatePacket.Mode.LocationPoint, 0l)
				.build().send(client);
			// Retrieve weather TODO : Weather from MAP
			new PacketWriter(PacketType.WEATHER_PACKET, 20)
				.putUnsignedByte(3) // Map effect
				.setOffset(8).putUnsignedInteger(30) // Intensity
				.putUnsignedInteger(75) // Direction
				.putUnsignedInteger(1) // colour
				.send(client);
			
			// Set PK Mode  TODO Retrieve from player
			new GeneralData(SubType.CHANGE_PK_MODE, hero).setDwParam(3).build().send(client);
			// TODO Start XP Circle
			
			// Send inventory, equips, skills and professions
			hero.inventory.send();
			hero.sendProficiencies();
			hero.sendSkills();
			new GeneralData(SubType.CONFIRM_PROFS, hero).build().send(client);
			new GeneralData(SubType.CONFIRM_SPELLS, hero).build().send(client);
			
			// Send friends and enemies
			new GeneralData(SubType.CONFIRM_FRIENDS, hero).build().send(client);
			
			// Send guilds
			GuildMember gm = client.getPlayer().getGuildMember();
			/** 
			new PacketWriter(PacketType.STRING_PACKET, 11 + 1 + gm.getGuild().getGuildName().length())
				.putUnsignedInteger(gm.getGuild().getUID()) // Guild  TODO
				.putUnsignedByte(3) // TYPE: GuildName 
				.putUnsignedByte(1) // String count
				.putUnsignedByte(gm.getGuild().getGuildName().length()) // Str length
				.putString(gm.getGuild().getGuildName())
				.send(client); */ 
			
			//new String_Packet(GuildRequestType.RequestName, gm.getGuild().getUID()).handle(client);
			
			new PacketWriter(PacketType.GUILD_INFORMATION, 40)
				.putUnsignedInteger(gm.getGuild().getUID()) // Guild ID
				.putUnsignedInteger(gm.getDonation()) // Donation
				.putUnsignedInteger(gm.getGuild().getFund()) // Fund
				.putUnsignedInteger(gm.getGuild().getMemberCount()) // Members count
				.setOffset(20).putUnsignedByte(gm.getRank().rank) // position
				.putString(gm.getGuild().getGuildLeaderName(), 16) // leader
				.send(client);
						
			new String_Packet(GuildRequestType.RequestName, gm.getGuild().getUID()).handle(client);

            new PacketSerializer(
		    	new MessagePacket(MessagePacket.SYSTEM, hero.getName(), "Guild bulletin!")
				    .setMessageType(MessagePacket.MessageType.GUILDBULLETIN)
            ).serialize().send(client);
			new GeneralData(SubType.CONFIRM_GUILD, hero).build().send(client);
			
			
			// TODO figure when to send this.. 
			new String_Packet(StringPacketType.EnemyGuild).handle(client);
			new String_Packet(StringPacketType.AllyGuild).handle(client);
			
			// Send animations
			/* 2NDMetempsychosis   for 2nd RB light of vigor
			 * letter1  - letter7 for King rank etc.
			 * coronet3 & coronet4 for unknown animations
			 */
			String animation = "letter7";
			new PacketWriter(PacketType.STRING_PACKET, 11 + animation.length())
				.putUnsignedInteger(hero.getIdentity())
				.putUnsignedByte(10) // Type
				.putUnsignedByte(1) // Str count
				.putUnsignedByte(animation.length()) // Str length
				.putString(animation)
				.send(client);
			
			// Send default messages
            new PacketSerializer(
                new MessagePacket(MessagePacket.SYSTEM, hero.getName(), "Players online " + client.getGameServer().getAmountOfPlayers())
                    .setMessageType(MessagePacket.MessageType.SYSTEM)
            ).serialize().send(client);
			
			hero.inventory.send();

			new GeneralData(GeneralData.SubType.COMPLETE_LOGIN, hero).build().send(hero);
			
			break;
		case PORTAL:
			{ // Update Location
				Location location = new Location(Map.CentralPlain, 250, 180);
				client.getPlayer().setLocation(location);
				new GeneralData(SubType.LOCATION, hero)
					.setDwParam(location.getMap().getMapID())
					.setwParam1(location.getxCord())
					.setwParam2(location.getyCord())
					.build().send(client);
				break;
			}
		/** just resend the packet, like change direction, though in future you might want to set something
		 * for increased stamina regen.
		 */
		case CHANGE_ACTION:
		{
			for(Player p : client.getPlayer().view.getPlayers())
			{
				if(client.getPlayer() == p)
					continue; 
				build().send(p.getClient());
			}
			break;
		}
		case CHANGE_DIRECTION:
		{
			Location oldLocation = client.getPlayer().getLocation();
			client.getPlayer().setLocation(new Location(oldLocation.map, oldLocation.xCord, 
										   oldLocation.yCord, wParam3)); 
			
			for(Player p : client.getPlayer().view.getPlayers())
			{
				if(client.getPlayer() == p)
					continue; 
				build().send(p.getClient());
			}
			break;
		}
		case END_XP_LIST:
		{
			client.getPlayer().setXPON(false);
			client.getPlayer().setXpRing(0);
			this.build().send(client);
			break;
		}
		
		default:
			System.out.println("Unimplemented " + subType.toString());
			break;
		}
	}
	
	

	@Override
	public PacketWriter build() {
		return new PacketWriter(PacketType.GENERAL_DATA_PACKET, 0x18)
			.putUnsignedInteger(timestamp) // 4
			.putUnsignedInteger(identity) // 8
			.putUnsignedInteger(dwParam) // 12
			.putUnsignedShort(wParam1) // 16
			.putUnsignedShort(wParam2) // 18
			.putUnsignedShort(wParam3) // 20
			.putUnsignedShort(subType.type); //22
	}

	/**
	 * Enumeration for the subtypes
	 * @author Thomas Gmelig Meyling
	 */
	public static enum SubType {
		UNIMPLEMENTED		(0x00), // 0
		LOCATION 			(0x4A), // 74
		HOTKEYS	(75),
		CONFIRM_FRIENDS(76),
		CONFIRM_PROFS(77),
		CONFIRM_SPELLS(78),
		CHANGE_DIRECTION 	(0x4F), // 79
		CHANGE_ACTION(81),
		PORTAL 				(0x55), // 85
		TELEPORT(86),
		LEVEL(92),
		END_XP_LIST(93),
		REVIVE(94),
		DELETE_CHARACTER(95),
		CHANGE_PK_MODE		(0x60), // 96
		CONFIRM_GUILD		(97),
		BEGIN_MINE(99),
		TEAMLEADERLOCATION(101),
		ENTITY_SPAWN(102),
		BEGIN_XP(103),
		CompleteMapChange(104),
		TeamMateLoc(106),
		CorrectCords(108),
		UNLEARN_SPELL(109),
		UNLEARN_PROF(110),
		Shop(111),
		OpenShop(113),
		GET_SURROUNDINGS	(0x72), //114
		REMOTE_COMMANDS(116),
		END_TRANSFORM(118),
		END_FLY(120),
		PLAYSOUND(124),
		PickupCashEffect(121),
		Dialog(126),
		GuardJump(129),
		COMPLETE_LOGIN		(130),
		ENTITY_REMOVE		(132),
		JUMP				(0x85), // 133
		RemoveWeaponMesh(135),
		RemoveWeaponMesh2(136),
		PathFinding(162);
		
		private final int type;
		
		private SubType(int type) {
			this.type = type;
		}
		
		public static SubType valueOf(int type) {
			for (SubType st : SubType.values()) {
				if (st.type == type) {
					return st;
				}
			}
			return UNIMPLEMENTED;
		}
	}
	
} @Type(type = PacketType.GENERAL_DATA_PACKET)
@Bidirectional(handler = GeneralDataPacketHandler.class)
@PacketLength(length = 24)
public class GeneralDataPacket extends Packet {
	
	/**
	 * Construct an GeneralData packet filled with data from
	 * an Incoming packet
	 * @param ip
	 */
	public GeneralDataPacket(IncomingPacket ip) {
		super(ip);
	}


    /**
	 * Construct a GeneralData packet with given data
	 * @param location
	 * @param entity
	 */
	public GeneralDataPacket(net.co.java.packets.GeneralData.SubType location, Entity entity) {
		super(null);
		this.setType(PacketType.GENERAL_DATA_PACKET);
		this.timestamp = System.currentTimeMillis() & 0xFFFFFFFF;
		this.subType = location;
		this.identity = entity.getIdentity();
	}

	@PacketValue(type = PacketValueType.ENUM_VALUE)
	@Offset(22)
	private SubType subType;
	
	@PacketValue(type = PacketValueType.UNSIGNED_INT)
	@Offset(value = 4)
	private long timestamp;
	
	@PacketValue(type = PacketValueType.UNSIGNED_INT)
	@Offset(value = 8)
	private long identity;
	
	@PacketValue(type = PacketValueType.UNSIGNED_INT)
	@Offset(value = 12)
	private long dwParam;
	
	@PacketValue(type = PacketValueType.UNSIGNED_SHORT)
	@Offset(value = 16)
	private int wParam1;
	
	@PacketValue(type = PacketValueType.UNSIGNED_SHORT)
	@Offset(value = 18)
	private int wParam2;
	
	@PacketValue(type = PacketValueType.UNSIGNED_SHORT)
	@Offset(value = 20)
	private int wParam3;


		
	public SubType getSubType() {
		return subType;
	}
	
	public GeneralDataPacket setSubType(SubType subType) {
		this.subType = subType;
		return this;
	}
	
	public long getTimestamp() {
		return timestamp;
	}

	public GeneralDataPacket setTimestamp(long timestamp) {
		this.timestamp = timestamp;
		return this;
	}
	
	public long getIdentity() {
		return identity;
	}

	public GeneralDataPacket setIdentity(long identity) {
		this.identity = identity;
		return this;
	}

	public long getDwParam() {
		return dwParam;
	}

	public GeneralDataPacket setDwParam(long dwParam) {
		this.dwParam = dwParam;
		return this;
	}

	public int getwParam1() {
		return wParam1;
	}

	public GeneralDataPacket setwParam1(int wParam1) {
		this.wParam1 = wParam1;
		return this;
	}

	public int getwParam2() {
		return wParam2;
	}

	public GeneralDataPacket setwParam2(int wParam2) {
		this.wParam2 = wParam2;
		return this;
	}

	public int getwParam3() {
		return wParam3;
	}

	public GeneralDataPacket setwParam3(int wParam3) {
		this.wParam3 = wParam3;
		return this;
	}

//	/**
//	 * Enumeration for the subtypes
//	 * @author Thomas Gmelig Meyling
//	 */
//	public static enum SubType {
//		UNIMPLEMENTED		(0x00), // 0
//		LOCATION 			(0x4A), // 74
//		HOTKEYS	(75),
//		CONFIRM_FRIENDS(76),
//		CONFIRM_PROFS(77),
//		CONFIRM_SPELLS(78),
//		CHANGE_DIRECTION 	(0x4F), // 79
//		CHANGE_ACTION(81),
//		PORTAL 				(0x55), // 85
//		TELEPORT(86),
//		LEVEL(92),
//		END_XP_LIST(93),
//		REVIVE(94),
//		DELETE_CHARACTER(95),
//		CHANGE_PK_MODE		(0x60), // 96
//		CONFIRM_GUILD		(97),
//		BEGIN_MINE(99),
//		TEAMLEADERLOCATION(101),
//		ENTITY_SPAWN(102),
//		BEGIN_XP(103),
//		CompleteMapChange(104),
//		TeamMateLoc(106),
//		CorrectCords(108),
//		UNLEARN_SPELL(109),
//		UNLEARN_PROF(110),
//		Shop(111),
//		OpenShop(113),
//		GET_SURROUNDINGS	(0x72), //114
//		REMOTE_COMMANDS(116),
//		END_TRANSFORM(118),
//		END_FLY(120),
//		PLAYSOUND(124),
//		PickupCashEffect(121),
//		Dialog(126),
//		GuardJump(129),
//		COMPLETE_LOGIN		(130),
//		ENTITY_REMOVE		(132),
//		JUMP				(0x85), // 133
//		RemoveWeaponMesh(135),
//		RemoveWeaponMesh2(136),
//		PathFinding(162);
//		
//		private final int type;
//		
//		private SubType(int type) {
//			this.type = type;
//		}
//		
//		public static SubType valueOf(int type) {
//			for (SubType st : SubType.values()) {
//				if (st.type == type) {
//					return st;
//				}
//			}
//			return UNIMPLEMENTED;
//		}
//	}
} @Type(type = PacketType.MESSAGE_PACKET)
@Bidirectional(handler = MessagePacketHandler.class)
@PacketLength(length = 32)
public class MessagePacket extends Packet /*implements PacketHandler*/ {

	public final static String SYSTEM = "SYSTEM";
	public final static String ALL_USERS = "ALLUSERS";

    @PacketValue(type = PacketValueType.UNSIGNED_INT)
    @Offset(4)
	private long aRGB = 0xFFFFFFFFL;

    @PacketValue(type = PacketValueType.ENUM_VALUE)
    @Offset(8)
   	private MessageType type = MessageType.TALK;

    @PacketValue(type = PacketValueType.UNSIGNED_INT)
    @Offset(12)
	private long chatID = 0L;

    @PacketValue(type = PacketValueType.UNSIGNED_INT)
    @Offset(16)
	private long send_avatar = 0;

    @PacketValue(type = PacketValueType.UNSIGNED_INT)
    @Offset(20)
	private long recv_avatar = 0;

    @PacketValue(type = PacketValueType.UNSIGNED_BYTE)
    @Offset(24)
    private short stringCount = 4; /** Always 4 with suffix  */

    @PacketValue(type = PacketValueType.STRING_WITH_LENGTH)
    @Offset(25)
	private String from;

    @PacketValue(type = PacketValueType.STRING_WITH_LENGTH)
    @Offset(26)
	private String to;

    @PacketValue(type = PacketValueType.BYTE)
    @Offset(27)
    private short suffixLength = 0; // always an empty string.

    @PacketValue(type = PacketValueType.STRING_WITH_LENGTH)
    @Offset(28)
	private String message;
	
	/**
	 * Construct a new Message packet with the from, to and message
	 * values already set, and the aRGB, type and chatID set to default
	 * values
	 * @param from
	 * @param to
	 * @param message
	 */
	public MessagePacket(String from, String to, String message) {
        super(null);
        this.setType(PacketType.MESSAGE_PACKET);
		this.from = from;
		this.to = to;
		this.message = message;
	}
	
	/**
	 * Construct a new Message packet based on a IncomingPacket
	 * @param ip
	 */
	public MessagePacket(IncomingPacket ip) {
        super(ip);
	}
	
	/**
	 * Set the colour for this message
	 * @param ARGB
	 * @return this MessagePacket (builder pattern)
	 */
	public MessagePacket setARGB(long ARGB) {
		this.aRGB = ARGB;
		return this;
	}
	
	/**
	 * @return ARGB.
	 */
	public long getARGB() {
		return this.aRGB;
	}

	
	/**
	 * @return MessageType for this message
	 */
	public MessageType getMessageType() {
		return type;
	}
	
	/**
	 * Set the ChatID for this message
	 * @param chatID
	 * @return this MessagePacket (builder pattern)
	 */
	public MessagePacket setChatID(long chatID) {
		this.chatID = chatID;
		return this;
	}


    /**
     * Set the MessageType for this message
     * @param mt
     * @return this MessagePacket (builder pattern)
     */
	public MessagePacket setMessageType(MessageType mt) {
		this.type = mt;
		return this;
	}
	
	/**
	 * @return CHATID
	 */
	public long getChatID() {
		return chatID;
	}
	
	/**
	 * @return the sender
	 */
	public String getFrom() {
		return from;
	}
	
	/**
	 * @return the reciepient
	 */
	public String getTo() {
		return to;
	}
	
	/**
	 * @return the message
	 */
	public String getMessage() {
		return message;
	}


	/**
	 * An enumeration of various Message Types for the MessagePackets
	 * @author Jan-Willem Gmelig Meyling
	 * @author Thomas Gmelig Meyling
	 */
	public static enum MessageType {
		TALK(2000), WHISPER(2001), ACTION(2002), TEAM(2003), GUILD(2004),
		TOPLEFT(2005), CLAN(2006), SYSTEM(2007), YELL(2008), FRIEND(2009),
		CENTER(2011), GHOST(2013), SERVICE(2014), TIP(2015), WORLD(2021),
		DIALOG(2100), LOGININFO(2101), VENDORHAWK(2104), WEBSITE(2105),
		CLEAR(2108), RIGHTCORNER(2109), GUILDBULLETIN(2111), TRADEBOARD(2201),
		FRIENDBOARD(2202), TEAMBOARD(2203), GUILDBOARD(2204), OTHERSBOARD(2205),
		BROADCAST(2500);		
		
		final int type;
		
		/**
		 * Constructor for MessageType enum values
		 * @param type
		 */
		private MessageType(int type) {
			this.type = type;
		}
		
		/**
		 * @param value
		 * @return {@code MessageType} for the given value
		 * @throws RuntimeException when there is no such MessageType
		 */
		public static MessageType valueOf(int value) {
			for ( MessageType mt : MessageType.values() )
				if ( mt.type == value )
					return mt;
			throw new RuntimeException("Unimplemented MessageType for " + value);
		}
	}
} public abstract class Packet {
	private IncomingPacket ip; 
	
	public PacketHeader header; 
	
	public Packet(IncomingPacket ip) { 
		this.ip = ip; 
		if(ip == null) { 
			header = new PacketHeader(); 
		}
	}
	
	public PacketType getType() {
        return (ip == null) ? header.type : ip.getPacketType();
    };


	public void setType(PacketType type) { 
		header.type = type; 
	}
	
	public int getLength() { 
		return header.length; 
	}
	
	public void setLength(int length) { 
		header.length = length; 
	}
	
	public IncomingPacket getIncomingPacket() { 
		return ip; 
	}
	
	public static final class PacketHeader { 
		
		@PacketValue(type = PacketValueType.ENUM_VALUE)
		private PacketType type; 
		
		@PacketValue(type = PacketValueType.UNSIGNED_SHORT)
		private int length; 
		
		public PacketType getType() {
			return this.type; 
		}
		
		public void setType(PacketType type) { 
			this.type = type; 
		}
		
		public int getLength() { 
			return this.length; 
		}
		
		public void setLength(int length) { 
			this.length = length; 
		}
	}
} public  enum PacketType {
	
	/**
	 * This packet is sent to the Account Server during login. It contains the
	 * login information such as the Account Name, Password, and Server which
	 * the client wishes to connect to. The Account Server must check this
	 * information with the database.
	 */
	AUTH_LOGIN_PACKET	(0x041B),
	
	/**
	 * The Auth Message Packet (0x41C) is sent by the client during initial
	 * Authentication, it is sent to the game server, the game server uses the
	 * keys to determine which account to link to the client.
	 * 
	 * It is also sent by the auth server when you get your username/password
	 * wrong or the server is down for example, the packet contains the string
	 * of bytes which identify the message to display. (For example: B7 FE CE F1
	 * C6 F7 CE B4 C6 F4 B6 AF)
	 */
	AUTH_LOGIN_RESPONSE (0x041C),
	
	/**
	 * The Authorization Response (0x41F) is made by the Auth Server if the
	 * account is permitted to enter the Game Server.
	 */
	AUTH_LOGIN_FORWARD	(0x041F),
	
	/**
	 * This packet is used to send messages to other players and to instruct the
	 * client on what errors to display on login. The server uses this packet by
	 * routing it to it's destination (the other player).
	 */
	MESSAGE_PACKET		(0x03EC),

	/**
	 * The EntityMove packet, also known as the Walk packet, this packet is used
	 * by all entitys that can move, such as Players, Monsters and if you wish
	 * NPCs. The EntityMove packet, also known as the Walk packet, this packet
	 * is used by all entitys that can move, such as Players, Monsters and if
	 * you wish NPCs. The EntityMove packet, also known as the Walk packet, this
	 * packet is used by all entitys that can move, such as Players, Monsters
	 * and if you wish NPCs.
	 */
	ENTITY_MOVE_PACKET   (0x3ED),
	
	/**
	 * The Character Information packet, this packet is sent primarily during
	 * the login process to set the majority of your characters values.
	 */
	CHAR_INFO_PACKET	(0x03EE),
	
	/**
	 * Sent by the client in response to the NEW_ROLE string when the account
	 * being used to login doesn't have a character associated with it on the
	 * target server. On successfully handling the Character Creation Packet the
	 * server responds with a Chat Packet with the message "ANSWER_OK" sent to
	 * "ALLUSERS" with the type being ChatType.Dialog. The client will then
	 * disconnect and return to the login screen, or in later versions of the
	 * client proceed with the typical login procedure.
	 */
	CHARACTER_CREATION_PACKET(0x03E9),
	
	/**
	 * Send by the server to add and remove items to and from equipment slots,
	 * inventory, inspect- and trade windows
	 */
	ITEM_INFORMATION_PACKET(0x3F0),
	
	/**
	 * The General Data packet performs a variety of tasks for the client, these
	 * vary from moving the client around the game, to ending an XP skill.
	 */
	GENERAL_DATA_PACKET	(0x03F2),
	
	/**
	 * The Entity Spawn packet is used to spawn both Monsters and Characters, it
	 * does not spawn NPCs. The packet's layout changes slightly depending on
	 * which you are spawning.
	 */
	ENTITY_SPAWN_PACKET (0x03F6),
	
	STRING_PACKET(0x3F7),
	
	WEATHER_PACKET(0x3F8),
	
	/**
	 * The Entity Status packet, also known as the "Update packet" is used to
	 * change the appearance(in some cases), certain values unique to a
	 * character (such as level, stat points, exp) and show active abilities (in
	 * other cases). This packet can be used to send 1 status update, or many.
	 */
	UPDATE_PACKET(0x3F9),
	
	/**
	 * The Item Usage packet used for interacting with the warehouse and
	 * equiping/un-equiping/updating items.
	 */
	ITEM_USAGE_PACKET (0x3F1),
	
	/**
	 * The Interact packet is most commonly used for direct melee/archer
	 * attacks, but also used for certain player to player actions, such as
	 * marriage.
	 */
	INTERACT_PACKET(0x3FE),	
	
	/** for if a player loots more than 1k gold. */ 
	REWARD_PACKET(0x405),
	
	/**
	 * The Skill Packet is sent during login and when you learn a new skill.
	 */
	SKILL_PACKET(0x44F),
	
	/**
	 * The Skill Experience packet can be used to update both Skills and
	 * Proficiency.
	 */
	SKILL_UPDATE_PACKET(0x450),
	
	/**
	 * The Magic Attack Packet, used for offensive and defensive spells of all
	 * kind.
	 */
	SKILL_ANIMATION_PACKET(0x451),
	
	GUILD_INFORMATION(0x452),
	
	GUILD_REQUEST(0x453),
	
	GUILD_MEMBER_INFORMATION(0x458),
	
	/**
	 * The Weapon Proficiency Packet is sent during login and when you learn a
	 * new Proficiency.
	 */
	PROFICIENCY(0x401),
	
	
	MAP_STATUS(0x456),//,
	//PORTAL_PACKET (0x24);
	
	/** 
	 * Used to spawn an npc to the client.
	 */ 
	NPC_SPAWN_PACKET(0x7EE),
	
	/** 
	 * Used when Dialog NPCs are clicked.
	 */
	NPC_INITIAL_PACKET(0x7EF),
	
	/**
	 * Used to chat with npcs
	 */
	NPC_DIALOG_PACKET(0x7F0);
	
	private final int type;
	
	private PacketType(int type) {
		this.type = type;
	}
	
	public int getType() {
		return type;
	}
	
	/**
	 * @param id
	 * @return the {@code PacketType} for a specific ID
	 * @throws UnimplementedPacketTypeException when the
	 * {@code PacketType} could not be found.
	 */
	public static PacketType valueOf(int id) throws UnimplementedPacketTypeException {
		for ( PacketType pt : PacketType.values() ) {
			if ( pt.type == id )
				return pt;
		}
		throw new UnimplementedPacketTypeException(id);
	}
	
	public static class UnimplementedPacketTypeException extends RuntimeException {
		
		private static final long serialVersionUID = 1L;

		UnimplementedPacketTypeException(int type) {
			super("Unimplemented PacketType: " + type);
		}
		
	}
} public class PacketWriter {

	public final ByteBuffer buffer;
	private final PacketType packetType;
	
	/**
	 * Create a new PacketWriter
	 * @param packet
	 */
	public PacketWriter(IncomingPacket packet) {
		this.packetType = packet.getPacketType();
		this.buffer = packet.buffer;
		this.buffer.position(4);
	}

	/**
	 * Create a new PacketWriter
	 * @param packetType
	 * @param size
	 */
	public PacketWriter(PacketType packetType, int size) {
		this.packetType = packetType;
		this.buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
		this.putUnsignedShort(size);
		this.putUnsignedShort(this.packetType.getType());
	}
	
	/**
	 * Set the offset for this PacketWriter
	 * @param offset
	 * @return this PacketWriter
	 */
	public PacketWriter setOffset(int offset) {
		this.buffer.position(offset);
		return this;
	}
	
	/**
	 * Increment the offset for this PacketWriter
	 * @param amount
	 * @return this PacketWriter
	 */
	public PacketWriter incrementOffset(int amount) {
		return this.setOffset(this.buffer.position() + amount);
	}

	/**
	 * Method used to put a bool into the packet.
	 * 
	 * @param b
	 *            boolean value to put.
	 * @return this PacketWriter
	 */
	public PacketWriter putBoolean(boolean b) {
		return this.putUnsignedByte(b ? 1 : 0);
	}

	/**
	 * Pushes an unsigned byte to the packet Since unsigned bytes can grow up to
	 * 2^8-1, and Java bytes can only grow up to 2^7-1 We supply the byte as
	 * short.
	 * 
	 * @param ubyte
	 * @return this PacketWriter
	 */
	public PacketWriter putUnsignedByte(short ubyte) {
		buffer.put((byte) (ubyte & 0xff));
		return this;
	}

	/**
	 * Pushes an unsigned byte to the packet Since unsigned bytes can grow up to
	 * 2^8-1, and Java bytes can only grow up to 2^7-1 We supply the byte as
	 * short.
	 * 
	 * @param ubyte
	 * @return this PacketWriter
	 */
	public PacketWriter putUnsignedByte(int ubyte) {
		buffer.put((byte) (ubyte & 0xff));
		return this;
	}

	/**
	 * Pushes an unsigned short to the packet Since unsigned shorts can grow up
	 * to 2^16-1, and Java shorts can only grow up to 2^15-1 we supply the short
	 * as integer
	 * 
	 * @param ushort value to be put
	 * @return this PacketWriter
	 */
	public PacketWriter putUnsignedShort(int ushort) {
		buffer.putShort((short) (ushort & 0xffff));
		return this;
	}

	/**
	 * Pushes an unsigned integer to the packet Since unsigned integers can grow
	 * up to 2^32-1, and Java integers can only grow up to 2^31-1 we supply the
	 * integer as long
	 * 
	 * @param uint value to be put
	 * @return this PacketWriter
	 */
	public PacketWriter putUnsignedInteger(long uint) {
		buffer.putInt((int) (uint & 0xffffffffL));
		return this;
	}
	
	public PacketWriter putSignedInteger(int sint) {
		buffer.putInt(sint);
		return this;
	}
	
	/**
	 * Pushes an unsigned long to the packet. Since unsigned longs can grow
	 * up to 2^64-1, and Java integers can only grow up to 2^64-1 we supply the
	 * long as BigInteger
	 * 
	 * @param ulong value to be put
	 * @return this PacketWriter
	 */
	public PacketWriter putUnsignedLong(BigInteger ulong) {
		buffer.putLong(ulong.longValue());
		return this;
	}

	/**
	 * Method to put a Java String to a byte[] and put it to the packet
	 * 
	 * @param str value to put
	 * @return this PacketWriter
	 */
	public PacketWriter putString(String str) {
		buffer.put(str.getBytes());
		return this;
	}

	/**
	 * Method to put a Java String to a byte[] and put it to the packet
	 * 
	 * @param str value to put
	 * @param length to extend
	 * @return this PacketWriter
	 */
	public PacketWriter putString(String str, int length) {
		if(str.length() <= length) {
			this.putString(str);
			if(str.length() < length) {
				this.incrementOffset(length - str.length());
			}
		}
		return this;
	}

	/**
	 * @return the length for this packet
	 */
	public int getLength() {
		return buffer.capacity();
	}
	
	/**
	 * Send the packet in this PacketWriter to a Player
	 * @param player
	 */
	public void send(Player player) {
		send(player.getClient());
	}
	
	/**
	 * Send the packet in this PacketWriter to a List of Players
	 * @param players
	 */
	public void sendTo(List<Player> players) {
		for ( Player player : players )
			send(player);
	}
	
	/**
	 * Send the packet in this PacketWriter to an array of Players
	 * @param players
	 */
	public void sendTo(Player[] players) {
		for ( Player player : players )
			send(player);
	}

	/**
	 * Send the packet in this PacketWriter to a Client
	 * @param client
	 */
	public void send(AbstractClient client) {
		client.write(buffer);
	}

} public class String_Packet implements PacketHandler{

	private StringPacketType type; 
	private long identity; 
	private String string = null; 
	
	
	public String_Packet(IncomingPacket ip) {
		type = StringPacketType.valueOf(ip.readUnsignedByte(8));
	}
	
	public String_Packet(StringPacketType type) {
		this.type = type;
	}
	
	public String_Packet(StringPacketType type, String string, long identity) {
		this(type);
		this.string = string;
		this.identity = identity;
	}
	
	public String_Packet(GuildRequestType type, long guild_id){
		if(type == GuildRequestType.RequestName)
			this.type = StringPacketType.GuildName;
		this.identity = guild_id; 
	}

	@Override
	public PacketWriter build() {
		switch(type) {
			case Effect:
			{
				if(string != null)
				{
					return new PacketWriter(PacketType.STRING_PACKET, 11 + 1 + string.length())
						.putUnsignedInteger(identity)
						.putUnsignedByte(type.type)
						.putUnsignedByte(1)
						.putUnsignedByte(string.length())
						.putString(string);
				}
			}
		default:
			return null;
		}
	}

	@Override
	public void handle(GameServerClient client) {
		switch(type)
		{
			case GuildMemberList:
			{
				List <GuildMember> members = client.getPlayer().getGuildMember().getGuild().getMembers();
				int totalstrlength = 0;
				ArrayList<String> memberNames = new ArrayList<String>();
				
				for(int i = 0; i < members.size() && i < 10; i++) {
					memberNames.add(members.get(i).getName());
					totalstrlength += memberNames.get(i).length();
				}
				
				
				PacketWriter outgoingPacket = new PacketWriter(PacketType.STRING_PACKET, (11 + (totalstrlength + memberNames.size())))
					.putUnsignedInteger(client.getIdentity())
					.putUnsignedByte(type.type)
					.putUnsignedByte(memberNames.size());
				
				for(String s : memberNames)	{
					outgoingPacket.putUnsignedByte(s.length());
					outgoingPacket.putString(s);
				}
				
				for(GuildMember gm : members){
					new Guild_Member_Information_Packet(gm).handle(client);
				}
				
				outgoingPacket.send(client);

				break;
			}
			case GuildName:
				//String guildName = client.getPlayer().getGuildMember().getGuild().getGuildName();
				String guildName; 
				for(Guild g : client.getModel().getGuilds())
						if(g.getUID() == identity)	
						{
							guildName = g.getGuildName();
							new PacketWriter(PacketType.STRING_PACKET, 11 + 1 + guildName.length())
								.putUnsignedInteger(g.getUID())
								.putUnsignedByte(type.type)
								.putUnsignedByte(1)
								.putUnsignedByte(guildName.length())
								.putString(guildName)
								.send(client);
						}
				break;
			case EnemyGuild: 
			{
				Guild g = client.getPlayer().getGuildMember().getGuild();
				int totalstrlength = 0; 
				int strcount = 0;
				
				for(int i = 0; i < g.getEnemies().length; i++)
					if(g.getEnemies()[i] != null) {
						totalstrlength += g.getEnemies()[i].getGuildName().length();
						strcount++; 
					}
				
				PacketWriter outgoingPacket = new PacketWriter(PacketType.STRING_PACKET, 11 + strcount + totalstrlength)
					.putUnsignedInteger(client.getIdentity())
					.putUnsignedByte(type.type)
					.putUnsignedByte(strcount);
				
				for(Guild enemy : g.getEnemies())
					if(enemy != null)
					{
						outgoingPacket.putUnsignedByte(enemy.getGuildName().length());
						outgoingPacket.putString(enemy.getGuildName());
					}
				
				outgoingPacket.send(client);
				
				break;
			}
			case AllyGuild:
			{
				Guild g = client.getPlayer().getGuildMember().getGuild();
				int totalstrlength = 0; 
				int strcount = 0;
				
				for(int i = 0; i < g.getAllies().length; i++)
					if(g.getAllies()[i] != null) {
						totalstrlength += g.getAllies()[i].getGuildName().length();
						strcount++; 
					}
				
				PacketWriter outgoingPacket = new PacketWriter(PacketType.STRING_PACKET, 11 + strcount + totalstrlength)
					.putUnsignedInteger(client.getIdentity())
					.putUnsignedByte(type.type)
					.putUnsignedByte(strcount);
				
				for(Guild enemy : g.getAllies())
					if(enemy != null)
					{
						outgoingPacket.putUnsignedByte(enemy.getGuildName().length());
						outgoingPacket.putString(enemy.getGuildName());
					}
				
				outgoingPacket.send(client);
				
				break;
			}
			default:
				System.out.println("String packet with type: " + type + " not implemented.");
		}
		
	}
	
	public enum StringPacketType
	{
		Fireworks			(1),
		GuildName 			(3),
		Spouse 				(6),
		Wanted 				(8),
		MapEffect 			(9),
		Effect 				(10),
		GuildMemberList 	(11),
		QueryWanted 		(13),
		QueryPoliceWanted 	(14),
		PoliceWanted 		(15),
		ViewEquipment 		(16),
		AddDicePlayer 		(17),
		DeleteDicePlayer 	(18),
		DiceBonus 			(19),
		Sound 				(20),
		AllyGuild 			(21),
		EnemyGuild 			(22);
		                
		public final int type; 
		
		private StringPacketType(int type){
			this.type = type;
		}
		
		public static StringPacketType valueOf(int type)  {
			for ( StringPacketType spt : StringPacketType.values() ) {
				if ( spt.type == type )
					return spt;
			}
			return null; 
		}
	}

} public class UpdatePacket implements PacketWrapper {

	private final Entity entity;
	private final Map<Mode, Long> attributes = new HashMap<Mode, Long>();
	
	/**
	 * Create a new UpdatePacket
	 * @param entity
	 */
	public UpdatePacket(Entity entity) {
		this.entity = entity;
	}
	
	/**
	 * Set an attribute for this UpdatePacket
	 * @param mode
	 * @param value
	 * @return this UpdatePacket instance (builder pattern)
	 */
	public UpdatePacket setAttribute(Mode mode, Long value) {
		attributes.put(mode, value);
		return this;
	}

	@Override
	public PacketWriter build() {
		int StatusCount = attributes.size();
		
		PacketWriter pw = new PacketWriter(PacketType.UPDATE_PACKET, 12 + 12 * StatusCount)
		.putUnsignedInteger(entity.getIdentity())
		.putUnsignedInteger(StatusCount);
		
		for( Entry<Mode, Long> kv : attributes.entrySet() ) {
			pw.putUnsignedInteger(kv.getKey().mode)
			.putUnsignedInteger(kv.getValue().longValue())
			.putUnsignedInteger(2);
		}
		
		return pw;
	}
	
	public static enum Mode {
		HP(0), MaxHP(1), Mana(2), MaxMana(3), Money(4), Experience(5), PKPoints(6), Job(7), Stamina(
				9), StatPoints(11), Model(12), Level(13), Spirit(14), Vitality(
				15), Strength(16), Agility(17), GuildDonation(20), KOSeconds(22), RaiseFlag(
				26), Hairstyle(27), XPCircle(28), LuckyTime(29),  LocationPoint(255);
		
		public final int mode;
		private Mode(int i) { mode = i; }
	}

} public static enum SubType {
	UNIMPLEMENTED		(0x00), // 0
	LOCATION 			(0x4A), // 74
	HOTKEYS	(75),
	CONFIRM_FRIENDS(76),
	CONFIRM_PROFS(77),
	CONFIRM_SPELLS(78),
	CHANGE_DIRECTION 	(0x4F), // 79
	CHANGE_ACTION(81),
	PORTAL 				(0x55), // 85
	TELEPORT(86),
	LEVEL(92),
	END_XP_LIST(93),
	REVIVE(94),
	DELETE_CHARACTER(95),
	CHANGE_PK_MODE		(0x60), // 96
	CONFIRM_GUILD		(97),
	BEGIN_MINE(99),
	TEAMLEADERLOCATION(101),
	ENTITY_SPAWN(102),
	BEGIN_XP(103),
	CompleteMapChange(104),
	TeamMateLoc(106),
	CorrectCords(108),
	UNLEARN_SPELL(109),
	UNLEARN_PROF(110),
	Shop(111),
	OpenShop(113),
	GET_SURROUNDINGS	(0x72), //114
	REMOTE_COMMANDS(116),
	END_TRANSFORM(118),
	END_FLY(120),
	PLAYSOUND(124),
	PickupCashEffect(121),
	Dialog(126),
	GuardJump(129),
	COMPLETE_LOGIN		(130),
	ENTITY_REMOVE		(132),
	JUMP				(0x85), // 133
	RemoveWeaponMesh(135),
	RemoveWeaponMesh2(136),
	PathFinding(162);
		
	private final int type;
		
	private SubType(int type) {
		this.type = type;
	}
		
	public static SubType valueOf(int type) {
		for (SubType st : SubType.values()) {
			if (st.type == type) {
				return st;
			}
		}
		return UNIMPLEMENTED;
	}
} public enum GuildRequestType
{
	RequestJoin		(1),
	AcceptJoin  	(2),
	Quit 			(3),
	RequestName 	(6),
	Ally 			(7),
	UnAlly 			(8),
	Enemy 			(9),
	UnEnemy 		(10),
	Donate 			(11),
	RequestInfo 	(12),
	UpdateGuild 	(13),
	UpdateBranch 	(14),
	UniteSubSyn 	(15),
	UniteSyn	 	(16),
	SetWhiteSyn 	(17),
	SetBlackSyn 	(18),
	Leave 			(19);
		
	public final int type; 
		
	private GuildRequestType(int type){
		this.type = type;
	}
		
	public static GuildRequestType valueOf(int type)  {
		for ( GuildRequestType grt : GuildRequestType.values() ) {
			if ( grt.type == type )
				return grt;
		}
		return null; 
	}
} public static enum MessageType {
	TALK(2000), WHISPER(2001), ACTION(2002), TEAM(2003), GUILD(2004),
	TOPLEFT(2005), CLAN(2006), SYSTEM(2007), YELL(2008), FRIEND(2009),
	CENTER(2011), GHOST(2013), SERVICE(2014), TIP(2015), WORLD(2021),
	DIALOG(2100), LOGININFO(2101), VENDORHAWK(2104), WEBSITE(2105),
	CLEAR(2108), RIGHTCORNER(2109), GUILDBULLETIN(2111), TRADEBOARD(2201),
	FRIENDBOARD(2202), TEAMBOARD(2203), GUILDBOARD(2204), OTHERSBOARD(2205),
	BROADCAST(2500);		
		
	final int type;
		
	/**
	 * Constructor for MessageType enum values
	 * @param type
	 */
	private MessageType(int type) {
		this.type = type;
	}
		
	/**
	 * @param value
	 * @return {@code MessageType} for the given value
	 * @throws RuntimeException when there is no such MessageType
	 */
	public static MessageType valueOf(int value) {
		for ( MessageType mt : MessageType.values() )
			if ( mt.type == value )
				return mt;
		throw new RuntimeException("Unimplemented MessageType for " + value);
	}
} public enum StringPacketType
{
	Fireworks			(1),
	GuildName 			(3),
	Spouse 				(6),
	Wanted 				(8),
	MapEffect 			(9),
	Effect 				(10),
	GuildMemberList 	(11),
	QueryWanted 		(13),
	QueryPoliceWanted 	(14),
	PoliceWanted 		(15),
	ViewEquipment 		(16),
	AddDicePlayer 		(17),
	DeleteDicePlayer 	(18),
	DiceBonus 			(19),
	Sound 				(20),
	AllyGuild 			(21),
	EnemyGuild 			(22);
		                
	public final int type; 
		
	private StringPacketType(int type){
		this.type = type;
	}
		
	public static StringPacketType valueOf(int type)  {
		for ( StringPacketType spt : StringPacketType.values() ) {
			if ( spt.type == type )
				return spt;
		}
		return null; 
	}
} public class PacketSerializer {
	protected int totalStringLength = 0;
    protected int currentStringLength = 0;
	protected final Packet packet;
	protected Class<? extends Packet> clasz;
	protected PacketWriter pw = null; 
	
	public PacketSerializer(Packet packet) { 
		this.packet = packet;
	
		try {
			clasz = Packets.getInstance().getPacketClass(packet.getType());
			setTotalStringLength(packet);
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
	}
	
	/** 
	 * @param packet
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	protected void setTotalStringLength(Packet packet) throws IllegalArgumentException, IllegalAccessException {
		for(Field field : clasz.getDeclaredFields()) {
			if(field.isAnnotationPresent(PacketValue.class)) {
                field.setAccessible(true);
				PacketValue value = field.getAnnotation(PacketValue.class);
				switch(value.type()) {
					case STRING_WITH_LENGTH:
						totalStringLength += ((String) field.get(packet)).length();
					default:
						break;
				}
			}
		}
	}
	
	public PacketWriter serialize() { 
		int length = 0; 
		
		if(clasz.isAnnotationPresent(PacketLength.class)) {
			PacketLength packetLength = clasz.getAnnotation(PacketLength.class);
			length = packetLength.length();
		}
		
		pw = new PacketWriter(packet.getType(), length + totalStringLength);
		
		for(Field field : clasz.getDeclaredFields()) {
			if(field.isAnnotationPresent(PacketValue.class)) {
				if(field.isAnnotationPresent(Offset.class)){	
					PacketValue value = field.getAnnotation(PacketValue.class);
					Offset offset = field.getAnnotation(Offset.class);					
					field.setAccessible(true);
					try {
						switch(value.type()) { 
							case ENUM_VALUE:
								setEnum(packet, field, value, offset);
								break;
							case STRING: 
								pw.setOffset(offset.value());
								//setString(packet, field, value, offset); 
								pw.putString((String) field.get(packet), 16);
								break;
							case STRING_WITH_LENGTH: 
								setStringWithLength(packet, field, value, offset);
							case UNSIGNED_BYTE:
							case BYTE:
							case UNSIGNED_SHORT: 
							case SHORT:
							case UNSIGNED_INT:
							case INT:
							default:
								setPrimitive(packet, field, value, offset);
								break;
						}	
					} catch (IllegalArgumentException e) {
						e.printStackTrace();
					} catch (IllegalAccessException e) {
						e.printStackTrace();
					} catch (NoSuchFieldException e) {
						e.printStackTrace();
					} catch (SecurityException e) {
						e.printStackTrace();
					}
				}
			}
		}
		
		return pw; 
	}
	
	private void setEnum(Packet packet, Field field, PacketValue value,
			Offset offset) throws IllegalArgumentException, IllegalAccessException,
			NoSuchFieldException, SecurityException {
		pw.setOffset(offset.value());
		pw.putUnsignedShort(EnumUtility.getEnumValue(field.get(packet)));
	}

	private void setStringWithLength(Packet packet, Field field,
			PacketValue value, Offset offset) throws IllegalArgumentException, IllegalAccessException {
		pw.setOffset(offset.value() + currentStringLength);
		pw.putUnsignedByte(((String) field.get(packet)).length());
        pw.putString(((String) field.get(packet)));
        currentStringLength += ((String) field.get(packet)).length();
	}
	


	public void setPrimitive(Packet packet, Field field, PacketValue value, Offset offset) 
			throws IllegalArgumentException, IllegalAccessException { 
		pw.setOffset(offset.value());
		switch(value.type()) {
			case BOOLEAN: 
				pw.putBoolean(field.getBoolean(packet));
				break;
			case UNSIGNED_BYTE:
				pw.putUnsignedByte(field.getInt(packet));
				break;
			case UNSIGNED_SHORT: 
				pw.putUnsignedShort(field.getInt(packet));
				break;
			case UNSIGNED_INT:
				pw.putUnsignedInteger(field.getLong(packet));
				break;
			case INT:
				pw.putSignedInteger(field.getInt(packet));
				break;
			default: 
				break;
		}
	}
} public class GameServerClient extends AbstractClient {
	
	private final GameServer gameServer;
	private final Model model;

	public GameServerClient(GameServer gameServer, AsynchronousSocketChannel channel) {
		super(channel);
		this.gameServer = gameServer;
		this.model = gameServer.getModel();
	}

	@Override
	protected void handle(IncomingPacket incomingPacket) throws AccessException,
			IOException {
		Player player = this.getPlayer();
				
		switch(incomingPacket.getPacketType()) {
		case AUTH_LOGIN_RESPONSE:
			// Read the identity and token from the packet
			// and set these as keys for the cipher
			long identity = incomingPacket.readUnsignedInt(4);
			long token = incomingPacket.readUnsignedInt(8);
			this.setIdentity(identity);
			AuthorizationPromise promise = model.getAuthorizationPromise(identity);
			this.setKeys(token, identity);
			// Inform the client that the login was successful
			if (promise.hasCharacter())
			{	
				// Create the Entity object for the player, bound to
				// the current identity and client thread
				player = model.loadPlayer(promise);
				this.setPlayer(player);
                model.getGameServerTicks().addEntity(player);
				player.setClient(this);

                new PacketSerializer(new MessagePacket(MessagePacket.SYSTEM, MessagePacket.ALL_USERS, "ANSWER_OK")
                        .setMessageType(MessageType.LOGININFO)).serialize().send(this);
				// Send the character information packet
				player.characterInformation().send(this);
			}
			else
			{
                new PacketSerializer(new MessagePacket(MessagePacket.SYSTEM, MessagePacket.ALL_USERS, "NEW_ROLE")
                        .setMessageType(MessageType.LOGININFO)).serialize().send(this);
			}
			break;
		case ENTITY_MOVE_PACKET:
			player.walk(incomingPacket.readUnsignedByte(8), incomingPacket);
            model.getGameServerTicks().didInteract(player);
			break;
		case ITEM_USAGE_PACKET:
			new ItemUsage(incomingPacket).handle(this);
			break;
		/*case MESSAGE_PACKET:
			MessagePacket mp = new MessagePacket(incomingPacket);
			
			if(mp.getMessage().startsWith("/")) {
				new Command(mp).handle(this);
			} else {
				mp.handle(this);
			}
			break; */
		case CHARACTER_CREATION_PACKET:
			if (model.createCharacter(new Character_Creation_Packet(incomingPacket)))
			{
                new PacketSerializer(
                    new MessagePacket(MessagePacket.SYSTEM, MessagePacket.ALL_USERS, "ANSWER_OK")
                    .setMessageType(MessageType.LOGININFO)).serialize().send(this);
				this.close();
			}
			else
			{
                new PacketSerializer(
                    new MessagePacket(MessagePacket.SYSTEM, MessagePacket.ALL_USERS, "Failed to create character. Character name already in use.")
                    .setMessageType(MessageType.DIALOG))
                    .serialize().send(this);
			}
			break;
		case INTERACT_PACKET:
			new InteractPacket(incomingPacket).handle(this);
			break;
		case NPC_DIALOG_PACKET:
//			NPC_Dialog packet = player.getActiveDialog();
//			if(packet != null)
//			{
//				int input = incomingPacket.readUnsignedByte(10);
//				packet.setInput(input); 
//				packet.handle(this);
//			}
			break;
		case GUILD_MEMBER_INFORMATION:
			//new Guild_Member_Information_Packet(incomingPacket).handle(this, null);
			break;
		case GUILD_REQUEST:
			new Guild_Request_Packet(incomingPacket).handle(this);
			break;	
		case STRING_PACKET:
			new String_Packet(incomingPacket).handle(this);
			break;
		default:
            try {
//				PacketDeserializer pd = PacketDeserializerFactory.valueOf(incomingPacket.getPacketType())
//					.getInstance(incomingPacket);
//				pd.getHandlerStrategy(pd.deserialize()).handle(this);
                System.out.println("No old fashioned way of handling the packet, will use the "
                + "PacketDeserializer and PacketHandler");
                PacketDeserializer pd = new PacketDeserializer(incomingPacket);
                Packets.getHandlerStrategy(pd.deserialize()).handle(this);
            } catch (DeserializationException e) {
                e.printStackTrace();
            }
			break;
		}
	}

	@Override
	protected void connected() {
		gameServer.connect(this);
	}

	@Override
	protected void disconnected() {
		gameServer.disconnect(this);
	}

	/**
	 * @return the Model for this {@code Client}
	 */
	public Model getModel() {
		return model;
	}

	/**
	 * @return the GameServer for this {@code Client}
	 */
	public GameServer getGameServer() {
		return gameServer;
	}

} public enum Map {
	
	/* http://www.elitepvpers.com/forum/co2-programming/177758-request-map-id-complete-list.html */
	Desert(1000),
	CentralPlain(1002),
	PromotionCenter(1004),
	PhoenixCastle(1011),
	BirdIsland(1015),
	ApeMoutain(1020);
	
	
	private final Integer id;
	
	private final List<Entity> entities = new CopyOnWriteArrayList<Entity>();

	/**
	 * Construct a new Map with a given id
	 * @param id
	 */
	private Map(int id) {
		this.id = Integer.valueOf(id);
		System.out.println("Initializing map: " + this.toString() + " (" + this.id + ")");
	}
	
	/**
	 * @return the id for a Map
	 */
	public int getMapID() {
		return id;
	}
	
	/**
	 * Add an {@code Entity} to this Map, for example due a windspell, portal or spawn.
	 * Send an EntitySpawn packet to surrounding players.
	 * @param entity
	 */
	public void addEntity(Entity entity) {
		if(!entities.contains(entity)) {
			entities.add(entity);
		}
	}
	
	/**
	 * Remove an {@code Entity} from this Map - death or teleport.
	 * Send an Entity Remove packet to surrounding players.
	 * @param entity
	 */
	public void removeEntity(Entity entity) {
		entities.remove(entity);
	}
	
	/**
	 * @return all entities in this map.
	 */
	public List<Entity> getEntities() {
		return entities;
	}
	
	public static Map valueOf(int id)
	{
		for ( Map mp : Map.values() ) {
			if ( mp.id == id )
				return mp;
		}
		return null;
	}
}
import net.co.java.entity.Location;
import net.co.java.entity.Player;
import net.co.java.guild.GuildMember;
import net.co.java.packets.AbstractPacketHandler;
import net.co.java.packets.GeneralData;
import net.co.java.packets.GeneralDataPacket;
import net.co.java.packets.MessagePacket;
import net.co.java.packets.Packet;
import net.co.java.packets.PacketType;
import net.co.java.packets.PacketWriter;
import net.co.java.packets.String_Packet;
import net.co.java.packets.UpdatePacket;
import net.co.java.packets.GeneralData.SubType;
import net.co.java.packets.Guild_Request_Packet.GuildRequestType;
import net.co.java.packets.MessagePacket.MessageType;
import net.co.java.packets.String_Packet.StringPacketType;
import net.co.java.packets.serialization.PacketSerializer;
import net.co.java.server.GameServerClient;
import net.co.java.server.Map;
package net.co.java.packets.packethandlers;



public class GeneralDataPacketHandler extends AbstractPacketHandler {
	

	public GeneralDataPacketHandler(Packet packet) {
		super(packet);
	}

	@Override
	public void handle(GameServerClient client) {
		if(!(packet instanceof GeneralDataPacket)) 
			return; 
		
		GeneralDataPacket gpd = (GeneralDataPacket) packet; 
		
		Player hero = client.getPlayer();
		switch(gpd.getSubType()){
		case GET_SURROUNDINGS:
			hero.spawn();
			break;
		case JUMP:
			if(System.currentTimeMillis() - client.getPlayer().getLastMovement() > 550) 
			{
                client.getModel().getGameServerTicks().didInteract(client.getPlayer());
				hero.jump((int) (gpd.getDwParam() & 0xFFFF), (int) (gpd.getDwParam() >> 16), 
						gpd.getwParam3(), packet.getIncomingPacket());
				client.getPlayer().setLastMovement(System.currentTimeMillis());
			}
			else
			{
                new PacketSerializer(
                    new MessagePacket(MessagePacket.SYSTEM, client.getPlayer().getName(), "Sorry, you can't jump yet.")
                        .setMessageType(MessageType.TOPLEFT)
                        .setARGB(0xFFFF0000)
                ).serialize().send(client);
				new GeneralData(SubType.LOCATION, hero)
					.setDwParam(hero.getLocation().getMap().getMapID())
					.setwParam1(hero.getLocation().getxCord())
					.setwParam2(hero.getLocation().getyCord())
					.build().send(client);
			}
			break;
		case LOCATION:
			{ // Update location
				Location location = hero.getLocation();
				
				new GeneralData(SubType.LOCATION, hero)
					.setDwParam(location.getMap().getMapID())
					.setwParam1(location.getxCord())
					.setwParam2(location.getyCord())
					.build().send(client);
				
				/*
				 * This packet is a bit unknown for now, but when sent, the
				 * map name is shown in the center of the screen and appears in the minimap
				 */
				new PacketWriter(PacketType.MAP_STATUS, 24)
					.putUnsignedInteger(System.currentTimeMillis() & 0xFFFFFFFF)
					.putUnsignedInteger(hero.getIdentity())
					.putUnsignedInteger(0xFFFFFFFFl)
					.putUnsignedShort(location.getxCord())
					.putUnsignedShort(location.getyCord())
					.setOffset(22).putUnsignedByte(0x68).send(client);
				
				// Retrieve map status TODO : Status from map
				// Sound plays
				new PacketWriter(PacketType.MAP_STATUS, 16)
					.putUnsignedInteger(location.getMap().getMapID()) // Map ID
					.putUnsignedInteger(location.getMap().getMapID()) // Map unique ID
					.putUnsignedInteger(0x0000) // Map status
				.send(client);
			}
		
			new UpdatePacket(hero)
				.setAttribute(UpdatePacket.Mode.RaiseFlag, hero.getFlags())
				.setAttribute(UpdatePacket.Mode.Stamina, (long) hero.getStamina())
				.setAttribute(UpdatePacket.Mode.HP, (long) hero.getHP())
				.setAttribute(UpdatePacket.Mode.Mana, (long) hero.getMana())
				.setAttribute(UpdatePacket.Mode.MaxHP, (long) hero.getMaxHP())
				.setAttribute(UpdatePacket.Mode.MaxMana, (long) hero.getMaxMana())
				.setAttribute(UpdatePacket.Mode.XPCircle, 0L)
				.setAttribute(UpdatePacket.Mode.Level, (long) hero.getLevel())
				.setAttribute(UpdatePacket.Mode.LocationPoint, 0l)
				.build().send(client);
			// Retrieve weather TODO : Weather from MAP
			new PacketWriter(PacketType.WEATHER_PACKET, 20)
				.putUnsignedByte(3) // Map effect
				.setOffset(8).putUnsignedInteger(30) // Intensity
				.putUnsignedInteger(75) // Direction
				.putUnsignedInteger(1) // colour
				.send(client);
			
			// Set PK Mode  TODO Retrieve from player
			new GeneralData(SubType.CHANGE_PK_MODE, hero).setDwParam(3).build().send(client);
			// TODO Start XP Circle
			
			// Send inventory, equips, skills and professions
			hero.inventory.send();
			hero.sendProficiencies();
			hero.sendSkills();
			new GeneralData(SubType.CONFIRM_PROFS, hero).build().send(client);
			new GeneralData(SubType.CONFIRM_SPELLS, hero).build().send(client);
			
			// Send friends and enemies
			new GeneralData(SubType.CONFIRM_FRIENDS, hero).build().send(client);
			
			// Send guilds
			GuildMember gm = client.getPlayer().getGuildMember();