###########################################################
# PlayerCraft - Railshooter Module
# Contains the class to create the player vehicle
###########################################################

# DESCRIPTION:
# This Class encompasses the Player and deals with the Player's
# controls, movements, statistics, Actor animations, death, and attacks.
# It contains an example of Finite State Machines used to help manage 
# the Animation States of the Actor.  Also, it gives an example on how 
# to move and control an object interactively by using Tasks.   


from direct.showbase.ShowBaseGlobal import *
from direct.interval.IntervalGlobal import *
from direct.fsm.State import *
from direct.fsm.ClassicFSM import *
from direct.showbase import DirectObject
from direct.actor import Actor
from direct.task import Task
from pandac.PandaModules import *
import random, math

# Import Other Railshooter Modules
import PlayerProjectile
import HomingMissile
import ClassGlobals
import Level1
import SoundFX
import Raycaster
import LockOnCrosshair
import LoadTextures
import specialEffects


########################################
# Player Class
########################################
class playerCraft(DirectObject.DirectObject):
    def __init__(self, classNum):   

	self.mouser = ClassGlobals.mouser
	self.c = str(classNum)


	########################################
	# Define Class Variables
	########################################
	
	# Player Attributes
	self.health = 100
	self.score = 0
	self.stunRecoveryTime = 0.1
	self.currentStun = 0.0
	self.isAlive = 1
	self.isInvincible = 0
	self.godMode = 0

	# Controls Player Movement and Speed
	self.horizSpeed = 0
	self.vertSpeed = 0
	self.acceleration = 0.03
	self.deceleration = 0.015
	self.deadZone = self.deceleration
	self.tiltDeadZone = 0.2
	self.maxSpeed = 1.0

	# Controls Player Tilting/Aiming
	self.tiltSpeed = 3.0
	self.resetTiltSpeed = 0.0
	self.maxTilt = 15  		# Regular 30
	self.aimSensitivity = 25

	# Controls Bobbing of the Craft
	self.bobSpeed = 0.01
	self.normalBobSpeed = 0.01
	self.maxBob = 0.05
	self.normalMaxBob = 0.05
	self.currentBob = 0
	
	# Used for Timing of Game Speed
	self.tick = 0
	self.currentTick = 0
	self.lastTick = 0
	self.prevTime = 0.0

	# Cooldown Time from Shooting
	self.shootDelay = 3
	self.currentShootDelay = 0
	self.homingLaserDelay = 3
	self.bombDelay = 10

	# For Automatic Rapid-Fire
	# Each press of the Mouse Button will release a burst
	# of self.burstMax bullets
	self.burstMax = 3
	self.currentBurst = 0

	# DEBUG
	self.maxCharge = 100
	self.currentCharge = 0
	self.isPicking = 0

	# Used for LockOn Homing Laser
	self.lockTargets = {}
	self.lockCrosshairs = {}
	self.numLockTargets = 0
	self.maxLock = 12
	self.laserDummies = {}

	# Regular Blast Attributes
	self.blastScale = 0.3
	self.blastUpgrade = 0.05
	self.blastLevel = 1
	self.maxLevel = 4
	
	# For Game Controls
        self.keypressHolder = {'shoot1':0, 'shoot2':0}
	self.lastMouseMove = 0.0


	########################################
	# Load the Player & Model
	########################################

     	# Add a dummy object "root" node for the Player
	# This object is the one that gets moved around
	self.player = Level1.railDummy.attachNewNode('player' + self.c)

     	# Add a dummy object "root" node for the craft
	# This object is for Tilting
	self.craftDummy = self.player.attachNewNode('craftDummy' + self.c)

 	# This object is the one that the Camera Tracks
	self.cameraTarget = self.player.attachNewNode('cameraTarget')
	self.cameraTarget.setPos(0, 0, 1.0)

 	# This object is the one that the Camera Tracks for the Death Sequence
	self.deathCamTarget = self.player.attachNewNode('cameraTarget')
	self.deathCamTarget.setPos(0, 0, 1.0)

        # Load the Player Model with All Animations
	# This object gets the Roll
        self.craft = Actor.Actor('Models/pilot/pilot-model', 
				{'fire':'Models/pilot/pilot-fire',
				'fireWindup':'Models/pilot/pilot-firewindup',
				'fireWinddown':'Models/pilot/pilot-firewinddown',
				'chargeWindup':'Models/pilot/pilot-chargewindup',
				'chargeFire':'Models/pilot/pilot-chargeshoot',
				'discLoop':'Models/pilot/pilot-discloop',
				'discWindup':'Models/pilot/pilot-discwindup',
				'discWinddown':'Models/pilot/pilot-discwinddown',
				'idle':'Models/pilot/pilot-newIdle',
				'pain':'Models/pilot/pilot-pain',
				'crash':'Models/pilot/pilot-crash',
				'death':'Models/pilot/pilot-newDeath'})

	self.craft.bindAnim("idle")
	self.craft.bindAnim("pain")
	self.craft.bindAnim("crash")
	self.craft.bindAnim("death")
	self.craft.bindAnim("fire")
	self.craft.bindAnim("fireWindup")
	self.craft.bindAnim("fireWinddown")
	self.craft.bindAnim("chargeWindup")
	self.craft.bindAnim("chargeFire")
	self.craft.bindAnim("discLoop")
	self.craft.bindAnim("discWindup")
	self.craft.bindAnim("discWinddown")

	# Set Craft Position, Scale, and Orientation.  
	# Then reparent to the craftDummy
	self.craft.setHpr(180, 0, 0)
        self.craft.setScale(0.07, 0.07, 0.07)
	self.craft.setPos(0, 0, -1)
	self.craft.reparentTo(self.craftDummy)

	# Disable Actor Culling when Actor is Offscreen
	self.craft.node().setBound(OmniBoundingVolume())
  	self.craft.node().setFinal(1)

	# Load the Airblade
	self.airblade = loader.loadModelCopy("Models/airblade/airblade")
	self.airblade.setPos(0, 0, -13)
	self.airblade.setScale(32, 32, 32)
	self.airblade.reparentTo(self.craft)

	# Make the Airblade Spin
	self.blade = self.airblade.find('**/blade')
	self.RotateBlade = self.blade.hprInterval(0.5, 
				Point3(360, 0, 0), 
				startHpr=Point3(0, 0, 0))
	self.RotateBlade.loop()


	########################################
	# Define Player's Collision Detection
	########################################

        # Create Player's Collision Sphere
        self.playerSphere = CollisionSphere(0, 0, 20, 30)
        self.collplayer = CollisionNode("collplayer" + self.c)
        self.collplayer.addSolid(self.playerSphere)
        self.collision = self.craft.attachNewNode(self.collplayer)
        # self.collision.show()

	# Set Bitmasks for the Player
	# Objects Colliding into Player need a Bitmask of 0
	self.collplayer.setIntoCollideMask(BitMask32().bit(0))

	# Set Bitmask for the Player
	self.collplayer.setFromCollideMask(BitMask32().bit(1))

	# Turn on collisions
	ClassGlobals.collider.playerTraverser.addCollider(self.collision, ClassGlobals.collider.collisionEvent)
	

	########################################
	# Setup Gun Dummies
	########################################
	# Setup the Gun Dummy objects.  Each Gun Dummy marks one possible 
	# position and orientation for projectiles to be fired from by 
	# the Player

	# Setup the Main Gun Dummy
	self.gunDummy = self.craftDummy.attachNewNode('gunDummy')
	self.gunDummy.setPos(0, 5, 2.7)

	# Setup the Secondary Gun Dummies
	self.gunDummy2 = self.craftDummy.attachNewNode('gunDummy2')
	self.gunDummy2.setPos(-1.5, 5, 2.7)

	self.gunDummy3 = self.craftDummy.attachNewNode('gunDummy3')
	self.gunDummy3.setPos(1.5, 5, 2.7)

	# Setup the Tertiary Gun Dummies
	self.gunDummy4 = self.craftDummy.attachNewNode('gunDummy4')
	self.gunDummy4.setPos(-0.3, 5, 2.7)
	self.gunDummy4.setHpr(5, 0, 0)

	self.gunDummy5 = self.craftDummy.attachNewNode('gunDummy5')
	self.gunDummy5.setPos(0.3, 5, 2.7)
	self.gunDummy5.setHpr(-5, 0, 0)


	########################################
	# Setup Homing Laser Gun Dummies
	########################################
	# Setup dummy objects for the Homing Laser.  Each Homing Projectile
	# will be initialized at each corresponding gunDummy

	self.laserDummy1 = self.craftDummy.attachNewNode('laserDummy1')
	self.laserDummy1.setPos(1.0, 1, 2)
	self.laserDummy1.setHpr(90, 10, 0)

	self.laserDummy2 = self.craftDummy.attachNewNode('laserDummy2')
	self.laserDummy2.setPos(-1.0, 1, 2)
	self.laserDummy2.setHpr(-90, 10, 0)

	self.laserDummy3 = self.craftDummy.attachNewNode('laserDummy3')
	self.laserDummy3.setPos(1.0, 1, 1)
	self.laserDummy3.setHpr(90, -10, 0)

	self.laserDummy4 = self.craftDummy.attachNewNode('laserDummy4')
	self.laserDummy4.setPos(-1.0, 1, 1)
	self.laserDummy4.setHpr(-90, -10, 0)

	self.laserDummy5 = self.craftDummy.attachNewNode('laserDummy5')
	self.laserDummy5.setPos(0.5, 1, 3)
	self.laserDummy5.setHpr(45, 45, 0)

	self.laserDummy6 = self.craftDummy.attachNewNode('laserDummy6')
	self.laserDummy6.setPos(-0.5, 1, 3)
	self.laserDummy6.setHpr(-45, 45, 0)

	self.laserDummy7 = self.craftDummy.attachNewNode('laserDummy7')
	self.laserDummy7.setPos(0.5, 1, 0)
	self.laserDummy7.setHpr(45, -45, 0)

	self.laserDummy8 = self.craftDummy.attachNewNode('laserDummy8')
	self.laserDummy8.setPos(-0.5, 1, 0)
	self.laserDummy8.setHpr(-45, -45, 0)

	self.laserDummy9 = self.craftDummy.attachNewNode('laserDummy9')
	self.laserDummy9.setPos(1.0, 1, 1.4)
	self.laserDummy9.setHpr(90, 0, 0)

	self.laserDummy10 = self.craftDummy.attachNewNode('laserDummy10')
	self.laserDummy10.setPos(-1.0, 1, 1.4)
	self.laserDummy10.setHpr(-90, 0, 0)

	self.laserDummy11 = self.craftDummy.attachNewNode('laserDummy11')
	self.laserDummy11.setPos(1.0, 1, 1.6)
	self.laserDummy11.setHpr(90, 0, 0)

	self.laserDummy12 = self.craftDummy.attachNewNode('laserDummy12')
	self.laserDummy12.setPos(-1.0, 1, 1.6)
	self.laserDummy12.setHpr(-90, 0, 0)


	# Enter the Laser Dummies into a List
	self.laserDummies[0] = self.laserDummy1
	self.laserDummies[1] = self.laserDummy2
	self.laserDummies[2] = self.laserDummy3
	self.laserDummies[3] = self.laserDummy4
	self.laserDummies[4] = self.laserDummy5
	self.laserDummies[5] = self.laserDummy6
	self.laserDummies[6] = self.laserDummy7
	self.laserDummies[7] = self.laserDummy8
	self.laserDummies[8] = self.laserDummy9
	self.laserDummies[9] = self.laserDummy10
	self.laserDummies[10] = self.laserDummy11
	self.laserDummies[11] = self.laserDummy12


	########################################
	# Load the Player's Crosshair
	########################################

     	# Add a dummy object "root" node for the Player
	self.crosshairDummy = self.craftDummy.attachNewNode('crosshairDummy' + self.c)

        # Load the First Crosshair
        self.crosshair = loader.loadModel("Models/basics/crosshair")
	self.crosshair.setScale(3.0, 3.0, 3.0)
	self.crosshair.setPos(self.craft, 0, -30000, -450)
     	self.crosshair.reparentTo(self.crosshairDummy)
	self.crosshair.hide()

        # Load Appropriate Texture
     	self.normalCrosshair = loader.loadTexture('Models/basics/crosshair.png')
        self.normalCrosshair.setMagfilter(1)
        self.normalCrosshair.setMinfilter(1)

   	self.chargeCrosshair = loader.loadTexture('Models/basics/crosshaircharge.png')
        self.chargeCrosshair.setMagfilter(1)
        self.chargeCrosshair.setMinfilter(1)
     
	self.rotateCrosshair = self.crosshair.hprInterval(2, 
				Point3(0, 0, 360), 
				startHpr=Point3(0, 0, 0))
	self.rotateCrosshair.loop()

	# Create a Blank Light Attribute
	# Results in Object being unaffected by light and
	# glowing as a result
	lightAttributes = LightAttrib.makeAllOff()
	self.crosshair.node().setAttrib(lightAttributes)

	# Alter the Drawing Order so that the Crosshair always 
	# Draws on Top by creating a GUI Layer for the Crosshair
	CullBinManager.getGlobalPtr().addBin('gui-popup', CullBinManager.BTUnsorted, 60)

	# Add the Crosshair to the new GUI Bin
	self.crosshair.setBin('gui-popup', 0)
	self.crosshair.setDepthTest(0)


	########################################
	# Load the Raycaster
	########################################

	# Create Raycaster Class
	self.raycaster1 = Raycaster.Raycaster(self.gunDummy)


	########################################
	# Accept Events
	########################################

	self.accept('entercollplayer1', self.checkCollision)


   	########################################
	# Define FSM Callback States
	########################################
	# This Finite State Machine is used to control
	# the player's animation State

	def enterIdle():
   	    print 'Player enters Idle State'
	    self.craft.loop('idle')
	    
	def exitIdle():
    	    print 'Player exits Idle State'
	    self.craft.stop('idle')

	def enterShootWindup():
    	    print 'Player enters Shooting Windup State'
	    # self.craft.play('fireWindup')
	    windupSeq = Sequence(self.fireWindupInterval,
		Func(requestShoot))
	    windupSeq.start()

	def exitShootWindup():
    	    print 'Player exits Shooting Windup State'

	def enterShootWinddown():
    	    print 'Player enters Shooting Winddown State'
	    winddownSeq = Sequence(Func(playFireWinddown),
		Wait(1.2),
		Func(requestIdle2))
	    winddownSeq.start()

	def exitShootWinddown():
    	    print 'Player exits Shooting Winddown State'

	def enterShoot():
    	    print 'Player enters Shooting State'
	    self.craft.loop('fire')
	  
	def exitShoot():
    	    print 'Player exits Shooting State'
	    self.craft.stop('fire')
    
	def enterCharge():
    	    print 'Player enters Charging State'
	    self.craft.play('chargeWindup')
	    
	def exitCharge():
    	    print 'Player exits Charging State'
    	    self.craft.stop('chargeWindup')

	def enterChargeFire():
    	    print 'Player enters Charge Firing State'
       	    self.cfireSeq = Sequence(Func(playChargeFire),
		Wait(1.0),
		Func(requestIdle))
	    self.cfireSeq.start()
	    
	def exitChargeFire():
    	    print 'Player exits Charge Firing State'
    	    self.craft.stop('chargeFire')

	def enterCoolPose():
    	    print 'Player enters Cool Pose State'
       	    self.cPoseSeq = Sequence(Func(playChargeWindup),
		Func(SoundFX.grunt.start),
		Wait(2.0),
		Func(requestIdle))
	    self.cPoseSeq.start()
	    
	def exitCoolPose():
    	    print 'Player exits Cool Pose State'
    	    self.craft.stop('chargeWindup')

	def enterPain():
    	    print 'Player enters Pain State'
       	    self.painSeq = Sequence(Func(playPain),
		Wait(1.0),
		Func(requestIdle))
	    self.painSeq.start()

	    # Play Random Pain Sound Effect
	    randomPain = random.uniform(0, 2)
	    if randomPain >= 1:
	        SoundFX.pain1.start()
	    else:
	        SoundFX.pain2.start()

	def exitPain():
    	    print 'Player exits Pain State'
	    self.craft.stop('pain')

	def enterCrash():
    	    print 'Player enters Crash State'
      	    self.crashSeq = Sequence(Func(playCrash),
		Wait(1.0),
		Func(requestIdle))
	    self.crashSeq.start()

	def exitCrash():
    	    print 'Player exits Crash State'
	    self.craft.stop('crash')

	def enterDeath():
    	    print 'Player enters Death State'

	    # Kill Controls to Player
            taskMgr.remove('moveP1')

	    self.craft.stop('pain')
	    self.craft.stop('crash')	
	    self.craft.setHpr(180, 0, 0)
	    self.craftDummy.setHpr(0, 0, 0)
	    self.craft.play('death')

	    # Play Sound Effect
	    SoundFX.death.start()


	def exitDeath():
    	    print 'Player exits Death State'
    	    

	# Functions used in the above FSM Sequences
	def requestIdle():
	    if self.isAlive == 1:
	        self.playerFSM.request('idle')

	# Request Idle when leaving Winddown
	# Only go to Idle if not charging
	def requestIdle2():
	    if self.currentCharge == 0 and self.isAlive == 1:
	        self.playerFSM.request('idle')

	def requestShoot():
	    self.playerFSM.request('shoot')

	def playPain():
	    self.craft.play('pain')

	def playFireWinddown():
	    self.craft.play('fireWinddown')

	def playCrash():
	    self.craft.play('crash')

	def playChargeFire():
	    self.craft.play('chargeFire')

	def playChargeWindup():
	    self.craft.play('chargeWindup')


	########################################
	# Finite State Machine 
	########################################
	
	# Define the States Themselves and their relationship to the callback functions
	self.idleState = State('idle', enterIdle, exitIdle, ['shoot', 'pain', 'death', 'charge', 'crash', 'coolPose'])

	self.painState = State('pain', enterPain, exitPain, ['idle', 'death', 'crash'])

	self.crashState = State('crash', enterCrash, exitCrash, ['idle', 'death', 'pain'])

	self.chargeState = State('charge', enterCharge, exitCharge, ['chargeFire', 'pain', 'crash', 'death', 'idle'])

	self.coolPoseState = State('coolPose', enterCoolPose, exitCoolPose, ['idle'])

	self.chargeFireState = State('chargeFire', enterChargeFire, exitChargeFire, ['idle', 'pain', 'crash', 'death'])

	self.shootState = State('shoot', enterShoot, exitShoot, ['pain', 'crash', 'death', 'charge', 'shootWinddown'])

	self.shootWindupState = State('shootWindup', enterShootWindup, exitShootWindup, ['shoot', 'pain', 'crash', 'death'])

	self.shootWinddownState = State('shootWinddown', enterShootWinddown, exitShootWinddown, ['shoot', 'pain', 'crash', 'death', 'idle', 'charge'])

	self.deathState = State('death', enterDeath, exitDeath, ['idle'])


	# Create the FSM itself from the above States
	self.playerFSM = ClassicFSM('playerAI',
              [self.idleState, self.painState, self.crashState, self.chargeState, self.coolPoseState, self.chargeFireState, self.shootWindupState, self.shootWinddownState, self.shootState, self.deathState],
              'idle', # Initial state
              'death', # Final state
              )

	# Tell the fsm to enter its initial state
	self.playerFSM.enterInitialState()


    ########################################
    # Define Tasks
    ########################################

    # Create a task to Move the Player
    def movePlayer(self, task):

	# Calculate the Current Tick (Game Time)	
	# This makes the game controls independent of Framerate
	self.currentTick = math.floor(task.time / ClassGlobals.gameSpeed)
	timeAdjust = self.currentTick - self.lastTick

	# Do Recovery Time from Damage
	# Adds time since the last Task Loop was Executed
	self.currentStun += task.time - self.prevTime 

	# If it is a Tick, then Update the World
	if self.currentTick > self.tick:
	    self.tick = self.currentTick
	    self.currentShootDelay = self.currentShootDelay - 1

	    # Get Player's Coordinates
            pX = self.player.getX()
	    pY = self.player.getY()
	    pZ = self.player.getZ()

	    pH = self.craftDummy.getH()
	    pP = self.craftDummy.getP()
	    pR = self.craft.getR()

	    pCraftZ = self.craft.getZ()

	    # If Mouse Controls are Enabled:
	    if ClassGlobals.mouseIsEnabled == 1:

		# Calculate the Change in X and Y
		changeX = self.mouser.mouseChangeX
		changeY = self.mouser.mouseChangeY

		if changeX != 0 and changeY != 0:
		    self.lastMouseMove = task.time

		# Check for Deadzones for the X and Y values
		if abs(changeX) <= self.mouser.deadZone:
	 	    changeX = 0
		if abs(changeY) <= self.mouser.deadZone:
		    changeY = 0

		if changeX >= self.mouser.maxZone:
		    changeX = self.mouser.maxZone
		elif changeX <= -self.mouser.maxZone:
		    changeX = -self.mouser.maxZone

		if changeY >= self.mouser.maxZone:
		    changeY = self.mouser.maxZone
		elif changeY <= -self.mouser.maxZone:
		    changeY = -self.mouser.maxZone

		# Use Half of Mouse Sensitivity for Crosshair Control
		cursorChangeX = changeX / self.mouser.mouseSensitivity * self.aimSensitivity
		cursorChangeY = changeY / self.mouser.mouseSensitivity * self.aimSensitivity

		# Update HPR
		pP -= cursorChangeY /2.0
		pH -= cursorChangeX /2.0
		pR -= cursorChangeX /2.0

		# Account for Mouse Sensitivity
		changeX = changeX / self.mouser.mouseSensitivity
		changeY = changeY / self.mouser.mouseSensitivity

		# Alter Horizontal Speeds with the Acceleration Values
		self.horizSpeed += changeX
		self.vertSpeed -= changeY

		# Make sure not above Speed Limit
		if self.vertSpeed > self.maxSpeed:
		    self.vertSpeed = self.maxSpeed
		if self.vertSpeed < -self.maxSpeed:
		    self.vertSpeed = -self.maxSpeed
		if self.horizSpeed > self.maxSpeed:
		    self.horizSpeed = self.maxSpeed
		if self.horizSpeed < -self.maxSpeed:
		    self.horizSpeed = -self.maxSpeed
		if pP > self.maxTilt:
		    pP = self.maxTilt
		if pP < -self.maxTilt:
		    pP = -self.maxTilt
		if pH > self.maxTilt:
		    pH = self.maxTilt
		if pH < -self.maxTilt:
		    pH = -self.maxTilt
		if pR > self.maxTilt:
		    pR = self.maxTilt
		if pR < -self.maxTilt:
		    pR = -self.maxTilt

	    # Alter Player Position according to Velocity
	    # Check for Level's Movement Boundaries
            if pZ < (Level1.railDummy.getZ()+Level1.maxBoundZ) and self.vertSpeed > 0:
            	pZ = pZ + self.vertSpeed
	    if pZ > (Level1.railDummy.getZ()-Level1.maxBoundNegZ) and self.vertSpeed < 0:
	    	pZ = pZ + self.vertSpeed

            if pX < (Level1.railDummy.getX()+Level1.maxBoundX) and self.horizSpeed > 0: 
	    	pX = pX + self.horizSpeed
	    if pX > (Level1.railDummy.getX()-Level1.maxBoundNegX) and self.horizSpeed < 0:
            	pX = pX + self.horizSpeed


	    # Check for Level's Pain Zones
	    if pX < (Level1.railDummy.getX()-Level1.maxBoundNegX+10):
		self.takeDamage('environment')
		self.horizSpeed = 2
		pR = self.maxTilt * -5
		self.bobSpeed = 0.25
		self.maxBob = 1
		self.currentBob = 0

	    if pX > (Level1.railDummy.getX()+Level1.maxBoundX-10):
		self.takeDamage('environment')
		self.horizSpeed = -2
		pR = self.maxTilt * 5
		self.bobSpeed = 0.25
		self.maxBob = 1
		self.currentBob = 0
	
	    # If Mouse Controls are Enabled:
	    if ClassGlobals.mouseIsEnabled == 1:

		# If Mouse has not been Moved Recently, reset tilt
		if self.lastMouseMove + 0.2 <= task.time:
	    	    # Reset the HPR of craft over time
	    	    if pH != 0 and abs(pH) >= self.tiltDeadZone :
	    	    	if pH > 0:
		    	    pH = pH - self.resetTiltSpeed * timeAdjust / 5
	            	else:
		    	    pH = pH + self.resetTiltSpeed * timeAdjust / 5

	    	    if pP != 0:
	            	if pP > 0 and abs(pP) >= self.tiltDeadZone:
		    	    pP = pP - self.resetTiltSpeed * timeAdjust / 5
	    	    	else:
		    	    pP = pP + self.resetTiltSpeed * timeAdjust / 5

	    	    if pR != 0:            	   
		    	if pR > 0 and abs(pR) >= self.tiltDeadZone:
		    	    pR = pR - self.resetTiltSpeed * timeAdjust / 5
	    	    	else:
		    	    pR = pR + self.resetTiltSpeed * timeAdjust / 5

	   	# Reset the Acceleration of the craft over time
	        if self.vertSpeed != 0:
	    	    if self.vertSpeed > 0:
	            	self.vertSpeed = self.vertSpeed - self.deceleration * timeAdjust
	            else:
		    	self.vertSpeed = self.vertSpeed + self.deceleration * timeAdjust

	    	if self.horizSpeed != 0:
	    	    if self.horizSpeed > 0:
		    	self.horizSpeed = self.horizSpeed - self.deceleration * timeAdjust
	    	    else:
		    	self.horizSpeed = self.horizSpeed + self.deceleration * timeAdjust

	    # Check for Deadzones for Position
	    if abs(self.horizSpeed) < self.deadZone:
		self.horizSpeed = 0
	    if abs(self.vertSpeed) < self.deadZone:
		self.vertSpeed = 0
	
	    # Check Deadzones for Rotation	
	    if abs(pH) < self.tiltDeadZone:
		pH = 0
	    if abs(pP) < self.tiltDeadZone:
		pP = 0
	    if abs(pR) < self.tiltDeadZone:
		pR = 0

	    # Calculate Slow Bobbing of Craft Up and Down
	    if pCraftZ < self.maxBob and self.currentBob == 0:
		pCraftZ = pCraftZ + self.bobSpeed * timeAdjust
		if pCraftZ >= self.maxBob:
			self.currentBob = 1
	    elif pCraftZ > -self.maxBob and self.currentBob == 1:
		pCraftZ = pCraftZ - self.bobSpeed * timeAdjust
		if pCraftZ <= -self.maxBob:
			self.currentBob = 0

	    # Calculate Bobbing Recovery from Crashes
	    if self.maxBob > self.normalMaxBob:
		self.maxBob -= 0.01
	    if self.bobSpeed > self.normalBobSpeed:
		self.bobSpeed -= 0.001

	    # Set New Player Location & Orientation
	    self.player.setPos(pX, pY, pZ)
	    self.craftDummy.setHpr(pH, pP, 0)	
	    self.craft.setR(pR)
	    self.craft.setZ(pCraftZ)


	    # Check for Shooting
   	    # If fire button down, start Charge
	    if self.keypressHolder['shoot1'] is 1 and self.currentShootDelay <= 0 and self.currentCharge < self.maxCharge:
		self.currentCharge = self.currentCharge + 1

	    # Player is Shooting Case
	    elif self.keypressHolder['shoot1'] is 0 and self.currentCharge != 0:
		# Stop Targeting Raytrace Picking Task
		if self.isPicking == 1:
		    self.isPicking = 0

		    # If any Targets are Locked, Fire Laser
		    if self.numLockTargets >= 1:

		        # Change State to Charge Firing
		        self.playerFSM.request('chargeFire')

		        # Fire Homing Laser
		        self.playerFireHomingLaser()

		    # Otherwise, revert to Idle Pose
		    else:
			self.playerFSM.request('idle')

		    # Stop Lock-On Mechanism
		    taskMgr.remove("raytrace")

		    # Reset Crosshair back to Normal
                    self.crosshair.setTexture(self.normalCrosshair, 1)
		    self.currentCharge = 0

		# Autofire for 3 Round Bursts if not in Crashing State
		elif self.playerFSM.getCurrentState().getName() != 'crash':
		    
		    self.currentBurst = 0
		    self.playerShoot()
	
		    # Play Shooting Animation
	            self.playerFSM.request('shoot')

	    # If Charge is significant, show Charge Effect
	    if self.currentCharge > 15:

		if self.isPicking == 0:
		    self.isPicking = 1

		    # self.playerCharge()
		    self.playerFSM.request('charge')

                    # Change Crosshair to Charge Mode
                    self.crosshair.setTexture(self.chargeCrosshair, 1)

		    # Begin Targeting Raytrace Picking Task
		    taskMgr.add(self.pickRaytrace, "raytrace")


	    # Check for firing on Secondary Weapon
	    if self.keypressHolder['shoot2'] is 1 and self.currentShootDelay <= 0:
		self.playerFireAux()
		self.currentBurst = 3


	    # Autofiring for 3 Round Bursts
	    if self.currentShootDelay == 0 and self.keypressHolder['shoot1'] is 0 and self.currentBurst < self.burstMax -1:
		self.currentBurst += 1
		self.playerShoot()

	    # Play Shooting Wind Down Animation if Done Shooting
	    # This eventually returns back to the Idle Animation
	    elif self.currentBurst == self.burstMax-1:
		self.playerFSM.request('shootWinddown')
		self.currentBurst += 1

	    # Update the Older Tick
	    self.lastTick = self.currentTick
	    self.prevTime = task.time

	# Make the task continue
	return Task.cont


    # Create a task to Move the Player
    def updateCrosshair(self, task):
	self.crosshair.lookAt(camera)
	return Task.cont


    ########################################
    # Methods: Makes Player Fire Projectiles
    ########################################

    # Creates Projectiles when the Fire Button is Pressed
    def playerShoot(self):

	# Activates a Shooting Cool-down time
	self.currentShootDelay = self.shootDelay

	# Clear Charge Counter
	self.currentCharge = 0

	# Fire Main GunDummy
	if self.blastLevel == 1 or self.blastLevel == 3 or self.blastLevel == 5:
	    # Get Player's Coordinates
	    playerPos = self.gunDummy
	    playerHpr = self.gunDummy.getHpr(Level1.railDummy)

	    # Fire Projectile
	    self.playerBullet = PlayerProjectile.Projectile(ClassGlobals.bulletNum,  playerPos, playerHpr, self.blastScale, self.currentCharge)
	    ClassGlobals.bulletNum = ClassGlobals.bulletNum + 1
	    taskMgr.add(self.playerBullet.moveProjectile, 'moveProjectile')


	# Fire Secondary GunDummies
	if self.blastLevel == 2 or self.blastLevel == 4 or self.blastLevel == 5:
	    # Get Player's Coordinates
	    playerPos = self.gunDummy2
	    playerHpr = self.gunDummy2.getHpr(Level1.railDummy)

	    # Fire Projectile
	    self.playerBullet = PlayerProjectile.Projectile(ClassGlobals.bulletNum,  playerPos, playerHpr, self.blastScale, self.currentCharge)
	    ClassGlobals.bulletNum = ClassGlobals.bulletNum + 1
	    self.currentCharge = 0
	    taskMgr.add(self.playerBullet.moveProjectile, 'moveProjectile')

	    # Get Player's Coordinates
	    playerPos = self.gunDummy3
	    playerHpr = self.gunDummy3.getHpr(Level1.railDummy)

	    # Fire Projectile
	    self.playerBullet = PlayerProjectile.Projectile(ClassGlobals.bulletNum,  playerPos, playerHpr, self.blastScale, self.currentCharge)
	    ClassGlobals.bulletNum = ClassGlobals.bulletNum + 1
	    self.currentCharge = 0
	    taskMgr.add(self.playerBullet.moveProjectile, 'moveProjectile')


	# Fire Tertiary GunDummies
	if self.currentBurst == 0 and (self.blastLevel == 3 or self.blastLevel == 4 or self.blastLevel == 5):
	    # Get Player's Coordinates
	    playerPos = self.gunDummy4
	    playerHpr = self.gunDummy4.getHpr(Level1.railDummy)

	    # Fire Projectile
	    self.playerBullet = PlayerProjectile.Projectile(ClassGlobals.bulletNum,  playerPos, playerHpr, self.blastScale, self.currentCharge)
	    ClassGlobals.bulletNum = ClassGlobals.bulletNum + 1
	    self.currentCharge = 0
	    taskMgr.add(self.playerBullet.moveProjectile, 'moveProjectile')

	    # Get Player's Coordinates
	    playerPos = self.gunDummy5
	    playerHpr = self.gunDummy5.getHpr(Level1.railDummy)

	    # Fire Projectile
	    self.playerBullet = PlayerProjectile.Projectile(ClassGlobals.bulletNum,  playerPos, playerHpr, self.blastScale, self.currentCharge)
	    ClassGlobals.bulletNum = ClassGlobals.bulletNum + 1
	    self.currentCharge = 0
	    taskMgr.add(self.playerBullet.moveProjectile, 'moveProjectile')

	# Play Sound Effect
	SoundFX.projectile.start()


    # Creates Secondary Weapon Projectile
    def playerFireAux(self):

	# This Function Would be used for firing a Secondary Weapon
	# It is currently not being used.
	print 'Fire Disc-Bomb Attack!'

	# Activates a Shooting Cool-down time
	self.currentShootDelay = self.shootDelay * self.bombDelay

	# Get Player's Coordinates
	playerPos = self.player.getPos()
	playerHpr = self.craftDummy.getHpr()


    ########################################
    # Method: Resolves Collision Results
    ########################################

    # Resolves Collision
    def checkCollision(self, collisionEntry):
	
	# As long as CollisionEntry is not a String called 'environment'
	if collisionEntry != 'environment':
 	    	
	    if self.isAlive == 1 and self.currentStun >= self.stunRecoveryTime:

	        # Reset Stun Time to prevent Multiple Collisions from same object
	        self.currentStun = 0

	        # Get the Victim's Name
	        incoming = collisionEntry.getFromNode()
	        incoming = incoming.getName()

	        # Collision Object is a Powerup
	        if incoming[0:11] == 'collpowerup':
		    if self.blastLevel < self.maxLevel:
	                # PLAYER GOT A POWERUP!
		        self.blastScale += self.blastUpgrade
		        self.blastLevel += 1

   	            # Create Effect Trail
	            self.effectTrail = specialEffects.ChainSpriteShockwave('pulser', 1, 0, 0.2, 5, self.craft, 1, 1)

		    # Flash Player
	            self.flashPlayer()

	   	    # Play Sound Effect
	   	    SoundFX.grunt.start()

	        # Otherwise, collision is a Hostile Attack
	        else:
		    self.takeDamage(collisionEntry)

	    # Otherwise, collision is a Hostile Attack
	    else:
	        self.takeDamage(collisionEntry)


    # Causes Player to Take Damage
    def takeDamage(self, collisionEntry):

	# If damage is from Environment Boundaries
	if collisionEntry == 'environment':
	    # PLAYER CRASHED INTO ENVIRONMENT!

	    # Reduce Health
	    damage = 2
	
	    # Subtract Damage if not Invincible
	    if self.isInvincible == 0 and self.godMode == 0:
 	        self.health -= damage

 	        # Update the Lifebar
	        ClassGlobals.hud1.damageLifebar(ClassGlobals.hud1.lifeBar, damage/2)

	    # Play Pain Animation
	    if self.health > 0:
	        self.playerFSM.request('crash')
	    else:
		self.playerFSM.request('death')

	    # Shake Camera
	    ClassGlobals.lazyCamera1.cameraShaker(0.1, 5)

	    # Play Pain Animation
	    if self.health > 0:
	    	self.playerFSM.request('pain')
	    else:
	    	self.playerFSM.request('death')

	    # Play Sound Effect
	    SoundFX.playerHit1.start()
	
	    # Camera Flashes Red
	    ClassGlobals.hud1.flash(0.2, 1, 0, 0, 0.6)

	    # Check for Death
	    if self.health <= 0:
	    	# Kaboom! You died.
	    	print 'PLAYER DIED!'
	    	self.explode()

	# Otherwise is from Collision Detection
	else:

	    # Get the Victim's Name
	    incoming = collisionEntry.getFromNode()
	    incoming = incoming.getName()

	    # If hit by Enemy Bullet
	    if incoming[0:15] == 'enemycollbullet':
	        # PLAYER SHOT BY ENEMY!

	    	# Reduce Health
		damage = 4*ClassGlobals.difficulty

	        # Subtract Damage if not Invincible
	        if self.isInvincible == 0 and self.godMode == 0:
 	    	    self.health -= damage

 		    # Update the Lifebar
		    ClassGlobals.hud1.damageLifebar(ClassGlobals.hud1.lifeBar, damage/2)
		# Shake Camera
		ClassGlobals.lazyCamera1.cameraShaker(0.06, 5)

	        # Play Pain Animation
	        if self.health > 0:
	    	    self.playerFSM.request('pain')
	        else:
	    	    self.playerFSM.request('death')

	        # Play Sound Effect
	        SoundFX.playerHit1.start()
	
	        # Camera Flashes Red
	        ClassGlobals.hud1.flash(0.2, 1, 0, 0, 0.6)

	        # Check for Death
	        if self.health <= 0:
	    	    # Kaboom! You died.
	    	    print 'PLAYER DIED!'
	    	    self.explode()

	    elif incoming[0:17] == 'enemyHomingBullet':
	        # PLAYER HIT BY HOMING MISSILE!

	        # Reduce Health
		damage = 2*ClassGlobals.difficulty

	        # Subtract Damage if not Invincible
	        if self.isInvincible == 0 and self.godMode == 0:
	    	    self.health -= damage

 	  	    # Update the Lifebar
		    ClassGlobals.hud1.damageLifebar(ClassGlobals.hud1.lifeBar, damage/2)

		# Shake Camera
		ClassGlobals.lazyCamera1.cameraShaker(0.05, 5)

	        # Play Pain Animation
	        if self.health > 0:
	    	    self.playerFSM.request('pain')
	        else:
	    	    self.playerFSM.request('death')

	        # Play Sound Effect
	        SoundFX.playerHit1.start()
	
	        # Camera Flashes Red
	        ClassGlobals.hud1.flash(0.2, 1, 0, 0, 0.6)

	        # Check for Death
	        if self.health <= 0:
	    	    # Kaboom! You died.
	    	    print 'PLAYER DIED!'
	    	    self.explode()

	    elif incoming[0:9] == 'collenemy':
	    	# PLAYER CRASHED INTO ENEMY!

	    	# Reduce Health
		damage = 4*ClassGlobals.difficulty

	        # Subtract Damage if not Invincible
	        if self.isInvincible == 0 and self.godMode == 0:
	    	    self.health -= damage

		    # Update the Lifebar
		    ClassGlobals.hud1.damageLifebar(ClassGlobals.hud1.lifeBar, damage/2)

		# Shake Camera
		ClassGlobals.lazyCamera1.cameraShaker(0.1, 5)

	        # Play Pain Animation
	        if self.health > 0:
	    	    self.playerFSM.request('pain')
	        else:
	    	    self.playerFSM.request('death')

	        # Play Sound Effect
	        SoundFX.playerHit1.start()
	
	        # Camera Flashes Red
	        ClassGlobals.hud1.flash(0.2, 1, 0, 0, 0.6)

	        # Check for Death
	        if self.health <= 0:
	    	    # Kaboom! You died.
	    	    print 'PLAYER DIED!'
	    	    self.explode()


    ########################################
    # Methods: Flashes Player when Hit
    ########################################

    # Makes Player Flash
    def flashPlayer(self):
	flasher = Sequence(Func(self.flash), 
		Wait(0.1),
		Func(self.restoreColor))
	flasher.start()

    # Causes Player to Flash
    def flash(self):
	if self.craft.isEmpty() != 1:
	    self.craft.setTexture(LoadTextures.white, 1)

    def restoreColor(self):
	if self.craft.isEmpty() != 1:
	    self.craft.setTextureOff()


    ########################################
    # Methods: Causes Player to Die
    ########################################

    # Player Explodes and dies
    def explode(self):

	# Player is Dead!
	self.isAlive = 0

	# Stop Collisions
	ClassGlobals.collider.playerTraverser.removeCollider(self.collision)
 	self.collision.removeNode()

        # Camera Fades to Black Background
	ClassGlobals.hud1.showGameOver()

	# Move Camera In Place for Death Animation
	camera.setPos(self.player, 3, 10, 0)

	# Request Death State
	self.playerFSM.request('death')

	# Play Sound Effect
	SoundFX.explosion2.start()


    ########################################
    # Homing Laser Functions
    ########################################

    # Traces a Ray each Frame to Check for Lock-Ons for Homing Laser 
    def pickRaytrace(self, task):

	# Do Raytrace Picking
    	nodePath = self.raycaster1.pick()

	# If Nodepath is Not Empty, get Name
	if nodePath != None:
	    nodeName = nodePath.getName()
	
	    # Only Lock on to Enemy Collision Spheres 
	    # Ignore all Bullets and Power-ups
    	    if nodeName[0:9] == 'collenemy':
	    	# LOCK-ON!

	    	# Add Target to List of Locked Targets
	    	self.addLock(nodePath)

	    	# Add LockOn Crosshair to Target
	    	g = nodePath.getChildren()
	    	g.reparentTo(nodePath.getParent())

    	return Task.cont


    # Creates Secondary Weapon Projectile
    def playerFireHomingLaser(self):

	# Fire Homing Laser!

	# Activates a Shooting Cool-down time
	self.currentShootDelay = self.shootDelay * self.homingLaserDelay

	# Fire Projectile
	for i in range(self.numLockTargets):
	    if self.lockTargets[i] != None:

		# Get Homing Laser Start Coordinates
		startPos = self.laserDummies[i].getPos(Level1.railDummy)
		startHpr = self.laserDummies[i].getHpr()

		# Create Homing Blast
		self.playerBullet = HomingMissile.HomingMissile('homingLaser', ClassGlobals.bulletNum, startPos, startHpr, self.lockTargets[i])
		ClassGlobals.bulletNum = ClassGlobals.bulletNum + 1
		self.currentCharge = 0
		taskMgr.add(self.playerBullet.moveProjectile, 'moveProjectile')

		# Play Sound Effect
		SoundFX.launchMissile.start()

	# Remove List of Locked Targets
	self.removeLock() 


    # Add to Homing Laser Targets
    def addLock(self, nodePath):

	if self.numLockTargets < self.maxLock:	

	    # Make sure Target is not already on List of Targets
	    for i in range(self.numLockTargets):
	    	if nodePath.compareTo(self.lockTargets[i]) == 0:
		    return None

	    # Add LockOn Crosshair Animation
	    cursor = LockOnCrosshair.LockOnCrosshair(nodePath)
	    self.lockCrosshairs[self.numLockTargets] = cursor

	    # Add new target to List
	    self.lockTargets[self.numLockTargets] = nodePath
	    self.numLockTargets += 1
	    SoundFX.lockon.start()


    # Remove all Targets
    def removeLock(self):
	for i in range(self.numLockTargets):
	    # Clear list of Targets			
	    self.lockTargets[i] = None
	    
	    # Remove Crosshairs & Clear Crosshair List		
	    self.lockCrosshairs[i].destroy()
	    self.lockCrosshairs[i] = None

	self.numLockTargets = 0


    ########################################
    # Reset Player Function
    ########################################

    # Resets the Player to be Respawned
    def resetPlayer(self):

	# Reset Key Variables
	self.health = 100
	self.currentStun = 0.0
	self.isAlive = 1
	self.numLockTargets = 0
	self.currentBurst = 0
	self.currentCharge = 0
	self.isPicking = 0
	self.numLockTargets = 0
	self.blastLevel = 1

	self.currentBob = 0
	self.tick = 0
	self.currentTick = 0
	self.lastTick = 0
	self.prevTime = 0.0

	# Reactivate Collisions
        self.collision = self.craft.attachNewNode(self.collplayer)
     	ClassGlobals.collider.playerTraverser.addCollider(self.collision, ClassGlobals.collider.collisionEvent)

	# Tell the fsm to enter its initial state
	self.playerFSM.request('idle')
