//=============================================================================
//
//	gnash.txt
//
//	This is the script for the E1L5 boss, Gnash. For his intro sequence,
//	he raises out of a hole in the ground and plays an animation. He has 4
//	attacks. Attack 1 has his blade start spinning, and him lunge in the
//	player's current direction (must be dodged). Attack 2 has him jump into
//	the air 3 times, and firing 360 degree seeking missiles each time he
//	lands (must be jumped over). Attack 3 has him slowly start spinning,
//	and then bounce around the room starting off in a random direction.
//	After a shot period, he stops spinning and the attack ends. Finally,
//	attack 4 has him fire out 4 seeking bouncing missiles (must be stood
//	under).
//
//	For documentation on scripting, please consult the Wrack Wiki at:
//	http://wrack.wikia.com/
//
//=============================================================================

#include "commondefines.h"

//== DEFINITIONS ==============================================================

#define	THRUST_HORIZONTAL	15
#define	THRUST_VERTICAL		18

//== VARIABLES ================================================================

int	g_iPreviousStateWasChase;

// Keep track of the last attack we did so we don't do the same attack twice in
// a row.
int	g_iLastAttack;

// If we're performing a looping attack, how many times it been done?
int	g_iAttackLoopCount;

//== STATE PROTOTYPES =========================================================

state	Spawn;
state	IntroSequence;
state	Chase;
state	WalkAlongObstruction;
state	WalkAwayFromObstruction;
state	ChooseAttack;
state	BeginAttack1;
state	Attack1;
state	EndAttack1;
state	Attack2;
state	BeginAttack3;
state	Attack3;
state	EndAttack3;
state	Attack4;
state	DeathSequence;

//== EVENTS ===================================================================

event_instant( "killed" )
	ChangeState( DeathSequence );

//-----------------------------------------------------------------------------
event_instant( "gibbed" )
	ChangeState( DeathSequence );

//-----------------------------------------------------------------------------
event_instant( "player revived" )
{
	// Destroy gibs, puddles, etc.
	DestroyChildren( );

	DestroySelf( );
}

//-----------------------------------------------------------------------------
event_instant( "player continued" )
{
	// Destroy gibs, puddles, etc.
	DestroyChildren( );

	DestroySelf( );
}

//== FUNCTIONS ================================================================

int CalcHorizontalGibThrust( void )
{
	return (( rand( 0, RAND_MAX ) - ( RAND_MAX / 2 )) * 2 * THRUST_HORIZONTAL );
}

//-----------------------------------------------------------------------------
int CalcVerticalGibThrust( void )
{
	return ( rand( 0, RAND_MAX ) * 2 * ( THRUST_VERTICAL / 2 ));
}

//== STATES ===================================================================

state Spawn
{
	OnEnter
	{

		// Play our secondary "disk spin" animation.
		SetSecondaryAnimation( "DiskSpin" );

		// Play the first part of our intro animation.
		SetAnimation( "Intro", -1, 0 );

		// Play the first part of our intro sound.
		PlaySound( "gnash_intro.wav", 100, SFXF_NON3D, SFXPRIORITY_HIGHEST );

		// Randomly determine our "last" attack.
		g_iLastAttack = rand( 0, 3 );
	}

	MainLoop
	{
		// Begin our intro sequence.
		ChangeState( IntroSequence );
	}

	OnExit
	{
		// This is not a chase state.
		g_iPreviousStateWasChase = false;
	}
}

//-----------------------------------------------------------------------------
state IntroSequence
{
	event( "animation over" )
	{

		changestate( Chase );
	}

	OnEnter
	{
	}

	MainLoop
	{
	}
}

