//Reaver #include "b_local.h" #include "anims.h" #include "g_nav.h" void Reaver_Hunt( void ); void Reaver_Strike( gentity_t *target ); void Reaver_Attack( void ); qboolean Reaver_CheckAttack( attack_e type ); void Reaver_Melee( void ); void Reaver_Ranged( float dist ); /* ------------------------- NPC_BSReaver_Idle ------------------------- */ void NPC_BSReaver_Idle( void ) { //See if we have a goal to run to if ( UpdateGoal() ) { NPC_MoveToGoal(); } NPC_UpdateAngles( qfalse, qtrue ); } /* ------------------------- Reaver_Strike ------------------------- */ #define STRIKE_DIST 80 #define STRIKE_DAMAGE 10 void Reaver_Strike( gentity_t *target ) { trace_t trace; vec3_t endpos, forward; //Get our forward direction AngleVectors( NPC->currentAngles, forward, NULL, NULL ); //Get the extent of our reach VectorMA( NPC->currentOrigin, STRIKE_DIST, forward, endpos ); vec3_t mins = { -8, -8, -8 }, maxs = { 8, 8, 8 }; //Trace to hit gi.trace( &trace, NPC->currentOrigin, mins, maxs, endpos, NPC->s.number, MASK_SHOT ); gentity_t *traceEnt = &g_entities[ trace.entityNum ]; //If we hit, damage the victim if ( traceEnt->takedamage ) { //FIXME: Pass in proper weapon information G_Damage( traceEnt, NPC, NPC, forward, trace.endpos, STRIKE_DAMAGE, DAMAGE_NO_KNOCKBACK, MOD_PHASER ); } } /* ------------------------- Reaver_Attack ------------------------- */ void Reaver_Attack( void ) { // The slash trail "formation" hasn't been started yet, so don't try and use the old points. NPC->trigger_formation = qfalse; if ( rand() & 1 ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPCInfo->weaponTime = level.time + 1000; NPCInfo->standTime = level.time + 2000; } else { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPCInfo->weaponTime = level.time + 1000; NPCInfo->standTime = level.time + 2000; } //Inflict damage Reaver_Strike( NPC->enemy ); } /* ------------------------- Reaver_CheckAttack ------------------------- */ qboolean Reaver_CheckAttack( attack_e type ) { switch( type ) { case ATTACK_MELEE: break; case ATTACK_RANGE: break; } return ( NPCInfo->weaponTime < level.time ); } /* ------------------------- Reaver_Melee ------------------------- */ void Reaver_Melee( void ) { //Check to hit if ( Reaver_CheckAttack( ATTACK_MELEE ) == qtrue ) { //Get um! Reaver_Attack(); } NPC_FaceEnemy(); NPC_UpdateAngles( qtrue, qtrue ); } /* ------------------------- Reaver_Pounce ------------------------- */ #define REAVER_JUMP_VELOCITY 400 #define REAVER_JUMP_HEIGHT 325 void Reaver_Start_Pounce( void ) { // Set up the jump animations, but don't jump just yet NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_JUMP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); client->ps.pm_flags |= PMF_JUMP_HELD; client->ps.groundEntityNum = ENTITYNUM_NONE; NPCInfo->pauseTime = level.time + 2000; //Time to fire a ranged shot again NPCInfo->standTime = level.time + 1500; //Time to stand still NPCInfo->weaponTime = level.time + 2000; //Time to do any attack again NPCInfo->jumpTime = level.time + 3000; //Time to jump again } /* ------------------------- Reaver_Jump ------------------------- */ void Reaver_Jump( void ) { if ( NPC->enemy ) NPC_FaceEnemy(); // This seems a bit risky, but here I'm trying to actually delay the jump process so it's timed up better with its anims if ( NPCInfo->jumpTime - 2500 > level.time && level.time + 2700 > NPCInfo->jumpTime ) { vec3_t dir; AngleVectors( NPC->currentAngles, dir, NULL, NULL ); VectorScale( dir, REAVER_JUMP_VELOCITY, dir ); dir[2] += REAVER_JUMP_HEIGHT; VectorCopy( dir, NPC->client->ps.velocity ); } } /* ------------------------- Reaver_JumpOK ------------------------- */ // This could be a bit expensive, but fortunately, it doesn't get called very often... // ...and it helps to keep them from doing jumps that would make them look stupid qboolean Reaver_JumpOK( void ) { vec3_t dir, org, end, mins, maxs; trace_t tr; // build a trace box VectorSet( maxs, 8, 8, 8 ); VectorScale( maxs, -1, mins ); AngleVectors( NPC->currentAngles, dir, NULL, NULL ); // trace diagonally up VectorMA( NPC->currentOrigin, 160, dir, org ); org[2] += 160; gi.trace( &tr, NPC->currentOrigin, mins, maxs, org, NPC->s.number, MASK_NPCSOLID ); // NOTE: box trace if ( tr.fraction != 1.0f ) return qfalse; // hit something // trace forward VectorMA( NPC->currentOrigin, 256, dir, org ); gi.trace( &tr, NPC->currentOrigin, NULL, NULL, org, NPC->s.number, MASK_NPCSOLID ); if ( tr.fraction != 1.0f ) return qfalse; // hit something // trace down from the forward point to see if we might fall to our deaths from this jump VectorSet( dir, 0, 0, -1 ); VectorMA( org, 200, dir, end ); gi.trace( &tr, org, NULL, NULL, end, NPC->s.number, MASK_NPCSOLID ); if ( tr.fraction == 1.0f ) return qfalse; // would fall too far return qtrue; // jump, baby, jump!! } /* ------------------------- Reaver_Ranged ------------------------- */ #define MIN_JUMP_DIST 380 #define MIN_JUMP_DIST_SQR ( MIN_JUMP_DIST * MIN_JUMP_DIST ) void Reaver_Ranged( float dist ) { if ( NPCInfo->weaponTime < level.time && NPC_CheckCanAttackExt() ) { //Attempt to fire off a shot at the player if ( NPCInfo->pauseTime < level.time ) { //Fire NPC->client->ps.weapon = WP_FORGE_PROJ; ucmd.buttons |= BUTTON_ATTACK; NPC_ApplyWeaponFireDelay(); //Setup delays NPCInfo->pauseTime = level.time + 2000; //Time to fire a ranged shot again NPCInfo->standTime = level.time + 1500; //Time to stand still NPCInfo->weaponTime = level.time + 2500; //Time to do any attack again return; } } //See if we should leap at the player if ( dist > MIN_JUMP_DIST_SQR ) { if ( ( Q_irand( 0, 50 ) > 48 ) && ( NPCInfo->jumpTime < level.time ) ) { if ( Reaver_JumpOK() ) { Reaver_Start_Pounce(); return; } } } Reaver_Hunt(); } /* ------------------------- Reaver_Hunt ------------------------- */ void Reaver_Hunt( void ) { NPCInfo->combatMove = qtrue; //If we're not supposed to stand still, pursue the player if ( NPCInfo->standTime < level.time ) { //Move towards our goal NPCInfo->goalEntity = NPC->enemy; NPCInfo->goalRadius = 12; NPC_MoveToGoal(); } //Update our angles regardless NPC_UpdateAngles( qtrue, qtrue ); } /* ------------------------- Reaver_Backup ------------------------- */ void Reaver_Backup() { vec3_t dir; // Cheesy push AngleVectors( NPC->currentAngles, dir, NULL, NULL ); VectorMA( NPC->client->ps.velocity, -50, dir, NPC->client->ps.velocity ); } /* ------------------------- NPC_BSReaver_Attack ------------------------- */ #define MIN_CRITICAL_DIST 64 #define MIN_CRITICAL_DIST_SQR ( MIN_CRITICAL_DIST * MIN_CRITICAL_DIST ) #define MIN_MELEE_RANGE 80 #define MIN_MELEE_RANGE_SQR ( MIN_MELEE_RANGE * MIN_MELEE_RANGE ) void NPC_BSReaver_Attack( void ) { // We may have a pounce animation started and are waiting to actually start the jump movement... Reaver_Jump(); //If we don't have an enemy, just idle if ( NPC_CheckEnemyExt() == qfalse ) { NPC_BSReaver_Idle(); return; } //Rate our distance to the target, and our visibilty float distance = (int) DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); distance_e distRate = ( distance > MIN_MELEE_RANGE_SQR ) ? DIST_LONG : DIST_MELEE; int visRate = NPC_ClearLOS( NPC->enemy ); //If we cannot see our target, move to see it if ( visRate == qfalse ) { Reaver_Hunt(); return; } if ( distance < MIN_CRITICAL_DIST_SQR ) { // We're not happy when the player gets too close Reaver_Backup(); } //Decide what to do next switch ( distRate ) { case DIST_MELEE: Reaver_Melee(); break; case DIST_LONG: Reaver_Ranged( distance ); break; } }