567 lines
14 KiB
C++
567 lines
14 KiB
C++
/*
|
|
================================================================
|
|
THUG & ADDITIONAL AI
|
|
================================================================
|
|
|
|
Copyright (C) 1998 by 2015, Inc.
|
|
All rights reserved.
|
|
|
|
This source is may not be distributed and/or modified without
|
|
expressly written permission by 2015, Inc.
|
|
*/
|
|
|
|
#include "g_local.h"
|
|
#include "actor.h"
|
|
#include "thug.h"
|
|
#include "behavior.h"
|
|
#include "path.h"
|
|
|
|
/****************************************************************************
|
|
|
|
DuckAttack Class Definition
|
|
|
|
Added by Boon, who doesn't know C++
|
|
I've made this class a descendant of Behavior, and based the code on
|
|
that of strafeattack, and commented out the methods I don't want to change.
|
|
It should really be called LeanAttack, since it lines the player up so he is
|
|
ready to lean out and fire. That is, except for the fact that it looks for
|
|
crouching animations when seeing if it can do stuff.
|
|
|
|
****************************************************************************/
|
|
|
|
CLASS_DECLARATION( Behavior, DuckAttack, NULL );
|
|
|
|
ResponseDef DuckAttack::Responses[] =
|
|
{
|
|
{ &EV_Behavior_AnimDone, ( Response )DuckAttack::AnimDone },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
|
|
void DuckAttack::ShowInfo
|
|
(
|
|
Actor &self
|
|
)
|
|
|
|
{
|
|
Behavior::ShowInfo( self );
|
|
|
|
turn.ShowInfo( self );
|
|
}
|
|
|
|
|
|
void DuckAttack::AnimDone
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
animdone = true;
|
|
}
|
|
|
|
|
|
void DuckAttack::Begin
|
|
(
|
|
Actor &self
|
|
)
|
|
|
|
{
|
|
state = 0;
|
|
animdone = true;
|
|
}
|
|
|
|
qboolean DuckAttack::Evaluate
|
|
(
|
|
Actor &self
|
|
)
|
|
|
|
{
|
|
int num;
|
|
Vector delta;
|
|
Vector left;
|
|
Vector pos;
|
|
|
|
if ( !self.currentEnemy )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!animdone)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
switch( state )
|
|
{
|
|
case 0 :
|
|
// Start turning towards the enemy
|
|
delta = self.currentEnemy->worldorigin - self.worldorigin;
|
|
delta.z = 0; //KDT fix to prevent tipping over :)
|
|
self.setAngles( delta.toAngles() );
|
|
|
|
state = 2;
|
|
break;
|
|
|
|
case 1 :
|
|
state = 2;
|
|
// No need to return here as we really just want to go onto the next case now
|
|
|
|
case 2 :
|
|
// This bit finds the direction of the enemy, so we can strafe around him
|
|
delta = self.currentEnemy->worldorigin - self.worldorigin;
|
|
left.x = -delta.y;
|
|
left.y = delta.x;
|
|
left.normalize();
|
|
|
|
//first, check to see if we can shoot him from where we are
|
|
if (self.CanShootFrom(self.worldorigin, self.currentEnemy,false))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//If we couldn't hit the enemy from where we are, see if we could hit him by
|
|
//leaning. If we can, let Lean_AimAndShoot take care of it
|
|
num = gi.Anim_Random( self.edict->s.modelindex, "crouch_strafe_left" );
|
|
if ( num != -1 )
|
|
{
|
|
gi.Anim_Delta( self.edict->s.modelindex, num, delta.vec3() );
|
|
delta *= self.edict->s.scale *0.8;
|
|
pos = self.worldorigin + left * delta.length();
|
|
if ( self.CanMoveTo( pos ) && self.CanShootFrom( pos, self.currentEnemy, false ) )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
num = gi.Anim_Random( self.edict->s.modelindex, "crouch_strafe_right" );
|
|
if ( num != -1 )
|
|
{
|
|
gi.Anim_Delta( self.edict->s.modelindex, num, delta.vec3() );
|
|
delta *= self.edict->s.scale *0.8;
|
|
pos = self.worldorigin - left * delta.length();
|
|
if ( self.CanMoveTo( pos ) && self.CanShootFrom( pos, self.currentEnemy, false ) )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//If we couldn't hit the enemy from where we are, see if we could hit him by
|
|
//strafing first and then leaning. If we can, do the strafing bit now
|
|
num = gi.Anim_Random( self.edict->s.modelindex, "crouch_strafe_left" );
|
|
if ( num != -1 )
|
|
{
|
|
gi.Anim_Delta( self.edict->s.modelindex, num, delta.vec3() );
|
|
delta *= self.edict->s.scale *1.8; /* times 1.8 because we have 1 for the strafe and 0.8 for the lean */
|
|
pos = self.worldorigin + left * delta.length();
|
|
if ( self.CanMoveTo( pos ) && self.CanShootFrom( pos, self.currentEnemy, false ) )
|
|
{
|
|
// We set the flag Actor_FinishedBehavior so that DuckAttack will terminate
|
|
// when the animation finishes. We return true so that it won't terminate
|
|
// immediately. We set state to 3 so that it won't do anything else while
|
|
// it is waiting to terminate.
|
|
self.SetAnim( "crouch_strafe_left", EV_Actor_FinishedBehavior );
|
|
state = 3;
|
|
return true;
|
|
}
|
|
}
|
|
num = gi.Anim_Random( self.edict->s.modelindex, "crouch_strafe_right" );
|
|
if ( num != -1 )
|
|
{
|
|
gi.Anim_Delta( self.edict->s.modelindex, num, delta.vec3() );
|
|
delta *= self.edict->s.scale *1.8;
|
|
pos = self.worldorigin - left * delta.length();
|
|
if ( self.CanMoveTo( pos ) && self.CanShootFrom( pos, self.currentEnemy, false ) )
|
|
{
|
|
self.SetAnim( "crouch_strafe_right", EV_Actor_FinishedBehavior );
|
|
state = 3;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Now see if we could do it by strafing even further...
|
|
num = gi.Anim_Random( self.edict->s.modelindex, "crouch_strafe_left" );
|
|
if ( num != -1 )
|
|
{
|
|
gi.Anim_Delta( self.edict->s.modelindex, num, delta.vec3() );
|
|
delta *= self.edict->s.scale *3.8; // times 4 = 3 for strafes and 0.8 for a lean
|
|
pos = self.worldorigin + left * delta.length();
|
|
if ( self.CanMoveTo( pos ) && self.CanShootFrom( pos, self.currentEnemy, false ) )
|
|
{
|
|
// Set the anim to go left, and let this case run through again to take
|
|
// care of the second (and possibly third) strafes left
|
|
// Note that I wanted to set EV_NotifyBehavior here so this would run recursively,
|
|
// but it didn't work for some reason, so I've had to set EV_Finished which means it
|
|
// doesn't work quite as well as I wanted it to.
|
|
animdone = false;
|
|
self.SetAnim( "crouch_strafe_left", EV_Actor_FinishedBehavior );
|
|
state = 3;//2;
|
|
return true;
|
|
}
|
|
}
|
|
num = gi.Anim_Random( self.edict->s.modelindex, "crouch_strafe_right" );
|
|
if ( num != -1 )
|
|
{
|
|
gi.Anim_Delta( self.edict->s.modelindex, num, delta.vec3() );
|
|
delta *= self.edict->s.scale *3.8;
|
|
pos = self.worldorigin - left * delta.length();
|
|
if ( self.CanMoveTo( pos ) && self.CanShootFrom( pos, self.currentEnemy, false ) )
|
|
{
|
|
animdone = false;
|
|
self.SetAnim( "crouch_strafe_right", EV_Actor_FinishedBehavior );
|
|
state = 3;//2;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//We couldn't hit him by moving a fair distance to either side, so give up on
|
|
//shooting him from here.
|
|
return false;
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void DuckAttack::End
|
|
(
|
|
Actor &self
|
|
)
|
|
|
|
{
|
|
turn.End( self );
|
|
self.SetAnim( "crouch_idle" );
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
Lean_AimAndShoot Class Definition
|
|
|
|
Added by Boon to allow the Thug to lean left and right to shoot around corners
|
|
|
|
****************************************************************************/
|
|
|
|
CLASS_DECLARATION( AimAndShoot, Lean_AimAndShoot, NULL );
|
|
|
|
ResponseDef Lean_AimAndShoot::Responses[] =
|
|
{
|
|
{ &EV_Behavior_Args, ( Response )Lean_AimAndShoot::SetArgs },
|
|
{ &EV_Behavior_AnimDone, ( Response )Lean_AimAndShoot::AnimDone },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
Lean_AimAndShoot::Lean_AimAndShoot()
|
|
{
|
|
maxshots = 1;
|
|
numshots = 0;
|
|
}
|
|
|
|
void Lean_AimAndShoot::Begin (Actor &self)
|
|
{
|
|
// Vector forward;
|
|
Vector left;
|
|
Vector delta;
|
|
Vector pos;
|
|
int num;
|
|
qboolean canshoot;
|
|
|
|
// Before we do anything, check that we have an enemy
|
|
if(!self.currentEnemy)
|
|
{
|
|
canshoot = false; // Setting this causes the evaluate section to drop out as well
|
|
return;
|
|
}
|
|
|
|
enemy_health = 0;
|
|
mode = 0;
|
|
animdone = false;
|
|
|
|
delta = self.currentEnemy->worldorigin - self.worldorigin;
|
|
left.x = -delta.y;
|
|
left.y = delta.x;
|
|
left.normalize();
|
|
|
|
// Find out if we can hit the player from where we are, and if we can't,
|
|
// try moving around
|
|
|
|
if ( !self.CanShootFrom( self.worldorigin, self.currentEnemy, false ) )
|
|
// If we couldn't hit him without moving, try moving left and then right,
|
|
// and if that works, don't actually move, but lean
|
|
{
|
|
moveanim = animprefix + "strafe_left";
|
|
num = gi.Anim_Random( self.edict->s.modelindex, moveanim.c_str() );
|
|
if ( num != -1 )
|
|
{
|
|
gi.Anim_Delta( self.edict->s.modelindex, num, delta.vec3() );
|
|
delta *= self.edict->s.scale *0.8;
|
|
pos = self.worldorigin + left * delta.length();
|
|
if ( self.CanShootFrom( pos, self.currentEnemy, false ) )
|
|
{
|
|
animprefix = animprefix + "left_";
|
|
}
|
|
else //ie self.can't shoot from
|
|
{
|
|
moveanim = animprefix + "strafe_right";
|
|
num = gi.Anim_Random( self.edict->s.modelindex, moveanim.c_str() );
|
|
if ( num != -1 )
|
|
{
|
|
gi.Anim_Delta( self.edict->s.modelindex, num, delta.vec3() );
|
|
delta *= self.edict->s.scale *0.8;
|
|
pos = self.worldorigin - left * delta.length();
|
|
if ( self.CanShootFrom( pos, self.currentEnemy, false ) )
|
|
{
|
|
animprefix = animprefix + "right_";
|
|
}
|
|
else
|
|
{ //we couldn't hit him
|
|
canshoot = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (canshoot)
|
|
{
|
|
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 = "";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
readyfireanim = "";
|
|
aimanim = "";
|
|
fireanim = "";
|
|
}
|
|
}
|
|
|
|
|
|
void Lean_AimAndShoot::SetMaxShots
|
|
(
|
|
int num
|
|
)
|
|
|
|
{
|
|
maxshots = (num>>1) + G_Random( num );
|
|
}
|
|
|
|
void Lean_AimAndShoot::SetArgs
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
SetMaxShots( ev->GetInteger( 2 ) );
|
|
if ( ev->NumArgs() > 2 )
|
|
{
|
|
animprefix = ev->GetString( 3 );
|
|
}
|
|
}
|
|
|
|
void Lean_AimAndShoot::AnimDone
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
animdone = true;
|
|
}
|
|
|
|
qboolean Lean_AimAndShoot::Evaluate
|
|
(
|
|
Actor &self
|
|
)
|
|
|
|
{
|
|
// Check that we were given a fire animation by the begin bit
|
|
if (!fireanim.length())
|
|
return false;
|
|
|
|
switch( mode )
|
|
{
|
|
case 0 :
|
|
if ( !self.currentEnemy )
|
|
{
|
|
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
|
|
if ( aimanim.length() )
|
|
{
|
|
animdone = false;
|
|
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
|
|
//Small piece of trivia: the true or false on the end of CanShoot tells the function whether
|
|
//to worry about which direction we are facing or not
|
|
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, true ) )
|
|
{
|
|
// 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 Lean_AimAndShoot::End
|
|
(
|
|
Actor &self
|
|
)
|
|
|
|
{
|
|
aim.End( self );
|
|
moveanim = animprefix + "idle";
|
|
if ( moveanim.length() )
|
|
{
|
|
self.SetAnim( moveanim.c_str(), EV_Actor_NotifyBehavior );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
Slim's concussion gun
|
|
|
|
This is a concussion gun with a different world model
|
|
|
|
*****************************************************************************
|
|
*/
|
|
|
|
CLASS_DECLARATION( ConcussionGun, Slimconcussion, "weapon_slimconcussion" );
|
|
|
|
ResponseDef Slimconcussion::Responses[] =
|
|
{
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
Slimconcussion::Slimconcussion()
|
|
{
|
|
SetModels( "concussion_slim.def", "concussion_slim.def" );
|
|
}
|