static void Sniper_ResolveBlockedShot( void ) { if ( TIMER_Done( NPC, "duck" ) ) {//we're not ducking if ( TIMER_Done( NPC, "roamTime" ) ) {//not roaming //FIXME: try to find another spot from which to hit the enemy if ( (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) && (!NPCInfo->goalEntity || NPCInfo->goalEntity == NPC->enemy) ) {//we were running after enemy //Try to find a combat point that can hit the enemy int cpFlags = (CP_CLEAR|CP_HAS_ROUTE); if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST ) { cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST); cpFlags |= CP_NEAREST; } int cp = NPC_FindCombatPoint( NPC->currentOrigin, NPC->currentOrigin, NPC->currentOrigin, cpFlags, 32 ); if ( cp == -1 && !(NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) ) {//okay, try one by the enemy cp = NPC_FindCombatPoint( NPC->currentOrigin, NPC->currentOrigin, NPC->enemy->currentOrigin, CP_CLEAR|CP_HAS_ROUTE|CP_HORZ_DIST_COLL, 32 ); } //NOTE: there may be a perfectly valid one, just not one within CP_COLLECT_RADIUS of either me or him... if ( cp != -1 ) {//found a combat point that has a clear shot to enemy NPC_SetCombatPoint( cp ); NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp ); TIMER_Set( NPC, "duck", -1 ); if ( NPC->client->NPC_class == CLASS_SABOTEUR ) { Saboteur_Decloak( NPC ); } TIMER_Set( NPC, "attackDelay", Q_irand( 1000, 3000 ) ); return; } } } } /* else {//maybe we should stand if ( TIMER_Done( NPC, "stand" ) ) {//stand for as long as we'll be here TIMER_Set( NPC, "stand", Q_irand( 500, 2000 ) ); return; } } //Hmm, can't resolve this by telling them to duck or telling me to stand //We need to doMove! TIMER_Set( NPC, "roamTime", -1 ); TIMER_Set( NPC, "stick", -1 ); TIMER_Set( NPC, "duck", -1 ); TIMER_Set( NPC, "attackDelay", Q_irand( 1000, 3000 ) ); */ }
void NPC_Sniper_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, vec3_t point, int damage, int mod ) { self->NPC->localState = LSTATE_UNDERFIRE; if ( self->client->NPC_class == CLASS_SABOTEUR ) { Saboteur_Decloak( self ); } TIMER_Set( self, "duck", -1 ); TIMER_Set( self, "stand", 2000 ); NPC_Pain( self, inflictor, other, point, damage, mod ); if ( !damage && self->health > 0 ) {//FIXME: better way to know I was pushed G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 ); } }
void G_MissileImpacted( gentity_t *ent, gentity_t *other, vec3_t impactPos, vec3_t normal, int hitLoc=HL_NONE ) { // impact damage if ( other->takedamage ) { // FIXME: wrong damage direction? if ( ent->damage ) { vec3_t velocity; EvaluateTrajectoryDelta( &ent->s.pos, level.time, velocity ); if ( VectorLength( velocity ) == 0 ) { velocity[2] = 1; // stepped on a grenade } int damage = ent->damage; if( other->client ) { class_t npc_class = other->client->NPC_class; // If we are a robot and we aren't currently doing the full body electricity... if ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE || npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 || npc_class == CLASS_REMOTE || npc_class == CLASS_MARK1 || npc_class == CLASS_MARK2 || //npc_class == CLASS_PROTOCOL ||//no protocol, looks odd npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST || npc_class == CLASS_SENTRY ) { // special droid only behaviors if ( other->client->ps.powerups[PW_SHOCKED] < level.time + 100 ) { // ... do the effect for a split second for some more feedback other->s.powerups |= ( 1 << PW_SHOCKED ); other->client->ps.powerups[PW_SHOCKED] = level.time + 450; } //FIXME: throw some sparks off droids,too } } G_Damage( other, ent, ent->owner, velocity, impactPos, damage, ent->dflags, ent->methodOfDeath, hitLoc); if ( ent->s.weapon == WP_DEMP2 ) {//a hit with demp2 decloaks saboteurs if ( other && other->client && other->client->NPC_class == CLASS_SABOTEUR ) {//FIXME: make this disabled cloak hold for some amount of time? Saboteur_Decloak( other, Q_irand( 3000, 10000 ) ); if ( ent->methodOfDeath == MOD_DEMP2_ALT ) {//direct hit with alt disabled cloak forever if ( other->NPC ) {//permanently disable the saboteur's cloak other->NPC->aiFlags &= ~NPCAI_SHIELDS; } } } } } } // is it cheaper in bandwidth to just remove this ent and create a new // one, rather than changing the missile into the explosion? //G_FreeEntity(ent); if ( (other->takedamage && other->client ) || (ent->s.weapon == WP_FLECHETTE && other->contents&CONTENTS_LIGHTSABER) ) { G_AddEvent( ent, EV_MISSILE_HIT, DirToByte( normal ) ); ent->s.otherEntityNum = other->s.number; } else { G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( normal ) ); ent->s.otherEntityNum = other->s.number; } VectorCopy( normal, ent->pos1 ); if ( ent->owner )//&& ent->owner->s.number == 0 ) { //Add the event AddSoundEvent( ent->owner, ent->currentOrigin, 256, AEL_SUSPICIOUS, qfalse, qtrue ); AddSightEvent( ent->owner, ent->currentOrigin, 512, AEL_DISCOVERED, 75 ); } ent->freeAfterEvent = qtrue; // change over to a normal entity right at the point of impact ent->s.eType = ET_GENERAL; //SnapVectorTowards( trace->endpos, ent->s.pos.trBase ); // save net bandwidth VectorCopy( impactPos, ent->s.pos.trBase ); G_SetOrigin( ent, impactPos ); // splash damage (doesn't apply to person directly hit) if ( ent->splashDamage ) { G_RadiusDamage( impactPos, ent->owner, ent->splashDamage, ent->splashRadius, other, ent->splashMethodOfDeath ); } if ( ent->s.weapon == WP_NOGHRI_STICK ) { G_SpawnNoghriGasCloud( ent ); } gi.linkentity( ent ); }
void NPC_BSSniper_Attack( void ) { //Don't do anything if we're hurt if ( NPC->painDebounceTime > level.time ) { NPC_UpdateAngles( qtrue, qtrue ); return; } //NPC_CheckEnemy( qtrue, qfalse ); //If we don't have an enemy, just idle if ( NPC_CheckEnemyExt() == qfalse )//!NPC->enemy )// { NPC_BSSniper_Patrol();//FIXME: or patrol? return; } if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) ) {//going to run NPC_UpdateAngles( qtrue, qtrue ); return; } if ( !NPC->enemy ) {//WTF? somehow we lost our enemy? NPC_BSSniper_Patrol();//FIXME: or patrol? return; } enemyLOS = enemyCS = qfalse; doMove = qtrue; faceEnemy = qfalse; shoot = qfalse; enemyDist = DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); if ( enemyDist < 16384 )//128 squared {//too close, so switch to primary fire if ( NPC->client->ps.weapon == WP_DISRUPTOR || NPC->client->ps.weapon == WP_TUSKEN_RIFLE ) {//sniping... should be assumed if ( NPCInfo->scriptFlags & SCF_ALT_FIRE ) {//use primary fire trace_t trace; gi.trace ( &trace, NPC->enemy->currentOrigin, NPC->enemy->mins, NPC->enemy->maxs, NPC->currentOrigin, NPC->enemy->s.number, NPC->enemy->clipmask, (EG2_Collision)0, 0 ); if ( !trace.allsolid && !trace.startsolid && (trace.fraction == 1.0 || trace.entityNum == NPC->s.number ) ) {//he can get right to me NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; //reset fire-timing variables NPC_ChangeWeapon( NPC->client->ps.weapon ); NPC_UpdateAngles( qtrue, qtrue ); return; } } //FIXME: switch back if he gets far away again? } } else if ( enemyDist > 65536 )//256 squared { if ( NPC->client->ps.weapon == WP_DISRUPTOR || NPC->client->ps.weapon == WP_TUSKEN_RIFLE ) {//sniping... should be assumed if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) ) {//use primary fire NPCInfo->scriptFlags |= SCF_ALT_FIRE; //reset fire-timing variables NPC_ChangeWeapon( NPC->client->ps.weapon ); NPC_UpdateAngles( qtrue, qtrue ); return; } } } Sniper_UpdateEnemyPos(); //can we see our target? if ( NPC_ClearLOS( NPC->enemy ) )//|| (NPCInfo->stats.aim >= 5 && gi.inPVS( NPC->client->renderInfo.eyePoint, NPC->enemy->currentOrigin )) ) { NPCInfo->enemyLastSeenTime = level.time; VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation ); enemyLOS = qtrue; float maxShootDist = NPC_MaxDistSquaredForWeapon(); if ( enemyDist < maxShootDist ) { vec3_t fwd, right, up, muzzle, end; trace_t tr; AngleVectors( NPC->client->ps.viewangles, fwd, right, up ); CalcMuzzlePoint( NPC, fwd, right, up, muzzle, 0 ); VectorMA( muzzle, 8192, fwd, end ); gi.trace ( &tr, muzzle, NULL, NULL, end, NPC->s.number, MASK_SHOT, G2_RETURNONHIT, 0 ); int hit = tr.entityNum; //can we shoot our target? if ( Sniper_EvaluateShot( hit ) ) { enemyCS = qtrue; } } } /* else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) ) { NPCInfo->enemyLastSeenTime = level.time; faceEnemy = qtrue; } */ if ( enemyLOS ) {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot? faceEnemy = qtrue; } if ( !TIMER_Done( NPC, "taunting" ) ) { doMove = qfalse; shoot = qfalse; } else if ( enemyCS ) { shoot = qtrue; } else if ( level.time - NPCInfo->enemyLastSeenTime > 3000 ) {//Hmm, have to get around this bastard... FIXME: this NPCInfo->enemyLastSeenTime builds up when ducked seems to make them want to run when they uncrouch Sniper_ResolveBlockedShot(); } else if ( NPC->client->ps.weapon == WP_TUSKEN_RIFLE && !Q_irand( 0, 100 ) ) {//start a taunt NPC_Tusken_Taunt(); TIMER_Set( NPC, "duck", -1 ); doMove = qfalse; } //Check for movement to take care of Sniper_CheckMoveState(); //See if we should override shooting decision with any special considerations Sniper_CheckFireState(); if ( doMove ) {//doMove toward goal if ( NPCInfo->goalEntity )//&& ( NPCInfo->goalEntity != NPC->enemy || enemyDist > 10000 ) )//100 squared { doMove = Sniper_Move(); } else { doMove = qfalse; } } if ( !doMove ) { if ( !TIMER_Done( NPC, "duck" ) ) { if ( TIMER_Done( NPC, "watch" ) ) {//not while watching ucmd.upmove = -127; if ( NPC->client->NPC_class == CLASS_SABOTEUR ) { Saboteur_Cloak( NPC ); } } } //FIXME: what about leaning? //FIXME: also, when stop ducking, start looking, if enemy can see me, chance of ducking back down again } else {//stop ducking! TIMER_Set( NPC, "duck", -1 ); if ( NPC->client->NPC_class == CLASS_SABOTEUR ) { Saboteur_Decloak( NPC ); } } if ( TIMER_Done( NPC, "duck" ) && TIMER_Done( NPC, "watch" ) && (TIMER_Get( NPC, "attackDelay" )-level.time) > 1000 && NPC->attackDebounceTime < level.time ) { if ( enemyLOS && (NPCInfo->scriptFlags&SCF_ALT_FIRE) ) { if ( NPC->fly_sound_debounce_time < level.time ) { NPC->fly_sound_debounce_time = level.time + 2000; } } } if ( !faceEnemy ) {//we want to face in the dir we're running if ( doMove ) {//don't run away and shoot NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; NPCInfo->desiredPitch = 0; shoot = qfalse; } NPC_UpdateAngles( qtrue, qtrue ); } else// if ( faceEnemy ) {//face the enemy Sniper_FaceEnemy(); } if ( NPCInfo->scriptFlags&SCF_DONT_FIRE ) { shoot = qfalse; } //FIXME: don't shoot right away! if ( shoot ) {//try to shoot if it's time if ( TIMER_Done( NPC, "attackDelay" ) ) { WeaponThink( qtrue ); if ( ucmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK) ) { G_SoundOnEnt( NPC, CHAN_WEAPON, "sound/null.wav" ); } //took a shot, now hide if ( !(NPC->spawnflags&SPF_NO_HIDE) && !Q_irand( 0, 1 ) ) { //FIXME: do this if in combat point and combat point has duck-type cover... also handle lean-type cover Sniper_StartHide(); } else { TIMER_Set( NPC, "attackDelay", NPCInfo->shotTime-level.time ); } } } }
// NOTE: this is 100% for the demp2 alt-fire effect, so changes to the visual effect will affect game side demp2 code //-------------------------------------------------- void DEMP2_AltRadiusDamage( gentity_t *ent ) { float frac = ( level.time - ent->fx_time ) / 1300.0f; // synchronize with demp2 effect float dist, radius; gentity_t *gent; gentity_t *entityList[MAX_GENTITIES]; int numListedEntities, i, e; vec3_t mins, maxs; vec3_t v, dir; frac *= frac * frac; // yes, this is completely ridiculous...but it causes the shell to grow slowly then "explode" at the end radius = frac * 200.0f; // 200 is max radius...the model is aprox. 100 units tall...the fx draw code mults. this by 2. for ( i = 0 ; i < 3 ; i++ ) { mins[i] = ent->currentOrigin[i] - radius; maxs[i] = ent->currentOrigin[i] + radius; } numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for ( e = 0 ; e < numListedEntities ; e++ ) { gent = entityList[ e ]; if ( !gent->takedamage || !gent->contents ) { continue; } // find the distance from the edge of the bounding box for ( i = 0 ; i < 3 ; i++ ) { if ( ent->currentOrigin[i] < gent->absmin[i] ) { v[i] = gent->absmin[i] - ent->currentOrigin[i]; } else if ( ent->currentOrigin[i] > gent->absmax[i] ) { v[i] = ent->currentOrigin[i] - gent->absmax[i]; } else { v[i] = 0; } } // shape is an ellipsoid, so cut vertical distance in half` v[2] *= 0.5f; dist = VectorLength( v ); if ( dist >= radius ) { // shockwave hasn't hit them yet continue; } if ( dist < ent->radius ) { // shockwave has already hit this thing... continue; } VectorCopy( gent->currentOrigin, v ); VectorSubtract( v, ent->currentOrigin, dir); // push the center of mass higher than the origin so players get knocked into the air more dir[2] += 12; G_Damage( gent, ent, ent->owner, dir, ent->currentOrigin, weaponData[WP_DEMP2].altDamage, DAMAGE_DEATH_KNOCKBACK, ent->splashMethodOfDeath ); if ( gent->takedamage && gent->client ) { gent->s.powerups |= ( 1 << PW_SHOCKED ); gent->client->ps.powerups[PW_SHOCKED] = level.time + 2000; Saboteur_Decloak( gent, Q_irand( 3000, 10000 ) ); } } // store the last fraction so that next time around we can test against those things that fall between that last point and where the current shockwave edge is ent->radius = radius; if ( frac < 1.0f ) { // shock is still happening so continue letting it expand ent->nextthink = level.time + 50; } }