// the trigger was just activated // ent->activator should be set to the activator so it can be held through a delay // so wait for the delay time before firing void AICast_trigger_trigger(gentity_t *ent, gentity_t *activator) { if(ent->nextthink) { return; // can't retrigger until the wait is over } ent->activator = AICast_FindEntityForName(ent->aiName); if(ent->activator) // they might be dead { // trigger the script event AICast_ScriptEvent(AICast_GetCastState(ent->activator->s.number), "trigger", ent->target); } if(ent->wait > 0) { ent->think = AICast_trigger_wait; ent->nextthink = level.time + (ent->wait + ent->random * crandom()) * 1000; } else { // we can't just remove (self) here, because this is a touch function // called while looping through area links... ent->touch = 0; ent->nextthink = level.time + FRAMETIME; ent->think = G_FreeEntity; } }
/* ================ AICast_ForceScriptEvent Definately run this event now, overriding any paised state ================ */ void AICast_ForceScriptEvent( struct cast_state_s *cs, char *eventStr, char *params ) { int oldPauseTime; oldPauseTime = cs->scriptPauseTime; cs->scriptPauseTime = 0; AICast_ScriptEvent( cs, eventStr, params ); cs->scriptPauseTime = oldPauseTime; }
/* ============ AIChar_AIScript_AlertEntity triggered spawning, called from AI scripting ============ */ void AIChar_AIScript_AlertEntity( gentity_t *ent ) { vec3_t mins, maxs; int numTouch, touch[10], i; cast_state_t *cs; if ( !ent->aiInactive ) { return; } cs = AICast_GetCastState( ent->s.number ); // if the current bounding box is invalid, then wait VectorAdd( ent->r.currentOrigin, ent->r.mins, mins ); VectorAdd( ent->r.currentOrigin, ent->r.maxs, maxs ); trap_UnlinkEntity( ent ); numTouch = trap_EntitiesInBox( mins, maxs, touch, 10 ); // check that another client isn't inside us if ( numTouch ) { for ( i = 0; i < numTouch; i++ ) { // RF, note we should only check against clients since zombies need to spawn inside func_explosive (so they dont clip into view after it explodes) if ( g_entities[touch[i]].client && g_entities[touch[i]].r.contents == CONTENTS_BODY ) { //if (g_entities[touch[i]].r.contents & MASK_PLAYERSOLID) break; } } if ( i == numTouch ) { numTouch = 0; } } if ( numTouch ) { // invalid location cs->aiFlags |= AIFL_WAITINGTOSPAWN; return; } // RF, has to disable this so I could test some maps which have erroneously placed alertentity calls //ent->AIScript_AlertEntity = NULL; cs->aiFlags &= ~AIFL_WAITINGTOSPAWN; ent->aiInactive = qfalse; trap_LinkEntity( ent ); // trigger a spawn script event AICast_ScriptEvent( AICast_GetCastState( ent->s.number ), "spawn", "" ); // make it think so we update animations/angles AICast_Think( ent->s.number, (float)FRAMETIME / 1000 ); cs->lastThink = level.time; AICast_UpdateInput( cs, FRAMETIME ); trap_BotUserCommand( cs->bs->client, &( cs->lastucmd ) ); }
/* ============ 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 ); } } }
/* ============ 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 ); } }
/*QUAKED target_script_trigger (1 .7 .2) (-8 -8 -8) (8 8 8) must have an aiName must have a target when used it will fire its targets */ void target_script_trigger_use (gentity_t *ent, gentity_t *other, gentity_t *activator ) { gentity_t *player; if (ent->aiName) { player = AICast_FindEntityForName("player"); if (player) AICast_ScriptEvent( AICast_GetCastState(player->s.number), "trigger", ent->target ); } // DHM - Nerve :: In multiplayer, we use the brush scripting only if ( g_gametype.integer >= GT_WOLF && ent->scriptName ) { G_Script_ScriptEvent( ent, "trigger", ent->target ); } G_UseTargets ( ent, other); }
/* ================= G_ScriptAction_Trigger syntax: trigger <aiName/scriptName> <trigger> Calls the specified trigger for the given ai character or script entity ================= */ qboolean G_ScriptAction_Trigger( gentity_t *ent, const char* params ) { gentity_t *trent; const char* pString; char* token; char name[MAX_QPATH]; char trigger[MAX_QPATH]; int oldId; // get the cast name pString = params; token = COM_ParseExt( &pString, qfalse ); Q_strncpyz( name, token, sizeof( name ) ); if ( !name[0] ) { G_Error( "G_Scripting: trigger must have a name and an identifier\n" ); } token = COM_ParseExt( &pString, qfalse ); Q_strncpyz( trigger, token, sizeof( trigger ) ); if ( !trigger[0] ) { G_Error( "G_Scripting: trigger must have a name and an identifier\n" ); } trent = AICast_FindEntityForName( name ); if ( trent ) { // we are triggering an AI //oldId = trent->scriptStatus.scriptId; AICast_ScriptEvent( AICast_GetCastState( trent->s.number ), "trigger", trigger ); return qtrue; } // look for an entity trent = G_Find( &g_entities[MAX_CLIENTS], FOFS( scriptName ), name ); if ( trent ) { oldId = trent->scriptStatus.scriptId; G_Script_ScriptEvent( trent, "trigger", trigger ); // if the script changed, return false so we don't muck with it's variables return ( ( trent != ent ) || ( oldId == trent->scriptStatus.scriptId ) ); } G_Error( "G_Scripting: trigger has unknown name: %s\n", name ); return qfalse; // shutup the compiler }
/* ============ AICast_Blocked ============ */ void AICast_Blocked( cast_state_t *cs, bot_moveresult_t *moveresult, int activate, bot_goal_t *goal ) { vec3_t pos, dir; aicast_predictmove_t move; usercmd_t ucmd; bot_input_t bi; cast_state_t *ocs; int i, blockEnt = -1; bot_goal_t ogoal; if ( cs->blockedAvoidTime < level.time ) { if ( cs->blockedAvoidTime < level.time - 300 ) { if ( VectorCompare( cs->bs->cur_ps.velocity, vec3_origin ) && !cs->bs->lastucmd.forwardmove && !cs->bs->lastucmd.rightmove ) { // not moving, don't bother checking cs->blockedAvoidTime = level.time - 1; return; } // are we going to hit someone soon? trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi ); AICast_InputToUserCommand( cs, &bi, &ucmd, cs->bs->cur_ps.delta_angles ); AICast_PredictMovement( cs, 1, 0.6, &move, &ucmd, ( goal && goal->entitynum > -1 ) ? goal->entitynum : cs->entityNum ); // blocked if we hit a client (or non-stationary mover) other than our enemy or goal if ( move.stopevent != PREDICTSTOP_HITCLIENT ) { // not blocked cs->blockedAvoidTime = level.time - 1; return; } // if we stopped passed our goal, ignore it if ( goal ) { if ( VectorDistance( cs->bs->origin, goal->origin ) < VectorDistance( cs->bs->origin, move.endpos ) ) { vec3_t v1, v2; VectorSubtract( goal->origin, cs->bs->origin, v1 ); VectorSubtract( goal->origin, move.endpos, v2 ); VectorNormalize( v1 ); VectorNormalize( v2 ); if ( DotProduct( v1, v2 ) < 0 ) { // we went passed the goal, so assume we can reach it cs->blockedAvoidTime = level.time - 1; return; } } } // try and get them to move, in case we can't get around them blockEnt = -1; for ( i = 0; i < move.numtouch; i++ ) { if ( move.touchents[i] >= MAX_CLIENTS ) { if ( !Q_stricmp( g_entities[move.touchents[i]].classname, "script_mover" ) ) { // avoid script_mover's blockEnt = move.touchents[i]; } // if we are close to the impact point, then avoid this entity else if ( VectorDistance( cs->bs->origin, move.endpos ) < 10 ) { //G_Printf("AI (%s) avoiding %s\n", g_entities[cs->entityNum].aiName, g_entities[move.touchents[i]].classname ); blockEnt = move.touchents[i]; } continue; } // ocs = AICast_GetCastState( move.touchents[i] ); if ( !ocs->bs ) { blockEnt = move.touchents[i]; } // reject this blocker if we are following or going to them else if ( cs->followEntity != ocs->entityNum ) { // 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, cs->bs->origin, v2 ); VectorNormalize( v2 ); VectorNormalize2( ocs->bs->cur_ps.velocity, v1 ); if ( DotProduct( v1, v2 ) > 0.0 ) { continue; } } // // if they recently were asked to avoid us, then they're probably not listening if ( ocs->obstructingTime > level.time - 500 ) { blockEnt = move.touchents[i]; } // // if they are not avoiding, ignore if ( !( ocs->aiFlags & AIFL_NOAVOID ) ) { continue; } // // they should avoid us if ( ocs->leaderNum >= 0 ) { ogoal.entitynum = ocs->leaderNum; VectorCopy( g_entities[ocs->leaderNum].r.currentOrigin, ogoal.origin ); if ( AICast_GetAvoid( ocs, &ogoal, ocs->obstructingPos, qfalse, cs->entityNum ) ) { // give them time to move somewhere else ocs->obstructingTime = level.time + 1000; } else { // make sure they don't call GetAvoid() for another few frames to let others avoid also ocs->obstructingTime = level.time - 1; blockEnt = move.touchents[i]; } } else { if ( AICast_GetAvoid( ocs, NULL, ocs->obstructingPos, qfalse, cs->entityNum ) ) { // give them time to move somewhere else ocs->obstructingTime = level.time + 1000; } else { // make sure they don't call GetAvoid() for another few frames to let others avoid also ocs->obstructingTime = level.time - 1; blockEnt = move.touchents[i]; } } } } } else { return; } if ( blockEnt < 0 ) { // nothing found to be worth avoding cs->blockedAvoidTime = level.time - 1; return; } // something is blocking our path if ( g_entities[blockEnt].aiName && g_entities[blockEnt].client ) { int oldId = cs->castScriptStatus.scriptId; AICast_ScriptEvent( cs, "blocked", g_entities[blockEnt].aiName ); if ( oldId != cs->castScriptStatus.scriptId ) { // the script has changed, so assume the scripting is handling the avoidance return; } } // avoid geometry and props, but assume clients will get out the way if ( /*blockEnt > MAX_CLIENTS &&*/ AICast_GetAvoid( cs, goal, pos, qfalse, blockEnt ) ) { VectorSubtract( pos, cs->bs->cur_ps.origin, dir ); VectorNormalize( dir ); cs->blockedAvoidYaw = vectoyaw( dir ); if ( blockEnt >= MAX_CLIENTS ) { cs->blockedAvoidTime = level.time + 100 + rand() % 200; } else { cs->blockedAvoidTime = level.time + 300 + rand() % 400; } } else { cs->blockedAvoidTime = level.time - 1; // don't look again for another few frames return; } } VectorClear( pos ); pos[YAW] = cs->blockedAvoidYaw; AngleVectors( pos, dir, NULL, NULL ); if ( moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE ) { trap_EA_Jump( cs->bs->entitynum ); } trap_EA_Move( cs->bs->entitynum, dir, 200 ); //400); vectoangles( dir, cs->bs->ideal_viewangles ); cs->bs->ideal_viewangles[2] *= 0.5; }
const char *AIFunc_Heinrich_Earthquake( cast_state_t *cs ) { gentity_t *ent = &g_entities[cs->entityNum]; gentity_t *enemy; cast_state_t *ecs; vec3_t enemyVec; float enemyDist, scale; trace_t *tr; cs->aiFlags |= AIFL_SPECIAL_FUNC; if ( cs->enemyNum < 0 ) { if ( !ent->client->ps.torsoTimer ) { return AIFunc_DefaultStart( cs ); } return NULL; } enemy = &g_entities[cs->enemyNum]; ecs = AICast_GetCastState( cs->enemyNum ); VectorMA( enemy->r.currentOrigin, HEINRICH_STOMP_DELAY, enemy->client->ps.velocity, enemyVec ); enemyDist = VectorDistance( ent->r.currentOrigin, enemyVec ); if ( ent->client->ps.torsoTimer < 500 ) { int rnd; aicast_predictmove_t move; vec3_t vec; AICast_PredictMovement( ecs, 2, 0.5, &move, &g_entities[cs->enemyNum].client->pers.cmd, -1 ); VectorSubtract( move.endpos, cs->bs->origin, vec ); vec[2] = 0; enemyDist = VectorLength( vec ); enemyDist -= g_entities[cs->enemyNum].r.maxs[0]; enemyDist -= ent->r.maxs[0]; // if ( enemyDist < 140 ) { // combo attack rnd = rand() % 3; switch ( rnd ) { case 0: return AIFunc_Heinrich_SwordSideSlashStart( cs ); case 1: return AIFunc_Heinrich_SwordKnockbackStart( cs ); case 2: return AIFunc_Heinrich_SwordLungeStart( cs ); } } else { // back to roaming ent->client->ps.legsTimer = 0; ent->client->ps.torsoTimer = 0; cs->castScriptStatus.scriptNoMoveTime = 0; AICast_Heinrich_Taunt( cs ); return AIFunc_DefaultStart( cs ); } } // time for the thump? if ( !( cs->aiFlags & AIFL_MISCFLAG1 ) ) { // face them AICast_AimAtEnemy( cs ); // ready for damage? if ( cs->thinkFuncChangeTime < level.time - HEINRICH_STOMP_DELAY ) { cs->aiFlags |= AIFL_MISCFLAG1; // play the stomp sound G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].soundScripts[ORDERSDENYSOUNDSCRIPT] ) ); // check for striking the player tr = CheckMeleeAttack( ent, 70, qfalse ); // do melee damage if ( tr && ( tr->entityNum == cs->enemyNum ) ) { G_Damage( &g_entities[tr->entityNum], ent, ent, vec3_origin, tr->endpos, HEINRICH_STOMP_DAMAGE, 0, MOD_GAUNTLET ); } // call the debris trigger AICast_ScriptEvent( cs, "trigger", "quake" ); } } enemyDist = Distance( enemy->s.pos.trBase, ent->s.pos.trBase ); // do the earthquake effects if ( cs->thinkFuncChangeTime < level.time - HEINRICH_STOMP_DELAY ) { // throw the player into the air, if they are on the ground if ( ( enemy->s.groundEntityNum != ENTITYNUM_NONE ) && enemyDist < HEINRICH_STOMP_RANGE ) { scale = 0.5 + 0.5 * ( (float)ent->client->ps.torsoTimer / 1000.0 ); if ( scale > 1.0 ) { scale = 1.0; } VectorSubtract( ent->s.pos.trBase, enemy->s.pos.trBase, enemyVec ); VectorScale( enemyVec, 2.0 * ( 0.6 + 0.5 * random() ) * scale * ( 0.6 + 0.6 * ( 1.0 - ( enemyDist / HEINRICH_STOMP_RANGE ) ) ), enemyVec ); enemyVec[2] = scale * HEINRICH_STOMP_VELOCITY_Z * ( 1.0 - 0.5 * ( enemyDist / HEINRICH_STOMP_RANGE ) ); // bounce the player using this velocity VectorAdd( enemy->client->ps.velocity, enemyVec, enemy->client->ps.velocity ); } } return NULL; }
/* ============ 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_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 } } }
/* ============== 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; } } } }
/* ================== player_die ================== */ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) { gentity_t *ent; int anim; int contents = 0; int killer; int i; char *killerName, *obit; qboolean nogib = qtrue; gitem_t *item = NULL; // JPW NERVE for flag drop vec3_t launchvel; // JPW NERVE gentity_t *flag; // JPW NERVE if ( self->client->ps.pm_type == PM_DEAD ) { return; } if ( level.intermissiontime ) { return; } //----(SA) commented out as we have no hook // if (self->client && self->client->hook) // Weapon_HookFree(self->client->hook); self->client->ps.pm_type = PM_DEAD; if ( attacker ) { killer = attacker->s.number; if ( attacker->client ) { killerName = attacker->client->pers.netname; } else { killerName = "<non-client>"; } } else { killer = ENTITYNUM_WORLD; killerName = "<world>"; } if ( killer < 0 || killer >= MAX_CLIENTS ) { killer = ENTITYNUM_WORLD; killerName = "<world>"; } if ( meansOfDeath < 0 || meansOfDeath >= sizeof( modNames ) / sizeof( modNames[0] ) ) { obit = "<bad obituary>"; } else { obit = modNames[ meansOfDeath ]; } G_LogPrintf( "Kill: %i %i %i: %s killed %s by %s\n", killer, self->s.number, meansOfDeath, killerName, self->client->pers.netname, obit ); // broadcast the death event to everyone ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY ); ent->s.eventParm = meansOfDeath; ent->s.otherEntityNum = self->s.number; ent->s.otherEntityNum2 = killer; ent->r.svFlags = SVF_BROADCAST; // send to everyone self->enemy = attacker; self->client->ps.persistant[PERS_KILLED]++; if ( attacker && attacker->client ) { if ( attacker == self || OnSameTeam( self, attacker ) ) { AddScore( attacker, -1 ); } else { AddScore( attacker, 1 ); // Ridah, not in single player if ( g_gametype.integer != GT_SINGLE_PLAYER ) { // done. if ( meansOfDeath == MOD_GAUNTLET ) { attacker->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT]++; attacker->client->ps.persistant[PERS_REWARD] = REWARD_GAUNTLET; attacker->client->ps.persistant[PERS_REWARD_COUNT]++; // add the sprite over the player's head // attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT /*| EF_AWARD_GAUNTLET*/ ); //attacker->client->ps.eFlags |= EF_AWARD_GAUNTLET; attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME; // also play humiliation on target self->client->ps.persistant[PERS_REWARD] = REWARD_GAUNTLET; self->client->ps.persistant[PERS_REWARD_COUNT]++; } // check for two kills in a short amount of time // if this is close enough to the last kill, give a reward sound if ( level.time - attacker->client->lastKillTime < CARNAGE_REWARD_TIME ) { attacker->client->ps.persistant[PERS_REWARD_COUNT]++; attacker->client->ps.persistant[PERS_REWARD] = REWARD_EXCELLENT; attacker->client->ps.persistant[PERS_EXCELLENT_COUNT]++; // add the sprite over the player's head // attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT /*| EF_AWARD_GAUNTLET*/ ); // attacker->client->ps.eFlags |= EF_AWARD_EXCELLENT; attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME; } // Ridah } // done. attacker->client->lastKillTime = level.time; } } else { AddScore( self, -1 ); } // Add team bonuses Team_FragBonuses( self, inflictor, attacker ); // if client is in a nodrop area, don't drop anything // JPW NERVE new drop behavior if ( g_gametype.integer == GT_SINGLE_PLAYER ) { // only drop here in single player; in multiplayer, drop @ limbo contents = trap_PointContents( self->r.currentOrigin, -1 ); if ( !( contents & CONTENTS_NODROP ) ) { TossClientItems( self ); } } // drop flag regardless if ( g_gametype.integer != GT_SINGLE_PLAYER ) { if ( self->client->ps.powerups[PW_REDFLAG] ) { item = BG_FindItem( "Red Flag" ); } if ( self->client->ps.powerups[PW_BLUEFLAG] ) { item = BG_FindItem( "Blue Flag" ); } launchvel[0] = crandom() * 20; launchvel[1] = crandom() * 20; launchvel[2] = 10 + random() * 10; if ( item ) { flag = LaunchItem( item,self->r.currentOrigin,launchvel ); flag->s.modelindex2 = self->s.otherEntityNum2; // JPW NERVE FIXME set player->otherentitynum2 with old modelindex2 from flag and restore here } } // jpw Cmd_Score_f( self ); // show scores // send updated scores to any clients that are following this one, // or they would get stale scoreboards for ( i = 0 ; i < level.maxclients ; i++ ) { gclient_t *client; client = &level.clients[i]; if ( client->pers.connected != CON_CONNECTED ) { continue; } if ( client->sess.sessionTeam != TEAM_SPECTATOR ) { continue; } if ( client->sess.spectatorClient == self->s.number ) { Cmd_Score_f( g_entities + i ); } } self->takedamage = qtrue; // can still be gibbed self->s.powerups = 0; // JPW NERVE -- only corpse in SP; in MP, need CONTENTS_BODY so medic can operate if ( g_gametype.integer == GT_SINGLE_PLAYER ) { self->r.contents = CONTENTS_CORPSE; self->s.weapon = WP_NONE; } else { self->client->limboDropWeapon = self->s.weapon; // store this so it can be dropped in limbo } // jpw self->s.angles[0] = 0; self->s.angles[2] = 0; LookAtKiller( self, inflictor, attacker ); VectorCopy( self->s.angles, self->client->ps.viewangles ); self->s.loopSound = 0; self->r.maxs[2] = -8; // don't allow respawn until the death anim is done // g_forcerespawn may force spawning at some later time self->client->respawnTime = level.time + 1700; // remove powerups memset( self->client->ps.powerups, 0, sizeof( self->client->ps.powerups ) ); if ( g_gametype.integer == GT_SINGLE_PLAYER ) { trap_SendServerCommand( -1, "mu_play sound/music/l_failed_1.wav 0\n" ); trap_SetConfigstring( CS_MUSIC_QUEUE, "" ); // clear queue so it'll be quiet after hit trap_SendServerCommand( -1, "cp missionfail0" ); } // never gib in a nodrop if ( self->health <= GIB_HEALTH && !( contents & CONTENTS_NODROP ) && g_blood.integer ) { // if(self->client->ps.eFlags & EF_HEADSHOT) // { // GibHead(self, killer); // } // else // gib death // { GibEntity( self, killer ); nogib = qfalse; // } } if ( nogib ) { // normal death static int i; switch ( i ) { case 0: anim = BOTH_DEATH1; break; case 1: anim = BOTH_DEATH2; break; case 2: default: anim = BOTH_DEATH3; break; } // for the no-blood option, we need to prevent the health // from going to gib level if ( self->health <= GIB_HEALTH ) { self->health = GIB_HEALTH + 1; } // JPW NERVE for medic self->client->medicHealAmt = 0; // jpw self->client->ps.legsAnim = ( ( self->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; self->client->ps.torsoAnim = ( ( self->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; G_AddEvent( self, EV_DEATH1 + 1, killer ); // the body can still be gibbed self->die = body_die; // globally cycle through the different death animations i = ( i + 1 ) % 3; } trap_LinkEntity( self ); if ( g_gametype.integer == GT_SINGLE_PLAYER ) { AICast_ScriptEvent( AICast_GetCastState( self->s.number ), "death", "" ); } }