//lock the owner into place relative to the cannon pos void EWebPositionUser(gentity_t *owner, gentity_t *eweb) { mdxaBone_t boltMatrix; vec3_t p, p2, d; trace_t tr; qboolean traceOver = qtrue; if ( owner->s.number < MAX_CLIENTS ) {//extra checks gi.trace(&tr, owner->currentOrigin, owner->mins, owner->maxs, owner->currentOrigin, owner->s.number, owner->clipmask, (EG2_Collision)0, 0); if ( tr.startsolid || tr.allsolid ) {//crap, they're already in solid somehow, don't bother tracing over traceOver = qfalse; } } if ( traceOver ) {//trace up VectorCopy( owner->currentOrigin, p2 ); p2[2] += STEPSIZE; gi.trace(&tr, owner->currentOrigin, owner->mins, owner->maxs, p2, owner->s.number, owner->clipmask, (EG2_Collision)0, 0); if (!tr.startsolid && !tr.allsolid ) { VectorCopy( tr.endpos, p2 ); } else { VectorCopy( owner->currentOrigin, p2 ); } } //trace over gi.G2API_GetBoltMatrix( eweb->ghoul2, 0, eweb->headBolt, &boltMatrix, eweb->s.apos.trBase, eweb->currentOrigin, (cg.time?cg.time:level.time), NULL, eweb->s.modelScale ); gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, p ); gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, d ); d[2] = 0; VectorNormalize( d ); VectorMA( p, -44.0f, d, p ); if ( !traceOver ) { VectorCopy( p, tr.endpos ); tr.allsolid = tr.startsolid = qfalse; } else { p[2] = p2[2]; if ( owner->s.number < MAX_CLIENTS ) {//extra checks //just see if end point is not in solid gi.trace(&tr, p, owner->mins, owner->maxs, p, owner->s.number, owner->clipmask, (EG2_Collision)0, 0); if ( tr.startsolid || tr.allsolid ) {//would be in solid there, so just trace over, I guess? gi.trace(&tr, p2, owner->mins, owner->maxs, p, owner->s.number, owner->clipmask, (EG2_Collision)0, 0); } } else {//trace over gi.trace(&tr, p2, owner->mins, owner->maxs, p, owner->s.number, owner->clipmask, (EG2_Collision)0, 0); } } if (!tr.startsolid && !tr.allsolid ) { //trace down VectorCopy( tr.endpos, p ); VectorCopy( p, p2 ); p2[2] -= STEPSIZE; gi.trace(&tr, p, owner->mins, owner->maxs, p2, owner->s.number, owner->clipmask, (EG2_Collision)0, 0); if (!tr.startsolid && !tr.allsolid )//&& tr.fraction == 1.0f) { //all clear, we can move there vec3_t moveDir; float moveDist; VectorCopy( tr.endpos, p ); VectorSubtract( p, eweb->pos4, moveDir ); moveDist = VectorNormalize( moveDir ); if ( moveDist > 4.0f ) {//moved past the threshold from last position vec3_t oRight; int strafeAnim; VectorCopy( p, eweb->pos4 );//update the position //find out what direction he moved in AngleVectors( owner->currentAngles, NULL, oRight, NULL ); if ( DotProduct( moveDir, oRight ) > 0 ) {//moved to his right, play right strafe strafeAnim = BOTH_STRAFE_RIGHT1; } else {//moved left, play left strafe strafeAnim = BOTH_STRAFE_LEFT1; } NPC_SetAnim( owner, SETANIM_LEGS, strafeAnim,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); } G_SetOrigin(owner, p); VectorCopy(p, owner->client->ps.origin); gi.linkentity( owner ); } } //FIXME: IK the hands to the handles of the gun? }
/* ------------------------- ATST_Idle ------------------------- */ void ATST_Idle( void ) { NPC_BSIdle(); NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND1, SETANIM_FLAG_NORMAL ); }
void TryUse( gentity_t *ent ) { gentity_t *target; trace_t trace; vec3_t src, dest, vf; if ( ent->s.number == 0 && ent->client->NPC_class == CLASS_ATST ) {//a player trying to get out of his ATST GEntity_UseFunc( ent->activator, ent, ent ); return; } //FIXME: this does not match where the new accurate crosshair aims... //cg.refdef.vieworg, basically VectorCopy( ent->client->renderInfo.eyePoint, src ); AngleVectors( ent->client->ps.viewangles, vf, NULL, NULL );//ent->client->renderInfo.eyeAngles was cg.refdef.viewangles, basically //extend to find end of use trace VectorMA( src, USE_DISTANCE, vf, dest ); //Trace ahead to find a valid target gi.trace( &trace, src, vec3_origin, vec3_origin, dest, ent->s.number, MASK_OPAQUE|CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_ITEM|CONTENTS_CORPSE ); if ( trace.fraction == 1.0f || trace.entityNum < 1 ) { //TODO: Play a failure sound /* if ( ent->s.number == 0 ) {//if nothing else, try the force telepathy power ForceTelepathy( ent ); } */ return; } target = &g_entities[trace.entityNum]; //Check for a use command if ( ValidUseTarget( target ) ) { NPC_SetAnim( ent, SETANIM_TORSO, BOTH_BUTTON_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); /* if ( !VectorLengthSquared( ent->client->ps.velocity ) && !PM_CrouchAnim( ent->client->ps.legsAnim ) ) { NPC_SetAnim( ent, SETANIM_LEGS, BOTH_BUTTON_HOLD, SETANIM_FLAG_NORMAL|SETANIM_FLAG_HOLD ); } */ //ent->client->ps.weaponTime = ent->client->ps.torsoAnimTimer; GEntity_UseFunc( target, ent, ent ); return; } else if ( target->client && target->client->ps.pm_type < PM_DEAD && target->NPC!=NULL && target->client->playerTeam && (target->client->playerTeam == ent->client->playerTeam || target->client->playerTeam == TEAM_NEUTRAL) && !(target->NPC->scriptFlags&SCF_NO_RESPONSE) ) { NPC_UseResponse ( target, ent, qfalse ); return; } /* if ( ent->s.number == 0 ) {//if nothing else, try the force telepathy power ForceTelepathy( ent ); } */ }
qboolean PM_AdjustAngleForWallRun( gentity_t *ent, usercmd_t *ucmd, qboolean doMove ) { if (( ent->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT || ent->client->ps.legsAnim == BOTH_WALL_RUN_LEFT ) && ent->client->ps.legsAnimTimer > 500 ) { //wall-running and not at end of anim //stick to wall, if there is one vec3_t rt, traceTo, mins = {ent->mins[0],ent->mins[1],0}, maxs = {ent->maxs[0],ent->maxs[1],24}, fwdAngles = {0, ent->client->ps.viewangles[YAW], 0}; trace_t trace; float dist, yawAdjust; AngleVectors( fwdAngles, NULL, rt, NULL ); if ( ent->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT ) { dist = 128; yawAdjust = -90; } else { dist = -128; yawAdjust = 90; } VectorMA( ent->currentOrigin, dist, rt, traceTo ); gi.trace( &trace, ent->currentOrigin, mins, maxs, traceTo, ent->s.number, ent->clipmask, (EG2_Collision)0, 0); if ( trace.fraction < 1.0f && trace.plane.normal[2] == 0.0f ) { //still a vertical wall there //FIXME: don't pull around 90 turns //FIXME: simulate stepping up steps here, somehow? if ( ent->s.number || !player_locked ) { if ( ent->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT ) { ucmd->rightmove = 127; } else { ucmd->rightmove = -127; } } if ( ucmd->upmove < 0 ) { ucmd->upmove = 0; } if ( ent->NPC ) { //invalid now VectorClear( ent->client->ps.moveDir ); } //make me face perpendicular to the wall ent->client->ps.viewangles[YAW] = vectoyaw( trace.plane.normal )+yawAdjust; if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) { //don't clamp angles when looking through a viewEntity SetClientViewAngle( ent, ent->client->ps.viewangles ); } ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; if ( ent->s.number || !player_locked ) { if ( doMove ) { //push me forward vec3_t fwd; float zVel = ent->client->ps.velocity[2]; if ( zVel > forceJumpStrength[FORCE_LEVEL_2]/2.0f ) { zVel = forceJumpStrength[FORCE_LEVEL_2]/2.0f; } if ( ent->client->ps.legsAnimTimer > 500 ) { //not at end of anim yet fwdAngles[YAW] = ent->client->ps.viewangles[YAW]; AngleVectors( fwdAngles, fwd, NULL, NULL ); //FIXME: or MA? float speed = 175; if ( ucmd->forwardmove < 0 ) { //slower speed = 100; } else if ( ucmd->forwardmove > 0 ) { speed = 250;//running speed } VectorScale( fwd, speed, ent->client->ps.velocity ); } ent->client->ps.velocity[2] = zVel;//preserve z velocity VectorMA( ent->client->ps.velocity, -128, trace.plane.normal, ent->client->ps.velocity ); //pull me toward the wall, too //VectorMA( ent->client->ps.velocity, dist, rt, ent->client->ps.velocity ); } } ucmd->forwardmove = 0; return qtrue; } else if ( doMove ) { //stop it if ( ent->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT ) { NPC_SetAnim( ent, SETANIM_BOTH, BOTH_WALL_RUN_RIGHT_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } else if ( ent->client->ps.legsAnim == BOTH_WALL_RUN_LEFT ) { NPC_SetAnim( ent, SETANIM_BOTH, BOTH_WALL_RUN_LEFT_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } } } return qfalse; }
void NPC_GM_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, vec3_t point, int damage, int mod,int hitLoc ) { if ( self->client->ps.powerups[PW_GALAK_SHIELD] == 0 ) {//shield is currently down //FIXME: allow for radius damage? if ( (hitLoc==HL_GENERIC1) && (self->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH) ) { int newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*antenna_base" ); if ( newBolt != -1 ) { GM_CreateExplosion( self, newBolt ); } gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "torso_shield_off", TURN_OFF ); gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "torso_antenna", TURN_OFF ); gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "torso_antenna_base_cap_off", TURN_ON ); self->client->ps.powerups[PW_GALAK_SHIELD] = 0;//temp, for effect self->client->ps.stats[STAT_ARMOR] = 0;//no more armor self->NPC->investigateDebounceTime = 0;//stop recharging NPC_SetAnim( self, SETANIM_BOTH, BOTH_ALERT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( self, "attackDelay", self->client->ps.torsoAnimTimer ); G_AddEvent( self, Q_irand( EV_DEATH1, EV_DEATH3 ), self->health ); } } else {//store the point for shield impact if ( point ) { VectorCopy( point, self->pos4 ); self->client->poisonTime = level.time; } } if ( !self->lockCount && !self->client->ps.torsoAnimTimer ) {//don't interrupt laser sweep attack or other special attacks/moves if ( self->count < 4 && self->health > 100 && hitLoc != HL_GENERIC1 ) { if ( self->delay < level.time ) { int speech; switch( self->count ) { default: case 0: speech = EV_PUSHED1; break; case 1: speech = EV_PUSHED2; break; case 2: speech = EV_PUSHED3; break; case 3: speech = EV_DETECTED1; break; } self->count++; self->NPC->blockedSpeechDebounceTime = 0; G_AddVoiceEvent( self, speech, Q_irand( 3000, 5000 ) ); self->delay = level.time + Q_irand( 5000, 7000 ); } } else { NPC_Pain( self, inflictor, attacker, point, damage, mod, hitLoc ); } } else if ( hitLoc == HL_GENERIC1 ) { NPC_SetPainEvent( self ); self->s.powerups |= ( 1 << PW_SHOCKED ); self->client->ps.powerups[PW_SHOCKED] = level.time + Q_irand( 500, 2500 ); } if ( inflictor && inflictor->lastEnemy == self ) {//He force-pushed my own lobfires back at me if ( mod == MOD_REPEATER_ALT && !Q_irand( 0, 2 ) ) { if ( TIMER_Done( self, "noRapid" ) ) { self->NPC->scriptFlags &= ~SCF_ALT_FIRE; self->alt_fire = qfalse; TIMER_Set( self, "noLob", Q_irand( 2000, 6000 ) ); } else {//hopefully this will make us fire the laser TIMER_Set( self, "noLob", Q_irand( 1000, 2000 ) ); } } else if ( mod == MOD_REPEATER && !Q_irand( 0, 5 ) ) { if ( TIMER_Done( self, "noLob" ) ) { self->NPC->scriptFlags |= SCF_ALT_FIRE; self->alt_fire = qtrue; TIMER_Set( self, "noRapid", Q_irand( 2000, 6000 ) ); } else {//hopefully this will make us fire the laser TIMER_Set( self, "noRapid", Q_irand( 1000, 2000 ) ); } } } }
void Rancor_Attack( float distance, qboolean doCharge ) { if ( !TIMER_Exists( NPC, "attacking" ) ) { if ( NPC->count == 2 && NPC->activator ) { } else if ( NPC->count == 1 && NPC->activator ) {//holding enemy if ( 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 vector3 fwd, yawAng; VectorSet( &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.z = 150; NPC->client->ps.groundEntityNum = ENTITYNUM_NONE; NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attack_dmg", 1250 ); } else if ( !Q_irand( 0, 1 ) ) {//smash NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attack_dmg", 1000 ); } else {//try to grab NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attack_dmg", 1000 ); } TIMER_Set( NPC, "attacking", NPC->client->ps.legsTimer + random() * 200 ); } // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks if ( TIMER_Done2( NPC, "attack_dmg", qtrue ) ) { vector3 shakePos; switch ( NPC->client->ps.legsAnim ) { case BOTH_MELEE1: Rancor_Smash(); G_GetBoltPosition( NPC, NPC->client->renderInfo.handLBolt, &shakePos, 0 ); G_ScreenShake( &shakePos, NULL, 4.0f, 1000, qfalse ); //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 ) { G_Damage( NPC->activator, NPC, NPC, &vec3_origin, &NPC->activator->r.currentOrigin, Q_irand( 25, 40 ), DAMAGE_NO_ARMOR | DAMAGE_NO_KNOCKBACK, MOD_MELEE ); if ( NPC->activator->health <= 0 ) {//killed him //make it look like we bit his head off //NPC->activator->client->dismembered = qfalse; G_Dismember( NPC->activator, NPC, &NPC->activator->r.currentOrigin, G2_MODELPART_HEAD, 90, 0, NPC->activator->client->ps.torsoAnim, qtrue ); //G_DoDismemberment( NPC->activator, NPC->activator->r.currentOrigin, MOD_SABER, 1000, HL_HEAD, qtrue ); NPC->activator->client->ps.forceHandExtend = HANDEXTEND_NONE; NPC->activator->client->ps.forceHandExtendTime = 0; NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); } G_Sound( NPC->activator, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/chomp.wav" ) ); } break; case BOTH_ATTACK2: //try to grab Rancor_Swing( qtrue ); break; case BOTH_ATTACK3: if ( NPC->count == 1 && NPC->activator ) { //cut in half if ( NPC->activator->client ) { //NPC->activator->client->dismembered = qfalse; G_Dismember( NPC->activator, NPC, &NPC->activator->r.currentOrigin, G2_MODELPART_WAIST, 90, 0, NPC->activator->client->ps.torsoAnim, qtrue ); //G_DoDismemberment( NPC->activator, NPC->enemy->r.currentOrigin, MOD_SABER, 1000, HL_WAIST, qtrue ); } //KILL G_Damage( NPC->activator, NPC, NPC, &vec3_origin, &NPC->activator->r.currentOrigin, NPC->enemy->health + 10, DAMAGE_NO_PROTECTION | DAMAGE_NO_ARMOR | DAMAGE_NO_KNOCKBACK | DAMAGE_NO_HIT_LOC, MOD_MELEE );//, HL_NONE );// if ( NPC->activator->client ) { NPC->activator->client->ps.forceHandExtend = HANDEXTEND_NONE; NPC->activator->client->ps.forceHandExtendTime = 0; 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, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); G_AddEvent( NPC->activator, EV_JUMP, NPC->activator->health ); } break; default: 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, CHAN_AUTO, 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 = qfalse; G_Dismember( NPC->activator, NPC, &NPC->activator->r.currentOrigin, G2_MODELPART_WAIST, 90, 0, NPC->activator->client->ps.torsoAnim, qtrue ); //G_DoDismemberment( NPC->activator, NPC->enemy->r.currentOrigin, MOD_SABER, 1000, HL_WAIST, qtrue ); //KILL G_Damage( NPC->activator, NPC, NPC, &vec3_origin, &NPC->activator->r.currentOrigin, NPC->enemy->health + 10, DAMAGE_NO_PROTECTION | DAMAGE_NO_ARMOR | DAMAGE_NO_KNOCKBACK | DAMAGE_NO_HIT_LOC, MOD_MELEE );//, HL_NONE ); NPC->activator->client->ps.forceHandExtend = HANDEXTEND_NONE; NPC->activator->client->ps.forceHandExtendTime = 0; NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); G_AddEvent( NPC->activator, EV_JUMP, NPC->activator->health ); } if ( NPC->activator->client ) {//*sigh*, can't get tags right, just remove them? NPC->activator->client->ps.eFlags |= EF_NODRAW; } NPC->count = 2; TIMER_Set( NPC, "clearGrabbed", 2600 ); } break; default: break; } } else if ( NPC->client->ps.legsAnim == BOTH_ATTACK2 ) { if ( NPC->client->ps.legsTimer >= 1200 && NPC->client->ps.legsTimer <= 1350 ) { if ( Q_irand( 0, 2 ) ) { Rancor_Swing( qfalse ); } else { Rancor_Swing( qtrue ); } } else if ( NPC->client->ps.legsTimer >= 1100 && NPC->client->ps.legsTimer <= 1550 ) { Rancor_Swing( qtrue ); } } // Just using this to remove the attacking flag at the right time TIMER_Done2( NPC, "attacking", qtrue ); }
/* ------------------------- NPC_BSDroid_Pain ------------------------- */ void NPC_Probe_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) { float pain_chance; VectorCopy( self->NPC->lastPathAngles, self->s.angles ); if ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) // demp2 always messes them up real good { vec3_t endPos; trace_t trace; VectorSet( endPos, self->currentOrigin[0], self->currentOrigin[1], self->currentOrigin[2] - 128 ); gi.trace( &trace, self->currentOrigin, NULL, NULL, endPos, self->s.number, MASK_SOLID, (EG2_Collision)0, 0 ); if ( trace.fraction == 1.0f || mod == MOD_DEMP2 ) // demp2 always does this { if (self->client->clientInfo.headModel != 0) { vec3_t origin; VectorCopy(self->currentOrigin,origin); origin[2] +=50; // G_PlayEffect( "small_chunks", origin ); G_PlayEffect( "chunks/probehead", origin ); G_PlayEffect( "env/med_explode2", origin ); self->client->clientInfo.headModel = 0; self->client->moveType = MT_RUNJUMP; self->client->ps.gravity = g_gravity->value*.1; } if ( (mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT) && other ) { vec3_t dir; NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); VectorSubtract( self->currentOrigin, other->currentOrigin, dir ); VectorNormalize( dir ); VectorMA( self->client->ps.velocity, 550, dir, self->client->ps.velocity ); self->client->ps.velocity[2] -= 127; } self->s.powerups |= ( 1 << PW_SHOCKED ); self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; self->NPC->localState = LSTATE_DROP; } } else { pain_chance = NPC_GetPainChance( self, damage ); if ( random() < pain_chance ) // Spin around in pain? { NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE); } } NPC_Pain( self, inflictor, other, point, damage, mod); }
void NPC_ChoosePainAnimation( gentity_t *self, gentity_t *other, vec3_t point, int damage, int mod, int hitLoc, int voiceEvent ) { int pain_anim = -1; float pain_chance; //If we've already taken pain, then don't take it again if ( level.time < self->painDebounceTime && /*mod != MOD_ELECTROCUTE &&*/ mod != MOD_MELEE ) //rwwFIXMEFIXME: MOD_ELECTROCUTE {//FIXME: if hit while recoving from losing a saber lock, we should still play a pain anim? return; } if ( self->s.weapon == WP_THERMAL && self->client->ps.weaponTime > 0 ) {//don't interrupt thermal throwing anim return; } else if ( self->client->NPC_class == CLASS_GALAKMECH ) { if ( hitLoc == HL_GENERIC1 ) {//hit the antenna! pain_chance = 1.0f; // self->s.powerups |= ( 1 << PW_SHOCKED ); // self->client->ps.powerups[PW_SHOCKED] = level.time + Q_irand( 500, 2500 ); //rwwFIXMEFIXME: support for this } // else if ( self->client->ps.powerups[PW_GALAK_SHIELD] ) // {//shield up // return; // } //rwwFIXMEFIXME: and this else if ( self->health > 200 && damage < 100 ) {//have a *lot* of health pain_chance = 0.05f; } else {//the lower my health and greater the damage, the more likely I am to play a pain anim pain_chance = (200.0f-self->health)/100.0f + damage/50.0f; } } else if ( self->client && self->client->playerTeam == NPCTEAM_PLAYER && other && !other->s.number ) {//ally shot by player always complains pain_chance = 1.1f; } else { if ( other && other->s.weapon == WP_SABER || /*mod == MOD_ELECTROCUTE ||*/ mod == MOD_CRUSH/*FIXME:MOD_FORCE_GRIP*/ ) { pain_chance = 1.0f;//always take pain from saber } else if ( mod == MOD_MELEE ) {//higher in rank (skill) we are, less likely we are to be fazed by a punch pain_chance = 1.0f - ((RANK_CAPTAIN-self->NPC->rank)/(float)RANK_CAPTAIN); } else if ( self->client->NPC_class == CLASS_PROTOCOL ) { pain_chance = 1.0f; } else { pain_chance = NPC_GetPainChance( self, damage ); } if ( self->client->NPC_class == CLASS_DESANN ) { pain_chance *= 0.5f; } } //See if we're going to flinch if ( random() < pain_chance ) { int animLength; //Pick and play our animation if ( self->client->ps.fd.forceGripBeingGripped < level.time ) {//not being force-gripped or force-drained if ( /*G_CheckForStrongAttackMomentum( self ) //rwwFIXMEFIXME: Is this needed? ||*/ PM_SpinningAnim( self->client->ps.legsAnim ) || BG_SaberInSpecialAttack( self->client->ps.torsoAnim ) || PM_InKnockDown( &self->client->ps ) || PM_RollingAnim( self->client->ps.legsAnim ) || (BG_FlippingAnim( self->client->ps.legsAnim )&&!PM_InCartwheel( self->client->ps.legsAnim )) ) {//strong attacks, rolls, knockdowns, flips and spins cannot be interrupted by pain } else {//play an anim int parts; if ( self->client->NPC_class == CLASS_GALAKMECH ) {//only has 1 for now //FIXME: never plays this, it seems... pain_anim = BOTH_PAIN1; } else if ( mod == MOD_MELEE ) { pain_anim = BG_PickAnim( self->localAnimIndex, BOTH_PAIN2, BOTH_PAIN3 ); } else if ( self->s.weapon == WP_SABER ) {//temp HACK: these are the only 2 pain anims that look good when holding a saber pain_anim = BG_PickAnim( self->localAnimIndex, BOTH_PAIN2, BOTH_PAIN3 ); } /* else if ( mod != MOD_ELECTROCUTE ) { pain_anim = G_PickPainAnim( self, point, damage, hitLoc ); } */ if ( pain_anim == -1 ) { pain_anim = BG_PickAnim( self->localAnimIndex, BOTH_PAIN1, BOTH_PAIN18 ); } self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_1;//next attack must be a quick attack self->client->ps.saberMove = LS_READY;//don't finish whatever saber move you may have been in parts = SETANIM_BOTH; if ( BG_CrouchAnim( self->client->ps.legsAnim ) || PM_InCartwheel( self->client->ps.legsAnim ) ) { parts = SETANIM_LEGS; } if (pain_anim != -1) { NPC_SetAnim( self, parts, pain_anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } } if ( voiceEvent != -1 ) { G_AddVoiceEvent( self, voiceEvent, Q_irand( 2000, 4000 ) ); } else { NPC_SetPainEvent( self ); } } else { G_AddVoiceEvent( self, Q_irand(EV_CHOKE1, EV_CHOKE3), 0 ); } //Setup the timing for it /* if ( mod == MOD_ELECTROCUTE ) { self->painDebounceTime = level.time + 4000; } */ animLength = bgAllAnims[self->localAnimIndex].anims[pain_anim].numFrames * fabs((float)(bgHumanoidAnimations[pain_anim].frameLerp)); self->painDebounceTime = level.time + animLength; self->client->ps.weaponTime = 0; } }
//void NPC_Rancor_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) void NPC_Rancor_Pain( gentity_t *self, gentity_t *attacker, int damage ) { qboolean hitByRancor = qfalse; //[CoOp] if ( self->NPC && self->NPC->ignorePain ) { return; } if ( !TIMER_Done( self, "breathAttack" ) ) {//nothing interrupts breath attack return; } TIMER_Remove( self, "confusionTime" ); //[/CoOp] if ( attacker&&attacker->client&&attacker->client->NPC_class==CLASS_RANCOR ) { hitByRancor = qtrue; } if ( attacker && attacker->inuse && attacker != self->enemy && !(attacker->flags&FL_NOTARGET) ) { if ( !self->count ) { //[CoOp] //adjusting for more than one player if ( (attacker->s.number < MAX_CLIENTS &&!Q_irand(0,3)) //if ( (!attacker->s.number&&!Q_irand(0,3)) //[/CoOp] || !self->enemy //[CoOp] //can be dead below 0 in MP || self->enemy->health <= 0 //|| self->enemy->health == 0 //[/CoOp] || (self->enemy->client&&self->enemy->client->NPC_class == CLASS_RANCOR) //[CoOp] SP Code || (!Q_irand(0, 4 ) && DistanceSquared( attacker->r.currentOrigin, self->r.currentOrigin ) < DistanceSquared( self->enemy->r.currentOrigin, self->r.currentOrigin )) ) //|| (self->NPC && self->NPC->consecutiveBlockedMoves>=10 && DistanceSquared( attacker->r.currentOrigin, self->r.currentOrigin ) < DistanceSquared( self->enemy->r.currentOrigin, self->r.currentOrigin )) ) ///[CoOp] {//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 //[CoOp] self->lastEnemy = self->enemy; //[/CoOp] G_SetEnemy( self, attacker ); //[CoOp] //RAFIXME - useDebounceTime not implimented /* SP Code if ( self->enemy != self->lastEnemy ) {//clear this so that we only sniff the player the first time we pick them up self->useDebounceTime = 0; } */ //[/CoOp] TIMER_Set( self, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); if ( hitByRancor ) {//stay mad at this Rancor for 2-5 secs before looking for attacker 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 //[CoOp] //move animations are uninterruptable in SP. && self->client->ps.legsAnim != BOTH_ATTACK2 && self->client->ps.legsAnim != BOTH_ATTACK10 && self->client->ps.legsAnim != BOTH_ATTACK11 ) //&& self->client->ps.legsAnim != BOTH_ATTACK2 ) //[/CoOp] {//cant interrupt one of the big attack anims /* if ( self->count != 1 || attacker == 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 ); } //[CoOp] SP Code TIMER_Set( self, "takingPain", self->client->ps.legsTimer+Q_irand(0, 500*(2-g_spskill.integer)) ); //TIMER_Set( self, "takingPain", self->client->ps.legsTimer+Q_irand(0, 500) ); //[CoOp] if ( self->NPC ) { self->NPC->localState = LSTATE_WAITING; } } } } } //let go /* if ( !Q_irand( 0, 3 ) && self->count == 1 ) { Rancor_DropVictim( self ); } */ } }
static void Howler_Howl( void ) { gentity_t *radiusEnts[ 128 ]; int numEnts; const float radius = (NPC->spawnflags&1)?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 ); 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->NPC_class == CLASS_HOWLER ) {//other howlers immune continue; } distSq = DistanceSquared( radiusEnts[i]->currentOrigin, boltOrg ); if ( distSq <= radiusSquared ) { if ( distSq < halfRadSquared ) {//close enough to do damage, too if ( Q_irand( 0, g_spskill->integer ) ) {//does no damage on easy, does 1 point every other frame on medium, more often on hard G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, NPC->currentOrigin, 1, DAMAGE_NO_KNOCKBACK, MOD_IMPACT ); } } if ( radiusEnts[i]->health > 0 && radiusEnts[i]->client && radiusEnts[i]->client->NPC_class != CLASS_RANCOR && radiusEnts[i]->client->NPC_class != CLASS_ATST && !PM_InKnockDown( &radiusEnts[i]->client->ps ) ) { if ( PM_HasAnimation( radiusEnts[i], BOTH_SONICPAIN_START ) ) { if ( radiusEnts[i]->client->ps.torsoAnim != BOTH_SONICPAIN_START && radiusEnts[i]->client->ps.torsoAnim != BOTH_SONICPAIN_HOLD ) { NPC_SetAnim( radiusEnts[i], SETANIM_LEGS, BOTH_SONICPAIN_START, SETANIM_FLAG_NORMAL ); NPC_SetAnim( radiusEnts[i], SETANIM_TORSO, BOTH_SONICPAIN_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); radiusEnts[i]->client->ps.torsoAnimTimer += 100; radiusEnts[i]->client->ps.weaponTime = radiusEnts[i]->client->ps.torsoAnimTimer; } else if ( radiusEnts[i]->client->ps.torsoAnimTimer <= 100 ) {//at the end of the sonic pain start or hold anim NPC_SetAnim( radiusEnts[i], SETANIM_LEGS, BOTH_SONICPAIN_HOLD, SETANIM_FLAG_NORMAL ); NPC_SetAnim( radiusEnts[i], SETANIM_TORSO, BOTH_SONICPAIN_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); radiusEnts[i]->client->ps.torsoAnimTimer += 100; radiusEnts[i]->client->ps.weaponTime = radiusEnts[i]->client->ps.torsoAnimTimer; } } /* else if ( distSq < halfRadSquared && radiusEnts[i]->client->ps.groundEntityNum != ENTITYNUM_NONE && !Q_irand( 0, 10 ) )//FIXME: base on skill {//within range G_Knockdown( radiusEnts[i], NPC, vec3_origin, 500, qfalse ); } */ } } } float playerDist = NPC_EntRangeFromBolt( player, NPC->genericBolt1 ); if ( playerDist < 256.0f ) { CGCam_Shake( 1.0f*playerDist/128.0f, 200 ); } }
//------------------------------ static void Howler_Attack( float enemyDist, qboolean howl ) { int dmg = (NPCInfo->localState==LSTATE_BERZERK)?5:2; if ( !TIMER_Exists( NPC, "attacking" )) { int attackAnim = BOTH_GESTURE1; // Going to do an attack if ( NPC->enemy && NPC->enemy->client && PM_InKnockDown( &NPC->enemy->client->ps ) && enemyDist <= MIN_DISTANCE ) { attackAnim = BOTH_ATTACK2; } else if ( !Q_irand( 0, 4 ) || howl ) {//howl attack //G_SoundOnEnt( NPC, CHAN_VOICE, "sound/chars/howler/howl.mp3" ); } else if ( enemyDist > MIN_DISTANCE && Q_irand( 0, 1 ) ) {//lunge attack //jump foward vec3_t fwd, yawAng = {0, NPC->client->ps.viewangles[YAW], 0}; AngleVectors( yawAng, fwd, NULL, NULL ); VectorScale( fwd, (enemyDist*3.0f), NPC->client->ps.velocity ); NPC->client->ps.velocity[2] = 200; NPC->client->ps.groundEntityNum = ENTITYNUM_NONE; attackAnim = BOTH_ATTACK1; } else {//tongue attack attackAnim = BOTH_ATTACK2; } NPC_SetAnim( NPC, SETANIM_BOTH, attackAnim, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD | SETANIM_FLAG_RESTART ); if ( NPCInfo->localState == LSTATE_BERZERK ) {//attack again right away TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer ); } else { TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer + Q_irand( 0, 1500 ) );//FIXME: base on skill TIMER_Set( NPC, "standing", -level.time ); TIMER_Set( NPC, "walking", -level.time ); TIMER_Set( NPC, "running", NPC->client->ps.legsAnimTimer + 5000 ); } TIMER_Set( NPC, "attack_dmg", 200 ); // level two damage } // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks switch ( NPC->client->ps.legsAnim ) { case BOTH_ATTACK1: case BOTH_MELEE1: if ( NPC->client->ps.legsAnimTimer > 650//more than 13 frames left && PM_AnimLength( NPC->client->clientInfo.animFileIndex, (animNumber_t)NPC->client->ps.legsAnim ) - NPC->client->ps.legsAnimTimer >= 800 )//at least 16 frames into anim { Howler_TryDamage( dmg, qfalse, qfalse ); } break; case BOTH_ATTACK2: case BOTH_MELEE2: if ( NPC->client->ps.legsAnimTimer > 350//more than 7 frames left && PM_AnimLength( NPC->client->clientInfo.animFileIndex, (animNumber_t)NPC->client->ps.legsAnim ) - NPC->client->ps.legsAnimTimer >= 550 )//at least 11 frames into anim { Howler_TryDamage( dmg, qtrue, qfalse ); } break; case BOTH_GESTURE1: { if ( NPC->client->ps.legsAnimTimer > 1800//more than 36 frames left && PM_AnimLength( NPC->client->clientInfo.animFileIndex, (animNumber_t)NPC->client->ps.legsAnim ) - NPC->client->ps.legsAnimTimer >= 950 )//at least 19 frames into anim { Howler_Howl(); if ( !NPC->count ) { G_PlayEffect( G_EffectIndex( "howler/sonic" ), NPC->playerModel, NPC->genericBolt1, NPC->s.number, NPC->currentOrigin, 4750, qtrue ); G_SoundOnEnt( NPC, CHAN_VOICE, "sound/chars/howler/howl.mp3" ); NPC->count = 1; } } } break; default: //anims seem to get reset after a load, so just stop attacking and it will restart as needed. TIMER_Remove( NPC, "attacking" ); break; } // Just using this to remove the attacking flag at the right time TIMER_Done2( NPC, "attacking", qtrue ); }
void NPC_HandleAIFlags (void) { //FIXME: make these flags checks a function call like NPC_CheckAIFlagsAndTimers if ( NPCInfo->aiFlags & NPCAI_LOST ) {//Print that you need help! //FIXME: shouldn't remove this just yet if cg_draw needs it NPCInfo->aiFlags &= ~NPCAI_LOST; /* if ( showWaypoints ) { Q3_DebugPrint(WL_WARNING, "%s can't navigate to target %s (my wp: %d, goal wp: %d)\n", NPC->targetname, NPCInfo->goalEntity->targetname, NPC->waypoint, NPCInfo->goalEntity->waypoint ); } */ if ( NPCInfo->goalEntity && NPCInfo->goalEntity == NPC->enemy ) {//We can't nav to our enemy //Drop enemy and see if we should search for him NPC_LostEnemyDecideChase(); } } if ( NPCInfo->aiFlags & NPCAI_AWAITING_COMM ) { if(NPCInfo->commWaitTime < level.time) { //FIXME: we shouldn't assume team_leader, we should remember who sent this hail! NPC_SetSayState(NPC, NPC->client->team_leader, Q_irand(SAY_BADHAIL1, SAY_BADHAIL4)); NPCInfo->aiFlags &= ~NPCAI_AWAITING_COMM; } } /* NPCInfo->canShove = qfalse; //flag never gets set in current nav implementation if (NPCInfo->aiFlags & NPCAI_BLOCKED) { NPCInfo->consecutiveBlockedMoves++; NPCInfo->blockedDebounceTime = level.time + 1000;//Remember you were blocked for a whole second //If totally blocked, should we see if we can jump the obstacle? if(NPCInfo->blockingEntNum == ENTITYNUM_WORLD)//WORLD {//Can't go anywhere G_ActivateBehavior( NPC, BSET_STUCK); //If you're in formation, what do we do here? } else { gentity_t *blocker = &g_entities[NPCInfo->blockingEntNum]; if( NPCInfo->consecutiveBlockedMoves > 10 ) {//Okay, shove them out of the way! if(NPCInfo->shoveCount > 3) {//Already tried shoving 4 times, just stand here NPCInfo->canShove = qfalse; } else { NPCInfo->canShove = qtrue; } } if(blocker->client && blocker->client->playerTeam == NPC->client->playerTeam) {//Should we ask it to get out of the way? //FIXME: NPC_SetSayBState(NPC, blocker, Q_irand(SAY_MOVEIT1, SAY_MOVEIT4);// ? if(NPCInfo->blockedSpeechDebounceTime < level.time) { if ( NPC->behaviorSet[BSET_BLOCKED] ) { G_ActivateBehavior( NPC, BSET_BLOCKED); } else { G_AddVoiceEvent( NPC, Q_irand(EV_BLOCKED1, EV_BLOCKED3), 0 ); } #ifdef _DEBUG //gi.Printf( "%s: 'Hey, %s, move it!'\n", NPC->targetname, blocker->targetname ); #endif //NPCInfo->blockedSpeechDebounceTime = level.time + 10000;//FIXME: make a define //Ok, need to make it get out of the way... } } else if((blocker->client || blocker->takedamage) && blocker->health > 0 && blocker->health < 200 ) {//Attack it!? Set enemy and temp behavior? Hmm... //Careful, what if it's explosive? G_SetEnemy( NPC, blocker ); if( NPCInfo->consecutiveBlockedMoves == 30 ) {//Blocked for three seconds straight G_ActivateBehavior( NPC, BSET_BLOCKED); } } } } else if(NPCInfo->blockedDebounceTime < level.time) {//Only clear if haven't been blocked for a whole second NPCInfo->consecutiveBlockedMoves = 0; NPCInfo->shoveCount = 0; } if(NPCInfo->shoveDebounce < level.time) {//We have shoved for 1 second at least NPCInfo->lastShoveDir = 0.0f; } //NAV_ClearBlockedInfo(NPC); */ //MRJ Request: if ( NPCInfo->aiFlags & NPCAI_GREET_ALLIES && !NPC->enemy )//what if "enemy" is the greetEnt? {//If no enemy, look for teammates to greet //FIXME: don't say hi to the same guy over and over again. if ( NPCInfo->greetingDebounceTime < level.time ) {//Has been at least 2 seconds since we greeted last if ( !NPCInfo->greetEnt ) {//Find a teammate whom I'm facing and who is facing me and within 128 NPCInfo->greetEnt = NPC_PickAlly( qtrue, 128, qtrue, qtrue ); } if ( NPCInfo->greetEnt && !Q_irand(0, 5) ) {//Start greeting someone qboolean greeted = qfalse; //TODO: If have a greetscript, run that instead? //FIXME: make them greet back? if( !Q_irand( 0, 2 ) ) {//Play gesture anim (press gesture button?) greeted = qtrue; NPC_SetAnim( NPC, SETANIM_TORSO, Q_irand( BOTH_GESTURE1, BOTH_GESTURE3 ), SETANIM_FLAG_NORMAL|SETANIM_FLAG_HOLD ); //NOTE: play full-body gesture if not moving? } if( !Q_irand( 0, 2 ) ) {//Play random voice greeting sound greeted = qtrue; //FIXME: need NPC sound sets G_AddVoiceEvent( NPC, Q_irand(EV_GREET1, EV_GREET3), 2000 ); } if( !Q_irand( 0, 1 ) ) {//set looktarget to them for a second or two greeted = qtrue; NPC_TempLookTarget( NPC, NPCInfo->greetEnt->s.number, 1000, 3000 ); } if ( greeted ) {//Did at least one of the things above //Don't greet again for 2 - 4 seconds NPCInfo->greetingDebounceTime = level.time + Q_irand( 2000, 4000 ); NPCInfo->greetEnt = NULL; } } } } }
/* =============== NPC_ExecuteBState MCG NPC Behavior state thinking =============== */ void NPC_ExecuteBState ( gentity_t *self)//, int msec ) { bState_t bState; NPC_HandleAIFlags(); //FIXME: these next three bits could be a function call, some sort of setup/cleanup func //Lookmode must be reset every think cycle if(NPC->aimDebounceTime < level.time) { NPCInfo->lookMode = LT_NONE; } if(NPC->delayScriptTime && NPC->delayScriptTime <= level.time) { G_ActivateBehavior( NPC, BSET_DELAYED); NPC->delayScriptTime = 0; } //Clear this and let bState set it itself, so it automatically handles changing bStates... but we need a set bState wrapper func NPCInfo->combatMove = qfalse; //Execute our bState if(NPCInfo->tempBehavior) {//Overrides normal behavior until cleared bState = NPCInfo->tempBehavior; } else { if(!NPCInfo->behaviorState) NPCInfo->behaviorState = NPCInfo->defaultBehavior; bState = NPCInfo->behaviorState; } //Pick the proper bstate for us and run it NPC_RunBehavior( self->client->playerTeam, bState ); //FIXME: Make these a func call if(bState != BS_FORMATION) {//So we know to re-acquire our closest squadpath point self->NPC->lastSquadPoint = -1; // NPCInfo->aiFlags |= NPCAI_OFF_PATH; } if(bState != BS_POINT_COMBAT && NPCInfo->combatPoint != -1) { //level.combatPoints[NPCInfo->combatPoint].occupied = qfalse; //NPCInfo->combatPoint = -1; } //Here we need to see what the scripted stuff told us to do //Only process snapshot if independant and in combat mode- this would pick enemies and go after needed items // ProcessSnapshot(); //Ignore my needs if I'm under script control- this would set needs for items // CheckSelf(); //Back to normal? All decisions made? //FIXME: don't walk off ledges unless we can get to our goal faster that way, or that's our goal's surface //NPCPredict(); if ( NPC->enemy ) { if ( !NPC->enemy->inuse ) {//just in case bState doesn't catch this G_ClearEnemy( NPC ); } } if ( !NPC_CheckLookTarget( NPC ) ) { if ( NPC->enemy ) { if ( NPC->client->ps.weapon != WP_IMPERIAL_BLADE && NPC->client->ps.weapon != WP_KLINGON_BLADE ) {//looking right at enemy during melee looks odd NPC_SetLookTarget( NPC, NPC->enemy->s.number, 0 ); } } } if ( NPC->enemy ) { if(NPC->enemy->flags & FL_DONT_SHOOT) { ucmd.buttons &= ~BUTTON_ATTACK; } if(client->ps.weaponstate == WEAPON_IDLE) { client->ps.weaponstate = WEAPON_READY; } } else { if(client->ps.weaponstate == WEAPON_READY) { client->ps.weaponstate = WEAPON_IDLE; } } if(!(ucmd.buttons & BUTTON_ATTACK) && NPC->attackDebounceTime > level.time) {//We just shot but aren't still shooting, so hold the gun up for a while if(client->ps.weapon == WP_PHASER ) {//One-handed NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY1,SETANIM_FLAG_NORMAL); } else if(client->ps.weapon == WP_COMPRESSION_RIFLE) {//Sniper pose NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY2,SETANIM_FLAG_NORMAL); } /*//FIXME: What's the proper solution here? else {//heavy weapon NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); } */ } else if (!NPC->enemy && bState != BS_FORMATION)//HACK! { if(client->ps.weapon != WP_TRICORDER) { if((NPC->s.torsoAnim&~ANIM_TOGGLEBIT) == TORSO_WEAPONREADY1 || (NPC->s.torsoAnim&~ANIM_TOGGLEBIT) == TORSO_WEAPONREADY2) {//we look ready for action, using one of the first 2 weapon, let's rest our weapon on our shoulder NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONIDLE1,SETANIM_FLAG_NORMAL); } } } NPC_CheckAttackHold(); NPC_ApplyScriptFlags(); //cliff and wall avoidance NPC_AvoidWallsAndCliffs(); // run the bot through the server like it was a real client //=== Save the ucmd for the second no-think Pmove ============================ ucmd.serverTime = level.time - 50; NPCInfo->last_ucmd = ucmd; if ( !NPCInfo->attackHoldTime ) { NPCInfo->last_ucmd.buttons &= ~BUTTON_ATTACK;//so we don't fire twice in one think } //============================================================================ NPC_CheckAttackScript(); NPC_KeepCurrentFacing(); if ( !NPC->next_roff_time || NPC->next_roff_time < level.time ) {//If we were following a roff, we don't do normal pmoves. ClientThink( NPC->s.number, &ucmd ); } else { NPC_ApplyRoff(); } //Had to leave this in, some legacy code must still be using s.angles //Shouldn't interfere with interpolation of angles, should it? VectorCopy( client->ps.viewangles, NPC->currentAngles ); // end of thinking cleanup NPCInfo->touchedByPlayer = NULL; NPC_CheckPlayerAim(); NPC_CheckAllClear(); /*if( ucmd.forwardmove || ucmd.rightmove ) { int i, la = -1, ta = -1; for(i = 0; i < MAX_ANIMATIONS; i++) { if((NPC->client->ps.legsAnim&~ANIM_TOGGLEBIT) == i) { la = i; } if((NPC->client->ps.torsoAnim&~ANIM_TOGGLEBIT) == i) { ta = i; } if(la != -1 && ta != -1) { break; } } if(la != -1 && ta != -1) {//FIXME: should never play same frame twice or restart an anim before finishing it gi.Printf("LegsAnim: %s(%d) TorsoAnim: %s(%d)\n", animTable[la].name, NPC->renderInfo.legsFrame, animTable[ta].name, NPC->client->renderInfo.torsoFrame); } }*/ }
/* =============== NPC_ExecuteBState MCG NPC Behavior state thinking =============== */ void NPC_ExecuteBState ( gentity_t *self)//, int msec ) { bState_t bState; NPC_HandleAIFlags(); //FIXME: these next three bits could be a function call, some sort of setup/cleanup func //Lookmode must be reset every think cycle if(NPC->delayScriptTime && NPC->delayScriptTime <= level.time) { G_ActivateBehavior( NPC, BSET_DELAYED); NPC->delayScriptTime = 0; } //Clear this and let bState set it itself, so it automatically handles changing bStates... but we need a set bState wrapper func NPCInfo->combatMove = qfalse; //Execute our bState if(NPCInfo->tempBehavior) {//Overrides normal behavior until cleared bState = NPCInfo->tempBehavior; } else { if(!NPCInfo->behaviorState) NPCInfo->behaviorState = NPCInfo->defaultBehavior; bState = NPCInfo->behaviorState; } //Pick the proper bstate for us and run it NPC_RunBehavior( self->client->playerTeam, bState ); if ( NPC->enemy ) { if ( !NPC->enemy->inuse ) {//just in case bState doesn't catch this G_ClearEnemy( NPC ); } } if ( NPC->client->ps.saberLockTime && NPC->client->ps.saberLockEnemy != ENTITYNUM_NONE ) { NPC_SetLookTarget( NPC, NPC->client->ps.saberLockEnemy, level.time+1000 ); } else if ( !NPC_CheckLookTarget( NPC ) ) { if ( NPC->enemy ) { NPC_SetLookTarget( NPC, NPC->enemy->s.number, 0 ); } } if ( NPC->enemy ) { if(NPC->enemy->flags & FL_DONT_SHOOT) { ucmd.buttons &= ~BUTTON_ATTACK; ucmd.buttons &= ~BUTTON_ALT_ATTACK; } else if ( NPC->client->playerTeam != NPCTEAM_ENEMY && NPC->enemy->NPC && (NPC->enemy->NPC->surrenderTime > level.time || (NPC->enemy->NPC->scriptFlags&SCF_FORCED_MARCH)) ) {//don't shoot someone who's surrendering if you're a good guy ucmd.buttons &= ~BUTTON_ATTACK; ucmd.buttons &= ~BUTTON_ALT_ATTACK; } if(client->ps.weaponstate == WEAPON_IDLE) { client->ps.weaponstate = WEAPON_READY; } } else { if(client->ps.weaponstate == WEAPON_READY) { client->ps.weaponstate = WEAPON_IDLE; } } if(!(ucmd.buttons & BUTTON_ATTACK) && NPC->attackDebounceTime > level.time) {//We just shot but aren't still shooting, so hold the gun up for a while if(client->ps.weapon == WP_SABER ) {//One-handed NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY1,SETANIM_FLAG_NORMAL); } else if(client->ps.weapon == WP_BRYAR_PISTOL) {//Sniper pose NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); } } else if ( !NPC->enemy )//HACK! { { if( NPC->s.torsoAnim == TORSO_WEAPONREADY1 || NPC->s.torsoAnim == TORSO_WEAPONREADY3 ) {//we look ready for action, using one of the first 2 weapon, let's rest our weapon on our shoulder NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONIDLE3,SETANIM_FLAG_NORMAL); } } } NPC_CheckAttackHold(); NPC_ApplyScriptFlags(); //cliff and wall avoidance NPC_AvoidWallsAndCliffs(); // run the bot through the server like it was a real client //=== Save the ucmd for the second no-think Pmove ============================ ucmd.serverTime = level.time - 50; memcpy( &NPCInfo->last_ucmd, &ucmd, sizeof( usercmd_t ) ); if ( !NPCInfo->attackHoldTime ) { NPCInfo->last_ucmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK);//so we don't fire twice in one think } //============================================================================ NPC_CheckAttackScript(); NPC_KeepCurrentFacing(); if ( !NPC->next_roff_time || NPC->next_roff_time < level.time ) {//If we were following a roff, we don't do normal pmoves. ClientThink( NPC->s.number, &ucmd ); } else { NPC_ApplyRoff(); } // end of thinking cleanup NPCInfo->touchedByPlayer = NULL; NPC_CheckPlayerAim(); NPC_CheckAllClear(); }
qboolean Pickup_Saber( gentity_t *self, qboolean hadSaber, gentity_t *pickUpSaber ) { //NOTE: loopAnim = saberSolo, alt_fire = saberLeftHand, NPC_type = saberType, NPC_targetname = saberColor qboolean foundIt = qfalse; if ( !pickUpSaber || !self || !self->client ) { return qfalse; } //G_RemoveWeaponModels( ent );//??? if ( Q_stricmp( "player", pickUpSaber->NPC_type ) == 0 ) { //"player" means use cvar info G_SetSabersFromCVars( self ); foundIt = qtrue; } else { saberInfo_t newSaber= {0}; qboolean swapSabers = qfalse; if ( self->client->ps.weapon == WP_SABER && self->client->ps.weaponTime > 0 ) { //can't pick up a new saber while the old one is busy (also helps to work as a debouncer so you don't swap out sabers rapidly when touching more than one at a time) return qfalse; } if ( pickUpSaber->count == 1 && g_saberPickuppableDroppedSabers->integer ) { swapSabers = qtrue; } if ( WP_SaberParseParms( pickUpSaber->NPC_type, &newSaber ) ) { //successfully found a saber .sab entry to use int saberNum = 0; qboolean removeLeftSaber = qfalse; if ( pickUpSaber->alt_fire ) { //always go in the left hand if ( !hadSaber ) { //can't have a saber only in your left hand! return qfalse; } saberNum = 1; //just in case... removeLeftSaber = qtrue; } else if ( !hadSaber ) { //don't have a saber at all yet, put it in our right hand saberNum = 0; //just in case... removeLeftSaber = qtrue; } else if ( pickUpSaber->loopAnim//only supposed to use this one saber when grab this pickup || (newSaber.saberFlags&SFL_TWO_HANDED) //new saber is two-handed || (hadSaber && (self->client->ps.saber[0].saberFlags&SFL_TWO_HANDED)) )//old saber is two-handed { //replace the old right-hand saber and remove the left hand one saberNum = 0; removeLeftSaber = qtrue; } else { //have, at least, a saber in our right hand and the new one could go in either left or right hand if ( self->client->ps.dualSabers ) { //I already have 2 sabers vec3_t dir2Saber, rightDir; //to determine which one to replace, see which side of me it's on VectorSubtract( pickUpSaber->currentOrigin, self->currentOrigin, dir2Saber ); dir2Saber[2] = 0; AngleVectors( self->currentAngles, NULL, rightDir, NULL ); rightDir[2] = 0; if ( DotProduct( rightDir, dir2Saber ) > 0 ) { saberNum = 0; } else { saberNum = 1; //just in case... removeLeftSaber = qtrue; } } else { //just add it as a second saber saberNum = 1; //just in case... removeLeftSaber = qtrue; } } if ( saberNum == 0 ) { //want to reach out with right hand if ( self->client->ps.torsoAnim == BOTH_BUTTON_HOLD ) { //but only if already playing the pickup with left hand anim... NPC_SetAnim( self, SETANIM_TORSO, BOTH_SABERPULL, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } if ( swapSabers ) { //drop first one where the one we're picking up is G_DropSaberItem( self->client->ps.saber[saberNum].name, self->client->ps.saber[saberNum].blade[0].color, pickUpSaber->currentOrigin, (float *)vec3_origin, pickUpSaber->currentAngles, pickUpSaber ); if ( removeLeftSaber ) { //drop other one at my origin G_DropSaberItem( self->client->ps.saber[1].name, self->client->ps.saber[1].blade[0].color, self->currentOrigin, (float *)vec3_origin, self->currentAngles, pickUpSaber ); } } } else { if ( swapSabers ) { G_DropSaberItem( self->client->ps.saber[saberNum].name, self->client->ps.saber[saberNum].blade[0].color, pickUpSaber->currentOrigin, (float *)vec3_origin, pickUpSaber->currentAngles, pickUpSaber ); } } if ( removeLeftSaber ) { WP_RemoveSaber( self, 1 ); } WP_SetSaber( self, saberNum, pickUpSaber->NPC_type ); WP_SaberInitBladeData( self ); if ( self->client->ps.saber[saberNum].stylesLearned ) { self->client->ps.saberStylesKnown |= self->client->ps.saber[saberNum].stylesLearned; } if ( self->client->ps.saber[saberNum].singleBladeStyle ) { self->client->ps.saberStylesKnown |= self->client->ps.saber[saberNum].singleBladeStyle; } if ( pickUpSaber->NPC_targetname != NULL ) { //NPC_targetname = saberColor saber_colors_t saber_color = TranslateSaberColor( pickUpSaber->NPC_targetname ); for ( int bladeNum = 0; bladeNum < MAX_BLADES; bladeNum++ ) { self->client->ps.saber[saberNum].blade[bladeNum].color = saber_color; } } if ( self->client->ps.torsoAnim == BOTH_BUTTON_HOLD || self->client->ps.torsoAnim == BOTH_SABERPULL ) { //don't let them attack right away, force them to finish the anim self->client->ps.weaponTime = self->client->ps.torsoAnimTimer; } foundIt = qtrue; } WP_SaberFreeStrings(newSaber); } return foundIt; }
void NPC_ChoosePainAnimation( gentity_t *self, gentity_t *other, const vec3_t point, int damage, int mod, int hitLoc, int voiceEvent = -1 ) { //If we've already taken pain, then don't take it again if ( level.time < self->painDebounceTime && mod != MOD_ELECTROCUTE && mod != MOD_MELEE ) {//FIXME: if hit while recoving from losing a saber lock, we should still play a pain anim? return; } int pain_anim = -1; float pain_chance; if ( self->s.weapon == WP_THERMAL && self->client->fireDelay > 0 ) {//don't interrupt thermal throwing anim return; } else if (self->client->ps.powerups[PW_GALAK_SHIELD]) { return; } else if ( self->client->NPC_class == CLASS_GALAKMECH ) { if ( hitLoc == HL_GENERIC1 ) {//hit the antenna! pain_chance = 1.0f; self->s.powerups |= ( 1 << PW_SHOCKED ); self->client->ps.powerups[PW_SHOCKED] = level.time + Q_irand( 500, 2500 ); } else if ( self->client->ps.powerups[PW_GALAK_SHIELD] ) {//shield up return; } else if ( self->health > 200 && damage < 100 ) {//have a *lot* of health pain_chance = 0.05f; } else {//the lower my health and greater the damage, the more likely I am to play a pain anim pain_chance = (200.0f-self->health)/100.0f + damage/50.0f; } } else if ( self->client && self->client->playerTeam == TEAM_PLAYER && other && !other->s.number ) {//ally shot by player always complains pain_chance = 1.1f; } else { if ( other && (other->s.weapon == WP_SABER || mod == MOD_ELECTROCUTE || mod == MOD_CRUSH/*FIXME:MOD_FORCE_GRIP*/) ) { if ( self->client->ps.weapon == WP_SABER && other->s.number < MAX_CLIENTS ) {//hmm, shouldn't *always* react to damage from player if I have a saber pain_chance = 1.05f - ((self->NPC->rank)/(float)RANK_CAPTAIN); } else { pain_chance = 1.0f;//always take pain from saber } } else if ( mod == MOD_GAS ) { pain_chance = 1.0f; } else if ( mod == MOD_MELEE ) {//higher in rank (skill) we are, less likely we are to be fazed by a punch pain_chance = 1.0f - ((RANK_CAPTAIN-self->NPC->rank)/(float)RANK_CAPTAIN); } else if ( self->client->NPC_class == CLASS_PROTOCOL ) { pain_chance = 1.0f; } else { pain_chance = NPC_GetPainChance( self, damage ); } if ( self->client->NPC_class == CLASS_DESANN ) { pain_chance *= 0.5f; } } //See if we're going to flinch if ( random() < pain_chance ) { //Pick and play our animation if ( (self->client->ps.eFlags&EF_FORCE_GRIPPED) ) { G_AddVoiceEvent( self, Q_irand(EV_CHOKE1, EV_CHOKE3), 0 ); } else if ( mod == MOD_GAS ) { //SIGH... because our choke sounds are inappropriately long, I have to debounce them in code! if ( TIMER_Done( self, "gasChokeSound" ) ) { TIMER_Set( self, "gasChokeSound", Q_irand( 1000, 2000 ) ); G_AddVoiceEvent( self, Q_irand(EV_CHOKE1, EV_CHOKE3), 0 ); } } else if ( (self->client->ps.eFlags&EF_FORCE_DRAINED) ) { NPC_SetPainEvent( self ); } else {//not being force-gripped or force-drained if ( G_CheckForStrongAttackMomentum( self ) || PM_SpinningAnim( self->client->ps.legsAnim ) || PM_SaberInSpecialAttack( self->client->ps.torsoAnim ) || PM_InKnockDown( &self->client->ps ) || PM_RollingAnim( self->client->ps.legsAnim ) || (PM_FlippingAnim( self->client->ps.legsAnim )&&!PM_InCartwheel( self->client->ps.legsAnim )) ) {//strong attacks, rolls, knockdowns, flips and spins cannot be interrupted by pain } else {//play an anim if ( self->client->NPC_class == CLASS_GALAKMECH ) {//only has 1 for now //FIXME: never plays this, it seems... pain_anim = BOTH_PAIN1; } else if ( mod == MOD_MELEE ) { pain_anim = PM_PickAnim( self, BOTH_PAIN2, BOTH_PAIN3 ); } else if ( self->s.weapon == WP_SABER ) {//temp HACK: these are the only 2 pain anims that look good when holding a saber pain_anim = PM_PickAnim( self, BOTH_PAIN2, BOTH_PAIN3 ); } else if ( mod != MOD_ELECTROCUTE ) { pain_anim = G_PickPainAnim( self, point, damage, hitLoc ); } if ( pain_anim == -1 ) { pain_anim = PM_PickAnim( self, BOTH_PAIN1, BOTH_PAIN18 ); } self->client->ps.saberAnimLevel = SS_FAST;//next attack must be a quick attack self->client->ps.saberMove = LS_READY;//don't finish whatever saber move you may have been in int parts = SETANIM_BOTH; if ( PM_CrouchAnim( self->client->ps.legsAnim ) || PM_InCartwheel( self->client->ps.legsAnim ) ) { parts = SETANIM_LEGS; } self->NPC->aiFlags &= ~NPCAI_KNEEL; NPC_SetAnim( self, parts, pain_anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } if ( voiceEvent != -1 ) { G_AddVoiceEvent( self, voiceEvent, Q_irand( 2000, 4000 ) ); } else { NPC_SetPainEvent( self ); } } //Setup the timing for it if ( mod == MOD_ELECTROCUTE ) { self->painDebounceTime = level.time + 4000; } self->painDebounceTime = level.time + PM_AnimLength( self->client->clientInfo.animFileIndex, (animNumber_t) pain_anim ); self->client->fireDelay = 0; } }
void Rancor_Swing( qboolean tryGrab ) { int radiusEntNums[128]; int numEnts; const float radius = 88; const float radiusSquared = (radius*radius); int i; vector3 boltOrg; numEnts = NPC_GetEntsNearBolt( radiusEntNums, radius, NPC->client->renderInfo.handRBolt, &boltOrg ); for ( i = 0; i < numEnts; i++ ) { gentity_t *radiusEnt = &g_entities[radiusEntNums[i]]; if ( !radiusEnt->inuse ) { continue; } if ( radiusEnt == NPC ) {//Skip the rancor ent continue; } if ( radiusEnt->client == NULL ) {//must be a client continue; } if ( (radiusEnt->client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) {//can't be one already being held continue; } if ( DistanceSquared( &radiusEnt->r.currentOrigin, &boltOrg ) <= radiusSquared ) { if (tryGrab && NPC->count != 1 //don't have one in hand or in mouth already - FIXME: allow one in hand and any number in mouth! && radiusEnt->client->NPC_class != CLASS_RANCOR && radiusEnt->client->NPC_class != CLASS_GALAKMECH && radiusEnt->client->NPC_class != CLASS_ATST && radiusEnt->client->NPC_class != CLASS_GONK && radiusEnt->client->NPC_class != CLASS_R2D2 && radiusEnt->client->NPC_class != CLASS_R5D2 && radiusEnt->client->NPC_class != CLASS_MARK1 && radiusEnt->client->NPC_class != CLASS_MARK2 && radiusEnt->client->NPC_class != CLASS_MOUSE && radiusEnt->client->NPC_class != CLASS_PROBE && radiusEnt->client->NPC_class != CLASS_SEEKER && radiusEnt->client->NPC_class != CLASS_REMOTE && radiusEnt->client->NPC_class != CLASS_SENTRY && radiusEnt->client->NPC_class != CLASS_INTERROGATOR && radiusEnt->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 = radiusEnt;//make him my new best friend radiusEnt->client->ps.eFlags2 |= EF2_HELD_BY_MONSTER; //FIXME: this makes it so that the victim can't hit us with shots! Just use activator or something radiusEnt->client->ps.hasLookTarget = qtrue; radiusEnt->client->ps.lookTarget = NPC->s.number; NPC->activator = radiusEnt;//remember him NPC->count = 1;//in my hand //wait to attack TIMER_Set(NPC, "attacking", NPC->client->ps.legsTimer + Q_irand(500, 2500)); if (radiusEnt->health > 0){//do pain on enemy if(radiusEnt->pain) radiusEnt->pain( radiusEnt, NPC, 100 ); JPLua::Entity_CallFunction( radiusEnt, JPLua::JPLUA_ENTITY_PAIN, (intptr_t)NPC, (intptr_t)100 ); //GEntity_PainFunc( radiusEnt, NPC, NPC, radiusEnt->r.currentOrigin, 0, MOD_CRUSH ); } else if ( radiusEnt->client ) { radiusEnt->client->ps.forceHandExtend = HANDEXTEND_NONE; radiusEnt->client->ps.forceHandExtendTime = 0; NPC_SetAnim( radiusEnt, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); } } else {//smack vector3 pushDir; vector3 angs; G_Sound( radiusEnt, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); //actually push the enemy /* //VectorSubtract( radiusEnt->r.currentOrigin, boltOrg, pushDir ); VectorSubtract( radiusEnt->r.currentOrigin, NPC->r.currentOrigin, pushDir ); pushDir[2] = flrand( 100, 200 ); VectorNormalize( pushDir ); */ VectorCopy( &NPC->client->ps.viewangles, &angs ); angs.yaw += flrand( 25, 50 ); angs.pitch = flrand( -25, -15 ); AngleVectors( &angs, &pushDir, NULL, NULL ); if ( radiusEnt->client->NPC_class != CLASS_RANCOR && radiusEnt->client->NPC_class != CLASS_ATST ) { G_Damage( radiusEnt, NPC, NPC, &vec3_origin, &radiusEnt->r.currentOrigin, Q_irand( 25, 40 ), DAMAGE_NO_ARMOR | DAMAGE_NO_KNOCKBACK, MOD_MELEE ); G_Throw( radiusEnt, &pushDir, 250 ); if ( radiusEnt->health > 0 ) {//do pain on enemy G_Knockdown( radiusEnt );//, NPC, pushDir, 100, qtrue ); } } } } } }
void Touch_Multi( gentity_t *self, gentity_t *other, trace_t *trace ) { if( !other->client ) { return; } if ( self->svFlags & SVF_INACTIVE ) {//set by target_deactivate return; } if( self->noDamageTeam ) { if ( other->client->playerTeam != self->noDamageTeam ) { return; } } // moved to just above multi_trigger because up here it just checks if the trigger is not being touched // we want it to check any conditions set on the trigger, if one of those isn't met, the trigger is considered to be "cleared" // if ( self->e_ThinkFunc == thinkF_trigger_cleared_fire ) // {//We're waiting to fire our target2 first // self->nextthink = level.time + self->speed; // return; // } if ( self->spawnflags & 1 ) { if ( other->s.number != 0 ) { return; } } else { if ( self->spawnflags & 16 ) {//NPCONLY if ( other->NPC == NULL ) { return; } } if ( self->NPC_targetname && self->NPC_targetname[0] ) { if ( other->script_targetname && other->script_targetname[0] ) { if ( Q_stricmp( self->NPC_targetname, other->script_targetname ) != 0 ) {//not the right guy to fire me off return; } } else { return; } } } if ( self->spawnflags & 2 ) {//FACING vec3_t forward; if ( other->client ) { AngleVectors( other->client->ps.viewangles, forward, NULL, NULL ); } else { AngleVectors( other->currentAngles, forward, NULL, NULL ); } if ( DotProduct( self->movedir, forward ) < 0.5 ) {//Not Within 45 degrees return; } } if ( self->spawnflags & 4 ) {//USE_BUTTON if ( !other->client ) { return; } if( !( other->client->usercmd.buttons & BUTTON_USE ) ) {//not pressing use button return; } } if ( self->spawnflags & 8 ) {//FIRE_BUTTON if ( !other->client ) { return; } if( !( other->client->ps.eFlags & EF_FIRING /*usercmd.buttons & BUTTON_ATTACK*/ ) && !( other->client->ps.eFlags & EF_ALT_FIRING/*usercmd.buttons & BUTTON_ALT_ATTACK*/ ) ) {//not pressing fire button or altfire button return; } //FIXME: do we care about the sniper rifle or not? if( other->s.number == 0 && ( other->client->ps.weapon > MAX_PLAYER_WEAPONS || other->client->ps.weapon <= WP_NONE ) ) {//don't care about non-player weapons if this is the player return; } } if ( other->client && self->radius ) { vec3_t eyeSpot; //Only works if your head is in it, but we allow leaning out //NOTE: We don't use CalcEntitySpot SPOT_HEAD because we don't want this //to be reliant on the physical model the player uses. VectorCopy(other->currentOrigin, eyeSpot); eyeSpot[2] += other->client->ps.viewheight; if ( G_PointInBounds( eyeSpot, self->absmin, self->absmax ) ) { if( !( other->client->ps.eFlags & EF_FIRING ) && !( other->client->ps.eFlags & EF_ALT_FIRING ) ) {//not attacking, so hiding bonus //FIXME: should really have sound events clear the hiddenDist other->client->hiddenDist = self->radius; //NOTE: movedir HAS to be normalized! if ( VectorLength( self->movedir ) ) {//They can only be hidden from enemies looking in this direction VectorCopy( self->movedir, other->client->hiddenDir ); } else { VectorClear( other->client->hiddenDir ); } } } } if ( self->spawnflags & 4 ) {//USE_BUTTON NPC_SetAnim( other, SETANIM_TORSO, BOTH_BUTTON_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); /* if ( !VectorLengthSquared( other->client->ps.velocity ) && !PM_CrouchAnim( other->client->ps.legsAnim ) ) { NPC_SetAnim( other, SETANIM_LEGS, BOTH_BUTTON_HOLD, SETANIM_FLAG_NORMAL|SETANIM_FLAG_HOLD ); } */ //other->client->ps.weaponTime = other->client->ps.torsoAnimTimer; } if ( self->e_ThinkFunc == thinkF_trigger_cleared_fire ) {//We're waiting to fire our target2 first self->nextthink = level.time + self->speed; return; } multi_trigger( self, other ); }
void NPC_Rancor_Pain( gentity_t *self, gentity_t *attacker, int damage ) { qboolean hitByRancor = qfalse; if ( attacker&&attacker->client&&attacker->client->NPC_class == CLASS_RANCOR ) { hitByRancor = qtrue; } if ( attacker && attacker->inuse && attacker != self->enemy && !(attacker->flags&FL_NOTARGET) ) { if ( !self->count ) { if ( (!attacker->s.number&&!Q_irand( 0, 3 )) || !self->enemy || self->enemy->health == 0 || (self->enemy->client&&self->enemy->client->NPC_class == CLASS_RANCOR) || (self->NPC && self->NPC->consecutiveBlockedMoves >= 10 && DistanceSquared( &attacker->r.currentOrigin, &self->r.currentOrigin ) < DistanceSquared( &self->enemy->r.currentOrigin, &self->r.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 G_SetEnemy( self, attacker ); TIMER_Set( self, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); if ( hitByRancor ) {//stay mad at this Rancor for 2-5 secs before looking for attacker 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 ) {//cant interrupt one of the big attack anims /* if ( self->count != 1 || attacker == 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.legsTimer + Q_irand( 0, 500 ) ); if ( self->NPC ) { self->NPC->localState = LSTATE_WAITING; } } } } } //let go /* if ( !Q_irand( 0, 3 ) && self->count == 1 ) { Rancor_DropVictim( self ); } */ } }
void NPC_BSSaberDroid_Patrol( void ) {//FIXME: pick up on bodies of dead buddies? if ( NPCInfo->confusionTime < level.time ) {//not confused by mindtrick //Look for any enemies if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES ) { if ( NPC_CheckPlayerTeamStealth() ) {//found an enemy //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be automatic now //NPC_AngerSound(); NPC_UpdateAngles( qtrue, qtrue ); return; } } if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) {//alert reaction behavior. //Is there danger nearby //[CoOp] int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_SUSPICIOUS, qfalse ); //int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_SUSPICIOUS ); //[/CoOp] //There is an event to look at if ( alertEvent >= 0 )//&& level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID ) { //NPCInfo->lastAlertID = level.alertEvents[alertEvent].ID; if ( level.alertEvents[alertEvent].level >= AEL_DISCOVERED ) { if ( level.alertEvents[alertEvent].owner && level.alertEvents[alertEvent].owner->client && level.alertEvents[alertEvent].owner->health >= 0 && level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam ) {//an enemy G_SetEnemy( NPC, level.alertEvents[alertEvent].owner ); //NPCInfo->enemyLastSeenTime = level.time; TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); } } else {//FIXME: get more suspicious over time? //Save the position for movement (if necessary) VectorCopy( level.alertEvents[alertEvent].position, NPCInfo->investigateGoal ); NPCInfo->investigateDebounceTime = level.time + Q_irand( 500, 1000 ); if ( level.alertEvents[alertEvent].level == AEL_SUSPICIOUS ) {//suspicious looks longer NPCInfo->investigateDebounceTime += Q_irand( 500, 2500 ); } } } if ( NPCInfo->investigateDebounceTime > level.time ) {//FIXME: walk over to it, maybe? Not if not chase enemies //NOTE: stops walking or doing anything else below vec3_t dir, angles; float o_yaw, o_pitch; VectorSubtract( NPCInfo->investigateGoal, NPC->client->renderInfo.eyePoint, dir ); vectoangles( dir, angles ); o_yaw = NPCInfo->desiredYaw; o_pitch = NPCInfo->desiredPitch; NPCInfo->desiredYaw = angles[YAW]; NPCInfo->desiredPitch = angles[PITCH]; NPC_UpdateAngles( qtrue, qtrue ); NPCInfo->desiredYaw = o_yaw; NPCInfo->desiredPitch = o_pitch; return; } } } //If we have somewhere to go, then do that if ( UpdateGoal() ) { ucmd.buttons |= BUTTON_WALKING; NPC_MoveToGoal( qtrue ); } else if ( !NPC->client->ps.weaponTime && TIMER_Done( NPC, "attackDelay" ) && TIMER_Done( NPC, "inactiveDelay" ) ) {//we want to turn off our saber if we need to. if ( !NPC->client->ps.saberHolstered ) {//saber is on. WP_DeactivateSaber( NPC, qfalse ); NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TURNOFF, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } } NPC_UpdateAngles( qtrue, qtrue ); }
/* ------------------------- NPC_BSDroid_Pain ------------------------- */ void NPC_Droid_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, vec3_t point, int damage, int mod,int hitLoc ) { int anim; float pain_chance; VectorCopy( self->NPC->lastPathAngles, self->s.angles ); if ( self->client->NPC_class == CLASS_R5D2 ) { pain_chance = NPC_GetPainChance( self, damage ); // Put it in pain if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT || random() < pain_chance ) // Spin around in pain? Demp2 always does this { // Health is between 0-30 or was hit by a DEMP2 so pop his head if ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) { if (!(self->spawnflags & 2)) // Doesn't have to ALWAYSDIE { if ((self->NPC->localState != LSTATE_SPINNING) && (!gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "head" ))) { gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "head", TURN_OFF ); // G_PlayEffect( "small_chunks" , self->currentOrigin ); G_PlayEffect( "r5d2head", self->currentOrigin ); self->s.powerups |= ( 1 << PW_SHOCKED ); self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; TIMER_Set( self, "droidsmoketotal", 5000); TIMER_Set( self, "droidspark", 100); self->NPC->localState = LSTATE_SPINNING; } } } // Just give him normal pain for a little while else { anim = self->client->ps.legsAnim; if ( anim == BOTH_STAND2 ) // On two legs? { anim = BOTH_PAIN1; } else // On three legs { anim = BOTH_PAIN2; } NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); // Spin around in pain self->NPC->localState = LSTATE_SPINNING; TIMER_Set( self, "roam", Q_irand(1000,2000)); } } } else if (self->client->NPC_class == CLASS_MOUSE) { if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) { self->NPC->localState = LSTATE_SPINNING; self->s.powerups |= ( 1 << PW_SHOCKED ); self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; } else { self->NPC->localState = LSTATE_BACKINGUP; } self->NPC->scriptFlags &= ~SCF_LOOK_FOR_ENEMIES; } else if ((self->client->NPC_class == CLASS_R2D2)) { pain_chance = NPC_GetPainChance( self, damage ); if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT || random() < pain_chance ) // Spin around in pain? Demp2 always does this { anim = self->client->ps.legsAnim; if ( anim == BOTH_STAND2 ) // On two legs? { anim = BOTH_PAIN1; } else // On three legs { anim = BOTH_PAIN2; } NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); // Spin around in pain self->NPC->localState = LSTATE_SPINNING; TIMER_Set( self, "roam", Q_irand(1000,2000)); } } else if ( self->client->NPC_class == CLASS_INTERROGATOR && ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) && other ) { vec3_t dir; VectorSubtract( self->currentOrigin, other->currentOrigin, dir ); VectorNormalize( dir ); VectorMA( self->client->ps.velocity, 550, dir, self->client->ps.velocity ); self->client->ps.velocity[2] -= 127; } NPC_Pain( self, inflictor, other, point, damage, mod); }
void G_SetTauntAnim( gentity_t *ent, int taunt ) { if ( !ent || !ent->client ) { return; } if ( !ent->client->ps.torsoAnimTimer && !ent->client->ps.legsAnimTimer && !ent->client->ps.weaponTime && ent->client->ps.saberLockTime < level.time ) { int anim = -1; switch ( taunt ) { case TAUNT_TAUNT: if ( ent->client->ps.weapon != WP_SABER ) { anim = BOTH_ENGAGETAUNT; } else if ( ent->client->ps.saber[0].tauntAnim != -1 ) { anim = ent->client->ps.saber[0].tauntAnim; } else if ( ent->client->ps.dualSabers && ent->client->ps.saber[1].tauntAnim != -1 ) { anim = ent->client->ps.saber[1].tauntAnim; } else { switch ( ent->client->ps.saberAnimLevel ) { case SS_FAST: case SS_TAVION: if ( ent->client->ps.saber[1].Active() ) {//turn off second saber G_Sound( ent, ent->client->ps.saber[1].soundOff ); } else if ( ent->client->ps.saber[0].Active() ) {//turn off first G_Sound( ent, ent->client->ps.saber[0].soundOff ); } ent->client->ps.SaberDeactivate(); anim = BOTH_GESTURE1; break; case SS_MEDIUM: case SS_STRONG: case SS_DESANN: anim = BOTH_ENGAGETAUNT; break; case SS_DUAL: ent->client->ps.SaberActivate(); anim = BOTH_DUAL_TAUNT; break; case SS_STAFF: ent->client->ps.SaberActivate(); anim = BOTH_STAFF_TAUNT; break; } } break; case TAUNT_BOW: if ( ent->client->ps.saber[0].bowAnim != -1 ) { anim = ent->client->ps.saber[0].bowAnim; } else if ( ent->client->ps.dualSabers && ent->client->ps.saber[1].bowAnim != -1 ) { anim = ent->client->ps.saber[1].bowAnim; } else { anim = BOTH_BOW; } if ( ent->client->ps.saber[1].Active() ) {//turn off second saber G_Sound( ent, ent->client->ps.saber[1].soundOff ); } else if ( ent->client->ps.saber[0].Active() ) {//turn off first G_Sound( ent, ent->client->ps.saber[0].soundOff ); } ent->client->ps.SaberDeactivate(); break; case TAUNT_MEDITATE: if ( ent->client->ps.saber[0].meditateAnim != -1 ) { anim = ent->client->ps.saber[0].meditateAnim; } else if ( ent->client->ps.dualSabers && ent->client->ps.saber[1].meditateAnim != -1 ) { anim = ent->client->ps.saber[1].meditateAnim; } else { anim = BOTH_MEDITATE; } if ( ent->client->ps.saber[1].Active() ) {//turn off second saber G_Sound( ent, ent->client->ps.saber[1].soundOff ); } else if ( ent->client->ps.saber[0].Active() ) {//turn off first G_Sound( ent, ent->client->ps.saber[0].soundOff ); } ent->client->ps.SaberDeactivate(); break; case TAUNT_FLOURISH: if ( ent->client->ps.weapon == WP_SABER ) { ent->client->ps.SaberActivate(); if ( ent->client->ps.saber[0].flourishAnim != -1 ) { anim = ent->client->ps.saber[0].flourishAnim; } else if ( ent->client->ps.dualSabers && ent->client->ps.saber[1].flourishAnim != -1 ) { anim = ent->client->ps.saber[1].flourishAnim; } else { switch ( ent->client->ps.saberAnimLevel ) { case SS_FAST: case SS_TAVION: anim = BOTH_SHOWOFF_FAST; break; case SS_MEDIUM: anim = BOTH_SHOWOFF_MEDIUM; break; case SS_STRONG: case SS_DESANN: anim = BOTH_SHOWOFF_STRONG; break; case SS_DUAL: anim = BOTH_SHOWOFF_DUAL; break; case SS_STAFF: anim = BOTH_SHOWOFF_STAFF; break; } } } break; case TAUNT_GLOAT: if ( ent->client->ps.saber[0].gloatAnim != -1 ) { anim = ent->client->ps.saber[0].gloatAnim; } else if ( ent->client->ps.dualSabers && ent->client->ps.saber[1].gloatAnim != -1 ) { anim = ent->client->ps.saber[1].gloatAnim; } else { switch ( ent->client->ps.saberAnimLevel ) { case SS_FAST: case SS_TAVION: anim = BOTH_VICTORY_FAST; break; case SS_MEDIUM: anim = BOTH_VICTORY_MEDIUM; break; case SS_STRONG: case SS_DESANN: ent->client->ps.SaberActivate(); anim = BOTH_VICTORY_STRONG; break; case SS_DUAL: ent->client->ps.SaberActivate(); anim = BOTH_VICTORY_DUAL; break; case SS_STAFF: ent->client->ps.SaberActivate(); anim = BOTH_VICTORY_STAFF; break; } } break; } if ( anim != -1 ) { if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) { int parts = SETANIM_TORSO; if ( anim != BOTH_ENGAGETAUNT ) { parts = SETANIM_BOTH; VectorClear( ent->client->ps.velocity ); } NPC_SetAnim( ent, parts, anim, (SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD) ); } if ( taunt != TAUNT_MEDITATE && taunt != TAUNT_BOW ) {//no sound for meditate or bow G_TauntSound( ent, taunt ); } } } }
/* ------------------------- Mark2_AttackDecision ------------------------- */ void Mark2_AttackDecision( void ) { float distance; qboolean visible; qboolean advance; NPC_FaceEnemy( qtrue ); distance = (int) DistanceHorizontalSquared( NPCS.NPC->r.currentOrigin, NPCS.NPC->enemy->r.currentOrigin ); visible = NPC_ClearLOS4( NPCS.NPC->enemy ); advance = (qboolean)(distance > MIN_DISTANCE_SQR); // He's been ordered to get up if (NPCS.NPCInfo->localState == LSTATE_RISINGUP) { NPCS.NPC->flags &= ~FL_SHIELDED; NPC_SetAnim( NPCS.NPC, SETANIM_BOTH, BOTH_RUN1START, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); if ((NPCS.NPC->client->ps.legsTimer<=0) && NPCS.NPC->client->ps.torsoAnim == BOTH_RUN1START ) { NPCS.NPCInfo->localState = LSTATE_NONE; // He's up again. } return; } // If we cannot see our target, move to see it if ((!visible) || (!NPC_FaceEnemy(qtrue))) { // If he's going down or is down, make him get up if ((NPCS.NPCInfo->localState == LSTATE_DOWN) || (NPCS.NPCInfo->localState == LSTATE_DROPPINGDOWN)) { if ( TIMER_Done( NPCS.NPC, "downTime" ) ) // Down being down?? (The delay is so he doesn't pop up and down when the player goes in and out of range) { NPCS.NPCInfo->localState = LSTATE_RISINGUP; NPC_SetAnim( NPCS.NPC, SETANIM_BOTH, BOTH_RUN1STOP, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); TIMER_Set( NPCS.NPC, "runTime", Q_irand( 3000, 8000) ); // So he runs for a while before testing to see if he should drop down. } } else { Mark2_Hunt(); } return; } // He's down but he could advance if he wants to. if ((advance) && (TIMER_Done( NPCS.NPC, "downTime" )) && (NPCS.NPCInfo->localState == LSTATE_DOWN)) { NPCS.NPCInfo->localState = LSTATE_RISINGUP; NPC_SetAnim( NPCS.NPC, SETANIM_BOTH, BOTH_RUN1STOP, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); TIMER_Set( NPCS.NPC, "runTime", Q_irand( 3000, 8000) ); // So he runs for a while before testing to see if he should drop down. } NPC_FaceEnemy( qtrue ); // Dropping down to shoot if (NPCS.NPCInfo->localState == LSTATE_DROPPINGDOWN) { NPC_SetAnim( NPCS.NPC, SETANIM_BOTH, BOTH_RUN1STOP, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); TIMER_Set( NPCS.NPC, "downTime", Q_irand( 3000, 9000) ); if ((NPCS.NPC->client->ps.legsTimer<=0) && NPCS.NPC->client->ps.torsoAnim == BOTH_RUN1STOP ) { NPCS.NPC->flags |= FL_SHIELDED; NPCS.NPCInfo->localState = LSTATE_DOWN; } } // He's down and shooting else if (NPCS.NPCInfo->localState == LSTATE_DOWN) { NPCS.NPC->flags |= FL_SHIELDED;//only damagable by lightsabers and missiles Mark2_BlasterAttack(qfalse); } else if (TIMER_Done( NPCS.NPC, "runTime" )) // Lowering down to attack. But only if he's done running at you. { NPCS.NPCInfo->localState = LSTATE_DROPPINGDOWN; } else if (advance) { // We can see enemy so shoot him if timer lets you. Mark2_BlasterAttack(advance); } }
void NPC_BSDefault( void ) { // vec3_t enemyDir; // float enemyDist; // float shootDist; // qboolean enemyFOV = qfalse; // qboolean enemyShotFOV = qfalse; // qboolean enemyPVS = qfalse; // vec3_t enemyHead; // vec3_t muzzle; // qboolean enemyLOS = qfalse; // qboolean enemyCS = qfalse; qboolean move = qtrue; // qboolean shoot = qfalse; if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) { WeaponThink( qtrue ); } if ( NPCInfo->scriptFlags & SCF_FORCED_MARCH ) {//being forced to walk if( NPC->client->ps.torsoAnim != TORSO_SURRENDER_START ) { NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_SURRENDER_START, SETANIM_FLAG_HOLD ); } } //look for a new enemy if don't have one and are allowed to look, validate current enemy if have one NPC_CheckEnemy( (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES), qfalse ); if ( !NPC->enemy ) {//still don't have an enemy if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) {//check for alert events //FIXME: Check Alert events, see if we should investigate or just look at it int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qtrue, AEL_DISCOVERED ); //There is an event to look at if ( alertEvent >= 0 )//&& level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID ) {//heard/saw something if ( level.alertEvents[alertEvent].level >= AEL_DISCOVERED && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) {//was a big event if ( level.alertEvents[alertEvent].owner && level.alertEvents[alertEvent].owner != NPC && level.alertEvents[alertEvent].owner->client && level.alertEvents[alertEvent].owner->health >= 0 && level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam ) {//an enemy G_SetEnemy( NPC, level.alertEvents[alertEvent].owner ); } } else {//FIXME: investigate lesser events } } //FIXME: also check our allies' condition? } } if ( NPC->enemy && !(NPCInfo->scriptFlags&SCF_FORCED_MARCH) ) { // just use the stormtrooper attack AI... NPC_CheckGetNewWeapon(); if ( NPC->client->leader && NPCInfo->goalEntity == NPC->client->leader && !Q3_TaskIDPending( NPC, TID_MOVE_NAV ) ) { NPC_ClearGoal(); } NPC_BSST_Attack(); return; /* //have an enemy //FIXME: if one of these fails, meaning we can't shoot, do we really need to do the rest? VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, enemyDir ); enemyDist = VectorNormalize( enemyDir ); enemyDist *= enemyDist; shootDist = NPC_MaxDistSquaredForWeapon(); enemyFOV = InFOV( NPC->enemy, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ); enemyShotFOV = InFOV( NPC->enemy, NPC, 20, 20 ); enemyPVS = gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ); if ( enemyPVS ) {//in the pvs trace_t tr; CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemyHead ); enemyHead[2] -= Q_flrand( 0.0f, NPC->enemy->maxs[2]*0.5f ); CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); enemyLOS = NPC_ClearLOS( muzzle, enemyHead ); gi.trace ( &tr, muzzle, vec3_origin, vec3_origin, enemyHead, NPC->s.number, MASK_SHOT ); enemyCS = NPC_EvaluateShot( tr.entityNum, qtrue ); } else {//skip thr 2 traces since they would have to fail enemyLOS = qfalse; enemyCS = qfalse; } if ( enemyCS && enemyShotFOV ) {//can hit enemy if we want NPC->cantHitEnemyCounter = 0; } else {//can't hit NPC->cantHitEnemyCounter++; } if ( enemyCS && enemyShotFOV && enemyDist < shootDist ) {//can shoot shoot = qtrue; if ( NPCInfo->goalEntity == NPC->enemy ) {//my goal is my enemy and I have a clear shot, no need to chase right now move = qfalse; } } else {//don't shoot yet, keep chasing shoot = qfalse; move = qtrue; } //shoot decision if ( !(NPCInfo->scriptFlags&SCF_DONT_FIRE) ) {//try to shoot if ( NPC->enemy ) { if ( shoot ) { if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here { WeaponThink( qtrue ); } } } } //chase decision if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) {//go after him NPCInfo->goalEntity = NPC->enemy; //FIXME: don't need to chase when have a clear shot and in range? if ( !enemyCS && NPC->cantHitEnemyCounter > 60 ) {//haven't been able to shoot enemy for about 6 seconds, need to do something //FIXME: combat points? Just chase? if ( enemyPVS ) {//in my PVS, just pick a combat point //FIXME: implement } else {//just chase him } } //FIXME: in normal behavior, should we use combat Points? Do we care? Is anyone actually going to ever use this AI? } else if ( NPC->cantHitEnemyCounter > 60 ) {//pick a new one NPC_CheckEnemy( qtrue, qfalse ); } if ( enemyPVS && enemyLOS )//&& !enemyShotFOV ) {//have a clear LOS to him//, but not looking at him //Find the desired angles vec3_t angles; GetAnglesForDirection( muzzle, enemyHead, angles ); NPCInfo->desiredYaw = AngleNormalize180( angles[YAW] ); NPCInfo->desiredPitch = AngleNormalize180( angles[PITCH] ); } */ } if ( UpdateGoal() ) {//have a goal if ( !NPC->enemy && NPC->client->leader && NPCInfo->goalEntity == NPC->client->leader && !Q3_TaskIDPending( NPC, TID_MOVE_NAV ) ) { NPC_BSFollowLeader(); } else { //set angles if ( (NPCInfo->scriptFlags & SCF_FACE_MOVE_DIR) || NPCInfo->goalEntity != NPC->enemy ) {//face direction of movement, NOTE: default behavior when not chasing enemy NPCInfo->combatMove = qfalse; } else {//face goal.. FIXME: what if have a navgoal but want to face enemy while moving? Will this do that? vec3_t dir, angles; NPCInfo->combatMove = qfalse; VectorSubtract( NPCInfo->goalEntity->currentOrigin, NPC->currentOrigin, dir ); vectoangles( dir, angles ); NPCInfo->desiredYaw = angles[YAW]; if ( NPCInfo->goalEntity == NPC->enemy ) { NPCInfo->desiredPitch = angles[PITCH]; } } //set movement //override default walk/run behavior //NOTE: redundant, done in NPC_ApplyScriptFlags if ( NPCInfo->scriptFlags & SCF_RUNNING ) { ucmd.buttons &= ~BUTTON_WALKING; } else if ( NPCInfo->scriptFlags & SCF_WALKING ) { ucmd.buttons |= BUTTON_WALKING; } else if ( NPCInfo->goalEntity == NPC->enemy ) { ucmd.buttons &= ~BUTTON_WALKING; } else { ucmd.buttons |= BUTTON_WALKING; } if ( NPCInfo->scriptFlags & SCF_FORCED_MARCH ) {//being forced to walk if ( g_crosshairEntNum != NPC->s.number ) {//don't walk if player isn't aiming at me move = qfalse; } } if ( move ) { //move toward goal NPC_MoveToGoal( qtrue ); } } } else if ( !NPC->enemy && NPC->client->leader ) { NPC_BSFollowLeader(); } //update angles NPC_UpdateAngles( qtrue, qtrue ); }
void NPC_BSGM_Attack( void ) { //Don't do anything if we're hurt if ( NPC->painDebounceTime > level.time ) { NPC_UpdateAngles( qtrue, qtrue ); return; } //FIXME: if killed enemy, use victory anim if ( NPC->enemy && NPC->enemy->health <= 0 && !NPC->enemy->s.number ) {//my enemy is dead if ( NPC->client->ps.torsoAnim == BOTH_STAND2TO1 ) { if ( NPC->client->ps.torsoAnimTimer <= 500 ) { G_AddVoiceEvent( NPC, Q_irand( EV_VICTORY1, EV_VICTORY3 ), 3000 ); NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsAnimTimer += 500; NPC->client->ps.torsoAnimTimer += 500; } } else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1START ) { if ( NPC->client->ps.torsoAnimTimer <= 500 ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1STARTGESTURE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsAnimTimer += 500; NPC->client->ps.torsoAnimTimer += 500; } } else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1STARTGESTURE ) { if ( NPC->client->ps.torsoAnimTimer <= 500 ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsAnimTimer += 500; NPC->client->ps.torsoAnimTimer += 500; } } else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1STOP ) { if ( NPC->client->ps.torsoAnimTimer <= 500 ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsAnimTimer = -1; NPC->client->ps.torsoAnimTimer = -1; } } else if ( NPC->wait ) { if ( TIMER_Done( NPC, "gloatTime" ) ) { GM_StartGloat(); } else if ( DistanceHorizontalSquared( NPC->client->renderInfo.eyePoint, NPC->enemy->currentOrigin ) > 4096 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//64 squared { NPCInfo->goalEntity = NPC->enemy; GM_Move(); } else {//got there GM_StartGloat(); } } NPC_FaceEnemy( qtrue ); NPC_UpdateAngles( qtrue, qtrue ); return; } //If we don't have an enemy, just idle if ( NPC_CheckEnemyExt() == qfalse || !NPC->enemy ) { NPC->enemy = NULL; NPC_BSGM_Patrol(); return; } enemyLOS = enemyCS = qfalse; bMove = qtrue; faceEnemy = qfalse; shoot = qfalse; hitAlly = qfalse; VectorClear( impactPos ); enemyDist = DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); if ( NPC->client->ps.torsoAnim == BOTH_ATTACK4 || NPC->client->ps.torsoAnim == BOTH_ATTACK5 ) { shoot = qfalse; if ( TIMER_Done( NPC, "smackTime" ) && !NPCInfo->blockedDebounceTime ) {//time to smack //recheck enemyDist and InFront if ( enemyDist < MELEE_DIST_SQUARED && InFront( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 0.3f ) ) { vec3_t smackDir; VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, smackDir ); smackDir[2] += 30; VectorNormalize( smackDir ); //hurt them G_Sound( NPC->enemy, G_SoundIndex( "sound/weapons/galak/skewerhit.wav" ) ); G_Damage( NPC->enemy, NPC, NPC, smackDir, NPC->currentOrigin, (g_spskill->integer+1)*Q_irand( 5, 10), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_CRUSH ); if ( NPC->client->ps.torsoAnim == BOTH_ATTACK4 ) {//smackdown int knockAnim = BOTH_KNOCKDOWN1; if ( PM_CrouchAnim( NPC->enemy->client->ps.legsAnim ) ) {//knockdown from crouch knockAnim = BOTH_KNOCKDOWN4; } //throw them smackDir[2] = 1; VectorNormalize( smackDir ); G_Throw( NPC->enemy, smackDir, 50 ); NPC_SetAnim( NPC->enemy, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } else {//uppercut //throw them G_Throw( NPC->enemy, smackDir, 100 ); //make them backflip NPC_SetAnim( NPC->enemy, SETANIM_BOTH, BOTH_KNOCKDOWN5, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } //done with the damage NPCInfo->blockedDebounceTime = 1; } } } else if ( NPC->lockCount ) //already shooting laser {//sometimes use the laser beam attack, but only after he's taken down our generator shoot = qfalse; if ( NPC->lockCount == 1 ) {//charging up if ( TIMER_Done( NPC, "beamDelay" ) ) {//time to start the beam int laserAnim; if ( Q_irand( 0, 1 ) ) { laserAnim = BOTH_ATTACK2; } else { laserAnim = BOTH_ATTACK7; } NPC_SetAnim( NPC, SETANIM_BOTH, laserAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoAnimTimer + Q_irand( 1000, 3000 ) ); //turn on beam effect NPC->lockCount = 2; G_PlayEffect( "galak/trace_beam", NPC->s.number ); NPC->s.loopSound = G_SoundIndex( "sound/weapons/galak/lasercutting.wav" ); if ( !NPCInfo->coverTarg ) {//for moving looping sound at end of trace NPCInfo->coverTarg = G_Spawn(); if ( NPCInfo->coverTarg ) { G_SetOrigin( NPCInfo->coverTarg, NPC->client->renderInfo.muzzlePoint ); NPCInfo->coverTarg->svFlags |= SVF_BROADCAST; NPCInfo->coverTarg->s.loopSound = G_SoundIndex( "sound/weapons/galak/lasercutting.wav" ); } } } } else {//in the actual attack now if ( !NPC->client->ps.torsoAnimTimer ) {//attack done! NPC->lockCount = 0; G_FreeEntity( NPCInfo->coverTarg ); NPC->s.loopSound = 0; NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_DROPWEAP2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoAnimTimer ); } else {//attack still going //do the trace and damage trace_t trace; vec3_t end, mins={-3,-3,-3}, maxs={3,3,3}; VectorMA( NPC->client->renderInfo.muzzlePoint, 1024, NPC->client->renderInfo.muzzleDir, end ); gi.trace( &trace, NPC->client->renderInfo.muzzlePoint, mins, maxs, end, NPC->s.number, MASK_SHOT ); if ( trace.allsolid || trace.startsolid ) {//oops, in a wall if ( NPCInfo->coverTarg ) { G_SetOrigin( NPCInfo->coverTarg, NPC->client->renderInfo.muzzlePoint ); } } else {//clear if ( trace.fraction < 1.0f ) {//hit something gentity_t *traceEnt = &g_entities[trace.entityNum]; if ( traceEnt && traceEnt->takedamage ) {//damage it G_SoundAtSpot( trace.endpos, G_SoundIndex( "sound/weapons/galak/laserdamage.wav" ) ); G_Damage( traceEnt, NPC, NPC, NPC->client->renderInfo.muzzleDir, trace.endpos, 10, 0, MOD_ENERGY ); } } if ( NPCInfo->coverTarg ) { G_SetOrigin( NPCInfo->coverTarg, trace.endpos ); } if ( !Q_irand( 0, 5 ) ) { G_SoundAtSpot( trace.endpos, G_SoundIndex( "sound/weapons/galak/laserdamage.wav" ) ); } } } } } else {//Okay, we're not in a special attack, see if we should switch weapons or start a special attack /* if ( NPC->s.weapon == WP_REPEATER && !(NPCInfo->scriptFlags & SCF_ALT_FIRE)//using rapid-fire && NPC->enemy->s.weapon == WP_SABER //enemy using saber && NPC->client && (NPC->client->ps.saberEventFlags&SEF_DEFLECTED) && !Q_irand( 0, 50 ) ) {//he's deflecting my shots, switch to the laser or the lob fire for a while TIMER_Set( NPC, "noRapid", Q_irand( 2000, 6000 ) ); NPCInfo->scriptFlags |= SCF_ALT_FIRE; NPC->alt_fire = qtrue; if ( NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH && (Q_irand( 0, 1 )||enemyDist < MAX_LOB_DIST_SQUARED) ) {//shield down, use laser NPC_GM_StartLaser(); } } else*/ if ( !NPC->client->ps.powerups[PW_GALAK_SHIELD] && enemyDist < MELEE_DIST_SQUARED && InFront( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 0.3f ) && G_StandardHumanoid( NPC->enemy->NPC_type ) )//within 80 and in front {//our shield is down, and enemy within 80, if very close, use melee attack to slap away if ( TIMER_Done( NPC, "attackDelay" ) ) { //animate me int swingAnim; if ( NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH ) {//generator down, use random melee swingAnim = Q_irand( BOTH_ATTACK4, BOTH_ATTACK5 );//smackdown or uppercut } else {//always knock-away swingAnim = BOTH_ATTACK5;//uppercut } //FIXME: swing sound NPC_SetAnim( NPC, SETANIM_BOTH, swingAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoAnimTimer + Q_irand( 1000, 3000 ) ); //delay the hurt until the proper point in the anim TIMER_Set( NPC, "smackTime", 600 ); NPCInfo->blockedDebounceTime = 0; //FIXME: say something? } } else if ( !NPC->lockCount && NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH && TIMER_Done( NPC, "attackDelay" ) && InFront( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 0.3f ) && ((!Q_irand( 0, 10*(2-g_spskill->integer))&& enemyDist > MIN_LOB_DIST_SQUARED&& enemyDist < MAX_LOB_DIST_SQUARED) ||(!TIMER_Done( NPC, "noLob" )&&!TIMER_Done( NPC, "noRapid" ))) && NPC->enemy->s.weapon != WP_TURRET ) {//sometimes use the laser beam attack, but only after he's taken down our generator shoot = qfalse; NPC_GM_StartLaser(); } else if ( enemyDist < MIN_LOB_DIST_SQUARED && (NPC->enemy->s.weapon != WP_TURRET || Q_stricmp( "PAS", NPC->enemy->classname )) && TIMER_Done( NPC, "noRapid" ) )//256 {//enemy within 256 if ( (NPC->client->ps.weapon == WP_REPEATER) && (NPCInfo->scriptFlags & SCF_ALT_FIRE) ) {//shooting an explosive, but enemy too close, switch to primary fire NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; NPC->alt_fire = qfalse; //FIXME: use weap raise & lower anims NPC_ChangeWeapon( WP_REPEATER ); } } else if ( (enemyDist > MAX_LOB_DIST_SQUARED || (NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ))) && TIMER_Done( NPC, "noLob" ) )//448 {//enemy more than 448 away and we are ready to try lob fire again if ( (NPC->client->ps.weapon == WP_REPEATER) && !(NPCInfo->scriptFlags & SCF_ALT_FIRE) ) {//enemy far enough away to use lobby explosives NPCInfo->scriptFlags |= SCF_ALT_FIRE; NPC->alt_fire = qtrue; //FIXME: use weap raise & lower anims NPC_ChangeWeapon( WP_REPEATER ); } } } //can we see our target? if ( NPC_ClearLOS( NPC->enemy ) ) { NPCInfo->enemyLastSeenTime = level.time;//used here for aim debouncing, not always a clear LOS enemyLOS = qtrue; if ( NPC->client->ps.weapon == WP_NONE ) { enemyCS = qfalse;//not true, but should stop us from firing NPC_AimAdjust( -1 );//adjust aim worse longer we have no weapon } else {//can we shoot our target? if ( ((NPC->client->ps.weapon == WP_REPEATER && (NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist < MIN_LOB_DIST_SQUARED )//256 { enemyCS = qfalse;//not true, but should stop us from firing hitAlly = qtrue;//us! //FIXME: if too close, run away! } else { int hit = NPC_ShotEntity( NPC->enemy, impactPos ); gentity_t *hitEnt = &g_entities[hit]; if ( hit == NPC->enemy->s.number || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) || ( hitEnt && hitEnt->takedamage ) ) {//can hit enemy or will hit glass or other breakable, so shoot anyway enemyCS = qtrue; NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation ); } else {//Hmm, have to get around this bastard NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam ) {//would hit an ally, don't fire!!! hitAlly = qtrue; } else {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire } } } } } else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) ) { if ( TIMER_Done( NPC, "talkDebounce" ) && !Q_irand( 0, 10 ) ) { if ( NPCInfo->enemyCheckDebounceTime < 8 ) { int speech = -1; switch( NPCInfo->enemyCheckDebounceTime ) { case 0: case 1: case 2: speech = EV_CHASE1 + NPCInfo->enemyCheckDebounceTime; break; case 3: case 4: case 5: speech = EV_COVER1 + NPCInfo->enemyCheckDebounceTime-3; break; case 6: case 7: speech = EV_ESCAPING1 + NPCInfo->enemyCheckDebounceTime-6; break; } NPCInfo->enemyCheckDebounceTime++; if ( speech != -1 ) { G_AddVoiceEvent( NPC, speech, Q_irand( 3000, 5000 ) ); TIMER_Set( NPC, "talkDebounce", Q_irand( 5000, 7000 ) ); } } } NPCInfo->enemyLastSeenTime = level.time; int hit = NPC_ShotEntity( NPC->enemy, impactPos ); gentity_t *hitEnt = &g_entities[hit]; if ( hit == NPC->enemy->s.number || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) || ( hitEnt && hitEnt->takedamage ) ) {//can hit enemy or will hit glass or other breakable, so shoot anyway enemyCS = qtrue; } else { faceEnemy = qtrue; NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy } } if ( enemyLOS ) { faceEnemy = qtrue; } else { if ( !NPCInfo->goalEntity ) { NPCInfo->goalEntity = NPC->enemy; } if ( NPCInfo->goalEntity == NPC->enemy ) {//for now, always chase the enemy bMove = qtrue; } } if ( enemyCS ) { shoot = qtrue; //NPCInfo->enemyCheckDebounceTime = level.time;//actually used here as a last actual LOS } else { if ( !NPCInfo->goalEntity ) { NPCInfo->goalEntity = NPC->enemy; } if ( NPCInfo->goalEntity == NPC->enemy ) {//for now, always chase the enemy bMove = qtrue; } } //Check for movement to take care of GM_CheckMoveState(); //See if we should override shooting decision with any special considerations GM_CheckFireState(); if ( NPC->client->ps.weapon == WP_REPEATER && (NPCInfo->scriptFlags&SCF_ALT_FIRE) && shoot && TIMER_Done( NPC, "attackDelay" ) ) { vec3_t muzzle; vec3_t angles; vec3_t target; vec3_t velocity = {0,0,0}; vec3_t mins = {-REPEATER_ALT_SIZE,-REPEATER_ALT_SIZE,-REPEATER_ALT_SIZE}, maxs = {REPEATER_ALT_SIZE,REPEATER_ALT_SIZE,REPEATER_ALT_SIZE}; CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); VectorCopy( NPC->enemy->currentOrigin, target ); target[0] += Q_flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2); target[1] += Q_flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2); target[2] += Q_flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2); //Find the desired angles qboolean clearshot = WP_LobFire( NPC, muzzle, target, mins, maxs, MASK_SHOT|CONTENTS_LIGHTSABER, velocity, qtrue, NPC->s.number, NPC->enemy->s.number, 300, 1100, 1500, qtrue ); if ( VectorCompare( vec3_origin, velocity ) || (!clearshot&&enemyLOS&&enemyCS) ) {//no clear lob shot and no lob shot that will hit something breakable if ( enemyLOS && enemyCS && TIMER_Done( NPC, "noRapid" ) ) {//have a clear straight shot, so switch to primary NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; NPC->alt_fire = qfalse; NPC_ChangeWeapon( WP_REPEATER ); //keep this weap for a bit TIMER_Set( NPC, "noLob", Q_irand( 500, 1000 ) ); } else { shoot = qfalse; } } else { vectoangles( velocity, angles ); NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] ); VectorCopy( velocity, NPC->client->hiddenDir ); NPC->client->hiddenDist = VectorNormalize ( NPC->client->hiddenDir ); } } else if ( faceEnemy ) {//face the enemy NPC_FaceEnemy( qtrue ); } if ( !TIMER_Done( NPC, "standTime" ) ) { bMove = qfalse; } if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) {//not supposed to chase my enemies if ( NPCInfo->goalEntity == NPC->enemy ) {//goal is my entity, so don't bMove bMove = qfalse; } } if ( bMove && !NPC->lockCount ) {//bMove toward goal if ( NPCInfo->goalEntity && NPC->client->ps.legsAnim != BOTH_ALERT1 && NPC->client->ps.legsAnim != BOTH_ATTACK2 && NPC->client->ps.legsAnim != BOTH_ATTACK4 && NPC->client->ps.legsAnim != BOTH_ATTACK5 && NPC->client->ps.legsAnim != BOTH_ATTACK7 ) { bMove = GM_Move(); } else { bMove = qfalse; } } if ( !TIMER_Done( NPC, "flee" ) ) {//running away faceEnemy = qfalse; } //FIXME: check scf_face_move_dir here? if ( !faceEnemy ) {//we want to face in the dir we're running if ( !bMove ) {//if we haven't moved, we should look in the direction we last looked? VectorCopy( NPC->client->ps.viewangles, NPCInfo->lastPathAngles ); } if ( bMove ) {//don't run away and shoot NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; NPCInfo->desiredPitch = 0; shoot = qfalse; } } NPC_UpdateAngles( qtrue, qtrue ); if ( NPCInfo->scriptFlags & SCF_DONT_FIRE ) { shoot = qfalse; } if ( NPC->enemy && NPC->enemy->enemy ) { if ( NPC->enemy->s.weapon == WP_SABER && NPC->enemy->enemy->s.weapon == WP_SABER ) {//don't shoot at an enemy jedi who is fighting another jedi, for fear of injuring one or causing rogue blaster deflections (a la Obi Wan/Vader duel at end of ANH) shoot = qfalse; } } //FIXME: don't shoot right away! if ( shoot ) {//try to shoot if it's time if ( TIMER_Done( NPC, "attackDelay" ) ) { if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here { WeaponThink( qtrue ); } } } //also: if ( NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ) ) {//crush turrets if ( G_BoundsOverlap( NPC->absmin, NPC->absmax, NPC->enemy->absmin, NPC->enemy->absmax ) ) {//have to do this test because placed turrets are not solid to NPCs (so they don't obstruct navigation) if ( NPC->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) { NPC->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME; G_Damage( NPC->enemy, NPC, NPC, NULL, NPC->currentOrigin, 100, DAMAGE_NO_KNOCKBACK, MOD_ELECTROCUTE ); } else { G_Damage( NPC->enemy, NPC, NPC, NULL, NPC->currentOrigin, 100, DAMAGE_NO_KNOCKBACK, MOD_CRUSH ); } } } else if ( NPCInfo->touchedByPlayer != NULL && NPCInfo->touchedByPlayer == NPC->enemy ) {//touched enemy if ( NPC->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) {//zap him! //animate me NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK6, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoAnimTimer ); TIMER_Set( NPC, "standTime", NPC->client->ps.legsAnimTimer ); //FIXME: debounce this? NPCInfo->touchedByPlayer = NULL; //FIXME: some shield effect? NPC->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME; vec3_t smackDir; VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, smackDir ); smackDir[2] += 30; VectorNormalize( smackDir ); G_Damage( NPC->enemy, NPC, NPC, smackDir, NPC->currentOrigin, (g_spskill->integer+1)*Q_irand( 5, 10), DAMAGE_NO_KNOCKBACK, MOD_ELECTROCUTE ); //throw them G_Throw( NPC->enemy, smackDir, 100 ); NPC->enemy->s.powerups |= ( 1 << PW_SHOCKED ); if ( NPC->enemy->client ) { NPC->enemy->client->ps.powerups[PW_SHOCKED] = level.time + 1000; } //stop any attacks ucmd.buttons = 0; } } if ( NPCInfo->movementSpeech < 3 && NPCInfo->blockedSpeechDebounceTime <= level.time ) { if ( NPC->enemy && NPC->enemy->health > 0 && NPC->enemy->painDebounceTime > level.time ) { if ( NPC->enemy->health < 50 && NPCInfo->movementSpeech == 2 ) { G_AddVoiceEvent( NPC, EV_ANGER2, Q_irand( 2000, 4000 ) ); NPCInfo->movementSpeech = 3; } else if ( NPC->enemy->health < 75 && NPCInfo->movementSpeech == 1 ) { G_AddVoiceEvent( NPC, EV_ANGER1, Q_irand( 2000, 4000 ) ); NPCInfo->movementSpeech = 2; } else if ( NPC->enemy->health < 100 && NPCInfo->movementSpeech == 0 ) { G_AddVoiceEvent( NPC, EV_ANGER3, Q_irand( 2000, 4000 ) ); NPCInfo->movementSpeech = 1; } } } }
void SandCreature_Attack( qboolean miss ) { //FIXME: make it able to grab a thermal detonator, take it down, // then have it explode inside them, killing them // (or, do damage, making them stick half out of the ground and // screech for a bit, giving you a chance to run for it!) //FIXME: effect and sound //FIXME: shootable during this anim? if ( !NPC->enemy->client ) { NPC_SetAnim( NPC, SETANIM_LEGS, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART ); } else { NPC_SetAnim( NPC, SETANIM_LEGS, Q_irand( BOTH_ATTACK1, BOTH_ATTACK2 ), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART ); } //don't do anything else while in this anim TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer ); float playerDist = Distance( player->currentOrigin, NPC->currentOrigin ); if ( playerDist < 256 ) { //FIXME: tone this down CGCam_Shake( 0.75f*playerDist/128.0f, NPC->client->ps.legsAnimTimer ); } if ( miss ) {//purposely missed him, chance of knocking him down //FIXME: if, during the attack anim, I do end up catching him close to my mouth, then snatch him anyway... if ( NPC->enemy && NPC->enemy->client ) { vec3_t dir2Enemy; VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, dir2Enemy ); if ( dir2Enemy[2] < 30 ) { dir2Enemy[2] = 30; } if ( g_spskill->integer > 0 ) { float enemyDist = VectorNormalize( dir2Enemy ); //FIXME: tone this down, smaller radius if ( enemyDist < 200 && NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) { float throwStr = ((200-enemyDist)*0.4f)+20; if ( throwStr > 45 ) { throwStr = 45; } G_Throw( NPC->enemy, dir2Enemy, throwStr ); if ( g_spskill->integer > 1 ) {//knock them down, too if ( NPC->enemy->health > 0 && Q_flrand( 50, 150 ) > enemyDist ) {//knock them down G_Knockdown( NPC->enemy, NPC, dir2Enemy, 300, qtrue ); if ( NPC->enemy->s.number < MAX_CLIENTS ) {//make the player look up at me vec3_t vAng; vectoangles( dir2Enemy, vAng ); VectorSet( vAng, AngleNormalize180(vAng[PITCH])*-1, NPC->enemy->client->ps.viewangles[YAW], 0 ); SetClientViewAngle( NPC->enemy, vAng ); } } } } } } } else { 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 //this guy isn't going anywhere anymore NPC->enemy->contents = 0; NPC->enemy->clipmask = 0; if ( NPC->activator->client ) { NPC->activator->client->ps.SaberDeactivate(); NPC->activator->client->ps.eFlags |= EF_HELD_BY_SAND_CREATURE; if ( NPC->activator->health > 0 && NPC->activator->client ) { G_AddEvent( NPC->activator, Q_irand(EV_DEATH1, EV_DEATH3), 0 ); NPC_SetAnim( NPC->activator, SETANIM_LEGS, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); NPC_SetAnim( NPC->activator, SETANIM_TORSO, BOTH_FALLDEATH1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); TossClientItems( NPC ); if ( NPC->activator->NPC ) {//no more thinking for you NPC->activator->NPC->nextBStateThink = Q3_INFINITE; } } /* if ( !NPC->activator->s.number ) { cg.overrides.active |= (CG_OVERRIDE_3RD_PERSON_CDP|CG_OVERRIDE_3RD_PERSON_RNG); cg.overrides.thirdPersonCameraDamp = 0; cg.overrides.thirdPersonRange = 120; } */ } else { NPC->activator->s.eFlags |= EF_HELD_BY_SAND_CREATURE; } } }
/* ------------------------- NPC_Mark1_Pain - look at what was hit and see if it should be removed from the model. ------------------------- */ void NPC_Mark1_Pain(gentity_t *self, gentity_t *attacker, int damage) { int newBolt,i,chance; int hitLoc = gPainHitLoc; NPC_Pain( self, attacker, damage ); G_Sound( self, CHAN_AUTO, G_SoundIndex("sound/chars/mark1/misc/mark1_pain")); // Hit in the CHEST??? if (hitLoc==HL_CHEST) { chance = Q_irand( 1, 4); if ((chance == 1) && (damage > 5)) { NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } } // Hit in the left arm? else if ((hitLoc==HL_ARM_LT) && (self->locationDamage[HL_ARM_LT] > LEFT_ARM_HEALTH)) { if (self->locationDamage[hitLoc] >= LEFT_ARM_HEALTH) // Blow it up? { newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*flash3" ); if ( newBolt != -1 ) { NPC_Mark1_Part_Explode(self,newBolt); } NPC_SetSurfaceOnOff( self, "l_arm", TURN_OFF ); } } // Hit in the right arm? else if ((hitLoc==HL_ARM_RT) && (self->locationDamage[HL_ARM_RT] > RIGHT_ARM_HEALTH)) // Blow it up? { if (self->locationDamage[hitLoc] >= RIGHT_ARM_HEALTH) { newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*flash4" ); if ( newBolt != -1 ) { // G_PlayEffect( "small_chunks", self->playerModel, self->genericBolt2, self->s.number); NPC_Mark1_Part_Explode( self, newBolt ); } NPC_SetSurfaceOnOff( self, "r_arm", TURN_OFF ); } } // Check ammo pods else { for (i=0;i<6;i++) { if ((hitLoc==HL_GENERIC1+i) && (self->locationDamage[HL_GENERIC1+i] > AMMO_POD_HEALTH)) // Blow it up? { if (self->locationDamage[hitLoc] >= AMMO_POD_HEALTH) { newBolt = trap_G2API_AddBolt( self->ghoul2, 0, va("*torso_tube%d",(i+1)) ); if ( newBolt != -1 ) { NPC_Mark1_Part_Explode(self,newBolt); } NPC_SetSurfaceOnOff( self, va("torso_tube%d",(i+1)), TURN_OFF ); NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); break; } } } } // Are both guns shot off? if ((trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "l_arm" )>0) && (trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "r_arm" )>0)) { G_Damage(self,NULL,NULL,NULL,NULL,self->health,0,MOD_UNKNOWN); } }
void NPC_Probe_Pain( gentity_t *self, gentity_t *attacker, int damage ) { float pain_chance; gentity_t *other = attacker; int mod = gPainMOD; VectorCopy( &self->NPC->lastPathAngles, &self->s.angles ); if ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) // demp2 always messes them up real good { vector3 endPos; trace_t trace; VectorSet( &endPos, self->r.currentOrigin.x, self->r.currentOrigin.y, self->r.currentOrigin.z - 128 ); trap->Trace( &trace, &self->r.currentOrigin, NULL, NULL, &endPos, self->s.number, MASK_SOLID, qfalse, 0, 0 ); if ( flcmp( trace.fraction, 1.0f, 0.001f ) || mod == MOD_DEMP2 ) // demp2 always does this { /* if (self->client->clientInfo.headModel != 0) { vector3 origin; VectorCopy(self->r.currentOrigin,origin); origin.z +=50; // G_PlayEffect( "small_chunks", origin ); G_PlayEffect( "chunks/probehead", origin ); G_PlayEffect( "env/med_explode2", origin ); self->client->clientInfo.headModel = 0; self->client->moveType = MT_RUNJUMP; self->client->ps.gravity = g_gravity->value*.1f; } */ if ( (mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT) && other ) { vector3 dir; NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); VectorSubtract( &self->r.currentOrigin, &other->r.currentOrigin, &dir ); VectorNormalize( &dir ); VectorMA( &self->client->ps.velocity, 550, &dir, &self->client->ps.velocity ); self->client->ps.velocity.z -= 127; } //self->s.powerups |= ( 1 << PW_SHOCKED ); //self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; self->client->ps.electrifyTime = level.time + 3000; self->NPC->localState = LSTATE_DROP; } } else { pain_chance = NPC_GetPainChance( self, damage ); if ( random() < pain_chance ) // Spin around in pain? { NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE ); } } NPC_Pain( self, attacker, damage ); }
/* ------------------------- Sentry_Fire ------------------------- */ void Sentry_Fire (void) { vec3_t muzzle; static vec3_t forward, vright, up; gentity_t *missile; mdxaBone_t boltMatrix; int bolt, which; NPCS.NPC->flags &= ~FL_SHIELDED; if ( NPCS.NPCInfo->localState == LSTATE_POWERING_UP ) { if ( TIMER_Done( NPCS.NPC, "powerup" )) { NPCS.NPCInfo->localState = LSTATE_ATTACKING; NPC_SetAnim( NPCS.NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } else { // can't do anything right now return; } } else if ( NPCS.NPCInfo->localState == LSTATE_ACTIVE ) { NPCS.NPCInfo->localState = LSTATE_POWERING_UP; G_Sound( NPCS.NPC, CHAN_AUTO, G_SoundIndex("sound/chars/sentry/misc/sentry_shield_open") ); NPC_SetAnim( NPCS.NPC, SETANIM_BOTH, BOTH_POWERUP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPCS.NPC, "powerup", 250 ); return; } else if ( NPCS.NPCInfo->localState != LSTATE_ATTACKING ) { // bad because we are uninitialized NPCS.NPCInfo->localState = LSTATE_ACTIVE; return; } // Which muzzle to fire from? which = NPCS.NPCInfo->burstCount % 3; switch( which ) { case 0: bolt = trap_G2API_AddBolt(NPCS.NPC->ghoul2, 0, "*flash1"); break; case 1: bolt = trap_G2API_AddBolt(NPCS.NPC->ghoul2, 0, "*flash2"); break; case 2: default: bolt = trap_G2API_AddBolt(NPCS.NPC->ghoul2, 0, "*flash03"); } trap_G2API_GetBoltMatrix( NPCS.NPC->ghoul2, 0, bolt, &boltMatrix, NPCS.NPC->r.currentAngles, NPCS.NPC->r.currentOrigin, level.time, NULL, NPCS.NPC->modelScale ); BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, muzzle ); AngleVectors( NPCS.NPC->r.currentAngles, forward, vright, up ); // G_Sound( NPC, G_SoundIndex("sound/chars/sentry/misc/shoot.wav")); G_PlayEffectID( G_EffectIndex("bryar/muzzle_flash"), muzzle, forward ); missile = CreateMissile( muzzle, forward, 1600, 10000, NPCS.NPC, qfalse ); missile->classname = "bryar_proj"; missile->s.weapon = WP_BRYAR_PISTOL; missile->dflags = DAMAGE_DEATH_KNOCKBACK; missile->methodOfDeath = MOD_BRYAR_PISTOL; missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; NPCS.NPCInfo->burstCount++; NPCS.NPC->attackDebounceTime = level.time + 50; missile->damage = 5; // now scale for difficulty if ( g_npcspskill.integer == 0 ) { NPCS.NPC->attackDebounceTime += 200; missile->damage = 1; } else if ( g_npcspskill.integer == 1 ) { NPCS.NPC->attackDebounceTime += 100; missile->damage = 3; } }
/* ------------------------- NPC_BSDroid_Pain ------------------------- */ void NPC_Droid_Pain(gentity_t *self, gentity_t *attacker, int damage) { gentity_t *other = attacker; int anim; int mod = gPainMOD; float pain_chance; VectorCopy( self->NPC->lastPathAngles, self->s.angles ); if ( self->client->NPC_class == CLASS_R5D2 ) { pain_chance = NPC_GetPainChance( self, damage ); // Put it in pain if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT || Q_flrand(0.0f, 1.0f) < pain_chance ) // Spin around in pain? Demp2 always does this { // Health is between 0-30 or was hit by a DEMP2 so pop his head if ( !self->s.m_iVehicleNum && ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) ) { if (!(self->spawnflags & 2)) // Doesn't have to ALWAYSDIE { if ((self->NPC->localState != LSTATE_SPINNING) && (!trap->G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "head" ))) { NPC_SetSurfaceOnOff( self, "head", TURN_OFF ); if ( self->client->ps.m_iVehicleNum ) { vec3_t up; AngleVectors( self->r.currentAngles, NULL, NULL, up ); G_PlayEffectID( G_EffectIndex("chunks/r5d2head_veh"), self->r.currentOrigin, up ); } else { G_PlayEffectID( G_EffectIndex("small_chunks") , self->r.currentOrigin, vec3_origin ); G_PlayEffectID( G_EffectIndex("chunks/r5d2head"), self->r.currentOrigin, vec3_origin ); } //self->s.powerups |= ( 1 << PW_SHOCKED ); //self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; self->client->ps.electrifyTime = level.time + 3000; TIMER_Set( self, "droidsmoketotal", 5000); TIMER_Set( self, "droidspark", 100); self->NPC->localState = LSTATE_SPINNING; } } } // Just give him normal pain for a little while else { anim = self->client->ps.legsAnim; if ( anim == BOTH_STAND2 ) // On two legs? { anim = BOTH_PAIN1; } else // On three legs { anim = BOTH_PAIN2; } NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); // Spin around in pain self->NPC->localState = LSTATE_SPINNING; TIMER_Set( self, "roam", Q_irand(1000,2000)); } } } else if (self->client->NPC_class == CLASS_MOUSE) { if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) { self->NPC->localState = LSTATE_SPINNING; //self->s.powerups |= ( 1 << PW_SHOCKED ); //self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; self->client->ps.electrifyTime = level.time + 3000; } else { self->NPC->localState = LSTATE_BACKINGUP; } self->NPC->scriptFlags &= ~SCF_LOOK_FOR_ENEMIES; } else if (self->client->NPC_class == CLASS_R2D2) { pain_chance = NPC_GetPainChance( self, damage ); if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT || Q_flrand(0.0f, 1.0f) < pain_chance ) // Spin around in pain? Demp2 always does this { // Health is between 0-30 or was hit by a DEMP2 so pop his head if ( !self->s.m_iVehicleNum && ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) ) { if (!(self->spawnflags & 2)) // Doesn't have to ALWAYSDIE { if ((self->NPC->localState != LSTATE_SPINNING) && (!trap->G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "head" ))) { NPC_SetSurfaceOnOff( self, "head", TURN_OFF ); if ( self->client->ps.m_iVehicleNum ) { vec3_t up; AngleVectors( self->r.currentAngles, NULL, NULL, up ); G_PlayEffectID( G_EffectIndex("chunks/r2d2head_veh"), self->r.currentOrigin, up ); } else { G_PlayEffectID( G_EffectIndex("small_chunks") , self->r.currentOrigin, vec3_origin ); G_PlayEffectID( G_EffectIndex("chunks/r2d2head"), self->r.currentOrigin, vec3_origin ); } //self->s.powerups |= ( 1 << PW_SHOCKED ); //self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; self->client->ps.electrifyTime = level.time + 3000; TIMER_Set( self, "droidsmoketotal", 5000); TIMER_Set( self, "droidspark", 100); self->NPC->localState = LSTATE_SPINNING; } } } // Just give him normal pain for a little while else { anim = self->client->ps.legsAnim; if ( anim == BOTH_STAND2 ) // On two legs? { anim = BOTH_PAIN1; } else // On three legs { anim = BOTH_PAIN2; } NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); // Spin around in pain self->NPC->localState = LSTATE_SPINNING; TIMER_Set( self, "roam", Q_irand(1000,2000)); } } } else if ( self->client->NPC_class == CLASS_INTERROGATOR && ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) && other ) { vec3_t dir; VectorSubtract( self->r.currentOrigin, other->r.currentOrigin, dir ); VectorNormalize( dir ); VectorMA( self->client->ps.velocity, 550, dir, self->client->ps.velocity ); self->client->ps.velocity[2] -= 127; } NPC_Pain( self, attacker, damage); }