Package it.marteEngine.entity

Source Code of it.marteEngine.entity.Entity

package it.marteEngine.entity;

import it.marteEngine.ME;
import it.marteEngine.StateManager;
import it.marteEngine.World;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import org.newdawn.slick.Animation;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.SpriteSheet;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.geom.Vector2f;

//TODO modify hitbox coordinates to a real shape without changing method interface.
//TODO a shape can be rotated and scaled when the entity is rotated and scaled.
public abstract class Entity implements Comparable<Entity> {

  /** default collidable type SOLID */
  public static final String SOLID = "Solid";

  /** predefined type for player */
  public static final String PLAYER = "Player";

  /** the world this entity lives in */
  public World world = null;

  /** unique identifier */
  public String name;

  /** x position */
  public float x;
  /** y position */
  public float y;

  /**
   * If this entity is centered the x,y position is in the center. otherwise
   * the x,y position is the top left corner.
   */
  public boolean centered = false;

  /**
   * width of the entity. not necessarily the width of the hitbox. Used for
   * world wrapping
   */
  public int width;
  /**
   * height of the entity. not necessarily the height of the hitbox. Used for
   * world wrapping
   */
  public int height;

  public float previousx, previousy;

  /** start x and y position stored for reseting for example. very helpful */
  public float startx, starty;

  public boolean wrapHorizontal = false;
  public boolean wrapVertical = false;

  /** speed vector (x,y): specifies x and y movement per update call in pixels */
  public Vector2f speed = new Vector2f(0, 0);

  /**
   * angle in degrees from 0 to 360, used for drawing the entity rotated. NOT
   * used for direction!
   */
  protected int angle = 0;

  /** scale used for both horizontal and vertical scaling. */
  public float scale = 1.0f;

  /**
   * color of the entity, mainly used for alpha transparency, but could also
   * be used for tinting
   */
  private Color color = new Color(Color.white);

  private AlarmContainer alarms;

  protected SpriteSheet sheet;
  private Map<String, Animation> animations = new HashMap<String, Animation>();
  private String currentAnim;
  public int duration = 200;
  public int depth = -1;

  /** static image for non-animated entity */
  public Image currentImage;

  /** input commands */
  public Map<String, int[]> commands = new HashMap<String, int[]>();

  /** The types this entity can collide with */
  private HashSet<String> collisionTypes = new HashSet<String>();

  /** true if this entity can receive updates */
  public boolean active = true;
  public boolean collidable = true;
  public boolean visible = true;

  public float hitboxOffsetX;
  public float hitboxOffsetY;
  public int hitboxWidth;
  public int hitboxHeight;

  public StateManager stateManager;

  /**
   * Create a new entity positioned at the (x,y) coordinates.
   */
  public Entity(float x, float y) {
    this.x = x;
    this.y = y;
    this.startx = x;
    this.starty = y;
    stateManager = new StateManager();
    alarms = new AlarmContainer(this);
  }

  /**
   * Create a new entity positioned at the (x,y) coordinates. Displayed as an
   * image.
   */
  public Entity(float x, float y, Image image) {
    this(x, y);
    setGraphic(image);
  }

  /**
   * Set if the image or animation must be centered
   */
  public void setCentered(boolean center) {
    int whalf = 0, hhalf = 0;
    if (currentImage != null) {
      whalf = currentImage.getWidth() / 2;
      hhalf = currentImage.getHeight() / 2;
    }
    if (currentAnim != null) {
      whalf = animations.get(currentAnim).getWidth() / 2;
      hhalf = animations.get(currentAnim).getHeight() / 2;
    }
    if (center) {
      // modify hitbox position accordingly - move it a bit up and left
      this.hitboxOffsetX -= whalf;
      this.hitboxOffsetY -= hhalf;
      this.centered = true;
    } else {
      if (centered) {
        // reset hitbox position to top left origin
        this.hitboxOffsetX += whalf;
        this.hitboxOffsetY += hhalf;
      }
      this.centered = false;
    }
  }

