/*************************************************************************
 * _enemylib Entity Library Script
 * 
 * Author: GD
 * Date:   19/02/02
 *
 * Desc:   An Enemy Library, contains functions used by enemies
 *
 * Usage:  Call these functions from other entity scripts using the
 *         CallFunction() function.
 *
 *         Uses _itemlib library.
 *         
 ************************************************************************/
#include <entity>
#include <general>
#include <animation>
#include <core>
#include <float>

//---------------------------------------------
// Name: CreateDeathAnim()
// Desc: Called to create the death animation
//---------------------------------------------
public CreateDeathAnim( buffer[] )
{
   /* Setup the Death animation - since this animation is the same for
      every enemy we can put it in a library function */
   AddAnimframe(buffer, 0, 0, "_edead1");
   AddAnimframe(buffer, 0, 0, "_edead2");
   AddAnimframe(buffer, 0, 0, "_edead1");
   AddAnimframe(buffer, 0, 0, "_edead2");
   AddAnimframe(buffer, 0, 0, "_edead3");
   AddAnimframe(buffer, 0, 0, "_edead4");
   AddAnimframe(buffer, 0, 0, "_edead5");

   // We only want the death animation to play once through, not to loop over and over
   SetAnimLoop(buffer, false);
}

//---------------------------------------------
// Name: CreateFallAnim()
// Desc: Called to create the falling animation
//---------------------------------------------
public CreateFallAnim( buffer[] )
{
   AddAnimframe(buffer, 0, 0, "_efall1");
   AddAnimframe(buffer, 0, 0, "_efall2");
   AddAnimframe(buffer, 0, 0, "_efall3");
   AddAnimframe(buffer, 0, 0, "_efall4");
   AddAnimframe(buffer, 0, 0, "_efall5");
   AddAnimframe(buffer, 0, 0, "_efall6");

   // We only want the falling animation to play once through, not to loop over and over
   SetAnimLoop(buffer, false);
}


//----------------------------------------
// Name: CheckForPlayer()
//----------------------------------------
public CheckForPlayer()
{
   if (GetState("this") == dying)
      return;

   // Check for a collision with the player
   if (Collide("this", "player1"))
   {         
      // Call the BeginHit() function in the player script
      CallFunction("player1", false, "BeginHit", "nnn", \
                    GetX("this"), GetY("this"), GetDamage("this"));

      return true;
   }

   return false;
}

//-------------------------------------------------------------------------
// Name: KillEnemy()
// Makes the enemy start their death animation
//-------------------------------------------------------------------------
public KillEnemy()
{
	SetState("this", dying);
   	PlaySound("_enemykilled.wav", 240);
}

//--------------------------------------------------------------
// Name: HandleDying()
//
// anim[]  - The identifier of the death animation to use.
// image[] - The sprite code of the enemies last image, used for
//           positioning the death animation.
//--------------------------------------------------------------
public HandleDying( anim[], image[])
{
   new item[20];
   new x = GetX("this");
   new y = GetY("this");
   new animWidth   = GetAnimWidth(anim);
   new animHeight  = GetAnimHeight(anim);
   new imageWidth  = GetWidth(image);
   new imageHeight = GetHeight(image);

   // Draw the death animation in the correct place - that is in the center
   // of the enemies last image.
   DrawAnim(anim, x + (imageWidth / 2)  - (animWidth  / 2), \
                  y + (imageHeight / 2) - (animHeight / 2), \
                  y + imageHeight);

   // Check if the death animation is finished
   if (FinishedAnim(anim))
   {
      SetAnimCount(anim, 0);
      AfterDeath();
  
      // Get the item string if there is one, we need to pass it to GetRandomItem()
      GetItem("this", item);

      // Make a random Item Appear beneath the enemy
      CallFunction("_itemlib", false, "GetRandomItem", "nns", \
                x + imageWidth / 2, y + imageHeight / 2, item);
   }
}


