/* ============== AICast_QueryThink ============== */ void AICast_QueryThink( cast_state_t *cs ) { gentity_t *ent; qboolean visible; cast_state_t *ocs; vec3_t vec; ent = &g_entities[cs->entityNum]; ocs = AICast_GetCastState( cs->bs->enemy ); // never crouch while in this state (by choice anyway) cs->bs->attackcrouch_time = 0; // look at where we last (thought we) saw them VectorSubtract( cs->vislist[cs->bs->enemy].visible_pos, cs->bs->origin, vec ); VectorNormalize( vec ); vectoangles( vec, cs->bs->ideal_viewangles ); // are they visible now? visible = AICast_VisibleFromPos( cs->bs->origin, cs->entityNum, g_entities[cs->bs->enemy].r.currentOrigin, cs->bs->enemy, qfalse ); // make sure we dont process the sighting of this enemy by going into query mode again, without them being visible again after we leave here cs->vislist[cs->bs->enemy].flags &= ~AIVIS_PROCESS_SIGHTING; // look towards where we last saw them AICast_AimAtEnemy( cs ); // if visible and alert time has expired, go POSTAL if ( ( cs->queryAlertSightTime < 0 ) || ( ( cs->queryAlertSightTime < level.time ) && visible ) ) { if ( !cs->queryAlertSightTime ) { // set the "short reaction" condition BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SHORT_REACTION, qtrue, qfalse ); } AICast_StateChange( cs, AISTATE_COMBAT ); BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SHORT_REACTION, qfalse, qfalse ); AIFunc_BattleStart( cs ); return; } // if they've fired since the start of the query mode, go POSTAL if ( ocs->lastWeaponFired > cs->queryStartTime ) { // set the "short reaction" condition BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SHORT_REACTION, qtrue, qfalse ); AICast_StateChange( cs, AISTATE_COMBAT ); BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SHORT_REACTION, qfalse, qfalse ); AIFunc_BattleStart( cs ); return; } // if not visible, then kill the Lock On timer if ( ( cs->queryAlertSightTime > 0 ) && !visible ) { cs->queryAlertSightTime = 0; } // if the query has expired, go back to relaxed if ( !ent->client->ps.legsTimer ) { AICast_StateChange( cs, AISTATE_RELAXED ); } }
/* ============ AICast_Pain ============ */ void AICast_Pain( gentity_t *targ, gentity_t *attacker, int damage, vec3_t point ) { cast_state_t *cs; cs = AICast_GetCastState( targ->s.number ); // print debugging message if ( aicast_debug.integer == 2 && attacker->s.number == 0 ) { G_Printf( "hit %s %i\n", targ->aiName, targ->health ); } // if we are below alert mode, then go there immediately if ( cs->aiState < AISTATE_ALERT ) { AICast_StateChange( cs, AISTATE_ALERT ); } if ( cs->aiFlags & AIFL_NOPAIN ) { return; } // process the event (turn to face the attacking direction? go into hide/retreat state?) // need to weigh up the situation, but foremost, an inactive AI cast should always react in some way to being hurt cs->lastPain = level.time; // record the sighting (FIXME: silent weapons shouldn't do this, but the AI should react in some way) if ( attacker->client ) { AICast_UpdateVisibility( targ, attacker, qtrue, qtrue ); } // if either of us are neutral, then we are now enemies if ( targ->aiTeam == AITEAM_NEUTRAL || attacker->aiTeam == AITEAM_NEUTRAL ) { cs->vislist[attacker->s.number].flags |= AIVIS_ENEMY; } AICast_ScriptEvent( cs, "pain", va( "%d %d", targ->health, targ->health + damage ) ); if ( cs->aiFlags & AIFL_DENYACTION ) { // dont play any sounds return; } // // call the painfunc for this cast, so we can play associated sounds, or do any character-specific things // if ( cs->painfunc ) { cs->painfunc( targ, attacker, damage, point ); } }
/* ============== AICast_UpdateVisibility ============== */ void AICast_UpdateVisibility( gentity_t *srcent, gentity_t *destent, qboolean shareVis, qboolean directview ) { cast_visibility_t *vis, *ovis, *svis, oldvis; cast_state_t *cs, *ocs; qboolean shareRange; int cnt, i; if ( destent->flags & FL_NOTARGET ) { return; } cs = AICast_GetCastState( srcent->s.number ); ocs = AICast_GetCastState( destent->s.number ); if ( cs->castScriptStatus.scriptNoSightTime >= level.time ) { return; // absolutely no sight (or hear) information allowed } shareRange = ( VectorDistance( srcent->client->ps.origin, destent->client->ps.origin ) < AIVIS_SHARE_RANGE ); vis = &cs->vislist[destent->s.number]; vis->chase_marker_count = 0; if ( aicast_debug.integer == 1 ) { if ( !vis->visible_timestamp || vis->visible_timestamp < level.time - 5000 ) { if ( directview ) { G_Printf( "SIGHT (direct): %s sees %s\n", srcent->aiName, destent->aiName ); } else { G_Printf( "SIGHT (non-direct/audible): %s sees %s\n", srcent->aiName, destent->aiName ); } } } // trigger the sight event AICast_Sight( srcent, destent, vis->visible_timestamp ); // update the values vis->lastcheck_timestamp = level.time; vis->visible_timestamp = level.time; VectorCopy( destent->client->ps.origin, vis->visible_pos ); VectorCopy( destent->client->ps.velocity, vis->visible_vel ); vis->lastcheck_health = destent->health - 1; // we may need to process this visibility at some point, even after they become not visible again vis->flags |= AIVIS_PROCESS_SIGHTING; if ( directview ) { vis->real_visible_timestamp = level.time; VectorCopy( destent->client->ps.origin, vis->real_visible_pos ); vis->real_update_timestamp = level.time; } // if we are on fire, then run away from anything we see if ( cs->attributes[AGGRESSION] < 1.0 && srcent->s.onFireEnd > level.time && ( !destent->s.number || cs->dangerEntityValidTime < level.time + 2000 ) && !( cs->aiFlags & AIFL_NO_FLAME_DAMAGE ) ) { cs->dangerEntity = destent->s.number; VectorCopy( destent->r.currentOrigin, cs->dangerEntityPos ); cs->dangerEntityValidTime = level.time + 5000; cs->dangerDist = 99999; cs->dangerEntityTimestamp = level.time; } // Look for reasons to make this character an enemy of ours // if they are an enemy and inside the detection radius, go hostile if ( !( vis->flags & AIVIS_ENEMY ) && !AICast_SameTeam( cs, destent->s.number ) ) { float idr; idr = cs->attributes[INNER_DETECTION_RADIUS]; if ( cs->aiFlags & AIFL_ZOOMING ) { idr *= 10; } if ( !( vis->flags & AIVIS_ENEMY ) && VectorDistance( vis->visible_pos, g_entities[cs->entityNum].r.currentOrigin ) < idr ) { // RF, moved them over to AICast_ScanForEnemies() //AICast_ScriptEvent( cs, "enemysight", destent->aiName ); vis->flags |= AIVIS_ENEMY; } // if we are in (or above) ALERT mode, then we now know this is an enemy else if ( cs->aiState >= AISTATE_ALERT ) { // RF, moved them over to AICast_ScanForEnemies() //AICast_ScriptEvent( cs, "enemysight", destent->aiName ); vis->flags |= AIVIS_ENEMY; } } // if they are friendly, then we should help them out if they are in trouble if ( AICast_SameTeam( cs, destent->s.number ) && ( srcent->aiTeam == AITEAM_ALLIES || srcent->aiTeam == AITEAM_NAZI ) ) { // if they are dead, we should check them out if ( destent->health <= 0 ) { // if we haven't already checked them out if ( !( vis->flags & AIVIS_INSPECTED ) ) { vis->flags |= AIVIS_INSPECT; } // if they are mad, we should help, or at least act concerned } else if ( cs->aiState < AISTATE_COMBAT && ocs->aiState >= AISTATE_COMBAT && ocs->bs && ( ocs->enemyNum >= 0 ) ) { // if we haven't already checked them out if ( !( vis->flags & AIVIS_INSPECTED ) ) { vis->flags |= AIVIS_INSPECT; } // if they are alert, we should also go alert } else if ( cs->aiState < AISTATE_ALERT && ocs->aiState == AISTATE_ALERT && ocs->bs ) { AICast_StateChange( cs, AISTATE_ALERT ); } } // if this is a friendly, then check them for hostile's that we currently haven't upgraded so if ( ( destent->health > 0 ) && ( srcent->aiTeam == destent->aiTeam ) && // only share with exact same team, and non-neutrals ( srcent->aiTeam != AITEAM_NEUTRAL ) ) { ocs = AICast_GetCastState( destent->s.number ); cnt = 0; // for ( i = 0; i < aicast_maxclients && cnt < level.numPlayingClients; i++ ) { if ( !g_entities[i].inuse ) { continue; } // cnt++; // if ( i == srcent->s.number ) { continue; } if ( i == destent->s.number ) { continue; } // ovis = &ocs->vislist[i]; svis = &cs->vislist[i]; // // if we are close to the friendly, then we should share their visibility info if ( destent->health > 0 && shareRange ) { oldvis = *svis; // if they have seen this character more recently than us, share if ( ( ovis->visible_timestamp > svis->visible_timestamp ) || ( ( ovis->visible_timestamp > level.time - 5000 ) && ( ovis->flags & AIVIS_ENEMY ) && !( svis->flags & AIVIS_ENEMY ) ) ) { // trigger an EVENT // trigger the sight event AICast_Sight( srcent, destent, ovis->visible_timestamp ); // we may need to process this visibility at some point, even after they become not visible again svis->flags |= AIVIS_PROCESS_SIGHTING; // if we are sharing information about an enemy, then trigger a scripted event if ( !svis->real_visible_timestamp && ovis->real_visible_timestamp && ( ovis->flags & AIVIS_ENEMY ) ) { // setup conditions BG_UpdateConditionValue( ocs->entityNum, ANIM_COND_ENEMY_TEAM, g_entities[i].aiTeam, qfalse ); // call the event BG_AnimScriptEvent( &g_entities[ocs->entityNum].client->ps, ANIM_ET_INFORM_FRIENDLY_OF_ENEMY, qfalse, qfalse ); } // copy the whole structure *svis = *ovis; // minus the flags svis->flags = oldvis.flags; // keep our sight time if it's sooner if ( oldvis.visible_timestamp > ovis->visible_timestamp ) { svis->visible_timestamp = oldvis.visible_timestamp; } // check to see if we just made this character an enemy of ours if ( /*(cs->aiState == AISTATE_COMBAT) &&*/ ( ovis->flags & AIVIS_ENEMY ) && !( oldvis.flags & AIVIS_ENEMY ) ) { svis->flags |= AIVIS_ENEMY; if ( !( cs->vislist[i].flags & AIVIS_SIGHT_SCRIPT_CALLED ) ) { AICast_ScriptEvent( cs, "enemysight", g_entities[i].aiName ); cs->vislist[i].flags |= AIVIS_SIGHT_SCRIPT_CALLED; if ( !( cs->aiFlags & AIFL_DENYACTION ) ) { G_AddEvent( srcent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].soundScripts[SIGHTSOUNDSCRIPT] ) ); } } } } } else { // if either of us haven't seen this character yet, then ignore it if ( !svis->visible_timestamp || !ovis->visible_timestamp ) { continue; } } // // if they have marked this character as hostile, then we should also if ( ( cs->aiState == AISTATE_COMBAT ) && AICast_HostileEnemy( ocs, i ) && !AICast_HostileEnemy( cs, i ) ) { if ( !( cs->vislist[i].flags & AIVIS_SIGHT_SCRIPT_CALLED ) ) { AICast_ScriptEvent( cs, "enemysight", g_entities[i].aiName ); cs->vislist[i].flags |= AIVIS_SIGHT_SCRIPT_CALLED; if ( !( cs->aiFlags & AIFL_DENYACTION ) ) { G_AddEvent( srcent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].soundScripts[SIGHTSOUNDSCRIPT] ) ); } } svis->flags |= AIVIS_ENEMY; } } } }