package com.pointcliki.core;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.geom.Vector2f;
import com.pointcliki.event.Dispatcher;
import com.pointcliki.event.FrameManager;
import com.pointcliki.event.IEvent;
/**
* An entity is a displayable object in a scene that can be saved or loaded
*
* @author hugheth
* @since 1
*/
public abstract class Entity implements ISavable, Comparable<Entity> {
/**
* Serial key
*/
private static final long serialVersionUID = 8256564243600872505L;
protected long fID = -1;
protected EntityContainer fParent;
protected Scene fScene;
protected EntityDispatcher fDispatcher;
protected int fOrder;
protected Vector2f fPosition = new Vector2f(0, 0);
protected float fRotation = 0;
protected Vector2f fOrigin = new Vector2f(0, 0);
protected Vector2f fScale = new Vector2f(1, 1);
protected float fOpacity = 1;
/**
* The span of the entity in space relative to its parent. This can be
* set by extending classes to aid the rending of entities to the correct
* bounds.
*/
protected Rectangle fSpan = null;
/**
* The bounds during render relative to the position of the entity. This
* should be used by extending classes to aid drawing of entities. The
* bounds will always be contained inside the span.
*/
protected Rectangle fBounds = new Rectangle(0, 0, 0, 0);
/**
* Clean up any resources that the entity was using prior to it being
* destroyed. This should be called before the entity is removed from the
* scene.
*/
/**
* Create a new entity
*/
public Entity() {
fDispatcher = new EntityDispatcher();
// Register the entity
PointClikiGame.entityManager().register(this);
}
/**
* @return The unique id of the entity
*/
public long id() {
return fID;
}
/**
* @return The current scene of the entity
*/
public Scene scene() {
return fScene;
}
public PointClikiGame game() {
return fScene.game();
}
/**
* Returns the scene of the entity, cast to a particular class
* @param cls The class type of the entity's scene
* @return The scene if it is an instance of cls, otherwise null
*/
@SuppressWarnings("unchecked")
public <V> V scene(Class<V> cls) {
Scene s = scene();
if (s == null || !cls.isInstance(s)) return null;
return (V) s;
}
@SuppressWarnings("unchecked")
public <V> V game(Class<V> cls) {
PointClikiGame g = fScene.game();
if (g == null || !cls.isInstance(g)) return null;
return (V) g;
}
/**
* @param cls The class of the manager
* @return The manager registered of this type for the entity
*/
public <V> V manager(Class<V> cls) {
V manager;
if (scene() == null) {
manager = PointClikiGame.instance().manager(cls);
} else {
manager = scene().manager(cls);
}
if (manager == null) {
System.err.println("Warning: Manager [" + cls + "] can't be found for " + this);
}
return manager;
}
/**
* @return The TimeManager for the entity
*/
public TimeManager timeManager() {
return manager(TimeManager.class);
}
/**
* @return The FrameManager for the entity
*/
public FrameManager frameManager() {
return manager(FrameManager.class);
}
/**
* Clean up the entity when it is no longer needed
*/
public void cleanup() {
fDispatcher.cleanup();
fDispatcher = null;
if (fParent != null) fParent.removeChild(this);
PointClikiGame.entityManager().unregister(this);
}
/**
* Get the entity's parent, that may be the scene root or another entity
* group
* @return The parent entity
*/
public EntityContainer getParent() {
return fParent;
}
/**
* Comparison here refers to the ordering of the entity on the screen. In
* the case that the entities have the same order, we arbitrarily choose
* one to be on top, otherwise they may be considered to be equal, such as
* is the case in a Set implementation, where "duplicates" are ignored.
*/
@Override
public int compareTo(Entity o) {
if (fOrder < o.order()) return -1;
return 1;
}
@Override
public boolean equals(Object o) {
return (o instanceof Entity) && ((Entity) o).fID == fID;
}
/**
* Set the entity's parent. This should be called when the entity is added
* to an entity group.
* @param parent The new parent of the entity
*/
protected void setParent(EntityContainer parent) {
fParent = parent;
if (parent != null) setScene(parent.scene());
else setScene(null);
}
/**
* @return The order of the entity which determines when it is rendered
*/
public int order() {
return fOrder;
}
/**
* @param o The order to set the entity to
*/
public void order(int o) {
fOrder = o;
if (fParent != null) fParent.fChildren.update(this);
}
/**
* @return The rotation of the entity around the position of the entity
*/
public float rotation() {
return fRotation;
}
/**
* @param rotation Set the rotation of the entity, in degrees
* @return The Entity instance
*/
public Entity rotate(float rotation) {
fRotation = rotation;
return this;
}
public Vector2f origin() {
return fOrigin;
}
public Entity origin(Vector2f origin) {
fOrigin = origin;
return this;
}
/**
* @return The (x, y) scale of the entity
*/
public Vector2f scale() {
return fScale;
}
/**
* @param scale The new scale of the entity
* @return The Entity instance
*/
public Entity scale(Vector2f scale) {
fScale = scale;
return this;
}
public Entity scale(float s) {
fScale = new Vector2f(s, s);
return this;
}
/**
* @return The position of the entity relative to its container
*/
public Vector2f position() {
return fPosition.copy();
}
/**
* @param position The new position of the entity
* @return The Entity instance
*/
public Entity position(Vector2f position) {
fPosition = position;
return this;
}
public Rectangle span() {
return fSpan;
}
/**
* @return The opacity of the entity, from 0 to 1
*/
public float opacity() {
return fOpacity;
}
/**
* @param opacity The new opacity of the entity
* @return The Entity instance
*/
public Entity opacity(float opacity) {
fOpacity = opacity;
return this;
}
/**
* @return The dispatcher for the entity that dispatches events originating
* from the entity
*/
public EntityDispatcher dispatcher() {
return fDispatcher;
}
/**
* @param v The new dimensions of the entity
* @return The entity instance
*/
public Entity resize(Vector2f v) {
fScale.x = v.x / fSpan.getWidth();
fScale.y = v.y / fSpan.getHeight();
return this;
}
@Override
public int hashCode() {
return (int) fID;
}
/**
* Sets the current scene of the entity
* @param scene The current scene of the entity
*/
protected void setScene(Scene scene) {
if (fScene != null) {
if (fScene.equals(scene)) return;
scene().unregister(this);
}
fScene = scene;
if (scene != null) {
// Re-register the entity to a new id
PointClikiGame.entityManager().register(this);
scene.register(this);
}
}
/**
* Render the entity to the screen
*
* @param graphics The graphics instance to draw the entity's appearance onto
* @param view The viewport being rendered through
* @param currentTime The time elapsed since the scene was started
*/
public void render(Graphics graphics, long currentTime) {
// Update the movement, if there is one
if (fDispatcher != null) fDispatcher.dispatchEvent(RenderEvent.TYPE, new RenderEvent(graphics, currentTime));
}
/**
* Calculate the bounds of the displayable area that should be drawn to
* by the entity during render. Entities should avoid drawing outside this
* area. If the entity's span is set, the bounds field will be updated with
* a sub-section of the span. Otherwise, the parent's bounds will just be
* translated to this entity.
*
* @param bounds The parent entity's bounds relative to its origin
public void calculateBounds(Rectangle bounds) {
// Position of the bounds relative to the origin
float ox = bounds.getX() - fPosition.x;
float oy = bounds.getY() - fPosition.y;
// Translate the bounds
fBounds.setBounds(ox, oy, bounds.getWidth(), bounds.getHeight());
// Crop the bounds
if (fSpan != null) {
// Position of the bounds or span relative to origin, whichever is
// more south easterly.
float lx = Math.max(ox, fSpan.getX());
float ly = Math.max(oy, fSpan.getY());
// Ensure these points don't lie outside the span
lx = Math.min(lx, fSpan.getMaxX());
ly = Math.min(ly, fSpan.getMaxY());
// Position of the south east corner of the bounds or span relative
// to origin, whichever is more north westerly.
float rx = Math.min(fSpan.getMaxX(), fBounds.getMaxX());
float ry = Math.min(fSpan.getMaxY(), fBounds.getMaxY());
// Ensure these points don't lie outside the span
rx = Math.max(rx, fSpan.getX());
ry = Math.max(ry, fSpan.getY());
// Update the bounds, ensuring they lie within the span
fBounds.setBounds(
lx,
ly,
Math.max(rx - lx, 0),
Math.max(ry - ly, 0)
);
}
}
*/
@Override
public ISavable snapshot() throws CloneNotSupportedException {
Entity clone = (Entity) super.clone();
// Clone rectangles
clone.fSpan = new Rectangle(fSpan.getX(), fSpan.getY(), fSpan.getWidth(), fSpan.getHeight());
// Clone vectors
clone.fPosition = fPosition.copy();
clone.fScale = fScale.copy();
// Ensure the movement is parented to the correct entity
clone.fDispatcher = (EntityDispatcher) fDispatcher.snapshot();
// Nullify parent and scene as this information is not retrievable here
clone.fParent = null;
clone.fScene = null;
return clone;
}
@Override
public String toString() {
return "[Entity #" + fID + "]";
}
/**
* A dispatcher for a particular entity
* @author Hugheth
* @since 3
*/
public class EntityDispatcher extends Dispatcher<IEvent> {
/**
* Serial key
*/
private static final long serialVersionUID = 786643740900375864L;
@Override
public void dispatchEvent(String type, IEvent event) {
super.dispatchEvent(type, event);
}
@Override
public int dispatchIterativeEvent(String type, IEvent event, int maxIterations) {
return super.dispatchIterativeEvent(type, event, maxIterations);
}
}
/**
* An event that is dispatched by an entity when it is being rendered
* @author Hugheth
* @since 3
*/
public class RenderEvent implements IEvent {
protected Graphics fGraphics;
protected long fCurrentTime;
/**
* The single type of this event
*/
public static final String TYPE = "render";
protected RenderEvent(Graphics graphics, long currentTime) {
fGraphics = graphics;
fCurrentTime = currentTime;
}
/**
* @return The graphics being rendered to
*/
public Graphics graphics() {
return fGraphics;
}
/**
* @return The current time at rendering
*/
public long currentTime() {
return fCurrentTime;
}
}
}