/*
   ================================================================
   CRAWLER MONSTER
   ================================================================

   Copyright (C) 1998 by 2015, Inc.
   All rights reserved.

   This source is may not be distributed and/or modified without
   expressly written permission by 2015, Inc.
 */

#include "crawler.h"
#include "vehicle.h"
#include "object.h"

//===============================================================
// CRAWLER WEAPON
//===============================================================
int G_ClipVelocity( Vector& in, Vector& normal, Vector& out, float overbounce, int gravaxis );

CLASS_DECLARATION( Projectile, CrawlerGoo, NULL );

ResponseDef CrawlerGoo::Responses[] =
{
	{&EV_Touch, (Response)CrawlerGoo::GooTouch},
	{&EV_FadeOut, (Response)CrawlerGoo::FadeOut },
	{NULL, NULL}
};

CrawlerGoo::CrawlerGoo()
{}

void CrawlerGoo::GooTouch( Event *ev )
{
	Entity      *other;
	Entity      *owner;
	int damg;
	Vector v;
	Vector norm;
	Vector shockangles;

	other = ev->GetEntity( 1 );
	assert( other );

	if ( other->isSubclassOf( Teleporter ) )
	{
		return;
	}

	if ( other->entnum == this->owner )
	{
		return;
	}

	if(other->isSubclassOf( Crawler ))
	{
		return;
	}

	owner = G_GetEntity( this->owner );

	if ( !owner )
		owner = world;

	setSolidType( SOLID_NOT );

	// Hit the sky, so remove everything
	if ( HitSky() )
	{
		PostEvent( EV_Remove, 0 );
		return;
	}

	damg = 10 + (int)G_Random( 5 );

	if (other->takedamage)
		other->Damage( this, owner, damg, worldorigin, velocity, level.impact_trace.plane.normal, 32, 0, MOD_PULSE, -1, -1, 1.0f );

	SpawnCrawlerSpitDamage( &level.impact_trace, damg, owner );

	PostEvent( EV_Remove, 0 );
}

void CrawlerGoo::FadeOut( Event *ev )
{
	PostEvent( EV_FadeOut, 0.1f );

	edict->s.renderfx |= RF_TRANSLUCENT;
	translucence += 0.03f;
	if ( translucence >= 0.96f )
	{
		PostEvent( EV_Remove, 0 );
	}

	setAlpha( 1.0f - translucence );
	// fade the dynamic light
	edict->s.color_g = 1.0f - translucence;
}

void CrawlerGoo::Setup( Entity *owner, Vector pos, Vector vel )
{
	this->owner = owner->entnum;
	edict->owner = owner->edict;

	// Align the projectile
	angles = vel.toAngles();
	angles[ PITCH ] = -angles[ PITCH ];
	setAngles( angles );

	// Flies like a grenade
	setMoveType( MOVETYPE_TOSS );
	setSolidType( SOLID_BBOX );
	edict->clipmask = MASK_PROJECTILE;
	setModel( "crawlergoo.def" );
	RandomAnimate( "goo", NULL );

	// Set the flying velocity
	velocity = vel * 900;
	gravity = 0.1;

	takedamage = DAMAGE_NO;

	flags |= FL_DONTSAVE;

	edict->s.effects |= EF_WARM;

	// Set size and origin
	setSize( "-1 -1 -1", "1 1 1" );
	setOrigin( pos );
	worldorigin.copyTo( edict->s.old_origin );

	// Remove the projectile in the future
	PostEvent( EV_Remove, 30 );
}

//===============================================================

CLASS_DECLARATION( Weapon, CrawlerWeapon, "weapon_crawlerweapon" );

ResponseDef CrawlerWeapon::Responses[] =
{
	{&EV_Weapon_Shoot, (Response)CrawlerWeapon::Shoot},
	{NULL, NULL}
};

CrawlerWeapon::CrawlerWeapon()
{
	SetModels( NULL, "view_crawlerweapon.def" );
	SetAmmo( "Rockets", 0, 0 );

	notdroppable = true;
}

void CrawlerWeapon::Shoot( Event *ev )
{
	CrawlerGoo  *goo;
	Vector pos;
	Vector dir;
	Vector target, vel;

	assert( owner );
	if ( !owner )
	{
		return;
	}

	GetMuzzlePosition( &pos, &dir );

	goo = new CrawlerGoo;
	goo->Setup( owner, pos, dir );

	NextAttack( 0.2 );
}

//===============================================================
// CRAWLER
//===============================================================