  public void update(GameContainer container, int delta)
      throws SlickException {
    previousx = x;
    previousy = y;
    if (stateManager != null && stateManager.currentState() != null) {
      stateManager.update(container, delta);
      return;
    }
    updateAnimation(delta);
    if (speed != null) {
      x += speed.x;
      y += speed.y;
    }
    checkWorldBoundaries();
    previousx = x;
    previousy = y;
  }

  protected void updateAnimation(int delta) {
    if (animations != null) {
      if (currentAnim != null) {
        Animation anim = animations.get(currentAnim);
        if (anim != null) {
          anim.update(delta);
        }
      }
    }
  }

  public void render(GameContainer container, Graphics g)
      throws SlickException {
    if (stateManager != null && stateManager.currentState() != null) {
      stateManager.render(g);
      return;
    }
    float xpos = x, ypos = y;
    if (currentAnim != null) {
      Animation anim = animations.get(currentAnim);
      int w = anim.getWidth();
      int h = anim.getHeight();
      int whalf = w / 2;
      int hhalf = h / 2;
      if (centered) {
        xpos = x - (whalf * scale);
        ypos = y - (hhalf * scale);
      }
      if (angle != 0) {
        g.rotate(x, y, angle);
      }
      anim.draw(xpos, ypos, w * scale, h * scale, color);
      if (angle != 0)
        g.resetTransform();
    } else if (currentImage != null) {
      currentImage.setAlpha(color.a);
      int w = currentImage.getWidth() / 2;
      int h = currentImage.getHeight() / 2;
      if (centered) {
        xpos -= w;
        ypos -= h;
        currentImage.setCenterOfRotation(w, h);
      } else
        currentImage.setCenterOfRotation(0, 0);

      if (angle != 0) {
        currentImage.setRotation(angle);
      }
      if (scale != 1.0f) {
        if (centered)
          g.translate(xpos - (w * scale - w), ypos - (h * scale - h));
        else
          g.translate(xpos, ypos);
        g.scale(scale, scale);
        g.drawImage(currentImage, 0, 0);
      } else
        g.drawImage(currentImage, xpos, ypos);
      if (scale != 1.0f)
        g.resetTransform();
    }
    if (ME.debugEnabled && collidable) {
      g.setColor(ME.borderColor);
      Rectangle hitBox = new Rectangle(x + hitboxOffsetX, y
          + hitboxOffsetY, hitboxWidth, hitboxHeight);
      g.draw(hitBox);
      g.setColor(Color.white);
      g.drawRect(x, y, 1, 1);
      // draw entity center
      if (width != 0 && height != 0) {
        float centerX = x + width / 2;
        float centerY = y + height / 2;
        g.setColor(Color.green);
        g.drawRect(centerX, centerY, 1, 1);
        g.setColor(Color.white);
      }
    }
  }

  /**
   * Set an image as graphic
   */
  public void setGraphic(Image image) {
    this.currentImage = image;
    this.width = image.getWidth();
    this.height = image.getHeight();
  }

  /**
   * Set a sprite sheet as graphic
   */
  public void setGraphic(SpriteSheet sheet) {
    this.sheet = sheet;
    this.width = sheet.getSprite(0, 0).getWidth();
    this.height = sheet.getSprite(0, 0).getHeight();
  }

  public void addAnimation(String animName, boolean loop, int row,
      int... frames) {
    Animation anim = new Animation(false);
    anim.setLooping(loop);
    for (int frame : frames) {
      anim.addFrame(sheet.getSprite(frame, row), duration);
    }
    addAnimation(animName, anim);
  }

  public Animation addAnimation(SpriteSheet sheet, String animName,
      boolean loop, int row, int... frames) {
    Animation anim = new Animation(false);
    anim.setLooping(loop);
    for (int frame : frames) {
      anim.addFrame(sheet.getSprite(frame, row), duration);
    }
    addAnimation(animName, anim);
    return anim;
  }

