package com.skyleanderson.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import org.newdawn.slick.Animation;
import org.newdawn.slick.Color;
import org.newdawn.slick.Game;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.geom.Vector2f;
/**
* A Prop is the fundamental display unit in a game. Everything in the game
* world from the characters to the powerups and bullets should be derived from
* prop.
*
* @author ska
*
*/
public class Prop
{
// the bounding box of the represented object, does not have to match the
// size of the associated sprite
public BoundingBox boundingBox = new BoundingBox();
public LinkedList<Integer> mapIndexes = new LinkedList<Integer>();
// a list of the animations that this prop can use for representation (i.e.
// stand, walk,jump, etc)
protected ArrayList<Animation> animations = new ArrayList<Animation>(1);
protected int animationState = 0;
protected Vector2f position = new Vector2f();
protected Vector2f velocity = new Vector2f(); // note: units for velocity are in pixels /
// frame
protected Vector2f acceleration = new Vector2f(); // note: units for acceleration are in
// pixels / frame*frame
protected boolean onGround = false;
protected Vector2f center = new Vector2f(); ;
public static final int DIRECTION_LEFT = 0;
public static final int DIRECTION_RIGHT = 1;
public static final int DIRECTION_UP = 2;
public static final int DIRECTION_DOWN = 3;
protected int currentDirection = DIRECTION_RIGHT;
protected float gravity;
public Prop( )
{}
public Prop( Color c, float width, float height )
{
boundingBox.setWidth(width);
boundingBox.setHeight(height);
boundingBox.setColor(c);
boundingBox.setFillOnRender(true);
}
public Prop( ArrayList<Animation> animations )
{
this.animations = animations;
boundingBox = new BoundingBox(animations.get(0).getWidth(), animations.get(0).getHeight());
position = new Vector2f();
velocity = new Vector2f();
acceleration = new Vector2f();
mapIndexes = new LinkedList<Integer>();
center = new Vector2f();
}
public Prop( Animation animation )
{
this.animations = new ArrayList<Animation>(1);
this.animations.add(0, animation);
boundingBox = new BoundingBox(animations.get(0).getWidth(), animations.get(0).getHeight());
position = new Vector2f();
velocity = new Vector2f();
acceleration = new Vector2f();
mapIndexes = new LinkedList<Integer>();
center = new Vector2f();
}
public Prop( Color c, float width, float height, ArrayList<Animation> animations )
{
this.animations = animations;
boundingBox = new BoundingBox(width, height, 0, 0, c);
position = new Vector2f();
velocity = new Vector2f();
acceleration = new Vector2f();
mapIndexes = new LinkedList<Integer>();
center = new Vector2f();
}
public Prop( Color c, float width, float height, Animation animation )
{
this.animations = new ArrayList<Animation>(1);
this.animations.add(animation);
boundingBox = new BoundingBox(width, height, 0, 0, c);
position = new Vector2f();
velocity = new Vector2f();
acceleration = new Vector2f();
mapIndexes = new LinkedList<Integer>();
center = new Vector2f();
}
public void setCurrentAnimationState( int state )
{
this.animationState = state;
// if (this.animations.get(animationState) != null) this.animations.get(animationState).restart();
}
public Animation getCurrentAnimation( )
{
return this.animations.get(this.animationState);
}
public ArrayList<Animation> getAllAnimations( )
{
return this.animations;
}
public float top( )
{
return position.getY() + this.boundingBox.offsetY;
}
public float bottom( )
{
return position.getY() + this.boundingBox.height + this.boundingBox.offsetY;
}
public float left( )
{
return position.getX() + this.boundingBox.offsetX;
}
public float right( )
{
return position.getX() + this.boundingBox.width + this.boundingBox.offsetX;
}
public Vector2f center( )
{
center.x = this.left() + ((this.right() - this.left()) / 2.0f);
center.y = this.top() + ((this.bottom() - this.top()) / 2.0f);
return center;
}
/**
* Radius is used for quick collision tests when the Prop is roughly
* circular. The radius is assumed to be the smaller of the bounding box
* width or height.
*
* @return
*/
public float getRadius( )
{
float diameter;
diameter = (boundingBox.width > boundingBox.height) ? boundingBox.height : boundingBox.width;
return .5f * diameter;
}
public void setPosition( float x, float y )
{
position.x = x;
position.y = y;
}
public void setPosition( Vector2f position )
{
this.position.set(position);
}
public Vector2f getPosition( )
{
return this.position;
}
public void setVelocity( float x, float y )
{
velocity.x = x;
velocity.y = y;
}
public void setVelocity( Vector2f velocity )
{
this.velocity.set(velocity);
}
public void setVelocityX( float x )
{
velocity.x = x;
}
public void setVelocityY( float y )
{
velocity.y = y;
}
public float getVelocityX( )
{
return velocity.getX();
}
public float getVelocityY( )
{
return velocity.getY();
}
public Vector2f getVelocity( )
{
return this.velocity;
}
public void setAcceleration( float x, float y )
{
acceleration.x = x;
acceleration.y = y;
}
public void setAcceleration( Vector2f v )
{
acceleration.set(v);
}
public boolean isOnGround( )
{
return onGround;
}
public void setOffGround( )
{
onGround = false;
}
public void setGravity( float g )
{
gravity = g;
}
public float getGravity( )
{
return gravity;
}
public void addAnimation( Animation anim )
{
this.animations.add(anim);
}
public void addAnimations( Collection<Animation> animations )
{
this.animations.addAll(animations);
}
// this function updates the velocity based on the current acceleration and
// the position based on the
// resulting velocity
public void update( )
{
if (!isOnGround()) acceleration.y += gravity;
velocity.add(acceleration);
if (onGround) velocity.y = 0;
position.add(velocity);
}
@SuppressWarnings( "deprecation" )
public void render( GameContainer gc, Game game, Graphics g, float offsetX, float offsetY )
{
if (animations.size() == 0 || animations.get(animationState) == null)
{
// only draw bounding box instead
this.boundingBox.render(g, position.x - offsetX, position.y - offsetY);
// g.setColor(Color.white);
// g.drawOval(this.position.x - offsetX, this.position.y - offsetY,
// 2*getRadius(), 2*getRadius());
} else
{
if (currentDirection == DIRECTION_RIGHT)
animations.get(this.animationState).draw(position.x - offsetX, position.y - offsetY);
else
{
// Animation currAnim = animations.get(0);
// Image currImage = currAnim.getImage(currAnim.getFrame());
// currImage.getFlippedCopy(true, false).draw(position.x,
// position.y);
animations.get(animationState).getCurrentFrame().getFlippedCopy(true, false).draw(position.x - offsetX, position.y - offsetY);
animations.get(animationState).updateNoDraw();
}
}
}
public void render( GameContainer gc, Game game, Graphics g )
{
this.render(gc, game, g, 0, 0);
}
/**
* Check for collisions with the given list.
*
* @param list
* the list of Prop items to check for collisions with
* @return true if there is a collision
*/
public boolean checkCollisions( Collection<Prop> list )
{
Iterator<Prop> it = list.iterator();
while (it.hasNext())
{
Prop p = it.next();
if (this.checkSingleCollision(p)) return true;
}
return false;
}
// fail-first collision test
public boolean checkSingleCollision( Prop p )
{
if (this.right() < p.left() || this.left() > p.right()) return false;
if (this.bottom() < p.top() || this.top() > p.bottom()) return false;
return true;
}
// fail-first collision test
public boolean checkSingleCollision( float left, float top, float right, float bottom )
{
if (this.right() < left || this.left() > right) return false;
if (this.bottom() < top || this.top() > bottom) return false;
return true;
}
public boolean checkRadialCollision( Prop p )
{
return (this.center().distance(p.center()) < (this.getRadius() + p.getRadius()));
}
public boolean updatePositionX( Collection<Prop> relevant )
{
boolean collision = false;
position.x += velocity.x;
// check if you ran into something -- horizontal
Iterator<Prop> it = relevant.iterator();
while (it.hasNext())
{
Prop p = it.next();
if (this.checkSingleCollision(p)) // find collisions
{
// check for walls:
if (!(this.bottom() == p.top() || this.top() == p.bottom())) // make sure it's not just the ground
{
if (velocity.getX() > 0)
{
if (this.right() >= p.left() && this.right() <= p.right())
{
// x collision, stop short of the wall
this.position.x = (p.left() - this.boundingBox.getWidth() - this.boundingBox.getOffsetX());
collision = true;
// this.velocity.x = 0;
}
} else if (velocity.getX() < 0) if (this.left() <= p.right() && this.left() >= p.left())
{
// x collision, stop short of the wall
this.position.x = p.right();
collision = true;
// this.velocity.x = 0;
}
} // end x collisions
} // end ifcollision
}// end while
return collision;
}
public boolean updatePositionY( Collection<Prop> relevant )
{
boolean collision = false;
this.position.y += velocity.y;
Iterator<Prop> it = relevant.iterator();
this.onGround = false;
while (it.hasNext())
{
Prop p = it.next();
if (this.checkSingleCollision(p))
{
// check for walls:
if (!(this.left() == p.right() || this.right() == p.left())) // same as before, no bump-ups
{
if (velocity.getY() < 0) // hit head on ceiling?
{
if (this.top() <= p.bottom() && this.top() >= p.top())
{
// y collision, stop at ceiling
this.position.y = p.bottom();
this.velocity.y = 0;
collision = true;
}
} else if (velocity.getY() > 0) // landed on ground?
{
if (this.bottom() >= p.top() && this.bottom() <= p.bottom())
{
// y collision, stop on ground
this.position.y = p.top() - this.boundingBox.getHeight();
this.velocity.y = 0;
this.onGround = true;
collision = true;
}
} else // if velocity.y == 0, see if you're standing on
// something
if (this.bottom() == p.top()) this.onGround = true;
} // end y collision check
} // end if singlecollision
} // end while it.hasnext()
return collision;
}
}