CLASS_DECLARATION( Actor, Crawler, "monster_crawler" );

extern Event EV_Actor_IfCanShoot;
extern Event EV_Actor_Idle;

// action commands
Event EV_Crawler_JumpToCeiling( "jumptoceiling" );
Event EV_Crawler_OrientToCeiling( "orienttoceiling" );
// state setting commands
Event EV_Crawler_LikesCeiling( "likesceiling" );
Event EV_Crawler_LikesFloor( "likesfloor" );
Event EV_Crawler_CeilingHeight( "ceilingheight" );
// conditional commands
Event EV_Crawler_IfOnCeiling( "ifonceiling" );
Event EV_Crawler_IfOnFloor( "ifonfloor" );
Event EV_Crawler_IfCeilingOK( "ifceilingok" );

ResponseDef Crawler::Responses[] =
{
	{&EV_Actor_Idle, (Response)Crawler::IdleEvent},
	{&EV_Pain, (Response)Crawler::Pain},
	{&EV_Killed, (Response)Crawler::Killed},
	{&EV_Actor_Dead, (Response)Crawler::Dead},
	{&EV_Crawler_JumpToCeiling, (Response)Crawler::JumpToCeilingEvent},
	{&EV_Crawler_OrientToCeiling, (Response)Crawler::OrientToCeilingEvent},

	{&EV_Crawler_LikesCeiling, (Response)Crawler::LikesCeilingEvent},
	{&EV_Crawler_LikesFloor, (Response)Crawler::LikesFloorEvent},
	{&EV_Crawler_CeilingHeight, (Response)Crawler::CeilingHeightEvent},

	{&EV_Crawler_IfOnCeiling, (Response)Crawler::IfOnCeilingEvent},
	{&EV_Crawler_IfOnFloor, (Response)Crawler::IfOnFloorEvent},
	{&EV_Crawler_IfCeilingOK, (Response)Crawler::IfCeilingOKEvent},

	{NULL, NULL}
};

Crawler::Crawler()
{
	setMoveType( MOVETYPE_STEP );

	modelIndex( "crawlergoo.def" );
	modelIndex( "sprites/crawlerspitmark.spr" );

	eyeposition = "0 0 20";

	onceiling = false;
	likesceiling = true;

	shots_per_attack = G_GetFloatArg( "shotsperattack", 1 + (1 * skill->value));
}

void Crawler::IdleEvent( Event *ev )
{
	Event *e;

	if ( ( deadflag ) && ( actortype != IS_INANIMATE ) )
	{
		return;
	}

	e = new Event( EV_Behavior_Args );
	e->SetSource( EV_FROM_SCRIPT );
	e->SetThread( ev->GetThread() );
	e->SetLineNumber( ev->GetLineNumber() );

	e->AddEntity( this );
	e->AddString( ev->GetToken( 1 ) );
	e->AddString( ev->GetToken( 2 ) );
	SetBehavior( new CrawlerIdle, e, ev->GetThread() );
}

void Crawler::Pain( Event *ev )
{
	float damage;
	Entity  *ent;
	float oldhealth;
	float newhealth;

	damage = ev->GetFloat( 1 );
	ent = ev->GetEntity( 2 );

	// if it's a Sentient and not liked, attack 'em.
	if ( ent && ent->isSubclassOf( Sentient ) && !Likes( ent ) )
	{
		MakeEnemy( ent );
		if ( ent != currentEnemy )
		{
			currentEnemy = BestTarget();
		}
	}

	if ( damage <= 0 )
	{
		return;
	}

	oldhealth = ( health + damage ) / max_health;
	newhealth = health / max_health;

	SetVariable( "other", ev->GetEntity( 2 ) );

	// If we pass more than one range,
	if ( ( oldhealth > 0.75 ) && ( newhealth <= 0.75 ) )
	{
		DoAction( "health_ok" );
	}
	if ( ( oldhealth > 0.5 ) && ( newhealth <= 0.5 ) )
	{
		DoAction( "health_med" );
	}
	if ( ( oldhealth > 0.25 ) && ( newhealth <= 0.25 ) )
	{
		DoAction( "health_low" );
	}
	if ( ( oldhealth > 0.1 ) && ( newhealth <= 0.1 ) )
	{
		DoAction( "health_danger" );
	}

	if ( damage <= pain_threshold )
	{
		Chatter( "snd_pain_taunt", 5, true );
		return;
	}

	// if on the ceiling, fall off
	if(onceiling)
	{
		onceiling = false;
		setMoveType( MOVETYPE_STEP );
		groundentity = NULL;

		// do falling off ceiling animation
		SetVariable( "painanim", "ceiling_pain" );

		DoAction( "pain" );
	}
	else if ( strncmp( animname.c_str(), "pain", 4 ) && strncmp( animname.c_str(), "crouch_pain", 11 ) )
	{
		str aname;
		int index;

		//
		// determine pain animation
		//
		if ( !strncmp( animname.c_str(), "crouch", 6 ) )
		{
			aname = "crouch_";
		}
		aname += str( "pain_" ) + str( ev->GetString( 3 ) );
		index = gi.Anim_Random( edict->s.modelindex, aname.c_str() );
		if ( ( index == -1 ) && !strncmp( animname.c_str(), "crouch", 6 ) )
		{
			aname = "crouch_pain";
			index = gi.Anim_Random( edict->s.modelindex, aname.c_str() );
		}

		if ( index == -1 )
		{
			aname = "pain";
		}

		SetVariable( "painanim", aname.c_str() );
		DoAction( "pain" );
	}
}