  /**
   * Add animation to entity. The frames can be flipped horizontally and/or
   * vertically.
   */
  public void addFlippedAnimation(String animName, boolean loop,
      boolean fliphorizontal, boolean flipvertical, int row,
      int... frames) {
    Animation anim = new Animation(false);
    anim.setLooping(loop);
    for (int frame : frames) {
      anim.addFrame(
          sheet.getSprite(frame, row).getFlippedCopy(fliphorizontal,
              flipvertical), duration);
    }
    addAnimation(animName, anim);
  }

  /**
   * Add an animation.The first animation added is set as the current
   * animation.
   */
  public void addAnimation(String animName, Animation animation) {
    boolean firstAnim = animations.isEmpty();
    animations.put(animName, animation);

    if (firstAnim) {
      setAnim(animName);
    }
  }

  /**
   * Start playing the animation stored as animName.
   *
   * @param animName
   *            The name of the animation to play
   * @throws IllegalArgumentException
   *             If there is no animation stored as animName
   * @see #addAnimation(String, org.newdawn.slick.Animation)
   */
  public void setAnim(String animName) {
    if (!animations.containsKey(animName)) {
      throw new IllegalArgumentException("No animation for " + animName);
    }
    currentAnim = animName;
    Animation currentAnimation = animations.get(currentAnim);
    width = currentAnimation.getWidth();
    height = currentAnimation.getHeight();
  }

  /**
   * define commands to handle inputs
   *
   * @param command
   *            name of the command
   * @param keys
   *            keys or mouse input from {@link Input} class
   */
  public void define(String command, int... keys) {
    commands.put(command, keys);
  }

  /**
   * Check if a command is down
   */
  public boolean check(String command) {
    if (!commands.containsKey(command))
      return false;

    int[] checked = commands.get(command);
    Input input = world.container.getInput();
    for (int i = 0; i < checked.length; i++) {
      if (input.isKeyDown(checked[i])) {
        return true;
      } else if (checked[i] < 10) {
        // 10 is max number of button on a mouse, @see Input
        if (input.isMousePressed(checked[i])) {
          return true;
        }
      }
    }
    return false;
  }

  /**
   * Check if a command is pressed
   */
  public boolean pressed(String command) {
    if (!commands.containsKey(command))
      return false;

    int[] checked = commands.get(command);
    Input input = world.container.getInput();
    for (int i = 0; i < checked.length; i++) {
      if (input.isKeyPressed(checked[i])) {
        return true;
      } else if (checked[i] == Input.MOUSE_LEFT_BUTTON
          || checked[i] == Input.MOUSE_RIGHT_BUTTON) {
        if (input.isMousePressed(checked[i])) {
          return true;
        }
      }
    }
    return false;
  }

  /**
   * Compare to another entity on zLevel
   */
  public int compareTo(Entity o) {
    if (depth == o.depth)
      return 0;
    if (depth > o.depth)
      return 1;
    return -1;
  }

  /**
   * Set the hitbox used for collision detection. If an entity has an hitbox,
   * it is collidable against other entities.
   *
   * @param xOffset
   *            The offset of the hitbox on the x axis. Relative to the top
   *            left point of the entity.
   * @param yOffset
   *            The offset of the hitbox on the y axis. Relative to the top
   *            left point of the entity.
   * @param width
   *            The width of the rectangle in pixels
   * @param height
   *            The height of the rectangle in pixels
   */
  public void setHitBox(float xOffset, float yOffset, int width, int height) {
    this.hitboxOffsetX = xOffset;
    this.hitboxOffsetY = yOffset;
    this.hitboxWidth = width;
    this.hitboxHeight = height;
    this.width = width;
    this.height = height;
    this.collidable = true;
  }

