/* =========================================================================== Copyright (C) 2000 - 2013, Raven Software, Inc. Copyright (C) 2001 - 2013, Activision, Inc. Copyright (C) 2013 - 2015, OpenJK contributors This file is part of the OpenJK source code. OpenJK is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . =========================================================================== */ #include "b_local.h" #include "g_navigator.h" // These define the working combat range for these suckers #define MIN_DISTANCE 48 #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 float enemyDist = 0; extern qboolean NAV_CheckAhead( gentity_t *self, vec3_t end, trace_t &trace, int clipmask ); extern int PM_AnimLength( int index, animNumber_t anim ); extern cvar_t *g_dismemberment; /* ------------------------- NPC_Wampa_Precache ------------------------- */ void NPC_Wampa_Precache( void ) { /* int i; for ( i = 1; i < 4; i ++ ) { G_SoundIndex( va("sound/chars/wampa/growl%d.wav", i) ); } for ( i = 1; i < 3; i ++ ) { G_SoundIndex( va("sound/chars/wampa/snort%d.wav", i) ); } */ G_SoundIndex( "sound/chars/rancor/swipehit.wav" ); //G_SoundIndex( "sound/chars/wampa/chomp.wav" ); } /* ------------------------- Wampa_Idle ------------------------- */ void Wampa_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 Wampa_CheckRoar( gentity_t *self ) { if ( self->wait < level.time ) { self->wait = level.time + Q_irand( 5000, 20000 ); NPC_SetAnim( self, SETANIM_BOTH, Q_irand(BOTH_GESTURE1,BOTH_GESTURE2), (SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD) ); TIMER_Set( self, "rageTime", self->client->ps.legsAnimTimer ); return qtrue; } return qfalse; } /* ------------------------- Wampa_Patrol ------------------------- */ void Wampa_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 ) { Wampa_Idle(); return; } Wampa_CheckRoar( NPC ); TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); } /* ------------------------- Wampa_Move ------------------------- */ void Wampa_Move( qboolean visible ) { if ( NPCInfo->localState != LSTATE_WAITING ) { NPCInfo->goalEntity = NPC->enemy; trace_t trace; if ( !NAV_CheckAhead( NPC, NPCInfo->goalEntity->currentOrigin, trace, (NPC->clipmask|CONTENTS_BOTCLIP) ) ) { if ( !NPC_MoveToGoal( qfalse ) ) { STEER::Activate(NPC); STEER::Seek(NPC, NPCInfo->goalEntity->currentOrigin); STEER::AvoidCollisions(NPC); STEER::DeActivate(NPC, &ucmd); } } NPCInfo->goalRadius = MIN_DISTANCE;//MAX_DISTANCE; // just get us within combat range if ( NPC->enemy ) {//pick correct movement speed and anim //run by default ucmd.buttons &= ~BUTTON_WALKING; if ( !TIMER_Done( NPC, "runfar" ) || !TIMER_Done( NPC, "runclose" ) ) {//keep running with this anim & speed for a bit } else if ( !TIMER_Done( NPC, "walk" ) ) {//keep walking for a bit ucmd.buttons |= BUTTON_WALKING; } else if ( visible && enemyDist > 350 && NPCInfo->stats.runSpeed == 200 )//180 ) {//fast run, all fours //BOTH_RUN1 NPCInfo->stats.runSpeed = 300; TIMER_Set( NPC, "runfar", Q_irand( 4000, 8000 ) ); if ( NPC->client->ps.legsAnim == BOTH_RUN2 ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN2TORUN1, SETANIM_FLAG_HOLD ); } } else if ( enemyDist > 200 && NPCInfo->stats.runSpeed == 300 ) {//slow run, upright //BOTH_RUN2 NPCInfo->stats.runSpeed = 200;//180; TIMER_Set( NPC, "runclose", Q_irand( 5000, 10000 ) ); if ( NPC->client->ps.legsAnim == BOTH_RUN1 ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1TORUN2, SETANIM_FLAG_HOLD ); } } else if ( enemyDist < 100 ) {//walk NPCInfo->stats.runSpeed = 200;//180; ucmd.buttons |= BUTTON_WALKING; TIMER_Set( NPC, "walk", Q_irand( 6000, 12000 ) ); } } } } //--------------------------------------------------------- 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 int NPC_GetEntsNearBolt( gentity_t **radiusEnts, float radius, int boltIndex, vec3_t boltOrg ); void Wampa_Slash( int boltIndex, qboolean backhand ) { gentity_t *radiusEnts[ 128 ]; int numEnts; const float radius = 88; const float radiusSquared = (radius*radius); int i; vec3_t boltOrg; int damage = (backhand)?Q_irand(10,15):Q_irand(20,30); numEnts = NPC_GetEntsNearBolt( radiusEnts, radius, boltIndex, boltOrg ); for ( i = 0; i < numEnts; i++ ) { if ( !radiusEnts[i]->inuse ) { continue; } if ( radiusEnts[i] == NPC ) {//Skip the wampa ent continue; } if ( radiusEnts[i]->client == NULL ) {//must be a client continue; } if ( DistanceSquared( radiusEnts[i]->currentOrigin, boltOrg ) <= radiusSquared ) { //smack G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, damage, ((backhand)?0:DAMAGE_NO_KNOCKBACK), MOD_MELEE ); if ( backhand ) { //actually push the enemy vec3_t pushDir; 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_WAMPA && 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, 65 ); if ( radiusEnts[i]->health > 0 && Q_irand( 0, 1 ) ) {//do pain on enemy G_Knockdown( radiusEnts[i], NPC, pushDir, 300, qtrue ); } } } else 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 < 4 ) { 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 ); } } else if ( !Q_irand( 0, 3 ) && radiusEnts[i]->health > 0 ) {//one out of every 4 normal hits does a knockdown, too vec3_t pushDir; 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 ); G_Knockdown( radiusEnts[i], NPC, pushDir, 35, qtrue ); } G_Sound( radiusEnts[i], G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); } } } //------------------------------ void Wampa_Attack( float distance, qboolean doCharge ) { if ( !TIMER_Exists( NPC, "attacking" ) ) { if ( !Q_irand(0, 3) && !doCharge ) {//double slash NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attack_dmg", 750 ); } else if ( doCharge || (distance > 270 && distance < 430 && !Q_irand(0, 1)) ) {//leap NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attack_dmg", 500 ); 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; } else if ( distance < 100 )//&& !Q_irand( 0, 4 ) ) {//grab NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_HOLD_START, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); NPC->client->ps.legsAnimTimer += 200; TIMER_Set( NPC, "attack_dmg", 250 ); } else {//backhand NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK3, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attack_dmg", 250 ); } TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer + Q_flrand(0.0f, 1.0f) * 200 ); //allow us to re-evaluate our running speed/anim TIMER_Set( NPC, "runfar", -1 ); TIMER_Set( NPC, "runclose", -1 ); TIMER_Set( NPC, "walk", -1 ); } // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks if ( TIMER_Done2( NPC, "attack_dmg", qtrue ) ) { switch ( NPC->client->ps.legsAnim ) { case BOTH_ATTACK1: Wampa_Slash( NPC->handRBolt, qfalse ); //do second hit TIMER_Set( NPC, "attack_dmg2", 100 ); break; case BOTH_ATTACK2: Wampa_Slash( NPC->handRBolt, qfalse ); TIMER_Set( NPC, "attack_dmg2", 100 ); break; case BOTH_ATTACK3: Wampa_Slash( NPC->handLBolt, qtrue ); break; } } else if ( TIMER_Done2( NPC, "attack_dmg2", qtrue ) ) { switch ( NPC->client->ps.legsAnim ) { case BOTH_ATTACK1: Wampa_Slash( NPC->handLBolt, qfalse ); break; case BOTH_ATTACK2: Wampa_Slash( NPC->handLBolt, qfalse ); break; } } // Just using this to remove the attacking flag at the right time TIMER_Done2( NPC, "attacking", qtrue ); if ( NPC->client->ps.legsAnim == BOTH_ATTACK1 && distance > (NPC->maxs[0]+MIN_DISTANCE) ) {//okay to keep moving ucmd.buttons |= BUTTON_WALKING; Wampa_Move( qtrue ); } } //---------------------------------- void Wampa_Combat( void ) { // If we cannot see our target or we have somewhere to go, then do that if ( !NPC_ClearLOS( NPC->enemy ) ) { if ( !Q_irand( 0, 10 ) ) { if ( Wampa_CheckRoar( NPC ) ) { return; } } NPCInfo->combatMove = qtrue; NPCInfo->goalEntity = NPC->enemy; NPCInfo->goalRadius = MIN_DISTANCE;//MAX_DISTANCE; // just get us within combat range Wampa_Move( qfalse ); return; } /* else if ( UpdateGoal() ) { NPCInfo->combatMove = qtrue; NPCInfo->goalEntity = NPC->enemy; NPCInfo->goalRadius = MIN_DISTANCE;//MAX_DISTANCE; // just get us within combat range Wampa_Move( 1 ); return; }*/ // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb //FIXME: always seems to face off to the left or right?!!!! NPC_FaceEnemy( qtrue ); float distance = enemyDist = Distance( NPC->currentOrigin, NPC->enemy->currentOrigin ); qboolean advance = (qboolean)( distance > (NPC->maxs[0]+MIN_DISTANCE) ? qtrue : qfalse ); qboolean doCharge = qfalse; if ( advance ) {//have to get closer vec3_t yawOnlyAngles = {0, NPC->currentAngles[YAW], 0}; if ( NPC->enemy->health > 0//enemy still alive && fabs(distance-350) <= 80 //enemy anywhere from 270 to 430 away && InFOV( NPC->enemy->currentOrigin, NPC->currentOrigin, yawOnlyAngles, 20, 20 ) )//enemy generally in front {//10% chance of doing charge anim if ( !Q_irand( 0, 6 ) ) {//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 { Wampa_Move( qtrue ); } } else { if ( !Q_irand( 0, 15 ) ) {//FIXME: only do this if we just damaged them or vice-versa? if ( Wampa_CheckRoar( NPC ) ) { return; } } Wampa_Attack( distance, doCharge ); } } /* ------------------------- NPC_Wampa_Pain ------------------------- */ void NPC_Wampa_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) { qboolean hitByWampa = qfalse; if ( self->count ) {//FIXME: need pain anim NPC_SetAnim( self, SETANIM_BOTH, BOTH_STAND2TO1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); TIMER_Set( self, "takingPain", self->client->ps.legsAnimTimer ); TIMER_Set(self,"attacking",-level.time); return; } if ( other&&other->client&&other->client->NPC_class==CLASS_WAMPA ) { hitByWampa = qtrue; } if ( other && other->inuse && other != self->enemy && !(other->flags&FL_NOTARGET) ) { if ( (!other->s.number&&!Q_irand(0,3)) || !self->enemy || self->enemy->health == 0 || (self->enemy->client&&self->enemy->client->NPC_class == CLASS_WAMPA) || (!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 = other; 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 ( hitByWampa ) {//stay mad at this Wampa for 2-5 secs before looking for other enemies TIMER_Set( self, "wampaInfight", Q_irand( 2000, 5000 ) ); } } } if ( (hitByWampa|| Q_irand( 0, 100 ) < damage )//hit by wampa, hit while holding live victim, or took a lot of damage && self->client->ps.legsAnim != BOTH_GESTURE1 && self->client->ps.legsAnim != BOTH_GESTURE2 && TIMER_Done( self, "takingPain" ) ) { if ( !Wampa_CheckRoar( self ) ) { if ( self->client->ps.legsAnim != BOTH_ATTACK1 && self->client->ps.legsAnim != BOTH_ATTACK2 && self->client->ps.legsAnim != BOTH_ATTACK3 ) {//cant interrupt one of the big attack anims if ( self->health > 100 || hitByWampa ) { TIMER_Remove( self, "attacking" ); VectorCopy( self->NPC->lastPathAngles, self->s.angles ); if ( !Q_irand( 0, 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)) ); TIMER_Set(self,"attacking",-level.time); //allow us to re-evaluate our running speed/anim TIMER_Set( self, "runfar", -1 ); TIMER_Set( self, "runclose", -1 ); TIMER_Set( self, "walk", -1 ); if ( self->NPC ) { self->NPC->localState = LSTATE_WAITING; } } } } } } void Wampa_DropVictim( gentity_t *self ) { //FIXME: if Wampa dies, it should drop its victim. //FIXME: if Wampa is removed, it must remove its victim. //FIXME: if in BOTH_HOLD_DROP, throw them a little, too? if ( self->health > 0 ) { NPC_SetAnim( self, SETANIM_BOTH, BOTH_STAND2TO1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } TIMER_Set(self,"attacking",-level.time); if ( self->activator ) { if ( self->activator->client ) { self->activator->client->ps.eFlags &= ~EF_HELD_BY_WAMPA; } self->activator->activator = NULL; NPC_SetAnim( self->activator, SETANIM_BOTH, BOTH_RELEASED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); self->activator->client->ps.legsAnimTimer += 500; self->activator->client->ps.weaponTime = self->activator->client->ps.torsoAnimTimer = self->activator->client->ps.legsAnimTimer; if ( self->activator->health > 0 ) { if ( self->activator->NPC ) {//start thinking again self->activator->NPC->nextBStateThink = level.time; } if ( self->activator->client && self->activator->s.number < MAX_CLIENTS ) { vec3_t vicAngles = {30,AngleNormalize180(self->client->ps.viewangles[YAW]+180),0}; SetClientViewAngle( self->activator, vicAngles ); } } else { if ( self->enemy == self->activator ) { self->enemy = NULL; } self->activator->clipmask &= ~CONTENTS_BODY; } self->activator = NULL; } self->count = 0;//drop him } qboolean Wampa_CheckDropVictim( gentity_t *self, qboolean excludeMe ) { if ( !self || !self->activator ) { return qtrue; } vec3_t mins={self->activator->mins[0]-1,self->activator->mins[1]-1,0}; vec3_t maxs={self->activator->maxs[0]+1,self->activator->maxs[1]+1,1}; vec3_t start={self->activator->currentOrigin[0],self->activator->currentOrigin[1],self->activator->absmin[2]}; vec3_t end={self->activator->currentOrigin[0],self->activator->currentOrigin[1],self->activator->absmax[2]-1}; trace_t trace; if ( excludeMe ) { gi.unlinkentity( self ); } gi.trace( &trace, start, mins, maxs, end, self->activator->s.number, self->activator->clipmask, (EG2_Collision)0, 0 ); if ( excludeMe ) { gi.linkentity( self ); } if ( !trace.allsolid && !trace.startsolid && trace.fraction >= 1.0f ) { Wampa_DropVictim( self ); return qtrue; } if ( excludeMe ) {//victim stuck in wall if ( self->NPC ) {//turn self->NPC->desiredYaw += Q_irand( -30, 30 ); self->NPC->lockedDesiredYaw = self->NPC->desiredYaw; } } return qfalse; } extern float NPC_EnemyRangeFromBolt( int boltIndex ); qboolean Wampa_TryGrab( void ) { const float radius = 64.0f; if ( !NPC->enemy || !NPC->enemy->client || NPC->enemy->health <= 0 ) { return qfalse; } float enemyDist = NPC_EnemyRangeFromBolt( NPC->handRBolt ); if ( enemyDist <= radius && !NPC->count //don't have one in hand already && NPC->enemy->client->NPC_class != CLASS_RANCOR && NPC->enemy->client->NPC_class != CLASS_GALAKMECH && NPC->enemy->client->NPC_class != CLASS_ATST && NPC->enemy->client->NPC_class != CLASS_GONK && NPC->enemy->client->NPC_class != CLASS_R2D2 && NPC->enemy->client->NPC_class != CLASS_R5D2 && NPC->enemy->client->NPC_class != CLASS_MARK1 && NPC->enemy->client->NPC_class != CLASS_MARK2 && NPC->enemy->client->NPC_class != CLASS_MOUSE && NPC->enemy->client->NPC_class != CLASS_PROBE && NPC->enemy->client->NPC_class != CLASS_SEEKER && NPC->enemy->client->NPC_class != CLASS_REMOTE && NPC->enemy->client->NPC_class != CLASS_SENTRY && NPC->enemy->client->NPC_class != CLASS_INTERROGATOR && NPC->enemy->client->NPC_class != CLASS_VEHICLE ) {//grab NPC->enemy = NPC->enemy;//make him my new best friend NPC->enemy->client->ps.eFlags |= EF_HELD_BY_WAMPA; //FIXME: this makes it so that the victim can't hit us with shots! Just use activator or something NPC->enemy->activator = NPC; // kind of dumb, but when we are locked to the Rancor, we are owned by it. NPC->activator = NPC->enemy;//remember him NPC->count = 1;//in my hand //wait to attack TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer + Q_irand(500, 2500) ); NPC_SetAnim( NPC->enemy, SETANIM_BOTH, BOTH_GRABBED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_HOLD_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "takingPain", -level.time ); return qtrue; } else if ( enemyDist < radius*2.0f ) {//smack G_Sound( NPC->enemy, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); //actually push the enemy vec3_t pushDir; 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 ( NPC->enemy->client->NPC_class != CLASS_RANCOR && NPC->enemy->client->NPC_class != CLASS_ATST && !(NPC->enemy->flags&FL_NO_KNOCKBACK) ) { G_Throw( NPC->enemy, pushDir, Q_irand( 30, 70 ) ); if ( NPC->enemy->health > 0 ) {//do pain on enemy G_Knockdown( NPC->enemy, NPC, pushDir, 300, qtrue ); } } } return qfalse; } /* ------------------------- NPC_BSWampa_Default ------------------------- */ void NPC_BSWampa_Default( void ) { //NORMAL ANIMS // stand1 = normal stand // walk1 = normal, non-angry walk // walk2 = injured // run1 = far away run // run2 = close run //VICTIM ANIMS // grabswipe = melee1 - sweep out and grab // stand2 attack = attack4 - while holding victim, swipe at him // walk3_drag = walk5 - walk with drag // stand2 = hold victim // stand2to1 = drop victim if ( NPC->client->ps.legsAnim == BOTH_HOLD_START ) { NPC_FaceEnemy( qtrue ); if ( NPC->client->ps.legsAnimTimer < 200 ) {//see if he's there to grab if ( !Wampa_TryGrab() ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_HOLD_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } } return; } if ( NPC->count ) { if ( !NPC->activator || !NPC->activator->client ) {//wtf? NPC->count = 0; NPC->activator = NULL; } else { if ( NPC->client->ps.legsAnim == BOTH_HOLD_DROP ) { if ( NPC->client->ps.legsAnimTimer < PM_AnimLength(NPC->client->clientInfo.animFileIndex, (animNumber_t)NPC->client->ps.legsAnim)-500 ) {//at least half a second into the anim if ( Wampa_CheckDropVictim( NPC, qfalse ) ) { TIMER_Set( NPC, "attacking", 1000+(Q_irand(500,1000)*(3-g_spskill->integer)) ); } } } else if ( !TIMER_Done( NPC, "takingPain" ) ) { Wampa_CheckDropVictim( NPC, qfalse ); } else if ( NPC->activator->health <= 0 ) { if ( TIMER_Done(NPC,"sniffCorpse") ) { Wampa_CheckDropVictim( NPC, 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+500 ); } } 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 { if ( TIMER_Done(NPC,"attacking") ) {//ready to attack NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_HOLD_ATTACK/*BOTH_ATTACK4*/, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set(NPC,"grabAttackDamage",1400); TIMER_Set(NPC,"attacking",NPC->client->ps.legsAnimTimer+Q_irand(3000,10000)); } if ( NPC->client->ps.legsAnim == BOTH_HOLD_ATTACK ) { if ( NPC->client->ps.legsAnimTimer ) { if ( TIMER_Done2(NPC,"grabAttackDamage",qtrue) ) { G_Sound( NPC->activator, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->currentOrigin, Q_irand( 25, 40 ), (DAMAGE_NO_KNOCKBACK|DAMAGE_NO_ARMOR), MOD_MELEE ); if ( NPC->activator->health <= 0 ) {//killed them, chance of dismembering int hitLoc = HL_WAIST; if ( g_dismemberment->integer < 4 ) { hitLoc = Q_irand( HL_BACK_RT, HL_HAND_LT ); } else { hitLoc = Q_irand( HL_WAIST, HL_HEAD ); } NPC->activator->client->dismembered = false; //FIXME: the limb should just disappear, cuz I ate it G_DoDismemberment( NPC->activator, NPC->activator->currentOrigin, MOD_SABER, 1000, hitLoc, qtrue ); TIMER_Set( NPC, "sniffCorpse", Q_irand( 2000, 5000 ) ); } NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_HANG_PAIN, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } } else { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_HOLD_IDLE/*BOTH_ATTACK4*/, SETANIM_FLAG_NORMAL ); } } else if ( NPC->client->ps.legsAnim == BOTH_STAND2TO1 && !NPC->client->ps.legsAnimTimer ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_HOLD_IDLE, SETANIM_FLAG_NORMAL ); } } } } NPC_UpdateAngles( qtrue, 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, "rageTime" ) ) {//do nothing but roar first time we see an enemy NPC_FaceEnemy( 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 scarier than me) {//they should be scared of ME and no-one else G_SetEnemy( NPC->enemy, NPC ); } if ( !TIMER_Done(NPC,"attacking") ) {//in middle of attack //face enemy NPC_FaceEnemy( qtrue ); //continue attack logic enemyDist = Distance( NPC->currentOrigin, NPC->enemy->currentOrigin ); Wampa_Attack( enemyDist, qfalse ); return; } else { if ( TIMER_Done(NPC,"angrynoise") ) { G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/wampa/misc/anger%d.wav", Q_irand(1, 2)) ); TIMER_Set( NPC, "angrynoise", Q_irand( 5000, 10000 ) ); } //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_WAMPA ) {//got mad at another Wampa, look for a valid enemy if ( TIMER_Done( NPC, "wampaInfight" ) ) { NPC_CheckEnemyExt( qtrue ); } } else { 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 ) ) {//it's been a while since the enemy died, or enemy is completely gone, get bored with him NPC->enemy = NULL; Wampa_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( (qboolean)(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 ) ); } } } Wampa_Combat(); return; } } else { if ( TIMER_Done(NPC,"idlenoise") ) { G_SoundOnEnt( NPC, CHAN_AUTO, "sound/chars/wampa/misc/anger3.wav" ); TIMER_Set( NPC, "idlenoise", Q_irand( 2000, 4000 ) ); } if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) { Wampa_Patrol(); } else { Wampa_Idle(); } } NPC_UpdateAngles( qtrue, qtrue ); }