// 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.numberactivator->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 ); }