  /**
   * Add a type that this entity can collide with. To allow collision with
   * other entities add at least 1 type. For example in a space invaders game.
   * To allow a ship to collide with a bullet and a monster:
   * ship.addType("bullet", "monster")
   *
   * @param types
   *            The types that this entity can collide with.
   */
  public boolean addType(String... types) {
    return collisionTypes.addAll(Arrays.asList(types));
  }

  /**
   * Reset the types that this entity can collide with
   */
  public void clearTypes() {
    collisionTypes.clear();
  }

  /**
   * Check for a collision with another entity of the given entity type. Two
   * entities collide when the hitbox of this entity intersects with the
   * hitbox of another entity.
   * <p/>
   * The hitbox starts at the provided x,y coordinates. The size and offset of
   * the hitbox is set in the {@link #setHitBox(float, float, int, int)}
   * method.
   * <p/>
   * If a collision occurred then both the entities are notified of the
   * collision by the {@link #collisionResponse(Entity)} method.
   *
   * @param type
   *            The type of another entity to check for collision.
   * @param x
   *            The x coordinate where the the collision check needs to be
   *            done.
   * @param y
   *            The y coordinate where the the collision check needs to be
   *            done.
   * @return The first entity that is colliding with this entity at the x,y
   *         coordinates, or NULL if there is no collision.
   */
  public Entity collide(String type, float x, float y) {
    if (type == null || type.isEmpty())
      return null;
    // offset
    for (Entity entity : world.getEntities()) {
      if (entity.collidable && entity.isType(type)) {
        if (!entity.equals(this)
            && x + hitboxOffsetX + hitboxWidth > entity.x
                + entity.hitboxOffsetX
            && y + hitboxOffsetY + hitboxHeight > entity.y
                + entity.hitboxOffsetY
            && x + hitboxOffsetX < entity.x + entity.hitboxOffsetX
                + entity.hitboxWidth
            && y + hitboxOffsetY < entity.y + entity.hitboxOffsetY
                + entity.hitboxHeight) {
          this.collisionResponse(entity);
          entity.collisionResponse(this);
          return entity;
        }
      }
    }
    return null;
  }

  /**
   * Checks for collision against multiple types.
   *
   * @see #collide(String, float, float)
   */
  public Entity collide(String[] types, float x, float y) {
    for (String type : types) {
      Entity e = collide(type, x, y);
      if (e != null)
        return e;
    }
    return null;
  }

  /**
   * Checks if this Entity collides with a specific Entity.
   *
   * @param other
   *            The Entity to check for collision
   * @param x
   *            The x coordinate where the the collision check needs to be
   *            done.
   * @param y
   *            The y coordinate where the the collision check needs to be
   *            done.
   * @return The entity that is colliding with the other entity at the x,y
   *         coordinates, or NULL if there is no collision.
   */
  public Entity collideWith(Entity other, float x, float y) {
    if (other.collidable) {
      if (!other.equals(this)
          && x + hitboxOffsetX + hitboxWidth > other.x
              + other.hitboxOffsetX
          && y + hitboxOffsetY + hitboxHeight > other.y
              + other.hitboxOffsetY
          && x + hitboxOffsetX < other.x + other.hitboxOffsetX
              + other.hitboxWidth
          && y + hitboxOffsetY < other.y + other.hitboxOffsetY
              + other.hitboxHeight) {
        this.collisionResponse(other);
        other.collisionResponse(this);
        return other;
      }
      return null;
    }
    return null;
  }

  public List<Entity> collideInto(String type, float x, float y) {
    if (type == null || type.isEmpty())
      return null;
    ArrayList<Entity> collidingEntities = null;
    for (Entity entity : world.getEntities()) {
      if (entity.collidable && entity.isType(type)) {
        if (!entity.equals(this)
            && x + hitboxOffsetX + hitboxWidth > entity.x
                + entity.hitboxOffsetX
            && y + hitboxOffsetY + hitboxHeight > entity.y
                + entity.hitboxOffsetY
            && x + hitboxOffsetX < entity.x + entity.hitboxOffsetX
                + entity.hitboxWidth
            && y + hitboxOffsetY < entity.y + entity.hitboxOffsetY
                + entity.hitboxHeight) {
          this.collisionResponse(entity);
          entity.collisionResponse(this);
          if (collidingEntities == null)
            collidingEntities = new ArrayList<Entity>();
          collidingEntities.add(entity);
        }
      }
    }
    return collidingEntities;
  }

