package com.bitwaffle.spaceout.entities.player;
import java.util.ArrayList;
import javax.vecmath.Point2f;
import javax.vecmath.Quat4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Quaternion;
import org.lwjgl.util.vector.Vector3f;
import com.bitwaffle.spaceguts.audio.SoundSource;
import com.bitwaffle.spaceguts.entities.DynamicEntity;
import com.bitwaffle.spaceguts.entities.Entities;
import com.bitwaffle.spaceguts.entities.Pickup;
import com.bitwaffle.spaceguts.entities.particles.trail.Trail;
import com.bitwaffle.spaceguts.graphics.gui.GUI;
import com.bitwaffle.spaceguts.graphics.render.Render3D;
import com.bitwaffle.spaceguts.graphics.shapes.Box2D;
import com.bitwaffle.spaceguts.input.KeyBindings;
import com.bitwaffle.spaceguts.input.MouseManager;
import com.bitwaffle.spaceguts.physics.CollisionTypes;
import com.bitwaffle.spaceguts.physics.ConvexResultCallback;
import com.bitwaffle.spaceguts.physics.Physics;
import com.bitwaffle.spaceguts.util.Debug;
import com.bitwaffle.spaceguts.util.QuaternionHelper;
import com.bitwaffle.spaceguts.util.console.Console;
import com.bitwaffle.spaceout.Runner;
import com.bitwaffle.spaceout.entities.dynamic.Asteroid;
import com.bitwaffle.spaceout.entities.dynamic.LaserBullet;
import com.bitwaffle.spaceout.entities.dynamic.Missile;
import com.bitwaffle.spaceout.interfaces.Health;
import com.bitwaffle.spaceout.interfaces.Inventory;
import com.bitwaffle.spaceout.resources.Models;
import com.bitwaffle.spaceout.resources.Sounds;
import com.bitwaffle.spaceout.resources.Textures;
import com.bitwaffle.spaceout.ship.Ship;
import com.bulletphysics.collision.dispatch.CollisionObject;
import com.bulletphysics.collision.shapes.BoxShape;
import com.bulletphysics.collision.shapes.SphereShape;
import com.bulletphysics.linearmath.Transform;
/**
* Just a player in the game
*
* @author TranquilMarmot
*/
public class Player extends DynamicEntity implements Health, Inventory{
final static short COL_GROUP = CollisionTypes.SHIP;
final static short COL_WITH = (short)(CollisionTypes.WALL | CollisionTypes.PLANET);
/** How long the player stays invincible for */
private static final float INVINCIBILITY_TIME = 1.0f;
/** Whether or not the player is currently invincible */
private boolean isInvincible = false;
/** How long the player has been invincible for (if this >= INVINCIBLE_TIME, the player is no longer invincible) */
private float timeSpentInvincible = 0.0f;
private static final float LOCKON_DISTANCE = 5000.0f;
/** Used for drawing the lockon target thing */
private static Box2D lockonbox = new Box2D(1.0f, 1.0f, Textures.TARGET.texture());
/** How big the lockon box is */
private static Point2f lockonboxSize = new Point2f(10.0f,10.0f);
/** Used for searching for lockon stuff*/
private BoxShape lockonSweepBox = new BoxShape(new javax.vecmath.Vector3f(5.0f, 5.0f, 5.0f));
/** entity that the player is locked on to */
public DynamicEntity lockon = null;
/** Sound source for making the gun shooting noise */
private SoundSource pew;
/** Radius for sphere that looks for pickups */
public float pickupSweepSize = 50.0f;
/** How far in front of the ship that sphere travels */
public float pickupSweepDistance = 10.0f;
/** How close a pickup has to be before it's added to the inventory */
public float pickupDistance = 2.0f;
private static final float MISSILE_INITIAL_SPEED = 300.0f;
private static final float MISSILE_SPEED_INCREASE = 200.0f;
private static final float MISSILE_DETONATION_TIME = 12.0f;
/**
* Backpack, backpack. Backpack, backpack.
* I'm the Backpack.
* Loaded up with things and nick-nacks too.
* Anything that you might need I got inside for you.
* Backpack, backpack. Backpack, backpack.
* YEAH!
*/
public Backpack backpack;
/** Used for info in equations */
public Ship ship;
// FIXME temp code
private Trail trail1, trail2;
/** to keep the button from being held down */
private boolean button0Down = false, button1Down = false, boosting = false;
/** the player's health */
public static final int MAX_HEALTH = 100;
public static int health = MAX_HEALTH;
public Player(Vector3f location, Quaternion rotation, Ship ship,
float mass, float restitution) {
super(location, rotation, ship.getModel(), mass, restitution, COL_GROUP, COL_WITH);
// make sure the rigid body doesn't deactivate
rigidBody.setActivationState(CollisionObject.DISABLE_DEACTIVATION);
this.ship = ship;
this.type = "Player";
backpack = new Backpack();
// FIXME temp code
trail1 = new Trail(this, 15, 0.6f, Textures.TRAIL, new Vector3f(0.9f, 0.13f, 2.34f));
trail2 = new Trail(this, 15, 0.6f, Textures.TRAIL, new Vector3f(-0.8f, 0.13f, 2.34f));
pew = new SoundSource(Sounds.PEW, false, this.location, new Vector3f(0.0f,0.0f,0.0f));
}
@Override
/**
* This is called for every dynamic entity at the end of each tick of the physics world
*/
public void update(float timeStep) {
// only update if we're not paused
if(!Runner.paused){
//FIXME temp code
trail1.update(timeStep);
trail2.update(timeStep);
// only update if a menu isn't up and we're not in free mode
if(!GUI.menuUp && !Entities.camera.freeMode){
boosting = KeyBindings.CONTROL_BOOST.isPressed();
// perform acceleration
zLogic(timeStep);
xLogic(timeStep);
yLogic(timeStep);
// cap the players' speed
checkSpeed();
// perform rotation
if(!Entities.camera.vanityMode)
rotationLogic(timeStep);
// handle bullet shooting
if (MouseManager.button0 && !button0Down && !Console.consoleOn) {
button0Down = true;
shootBullet();
}
if (!MouseManager.button0)
button0Down = false;
if(MouseManager.button1 && !button1Down && !Console.consoleOn){
button1Down = true;
shootMissile();
}
if(!MouseManager.button1)
button1Down = false;
// handle stopping
if (KeyBindings.CONTROL_STOP.isPressed())
brake(timeStep);
checkForPickups();
lockOn();
if(isInvincible){
timeSpentInvincible += timeStep;
if(timeSpentInvincible >= INVINCIBILITY_TIME)
isInvincible = false;
}
}
}
}
/**
* Gracefully stops the player
*/
private void brake(float timeStep) {
javax.vecmath.Vector3f linearVelocity = new javax.vecmath.Vector3f(
0.0f, 0.0f, 0.0f);
rigidBody.getLinearVelocity(linearVelocity);
float stopX = linearVelocity.x - ((linearVelocity.x / ship.getStopSpeed()) * timeStep);
float stopY = linearVelocity.y - ((linearVelocity.y / ship.getStopSpeed()) * timeStep);
float stopZ = linearVelocity.z - ((linearVelocity.z / ship.getStopSpeed()) * timeStep);
rigidBody.setLinearVelocity(new javax.vecmath.Vector3f(stopX, stopY,
stopZ));
}
/**
* Pew pew
*/
private void shootBullet() {
Vector3f bulletLocation = new Vector3f(this.location.x,
this.location.y, this.location.z);
Quaternion bulletRotation = new Quaternion(this.rotation.x,
this.rotation.y, this.rotation.z, this.rotation.w);
// move the bullet to in front of the player
// FIXME this should be an offset from the center to represent the location of a gun or something
Vector3f bulletMoveAmount = new Vector3f(0.0f, 0.0f, 10.0f);
bulletMoveAmount = QuaternionHelper.rotateVectorByQuaternion(
bulletMoveAmount, bulletRotation);
Vector3f.add(bulletLocation, bulletMoveAmount, bulletLocation);
Models bulletModel = Models.LASERBULLET;
float bulletMass = 0.25f;
float bulletRestitution = 1.0f;
int bulletDamage = 10;
float bulletSpeed = 2500.0f;
LaserBullet bullet = new LaserBullet(this, bulletLocation, bulletRotation,
bulletModel, bulletMass, bulletRestitution, bulletDamage, bulletSpeed);
Entities.addDynamicEntity(bullet);
pew.setLocation(Entities.camera.location);
javax.vecmath.Vector3f linvec = new javax.vecmath.Vector3f();
this.rigidBody.getLinearVelocity(linvec);
pew.setVelocity(new Vector3f(linvec.x, linvec.y, linvec.z));
pew.playSound();
}
/**
* Ker-pow
*/
private void shootMissile() {
Vector3f missileLocation = new Vector3f(this.location.x,
this.location.y, this.location.z);
Quaternion missileRotation = new Quaternion(this.rotation.x,
this.rotation.y, this.rotation.z, this.rotation.w);
// move the missile to in front of the player
// FIXME this should be an offset from the center to represent the location of a gun or something
Vector3f missileMoveAmount = new Vector3f(0.0f, 0.0f, 10.0f);
missileMoveAmount = QuaternionHelper.rotateVectorByQuaternion(
missileMoveAmount, missileRotation);
Vector3f.add(missileLocation, missileMoveAmount, missileLocation);
Missile miss = new Missile(missileLocation, missileRotation, lockon, MISSILE_INITIAL_SPEED, MISSILE_SPEED_INCREASE, MISSILE_DETONATION_TIME);
Entities.addDynamicEntity(miss);
}
/**
* Accelerate/decelerate along the Z axis
*/
private void zLogic(float timeStep) {
boolean forward = KeyBindings.CONTROL_FORWARD.isPressed();
boolean backward = KeyBindings.CONTROL_BACKWARD.isPressed();
if (forward || backward) {
if (forward) {
Vector3f vec;
if(boosting)
vec = QuaternionHelper.rotateVectorByQuaternion(
new Vector3f(0.0f, 0.0f, ship.getBoostSpeed().z * timeStep), rotation);
else
vec = QuaternionHelper.rotateVectorByQuaternion(
new Vector3f(0.0f, 0.0f, ship.getAccelerationSpeed().z * timeStep), rotation);
rigidBody.applyCentralImpulse(new javax.vecmath.Vector3f(vec.x,
vec.y, vec.z));
}
if (backward) {
Vector3f vec;
if(boosting)
vec = QuaternionHelper.rotateVectorByQuaternion(
new Vector3f(0.0f, 0.0f, -ship.getBoostSpeed().z * timeStep), rotation);
else
vec = QuaternionHelper.rotateVectorByQuaternion(
new Vector3f(0.0f, 0.0f, -ship.getAccelerationSpeed().z * timeStep), rotation);
rigidBody.applyCentralImpulse(new javax.vecmath.Vector3f(vec.x,
vec.y, vec.z));
}
}
}
/**
* Accelerate/decelerate along the X axis
*/
private void xLogic(float timeStep) {
boolean left = KeyBindings.CONTROL_LEFT.isPressed();
boolean right = KeyBindings.CONTROL_RIGHT.isPressed();
if (left || right) {
if (left) {
Vector3f vec;
if(boosting)
vec = QuaternionHelper.rotateVectorByQuaternion(
new Vector3f(ship.getBoostSpeed().x * timeStep, 0.0f, 0.0f), rotation);
else
vec = QuaternionHelper.rotateVectorByQuaternion(
new Vector3f(ship.getAccelerationSpeed().x * timeStep, 0.0f, 0.0f), rotation);
rigidBody.applyCentralImpulse(new javax.vecmath.Vector3f(vec.x,
vec.y, vec.z));
}
if (right) {
Vector3f vec;
if(boosting)
vec = QuaternionHelper.rotateVectorByQuaternion(
new Vector3f(-ship.getBoostSpeed().x * timeStep, 0.0f, 0.0f), rotation);
else
vec = QuaternionHelper.rotateVectorByQuaternion(
new Vector3f(-ship.getAccelerationSpeed().x * timeStep, 0.0f, 0.0f), rotation);
rigidBody.applyCentralImpulse(new javax.vecmath.Vector3f(vec.x,
vec.y, vec.z));
}
}
}
/**
* Accelerate/decelerate along the Y axis
*/
private void yLogic(float timeStep) {
boolean ascend = KeyBindings.CONTROL_ASCEND.isPressed();
boolean descend = KeyBindings.CONTROL_DESCEND.isPressed();
if (ascend || descend) {
if (ascend) {
Vector3f vec;
if(boosting)
vec = QuaternionHelper.rotateVectorByQuaternion(
new Vector3f(0.0f, -ship.getBoostSpeed().y * timeStep, 0.0f), rotation);
else
vec = QuaternionHelper.rotateVectorByQuaternion(
new Vector3f(0.0f, -ship.getAccelerationSpeed().y * timeStep, 0.0f), rotation);
rigidBody.applyCentralImpulse(new javax.vecmath.Vector3f(vec.x,
vec.y, vec.z));
}
if (descend) {
Vector3f vec;
if(boosting)
vec = QuaternionHelper.rotateVectorByQuaternion(
new Vector3f(0.0f, ship.getBoostSpeed().y * timeStep, 0.0f), rotation);
else
vec = QuaternionHelper.rotateVectorByQuaternion(
new Vector3f(0.0f, ship.getAccelerationSpeed().y * timeStep, 0.0f), rotation);
rigidBody.applyCentralImpulse(new javax.vecmath.Vector3f(vec.x,
vec.y, vec.z));
}
}
}
/**
* Keep the speed in range of this ship's top speed
*/
private void checkSpeed(){
javax.vecmath.Vector3f velocity = new javax.vecmath.Vector3f();
rigidBody.getLinearVelocity(velocity);
float speed = velocity.length();
if(speed > ship.getTopSpeed()){
velocity.x *= ship.getTopSpeed() / speed;
velocity.y *= ship.getTopSpeed() / speed;
velocity.z *= ship.getTopSpeed() / speed;
rigidBody.setLinearVelocity(velocity);
}
}
/**
* Uses spherical linear interpolation (slerp) to rotate the ship towards where the camera is looking
* @param timeStep
*/
private void rotationLogic(float timeStep){
javax.vecmath.Vector3f angVec = new javax.vecmath.Vector3f();
this.rigidBody.getAngularVelocity(angVec);
float currentAngularVelocity = angVec.length();
// if the mouse has moved, set the angular velocity to zero (to prevent spinning out of control)
if(MouseManager.dx != 0.0f && MouseManager.dy != 0.0f && currentAngularVelocity != 0)
this.rigidBody.setAngularVelocity(new javax.vecmath.Vector3f(0.0f, 0.0f, 0.0f));
// only interpolate values if the angular velocity is 0 (we're NOT spinning out of control) and the two rotations aren't already equal (dot product == 1 if the rotations are the same)
if(currentAngularVelocity == 0 && Quaternion.dot(this.rotation, Entities.camera.rotation) != 1.0f){
Quat4f camquat = new Quat4f(Entities.camera.rotation.x, Entities.camera.rotation.y, Entities.camera.rotation.z, Entities.camera.rotation.w);
Quat4f thisquat = new Quat4f(rotation.x, rotation.y, rotation.z, rotation.w);
float interpolationAmount = timeStep * ship.getTurnSpeed();
// Slerp magix!
thisquat.interpolate(camquat, thisquat, interpolationAmount);
// set current rotation
this.rotation.set(thisquat.x, thisquat.y, thisquat.z, thisquat.w);
// set world transform to represent interpolated value
Transform trans = new Transform();
this.rigidBody.getWorldTransform(trans);
trans.setRotation(thisquat);
this.rigidBody.setWorldTransform(trans);
}
}
/**
* Searches for things to lock on to
*/
private void lockOn(){
ArrayList<Asteroid> hits = new ArrayList<Asteroid>();
ConvexResultCallback<Asteroid> callback = new ConvexResultCallback<Asteroid>(hits, CollisionTypes.PLANET);
Physics.convexSweepTest(this, new Vector3f(0.0f, 0.0f, LOCKON_DISTANCE), lockonSweepBox, callback);
if(hits.size() > 0){
this.lockon = hits.get(0);
if(this.lockon instanceof Asteroid){
lockonboxSize.x = ((Asteroid) this.lockon).getSize();
lockonboxSize.y = ((Asteroid) this.lockon).getSize();
}
}
// un-lock on to something if it's being removed
if(lockon != null && lockon.removeFlag)
lockon = null;
}
/**
* Performs a convex sweep test in front of the player and sets any found pickups
* to follow the player.
*/
private void checkForPickups(){
// we'll use a sphere for simplicity
SphereShape shape = new SphereShape(pickupSweepSize);
/*
* See ConvexResultCallback class
* Any pickups found from the sweep test are added to hits
* It is possible to do multiple tests and have them all add to the same list,
* but there's no guarantee that there won't be duplicates so be careful
* (Would be possible to get a list containing no duplicates if, say, a
* hash map is used)
*/
ArrayList<Pickup> hits = new ArrayList<Pickup>();
ConvexResultCallback<Pickup> callback = new ConvexResultCallback<Pickup>(hits, CollisionTypes.PICKUP);
Physics.convexSweepTest(this, new Vector3f(0.0f, 0.0f, pickupSweepDistance), shape, callback);
// make found pickups follow player
for(Pickup item : hits)
item.setFollowing(this, pickupDistance, backpack);
}
@Override
public void draw(){
super.draw();
//FIXME temp code
trail1.draw();
trail2.draw();
if(lockon != null)
drawTarget();
}
/**
* Draws a target over what the player is locked on to
*/
private void drawTarget(){
// enable blending
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
// disable depth test to draw target in front of everything
GL11.glDisable(GL11.GL_DEPTH_TEST);
Render3D.program.setUniform("Light.LightEnabled", false);
Textures.TARGET.texture().bind();
// save the modelview matrix so we can muck wit it
Matrix4f oldModelView = new Matrix4f();
oldModelView.load(Render3D.modelview);
// undo rotation (modelview is currently rotated to draw player)
Quaternion revQuat = new Quaternion();
this.rotation.negate(revQuat);
Matrix4f.mul(Render3D.modelview, QuaternionHelper.toMatrix(revQuat), Render3D.modelview);
// new translation
float transx = location.x - lockon.location.x;
float transy = location.y - lockon.location.y;
float transz = location.z - lockon.location.z;
// translate and scale the modelview
Render3D.modelview.translate(new Vector3f(transx, transy, transz));
// billboard the target
Matrix4f.mul(Render3D.modelview, QuaternionHelper.toMatrix(Entities.camera.rotation), Render3D.modelview);
// make it bigger!
Render3D.modelview.scale(new Vector3f(lockonboxSize.x, lockonboxSize.y, 1.0f));
// don't forget to set the modelview before drawing
Render3D.program.setUniform("ModelViewMatrix", Render3D.modelview);
lockonbox.draw();
// reset everything to the way it was
GL11.glEnable(GL11.GL_DEPTH_TEST);
Render3D.modelview.load(oldModelView);
GL11.glDisable(GL11.GL_BLEND);
Render3D.program.setUniform("Light.LightEnabled", true);
Render3D.modelview.load(oldModelView);
}
@Override
public int getCurrentHealth() {
return health;
}
@Override
public void hurt(int amount) {
if(!isInvincible){
health -= amount;
isInvincible = true;
timeSpentInvincible = 0.0f;
System.out.printf("Ouch! You got hit and now have %d health\n", health);
if (health <= 0) {
Debug.pauseText = "GAME OVER!";
Runner.paused = true;
}
}
}
@Override
public void heal(int amount) {
health += amount;
}
@Override
public void addInventoryItem(Pickup item) {
backpack.addInventoryItem(item);
}
@Override
public void removeInventoryItem(Pickup item) {
backpack.removeInventoryItem(item);
}
@Override
public ArrayList<Pickup> getItems() {
return backpack.getItems();
}
public int numDiamonds(){
return backpack.numDiamonds();
}
}