5186 lines
98 KiB
C++
5186 lines
98 KiB
C++
//-----------------------------------------------------------------------------
|
|
//
|
|
// $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() );
|
|
}
|
|
}
|