void Crawler::Killed( Event *ev )
{
	// make sure they don't die on the ceiling
	if(onceiling)
	{
		onceiling = false;
		setMoveType( MOVETYPE_STEP );
		groundentity = NULL;
	}

	Actor::Killed( ev );
}

void Crawler::Dead( Event *ev )
{
	// make sure they don't die on the ceiling
	if(onceiling)
	{
		onceiling = false;
		setMoveType( MOVETYPE_STEP );
		groundentity = NULL;
	}

	Actor::Dead( ev );
}

inline qboolean Crawler::CanMoveTo( Vector pos )
{
	Vector min;
	trace_t trace;
	Vector start;
	Vector end;
	Vector s;

	if(onceiling)
		s = Vector( 0, 0, -STEPSIZE );
	else
		s = Vector( 0, 0, STEPSIZE );

	start = worldorigin + s;
	end = pos + s;
	trace = G_Trace( start, mins, maxs, end, this, edict->clipmask, "Crawler::CanMoveTo" );
	if(trace.fraction == 1)
	{
		return true;
	}
	return false;
}

qboolean Crawler::CanShootFrom( Vector pos,Entity *ent,qboolean usecurrentangles )
{
	int mask;
	Vector delta;
	Vector start;
	Vector end;
	float len;
	trace_t trace;
	Vehicle *v;
	Entity  *t;
	Vector ang;

	if ( !currentWeapon || !WithinDistance( ent, vision_distance ) )
	{
		if (!currentWeapon && !has_melee )
			return false;
	}

	if ( usecurrentangles )
	{
		Vector dir;

		start = pos + GunPosition() - worldorigin;
		end = ent->centroid;
		if(onceiling)
			end.z += ( ent->absmin.z - ent->centroid.z ) * 0.75f;
		else
			end.z += ( ent->absmax.z - ent->centroid.z ) * 0.75f;
		delta = end - start;
		ang = delta.toAngles();
		ang.x = -ang.x;
		ang.y = angles.y;
		len = delta.length();
		ang.AngleVectors( &dir, NULL, NULL );
		dir *= len;
		end = start + dir;
	}
	else
	{
		start = pos + GunPosition() - worldorigin;
		end = ent->centroid;
		if(onceiling)
			end.z += ( ent->absmin.z - ent->centroid.z ) * 0.75f;
		else
			end.z += ( ent->absmax.z - ent->centroid.z ) * 0.75f;
		delta = end - start;
		len = delta.length();
	}

	// check if we're too far away, or too close
	if ( currentWeapon )
	{
		if ( ( len > attack_range ) || ( len > currentWeapon->GetMaxRange() ) || ( len < currentWeapon->GetMinRange() ) )
		{
			return false;
		}
		mask = MASK_SHOT;
	}
	else
	{
		if ( ( len > attack_range ) || ( len > melee_range ) )
		{
			return false;
		}
		mask = MASK_PROJECTILE;
	}

	// shoot past the guy we're shooting at
	end += delta * 4;

#if 0
	if ( usecurrentangles )
	{
		G_DebugLine( start, end, 1, 0, 0, 1 );
	}
	else
	{
		G_DebugLine( start, end, 1, 1, 0, 1 );
	}
#endif

	// Check if he's visible
	trace = G_Trace( start, vec_zero, vec_zero, end, this, mask, "Actor::CanShootFrom" );
	if ( trace.startsolid )
	{
		return false;
	}

	// If we hit the guy we wanted, then shoot
	if ( trace.ent == ent->edict )
	{
		return true;
	}

	// if we hit a vehicle, check if the driver is someone we want to hit
	t = trace.ent->entity;
	if ( t && t->isSubclassOf( Vehicle ) )
	{
		v = ( Vehicle * )t;
		if ( ( v->Driver() == ent ) || IsEnemy( v->Driver() ) )
		{
			return true;
		}
		return false;
	}

	// If we hit someone else we don't like, then shoot
	if ( IsEnemy( t ) )
	{
		return true;
	}

	// if we hit something breakable, check if shooting it will
	// let us shoot someone.
	if ( t->isSubclassOf( Shatter ) ||
	     t->isSubclassOf( Object ) ||
	     t->isSubclassOf( DamageThreshold ) ||
	     t->isSubclassOf( ScriptModel ) )
	{
		trace = G_Trace( Vector( trace.endpos ), vec_zero, vec_zero, end, t, mask, "Actor::CanShootFrom 2" );
		if ( trace.startsolid )
		{
			return false;
		}

		// If we hit the guy we wanted, then shoot
		if ( trace.ent == ent->edict )
		{
			return true;
		}

		// If we hit someone else we don't like, then shoot
		if ( IsEnemy( trace.ent->entity ) )
		{
			return true;
		}

		// Forget it then
		return false;
	}

	return false;
}

