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