//=============================================================================
// DevilFish.
//=============================================================================
class DevilFish expands ScriptedPawn;

#exec MESH IMPORT MESH=fish ANIVFILE=MODELS\fish_a.3D DATAFILE=MODELS\fish_d.3D X=0 Y=0 Z=0
#exec MESH ORIGIN MESH=fish X=0 Y=-60 Z=0 YAW=64 ROLL=0 

#exec MESH SEQUENCE MESH=fish SEQ=All          STARTFRAME=0   NUMFRAMES=84
#exec MESH SEQUENCE MESH=fish SEQ=Swimming     STARTFRAME=0   NUMFRAMES=10
#exec MESH SEQUENCE MESH=fish SEQ=flopping     STARTFRAME=10  NUMFRAMES=10
#exec MESH SEQUENCE MESH=fish SEQ=fighter 	   STARTFRAME=20  NUMFRAMES=1
#exec MESH SEQUENCE MESH=fish SEQ=bite1        STARTFRAME=20  NUMFRAMES=5  Group=Attack
#exec MESH SEQUENCE MESH=fish SEQ=threatbite1  STARTFRAME=20  NUMFRAMES=5
#exec MESH SEQUENCE MESH=fish SEQ=bite2        STARTFRAME=25  NUMFRAMES=5  Group=Attack
#exec MESH SEQUENCE MESH=fish SEQ=bite3        STARTFRAME=30  NUMFRAMES=5  Group=Attack
#exec MESH SEQUENCE MESH=fish SEQ=breathing    STARTFRAME=35  NUMFRAMES=15
#exec MESH SEQUENCE MESH=fish SEQ=takehit      STARTFRAME=50  NUMFRAMES=1
#exec MESH SEQUENCE MESH=fish SEQ=dead1        STARTFRAME=50  NUMFRAMES=18
#exec MESH SEQUENCE MESH=fish SEQ=ripper       STARTFRAME=68  NUMFRAMES=13 Group=Attack
#exec MESH SEQUENCE MESH=fish SEQ=grab1        STARTFRAME=81  NUMFRAMES=3  Group=Attack

#exec TEXTURE IMPORT NAME=Jfish1 FILE=MODELS\shark.PCX GROUP=Skins 
#exec MESHMAP SCALE MESHMAP=fish X=0.085 Y=0.085 Z=0.17
#exec MESHMAP SETTEXTURE MESHMAP=fish NUM=1 TEXTURE=Jfish1

#exec AUDIO IMPORT FILE="Sounds\Devilfish\ambfs.WAV" NAME="ambfs" GROUP="Razorfish"
#exec AUDIO IMPORT FILE="Sounds\Devilfish\breath1fs.WAV" NAME="breath1fs" GROUP="Razorfish"
#exec AUDIO IMPORT FILE="Sounds\Devilfish\chomp1fs.WAV" NAME="chomp1fs" GROUP="Razorfish"
#exec AUDIO IMPORT FILE="Sounds\Devilfish\death1fs.WAV" NAME="death1fs" GROUP="Razorfish"
#exec AUDIO IMPORT FILE="Sounds\Devilfish\flop1fs.WAV" NAME="flop1fs" GROUP="Razorfish"
#exec AUDIO IMPORT FILE="Sounds\Devilfish\tear1fs.WAV" NAME="tear1fs" GROUP="Razorfish"
#exec AUDIO IMPORT FILE="Sounds\Devilfish\miss1fs.WAV" NAME="miss1fs" GROUP="Razorfish"
#exec AUDIO IMPORT FILE="Sounds\Devilfish\cmphitfs.WAV" NAME="cmphitfs" GROUP="Razorfish"
//FIXME not using breath1fs or cmphitfs
//-----------------------------------------------------------------------------
// RazorFish variables.

// Attack damage.
var() byte
	BiteDamage,		// Basic damage done by bite.
	RipDamage;
var bool bAttackBump;
var(Sounds) sound bite;
var(Sounds) sound rip;

//-----------------------------------------------------------------------------
// RazorFish functions.

function ZoneChange(ZoneInfo newZone)
{
	local vector start, checkpoint, HitNormal, HitLocation;
	local actor HitActor;
	
	if ( newZone.bWaterZone )
	{
		if (Physics != PHYS_Swimming)
			setPhysics(PHYS_Swimming);
	}
	else if (Physics == PHYS_Swimming)
	{
		SetPhysics(PHYS_Falling);
		MoveTimer = -1.0;
		GotoState('Flopping');
	}
}