//----------------------------------------------------------
// Name: AfterDeath()
//----------------------------------------------------------
AfterDeath()
{
    SetActiveFlag("this", false);
    SetDeadFlag("this", true);
    SetState("this", standing);
    SetSpeedMod("this", 0);
    SetHealth("this", 0);
    Respawn("this", 30);
    
    // Reset the enemies position to where they started
    SetX("this", GetInitialX("this"));
    SetY("this", GetInitialY("this"));
}


//----------------------------------------------------------
// Name: CheckForHoles()
//
// anim[] - The identifier of the falling animation to use.
//
//----------------------------------------------------------
public CheckForHoles( anim[] )
{
	new State = GetState("this");
	
	// Do not proceed if we are in certain states
	if (State == hit || State == falling || State == dying)
      	return false;

   	new x = GetX("this");
   	new y = GetY("this");
   	new HoleType;
 
   	// Check if the enemy is over a hole
   	HoleType = CheckForHole("this");
 
   	if ( HoleType != -1)
   	{
   	   	// Set the enemies state to falling
    	SetState("this", falling);
    	SetAnimCount(anim, 0);
      	PlaySound("_dropping.wav");
      	return true;
   	}

   	return false;
}


//-------------------------------------------------------------
// Name: Fall()
// Desc: For when the enemy falls down a hole
//
// anim[]  - The identifier of the falling animation to use.
// width   - The Width of a normal enemy image.
// height  - The Height of a normal enemy image.
//
//-------------------------------------------------------------
public Fall( anim[], width, height )
{
   new x = GetX("this");
   new y = GetY("this");
   new AnimWidth = GetAnimWidth( anim );
   new AnimHeight= GetAnimHeight( anim );
   new HoleType;
  
   // Display Falling Animation
   if (isVisible("this"))
   {    
      DrawAnim( anim, (x + (width / 2)) - (AnimWidth / 2), \
                      (y + (height / 2)) - (AnimHeight / 2), y );
   }
   else
      IncrementAnim( anim );

   if (FinishedAnim( anim ))
   {
      // Check what type of hole we are in
      HoleType = CheckForHole("this");

      // Check for a normal hole
      if (HoleType == 1)
      {
         // Kill The enemy
         SetHealth("this", 0);
         AfterDeath();
      }
      else  // The enemy falls to another floor
      {
         // Work out the new world coordinates for the enemy
         new screenx = x / 320;
         new screeny = y / 240;
         SetX("this", (GetLowerLevelX() * 320) + (x - (screenx * 320)));
         SetY("this", (GetLowerLevelY() * 240) + (y - (screeny * 240)));

         // Check the lower level is not in fact the screen we are on, this
         // Would cause an infinite loop of falling.
         if (screenx == GetLowerLevelX() && screeny == GetLowerLevelY())
         {
            // Kill The enemy
            SetHealth("this", 0);
            AfterDeath();
         }

         SetState("this", standing);
         ClearCollisionRect("this", 0);
      } 
   }
}

//-----------------------------------------------------------------------
// Name: CheckForChase()
// Checks if the player is near this enemy, it also checks if the player
// if in the enemies field of vision.
//-----------------------------------------------------------------------
public CheckForChase( dir, enemyx, enemyy, detect )
{
	new px = GetX("player1");
	new py = GetY("player1");
		
	// Check the player is Near enough
	if ( px >= enemyx - detect && px <= enemyx + detect)
	{
		if ( py >= enemyy - detect && py <= enemyy + detect)
		{
			if (px < enemyx)
			{
				if (py > enemyy)
				{
					if ( (enemyx -	px ) > (py - enemyy) )
					{
						if ( dir ==	west)
							return 1;
					}
					else
					{
						if ( dir ==	south)
							return 1;
					}
				}
				
				if (py < enemyy)
				{
					if ( (enemyx -	px ) > ( enemyy - py )	)
					{
						if (dir ==	west)
							return 1;
					}
					else
					{
						if (dir ==	north)
							return 1;
					}
				}
			}
			if (px >= enemyx)
			{
				if (py >= enemyy)
				{
					if ( ( px - enemyx) >= (py - enemyy) )
					{
						
						if (dir == east)
							return 1;
					}
					else
					{
						if (dir == south)
							return 1;
					}
				}
				
				if (py <= enemyy)
				{
					if ( ( px - enemyx) >= ( enemyy - py ) )
					{
						if (dir == east)
							return 1;
					}
					else
					{
						if (dir == north)
							return 1;
					}
				}
			}
		}
	}
	return 0;
}