//===============================================================

void Crawler::JumpToCeilingEvent( Event *ev )
{
	Event *e;

	if(deadflag)
	{
		return;
	}

	e = new Event( EV_Behavior_Args );
	e->SetSource( EV_FROM_SCRIPT );
	e->SetThread( ev->GetThread() );
	e->SetLineNumber( ev->GetLineNumber() );

	e->AddEntity( this );
	e->AddString( ev->GetToken( 1 ) );
	SetBehavior( new JumpToCeiling, e, ev->GetThread() );
}

// called when the crawler should actually
// move up towards the ceiling
void Crawler::OrientToCeilingEvent( Event *ev )
{
	onceiling = true;
	setMoveType( MOVETYPE_CEILINGSTEP );
	velocity.z = 300;
	groundentity = NULL;
}


//===============================================================

void Crawler::LikesCeilingEvent( Event *ev )
{
	likesceiling = true;
}

void Crawler::LikesFloorEvent( Event *ev )
{
	likesceiling = false;
}

void Crawler::CeilingHeightEvent( Event *ev )
{
	if(ev->NumArgs())
		ceilingheight = ev->GetInteger( 1 );
	else
		ceilingheight = 512;
}

//===============================================================

void Crawler::IfOnCeilingEvent( Event *ev )
{
	ScriptThread    *thread;

	thread = ev->GetThread();
	assert( thread );
	if(!thread)
	{
		return;
	}

	if(onceiling)
	{
		thread->ProcessCommandFromEvent( ev, 1 );
	}
}

void Crawler::IfOnFloorEvent( Event *ev )
{
	ScriptThread    *thread;

	thread = ev->GetThread();
	assert( thread );
	if(!thread)
	{
		return;
	}

	if(!onceiling)
	{
		thread->ProcessCommandFromEvent( ev, 1 );
	}
}

// check if it's ok to jump up to the ceiling
void Crawler::IfCeilingOKEvent( Event *ev )
{
	trace_t trace;
	Vector ceilingposition;
	ScriptThread    *thread;

	thread = ev->GetThread();
	assert( thread );
	if(!thread)
	{
		return;
	}

	if(onceiling)
		return;

	ceilingposition = origin + Vector( 0, 0, 1024 );
	trace = G_Trace( origin, mins, maxs, ceilingposition, this, edict->clipmask, "Crawler::IfCeilingOKEvent" );

	// can't reach the ceiling
	if(trace.fraction == 1)
		return;

	// don't go up to a non-BSP entity
	if(trace.ent && trace.ent->solid != SOLID_BSP)
		return;

	thread->ProcessCommandFromEvent( ev, 1 );
}

//===============================================================
// CRAWLER BEHAIVIORS
//===============================================================

CLASS_DECLARATION( Behavior, JumpToCeiling, NULL );

ResponseDef JumpToCeiling::Responses[] =
{
	{&EV_Behavior_Args, (Response)JumpToCeiling::SetArgs },
	{NULL, NULL}
};

JumpToCeiling::JumpToCeiling()
{
	jumpchance = 1.0;
}

void JumpToCeiling::SetArgs( Event *ev )
{
	jumpchance = ev->GetFloat( 2 );
	if(!jumpchance)
		jumpchance = 1.0;
}