//-----------------------------------------------------------------------------
state Chase
{
	int	iRand;
	int	iTicks;

	event( "struck wall" )
	{
		// If we hit a wall, try to turn and move towards our target.
		// If we can't move towards it, first try to walk along the
		// obstruction.
		FaceTarget8Way( true, false );

		if ( CannotMoveForward( ))
			changestate( WalkAlongObstruction );
	}

	event( "target died" )
		changestate( Spawn );

	OnEnter
	{
		// If we're coming from another chase state, don't reset our chase
		// animation/sound since they're already playing.
		if ( g_iPreviousStateWasChase == false )
		{
			// Play our movement sound.
			PlaySound( "gnash_chase.wav", 100, SFXF_STOPEXISTING_LOOPING | SFXF_LOOP, SFXPRIORITY_HIGHEST );

			// Set this state's animation.
			SetAnimation( "Chase", -1, ANIMF_LOOP );
		}

		iTicks = 0;
	}

	MainLoop
	{
		// Determine when to attack. If the skill setting calls for the
		// monsters to be more aggressive, then there's a higher chance
		// that the monster should attack.
		if ( GetSkillSettingFlags( ) & SKILL_VERYAGGRESSIVE )
			iRand = rand( 0, FPS );
		else if ( GetSkillSettingFlags( ) & SKILL_AGGRESSIVE )
			iRand = rand( 0, FPS * 2 );
		else
			iRand = rand( 0, FPS * 3 );

		// In this state, attack about once every 3 seconds.
		if (( iRand == 0 ) && ( CanSeeTarget( true )))
			ChangeState( ChooseAttack );

		if ( iTicks == 0 )
		{
			// Change directions.
			FaceTarget8Way( true, false );
			iTicks = rand( 1, 2 ) * FPS / 4;
		}
		else
			iTicks--;

		MoveForward( );
	}

	OnExit
	{
		// This is a chase state.
		g_iPreviousStateWasChase = true;
	}
}

//-----------------------------------------------------------------------------
state WalkAlongObstruction
{
	int	iRand;
	int	iTicks;
	int	iDistance;

	event( "struck wall" )
	{
		// If we hit a wall while trying to walk along our previous
		// obstruction, we must be really stuck. Try walking in some
		// random direction. But first, see if we can move towards our
		// target once again (maybe we're free in that direction now!)
		FaceTarget8Way( true, false );

		if ( CannotMoveForward( ))
			changestate( WalkAwayFromObstruction );
		else
			changestate( Chase );
	}

	event( "target died" )
		changestate( Spawn );

	OnEnter
	{
		// Try to walk along the wall we struck. Since we can walk in
		// one of two directions, try to go in the direction that takes
		// us more towards our target.
		SetAngle( ANGLE_YAW, GetHitFaceAngle( ) + ANGLE_90, true );
		if ( TargetInFront( ) == false )
			Turn( ANGLE_YAW, ANGLE_180, true );

		// Walk in this new direction between 32 and 96 units.
		iDistance = rand( 1, 3 ) * 32 * 65536;
		iTicks = iDistance / GetIntProperty( "speed" );
	}

	MainLoop
	{
		// Determine when to attack. If the skill setting calls for the
		// monsters to be more aggressive, then there's a higher chance
		// that the monster should attack.
		if ( GetSkillSettingFlags( ) & SKILL_VERYAGGRESSIVE )
			iRand = rand( 0, FPS );
		else if ( GetSkillSettingFlags( ) & SKILL_AGGRESSIVE )
			iRand = rand( 0, FPS * 2 );
		else
			iRand = rand( 0, FPS * 3 );

		// In this state, attack about once every 3 seconds.
		if (( iRand == 0 ) && ( CanSeeTarget( true )))
			ChangeState( ChooseAttack );

		// If we're done walking in this new direction, potentially 
		// resume chasing our target.
		if ( iTicks == 0 )
		{
			// Face our target once again. If we can move towards
			// it, resume chasing.
			FaceTarget8Way( true, false );
			if ( CannotMoveForward( ))
			{
				// Walk away from the obstruction we previously
				// hit.
				changestate( WalkAwayFromObstruction );
			}
			else
				changestate( Chase );
		}

		// Decrement our timer, and move forward.
		iTicks--;
		MoveForward( );
	}

	OnExit
	{
		// This is a chase state.
		g_iPreviousStateWasChase = true;
	}
}

