/* $Id: StendhalRPZone.java,v 1.70 2011/06/05 18:12:18 kiheru Exp $ */
/***************************************************************************
* (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 games.stendhal.common.CRC;
import games.stendhal.common.CollisionDetection;
import games.stendhal.common.Debug;
import games.stendhal.common.Line;
import games.stendhal.common.filter.FilterCriteria;
import games.stendhal.common.grammar.Grammar;
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 games.stendhal.tools.tiled.LayerDefinition;
import games.stendhal.tools.tiled.TileSetDefinition;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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;
import org.apache.log4j.Logger;
public class StendhalRPZone extends MarauroaRPZone {
TeleportationRules teleRules = new TeleportationRules();
/** the logger instance. */
private static final Logger logger = Logger.getLogger(StendhalRPZone.class);
private final List<TransferContent> contents;
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;
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();
}
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);
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) {
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);
}
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);
}
}
}
}
/**
* 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 PORTAL_STAIRS_DOWN = 3;
final int PORTAL_STAIRS_UP = 2;
final int ONE_WAY_PORTAL_DESTINATION = 5;
try {
if (clazz.contains("logic/portal")) {
switch (type) {
case ENTRY_POINT:
case ZONE_CHANGE:
setEntryPoint(x, y);
break;
case ONE_WAY_PORTAL_DESTINATION:
case PORTAL_STAIRS_UP:
case PORTAL_STAIRS_DOWN:
createLevelPortalAt(type, 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 Exception 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();
} 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 (type == 2) {
/* portal stairs up */
if ((zone.getLevel() - getLevel()) != 1) {
continue;
}
} else if (type == 3) {
/* 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() {
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);
}
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()) {
// We modify the base container if the object change.
// TODO: Remove? Isn't this the same as _in_ modify()?
RPObject base = object.getContainer();
while (base.isContained()) {
base = base.getContainer();
}
modify(base);
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) {
if (object.isContained()) {
// We modify the base container if the object change.
RPObject base = object.getContainer();
while (base.isContained()) {
base = base.getContainer();
}
super.modify(base);
} else {
super.modify(object);
}
}
/**
* 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) {
final Vector<Point> points = Line.renderLine(x1, y1, x2, y2);
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.
return getCollidingObject(entity, area) != null;
}
private Entity getCollidingObject(final Entity entity, final Rectangle2D area) {
for (final RPObject other : objects.values()) {
// Ignore same object
if (entity != other) {
final Entity otherEntity = (Entity) other;
// Check if the objects overlap
if (area.intersects(otherEntity.getX(), otherEntity.getY(), otherEntity.getWidth(), otherEntity.getHeight())) {
// Check if it's blocking
if (otherEntity.isObstacle(entity)) {
return otherEntity;
}
}
}
}
return null;
}
/**
* Finds an Entity at the given coordinates.
*
* @param x coordinate
* @param y coordinate
* @return the first entity found if there are more than one or null if there are none
*/
public synchronized Entity getEntityAt(final double x, final double y) {
for (final RPObject other : objects.values()) {
final Entity otherEntity = (Entity) other;
final Rectangle2D rect = otherEntity.getArea();
if (rect.contains(x, y)) {
return otherEntity;
}
}
return null;
}
/**
* Finds all entities at the given coordinates.
* @param x coordinate
* @param y coordinate
* @return list of entities at (x, y)
*/
public synchronized List<Entity> getEntitiesAt(final double x, final double y) {
List<Entity> entities = new LinkedList<Entity>();
for (final RPObject other : objects.values()) {
final Entity entity = (Entity) other;
final Rectangle2D rect = entity.getArea();
if (rect.contains(x, y)) {
entities.add(entity);
}
}
return entities;
}
/**
* Get the zone name. This is the same as <code>getID().getID()</code>,
* only cleaner to use.
*
* @return The zone name.
*/
public String getName() {
return getID().getID();
}
/**
* Notify anything interested in when an entity entered.
*
* @param entity
* The entity that entered.
* @param newX
* The new X coordinate.
* @param newY
* The new Y coordinate.
*/
public void notifyEntered(final ActiveEntity entity, final int newX, final int newY) {
Rectangle2D eArea;
eArea = entity.getArea(newX, newY);
for (final MovementListener l : movementListeners) {
Rectangle2D area = l.getArea();
if (area.intersects(eArea)) {
l.onEntered(entity, this, newX, newY);
}
}
}
/**
* Notify anything interested in when an entity exited.
*
* @param entity
* The entity that moved.
* @param oldX
* The old X coordinate.
* @param oldY
* The old Y coordinate.
*/
public void notifyExited(final ActiveEntity entity, final int oldX, final int oldY) {
Rectangle2D eArea;
eArea = entity.getArea(oldX, oldY);
for (final MovementListener l : movementListeners) {
Rectangle2D area = l.getArea();
if (area.intersects(eArea)) {
l.onExited(entity, this, oldX, oldY);
}
}
}
/**
* Notify anything interested that an entity moved.
*
* @param entity
* The entity that moved.
* @param oldX
* The old X coordinate.
* @param oldY
* The old Y coordinate.
* @param newX
* The new X coordinate.
* @param newY
* The new Y coordinate.
*/
public void notifyMovement(final ActiveEntity entity, final int oldX, final int oldY,
final int newX, final int newY) {
Rectangle2D oeArea;
Rectangle2D neArea;
boolean oldIn;
boolean newIn;
oeArea = entity.getArea(oldX, oldY);
neArea = entity.getArea(newX, newY);
for (final MovementListener l : movementListeners) {
Rectangle2D area = l.getArea();
oldIn = area.intersects(oeArea);
newIn = area.intersects(neArea);
if (!oldIn && newIn) {
l.onEntered(entity, this, newX, newY);
}
if (oldIn && newIn) {
l.onMoved(entity, this, oldX, oldY, newX, newY);
}
if (oldIn && !newIn) {
l.onExited(entity, this, oldX, oldY);
}
}
}
public void addZoneEnterExitListener(final ZoneEnterExitListener listener) {
zoneListeners.add(listener);
}
public void removeZoneEnterExitListener(final ZoneEnterExitListener listener) {
zoneListeners.add(listener);
}
/**
* Register a movement listener for notification. Eventually create a
* macro-block hash to cut down on listeners to check.
*
* @param listener
* A movement listener to register.
*/
public void addMovementListener(final MovementListener listener) {
movementListeners.add(listener);
}
/**
* Unregister a movement listener from notification.
*
* @param listener
* A movement listener to unregister.
*/
public void removeMovementListener(final MovementListener listener) {
movementListeners.remove(listener);
}
@Override
public String toString() {
return "zone " + zoneid + " at (" + x + "," + y + ", " + level + ") interior: " + isInterior();
}
/**
* @return a set of all items that are lying on the ground in this zone.
*/
public Set<Item> getItemsOnGround() {
return itemsOnGround;
}
/**
* Gets all players in this zone.
*
* @return A list of all players.
*/
public List<Player> getPlayers() {
return players;
}
/**
* Gets all players in this zone, as well as friendly entities such as
* sheep. These are the targets (enemies) for wild creatures such as orcs.
*
* @return a list of all players and friendly entities
*/
public List<RPEntity> getPlayerAndFriends() {
return playersAndFriends;
}
/**
* Can moveto (mouse movement using pathfinding) be done on this map?
*
* @return true, if moveto is possible, false otherwise
*/
public boolean isMoveToAllowed() {
return moveToAllowed;
}
/**
* Sets the flag whether moveto (mouse movement using pathfinding) is
* possible in this zone.
*
* @param moveToAllowed
* true, if it is possible, false otherwise
*/
public void setMoveToAllowed(final boolean moveToAllowed) {
this.moveToAllowed = moveToAllowed;
}
private int debugturn;
private boolean accessible;
private String noItemMoveMessage;
@Override
@SuppressWarnings("unused")
public void nextTurn() {
super.nextTurn();
debugturn++;
if (Debug.SHOW_LIST_SIZES && (debugturn % 1000 == 0)) {
final StringBuilder os = new StringBuilder("Name: " + this.getID());
os.append("blood: " + bloods.size() + "\n");
os.append("itemsOnGround: " + itemsOnGround.size() + "\n");
os.append("movementListeners: " + movementListeners.size() + "\n");
os.append("npcs: " + npcs.size() + "\n");
os.append("plantGrowers: " + plantGrowers.size() + "\n");
os.append("players: " + players.size() + "\n");
os.append("playersAndFriends: " + playersAndFriends.size() + "\n");
os.append("portals: " + portals.size() + "\n");
os.append("respawnPoints: " + respawnPoints.size() + "\n");
os.append("sheepFoods: " + sheepFoods.size() + "\n");
os.append("objects: " + objects.size() + "\n");
logger.info(os);
}
}
public void logic() {
for (final NPC npc : npcs) {
try {
npc.logic();
} catch (final Exception e) {
logger.error("Error in npc logic for zone " + getID().getID(), e);
}
}
}
/**
* Return whether the zone is completely empty.
* @return true if there are no objects in zone
*/
public boolean isEmpty() {
return objects.isEmpty();
}
/**
* Return whether the zone contains one or more players.
* @return if there are players in zone
*/
public boolean containsPlayer() {
for (final RPObject obj : objects.values()) {
if (obj instanceof Player) {
return true;
}
}
return false;
}
/**
* Return whether the zone contains one or more animals.
* @return true if there are domesticalanimals in zone
*/
public boolean containsAnimal() {
for (final RPObject obj : objects.values()) {
if (obj instanceof DomesticAnimal) {
return true;
}
}
return false;
}
/**
* Return whether the zone contains any creature including players and animals.
* @return true if there are creatures in zone
*/
public boolean containsCreature() {
for (final RPObject obj : objects.values()) {
if (obj instanceof Creature) {
return true;
}
}
return false;
}
public List<Entity> getFilteredEntities(final FilterCriteria<Entity> criteria) {
final List <Entity> result = new LinkedList<Entity>();
for (final RPObject obj : objects.values()) {
if (obj instanceof Entity) {
final Entity entity = (Entity) obj;
if (criteria.passes(entity)) {
result.add(entity);
}
}
}
return result;
}
/**
* Sets the flag whether magic scrolls for teleportation may be uses in this
* zone.
*/
public void disAllowTeleport() {
disallowIn();
disallowOut();
}
/**
* Disallow teleporting to and from a specified area.
*
* @param x left x coordinate
* @param y top y coordinate
* @param width width of the area
* @param height height of the area
*/
public void disAllowTeleport(int x, int y, int width, int height) {
disallowIn(x, y, width, height);
disallowOut(x, y, width, height);
}
/**
* Check if teleporting with a scroll to a location is allowed.
*
* @param x x coordinate
* @param y y coordinate
* @return <code>true</code> iff teleporting is allowed
*/
public boolean isTeleportInAllowed(int x, int y) {
return teleRules.isInAllowed(x, y);
}
/**
* Check if teleporting with a scroll from a location is allowed.
*
* @param x x coordinate
* @param y y coordinate
* @return <code>true</code> iff teleporting is allowed
*/
public boolean isTeleportOutAllowed(int x, int y) {
return teleRules.isOutAllowed(x, y);
}
/**
* Forbid teleporting to the entire zone using a scroll.
*/
public void disallowIn() {
teleRules.disallowIn();
}
/**
* Disallow teleporting to specified area.
*
* @param x left x coordinate
* @param y top y coordinate
* @param width width of the area
* @param height height of the area
*/
public void disallowIn(int x, int y, int width, int height) {
teleRules.disallowIn(x, y, width, height);
}
/**
* Forbid teleporting from the entire zone using a scroll.
*/
public void disallowOut() {
teleRules.disallowOut();
}
/**
* Disallow teleporting from specified area.
*
* @param x left x coordinate
* @param y top y coordinate
* @param width width of the area
* @param height height of the area
*/
public void disallowOut(int x, int y, int width, int height) {
teleRules.disallowOut(x, y, width, height);
}
public void onRemoved() {
for (RPObject inspected : this) {
if (inspected instanceof ActiveEntity) {
((ActiveEntity) inspected).onRemoved(this);
}
}
}
/**
* @return is this zone accessible by the public
*/
public boolean isPublicAccessible() {
return accessible;
}
/**
* Sets the public accessibility of this zone
*
* @param accessible
*/
public void setPublicAccessible(boolean accessible) {
this.accessible = accessible;
}
/**
* Mappings for the zone names that would look weird with the dynamic
* translation.
*/
private static final Map<String, String> zoneNameMappings = new HashMap<String, String>();
static {
zoneNameMappings.put("0_athor_ship_w2", "on Athor ferry");
zoneNameMappings.put("-1_athor_ship_w2", "on Athor ferry");
zoneNameMappings.put("-2_athor_ship_w2", "on Athor ferry");
zoneNameMappings.put("hell", "in Hell");
}
/**
* Translate zone name into a more readable form.
*
* @param zoneName
* @return translated zone name
*/
private static String translateZoneName(final String zoneName) {
if (zoneNameMappings.get(zoneName) != null) {
return zoneNameMappings.get(zoneName);
}
String result = "";
final Pattern p = Pattern.compile("^(-?[\\d]|int)_(.+)$");
final Matcher m = p.matcher(zoneName);
int levelValue = -1;
if (m.matches()) {
final String level = m.group(1);
String remainder = m.group(2);
if ("int".equals(level)) {
return "inside a building in " + Grammar.makeUpperCaseWord(getInteriorName(zoneName));
} else if (level.startsWith("-")) {
try {
levelValue = Integer.parseInt(level);
} catch (final NumberFormatException e) {
levelValue = 0;
}
if (levelValue < -2) {
result = "deep below ground level at ";
} else {
result = "below ground level at ";
}
} else if (level.matches("^\\d")) {
/* positive floor */
try {
levelValue = Integer.parseInt(level);
} catch (final NumberFormatException e) {
levelValue = 0;
}
if (levelValue != 0) {
if (levelValue > 1) {
result = "high above the ground level at ";
} else {
result = "above the ground level at ";
}
}
}
final StringBuilder sb = new StringBuilder();
final String[] directions = new String[] { ".+_n\\d?e\\d?($|_).*",
"north east ", "_n\\d?e\\d?($|_)", "_",
".+_n\\d?w\\d?($|_).*", "north west ", "_n\\d?w\\d?($|_)",
"_", ".+_s\\d?e\\d?($|_).*", "south east ",
"_s\\d?e\\d?($|_)", "_", ".+_s\\d?w\\d?($|_).*",
"south west ", "_s\\d?w\\d?($|_)", "_", ".+_n\\d?($|_).*",
"north ", "_n\\d?($|_)", "_", ".+_s\\d?($|_).*", "south ",
"_s\\d?($|_)", "_", ".+_w\\d?($|_).*", "west ",
"_w\\d?($|_)", "_", ".+_e\\d?($|_).*", "east ",
"_e\\d?($|_)", "_", };
for (int i = 0; i < directions.length; i += 4) {
if (remainder.matches(directions[i])) {
sb.append(directions[i + 1]);
remainder = remainder.replaceAll(directions[i + 2],
directions[i + 3]);
}
}
String direction = sb.toString();
if (direction.length() > 0) {
result += direction + "of ";
} else if (levelValue == 0) {
// if level 0 and no other direction we need an extra in for grammar
result =" in ";
}
// here we need to capitalise the city name
result += Grammar.makeUpperCaseWord(remainder.replaceAll("_", " "));
} else {
System.err.println("no match: " + zoneName);
}
if ("".equals(result)) {
return zoneName;
} else {
return result.trim();
}
}
private static String getInteriorName(final String zoneName) {
if (zoneName == null) {
throw new IllegalArgumentException("zoneName is null");
}
final int start = zoneName.indexOf('_') + 1;
int end = zoneName.indexOf('_', start);
if (end < 0) {
end = zoneName.length();
}
if (start > 0 && end > start) {
return zoneName.substring(start, end);
} else {
return zoneName;
}
}
public static String describe(final String zoneName) {
return StendhalRPZone.translateZoneName(zoneName);
}
public String describe() {
return StendhalRPZone.translateZoneName(this.getName());
}
/**
* Disabled movement of items in this zone.
*
* @param message in game error message
*/
public void setNoItemMoveMessage(String message) {
this.noItemMoveMessage = message;
}
/**
* Gets the in game error message if movement of items is disabled in this zone.
*
* @return message in game error message or <code>null</code>
*/
public String getNoItemMoveMessage() {
return this.noItemMoveMessage;
}
}