/* $Id: Entity.java,v 1.226 2011/03/09 19:51:35 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.client.entity;
import games.stendhal.client.listener.RPObjectChangeListener;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import marauroa.common.game.RPObject;
import marauroa.common.game.RPSlot;
public class Entity implements RPObjectChangeListener, IEntity {
/** The current x location of this entity. */
protected double x;
/** The current y location of this entity. */
protected double y;
/**
* Amount of entity-to-entity resistance (0-100).
*/
protected int resistance;
/**
* The entity visibility.
*/
protected int visibility;
/**
* Change listeners.
*/
protected EntityChangeListener[] changeListeners;
private final List<ContentChangeListener> contentChangeListeners = new CopyOnWriteArrayList<ContentChangeListener>();
/** The arianne object associated with this game entity. */
protected RPObject rpObject;
/**
* The entity class.
*/
protected String clazz;
/**
* The entity name.
*/
protected String name;
/**
* The entity sub-class.
*/
protected String subclazz;
/**
* The entity title.
*/
protected String title;
/**
* The entity type.
*/
protected String type;
/**
* Quick work-around to prevent fireMovementEvent() from calling in
* onChangedAdded() from other initialize() hack.
* TODO: get rid off inAdd variable.
*/
protected boolean inAdd;
/**
* The entity width.
*/
private double width;
/**
* The entity height.
*/
private double height;
public Entity() {
clazz = null;
name = null;
subclazz = null;
title = null;
type = null;
x = 0.0;
y = 0.0;
changeListeners = new EntityChangeListener[0];
}
//
// Entity
//
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#addChangeListener(games.stendhal.client.entity.EntityChangeListener)
*/
public void addChangeListener(final EntityChangeListener listener) {
EntityChangeListener[] newListeners;
final int len = changeListeners.length;
newListeners = new EntityChangeListener[len + 1];
System.arraycopy(changeListeners, 0, newListeners, 0, len);
newListeners[len] = listener;
changeListeners = newListeners;
}
public void addContentChangeListener(final ContentChangeListener listener) {
contentChangeListeners.add(listener);
}
public void removeContentChangeListener(final ContentChangeListener listener) {
contentChangeListeners.remove(listener);
}
/**
* Fire change to all registered listeners.
*
* @param property
* The changed property.
*/
public void fireChange(final Property property) {
final EntityChangeListener[] listeners = changeListeners;
for (final EntityChangeListener l : listeners) {
l.entityChanged(this, property);
}
}
/*
* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#isUser()
*/
public boolean isUser() {
return false;
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#getArea()
*/
public Rectangle2D getArea() {
return new Rectangle.Double(getX(), getY(), getWidth(), getHeight());
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#getVisibility()
*/
public int getVisibility() {
return visibility;
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#getHeight()
*/
public double getHeight() {
return height;
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#getID()
*/
public final RPObject.ID getID() {
if (rpObject == null) {
return null;
} else {
return rpObject.getID();
}
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#getID()
*/
public final int getObjectID() {
if (rpObject == null) {
return -1;
} else {
return rpObject.getInt("id");
}
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#getEntityClass()
*/
public String getEntityClass() {
return clazz;
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#getName()
*/
public String getName() {
return name;
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#getEntitySubclass()
*/
public String getEntitySubclass() {
return subclazz;
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#getTitle()
*/
public String getTitle() {
if (title != null) {
return title;
} else if (name != null) {
return name;
} else if (type != null) {
return type;
} else {
return null;
}
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#getType()
*/
public String getType() {
return type;
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#getX()
*/
public double getX() {
return x;
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#getY()
*/
public double getY() {
return y;
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#getRPObject()
*/
public RPObject getRPObject() {
return rpObject;
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#getWidth()
*/
public double getWidth() {
return width;
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#isOnGround()
*/
public boolean isOnGround() {
return !rpObject.isContained();
}
/**
* Process attribute changes that may affect positioning. This is needed
* because different entities may want to process coordinate changes more
* gracefully.
*
* @param base
* The previous values.
* @param diff
* The changes.
*/
protected void processPositioning(final RPObject base, final RPObject diff) {
boolean moved = false;
if (diff.has("x")) {
final int nx = diff.getInt("x");
if (nx != x) {
x = nx;
moved = true;
}
}
if (diff.has("y")) {
final int ny = diff.getInt("y");
if (ny != y) {
y = ny;
moved = true;
}
}
if (moved) {
onPosition(x, y);
}
}
/**
* When the entity's position changed.
*
* @param x
* The new X coordinate.
* @param y
* The new Y coordinate.
*/
protected void onPosition(final double x, final double y) {
fireChange(PROP_POSITION);
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#getResistance()
*/
public int getResistance() {
return resistance;
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#getResistance(games.stendhal.client.entity.IEntity)
*/
public int getResistance(final IEntity entity) {
return ((getResistance() * entity.getResistance()) / 100);
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#getSlot(java.lang.String)
*/
public RPSlot getSlot(final String name) {
if (rpObject.hasSlot(name)) {
return rpObject.getSlot(name);
}
return null;
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#initialize(marauroa.common.game.RPObject)
*/
public void initialize(final RPObject object) {
rpObject = object;
/*
* Class
*/
if (object.has("class")) {
clazz = object.get("class");
} else {
clazz = null;
}
/*
* Name
*/
if (object.has("name")) {
name = object.get("name");
} else {
name = null;
}
/*
* Sub-Class
*/
if (object.has("subclass")) {
subclazz = object.get("subclass");
} else {
subclazz = null;
}
/*
* Size
*/
if (object.has("height")) {
height = object.getDouble("height");
} else {
height = 1.0;
}
if (object.has("width")) {
width = object.getDouble("width");
} else {
width = 1.0;
}
/*
* Title
*/
if (object.has("title")) {
title = object.get("title");
} else {
title = null;
}
/*
* Resistance
*/
if (object.has("resistance")) {
resistance = object.getInt("resistance");
} else {
resistance = 0;
}
/*
* Visibility
*/
if (object.has("visibility")) {
visibility = object.getInt("visibility");
} else {
visibility = 100;
}
/*
* Coordinates
*/
if (object.has("x")) {
x = object.getInt("x");
} else {
x = 0.0;
}
if (object.has("y")) {
y = object.getInt("y");
} else {
y = 0.0;
}
/*
* Notify placement
*/
onPosition(x, y);
/*
* Type
*/
type = object.getRPClass().getName();
inAdd = true;
onChangedAdded(new RPObject(), object);
inAdd = false;
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#isObstacle(games.stendhal.client.entity.IEntity)
*/
public boolean isObstacle(final IEntity entity) {
return ((entity != this) && (getResistance(entity) > 95));
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#release()
*/
public void release() {
// ignored
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#removeChangeListener(games.stendhal.client.entity.EntityChangeListener)
*/
public void removeChangeListener(final EntityChangeListener listener) {
EntityChangeListener[] newListeners;
int idx;
idx = changeListeners.length;
while (idx-- != 0) {
if (changeListeners[idx] == listener) {
newListeners = new EntityChangeListener[changeListeners.length - 1];
if (idx != 0) {
System.arraycopy(changeListeners, 0, newListeners, 0, idx);
}
if (++idx != changeListeners.length) {
System.arraycopy(changeListeners, idx, newListeners,
idx - 1, changeListeners.length - idx);
}
changeListeners = newListeners;
break;
}
}
}
/* (non-Javadoc)
* @see games.stendhal.client.entity.IEntity#update(int)
*/
public void update(final int delta) {
}
//
// RPObjectChangeListener
//
/**
* An object was added.
*
* @param object
* The object.
*/
public final void onAdded(final RPObject object) {
// DEPRECATED - Moving to different listener. Use initialize().
}
/**
* The object added/changed attribute(s).
*
* @param object
* The base object.
* @param changes
* The changes.
*/
public void onChangedAdded(final RPObject object, final RPObject changes) {
if (inAdd) {
return;
}
/*
* Class
*/
if (changes.has("class")) {
clazz = changes.get("class");
fireChange(PROP_CLASS);
}
/*
* Name
*/
if (changes.has("name")) {
name = changes.get("name");
fireChange(PROP_NAME);
fireChange(PROP_TITLE);
}
/*
* Sub-Class
*/
if (changes.has("subclass")) {
subclazz = changes.get("subclass");
fireChange(PROP_CLASS);
}
/*
* Size
*/
boolean sizeChange = false;
if (changes.has("width")) {
width = changes.getDouble("width");
sizeChange = true;
}
if (changes.has("height")) {
height = changes.getDouble("height");
sizeChange = true;
}
if (sizeChange) {
fireChange(PROP_SIZE);
}
/*
* Title
*/
if (changes.has("title")) {
title = changes.get("title");
fireChange(PROP_TITLE);
}
/*
* State
*/
// TODO: move into child class?
if (changes.has("state")) {
fireChange(PROP_STATE);
}
/*
* Resistance
*/
if (changes.has("resistance")) {
resistance = changes.getInt("resistance");
}
/*
* Entity visibility
*/
if (changes.has("visibility")) {
visibility = changes.getInt("visibility");
fireChange(PROP_VISIBILITY);
}
/*
* Content changes
*/
for (RPSlot slot : changes.slots()) {
for (ContentChangeListener listener : contentChangeListeners) {
listener.contentAdded(slot);
}
}
/*
* Position changes
*/
processPositioning(object, changes);
}
/**
* The object removed attribute(s).
*
* @param object
* The base object.
* @param changes
* The changes.
*/
public void onChangedRemoved(final RPObject object, final RPObject changes) {
/*
* Class
*/
if (changes.has("class")) {
clazz = null;
fireChange(PROP_CLASS);
}
/*
* Name
*/
if (changes.has("name")) {
name = null;
fireChange(PROP_NAME);
fireChange(PROP_TITLE);
}
/*
* Sub-Class
*/
if (changes.has("subclass")) {
subclazz = null;
fireChange(PROP_CLASS);
}
/*
* Size
*/
boolean sizeChange = false;
if (changes.has("width")) {
width = 1.0;
sizeChange = true;
}
if (changes.has("height")) {
height = 1.0;
sizeChange = true;
}
if (sizeChange) {
fireChange(PROP_SIZE);
}
/*
* Title
*/
if (changes.has("title")) {
title = null;
fireChange(PROP_TITLE);
}
/*
* Resistance
*/
if (changes.has("resistance")) {
resistance = 0;
}
/*
* Visibility
*/
if (changes.has("visibility")) {
visibility = 100;
fireChange(PROP_VISIBILITY);
}
/*
* Content changes
*/
for (RPSlot slot : changes.slots()) {
for (ContentChangeListener listener : contentChangeListeners) {
listener.contentRemoved(slot);
}
}
}
/**
* An object was removed.
*
* @param object
* The object.
* @deprecated Moving to different listener. Use release().
*/
@Deprecated
public final void onRemoved(final RPObject object) {
}
/**
* A slot object was added.
*
* @param object
* The container object.
* @param slotName
* The slot name.
* @param sobject
* The slot object.
*/
public void onSlotAdded(final RPObject object, final String slotName,
final RPObject sobject) {
}
/**
* A slot object added/changed attribute(s).
*
* @param object
* The base container object.
* @param slotName
* The container's slot name.
* @param sobject
* The slot object.
* @param schanges
* The slot object changes.
*/
public void onSlotChangedAdded(final RPObject object,
final String slotName, final RPObject sobject,
final RPObject schanges) {
}
/**
* A slot object removed attribute(s).
*
* @param object
* The base container object.
* @param slotName
* The container's slot name.
* @param sobject
* The slot object.
* @param schanges
* The slot object changes.
*/
public void onSlotChangedRemoved(final RPObject object,
final String slotName, final RPObject sobject,
final RPObject schanges) {
}
/**
* A slot object was removed.
*
* @param object
* The container object.
* @param slotName
* The slot name.
* @param sobject
* The slot object.
*/
public void onSlotRemoved(final RPObject object, final String slotName,
final RPObject sobject) {
}
//
// Object
//
@Override
public String toString() {
final StringBuilder sbuf = new StringBuilder();
sbuf.append(getClass().getName());
/*
* Technically not all entities have 'name', but enough to debug most
* when used
*/
if ((rpObject != null) && rpObject.has("name")) {
sbuf.append('[');
sbuf.append(rpObject.get("name"));
sbuf.append(']');
}
sbuf.append('@');
sbuf.append(System.identityHashCode(this));
return sbuf.toString();
}
/**
* gets the name of the cursor to use for this entity
*
* @return name of cursor
*/
public String getCursor() {
if (!rpObject.has("cursor")) {
return null;
}
return rpObject.get("cursor");
}
/**
* Get identifier path for the entity.
*
* @return List of object identifiers and slot names that make up the
* complete path to the entity
*/
public List<String> getPath() {
LinkedList<String> path = new LinkedList<String>();
RPObject object = getRPObject();
while (object != null) {
// Prepend the items; They'll be in a nice container-slot-content
// order for the server
path.add(0, Integer.toString(object.getID().getObjectID()));
RPSlot slot = object.getContainerSlot();
if (slot != null) {
path.add(0, slot.getName());
}
object = object.getContainer();
}
return path;
}
}