//-----------------------------------------------------------------------------
state WalkAwayFromObstruction
{
	int	iRand;
	int	iTicks;
	int	iDistance;

	event( "struck wall" )
	{
		// We hit a wall... again! Once again try to walk towards our
		// target. If we can't, just pick another direction to go in.
		FaceTarget8Way( true, false );

		if ( CannotMoveForward( ))
			changestate( WalkAwayFromObstruction );
		else
			changestate( Chase );
	}

	event( "target died" )
		changestate( Spawn );

	OnEnter
	{
		// Start off by facing directly at the wall we struck.
		SetAngle( ANGLE_YAW, GetHitFaceAngle( ) + ANGLE_180, true );

		// Determine how many degrees to turn. Turn in multiples of 45
		// degrees.
		iRand = rand( 0, 5 );
		if ( iRand == 0 )
			Turn( ANGLE_YAW, ANGLE_45 * 2, true );
		else if ( iRand == 1 )
			Turn( ANGLE_YAW, ANGLE_45 * 3, true );
		else if ( iRand == 2 )
			Turn( ANGLE_YAW, ANGLE_45 * 4, true );
		else if ( iRand == 3 )
			Turn( ANGLE_YAW, ANGLE_45 * 5, true );
		else
			Turn( ANGLE_YAW, ANGLE_45 * 6, true );

		// Walk in this new direction between 32 and 96 units.
		iDistance = rand( 1, 3 ) * 32 * 65536;
		iTicks = iDistance / GetIntProperty( "speed" );
	}

	MainLoop
	{
		// Determine when to attack. If the skill setting calls for the
		// monsters to be more aggressive, then there's a higher chance
		// that the monster should attack.
		if ( GetSkillSettingFlags( ) & SKILL_VERYAGGRESSIVE )
			iRand = rand( 0, FPS );
		else if ( GetSkillSettingFlags( ) & SKILL_AGGRESSIVE )
			iRand = rand( 0, FPS * 2 );
		else
			iRand = rand( 0, FPS * 3 );

		// In this state, attack about once every 3 seconds.
		if (( iRand == 0 ) && ( CanSeeTarget( true )))
			ChangeState( ChooseAttack );

		// If we're done walking in this new direction, potentially 
		// resume chasing our target.
		if ( iTicks == 0 )
		{
			// Face our target once again. If we can move towards
			// it, resume chasing.
			FaceTarget8Way( true, false );
			if ( CannotMoveForward( ))
			{
				// Walk away from the obstruction we previously
				// hit.
				changestate( WalkAwayFromObstruction );
			}
			else
				changestate( Chase );
		}

		// Decrement our timer, and move forward.
		iTicks--;
		MoveForward( );
	}

	OnExit
	{
		// This is a chase state.
		g_iPreviousStateWasChase = true;
	}
}

//-----------------------------------------------------------------------------
state ChooseAttack
{
	int	iAttack;

	MainLoop
	{
		// Randomly pick our attack.
		iAttack = rand( 1, 4 );

		// Make sure we don't do the same attack twice in a row.
		while ( iAttack == g_iLastAttack )
			iAttack = rand( 1, 4 );

		// Save the attack.
		g_iLastAttack = iAttack;

		g_iAttackLoopCount = 0;
		switch ( iAttack )
		{
		case 1:

			changestate( BeginAttack1 );
			break;
		case 2:

			changestate( Attack2 );
			break;
		case 3:

			changestate( BeginAttack3 );
			break;
		case 4:

			changestate( Attack4 );
			break;
		}
	}

	OnExit
	{
		// Not a chase state.
		g_iPreviousStateWasChase = false;
	}
}

//-----------------------------------------------------------------------------
state BeginAttack1
{
	event( "animation over" )
		changestate( Attack1 );

	OnEnter
	{
		// Play the sound.
		PlaySound( "gnash_ramattack_begin.wav", 100, SFXF_STOPEXISTING_NONLOOPING, SFXPRIORITY_HIGHEST );

		// Play the beginning animation to the ram attack.
		SetAnimation( "RamAttack_Begin", -1, 0 );
	}

	MainLoop
	{
		// Continusously face our target.
		FaceTarget( false, -1 );
	}
}

