/*
* Copyright (C) 2014 MillerV
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package aspect.entity;
import aspect.event.EntityEvent;
import aspect.util.Angles;
import aspect.util.Vector3;
import aspect.entity.behavior.Behavior;
import aspect.render.ViewModel;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.LinkedList;
import static aspect.core.AspectRenderer.*;
import aspect.physics.Collider;
import aspect.physics.Motion;
import aspect.physics.RigidBody;
import aspect.util.Matrix4x4;
import aspect.util.Transform;
import aspect.world.World;
/**
* An object in the 3D or 2D world. Update and render behavior is typically
* defined by Components rather than by overriding methods in the Entity itself.
*
* @author MillerV
*/
public class Entity {
/**
* The child Entities. Their update() and draw() methods will be called when
* this Entity's update() and draw() methods are called, and they will be
* drawn relative to the position of this Entity. Entities also function as
* CameraViewpoints.
*/
public final LinkedList<Entity> children;
private final LinkedList<Behavior> behaviors;
/**
* The colliders used in this Entity's collision detection. It is not
* recommended that you add to this list yourself.
*
* @see aspect.entity.component.CmpntCollider
*/
public final Transform transform;
/**
* The container that holds this Entity.
*/
public World container;
/**
* The name of this Entity, which is returned by it's toString() method.
*/
public String name;
/**
* Whether this Entity is solid. Setting this to false does not prevent
* collision detection, but could be used to change collision handling
* behavior.
*/
public boolean solid = true;
private ViewModel viewModel;
private RigidBody rigidBody;
private Collider collider;
/**
* Create a new Entity.
*/
public Entity() {
children = new LinkedList<>();
behaviors = new LinkedList<>();
transform = new Transform();
}
/**
* Create a new Entity with the specified ViewModel. ViewModels can also be
* added using a CmpntViewModel.
*
* @param viewModel the initial ViewModel
* @see CmpntViewModel
*/
public Entity(ViewModel viewModel) {
this();
addBehavior(viewModel);
}
/**
* Add a Component to this Entity.
*
* @param cmpnt the Component to add
*/
public final void addBehavior(Behavior cmpnt) {
behaviors.add(cmpnt);
cmpnt.ent = this;
cmpnt.onAdd();
if (viewModel == null && cmpnt instanceof ViewModel) {
viewModel = (ViewModel)cmpnt;
} else if (rigidBody == null && cmpnt instanceof RigidBody) {
rigidBody = (RigidBody)cmpnt;
} else if (collider == null && cmpnt instanceof Collider) {
collider = (Collider)cmpnt;
}
}
/**
* Construct a new Component of the given class and add it to this Entity.
* This is very inefficient compared to adding a pre-constructed Component,
* and therefore is deprecated. Use {@link #addBehavior(aspect.entity.behavior.Behavior)
* }
*
* @param c the Class of the Component to add
* @param args the arguments to pass to the Component's constructor
* @deprecated
*/
@Deprecated
public final void addBehavior(Class<? extends Behavior> c, Object... args) {
Constructor cons = null;
for (Constructor check : c.getConstructors()) {
boolean canUse = true;
Class[] argTypes = check.getParameterTypes();
if (argTypes.length == args.length) {
for (int i = 0; i < args.length; i++) {
if (!argTypes[i].isAssignableFrom(args[i].getClass())) {
canUse = false;
break;
}
}
if (canUse) {
cons = check;
break;
}
}
}
try {
if (cons == null) {
throw new NoSuchMethodException();
}
Behavior component = (Behavior) cons.newInstance(args);
addBehavior(component);
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException ex) {
throw new RuntimeException(ex);
}
}
/**
* Remove the specified Behavior.
*
* @param b the Behavior to remove
*/
public void removeBehavior(Behavior b) {
b.onRemove();
b.ent = null;
behaviors.remove(b);
if (viewModel != null && b instanceof ViewModel) {
viewModel = null;
} else if (rigidBody != null && b instanceof RigidBody) {
rigidBody = null;
} else if (collider != null && b instanceof Collider) {
collider = null;
}
}
/**
* Remove a Behavior of the specified class, or whose class is a subclass of
* the specified class.
*
* @param c the Class of the Behavior to remove
*/
public void removeBehavior(Class c) {
if (!Behavior.class.isAssignableFrom(c)) {
return;
}
Behavior toRemove = null;
for (Behavior cmpnt : behaviors) {
if (c.isInstance(cmpnt)) {
toRemove = cmpnt;
break;
}
}
if (toRemove != null) {
removeBehavior(toRemove);
}
}
/**
* Get a Behavior of the specified class.
*
* @param <C> the type that will be returned
* @param c the Class to search for
* @return the Behavior or null if that Behavior was not found
*/
public <C extends Behavior> C getBehavior(Class<C> c) {
for (Behavior cmpnt : behaviors) {
if (c.isInstance(cmpnt)) {
return (C) cmpnt;
}
}
return null;
}
public ViewModel viewModel() {
return viewModel;
}
public Collider collider() {
return collider;
}
public Motion motion() {
return getBehavior(Motion.class);
}
public RigidBody rigidBody() {
return rigidBody;
}
public void onRemove() {
for (Behavior behavior : behaviors) {
behavior.onRemove();
}
}
/**
* Update all components and children of this Entity.
*/
public void update() {
for (Behavior b : behaviors) {
b.ent = this;
b.update();
}
for (Entity child : children) {
child.transform.global = transform.concat(child.transform);
child.update();
}
}
/**
* Add the specified Entity as a child of this Entity.
*
* @param child the Entity to add as a child
*/
public void addChild(Entity child) {
children.add(child);
}
/**
* Render all components and children of this Entity.
*/
public void render() {
for (Behavior b : behaviors) {
b.ent = this;
b.render();
}
for (Entity child : children) {
child.transform.global = transform.global.concat(child.transform);
child.render();
}
}
/**
* Remove this Entity from it's container, if the container is not null.
*/
public void destroy() {
if (container != null) {
container.remove(this);
}
for (Behavior behavior : behaviors) {
behavior.ent = this;
behavior.onEntityDestroy();
}
}
/**
* Fire an EntityEvent with the given name and data, which will be passed to
* all components of this Entity.
*
* @param name the event name
* @param args the event data
*/
public void fireEvent(String name, Object... args) {
fireEvent(new EntityEvent(Behavior.class, name, args));
}
/**
* Fire an EntityEvent, which will be passed to all Components of this
* Entity that are instances of the event's target class.
*
* @param event the EntityEvent to fire
*/
public void fireEvent(EntityEvent event) {
for (Behavior cmpnt : behaviors) {
cmpnt.ent = this;
event.fire(cmpnt);
}
}
@Override
public String toString() {
if (name != null) {
return name;
} else {
return super.toString();
}
}
}