//-----------------------------------------------------------------------
// Name: SetOnFire()
// Sets the enemy on fire and creates a fire entity around it
//-----------------------------------------------------------------------
public SetOnFire( lastImage[] )
{
	new entity[20];
	new FireWidth  = 16;
	new FireHeight = 16;
	new width  = GetWidth(lastImage);
	new height = GetHeight(lastImage);
	new State  = GetState("this");
	
	// Don't start burning if we are in certain states already
	if ( State == burning || State == frozen)
		return 0;
	
	// Set the enemy state to burning
	SetState("this", burning );
	PlaySound("_enemyhurt.wav", 240);
	
    // Create a Burning fire entity on this enemy
	CreateEntity("_fire1b", GetX("this") + (width  / 2) - (FireWidth  / 2), \
	                        GetY("this") + (height / 2) - (FireHeight / 2) + 4, entity );
	
	// You will now need a function in your script for handling the burning state                        	                        
	return 1;
}


//-------------------------------------------------------------------------
// Name: Stunned()
// Handles when the enemy is stunned, when they cant move for a while
//-------------------------------------------------------------------------
public Stunned( StunCount )
{
	// This function mainly just handles making the enemy shake before
	// becoming un-stunned.
	
	const MAX_SHAKE = 5;
	static ShakeCount = 0;
	new ShakeOffset[MAX_SHAKE] = { -2, -2, 0, 2, 2 }; 
	
	// If the enemy is close to becomming unstunned then make them shake
	if (StunCount <= 10)
	{
		// Move the enemy slightly to shake them
		SetX("this", GetX("this") + ShakeOffset[ ShakeCount ] );
		
		// Increment the shaking counter
		ShakeCount++;
		if ( ShakeCount >= MAX_SHAKE )
			ShakeCount = 0;
	}
	
	// Check if we are no longer stunned
	if (StunCount <= 0)
	{
		// Leave the Stunned state
		SetState("this", standing);
		SetSpeedMod("this", 0);
	}
}

//-----------------------------------------------------------------------------
// Name: BeginFreeze()
// lastImage[] - the sprite holding the last image of the enemy, this is used
//                to correctly position the frozen effect.
//-----------------------------------------------------------------------------
public BeginFreeze( lastImage[] )
{
	new effect[20];
	new State = GetState("this");
	new width  = GetWidth(lastImage);
	new height = GetHeight(lastImage);
	
	// Don't start if we are in certain states already
	if ( State == burning || State == frozen)
		return 0;
	
	// Set the enemy state to Frozen
	SetState("this", frozen );
	PlaySound("_enemyhurt.wav", 240);
	
	// make the enemy very likely to die next hit
	SetHealth("this", 1);
	
	// Create a frozen effect around the enemy
	CreateEntity("_freezeeffect", GetX("this"), GetY("this"), effect);
	CallFunction(effect, false, "SetArea", "nnnn", GetX("this"), GetY("this"), width, height);
}

//-----------------------------------------------------------------------------
// Name: BeginHit()
// 		 Called when an enemy is hit and needs to go into the hit state
//		 This will also knock the enemy back.
//-----------------------------------------------------------------------------
public BeginHit( damage, x, y )
{
	if ( damage == 0 )
		return;
	
	// Work out a new move angle to move the enemy away from the thing that hit him
	new Angle = CalculateAngle( x, y,  GetX("this"), GetY("this"));
	
	// Set the new move angle	
	SetMoveAngle("this", Angle);
	
	// Increase the enemy speed as they move away from the weapon that hit them
	SetSpeedMod("this", 100);
	ClearCollisionRect("this", 0); 
	PlaySound("_enemyhurt.wav", 240);
	
	// Set the enemy state to hit
	SetState("this", hit);
	
	// Handle damage to the enemy
	SetHealth("this",  GetHealth("this") - damage);
	
	if (GetHealth("this") <= 0)
		KillEnemy();
}