/* $Id: DestinationObject.java,v 1.57 2011/06/22 19:19:49 madmetzger Exp $ */
/***************************************************************************
* (C) Copyright 2003-2010 - Stendhal *
***************************************************************************
***************************************************************************
* *
* 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.actions.equip;
import games.stendhal.common.EquipActionConsts;
import games.stendhal.common.MathHelper;
import games.stendhal.server.core.engine.ItemLogger;
import games.stendhal.server.core.engine.SingletonRepository;
import games.stendhal.server.core.engine.StendhalRPZone;
import games.stendhal.server.core.pathfinder.Node;
import games.stendhal.server.core.pathfinder.Path;
import games.stendhal.server.entity.Entity;
import games.stendhal.server.entity.item.Item;
import games.stendhal.server.entity.item.Stackable;
import games.stendhal.server.entity.item.StackableItem;
import games.stendhal.server.entity.player.Player;
import games.stendhal.server.entity.slot.EntitySlot;
import java.awt.Rectangle;
import java.util.Iterator;
import java.util.List;
import marauroa.common.game.RPAction;
import marauroa.common.game.RPObject;
import marauroa.common.game.RPSlot;
import org.apache.log4j.Logger;
/**
* this encapsulates the equip/drop destination.
*/
class DestinationObject extends MoveableObject {
private static Logger logger = Logger.getLogger(DestinationObject.class);
/** true when this object is valid. */
private boolean valid;
/** x coordinate when dropped on ground. */
private int x;
/** y coordinate when dropped on ground.*/
private int y;
/** interprets the given action.
* @param action
* @param player
*/
public DestinationObject(final RPAction action, final Player player) {
super(player);
valid = false;
if (action.has(EquipActionConsts.TARGET_PATH)) {
List<String> path = action.getList(EquipActionConsts.TARGET_PATH);
Iterator<String> it = path.iterator();
parent = EquipUtil.getEntityFromId(player, MathHelper.parseInt(it.next()));
// check slot
if (parent == null) {
logger.warn("cannot find target entity for action " + action);
// Not valid...
return;
}
// is the top level parent a player and not the current one?
if ((parent instanceof Player)
&& !parent.getID().equals(player.getID())) {
logger.warn("trying to drop an item into another players inventory");
// trying to drop an item into another players inventory
return;
}
// Walk the slot path
slot = null;
while (it.hasNext()) {
slot = it.next();
if (!parent.hasSlot(slot)) {
logger.error(player.getName() + " tried to use non existing slot " + slot + " of " + parent
+ " as destination. player zone: " + player.getZone() + " object zone: " + parent.getZone());
return;
}
final RPSlot rpslot = parent.getSlot(slot);
if (it.hasNext()) {
final RPObject.ID itemId = new RPObject.ID(MathHelper.parseInt(it.next()), "");
if (!rpslot.has(itemId)) {
return;
}
parent = (Entity) rpslot.get(itemId);
}
}
valid = slot != null;
return;
} else if (action.has(EquipActionConsts.TARGET_OBJECT)
&& action.has(EquipActionConsts.TARGET_SLOT)) {
// ** Compatibility mode **
// get base item and slot
parent = EquipUtil.getEntityFromId(player,
action.getInt(EquipActionConsts.TARGET_OBJECT));
// check slot
if (parent == null) {
logger.warn("cannot find target entity for action " + action);
// Not valid...
return;
}
slot = action.get(EquipActionConsts.TARGET_SLOT);
// is the container a player and not the current one?
if ((parent instanceof Player)
&& !parent.getID().equals(player.getID())) {
logger.warn("trying to drop an item into another players inventory");
// trying to drop an item into another players inventory
return;
}
// check slot
if (!parent.hasSlot(slot)) {
logger.warn("Parent don't have slot: " + action);
return;
}
// ok, action is valid.
valid = true;
return;
}
// dropped to the ground
if (action.has(EquipActionConsts.GROUND_X)
&& action.has(EquipActionConsts.GROUND_Y)) {
x = action.getInt(EquipActionConsts.GROUND_X);
y = action.getInt(EquipActionConsts.GROUND_Y);
valid = true;
}
// not valid
}
/** checks if it is possible to add the entity to the world.
* @param entity
* @param player
* @return true if can be added to the world
* */
@SuppressWarnings("unchecked")
public boolean preCheck(final Entity entity, final Player player) {
final StendhalRPZone zone = player.getZone();
if (parent != null) {
final EntitySlot rpslot = (EntitySlot) parent.getSlot(slot);
rpslot.clearErrorMessage();
if (!rpslot.isReachableForThrowingThingsIntoBy(player)) {
player.sendPrivateText(rpslot.getErrorMessage());
logger.debug("Unreachable slot");
return false;
}
if (rpslot.isFull()) {
boolean isStackable = false;
// is the entity stackable
if (entity instanceof Stackable<?>) {
final Stackable<?> stackEntity = (Stackable<?>) entity;
// now check if it can be stacked on top of another item
final Iterator<RPObject> it = rpslot.iterator();
while (it.hasNext()) {
final RPObject object = it.next();
if (object instanceof Stackable<?>) {
// found another stackable
@SuppressWarnings("rawtypes")
final Stackable other = (Stackable<?>) object;
if (other.isStackable(stackEntity)) {
// other is the same type...merge them
isStackable = true;
}
}
}
}
if (!isStackable) {
// entity cannot be stacked on top of another...
// so the equip is invalid
player.sendPrivateText("There is no space in there.");
return false;
}
}
// check if someone tried to put an item into itself (maybe
// through various levels of indirection)
if (rpslot.hasAsAncestor(entity)) {
logger.warn("tried to put item " + entity.getID()
+ " into itself, equip rejected");
return false;
}
if (entity instanceof Item) {
Item item = (Item) entity;
if (item.isBound() && rpslot.isTargetBoundCheckRequired()) {
player.sendPrivateText("You cannot put this special quest reward there because it can only be used by you.");
return false;
}
// check if an item that is sent to a trade slot is not damaged
if (item.getDeterioration() > 0 && rpslot.getName().equals("trade")) {
player.sendPrivateText("You must not trade a damaged item with other players.");
return false;
}
}
} else {
logger.debug("entity: " + entity + " zone: " + zone);
// check if the destination is free
if ((zone != null) && zone.simpleCollides(entity, x, y, entity.getWidth(), entity.getHeight())) {
logger.warn("object " + entity + " collides with " + x + "x"
+ y);
player.sendPrivateText("There is no space on there.");
return false;
}
// and in reach
if (!entity.isContained()
&& (entity.squaredDistance(x, y) > (8 * 8))) {
logger.warn("object " + entity + " is too far away from " + x
+ "," + y);
player.sendPrivateText("That is too far away.");
return false;
}
if (!isGamblingZoneAndIsDice(entity, player)) {
// and there is a path there
final List<Node> path = Path.searchPath(entity, zone,
player.getX(), player.getY(), new Rectangle(x, y, 1, 1),
64 /* maxDestination * maxDestination */, false);
if (path.isEmpty()) {
player.sendPrivateText("There is no easy path to that place.");
return false;
}
}
}
return true;
}
/**
* returns true if zone is semos tavern and entity is dice
*
* @param entity the item
* @param player the player to get the zone from
*/
private boolean isGamblingZoneAndIsDice(final Entity entity, final Player player) {
final StendhalRPZone zone = player.getZone();
return "int_semos_tavern_0".equals(zone.getName()) && ("dice").equals(entity.getTitle());
}
/** returns true when this DestinationObject is valid. */
@Override
public boolean isValid() {
return valid;
}
/**
* returns true when this entity and the other is within the given distance.
*/
@Override
public boolean checkDistance(final Entity other, final double distance) {
if (parent != null) {
Entity base = parent;
RPObject obj = parent.getBaseContainer();
if (obj instanceof Entity) {
base = (Entity) obj;
}
return (other.nextTo(base, distance));
}
// Should be dropped to the ground. Do a proper distance calculation
return (other.squaredDistance(x, y) < distance * distance);
}
/**
* gets the name of the content slot used for the can equipped check
*
* @return name of slot
*/
public String getContentSlotName() {
if (parent != null) {
final EntitySlot entitySlot = parent.getEntitySlot(slot);
return entitySlot.getContentSlotName();
} else {
return null;
}
}
/**
* add the entity to the world (specified by the action during construction).
* Note that you should call isValid(), preCheck(..) and checkDistance(..)
* before adding an item to the world
* @param entity
* @param player
*/
public void addToWorld(Entity entity, final Player player) {
if (parent != null) {
// drop the entity into a slot
final RPSlot rpslot = ((EntitySlot) parent.getSlot(slot)).getWriteableSlot();
// check if the item can be merged with one already in the slot
if (entity instanceof StackableItem) {
final StackableItem stackEntity = (StackableItem) entity;
// find a stackable item of the same type
final Iterator<RPObject> it = rpslot.iterator();
while (it.hasNext()) {
final RPObject object = it.next();
if (object instanceof StackableItem) {
// found another stackable
final StackableItem other = (StackableItem) object;
if (other.isStackable(stackEntity)) {
new ItemLogger().merge(player, stackEntity, other);
// other is the same type...merge them
other.add(stackEntity);
entity = null;
// do not process the entity further
break;
}
}
}
}
// entity still there?
if (entity != null) {
// Set position to 0,0 (relative to container)
entity.setPosition(0, 0);
// yep, so it is not stacked. simply add it
rpslot.add(entity);
}
SingletonRepository.getRPWorld().modify(parent.getBaseContainer());
} else {
// drop the entity to the ground. Do this always in the player's
// zone.
final StendhalRPZone zone = player.getZone();
logger.debug("adding " + entity.get("name") + " to " + zone);
// HACK: Avoid a problem on database
if (entity.has("#db_id")) {
entity.remove("#db_id");
}
entity.setPosition(x, y);
logger.debug("entity set to " + x + ", " + y);
zone.add(entity, player);
logger.debug("entity has valid id: " + entity.getID());
}
}
@Override
public String[] getLogInfo() {
final String[] res = new String[3];
if (parent != null) {
res[0] = "slot";
if (parent.has("name")) {
res[1] = parent.get("name");
} else {
res[1] = parent.getDescriptionName(false);
}
res[2] = slot;
} else {
res[0] = "ground";
res[1] = player.getZone().getName();
res[2] = x + " " + y;
}
return res;
}
}