package model_pkg.gameobject_pkg;
import model_pkg.GfxState;
import java.util.Random;
import def_classes.*;
import display_pkg.*;
import def_classes.Box;
/**
* A player type.
* @author Johansson M., Kullman K.
*/
public class Player extends GameObject {
private int lastDirection = 1; //
private int hMovement; //
private boolean jump, shoot, crouch; //
private GameObject standingOn; //
private double runForce = 20; //
private double jumpForce = 130; //
private double airControl = 0.5; //
private double gravityForce = 4; //
private double groundFriction = 0.5; //
private double airFriction = 0.972; //
private double maxVertSpeed = 18; //
private double maxHoriSpeed = 10; //
private int cooldown = 10; // [frames]
private int cooldownCounter = cooldown;
//
private int jumpTime = 8; // milliseconds to listen to jump input
private int jumpTimeLeft = 0; //
private Random randG; //
private State currentState; //
private OnGround onGround; //
private InAir inAir; //
private Modifier modifiers; //
/**
* Abstract superclass for the different states.
* @author Kullman K.
*/
private abstract class State {
abstract public void updateSpeed();
}
/**
* The state of being in air, that is, how to react in input and calculate speed when in air.
*/
private class InAir extends State {
public void updateSpeed() {
addVelocityY(gravityForce);
if (jumpTimeLeft > 0) {
jumpTimeLeft -= 1;
if (jump) {
addVelocityY(-jumpForce);
}
}
if (hMovement != 0) {
addVelocityX(hMovement * runForce * airControl);
} else {
// apply air friction
setVelocity(getVelocity().scalarMultX(airFriction));
}
if (jump && jumpTimeLeft > 0) {
addVelocityY(-jumpForce);
}
}
}
/**
* The state of being on ground, how to react in input and calculate speed when on ground.
*/
private class OnGround extends State {
public void updateSpeed() {
addVelocityX(standingOn.getVelocity().getX());
addVelocityY(gravityForce);
jumpTimeLeft = jumpTime;
if (hMovement != 0) {
addVelocityX(hMovement * runForce);
} else {
// apply ground friction
setVelocity(getVelocity().scalarMultX(groundFriction));
}
if (jump) {
addVelocityY(-jumpForce);
}
}
}
/**
* Sets which state to be in. Selected the simplest possible system with two predeclaired states.
* @param newState The new state of the object
*/
public void setState(State newState) {
currentState = newState;
}
/**
* <p><b>Modifier superclass</b></p>
* <p>
* Modifiers are changes to how the object works, many can be made to apply to the object
* at the same time. This trivial implementation only allows one modifier,
* which is fine since we only have one. But it can easily be extended to handle more
* modifiers simultaneously.
* </p>
*/
private abstract class Modifier {
abstract public void applyEffects();
}
/**
* A type of modifier. Changes some of the behavior of the player, inverting left and right.
*/
private class High extends Modifier {
private int timer;
public High() {
timer = 120;
}
public void applyEffects() {
hMovement = hMovement *-1;
if (randG.nextInt(30) == 1) {
jump = true;
}
timer--;
if (timer < 0) {
removeModifier(this);
}
}
}
/**
* adds a modifier to the object.
* @param newModifier The modifier to add.
*/
public void addModifier(Modifier newModifier) {
modifiers = newModifier; //should be converted to a list if more modifiers are added.
}
/**
* Removes a modifier.
* @param removeModifier The modifier to remove.
*/
public void removeModifier(Modifier removeModifier) {
modifiers = null; //should be converted to a list if more types of modifiers are added.
}
/**
* Applies the modifiers to the object.
*/
public void applyModifiers() {
if (modifiers != null) {
modifiers.applyEffects();
}
}
/**
* Creates a player
* @param x the staring x-position
* @param y the starting y-position
* @param bbox the bounding box
* @param sprSheet the sprite sheet of the player
*/
public Player(int x, int y, Box bbox, SpriteSheet sprSheet) {
super(x, y, bbox);
hMovement = 0;
jump = false;
crouch = false;
shoot = false;
currentAnim = new GfxState(sprSheet, 0);
setMovable(true);
setDrugable(true);
randG = new Random();
onGround = new OnGround();
inAir = new InAir();
currentState = inAir;
}
/**
* Method called when this object has collided with another.
* @param obj The object is has collided with.
* @param offsetDisplacement The overlap of this object with the other, that is,
* how much it has to move away to not overlap.
*/
public Vector2D collisionOccured(GameObject obj, Vector2D offsetDisplacement){
if (obj.isCollectable()) {
obj.destroyObject();
}
if (obj.isSolidSurfaceP()) {
align(obj,offsetDisplacement);
if (collisionDirection(offsetDisplacement) == 3) {
setState(onGround);
standingOn = obj;
}
}
return getMovement();
}
/**
* <p>
* Updates all properties in sequence.<br />
* The speed, how fast the objects moves.<br />
* The movement, how far it should move this frame.<br />
* Checks if the object collides with anything and handles collisions.<br />
* Updates position based on how far it could actually move.<br />
* Try to shoot if the player has pushed the shoot-button.<br />
* Set the right animation and update sprites.<br />
* </p>
*/
public void updateObject() {
applyModifiers();
currentState.updateSpeed();
reboundVelocity();
setMovement(getVelocity());
setState(inAir); //default state
myWorld.checkForCollisions(this);
updatePosition();
processShooting();
setAnimationIndex();
updateSpriteBase();
}
/**
* Handles cooldown and creates new shoots when appropriate.
*/
private void processShooting() {
cooldownCounter -= 1;
if (shoot & cooldownCounter <= 0) {
cooldownCounter = cooldown;
Point pos = new Point(25, 25);
pos.add(getWorldPosition());
BulletFactory.setTemplatePosition(pos);
BulletFactory.setTemplateVelocity(new Vector2D(lastDirection * 20, 0));
myWorld.createInstance(BulletFactory.getFactory());
}
}
/**
* Clips velocity into suitable, object-specific limits;
*/
private void reboundVelocity() {
approximateVelocityToZero();
if (getVelocity().getX() < -maxHoriSpeed) {
setVelocityX(-maxHoriSpeed);
} else if (getVelocity().getX() > maxHoriSpeed) {
setVelocityX(maxHoriSpeed);
}
if (getVelocity().getY() < -maxVertSpeed) {
setVelocityY(-maxVertSpeed);
} else if (getVelocity().getY() > maxVertSpeed) {
setVelocityY(maxVertSpeed);
}
}
/**
* adds the modifier of being high to the player.
*/
public void high() {
addModifier(new High());
}
/**
* Check so that the movement doesn't take the player outside the screen
* and update position
*/
private void updatePosition() {
setMovement(myWorld.clipMovement(getBoundingBox(), getWorldPosition(), getMovement()));
addToWorldPosition(getMovement());
}
/**
* Sets the animation index depending on the velocity.
*/
private void setAnimationIndex() {
double hspeed = getVelocity().getX();
double vspeed = getVelocity().getY();
if (vspeed < 0) {
currentAnim.setAnimationIndex(3);
} else if (hspeed < 0) {
currentAnim.setAnimationIndex(1);
} else if (hspeed > 0) {
currentAnim.setAnimationIndex(2);
} else {
currentAnim.setAnimationIndex(0);
}
}
/**
* Sets which direction the player is to move in.
* @param direction -1, 0 or 1 to distinguish the direction of the horisontal movement
*/
public void setHorisontalMovement(int direction) {
hMovement = direction;
if (hMovement != 0)
lastDirection = hMovement;
}
/**
* Sets if the player is to jump.
* @param pJumping true or false
*/
public void setJumping(boolean pJumping) {
jump = pJumping;
}
/**
* Not fully implemented functionality
* @param pCrouching true or false
*/
public void setCrouching(boolean pCrouching) {
crouch = pCrouching;
}
/**
* Sets if the player is to shoot.
* @param pShooting true or false
*/
public void setShooting(boolean pShooting) {
shoot = pShooting;
}
}