//----------------------------------------------------------------------------- // // $Logfile:: /Quake 2 Engine/Sin/code/game/behavior.cpp $ // $Revision:: 117 $ // $Author:: Markd $ // $Date:: 11/20/98 7:17p $ // // Copyright (C) 1998 by Ritual Entertainment, Inc. // All rights reserved. // // This source may not be distributed and/or modified without // expressly written permission by Ritual Entertainment, Inc. // // $Log:: /Quake 2 Engine/Sin/code/game/behavior.cpp $ // // 117 11/20/98 7:17p Markd // Fixed Hide behavior // // 116 11/18/98 8:53p Markd // Made nearestnode check for one without bounding box for thrall // // 115 11/18/98 7:47p Markd // made it so FindNearEnemy doesn't use bounding box mainly for THrall // // 114 11/15/98 8:02p Jimdose // Swimming monsters no longer choose random directions that are in air // Flying monsters no longer choose random directions that are in water // Actors using FindEnemy no longer get stuck when the nearest node to them is // the same as the nearest node to the enemy. // // 113 11/15/98 1:24a Jimdose // monsters don't play snd_sightenemy now unless DoAction( "sightenemy" ) // succeeds. // // 112 11/13/98 3:29p Markd // Fixed some ChooseRandomDirection stuff // // 111 11/07/98 8:14p Jimdose // Made it so Hide and FleeAndRemove ensure the guy either runs away or // crouches down // // 110 10/27/98 7:48p Jimdose // Added PlayAnimSeekEnemy // // 109 10/27/98 3:52a Markd // put in state = 0 if can't resolve FindEnemy // // 108 10/27/98 12:42a Jimdose // added animprefix to AimAndShoot // // 107 10/26/98 2:18p Markd // Put in last_jump_time support // // 106 10/25/98 4:56a Jimdose // Fixed bug with chase where guys would do a spin at the end of a path // Changed wait time in opendoor // made FindEnemy check if the enemy has moved far before doing another search // // 105 10/23/98 4:44a Markd // made jump early exit if no land animation // // 104 10/23/98 12:30a Markd // Fixed up Flee behavior // // 103 10/22/98 11:36p Jimdose // Added GetCloseToEnemy // Changed FindEnemy's seektime // // 102 10/20/98 11:29p Markd // guys won't get stuck aiming anymore // // 101 10/20/98 4:05a Markd // made snd_idle more frequent in wander // // 100 10/20/98 3:52a Markd // Added idle chatter to Wander // // 99 10/20/98 2:37a Markd // Put in ClearEnemies and JumpTo modifications // // 98 10/19/98 12:07a Jimdose // made all code use fast checks for inheritance (no text lookups when // possible) // isSubclassOf no longer requires ::_classinfo() // // 97 10/18/98 4:11p Markd // Made FindEnemy not use FindClosestSightNode // // 96 10/18/98 12:34a Markd // Added randomness to AimAndShoot and AimAndMelee // // 95 10/17/98 11:01p Markd // Added code to AimAndShoot // // 94 10/17/98 4:43p Markd // Added curious time and xydist to Investigate // // 93 10/17/98 4:05p Markd // replaced OpenDOor initial trace with vec_zero's again // // 92 10/17/98 3:30p Markd // Fixed OpenDoor code not taking into account bounding boxes // // 91 10/17/98 12:34a Markd // Commented out warning for the time being // // 90 10/16/98 7:20p Markd // put in numshots for AimAndMelee, also tweaked PlayAnim warning reporting // // 89 10/14/98 11:53p Markd // If PlayAnim doesn't resolve an anim, make sure and post and EndBehavior // // 88 10/14/98 9:02p Markd // Made the Jump behavior more robust, and added landing // // 87 10/14/98 5:20p Markd // Updated Jump behavior // // 86 10/14/98 2:17a Markd // Took out can_melee code // // 85 10/14/98 1:27a Markd // Put in can_melee in FindEnemy // // 84 10/13/98 11:13p Markd // Scaled anim_deltas and fixed closeattacks not waiting long enough to reach // player // // 83 10/13/98 9:11p Markd // Fixed RandomChooseDirection bug // // 82 10/13/98 7:38p Markd // Created behaviors for Wander, Fly, FLyCloseAttack, and WanderCloseAttack // // 81 10/13/98 12:25a Markd // Fixed swimming creatures AGAIN! // // 80 10/10/98 6:08p Markd // Added FleeAndRemove, FindFlee, fixed max_ainode bug // // 79 10/10/98 5:01p Markd // Changed trace masks to edict->clipmasks // // 78 10/10/98 3:27p Markd // Fixed FindEnemy behavior // // 77 10/09/98 11:57p Markd // Fixed up melee close range attacks // // 76 10/06/98 5:25p Markd // Added ForceAction, hopefully fixed some sightEnemy issues // // 75 10/05/98 10:32p Markd // Made Strafe Attack use a random strafe direction, also tweaked // SwimCloseAttack // // 74 10/05/98 4:36p Markd // Clear out seenEnemy // // 73 10/05/98 3:04p Markd // Added miss detection to AimAndShoot // // 72 10/05/98 12:29a Jimdose // moved angledist to q_shared // // 71 10/04/98 7:49p Markd // When done GotoPathNode, play the idle animation if no animation exists at // the movegoal // // 70 10/04/98 5:32p Markd // Fixed ChooseRandomDirection problems // // 69 10/04/98 3:47p Markd // fixed swimming getting stuck // // 68 10/04/98 3:01p Markd // fixed swimming behavior // // 67 10/03/98 7:19p Markd // Added SwimCloseAttack and Melee behaviors // // 66 10/01/98 8:00p Markd // Added AimAndMelee // // 65 10/01/98 4:14p Jimdose // Improved Aim::Evaluate so that it properly targets partially obscured guys. // // 64 9/22/98 4:16a Jimdose // Made GotoPathNode check new paths every 4 seconds instead of every 20. // // 63 9/22/98 1:54a Jimdose // Completed ShowInfo for all behaviors // Added StrafeTo // // 62 9/18/98 10:56p Jimdose // Separated steering behaviors into their own file // Made AimAndShoot exit when enemy is killed while aiming // // 61 9/14/98 5:29p Jimdose // Added SetPathRate to Chase so that actors can choose how often they search // for a new path. // NearestNode now requires that you pass in the entity that is going to use // the path. // // 60 8/31/98 7:48p Jimdose // Removed unused variables from FollowPath // // 59 8/31/98 7:46p Jimdose // Simplified FollowPath // // 58 8/29/98 9:39p Jimdose // Added call info to G_Trace // // 57 8/26/98 11:13p Jimdose // Added StrafeAttack // // 56 8/24/98 6:57p Jimdose // Moved hueristics for path finding to actor // Modified how closely actors follow paths // // 55 8/19/98 8:47p Jimdose // Added Jump behavior // // 54 8/19/98 7:58p Jimdose // took out debug print // starting jump node support // // 53 8/19/98 4:00p Jimdose // Made PickupAndThrow exit if it has a NULL pickup_target // // 52 8/19/98 2:31p Jimdose // Changed Chase so that if the target is an entity, it ends when it touches // that entity. // // 51 8/18/98 10:00p Jimdose // Actors follow paths tighter when near doors // // 50 8/15/98 11:18p Jimdose // Extended reach for opening doors // // 49 8/14/98 8:30p Jimdose // TurnTo now doesn't return false when turning to ents. (need to change // eventually). // FindEnemy now searches based on CanShoot rather than CanSee // // 48 8/14/98 6:26p Jimdose // Got rid of decelleration for steering // Added GoalEnt to GotoPathNode // Added check for arrival to seek. Reduces running in circles // // 47 8/12/98 2:12p Markd // fixed initialization of bestnode parameter in FindWanderNode // // 46 8/10/98 6:51p Aldie // Modified wander behavior a little to make it less intensive // // 45 8/09/98 5:49p Markd // Rewrote ThrowObjects pickup and throw // // 44 8/08/98 8:39p Markd // fixed throwing bug // // 43 8/08/98 8:41p Jimdose // Fixed oscillating problem in Aim // // 42 8/08/98 8:24p Markd // working on pickupandthrow behavior // // 41 8/07/98 8:47p Markd // working on pickupandthrow // // 40 8/07/98 6:01p Jimdose // Rewrote AimAndShoot // // 39 8/06/98 6:57p Jimdose // Removed FireFromCover (now done in script) // Added SetArgs to AimAndShoot // // 38 8/05/98 7:18p Jimdose // Renamed states // // 37 8/03/98 7:58p Aldie // Added a first attempt at wander behavior // // 36 7/26/98 11:43a Jimdose // Tweaked hiding behavior // // 35 7/26/98 6:39a Jimdose // Avoiding occupied nodes now works // // 34 7/26/98 3:48a Jimdose // Modified aim based on skill // // 33 7/26/98 2:45a Jimdose // Changed pathway_t structure // // 32 7/25/98 2:09a Jimdose // FindCoverNode now uses the closest cover node to search from, rather than // choosing a random node and letting the search algorithm find a closer one. // // 31 7/22/98 10:51p Jimdose // Added repel behavior // // 30 7/21/98 4:14p Jimdose // disabled the - operator on Vectors // // 29 7/19/98 9:19p Jimdose // Fixed bug in FindCoverNode where the actor would try searching for a path // to every cover node in the level. // // 28 7/08/98 9:00p Jimdose // Made OpenDoor script callable // // 27 7/06/98 1:06p Jimdose // working on ai // // 26 6/30/98 6:03p Jimdose // Chase and GotoPathNode now can use vectors for goals // Investigate no longer needs currentEnemy to be set // // 25 6/25/98 8:11p Jimdose // Changed Idle stuff // // 24 6/24/98 4:26p Jimdose // Fixed bugs in TurnTo // // 23 6/17/98 9:59p Jimdose // Changed path radius // // 22 6/17/98 3:03p Markd // Changed NumArgs back to previous behavior // // 21 6/17/98 1:19a Jimdose // Removed TargetEnemies. Moved it back into actor // Working on weapon firing and sighting enemies // // 20 6/15/98 10:47p Jimdose // Added Hide, and CoverFire // // 19 6/13/98 8:23p Jimdose // Added FindCover // // 18 6/11/98 12:44a Jimdose // behaviors now get info from the script at startup // // 17 6/10/98 10:25p Jimdose // Added priority based state system // // 16 6/09/98 5:34p Jimdose // made flee better at finding a random node. // // 15 6/09/98 4:18p Jimdose // worked on ai // // 14 6/04/98 10:48p Jimdose // Fixed a bunch of things that got broken just in time for E3. Paths and // scripting actually work now. // // 13 6/03/98 5:43p Jimdose // Fixed spelling of behavior. :) // // 12 5/27/98 7:12a Jimdose // ai ai ai // // 11 5/27/98 6:39a Jimdose // working on ai // // 10 5/27/98 5:11a Jimdose // working on ai // // 9 5/25/98 5:31p Jimdose // Pathnodes are no longer a subclass of Entity. This was done to save on // edicts // // 8 5/25/98 1:06a Jimdose // Added chatter // // 7 5/24/98 1:02a Jimdose // added Investigate // // 6 5/23/98 6:27p Jimdose // improved steering code // // 5 5/22/98 9:45p Jimdose // Disabled debug lines for forces // // 4 5/22/98 9:36p Jimdose // AI Scripting works again (somewhat) // // 3 5/20/98 6:36p Jimdose // Working on ai // // 2 5/18/98 8:15p Jimdose // Created file // // DESCRIPTION: // Behaviors used by the AI. // #include "g_local.h" #include "behavior.h" #include "actor.h" #include "doors.h" #include "object.h" Event EV_Behavior_Args( "args" ); Event EV_Behavior_AnimDone( "animdone" ); /**************************************************************************** Behavior Class Definition ****************************************************************************/ CLASS_DECLARATION( Listener, Behavior, NULL ); ResponseDef Behavior::Responses[] = { { NULL, NULL } }; Behavior::Behavior() { } void Behavior::ShowInfo ( Actor &self ) { if ( movegoal ) { gi.printf( "movegoal: ( %f, %f, %f ) - '%s'\n", movegoal->worldorigin.x, movegoal->worldorigin.y, movegoal->worldorigin.z, movegoal->targetname.c_str() ); } else { gi.printf( "movegoal: NULL\n" ); } } void Behavior::Begin ( Actor &self ) { } qboolean Behavior::Evaluate ( Actor &self ) { return false; } void Behavior::End ( Actor &self ) { } /**************************************************************************** Idle Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, Idle, NULL ); ResponseDef Idle::Responses[] = { { &EV_Behavior_Args, ( Response )Idle::SetArgs }, { NULL, NULL } }; void Idle::SetArgs ( Event *ev ) { anim = ev->GetString( 2 ); } void Idle::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nnexttwitch : %f\n", nexttwitch ); gi.printf( "anim : %s\n", anim.c_str() ); } void Idle::Begin ( Actor &self ) { self.currentEnemy = NULL; self.seenEnemy = false; nexttwitch = level.time + 10 + G_Random( 30 ); if ( anim.length() ) { self.SetAnim( anim ); } } qboolean Idle::Evaluate ( Actor &self ) { if ( self.currentEnemy ) { if ( self.DoAction( "sightenemy" ) ) { self.seenEnemy = true; self.Chatter( "snd_sightenemy", 5 ); } else { self.currentEnemy = NULL; } return true; } if ( nexttwitch < level.time ) { self.chattime += 10; self.DoAction( "twitch" ); return true; } else { self.Chatter( "snd_idle", 1 ); } return true; } void Idle::End ( Actor &self ) { } /**************************************************************************** Aim Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, Aim, NULL ); ResponseDef Aim::Responses[] = { { NULL, NULL } }; void Aim::SetTarget ( Entity *ent ) { target = ent; } void Aim::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nseek:\n" ); seek.ShowInfo( self ); if ( target ) { gi.printf( "\ntarget : #%d '%s'\n", target->entnum, target->targetname.c_str() ); } else { gi.printf( "\ntarget : NULL\n" ); } } void Aim::Begin ( Actor &self ) { seek.Begin( self ); } qboolean Aim::Evaluate ( Actor &self ) { Vector dir; Vector ang; Vector pos; if ( !target ) { return false; } // // get my gun pos // pos = self.GunPosition(); ang = self.MyGunAngles( pos, false ); // // invert PITCH // ang[ PITCH ] = -ang[ PITCH ]; ang.AngleVectors( &dir, NULL, NULL ); seek.SetTargetPosition( target->centroid ); seek.SetTargetVelocity( target->velocity ); seek.SetPosition( self.centroid ); seek.SetDir( dir ); seek.SetMaxSpeed( 1400 + skill->value * 600 ); seek.Evaluate( self ); if ( ( fabs( seek.steeringforce.y ) > 5 ) && ( self.enemyRange > RANGE_MELEE ) ) { seek.steeringforce.y *= 2; } self.Accelerate( seek.steeringforce ); if ( seek.steeringforce.y < 0.25f ) { // dead on return false; } return true; } void Aim::End ( Actor &self ) { seek.End( self ); } /**************************************************************************** FireOnSight Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, FireOnSight, NULL ); ResponseDef FireOnSight::Responses[] = { { &EV_Behavior_Args, ( Response )FireOnSight::SetArgs }, { NULL, NULL } }; void FireOnSight::SetArgs ( Event *ev ) { if ( ev->NumArgs() > 1 ) { anim = ev->GetString( 1 ); } } void FireOnSight::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nchase:\n" ); chase.ShowInfo( self ); gi.printf( "\naim:\n" ); aim.ShowInfo( self ); gi.printf( "\nmode : %d\n", mode ); gi.printf( "anim : %s\n", anim.c_str() ); } void FireOnSight::Begin ( Actor &self ) { mode = 0; if ( !anim.length() ) { anim = "run"; } } qboolean FireOnSight::Evaluate ( Actor &self ) { if ( !self.currentEnemy || self.currentEnemy->deadflag || self.currentEnemy->health <= 0 ) { return false; } switch( mode ) { case 0 : // Start chasing self.SetAnim( anim ); chase.Begin( self ); mode = 1; case 1 : // Chasing if ( self.WeaponReady() && self.CanShoot( self.currentEnemy, false ) ) { chase.End( self ); self.SetAnim( "readyfire" ); aim.Begin( self ); mode = 2; break; } else { self.Chatter( "snd_pursuit", 1 ); } chase.SetTarget( self.currentEnemy ); chase.Evaluate( self ); break; case 2 : // Aiming aim.SetTarget( self.currentEnemy ); aim.Evaluate( self ); if ( self.WeaponReady() && self.CanShoot( self.currentEnemy, true ) ) { self.Chatter( "snd_inmysights", 5 ); self.SetAnim( "fire" ); mode = 3; } else if ( !self.WeaponReady() || !self.CanShoot( self.currentEnemy, false ) ) { aim.End( self ); mode = 0; break; } break; case 3 : // Fire aim.SetTarget( self.currentEnemy ); aim.Evaluate( self ); if ( !self.CanShoot( self.currentEnemy, true ) ) { self.SetAnim( "aim" ); mode = 2; } else { self.Chatter( "snd_attacktaunt", 4 ); } break; } return true; } void FireOnSight::End ( Actor &self ) { chase.End( self ); aim.End( self ); } /**************************************************************************** TurnTo Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, TurnTo, NULL ); ResponseDef TurnTo::Responses[] = { { NULL, NULL } }; TurnTo::TurnTo() { dir = Vector( 1, 0, 0 ); mode = 0; ent = NULL; yaw = 0; } void TurnTo::SetDirection ( float yaw ) { Vector ang; ang = Vector( 0, yaw, 0 ); this->yaw = anglemod( yaw ); ang.AngleVectors( &dir, NULL, NULL ); mode = 1; } void TurnTo::SetTarget ( Entity *ent ) { this->ent = ent; mode = 2; } void TurnTo::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nseek:\n" ); seek.ShowInfo( self ); if ( ent ) { gi.printf( "\nent: #%d '%s'\n", ent->entnum, ent->targetname.c_str() ); } else { gi.printf( "\nent: NULL\n" ); } gi.printf( "dir: ( %f, %f, %f )\n", dir.x, dir.y, dir.z ); gi.printf( "yaw: %f\n", yaw ); gi.printf( "mode: %d\n", mode ); } void TurnTo::Begin ( Actor &self ) { seek.Begin( self ); } qboolean TurnTo::Evaluate ( Actor &self ) { Vector delta; float ang; switch( mode ) { case 1 : ang = angledist( yaw - self.angles.yaw() ); if ( fabs( ang ) < 1 ) { self.Accelerate( Vector( 0, ang, 0 ) ); return false; } seek.SetTargetPosition( self.worldorigin + dir ); seek.SetTargetVelocity( vec_zero ); break; case 2 : if ( !ent ) { return false; } delta = ent->worldorigin - self.worldorigin; yaw = delta.toYaw(); //if ( self.angles.yaw() == yaw ) // { // return false; // } seek.SetTargetPosition( ent->worldorigin ); seek.SetTargetVelocity( vec_zero ); break; default : return false; } seek.SetPosition( self.worldorigin ); seek.SetDir( self.movedir ); seek.SetMaxSpeed( self.movespeed ); seek.Evaluate( self ); //seek.DrawForces(); self.Accelerate( seek.steeringforce ); return true; } void TurnTo::End ( Actor &self ) { seek.End( self ); } /**************************************************************************** GotoPathNode Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, GotoPathNode, NULL ); ResponseDef GotoPathNode::Responses[] = { { &EV_Behavior_Args, ( Response )GotoPathNode::SetArgs }, { NULL, NULL } }; GotoPathNode::GotoPathNode() { usevec = false; movegoal = NULL; goal = vec_zero; goalent = NULL; } void GotoPathNode::SetArgs ( Event *ev ) { anim = ev->GetString( 2 ); if ( ev->IsVectorAt( 3 ) ) { goal = ev->GetVector( 3 ); usevec = true; } else { usevec = false; movegoal = AI_FindNode( ev->GetString( 3 ) ); if ( !movegoal ) { goalent = ev->GetEntity( 3 ); } } } void GotoPathNode::SetGoal ( PathNode *node ) { usevec = false; movegoal = node; } void GotoPathNode::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nturnto:\n" ); turnto.ShowInfo( self ); gi.printf( "\nchase:\n" ); chase.ShowInfo( self ); gi.printf( "\nstate: %d\n", state ); gi.printf( "usevec: %d\n", usevec ); gi.printf( "time: %f\n", time ); gi.printf( "anim: %s\n", anim.c_str() ); if ( goalent ) { gi.printf( "\ngoalent: #%d '%s'\n", goalent->entnum, goalent->targetname.c_str() ); } else { gi.printf( "\ngoalent: NULL\n" ); } gi.printf( "goal: ( %f, %f, %f )\n", goal.x, goal.y, goal.z ); } void GotoPathNode::Begin ( Actor &self ) { state = 0; chase.Begin( self ); turnto.Begin( self ); if ( goalent ) { chase.SetTarget( goalent ); } else if ( movegoal ) { chase.SetGoal( movegoal ); } else { chase.SetGoalPos( goal ); } // don't check for new paths as often chase.SetPathRate( 4 ); if ( anim.length() ) { self.SetAnim( anim ); } } qboolean GotoPathNode::Evaluate ( Actor &self ) { float yaw; if ( !usevec && !goalent && !movegoal ) { return false; } switch( state ) { case 0 : if ( chase.Evaluate( self ) ) { break; } state = 1; self.SetAnim( "idle" ); // cascade down to case 1 case 1 : if ( !movegoal ) { return false; } if ( movegoal->setangles ) { yaw = movegoal->worldangles.yaw(); turnto.SetDirection( yaw ); if ( turnto.Evaluate( self ) ) { break; } } if ( movegoal->animname == "" ) { self.SetAnim( "idle" ); return false; } self.SetAnim( movegoal->animname, EV_Actor_FinishedBehavior ); state = 2; break; case 2 : break; } return true; } void GotoPathNode::End ( Actor &self ) { chase.End( self ); } /**************************************************************************** Investigate Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, Investigate, NULL ); ResponseDef Investigate::Responses[] = { { &EV_Behavior_Args, ( Response )Investigate::SetArgs }, { NULL, NULL } }; void Investigate::SetArgs ( Event *ev ) { Entity *ent; Actor *self; ent = ev->GetEntity( 1 ); if ( ent && ent->isSubclassOf( Actor ) ) { self = ( Actor * )ent; } anim = ev->GetString( 2 ); goal = ev->GetVector( 3 ); } void Investigate::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nchase:\n" ); chase.ShowInfo( self ); gi.printf( "\nanim: %s\n", anim.c_str() ); gi.printf( "curioustime: %f\n", curioustime ); gi.printf( "goal: ( %f, %f, %f )\n", goal.x, goal.y, goal.z ); } void Investigate::Begin ( Actor &self ) { // // we are only interested for about 10 seconds, if we can't get there, lets go back to what we were doing // curioustime = level.time + 10; self.Chatter( "snd_investigate", 10 ); chase.Begin( self ); chase.SetGoalPos( goal ); // Don't allow guys to change their anim if we're already close enough to the goal if ( !Done( self ) && anim.length() ) { self.SetAnim( anim ); } } qboolean Investigate::Done ( Actor &self ) { Vector delta; float xydist; if ( curioustime < level.time ) { return true; } if ( self.CanSeeEnemyFrom( self.worldorigin ) ) { return true; } if ( self.lastmove == STEPMOVE_STUCK ) { return true; } delta = goal - self.worldorigin; // // get rid of Z variance // delta[ 2 ] = 0; xydist = delta.length(); if ( xydist < 100 ) { return true; } return false; } qboolean Investigate::Evaluate ( Actor &self ) { if ( Done( self ) || !chase.Evaluate( self ) ) { return false; } return true; } void Investigate::End ( Actor &self ) { chase.End( self ); } /**************************************************************************** Flee Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, Flee, NULL ); ResponseDef Flee::Responses[] = { { &EV_Behavior_Args, ( Response )Flee::SetArgs }, { NULL, NULL } }; void Flee::SetArgs ( Event *ev ) { anim = ev->GetString( 2 ); } void Flee::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nfollow:\n" ); follow.ShowInfo( self ); if ( path ) { gi.printf( "\npath : ( %f, %f, %f ) to ( %f, %f, %f )\n", path->Start()->worldorigin.x, path->Start()->worldorigin.y, path->Start()->worldorigin.z, path->End()->worldorigin.x, path->End()->worldorigin.y, path->End()->worldorigin.z ); } else { gi.printf( "\npath : NULL\n" ); } gi.printf( "\navoid:\n" ); avoid.ShowInfo( self ); gi.printf( "\navoidtime: %f\n", avoidtime ); gi.printf( "anim: %s\n", anim.c_str() ); } void Flee::Begin ( Actor &self ) { follow.Begin( self ); avoid.AvoidWalls( false ); avoid.Begin( self ); avoidtime = 0; path = NULL; if ( anim.length() ) { self.SetAnim( anim ); } } qboolean Flee::Evaluate ( Actor &self ) { PathNode *node; int i; self.Chatter( "snd_panic", 3 ); if ( path && follow.DoneWithPath( self ) ) { path = NULL; if ( !self.currentEnemy || !self.CanSee( self.currentEnemy ) ) { return false; } } if ( !path ) { for( i = 0; i < 5; i++ ) { node = AI_GetNode( ( int )G_Random( ai_maxnode + 1 ) ); if ( node ) { break; } } if ( node ) { path = follow.SetPath( self, self.worldorigin, node->worldorigin ); } else { return false; } } follow.SetPosition( self.worldorigin ); follow.SetDir( self.movedir ); follow.SetMaxSpeed( self.movespeed ); follow.Evaluate( self ); if ( avoidtime < level.time ) { avoidtime = level.time + 0.4; avoid.SetMaxSpeed( self.movespeed ); avoid.SetPosition( self.worldorigin ); avoid.SetDir( self.movedir ); avoid.Evaluate( self ); follow.steeringforce += avoid.steeringforce; } self.Accelerate( follow.steeringforce ); return true; } void Flee::End ( Actor &self ) { avoid.End( self ); follow.End( self ); path = NULL; } /**************************************************************************** OpenDoor Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, OpenDoor, NULL ); ResponseDef OpenDoor::Responses[] = { { NULL, NULL } }; OpenDoor::OpenDoor() { usedir = false; } void OpenDoor::SetArgs ( Event *ev ) { if ( ev->NumArgs() > 1 ) { dir = ev->GetVector( 2 ); //usedir = true; } } void OpenDoor::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\ntime: %f\n", time ); gi.printf( "endtime: %f\n", endtime ); gi.printf( "usedir: %d\n", usedir ); gi.printf( "dir: ( %f, %f, %f )\n", dir.x, dir.y, dir.z ); } void OpenDoor::Begin ( Actor &self ) { Event *e; trace_t trace; Entity *ent; Vector pos; Vector end; endtime = 0; pos = self.worldorigin + self.eyeposition; if ( usedir ) { end = pos + dir; } else { end = pos + Vector( self.orientation[ 0 ] ) * 64; } trace = G_Trace( pos, vec_zero, vec_zero, end, &self, self.edict->clipmask, "OpenDoor 1" ); ent = trace.ent->entity; if ( ent && ent->isSubclassOf( Door ) ) { self.SetAnim( "idle" ); time = level.time + 0.1; endtime = time + 1; e = new Event( EV_Use ); e->AddEntity( &self ); ent->ProcessEvent( e ); } } qboolean OpenDoor::Evaluate ( Actor &self ) { trace_t trace; Vector pos; if ( level.time > endtime ) { return false; } if ( time < level.time ) { pos = self.worldorigin + self.eyeposition; trace = G_Trace( pos, self.mins, self.maxs, pos + Vector( self.orientation[ 0 ] ) * 32, &self, self.edict->clipmask, "OpenDoor 2" ); if ( trace.fraction == 1 ) { return false; } } return true; } void OpenDoor::End ( Actor &self ) { } /**************************************************************************** PlayAnim Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, PlayAnim, NULL ); ResponseDef PlayAnim::Responses[] = { { &EV_Behavior_Args, ( Response )PlayAnim::SetArgs }, { NULL, NULL } }; void PlayAnim::SetArgs ( Event *ev ) { anim = ev->GetString( 2 ); } void PlayAnim::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nanim: %s\n", anim.c_str() ); } void PlayAnim::Begin ( Actor &self ) { if ( anim.length() ) { if ( !self.SetAnim( anim, EV_Actor_FinishedBehavior ) ) { //warning( "Begin", "%s does not exist for %s.", anim.c_str(), self.targetname.c_str() ); self.PostEvent( EV_Actor_FinishedBehavior, 0 ); } } } qboolean PlayAnim::Evaluate ( Actor &self ) { return true; } void PlayAnim::End ( Actor &self ) { } /**************************************************************************** Wander Class Definition ****************************************************************************/ /* CLASS_DECLARATION( Behavior, Wander, NULL ); ResponseDef Wander::Responses[] = { { &EV_Behavior_Args, ( Response )Wander::SetArgs }, { NULL, NULL } }; void Wander::SetArgs ( Event *ev ) { anim = ev->GetString( 2 ); maxdistance = ev->GetFloat( 3 ); maxdistance *= maxdistance; } PathNode *Wander::FindWanderNode ( Actor &self ) { int i; PathNode *bestnode; PathNode *node; FindCoverPath find; Path *path; Vector delta; float dist; Vector pos; int count = 0; pos = self.worldorigin; bestnode = NULL; for ( i = 0; i < 5; i++ ) { node = AI_GetNode( G_Random( ai_maxnode + 1 ) ); if ( !node ) continue; delta = node->worldorigin - pos; dist = delta * delta; if ( ( dist > 1024 ) && ( dist < maxdistance ) ) { bestnode = node; break; } } if ( bestnode ) { find.heuristic.self = &self; find.heuristic.setSize( self.size ); find.heuristic.entnum = self.entnum; path = find.FindPath( self.worldorigin, bestnode->worldorigin ); if ( path ) { node = path->End(); // Mark node as occupied for a short time node->occupiedTime = level.time + 1.5; node->entnum = self.entnum; chase.SetGoal( node ); chase.SetPath( path ); return node; } } return NULL; } void Wander::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nchase:\n" ); chase.ShowInfo( self ); gi.printf( "\nanim: %s\n", anim.c_str() ); gi.printf( "state: %d\n", state ); gi.printf( "maxdistance: %f\n", maxdistance ); } void Wander::Begin ( Actor &self ) { if ( !anim.length() ) { anim = "walk"; } movegoal = NULL; state = 0; } qboolean Wander::Evaluate ( Actor &self ) { if ( !movegoal ) { state = 0; } switch( state ) { case 0 : chase.Begin( self ); movegoal = FindWanderNode( self ); if ( !movegoal ) { return false; } if ( anim.length() && ( anim != self.animname ) ) { self.SetAnim( anim ); } state = 1; case 1 : if ( chase.Evaluate( self ) ) { return true; } // Look for another wander node state = 0; chase.End( self ); return false; break; } return true; } void Wander::End ( Actor &self ) { chase.End( self ); } */ /**************************************************************************** FindCover Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, FindCover, NULL ); ResponseDef FindCover::Responses[] = { { &EV_Behavior_Args, ( Response )FindCover::SetArgs }, { NULL, NULL } }; void FindCover::SetArgs ( Event *ev ) { anim = ev->GetString( 2 ); } PathNode *FindCover::FindCoverNode ( Actor &self ) { int i; PathNode *bestnode; float bestdist; PathNode *desperatebestnode; float desperatebestdist; PathNode *node; FindCoverPath find; Path *path; Vector delta; float dist; Vector pos; pos = self.worldorigin; bestnode = NULL; bestdist = 999999999; // greater than ( 8192 * sqr(2) ) ^ 2 -- maximum squared distance desperatebestnode = NULL; desperatebestdist = 999999999; // greater than ( 8192 * sqr(2) ) ^ 2 -- maximum squared distance for( i = 0; i <= ai_maxnode; i++ ) { node = AI_GetNode( i ); if ( node && ( node->nodeflags & ( AI_DUCK | AI_COVER ) ) && ( ( 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.CanSeeEnemyFrom( node->worldorigin ) ||//) )//|| ( ( node->nodeflags & AI_DUCK ) && !self.CanSeeEnemyFrom( node->worldorigin - Vector( 0, 0, 32 ) ) ) ) ) { bestnode = node; bestdist = dist; } else if ( ( dist < desperatebestdist ) && ( desperatebestdist > ( 64 * 64 ) ) ) { desperatebestnode = node; desperatebestdist = dist; } } } if ( !bestnode ) { bestnode = desperatebestnode; } if ( bestnode ) { find.heuristic.self = &self; find.heuristic.setSize( self.size ); find.heuristic.entnum = self.entnum; path = find.FindPath( self.worldorigin, bestnode->worldorigin ); if ( path ) { node = path->End(); // Mark node as occupied for a short time node->occupiedTime = level.time + 1.5; node->entnum = self.entnum; chase.SetGoal( node ); chase.SetPath( path ); return node; } } return NULL; } void FindCover::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nchase:\n" ); chase.ShowInfo( self ); gi.printf( "\nstate: %d\n", state ); gi.printf( "anim: %s\n", anim.c_str() ); gi.printf( "nextsearch: %f\n", nextsearch ); } void FindCover::Begin ( Actor &self ) { if ( !anim.length() ) { anim = "run"; } movegoal = NULL; state = 0; } qboolean FindCover::Evaluate ( Actor &self ) { if ( !movegoal ) { state = 0; } switch( state ) { case 0 : // Checking for cover chase.Begin( self ); movegoal = FindCoverNode( self ); if ( !movegoal ) { // Couldn't find any! return false; } // Found cover, going to it if ( anim.length() && ( anim != self.animname ) ) { self.SetAnim( anim ); } state = 1; nextsearch = level.time + 1; case 1 : if ( chase.Evaluate( self ) ) { if ( nextsearch < level.time ) { state = 0; } return true; } // Reached cover if ( self.CanSeeEnemyFrom( self.worldorigin ) ) { state = 0; } if ( movegoal->nodeflags & AI_DUCK ) { // ducking self.SetAnim( "crouch_down" ); } else { // standing self.SetAnim( "idle" ); } chase.End( self ); return false; break; } return true; } void FindCover::End ( Actor &self ) { chase.End( self ); } /**************************************************************************** FindFlee Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, FindFlee, NULL ); ResponseDef FindFlee::Responses[] = { { &EV_Behavior_Args, ( Response )FindFlee::SetArgs }, { NULL, NULL } }; void FindFlee::SetArgs ( Event *ev ) { anim = ev->GetString( 2 ); } PathNode *FindFlee::FindFleeNode ( Actor &self ) { int i; PathNode *bestnode; float bestdist; PathNode *desperatebestnode; float desperatebestdist; PathNode *node; FindFleePath find; Path *path; Vector delta; float dist; Vector pos; pos = self.worldorigin; bestnode = NULL; bestdist = 999999999; // greater than ( 8192 * sqr(2) ) ^ 2 -- maximum squared distance desperatebestnode = NULL; desperatebestdist = 999999999; // greater than ( 8192 * sqr(2) ) ^ 2 -- maximum squared distance for( i = 0; i <= ai_maxnode; i++ ) { node = AI_GetNode( i ); if ( node && ( node->nodeflags & AI_FLEE ) && ( ( 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.CanSeeEnemyFrom( node->worldorigin ) ) { bestnode = node; bestdist = dist; } else if ( ( dist < desperatebestdist ) && ( desperatebestdist > ( 64 * 64 ) ) ) { desperatebestnode = node; desperatebestdist = dist; } } } if ( !bestnode ) { bestnode = desperatebestnode; } if ( bestnode ) { find.heuristic.self = &self; find.heuristic.setSize( self.size ); find.heuristic.entnum = self.entnum; path = find.FindPath( self.worldorigin, bestnode->worldorigin ); if ( path ) { node = path->End(); // Mark node as occupied for a short time node->occupiedTime = level.time + 1.5; node->entnum = self.entnum; chase.SetGoal( node ); chase.SetPath( path ); return node; } } return NULL; } void FindFlee::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nchase:\n" ); chase.ShowInfo( self ); gi.printf( "\nstate: %d\n", state ); gi.printf( "anim: %s\n", anim.c_str() ); gi.printf( "nextsearch: %f\n", nextsearch ); } void FindFlee::Begin ( Actor &self ) { if ( !anim.length() ) { anim = "run"; } movegoal = NULL; state = 0; } qboolean FindFlee::Evaluate ( Actor &self ) { if ( !movegoal ) { state = 0; } switch( state ) { case 0 : // Checking for flee node chase.Begin( self ); movegoal = FindFleeNode( self ); if ( !movegoal ) { // Couldn't find any! return false; } // Found flee node, going to it if ( anim.length() && ( anim != self.animname ) ) { self.SetAnim( anim ); } state = 1; nextsearch = level.time + 1; case 1 : if ( chase.Evaluate( self ) ) { if ( nextsearch < level.time ) { state = 0; } return true; } // Reached cover if ( self.CanSeeEnemyFrom( self.worldorigin ) ) { state = 0; } else { // standing self.SetAnim( "idle" ); chase.End( self ); return false; } break; } return true; } void FindFlee::End ( Actor &self ) { chase.End( self ); } /**************************************************************************** FindEnemy Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, FindEnemy, NULL ); ResponseDef FindEnemy::Responses[] = { { &EV_Behavior_Args, ( Response )FindEnemy::SetArgs }, { NULL, NULL } }; void FindEnemy::SetArgs ( Event *ev ) { anim = ev->GetString( 2 ); } void FindEnemy::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); 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() ); } void FindEnemy::Begin ( Actor &self ) { if ( !anim.length() ) { anim = "run"; } movegoal = NULL; lastSearchNode = NULL; state = 0; } PathNode *FindEnemy::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; } 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 FindEnemy::Evaluate ( Actor &self ) { if ( !self.currentEnemy ) { return false; } if ( nextsearch < level.time ) { // check if we should search for the first time if ( !lastSearchNode ) { //gi.dprintf( "%d: %s(%d) first time\n", level.framenum, self.targetname.c_str(), self.entnum ); 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 ) ) { //gi.dprintf( "%d: %s(%d) searching again\n", level.framenum, self.targetname.c_str(), self.entnum ); state = 0; } } } switch( state ) { case 0 : // Searching for enemy 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 ) ) { 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 ( anim.length() && ( anim != self.animname ) ) { self.SetAnim( anim ); } state = 1; case 1 : if ( self.CanShoot( self.currentEnemy, false ) ) { // Reached enemy chase.End( self ); return false; } if ( !chase.Evaluate( self ) ) { state = 0; nextsearch = 0; } break; } return true; } void FindEnemy::End ( Actor &self ) { chase.End( self ); } /**************************************************************************** Hide Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, Hide, NULL ); ResponseDef Hide::Responses[] = { { &EV_Behavior_Args, ( Response )Hide::SetArgs }, { NULL, NULL } }; void Hide::SetArgs ( Event *ev ) { anim = ev->GetString( 2 ); } void Hide::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nhide:\n" ); hide.ShowInfo( self ); gi.printf( "\nanim: %s\n", anim.c_str() ); gi.printf( "state: %d\n", state ); gi.printf( "checktime: %f\n", checktime ); } void Hide::Begin ( Actor &self ) { if ( !anim.length() ) { anim = "run"; } state = 0; } qboolean Hide::Evaluate ( Actor &self ) { switch( state ) { // init run for cover case 0 : state = 1; hide.Begin( self ); if ( hide.Evaluate( self ) && anim.length() ) { self.SetAnim( anim ); } else { hide.End( self ); self.SetAnim( "crouch_down" ); state = 2; checktime = level.time + 1; } break; // running for cover case 1 : if ( !hide.Evaluate( self ) ) { hide.End( self ); self.SetAnim( "crouch_down" ); state = 2; checktime = level.time + 1; } break; // wait for a bit case 2 : if ( checktime < level.time ) { checktime = level.time + 0.5f; if ( self.CanSeeEnemyFrom( self.worldorigin ) ) { hide.Begin( self ); if ( hide.Evaluate( self ) && anim.length() ) { self.SetAnim( anim ); state = 1; } else { hide.End( self ); checktime = level.time + 2; } } } break; } return true; } void Hide::End ( Actor &self ) { hide.End( self ); self.SetAnim( "crouch_down" ); } /**************************************************************************** FleeAndRemove Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, FleeAndRemove, NULL ); ResponseDef FleeAndRemove::Responses[] = { { &EV_Behavior_Args, ( Response )FleeAndRemove::SetArgs }, { NULL, NULL } }; void FleeAndRemove::SetArgs ( Event *ev ) { anim = ev->GetString( 2 ); } void FleeAndRemove::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nfindflee:\n" ); flee.ShowInfo( self ); gi.printf( "\nanim: %s\n", anim.c_str() ); gi.printf( "state: %d\n", state ); gi.printf( "checktime: %f\n", checktime ); } void FleeAndRemove::Begin ( Actor &self ) { if ( !anim.length() ) { anim = "run"; } state = 0; } qboolean FleeAndRemove::Evaluate ( Actor &self ) { if ( !self.currentEnemy ) { return false; } switch( state ) { // init run for cover case 0 : state = 1; if ( anim.length() ) { self.SetAnim( anim ); } flee.Begin( self ); // if we fail on the first try, probably can't flee. if ( !flee.Evaluate( self ) ) { flee.End( self ); return false; } return true; // running for cover case 1 : if ( !flee.Evaluate( self ) ) { flee.End( self ); state = 2; checktime = 0; self.SetAnim( "crouch_down" ); } break; // wait for a bit case 2 : if ( checktime < level.time ) { checktime = level.time + 1; if ( self.CanSeeEnemyFrom( self.worldorigin ) ) { state = 0; } else { self.PostEvent( EV_Remove, 0 ); } } break; } return true; } void FleeAndRemove::End ( Actor &self ) { flee.End( self ); self.SetAnim( "crouch_down" ); } /**************************************************************************** AimAndShoot Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, AimAndShoot, NULL ); ResponseDef AimAndShoot::Responses[] = { { &EV_Behavior_Args, ( Response )AimAndShoot::SetArgs }, { &EV_Behavior_AnimDone, ( Response )AimAndShoot::AnimDone }, { NULL, NULL } }; AimAndShoot::AimAndShoot() { maxshots = 1; numshots = 0; } void AimAndShoot::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\naim:\n" ); aim.ShowInfo( self ); gi.printf( "\nmode: %d\n", mode ); gi.printf( "maxshots: %d\n", maxshots ); gi.printf( "numshots: %d\n", numshots ); gi.printf( "animdone: %d\n", animdone ); } void AimAndShoot::Begin ( Actor &self ) { enemy_health = 0; mode = 0; animdone = false; readyfireanim = animprefix + "readyfire"; if ( !self.HasAnim( readyfireanim.c_str() ) ) { readyfireanim = ""; } aimanim = animprefix + "aim"; if ( !self.HasAnim( aimanim.c_str() ) ) { aimanim = ""; } fireanim = animprefix + "fire"; if ( !self.HasAnim( fireanim.c_str() ) ) { fireanim = ""; } } void AimAndShoot::SetMaxShots ( int num ) { maxshots = (num>>1) + G_Random( num ); } void AimAndShoot::SetArgs ( Event *ev ) { SetMaxShots( ev->GetInteger( 2 ) ); if ( ev->NumArgs() > 2 ) { animprefix = ev->GetString( 3 ); } } void AimAndShoot::AnimDone ( Event *ev ) { animdone = true; } qboolean AimAndShoot::Evaluate ( Actor &self ) { switch( mode ) { case 0 : if ( !self.currentEnemy ) { return false; } if ( !self.CanShoot( self.currentEnemy, false ) ) { return false; } if ( readyfireanim.length() ) { animdone = false; self.SetAnim( readyfireanim.c_str(), EV_Actor_NotifyBehavior ); aim.Begin( self ); mode = 1; } else { // skip the ready fire animation animdone = true; } case 1 : // readying gun if ( !animdone ) { aim.SetTarget( self.currentEnemy ); aim.Evaluate( self ); break; } // start Aiming animdone = false; if ( aimanim.length() ) { self.SetAnim( aimanim.c_str() ); } // // save off time, in case we aim for too long // aim_time = level.time + 1; mode = 2; case 2 : // Aiming if ( !self.currentEnemy ) { return false; } // // see if we aimed for too long // if ( aim_time < level.time ) { return false; } aim.SetTarget( self.currentEnemy ); aim.Evaluate( self ); // don't go into our firing animation until our weapon is ready, and we are on target if ( self.WeaponReady() && self.currentEnemy && self.CanShoot( self.currentEnemy, true ) ) { animdone = false; self.Chatter( "snd_inmysights", 5 ); self.SetAnim( fireanim.c_str(), EV_Actor_NotifyBehavior ); enemy_health = self.currentEnemy->health; mode = 3; } else if ( !self.currentEnemy || self.currentEnemy->deadflag || ( self.currentEnemy->health <= 0 ) || !self.CanShoot( self.currentEnemy, false ) ) { // either our enemy is dead, or we can't shoot the enemy from here if ( self.CurrentWeapon() ) { self.CurrentWeapon()->ForceReload(); } return false; } break; case 3 : // Fire aim.SetTarget( self.currentEnemy ); aim.Evaluate( self ); if ( animdone ) { if ( !self.currentEnemy || ( self.currentEnemy->health < enemy_health ) ) self.Chatter( "snd_attacktaunt", 4 ); else self.Chatter( "snd_missed", 7 ); animdone = false; numshots++; if ( ( numshots >= maxshots ) || !self.currentEnemy || self.currentEnemy->deadflag || ( self.currentEnemy->health <= 0 ) || !self.CanShoot( self.currentEnemy, false ) ) { // either we're out of shots, our enemy is dead, or we can't shoot the enemy from here if ( self.CurrentWeapon() ) { self.CurrentWeapon()->ForceReload(); } return false; } else if ( !self.WeaponReady() || !self.CanShoot( self.currentEnemy, false ) ) { // weapon not ready or not aimed at enemy, so just keep trying to get enemy in our sights if ( aimanim.length() ) { self.SetAnim( aimanim.c_str() ); } // // save off time, in case we aim for too long // aim_time = level.time + 1; mode = 2; } else { // keep firing self.SetAnim( fireanim.c_str(), EV_Actor_NotifyBehavior ); enemy_health = self.currentEnemy->health; } } break; } return true; } void AimAndShoot::End ( Actor &self ) { aim.End( self ); self.SetAnim( "idle" ); } /**************************************************************************** AimAndMelee Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, AimAndMelee, NULL ); ResponseDef AimAndMelee::Responses[] = { { &EV_Behavior_Args, ( Response )AimAndMelee::SetArgs }, { &EV_Behavior_AnimDone, ( Response )AimAndMelee::AnimDone }, { NULL, NULL } }; AimAndMelee::AimAndMelee() { } void AimAndMelee::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\naim:\n" ); aim.ShowInfo( self ); gi.printf( "\nmode: %d\n", mode ); gi.printf( "maxshots: %d\n", maxshots ); gi.printf( "numshots: %d\n", numshots ); gi.printf( "animdone: %d\n", animdone ); } void AimAndMelee::Begin ( Actor &self ) { mode = 0; numshots = 0; maxshots = 2 + G_Random( 4 ); animdone = false; } void AimAndMelee::SetArgs ( Event *ev ) { int num; num = ev->GetInteger( 2 ); maxshots = (num>>1) + G_Random( num ); } void AimAndMelee::AnimDone ( Event *ev ) { animdone = true; } qboolean AimAndMelee::Evaluate ( Actor &self ) { float r; Vector delta; switch( mode ) { case 0 : if ( !self.has_melee || !self.currentEnemy ) { return false; } delta = self.centroid - self.currentEnemy->centroid; r = delta.length(); if ( r > self.melee_range ) { return false; } aim.SetTarget( self.currentEnemy ); if ( aim.Evaluate( self ) ) { break; } numshots++; animdone = false; // melee self.SetAnim( "melee", EV_Actor_NotifyBehavior ); self.Chatter( "snd_attacktaunt", 4 ); mode = 1; case 1 : // finish up the attack if ( animdone ) { if ( numshots < maxshots ) { mode = 0; } else { return false; } } break; } return true; } void AimAndMelee::End ( Actor &self ) { aim.End( self ); self.SetAnim( "idle" ); } /**************************************************************************** Melee Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, Melee, NULL ); ResponseDef Melee::Responses[] = { { &EV_Behavior_Args, ( Response )Melee::SetArgs }, { &EV_Behavior_AnimDone, ( Response )Melee::AnimDone }, { NULL, NULL } }; Melee::Melee() { } void Melee::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nmode: %d\n", mode ); gi.printf( "animdone: %d\n", animdone ); } void Melee::Begin ( Actor &self ) { mode = 0; animdone = false; } void Melee::SetArgs ( Event *ev ) { } void Melee::AnimDone ( Event *ev ) { animdone = true; } qboolean Melee::Evaluate ( Actor &self ) { float r; Vector delta; Vector ang; switch( mode ) { case 0 : if ( !self.has_melee || !self.currentEnemy ) { return false; } delta = self.currentEnemy->worldorigin - self.worldorigin; r = delta.length(); if ( r > self.melee_range ) { return false; } animdone = false; // melee ang = delta.toAngles(); ang[ PITCH ] = -ang[ PITCH ]; self.setAngles( ang ); self.SetAnim( "melee", EV_Actor_NotifyBehavior ); self.Chatter( "snd_attacktaunt", 4 ); mode = 1; case 1 : // finsh up the attack if ( animdone ) { return false; } break; } return true; } void Melee::End ( Actor &self ) { self.SetAnim( "idle" ); } /**************************************************************************** Repel Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, Repel, NULL ); ResponseDef Repel::Responses[] = { { &EV_Behavior_Args, ( Response )Repel::SetArgs }, { NULL, NULL } }; void Repel::SetArgs ( Event *ev ) { anim = ev->GetString( 2 ); movegoal = AI_FindNode( ev->GetString( 3 ) ); if ( movegoal ) { goal = movegoal->worldorigin; } speed = ev->GetFloat( 4 ); } void Repel::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nanim: %s\n", anim.c_str() ); gi.printf( "dist: %f\n", dist ); gi.printf( "len: %f\n", len ); gi.printf( "speed: %f\n", speed ); gi.printf( "goal: ( %f, %f, %f )\n", goal.x, goal.y, goal.z ); gi.printf( "start: ( %f, %f, %f )\n", start.x, start.y, start.z ); gi.printf( "dir: ( %f, %f, %f )\n", dir.x, dir.y, dir.z ); } void Repel::Begin ( Actor &self ) { start.x = goal.x; start.y = goal.y; start.z = self.worldorigin.z; dir = goal - start; len = dir.length(); dir *= 1 / len; if ( speed <= 0 ) { speed = 32; } if ( anim.length() ) { self.SetAnim( anim ); } dist = -1; } qboolean Repel::Evaluate ( Actor &self ) { Vector pos; qboolean done; float sp; trace_t trace; done = false; // this is silly, but it works if ( dist < 0 ) { sp = 0; } else if ( dist < 32 ) { sp = dist * speed / 32; } else if ( ( len - dist ) < 32 ) { sp = ( len - dist ) * speed / 32; } else { sp = speed; } pos = start + dir * ( dist + sp ); dist = ( pos - start ) * dir; if ( dist >= (len-1) ) { pos = goal; done = true; } else if ( dist < 1 ) { dist = 1; } trace = G_Trace( self.worldorigin, self.mins, self.maxs, pos, &self, self.edict->clipmask, "Repel" ); self.setOrigin( trace.endpos ); return !done; } void Repel::End ( Actor &self ) { self.SetAnim( "idle" ); } /**************************************************************************** Pickup Behavior Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, PickupAndThrow, NULL ); Event EV_PickupAndThrow_Pickup( "pickup" ); Event EV_PickupAndThrow_Throw( "throw" ); ResponseDef PickupAndThrow::Responses[] = { { &EV_Behavior_Args, ( Response )PickupAndThrow::SetArgs }, { &EV_Behavior_AnimDone, ( Response )PickupAndThrow::AnimDone }, { &EV_PickupAndThrow_Pickup,( Response )PickupAndThrow::Pickup }, { &EV_PickupAndThrow_Throw,( Response )PickupAndThrow::Throw }, { NULL, NULL } }; PickupAndThrow::PickupAndThrow() { } void PickupAndThrow::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\naim:\n" ); aim.ShowInfo( self ); gi.printf( "\nmode: %d\n", mode ); gi.printf( "animdone: %d\n", animdone ); if ( pickup_target ) { gi.printf( "\npickup_target: #%d '%s'\n", pickup_target->entnum, pickup_target->targetname.c_str() ); } else { gi.printf( "\npickup_target: NULL\n" ); } } void PickupAndThrow::Begin ( Actor &self ) { mode = 0; animdone = false; } void PickupAndThrow::SetArgs ( Event *ev ) { pickup_target = ev->GetEntity( 2 ); } void PickupAndThrow::AnimDone ( Event *ev ) { animdone = true; } void PickupAndThrow::Pickup ( Event *ev ) { Entity * ent; Event * e; ent = ev->GetEntity( 1 ); if ( pickup_target ) { e = new Event( EV_ThrowObject_Pickup ); e->AddEntity( ent ); e->AddString( ev->GetString( 2 ) ); pickup_target->ProcessEvent( e ); } } void PickupAndThrow::Throw ( Event *ev ) { Actor * act; Event * e; act = (Actor *)ev->GetEntity( 1 ); if ( pickup_target ) { if ( !act->currentEnemy ) return; e = new Event( EV_ThrowObject_Throw ); e->AddEntity( act ); e->AddFloat( 500 ); e->AddEntity( act->currentEnemy ); e->AddFloat( 1 ); pickup_target->ProcessEvent( e ); } } qboolean PickupAndThrow::Evaluate ( Actor &self ) { Event * ev; if ( !self.currentEnemy || !pickup_target ) return false; switch( mode ) { case 0 : if ( self.HasAnim( "pickup" ) ) { animdone = false; self.SetAnim( "pickup", EV_Actor_NotifyBehavior ); mode = 1; } else { // skip the pickup animation ev = new Event( EV_PickupAndThrow_Pickup ); ev->AddEntity( &self ); ev->AddString( "gun" ); ProcessEvent( ev ); animdone = true; } case 1 : if ( !animdone ) break; aim.Begin( self ); mode = 2; if ( self.HasAnim( "throw_aim" ) ) { self.SetAnim( "throw_aim", EV_Actor_NotifyBehavior ); } else { self.SetAnim( "idle" ); } case 2 : // aim towards our target aim.SetTarget( self.currentEnemy ); if ( aim.Evaluate( self ) ) { break; } mode = 3; case 3 : // Throwing mode = 4; if ( self.HasAnim( "throw" ) ) { animdone = false; self.SetAnim( "throw", EV_Actor_NotifyBehavior ); mode = 4; } else { // skip the pickup animation ev = new Event( EV_PickupAndThrow_Throw ); ev->AddEntity( &self ); ProcessEvent( ev ); animdone = true; } break; case 4 : if ( !animdone ) break; return false; break; } return true; } void PickupAndThrow::End ( Actor &self ) { aim.End( self ); self.SetAnim( "idle" ); } /**************************************************************************** Jump Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, Jump, NULL ); ResponseDef Jump::Responses[] = { { &EV_Behavior_Args, ( Response )Jump::SetArgs }, { &EV_Behavior_AnimDone, ( Response )Jump::AnimDone }, { NULL, NULL } }; Jump::Jump() { endtime = 0; speed = 200; state = 0; } void Jump::AnimDone ( Event *ev ) { animdone = true; } void Jump::SetArgs ( Event *ev ) { anim = ev->GetString( 2 ); // // see if it is an entity first // movegoal = AI_FindNode( ev->GetString( 3 ) ); if ( movegoal ) { goal = movegoal->worldorigin; } else { Entity *ent; ent = ev->GetEntity( 3 ); if ( ent ) { goal = ent->worldorigin; } else { gi.dprintf("Jump::SetArgs invalid target %s", ev->GetString( 3 ) ); } } if ( ev->NumArgs() >= 4 ) speed = ev->GetFloat( 4 ); else speed = 0; } void Jump::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nendtime: %f\n", endtime ); gi.printf( "speed: %f\n", speed ); gi.printf( "state: %d\n", state ); gi.printf( "animdone: %d\n", animdone ); gi.printf( "anim: %s\n", anim.c_str() ); } void Jump::Begin ( Actor &self ) { float traveltime; if ( anim.length() ) { self.SetAnim( anim ); } traveltime = self.JumpTo( goal, speed ); endtime = traveltime + level.time; self.last_jump_time = endtime; state = 0; } qboolean Jump::Evaluate ( Actor &self ) { switch( state ) { case 0: state = 1; // this is here so that we at least hit this function at least once // this gaves the character the chance to leave the ground, nulling out // self.groundentity break; case 1: // // wait for the character to hit the ground // if ( self.groundentity ) { state = 2; // // if we have an anim, we go to state 3 // if ( self.HasAnim( "land" ) ) { animdone = false; self.SetAnim( "land", EV_Actor_NotifyBehavior ); state = 3; } else { return false; } } break; case 2: // // we are on the ground and waiting to timeout // if ( level.time > endtime ) return false; break; case 3: // // we are on the ground and waiting for our landing animation to finish // if ( animdone ) { return false; } break; } return true; } void Jump::End ( Actor &self ) { //turn.End( self ); self.SetAnim( "idle" ); } /**************************************************************************** StrafeAttack Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, StrafeAttack, NULL ); ResponseDef StrafeAttack::Responses[] = { { NULL, NULL } }; void StrafeAttack::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nturn:\n" ); turn.ShowInfo( self ); gi.printf( "\nstate: %d\n", state ); } void StrafeAttack::Begin ( Actor &self ) { state = 0; } qboolean StrafeAttack::Evaluate ( Actor &self ) { int num; Vector delta; Vector left; Vector pos; if ( !self.currentEnemy ) { 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, "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 ( self.CanMoveTo( pos ) && self.CanShootFrom( pos, self.currentEnemy, false ) ) { self.SetAnim( "step_left", EV_Actor_FinishedBehavior ); state = 3; return true; } } num = gi.Anim_Random( self.edict->s.modelindex, "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 ( self.CanMoveTo( pos ) && self.CanShootFrom( pos, self.currentEnemy, false ) ) { self.SetAnim( "step_right", EV_Actor_FinishedBehavior ); state = 3; return true; } } } else { num = gi.Anim_Random( self.edict->s.modelindex, "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 ( self.CanMoveTo( pos ) && self.CanShootFrom( pos, self.currentEnemy, false ) ) { self.SetAnim( "step_right", EV_Actor_FinishedBehavior ); state = 3; return true; } } num = gi.Anim_Random( self.edict->s.modelindex, "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 ( self.CanMoveTo( pos ) && self.CanShootFrom( pos, self.currentEnemy, false ) ) { self.SetAnim( "step_left", EV_Actor_FinishedBehavior ); state = 3; return true; } } } return false; break; } return true; } void StrafeAttack::End ( Actor &self ) { turn.End( self ); self.SetAnim( "idle" ); } /**************************************************************************** StrafeTo Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, StrafeTo, NULL ); ResponseDef StrafeTo::Responses[] = { { &EV_Behavior_Args, ( Response )StrafeTo::SetArgs }, { NULL, NULL } }; void StrafeTo::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 StrafeTo::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 StrafeTo::Begin ( Actor &self ) { Vector delta; float dot; seek.Begin( self ); delta = goal - self.worldorigin; dot = delta * self.orientation[ 1 ]; if ( dot < 0 ) { self.SetAnim( "step_right" ); } else { self.SetAnim( "step_left" ); } fail = 0; } qboolean StrafeTo::Evaluate ( Actor &self ) { seek.SetMaxSpeed( self.movespeed ); seek.SetPosition( self.worldorigin ); seek.SetDir( self.movedir ); seek.SetTargetPosition( goal ); if ( !seek.Evaluate( self ) ) { 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 StrafeTo::End ( Actor &self ) { seek.End( self ); self.SetAnim( "idle" ); } /**************************************************************************** Random Direction Utility function ****************************************************************************/ Vector ChooseRandomDirection ( Actor &self, Vector previousdir, float *time, int allowcontentsmask, int disallowcontentsmask, qboolean usepitch ) { static float x[ 9 ] = { 0, 22, -22, 45, -45, 0, 22, -22, 45 }; Vector dir; Vector ang; Vector bestdir; Vector newdir; Vector s; float bestfraction; trace_t trace; int i; int j; int k; int t; int u; int contents; qboolean checkmask; Vector centroid; centroid = self.centroid - self.worldorigin; checkmask = allowcontentsmask || disallowcontentsmask; s = Vector( 0, 0, STEPSIZE ); bestfraction = -1; bestdir = self.worldorigin; for( i = 0; i <= 4; i++ ) { t = i * 45; if ( rand() < 0.5 ) { // sometimes we choose left first, other times right. t = -t; } for( j = -1; j < 2; j += 2 ) { ang.y = self.worldangles.y + ( t * j ); if ( usepitch ) { u = ( int )G_Random( 5 ); for( k = 0; k < 5; k++ ) { ang.x = x[ k + u ]; ang.AngleVectors( &dir, NULL, NULL ); dir *= self.movespeed * (*time); dir += self.worldorigin; trace = G_Trace( self.worldorigin, self.mins, self.maxs, dir, &self, self.edict->clipmask, "ChooseRandomDirection 1" ); if ( !trace.startsolid && !trace.allsolid ) { newdir = Vector( trace.endpos ); if ( checkmask ) { contents = gi.pointcontents( ( newdir + centroid ).vec3() ); if ( ( !allowcontentsmask || ( contents & allowcontentsmask ) ) && ( !disallowcontentsmask || !( contents & disallowcontentsmask ) ) && ( trace.fraction > bestfraction ) && ( newdir != bestdir ) && ( newdir != previousdir ) ) { bestdir = newdir; bestfraction = trace.fraction; } } else { if ( ( trace.fraction > bestfraction ) && ( newdir != bestdir ) && ( newdir != previousdir ) ) { bestdir = newdir; bestfraction = trace.fraction; } } } } } else { ang.x = 0; ang.AngleVectors( &dir, NULL, NULL ); dir *= self.movespeed * (*time); dir += self.worldorigin; dir += s; trace = G_Trace( self.worldorigin + s, self.mins, self.maxs, dir, &self, self.edict->clipmask, "ChooseRandomDirection 2" ); if ( !trace.startsolid && !trace.allsolid ) { newdir = Vector( trace.endpos ); if ( checkmask ) { contents = gi.pointcontents( ( newdir + centroid ).vec3() ); if ( ( !allowcontentsmask || ( contents & allowcontentsmask ) ) && ( !disallowcontentsmask || !( contents & disallowcontentsmask ) ) && ( trace.fraction > bestfraction ) && ( newdir != bestdir ) && ( newdir != previousdir ) ) { bestdir = newdir; bestfraction = trace.fraction; } } else { if ( ( trace.fraction > bestfraction ) && ( newdir != bestdir ) && ( newdir != previousdir ) ) { bestdir = newdir; bestfraction = trace.fraction; } } } } if ( ( i == 4 ) || ( i == 0 ) ) { break; } } } if ( bestfraction > 0 ) { *time *= bestfraction; } else { *time = 0; } return bestdir; } /**************************************************************************** Swim Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, Swim, NULL ); ResponseDef Swim::Responses[] = { { &EV_Behavior_Args, ( Response )Swim::SetArgs }, { NULL, NULL } }; void Swim::SetArgs ( Event *ev ) { if ( ev->NumArgs() > 1 ) anim = ev->GetString( 2 ); } void Swim::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nseek:\n" ); seek.ShowInfo( self ); gi.printf( "\navoid:\n" ); avoid.ShowInfo( self ); } void Swim::Begin ( Actor &self ) { avoidtime = 0; avoidvec = vec_zero; avoid.Begin( self ); seek.Begin( self ); seek.SetTargetVelocity( vec_zero ); if ( anim.length() ) { self.SetAnim( anim ); } } qboolean Swim::Evaluate ( Actor &self ) { if ( ( self.lastmove != STEPMOVE_OK ) || ( avoidtime <= level.time ) ) { Vector dir; Vector ang; float time; time = 3; avoidvec = ChooseRandomDirection( self, avoidvec, &time, MASK_WATER, 0, true ); avoidtime = level.time + time; dir = avoidvec - self.worldorigin; ang = dir.toAngles(); ang[ PITCH ] = -ang[ PITCH ]; self.setAngles( ang ); } seek.SetMaxSpeed( self.movespeed ); seek.SetPosition( self.worldorigin ); seek.SetDir( self.movedir ); seek.SetTargetPosition( avoidvec ); // if we reached the goal, re-evaluate if ( !seek.Evaluate( self ) ) { avoidtime = 0; } avoid.SetMaxSpeed( self.movespeed * 2 ); avoid.SetPosition( self.worldorigin ); avoid.SetDir( self.movedir ); avoid.Evaluate( self ); self.Accelerate( seek.steeringforce + avoid.steeringforce ); return true; } void Swim::End ( Actor &self ) { avoid.End( self ); seek.End( self ); } /**************************************************************************** SwimCloseAttack Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, SwimCloseAttack, NULL ); ResponseDef SwimCloseAttack::Responses[] = { { &EV_Behavior_Args, ( Response )SwimCloseAttack::SetArgs }, { NULL, NULL } }; void SwimCloseAttack::SetArgs ( Event *ev ) { anim = ev->GetString( 2 ); } void SwimCloseAttack::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nseek:\n" ); seek.ShowInfo( self ); gi.printf( "\navoid:\n" ); avoid.ShowInfo( self ); gi.printf( "\navoidtime: %.2f\n", avoidtime ); } void SwimCloseAttack::Begin ( Actor &self ) { avoidtime = 0; avoiding = false; avoidvec = vec_zero; avoid.Begin( self ); seek.Begin( self ); seek.SetTargetVelocity( vec_zero ); if ( anim.length() ) { self.SetAnim( anim ); } } qboolean SwimCloseAttack::Evaluate ( Actor &self ) { trace_t trace; Vector dir; Vector ang; Vector goal; Vector end; if ( !self.currentEnemy ) { return false; } // // close enough // if ( self.CanShoot( self.currentEnemy, false ) ) { if ( self.WeaponReady() ) return false; else return true; } if ( ( self.lastmove == STEPMOVE_STUCK ) || ( avoidtime <= level.time ) ) { // // see if we can get to the player // dir = self.currentEnemy->worldorigin - self.worldorigin; dir.normalize(); goal = self.currentEnemy->worldorigin; trace = G_Trace( self.worldorigin, self.mins, self.maxs, goal, &self, self.edict->clipmask, "SwimCloseAttack::Evaluate 2" ); if ( trace.fraction < 0.5f ) { float time; time = 2.5f; goal = ChooseRandomDirection( self, goal, &time, MASK_WATER, 0, true ); avoidtime = level.time + time; } else { goal = trace.endpos; avoidtime = level.time + 2.5f; } dir = goal - self.worldorigin; dir.normalize(); //self.movedir = dir; ang = dir.toAngles(); ang[ PITCH ] = -ang[ PITCH ]; self.setAngles( ang ); seek.SetTargetPosition( goal ); } seek.SetMaxSpeed( self.movespeed ); seek.SetPosition( self.worldorigin ); seek.SetDir( self.movedir ); // if we reached the goal, re-evaluate if ( !seek.Evaluate( self ) ) { avoidtime = 0; } avoid.SetMaxSpeed( self.movespeed * 2 ); avoid.SetPosition( self.worldorigin ); avoid.SetDir( self.movedir ); avoid.Evaluate( self ); self.Accelerate( seek.steeringforce + avoid.steeringforce ); return true; } void SwimCloseAttack::End ( Actor &self ) { seek.End( self ); } /**************************************************************************** Fly Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, Fly, NULL ); ResponseDef Fly::Responses[] = { { &EV_Behavior_Args, ( Response )Fly::SetArgs }, { NULL, NULL } }; void Fly::SetArgs ( Event *ev ) { if ( ev->NumArgs() > 1 ) anim = ev->GetString( 2 ); } void Fly::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nseek:\n" ); seek.ShowInfo( self ); gi.printf( "\navoid:\n" ); avoid.ShowInfo( self ); } void Fly::Begin ( Actor &self ) { avoidtime = 0; avoidvec = vec_zero; avoid.Begin( self ); seek.Begin( self ); seek.SetTargetVelocity( vec_zero ); if ( anim.length() ) { self.SetAnim( anim ); } } qboolean Fly::Evaluate ( Actor &self ) { if ( ( self.lastmove != STEPMOVE_OK ) || ( avoidtime <= level.time ) ) { Vector dir; Vector ang; float time; time = 3; avoidvec = ChooseRandomDirection( self, avoidvec, &time, 0, MASK_WATER, true ); avoidtime = level.time + time; dir = avoidvec - self.worldorigin; ang = dir.toAngles(); ang[ PITCH ] = -ang[ PITCH ]; self.setAngles( ang ); } seek.SetMaxSpeed( self.movespeed ); seek.SetPosition( self.worldorigin ); seek.SetDir( self.movedir ); seek.SetTargetPosition( avoidvec ); // if we reached the goal, re-evaluate if ( !seek.Evaluate( self ) ) { avoidtime = 0; } avoid.SetMaxSpeed( self.movespeed * 2 ); avoid.SetPosition( self.worldorigin ); avoid.SetDir( self.movedir ); avoid.Evaluate( self ); self.Accelerate( seek.steeringforce + avoid.steeringforce ); return true; } void Fly::End ( Actor &self ) { avoid.End( self ); seek.End( self ); } /**************************************************************************** FlyCloseAttack Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, FlyCloseAttack, NULL ); ResponseDef FlyCloseAttack::Responses[] = { { &EV_Behavior_Args, ( Response )FlyCloseAttack::SetArgs }, { NULL, NULL } }; void FlyCloseAttack::SetArgs ( Event *ev ) { anim = ev->GetString( 2 ); } void FlyCloseAttack::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nseek:\n" ); seek.ShowInfo( self ); gi.printf( "\navoid:\n" ); avoid.ShowInfo( self ); gi.printf( "\navoidtime: %.2f\n", avoidtime ); } void FlyCloseAttack::Begin ( Actor &self ) { avoidtime = 0; avoiding = false; avoidvec = vec_zero; avoid.Begin( self ); seek.Begin( self ); seek.SetTargetVelocity( vec_zero ); if ( anim.length() ) { self.SetAnim( anim ); } } qboolean FlyCloseAttack::Evaluate ( Actor &self ) { trace_t trace; Vector dir; Vector ang; float minrange; Vector goal; Vector end; Sentient *sent; if ( !self.currentEnemy ) { return false; } if ( !self.CurrentWeapon() && self.currentEnemy->isSubclassOf( Sentient ) ) { sent = ( Sentient * )( Entity * )self.currentEnemy; if ( sent->waterlevel > 1 ) { // can no longer get to enemy self.ClearEnemies(); return false; } } minrange = self.MinimumAttackRange(); // // close enough // if ( self.CanShoot( self.currentEnemy, false ) ) { if ( self.WeaponReady() ) return false; else return true; } if ( ( self.lastmove == STEPMOVE_STUCK ) || ( avoidtime <= level.time ) ) { // // see if we can get to the player // goal = self.currentEnemy->centroid; trace = G_Trace( self.worldorigin, self.mins, self.maxs, goal, &self, self.edict->clipmask, "FlyCloseAttack::Evaluate 2" ); if ( trace.fraction < 0.5f ) { float time; time = 2.5f; goal = ChooseRandomDirection( self, goal, &time, 0, MASK_WATER, true ); avoidtime = level.time + time; } else { goal = trace.endpos; avoidtime = level.time + 2.5f; } dir = goal - self.worldorigin; dir.normalize(); //self.movedir = dir; ang = dir.toAngles(); ang[ PITCH ] = -ang[ PITCH ]; self.setAngles( ang ); seek.SetTargetPosition( goal ); } seek.SetMaxSpeed( self.movespeed ); seek.SetPosition( self.worldorigin ); seek.SetDir( self.movedir ); // if we reached the goal, re-evaluate if ( !seek.Evaluate( self ) ) { avoidtime = 0; } avoid.SetMaxSpeed( self.movespeed * 2 ); avoid.SetPosition( self.worldorigin ); avoid.SetDir( self.movedir ); avoid.Evaluate( self ); self.Accelerate( seek.steeringforce + avoid.steeringforce ); return true; } void FlyCloseAttack::End ( Actor &self ) { seek.End( self ); } /**************************************************************************** Wander Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, Wander, NULL ); ResponseDef Wander::Responses[] = { { &EV_Behavior_Args, ( Response )Wander::SetArgs }, { NULL, NULL } }; void Wander::SetArgs ( Event *ev ) { if ( ev->NumArgs() > 1 ) anim = ev->GetString( 2 ); } void Wander::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nseek:\n" ); seek.ShowInfo( self ); gi.printf( "\navoid:\n" ); avoid.ShowInfo( self ); } void Wander::Begin ( Actor &self ) { avoidtime = 0; avoidvec = vec_zero; avoid.Begin( self ); seek.Begin( self ); seek.SetTargetVelocity( vec_zero ); if ( anim.length() ) { self.SetAnim( anim ); } } qboolean Wander::Evaluate ( Actor &self ) { if ( ( self.lastmove != STEPMOVE_OK ) || ( avoidtime <= level.time ) ) { Vector dir; Vector ang; float time; time = 5; self.Chatter( "snd_idle", 4 ); avoidvec = ChooseRandomDirection( self, avoidvec, &time, 0, 0, false ); avoidtime = level.time + time; dir = avoidvec - self.worldorigin; ang = dir.toAngles(); self.angles[ YAW ] = ang[ YAW ]; self.setAngles( self.angles ); } seek.SetMaxSpeed( self.movespeed ); seek.SetPosition( self.worldorigin ); seek.SetDir( self.movedir ); seek.SetTargetPosition( avoidvec ); // if we reached the goal, re-evaluate if ( !seek.Evaluate( self ) ) { avoidtime = 0; } avoid.SetMaxSpeed( self.movespeed * 2 ); avoid.SetPosition( self.worldorigin ); avoid.SetDir( self.movedir ); avoid.Evaluate( self ); self.Accelerate( seek.steeringforce + avoid.steeringforce ); return true; } void Wander::End ( Actor &self ) { avoid.End( self ); seek.End( self ); } /**************************************************************************** WanderCloseAttack Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, WanderCloseAttack, NULL ); ResponseDef WanderCloseAttack::Responses[] = { { &EV_Behavior_Args, ( Response )WanderCloseAttack::SetArgs }, { NULL, NULL } }; void WanderCloseAttack::SetArgs ( Event *ev ) { anim = ev->GetString( 2 ); } void WanderCloseAttack::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nseek:\n" ); seek.ShowInfo( self ); gi.printf( "\navoid:\n" ); avoid.ShowInfo( self ); gi.printf( "\navoidtime: %.2f\n", avoidtime ); } void WanderCloseAttack::Begin ( Actor &self ) { avoidtime = 0; avoiding = false; avoidvec = vec_zero; avoid.Begin( self ); seek.Begin( self ); seek.SetTargetVelocity( vec_zero ); if ( anim.length() ) { self.SetAnim( anim ); } } qboolean WanderCloseAttack::Evaluate ( Actor &self ) { trace_t trace; Vector dir; Vector ang; float minrange; Vector goal; Vector end; if ( !self.currentEnemy ) { return false; } minrange = self.MinimumAttackRange(); // // close enough // if ( self.CanShoot( self.currentEnemy, false ) ) { if ( self.WeaponReady() ) return false; else return true; } if ( ( self.lastmove == STEPMOVE_STUCK ) || ( avoidtime <= level.time ) ) { // // see if we can get to the player // dir = self.currentEnemy->worldorigin - self.worldorigin; dir.normalize(); goal = self.currentEnemy->worldorigin; trace = G_Trace( self.worldorigin, self.mins, self.maxs, goal, &self, self.edict->clipmask, "WanderCloseAttack::Evaluate 2" ); if ( trace.fraction < 0.5f ) { float time; time = 2.5f; goal = ChooseRandomDirection( self, goal, &time, 0, 0, false ); avoidtime = level.time + time; } else { goal = trace.endpos; avoidtime = level.time + 2.5f; } dir = goal - self.worldorigin; dir.normalize(); //self.movedir = dir; ang = dir.toAngles(); ang[ PITCH ] = -ang[ PITCH ]; self.setAngles( ang ); seek.SetTargetPosition( goal ); } seek.SetMaxSpeed( self.movespeed ); seek.SetPosition( self.worldorigin ); seek.SetDir( self.movedir ); // if we reached the goal, re-evaluate if ( !seek.Evaluate( self ) ) { avoidtime = 0; } avoid.SetMaxSpeed( self.movespeed * 2 ); avoid.SetPosition( self.worldorigin ); avoid.SetDir( self.movedir ); avoid.Evaluate( self ); self.Accelerate( seek.steeringforce + avoid.steeringforce ); return true; } void WanderCloseAttack::End ( Actor &self ) { seek.End( self ); } /**************************************************************************** GetCloseToEnemy Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, GetCloseToEnemy, NULL ); ResponseDef GetCloseToEnemy::Responses[] = { { &EV_Behavior_Args, ( Response )GetCloseToEnemy::SetArgs }, { NULL, NULL } }; GetCloseToEnemy::GetCloseToEnemy() { howclose = 32; } void GetCloseToEnemy::SetArgs ( Event *ev ) { int num; num = 2; if ( !ev->IsNumericAt( num ) ) { anim = ev->GetString( num ); num++; } howclose = 32; if ( ev->IsNumericAt( num ) ) { howclose = ev->GetFloat( num ); num++; } } void GetCloseToEnemy::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\nchase:\n" ); chase.ShowInfo( self ); gi.printf( "\nstate: %d\n", state ); gi.printf( "howclose: %f\n", howclose ); gi.printf( "nextsearch: %f\n", nextsearch ); gi.printf( "anim: %s\n", anim.c_str() ); } void GetCloseToEnemy::Begin ( Actor &self ) { if ( !anim.length() ) { anim = "run"; } movegoal = NULL; state = 0; } qboolean GetCloseToEnemy::Evaluate ( Actor &self ) { if ( !self.currentEnemy ) { return false; } if ( nextsearch < level.time ) { state = 0; } switch( state ) { case 0 : chase.Begin( self ); movegoal = PathManager.NearestNode( self.currentEnemy->worldorigin, &self, false ); if ( movegoal ) { Path *path; FindEnemyPath find; find.heuristic.self = &self; find.heuristic.setSize( self.size ); find.heuristic.entnum = self.entnum; path = find.FindPath( self.worldorigin, movegoal->worldorigin ); movegoal = NULL; if ( path ) { float dist; float movedist; // // check range // if distance is less then total_delta, than lets go straight for him // dist = path->DistanceAlongPath( self.worldorigin ); movedist = self.total_delta.length(); if ( dist >= movedist ) { movegoal = path->End(); chase.SetGoal( movegoal ); chase.SetPath( path ); } } } if ( !movegoal ) { if ( self.CanSee( self.currentEnemy ) ) { chase.SetGoalPos( self.currentEnemy->worldorigin ); } else { // // since we can't reach em // clear out enemy state // self.ClearEnemies(); return false; } } if ( anim.length() && ( anim != self.animname ) ) { self.SetAnim( anim ); } state = 1; nextsearch = level.time + 3; case 1 : if ( self.WithinDistance( self.currentEnemy, howclose ) ) { chase.End( self ); return false; } if ( !chase.Evaluate( self ) || ( nextsearch < level.time ) ) { state = 0; } break; } return true; } void GetCloseToEnemy::End ( Actor &self ) { chase.End( self ); } /**************************************************************************** PlayAnimSeekEnemy Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, PlayAnimSeekEnemy, NULL ); ResponseDef PlayAnimSeekEnemy::Responses[] = { { &EV_Behavior_Args, ( Response )PlayAnimSeekEnemy::SetArgs }, { &EV_Behavior_AnimDone, ( Response )PlayAnimSeekEnemy::AnimDone }, { NULL, NULL } }; void PlayAnimSeekEnemy::ShowInfo ( Actor &self ) { Behavior::ShowInfo( self ); gi.printf( "\naim:\n" ); aim.ShowInfo( self ); gi.printf( "\nmode: %d\n", mode ); gi.printf( "anim: %s\n", anim.c_str() ); gi.printf( "animdone: %d\n", animdone ); } void PlayAnimSeekEnemy::Begin ( Actor &self ) { oldanim = self.animname; mode = 0; animdone = false; } void PlayAnimSeekEnemy::SetArgs ( Event *ev ) { anim = ev->GetString( 2 ); } void PlayAnimSeekEnemy::AnimDone ( Event *ev ) { animdone = true; } qboolean PlayAnimSeekEnemy::Evaluate ( Actor &self ) { switch( mode ) { case 0 : if ( !anim.length() ) { } if ( !self.currentEnemy ) { return false; } animdone = false; self.SetAnim( anim.c_str(), EV_Actor_NotifyBehavior ); mode = 1; case 1 : aim.SetTarget( self.currentEnemy ); aim.Evaluate( self ); // finish up the attack if ( animdone ) { return false; } break; } return true; } void PlayAnimSeekEnemy::End ( Actor &self ) { aim.End( self ); if ( oldanim.length() ) { self.SetAnim( oldanim.c_str() ); } }