//-----------------------------------------------------------------------------
state Attack1
{
	event_instant( "struck object" )
	{
		// Bounce backwards slightly.
		Turn( ANGLE_YAW, ANGLE_180, false );
		UpdateVelocity( 8 * MAPUNIT_MULTIPLIER, true );
		Turn( ANGLE_YAW, ANGLE_180, false );

		DamageStruckObject( GetIntProperty( "damage" ), GetIntProperty( "knockback" ), 0 );
		changestate( EndAttack1 );
	}

	event( "struck wall" )
	{
		// Bounce backwards slightly.
		Turn( ANGLE_YAW, ANGLE_180, false );
		UpdateVelocity( 8 * MAPUNIT_MULTIPLIER, true );
		Turn( ANGLE_YAW, ANGLE_180, false );

		changestate( EndAttack1 );
	}

	OnEnter
	{
		// Disable friction when in the air.
		AddFlag( FLAG_NOFRICTION );

		// Play the sound.
		PlaySound( "gnash_ramattack.wav", 100, SFXF_STOPEXISTING_NONLOOPING | SFXF_LOOP, SFXPRIORITY_HIGHEST );

		// Play a looping animation during the ram attack.
		SetAnimation( "RamAttack", -1, ANIMF_LOOP );
	}

	MainLoop
	{
		// Continuiously move forward quickly.
		if ( GetSkillSettingFlags( ) & SKILL_VERYAGGRESSIVE )
			UpdateVelocity( 16 * MAPUNIT_MULTIPLIER, true );
		else if ( GetSkillSettingFlags( ) & SKILL_AGGRESSIVE )
			UpdateVelocity( 12 * MAPUNIT_MULTIPLIER, true );
		else
			UpdateVelocity( 8 * MAPUNIT_MULTIPLIER, true );
	}	

	OnExit
	{
		// Restore friction upon leaving this state.
		ClearFlag( FLAG_NOFRICTION );
	}
}

//-----------------------------------------------------------------------------
state EndAttack1
{
	event( "animation almost over" )
		changestate( Chase );

	OnEnter
	{
		// Play the sound.
		PlaySound( "gnash_ramattack_end.wav", 100, SFXF_STOPEXISTING_ALL, SFXPRIORITY_HIGHEST );
		PlaySound( "gnash_chase.wav", 100, SFXF_LOOP, SFXPRIORITY_HIGHEST );

		// Play an animation to end the ram attack.
		SetAnimation( "RamAttack_End", -1, 0 );
	}

	MainLoop
	{
	}
}

//-----------------------------------------------------------------------------
state Attack2
{
	event( "animation over" )
	{
		// If this is our third loop of the attack, change back to the
		// main state. Otherwise, go again!
		g_iAttackLoopCount++;
		if ( g_iAttackLoopCount == 3 )
			changestate( Chase );
		else
			changestate( Attack2 );
	}

	event( "fire projectile" )
	{
		// When we strike the floor, fire 360 degree seeking projectiles.
		SetSpawnPosition( SPAWNPOSITION_FROMCENTER, 0, false, "Missile_Seeking_Gnash_Special" );
		FireProjectileAtTarget( "Missile_Seeking_Gnash_Special", 36, ANGLE_1 * 10, FPF_DONTPITCH );
	}

	OnEnter
	{
		// Face our target.
		FaceTarget( true, -1 );

		// Play the sound.
		PlaySound( "gnash_jump.wav", 100, SFXF_STOPEXISTING_NONLOOPING, SFXPRIORITY_HIGHEST );

		// Play our jumping animation.
		if ( GetSkillSettingFlags( ) & SKILL_VERYAGGRESSIVE )
			SetAnimation( "Jump", 60, 0 );
		else if ( GetSkillSettingFlags( ) & SKILL_AGGRESSIVE )
			SetAnimation( "Jump", 75, 0 );
		else
			SetAnimation( "Jump", 90, 0 );
	}

	MainLoop
	{
	}
}