void JumpToCeiling::ShowInfo( Actor &self )
{
	Behavior::ShowInfo( self );

	gi.printf( "\njumpchance : %f\n", jumpchance );
}

void JumpToCeiling::Begin( Actor &self )
{
	trace_t trace;
	Vector ceilingposition;

	// check if we want to jump
	if((((Crawler *)&self)->onceiling) || (G_Random() > jumpchance))
	{
		jumpchance = 0; // to make evaluate crap out
		return;
	}

	// check that the ceiling's ok
	ceilingposition = self.origin + Vector( 0, 0, 1024 );
	trace = G_Trace( self.origin, self.mins, self.maxs, ceilingposition, &self, self.edict->clipmask, "JumpToCeiling::Begin" );

	if((trace.fraction == 1) || (trace.ent && trace.ent->solid != SOLID_BSP) || self.HitSky( &trace ))
	{
		jumpchance = 0;
		return;
	}

	self.SetAnim( "ceilingjump" );
}

qboolean JumpToCeiling::Evaluate( Actor &self )
{
	trace_t trace;
	Vector ceilingposition;

	// sorry, we don't want to jump
	if(!jumpchance)
		return false;

	ceilingposition = self.origin;
	ceilingposition.z += STEPSIZE;
	trace = G_Trace( self.origin, self.mins, self.maxs, ceilingposition, &self, self.edict->clipmask, "JumpToCeiling::Evaluate" );

	// we should basically be on the ceiling now
	if(trace.fraction < 1)
		return false;

	return true;
}

void JumpToCeiling::End( Actor &self )
{
	if(((Crawler *)&self)->onceiling)
		self.SetAnim( "ceiling_idle" );
	else
		self.SetAnim( "idle" );
}

/****************************************************************************

   CrawlerFindEnemy Class Definition

****************************************************************************/

CLASS_DECLARATION( Behavior, CrawlerFindEnemy, NULL );

ResponseDef CrawlerFindEnemy::Responses[] =
{
	{&EV_Behavior_Args, (Response)CrawlerFindEnemy::SetArgs},
	{NULL, NULL}
};

void CrawlerFindEnemy::SetArgs( Event *ev )
{
	anim = ev->GetString( 2 );
	ceilinganim = ev->GetString( 3 );
}

void CrawlerFindEnemy::ShowInfo( Actor &self )
{
	Behavior::ShowInfo( self );

	if(lastceilingstate)
	{
		gi.printf( "\nceilingchase:\n" );
		ceilingchase.ShowInfo( self );
	}
	else
	{
		gi.printf( "\nchase:\n" );
		chase.ShowInfo( self );
	}

	gi.printf( "\nstate: %d\n", state );
	gi.printf( "nextsearch: %f\n", nextsearch );
	gi.printf( "anim: %s\n", anim.c_str() );
	gi.printf( "ceilinganim: %s\n", ceilinganim.c_str() );
}

void CrawlerFindEnemy::Begin( Actor &self )
{
	if(!anim.length())
	{
		anim = "run";
	}

	if(!ceilinganim.length())
	{
		ceilinganim = "ceiling_walk";
	}

	movegoal = NULL;
	lastSearchNode = NULL;
	lastceilingstate = ((Crawler *)&self)->onceiling;
	state = 0;
}

PathNode *CrawlerFindEnemy::FindClosestSightNode( Actor &self )
{
	int i;
	PathNode    *bestnode;
	float bestdist;
	PathNode *node;
	Vector delta;
	float dist;
	Vector pos;

	if ( self.currentEnemy )
	{
		pos = self.currentEnemy->worldorigin;
	}
	else
	{
		pos = self.worldorigin;
	}

	if(lastceilingstate)
		return NULL;

	bestnode = NULL;
	bestdist = 999999999; // greater than ( 8192 * sqr(2) ) ^ 2 -- maximum squared distance
	for( i = 0; i <= ai_maxnode; i++ )
	{
		node = AI_GetNode( i );
		if ( node && ( ( node->occupiedTime <= level.time ) || ( node->entnum != self.entnum ) ) )
		{
			// get the distance squared (faster than getting real distance)
			delta = node->worldorigin - pos;
			dist = delta * delta;
			if ( ( dist < bestdist ) && self.CanSeeFrom( node->worldorigin, self.currentEnemy ) )
			{
				bestnode = node;
				bestdist = dist;
			}
		}
	}

	return bestnode;

}