  /**
   * Checks if this Entity contains the specified point. The
   * {@link #collisionResponse(Entity)} is called to notify this entity of the
   * collision.
   *
   * @param x
   *            The x coordinate of the point to check
   * @param y
   *            The y coordinate of the point to check
   * @return If this entity contains the specified point
   */
  public boolean collidePoint(float x, float y) {
    if (x >= this.x - hitboxOffsetX && y >= this.y - hitboxOffsetY
        && x < this.x - hitboxOffsetX + width
        && y < this.y - hitboxOffsetY + height) {
      this.collisionResponse(null);
      return true;
    }
    return false;
  }

  /**
   * overload if you want to act on addition to the world
   */
  public void addedToWorld() {

  }

  /**
   * overload if you want to act on removal from the world
   */
  public void removedFromWorld() {

  }

  /**
   * Response to a collision with another entity
   *
   * @param other
   *            The other entity that collided with us.
   */
  public void collisionResponse(Entity other) {

  }

  /**
   * overload if you want to act on leaving world boundaries
   */
  public void leftWorldBoundaries() {

  }

  public Image getCurrentImage() {
    return currentImage;
  }

  public void setWorld(World world) {
    this.world = world;
  }

  public void checkWorldBoundaries() {
    if ((x + width) < 0) {
      leftWorldBoundaries();
      if (wrapHorizontal) {
        x = this.world.width + 1;
      }
    }
    if (x > this.world.width) {
      leftWorldBoundaries();
      if (wrapHorizontal) {
        x = (-width + 1);
      }
    }
    if ((y + height) < 0) {
      leftWorldBoundaries();
      if (wrapVertical) {
        y = this.world.height + 1;
      }
    }
    if (y > this.world.height) {
      leftWorldBoundaries();
      if (wrapVertical) {
        y = (-height + 1);
      }
    }
  }

  public String toString() {
    StringBuffer sb = new StringBuffer();
    sb.append("name: ").append(name);
    sb.append(", types: ").append(collisionTypesToString());
    sb.append(", depth: ").append(depth);
    sb.append(", x: ").append(x);
    sb.append(", y: ").append(y);
    return sb.toString();
  }

  public String[] getCollisionTypes() {
    return collisionTypes.toArray(new String[collisionTypes.size()]);
  }

  public boolean isType(String type) {
    return collisionTypes.contains(type);
  }

  /**
   * remove ourselves from world
   */
  public void destroy() {
    this.world.remove(this);
    this.visible = false;
  }

  /***************** some methods to deal with angles and vectors ************************************/

  public int getAngleToPosition(Vector2f otherPos) {
    Vector2f diff = otherPos.sub(new Vector2f(x, y));
    return (((int) diff.getTheta()) + 90) % 360;
  }

  public int getAngleDiff(int angle1, int angle2) {
    return ((((angle2 - angle1) % 360) + 540) % 360) - 180;
  }

  public Vector2f getPointWithAngleAndDistance(int angle, float distance) {
    Vector2f point;
    float tx, ty;
    double theta = StrictMath.toRadians(angle + 90);
    tx = (float) (this.x + distance * StrictMath.cos(theta));
    ty = (float) (this.y + distance * StrictMath.sin(theta));
    point = new Vector2f(tx, ty);
    return point;
  }

  public float getDistance(Entity other) {
    return getDistance(new Vector2f(other.x, other.y));
  }

  public float getDistance(Vector2f otherPos) {
    Vector2f myPos = new Vector2f(x, y);
    return myPos.distance(otherPos);
  }

