public abstract class Entity implements Spawnable, Serializable {

	private static final long serialVersionUID = -8544332465232461099L;
	protected transient final long identity;
	protected final String name;
	
	protected volatile int mesh;
	protected volatile int hairstyle;
	protected volatile Location location;
	protected volatile int HP;
	protected volatile int mana;
	protected volatile int level;
	protected volatile long flags = 0;
	public final transient View view;

	/**
	 * Construct a new entity
	 * @param identity
	 * @param mesh
	 * @param hairstyle
	 * @param name
	 * @param location
	 * @param HP
	 */
	public Entity(long identity, int mesh, int hairstyle, String name, Location location, int HP) {
		this.identity = identity;
		this.location = location;
		this.mesh = mesh;
		this.hairstyle = hairstyle;
		this.name = name;
		this.HP = HP;
		this.view = new ViewImpl(this);
	}
	
	/**
	 * Set the location for this entity
	 * @param location
	 */
	public void setLocation(Location location) {
		if(this.location != null && this.location.getMap() != location.getMap()) {
				this.location.getMap().removeEntity(this);
		}
		location.getMap().addEntity(this);
		this.location = location;
		this.view.update(location);
	}
	
	/**
	 * The walk method is called when an Entity moves.
	 * This can be called from AI (monsters/NPCs) or an Entity Move
	 * packet from the client. When existing, the packet can be
	 * forwarded. Otherwise, it needs to be created runtime.
	 * 
	 * When the Entity moves, old surroundings should receive
	 * an entity remove packet. New surroundings should receive
	 * an entity spawn packet. The intersection of surroundings
	 * should receive the delegated entity move packet.
	 * 
	 * @param direction
	 * @param walkPacket
	 */	
	public void walk(int direction, IncomingPacket walkPacket) {
		PacketWriter pw = walkPacket != null ? new PacketWriter(walkPacket) : 
			new PacketWriter(PacketType.ENTITY_MOVE_PACKET, 12)
				.putUnsignedInteger(identity)
				.putUnsignedByte(direction)
				.putUnsignedByte(1);
		// Send walk packet to me and surroundings
		for(Player player : view.getPlayers())
			pw.send(player);
		// Change location and send spawn / entity remove packets
		setLocation(this.location.inDirection(direction));
	}
	
	/**
	 * @param x
	 * @param y
	 * @param direction
	 * @param jumpPacket
	 */
	public void jump(int x, int y, int direction, IncomingPacket jumpPacket) {
//		PacketWriter pw = jumpPacket != null ? new PacketWriter(jumpPacket) : 
//			new GeneralData(GeneralData.SubType.JUMP, this).setDwParam(y << 16 | x).build();
	
		PacketWriter pw = new PacketBuilder(new GeneralDataPacket(SubType.JUMP, this)
				.setDwParam(y << 16 | x)).build();
		// Send jump packet to me and surroundings
		for(Player player : view.getPlayers())
			pw.send(player);
		// Change location and send spawn / entity remove packets
		setLocation(new Location(this.location.map, x, y, direction));
			
	}

	/**
	 * This method is called to setup the initial spawn. It adds the Entity
	 * to a new Map and sends the Spawn packet to the surrounding Players.
	 */
	public void spawn() {
		if(location != null)
			setLocation(location);
	}

	/**
	 * Remove the entity
	 */
	public void remove() {
		if(location != null) {
			location.getMap().removeEntity(this);
			PacketWriter remove = new GeneralData(SubType.ENTITY_REMOVE, this).build();
			for(Player player : view.getPlayers())
				if(player!=this)
					remove.send(player);
		}
	}

	/**
	 * Set a flag for this Entity
	 * @param flag
	 */
	public void setFlag(Flag flag) {
		flags |= flag.value;
	}

	/**
	 * Remove a flag for this Entity
	 * @param flag
	 */
	public void removeFlag(Flag flag) {
		flags ^= flag.value;
	}

	/**
	 * Check if this Entity has a given flag
	 * @param flag
	 * @return true if the Entity has the flag
	 */
	public boolean hasFlag(Flag flag) {
		return (flags & flag.value) == flag.value;
	}

	/**
	 * @return the Flags for this entity
	 */
	public long getFlags() {
		return flags;
	}

	public int getMesh() {
		return mesh;
	}