qboolean CrawlerFindEnemy::Evaluate( Actor &self )
{
	if ( !self.currentEnemy )
	{
		return false;
	}

	// check ceiling state
	if(((Crawler *)&self)->onceiling != lastceilingstate)
	{
		if(lastceilingstate)
		{
			ceilingchase.End( self );
		}
		else
		{
			chase.End( self );
		}

		lastceilingstate = ((Crawler *)&self)->onceiling;
		state = 0;
	}

	if ( nextsearch < level.time )
	{
		// check if we should search for the first time
		if ( !lastSearchNode )
		{
			state = 0;
		}
		else
		{
			// search less often if we're far away
			nextsearch = self.DistanceTo( self.currentEnemy ) * ( 1.0 / 512.0 );
			if ( nextsearch < 1 )
			{
				nextsearch = 1;
			}
			nextsearch += level.time;

			// don't search again if our enemy hasn't moved very far
			if ( !self.currentEnemy->WithinDistance( lastSearchPos, 256 ) )
			{
				state = 0;
			}
		}
	}

	switch( state )
	{
	case 0:
		// Searching for enemy
		if(lastceilingstate)
		{
			ceilingchase.Begin( self );
			lastSearchPos = self.currentEnemy->worldorigin;

			movegoal = NULL;
		}
		else
		{
			chase.Begin( self );
			lastSearchPos = self.currentEnemy->worldorigin;

			movegoal = PathManager.NearestNode( lastSearchPos, &self );
			if ( !movegoal )
			{
				movegoal = PathManager.NearestNode( lastSearchPos, &self, false );
			}
		}
		lastSearchNode = movegoal;
		if ( movegoal )
		{
			Path *path;
			FindEnemyPath find;
			PathNode *from;

			find.heuristic.self = &self;
			find.heuristic.setSize( self.size );
			find.heuristic.entnum = self.entnum;

			from = PathManager.NearestNode( self.worldorigin, &self );
			if ( ( from == movegoal ) && ( self.DistanceTo( from->worldorigin ) < 8 ) )
			{
				movegoal = NULL;
				from = NULL;
			}

			if ( from )
			{
				path = find.FindPath( from, movegoal );
				if ( path )
				{
					chase.SetGoal( movegoal );
					chase.SetPath( path );
				}
				else
				{
					movegoal = NULL;
				}
			}
		}

		if ( !movegoal )
		{
			if ( self.CanSee( self.currentEnemy ) || ( !self.currentEnemy->groundentity && !self.waterlevel ) )
			{
				if(lastceilingstate)
					ceilingchase.SetGoalPos( self.currentEnemy->worldorigin );
				else
					chase.SetGoalPos( self.currentEnemy->worldorigin );
			}
			else
			{
				// Couldn't find enemy
				// since we can't reach em
				// clear out enemy state
				//
				self.ClearEnemies();
				return false;
			}
		}

		// Found enemy, going to it
		if(lastceilingstate)
		{
			if(ceilinganim.length() && (ceilinganim != self.animname))
			{
				self.SetAnim( ceilinganim );
			}
		}
		else
		{
			if ( anim.length() && ( anim != self.animname ) )
			{
				self.SetAnim( anim );
			}
		}

		state = 1;

	case 1:
		if ( self.CanShoot( self.currentEnemy, false ) )
		{
			// Reached enemy
			if(lastceilingstate)
				ceilingchase.End( self );
			else
				chase.End( self );
			return false;
		}

		if(lastceilingstate)
		{
			if(!ceilingchase.Evaluate( self ))
			{
				state = 0;
				nextsearch = 0;
			}
		}
		else
		{
			if ( !chase.Evaluate( self ) )
			{
				state = 0;
				nextsearch = 0;
			}
		}
		break;
	}

	return true;
}

void CrawlerFindEnemy::End( Actor &self )
{
	if(lastceilingstate)
		ceilingchase.End( self );
	else
		chase.End( self );
}

/****************************************************************************

   CrawlerIdle Class Definition

****************************************************************************/

CLASS_DECLARATION( Behavior, CrawlerIdle, NULL );

ResponseDef CrawlerIdle::Responses[] =
{
	{ &EV_Behavior_Args,            ( Response )CrawlerIdle::SetArgs },
	{ NULL, NULL }
};

void CrawlerIdle::SetArgs( Event *ev )
{
	anim = ev->GetString( 2 );
	ceilinganim = ev->GetString( 3 );
}