  public static Vector2f calculateVector(float angle, float magnitude) {
    Vector2f v = new Vector2f();
    v.x = (float) Math.sin(Math.toRadians(angle));
    v.x *= magnitude;
    v.y = (float) -Math.cos(Math.toRadians(angle));
    v.y *= magnitude;
    return v;
  }

  public static float calculateAngle(float x, float y, float x1, float y1) {
    double angle = Math.atan2(y - y1, x - x1);
    return (float) (Math.toDegrees(angle) - 90);
  }

  /***************** some methods to deal with alarms ************************************/

  /**
   * Add an alarm with the given parameters and add it to this Entity
   */
  public void addAlarm(String alarmName, int triggerTime, boolean oneShot) {
    addAlarm(alarmName, triggerTime, oneShot, true);
  }

  /**
   * Add an alarm with given parameters and add it to this Entity
   */
  public void addAlarm(String alarmName, int triggerTime, boolean oneShot,
      boolean startNow) {
    Alarm alarm = new Alarm(alarmName, triggerTime, oneShot);
    alarms.addAlarm(alarm, startNow);
  }

  public boolean restartAlarm(String alarmName) {
    return alarms.restartAlarm(alarmName);
  }

  public boolean pauseAlarm(String alarmName) {
    return alarms.pauseAlarm(alarmName);
  }

  public boolean resumeAlarm(String alarmName) {
    return alarms.resumeAlarm(alarmName);
  }

  public boolean destroyAlarm(String alarmName) {
    return alarms.destroyAlarm(alarmName);
  }

  public boolean hasAlarm(String alarmName) {
    return alarms.hasAlarm(alarmName);
  }

  /**
   * Overwrite this method if your entity shall react on alarms that reached
   * their triggerTime.
   *
   * @param alarmName
   *            the name of the alarm that triggered right now
   */
  public void alarmTriggered(String alarmName) {
    // this method needs to be overwritten to deal with alarms
  }

  /**
   * this method is called automatically by the World and must not be called
   * by your game code. Don't touch this method ;-) Consider it private!
   */
  public void updateAlarms(int delta) {
    alarms.update(delta);
  }

  public int getAngle() {
    return angle;
  }

  // TODO: add proper rotation for the hitbox/shape here!!!
  public void setAngle(int angle) {
    this.angle = angle;
  }

  public Color getColor() {
    return color;
  }

  public void setColor(Color color) {
    this.color = color;
  }

  public float getAlpha() {
    return color.a;
  }

  public void setAlpha(float alpha) {
    if (alpha >= 0.0f && alpha <= 1.0f)
      color.a = alpha;
  }

  public void setPosition(Vector2f pos) {
    if (pos != null) {
      this.x = pos.x;
      this.y = pos.y;
    }
  }

  public boolean isCurrentAnim(String animName) {
    return currentAnim.equals(animName);
  }

  public String toCsv() {
    return "" + (int) x + "," + (int) y + "," + name + ","
        + collisionTypesToString();
  }

  private String collisionTypesToString() {
    StringBuffer sb = new StringBuffer();
    for (String type : collisionTypes) {
      if (sb.length() > 0) {
        sb.append(", ");
      }
      sb.append(type);
    }
    return sb.toString();
  }

  /**
   * @param shape
   *            the shape to check for intersection
   * @return The entities that intersect with their hitboxes into the given
   *         shape
   */
  public List<Entity> intersect(Shape shape) {
    if (shape == null)
      return null;
    List<Entity> result = new ArrayList<Entity>();
    for (Entity entity : world.getEntities()) {
      if (entity.collidable && !entity.equals(this)) {
        Rectangle rec = new Rectangle(entity.x, entity.y, entity.width,
            entity.height);
        if (shape.intersects(rec)) {
          result.add(entity);
        }
      }
    }
    return result;
  }

}
TOP

Related Classes of it.marteEngine.entity.Entity

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.