qboolean PM_AdjustAnglesForKnockdown( gentity_t *ent, usercmd_t *ucmd, qboolean angleClampOnly ) { if ( PM_InKnockDown( &ent->client->ps ) ) {//being knocked down or getting up, can't do anything! if ( !angleClampOnly ) { ucmd->forwardmove = 0; ucmd->rightmove = 0; if ( ent->NPC ) { VectorClear( ent->client->ps.moveDir ); } //you can jump up out of a knockdown and you get get up into a crouch from a knockdown //ucmd->upmove = 0; //if ( !PM_InForceGetUp( &ent->client->ps ) || ent->client->ps.torsoAnimTimer > 800 || ent->s.weapon != WP_SABER ) if ( ent->health > 0 ) {//can only attack if you've started a force-getup and are using the saber ucmd->buttons = 0; } } if ( !PM_InForceGetUp( &ent->client->ps ) ) {//can't turn unless in a force getup 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[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH]; ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; return qtrue; } } return qfalse; }
//extern void G_Knockdown( gentity_t *victim ); //[/KnockdownSys] static void Howler_TryDamage( int damage, qboolean tongue, qboolean knockdown ) { vec3_t start, end, dir; trace_t tr; float dist; if ( tongue ) { G_GetBoltPosition( NPC, NPC->NPC->genericBolt1, start, 0 ); G_GetBoltPosition( NPC, NPC->NPC->genericBolt2, end, 0 ); VectorSubtract( end, start, dir ); dist = VectorNormalize( dir ); VectorMA( start, dist+16, dir, end ); } else { VectorCopy( NPC->r.currentOrigin, start ); AngleVectors( NPC->r.currentAngles, dir, NULL, NULL ); VectorMA( start, MIN_DISTANCE*2, dir, end ); } /* RACC - don't need this for now. #ifndef FINAL_BUILD if ( d_saberCombat.integer > 1 ) { G_DebugLine(start, end, 1000, 0x000000ff, qtrue); } #endif */ // Should probably trace from the mouth, but, ah well. trap_Trace( &tr, start, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT ); if ( tr.entityNum < ENTITYNUM_WORLD ) {//hit *something* gentity_t *victim = &g_entities[tr.entityNum]; if ( !victim->client || victim->client->NPC_class != CLASS_HOWLER ) {//not another howler if ( knockdown && victim->client ) {//only do damage if victim isn't knocked down. If he isn't, knock him down if ( PM_InKnockDown( &victim->client->ps ) ) { return; } } //FIXME: some sort of damage effect (claws and tongue are cutting you... blood?) G_Damage( victim, NPC, NPC, dir, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_MELEE ); if ( knockdown && victim->health > 0 ) {//victim still alive //[KnockdownSys] G_Knockdown( victim, NPC, NPC->client->ps.velocity, 500, qfalse ); //G_Knockdown(victim); //[/KnockdownSys] } } } }
qboolean NPC_CanTryJump() { if (!(NPCInfo->scriptFlags&SCF_NAV_CAN_JUMP) || // Can't Jump (NPCInfo->scriptFlags&SCF_NO_ACROBATICS) || // If Can't Jump At All (level.time<NPCInfo->jumpBackupTime) || // If Backing Up, Don't Try The Jump Again (level.time<NPCInfo->jumpNextCheckTime) || // Don't Even Try To Jump Again For This Amount Of Time (NPCInfo->jumpTime) || // Don't Jump If Already Going (PM_InKnockDown(&NPC->client->ps)) || // Don't Jump If In Knockdown (BG_InRoll(&NPC->client->ps, NPC->client->ps.legsAnim)) || // ... Or Roll (NPC->client->ps.groundEntityNum==ENTITYNUM_NONE) // ... Or In The Air ) { return qfalse; } return qtrue; }
void NPC_Surrender( void ) {//FIXME: say "don't shoot!" if we weren't already surrendering if ( NPC->client->ps.weaponTime || PM_InKnockDown( &NPC->client->ps ) ) { return; } if ( NPC->s.weapon != WP_NONE && NPC->s.weapon != WP_MELEE && NPC->s.weapon != WP_SABER ) { WP_DropWeapon( NPC, NULL ); } if ( NPCInfo->surrenderTime < level.time - 5000 ) {//haven't surrendered for at least 6 seconds, tell them what you're doing //FIXME: need real dialogue EV_SURRENDER NPCInfo->blockedSpeechDebounceTime = 0;//make sure we say this G_AddVoiceEvent( NPC, Q_irand( EV_PUSHED1, EV_PUSHED3 ), 3000 ); } NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_SURRENDER_START, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); NPC->client->ps.torsoAnimTimer = 1000; NPCInfo->surrenderTime = level.time + 1000;//stay surrendered for at least 1 second //FIXME: while surrendering, make a big sight/sound alert? Or G_AlertTeam? }
qboolean NPC_CheckSurrender( void ) { if ( !g_AIsurrender->integer ) {//not enabled return qfalse; } if ( !Q3_TaskIDPending( NPC, TID_MOVE_NAV ) && NPC->client->ps.groundEntityNum != ENTITYNUM_NONE && !NPC->client->ps.weaponTime && !PM_InKnockDown( &NPC->client->ps ) && NPC->enemy && NPC->enemy->client && NPC->enemy->enemy == NPC && NPC->enemy->s.weapon != WP_NONE && NPC->enemy->s.weapon != WP_MELEE && NPC->enemy->health > 20 && NPC->enemy->painDebounceTime < level.time - 3000 && NPC->enemy->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] < level.time - 1000 ) {//don't surrender if scripted to run somewhere or if we're in the air or if we're busy or if we don't have an enemy or if the enemy is not mad at me or is hurt or not a threat or busy being attacked //FIXME: even if not in a group, don't surrender if there are other enemies in the PVS and within a certain range? if ( NPC->s.weapon != WP_ROCKET_LAUNCHER && NPC->s.weapon != WP_REPEATER && NPC->s.weapon != WP_FLECHETTE && NPC->s.weapon != WP_SABER ) {//jedi and heavy weapons guys never surrender //FIXME: rework all this logic into some orderly fashion!!! if ( NPC->s.weapon != WP_NONE ) {//they have a weapon so they'd have to drop it to surrender //don't give up unless low on health if ( NPC->health > 25 || NPC->health >= NPC->max_health ) { return qfalse; } if ( g_crosshairEntNum == NPC->s.number && NPC->painDebounceTime > level.time ) {//if he just shot me, always give up //fall through } else {//don't give up unless facing enemy and he's very close if ( !InFOV( player, NPC, 60, 30 ) ) {//I'm not looking at them return qfalse; } else if ( DistanceSquared( NPC->currentOrigin, player->currentOrigin ) < 65536/*256*256*/ ) {//they're not close return qfalse; } else if ( !gi.inPVS( NPC->currentOrigin, player->currentOrigin ) ) {//they're not in the same room return qfalse; } } } if ( NPCInfo->group && NPCInfo->group->numGroup <= 1 ) {//I'm alone but I was in a group//FIXME: surrender anyway if just melee or no weap? if ( NPC->s.weapon == WP_NONE //NPC has a weapon || NPC->enemy == player || (NPC->enemy->s.weapon == WP_SABER&&NPC->enemy->client&&NPC->enemy->client->ps.saberActive) || (NPC->enemy->NPC && NPC->enemy->NPC->group && NPC->enemy->NPC->group->numGroup > 2) ) {//surrender only if have no weapon or fighting a player or jedi or if we are outnumbered at least 3 to 1 if ( NPC->enemy == player ) {//player is the guy I'm running from if ( g_crosshairEntNum == NPC->s.number ) {//give up if player is aiming at me NPC_Surrender(); NPC_UpdateAngles( qtrue, qtrue ); return qtrue; } else if ( player->s.weapon == WP_SABER ) {//player is using saber if ( InFOV( NPC, player, 60, 30 ) ) {//they're looking at me if ( DistanceSquared( NPC->currentOrigin, player->currentOrigin ) < 16384/*128*128*/ ) {//they're close if ( gi.inPVS( NPC->currentOrigin, player->currentOrigin ) ) {//they're in the same room NPC_Surrender(); NPC_UpdateAngles( qtrue, qtrue ); return qtrue; } } } } } else if ( NPC->enemy ) {//??? //should NPC's surrender to others? if ( InFOV( NPC, NPC->enemy, 30, 30 ) ) {//they're looking at me if ( DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ) < 4096 ) {//they're close if ( gi.inPVS( NPC->currentOrigin, NPC->enemy->currentOrigin ) ) {//they're in the same room //FIXME: should player-team NPCs not fire on surrendered NPCs? NPC_Surrender(); NPC_UpdateAngles( qtrue, qtrue ); return qtrue; } } } } } } } } return qfalse; }
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 ( Q_flrand(0.0f, 1.0f) < 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 return; } 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 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; } }
qboolean NPC_MoveToGoal( qboolean tryStraight ) { float distance; vec3_t dir; #if AI_TIMERS int startTime = GetTime(0); #endif// AI_TIMERS //If taking full body pain, don't move if ( PM_InKnockDown( &NPC->client->ps ) || ( ( NPC->s.legsAnim >= BOTH_PAIN1 ) && ( NPC->s.legsAnim <= BOTH_PAIN18 ) ) ) { return qtrue; } /* if( NPC->s.eFlags & EF_LOCKED_TO_WEAPON ) {//If in an emplaced gun, never try to navigate! return qtrue; } */ //rwwFIXMEFIXME: emplaced support //FIXME: if can't get to goal & goal is a target (enemy), try to find a waypoint that has line of sight to target, at least? //Get our movement direction #if 1 if ( NPC_GetMoveDirectionAltRoute( dir, &distance, tryStraight ) == qfalse ) #else if ( NPC_GetMoveDirection( dir, &distance ) == qfalse ) #endif return qfalse; NPCInfo->distToGoal = distance; //Convert the move to angles vectoangles( dir, NPCInfo->lastPathAngles ); if ( (ucmd.buttons&BUTTON_WALKING) ) { NPC->client->ps.speed = NPCInfo->stats.walkSpeed; } else { NPC->client->ps.speed = NPCInfo->stats.runSpeed; } //FIXME: still getting ping-ponging in certain cases... !!! Nav/avoidance error? WTF???!!! //If in combat move, then move directly towards our goal if ( NPC_CheckCombatMove() ) {//keep current facing G_UcmdMoveForDir( NPC, &ucmd, dir ); } else {//face our goal //FIXME: strafe instead of turn if change in dir is small and temporary NPCInfo->desiredPitch = 0.0f; NPCInfo->desiredYaw = AngleNormalize360( NPCInfo->lastPathAngles[YAW] ); //Pitch towards the goal and also update if flying or swimming if ( (NPC->client->ps.eFlags2&EF2_FLYING) )//moveType == MT_FLYSWIM ) { NPCInfo->desiredPitch = AngleNormalize360( NPCInfo->lastPathAngles[PITCH] ); if ( dir[2] ) { float scale = (dir[2] * distance); if ( scale > 64 ) { scale = 64; } else if ( scale < -64 ) { scale = -64; } NPC->client->ps.velocity[2] = scale; //NPC->client->ps.velocity[2] = (dir[2] > 0) ? 64 : -64; } } //Set any final info ucmd.forwardmove = 127; } #if AI_TIMERS navTime += GetTime( startTime ); #endif// AI_TIMERS return qtrue; }
qboolean NPC_MoveToGoal( qboolean tryStraight ) { float distance; vec3_t dir; #if AI_TIMERS int startTime = GetTime(0); #endif// AI_TIMERS //If taking full body pain, don't move if ( PM_InKnockDown( &NPC->client->ps ) || ( ( NPC->s.legsAnim >= BOTH_PAIN1 ) && ( NPC->s.legsAnim <= BOTH_PAIN18 ) ) ) { return qfalse; } #ifdef __DOMINANCE_NPC__ if (NPC->enemy && NPC->s.weapon == WP_SABER && (!NPC_EnemyVisible( NPC, NPC->enemy ) || (Distance(NPC->r.currentOrigin, NPC->enemy->r.currentOrigin) > 96 || NPC->genericValue15 < level.time))) { // Enemy is visible, but out of range for lghtsaber... Move closer... NPC->client->ps.speed = NPCInfo->stats.runSpeed; if (!NPC_FollowRoutes()) { //G_Printf("NPC_FollowRoutes failed!\n"); return qfalse; } return qtrue; } else if (NPC->enemy && NPC_EnemyVisible( NPC, NPC->enemy )) { // Enemy is visible and in range, no need to move at the moment... return qfalse; } else if (NPC->enemy) {// Have an enemy that is not currently visible... if (NPC->s.weapon == WP_SABER && (Distance(NPC->r.currentOrigin, NPC->enemy->r.currentOrigin) > 96 || NPC->genericValue15 < level.time)) { if (NPC->genericValue14 < level.time) { // Give up... NPC->enemy = NULL; NPCInfo->goalEntity = NULL; NPC->longTermGoal = -1; } } else if (NPC->enemy && NPC->genericValue15 < level.time) { if (NPC->genericValue14 < level.time) { // Give up... NPC->enemy = NULL; NPCInfo->goalEntity = NULL; NPC->longTermGoal = -1; } } NPC->client->ps.speed = NPCInfo->stats.runSpeed; if (!NPC_FollowRoutes()) { //G_Printf("NPC_FollowRoutes failed!\n"); return qfalse; } return qtrue; } else {// Dominance: Use bot waypointing AI if it is available! - Unique1 NPC->enemy = NULL; NPCInfo->goalEntity = NULL; NPC->client->ps.speed = NPCInfo->stats.walkSpeed; if (!NPC_FollowRoutes()) { //G_Printf("NPC_FollowRoutes failed!\n"); return qfalse; } return qtrue; } #endif //__DOMINANCE_NPC__ /* if( NPC->s.eFlags & EF_LOCKED_TO_WEAPON ) {//If in an emplaced gun, never try to navigate! return qtrue; } */ //rwwFIXMEFIXME: emplaced support //FIXME: if can't get to goal & goal is a target (enemy), try to find a waypoint that has line of sight to target, at least? //Get our movement direction #if 1 if ( NPC_GetMoveDirectionAltRoute( dir, &distance, tryStraight ) == qfalse ) #else if ( NPC_GetMoveDirection( dir, &distance ) == qfalse ) #endif return qfalse; NPCInfo->distToGoal = distance; //Convert the move to angles vectoangles( dir, NPCInfo->lastPathAngles ); if ( (ucmd.buttons&BUTTON_WALKING) ) { NPC->client->ps.speed = NPCInfo->stats.walkSpeed; } else { NPC->client->ps.speed = NPCInfo->stats.runSpeed; } //FIXME: still getting ping-ponging in certain cases... !!! Nav/avoidance error? WTF???!!! //If in combat move, then move directly towards our goal if ( NPC_CheckCombatMove() ) {//keep current facing G_UcmdMoveForDir( NPC, &ucmd, dir ); } else {//face our goal //FIXME: strafe instead of turn if change in dir is small and temporary NPCInfo->desiredPitch = 0.0f; NPCInfo->desiredYaw = AngleNormalize360( NPCInfo->lastPathAngles[YAW] ); //Pitch towards the goal and also update if flying or swimming if ( (NPC->client->ps.eFlags2&EF2_FLYING) )//moveType == MT_FLYSWIM ) { NPCInfo->desiredPitch = AngleNormalize360( NPCInfo->lastPathAngles[PITCH] ); if ( dir[2] ) { float scale = (dir[2] * distance); if ( scale > 64 ) { scale = 64; } else if ( scale < -64 ) { scale = -64; } NPC->client->ps.velocity[2] = scale; //NPC->client->ps.velocity[2] = (dir[2] > 0) ? 64 : -64; } } //Set any final info ucmd.forwardmove = 127; } #if AI_TIMERS navTime += GetTime( startTime ); #endif// AI_TIMERS return qtrue; }
qboolean NPC_MoveToGoal( qboolean tryStraight ) { qboolean ENEMY_VISIBLE = qfalse; if ( PM_InKnockDown( &NPC->client->ps ) || ( ( NPC->s.legsAnim >= BOTH_PAIN1 ) && ( NPC->s.legsAnim <= BOTH_PAIN18 ) ) ) {// If taking full body pain, don't move... return qfalse; } if (!NPC->enemy) { NPCInfo->goalEntity = NPC->enemy = NPC_PickEnemyExt( qtrue ); } if (NPC->enemy) ENEMY_VISIBLE = NPC_EnemyVisible( NPC, NPC->enemy ); if (NPC->enemy && ENEMY_VISIBLE && NPC->s.weapon == WP_SABER && Distance(NPC->r.currentOrigin, NPC->enemy->r.currentOrigin) > 96) {// Enemy is visible, but out of range for lghtsaber... Move closer NPC->client->ps.speed = NPCInfo->stats.walkSpeed; } else if (NPC->enemy && !ENEMY_VISIBLE) {// Have an enemy that is not currently visible... if (NPC->genericValue14 < level.time) {// Give up... NPC->enemy = NULL; NPCInfo->goalEntity = NULL; NPC->longTermGoal = -1; } NPC->client->ps.speed = NPCInfo->stats.walkSpeed; } else if (NPC->enemy && ENEMY_VISIBLE) {// Enemy is visible and in range, no need to move at the moment... NPC->client->ps.speed = NPCInfo->stats.walkSpeed; return qfalse; } else {// No enemy at all, continue to follow routes... if (NPC->genericValue14 < level.time) {// Give up... NPC->enemy = NULL; NPCInfo->goalEntity = NULL; NPC->longTermGoal = -1; } NPC->client->ps.speed = NPCInfo->stats.walkSpeed; } // Do the actual routing and movement... if (!NPC_FollowRoutes()) { //G_Printf("NPC_FollowRoutes failed!\n"); return qfalse; } #if AI_TIMERS navTime += GetTime( startTime ); #endif// AI_TIMERS return qtrue; }
//---------------------------------- //replaced with SP version. static void Howler_Combat( void ) { qboolean faced = qfalse; float distance; qboolean advance = qfalse; if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE ) {//not on the ground if ( NPC->client->ps.legsAnim == BOTH_JUMP1 || NPC->client->ps.legsAnim == BOTH_INAIR1 ) {//flying through the air with the greatest of ease, etc Howler_TryDamage( 10, qfalse, qfalse ); } } else {//not in air, see if we should attack or advance // If we cannot see our target or we have somewhere to go, then do that if ( !NPC_ClearLOS4( NPC->enemy ) )//|| UpdateGoal( )) { NPCInfo->goalEntity = NPC->enemy; NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range if ( NPCInfo->localState == LSTATE_BERZERK ) { NPC_Howler_Move( 3 ); } else { NPC_Howler_Move( 10 ); } NPC_UpdateAngles( qfalse, qtrue ); return; } distance = DistanceHorizontal( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); if ( NPC->enemy && NPC->enemy->client && PM_InKnockDown( &NPC->enemy->client->ps ) ) {//get really close to knocked down enemies advance = (qboolean)( distance > MIN_DISTANCE ? qtrue : qfalse ); } else { advance = (qboolean)( distance > MAX_DISTANCE ? qtrue : qfalse );//MIN_DISTANCE } if (( advance || NPCInfo->localState == LSTATE_WAITING ) && TIMER_Done( NPC, "attacking" )) // waiting monsters can't attack { if ( TIMER_Done2( NPC, "takingPain", qtrue )) { NPCInfo->localState = LSTATE_CLEAR; } else if ( TIMER_Done( NPC, "standing" ) ) { faced = Howler_Move( qtrue ); } } else { Howler_Attack( distance, qfalse ); } } if ( !faced ) { if ( //TIMER_Done( NPC, "standing" ) //not just standing there //!advance //not moving TIMER_Done( NPC, "attacking" ) )// not attacking {//not standing around // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb NPC_FaceEnemy( qtrue ); } else { NPC_UpdateAngles( qfalse, qtrue ); } } }
static void Howler_Attack( float enemyDist, qboolean howl ) { int dmg = (NPCInfo->localState==LSTATE_BERZERK)?5:2; vec3_t boltOrg; vec3_t fwd; 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; VectorSet( 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.legsTimer ); } else { TIMER_Set( NPC, "attacking", NPC->client->ps.legsTimer + 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.legsTimer + 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.legsTimer > 650//more than 13 frames left && BG_AnimLength( NPC->localAnimIndex, (animNumber_t)NPC->client->ps.legsAnim ) - NPC->client->ps.legsTimer >= 800 )//at least 16 frames into anim { Howler_TryDamage( dmg, qfalse, qfalse ); } break; case BOTH_ATTACK2: case BOTH_MELEE2: if ( NPC->client->ps.legsTimer > 350//more than 7 frames left && BG_AnimLength( NPC->localAnimIndex, (animNumber_t)NPC->client->ps.legsAnim ) - NPC->client->ps.legsTimer >= 550 )//at least 11 frames into anim { Howler_TryDamage( dmg, qtrue, qfalse ); } break; case BOTH_GESTURE1: { if ( NPC->client->ps.legsTimer > 1800//more than 36 frames left && BG_AnimLength( NPC->localAnimIndex, (animNumber_t)NPC->client->ps.legsAnim ) - NPC->client->ps.legsTimer >= 950 )//at least 19 frames into anim { Howler_Howl(); if ( !NPC->count ) { //RAFIXME - this probably won't work correctly. G_GetBoltPosition(NPC, NPC->NPC->genericBolt1, boltOrg, 0); AngleVectors( NPC->client->ps.viewangles, fwd, NULL, NULL ); G_PlayEffectID( G_EffectIndex( "howler/sonic" ), boltOrg, fwd); //G_PlayEffect( G_EffectIndex( "howler/sonic" ), NPC->ghoul2, NPC->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 ); }
static void Howler_Howl( void ) { //gentity_t *radiusEnts[ 128 ]; int radiusEntsNums[128]; gentity_t *ent; 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->r.currentOrigin, 512, AEL_DANGER, qfalse, qtrue ); //RACC - handLBolt never defined for Howlers? *shrug* just use the tongue base numEnts = NPC_GetEntsNearBolt( radiusEntsNums, radius, NPC->NPC->genericBolt1, boltOrg ); //numEnts = NPC_GetEntsNearBolt( radiusEnts, radius, NPC->handLBolt, boltOrg ); for ( i = 0; i < numEnts; i++ ) { ent = &g_entities[radiusEntsNums[i]]; if ( !ent->inuse ) { continue; } if ( ent == NPC ) {//Skip the rancor ent //RACC - assume this means the source howler continue; } if ( ent->client == NULL ) {//must be a client continue; } if ( ent->client->NPC_class == CLASS_HOWLER ) {//other howlers immune continue; } distSq = DistanceSquared( ent->r.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 //RACC - Impact isn't a MOD in MP. G_Damage( ent, NPC, NPC, vec3_origin, NPC->r.currentOrigin, 1, DAMAGE_NO_KNOCKBACK, MOD_CRUSH ); //G_Damage( ent, NPC, NPC, vec3_origin, NPC->r.currentOrigin, 1, DAMAGE_NO_KNOCKBACK, MOD_IMPACT ); } } if ( ent->health > 0 && ent->client && ent->client->NPC_class != CLASS_RANCOR && ent->client->NPC_class != CLASS_ATST && !PM_InKnockDown( &ent->client->ps ) ) { if ( BG_HasAnimation( ent->localAnimIndex, BOTH_SONICPAIN_START ) ) { if ( ent->client->ps.torsoAnim != BOTH_SONICPAIN_START && ent->client->ps.torsoAnim != BOTH_SONICPAIN_HOLD ) { NPC_SetAnim( ent, SETANIM_LEGS, BOTH_SONICPAIN_START, SETANIM_FLAG_NORMAL ); NPC_SetAnim( ent, SETANIM_TORSO, BOTH_SONICPAIN_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); ent->client->ps.torsoTimer += 100; ent->client->ps.weaponTime = ent->client->ps.torsoTimer; } else if ( ent->client->ps.torsoTimer <= 100 ) {//at the end of the sonic pain start or hold anim NPC_SetAnim( ent, SETANIM_LEGS, BOTH_SONICPAIN_HOLD, SETANIM_FLAG_NORMAL ); NPC_SetAnim( ent, SETANIM_TORSO, BOTH_SONICPAIN_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); ent->client->ps.torsoTimer += 100; ent->client->ps.weaponTime = ent->client->ps.torsoTimer; } } /* racc - commented out of SP code? 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 ); } */ } } //camera shaking distSq = Distance( boltOrg, ent->r.currentOrigin ); if ( distSq < 256.0f ) { G_ScreenShake(boltOrg, ent, 1.0f*distSq/128.0f, 200, qfalse); } } //RACC - changed to work for multiple players /* playerDist = NPC_EntRangeFromBolt( player, NPC->genericBolt1 ); if ( playerDist < 256.0f ) { CGCam_Shake( 1.0f*playerDist/128.0f, 200 ); } */ }
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 ); } }
void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) { int respawn = 0; if (!other->client) return; if (other->health < 1) return; // dead people can't pickup if ( other->client->ps.pm_time > 0 ) {//cant pick up when out of control return; } // NPCs can pick it up if ((ent->spawnflags & ITMSF_ALLOWNPC) && (!other->s.number)) { return; } // Players cannot pick it up if ( (ent->spawnflags & ITMSF_NOPLAYER) && (other->s.number) ) { return; } if ( ent->noDamageTeam != TEAM_FREE && other->client->playerTeam != ent->noDamageTeam ) {//only one team can pick it up return; } if ( !G_CanPickUpWeapons( other ) ) {//FIXME: some flag would be better //droids can't pick up items/weapons! return; } //FIXME: need to make them run toward a dropped weapon when fleeing without one? //FIXME: need to make them come out of flee mode when pick up their old weapon? if ( CheckItemCanBePickedUpByNPC( ent, other ) ) { if ( other->NPC && other->NPC->goalEntity && other->NPC->goalEntity == ent ) {//they were running to pick me up, they did, so clear goal other->NPC->goalEntity = NULL; other->NPC->squadState = SQUAD_STAND_AND_SHOOT; NPCInfo->tempBehavior = BS_DEFAULT; TIMER_Set(other, "flee", -1); } else { return; } } else if ( !(ent->spawnflags & ITMSF_ALLOWNPC) ) {// NPCs cannot pick it up if ( other->s.number != 0 ) {// Not the player? return; } } // the same pickup rules are used for client side and server side if ( !BG_CanItemBeGrabbed( &ent->s, &other->client->ps ) ) { return; } if ( other->client ) { if ( (other->client->ps.eFlags&EF_FORCE_GRIPPED) || (other->client->ps.eFlags&EF_FORCE_DRAINED) ) {//can't pick up anything while being gripped return; } if ( PM_InKnockDown( &other->client->ps ) && !PM_InGetUp( &other->client->ps ) ) {//can't pick up while in a knockdown return; } } if (!ent->item) { //not an item! gi.Printf( "Touch_Item: %s is not an item!\n", ent->classname); return; } if ( ent->item->giType == IT_WEAPON && ent->item->giTag == WP_SABER ) {//a saber if ( ent->delay > level.time ) {//just picked it up, don't pick up again right away return; } } if ( other->s.number < MAX_CLIENTS && (ent->spawnflags&ITMSF_USEPICKUP) ) {//only if player is holing use button if ( !(other->client->usercmd.buttons&BUTTON_USE) ) {//not holding use? return; } } qboolean bHadWeapon = qfalse; // call the item-specific pickup function switch( ent->item->giType ) { case IT_WEAPON: if ( other->NPC && other->s.weapon == WP_NONE ) {//Make them duck and sit here for a few seconds int pickUpTime = Q_irand( 1000, 3000 ); TIMER_Set( other, "duck", pickUpTime ); TIMER_Set( other, "roamTime", pickUpTime ); TIMER_Set( other, "stick", pickUpTime ); TIMER_Set( other, "verifyCP", pickUpTime ); TIMER_Set( other, "attackDelay", 600 ); respawn = 0; } if ( other->client->ps.stats[STAT_WEAPONS] & ( 1 << ent->item->giTag ) ) { bHadWeapon = qtrue; } respawn = Pickup_Weapon(ent, other); break; case IT_AMMO: respawn = Pickup_Ammo(ent, other); break; case IT_ARMOR: respawn = Pickup_Armor(ent, other); break; case IT_HEALTH: respawn = Pickup_Health(ent, other); break; case IT_HOLDABLE: respawn = Pickup_Holdable(ent, other); break; case IT_BATTERY: respawn = Pickup_Battery( ent, other ); break; case IT_HOLOCRON: respawn = Pickup_Holocron( ent, other ); break; default: return; } if ( !respawn ) { return; } // play the normal pickup sound if ( !other->s.number && g_timescale->value < 1.0f ) {//SIGH... with timescale on, you lose events left and right extern void CG_ItemPickup( int itemNum, qboolean bHadItem ); // but we're SP so we'll cheat cgi_S_StartSound( NULL, other->s.number, CHAN_AUTO, cgi_S_RegisterSound( ent->item->pickup_sound ) ); // show icon and name on status bar CG_ItemPickup( ent->s.modelindex, bHadWeapon ); } else { if ( bHadWeapon ) { G_AddEvent( other, EV_ITEM_PICKUP, -ent->s.modelindex ); } else { G_AddEvent( other, EV_ITEM_PICKUP, ent->s.modelindex ); } } // fire item targets G_UseTargets (ent, other); if ( ent->item->giType == IT_WEAPON && ent->item->giTag == WP_SABER ) {//a saber that was picked up if ( ent->count < 0 ) {//infinite supply ent->delay = level.time + 500; return; } ent->count--; if ( ent->count > 0 ) {//still have more to pick up ent->delay = level.time + 500; return; } } // wait of -1 will not respawn // if ( ent->wait == -1 ) { //why not just remove me? G_FreeEntity( ent ); /* //NOTE: used to do this: (for respawning?) ent->svFlags |= SVF_NOCLIENT; ent->s.eFlags |= EF_NODRAW; ent->contents = 0; ent->unlinkAfterEvent = qtrue; */ return; } }
void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) { int respawn = 0; if (!other->client) return; if (other->health < 1) return; // dead people can't pickup if ( other->client->ps.pm_time > 0 ) {//cant pick up when out of control return; } // Only monsters can pick it up if ((ent->spawnflags & ITMSF_MONSTER) && (other->client->playerTeam == TEAM_PLAYER)) { return; } // Only starfleet can pick it up if ((ent->spawnflags & ITMSF_TEAM) && (other->client->playerTeam != TEAM_PLAYER)) { return; } if ( other->client->NPC_class == CLASS_ATST || other->client->NPC_class == CLASS_GONK || other->client->NPC_class == CLASS_MARK1 || other->client->NPC_class == CLASS_MARK2 || other->client->NPC_class == CLASS_MOUSE || other->client->NPC_class == CLASS_PROBE || other->client->NPC_class == CLASS_PROTOCOL || other->client->NPC_class == CLASS_R2D2 || other->client->NPC_class == CLASS_R5D2 || other->client->NPC_class == CLASS_SEEKER || other->client->NPC_class == CLASS_REMOTE || other->client->NPC_class == CLASS_SENTRY ) {//FIXME: some flag would be better //droids can't pick up items/weapons! return; } //FIXME: need to make them run toward a dropped weapon when fleeing without one? //FIXME: need to make them come out of flee mode when pick up their old weapon? if ( CheckItemCanBePickedUpByNPC( ent, other ) ) { if ( other->NPC && other->NPC->goalEntity && other->NPC->goalEntity->enemy == ent ) {//they were running to pick me up, they did, so clear goal other->NPC->goalEntity = NULL; other->NPC->squadState = SQUAD_STAND_AND_SHOOT; } } else if (!(ent->spawnflags & ITMSF_TEAM) && !(ent->spawnflags & ITMSF_MONSTER)) {// Only player can pick it up if ( other->s.number != 0 ) // Not the player? { return; } } // the same pickup rules are used for client side and server side if ( !BG_CanItemBeGrabbed( &ent->s, &other->client->ps ) ) { return; } if ( other->client ) { if ( other->client->ps.eFlags&EF_FORCE_GRIPPED ) {//can't pick up anything while being gripped return; } if ( PM_InKnockDown( &other->client->ps ) && !PM_InGetUp( &other->client->ps ) ) {//can't pick up while in a knockdown return; } } if (!ent->item) { //not an item! gi.Printf( "Touch_Item: %s is not an item!\n", ent->classname); return; } qboolean bHadWeapon = qfalse; // call the item-specific pickup function switch( ent->item->giType ) { case IT_WEAPON: if ( other->NPC && other->s.weapon == WP_NONE ) {//Make them duck and sit here for a few seconds int pickUpTime = Q_irand( 1000, 3000 ); TIMER_Set( other, "duck", pickUpTime ); TIMER_Set( other, "roamTime", pickUpTime ); TIMER_Set( other, "stick", pickUpTime ); TIMER_Set( other, "verifyCP", pickUpTime ); TIMER_Set( other, "attackDelay", 600 ); respawn = 0; } if ( other->client->ps.stats[STAT_WEAPONS] & ( 1 << ent->item->giTag ) ) { bHadWeapon = qtrue; } respawn = Pickup_Weapon(ent, other); break; case IT_AMMO: respawn = Pickup_Ammo(ent, other); break; case IT_ARMOR: respawn = Pickup_Armor(ent, other); break; case IT_HEALTH: respawn = Pickup_Health(ent, other); break; case IT_HOLDABLE: respawn = Pickup_Holdable(ent, other); break; case IT_BATTERY: respawn = Pickup_Battery( ent, other ); break; case IT_HOLOCRON: respawn = Pickup_Holocron( ent, other ); break; default: return; } if ( !respawn ) { return; } // play the normal pickup sound if ( !other->s.number && g_timescale->value < 1.0f ) {//SIGH... with timescale on, you lose events left and right extern void CG_ItemPickup( int itemNum, qboolean bHadItem ); // but we're SP so we'll cheat cgi_S_StartSound( NULL, other->s.number, CHAN_AUTO, cgi_S_RegisterSound( ent->item->pickup_sound ) ); // show icon and name on status bar CG_ItemPickup( ent->s.modelindex, bHadWeapon ); } else { if ( bHadWeapon ) { G_AddEvent( other, EV_ITEM_PICKUP, -ent->s.modelindex ); } else { G_AddEvent( other, EV_ITEM_PICKUP, ent->s.modelindex ); } } // fire item targets G_UseTargets (ent, other); // wait of -1 will not respawn // if ( ent->wait == -1 ) { //why not just remove me? G_FreeEntity( ent ); /* //NOTE: used to do this: (for respawning?) ent->svFlags |= SVF_NOCLIENT; ent->s.eFlags |= EF_NODRAW; ent->contents = 0; ent->unlinkAfterEvent = qtrue; */ return; } }