###########################################################
# EnemyCraft - Railshooter Module
# Contains the class to create enemy vehicle
###########################################################

# DESCRIPTION:
# This Class creates Enemies of different types, depending on the
# enemyClass string passed into the Constructor.  This class defines
# the enemies health, statistics, causes the enemies to explode when
# destroyed, and fires projectiles from the enemies to the player.  The
# enemies also Auto-Destruct after a certain time period (usually when
# they are long off-screen)

# Movement of the enemies occurs in the Enemy Formations file, since
# the enemies are created in a formation and all moved together with 
# one Interval.


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

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


########################################
# Enemy Craft Class
########################################

class enemyCraft(DirectObject.DirectObject):
    def __init__(self, enemyNum, enemyClass, numShots, accuracy, shootWait, shootDelay, lifespan):    

	self.enemyNum = str(enemyNum)
	self.p1 = ClassGlobals.p1
	self.isAlive = 1

	self.gameSpeed = ClassGlobals.gameSpeed
	self.collider = ClassGlobals.collider
	self.enemyClass = enemyClass
	self.numShots = numShots
	self.accuracy = accuracy 	# Lower is better aim
	self.shootDelay = shootDelay
	self.shootWait = shootWait
	self.lifespan = lifespan

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

	self.tick = 0
	self.currentTick = 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.enemy = render.attachNewNode('enemy' + self.enemyNum)
	self.enemy.reparentTo(Level1.railDummy)

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

        # Load the Enemy Craft or Vehicle Model
	# This object gets the Roll

	# Load Light Class Enemy
	if self.enemyClass == 'light':
            self.craft = loader.loadModelCopy("Models/enemies/BugHopper")
	    self.craft.setScale(2.5, 2.5, 2.5)

	    self.health = 3 + (ClassGlobals.difficulty * 2)
	    self.points = self.health*10
	    self.explosions = 1

      	    # Create Enemy's Collision Sphere
            self.enemySphere = CollisionSphere(0, 0, 0.5, 2)
            self.collenemy = CollisionNode("collenemy" + self.enemyNum)
            self.collenemy.addSolid(self.enemySphere)
            self.collision = self.craft.attachNewNode(self.collenemy)
            # self.collision.show()

	# Load Standard Class Enemy
	if self.enemyClass == 'standard':
            self.craft = loader.loadModelCopy("Models/enemies/DeuceHopper")
	    self.craft.setScale(2.5, 2.5, 2.5)

	    self.health = 10 + (ClassGlobals.difficulty * 10)
	    self.points = self.health*10
	    self.explosions = 2

      	    # Create Enemy's Collision Sphere
            self.enemySphere = CollisionSphere(0, 0, 0.5, 2)
            self.collenemy = CollisionNode("collenemy" + self.enemyNum)
            self.collenemy.addSolid(self.enemySphere)
            self.collision = self.craft.attachNewNode(self.collenemy)
            # self.collision.show()

	# Load Heavy Class Enemy
	if self.enemyClass == 'heavy':
            self.craft = loader.loadModelCopy("Models/enemies/NewHopper")
	    self.craft.setScale(3.0, 3.0, 3.0)

	    self.health = 30 + (ClassGlobals.difficulty * 10)
	    self.points = self.health*10
	    self.explosions = 3

      	    # Create Enemy's Collision Sphere
            self.enemySphere = CollisionSphere(0, 0, 1.0, 2.5)
            self.collenemy = CollisionNode("collenemy" + self.enemyNum)
            self.collenemy.addSolid(self.enemySphere)
            self.collision = self.craft.attachNewNode(self.collenemy)
            # self.collision.show()

	# Load Tank Class Enemy
	if self.enemyClass == 'superHeavy':
            self.craft = loader.loadModelCopy("Models/enemies/PowerHopper")
	    self.craft.setScale(4.0, 4.0, 4.0)

	    self.health = 290 + (ClassGlobals.difficulty * 5)
	    self.points = self.health*10
	    self.explosions = 6

      	    # Create Enemy's Collision Sphere
            self.enemySphere = CollisionSphere(0, 0, 1.0, 2.5)
            self.collenemy = CollisionNode("collenemy" + self.enemyNum)
            self.collenemy.addSolid(self.enemySphere)
            self.collision = self.craft.attachNewNode(self.collenemy)
            # self.collision.show()


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

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

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

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

	# If the Enemy Can Shoot, add to Task
	if self.numShots != 0:
	    taskMgr.add(self.enemyAim, 'enemyAim')

	# Allow autodestruct after time is up
	taskMgr.add(self.autodestruct, 'autodestruct')


	########################################
	# Setup Gun Dummies for Missile Slavo & Plasma Blast
	########################################
	# These Dummy objects hold the positions that projectiles are
	# spawned at

	self.gunDummy1 = self.craftDummy.attachNewNode('gunDummy1')
	self.gunDummy1.setPos(-7, 0, 7)

	self.gunDummy2 = self.craftDummy.attachNewNode('gunDummy2')
	self.gunDummy2.setPos(7, 0, 7)

	self.gunDummy3 = self.craftDummy.attachNewNode('gunDummy3')
	self.gunDummy3.setPos(0, 0, 14)

	self.gunDummy4 = self.craftDummy.attachNewNode('gunDummy4')
	self.gunDummy4.setPos(0, 0, 0)

	self.gunDummy5 = self.craftDummy.attachNewNode('gunDummy5')
	self.gunDummy5.setPos(0, 0, 7)

	# Enter the Laser Dummies into a List
	self.gunDummies = {}
	self.gunDummies[0] = self.gunDummy1
	self.gunDummies[1] = self.gunDummy2
	self.gunDummies[2] = self.gunDummy3
	self.gunDummies[3] = self.gunDummy4
	self.gunDummies[4] = self.gunDummy5


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

	self.accept('entercollenemy'+self.enemyNum, self.damage)


    ########################################
    # Define Tasks
    ########################################    
    # Auto-destruct after no longer needed
    def autodestruct(self, task):
	if task.time > self.lifespan or ClassGlobals.gameIsOver == 1:
	    # Enemy Auto-destroyed
	    self.destroy()
	    return Task.done
	return Task.cont

    # Define a Task to Fire Enemy Bullets at Player
    def enemyAim(self, task):
	# If cooldown time done, shoot!
	if task.time >= self.currentShootDelay :
	    self.shotsFired += 1
	    self.enemyShoot()

 	    # If still shots left to fire, restore Cooldown Time
	    if self.numShots > self.shotsFired:
		self.currentShootDelay += self.shootDelay + random.uniform(0, 0.4)

	# If all done Shooting, kill Task
	if self.shotsFired >= self.numShots or self.health <= 0:
	    return Task.done
	else:
	    return Task.cont


    ########################################
    # Methods: Causes Enemy to Take Damage
    ########################################
  
    # Enemy Takes Damage
    def damage(self, collisionEntry):

	# Enemy Got HIT!

	# 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.ChainSpriteShockwave('plasma', 1, 0.2, 0.3, 15, self.craft, 0.3, 1)

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

			# Flash Enemy
			self.flashEnemy()
	    else:
			self.explode()


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

    # Makes Enemy Flash
    def flashEnemy(self):
	flasher = Sequence(Func(self.flash), 
		Wait(0.001),
		Func(self.restoreColor))
	flasher.start()

    # Causes Enemy to Flash Red
    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: Make Enemy Explode & Remove Its Part
    ########################################

    # Enemy Explodes and dies
    def explode(self):

	# Restore Normal Color
	self.restoreColor()

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

            # Play Sound Effect
            SoundFX.explosion1.start()

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

            # Spawn Explosion Effect
            self.kaboom = specialEffects.ChainExplosion(self.explosions, 4, 0.1, 12, self.enemy, 0)

	    # If Enemy was a Super Heavy Classed Robot, then
	    # Shake the Camera and Flash the Screen White 
	    if self.enemyClass == 'superHeavy':

	        # Spawn Powerup!  Whohoo!
	        self.powerUp = Powerup.Powerup('laser', ClassGlobals.powerupNum, self.enemy)
	        ClassGlobals.powerupNum += 1

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

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

		# Find the Model Parts
	    	self.neck1 = self.craft.find('**/neckLower')
	    	self.neck2 = self.craft.find('**/neckUpper')	

		neck1X = self.neck1.getX() +random.uniform(-100, +100)
		neck1Y = self.neck1.getY() +random.uniform(-100, +100)
		neck1Z = -self.neck1.getZ(Level1.groundDummy)

		neck2X = self.neck2.getX() +random.uniform(-100, +100)
		neck2Y = self.neck2.getY() +random.uniform(-100, +100)
		neck2Z = -self.neck2.getZ(Level1.groundDummy)

		# Cause the Appendages to break apart and fall
		randomNum1 = random.uniform(2.0, 4.0)
	    	self.neck1FallInt = self.neck1.posInterval(randomNum1, 
				Point3(neck1X, neck1Y, neck1Z),
				blendType='easeOut')

		randomNum2 = random.uniform(2.0, 4.0)
	    	self.neck2FallInt = self.neck2.posInterval(randomNum2, 
				Point3(neck2X, neck2Y, neck2Z),
				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.neck1RotateInt = self.neck1.hprInterval(2, 
				Point3(randomNumX, randomNumY, randomNumZ))

		randomNumX = random.uniform(0, 360)
		randomNumY = random.uniform(0, 360)
		randomNumZ = random.uniform(0, 360)
	    	self.neck2RotateInt = self.neck2.hprInterval(2, 
				Point3(randomNumX, randomNumY, randomNumZ))	

		# Loop Rotation Intervals
	    	self.neck1RotateInt.loop()
		self.neck2RotateInt.loop()


	    # Make the Enemy Break Apart
	    if self.enemyClass == 'light' or self.enemyClass == 'standard' or self.enemyClass == 'heavy' or self.enemyClass == 'superHeavy':

		# Find the Model Parts
	    	self.head = self.craft.find('**/head')
	    	self.leftArm = self.craft.find('**/leftArm')
	    	self.rightArm = self.craft.find('**/rightArm')

  		# Hide Body, but show Appendages 
	    	self.body = self.craft.find('**/body')
		self.body.hide()

		# Get Coordinates
		headX = self.head.getX() +random.uniform(-100, +100)
		headY = self.head.getY() +random.uniform(-100, +100)
		headZ = -self.head.getZ(Level1.groundDummy)

		lArmX = self.leftArm.getX() +random.uniform(-100, +100)
		lArmY = self.leftArm.getY() +random.uniform(-100, +100)
		lArmZ = -self.leftArm.getZ(Level1.groundDummy)

		rArmX = self.rightArm.getX() +random.uniform(-100, +100)
		rArmY = self.rightArm.getY() +random.uniform(-100, +100)
		rArmZ = -self.rightArm.getZ(Level1.groundDummy)

		# Cause the Appendages to break apart and fall
		randomNum1 = random.uniform(2.0, 4.0)
	    	self.headFallInt = self.head.posInterval(randomNum1, 
				Point3(headX, headY, headZ),
				blendType='easeOut')

		randomNum2 = random.uniform(2.0, 4.0)
	    	self.lArmFallInt = self.leftArm.posInterval(randomNum2, 
				Point3(lArmX, lArmY, lArmZ),
				blendType='easeOut')

		randomNum3 = random.uniform(2.0, 4.0)
	    	self.rArmFallInt = self.rightArm.posInterval(randomNum3, 
				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.headRotateInt = self.head.hprInterval(2, 
				Point3(randomNumX, randomNumY, randomNumZ))
	
		randomNumX = random.uniform(0, 360)
		randomNumY = random.uniform(0, 360)
		randomNumZ = random.uniform(0, 360)
	    	self.lArmRotateInt = self.leftArm.hprInterval(2, 
				Point3(randomNumX, randomNumY, randomNumZ))		
		randomNumX = random.uniform(0, 360)
		randomNumY = random.uniform(0, 360)
		randomNumZ = random.uniform(0, 360)
	    	self.rArmRotateInt = self.rightArm.hprInterval(2, 
				Point3(randomNumX, randomNumY, randomNumZ))

		# Create Sequences & Parallels
		# Model Autodestructs after all pieces have hit the floor
		if self.enemyClass == 'superHeavy':
		    self.breakApart = Parallel(self.headFallInt, self.lArmFallInt, self.rArmFallInt, self.neck1FallInt, self.neck2FallInt, name='breakApart' + str(ClassGlobals.numSequences))
	  	else:
		    self.breakApart = Parallel(self.headFallInt, self.lArmFallInt, self.rArmFallInt, name='breakApart' + str(ClassGlobals.numSequences))

		self.runBreak = Sequence(self.breakApart, Func(self.destroy), name='runBreak' + str(ClassGlobals.numSequences))
		ClassGlobals.numSequences += 1

		# Play Intervals
		self.runBreak.start()
	    	self.headRotateInt.loop()
		self.lArmRotateInt.loop()
		self.rArmRotateInt.loop()

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


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

        self.isAlive = 0	
	self.health = 0    
	self.craft.removeNode()
	self.craftDummy.removeNode()
	self.enemy.removeNode()


    ########################################
    # Method: Make Enemy Shoot Projectiles at Player
    ########################################

    # Creates Projectiles the Enemies Fire
    def enemyShoot(self):
	if self.health > 0:
	    # ENEMY FIRES!

	    # Fire Rotating Fireball Projectile
	    if self.enemyClass == 'light' or self.enemyClass == 'standard':
		
	    	# Get Enemy's Coordinates
	    	enemyPos = self.gunDummy5.getPos(Level1.railDummy)
	    	enemyHpr = self.craftDummy.getHpr()

		# Make Fireball Projectile
	    	self.enemyBullet = EnemyProjectile.EnemyProjectile('fire', ClassGlobals.enemyBulletNum, enemyPos, enemyHpr, self.accuracy, self.p1)
	    	ClassGlobals.enemyBulletNum += 1
	    	taskMgr.add(self.enemyBullet.moveProjectile, 'moveProjectile')

		# Add to List of Enemy Projectiles and Update Count
		ClassGlobals.enemyProjectileList[ClassGlobals.enemyBulletNum] = self.enemyBullet.enemycollbullet
        	ClassGlobals.enemyBulletNum += 1

	        # Play Sound Effect
	        SoundFX.regularBlast.start()


	    # Fire Radiation Plasma Blast
	    elif self.enemyClass == 'heavy':

	    	# Get Enemy's Coordinates
	    	enemyPos = self.gunDummy5.getPos(Level1.railDummy)
	    	enemyHpr = self.craftDummy.getHpr()

		# Make Plasma Projectile
	    	self.enemyBullet = EnemyProjectile.EnemyProjectile('plasma', ClassGlobals.enemyBulletNum, enemyPos, enemyHpr, self.accuracy, self.p1)
	    	ClassGlobals.enemyBulletNum += 1
	    	taskMgr.add(self.enemyBullet.moveProjectile, 'moveProjectile')

		# Add to List of Enemy Projectiles and Update Count
		ClassGlobals.enemyProjectileList[ClassGlobals.enemyBulletNum] = self.enemyBullet.enemycollbullet
        	ClassGlobals.enemyBulletNum += 1

	        # Play Sound Effect
	        SoundFX.radBlast.start()


            # Fire Missile Salvo!
	    elif self.enemyClass == 'superHeavy':

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

		# Fire 5 Missiles!
	        for i in range(missileNum):

		    # Get Missile Start Coordinates
		    startPos = self.gunDummies[i].getPos(Level1.railDummy)
		    startHpr = self.gunDummies[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()

