/* ------------------------- NPC_CheckSightEvents ------------------------- */ static int G_CheckSightEvents( gentity_t *self, int hFOV, int vFOV, float maxSeeDist, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel ) { int bestEvent = -1; int bestAlert = -1; int bestTime = -1; float dist, radius; maxSeeDist *= maxSeeDist; for ( int i = 0; i < level.numAlertEvents; i++ ) { //are we purposely ignoring this alert? if ( level.alertEvents[i].ID == ignoreAlert ) continue; //We're only concerned about sounds if ( level.alertEvents[i].type != AET_SIGHT ) continue; //must be at least this noticable if ( level.alertEvents[i].level < minAlertLevel ) continue; //must have an owner? if ( mustHaveOwner && !level.alertEvents[i].owner ) continue; //Must be within range dist = DistanceSquared( level.alertEvents[i].position, self->currentOrigin ); //can't see it if ( dist > maxSeeDist ) continue; radius = level.alertEvents[i].radius * level.alertEvents[i].radius; if ( dist > radius ) continue; //Must be visible if ( InFOV( level.alertEvents[i].position, self, hFOV, vFOV ) == qfalse ) continue; if ( G_ClearLOS( self, level.alertEvents[i].position ) == qfalse ) continue; //FIXME: possibly have the light level at this point affect the // visibility/alert level of this event? Would also // need to take into account how bright the event // itself is. A lightsaber would stand out more // in the dark... maybe pass in a light level that // is added to the actual light level at this position? //See if this one takes precedence over the previous one if ( level.alertEvents[i].level >= bestAlert //higher alert level || (level.alertEvents[i].level==bestAlert&&level.alertEvents[i].timestamp >= bestTime) )//same alert level, but this one is newer {//NOTE: equal is better because it's later in the array bestEvent = i; bestAlert = level.alertEvents[i].level; bestTime = level.alertEvents[i].timestamp; } } return bestEvent; }
//Entity to position qboolean G_ClearLOS( gentity_t *self, gentity_t *ent, const vec3_t end ) { vec3_t eyes; CalcEntitySpot( ent, SPOT_HEAD_LEAN, eyes ); return G_ClearLOS( self, eyes, end ); }
//NPC's eyes to position qboolean G_ClearLOS( gentity_t *self, const vec3_t end ) { vec3_t eyes; //Calculate the my position CalcEntitySpot( self, SPOT_HEAD_LEAN, eyes ); return G_ClearLOS( self, eyes, end ); }
//Position to entity qboolean G_ClearLOS( gentity_t *self, const vec3_t start, gentity_t *ent ) { vec3_t spot; //Look for the chest first CalcEntitySpot( ent, SPOT_ORIGIN, spot ); if ( G_ClearLOS( self, start, spot ) ) return qtrue; //Look for the head next CalcEntitySpot( ent, SPOT_HEAD_LEAN, spot ); if ( G_ClearLOS( self, start, spot ) ) return qtrue; return qfalse; }
/* ------------------------- NPC_CheckSoundEvents ------------------------- */ static int G_CheckSoundEvents( gentity_t *self, float maxHearDist, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel ) { int bestEvent = -1; int bestAlert = -1; int bestTime = -1; float dist, radius; maxHearDist *= maxHearDist; for ( int i = 0; i < level.numAlertEvents; i++ ) { //are we purposely ignoring this alert? if ( i == ignoreAlert ) continue; //We're only concerned about sounds if ( level.alertEvents[i].type != AET_SOUND ) continue; //must be at least this noticable if ( level.alertEvents[i].level < minAlertLevel ) continue; //must have an owner? if ( mustHaveOwner && !level.alertEvents[i].owner ) continue; //Must be within range dist = DistanceSquared( level.alertEvents[i].position, self->currentOrigin ); //can't hear it if ( dist > maxHearDist ) continue; radius = level.alertEvents[i].radius * level.alertEvents[i].radius; if ( dist > radius ) continue; if ( level.alertEvents[i].addLight ) {//a quiet sound, must have LOS to hear it if ( G_ClearLOS( self, level.alertEvents[i].position ) == qfalse ) {//no LOS, didn't hear it continue; } } //See if this one takes precedence over the previous one if ( level.alertEvents[i].level >= bestAlert //higher alert level || (level.alertEvents[i].level==bestAlert&&level.alertEvents[i].timestamp >= bestTime) )//same alert level, but this one is newer {//NOTE: equal is better because it's later in the array bestEvent = i; bestAlert = level.alertEvents[i].level; bestTime = level.alertEvents[i].timestamp; } } return bestEvent; }
/* static void G_DynamicMusicUpdate( usercmd_t *ucmd ) FIXME: can we merge any of this with the G_ChooseLookEnemy stuff? */ static void G_DynamicMusicUpdate( void ) { gentity_t *ent; gentity_t *entityList[MAX_GENTITIES]; int numListedEntities; vec3_t mins, maxs; int i, e; int distSq, radius = 2048; vec3_t center; int danger = 0; int battle = 0; int entTeam; qboolean dangerNear = qfalse; qboolean suspicious = qfalse; qboolean LOScalced = qfalse, clearLOS = qfalse; //FIXME: intro and/or other cues? (one-shot music sounds) //loops //player-based if ( !player ) {//WTF? player = &g_entities[0]; return; } if ( !player->client || player->client->pers.teamState.state != TEAM_ACTIVE || level.time - player->client->pers.enterTime < 100 ) {//player hasn't spawned yet return; } if ( player->health <= 0 && player->max_health > 0 ) {//defeat music if ( level.dmState != DM_DEATH ) { level.dmState = DM_DEATH; } } if ( level.dmState == DM_DEATH ) { gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "death" ); return; } if ( level.dmState == DM_BOSS ) { gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "boss" ); return; } if ( level.dmState == DM_SILENCE ) { gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "silence" ); return; } if ( level.dmBeatTime > level.time ) {//not on a beat return; } level.dmBeatTime = level.time + 1000;//1 second beats if ( player->health <= 20 ) { danger = 1; } //enemy-based VectorCopy( player->currentOrigin, center ); for ( i = 0 ; i < 3 ; i++ ) { mins[i] = center[i] - radius; maxs[i] = center[i] + radius; } numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for ( e = 0 ; e < numListedEntities ; e++ ) { ent = entityList[ e ]; if ( !ent || !ent->inuse ) { continue; } if ( !ent->client || !ent->NPC ) { if ( ent->classname && (!Q_stricmp( "PAS", ent->classname )||!Q_stricmp( "misc_turret", ent->classname )) ) {//a turret entTeam = ent->noDamageTeam; } else { continue; } } else {//an NPC entTeam = ent->client->playerTeam; } if ( entTeam == player->client->playerTeam ) {//ally continue; } if ( entTeam == TEAM_NEUTRAL && (!ent->enemy || !ent->enemy->client || ent->enemy->client->playerTeam != player->client->playerTeam) ) {//a droid that is not mad at me or my allies continue; } if ( !gi.inPVS( player->currentOrigin, ent->currentOrigin ) ) {//not potentially visible continue; } if ( ent->client && ent->s.weapon == WP_NONE ) {//they don't have a weapon... FIXME: only do this for droids? continue; } LOScalced = clearLOS = qfalse; if ( (ent->enemy==player&&(!ent->NPC||ent->NPC->confusionTime<level.time)) || (ent->client&&ent->client->ps.weaponTime) || (!ent->client&&ent->attackDebounceTime>level.time)) {//mad if ( ent->health > 0 ) {//alive //FIXME: do I really need this check? if ( ent->s.weapon == WP_SABER && ent->client && !ent->client->ps.saberActive && ent->enemy != player ) {//a Jedi who has not yet gotten made at me continue; } if ( ent->NPC && ent->NPC->behaviorState == BS_CINEMATIC ) {//they're not actually going to do anything about being mad at me... continue; } //okay, they're in my PVS, but how close are they? Are they actively attacking me? if ( !ent->client && ent->s.weapon == WP_TURRET && ent->fly_sound_debounce_time && ent->fly_sound_debounce_time - level.time < 10000 ) {//a turret that shot at me less than ten seconds ago } else if ( ent->client && ent->client->ps.lastShotTime && ent->client->ps.lastShotTime - level.time < 10000 ) {//an NPC that shot at me less than ten seconds ago } else {//not actively attacking me lately, see how far away they are distSq = DistanceSquared( ent->currentOrigin, player->currentOrigin ); if ( distSq > 4194304/*2048*2048*/ ) {//> 2048 away continue; } else if ( distSq > 1048576/*1024*1024*/ ) {//> 1024 away clearLOS = G_ClearLOS( player, player->client->renderInfo.eyePoint, ent ); LOScalced = qtrue; if ( clearLOS == qfalse ) {//No LOS continue; } } } battle++; } } if ( level.dmState == DM_EXPLORE ) {//only do these visibility checks if you're still in exploration mode if ( !InFront( ent->currentOrigin, player->currentOrigin, player->client->ps.viewangles, 0.0f) ) {//not in front continue; } if ( !LOScalced ) { clearLOS = G_ClearLOS( player, player->client->renderInfo.eyePoint, ent ); } if ( !clearLOS ) {//can't see them directly continue; } } if ( ent->health <= 0 ) {//dead if ( !ent->client || level.time - ent->s.time > 10000 ) {//corpse has been dead for more than 10 seconds //FIXME: coming across corpses should cause danger sounds too? continue; } } //we see enemies and/or corpses danger++; } if ( !battle ) {//no active enemies, but look for missiles, shot impacts, etc... int alert = G_CheckAlertEvents( player, qtrue, qtrue, 1024, 1024, -1, qfalse, AEL_SUSPICIOUS ); if ( alert != -1 ) {//FIXME: maybe tripwires and other FIXED things need their own sound, some kind of danger/caution theme if ( G_CheckForDanger( player, alert ) ) {//found danger near by danger++; battle = 1; } else if ( level.alertEvents[alert].owner && (level.alertEvents[alert].owner == player->enemy || (level.alertEvents[alert].owner->client && level.alertEvents[alert].owner->client->playerTeam == player->client->enemyTeam) ) ) {//NPC on enemy team of player made some noise switch ( level.alertEvents[alert].level ) { case AEL_DISCOVERED: dangerNear = qtrue; break; case AEL_SUSPICIOUS: suspicious = qtrue; break; case AEL_MINOR: //distraction = qtrue; break; } } } } if ( battle ) {//battle - this can interrupt level.dmDebounceTime of lower intensity levels //play battle if ( level.dmState != DM_ACTION ) { gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "action" ); } level.dmState = DM_ACTION; if ( battle > 5 ) { //level.dmDebounceTime = level.time + 8000;//don't change again for 5 seconds } else { //level.dmDebounceTime = level.time + 3000 + 1000*battle; } } else { if ( level.dmDebounceTime > level.time ) {//not ready to switch yet return; } else {//at least 1 second (for beats) //level.dmDebounceTime = level.time + 1000;//FIXME: define beat time? } /* if ( danger || dangerNear ) {//danger //stay on whatever we were on, action or exploration if ( !danger ) {//minimum danger = 1; } if ( danger > 3 ) { level.dmDebounceTime = level.time + 5000; } else { level.dmDebounceTime = level.time + 2000 + 1000*danger; } } else */ {//still nothing dangerous going on if ( level.dmState != DM_EXPLORE ) {//just went to explore, hold it for a couple seconds at least //level.dmDebounceTime = level.time + 2000; gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "explore" ); } level.dmState = DM_EXPLORE; //FIXME: look for interest points and play "mysterious" music instead of exploration? //FIXME: suspicious and distraction sounds should play some cue or change music in a subtle way? //play exploration } //FIXME: when do we go to silence? } }
qboolean NPC_ClearLOS( const vec3_t start, const vec3_t end ) { return G_ClearLOS( NPC, start, end ); }