	public void setMesh(int mesh) {
		this.mesh = mesh;
	}
	
	public int getHairstyle() {
		return hairstyle;
	}
	
	public void setHairstyle(int hairstyle) {
		this.hairstyle = hairstyle;
	}

	public int getMana() {
		return mana;
	}

	public void setMana(int mana) {
		this.mana = mana;
	}

	public int getLevel() {
		return level;
	}

	public void setLevel(int level) {
		this.level = level;
	}

	public long getIdentity() {
		return identity;
	}
	
	public String getName() {
		return name;
	}
	
	public int getHP() {
		return HP;
	}
	
	public void setHP(int HP) {
		// Prevent health points below zero
		if ( HP < 0 )
			HP = 0;
		int maxHP = getMaxHP();
		if ( HP > maxHP )
			HP = maxHP;
		this.HP = HP;
	}
	
	public boolean isDead() {
		return HP == 0;
	}
	
	public void dealDamage(int damage) {
		setHP(HP - damage);
	}
	
	public abstract int getMaxHP();
	public abstract int getMaxMana();
	public abstract void notify(PacketWriter writer);

	@Override
	public Location getLocation() {
		return location;
	}

	@Override
	public boolean inView(Spawnable spawnable) {
		return location.inView(spawnable.getLocation());
	}

	@Override
	public String toString() {
		return "Entity [identity=" + identity + ", name=" + name + ", mesh="
				+ mesh + ", hairstyle=" + hairstyle + ", location=" + location
				+ ", HP=" + HP + ", mana=" + mana + ", level=" + level + "]";
	}

	/**
	 * Types for Flags
	 * 
	 * @author Jan-Willem Gmelig Meyling
	 * @author Thomas Gmelig Meyling
	 *
	 */
	public static enum Flag {
		CLEAR(0),
		
		/**
		 * Once you attack white/red name players, your name will turn blue and
		 * players or guards/patrols may attack you without punishment
		 */
		BLUENAME(1),
		
		/**
		 * Poison animation
		 */
		POISON(2),
		
		/**
		 * Exp bar
		 */
		EXP(2 << 3),
		
		/**
		 * Unknown for now
		 */
		TRADE_OR_DEAD(2 << 4),
		
		/**
		 * Team leader (star)
		 */
		TEAMLEADER(2 << 5),
		
		/**
		 * Star of accuracy beneath entity
		 */
		ACCURACY(2 << 6),
		
		/**
		 * Shield around entity
		 */
		SHIELD(2 << 7),
				
		
		/**
		 * With this Flag an Stigma appears beneath the Entity. The Stigma always is in the
		 * new fixed form (red). When you want to use another animation render for different
		 * spell levels, you can use a StringPacket instead:
		 * 
		 * <pre>
		 * {@code
		 *  String animation = "attackup35"; // See ini/3DEffect.ini
		 * 	new PacketWriter(PacketType.STRING_PACKET, 11 + animation.length())
		 * 	  .putUnsignedInteger(client.getPlayer().getIdentity())
		 * 	  .putUnsignedByte(10) // Type
		 * 	  .putUnsignedByte(1) // Str count
		 * 	  .putUnsignedByte(animation.length()) // Str length
		 * 	  .putString(animation)
		 * 	  .send(client);
		 * }
		 * </pre>
		 */
		STIGMA(2 << 8),
		
		REVIVE_COUNTER_GHOST(2 << 9),
		FADE_AWAY(2 << 10),
		REDNAME(2 << 13),
		BLACKNAME(2 << 14),
		REFLECT(2 << 16),
		SUPERMAN(2 << 17),
		BALL(2 << 18), BALL2(2 << 19),
		INVISIBLE(2 << 21),
		CYCLONE(2 << 22),
		DODGE(2 << 25),
		FLY(2 << 26),
		INTENSIFY(2 << 27),
		LUCKYTIME(2 << 29),
		PRAY(2 << 30),
		FATAL_STRIKE(0x800000000000l),
		CHAIN_LIGHTING(268435456l);
		
		public final long value;
		private Flag(long value) { this.value = value; }
	}

	
} 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 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.Entity;
import net.co.java.entity.Location;
import net.co.java.entity.Player;
import net.co.java.guild.GuildMember;
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;
			{
				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);