jediacademy/code/game/AI_Rancor.cpp

1685 lines
54 KiB
C++

// leave this line at the top of all AI_xxxx.cpp files for PCH reasons...
#include "g_headers.h"
#include "b_local.h"
// These define the working combat range for these suckers
#define MIN_DISTANCE 128
#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE )
#define MAX_DISTANCE 1024
#define MAX_DISTANCE_SQR ( MAX_DISTANCE * MAX_DISTANCE )
#define LSTATE_CLEAR 0
#define LSTATE_WAITING 1
#define SPF_RANCOR_MUTANT 1
#define SPF_RANCOR_FASTKILL 2
extern qboolean G_EntIsBreakable( int entityNum, gentity_t *breaker );
extern cvar_t *g_dismemberment;
extern cvar_t *g_bobaDebug;
void Rancor_Attack( float distance, qboolean doCharge, qboolean aimAtBlockedEntity );
/*
-------------------------
NPC_Rancor_Precache
-------------------------
*/
void NPC_Rancor_Precache( void )
{
int i;
for ( i = 1; i < 5; i ++ )
{
G_SoundIndex( va("sound/chars/rancor/snort_%d.wav", i) );
}
G_SoundIndex( "sound/chars/rancor/swipehit.wav" );
G_SoundIndex( "sound/chars/rancor/chomp.wav" );
}
void NPC_MutantRancor_Precache( void )
{
G_SoundIndex( "sound/chars/rancor/breath_start.wav" );
G_SoundIndex( "sound/chars/rancor/breath_loop.wav" );
G_EffectIndex( "mrancor/breath" );
}
//FIXME: initialize all my timers
qboolean Rancor_CheckAhead( vec3_t end )
{
trace_t trace;
int clipmask = NPC->clipmask|CONTENTS_BOTCLIP;
//make sure our goal isn't underground (else the trace will fail)
vec3_t bottom = {end[0],end[1],end[2]+NPC->mins[2]};
gi.trace( &trace, end, vec3_origin, vec3_origin, bottom, NPC->s.number, NPC->clipmask );
if ( trace.fraction < 1.0f )
{//in the ground, raise it up
end[2] -= NPC->mins[2]*(1.0f-trace.fraction)-0.125f;
}
gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, end, NPC->s.number, clipmask );
if ( trace.startsolid&&(trace.contents&CONTENTS_BOTCLIP) )
{//started inside do not enter, so ignore them
clipmask &= ~CONTENTS_BOTCLIP;
gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, end, NPC->s.number, clipmask );
}
//Do a simple check
if ( ( trace.allsolid == qfalse ) && ( trace.startsolid == qfalse ) && ( trace.fraction == 1.0f ) )
return qtrue;
if ( trace.entityNum < ENTITYNUM_WORLD
&& G_EntIsBreakable( trace.entityNum, NPC ) )
{//breakable brush in our way, break it
// NPCInfo->blockedEntity = &g_entities[trace.entityNum];
return qtrue;
}
//Aw screw it, always try to go straight at him if we can at all
if ( trace.fraction >= 0.25f )
return qtrue;
//FIXME: if something in the way that's not the world, set blocked ent
return qfalse;
}
/*
-------------------------
Rancor_Idle
-------------------------
*/
void Rancor_Idle( void )
{
NPCInfo->localState = LSTATE_CLEAR;
//If we have somewhere to go, then do that
if ( UpdateGoal() )
{
ucmd.buttons &= ~BUTTON_WALKING;
NPC_MoveToGoal( qtrue );
}
}
qboolean Rancor_CheckRoar( gentity_t *self )
{
if ( !self->wait )
{//haven't ever gotten mad yet
self->wait = 1;//do this only once
NPC_SetAnim( self, SETANIM_BOTH, BOTH_STAND1TO2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
TIMER_Set( self, "rageTime", self->client->ps.legsAnimTimer );
return qtrue;
}
return qfalse;
}
/*
-------------------------
Rancor_Patrol
-------------------------
*/
void Rancor_Patrol( void )
{
NPCInfo->localState = LSTATE_CLEAR;
//If we have somewhere to go, then do that
if ( UpdateGoal() )
{
ucmd.buttons &= ~BUTTON_WALKING;
NPC_MoveToGoal( qtrue );
}
if ( NPC_CheckEnemyExt( qtrue ) == qfalse )
{
Rancor_Idle();
return;
}
Rancor_CheckRoar( NPC );
TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) );
}
/*
-------------------------
Rancor_Move
-------------------------
*/
void Rancor_Move( qboolean visible )
{
if ( NPCInfo->localState != LSTATE_WAITING )
{
NPCInfo->goalEntity = NPC->enemy;
NPCInfo->goalRadius = NPC->maxs[0]+(MIN_DISTANCE*NPC->s.modelScale[0]); // just get us within combat range
//FIXME: for some reason, if NPC_MoveToGoal fails, it sets my angles to my lastPathAngles, which I don't want
float savYaw = NPCInfo->desiredYaw;
bool savWalking = !!(ucmd.buttons&BUTTON_WALKING);
if ( !NPC_MoveToGoal( qtrue ) )
{//can't macro-nav, just head right for him
//FIXME: if something in the way that's not the world, set blocked ent
vec3_t dest;
VectorCopy( NPCInfo->goalEntity->currentOrigin, dest );
if ( Rancor_CheckAhead( dest ) )
{//use our temp move straight to goal check
if (!savWalking)
{
ucmd.buttons &= ~BUTTON_WALKING; // Unset from MoveToGoal()
}
STEER::Activate(NPC);
STEER::Seek(NPC, dest);
STEER::AvoidCollisions(NPC);
STEER::DeActivate(NPC, &ucmd);
/*
VectorSubtract( dest, NPC->currentOrigin, NPC->client->ps.moveDir );
NPC->client->ps.speed = VectorNormalize( NPC->client->ps.moveDir );
NPCInfo->desiredYaw = vectoyaw( NPC->client->ps.moveDir );
if ( (ucmd.buttons&BUTTON_WALKING) && NPC->client->ps.speed > NPCInfo->stats.walkSpeed )
{
NPC->client->ps.speed = NPCInfo->stats.walkSpeed;
}
else
{
if ( NPC->client->ps.speed < NPCInfo->stats.walkSpeed )
{
NPC->client->ps.speed = NPCInfo->stats.walkSpeed;
}
if ( !(ucmd.buttons&BUTTON_WALKING) && NPC->client->ps.speed < NPCInfo->stats.runSpeed )
{
NPC->client->ps.speed = NPCInfo->stats.runSpeed;
}
else if ( NPC->client->ps.speed > NPCInfo->stats.runSpeed )
{
NPC->client->ps.speed = NPCInfo->stats.runSpeed;
}
}
*/
}
else
{//all else fails, look at him
// gi.Printf("Fail\n");
NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw = savYaw;
/* if ( !NPCInfo->blockedEntity )
{//not already trying to break a breakable somewhere
if ( NPC->enemy && NPC->enemy->client && NPC->enemy->client->ps.groundEntityNum < ENTITYNUM_WORLD )
{//hmm, maybe he's on a breakable brush?
if ( G_EntIsBreakable( NPC->enemy->client->ps.groundEntityNum, NPC ) )
{//break it!
gentity_t *breakable = &g_entities[NPC->enemy->client->ps.groundEntityNum];
int sanityCheck = 0;
//FiXME: and if he's on a stack of 3 breakables?
//FIXME: See if the breakable has a targetname, if so see if the thing targeting it is a breakable, if so, etc...
while ( sanityCheck < 20 && breakable && breakable->targetname )
{
gentity_t *breakableNext = NULL;
while ( sanityCheck < 20 && (breakableNext = G_Find( breakableNext, FOFS(target), breakable->targetname )) != NULL )
{
if ( breakableNext && G_EntIsBreakable( breakableNext->s.number, NPC ) )
{
breakable = breakableNext;
break;
}
else
{
sanityCheck++;
}
}
if ( !breakableNext )
{//not targetted by another breakable that we can break
break;
}
else
{
sanityCheck++;
}
}
NPCInfo->blockedEntity = breakable;
}
}
}*/
if ( !NPCInfo->blockedEntity && NPC->enemy && gi.inPVS(NPC->currentOrigin, NPC->enemy->currentOrigin))
{//nothing to destroy? just go straight at goal dest
qboolean horzClose = qfalse;
if (!savWalking)
{
ucmd.buttons &= ~BUTTON_WALKING; // Unset from MoveToGoal()
}
if ( DistanceHorizontal( NPC->enemy->currentOrigin, NPC->currentOrigin ) < (NPC->maxs[0]+(MIN_DISTANCE*NPC->s.modelScale[0])) )
{//close, just look at him
horzClose = qtrue;
NPC_FaceEnemy( qtrue );
}
else
{//try to move towards him
STEER::Activate(NPC);
STEER::Seek(NPC, dest);
STEER::AvoidCollisions(NPC);
STEER::DeActivate(NPC, &ucmd);
}
//let him know he should attack at random out of frustration?
if ( NPCInfo->goalEntity == NPC->enemy )
{
if ( TIMER_Done( NPC, "attacking" )
&& TIMER_Done( NPC, "frustrationAttack" ) )
{
float enemyDist = Distance( dest, NPC->currentOrigin );
if ( (!horzClose||!Q_irand(0,5))
&& Q_irand( 0, 1 ) )
{
Rancor_Attack( enemyDist, qtrue, qfalse );
}
else
{
Rancor_Attack( enemyDist, qfalse, qfalse );
}
if ( horzClose )
{
TIMER_Set( NPC, "frustrationAttack", Q_irand( 2000, 5000 ) );
}
else
{
TIMER_Set( NPC, "frustrationAttack", Q_irand( 5000, 15000 ) );
}
}
}
}
}
}
}
}
//---------------------------------------------------------
extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock );
extern qboolean G_DoDismemberment( gentity_t *self, vec3_t point, int mod, int damage, int hitLoc, qboolean force = qfalse );
extern float NPC_EntRangeFromBolt( gentity_t *targEnt, int boltIndex );
extern int NPC_GetEntsNearBolt( gentity_t **radiusEnts, float radius, int boltIndex, vec3_t boltOrg );
void Rancor_DropVictim( gentity_t *self )
{
//FIXME: if Rancor dies, it should drop its victim.
//FIXME: if Rancor is removed, it must remove its victim.
//FIXME: if in BOTH_HOLD_DROP, throw them a little, too?
if ( self->activator )
{
if ( self->activator->client )
{
self->activator->client->ps.eFlags &= ~EF_HELD_BY_RANCOR;
}
self->activator->activator = NULL;
if ( self->activator->health <= 0 )
{
if ( self->activator->s.number )
{//never free player
if ( self->count == 1 )
{//in my hand, just drop them
if ( self->activator->client )
{
self->activator->client->ps.legsAnimTimer = self->activator->client->ps.torsoAnimTimer = 0;
//FIXME: ragdoll?
}
}
else
{
G_FreeEntity( self->activator );
}
}
else
{
self->activator->s.eFlags |= EF_NODRAW;
if ( self->activator->client )
{
self->activator->client->ps.eFlags |= EF_NODRAW;
}
self->activator->clipmask &= ~CONTENTS_BODY;
}
}
else
{
if ( self->activator->NPC )
{//start thinking again
self->activator->NPC->nextBStateThink = level.time;
}
//clear their anim and let them fall
self->activator->client->ps.legsAnimTimer = self->activator->client->ps.torsoAnimTimer = 0;
}
if ( self->enemy == self->activator )
{
self->enemy = NULL;
}
if ( self->activator->s.number == 0 )
{//don't attack the player again for a bit
TIMER_Set( self, "attackDebounce", Q_irand( 2000, 4000+((2-g_spskill->integer)*2000) ) );
}
self->activator = NULL;
}
self->count = 0;//drop him
}
void Rancor_Swing( int boltIndex, qboolean tryGrab )
{
gentity_t *radiusEnts[ 128 ];
int numEnts;
const float radius = (NPC->spawnflags&SPF_RANCOR_MUTANT)?200:88;
const float radiusSquared = (radius*radius);
int i;
vec3_t boltOrg;
vec3_t originUp;
VectorCopy(NPC->currentOrigin, originUp);
originUp[2] += (NPC->maxs[2]*0.75f);
numEnts = NPC_GetEntsNearBolt( radiusEnts, radius, boltIndex, boltOrg );
//if ( NPCInfo->blockedEntity && G_EntIsBreakable( NPCInfo->blockedEntity->s.number, NPC ) )
{//attacking a breakable brush
//HMM... maybe always do this?
//if boltOrg inside a breakable brush, damage it
trace_t trace;
gi.trace( &trace, NPC->pos3, vec3_origin, vec3_origin, boltOrg, NPC->s.number, CONTENTS_SOLID|CONTENTS_BODY );
#ifndef FINAL_BUILD
if ( g_bobaDebug->integer > 0 )
{
G_DebugLine(NPC->pos3, boltOrg, 1000, 0x000000ff, qtrue);
}
#endif
//remember pos3 for the trace from last hand pos to current hand pos next time
VectorCopy( boltOrg, NPC->pos3 );
//FIXME: also do a trace TO the bolt from where we are...?
if ( G_EntIsBreakable( trace.entityNum, NPC ) )
{
G_Damage( &g_entities[trace.entityNum], NPC, NPC, vec3_origin, boltOrg, 100, 0, MOD_MELEE );
}
else
{//fuck, do an actual line trace, I guess...
gi.trace( &trace, originUp, vec3_origin, vec3_origin, boltOrg, NPC->s.number, CONTENTS_SOLID|CONTENTS_BODY );
#ifndef FINAL_BUILD
if ( g_bobaDebug->integer > 0 )
{
G_DebugLine(originUp, boltOrg, 1000, 0x000000ff, qtrue);
}
#endif
if ( G_EntIsBreakable( trace.entityNum, NPC ) )
{
G_Damage( &g_entities[trace.entityNum], NPC, NPC, vec3_origin, boltOrg, 200, 0, MOD_MELEE );
}
}
}
for ( i = 0; i < numEnts; i++ )
{
if ( !radiusEnts[i]->inuse )
{
continue;
}
if ( radiusEnts[i] == NPC )
{//Skip the rancor ent
continue;
}
if ( radiusEnts[i]->client == NULL )
{//must be a client
continue;
}
if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_RANCOR)
||(radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_WAMPA) )
{//can't be one already being held
continue;
}
if ( (radiusEnts[i]->s.eFlags&EF_NODRAW) )
{//not if invisible
continue;
}
/*
if ( !radiusEnts[i]->contents )
{//not if non-solid
continue;
}
*/
if ( DistanceSquared( radiusEnts[i]->currentOrigin, boltOrg ) <= radiusSquared )
{
if ( !gi.inPVS( radiusEnts[i]->currentOrigin, NPC->currentOrigin ) )
{//don't grab anything that's in another PVS
continue;
}
/*
qboolean skipGrab = qfalse;
if ( tryGrab//want to grab
&& (NPC->spawnflags&SPF_RANCOR_FASTKILL)//mutant rancor
&& radiusEnts[i]->s.number >= MAX_CLIENTS //not the player
&& Q_irand( 0, 1 ) )//50% chance
{//don't grab them, just smack them away
skipGrab = qtrue;
}
*/
if ( tryGrab
//&& !skipGrab
&& NPC->count != 1 //don't have one in hand or in mouth already - FIXME: allow one in hand and any number in mouth!
&& radiusEnts[i]->client->NPC_class != CLASS_RANCOR
&& radiusEnts[i]->client->NPC_class != CLASS_GALAKMECH
&& radiusEnts[i]->client->NPC_class != CLASS_ATST
&& radiusEnts[i]->client->NPC_class != CLASS_GONK
&& radiusEnts[i]->client->NPC_class != CLASS_R2D2
&& radiusEnts[i]->client->NPC_class != CLASS_R5D2
&& radiusEnts[i]->client->NPC_class != CLASS_MARK1
&& radiusEnts[i]->client->NPC_class != CLASS_MARK2
&& radiusEnts[i]->client->NPC_class != CLASS_MOUSE
&& radiusEnts[i]->client->NPC_class != CLASS_PROBE
&& radiusEnts[i]->client->NPC_class != CLASS_SEEKER
&& radiusEnts[i]->client->NPC_class != CLASS_REMOTE
&& radiusEnts[i]->client->NPC_class != CLASS_SENTRY
&& radiusEnts[i]->client->NPC_class != CLASS_INTERROGATOR
&& radiusEnts[i]->client->NPC_class != CLASS_VEHICLE )
{//grab
if ( NPC->count == 2 )
{//have one in my mouth, remove him
TIMER_Remove( NPC, "clearGrabbed" );
Rancor_DropVictim( NPC );
}
NPC->enemy = radiusEnts[i];//make him my new best friend
radiusEnts[i]->client->ps.eFlags |= EF_HELD_BY_RANCOR;
//FIXME: this makes it so that the victim can't hit us with shots! Just use activator or something
radiusEnts[i]->activator = NPC; // kind of dumb, but when we are locked to the Rancor, we are owned by it.
NPC->activator = radiusEnts[i];//remember him
NPC->count = 1;//in my hand
//wait to attack
TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer + Q_irand(500, 2500) );
if ( radiusEnts[i]->health > 0 )
{//do pain on enemy
GEntity_PainFunc( radiusEnts[i], NPC, NPC, radiusEnts[i]->currentOrigin, 0, MOD_CRUSH );
}
else if ( radiusEnts[i]->client )
{
NPC_SetAnim( radiusEnts[i], SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
}
}
else
{//smack
G_Sound( radiusEnts[i], G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) );
//actually push the enemy
vec3_t pushDir;
/*
//VectorSubtract( radiusEnts[i]->currentOrigin, boltOrg, pushDir );
VectorSubtract( radiusEnts[i]->currentOrigin, NPC->currentOrigin, pushDir );
pushDir[2] = Q_flrand( 100, 200 );
VectorNormalize( pushDir );
*/
if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL)
&& radiusEnts[i]->s.number >= MAX_CLIENTS )
{
G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, boltOrg, radiusEnts[i]->health+1000, (DAMAGE_NO_KNOCKBACK|DAMAGE_NO_PROTECTION), MOD_MELEE );
}
vec3_t angs;
VectorCopy( NPC->client->ps.viewangles, angs );
angs[YAW] += Q_flrand( 25, 50 );
angs[PITCH] = Q_flrand( -25, -15 );
AngleVectors( angs, pushDir, NULL, NULL );
if ( radiusEnts[i]->client->NPC_class != CLASS_RANCOR
&& radiusEnts[i]->client->NPC_class != CLASS_ATST
&& !(radiusEnts[i]->flags&FL_NO_KNOCKBACK) )
{
G_Throw( radiusEnts[i], pushDir, 250 );
if ( radiusEnts[i]->health > 0 )
{//do pain on enemy
G_Knockdown( radiusEnts[i], NPC, pushDir, 100, qtrue );
}
}
}
}
}
}
void Rancor_Smash( void )
{
gentity_t *radiusEnts[ 128 ];
int numEnts;
const float radius = (NPC->spawnflags&SPF_RANCOR_MUTANT)?256:128;
const float halfRadSquared = ((radius/2)*(radius/2));
const float radiusSquared = (radius*radius);
float distSq;
int i;
vec3_t boltOrg;
AddSoundEvent( NPC, NPC->currentOrigin, 512, AEL_DANGER, qfalse, qtrue );
numEnts = NPC_GetEntsNearBolt( radiusEnts, radius, NPC->handLBolt, boltOrg );
//if ( NPCInfo->blockedEntity && G_EntIsBreakable( NPCInfo->blockedEntity->s.number, NPC ) )
{//attacking a breakable brush
//HMM... maybe always do this?
//if boltOrg inside a breakable brush, damage it
trace_t trace;
gi.trace( &trace, boltOrg, vec3_origin, vec3_origin, NPC->pos3, NPC->s.number, CONTENTS_SOLID|CONTENTS_BODY );
#ifndef FINAL_BUILD
if ( g_bobaDebug->integer > 0 )
{
G_DebugLine(NPC->pos3, boltOrg, 1000, 0x000000ff, qtrue);
}
#endif
//remember pos3 for the trace from last hand pos to current hand pos next time
VectorCopy( boltOrg, NPC->pos3 );
//FIXME: also do a trace TO the bolt from where we are...?
if ( G_EntIsBreakable( trace.entityNum, NPC ) )
{
G_Damage( &g_entities[trace.entityNum], NPC, NPC, vec3_origin, boltOrg, 200, 0, MOD_MELEE );
}
else
{//fuck, do an actual line trace, I guess...
gi.trace( &trace, NPC->currentOrigin, vec3_origin, vec3_origin, boltOrg, NPC->s.number, CONTENTS_SOLID|CONTENTS_BODY );
#ifndef FINAL_BUILD
if ( g_bobaDebug->integer > 0 )
{
G_DebugLine(NPC->currentOrigin, boltOrg, 1000, 0x000000ff, qtrue);
}
#endif
if ( G_EntIsBreakable( trace.entityNum, NPC ) )
{
G_Damage( &g_entities[trace.entityNum], NPC, NPC, vec3_origin, boltOrg, 200, 0, MOD_MELEE );
}
}
}
for ( i = 0; i < numEnts; i++ )
{
if ( !radiusEnts[i]->inuse )
{
continue;
}
if ( radiusEnts[i] == NPC )
{//Skip the rancor ent
continue;
}
if ( radiusEnts[i]->client == NULL )
{//must be a client
if ( G_EntIsBreakable( radiusEnts[i]->s.number, NPC ) )
{//damage breakables within range, but not as much
if ( !Q_irand( 0, 1 ) )
{
G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, 100, 0, MOD_MELEE );
}
}
continue;
}
if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_RANCOR) )
{//can't be one being held
continue;
}
if ( (radiusEnts[i]->s.eFlags&EF_NODRAW) )
{//not if invisible
continue;
}
distSq = DistanceSquared( radiusEnts[i]->currentOrigin, boltOrg );
if ( distSq <= radiusSquared )
{
if ( distSq < halfRadSquared )
{//close enough to do damage, too
G_Sound( radiusEnts[i], G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) );
if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL)
&& radiusEnts[i]->s.number >= MAX_CLIENTS )
{
G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, boltOrg, radiusEnts[i]->health+1000, (DAMAGE_NO_KNOCKBACK|DAMAGE_NO_PROTECTION), MOD_MELEE );
}
else if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )//FIXME: a flag or something would be better
{//more damage
G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, Q_irand( 40, 55 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE );
}
else
{
G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, Q_irand( 10, 25 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE );
}
}
if ( radiusEnts[i]->health > 0
&& radiusEnts[i]->client
&& radiusEnts[i]->client->NPC_class != CLASS_RANCOR
&& radiusEnts[i]->client->NPC_class != CLASS_ATST )
{
if ( distSq < halfRadSquared
|| radiusEnts[i]->client->ps.groundEntityNum != ENTITYNUM_NONE )
{//within range of my fist or withing ground-shaking range and not in the air
if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )
{
G_Knockdown( radiusEnts[i], NPC, vec3_origin, 500, qtrue );
}
else
{
G_Knockdown( radiusEnts[i], NPC, vec3_origin, Q_irand( 200, 350), qtrue );
}
}
}
}
}
}
void Rancor_Bite( void )
{
gentity_t *radiusEnts[ 128 ];
int numEnts;
const float radius = 100;
const float radiusSquared = (radius*radius);
int i;
vec3_t boltOrg;
numEnts = NPC_GetEntsNearBolt( radiusEnts, radius, NPC->gutBolt, boltOrg );
for ( i = 0; i < numEnts; i++ )
{
if ( !radiusEnts[i]->inuse )
{
continue;
}
if ( radiusEnts[i] == NPC )
{//Skip the rancor ent
continue;
}
if ( radiusEnts[i]->client == NULL )
{//must be a client
continue;
}
if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_RANCOR) )
{//can't be one already being held
continue;
}
if ( (radiusEnts[i]->s.eFlags&EF_NODRAW) )
{//not if invisible
continue;
}
if ( DistanceSquared( radiusEnts[i]->currentOrigin, boltOrg ) <= radiusSquared )
{
if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL)
&& radiusEnts[i]->s.number >= MAX_CLIENTS )
{
G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, radiusEnts[i]->health+1000, (DAMAGE_NO_KNOCKBACK|DAMAGE_NO_PROTECTION), MOD_MELEE );
}
else if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )
{//more damage
G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, Q_irand( 35, 50 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE );
}
else
{
G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, Q_irand( 15, 30 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE );
}
if ( radiusEnts[i]->health <= 0 && radiusEnts[i]->client )
{//killed them, chance of dismembering
if ( !Q_irand( 0, 1 ) )
{//bite something off
int hitLoc = HL_WAIST;
if ( g_dismemberment->integer < 3 )
{
hitLoc = Q_irand( HL_BACK_RT, HL_HAND_LT );
}
else
{
hitLoc = Q_irand( HL_WAIST, HL_HEAD );
}
if ( hitLoc == HL_HEAD )
{
NPC_SetAnim( radiusEnts[i], SETANIM_BOTH, BOTH_DEATH17, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
}
else if ( hitLoc == HL_WAIST )
{
NPC_SetAnim( radiusEnts[i], SETANIM_BOTH, BOTH_DEATHBACKWARD2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
}
radiusEnts[i]->client->dismembered = false;
//FIXME: the limb should just disappear, cuz I ate it
G_DoDismemberment( radiusEnts[i], radiusEnts[i]->currentOrigin, MOD_SABER, 1000, hitLoc, qtrue );
}
}
G_Sound( radiusEnts[i], G_SoundIndex( "sound/chars/rancor/chomp.wav" ) );
}
}
}
//------------------------------
extern gentity_t *TossClientItems( gentity_t *self );
void Rancor_Attack( float distance, qboolean doCharge, qboolean aimAtBlockedEntity )
{
if ( !TIMER_Exists( NPC, "attacking" )
&& TIMER_Done( NPC, "attackDebounce" ) )
{
if ( NPC->count == 2 && NPC->activator )
{
}
else if ( NPC->count == 1 && NPC->activator )
{//holding enemy
if ( (!(NPC->spawnflags&SPF_RANCOR_FASTKILL) ||NPC->activator->s.number<MAX_CLIENTS)
&& NPC->activator->health > 0
&& Q_irand( 0, 1 ) )
{//quick bite
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
TIMER_Set( NPC, "attack_dmg", 450 );
}
else
{//full eat
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK3, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
TIMER_Set( NPC, "attack_dmg", 900 );
//Make victim scream in fright
if ( NPC->activator->health > 0 && NPC->activator->client )
{
G_AddEvent( NPC->activator, Q_irand(EV_DEATH1, EV_DEATH3), 0 );
NPC_SetAnim( NPC->activator, SETANIM_TORSO, BOTH_FALLDEATH1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
if ( NPC->activator->NPC )
{//no more thinking for you
TossClientItems( NPC );
NPC->activator->NPC->nextBStateThink = Q3_INFINITE;
}
}
}
}
else if ( NPC->enemy->health > 0 && doCharge )
{//charge
if ( !Q_irand( 0, 3 ) )
{
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK5, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
TIMER_Set( NPC, "attack_dmg", 1250 );
if ( NPC->enemy && NPC->enemy->s.number == 0 )
{//don't attack the player again for a bit
TIMER_Set( NPC, "attackDebounce", NPC->client->ps.legsAnimTimer + Q_irand( 2000, 4000+((2-g_spskill->integer)*2000) ) );
}
}
else if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )
{//breath attack
int breathAnim = BOTH_ATTACK4;
gentity_t *checkEnt = NULL;
vec3_t center;
if ( NPC->enemy && NPC->enemy->inuse )
{
checkEnt = NPC->enemy;
VectorCopy( NPC->enemy->currentOrigin, center );
}
else if ( NPCInfo->blockedEntity && NPCInfo->blockedEntity->inuse )
{
checkEnt = NPCInfo->blockedEntity;
//if it has an origin brush, use it...
if ( VectorCompare( NPCInfo->blockedEntity->s.origin, vec3_origin ) )
{//no origin brush, calc center
VectorAdd( NPCInfo->blockedEntity->mins, NPCInfo->blockedEntity->maxs, center );
VectorScale( center, 0.5f, center );
}
else
{//use origin brush as center
VectorCopy( NPCInfo->blockedEntity->s.origin, center );
}
}
if ( checkEnt )
{
float zHeightRelative = center[2]-NPC->currentOrigin[2];
if ( zHeightRelative >= (128.0f*NPC->s.modelScale[2]) )
{
breathAnim = BOTH_ATTACK7;
}
else if ( zHeightRelative >= (64.0f*NPC->s.modelScale[2]) )
{
breathAnim = BOTH_ATTACK6;
}
}
NPC_SetAnim( NPC, SETANIM_BOTH, breathAnim, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
//start effect here
G_PlayEffect( G_EffectIndex( "mrancor/breath" ), NPC->playerModel, NPC->gutBolt, NPC->s.number, NPC->currentOrigin, (NPC->client->ps.legsAnimTimer-500), qfalse );
TIMER_Set( NPC, "breathAttack", NPC->client->ps.legsAnimTimer-500 );
G_SoundOnEnt( NPC, CHAN_WEAPON, "sound/chars/rancor/breath_start.wav" );
NPC->s.loopSound = G_SoundIndex( "sound/chars/rancor/breath_loop.wav" );
if ( NPC->enemy && NPC->enemy->s.number == 0 )
{//don't attack the player again for a bit
TIMER_Set( NPC, "attackDebounce", NPC->client->ps.legsAnimTimer + Q_irand( 2000, 4000+((2-g_spskill->integer)*2000) ) );
}
}
else
{
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
TIMER_Set( NPC, "attack_dmg", 1250 );
vec3_t fwd, yawAng ={0, NPC->client->ps.viewangles[YAW], 0};
AngleVectors( yawAng, fwd, NULL, NULL );
VectorScale( fwd, distance*1.5f, NPC->client->ps.velocity );
NPC->client->ps.velocity[2] = 150;
NPC->client->ps.groundEntityNum = ENTITYNUM_NONE;
if ( NPC->enemy && NPC->enemy->s.number == 0 )
{//don't attack the player again for a bit
TIMER_Set( NPC, "attackDebounce", NPC->client->ps.legsAnimTimer + Q_irand( 2000, 4000+((2-g_spskill->integer)*2000) ) );
}
}
}
else if ( !Q_irand(0, 1)
/*&& (NPC->spawnflags&SPF_RANCOR_MUTANT)*/ )
{//mutant rancor can smash
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
TIMER_Set( NPC, "attack_dmg", 900 );
//init pos3 for the trace from last hand pos to current hand pos
VectorCopy( NPC->currentOrigin, NPC->pos3 );
}
else if ( (NPC->spawnflags&SPF_RANCOR_MUTANT)
|| distance >= NPC->maxs[0]+(MIN_DISTANCE*NPC->s.modelScale[0])-64.0f )
{//try to grab
int grabAnim = BOTH_ATTACK2;
gentity_t *checkEnt = NULL;
vec3_t center;
if ( (!aimAtBlockedEntity||!NPCInfo->blockedEntity) && NPC->enemy && NPC->enemy->inuse )
{
checkEnt = NPC->enemy;
VectorCopy( NPC->enemy->currentOrigin, center );
}
else if ( NPCInfo->blockedEntity && NPCInfo->blockedEntity->inuse )
{
checkEnt = NPCInfo->blockedEntity;
//if it has an origin brush, use it...
if ( VectorCompare( NPCInfo->blockedEntity->s.origin, vec3_origin ) )
{//no origin brush, calc center
VectorAdd( NPCInfo->blockedEntity->mins, NPCInfo->blockedEntity->maxs, center );
VectorScale( center, 0.5f, center );
}
else
{//use origin brush as center
VectorCopy( NPCInfo->blockedEntity->s.origin, center );
}
}
if ( checkEnt )
{
float zHeightRelative = center[2]-NPC->currentOrigin[2];
if ( zHeightRelative >= (128.0f*NPC->s.modelScale[2]) )
{
grabAnim = BOTH_ATTACK11;
}
else if ( zHeightRelative >= (64.0f*NPC->s.modelScale[2]) )
{
grabAnim = BOTH_ATTACK10;
}
}
NPC_SetAnim( NPC, SETANIM_BOTH, grabAnim, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
TIMER_Set( NPC, "attack_dmg", 800 );
if ( NPC->enemy && NPC->enemy->s.number == 0 )
{//don't attack the player again for a bit
TIMER_Set( NPC, "attackDebounce", NPC->client->ps.legsAnimTimer + Q_irand( 2000, 4000+((2-g_spskill->integer)*2000) ) );
}
//init pos3 for the trace from last hand pos to current hand pos
VectorCopy( NPC->currentOrigin, NPC->pos3 );
}
else
{
//FIXME: back up?
ucmd.forwardmove = -64;
//FIXME: check for walls/ledges?
return;
}
TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer + random() * 200 );
}
// Need to do delayed damage since the attack animations encapsulate multiple mini-attacks
float playerDist;
if ( TIMER_Done2( NPC, "attack_dmg", qtrue ) )
{
switch ( NPC->client->ps.legsAnim )
{
case BOTH_MELEE1:
Rancor_Smash();
playerDist = NPC_EntRangeFromBolt( player, NPC->handLBolt );
if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )
{
if ( playerDist < 512 )
{
CGCam_Shake( 1.0f*playerDist/256, 1000 );
}
}
else
{
if ( playerDist < 256 )
{
CGCam_Shake( 1.0f*playerDist/128.0f, 1000 );
}
}
break;
case BOTH_MELEE2:
Rancor_Bite();
TIMER_Set( NPC, "attack_dmg2", 450 );
break;
case BOTH_ATTACK1:
if ( NPC->count == 1 && NPC->activator )
{
if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL)
&& NPC->activator->s.number >= MAX_CLIENTS )
{
G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->currentOrigin, NPC->activator->health+1000, (DAMAGE_NO_KNOCKBACK|DAMAGE_NO_PROTECTION), MOD_MELEE );
}
else if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )//FIXME: a flag or something would be better
{//more damage
G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->currentOrigin, Q_irand( 55, 70 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE );
}
else
{
G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->currentOrigin, Q_irand( 25, 40 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE );
}
if ( NPC->activator->health <= 0 )
{//killed him
if ( g_dismemberment->integer >= 3 )
{//make it look like we bit his head off
NPC->activator->client->dismembered = false;
G_DoDismemberment( NPC->activator, NPC->activator->currentOrigin, MOD_SABER, 1000, HL_HEAD, qtrue );
}
NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
}
G_Sound( NPC->activator, G_SoundIndex( "sound/chars/rancor/chomp.wav" ) );
}
break;
case BOTH_ATTACK2:
case BOTH_ATTACK10:
case BOTH_ATTACK11:
//try to grab
Rancor_Swing( NPC->handRBolt, qtrue );
break;
case BOTH_ATTACK3:
if ( NPC->count == 1 && NPC->activator )
{
//cut in half
if ( NPC->activator->client )
{
NPC->activator->client->dismembered = false;
G_DoDismemberment( NPC->activator, NPC->enemy->currentOrigin, MOD_SABER, 1000, HL_WAIST, qtrue );
}
//KILL
G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->currentOrigin, NPC->enemy->health+1000, DAMAGE_NO_PROTECTION|DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_MELEE, HL_NONE );
if ( NPC->activator->client )
{
NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
}
TIMER_Set( NPC, "attack_dmg2", 1350 );
G_Sound( NPC->activator, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) );
G_AddEvent( NPC->activator, EV_JUMP, NPC->activator->health );
}
break;
}
}
else if ( TIMER_Done2( NPC, "attack_dmg2", qtrue ) )
{
switch ( NPC->client->ps.legsAnim )
{
case BOTH_MELEE1:
break;
case BOTH_MELEE2:
Rancor_Bite();
break;
case BOTH_ATTACK1:
break;
case BOTH_ATTACK2:
break;
case BOTH_ATTACK3:
if ( NPC->count == 1 && NPC->activator )
{//swallow victim
G_Sound( NPC->activator, G_SoundIndex( "sound/chars/rancor/chomp.wav" ) );
//FIXME: sometimes end up with a live one in our mouths?
//just make sure they're dead
if ( NPC->activator->health > 0 )
{
//cut in half
NPC->activator->client->dismembered = false;
G_DoDismemberment( NPC->activator, NPC->enemy->currentOrigin, MOD_SABER, 1000, HL_WAIST, qtrue );
//KILL
G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->currentOrigin, NPC->enemy->health+1000, DAMAGE_NO_PROTECTION|DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_MELEE, HL_NONE );
NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
G_AddEvent( NPC->activator, EV_JUMP, NPC->activator->health );
}
NPC->count = 2;
TIMER_Set( NPC, "clearGrabbed", 2600 );
}
break;
}
}
// Just using this to remove the attacking flag at the right time
TIMER_Done2( NPC, "attacking", qtrue );
}
//----------------------------------
void Rancor_Combat( void )
{
if ( NPC->count )
{//holding my enemy
NPCInfo->enemyLastSeenTime = level.time;
if ( TIMER_Done2( NPC, "takingPain", qtrue ))
{
NPCInfo->localState = LSTATE_CLEAR;
}
else if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL)
&& NPC->activator
&& NPC->activator->s.number >= MAX_CLIENTS )
{
Rancor_Attack( 0, qfalse, qfalse );
}
else if ( NPC->useDebounceTime >= level.time
&& NPC->activator )
{//just sniffing the guy
if ( NPC->useDebounceTime <= level.time + 100
&& NPC->client->ps.legsAnim != BOTH_HOLD_DROP)
{//just about done, drop him
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_HOLD_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer+(Q_irand(500,1000)*(3-g_spskill->integer)) );
}
}
else
{
if ( !NPC->useDebounceTime
&& NPC->activator
&& NPC->activator->s.number < MAX_CLIENTS )
{//first time I pick the player, just sniff them
if ( TIMER_Done(NPC,"attacking") )
{//ready to attack
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_HOLD_SNIFF, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
NPC->useDebounceTime = level.time + NPC->client->ps.legsAnimTimer + Q_irand( 500, 2000 );
}
}
else
{
Rancor_Attack( 0, qfalse, qfalse );
}
}
NPC_UpdateAngles( qtrue, qtrue );
return;
}
NPCInfo->goalRadius = NPC->maxs[0]+(MAX_DISTANCE*NPC->s.modelScale[0]); // just get us within combat range
// If we cannot see our target or we have somewhere to go, then do that
if ( !NPC_ClearLOS( NPC->enemy ) || UpdateGoal( ))
{
NPCInfo->combatMove = qtrue;
NPCInfo->goalEntity = NPC->enemy;
Rancor_Move( qfalse );
return;
}
NPCInfo->enemyLastSeenTime = level.time;
// Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb
NPC_FaceEnemy( qtrue );
float distance = Distance( NPC->currentOrigin, NPC->enemy->currentOrigin );
qboolean advance = (qboolean)( distance > (NPC->maxs[0]+(MIN_DISTANCE*NPC->s.modelScale[0])) ? qtrue : qfalse );
qboolean doCharge = qfalse;
if ( advance )
{//have to get closer
if ( (NPC->spawnflags&SPF_RANCOR_MUTANT)
&& (!NPC->enemy||!NPC->enemy->client) )
{//don't do breath attack vs. bbrushes
}
else
{
vec3_t yawOnlyAngles = {0, NPC->currentAngles[YAW], 0};
if ( NPC->enemy->health > 0
&& fabs(distance-(250.0f*NPC->s.modelScale[0])) <= (80.0f*NPC->s.modelScale[0])
&& InFOV( NPC->enemy->currentOrigin, NPC->currentOrigin, yawOnlyAngles, 30, 30 ) )
{
int chance = 9;
if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )
{//higher chance of doing breath attack
chance = 5-g_spskill->integer;
}
if ( !Q_irand( 0, chance ) )
{//go for the charge
doCharge = qtrue;
advance = qfalse;
}
}
}
}
if (( advance || NPCInfo->localState == LSTATE_WAITING ) && TIMER_Done( NPC, "attacking" )) // waiting monsters can't attack
{
if ( TIMER_Done2( NPC, "takingPain", qtrue ))
{
NPCInfo->localState = LSTATE_CLEAR;
}
else
{
Rancor_Move( 1 );
}
}
else
{
Rancor_Attack( distance, doCharge, qfalse );
}
}
/*
-------------------------
NPC_Rancor_Pain
-------------------------
*/
void NPC_Rancor_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc )
{
qboolean hitByRancor = qfalse;
if ( self->NPC && self->NPC->ignorePain )
{
return;
}
if ( !TIMER_Done( self, "breathAttack" ) )
{//nothing interrupts breath attack
return;
}
TIMER_Remove( self, "confusionTime" );
if ( other&&other->client&&other->client->NPC_class==CLASS_RANCOR )
{
hitByRancor = qtrue;
}
if ( other
&& other->inuse
&& other != self->enemy
&& !(other->flags&FL_NOTARGET) )
{
if ( !self->count )
{
if ( (!other->s.number&&!Q_irand(0,3))
|| !self->enemy
|| self->enemy->health == 0
|| (self->enemy->client&&self->enemy->client->NPC_class == CLASS_RANCOR)
|| (!Q_irand(0, 4 ) && DistanceSquared( other->currentOrigin, self->currentOrigin ) < DistanceSquared( self->enemy->currentOrigin, self->currentOrigin )) )
{//if my enemy is dead (or attacked by player) and I'm not still holding/eating someone, turn on the attacker
//FIXME: if can't nav to my enemy, take this guy if I can nav to him
self->lastEnemy = self->enemy;
G_SetEnemy( self, other );
if ( self->enemy != self->lastEnemy )
{//clear this so that we only sniff the player the first time we pick them up
self->useDebounceTime = 0;
}
TIMER_Set( self, "lookForNewEnemy", Q_irand( 5000, 15000 ) );
if ( hitByRancor )
{//stay mad at this Rancor for 2-5 secs before looking for other enemies
TIMER_Set( self, "rancorInfight", Q_irand( 2000, 5000 ) );
}
}
}
}
if ( (hitByRancor|| (self->count==1&&self->activator&&!Q_irand(0,4)) || Q_irand( 0, 200 ) < damage )//hit by rancor, hit while holding live victim, or took a lot of damage
&& self->client->ps.legsAnim != BOTH_STAND1TO2
&& TIMER_Done( self, "takingPain" ) )
{
if ( !Rancor_CheckRoar( self ) )
{
if ( self->client->ps.legsAnim != BOTH_MELEE1
&& self->client->ps.legsAnim != BOTH_MELEE2
&& self->client->ps.legsAnim != BOTH_ATTACK2
&& self->client->ps.legsAnim != BOTH_ATTACK10
&& self->client->ps.legsAnim != BOTH_ATTACK11 )
{//cant interrupt one of the big attack anims
/*
if ( self->count != 1
|| other == self->activator
|| (self->client->ps.legsAnim != BOTH_ATTACK1&&self->client->ps.legsAnim != BOTH_ATTACK3) )
*/
{//if going to bite our victim, only victim can interrupt that anim
if ( self->health > 100 || hitByRancor )
{
TIMER_Remove( self, "attacking" );
VectorCopy( self->NPC->lastPathAngles, self->s.angles );
if ( self->count == 1 )
{
NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
}
else
{
NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
}
TIMER_Set( self, "takingPain", self->client->ps.legsAnimTimer+Q_irand(0, 500*(2-g_spskill->integer)) );
if ( self->NPC )
{
self->NPC->localState = LSTATE_WAITING;
}
}
}
}
}
//let go
/*
if ( !Q_irand( 0, 3 ) && self->count == 1 )
{
Rancor_DropVictim( self );
}
*/
}
}
void Rancor_CheckDropVictim( void )
{
if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL)
&& NPC->activator->s.number >= MAX_CLIENTS )
{
return;
}
vec3_t mins={NPC->activator->mins[0]-1,NPC->activator->mins[1]-1,0};
vec3_t maxs={NPC->activator->maxs[0]+1,NPC->activator->maxs[1]+1,1};
vec3_t start={NPC->activator->currentOrigin[0],NPC->activator->currentOrigin[1],NPC->activator->absmin[2]};
vec3_t end={NPC->activator->currentOrigin[0],NPC->activator->currentOrigin[1],NPC->activator->absmax[2]-1};
trace_t trace;
gi.trace( &trace, start, mins, maxs, end, NPC->activator->s.number, NPC->activator->clipmask );
if ( !trace.allsolid && !trace.startsolid && trace.fraction >= 1.0f )
{
Rancor_DropVictim( NPC );
}
}
qboolean Rancor_AttackBBrush( void )
{
trace_t trace;
vec3_t center;
vec3_t dir2Brush, end;
float checkDist = 64.0f;//32.0f;
if ( VectorCompare( NPCInfo->blockedEntity->s.origin, vec3_origin ) )
{//no origin brush, calc center
VectorAdd( NPCInfo->blockedEntity->mins, NPCInfo->blockedEntity->maxs, center );
VectorScale( center, 0.5f, center );
}
else
{
VectorCopy( NPCInfo->blockedEntity->s.origin, center );
}
if ( NAVDEBUG_showCollision )
{
CG_DrawEdge( NPC->currentOrigin, center, EDGE_IMPACT_POSSIBLE );
}
center[2] = NPC->currentOrigin[2];//we can't fly, so let's ignore z diff
NPC_FacePosition( center, qfalse );
//see if we're close to it
VectorSubtract( center, NPC->currentOrigin, dir2Brush );
float brushSize = ((NPCInfo->blockedEntity->maxs[0] - NPCInfo->blockedEntity->mins[0])*0.5f+(NPCInfo->blockedEntity->maxs[1] - NPCInfo->blockedEntity->mins[1])*0.5f) * 0.5f;
float dist2Brush = VectorNormalize( dir2Brush )-(NPC->maxs[0])-brushSize;
if ( dist2Brush < (MIN_DISTANCE*NPC->s.modelScale[0]) )
{//close enough to just hit it
trace.fraction = 0.0f;
trace.entityNum = NPCInfo->blockedEntity->s.number;
}
else
{
VectorMA( NPC->currentOrigin, checkDist, dir2Brush, end );
gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, end, NPC->s.number, NPC->clipmask );
if ( trace.allsolid || trace.startsolid )
{//wtf?
NPCInfo->blockedEntity = NULL;
return qfalse;
}
}
if ( trace.fraction >= 1.0f //too far away
|| trace.entityNum != NPCInfo->blockedEntity->s.number )//OR blocked by something else
{//keep moving towards it
ucmd.buttons &= ~BUTTON_WALKING; // Unset from MoveToGoal()
STEER::Activate(NPC);
STEER::Seek(NPC, center);
STEER::AvoidCollisions(NPC);
STEER::DeActivate(NPC, &ucmd);
/*
VectorCopy( dir2Brush, NPC->client->ps.moveDir );
if ( NPC->client->ps.speed < NPCInfo->stats.walkSpeed )
{
NPC->client->ps.speed = NPCInfo->stats.walkSpeed;
ucmd.buttons |= BUTTON_WALKING;
}
*/
//NPCInfo->enemyLastSeenTime = level.time;
//let the function that called us know that we called NAV ourselves
}
else if ( trace.entityNum == NPCInfo->blockedEntity->s.number )
{//close enough, smash it!
Rancor_Attack( (trace.fraction*checkDist), qfalse, qtrue );//FIXME: maybe charge at it, smash through?
TIMER_Remove( NPC, "attackDebounce" );//don't wait on these
NPCInfo->enemyLastSeenTime = level.time;
}
else
{
//Com_Printf( S_COLOR_RED"RANCOR cannot reach intended breakable %s, blocked by %s\n", NPC->blockedEntity->targetname, g_entities[trace.entityNum].classname );
if ( G_EntIsBreakable( trace.entityNum, NPC ) )
{//oh, well, smash that, then
//G_SetEnemy( NPC, &g_entities[trace.entityNum] );
gentity_t* prevblockedEnt = NPCInfo->blockedEntity;
NPCInfo->blockedEntity = &g_entities[trace.entityNum];
Rancor_Attack( (trace.fraction*checkDist), qfalse, qtrue );//FIXME: maybe charge at it, smash through?
TIMER_Remove( NPC, "attackDebounce" );//don't wait on these
NPCInfo->enemyLastSeenTime = level.time;
NPCInfo->blockedEntity = prevblockedEnt;
}
else
{
NPCInfo->blockedEntity = NULL;
return qfalse;
}
}
return qtrue;
}
void Rancor_FireBreathAttack( void )
{
int damage = Q_irand( 10, 15 );
trace_t tr;
gentity_t *traceEnt = NULL;
mdxaBone_t boltMatrix;
vec3_t start, end, dir, traceMins = {-4, -4, -4}, traceMaxs = {4, 4, 4};
vec3_t rancAngles = {0,NPC->client->ps.viewangles[YAW],0};
gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel, NPC->gutBolt,
&boltMatrix, rancAngles, NPC->currentOrigin, (cg.time?cg.time:level.time),
NULL, NPC->s.modelScale );
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, start );
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Z, dir );
VectorMA( start, 512, dir, end );
gi.trace( &tr, start, traceMins, traceMaxs, end, NPC->s.number, MASK_SHOT );
traceEnt = &g_entities[tr.entityNum];
if ( tr.entityNum < ENTITYNUM_WORLD
&& traceEnt->takedamage
&& traceEnt->client )
{//breath attack only does damage to living things
G_Damage( traceEnt, NPC, NPC, dir, tr.endpos, damage*2, DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC|DAMAGE_IGNORE_TEAM, MOD_LAVA, HL_NONE );
}
if ( tr.fraction < 1.0f )
{//hit something, do radius damage
G_RadiusDamage( tr.endpos, NPC, damage, 250, NPC, MOD_LAVA );
}
}
void Rancor_CheckAnimDamage( void )
{
if ( NPC->client->ps.legsAnim == BOTH_ATTACK2
|| NPC->client->ps.legsAnim == BOTH_ATTACK10
|| NPC->client->ps.legsAnim == BOTH_ATTACK11 )
{
if ( NPC->client->ps.legsAnimTimer >= 1200 && NPC->client->ps.legsAnimTimer <= 1350 )
{
if ( Q_irand( 0, 2 ) )
{
Rancor_Swing( NPC->handRBolt, qfalse );
}
else
{
Rancor_Swing( NPC->handRBolt, qtrue );
}
}
else if ( NPC->client->ps.legsAnimTimer >= 1100 && NPC->client->ps.legsAnimTimer <= 1550 )
{
Rancor_Swing( NPC->handRBolt, qtrue );
}
}
else if ( NPC->client->ps.legsAnim == BOTH_ATTACK5 )
{
if ( NPC->client->ps.legsAnimTimer >= 750 && NPC->client->ps.legsAnimTimer <= 1300 )
{
Rancor_Swing( NPC->handLBolt, qfalse );
}
else if ( NPC->client->ps.legsAnimTimer >= 1700 && NPC->client->ps.legsAnimTimer <= 2300 )
{
Rancor_Swing( NPC->handRBolt, qfalse );
}
}
}
/*
-------------------------
NPC_BSRancor_Default
-------------------------
*/
void NPC_BSRancor_Default( void )
{
AddSightEvent( NPC, NPC->currentOrigin, 1024, AEL_DANGER_GREAT, 50 );
if (NPCInfo->blockedEntity && TIMER_Done(NPC, "blockedEntityIgnore"))
{
if (!TIMER_Exists(NPC, "blockedEntityTimeOut"))
{
TIMER_Set(NPC, "blockedEntityTimeOut", 5000);
}
else if (TIMER_Done(NPC, "blockedEntityTimeOut"))
{
TIMER_Remove(NPC, "blockedEntityTimeOut");
TIMER_Set(NPC, "blockedEntityIgnore", 25000);
NPCInfo->blockedEntity = NULL;
}
}
else
{
TIMER_Remove(NPC, "blockedEntityTimeOut");
TIMER_Remove(NPC, "blockedEntityIgnore");
}
Rancor_CheckAnimDamage();
if ( !TIMER_Done( NPC, "breathAttack" ) )
{//doing breath attack, just do damage
Rancor_FireBreathAttack();
NPC_UpdateAngles( qtrue, qtrue );
return;
}
else if ( NPC->client->ps.legsAnim == BOTH_ATTACK4
|| NPC->client->ps.legsAnim == BOTH_ATTACK6
|| NPC->client->ps.legsAnim == BOTH_ATTACK7 )
{
G_StopEffect( G_EffectIndex( "mrancor/breath" ), NPC->playerModel, NPC->gutBolt, NPC->s.number );
NPC->s.loopSound = 0;
}
if ( TIMER_Done2( NPC, "clearGrabbed", qtrue ) )
{
Rancor_DropVictim( NPC );
}
else if ( (NPC->client->ps.legsAnim == BOTH_PAIN2 || NPC->client->ps.legsAnim == BOTH_HOLD_DROP )
&& NPC->count == 1
&& NPC->activator )
{
Rancor_CheckDropVictim();
}
if ( !TIMER_Done( NPC, "rageTime" ) )
{//do nothing but roar first time we see an enemy
AddSoundEvent( NPC, NPC->currentOrigin, 1024, AEL_DANGER_GREAT, qfalse, qfalse );
NPC_FaceEnemy( qtrue );
return;
}
if ( NPCInfo->localState == LSTATE_WAITING
&& TIMER_Done2( NPC, "takingPain", qtrue ) )
{//was not doing anything because we were taking pain, but pain is done now, so clear it...
NPCInfo->localState = LSTATE_CLEAR;
}
if ( !TIMER_Done( NPC, "confusionTime" ) )
{
NPC_UpdateAngles( qtrue, qtrue );
return;
}
if ( NPC->enemy )
{
if ( NPC->enemy->client //enemy is a client
&& (NPC->enemy->client->NPC_class == CLASS_UGNAUGHT || NPC->enemy->client->NPC_class == CLASS_JAWA )//enemy is a lowly jawa or ugnaught
&& NPC->enemy->enemy != NPC//enemy's enemy is not me
&& (!NPC->enemy->enemy || !NPC->enemy->enemy->client || NPC->enemy->enemy->client->NPC_class!=CLASS_RANCOR) )//enemy's enemy is not a client or is not a rancor (which is as scary as me anyway)
{//they should be scared of ME and no-one else
G_SetEnemy( NPC->enemy, NPC );
}
if ( TIMER_Done(NPC,"angrynoise") )
{
G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/rancor/anger%d.wav", Q_irand(1, 3)) );
TIMER_Set( NPC, "angrynoise", Q_irand( 5000, 10000 ) );
}
else
{
AddSoundEvent( NPC, NPC->currentOrigin, 512, AEL_DANGER_GREAT, qfalse, qfalse );
}
if ( NPC->count == 2 && NPC->client->ps.legsAnim == BOTH_ATTACK3 )
{//we're still chewing our enemy up
NPC_UpdateAngles( qtrue, qtrue );
return;
}
//else, if he's in our hand, we eat, else if he's on the ground, we keep attacking his dead body for a while
if( NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_RANCOR )
{//got mad at another Rancor, look for a valid enemy
if ( TIMER_Done( NPC, "rancorInfight" ) )
{
NPC_CheckEnemyExt( qtrue );
}
}
else if ( !NPC->count )
{
if ( NPCInfo->blockedEntity )
{//something in our way
if ( !NPCInfo->blockedEntity->inuse )
{//was destroyed
NPCInfo->blockedEntity = NULL;
}
else
{
//a breakable?
if ( G_EntIsBreakable( NPCInfo->blockedEntity->s.number, NPC ) )
{//breakable brush
if ( !Rancor_AttackBBrush() )
{//didn't move inside that func, so call move here...?
Rancor_Move( 1 );
}
NPC_UpdateAngles( qtrue, qtrue );
return;
}
else
{//if it's a client and in our way, get mad at it!
if ( NPCInfo->blockedEntity != NPC->enemy
&& NPCInfo->blockedEntity->client
&& NPC_ValidEnemy( NPCInfo->blockedEntity )
&& !Q_irand( 0, 9 ) )
{
G_SetEnemy( NPC, NPCInfo->blockedEntity );
//look again in 2-5 secs
TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 2000, 5000 ) );
NPCInfo->blockedEntity = NULL;
}
}
}
}
if ( NPC_ValidEnemy( NPC->enemy ) == qfalse )
{
TIMER_Remove( NPC, "lookForNewEnemy" );//make them look again right now
if ( !NPC->enemy->inuse
|| level.time - NPC->enemy->s.time > Q_irand( 10000, 15000 )
|| (NPC->spawnflags&SPF_RANCOR_FASTKILL) )//don't linger on dead bodies
{//it's been a while since the enemy died, or enemy is completely gone, get bored with him
if ( (NPC->spawnflags&SPF_RANCOR_MUTANT)
&& player && player->health >= 0 )
{//all else failing, always go after the player
NPC->lastEnemy = NPC->enemy;
G_SetEnemy( NPC, player );
if ( NPC->enemy != NPC->lastEnemy )
{//clear this so that we only sniff the player the first time we pick them up
NPC->useDebounceTime = 0;
}
}
else
{
NPC->enemy = NULL;
Rancor_Patrol();
NPC_UpdateAngles( qtrue, qtrue );
return;
}
}
}
if ( TIMER_Done( NPC, "lookForNewEnemy" ) )
{
gentity_t *sav_enemy = NPC->enemy;//FIXME: what about NPC->lastEnemy?
NPC->enemy = NULL;
gentity_t *newEnemy = NPC_CheckEnemy( NPCInfo->confusionTime < level.time, qfalse, qfalse );
NPC->enemy = sav_enemy;
if ( newEnemy && newEnemy != sav_enemy )
{//picked up a new enemy!
NPC->lastEnemy = NPC->enemy;
G_SetEnemy( NPC, newEnemy );
if ( NPC->enemy != NPC->lastEnemy )
{//clear this so that we only sniff the player the first time we pick them up
NPC->useDebounceTime = 0;
}
//hold this one for at least 5-15 seconds
TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) );
}
else
{//look again in 2-5 secs
TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 2000, 5000 ) );
}
}
}
Rancor_Combat();
if ( TIMER_Done( NPC, "attacking" )
&& TIMER_Done( NPC, "takingpain" )
&& TIMER_Done( NPC, "confusionDebounce" )
&& NPCInfo->localState == LSTATE_CLEAR
&& !NPC->count )
{//not busy
if ( !ucmd.forwardmove
&& !ucmd.rightmove
&& VectorCompare( NPC->client->ps.moveDir, vec3_origin ) )
{//not moving
if ( level.time - NPCInfo->enemyLastSeenTime > 5000 )
{//haven't seen an enemy in a while
if ( !Q_irand( 0, 20 ) )
{
if ( Q_irand( 0, 1 ) )
{
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_GUARD_IDLE1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
}
else
{
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_GUARD_LOOKAROUND1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
}
TIMER_Set( NPC, "confusionTime", NPC->client->ps.legsAnimTimer );
TIMER_Set( NPC, "confusionDebounce", NPC->client->ps.legsAnimTimer+Q_irand( 4000, 8000 ) );
}
}
}
}
}
else
{
if ( TIMER_Done(NPC,"idlenoise") )
{
G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/rancor/snort_%d.wav", Q_irand(1, 4)) );
TIMER_Set( NPC, "idlenoise", Q_irand( 2000, 4000 ) );
AddSoundEvent( NPC, NPC->currentOrigin, 384, AEL_DANGER, qfalse, qfalse );
}
if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES )
{
Rancor_Patrol();
if ( !NPC->enemy && NPC->wait )
{//we've been mad before and can't find an enemy
if ( (NPC->spawnflags&SPF_RANCOR_MUTANT)
&& player && player->health >= 0 )
{//all else failing, always go after the player
NPC->lastEnemy = NPC->enemy;
G_SetEnemy( NPC, player );
if ( NPC->enemy != NPC->lastEnemy )
{//clear this so that we only sniff the player the first time we pick them up
NPC->useDebounceTime = 0;
}
}
}
}
else
{
Rancor_Idle();
}
}
NPC_UpdateAngles( qtrue, qtrue );
}