void CrawlerIdle::ShowInfo( Actor &self )
{
	Behavior::ShowInfo( self );

	gi.printf( "\nnexttwitch : %f\n", nexttwitch );
	gi.printf( "anim : %s\n", anim.c_str() );
	gi.printf( "ceilinganim : %s\n", anim.c_str() );
}

void CrawlerIdle::Begin( Actor &self )
{
	self.currentEnemy = NULL;
	self.seenEnemy = false;
	nexttwitch = level.time + 10 + G_Random( 20 );
	lastceilingstate = ((Crawler *)&self)->onceiling;

	if(lastceilingstate)
	{
		if(ceilinganim.length())
		{
			self.SetAnim( ceilinganim );
		}
	}
	else
	{
		if ( anim.length() )
		{
			self.SetAnim( anim );
		}
	}
}

qboolean CrawlerIdle::Evaluate( Actor &self )
{
	if ( self.currentEnemy )
	{
		if ( self.DoAction( "sightenemy" ) )
		{
			self.seenEnemy = true;
			self.Chatter( "snd_sightenemy", 5 );
		}
		else
		{
			self.currentEnemy = NULL;
		}
		return true;
	}

	// check ceiling state
	if(lastceilingstate != ((Crawler *)&self)->onceiling)
	{
		lastceilingstate = ((Crawler *)&self)->onceiling;

		if(lastceilingstate)
		{
			if(ceilinganim.length())
			{
				self.SetAnim( ceilinganim );
			}
		}
		else
		{
			if ( anim.length() )
			{
				self.SetAnim( anim );
			}
		}
	}

	if ( nexttwitch < level.time )
	{
		self.chattime += 10;
		self.DoAction( "twitch" );
		return true;
	}
	else
	{
		self.Chatter( "snd_idle", 1 );
	}

	return true;
}

void CrawlerIdle::End( Actor &self )
{}

/****************************************************************************

   CrawlerStrafeTo Class Definition

****************************************************************************/

CLASS_DECLARATION( Behavior, CrawlerStrafeTo, NULL );

ResponseDef CrawlerStrafeTo::Responses[] =
{
	{ &EV_Behavior_Args,            ( Response )CrawlerStrafeTo::SetArgs },
	{ NULL, NULL }
};

void CrawlerStrafeTo::ShowInfo( Actor &self )
{
	Behavior::ShowInfo( self );

	gi.printf( "goal: ( %f, %f, %f )\n", goal.x, goal.y, goal.z );
	gi.printf( "fail: %d\n", fail );

	gi.printf( "\nseek:\n" );
	seek.ShowInfo( self );
}

void CrawlerStrafeTo::SetArgs( Event *ev )
{
	Entity *ent;

	if ( ev->IsVectorAt( 2 ) )
	{
		goal = ev->GetVector( 2 );
	}
	else
	{
		movegoal = AI_FindNode( ev->GetString( 2 ) );
		if ( movegoal )
		{
			goal = movegoal->worldorigin;
		}
		else
		{
			ent = ev->GetEntity( 2 );
			if ( ent )
			{
				goal = ent->worldorigin;
			}
		}
	}
}

void CrawlerStrafeTo::Begin( Actor &self )
{
	Vector delta;
	float dot;

	seek.Begin( self );

	lastceilingstate = ((Crawler *)&self)->onceiling;

	delta = goal - self.worldorigin;
	dot = delta * self.orientation[ 1 ];

	if(lastceilingstate)
	{
		if ( dot < 0 )
		{
			self.SetAnim( "ceiling_step_right" );
		}
		else
		{
			self.SetAnim( "ceiling_step_left" );
		}
	}
	else
	{
		if ( dot < 0 )
		{
			self.SetAnim( "step_right" );
		}
		else
		{
			self.SetAnim( "step_left" );
		}
	}

	fail = 0;
}

qboolean CrawlerStrafeTo::Evaluate( Actor &self )
{
	seek.SetMaxSpeed( self.movespeed );
	seek.SetPosition( self.worldorigin );
	seek.SetDir( self.movedir );
	seek.SetTargetPosition( goal );

	if ( !seek.Evaluate( self ) )
	{
		return false;
	}

	// stop if ceiling state changes
	if(lastceilingstate != ((Crawler *)&self)->onceiling)
	{
		lastceilingstate = ((Crawler *)&self)->onceiling;
		return false;
	}

	self.Accelerate( seek.steeringforce );

	// prevent him from trying to strafing forever if he's stuck
	if ( self.lastmove != STEPMOVE_OK )
	{
		if ( fail )
		{
			return false;
		}

		fail++;
	}
	else
	{
		fail = 0;
	}

	return true;
}