function PreSetMovement()
{
	bCanJump = true;
	bCanWalk = false;
	bCanSwim = true;
	bCanFly = false;
	MinHitWall = -0.6;
	bCanOpenDoors = false;
	bCanDoSpecial = false;
}

function SetMovementPhysics()
{
	if (Region.Zone.bWaterZone)
		SetPhysics(PHYS_Swimming);
	else
	{
		SetPhysics(PHYS_Falling);
		MoveTimer = -1.0;
		GotoState('Flopping');
	} 
}

function eAttitude AttitudeToCreature(Pawn Other)
{
	if ( Other.IsA('Devilfish') )
		return ATTITUDE_Friendly;
	else if ( Other.IsA('ScriptedPawn') )
		return ATTITUDE_Hate;
	else
		return ATTITUDE_Ignore;
}

function PlayWaiting()
{
	LoopAnim('Swimming', 0.1 + 0.3 * FRand());
}

function PlayPatrolStop()
{
	LoopAnim('Swimming', 0.1 + 0.3 * FRand());
}

function PlayWaitingAmbush()
{
	LoopAnim('Swimming', 0.1 + 0.3 * FRand());
}

function TweenToFighter(float tweentime)
{
	TweenAnim('Fighter', tweentime);
}

function TweenToRunning(float tweentime)
{
	if ( (AnimSequence != 'Swimming') || !bAnimLoop )
		TweenAnim('Swimming', tweentime);
}

function TweenToWalking(float tweentime)
{
	if ( (AnimSequence != 'Swimming') || !bAnimLoop )
		TweenAnim('Swimming', tweentime);
}

function TweenToWaiting(float tweentime)
{
	PlayAnim('Swimming', 0.2 + 0.8 * FRand());
}

function TweenToPatrolStop(float tweentime)
{
	TweenAnim('Swimming', tweentime);
}

function PlayRunning()
{
	LoopAnim('Swimming', -0.8/WaterSpeed,, 0.4);
}

function PlayWalking()
{
	LoopAnim('Swimming', -0.8/WaterSpeed,, 0.4);
}

function PlayThreatening()
{
	if ( FRand() < 0.5 )
		PlayAnim('Swimming', 0.4);
	else
		PlayAnim('Bite1', 0.4);
}

function PlayTurning()
{
	LoopAnim('Swimming', 0.8);
}

function PlayDying(name DamageType, vector HitLocation)
{
	PlaySound(Die, SLOT_Talk);
	PlayAnim('Dead1', 0.7, 0.1);
}

function PlayTakeHit(float tweentime, vector HitLoc, int damage)
{
	TweenAnim('TakeHit', tweentime);
}

function TweenToFalling()
{
	TweenAnim('Flopping', 0.2);
}

function PlayInAir()
{
	LoopAnim('Flopping', 0.7);
}

function PlayLanded(float impactVel)
{
	TweenAnim('breathing', 0.2);
}


function PlayVictoryDance()
{
	PlayAnim('ripper', 0.6, 0.1);
}
	
function PlayMeleeAttack()
{
	local vector adjust;
	local float decision;
	adjust = vect(0,0,0.5) * FRand() * Target.CollisionHeight;
	Acceleration = AccelRate * Normal(Target.Location - Location + adjust);
	bAttackBump = false;
	if (AnimSequence == 'Grab1')
	{
		PlayAnim('ripper', 0.5 + 0.5 * FRand());
		PlaySound(rip,SLOT_Interact,,,500);
		MeleeDamageTarget(RipDamage, vect(0,0,0));
		Disable('Bump');
		return;
	}
	decision = FRand();
	PlaySound(bite,SLOT_Interact,,,500);
	if (decision < 0.3)
	{
		Disable('Bump');
		PlayAnim('Grab1', 0.3);
		return;
	}
	
	Enable('Bump');
	//log("Start Melee Attack");
	if (decision < 0.55)
	{
		PlayAnim('Bite1', 0.3);
	}
	else if (decision < 0.8)
	{
 		PlayAnim('Bite2', 0.3); 
 	}
 	else 
 	{
 		PlayAnim('Bite3', 0.3);
 	}
}

state MeleeAttack
{
ignores SeePlayer, HearNoise;

	singular function Bump(actor Other)
	{
		Disable('Bump');
		if ( (AnimSequence == 'Bite1') || (AnimSequence == 'Bite2') || (AnimSequence == 'Bite3') )
			MeleeDamageTarget(BiteDamage, (BiteDamage * 1000.0 * Normal(Target.Location - Location)));
		else 
			return;
		bAttackBump = true;
		Velocity *= -0.5;
		Acceleration *= -1;
		if (Acceleration.Z < 0)
			Acceleration.Z *= -1;
	}

	function KeepAttacking()
	{
		if (Target == None) 
			GotoState('Attacking', 'SetAttackTimer');
		else if ( (Pawn(Target) != None) && (Pawn(Target).Health == 0) )
			GotoState('Attacking', 'SetAttackTimer');
		else if ( bAttackBump && (FRand() < 0.5) )
		{
			SetTimer(TimeBetweenAttacks, false);
			GotoState('TacticalMove', 'NoCharge');
		}
	}
}		


