###########################################################
# Homing Missile - Railshooter Module
# Creates a projectile that slowly homing in on targets
###########################################################

# DESCRIPTION:
# This Class creates a laser that slowly re-adjuststs its trajectory to 
# aim at a moving target.  The projectile is created in a starting position
# and travels in its starting direction for a short time before slowly 
# re-aiming for its target.  The projectile will inflict damage on anything it
# passes through on the way to its target and will be destroyed on contact
# with it's target.

# This file defines the statistics of the bullet, loads the models and
# effects, moves the homing bullet, and resolves collisions.  Also, 
# the Homing Missile, like all projectiles, will Auto-Destruct over time.


from direct.showbase.ShowBaseGlobal import *
from direct.showbase import DirectObject
from direct.task import Task
from pandac.PandaModules import *
import math

# Import Other Railshooter Modules
import ClassGlobals
import Level1
import LoadParticles


########################################
# Homing Missile Class
########################################
class HomingMissile(DirectObject.DirectObject):
    def __init__(self, bulletType, bulletNum, playerPos, playerHpr, target):   

	self.bulletType = bulletType
	self.collider = ClassGlobals.collider
	self.bulletNum = str(bulletNum)
	self.target = target

	########################################
	# Initialize Variables
	########################################
	self.gameSpeed = ClassGlobals.gameSpeed

	self.hasCollided = 0

	self.speed = 50.0
	self.acceleration = 5.0
	self.lifespan = 150
	self.turningSpeed = 1
	self.turningAcceleration = 1

	self.startPos = playerPos
	self.startHpr = playerHpr
	
	self.tick = 0
	self.currentTick = 0

	self.bulletDamage = 3.0
	self.prevT = 0
	self.prevX = 0
	self.prevY = 0
	self.prevZ = 0


	########################################
	# Load the Homing Missile Model
	########################################

	# Dummy object for the bullet to keep track of its starting 
	# Pos and Orientation
	self.anchor = render.attachNewNode('anchor' + self.bulletNum)
	self.anchor.reparentTo(Level1.railDummy)

     	# Add a dummy object for the projectile
	# This object is the one that gets moved around
	self.bullet = self.anchor.attachNewNode('bullet' + self.bulletNum)

        # Load the Projectile Model
 	self.shell = loader.loadModelCopy("Models/effects/laser")
	self.shell.setScale(0.4, 0.4, 0.4)
        self.shell.setHpr(180, 0, 0)
     	self.shell.reparentTo(self.bullet)
	self.shell.setTransparency(1)

	# Set the Laser Colors
	self.innerBeam = self.shell.find('**/innerBeam')
	self.innerBeam.setColor(1, 1, 1, 1)
	self.outerBeam = self.shell.find('**/outerBeam')
	self.outerBeam.setColor(0.1, 0.4, 0.8, 0.6)
	self.outerBeam2 = self.shell.find('**/outerBeam2')
	self.outerBeam2.setColor(0.1, 0.2, 0.4, 0.3)

        # Create Player's Collision Sphere
        self.bulletSphere = CollisionSphere(0, 0, 0, 2)
        self.homingBullet = CollisionNode("homingBullet" + self.bulletNum)
        self.homingBullet.addSolid(self.bulletSphere)
        self.collision = self.bullet.attachNewNode(self.homingBullet)
        # self.collision.show()

        # Set Bitmask for the Bullet
	# Targets need Bitmask 1 to Register a Hit
	self.homingBullet.setFromCollideMask(BitMask32.bit(1))

	# Victims with Bitmask 0 Collide with Bullet
	self.homingBullet.setIntoCollideMask(BitMask32.bit(0))

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

	# Set the Starting Anchor Point
	self.anchor.setPos(self.startPos)
	self.anchor.setHpr(self.startHpr)


	########################################
	# Make Bullet Mesh "Unlit"
	########################################

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


	########################################
	# Load Particle Effect
	########################################

	# Add Particle Trail to Bullet
	# LoadParticles.homingBlastTrail(self.bullet)


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

	self.accept('enterhomingBullet' + self.bulletNum, self.checkCollision)


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

    # Create a task to Move the Projectile
    def moveProjectile(self, task):

	# If Projectile has not Collided yet
	if self.hasCollided == 0:

	    # Calculate the Current Tick (Game Time)
	    # This makes the game controls independent of Framerate
	    self.currentTick = math.floor(task.time / self.gameSpeed)
	
	    # If it is a Tick, then Update the World
	    if self.currentTick > self.tick:
	        self.tick = self.currentTick
	    
            	# Get Change in Time
 	    	dt = task.time - self.prevT
            	self.prevT = task.time

		# Alter Projectile Facing To Aim More At Target
    	    	self.faceTarget(self.target)
		
  	        # Get Projectile's New Speed
		self.speed += self.acceleration
		
		# Get New Turning Speed
		self.turningSpeed += self.turningAcceleration

	        # Set New Projectile Location & Orientation
	        self.bullet.setY(self.bullet, dt*self.speed)

	   # If the Projectile lasts over 10 seconds, destroy it
	    if self.tick > self.lifespan or ClassGlobals.gameIsOver == 1:
		# End the Task
		self.destroy()
		return Task.done
	    else:
		# Make the task continue
		return Task.cont

	# Else, Projectile is Dead, so End Task
	else:
	    return Task.done


    # Immediately Face Target
    def absoluteFace(self, target):
	self.bullet.lookAt(self.target)


    # Alters the facing of the missile to slowly aim at the target
    def faceTarget(self, target):

	# Get Bullet's Current Facing
	bulletH = self.bullet.getH()
	bulletP = self.bullet.getP()
	bulletR = self.bullet.getR()

	# Get Exact Correct Facing towards the Target
	self.bullet.lookAt(self.target)
	goodH = self.bullet.getH()
	goodP = self.bullet.getP()
	goodR = self.bullet.getR()

	# Use the Current and Optimal Facing Data to Slowly
	# Adjust the Bullet's Facing Over Time
	if goodH > 0:
	    if bulletH + self.turningSpeed >= goodH:
	 	bulletH = goodH
	    else:
	     	bulletH = bulletH + self.turningSpeed
	elif goodH < 0:
	    if bulletH - self.turningSpeed <= goodH:
		bulletH = goodH
	    else:
	   	bulletH = bulletH - self.turningSpeed

	if goodP > 0:
    	    if bulletP + self.turningSpeed >= goodP:
	 	bulletP = goodP
	    else:
	    	bulletP = bulletP + self.turningSpeed
	elif goodP < 0:
    	    if bulletP - self.turningSpeed <= goodP:
	 	bulletP = goodP
	    else:
	     	bulletP = bulletP - self.turningSpeed

	if goodR > 0:
    	    if bulletR + self.turningSpeed >= goodR:
	 	bulletR = goodR
	    else:
	     	bulletR = bulletR + self.turningSpeed
	elif goodR < 0:
    	    if bulletR - self.turningSpeed <= goodR:
	 	bulletR = goodR
	    else:
	     	bulletR = bulletR - self.turningSpeed

	# Set New Bullet Facing
	self.bullet.setHpr(bulletH, bulletP, bulletR)


    # Check whether to Accept the Collision
    def checkCollision(self, collisionEntry):

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

	# If Victim is the Correct Target, destroy Missile
	if (victim == self.target.getName()):
	    self.destroy()


    # Destroy the Projectile and Remove its Parts
    def destroy(self):

        # The Bullet has Collided (Mark for Destruction)
	self.hasCollided = 1

	self.collider.laserTraverser.removeCollider(self.collision)
 	self.collision.removeNode()
	self.shell.removeNode()
	self.bullet.removeNode()
	self.anchor.removeNode()