void CrawlerStrafeTo::End( Actor &self )
{
	seek.End( self );
	if(((Crawler *)&self)->onceiling)
		self.SetAnim( "ceiling_idle" );
	else
		self.SetAnim( "idle" );
}

/****************************************************************************

   CeilingStrafeAttack Class Definition

****************************************************************************/

CLASS_DECLARATION( Behavior, CeilingStrafeAttack, NULL );

ResponseDef CeilingStrafeAttack::Responses[] =
{
	{ NULL, NULL }
};

void CeilingStrafeAttack::ShowInfo( Actor &self )
{
	Behavior::ShowInfo( self );

	gi.printf( "\nturn:\n" );
	turn.ShowInfo( self );

	gi.printf( "\nstate: %d\n", state );
}

void CeilingStrafeAttack::Begin( Actor &self )
{
	state = 0;
}

qboolean CeilingStrafeAttack::Evaluate( Actor &self )
{
	int num;
	Vector delta;
	Vector left;
	Vector pos;

	if ( !self.currentEnemy )
	{
		return false;
	}

	// stop if not on the ceiling
	if(!((Crawler *)&self)->onceiling)
	{
		return false;
	}

	switch( state )
	{
	case 0:
		delta = self.currentEnemy->worldorigin - self.worldorigin;
		turn.SetDirection( delta.toYaw() );
		turn.Begin( self );
		state = 1;
		break;

	case 1:
		if ( turn.Evaluate( self ) )
		{
			return true;
		}

		turn.End( self );
		state = 2;

	case 2:
		delta = self.currentEnemy->worldorigin - self.worldorigin;
		left.x = -delta.y;
		left.y = delta.x;
		left.normalize();

		if ( G_Random( 10 ) < 5 )
		{
			num = gi.Anim_Random( self.edict->s.modelindex, "ceiling_step_left" );
			if ( num != -1 )
			{
				gi.Anim_Delta( self.edict->s.modelindex, num, delta.vec3() );
				delta *= self.edict->s.scale;
				pos = self.worldorigin + left * delta.length();
				if ( ((Crawler *)&self)->CanMoveTo( pos ) && self.CanShootFrom( pos, self.currentEnemy, false ) )
				{
					self.SetAnim( "ceiling_step_left", EV_Actor_FinishedBehavior );
					state = 3;
					return true;
				}
			}

			num = gi.Anim_Random( self.edict->s.modelindex, "ceiling_step_right" );
			if ( num != -1 )
			{
				gi.Anim_Delta( self.edict->s.modelindex, num, delta.vec3() );
				delta *= self.edict->s.scale;
				pos = self.worldorigin - left * delta.length();
				if ( ((Crawler *)&self)->CanMoveTo( pos ) && self.CanShootFrom( pos, self.currentEnemy, false ) )
				{
					self.SetAnim( "ceiling_step_right", EV_Actor_FinishedBehavior );
					state = 3;
					return true;
				}
			}
		}
		else
		{
			num = gi.Anim_Random( self.edict->s.modelindex, "ceiling_step_right" );
			if ( num != -1 )
			{
				gi.Anim_Delta( self.edict->s.modelindex, num, delta.vec3() );
				delta *= self.edict->s.scale;
				pos = self.worldorigin - left * delta.length();
				if ( ((Crawler *)&self)->CanMoveTo( pos ) && self.CanShootFrom( pos, self.currentEnemy, false ) )
				{
					self.SetAnim( "ceiling_step_right", EV_Actor_FinishedBehavior );
					state = 3;
					return true;
				}
			}

			num = gi.Anim_Random( self.edict->s.modelindex, "ceiling_step_left" );
			if ( num != -1 )
			{
				gi.Anim_Delta( self.edict->s.modelindex, num, delta.vec3() );
				delta *= self.edict->s.scale;
				pos = self.worldorigin + left * delta.length();
				if ( ((Crawler *)&self)->CanMoveTo( pos ) && self.CanShootFrom( pos, self.currentEnemy, false ) )
				{
					self.SetAnim( "ceiling_step_left", EV_Actor_FinishedBehavior );
					state = 3;
					return true;
				}
			}

		}

		return false;
		break;
	}

	return true;
}

void CeilingStrafeAttack::End( Actor &self )
{
	turn.End( self );
	if(((Crawler *)&self)->onceiling)
		self.SetAnim( "ceiling_idle" );
	else
		self.SetAnim( "idle" );
}
