/* ============== AICast_SightSoundEvent this cast has made a sound which should be heard by others ============== */ void AICast_SightSoundEvent( cast_state_t *cs, float range ) { int i; cast_state_t *ocs; gentity_t *oent, *ent; ent = &g_entities[cs->entityNum]; if ( ent->flags & FL_NOTARGET ) { return; } for ( i = 0, ocs = caststates, oent = g_entities; i < level.maxclients; i++, ocs++, oent++ ) { if ( !oent->inuse ) { continue; } if ( oent->aiInactive ) { continue; } if ( !ocs->bs ) { continue; } if ( oent->health <= 0 ) { continue; } if ( Distance( oent->r.currentOrigin, ent->r.currentOrigin ) > range * ocs->attributes[HEARING_SCALE] ) { continue; } // they heard us AICast_UpdateVisibility( oent, ent, qfalse, qfalse ); } }
char *AIFunc_Heinrich_RaiseDead(cast_state_t *cs) { int i; gentity_t *ent = &g_entities[cs->entityNum]; gentity_t *enemy = &g_entities[cs->enemyNum]; gentity_t *trav, *closest; float closestDist, dist; cs->aiFlags |= AIFL_SPECIAL_FUNC; if (cs->enemyNum < 0) { if (!ent->client->ps.torsoTimer) { return AIFunc_DefaultStart(cs); } return NULL; } // record weapon fire cs->weaponFireTimes[cs->weaponNum] = level.time; if (!ent->client->ps.torsoTimer) { return AIFunc_DefaultStart(cs); } if (ent->count2 && lastRaise < level.time - HEINRICH_RAISEDEAD_DELAY) { lastRaise = level.time; // summons the closest warrior closest = NULL; closestDist = 0; // shutup the compiler for (i = 0, trav = g_entities; i < level.maxclients; i++, trav++) { if (!trav->inuse) { continue; } if (!trav->aiInactive) { continue; } if (trav->aiCharacter != AICHAR_WARZOMBIE) { continue; } dist = VectorDistance(trav->s.pos.trBase, enemy->r.currentOrigin); if (!closest || dist < closestDist) { closest = trav; closestDist = dist; } } if (closest) { closest->AIScript_AlertEntity(closest); // make them aware of the player AICast_UpdateVisibility(closest, enemy, qtrue, qtrue); // reduce the count ent->count2--; } } return NULL; }
/* ============== AIFunc_FlameZombie_Portal ============== */ const char *AIFunc_FlameZombie_Portal( cast_state_t *cs ) { gentity_t *ent = &g_entities[cs->entityNum]; // if ( cs->thinkFuncChangeTime < level.time - PORTAL_ZOMBIE_SPAWNTIME ) { // HACK, make them aware of the player AICast_UpdateVisibility( &g_entities[cs->entityNum], AICast_FindEntityForName( "player" ), qfalse, qtrue ); ent->s.time2 = 0; // turn spawning effect off return AIFunc_DefaultStart( cs ); } // return NULL; }
/* ============ 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_EvaluatePmove Avoidance after the event (leaders instruct AI's to get out the way, AI's instruct other non-moving AI's to get out the way) ================ */ void AICast_EvaluatePmove( int clientnum, pmove_t *pm ) { cast_state_t *cs, *ocs; int i, ent; bot_goal_t ogoal; //vec3_t pos, dir; cs = AICast_GetCastState( clientnum ); // 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 ); // NOTE: this is only enabled for real clients, so their followers get out of their way //if (cs->bs) // return; // look through the touchent's to see if we've bumped into something we should avoid, or react to for ( i = 0; i < pm->numtouch; i++ ) { // mark the time, so they can deal with the obstruction in their own think functions cs->blockedTime = level.time; if ( pm->touchents[i] == pm->ps->groundEntityNum ) { continue; } // if they are an AI Cast, inform them of our disposition, and hope that they are reasonable // enough to assist us in our desire to move beyond our current position if ( pm->touchents[i] < aicast_maxclients ) { if ( !AICast_EntityVisible( cs, pm->touchents[i], qtrue ) ) { continue; } // if we are inspecting the body, abort if we touch anything if ( cs->bs && cs->bs->enemy >= 0 && g_entities[cs->bs->enemy].health <= 0 ) { cs->bs->enemy = -1; } // anything we touch, should see us AICast_UpdateVisibility( &g_entities[pm->touchents[i]], &g_entities[cs->entityNum], qfalse, qtrue ); ocs = AICast_GetCastState( pm->touchents[i] ); if ( ( ocs->bs ) && ( !( ocs->aiFlags & AIFL_NOAVOID ) ) && ( ( ocs->leaderNum == cs->entityNum ) || ( VectorLength( ocs->bs->velocity ) < 5 ) ) && ( ocs->obstructingTime < ( level.time + 100 ) ) ) { // if they are moving away from us already, let them go if ( VectorLength( ocs->bs->cur_ps.velocity ) > 10 ) { vec3_t v1, v2; VectorSubtract( ocs->bs->origin, g_entities[clientnum].client->ps.velocity, v2 ); VectorNormalize( v2 ); VectorNormalize2( ocs->bs->cur_ps.velocity, v1 ); if ( DotProduct( v1, v2 ) > 0.0 ) { continue; } } if ( ocs->leaderNum >= 0 ) { VectorCopy( g_entities[ocs->leaderNum].r.currentOrigin, ogoal.origin ); ogoal.areanum = BotPointAreaNum( ogoal.origin ); ogoal.entitynum = ocs->leaderNum; if ( ocs->bs && AICast_GetAvoid( ocs, &ogoal, ocs->obstructingPos, qfalse, cs->entityNum ) ) { // give them time to move somewhere else ocs->obstructingTime = level.time + 1000; } } else { if ( ocs->bs && AICast_GetAvoid( ocs, NULL, ocs->obstructingPos, qfalse, cs->entityNum ) ) { // give them time to move somewhere else ocs->obstructingTime = level.time + 1000; } } } } else if ( cs->bs ) { // if we are blocked by a brush entity, see if we can activate it ent = pm->touchents[i]; if ( g_entities[ent].s.modelindex > 0 && g_entities[ent].s.eType == ET_MOVER ) { //find the bsp entity which should be activated in order to remove //the blocking entity if ( !g_entities[ent].isProp && Q_stricmp( g_entities[ent].classname, "func_static" ) && Q_stricmp( g_entities[ent].classname, "func_button" ) && Q_stricmp( g_entities[ent].classname, "func_tram" ) ) { G_Activate( &g_entities[ent], &g_entities[cs->entityNum] ); } } } } }
/* ============ AICast_Die ============ */ void AICast_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) { int contents; int killer; cast_state_t *cs; qboolean nogib = qtrue; // print debugging message if ( aicast_debug.integer == 2 && attacker->s.number == 0 ) { G_Printf( "killed %s\n", self->aiName ); } cs = AICast_GetCastState( self->s.number ); if ( attacker ) { killer = attacker->s.number; } else { killer = ENTITYNUM_WORLD; } // record the sighting (FIXME: silent weapons shouldn't do this, but the AI should react in some way) if ( attacker->client ) { AICast_UpdateVisibility( self, attacker, qtrue, qtrue ); } // the zombie should show special effect instead of gibbing if ( self->aiCharacter == AICHAR_ZOMBIE && cs->secondDeadTime ) { if ( cs->secondDeadTime > 1 ) { // we are already totally dead self->health += damage; // don't drop below gib_health if we weren't already below it return; } /* if (!cs->rebirthTime) { self->health = -999; damage = 999; } else if ( self->health >= GIB_HEALTH ) { // while waiting for rebirth, we only "die" if we drop below gib health return; } */ // always gib self->health = -999; damage = 999; } // Zombies are very fragile against highly explosives if ( self->aiCharacter == AICHAR_ZOMBIE && damage > 20 && inflictor != attacker ) { self->health = -999; damage = 999; } // process the event if ( self->client->ps.pm_type == PM_DEAD ) { // already dead if ( self->health < GIB_HEALTH ) { if ( self->aiCharacter == AICHAR_ZOMBIE ) { // RF, changed this so Zombies always gib now GibEntity( self, killer ); nogib = qfalse; /* // Zombie has special exploding cloud effect if (attacker != inflictor || attacker->s.weapon == WP_VENOM) { GibEntity( self, killer ); nogib = qfalse; } else { // Zombie will decompose upon dying self->client->ps.eFlags |= EF_MONSTER_EFFECT2; self->s.effect2Time = level.time+200; self->health = -1; } */ self->takedamage = qfalse; self->r.contents = 0; cs->secondDeadTime = 2; cs->rebirthTime = 0; cs->revivingTime = 0; } else { body_die( self, inflictor, attacker, damage, meansOfDeath ); return; } } } else { // this is our first death, so set everything up if ( level.intermissiontime ) { return; } self->client->ps.pm_type = PM_DEAD; self->enemy = attacker; // drop a weapon? // if client is in a nodrop area, don't drop anything contents = trap_PointContents( self->r.currentOrigin, -1 ); if ( !( contents & CONTENTS_NODROP ) ) { TossClientItems( self ); } // make sure the client doesn't forget about this entity until it's set to "dead" frame // otherwise it might replay it's death animation if it goes out and into client view self->r.svFlags |= SVF_BROADCAST; self->takedamage = qtrue; // can still be gibbed self->s.weapon = WP_NONE; self->s.powerups = 0; self->r.contents = CONTENTS_CORPSE; self->s.angles[0] = 0; self->s.angles[1] = self->client->ps.viewangles[1]; self->s.angles[2] = 0; VectorCopy( self->s.angles, self->client->ps.viewangles ); self->s.loopSound = 0; self->r.maxs[2] = -8; self->client->ps.maxs[2] = self->r.maxs[2]; // remove powerups memset( self->client->ps.powerups, 0, sizeof( self->client->ps.powerups ) ); //cs->rebirthTime = 0; // never gib in a nodrop if ( self->health <= GIB_HEALTH ) { if ( self->aiCharacter == AICHAR_ZOMBIE ) { // RF, changed this so Zombies always gib now GibEntity( self, killer ); nogib = qfalse; /* // Zombie has special exploding cloud effect if (attacker != inflictor || attacker->s.weapon == WP_VENOM) { GibEntity( self, killer ); nogib = qfalse; self->takedamage = qfalse; self->r.contents = 0; cs->secondDeadTime = 2; } else { self->client->ps.eFlags |= EF_MONSTER_EFFECT2; self->s.effect2Time = level.time+200; self->takedamage = qfalse; self->r.contents = 0; self->health = -1; cs->secondDeadTime = 2; } */ } else if ( !( contents & CONTENTS_NODROP ) ) { body_die( self, inflictor, attacker, damage, meansOfDeath ); //GibEntity( self, killer ); nogib = qfalse; } } // if we are a zombie, and lying down during our first death, then we should just die if ( !( self->aiCharacter == AICHAR_ZOMBIE && cs->secondDeadTime && cs->rebirthTime ) ) { // set enemy weapon BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_WEAPON, 0, qfalse ); if ( attacker->client ) { BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_WEAPON, inflictor->s.weapon, qtrue ); } else { BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_WEAPON, 0, qfalse ); } // set enemy location BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_POSITION, 0, qfalse ); if ( infront( self, inflictor ) ) { BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_POSITION, POSITION_INFRONT, qtrue ); } else { BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_POSITION, POSITION_BEHIND, qtrue ); } // play the animation BG_AnimScriptEvent( &self->client->ps, ANIM_ET_DEATH, qfalse, qtrue ); // set this flag so no other anims override us self->client->ps.eFlags |= EF_DEAD; self->s.eFlags |= EF_DEAD; } } if ( nogib ) { // set for rebirth if ( self->aiCharacter == AICHAR_ZOMBIE ) { if ( !cs->secondDeadTime ) { cs->rebirthTime = level.time + 5000 + rand() % 2000; cs->secondDeadTime = qtrue; cs->revivingTime = 0; } else if ( cs->secondDeadTime > 1 ) { cs->rebirthTime = 0; cs->revivingTime = 0; cs->deathTime = level.time; } } else { // the body can still be gibbed self->die = body_die; } } trap_LinkEntity( self ); // mark the time of death cs->deathTime = level.time; // dying ai's can trigger a target if ( !cs->rebirthTime ) { G_UseTargets( self, self ); // really dead now, so call the script AICast_ScriptEvent( cs, "death", "" ); // call the deathfunc for this cast, so we can play associated sounds, or do any character-specific things if ( !( cs->aiFlags & AIFL_DENYACTION ) && cs->deathfunc ) { cs->deathfunc( self, attacker, damage, meansOfDeath ); //----(SA) added mod } } else { // really dead now, so call the script AICast_ScriptEvent( cs, "fakedeath", "" ); // call the deathfunc for this cast, so we can play associated sounds, or do any character-specific things if ( !( cs->aiFlags & AIFL_DENYACTION ) && cs->deathfunc ) { cs->deathfunc( self, attacker, damage, meansOfDeath ); //----(SA) added mod } } }
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; }