//-----------------------------------------------------------------------------
state BeginAttack3
{
	event( "animation almost over" )
		changestate( Attack3 );

	OnEnter
	{
		// Play the sound.
		PlaySound( "gnash_spinattack_begin.wav", 100, SFXF_STOPEXISTING_NONLOOPING, SFXPRIORITY_HIGHEST );

		// Play the beginning animation to the spin attack.
		SetAnimation( "SpinAttack_Begin", -1, 0 );

		// Face our target.
		FaceTarget( true, -1 );
	}

	MainLoop
	{
	}
}

//-----------------------------------------------------------------------------
state Attack3
{
	int	iTick;
	int	iAngle;

	event_instant( "struck wall" )
	{
		// Play the sound.
		PlaySound( "gnash_spinattack_hit.wav", 100, 0, SFXPRIORITY_HIGHEST );
	}

	event_instant( "struck object" )
	{
		// Damage the struck object.
		DamageStruckObject( GetIntProperty( "damage" ), GetIntProperty( "knockback" ), 0 );

		// Play the sound.
		PlaySound( "gnash_spinattack_hit.wav", 100, 0, SFXPRIORITY_HIGHEST );
	}

	OnEnter
	{
		iTick = 300;

		// Play the sound.
		PlaySound( "gnash_spinattack.wav", 100, SFXF_STOPEXISTING_NONLOOPING | SFXF_LOOP, SFXPRIORITY_HIGHEST );

		// Play our looping spin attack animation.
		SetAnimation( "SpinAttack", -1, ANIMF_LOOP );

		// Remove friction and give bounce collision.
		AddFlag( FLAG_NOFRICTION );
		AddFlag( FLAG_BOUNCECOLLISION );

		// Send Gnash off in a random direction.
		iAngle = rand( 0, ANGLE_360 );
		Turn( ANGLE_YAW, iAngle, false );
		if ( GetSkillSettingFlags( ) & SKILL_VERYAGGRESSIVE )
			UpdateVelocity( 12 * MAPUNIT_MULTIPLIER, true );
		else if ( GetSkillSettingFlags( ) & SKILL_AGGRESSIVE )
			UpdateVelocity( 10 * MAPUNIT_MULTIPLIER, true );
		else
			UpdateVelocity( 8 * MAPUNIT_MULTIPLIER, true );

		// Face our original direction.
		Turn( ANGLE_YAW, ANGLE_360 - iAngle, false );
	}

	MainLoop
	{
		iTick--;
		if ( iTick == 0 )
			changestate( EndAttack3 );
	}

	OnExit
	{
		ClearFlag( FLAG_NOFRICTION );
		ClearFlag( FLAG_BOUNCECOLLISION );
	}
}

//-----------------------------------------------------------------------------
state EndAttack3
{
	event( "animation over" )
		changestate( Chase );

	OnEnter
	{
		// Play the sound.
		PlaySound( "gnash_spinattack_end.wav", 100, SFXF_STOPEXISTING_LOOPING, SFXPRIORITY_HIGHEST );
		PlaySound( "gnash_chase.wav", 100, SFXF_LOOP, SFXPRIORITY_HIGHEST );

		// Play an animation to end the ram attack.
		SetAnimation( "SpinAttack_End", -1, 0 );
	}

	MainLoop
	{
	}
}

//-----------------------------------------------------------------------------
state Attack4
{
	event( "animation over" )
	{
		// If this is our third loop of the attack, change back to the
		// main state. Otherwise, go again!
		g_iAttackLoopCount++;
		if ( g_iAttackLoopCount == 3 )
			changestate( Chase );
		else
			changestate( Attack4 );
	}

	event( "fire projectile" )
	{
		// Fire bouncing projectiles at our target that must be stood
		// under.
		SetSpawnPosition( SPAWNPOSITION_INFRONT, GetIntProperty( "projectileheight" ), true, "Missile_Seeking_Bounce" );
		FireProjectileAtTarget( "Missile_Seeking_Bounce1", 1, 0, FPF_DONTPITCH );
	}

	OnEnter
	{
		// Face our target.
		FaceTarget( true, -1 );

		// Play the sound.
		PlaySound( "gnash_fireprojectile.wav", 100, SFXF_STOPEXISTING_NONLOOPING, SFXPRIORITY_HIGHEST );

		// Play our projectile firing animation.
		if ( GetSkillSettingFlags( ) & SKILL_VERYAGGRESSIVE )
			SetAnimation( "FireProjectile", 48, 0 );
		else if ( GetSkillSettingFlags( ) & SKILL_AGGRESSIVE )
			SetAnimation( "FireProjectile", 60, 0 );
		else
			SetAnimation( "FireProjectile", 72, 0 );
	}

	MainLoop
	{
	}
}

