###########################################################
# EnemyBoss - Railshooter Module
# Contains the class to create the Boss Character
###########################################################

# DESCRIPTION:
# This file creates a Large Robot Boss Character.  It deals with
# complex collision detection by attaching collision spheres to parts
# of the animated Actor.  Also, it contains the AI defining the Boss's
# attack pattern.


from direct.showbase.ShowBaseGlobal import *
from direct.interval.IntervalGlobal import *
from direct.showbase import DirectObject
from direct.task import Task
import random
from direct.actor import Actor

# Import Other Railshooter Modules
import EnemyProjectile
import ClassGlobals
import Level1
import SoundFX
import specialEffects
import LoadParticles
import LoadTextures
import EnemyHomingMissile


########################################
# Enemy Boss Class
########################################
class enemyBoss(DirectObject.DirectObject):
    def __init__(self): 

	self.p1 = ClassGlobals.p1
	self.isAlive = 1

	self.gameSpeed = ClassGlobals.gameSpeed
	self.collider = ClassGlobals.collider

	self.accuracy = 2.0		# Lower is better aim
	self.shootDelay = 2.0
	self.shootWait = 6.0


	########################################
	# Define Class Variables
	########################################

	self.health = 4500 + (ClassGlobals.difficulty*100)
	self.lArmHealth = 200 + (ClassGlobals.difficulty*100)
	self.rArmHealth = 200 + (ClassGlobals.difficulty*100)
	self.headHealth = 600 + (ClassGlobals.difficulty*100)

	self.headIsAlive = 1
	self.chestIsAlive = 1
	self.lArmIsAlive = 1
	self.rArmIsAlive = 1

	self.clearPoints = 46000 + (ClassGlobals.difficulty*2000)
	self.headPoints = 11000 + (ClassGlobals.difficulty*2000)
	self.lArmPoints = 6000 + (ClassGlobals.difficulty*2000)
	self.rArmPoints = 6000 + (ClassGlobals.difficulty*2000)

	self.explosions = 100

	self.tick = 0
	self.currentTick = 0

	self.AIcounter = 0
	self.currentTime = 0
	self.timeOffset = 0

	self.shotsFired = 0
	self.currentShootDelay = self.shootWait + random.uniform(0, 0.4)


	########################################
	# Load the Enemy Models
	########################################

     	# Add a dummy object "root" node for the Enemy
	# This object is the one that gets moved around
	self.boss = render.attachNewNode('boss')

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

	# Enemy Shoots from this Dummy Object's location
	self.gunDummy = self.craftDummy.attachNewNode('gunDummy')

        # Load the Enemy Craft or Vehicle Model
	# This object gets the Roll
        self.craft = Actor.Actor("Models/boss/boss-skeleton",
				{'idle':'Models/boss/boss-idle',
				'pain':'Models/boss/boss-pain',
				'hit':'Models/boss/boss-hit',
				'leftfire':'Models/boss/boss-leftfire',
				'rightfire':'Models/boss/boss-rightfire'})
	self.craft.setScale(0.6, 0.6, 0.6)
	
        # Bind animations to Preload them
	self.craft.bindAnim("idle")
	self.craft.bindAnim("pain")
	self.craft.bindAnim("hit")
	self.craft.bindAnim("leftfire")
	self.craft.bindAnim("rightfire")
	
	# Disable Actor Culling when Actor is Offscreen
	self.craft.node().setBound(OmniBoundingVolume())
  	self.craft.node().setFinal(1)

  	# Loop Idle Animation
	self.craft.loop('idle')

	# Reparent Craft Model to Craft Dummy
	self.craft.reparentTo(self.craftDummy)


	########################################
	# Load Breakable Boss Parts and Attach to Skeleton
	########################################

	# Load the Chest Carapace Mesh
 	self.chest = loader.loadModelCopy("Models/boss/boss-chest")
	self.chest.reparentTo(self.craft)

	# Attach the Collision Sphere to the Chest Bone
	self.craft.exposeJoint(self.chest, "modelRoot", "Bone_chest")


	# Load the Lower Right Arm Mesh
 	self.rightArm = loader.loadModelCopy("Models/boss/boss-rarmlower")
	self.rightArm.reparentTo(self.craft)

	# Attach the Collision Sphere to the Chest Bone
	self.craft.exposeJoint(self.rightArm, "modelRoot", "Bone_r_arm_lower")


	# Load the Lower Left Arm Mesh
 	self.leftArm = loader.loadModelCopy("Models/boss/boss-larmlower")
	self.leftArm.reparentTo(self.craft)

	# Attach the Collision Sphere to the Chest Bone
	self.craft.exposeJoint(self.leftArm, "modelRoot", "Bone_l_arm_lower")


	# Load the Head Mesh
 	self.head = loader.loadModelCopy("Models/boss/boss-head")
	self.head.reparentTo(self.craft)

	# Attach the Collision Sphere to the Head Bone
	self.craft.exposeJoint(self.head, "modelRoot", "Bone_head")


	########################################
	# Setup Fancy Boss Collision Detection
	########################################

      	# Create Boss Chest Collision Sphere
        self.enemySphere = CollisionSphere(0, 0, 0, 65)
        self.collbossChest = CollisionNode("collbossChest")
        self.collbossChest.addSolid(self.enemySphere)
        self.collision = self.craft.attachNewNode(self.collbossChest)
        # self.collision.show()

	# Attach the Collision Sphere to the Chest Bone
	self.craft.exposeJoint(self.collision, "modelRoot", "Bone_chest")

	# Set Bitmask for the Enemy
	# Enemy can Collide against Victims with Bitmask 0
	self.collbossChest.setFromCollideMask(BitMask32.bit(0))

	# Set Bitmask for the Enemy
	# Accept Collisions with BitMask 1 (Projectiles)
	self.collbossChest.setIntoCollideMask(BitMask32.bit(1))

	# Register to Traverser
	self.collider.enemyTraverser.addCollider(self.collision, self.collider.collisionEvent)


      	# Create Left Arm Collision Sphere
	self.enemySphere2 = CollisionSphere(50, 0, 0, 60)
        self.collbossLArm = CollisionNode("collbossLArm")
        self.collbossLArm.addSolid(self.enemySphere2)
        self.collision2 = self.craft.attachNewNode(self.collbossLArm)
        # self.collision2.show()

	# Attach the Collision Sphere to the Left Arm Bone
	self.craft.exposeJoint(self.collision2, "modelRoot", "Bone_l_arm_lower")

	# Set Bitmask for the Enemy
	# Enemy can Collide against Victims with Bitmask 0
	self.collbossLArm.setFromCollideMask(BitMask32.bit(0))

	# Set Bitmask for the Enemy
	# Accept Collisions with BitMask 1 (Projectiles)
	self.collbossLArm.setIntoCollideMask(BitMask32.bit(1))

	# Register to Traverser
	self.collider.enemyTraverser.addCollider(self.collision2, self.collider.collisionEvent)

      	# Create Right Arm Collision Sphere
	self.enemySphere3 = CollisionSphere(50, 0, 0, 60)
        self.collbossRArm = CollisionNode("collbossRArm")
        self.collbossRArm.addSolid(self.enemySphere3)
        self.collision3 = self.craft.attachNewNode(self.collbossRArm)
        # self.collision3.show()

	# Attach the Collision Sphere to the Right Arm Bone
	self.craft.exposeJoint(self.collision3, "modelRoot", "Bone_r_arm_lower")

	# Set Bitmask for the Enemy
	# Enemy can Collide against Victims with Bitmask 0
	self.collbossRArm.setFromCollideMask(BitMask32.bit(0))

	# Set Bitmask for the Enemy
	# Accept Collisions with BitMask 1 (Projectiles)
	self.collbossRArm.setIntoCollideMask(BitMask32.bit(1))

	# Register to Traverser
	self.collider.enemyTraverser.addCollider(self.collision3, self.collider.collisionEvent)


      	# Create Head Collision Sphere
        self.enemySphere4 = CollisionSphere(15, 0, 0, 45)
        self.collbossHead = CollisionNode("collbossHead")
        self.collbossHead.addSolid(self.enemySphere4)
        self.collision4 = self.craft.attachNewNode(self.collbossHead)
        # self.collision4.show()

	# Attach the Collision Sphere to the Head Bone
	self.craft.exposeJoint(self.collision4, "modelRoot", "Bone_head")

	# Set Bitmask for the Enemy
	# Enemy can Collide against Victims with Bitmask 0
	self.collbossHead.setFromCollideMask(BitMask32.bit(0))

	# Set Bitmask for the Enemy
	# Accept Collisions with BitMask 1 (Projectiles)
	self.collbossHead.setIntoCollideMask(BitMask32.bit(1))

	# Register to Traverser
	self.collider.enemyTraverser.addCollider(self.collision4, self.collider.collisionEvent)


	########################################
	# Setup Arm Missile Dummies
	########################################
	# These mark the Positions that Homing Missiles will
	# be fired from near the Arm Cannon

	# All Arm Dummies are attached to the Missile Dummy for ease
	# of Movement
	self.missileDummy = self.craftDummy.attachNewNode('missileDummy')

	self.ArmDummy1 = self.missileDummy.attachNewNode('ArmDummy1')
	self.ArmDummy1.setPos(8.0, 0, 8)

	self.ArmDummy2 = self.missileDummy.attachNewNode('ArmDummy2')
	self.ArmDummy2.setPos(-8.0, 0, 8)

	self.ArmDummy3 = self.missileDummy.attachNewNode('ArmDummy3')
	self.ArmDummy3.setPos(8.0, 0, 0)

	self.ArmDummy4 = self.missileDummy.attachNewNode('ArmDummy4')
	self.ArmDummy4.setPos(-8.0, 0, 0)

	self.ArmDummy5 = self.missileDummy.attachNewNode('ArmDummy5')
	self.ArmDummy5.setPos(4.0, 0, 12)

	self.ArmDummy6 = self.missileDummy.attachNewNode('ArmDummy6')
	self.ArmDummy6.setPos(-4.0, 0, 12)

	self.ArmDummy7 = self.missileDummy.attachNewNode('ArmDummy7')
	self.ArmDummy7.setPos(4.0, 0, -4)

	self.ArmDummy8 = self.missileDummy.attachNewNode('ArmDummy8')
	self.ArmDummy8.setPos(-4.0, 0, -4)

	self.ArmDummy9 = self.missileDummy.attachNewNode('ArmDummy9')
	self.ArmDummy9.setPos(4.0, 0, 4)

	self.ArmDummy10 = self.missileDummy.attachNewNode('ArmDummy10')
	self.ArmDummy10.setPos(-4.0, 0, 4)

	# Enter the Laser Dummies into a List
	self.ArmDummies = {}
	self.ArmDummies[0] = self.ArmDummy1
	self.ArmDummies[1] = self.ArmDummy2
	self.ArmDummies[2] = self.ArmDummy3
	self.ArmDummies[3] = self.ArmDummy4
	self.ArmDummies[4] = self.ArmDummy5
	self.ArmDummies[5] = self.ArmDummy6
	self.ArmDummies[6] = self.ArmDummy7
	self.ArmDummies[7] = self.ArmDummy8
	self.ArmDummies[8] = self.ArmDummy9
	self.ArmDummies[9] = self.ArmDummy10


	########################################
	# Setup Plasma Dummies for the Head's Attack
	########################################

	self.plasmaDummy1 = self.craftDummy.attachNewNode('plasmaDummy1')
	self.plasmaDummy1.setPos(10.0, -100, 110)
	self.plasmaDummy1.setHpr(90, 10, 0)

	self.plasmaDummy2 = self.craftDummy.attachNewNode('plasmaDummy2')
	self.plasmaDummy2.setPos(-10.0, -100, 110)
	self.plasmaDummy2.setHpr(-90, 10, 0)

	self.plasmaDummy3 = self.craftDummy.attachNewNode('plasmaDummy3')
	self.plasmaDummy3.setPos(10.0, -100, 100)
	self.plasmaDummy3.setHpr(90, -10, 0)

	self.plasmaDummy4 = self.craftDummy.attachNewNode('plasmaDummy4')
	self.plasmaDummy4.setPos(-10.0, -100, 100)
	self.plasmaDummy4.setHpr(-90, -10, 0)

	self.plasmaDummy5 = self.craftDummy.attachNewNode('plasmaDummy5')
	self.plasmaDummy5.setPos(5.0, -100, 120)
	self.plasmaDummy5.setHpr(45, 45, 0)

	self.plasmaDummy6 = self.craftDummy.attachNewNode('plasmaDummy6')
	self.plasmaDummy6.setPos(-5.0, -100, 120)
	self.plasmaDummy6.setHpr(-45, 45, 0)

	# Enter the Laser Dummies into a List
	self.plasmaDummies = {}
	self.plasmaDummies[0] = self.plasmaDummy1
	self.plasmaDummies[1] = self.plasmaDummy2
	self.plasmaDummies[2] = self.plasmaDummy3
	self.plasmaDummies[3] = self.plasmaDummy4
	self.plasmaDummies[4] = self.plasmaDummy5
	self.plasmaDummies[5] = self.plasmaDummy6


    	########################################
    	# Activate the Boss AI
    	########################################

	# Run Boss AI
	taskMgr.add(self.bossIntroAI, 'bossIntroAI')


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

	self.accept('entercollbossChest', self.damage)
	self.accept('entercollbossLArm', self.leftArmDamage)
	self.accept('entercollbossRArm', self.rightArmDamage)
	self.accept('entercollbossHead', self.headDamage)


    	########################################
    	# Play Ambient Sound Effects
    	########################################

	# Play Ambient Sound
	SoundFX.ambientBoss.loop()


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

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

	def enterLeftShoot():
    	    print 'Boss enters Left Arm Shooting State'
       	    self.fireSeq = Sequence(Func(playLeftFire),
		Wait(1.0),
		Func(requestIdle))
	    self.fireSeq.start()
	  
	def exitLeftShoot():
    	    print 'Boss exits Left Arm Shooting State'
	    self.craft.stop('fire')

	def enterRightShoot():
    	    print 'Boss enters Right Arm Shooting State'
       	    self.fireSeq = Sequence(Func(playRightFire),
		Wait(1.0),
		Func(requestIdle))
	    self.fireSeq.start()
	  
	def exitRightShoot():
    	    print 'Boss exits Right Arm Shooting State'
	    self.craft.stop('fire')

	def enterPain():
    	    print 'Boss enters Pain State'
       	    self.painSeq = Sequence(Func(playPain),
		Wait(4.0),
		Func(requestIdleFromPain))
	    self.painSeq.start()

	    # Play Boss Roar Sound
	    SoundFX.bossRoar.start()
	
	def exitPain():
    	    print 'Boss exits Pain State'
	    self.craft.stop('pain')

	    # Choose a new AI Attack Pattern:
	    # 1.)  Either All Parts Alive, or only 1 Arm Destroyed
	    if self.headIsAlive == 1 and (self.lArmIsAlive == 1 or self.rArmIsAlive == 1):
	 	taskMgr.add(self.bossAI1, 'bossAI1')

	    # 2.)  Head Destroyed, but Arm(s) Remain
	    elif self.headIsAlive == 0 and (self.lArmIsAlive == 1 or self.rArmIsAlive == 1):
	        taskMgr.add(self.bossAInoHead, 'bossAInoHead')

	    # 3.)  Only Head Remaining
	    elif self.headIsAlive == 1 and self.lArmIsAlive == 0 and self.rArmIsAlive == 0:
	    	taskMgr.add(self.bossAIonlyHead, 'bossAIonlyHead')


	def enterHit():
    	    print 'Boss enters Hit React State'
       	    self.hitSeq = Sequence(Func(playHit),
		Wait(1.0),
		Func(requestIdle))
	    self.hitSeq.start()

	def exitHit():
    	    print 'Boss exits Hit React State'
	    self.craft.stop('hit')

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

	    # Kill Boss AI
            taskMgr.remove('bossAI')
            taskMgr.remove('bossAInoHead')
            taskMgr.remove('bossAIonlyHead')

	    # Halt All Other Animations
	    self.craft.stop('pain')
	    self.craft.stop('hit')	
	    self.craft.play('pain')

	    # Plays Death Animations and Big Explosion
    	    print 'Boss enters Pain State'
       	    self.painSeq = Sequence(Func(playPain),
		Wait(4.0),
		Func(playPain),
		Wait(2.0),
		Func(self.bigBang))
	    self.painSeq.start()

	    # Play Boss Roar Sound
	    SoundFX.bossRoar.start()

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

	def requestIdle():
            # If not in Pain Animation		
	    if self.bossFSM.getCurrentState().getName() != 'pain':
		self.bossFSM.request('idle')

	def requestIdleFromPain():
	    self.bossFSM.request('idle')

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

	def playHit():
	    self.craft.play('hit')

	def playLeftFire():
	    self.craft.play('leftfire')

	def playRightFire():
	    self.craft.play('rightfire')


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

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

	self.hitState = State.State('hit', enterHit, exitHit, ['idle', 'death', 'pain', 'rightshoot', 'leftshoot'])

	self.leftshootState = State.State('leftshoot', enterLeftShoot, exitLeftShoot, ['idle', 'pain', 'death', 'hit', 'rightshoot'])

	self.rightshootState = State.State('rightshoot', enterRightShoot, exitRightShoot, ['idle', 'pain', 'death', 'hit', 'leftshoot'])

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


	# Create the FSM itself from the above States
	self.bossFSM = ClassicFSM.ClassicFSM('bossAI',
              [self.idleState, self.painState, self.hitState, self.leftshootState, self.rightshootState, self.deathState],
              'idle', # Initial state
              'death', # Final state
              )

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



    ########################################
    # Methods: Boss AI Attack Patterns
    ########################################
    # Notes:  Animations blend better if played on numbers divisible  
    # by 4 since the idle takes roughly 4 seconds to play through 
    # a cycle

    # Define a Task for the Boss Introduction
    def bossIntroAI(self, task):	

	self.currentTime = task.time

	if self.currentTime > 4.0 and self.AIcounter == 0:
	    self.AIcounter += 1

            # Change State to Roaring Intro
	    self.bossFSM.request('pain')
   	    return Task.done

	return Task.cont


    # Define First Boss AI Attack Pattern
    def bossAI1(self, task):	

	self.currentTime = task.time - self.timeOffset

	if self.currentTime > 2.0 and self.AIcounter == 0:
	    self.AIcounter += 1
	    self.enemyLeftMissileCannon()

	if self.currentTime >= 4.0 and self.AIcounter == 1:
	    self.AIcounter += 1
	    self.enemyRightMissileCannon()

	if self.currentTime >= 6.0 and self.AIcounter == 2:
	    self.AIcounter += 1
	    self.enemyPlasmaCannon()

	# Reset the Timer to Loop the Boss's Attack Pattern
	if self.currentTime >= 12.0 and self.AIcounter == 3:
	    self.AIcounter = 0
	    self.timeOffset = task.time

	return Task.cont


    # Define Boss AI Attack Pattern if Head Destroyed Only
    def bossAInoHead(self, task):	

	self.currentTime = task.time - self.timeOffset

	if self.currentTime > 2.0 and self.AIcounter == 0:
	    self.AIcounter += 1
	    self.enemyLeftMissileCannon()

	if self.currentTime >= 4.0 and self.AIcounter == 1:
	    self.AIcounter += 1
	    self.enemyRightMissileCannon()

	if self.currentTime >= 6.0 and self.AIcounter == 2:
	    self.AIcounter += 1
	    self.enemyLeftMissileCannon()

	if self.currentTime >= 8.0 and self.AIcounter == 3:
	    self.AIcounter += 1
	    self.enemyRightMissileCannon()

	# Reset the Timer to Loop the Boss's Attack Pattern
	if self.currentTime >= 12.0 and self.AIcounter == 4:
	    self.AIcounter = 0
	    self.timeOffset = task.time

	return Task.cont


    # Define Boss AI Attack Pattern if everythin except the Head is Destroyed
    def bossAIonlyHead(self, task):	

	self.currentTime = task.time - self.timeOffset

	if self.currentTime > 2.0 and self.AIcounter == 0:
	    self.AIcounter += 1
	    self.enemyPlasmaCannon()

	if self.currentTime >= 4.0 and self.AIcounter == 1:
	    self.AIcounter += 1
	    self.enemyPlasmaCannon()

	if self.currentTime >= 6.0 and self.AIcounter == 2:
	    self.AIcounter += 1
	    self.enemyPlasmaCannon()

	# Reset the Timer to Loop the Boss's Attack Pattern
	if self.currentTime >= 8.0 and self.AIcounter == 3:
	    self.AIcounter = 0
	    self.timeOffset = task.time

	return Task.cont


    ########################################
    # Methods: Boss Projectile Shooting Methods
    ########################################

    # Creates a Slavo of Rockets the Boss Fires
    def enemyLeftMissileCannon(self):
	if self.health > 0 and self.lArmIsAlive == 1:

            # Change State to Charge Firing
	    self.bossFSM.request('leftshoot')

	    # Move the Missile Dummies in front of Cannon
	    self.missileDummy.setPos(55, -50, 70)

	    # Spawn a Muzzle Explosion
            self.kaboom = specialEffects.ChainExplosion(1, 4, 0.1, 50, self.missileDummy, 1)

	    # Adjust number of Missiles according to Game Difficulty
	    if ClassGlobals.difficulty == 1:
   	    	missileNum = 6
	    elif ClassGlobals.difficulty == 2:
	    	missileNum = 8
	    elif ClassGlobals.difficulty >= 3:
	    	missileNum = 10	

	    # Fire Projectile
	    for i in range(missileNum):

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

	    	# Fire Projectile
	    	self.enemyBullet = EnemyHomingMissile.EnemyHomingMissile('enemy', ClassGlobals.enemyBulletNum, startPos, startHpr, ClassGlobals.p1.craft)

	    	ClassGlobals.enemyBulletNum += 1
	    	taskMgr.add(self.enemyBullet.moveProjectile, 'moveProjectile')

	    	# Play Sound Effect
	    	SoundFX.eMissile.start()


    # Creates a Slavo of Rockets the Boss Fires
    def enemyRightMissileCannon(self):
	if self.health > 0 and self.rArmIsAlive == 1:

            # Change State to Charge Firing
	    self.bossFSM.request('rightshoot')

	    # Move the Missile Dummies in front of Cannon
	    self.missileDummy.setPos(-55, -50, 70)

	    # Spawn a Muzzle Explosion
            self.kaboom = specialEffects.ChainExplosion(1, 4, 0.1, 50, self.missileDummy, 1)

	    # Adjust number of Missiles according to Game Difficulty
	    if ClassGlobals.difficulty == 1:
   	    	missileNum = 6
	    elif ClassGlobals.difficulty == 2:
	    	missileNum = 8
	    elif ClassGlobals.difficulty >= 3:
	    	missileNum = 10	

	    # Fire Projectile
	    for i in range(missileNum):

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

	    	# Fire Projectile
	    	self.enemyBullet = EnemyHomingMissile.EnemyHomingMissile('enemy', ClassGlobals.enemyBulletNum, startPos, startHpr, ClassGlobals.p1.craft)

	    	ClassGlobals.enemyBulletNum += 1
	    	taskMgr.add(self.enemyBullet.moveProjectile, 'moveProjectile')

	    	# Play Sound Effect
	    	SoundFX.eMissile.start()


    # Creates a Plasma Blast the Boss Fires
    def enemyPlasmaCannon(self):
	if self.health > 0 and self.headIsAlive == 1:

            # Change State to Charge Firing
	    self.bossFSM.request('hit')

	    # Fire Projectile
	    for i in range(6):

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

	    	# Fire Projectile
	   	self.enemyBullet = EnemyProjectile.EnemyProjectile('plasma', ClassGlobals.enemyBulletNum, startPos, startHpr, self.accuracy, self.p1)

	    	ClassGlobals.enemyBulletNum += 1
	    	taskMgr.add(self.enemyBullet.moveProjectile, 'moveProjectile')

	    	# Play Sound Effect
	    	SoundFX.radBlast.start()


    ########################################
    # Methods: Causes Boss Parts to Take Damage
    ########################################
  
    # Boss Chest & Main Body Takes Damage
    def damage(self, collisionEntry):

	# Don't do this if Craft is Already Destroyed
	if self.craft.isEmpty() != 1:
	    # Get the Incoming Bullet's Name
	    incoming = collisionEntry.getFromNode()
	    incoming = incoming.getName()

	    # Get Coordinates of the Bullet for Special Explosion Effects
	    x = self.craft.getX(Level1.railDummy)
	    y = self.craft.getY(Level1.railDummy)
	    z = self.craft.getZ(Level1.railDummy)

	    # Calculate Damage depending on Type of Bullet
	    if incoming[0:10] == 'collbullet':
	    	# Reduce Health
 	    	self.health -= 5

	    elif incoming[0:12] == 'homingBullet':
	    	# Reduce Health
	    	self.health -= 50

	    	# Create Homing Laser Hit Sprite Explosion
	    	self.kaboom = specialEffects.SpriteShockwave('plasma', x, y, z, 0.5, 50, 0.5)

	    # If not dead, flash to show damage
	    if self.health > 0:
			# Play Sound Effect
			SoundFX.enemyHit1.start()
			
			# Flash Enemy
			self.flashChest()
	    else:
			self.explode()


    # Left Arm Takes Damage
    def leftArmDamage(self, collisionEntry):

	# Don't do this if Craft is Already Destroyed
	if self.craft.isEmpty() != 1 and self.lArmIsAlive == 1:
	    # Get the Incoming Bullet's Name
	    incoming = collisionEntry.getFromNode()
	    incoming = incoming.getName()

	    # Get Coordinates of the Bullet for Special Explosion Effects
	    x = self.craft.getX(Level1.railDummy)
	    y = self.craft.getY(Level1.railDummy)
	    z = self.craft.getZ(Level1.railDummy)

	    # Calculate Damage depending on Type of Bullet
	    if incoming[0:10] == 'collbullet':
	    	# Reduce Health
 	    	self.lArmHealth -= 5

	    elif incoming[0:12] == 'homingBullet':
	    	# Reduce Health
	    	self.lArmHealth -= 50

	    	# Create Homing Laser Hit Sprite Explosion
	    	self.kaboom = specialEffects.SpriteShockwave('plasma', x, y, z, 0.5, 50, 0.5)

	    # If not dead, flash to show damage
	    if self.lArmHealth > 0:
			# Play Sound Effect
			SoundFX.enemyHit1.start()
			
			# Flash Enemy
			self.flashLArm()
	    else:
			self.leftArmExplode()


    # Right Arm Takes Damage
    def rightArmDamage(self, collisionEntry):

	# Don't do this if Craft is Already Destroyed
	if self.craft.isEmpty() != 1 and self.rArmIsAlive == 1:
	    # Get the Incoming Bullet's Name
	    incoming = collisionEntry.getFromNode()
	    incoming = incoming.getName()

	    # Get Coordinates of the Bullet for Special Explosion Effects
	    x = self.craft.getX(Level1.railDummy)
	    y = self.craft.getY(Level1.railDummy)
	    z = self.craft.getZ(Level1.railDummy)

	    # Calculate Damage depending on Type of Bullet
	    if incoming[0:10] == 'collbullet':
	    	# Reduce Health
 	    	self.rArmHealth -= 5

	    elif incoming[0:12] == 'homingBullet':
	    	# Reduce Health
	    	self.rArmHealth -= 50

	    	# Create Homing Laser Hit Sprite Explosion
	    	self.kaboom = specialEffects.SpriteShockwave('plasma', x, y, z, 0.5, 50, 0.5)

	    # If not dead, flash to show damage
	    if self.rArmHealth > 0:
			# Play Sound Effect
			SoundFX.enemyHit1.start()
			
			# Flash Enemy
			self.flashRArm()
	    else:
			self.rightArmExplode()


    # Boss's Head Takes Damage
    def headDamage(self, collisionEntry):

	# Don't do this if Craft is Already Destroyed
	if self.craft.isEmpty() != 1 and self.headIsAlive == 1:
	    # Get the Incoming Bullet's Name
	    incoming = collisionEntry.getFromNode()
	    incoming = incoming.getName()

	    # Get Coordinates of the Bullet for Special Explosion Effects
	    x = self.craft.getX(Level1.railDummy)
	    y = self.craft.getY(Level1.railDummy)
	    z = self.craft.getZ(Level1.railDummy)

	    # Calculate Damage depending on Type of Bullet
	    if incoming[0:10] == 'collbullet':
	    	# Reduce Health
 	    	self.headHealth -= 5

	    elif incoming[0:12] == 'homingBullet':
	    	# Reduce Health
	    	self.headHealth -= 50

	    	# Create Homing Laser Hit Sprite Explosion
	    	self.kaboom = specialEffects.SpriteShockwave('plasma', x, y, z, 0.5, 50, 0.5)

	    # If not dead, flash to show damage
	    if self.headHealth > 0:
			# Play Sound Effect
			SoundFX.enemyHit1.start()
			
			# Flash Enemy
			self.flashHead()
	    else:
			self.headExplode()


    ########################################
    # Methods: Causes Boss to Flash when Hit
    ########################################

    # Makes Entire Boss Flash
    def flashChest(self):
	flasher = Sequence(Func(self.flashChestWhite), 
		Wait(0.001),
		Func(self.restoreColorChest))
	flasher.start()

    # Causes Boss to Flash
    def flashChestWhite(self):
	if self.chest.isEmpty() != 1:
	    self.chest.setTexture(LoadTextures.white, 1)
    def restoreColorChest(self):
	if self.chest.isEmpty() != 1:
	    self.chest.setTextureOff()


    # Makes Boss's Head Flash
    def flashHead(self):
	flasher = Sequence(Func(self.flashHeadWhite), 
		Wait(0.001),
		Func(self.restoreColorHead))
	flasher.start()

    # Causes Boss to Flash
    def flashHeadWhite(self):
	if self.head.isEmpty() != 1:
	    self.head.setTexture(LoadTextures.white, 1)
    def restoreColorHead(self):
	if self.head.isEmpty() != 1:
	    self.head.setTextureOff()


    # Makes Boss's Left Arm Flash
    def flashLArm(self):
	flasher = Sequence(Func(self.flashLArmWhite), 
		Wait(0.001),
		Func(self.restoreColorLArm))
	flasher.start()

    # Causes Boss to Flash
    def flashLArmWhite(self):
	if self.leftArm.isEmpty() != 1:
	    self.leftArm.setTexture(LoadTextures.white, 1)
    def restoreColorLArm(self):
	if self.leftArm.isEmpty() != 1:
	    self.leftArm.setTextureOff()


    # Makes Boss's Left Arm Flash
    def flashRArm(self):
	flasher = Sequence(Func(self.flashRArmWhite), 
		Wait(0.001),
		Func(self.restoreColorRArm))
	flasher.start()

    # Causes Boss to Flash
    def flashRArmWhite(self):
	if self.rightArm.isEmpty() != 1:
	    self.rightArm.setTexture(LoadTextures.white, 1)
    def restoreColorRArm(self):
	if self.rightArm.isEmpty() != 1:
	    self.rightArm.setTextureOff()


    ########################################
    # Methods: Causes Boss Appendages to Break Apart
    ########################################

    # Cause Enemy Head to Explode
    def headExplode(self):

        # Don't Do this if Already Destroyed
	if self.headIsAlive == 1:

	    # Mark as Destroyed
	    self.headIsAlive = 0

	    # Add Point Scoring
	    ClassGlobals.p1.score += self.headPoints

	    # Damage Main Body
	    self.health -= 2000
 
	    # Check for Death
	    if self.health <= 0 or (self.headIsAlive == 0 and self.lArmIsAlive == 0 and self.rArmIsAlive == 0):
		self.explode()

            # Play Sound Effect
            SoundFX.explosion1.start()

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

	    # Camera Flashes White
	    ClassGlobals.hud1.flash(0.1, 1, 1, 1, 0.8)

            # Spawn Explosion Effect
            self.kaboom = specialEffects.ChainExplosion(20, 20, 0.2, 40, self.head, 0)

	    # Stop Collisions
	    self.collider.enemyTraverser.removeCollider(self.collision4)
 	    self.collision4.removeNode()

	    # Hide Head
	    self.head.hide()

	    # Spawn a new Boss Head that falls off
 	    self.head2 = loader.loadModelCopy("Models/boss/boss-head")
	    self.head2.reparentTo(self.craft)
	    self.head2.setPos(self.head.getPos())
	    self.head2.setHpr(self.head.getHpr())

	    # Create Explosion Trails for Falling Parts
            self.headTrail = specialEffects.ChainExplosion(30, 0, 0.1, 90, self.head2, 1)
	
	    # Animate Head Falling Off
	    # Get Coordinates
	    headX = self.head2.getX() +random.uniform(-100, +100)
	    headY = self.head2.getY() -200
	    headZ = -self.head2.getZ(Level1.groundDummy)-600

	    # Cause the Appendages to break apart and fall
	    self.headFallInt = self.head2.posInterval(3, 
				Point3(headX, headY, headZ),
				blendType='easeOut')

	    # Cause the Appendages to Rotate while falling
	    randomNumX = random.uniform(0, 360)
	    randomNumY = random.uniform(0, 360)
	    randomNumZ = random.uniform(0, 360)
	    self.headRotateInt = self.head2.hprInterval(2, 
				Point3(randomNumX, randomNumY, randomNumZ))

	    # Play Intervals
	    self.headFallInt.start()
	    self.headRotateInt.loop()

            # Change State to Roaring Intro and Remove AI
	    taskMgr.remove('bossAI1')
	    self.AIcounter = 0
	    self.timeOffset = 0 
	    self.bossFSM.request('pain')


    # Cause Enemy Head to Explode
    def leftArmExplode(self):

        # Don't Do this if Already Destroyed
	if self.lArmIsAlive == 1:

	    # Mark as Destroyed
	    self.lArmIsAlive = 0

	    # Add Point Scoring
	    ClassGlobals.p1.score += self.lArmPoints

	    # Damage Main Body
	    self.health -= 1000

	    # Check for Death
	    if self.health <= 0 or (self.headIsAlive == 0 and self.lArmIsAlive == 0 and self.rArmIsAlive == 0):
		self.explode()

            # Play Sound Effect
            SoundFX.explosion1.start()

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

	    # Camera Flashes White
	    ClassGlobals.hud1.flash(0.1, 1, 1, 1, 0.8)

            # Spawn Explosion Effect
            self.kaboom = specialEffects.ChainExplosion(20, 20, 0.2, 40, self.leftArm, 0)

	    # Stop Collisions
	    self.collider.enemyTraverser.removeCollider(self.collision2)
 	    self.collision2.removeNode()

	    # Hide Head
	    self.leftArm.hide()

	    # Spawn a new Boss Head that falls off
 	    self.leftArm2 = loader.loadModelCopy("Models/boss/boss-rarmlower")
	    self.leftArm2.reparentTo(self.craft)
	    self.leftArm2.setPos(self.leftArm.getPos())
	    self.leftArm2.setHpr(self.leftArm.getHpr())

	    # Create Explosion Trails for Falling Parts
            self.lArmTrail = specialEffects.ChainExplosion(30, 0, 0.1, 90, self.leftArm2, 1)
	
	    # Animate Head Falling Off
	    # Get Coordinates
	    lArmX = self.leftArm2.getX() +random.uniform(-100, +100)
	    lArmY = self.leftArm2.getY() -200
	    lArmZ = -self.leftArm2.getZ(Level1.groundDummy)-600

	    # Cause the Appendages to break apart and fall
	    self.lArmFallInt = self.leftArm2.posInterval(3, 
				Point3(lArmX, lArmY, lArmZ),
				blendType='easeOut')

	    # Cause the Appendages to Rotate while falling
	    randomNumX = random.uniform(0, 360)
	    randomNumY = random.uniform(0, 360)
	    randomNumZ = random.uniform(0, 360)
	    self.lArmRotateInt = self.leftArm2.hprInterval(2, 
				Point3(randomNumX, randomNumY, randomNumZ))

	    # Play Intervals
	    self.lArmFallInt.start()
	    self.lArmRotateInt.loop()

            # Change State to Roaring Intro and Remove AI
	    taskMgr.remove('bossAI1')
	    self.AIcounter = 0
	    self.timeOffset = 0 
	    self.bossFSM.request('pain')


   # Cause Enemy Head to Explode
    def rightArmExplode(self):

        # Don't Do this if Already Destroyed
	if self.rArmIsAlive == 1:

	    # Mark as Destroyed
	    self.rArmIsAlive = 0

	    # Add Point Scoring
	    ClassGlobals.p1.score += self.rArmPoints

	    # Damage Main Body
	    self.health -= 1000

	    # Check for Death
	    if self.health <= 0 or (self.headIsAlive == 0 and self.lArmIsAlive == 0 and self.rArmIsAlive == 0):
		self.explode()

            # Play Sound Effect
            SoundFX.explosion1.start()

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

	    # Camera Flashes White
	    ClassGlobals.hud1.flash(0.1, 1, 1, 1, 0.8)

            # Spawn Explosion Effect
            self.kaboom = specialEffects.ChainExplosion(20, 20, 0.2, 40, self.rightArm, 0)

	    # Stop Collisions
	    self.collider.enemyTraverser.removeCollider(self.collision3)
 	    self.collision3.removeNode()

	    # Hide Head
	    self.rightArm.hide()

	    # Spawn a new Boss Head that falls off
 	    self.rightArm2 = loader.loadModelCopy("Models/boss/boss-rarmlower")
	    self.rightArm2.reparentTo(self.craft)
	    self.rightArm2.setPos(self.rightArm.getPos())
	    self.rightArm2.setHpr(self.rightArm.getHpr())

	    # Create Explosion Trails for Falling Parts
            self.rArmTrail = specialEffects.ChainExplosion(30, 0, 0.1, 90, self.rightArm2, 1)
	
	    # Animate Head Falling Off
	    # Get Coordinates
	    rArmX = self.rightArm2.getX() +random.uniform(-100, +100)
	    rArmY = self.rightArm2.getY() -200
	    rArmZ = -self.rightArm2.getZ(Level1.groundDummy)-600

	    # Cause the Appendages to break apart and fall
	    self.rArmFallInt = self.rightArm2.posInterval(3, 
				Point3(rArmX, rArmY, rArmZ),
				blendType='easeOut')

	    # Cause the Appendages to Rotate while falling
	    randomNumX = random.uniform(0, 360)
	    randomNumY = random.uniform(0, 360)
	    randomNumZ = random.uniform(0, 360)
	    self.rArmRotateInt = self.rightArm2.hprInterval(2, 
				Point3(randomNumX, randomNumY, randomNumZ))

	    # Play Intervals
	    self.rArmFallInt.start()
	    self.rArmRotateInt.loop()

            # Change State to Roaring Intro and Remove AIx
	    taskMgr.remove('bossAI1')
	    self.AIcounter = 0
	    self.timeOffset = 0 
	    self.bossFSM.request('pain')


    ########################################
    # Methods: Boss Defeated, Remove Its Parts
    ########################################

    # Enemy Explodes and dies
    def explode(self):

	# Add Point Scoring
	ClassGlobals.p1.score += self.clearPoints

        # Don't Do this if Already Destroyed
	if self.isAlive == 1:

	    # Mark Entire Boss As Destroyed
	    self.isAlive = 0

	    # Make Player Invincible incase he dies after winning
	    ClassGlobals.p1.isInvincible = 1

	    # Explode all Unexploded Parts
	    self.headExplode()
	    self.leftArmExplode()
	    self.rightArmExplode()

            # Change State to Hit State
	    self.bossFSM.request('death')

            # Play Sound Effect
            SoundFX.explosion1.start()

	    # Camera Flashes White
	    ClassGlobals.hud1.flash(0.1, 1, 1, 1, 0.8)

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

            # Spawn Explosion Effect
            self.kaboom = specialEffects.ChainExplosion(self.explosions, 60, 0.05, 50, self.craftDummy, 0)

	    # Stop Collisions
	    self.collider.enemyTraverser.removeCollider(self.collision)
 	    self.collision.removeNode()


    # The Final Big Bang Explosion that Removes the Boss
    def bigBang(self):

            # Play Sound Effects
            SoundFX.explosion1.start()
            SoundFX.explosion2.start()
            SoundFX.explosion3.start()

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

	    # Camera Flashes White
	    ClassGlobals.hud1.flash(0.3, 1, 1, 1, 0.8)

	    # Game End Sequence where it Fades to Black
	    gameEnd = Sequence(Wait(1.0),
			 Func(ClassGlobals.hud1.showGameOver))
	    gameEnd.start()

	    # Remove parts of the Mesh
	    self.destroy()


    # Destroy the Enemy and Remove its parts
    def destroy(self):

	# Mark Everything as Destroyed
        self.chestIsAlive = 0	
        self.headIsAlive = 0	
        self.lArmIsAlive = 0	
        self.rArmIsAlive = 0	

	# Remove Boss Parts
	taskMgr.remove('flashEnemy')
	self.craft.removeNode()
	self.head.removeNode()
	self.leftArm.removeNode()
	self.rightArm.removeNode()
	self.craftDummy.removeNode()
	self.boss.removeNode()

	# Stop Ambient Sound
	SoundFX.ambientBoss.finish()