State Flopping
{
ignores seeplayer, hearnoise, enemynotvisible, hitwall; 	
	function Timer()
	{
		SetPhysics(PHYS_Falling);
		Velocity = 200 * VRand();
		Velocity.Z = 170 + 200 * FRand();
		DesiredRotation.Pitch = Rand(8192) - 4096;
		DesiredRotation.Yaw = Rand(65535);
	}
	
	function ZoneChange( ZoneInfo NewZone )
	{
		local Rotation newRotation;
		if (NewZone.bWaterZone)
		{
			newRotation = Rotation;
			newRotation.Roll = 0;
			SetRotation(newRotation);
			SetPhysics(PHYS_Swimming);
			GotoState('Attacking');
		}
		else if (Physics != PHYS_Falling)
			SetPhysics(PHYS_Falling);
	}
	
	function Landed(vector HitNormal)
	{
		local rotation newRotation;
		SetPhysics(PHYS_None);
		SetTimer(0.3 + FRand(), false);
		newRotation = Rotation;
		newRotation.Pitch = 0;
		newRotation.Roll = Rand(32768) - 16384;
		SetRotation(newRotation);
		PlaySound(land,SLOT_Interact,,,400);
	}
	
	function AnimEnd()
	{
		if (Physics == PHYS_None)
		{
			if (AnimSequence == 'Breathing')
			{
				PlaySound(sound'breath1fs',SLOT_Interact,,,300);
				PlayAnim('Breathing');
			}
			else 
				TweenAnim('Breathing', 0.2);
		}
		else
			PlayAnim('Flopping', 0.7);
	}

Begin:
	SetTimer(0.3 + FRand(), false);
	TweenAnim('Flopping', 0.7);
}


state TacticalMove
{
	function PickDestination(bool bNoCharge)
	{
		local vector pick, pickdir, enemydir,Y, minDest;
		local float Aggression, enemydist, minDist, strafeSize;
		local bool success;
	
		success = false;
		enemyDist = Size(Location - Enemy.Location);
		Aggression = 2 * (CombatStyle + FRand()) - 1.0;

		if (enemyDist < CollisionRadius + Enemy.CollisionRadius + MeleeRange)
			Aggression -= 1;	
		else if (enemyDist > FMax(Size(OldLocation - Enemy.OldLocation), 240))
			Aggression += 0.4 * FRand();
		
		enemydir = (Enemy.Location - Location)/enemyDist;
		if ( !enemy.Region.Zone.bWaterZone )
			enemydir.Z *= 0.5;
		minDist = FMin(160.0, 3*CollisionRadius);
			
		Y = (enemydir Cross vect(0,0,1));
		
		strafeSize = FMin(1.0, (2 * Abs(Aggression) * FRand() - 0.2));
		if (Aggression < 0)
			strafeSize *= -1;
		enemydir = enemydir * strafeSize;
		enemydir.Z = FMax(0,enemydir.Z);
		strafeSize = FMax(0.0, 1 - Abs(strafeSize));
		pickdir = strafeSize * Y;
		pick = Location + (pickdir + enemydir) * (minDist + 500 * FRand());
		minDest = Location + minDist * (pickdir + enemydir); 
		if (pointReachable(minDest))
		{
			success = true;
			Destination = pick;	
		}
						
		if (!success)
		{
			pick = Location + (enemydir - pickdir) * (minDist + 500 * FRand());
			pick.Z = Location.Z;
			minDest = Location + minDist * (enemydir - pickdir); 
			if (pointReachable(minDest))
			{
				success = true;
				Destination = pick;	
			}
		}
		
		if (!success)
		{
			pick = Location - enemydir * (minDist + 500 * FRand());
			pick.Z = Location.Z + 200 * FRand() - 100;
		}
	}


	function Bump(Actor Other)
	{
		Disable('Bump');
		if (bAttackBump == true)
			bAttackBump = false;
		else if (Other == Enemy)
		{
			bReadyToAttack = true;
			Target = Enemy;
			GotoState('MeleeAttack');
		}
		else if (Enemy.Health <= 0)
			GotoState('Attacking');
	}
}