//-----------------------------------------------------------------------------
state DeathSequence
{
	event_instant( "animation over" )
	{
		// Stop our sounds.
		StopSounds( true );
		StopSounds( false );

		// Play our gib sound.
		PlaySound( "gnash_explode.wav", 100, SFXF_STOPEXISTING_ALL | SFXF_NON3D, SFXPRIORITY_HIGH );

		// Spawn an explosion particle.
		SetSpawnPosition( SPAWNPOSITION_FROMCENTER, GetIntProperty( "height" ) / 2, false, "" );
		SpawnParticles( "ExplosionParticle", 1, true, 0, ANGLE_90 * -1 );

		// Make our body disappear.
		AddFlag( FLAG_DONTRENDER );

		// Toss some gibs.
		SetSpawnPosition( SPAWNPOSITION_FROMCENTER, GetIntProperty( "height" ) / 2, false, "" );
		TossGib( "Gnash_Gib1", CalcHorizontalGibThrust( ), CalcVerticalGibThrust( ), CalcHorizontalGibThrust( ), true );
		TossGib( "Gnash_Gib2", CalcHorizontalGibThrust( ), CalcVerticalGibThrust( ), CalcHorizontalGibThrust( ), true );
		TossGib( "Gnash_Gib3", CalcHorizontalGibThrust( ), CalcVerticalGibThrust( ), CalcHorizontalGibThrust( ), true );
		TossGib( "Gnash_Gib4", CalcHorizontalGibThrust( ), CalcVerticalGibThrust( ), CalcHorizontalGibThrust( ), true );
		TossGib( "Gnash_Gib4", CalcHorizontalGibThrust( ), CalcVerticalGibThrust( ), CalcHorizontalGibThrust( ), true );
		TossGib( "Gnash_Gib5", CalcHorizontalGibThrust( ), CalcVerticalGibThrust( ), CalcHorizontalGibThrust( ), true );
		TossGib( "Gnash_Gib5", CalcHorizontalGibThrust( ), CalcVerticalGibThrust( ), CalcHorizontalGibThrust( ), true );
		TossGib( "Gnash_Gib6", CalcHorizontalGibThrust( ), CalcVerticalGibThrust( ), CalcHorizontalGibThrust( ), true );
		TossGib( "Gnash_Gib6", CalcHorizontalGibThrust( ), CalcVerticalGibThrust( ), CalcHorizontalGibThrust( ), true );
		TossGib( "Gnash_Gib7", CalcHorizontalGibThrust( ), CalcVerticalGibThrust( ), CalcHorizontalGibThrust( ), true );

		// Fade from white and pause for a couple seconds.
		FadeFromColor( 255, 255, 255, 60 );
		delay( 120 );
	}

	// One second before the end of the death animation, begin fading to
	// white.
	event_instant( "fade to white" )
{
}

	OnEnter
	{
		// Face our target.
		FaceTarget( true, -1 );

		// Destroy our child projectiles.
		SendEvent( SENDEVENT_CHILDREN, "explode" );

		// Face the boss as it blows apart.
		ExecuteMapScript( "GnashSpecialDeath", 0, 0, 0, 0 );

		// Play our death sound.
		PlaySound( "gnash_dead.wav", 100, SFXF_STOPEXISTING_ALL | SFXF_NON3D, SFXPRIORITY_HIGHEST );

		// Stop allowing other objects to collide with us.
		StopBlocking( );

		// Play our death animation.
		SetAnimation( "Death", -1, ANIMF_DONTBLEND );
	}

	MainLoop
	{
	}
}
