gt: 		return getCollidingObject(entity, area) != null;
context: /* $Id$ */
  /***************************************************************************
   *                      (C) Copyright 2003 - Marauroa                      *
   ***************************************************************************
   ***************************************************************************
   *                                                                         *
   *   This program is free software; you can redistribute it and/or modify  *
   *   it under the terms of the GNU General Public License as published by  *
   *   the Free Software Foundation; either version 2 of the License, or     *
   *   (at your option) any later version.                                   *
   *                                                                         *
   ***************************************************************************/
  package games.stendhal.server.core.engine;
  
  import java.awt.Point;
  import java.awt.Rectangle;
  import java.awt.geom.Rectangle2D;
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  import org.apache.log4j.Logger;
  
  import games.stendhal.common.CRC;
  import games.stendhal.common.CollisionDetection;
  import games.stendhal.common.Debug;
  import games.stendhal.common.Direction;
  import games.stendhal.common.Line;
  import games.stendhal.common.MathHelper;
  import games.stendhal.common.filter.FilterCriteria;
  import games.stendhal.common.grammar.Grammar;
  import games.stendhal.common.tiled.LayerDefinition;
  import games.stendhal.common.tiled.TileSetDefinition;
  import games.stendhal.server.core.config.zone.TeleportationRules;
  import games.stendhal.server.core.events.MovementListener;
  import games.stendhal.server.core.events.ZoneEnterExitListener;
  import games.stendhal.server.core.rp.StendhalRPAction;
  import games.stendhal.server.core.rule.EntityManager;
  import games.stendhal.server.entity.ActiveEntity;
  import games.stendhal.server.entity.Blood;
  import games.stendhal.server.entity.Entity;
  import games.stendhal.server.entity.RPEntity;
  import games.stendhal.server.entity.creature.AttackableCreature;
  import games.stendhal.server.entity.creature.BabyDragon;
  import games.stendhal.server.entity.creature.Creature;
  import games.stendhal.server.entity.creature.DomesticAnimal;
  import games.stendhal.server.entity.creature.Sheep;
  import games.stendhal.server.entity.item.Item;
  import games.stendhal.server.entity.mapstuff.portal.OneWayPortalDestination;
  import games.stendhal.server.entity.mapstuff.portal.Portal;
  import games.stendhal.server.entity.mapstuff.spawner.CreatureRespawnPoint;
  import games.stendhal.server.entity.mapstuff.spawner.PassiveEntityRespawnPoint;
  import games.stendhal.server.entity.mapstuff.spawner.PassiveEntityRespawnPointFactory;
  import games.stendhal.server.entity.mapstuff.spawner.SheepFood;
  import games.stendhal.server.entity.npc.NPC;
  import games.stendhal.server.entity.npc.SpeakerNPC;
  import games.stendhal.server.entity.player.Player;
  import marauroa.common.game.IRPZone;
  import marauroa.common.game.RPObject;
  import marauroa.common.game.RPSlot;
  import marauroa.common.net.OutputSerializer;
  import marauroa.common.net.message.TransferContent;
  import marauroa.server.game.rp.MarauroaRPZone;
  
  public class StendhalRPZone extends MarauroaRPZone {
  	/**
  	 * When calculating the danger level, short distance between creatures
  	 * (weighted with creature levels) has an effect of adding to the total
  	 * score. Adjust this constant to make the effect stronger or weaker. Value
  	 * of 0 would mean that the danger level is equal to the level of the
  	 * highest level creature.
  	 */
  	private static final double DANGER_WEIGHT_CREATURE_DENSITY = 1.0;
  
  	TeleportationRules teleRules = new TeleportationRules();
  
  	/** the logger instance. */
  	private static final Logger logger = Logger.getLogger(StendhalRPZone.class);
  
  	private final List<TransferContent> contents;
  
  	/** Data layer for zone attributes. */
  	private ZoneAttributes attributes;
  
  	private Point entryPoint;
  
  	private final List<Portal> portals;
  
  	private final List<NPC> npcs;
  
  	/**
  	 * The sheep foods in the zone.
  	 */
  	private final List<SheepFood> sheepFoods;
  
  	private final List<CreatureRespawnPoint> respawnPoints;
  
  	private final List<PassiveEntityRespawnPoint> plantGrowers;
  
  	private final List<RPEntity> playersAndFriends;
  
  	private final List<Player> players;
  
  	/**
  	 * The blood spills.
  	 */
  	private final List<Blood> bloods;
  
  	//private boolean teleportAllowed = true;
  
  	private boolean moveToAllowed = true;
  
  	/**
  	 * Objects that implement MovementListener.
  	 */
  	private final List<MovementListener> movementListeners;
  
  
  	private final List<ZoneEnterExitListener> zoneListeners;
  
  	/**
  	 * A set of all items that are lying on the ground in this zone. This set is
  	 * currently only used for plant growers, and these might be changed so that
  	 * this set is no longer needed, so try to avoid using it.
  	 */
  	private final Set<Item> itemsOnGround;
  
  	/** contains data to if a certain area is walkable. */
  	public CollisionDetection collisionMap;
  
  	/** Contains data to verify is someone is in a PK-free area. */
  	public CollisionDetection protectionMap;
  
  	/** Position of this zone in the world map. */
  	private boolean interior = true;
  
  	private int level;
  
  	private int x;
  
  	private int y;
  
  	/** User representable name of the zone. */
  	private final String readableName;
  
  	/** Facing directions for portals. */
  	private final int UP_FN = 8;
  	private final int UP_FE = 9;
  	private final int UP_FS = 10;
  	private final int UP_FW = 11;
  	private final int DOWN_FN = 12;
  	private final int DOWN_FE = 13;
  	private final int DOWN_FS = 14;
  	private final int DOWN_FW = 15;
  	private final List<Integer> stairsUp = new ArrayList<Integer>() {{
  		add(2);
  		add(UP_FN);
  		add(UP_FE);
  		add(UP_FS);
  		add(UP_FW);
  	}};
  	private final List<Integer> stairsDown = new ArrayList<Integer>() {{
  		add(3);
  		add(DOWN_FN);
  		add(DOWN_FE);
  		add(DOWN_FS);
  		add(DOWN_FW);
  	}};
  
  	public StendhalRPZone(final String name) {
  		super(name);
  
  		contents = new LinkedList<TransferContent>();
  		entryPoint = null;
  		portals = new LinkedList<Portal>();
  		itemsOnGround = new HashSet<Item>();
  		bloods = new LinkedList<Blood>();
  		npcs = new LinkedList<NPC>();
  		sheepFoods = new LinkedList<SheepFood>();
  		respawnPoints = new LinkedList<CreatureRespawnPoint>();
  		plantGrowers = new LinkedList<PassiveEntityRespawnPoint>();
  		players = new LinkedList<Player>();
  		playersAndFriends = new LinkedList<RPEntity>();
  
  		movementListeners = new LinkedList<MovementListener>();
  		zoneListeners = new LinkedList<ZoneEnterExitListener>();
  
  		collisionMap = new CollisionDetection();
  		protectionMap = new CollisionDetection();
  		String readable = createReadableName(name);
  		if (!name.equals(readable)) {
  			readableName = readable;
  			// Ensure that the zone has attribute layer if it has a readable
  			// name that differs from the internal name
  			ZoneAttributes attr = new ZoneAttributes(this);
  			setAttributes(attr);
  		} else {
  			readableName = null;
  		}
  	}
  
  	public StendhalRPZone(final String name, final int width, final int height) {
  		this(name);
  		collisionMap.init(width, height);
  	}
  
  	public StendhalRPZone(final String name, final StendhalRPZone zone) {
  		this(name);
  		if (attributes != null) {
  			// Try to match the attribute layer name with the rest of the zone
  			attributes.setBaseName(zone.getName());
  		}
  		contents.addAll(zone.contents);
  		collisionMap = zone.collisionMap;
  		protectionMap  = zone.protectionMap;
  
  		this.zoneid = new ID(name);
  	}
  
  	/**
  	 * Get blood (if any) at a specified zone position.
  	 *
  	 * @param x
  	 *            The X coordinate.
  	 * @param y
  	 *            The Y coordinate.
  	 *
  	 * @return The blood, or <code>null</code>.
  	 */
  	public Blood getBlood(final int x, final int y) {
  		for (final Blood blood : bloods) {
  			if ((blood.getX() == x) && (blood.getY() == y)) {
  				return blood;
  			}
  		}
  
  		return null;
  	}
  
  	public List<NPC> getNPCList() {
  		return npcs;
  	}
  
  	public List<Portal> getPortals() {
  		return portals;
  	}
  
  	public Portal getPortal(final Object reference) {
  		if (reference == null) {
  			return null;
  		}
  		for (final Portal portal : portals) {
  			if (reference.equals(portal.getIdentifier())) {
  				return portal;
  			}
  		}
  
  		return null;
  	}
  
  	/**
  	 * Get the portal (if any) at a specified zone position.
  	 *
  	 * @param x
  	 *            The X coordinate.
  	 * @param y
  	 *            The Y coordinate.
  	 *
  	 * @return The portal, or <code>null</code>.
  	 */
  	public Portal getPortal(final int x, final int y) {
  		for (final Portal portal : portals) {
  			if ((portal.getX() == x) && (portal.getY() == y)) {
  				return portal;
  			}
  		}
  
  		return null;
  	}
  
  	/**
  	 * Get the list of sheep foods in the zone.
  	 *
  	 * @return The list of sheep foods.
  	 */
  	public List<SheepFood> getSheepFoodList() {
  		return sheepFoods;
  	}
  
  	public List<CreatureRespawnPoint> getRespawnPointList() {
  		return respawnPoints;
  	}
  
  	/**
  	 * Add a creature respawn point to the zone.
  	 *
  	 * @param point
  	 *            The respawn point.
  	 */
  	public void add(final CreatureRespawnPoint point) {
  		respawnPoints.add(point);
  	}
  
  	/**
  	 * Remove a creature respawn point from the zone.
  	 *
  	 * @param point
  	 *            The respawn point.
  	 */
  	public void remove(final CreatureRespawnPoint point) {
  		respawnPoints.remove(point);
  	}
  
  	public List<PassiveEntityRespawnPoint> getPlantGrowers() {
  		return plantGrowers;
  	}
  
  	/** We reserve the first 64 portals ids for hand made portals. */
  	private int maxPortalNumber = 64;
  
  	public Object assignPortalID(final Portal portal) {
  		portal.setIdentifier(Integer.valueOf(++maxPortalNumber));
  
  		return portal.getIdentifier();
  	}
  
  	public void setEntryPoint(final int x, final int y) {
  		entryPoint = new Point(x, y);
  	}
  
  	public boolean placeObjectAtEntryPoint(final Entity entity) {
  		if (entryPoint != null) {
  			return StendhalRPAction.placeat(this, entity, entryPoint.x,
  					entryPoint.y);
  		} else {
  			return false;
  		}
  	}
  
  	public void addLayer(final String name, final LayerDefinition layer) throws IOException {
  		final byte[] byteContents = layer.encode();
  		addToContent(name, byteContents);
  	}
  
  	public void addTilesets(final String name, final List<TileSetDefinition> tilesets)
  			throws IOException {
  		/*
  		 * Serialize the tileset data to send it to client.
  		 */
  		final ByteArrayOutputStream array = new ByteArrayOutputStream();
  		final OutputSerializer out = new OutputSerializer(array);
  
  		int amount = 0;
  
  		for (final TileSetDefinition set : tilesets) {
  			if (!set.getSource().contains("logic/")) {
  				amount++;
  			}
  
  		}
  
  		out.write(amount);
  		for (final TileSetDefinition set : tilesets) {
  			if (!set.getSource().contains("logic/")) {
  				set.writeObject(out);
  			}
  		}
  
  		addToContent(name, array.toByteArray());
  	}
  
  	/**
  	 * Creates a new TransferContent for the specified data and adds it to the
  	 * contents list.
  	 * @param name
  	 * @param byteContents
  	 */
  	private void addToContent(final String name, final byte[] byteContents) {
  		// Remove old data by the same name if it exists
  		Iterator<TransferContent> it = contents.iterator();
  		while (it.hasNext()) {
  			if (name.equals(it.next().name)) {
  				logger.info("Replacing old '" + name + "' layer.");
  				it.remove();
  			}
  		}
  
  		final TransferContent content = new TransferContent();
  		content.name = name;
  		content.cacheable = true;
  		logger.debug("Layer timestamp: " + Integer.toString(content.timestamp));
  		content.data = byteContents;
  		content.timestamp = CRC.cmpCRC(content.data);
  
  		contents.add(content);
  	}
  
  	/**
  	 * Resend the zone data to players on the zone. This is meant for situations
  	 * where the map data changes. (Weather and lighting changes, and so on).
  	 */
  	public void notifyOnlinePlayers() {
  		// Notify resident players about the changed weather
  		if (!getPlayers().isEmpty()) {
  			List<TransferContent> newContents = getContents();
  			for (Player player : getPlayers()) {
  				// Old clients do not understand content transfer that just
  				// update the old map, and end up with no entities on the screen
  				if (!player.isDisconnected() && player.isClientNewerThan("0.97")) {
  					StendhalRPAction.transferContent(player, newContents);
  				}
  			}
  		}
  	}
  
  	/**
  	 * Set zone attributes that should be passed to the client.
  	 *
  	 * @param attr attributes
  	 */
  	public void setAttributes(ZoneAttributes attr) {
  		if (readableName != null) {
  			attr.put("readable_name", readableName);
  		}
  		attributes = attr;
  	}
  
  	public void addCollisionLayer(final String name, final LayerDefinition collisionLayer)
  			throws IOException {
  		addToContent(name, collisionLayer.encode());
  		collisionMap.setCollisionData(collisionLayer);
  	}
  
  	public void addProtectionLayer(final String name, final LayerDefinition protectionLayer)
  			throws IOException {
  		addToContent(name, protectionLayer.encode());
  		protectionMap.setCollisionData(protectionLayer);
  	}
  
  	public void setPosition(final int level, final int x, final int y) {
  		this.interior = false;
  		this.level = level;
  		this.x = x;
  		this.y = y;
  	}
  
  	public void setPosition() {
  		this.interior = true;
  	}
  
  	public int getX() {
  		return x;
  	}
  
  	public int getY() {
  		return y;
  	}
  
  	public int getLevel() {
  		return level;
  	}
  
  	public boolean isInterior() {
  		return interior;
  	}
  
  	/**
  	 * Determine if this zone overlaps an area in global coordinates.
  	 *
  	 * @param area
  	 *            The area (in global coordinate space).
  	 *
  	 * @return <code>true</code> if the area overlaps.
  	 */
  	public boolean intersects(final Rectangle2D area) {
  		final Rectangle2D zone = new Rectangle(x, y, getWidth(), getHeight());
  
  		return zone.intersects(area);
  	}
  
  	/**
  	 * Populate a zone based on it's map content.
  	 *
  	 * TODO: This should be moved to the zone loader or something.
  	 * @param objectsLayer
  	 */
  	public void populate(final LayerDefinition objectsLayer) {
  		/* We build the layer data */
  		objectsLayer.build();
  
  		for (int yTemp = 0; yTemp < objectsLayer.getHeight(); yTemp++) {
  			for (int xTemp = 0; xTemp < objectsLayer.getWidth(); xTemp++) {
  				final int value = objectsLayer.getTileAt(xTemp, yTemp);
  				if (value > 0) {
  					/*
  					 * When the value is 0, it means that there is no tile at
  					 * that point.
  					 */
  					final TileSetDefinition tileset = objectsLayer.getTilesetFor(value);
  					createEntityAt(tileset.getSource(), value
  							- tileset.getFirstGid(), xTemp, yTemp);
  				}
  			}
  		}
  	}
  
  	/**
  	 * Calculate danger level for the zone, and store it in the data layer.
  	 */
  	public void calculateDangerLevel() {
  		// Avoid divide by zero
  		int maxLevel = 1;
  		int levelSum = 1;
  		for (CreatureRespawnPoint spawner : respawnPoints) {
  			Creature creature = spawner.getPrototypeCreature();
  			// Rare creatures should not count.
  			if (creature.isRare()) {
  				continue;
  			}
  			// Add 1, so that level 0 creatures do not get completely ignored.
  			int level = creature.getLevel() + 1;
  			// The level restriction is a hack to keep the chess pieces from
  			// being included.
  			if (level > 1000) {
  				continue;
  			}
  			maxLevel = Math.max(level, maxLevel);
  			levelSum += level;
  		}
  		// Avoid divide by zero
  		int area = getFreeArea() + 1;
  		/*
  		 * Use as the level of the highest level creature as the base, and
  		 * adjust it upwards by creatures near the same level, inversely
  		 * proportionally to the average distance between the creatures.
  		 */
  		double dangerLevel = maxLevel * (1 + DANGER_WEIGHT_CREATURE_DENSITY * (levelSum - maxLevel) / maxLevel / Math.sqrt(area)) - 1;
  		/*
  		 * Leave out if 0; the client does not need it, and it can save needing
  		 * to send a data layer for zones that do not have any other attributes.
  		 */
  		if (maxLevel > 0) {
  			if (attributes == null) {
  				attributes = new ZoneAttributes(this);
  			}
  			attributes.put("danger_level", Double.toString(dangerLevel));
  		}
  	}
  
  	/**
  	 * Get the area of the zone, excluding static collisions.
  	 *
  	 * @return free area size
  	 */
  	private int getFreeArea() {
  		int res = 0;
  		for (int y = 0; y < getHeight(); y++) {
  			for (int x = 0; x < getWidth(); x++) {
  				if (!collides(x, y)) {
  					res++;
  				}
  			}
  		}
  		return res;
  	}
  
  	/**
  	 * Create a map entity as a given coordinate.
  	 *
  	 * @param clazz
  	 *            the clazz of entity we are loading.<br>
  	 *            It is related to the way entities are stored in tilesets now.
  	 * @param type integer to represent the type of entity to be created.
  	 * <p> if the class contains portal type is evaluated as follows:
  	 * <ul>
  	 * <li> 0 , 1  entry point
  	 * <li> 1 zone change
  	 * <li> 5 ,2 , 3 LevelPortal
  	 * </ul>
  
  	 * @param x
  	 * @param y
  	 *
  	 *
  	 */
  	protected void createEntityAt(final String clazz, final int type, final int x, final int y) {
  		logger.debug("creating " + clazz + ":" + type + " at " + x + "," + y);
  		final int ENTRY_POINT = 0;
  		final int ZONE_CHANGE = 1;
  		final int DOOR = 6;
  		final int PORTAL = 4;
  		final int ONE_WAY_PORTAL_DESTINATION = 5;
  
  		try {
  			if (clazz.contains("logic/portal")) {
  				if (stairsUp.contains(type) || stairsDown.contains(type) || (type == ONE_WAY_PORTAL_DESTINATION)) {
  					createLevelPortalAt(type, x, y);
  				} else {
  					switch (type) {
  
  					case ENTRY_POINT:
  					case ZONE_CHANGE:
  						setEntryPoint(x, y);
  						break;
  
  					case PORTAL:
  						break;
  
  					case DOOR:
  						break;
  
  					default:
  						logger.error("Unknown Portal (class/type: " + clazz + ":"
  								+ type + ") at (" + x + "," + y + ") of " + getID()
  								+ " found");
  						break;
  					}
  				}
  			} else if (clazz.contains("sheep.png")) {
  				final Sheep sheep = new Sheep();
  				sheep.setPosition(x, y);
  				add(sheep);
  			} else if (clazz.contains("logic/creature")) {
  				// get the default EntityManager
  				final EntityManager manager = SingletonRepository.getEntityManager();
  
  				// Is the entity a creature
  				if (manager.isCreature(clazz, type)) {
  					final Creature creature = manager.getCreature(clazz, type);
  					final CreatureRespawnPoint point = new CreatureRespawnPoint(this,
  							x, y, creature, 1);
  					add(point);
  				} else {
  					logger.error("Unknown Entity (class/type: " + clazz + ":"
  							+ type + ") at (" + x + "," + y + ") of " + getID()
  							+ " found");
  				}
  			} else if (clazz.contains("logic/item")) {
  				final PassiveEntityRespawnPoint passiveEntityrespawnPoint = PassiveEntityRespawnPointFactory.create(
  						clazz, type, getID(), x, y);
  				if (passiveEntityrespawnPoint != null) {
  					passiveEntityrespawnPoint.setPosition(x, y);
  					add(passiveEntityrespawnPoint);
  					passiveEntityrespawnPoint.setStartState();
  
  				}
  			}
  		} catch (final RuntimeException e) {
  			logger.error("error creating entity " + type + " at (" + x + ","
  					+ y + ")", e);
  		}
  	}
  
  	/*
  	 * Create a portal between levels.
  	 *
  	 *
  	 */
  	protected void createLevelPortalAt(final int type, final int x, final int y) {
  		if (logger.isDebugEnabled()) {
  			logger.debug("Portal stairs at " + this + ": " + x + "," + y);
  		}
  
  		Portal portal;
  
  		if (type != 5) {
  			portal = new Portal();
  
  			switch (type) {
  
  			case UP_FN:
  			case DOWN_FN:
  				portal.setFaceDirection(Direction.UP);
  				break;
  			case UP_FE:
  			case DOWN_FE:
  				portal.setFaceDirection(Direction.RIGHT);
  				break;
  			case UP_FS:
  			case DOWN_FS:
  				portal.setFaceDirection(Direction.DOWN);
  				break;
  			case UP_FW:
  			case DOWN_FW:
  				portal.setFaceDirection(Direction.LEFT);
  				break;
  			default:
  				break;
  			}
  		} else {
  			portal = new OneWayPortalDestination();
  		}
  
  		portal.setPosition(x, y);
  		assignPortalID(portal);
  		add(portal);
  
  		boolean assigned = false;
  
  		if (isInterior()) {
  			// The algo doesn't work on interiors
  			return;
  		}
  
  		for (final IRPZone i : SingletonRepository.getRPWorld()) {
  			final StendhalRPZone zone = (StendhalRPZone) i;
  
  			if (zone.isInterior()) {
  				continue;
  			}
  
  			/*
  			 * Portals in the correct direction?
  			 */
  			if (stairsUp.contains(type)) {
  				/* portal stairs up */
  				if ((zone.getLevel() - getLevel()) != 1) {
  					continue;
  				}
  			} else if (stairsDown.contains(type)) {
  				/* portal stairs down */
  				if ((zone.getLevel() - getLevel()) != -1) {
  					continue;
  				}
  			} else {
  				/* one way portal - POTENTIALLY WRONG LEVEL */
  				/* Should they always go down (drop only)? */
  				if (Math.abs(zone.getLevel() - getLevel()) != 1) {
  					continue;
  				}
  			}
  
  			final Portal target = zone.getPortal(
  					portal.getX() + getX() - zone.getX(), portal.getY()
  							+ getY() - zone.getY());
  
  			if (target == null) {
  				continue;
  			}
  
  			logger.debug(zone + " contains " + target);
  
  			if (target.loaded()) {
  				logger.debug(target + " already loaded");
  				continue;
  			}
  
  			if (type != 5) {
  				portal.setDestination(zone.getName(),
  						zone.assignPortalID(target));
  			}
  
  			target.setDestination(getName(), portal.getIdentifier());
  
  			logger.debug("Portals LINKED");
  			logger.debug(portal);
  			logger.debug(target);
  			assigned = true;
  			break;
  		}
  
  		if (!assigned) {
  			logger.debug(portal + " has no destination");
  		}
  	}
  
  	public int getWidth() {
  		return collisionMap.getWidth();
  	}
  
  	public int getHeight() {
  		return collisionMap.getHeight();
  	}
  
  	public List<TransferContent> getContents() {
  		if (attributes != null) {
  			TransferContent attr = attributes.getContents();
  			// Remove old attributes, if needed
  			if (!contents.isEmpty() && (contents.get(0).name.equals(attr.name))) {
  				contents.remove(0);
  			}
  			// Ensure the attributes comes first, so that the client has coloring
  			// information
  			contents.add(0, attr);
  		}
  		return contents;
  	}
  
  	public boolean isInProtectionArea(final Entity entity) {
  		final Rectangle2D area = entity.getArea();
  		return protectionMap.collides(area);
  	}
  
  	public boolean leavesZone(final Entity entity, final double x, final double y) {
  		final Rectangle2D area = entity.getArea(x, y);
  		return collisionMap.leavesZone(area);
  	}
  
  	public boolean simpleCollides(final Entity entity, final double x, final double y, final double w, final double h) {
  		return collisionMap.collides(x, y, w, h);
  	}
  
  	@Override
  	public synchronized void add(final RPObject object) {
  		add(object, true);
  	}
  
  	/**
  	 * Adds an object to the ground.
  	 *
  	 * The player parameter can be used to create special items that react when
  	 * they are dropped on the ground by a player.
  	 *
  	 * @param object
  	 *            The object that should be added to the zone
  	 * @param player
  	 * 		The player that dropped the item
  	 */
  	public void add(final RPObject object, final Player player) {
  		add(object, player, true);
  	}
  
  	/**
  	 * Adds an object to the ground.
  	 *
  	 * @param object
  	 *            The object that should be added to the zone
  	 * @param expire
  	 *            True if the item should expire according to its normal behaviour,
  	 *            false otherwise
  	 */
  	public void add(final RPObject object, final boolean expire) {
  		add(object, null, expire);
  	}
  
  	private synchronized void add(final RPObject object, final Player player, final boolean expire) {
  		/*
  		 * Assign [zone relative] ID info. TODO: Move up to MarauroaRPZone
  		 */
  		assignRPObjectID(object);
  		super.add(object);
  
  		notifyAdded(object);
  
  		// Needs to be before adding an item, in case Item.onPutOnGround()
  		// needs proper zone information
  		if (object instanceof Entity) {
  			((Entity) object).onAdded(this);
  		}
  
  		if (object instanceof Item) {
  			final Item item = (Item) object;
  			if (player != null) {
  				// let item decide what to do when it's thrown by a player
  				item.onPutOnGround(player);
  			} else {
  				// otherwise follow expire
  				item.onPutOnGround(expire);
  			}
  			itemsOnGround.add(item);
  		}
  
  		/*
  		 * Eventually move to <Entity>.onAdded().
  		 */
  		if (object instanceof PassiveEntityRespawnPoint) {
  			plantGrowers.add((PassiveEntityRespawnPoint) object);
  		}
  
  		if (object instanceof Blood) {
  			bloods.add((Blood) object);
  		} else if (object instanceof Player) {
  			Player playerObject = (Player) object;
  			players.add(playerObject);
  			playersAndFriends.add(playerObject);
  			/*
  			 * super.add() clears the events, so this needs to be after it for
  			 * the player to see the zone achievements. Also, Player.onAdded()
  			 * sets the !visited slot, so this should be after it to have the
  			 * achievement appear when the player enters the last missing zone.
  			 */
  			SingletonRepository.getAchievementNotifier().onZoneEnter(playerObject);
  		} else if (object instanceof AttackableCreature) {
  			playersAndFriends.add((AttackableCreature) object);
  		} else if (object instanceof Sheep) {
  			if (((Sheep) object).wasOwned()) {
  				playersAndFriends.add((Sheep) object);
  			}
  		} else if (object instanceof SheepFood) {
  			sheepFoods.add((SheepFood) object);
  		} else if (object instanceof BabyDragon) {
  			playersAndFriends.add((BabyDragon) object);
  		} else if (object instanceof SpeakerNPC) {
  			SingletonRepository.getNPCList().add((SpeakerNPC) object);
  		} else if (object instanceof Portal) {
  			portals.add((Portal) object);
  		}
  
  		if (object instanceof NPC) {
  			npcs.add((NPC) object);
  		}
  
  		// TODO: Move up to MarauroaRPZone?
  		SingletonRepository.getRPWorld().requestSync(object);
  	}
  
  	/**
  	 * adds an RPEntity to the playersAndFriends list.
  	 *
  	 * @param object RPEntity
  	 */
  	public void addToPlayersAndFriends(RPEntity object) {
  		if (!playersAndFriends.contains(object)) {
  			playersAndFriends.add(object);
  		}
  	}
  
  	private void notifyAdded(final RPObject object) {
  		for (final ZoneEnterExitListener l : zoneListeners) {
  				l.onEntered(object, this);
  		}
  	}
  
  	private void notifyRemoved(final RPObject object) {
  		for (final ZoneEnterExitListener l : zoneListeners) {
  			l.onExited(object, this);
  		}
  	}
  
  	@Override
  	public synchronized RPObject remove(final RPObject.ID id) {
  
  		final RPObject object = get(id);
  		notifyRemoved(object);
  		if (object instanceof Entity) {
  			((Entity) object).onRemoved(this);
  		}
  
  		if (object instanceof NPC) {
  			npcs.remove(object);
  		}
  
  		/*
  		 * Eventually move to <Entity>.onRemoved().
  		 */
  		if (object instanceof PassiveEntityRespawnPoint) {
  			plantGrowers.remove(object);
  		}
  
  		if (object instanceof Blood) {
  			bloods.remove(object);
  		} else if (object instanceof Player) {
  			players.remove(object);
  			playersAndFriends.remove(object);
  		} else if (object instanceof AttackableCreature) {
  			playersAndFriends.remove(object);
  		} else if (object instanceof Sheep) {
  			playersAndFriends.remove(object);
  		} else if (object instanceof SheepFood) {
  			sheepFoods.remove(object);
  		} else if (object instanceof BabyDragon) {
  			playersAndFriends.remove(object);
  		} else if (object instanceof SpeakerNPC) {
  			SingletonRepository.getNPCList().remove(((SpeakerNPC) object).getName());
  		} else if (object instanceof Portal) {
  			portals.remove(object);
  		}
  
  		if (object instanceof ZoneEnterExitListener) {
  			removeZoneEnterExitListener((ZoneEnterExitListener) object);
  		}
  		if (object instanceof MovementListener) {
  			removeMovementListener((MovementListener) object);
  		}
  
  		super.remove(id);
  
  		if (object instanceof Item) {
  			final Item item = (Item) object;
  			itemsOnGround.remove(item);
  			item.onRemoveFromGround();
  		}
  
  		return object;
  	}
  
  
  
  	/**
  	 * removes object from zone.
  	 *
  	 * @param object
  	 * @return the removed object
  	 */
  	public synchronized RPObject remove(final RPObject object) {
  		if (object.isContained()) {
  			modify(object);
  
  			if (object instanceof SpeakerNPC) {
  				SingletonRepository.getNPCList().remove(((SpeakerNPC) object).getName());
  			}
  
  			if (object instanceof NPC) {
  				npcs.remove(object);
  			}
  
  			final RPSlot slot = object.getContainerSlot();
  			return slot.remove(object.getID());
  		} else {
  			return remove(object.getID());
  		}
  	}
  
  	@Override
  	public synchronized void modify(final RPObject object) {
  		// We modify the base container if the object changes.
  		super.modify(object.getBaseContainer());
  	}
  
  	/**
  	 * Checks if there is a collision on the airline between 2 positions. Only
  	 * the collision map will be used.
  	 *
  	 * @param x1
  	 *            x value of position 1
  	 * @param y1
  	 *            y value of position 1
  	 * @param x2
  	 *            x value of position 2
  	 * @param y2
  	 *            y value of position 2
  	 * @return true if there is a collision
  	 */
  	public boolean collidesOnLine(final int x1, final int y1, final int x2, final int y2) {
  		List<Point> points;
  		// Always draw the line to the same direction, so that if A to B
  		// collides, then so does B to A
  		if ((x1 < x2) || ((x1 == x2) && (y1 < y2))) {
  			points = Line.renderLine(x1, y1, x2, y2);
  		} else {
  			points = Line.renderLine(x2, y2, x1, y1);
  		}
  		for (final Point point : points) {
  			if (collides((int) point.getX(), (int) point.getY())) {
  				return true;
  			}
  		}
  		return false;
  	}
  
  	public boolean collides(final int x, final int y) {
  		return collisionMap.collides(x, y);
  	}
  
  	/**
  	 * Checks whether the given entity would be able to stand at the given
  	 * position, or if it would collide with the collision map or with another
  	 * entity.
  	 *
  	 * @param entity
  	 *            The entity that would stand on the given position
  	 * @param x
  	 *            The x coordinate of the position where the entity would stand
  	 * @param y
  	 *            The y coordinate of the position where the entity would stand
  	 * @return true iff the entity could stand on the given position
  	 */
  	public synchronized boolean collides(final Entity entity, final double x, final double y) {
  		return collides(entity, x, y, true);
  	}
  
  	/**
  	 * Checks whether the given entity would be able to stand at the given
  	 * position, or if it would collide with the collision map or (if
  	 * <i>checkObjects</i> is enabled) with another entity.
  	 *
  	 * @param entity
  	 *            The entity that would stand on the given position
  	 * @param x
  	 *            The x coordinate of the position where the entity would stand
  	 * @param y
  	 *            The y coordinate of the position where the entity would stand
  	 * @param checkObjects
  	 *            If false, only the collision map will be used.
  	 * @return true iff the entity could stand on the given position
  	 */
  	public synchronized boolean collides(final Entity entity, final double x, final double y,
  			final boolean checkObjects) {
  
  		if (collisionMap.collides(x, y, entity.getWidth(), entity.getHeight())) {
  			return true;
  		}
  
  		if (checkObjects) {
  			Rectangle2D area = entity.getArea(x, y);
  			return collidesObjects(entity, area);
  		}
  
  		return false;
  	}
  
  	public boolean collidesObjects(final Entity entity, final Rectangle2D area) {
  		// For every other object in this zone, check whether it's in the
  		// way.
  