/* ============ AICast_Sight ============ */ void AICast_Sight( gentity_t *ent, gentity_t *other, int lastSight ) { cast_state_t *cs, *ocs; cs = AICast_GetCastState( ent->s.number ); ocs = AICast_GetCastState( other->s.number ); // // call the sightfunc for this cast, so we can play associated sounds, or do any character-specific things // if ( cs->sightfunc ) { // factor in the reaction time if ( AICast_EntityVisible( cs, other->s.number, qfalse ) ) { cs->sightfunc( ent, other, lastSight ); } } if ( other->aiName && other->health <= 0 ) { // they died since we last saw them if ( ocs->deathTime > lastSight ) { if ( !AICast_SameTeam( cs, other->s.number ) ) { AICast_ScriptEvent( cs, "enemysightcorpse", other->aiName ); } else if ( !( cs->castScriptStatus.scriptFlags & SFL_FRIENDLYSIGHTCORPSE_TRIGGERED ) ) { cs->castScriptStatus.scriptFlags |= SFL_FRIENDLYSIGHTCORPSE_TRIGGERED; AICast_ScriptEvent( cs, "friendlysightcorpse", "" ); } } // if this is the first time, call the sight script event } else if ( !lastSight && other->aiName ) { if ( !AICast_SameTeam( cs, other->s.number ) ) { // disabled.. triggered when entering combat mode //AICast_ScriptEvent( cs, "enemysight", other->aiName ); } else { AICast_ScriptEvent( cs, "sight", other->aiName ); } } }
void AICast_SightUpdate( int numchecks ) { int count = 0, destcount, srccount; int src, dest; gentity_t *srcent, *destent; cast_state_t *cs, *dcs; //static int lastNumUpdated; // TTimo: unused cast_visibility_t *vis; #define SIGHT_MIN_DELAY 200 src = 0; dest = 0; if ( numchecks < 5 ) { numchecks = 5; } if ( trap_Cvar_VariableIntegerValue( "savegame_loading" ) ) { return; } if ( saveGamePending ) { return; } // First, check all REAL clients, so sighting player is only effected by reaction_time, not // effected by framerate also for ( srccount = 0, src = 0, srcent = &g_entities[0]; src < aicast_maxclients && srccount < level.numPlayingClients; src++, srcent++ ) { if ( !srcent->inuse ) { continue; } srccount++; if ( srcent->aiInactive ) { continue; } if ( srcent->health <= 0 ) { continue; } if ( !( srcent->r.svFlags & SVF_CASTAI ) ) { // only source check AI Cast continue; } cs = AICast_GetCastState( src ); if ( cs->castScriptStatus.scriptNoSightTime >= level.time ) { continue; } // make sure we are using the right AAS data for this entity (one's that don't get set will default to the player's AAS data) trap_AAS_SetCurrentWorld( cs->aasWorldIndex ); for ( destcount = 0, dest = 0, destent = g_entities; //dest < aicast_maxclients && destcount < level.numPlayingClients; destent == g_entities; // only check the player dest++, destent++ ) { if ( !destent->inuse ) { continue; } destcount++; if ( destent->health <= 0 ) { continue; } if ( destent->r.svFlags & SVF_CASTAI ) { // only dest check REAL clients continue; } if ( src == dest ) { continue; } vis = &cs->vislist[destent->s.number]; // OPTIMIZATION: if we have seen the player, abort checking each frame //if (vis->real_visible_timestamp && cs->aiState > AISTATE_QUERY && AICast_HostileEnemy(cs, destent->s.number)) // continue; // if we saw them last frame, skip this test, so we only check initial sightings each frame if ( vis->lastcheck_timestamp == vis->real_visible_timestamp ) { continue; } // if we recently checked this vis, skip if ( vis->lastcheck_timestamp >= level.time - ( 40 + rand() % 40 ) ) { continue; } // check for visibility if ( !( destent->flags & FL_NOTARGET ) && ( AICast_CheckVisibility( srcent, destent ) ) ) { // record the sighting AICast_UpdateVisibility( srcent, destent, qtrue, qtrue ); } else // if (vis->lastcheck_timestamp == vis->real_update_timestamp) { AICast_UpdateNonVisibility( srcent, destent, qtrue ); } } } // Now do the normal timeslice checks for ( srccount = 0, src = lastsrc, srcent = &g_entities[lastsrc]; src < aicast_maxclients; // && srccount < level.numPlayingClients; src++, srcent++ ) { if ( !srcent->inuse ) { continue; } srccount++; if ( srcent->aiInactive ) { continue; } if ( srcent->health <= 0 ) { continue; } cs = AICast_GetCastState( src ); if ( cs->castScriptStatus.scriptNoSightTime >= level.time ) { continue; } // make sure we are using the right AAS data for this entity (one's that don't get set will default to the player's AAS data) trap_AAS_SetCurrentWorld( cs->aasWorldIndex ); if ( lastdest < 0 ) { lastdest = 0; } for ( destcount = 0, dest = lastdest, destent = &g_entities[lastdest]; dest < aicast_maxclients; // && destcount < level.numPlayingClients; dest++, destent++ ) { if ( !destent->inuse ) { continue; } destcount++; if ( destent->aiInactive ) { continue; } if ( src == dest ) { continue; } dcs = AICast_GetCastState( destent->s.number ); vis = &cs->vislist[destent->s.number]; // don't check too often if ( vis->lastcheck_timestamp > ( level.time - SIGHT_MIN_DELAY ) ) { continue; } if ( destent->health <= 0 ) { // only check dead guys until they are sighted if ( vis->lastcheck_health < 0 ) { continue; } } if ( vis->lastcheck_timestamp > level.time ) { continue; // let the loadgame settle down } // if they are friends, only check very infrequently if ( AICast_SameTeam( cs, destent->s.number ) && ( vis->lastcheck_timestamp == vis->visible_timestamp ) && ( destent->health == vis->lastcheck_health + 1 ) ) { if ( dcs->aiState < AISTATE_COMBAT ) { if ( vis->lastcheck_timestamp > ( level.time - ( 2000 + rand() % 1000 ) ) ) { continue; // dont check too often } } else { // check a little more frequently if ( vis->lastcheck_timestamp > ( level.time - ( 500 + rand() % 500 ) ) ) { continue; // dont check too often } } } // check for visibility if ( !( destent->flags & FL_NOTARGET ) && ( AICast_CheckVisibility( srcent, destent ) ) ) { // make sure they are still with us if ( destent->inuse ) { // record the sighting AICast_UpdateVisibility( srcent, destent, qtrue, qtrue ); } } else // if (vis->lastcheck_timestamp == vis->real_update_timestamp) { AICast_UpdateNonVisibility( srcent, destent, qtrue ); } // break if we've processed the maximum visibilities if ( ++count > numchecks ) { dest++; if ( dest >= aicast_maxclients ) { src++; } goto escape; } } lastdest = 0; } escape: if ( src >= aicast_maxclients ) { src = 0; } lastsrc = src; if ( dest >= aicast_maxclients ) { dest = 0; } lastdest = dest; }
/* ============ AICast_ProcessActivate ============ */ void AICast_ProcessActivate( int entNum, int activatorNum ) { cast_state_t *cs; gentity_t *newent, *ent, *activator; gclient_t *client; cs = AICast_GetCastState( entNum ); client = &level.clients[entNum]; ent = &g_entities[entNum]; activator = &g_entities[activatorNum]; if ( !AICast_SameTeam( cs, activatorNum ) ) { if ( ent->aiTeam == AITEAM_NEUTRAL ) { AICast_ScriptEvent( cs, "activate", g_entities[activatorNum].aiName ); } return; } // try running the activate event, if it denies us the request, then abort cs->aiFlags &= ~AIFL_DENYACTION; AICast_ScriptEvent( cs, "activate", g_entities[activatorNum].aiName ); if ( cs->aiFlags & AIFL_DENYACTION ) { return; } // if we are doing something else if ( cs->castScriptStatus.castScriptEventIndex >= 0 ) { if ( ent->eventTime != level.time ) { G_AddEvent( &g_entities[entNum], EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].ordersDenySoundScript ) ); } return; } // if we are already following them, stop following if ( cs->leaderNum == activatorNum ) { if ( ent->eventTime != level.time ) { G_AddEvent( &g_entities[entNum], EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].staySoundScript ) ); } cs->leaderNum = -1; // create a goal at this position newent = G_Spawn(); newent->classname = "AI_wait_goal"; newent->r.ownerNum = entNum; G_SetOrigin( newent, cs->bs->origin ); AIFunc_ChaseGoalStart( cs, newent->s.number, 128, qtrue ); //AIFunc_IdleStart( cs ); } else { // start following int count, i; cast_state_t *tcs; // if they already have enough followers, deny for ( count = 0, i = 0, tcs = caststates; i < level.maxclients; i++, tcs++ ) { if ( tcs->bs && tcs != cs && tcs->entityNum != activatorNum && g_entities[tcs->entityNum].health > 0 && tcs->leaderNum == activatorNum ) { count++; } } if ( count >= 3 ) { if ( ent->eventTime != level.time ) { G_AddEvent( &g_entities[entNum], EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].ordersDenySoundScript ) ); } return; } if ( ent->eventTime != level.time ) { G_AddEvent( &g_entities[entNum], EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].followSoundScript ) ); } // if they have a wait goal, free it if ( cs->followEntity >= MAX_CLIENTS && g_entities[cs->followEntity].classname && !strcmp( g_entities[cs->followEntity].classname, "AI_wait_goal" ) ) { G_FreeEntity( &g_entities[cs->followEntity] ); } cs->followEntity = -1; cs->leaderNum = activatorNum; } }
/* ============== 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; } } } }