sin-2015/crawler.cpp
1999-04-22 00:00:00 +00:00

1426 lines
29 KiB
C++

/*
================================================================
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");
}