void NPC_BSGM_Attack( void ) { //Don't do anything if we're hurt if ( NPC->painDebounceTime > level.time ) { NPC_UpdateAngles( qtrue, qtrue ); return; } //FIXME: if killed enemy, use victory anim if ( NPC->enemy && NPC->enemy->health <= 0 && !NPC->enemy->s.number ) {//my enemy is dead if ( NPC->client->ps.torsoAnim == BOTH_STAND2TO1 ) { if ( NPC->client->ps.torsoAnimTimer <= 500 ) { G_AddVoiceEvent( NPC, Q_irand( EV_VICTORY1, EV_VICTORY3 ), 3000 ); NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsAnimTimer += 500; NPC->client->ps.torsoAnimTimer += 500; } } else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1START ) { if ( NPC->client->ps.torsoAnimTimer <= 500 ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1STARTGESTURE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsAnimTimer += 500; NPC->client->ps.torsoAnimTimer += 500; } } else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1STARTGESTURE ) { if ( NPC->client->ps.torsoAnimTimer <= 500 ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsAnimTimer += 500; NPC->client->ps.torsoAnimTimer += 500; } } else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1STOP ) { if ( NPC->client->ps.torsoAnimTimer <= 500 ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsAnimTimer = -1; NPC->client->ps.torsoAnimTimer = -1; } } else if ( NPC->wait ) { if ( TIMER_Done( NPC, "gloatTime" ) ) { GM_StartGloat(); } else if ( DistanceHorizontalSquared( NPC->client->renderInfo.eyePoint, NPC->enemy->currentOrigin ) > 4096 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//64 squared { NPCInfo->goalEntity = NPC->enemy; GM_Move(); } else {//got there GM_StartGloat(); } } NPC_FaceEnemy( qtrue ); NPC_UpdateAngles( qtrue, qtrue ); return; } //If we don't have an enemy, just idle if ( NPC_CheckEnemyExt() == qfalse || !NPC->enemy ) { NPC->enemy = NULL; NPC_BSGM_Patrol(); return; } enemyLOS = enemyCS = qfalse; bMove = qtrue; faceEnemy = qfalse; shoot = qfalse; hitAlly = qfalse; VectorClear( impactPos ); enemyDist = DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); if ( NPC->client->ps.torsoAnim == BOTH_ATTACK4 || NPC->client->ps.torsoAnim == BOTH_ATTACK5 ) { shoot = qfalse; if ( TIMER_Done( NPC, "smackTime" ) && !NPCInfo->blockedDebounceTime ) {//time to smack //recheck enemyDist and InFront if ( enemyDist < MELEE_DIST_SQUARED && InFront( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 0.3f ) ) { vec3_t smackDir; VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, smackDir ); smackDir[2] += 30; VectorNormalize( smackDir ); //hurt them G_Sound( NPC->enemy, G_SoundIndex( "sound/weapons/galak/skewerhit.wav" ) ); G_Damage( NPC->enemy, NPC, NPC, smackDir, NPC->currentOrigin, (g_spskill->integer+1)*Q_irand( 5, 10), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_CRUSH ); if ( NPC->client->ps.torsoAnim == BOTH_ATTACK4 ) {//smackdown int knockAnim = BOTH_KNOCKDOWN1; if ( PM_CrouchAnim( NPC->enemy->client->ps.legsAnim ) ) {//knockdown from crouch knockAnim = BOTH_KNOCKDOWN4; } //throw them smackDir[2] = 1; VectorNormalize( smackDir ); G_Throw( NPC->enemy, smackDir, 50 ); NPC_SetAnim( NPC->enemy, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } else {//uppercut //throw them G_Throw( NPC->enemy, smackDir, 100 ); //make them backflip NPC_SetAnim( NPC->enemy, SETANIM_BOTH, BOTH_KNOCKDOWN5, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } //done with the damage NPCInfo->blockedDebounceTime = 1; } } } else if ( NPC->lockCount ) //already shooting laser {//sometimes use the laser beam attack, but only after he's taken down our generator shoot = qfalse; if ( NPC->lockCount == 1 ) {//charging up if ( TIMER_Done( NPC, "beamDelay" ) ) {//time to start the beam int laserAnim; if ( Q_irand( 0, 1 ) ) { laserAnim = BOTH_ATTACK2; } else { laserAnim = BOTH_ATTACK7; } NPC_SetAnim( NPC, SETANIM_BOTH, laserAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoAnimTimer + Q_irand( 1000, 3000 ) ); //turn on beam effect NPC->lockCount = 2; G_PlayEffect( "galak/trace_beam", NPC->s.number ); NPC->s.loopSound = G_SoundIndex( "sound/weapons/galak/lasercutting.wav" ); if ( !NPCInfo->coverTarg ) {//for moving looping sound at end of trace NPCInfo->coverTarg = G_Spawn(); if ( NPCInfo->coverTarg ) { G_SetOrigin( NPCInfo->coverTarg, NPC->client->renderInfo.muzzlePoint ); NPCInfo->coverTarg->svFlags |= SVF_BROADCAST; NPCInfo->coverTarg->s.loopSound = G_SoundIndex( "sound/weapons/galak/lasercutting.wav" ); } } } } else {//in the actual attack now if ( !NPC->client->ps.torsoAnimTimer ) {//attack done! NPC->lockCount = 0; G_FreeEntity( NPCInfo->coverTarg ); NPC->s.loopSound = 0; NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_DROPWEAP2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoAnimTimer ); } else {//attack still going //do the trace and damage trace_t trace; vec3_t end, mins={-3,-3,-3}, maxs={3,3,3}; VectorMA( NPC->client->renderInfo.muzzlePoint, 1024, NPC->client->renderInfo.muzzleDir, end ); gi.trace( &trace, NPC->client->renderInfo.muzzlePoint, mins, maxs, end, NPC->s.number, MASK_SHOT ); if ( trace.allsolid || trace.startsolid ) {//oops, in a wall if ( NPCInfo->coverTarg ) { G_SetOrigin( NPCInfo->coverTarg, NPC->client->renderInfo.muzzlePoint ); } } else {//clear if ( trace.fraction < 1.0f ) {//hit something gentity_t *traceEnt = &g_entities[trace.entityNum]; if ( traceEnt && traceEnt->takedamage ) {//damage it G_SoundAtSpot( trace.endpos, G_SoundIndex( "sound/weapons/galak/laserdamage.wav" ) ); G_Damage( traceEnt, NPC, NPC, NPC->client->renderInfo.muzzleDir, trace.endpos, 10, 0, MOD_ENERGY ); } } if ( NPCInfo->coverTarg ) { G_SetOrigin( NPCInfo->coverTarg, trace.endpos ); } if ( !Q_irand( 0, 5 ) ) { G_SoundAtSpot( trace.endpos, G_SoundIndex( "sound/weapons/galak/laserdamage.wav" ) ); } } } } } else {//Okay, we're not in a special attack, see if we should switch weapons or start a special attack /* if ( NPC->s.weapon == WP_REPEATER && !(NPCInfo->scriptFlags & SCF_ALT_FIRE)//using rapid-fire && NPC->enemy->s.weapon == WP_SABER //enemy using saber && NPC->client && (NPC->client->ps.saberEventFlags&SEF_DEFLECTED) && !Q_irand( 0, 50 ) ) {//he's deflecting my shots, switch to the laser or the lob fire for a while TIMER_Set( NPC, "noRapid", Q_irand( 2000, 6000 ) ); NPCInfo->scriptFlags |= SCF_ALT_FIRE; NPC->alt_fire = qtrue; if ( NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH && (Q_irand( 0, 1 )||enemyDist < MAX_LOB_DIST_SQUARED) ) {//shield down, use laser NPC_GM_StartLaser(); } } else*/ if ( !NPC->client->ps.powerups[PW_GALAK_SHIELD] && enemyDist < MELEE_DIST_SQUARED && InFront( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 0.3f ) && G_StandardHumanoid( NPC->enemy->NPC_type ) )//within 80 and in front {//our shield is down, and enemy within 80, if very close, use melee attack to slap away if ( TIMER_Done( NPC, "attackDelay" ) ) { //animate me int swingAnim; if ( NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH ) {//generator down, use random melee swingAnim = Q_irand( BOTH_ATTACK4, BOTH_ATTACK5 );//smackdown or uppercut } else {//always knock-away swingAnim = BOTH_ATTACK5;//uppercut } //FIXME: swing sound NPC_SetAnim( NPC, SETANIM_BOTH, swingAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoAnimTimer + Q_irand( 1000, 3000 ) ); //delay the hurt until the proper point in the anim TIMER_Set( NPC, "smackTime", 600 ); NPCInfo->blockedDebounceTime = 0; //FIXME: say something? } } else if ( !NPC->lockCount && NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH && TIMER_Done( NPC, "attackDelay" ) && InFront( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 0.3f ) && ((!Q_irand( 0, 10*(2-g_spskill->integer))&& enemyDist > MIN_LOB_DIST_SQUARED&& enemyDist < MAX_LOB_DIST_SQUARED) ||(!TIMER_Done( NPC, "noLob" )&&!TIMER_Done( NPC, "noRapid" ))) && NPC->enemy->s.weapon != WP_TURRET ) {//sometimes use the laser beam attack, but only after he's taken down our generator shoot = qfalse; NPC_GM_StartLaser(); } else if ( enemyDist < MIN_LOB_DIST_SQUARED && (NPC->enemy->s.weapon != WP_TURRET || Q_stricmp( "PAS", NPC->enemy->classname )) && TIMER_Done( NPC, "noRapid" ) )//256 {//enemy within 256 if ( (NPC->client->ps.weapon == WP_REPEATER) && (NPCInfo->scriptFlags & SCF_ALT_FIRE) ) {//shooting an explosive, but enemy too close, switch to primary fire NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; NPC->alt_fire = qfalse; //FIXME: use weap raise & lower anims NPC_ChangeWeapon( WP_REPEATER ); } } else if ( (enemyDist > MAX_LOB_DIST_SQUARED || (NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ))) && TIMER_Done( NPC, "noLob" ) )//448 {//enemy more than 448 away and we are ready to try lob fire again if ( (NPC->client->ps.weapon == WP_REPEATER) && !(NPCInfo->scriptFlags & SCF_ALT_FIRE) ) {//enemy far enough away to use lobby explosives NPCInfo->scriptFlags |= SCF_ALT_FIRE; NPC->alt_fire = qtrue; //FIXME: use weap raise & lower anims NPC_ChangeWeapon( WP_REPEATER ); } } } //can we see our target? if ( NPC_ClearLOS( NPC->enemy ) ) { NPCInfo->enemyLastSeenTime = level.time;//used here for aim debouncing, not always a clear LOS enemyLOS = qtrue; if ( NPC->client->ps.weapon == WP_NONE ) { enemyCS = qfalse;//not true, but should stop us from firing NPC_AimAdjust( -1 );//adjust aim worse longer we have no weapon } else {//can we shoot our target? if ( ((NPC->client->ps.weapon == WP_REPEATER && (NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist < MIN_LOB_DIST_SQUARED )//256 { enemyCS = qfalse;//not true, but should stop us from firing hitAlly = qtrue;//us! //FIXME: if too close, run away! } else { int hit = NPC_ShotEntity( NPC->enemy, impactPos ); gentity_t *hitEnt = &g_entities[hit]; if ( hit == NPC->enemy->s.number || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) || ( hitEnt && hitEnt->takedamage ) ) {//can hit enemy or will hit glass or other breakable, so shoot anyway enemyCS = qtrue; NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation ); } else {//Hmm, have to get around this bastard NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam ) {//would hit an ally, don't fire!!! hitAlly = qtrue; } else {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire } } } } } else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) ) { if ( TIMER_Done( NPC, "talkDebounce" ) && !Q_irand( 0, 10 ) ) { if ( NPCInfo->enemyCheckDebounceTime < 8 ) { int speech = -1; switch( NPCInfo->enemyCheckDebounceTime ) { case 0: case 1: case 2: speech = EV_CHASE1 + NPCInfo->enemyCheckDebounceTime; break; case 3: case 4: case 5: speech = EV_COVER1 + NPCInfo->enemyCheckDebounceTime-3; break; case 6: case 7: speech = EV_ESCAPING1 + NPCInfo->enemyCheckDebounceTime-6; break; } NPCInfo->enemyCheckDebounceTime++; if ( speech != -1 ) { G_AddVoiceEvent( NPC, speech, Q_irand( 3000, 5000 ) ); TIMER_Set( NPC, "talkDebounce", Q_irand( 5000, 7000 ) ); } } } NPCInfo->enemyLastSeenTime = level.time; int hit = NPC_ShotEntity( NPC->enemy, impactPos ); gentity_t *hitEnt = &g_entities[hit]; if ( hit == NPC->enemy->s.number || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) || ( hitEnt && hitEnt->takedamage ) ) {//can hit enemy or will hit glass or other breakable, so shoot anyway enemyCS = qtrue; } else { faceEnemy = qtrue; NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy } } if ( enemyLOS ) { faceEnemy = qtrue; } else { if ( !NPCInfo->goalEntity ) { NPCInfo->goalEntity = NPC->enemy; } if ( NPCInfo->goalEntity == NPC->enemy ) {//for now, always chase the enemy bMove = qtrue; } } if ( enemyCS ) { shoot = qtrue; //NPCInfo->enemyCheckDebounceTime = level.time;//actually used here as a last actual LOS } else { if ( !NPCInfo->goalEntity ) { NPCInfo->goalEntity = NPC->enemy; } if ( NPCInfo->goalEntity == NPC->enemy ) {//for now, always chase the enemy bMove = qtrue; } } //Check for movement to take care of GM_CheckMoveState(); //See if we should override shooting decision with any special considerations GM_CheckFireState(); if ( NPC->client->ps.weapon == WP_REPEATER && (NPCInfo->scriptFlags&SCF_ALT_FIRE) && shoot && TIMER_Done( NPC, "attackDelay" ) ) { vec3_t muzzle; vec3_t angles; vec3_t target; vec3_t velocity = {0,0,0}; vec3_t mins = {-REPEATER_ALT_SIZE,-REPEATER_ALT_SIZE,-REPEATER_ALT_SIZE}, maxs = {REPEATER_ALT_SIZE,REPEATER_ALT_SIZE,REPEATER_ALT_SIZE}; CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); VectorCopy( NPC->enemy->currentOrigin, target ); target[0] += Q_flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2); target[1] += Q_flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2); target[2] += Q_flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2); //Find the desired angles qboolean clearshot = WP_LobFire( NPC, muzzle, target, mins, maxs, MASK_SHOT|CONTENTS_LIGHTSABER, velocity, qtrue, NPC->s.number, NPC->enemy->s.number, 300, 1100, 1500, qtrue ); if ( VectorCompare( vec3_origin, velocity ) || (!clearshot&&enemyLOS&&enemyCS) ) {//no clear lob shot and no lob shot that will hit something breakable if ( enemyLOS && enemyCS && TIMER_Done( NPC, "noRapid" ) ) {//have a clear straight shot, so switch to primary NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; NPC->alt_fire = qfalse; NPC_ChangeWeapon( WP_REPEATER ); //keep this weap for a bit TIMER_Set( NPC, "noLob", Q_irand( 500, 1000 ) ); } else { shoot = qfalse; } } else { vectoangles( velocity, angles ); NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] ); VectorCopy( velocity, NPC->client->hiddenDir ); NPC->client->hiddenDist = VectorNormalize ( NPC->client->hiddenDir ); } } else if ( faceEnemy ) {//face the enemy NPC_FaceEnemy( qtrue ); } if ( !TIMER_Done( NPC, "standTime" ) ) { bMove = qfalse; } if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) {//not supposed to chase my enemies if ( NPCInfo->goalEntity == NPC->enemy ) {//goal is my entity, so don't bMove bMove = qfalse; } } if ( bMove && !NPC->lockCount ) {//bMove toward goal if ( NPCInfo->goalEntity && NPC->client->ps.legsAnim != BOTH_ALERT1 && NPC->client->ps.legsAnim != BOTH_ATTACK2 && NPC->client->ps.legsAnim != BOTH_ATTACK4 && NPC->client->ps.legsAnim != BOTH_ATTACK5 && NPC->client->ps.legsAnim != BOTH_ATTACK7 ) { bMove = GM_Move(); } else { bMove = qfalse; } } if ( !TIMER_Done( NPC, "flee" ) ) {//running away faceEnemy = qfalse; } //FIXME: check scf_face_move_dir here? if ( !faceEnemy ) {//we want to face in the dir we're running if ( !bMove ) {//if we haven't moved, we should look in the direction we last looked? VectorCopy( NPC->client->ps.viewangles, NPCInfo->lastPathAngles ); } if ( bMove ) {//don't run away and shoot NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; NPCInfo->desiredPitch = 0; shoot = qfalse; } } NPC_UpdateAngles( qtrue, qtrue ); if ( NPCInfo->scriptFlags & SCF_DONT_FIRE ) { shoot = qfalse; } if ( NPC->enemy && NPC->enemy->enemy ) { if ( NPC->enemy->s.weapon == WP_SABER && NPC->enemy->enemy->s.weapon == WP_SABER ) {//don't shoot at an enemy jedi who is fighting another jedi, for fear of injuring one or causing rogue blaster deflections (a la Obi Wan/Vader duel at end of ANH) shoot = qfalse; } } //FIXME: don't shoot right away! if ( shoot ) {//try to shoot if it's time if ( TIMER_Done( NPC, "attackDelay" ) ) { if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here { WeaponThink( qtrue ); } } } //also: if ( NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ) ) {//crush turrets if ( G_BoundsOverlap( NPC->absmin, NPC->absmax, NPC->enemy->absmin, NPC->enemy->absmax ) ) {//have to do this test because placed turrets are not solid to NPCs (so they don't obstruct navigation) if ( NPC->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) { NPC->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME; G_Damage( NPC->enemy, NPC, NPC, NULL, NPC->currentOrigin, 100, DAMAGE_NO_KNOCKBACK, MOD_ELECTROCUTE ); } else { G_Damage( NPC->enemy, NPC, NPC, NULL, NPC->currentOrigin, 100, DAMAGE_NO_KNOCKBACK, MOD_CRUSH ); } } } else if ( NPCInfo->touchedByPlayer != NULL && NPCInfo->touchedByPlayer == NPC->enemy ) {//touched enemy if ( NPC->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) {//zap him! //animate me NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK6, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoAnimTimer ); TIMER_Set( NPC, "standTime", NPC->client->ps.legsAnimTimer ); //FIXME: debounce this? NPCInfo->touchedByPlayer = NULL; //FIXME: some shield effect? NPC->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME; vec3_t smackDir; VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, smackDir ); smackDir[2] += 30; VectorNormalize( smackDir ); G_Damage( NPC->enemy, NPC, NPC, smackDir, NPC->currentOrigin, (g_spskill->integer+1)*Q_irand( 5, 10), DAMAGE_NO_KNOCKBACK, MOD_ELECTROCUTE ); //throw them G_Throw( NPC->enemy, smackDir, 100 ); NPC->enemy->s.powerups |= ( 1 << PW_SHOCKED ); if ( NPC->enemy->client ) { NPC->enemy->client->ps.powerups[PW_SHOCKED] = level.time + 1000; } //stop any attacks ucmd.buttons = 0; } } if ( NPCInfo->movementSpeech < 3 && NPCInfo->blockedSpeechDebounceTime <= level.time ) { if ( NPC->enemy && NPC->enemy->health > 0 && NPC->enemy->painDebounceTime > level.time ) { if ( NPC->enemy->health < 50 && NPCInfo->movementSpeech == 2 ) { G_AddVoiceEvent( NPC, EV_ANGER2, Q_irand( 2000, 4000 ) ); NPCInfo->movementSpeech = 3; } else if ( NPC->enemy->health < 75 && NPCInfo->movementSpeech == 1 ) { G_AddVoiceEvent( NPC, EV_ANGER1, Q_irand( 2000, 4000 ) ); NPCInfo->movementSpeech = 2; } else if ( NPC->enemy->health < 100 && NPCInfo->movementSpeech == 0 ) { G_AddVoiceEvent( NPC, EV_ANGER3, Q_irand( 2000, 4000 ) ); NPCInfo->movementSpeech = 1; } } } }
//---------------------------------------------------------- void emplaced_gun_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { vec3_t fwd1, fwd2; if ( self->health <= 0 ) { // can't use a dead gun. return; } if ( self->svFlags & SVF_INACTIVE ) { return; // can't use inactive gun } if ( !activator->client ) { return; // only a client can use it. } if ( self->activator ) { // someone is already in the gun. return; } if ( other && other->client && G_IsRidingVehicle( other ) ) {//can't use eweb when on a vehicle return; } if ( activator && activator->client && G_IsRidingVehicle( activator ) ) {//can't use eweb when on a vehicle return; } // We'll just let the designers duke this one out....I mean, as to whether they even want to limit such a thing. if ( self->spawnflags & EMPLACED_FACING ) { // Let's get some direction vectors for the users AngleVectors( activator->client->ps.viewangles, fwd1, NULL, NULL ); // Get the guns direction vector AngleVectors( self->pos1, fwd2, NULL, NULL ); float dot = DotProduct( fwd1, fwd2 ); // Must be reasonably facing the way the gun points ( 90 degrees or so ), otherwise we don't allow to use it. if ( dot < 0.0f ) { return; } } // don't allow using it again for half a second if ( self->delay + 500 < level.time ) { int oldWeapon = activator->s.weapon; if ( oldWeapon == WP_SABER ) { self->alt_fire = activator->client->ps.SaberActive(); } // swap the users weapon with the emplaced gun and add the ammo the gun has to the player activator->client->ps.weapon = self->s.weapon; Add_Ammo( activator, WP_EMPLACED_GUN, self->count ); activator->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_EMPLACED_GUN ); // Allow us to point from one to the other activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it. self->activator = activator; G_RemoveWeaponModels( activator ); extern void ChangeWeapon( gentity_t *ent, int newWeapon ); if ( activator->NPC ) { ChangeWeapon( activator, WP_EMPLACED_GUN ); } else if ( activator->s.number == 0 ) { // we don't want for it to draw the weapon select stuff cg.weaponSelect = WP_EMPLACED_GUN; CG_CenterPrint( "@SP_INGAME_EXIT_VIEW", SCREEN_HEIGHT * 0.95 ); } // Since we move the activator inside of the gun, we reserve a solid spot where they were standing in order to be able to get back out without being in solid if ( self->nextTrain ) {//you never know G_FreeEntity( self->nextTrain ); } self->nextTrain = G_Spawn(); //self->nextTrain->classname = "emp_placeholder"; self->nextTrain->contents = CONTENTS_MONSTERCLIP|CONTENTS_PLAYERCLIP;//hmm... playerclip too now that we're doing it for NPCs? G_SetOrigin( self->nextTrain, activator->client->ps.origin ); VectorCopy( activator->mins, self->nextTrain->mins ); VectorCopy( activator->maxs, self->nextTrain->maxs ); gi.linkentity( self->nextTrain ); //need to inflate the activator's mins/maxs since the gunsit anim puts them outside of their bbox VectorSet( activator->mins, -24, -24, -24 ); VectorSet( activator->maxs, 24, 24, 40 ); // Move the activator into the center of the gun. For NPC's the only way the can get out of the gun is to die. VectorCopy( self->s.origin, activator->client->ps.origin ); activator->client->ps.origin[2] += 30; // move them up so they aren't standing in the floor gi.linkentity( activator ); // the gun will track which weapon we used to have self->s.weapon = oldWeapon; // Lock the player activator->client->ps.eFlags |= EF_LOCKED_TO_WEAPON; activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it. self->activator = activator; self->delay = level.time; // can't disconnect from the thing for half a second // Let the gun be considered an enemy //Ugh, so much AI code seems to assume enemies are clients, maybe this shouldn't be on, but it's too late in the game to change it now without knowing what side-effects this will have self->svFlags |= SVF_NONNPC_ENEMY; self->noDamageTeam = activator->client->playerTeam; // FIXME: don't do this, we'll try and actually put the player in this beast // move the player to the center of the gun // activator->contents = 0; // VectorCopy( self->currentOrigin, activator->client->ps.origin ); SetClientViewAngle( activator, self->pos1 ); //FIXME: should really wait a bit after spawn and get this just once? self->waypoint = NAV::GetNearestNode(self); #ifdef _DEBUG if ( self->waypoint == -1 ) { gi.Printf( S_COLOR_RED"ERROR: no waypoint for emplaced_gun %s at %s\n", self->targetname, vtos(self->currentOrigin) ); } #endif G_Sound( self, G_SoundIndex( "sound/weapons/emplaced/emplaced_mount.mp3" )); #ifdef _IMMERSION G_Force( self, G_ForceIndex( "fffx/weapons/emplaced/emplaced_mount", FF_CHANNEL_TOUCH ) ); #endif // _IMMERSION if ( !(self->spawnflags&EMPLACED_PLAYERUSE) || activator->s.number == 0 ) {//player-only usescript or any usescript // Run use script G_ActivateBehavior( self, BSET_USE ); } } }
void NPC_Touch(gentity_t *self, gentity_t *other, trace_t *trace) { if(!self->NPC) return; SaveNPCGlobals(); SetNPCGlobals( self ); if ( self->message && self->health <= 0 ) {//I am dead and carrying a key if ( other && player && player->health > 0 && other == player ) {//player touched me char *text; qboolean keyTaken; //give him my key if ( Q_stricmp( "goodie", self->message ) == 0 ) {//a goodie key if ( (keyTaken = INV_GoodieKeyGive( other )) == qtrue ) { text = "cp @INGAME_TOOK_IMPERIAL_GOODIE_KEY"; G_AddEvent( other, EV_ITEM_PICKUP, (FindItemForInventory( INV_GOODIE_KEY )-bg_itemlist) ); } else { text = "cp @INGAME_CANT_CARRY_GOODIE_KEY"; } } else {//a named security key if ( (keyTaken = INV_SecurityKeyGive( player, self->message )) == qtrue ) { text = "cp @INGAME_TOOK_IMPERIAL_SECURITY_KEY"; G_AddEvent( other, EV_ITEM_PICKUP, (FindItemForInventory( INV_SECURITY_KEY )-bg_itemlist) ); } else { text = "cp @INGAME_CANT_CARRY_SECURITY_KEY"; } } if ( keyTaken ) {//remove my key gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "l_arm_key", 0x00000002 ); self->message = NULL; //FIXME: temp pickup sound G_Sound( player, G_SoundIndex( "sound/weapons/key_pkup.wav" ) ); //FIXME: need some event to pass to cgame for sound/graphic/message? } //FIXME: temp message gi.SendServerCommand( NULL, text ); } } if ( other->client ) {//FIXME: if pushing against another bot, both ucmd.rightmove = 127??? //Except if not facing one another... if ( other->health > 0 ) { NPCInfo->touchedByPlayer = other; } if ( other == NPCInfo->goalEntity ) { NPCInfo->aiFlags |= NPCAI_TOUCHED_GOAL; } if( !(self->svFlags&SVF_LOCKEDENEMY) && !(self->svFlags&SVF_IGNORE_ENEMIES) && !(other->flags & FL_NOTARGET) ) { if ( self->client->enemyTeam ) {//See if we bumped into an enemy if ( other->client->playerTeam == self->client->enemyTeam ) {//bumped into an enemy if( NPCInfo->behaviorState != BS_HUNT_AND_KILL && !NPCInfo->tempBehavior ) {//MCG - Begin: checking specific BS mode here, this is bad, a HACK //FIXME: not medics? if ( NPC->enemy != other ) {//not already mad at them G_SetEnemy( NPC, other ); } // NPCInfo->tempBehavior = BS_HUNT_AND_KILL; } } } } //FIXME: do this if player is moving toward me and with a certain dist? /* if ( other->s.number == 0 && self->client->playerTeam == other->client->playerTeam ) { VectorAdd( self->client->pushVec, other->client->ps.velocity, self->client->pushVec ); } */ } else {//FIXME: check for SVF_NONNPC_ENEMY flag here? if ( other->health > 0 ) { if ( NPC->enemy == other && (other->svFlags&SVF_NONNPC_ENEMY) ) { NPCInfo->touchedByPlayer = other; } } if ( other == NPCInfo->goalEntity ) { NPCInfo->aiFlags |= NPCAI_TOUCHED_GOAL; } } RestoreNPCGlobals(); }
/* ------------------------- Mark1_FireBlaster - Shoot the left weapon, the multi-blaster ------------------------- */ void Mark1_FireBlaster(void) { vec3_t muzzle1,enemy_org1,delta1,angleToEnemy1; static vec3_t forward, vright, up; static vec3_t muzzle; gentity_t *missile; mdxaBone_t boltMatrix; int bolt; // Which muzzle to fire from? if ((NPCInfo->localState <= LSTATE_FIRED0) || (NPCInfo->localState == LSTATE_FIRED4)) { NPCInfo->localState = LSTATE_FIRED1; bolt = NPC->genericBolt1; } else if (NPCInfo->localState == LSTATE_FIRED1) { NPCInfo->localState = LSTATE_FIRED2; bolt = NPC->genericBolt2; } else if (NPCInfo->localState == LSTATE_FIRED2) { NPCInfo->localState = LSTATE_FIRED3; bolt = NPC->genericBolt3; } else { NPCInfo->localState = LSTATE_FIRED4; bolt = NPC->genericBolt4; } gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel, bolt, &boltMatrix, NPC->currentAngles, NPC->currentOrigin, (cg.time?cg.time:level.time), NULL, NPC->s.modelScale ); gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, muzzle1 ); if (NPC->health) { CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org1 ); VectorSubtract (enemy_org1, muzzle1, delta1); vectoangles ( delta1, angleToEnemy1 ); AngleVectors (angleToEnemy1, forward, vright, up); } else { AngleVectors (NPC->currentAngles, forward, vright, up); } G_PlayEffect( "bryar/muzzle_flash", muzzle1, forward ); G_Sound( NPC, G_SoundIndex("sound/chars/mark1/misc/mark1_fire")); missile = CreateMissile( muzzle1, forward, 1600, 10000, NPC ); missile->classname = "bryar_proj"; missile->s.weapon = WP_BRYAR_PISTOL; missile->damage = 1; missile->dflags = DAMAGE_DEATH_KNOCKBACK; missile->methodOfDeath = MOD_ENERGY; missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; }
void RunEmplacedWeapon( gentity_t *ent, usercmd_t **ucmd ) { if (( (*ucmd)->buttons & BUTTON_USE || (*ucmd)->forwardmove < 0 || (*ucmd)->upmove > 0 ) && ent->owner && ent->owner->delay + 500 < level.time ) { ent->owner->s.loopSound = 0; if ( ent->owner->e_UseFunc == useF_eweb_use )//yeah, crappy way to check this, but... { G_Sound( ent, G_SoundIndex( "sound/weapons/eweb/eweb_dismount.mp3" )); } else { G_Sound( ent, G_SoundIndex( "sound/weapons/emplaced/emplaced_dismount.mp3" )); } #ifdef _IMMERSION G_Force( ent, G_ForceIndex( "fffx/weapons/emplaced/emplaced_dismount", FF_CHANNEL_TOUCH ) ); #endif // _IMMERSION ExitEmplacedWeapon( ent ); (*ucmd)->buttons &= ~BUTTON_USE; if ( (*ucmd)->upmove > 0 ) {//don't actually jump (*ucmd)->upmove = 0; } } else { // this is a crappy way to put sounds on a moving eweb.... if ( ent->owner && ent->owner->e_UseFunc == useF_eweb_use )//yeah, crappy way to check this, but... { if ( !VectorCompare( ent->client->ps.viewangles, ent->owner->movedir )) { ent->owner->s.loopSound = G_SoundIndex( "sound/weapons/eweb/eweb_aim.wav" ); ent->owner->fly_sound_debounce_time = level.time; } else { if ( ent->owner->fly_sound_debounce_time + 100 <= level.time ) { ent->owner->s.loopSound = 0; } } VectorCopy( ent->client->ps.viewangles, ent->owner->movedir ); } // don't allow movement, weapon switching, and most kinds of button presses (*ucmd)->forwardmove = 0; (*ucmd)->rightmove = 0; (*ucmd)->upmove = 0; (*ucmd)->buttons &= (BUTTON_ATTACK|BUTTON_ALT_ATTACK); (*ucmd)->weapon = ent->client->ps.weapon; //WP_EMPLACED_GUN; if ( ent->health <= 0 ) { ExitEmplacedWeapon( ent ); } } }
void fx_rain_think( gentity_t *ent ) { if (player) { if (ent->count!=0) { ent->count--; if (ent->count==0 || (ent->count%2)==0) { gi.WE_SetTempGlobalFogColor(ent->pos2); // Turn Off if (ent->count==0) { ent->nextthink = level.time + Q_irand(1000, 12000); } else if (ent->count==2) { ent->nextthink = level.time + Q_irand(150, 450); } else { ent->nextthink = level.time + Q_irand(50, 150); } } else { gi.WE_SetTempGlobalFogColor(ent->pos3); // Turn On ent->nextthink = level.time + 50; } } else if (gi.WE_IsOutside(player->currentOrigin)) { vec3_t effectPos; vec3_t effectDir; VectorClear(effectDir); effectDir[0] += Q_flrand(-1.0f, 1.0f); effectDir[1] += Q_flrand(-1.0f, 1.0f); bool PlayEffect = Q_irand(1,ent->aimDebounceTime)==1; bool PlayFlicker = Q_irand(1,ent->attackDebounceTime)==1; bool PlaySound = (PlayEffect || PlayFlicker || Q_irand(1,ent->pushDebounceTime)==1); // Play The Sound //---------------- if (PlaySound && !PlayEffect) { VectorMA(player->currentOrigin, 250.0f, effectDir, effectPos); G_SoundAtSpot(effectPos, G_SoundIndex(va("sound/ambience/thunder%d", Q_irand(1,4))), qtrue); } // Play The Effect //----------------- if (PlayEffect) { VectorMA(player->currentOrigin, 400.0f, effectDir, effectPos); if (PlaySound) { G_Sound(player, G_SoundIndex(va("sound/ambience/thunder_close%d", Q_irand(1,2)))); } // Raise It Up Into The Sky //-------------------------- effectPos[2] += Q_flrand(600.0f, 1000.0f); VectorClear(effectDir); effectDir[2] = -1.0f; G_PlayEffect("env/huge_lightning", effectPos, effectDir); ent->nextthink = level.time + Q_irand(100, 200); } // Change The Fog Color //---------------------- if (PlayFlicker) { ent->count = (Q_irand(1,4) * 2); ent->nextthink = level.time + 50; gi.WE_SetTempGlobalFogColor(ent->pos3); } else { ent->nextthink = level.time + Q_irand(1000, ent->delay); } } else { ent->nextthink = level.time + Q_irand(1000, ent->delay); } } else { ent->nextthink = level.time + Q_irand(1000, ent->delay); } }
void Pilot_Steer_Vehicle() { if (!NPC->enemy || !NPC->enemy->client) { return; } // SETUP //======= // Setup Actor Data //------------------ CVec3 ActorPos(NPC->currentOrigin); CVec3 ActorAngles(NPC->currentAngles); ActorAngles[2] = 0; Vehicle_t* ActorVeh = NPCInfo->greetEnt->m_pVehicle; bool ActorInTurbo = (ActorVeh->m_iTurboTime>level.time); float ActorSpeed = (ActorVeh)?(VectorLength(ActorVeh->m_pParentEntity->client->ps.velocity)):(NPC->client->ps.speed); // If my vehicle is spinning out of control, just hold on, we're going to die!!!!! //--------------------------------------------------------------------------------- if (ActorVeh && (ActorVeh->m_ulFlags & VEH_OUTOFCONTROL)) { if (NPC->client->ps.weapon!=WP_NONE) { NPC_ChangeWeapon(WP_NONE); } ucmd.buttons &=~BUTTON_ATTACK; ucmd.buttons &=~BUTTON_ALT_ATTACK; return; } CVec3 ActorDirection; AngleVectors(ActorAngles.v, ActorDirection.v, 0, 0); CVec3 ActorFuturePos(ActorPos); ActorFuturePos.ScaleAdd(ActorDirection, FUTURE_PRED_DIST); bool ActorDoTurbo = false; bool ActorAccelerate = false; bool ActorAimAtTarget= true; float ActorYawOffset = 0.0f; // Setup Enemy Data //------------------ CVec3 EnemyPos(NPC->enemy->currentOrigin); CVec3 EnemyAngles(NPC->enemy->currentAngles); EnemyAngles[2] = 0; Vehicle_t* EnemyVeh = (NPC->enemy->s.m_iVehicleNum)?(g_entities[NPC->enemy->s.m_iVehicleNum].m_pVehicle):(0); bool EnemyInTurbo = (EnemyVeh && EnemyVeh->m_iTurboTime>level.time); float EnemySpeed = (EnemyVeh)?(EnemyVeh->m_pParentEntity->client->ps.speed):(NPC->enemy->resultspeed); bool EnemySlideBreak = (EnemyVeh && (EnemyVeh->m_ulFlags&VEH_SLIDEBREAKING || EnemyVeh->m_ulFlags&VEH_STRAFERAM)); bool EnemyDead = (NPC->enemy->health<=0); bool ActorFlank = (NPCInfo->lastAvoidSteerSideDebouncer>level.time && EnemyVeh && EnemySpeed>10.0f); CVec3 EnemyDirection; CVec3 EnemyRight; AngleVectors(EnemyAngles.v, EnemyDirection.v, EnemyRight.v, 0); CVec3 EnemyFuturePos(EnemyPos); EnemyFuturePos.ScaleAdd(EnemyDirection, FUTURE_PRED_DIST); ESide EnemySide = ActorPos.LRTest(EnemyPos, EnemyFuturePos); CVec3 EnemyFlankPos(EnemyFuturePos); EnemyFlankPos.ScaleAdd(EnemyRight, (EnemySide==Side_Right)?(FUTURE_SIDE_DIST):(-FUTURE_SIDE_DIST)); // Debug Draw Enemy Data //----------------------- if (false) { CG_DrawEdge(EnemyPos.v, EnemyFuturePos.v, EDGE_IMPACT_SAFE); CG_DrawEdge(EnemyFuturePos.v, EnemyFlankPos.v, EDGE_IMPACT_SAFE); } // Setup Move And Aim Directions //------------------------------- CVec3 MoveDirection((ActorFlank)?(EnemyFlankPos):(EnemyFuturePos)); MoveDirection -= ActorPos; float MoveDistance = MoveDirection.SafeNorm(); float MoveAccuracy = MoveDirection.Dot(ActorDirection); CVec3 AimDirection(EnemyPos); AimDirection -= ActorPos; float AimDistance = AimDirection.SafeNorm(); float AimAccuracy = AimDirection.Dot(ActorDirection); if (!ActorFlank && TIMER_Done(NPC, "FlankAttackCheck")) { TIMER_Set(NPC, "FlankAttackCheck", Q_irand(1000, 3000)); if (MoveDistance<4000 && Q_irand(0, 1)==0) { NPCInfo->lastAvoidSteerSideDebouncer = level.time + Q_irand(8000, 14000); } } // Fly By Sounds //--------------- if ((ActorVeh->m_pVehicleInfo->soundFlyBy || ActorVeh->m_pVehicleInfo->soundFlyBy2) && EnemyVeh && MoveDistance<800 && ActorSpeed>500.0f && TIMER_Done(NPC, "FlybySoundDebouncer") ) { if (EnemySpeed<100.0f || (ActorDirection.Dot(EnemyDirection)*(MoveDistance/800.0f))<-0.5f) { TIMER_Set(NPC, "FlybySoundDebouncer", 2000); int soundFlyBy = ActorVeh->m_pVehicleInfo->soundFlyBy; if (ActorVeh->m_pVehicleInfo->soundFlyBy2 && (!soundFlyBy || !Q_irand(0,1))) { soundFlyBy = ActorVeh->m_pVehicleInfo->soundFlyBy2; } G_Sound(ActorVeh->m_pParentEntity, soundFlyBy); } } // FLY PAST BEHAVIOR //=================== if (EnemySlideBreak || !TIMER_Done(NPC, "MinHoldDirectionTime")) { if (TIMER_Done(NPC, "MinHoldDirectionTime")) { TIMER_Set(NPC, "MinHoldDirectionTime", 500); // Hold For At Least 500 ms } ActorAccelerate = true; // Go ActorAimAtTarget = false; // Don't Alter Our Aim Direction ucmd.buttons &=~BUTTON_VEH_SPEED; // Let Normal Vehicle Controls Go } // FLANKING BEHAVIOR //=================== else if (ActorFlank) { ActorAccelerate = true; ActorDoTurbo = (MoveDistance>2500 || EnemyInTurbo); ucmd.buttons |= BUTTON_VEH_SPEED; // Tells PMove to use the ps.speed we calculate here, not the one from g_vehicles.c // For Flanking, We Calculate The Speed By Hand, Rather Than Using Pure Accelerate / No Accelerate Functionality //--------------------------------------------------------------------------------------------------------------- NPC->client->ps.speed = ActorVeh->m_pVehicleInfo->speedMax * ((ActorInTurbo)?(1.35f):(1.15f)); // If In Slowing Distance, Scale Down The Speed As We Approach Our Move Target //----------------------------------------------------------------------------- if (MoveDistance<ATTACK_FLANK_SLOWING) { NPC->client->ps.speed *= (MoveDistance/ATTACK_FLANK_SLOWING); NPC->client->ps.speed += EnemySpeed; // Match Enemy Speed //------------------- if (NPC->client->ps.speed<5.0f && EnemySpeed<5.0f) { NPC->client->ps.speed = EnemySpeed; } // Extra Slow Down When Out In Front //----------------------------------- if (MoveAccuracy<0.0f) { NPC->client->ps.speed *= (MoveAccuracy + 1.0f); } MoveDirection *= (MoveDistance/ATTACK_FLANK_SLOWING); EnemyDirection *= 1.0f - (MoveDistance/ATTACK_FLANK_SLOWING); MoveDirection += EnemyDirection; if (TIMER_Done(NPC, "RamCheck")) { TIMER_Set(NPC, "RamCheck", Q_irand(1000, 3000)); if (MoveDistance<RAM_DIST && Q_irand(0, 2)==0) { VEH_StartStrafeRam(ActorVeh, (EnemySide==Side_Left)); } } } } // NORMAL CHASE BEHAVIOR //======================= else { if (!EnemyVeh && AimAccuracy>0.99f && MoveDistance<500 && !EnemyDead) { ActorAccelerate = true; ActorDoTurbo = false; } else { ActorAccelerate = ((MoveDistance>500 && EnemySpeed>20.0f) || MoveDistance>1000); ActorDoTurbo = (MoveDistance>3000 && EnemySpeed>20.0f); } ucmd.buttons &=~BUTTON_VEH_SPEED; } // APPLY RESULTS //======================= // Decide Turbo //-------------- if (ActorDoTurbo || ActorInTurbo) { ucmd.buttons |= BUTTON_ALT_ATTACK; } else { ucmd.buttons &=~BUTTON_ALT_ATTACK; } // Decide Acceleration //--------------------- ucmd.forwardmove = (ActorAccelerate)?(127):(0); // Decide To Shoot //----------------- ucmd.buttons &=~BUTTON_ATTACK; ucmd.rightmove = 0; if (AimDistance<2000 && !EnemyDead) { // If Doing A Ram Attack //----------------------- if (ActorYawOffset!=0) { if (NPC->client->ps.weapon!=WP_NONE) { NPC_ChangeWeapon(WP_NONE); } ucmd.buttons &=~BUTTON_ATTACK; } else if (AimAccuracy>ATTACK_FWD) { if (NPC->client->ps.weapon!=WP_NONE) { NPC_ChangeWeapon(WP_NONE); } ucmd.buttons |= BUTTON_ATTACK; } else if (AimAccuracy<AIM_SIDE && AimAccuracy>-AIM_SIDE) { if (NPC->client->ps.weapon!=WP_BLASTER) { NPC_ChangeWeapon(WP_BLASTER); } if (AimAccuracy<ATTACK_SIDE && AimAccuracy>-ATTACK_SIDE) { //if (!TIMER_Done(NPC, "RiderAltAttack")) //{ // ucmd.buttons |= BUTTON_ALT_ATTACK; //} //else //{ ucmd.buttons |= BUTTON_ATTACK; /* if (TIMER_Done(NPC, "RiderAltAttackCheck")) { TIMER_Set(NPC, "RiderAltAttackCheck", Q_irand(1000, 3000)); if (Q_irand(0, 2)==0) { TIMER_Set(NPC, "RiderAltAttack", 300); } }*/ //} WeaponThink(true); } ucmd.rightmove = (EnemySide==Side_Left)?( 127):(-127); } else { if (NPC->client->ps.weapon!=WP_NONE) { NPC_ChangeWeapon(WP_NONE); } } } else { if (NPC->client->ps.weapon!=WP_NONE) { NPC_ChangeWeapon(WP_NONE); } } // Aim At Target //--------------- if (ActorAimAtTarget) { MoveDirection.VecToAng(); NPCInfo->desiredPitch = AngleNormalize360(MoveDirection[PITCH]); NPCInfo->desiredYaw = AngleNormalize360(MoveDirection[YAW] + ActorYawOffset); } NPC_UpdateAngles(qtrue, qtrue); }
//----------------------------------------------------- static qboolean turretG2_find_enemies( gentity_t *self ) //----------------------------------------------------- { qboolean found = qfalse; int i, count; float bestDist = self->radius * self->radius; float enemyDist; vec3_t enemyDir, org, org2; qboolean foundClient = qfalse; gentity_t *entity_list[MAX_GENTITIES], *target, *bestTarget = NULL; if ( self->aimDebounceTime > level.time ) // time since we've been shut off { // We were active and alert, i.e. had an enemy in the last 3 secs if ( self->painDebounceTime < level.time ) { if ( !(self->spawnflags&SPF_TURRETG2_TURBO) ) { G_Sound(self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/ping.wav" )); } self->painDebounceTime = level.time + 1000; } } VectorCopy( self->r.currentOrigin, org2 ); if ( self->spawnflags & 2 ) { org2[2] += 20; } else { org2[2] -= 20; } count = G_RadiusList( org2, self->radius, self, qtrue, entity_list ); for ( i = 0; i < count; i++ ) { trace_t tr; target = entity_list[i]; if ( !target->client ) { // only attack clients if ( !(target->flags&FL_BBRUSH)//not a breakable brush || !target->takedamage//is a bbrush, but invincible || (target->NPC_targetname&&self->targetname&&Q_stricmp(target->NPC_targetname,self->targetname)!=0) )//not in invicible bbrush, but can only be broken by an NPC that is not me { continue; } //else: we will shoot at bbrushes! } if ( target == self || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET )) { continue; } if ( target->client && target->client->sess.sessionTeam == TEAM_SPECTATOR ) { continue; } if ( self->alliedTeam ) { if ( target->client ) { if ( target->client->sess.sessionTeam == self->alliedTeam ) { // A bot/client/NPC we don't want to shoot continue; } } else if ( target->teamnodmg == self->alliedTeam ) { // An ent we don't want to shoot continue; } } if ( !trap_InPVS( org2, target->r.currentOrigin )) { continue; } if ( target->client ) { VectorCopy( target->client->renderInfo.eyePoint, org ); } else { VectorCopy( target->r.currentOrigin, org ); } if ( self->spawnflags & 2 ) { org[2] -= 15; } else { org[2] += 5; } trap_Trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT ); if ( !tr.allsolid && !tr.startsolid && ( tr.fraction == 1.0 || tr.entityNum == target->s.number )) { // Only acquire if have a clear shot, Is it in range and closer than our best? VectorSubtract( target->r.currentOrigin, self->r.currentOrigin, enemyDir ); enemyDist = VectorLengthSquared( enemyDir ); if ( enemyDist < bestDist || (target->client && !foundClient))// all things equal, keep current { if ( self->attackDebounceTime < level.time ) { // We haven't fired or acquired an enemy in the last 2 seconds-start-up sound if ( !(self->spawnflags&SPF_TURRETG2_TURBO) ) { G_Sound( self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/startup.wav" )); } // Wind up turrets for a bit self->attackDebounceTime = level.time + 1400; } bestTarget = target; bestDist = enemyDist; found = qtrue; if ( target->client ) { //prefer clients over non-clients foundClient = qtrue; } } } } if ( found ) { /* if ( !self->enemy ) {//just aquired one AddSoundEvent( bestTarget, self->r.currentOrigin, 256, AEL_DISCOVERED ); AddSightEvent( bestTarget, self->r.currentOrigin, 512, AEL_DISCOVERED, 20 ); } */ G_SetEnemy( self, bestTarget ); if ( VALIDSTRING( self->target2 )) { G_UseTargets2( self, self, self->target2 ); } } return found; }
void Rancor_Swing( qboolean tryGrab ) { int radiusEntNums[128]; int numEnts; const float radius = 88; const float radiusSquared = (radius*radius); int i; vec3_t boltOrg; numEnts = NPC_GetEntsNearBolt( radiusEntNums, radius, NPC->client->renderInfo.handRBolt, boltOrg ); for ( i = 0; i < numEnts; i++ ) { gentity_t *radiusEnt = &g_entities[radiusEntNums[i]]; if ( !radiusEnt->inuse ) { continue; } if ( radiusEnt == NPC ) {//Skip the rancor ent continue; } if ( radiusEnt->client == NULL ) {//must be a client continue; } if ( (radiusEnt->client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) {//can't be one already being held continue; } if ( DistanceSquared( radiusEnt->r.currentOrigin, boltOrg ) <= radiusSquared ) { if ( tryGrab && NPC->count != 1 //don't have one in hand or in mouth already - FIXME: allow one in hand and any number in mouth! && radiusEnt->client->NPC_class != CLASS_RANCOR && radiusEnt->client->NPC_class != CLASS_GALAKMECH && radiusEnt->client->NPC_class != CLASS_ATST && radiusEnt->client->NPC_class != CLASS_GONK && radiusEnt->client->NPC_class != CLASS_R2D2 && radiusEnt->client->NPC_class != CLASS_R5D2 && radiusEnt->client->NPC_class != CLASS_MARK1 && radiusEnt->client->NPC_class != CLASS_MARK2 && radiusEnt->client->NPC_class != CLASS_MOUSE && radiusEnt->client->NPC_class != CLASS_PROBE && radiusEnt->client->NPC_class != CLASS_SEEKER && radiusEnt->client->NPC_class != CLASS_REMOTE && radiusEnt->client->NPC_class != CLASS_SENTRY && radiusEnt->client->NPC_class != CLASS_INTERROGATOR && radiusEnt->client->NPC_class != CLASS_VEHICLE ) {//grab if ( NPC->count == 2 ) {//have one in my mouth, remove him TIMER_Remove( NPC, "clearGrabbed" ); Rancor_DropVictim( NPC ); } NPC->enemy = radiusEnt;//make him my new best friend radiusEnt->client->ps.eFlags2 |= EF2_HELD_BY_MONSTER; //FIXME: this makes it so that the victim can't hit us with shots! Just use activator or something radiusEnt->client->ps.hasLookTarget = qtrue; radiusEnt->client->ps.lookTarget = NPC->s.number; NPC->activator = radiusEnt;//remember him NPC->count = 1;//in my hand //wait to attack TIMER_Set( NPC, "attacking", NPC->client->ps.legsTimer + Q_irand(500, 2500) ); if ( radiusEnt->health > 0 && radiusEnt->pain ) {//do pain on enemy radiusEnt->pain( radiusEnt, NPC, 100 ); //GEntity_PainFunc( radiusEnt, NPC, NPC, radiusEnt->r.currentOrigin, 0, MOD_CRUSH ); } else if ( radiusEnt->client ) { radiusEnt->client->ps.forceHandExtend = HANDEXTEND_NONE; radiusEnt->client->ps.forceHandExtendTime = 0; NPC_SetAnim( radiusEnt, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); } } else {//smack vec3_t pushDir; vec3_t angs; G_Sound( radiusEnt, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); //actually push the enemy /* //VectorSubtract( radiusEnt->r.currentOrigin, boltOrg, pushDir ); VectorSubtract( radiusEnt->r.currentOrigin, NPC->r.currentOrigin, pushDir ); pushDir[2] = Q_flrand( 100, 200 ); VectorNormalize( pushDir ); */ VectorCopy( NPC->client->ps.viewangles, angs ); angs[YAW] += flrand( 25, 50 ); angs[PITCH] = flrand( -25, -15 ); AngleVectors( angs, pushDir, NULL, NULL ); if ( radiusEnt->client->NPC_class != CLASS_RANCOR && radiusEnt->client->NPC_class != CLASS_ATST ) { G_Damage( radiusEnt, NPC, NPC, vec3_origin, radiusEnt->r.currentOrigin, Q_irand( 25, 40 ), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_MELEE ); G_Throw( radiusEnt, pushDir, 250 ); if ( radiusEnt->health > 0 ) {//do pain on enemy G_Knockdown( radiusEnt );//, NPC, pushDir, 100, qtrue ); } } } } } }
/* ------------------------- NPC_BSWampa_Default ------------------------- */ void NPC_BSWampa_Default( void ) { NPC->client->ps.eFlags2 &= ~EF2_USE_ALT_ANIM; //NORMAL ANIMS // stand1 = normal stand // walk1 = normal, non-angry walk // walk2 = injured // run1 = far away run // run2 = close run //VICTIM ANIMS // grabswipe = melee1 - sweep out and grab // stand2 attack = attack4 - while holding victim, swipe at him // walk3_drag = walk5 - walk with drag // stand2 = hold victim // stand2to1 = drop victim if ( !TIMER_Done( NPC, "rageTime" ) ) {//do nothing but roar first time we see an enemy NPC_FaceEnemy( qtrue ); return; } if ( NPC->enemy ) { if ( !TIMER_Done(NPC,"attacking") ) {//in middle of attack //face enemy NPC_FaceEnemy( qtrue ); //continue attack logic enemyDist = Distance( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); Wampa_Attack( enemyDist, qfalse ); return; } else { if ( TIMER_Done(NPC,"angrynoise") ) { G_Sound( NPC, CHAN_VOICE, G_SoundIndex( va("sound/chars/wampa/misc/anger%d.wav", Q_irand(1, 2)) ) ); TIMER_Set( NPC, "angrynoise", Q_irand( 5000, 10000 ) ); } //else, if he's in our hand, we eat, else if he's on the ground, we keep attacking his dead body for a while if( NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_WAMPA ) {//got mad at another Wampa, look for a valid enemy if ( TIMER_Done( NPC, "wampaInfight" ) ) { NPC_CheckEnemyExt( qtrue ); } } else { if ( ValidEnemy( NPC->enemy ) == qfalse ) { TIMER_Remove( NPC, "lookForNewEnemy" );//make them look again right now if ( !NPC->enemy->inuse || level.time - NPC->enemy->s.time > Q_irand( 10000, 15000 ) ) {//it's been a while since the enemy died, or enemy is completely gone, get bored with him NPC->enemy = NULL; Wampa_Patrol(); NPC_UpdateAngles( qtrue, qtrue ); //just lost my enemy if ( (NPC->spawnflags&2) ) {//search around me if I don't have an enemy NPC_BSSearchStart( NPC->waypoint, BS_SEARCH ); NPCInfo->tempBehavior = BS_DEFAULT; } else if ( (NPC->spawnflags&1) ) {//wander if I don't have an enemy NPC_BSSearchStart( NPC->waypoint, BS_WANDER ); NPCInfo->tempBehavior = BS_DEFAULT; } return; } } if ( TIMER_Done( NPC, "lookForNewEnemy" ) ) { gentity_t *newEnemy, *sav_enemy = NPC->enemy;//FIXME: what about NPC->lastEnemy? NPC->enemy = NULL; newEnemy = NPC_CheckEnemy( NPCInfo->confusionTime < level.time, qfalse, qfalse ); NPC->enemy = sav_enemy; if ( newEnemy && newEnemy != sav_enemy ) {//picked up a new enemy! NPC->lastEnemy = NPC->enemy; G_SetEnemy( NPC, newEnemy ); //hold this one for at least 5-15 seconds TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); } else {//look again in 2-5 secs TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 2000, 5000 ) ); } } } Wampa_Combat(); return; } } else { if ( TIMER_Done(NPC,"idlenoise") ) { G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/wampa/misc/anger3.wav" ) ); TIMER_Set( NPC, "idlenoise", Q_irand( 2000, 4000 ) ); } if ( (NPC->spawnflags&2) ) {//search around me if I don't have an enemy if ( NPCInfo->homeWp == WAYPOINT_NONE ) {//no homewap, initialize the search behavior NPC_BSSearchStart( WAYPOINT_NONE, BS_SEARCH ); NPCInfo->tempBehavior = BS_DEFAULT; } ucmd.buttons |= BUTTON_WALKING; NPC_BSSearch();//this automatically looks for enemies } else if ( (NPC->spawnflags&1) ) {//wander if I don't have an enemy if ( NPCInfo->homeWp == WAYPOINT_NONE ) {//no homewap, initialize the wander behavior NPC_BSSearchStart( WAYPOINT_NONE, BS_WANDER ); NPCInfo->tempBehavior = BS_DEFAULT; } ucmd.buttons |= BUTTON_WALKING; NPC_BSWander(); if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) { if ( NPC_CheckEnemyExt( qtrue ) == qfalse ) { Wampa_Idle(); } else { Wampa_CheckRoar( NPC ); TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); } } } else { if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) { Wampa_Patrol(); } else { Wampa_Idle(); } } } NPC_UpdateAngles( qtrue, qtrue ); }
// 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 multi_trigger_run( gentity_t *ent ) { ent->think = 0; G_ActivateBehavior( ent, BSET_USE ); if ( ent->soundSet && ent->soundSet[0] ) { trap->SetConfigstring( CS_GLOBAL_AMBIENT_SET, ent->soundSet ); } if (ent->genericValue4) { //we want to activate target3 for team1 or target4 for team2 if (ent->genericValue4 == SIEGETEAM_TEAM1 && ent->target3 && ent->target3[0]) { G_UseTargets2(ent, ent->activator, ent->target3); } else if (ent->genericValue4 == SIEGETEAM_TEAM2 && ent->target4 && ent->target4[0]) { G_UseTargets2(ent, ent->activator, ent->target4); } ent->genericValue4 = 0; } G_UseTargets (ent, ent->activator); if ( ent->noise_index ) { G_Sound( ent->activator, CHAN_AUTO, ent->noise_index ); } if ( ent->target2 && ent->target2[0] && ent->wait >= 0 ) { ent->think = trigger_cleared_fire; ent->nextthink = level.time + ent->speed; } else if ( ent->wait > 0 ) { if ( ent->painDebounceTime != level.time ) {//first ent to touch it this frame //ent->e_ThinkFunc = thinkF_multi_wait; ent->nextthink = level.time + ( ent->wait + ent->random * crandom() ) * 1000; ent->painDebounceTime = level.time; } } else if ( ent->wait < 0 ) { // we can't just remove (self) here, because this is a touch function // called while looping through area links... ent->r.contents &= ~CONTENTS_TRIGGER;//so the EntityContact trace doesn't have to be done against me ent->think = 0; ent->use = 0; //Don't remove, Icarus may barf? //ent->nextthink = level.time + FRAMETIME; //ent->think = G_FreeEntity; } if( ent->activator && ent->activator->client ) { // mark the trigger as being touched by the player ent->aimDebounceTime = level.time; } }
void Wampa_Slash( int boltIndex, qboolean backhand ) { int radiusEntNums[128]; int numEnts; const float radius = 88; const float radiusSquared = (radius*radius); int i; vec3_t boltOrg; int damage = (backhand)?Q_irand(10,15):Q_irand(20,30); numEnts = NPC_GetEntsNearBolt( radiusEntNums, radius, boltIndex, boltOrg ); for ( i = 0; i < numEnts; i++ ) { gentity_t *radiusEnt = &g_entities[radiusEntNums[i]]; if ( !radiusEnt->inuse ) { continue; } if ( radiusEnt == NPC ) {//Skip the wampa ent continue; } if ( radiusEnt->client == NULL ) {//must be a client continue; } if ( DistanceSquared( radiusEnt->r.currentOrigin, boltOrg ) <= radiusSquared ) { //smack G_Damage( radiusEnt, NPC, NPC, vec3_origin, radiusEnt->r.currentOrigin, damage, ((backhand)?DAMAGE_NO_ARMOR:(DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK)), MOD_MELEE ); if ( backhand ) { //actually push the enemy vec3_t pushDir; vec3_t angs; VectorCopy( NPC->client->ps.viewangles, angs ); angs[YAW] += flrand( 25, 50 ); angs[PITCH] = flrand( -25, -15 ); AngleVectors( angs, pushDir, NULL, NULL ); if ( radiusEnt->client->NPC_class != CLASS_WAMPA && radiusEnt->client->NPC_class != CLASS_RANCOR && radiusEnt->client->NPC_class != CLASS_ATST ) { G_Throw( radiusEnt, pushDir, 65 ); if ( BG_KnockDownable(&radiusEnt->client->ps) && radiusEnt->health > 0 && Q_irand( 0, 1 ) ) {//do pain on enemy radiusEnt->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN; radiusEnt->client->ps.forceDodgeAnim = 0; radiusEnt->client->ps.forceHandExtendTime = level.time + 1100; radiusEnt->client->ps.quickerGetup = qfalse; } } } else if ( radiusEnt->health <= 0 && radiusEnt->client ) {//killed them, chance of dismembering if ( !Q_irand( 0, 1 ) ) {//bite something off int hitLoc = Q_irand( G2_MODELPART_HEAD, G2_MODELPART_RLEG ); if ( hitLoc == G2_MODELPART_HEAD ) { NPC_SetAnim( radiusEnt, SETANIM_BOTH, BOTH_DEATH17, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); } else if ( hitLoc == G2_MODELPART_WAIST ) { NPC_SetAnim( radiusEnt, SETANIM_BOTH, BOTH_DEATHBACKWARD2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); } G_Dismember( radiusEnt, NPC, radiusEnt->r.currentOrigin, hitLoc, 90, 0, radiusEnt->client->ps.torsoAnim, qtrue); } } else if ( !Q_irand( 0, 3 ) && radiusEnt->health > 0 ) {//one out of every 4 normal hits does a knockdown, too vec3_t pushDir; vec3_t angs; VectorCopy( NPC->client->ps.viewangles, angs ); angs[YAW] += flrand( 25, 50 ); angs[PITCH] = flrand( -25, -15 ); AngleVectors( angs, pushDir, NULL, NULL ); //[KnockdownSys] //ported multi-direction knockdowns from SP. G_Knockdown( radiusEnt, NPC, pushDir, 35, qtrue ); //G_Knockdown( radiusEnt ); //[/KnockdownSys] } G_Sound( radiusEnt, CHAN_WEAPON, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); } } }
/* ============= P_WorldEffects Check for lava / slime contents and drowning ============= */ void P_WorldEffects(gentity_t *ent) { int waterlevel; if (ent->client->noclip) { ent->client->airOutTime = level.time + HOLDBREATHTIME; // don't need air return; } waterlevel = ent->waterlevel; // // check for drowning // if (waterlevel == 3) { // if out of air, start drowning if (ent->client->airOutTime < level.time) { if (ent->client->ps.powerups[PW_BREATHER]) { // take air from the breather now that we need it ent->client->ps.powerups[PW_BREATHER] -= (level.time - ent->client->airOutTime); ent->client->airOutTime = level.time + (level.time - ent->client->airOutTime); } else if (!g_disableDrowning.integer) { // Nico, check if drowning is disabled // drown! ent->client->airOutTime += 1000; if (ent->health > 0) { // take more damage the longer underwater ent->damage += 2; if (ent->damage > 15) { ent->damage = 15; } // play a gurp sound instead of a normal pain sound if (ent->health <= ent->damage) { G_Sound(ent, G_SoundIndex("*drown.wav")); } else if (rand() & 1) { G_Sound(ent, G_SoundIndex("sound/player/gurp1.wav")); } else { G_Sound(ent, G_SoundIndex("sound/player/gurp2.wav")); } // don't play a normal pain sound ent->pain_debounce_time = level.time + 200; G_Damage(ent, NULL, NULL, NULL, NULL, ent->damage, 0, MOD_WATER); } } } } else { ent->client->airOutTime = level.time + 12000; ent->damage = 2; } // // check for sizzle damage (move to pmove?) // if (waterlevel && (ent->watertype & CONTENTS_LAVA) && ent->health > 0 && ent->pain_debounce_time <= level.time) { G_Damage(ent, NULL, NULL, NULL, NULL, 30 * waterlevel, 0, MOD_LAVA); } // // check for burning from flamethrower // // JPW NERVE MP way if (ent->s.onFireEnd && ent->client && level.time - ent->client->lastBurnTime >= MIN_BURN_INTERVAL) { // JPW NERVE server-side incremental damage routine / player damage/health is int (not float) // so I can't allocate 1.5 points per server tick, and 1 is too weak and 2 is too strong. // solution: allocate damage far less often (MIN_BURN_INTERVAL often) and do more damage. // That way minimum resolution (1 point) damage changes become less critical. ent->client->lastBurnTime = level.time; if ((ent->s.onFireEnd > level.time) && (ent->health > 0)) { gentity_t *attacker; attacker = g_entities + ent->flameBurnEnt; G_Damage(ent, attacker, attacker, NULL, NULL, 5, DAMAGE_NO_KNOCKBACK, MOD_FLAMETHROWER); // JPW NERVE was 7 } } // jpw }
/* ------------------------- Sentry_Fire ------------------------- */ void Sentry_Fire (void) { vec3_t muzzle; static vec3_t forward, vright, up; gentity_t *missile; mdxaBone_t boltMatrix; int bolt, which; NPCS.NPC->flags &= ~FL_SHIELDED; if ( NPCS.NPCInfo->localState == LSTATE_POWERING_UP ) { if ( TIMER_Done( NPCS.NPC, "powerup" )) { NPCS.NPCInfo->localState = LSTATE_ATTACKING; NPC_SetAnim( NPCS.NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } else { // can't do anything right now return; } } else if ( NPCS.NPCInfo->localState == LSTATE_ACTIVE ) { NPCS.NPCInfo->localState = LSTATE_POWERING_UP; G_Sound( NPCS.NPC, CHAN_AUTO, G_SoundIndex("sound/chars/sentry/misc/sentry_shield_open") ); NPC_SetAnim( NPCS.NPC, SETANIM_BOTH, BOTH_POWERUP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPCS.NPC, "powerup", 250 ); return; } else if ( NPCS.NPCInfo->localState != LSTATE_ATTACKING ) { // bad because we are uninitialized NPCS.NPCInfo->localState = LSTATE_ACTIVE; return; } // Which muzzle to fire from? which = NPCS.NPCInfo->burstCount % 3; switch( which ) { case 0: bolt = trap_G2API_AddBolt(NPCS.NPC->ghoul2, 0, "*flash1"); break; case 1: bolt = trap_G2API_AddBolt(NPCS.NPC->ghoul2, 0, "*flash2"); break; case 2: default: bolt = trap_G2API_AddBolt(NPCS.NPC->ghoul2, 0, "*flash03"); } trap_G2API_GetBoltMatrix( NPCS.NPC->ghoul2, 0, bolt, &boltMatrix, NPCS.NPC->r.currentAngles, NPCS.NPC->r.currentOrigin, level.time, NULL, NPCS.NPC->modelScale ); BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, muzzle ); AngleVectors( NPCS.NPC->r.currentAngles, forward, vright, up ); // G_Sound( NPC, G_SoundIndex("sound/chars/sentry/misc/shoot.wav")); G_PlayEffectID( G_EffectIndex("bryar/muzzle_flash"), muzzle, forward ); missile = CreateMissile( muzzle, forward, 1600, 10000, NPCS.NPC, qfalse ); missile->classname = "bryar_proj"; missile->s.weapon = WP_BRYAR_PISTOL; missile->dflags = DAMAGE_DEATH_KNOCKBACK; missile->methodOfDeath = MOD_BRYAR_PISTOL; missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; NPCS.NPCInfo->burstCount++; NPCS.NPC->attackDebounceTime = level.time + 50; missile->damage = 5; // now scale for difficulty if ( g_npcspskill.integer == 0 ) { NPCS.NPC->attackDebounceTime += 200; missile->damage = 1; } else if ( g_npcspskill.integer == 1 ) { NPCS.NPC->attackDebounceTime += 100; missile->damage = 3; } }
/* ------------------------- Remote_MaintainHeight ------------------------- */ void Remote_MaintainHeight( void ) { float dif; // Update our angles regardless NPC_UpdateAngles( qtrue, qtrue ); if ( NPC->client->ps.velocity[2] ) { NPC->client->ps.velocity[2] *= VELOCITY_DECAY; if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) { NPC->client->ps.velocity[2] = 0; } } // If we have an enemy, we should try to hover at or a little below enemy eye level if ( NPC->enemy ) { if (TIMER_Done( NPC, "heightChange")) { TIMER_Set( NPC,"heightChange",Q_irand( 1000, 3000 )); // Find the height difference dif = (NPC->enemy->r.currentOrigin[2] + Q_irand( 0, NPC->enemy->r.maxs[2]+8 )) - NPC->r.currentOrigin[2]; // cap to prevent dramatic height shifts if ( fabs( dif ) > 2 ) { if ( fabs( dif ) > 24 ) { dif = ( dif < 0 ? -24 : 24 ); } dif *= 10; NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; // NPC->fx_time = level.time; G_Sound( NPC, CHAN_AUTO, G_SoundIndex("sound/chars/remote/misc/hiss.wav")); } } } else { gentity_t *goal = NULL; if ( NPCInfo->goalEntity ) // Is there a goal? { goal = NPCInfo->goalEntity; } else { goal = NPCInfo->lastGoalEntity; } if ( goal ) { dif = goal->r.currentOrigin[2] - NPC->r.currentOrigin[2]; if ( fabs( dif ) > 24 ) { dif = ( dif < 0 ? -24 : 24 ); NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; } } } // Apply friction if ( NPC->client->ps.velocity[0] ) { NPC->client->ps.velocity[0] *= VELOCITY_DECAY; if ( fabs( NPC->client->ps.velocity[0] ) < 1 ) { NPC->client->ps.velocity[0] = 0; } } if ( NPC->client->ps.velocity[1] ) { NPC->client->ps.velocity[1] *= VELOCITY_DECAY; if ( fabs( NPC->client->ps.velocity[1] ) < 1 ) { NPC->client->ps.velocity[1] = 0; } } }
void Rancor_Attack( float distance, qboolean doCharge ) { if ( !TIMER_Exists( NPC, "attacking" ) ) { if ( NPC->count == 2 && NPC->activator ) { } else if ( NPC->count == 1 && NPC->activator ) {//holding enemy if ( NPC->activator->health > 0 && Q_irand( 0, 1 ) ) {//quick bite NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attack_dmg", 450 ); } else {//full eat NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK3, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attack_dmg", 900 ); //Make victim scream in fright if ( NPC->activator->health > 0 && NPC->activator->client ) { G_AddEvent( NPC->activator, Q_irand(EV_DEATH1, EV_DEATH3), 0 ); NPC_SetAnim( NPC->activator, SETANIM_TORSO, BOTH_FALLDEATH1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); if ( NPC->activator->NPC ) {//no more thinking for you TossClientItems( NPC ); NPC->activator->NPC->nextBStateThink = Q3_INFINITE; } } } } else if ( NPC->enemy->health > 0 && doCharge ) {//charge vec3_t fwd, yawAng; VectorSet( yawAng, 0, NPC->client->ps.viewangles[YAW], 0 ); AngleVectors( yawAng, fwd, NULL, NULL ); VectorScale( fwd, distance*1.5f, NPC->client->ps.velocity ); NPC->client->ps.velocity[2] = 150; NPC->client->ps.groundEntityNum = ENTITYNUM_NONE; NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attack_dmg", 1250 ); } else if ( !Q_irand(0, 1) ) {//smash NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attack_dmg", 1000 ); } else {//try to grab NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attack_dmg", 1000 ); } TIMER_Set( NPC, "attacking", NPC->client->ps.legsTimer + random() * 200 ); } // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks if ( TIMER_Done2( NPC, "attack_dmg", qtrue ) ) { vec3_t shakePos; switch ( NPC->client->ps.legsAnim ) { case BOTH_MELEE1: Rancor_Smash(); G_GetBoltPosition( NPC, NPC->client->renderInfo.handLBolt, shakePos, 0 ); G_ScreenShake( shakePos, NULL, 4.0f, 1000, qfalse ); //CGCam_Shake( 1.0f*playerDist/128.0f, 1000 ); break; case BOTH_MELEE2: Rancor_Bite(); TIMER_Set( NPC, "attack_dmg2", 450 ); break; case BOTH_ATTACK1: if ( NPC->count == 1 && NPC->activator ) { G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->r.currentOrigin, Q_irand( 25, 40 ), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_MELEE ); if ( NPC->activator->health <= 0 ) {//killed him //make it look like we bit his head off //NPC->activator->client->dismembered = qfalse; G_Dismember( NPC->activator, NPC, NPC->activator->r.currentOrigin, G2_MODELPART_HEAD, 90, 0, NPC->activator->client->ps.torsoAnim, qtrue); //G_DoDismemberment( NPC->activator, NPC->activator->r.currentOrigin, MOD_SABER, 1000, HL_HEAD, qtrue ); NPC->activator->client->ps.forceHandExtend = HANDEXTEND_NONE; NPC->activator->client->ps.forceHandExtendTime = 0; NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); } G_Sound( NPC->activator, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/chomp.wav" ) ); } break; case BOTH_ATTACK2: //try to grab Rancor_Swing( qtrue ); break; case BOTH_ATTACK3: if ( NPC->count == 1 && NPC->activator ) { //cut in half if ( NPC->activator->client ) { //NPC->activator->client->dismembered = qfalse; G_Dismember( NPC->activator, NPC, NPC->activator->r.currentOrigin, G2_MODELPART_WAIST, 90, 0, NPC->activator->client->ps.torsoAnim, qtrue); //G_DoDismemberment( NPC->activator, NPC->enemy->r.currentOrigin, MOD_SABER, 1000, HL_WAIST, qtrue ); } //KILL G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->r.currentOrigin, NPC->enemy->health+10, DAMAGE_NO_PROTECTION|DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_MELEE );//, HL_NONE );// if ( NPC->activator->client ) { NPC->activator->client->ps.forceHandExtend = HANDEXTEND_NONE; NPC->activator->client->ps.forceHandExtendTime = 0; NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); } TIMER_Set( NPC, "attack_dmg2", 1350 ); G_Sound( NPC->activator, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); G_AddEvent( NPC->activator, EV_JUMP, NPC->activator->health ); } break; } } else if ( TIMER_Done2( NPC, "attack_dmg2", qtrue ) ) { switch ( NPC->client->ps.legsAnim ) { case BOTH_MELEE1: break; case BOTH_MELEE2: Rancor_Bite(); break; case BOTH_ATTACK1: break; case BOTH_ATTACK2: break; case BOTH_ATTACK3: if ( NPC->count == 1 && NPC->activator ) {//swallow victim G_Sound( NPC->activator, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/chomp.wav" ) ); //FIXME: sometimes end up with a live one in our mouths? //just make sure they're dead if ( NPC->activator->health > 0 ) { //cut in half //NPC->activator->client->dismembered = qfalse; G_Dismember( NPC->activator, NPC, NPC->activator->r.currentOrigin, G2_MODELPART_WAIST, 90, 0, NPC->activator->client->ps.torsoAnim, qtrue); //G_DoDismemberment( NPC->activator, NPC->enemy->r.currentOrigin, MOD_SABER, 1000, HL_WAIST, qtrue ); //KILL G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->r.currentOrigin, NPC->enemy->health+10, DAMAGE_NO_PROTECTION|DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_MELEE );//, HL_NONE ); NPC->activator->client->ps.forceHandExtend = HANDEXTEND_NONE; NPC->activator->client->ps.forceHandExtendTime = 0; NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); G_AddEvent( NPC->activator, EV_JUMP, NPC->activator->health ); } if ( NPC->activator->client ) {//*sigh*, can't get tags right, just remove them? NPC->activator->client->ps.eFlags |= EF_NODRAW; } NPC->count = 2; TIMER_Set( NPC, "clearGrabbed", 2600 ); } break; } } else if ( NPC->client->ps.legsAnim == BOTH_ATTACK2 ) { if ( NPC->client->ps.legsTimer >= 1200 && NPC->client->ps.legsTimer <= 1350 ) { if ( Q_irand( 0, 2 ) ) { Rancor_Swing( qfalse ); } else { Rancor_Swing( qtrue ); } } else if ( NPC->client->ps.legsTimer >= 1100 && NPC->client->ps.legsTimer <= 1550 ) { Rancor_Swing( qtrue ); } } // Just using this to remove the attacking flag at the right time TIMER_Done2( NPC, "attacking", qtrue ); }
/* ================ G_BounceMissile ================ */ void G_BounceMissile( gentity_t *ent, trace_t *trace ) { vec3_t velocity; float dot; int hitTime; gentity_t *other; float coeff; other = &g_entities[trace->entityNum]; // reflect the velocity on the trace plane hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction; BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity ); dot = DotProduct( velocity, trace->plane.normal ); VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta ); if ( ent->flags & FL_BOUNCE_SHRAPNEL ) { VectorScale( ent->s.pos.trDelta, 0.25f, ent->s.pos.trDelta ); ent->s.pos.trType = TR_GRAVITY; // check for stop if ( trace->plane.normal[2] > 0.7 && ent->s.pos.trDelta[2] < 40 ) //this can happen even on very slightly sloped walls, so changed it from > 0 to > 0.7 { G_SetOrigin( ent, trace->endpos ); ent->nextthink = level.time + 100; return; } } else if ( ent->flags & FL_BOUNCE_HALF ) { VectorScale( ent->s.pos.trDelta, 0.65, ent->s.pos.trDelta ); // check for stop if ( trace->plane.normal[2] > 0.2 && VectorLength( ent->s.pos.trDelta ) < 40 ) { G_SetOrigin( ent, trace->endpos ); return; } } if (ent->s.weapon == WP_THERMAL) { //slight hack for hit sound G_Sound(ent, CHAN_BODY, G_SoundIndex(va("sound/weapons/thermal/bounce%i.wav", Q_irand(1, 2)))); } else if (ent->s.weapon == WP_SABER) { G_Sound(ent, CHAN_BODY, G_SoundIndex(va("sound/weapons/saber/bounce%i.wav", Q_irand(1, 3)))); } else if (ent->s.weapon == G2_MODEL_PART) { //Limb bounce sound? } coeff = 1.0f; if (((other->s.pos.trType == TR_LINEAR_STOP) || (other->s.pos.trType == TR_NONLINEAR_STOP)) && (VectorLength(trace->plane.normal) != 0.0f)){ dot = DotProduct( other->s.pos.trDelta, trace->plane.normal ); if (dot > 0) coeff = 0.35f*dot; VectorMA( ent->s.pos.trDelta, coeff, trace->plane.normal, ent->s.pos.trDelta); } VectorMA( ent->r.currentOrigin, /*1.0f*/coeff, trace->plane.normal, ent->r.currentOrigin); VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase ); ent->s.pos.trTime = level.time; if (ent->bounceCount != -5) { ent->bounceCount--; } }
/* ------------------------- NPC_BSRancor_Default ------------------------- */ void NPC_BSRancor_Default( void ) { AddSightEvent( NPC, NPC->r.currentOrigin, 1024, AEL_DANGER_GREAT, 50 ); Rancor_Crush(); NPC->client->ps.eFlags2 &= ~(EF2_USE_ALT_ANIM|EF2_GENERIC_NPC_FLAG); if ( NPC->count ) {//holding someone NPC->client->ps.eFlags2 |= EF2_USE_ALT_ANIM; if ( NPC->count == 2 ) {//in my mouth NPC->client->ps.eFlags2 |= EF2_GENERIC_NPC_FLAG; } } else { NPC->client->ps.eFlags2 &= ~(EF2_USE_ALT_ANIM|EF2_GENERIC_NPC_FLAG); } if ( TIMER_Done2( NPC, "clearGrabbed", qtrue ) ) { Rancor_DropVictim( NPC ); } else if ( NPC->client->ps.legsAnim == BOTH_PAIN2 && NPC->count == 1 && NPC->activator ) { if ( !Q_irand( 0, 3 ) ) { Rancor_CheckDropVictim(); } } if ( !TIMER_Done( NPC, "rageTime" ) ) {//do nothing but roar first time we see an enemy AddSoundEvent( NPC, NPC->r.currentOrigin, 1024, AEL_DANGER_GREAT, qfalse );//, qfalse ); NPC_FaceEnemy( qtrue ); return; } if ( NPC->enemy ) { /* if ( NPC->enemy->client //enemy is a client && (NPC->enemy->client->NPC_class == CLASS_UGNAUGHT || NPC->enemy->client->NPC_class == CLASS_JAWA )//enemy is a lowly jawa or ugnaught && NPC->enemy->enemy != NPC//enemy's enemy is not me && (!NPC->enemy->enemy || !NPC->enemy->enemy->client || NPC->enemy->enemy->client->NPC_class!=CLASS_RANCOR) )//enemy's enemy is not a client or is not a rancor (which is as scary as me anyway) {//they should be scared of ME and no-one else G_SetEnemy( NPC->enemy, NPC ); } */ if ( TIMER_Done(NPC,"angrynoise") ) { G_Sound( NPC, CHAN_AUTO, G_SoundIndex( va("sound/chars/rancor/misc/anger%d.wav", Q_irand(1, 3))) ); TIMER_Set( NPC, "angrynoise", Q_irand( 5000, 10000 ) ); } else { AddSoundEvent( NPC, NPC->r.currentOrigin, 512, AEL_DANGER_GREAT, qfalse );//, qfalse ); } if ( NPC->count == 2 && NPC->client->ps.legsAnim == BOTH_ATTACK3 ) {//we're still chewing our enemy up NPC_UpdateAngles( qtrue, qtrue ); return; } //else, if he's in our hand, we eat, else if he's on the ground, we keep attacking his dead body for a while if( NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_RANCOR ) {//got mad at another Rancor, look for a valid enemy if ( TIMER_Done( NPC, "rancorInfight" ) ) { NPC_CheckEnemyExt( qtrue ); } } else if ( !NPC->count ) { if ( ValidEnemy( NPC->enemy ) == qfalse ) { TIMER_Remove( NPC, "lookForNewEnemy" );//make them look again right now if ( !NPC->enemy->inuse || level.time - NPC->enemy->s.time > Q_irand( 10000, 15000 ) ) {//it's been a while since the enemy died, or enemy is completely gone, get bored with him NPC->enemy = NULL; Rancor_Patrol(); NPC_UpdateAngles( qtrue, qtrue ); return; } } if ( TIMER_Done( NPC, "lookForNewEnemy" ) ) { gentity_t *newEnemy, *sav_enemy = NPC->enemy;//FIXME: what about NPC->lastEnemy? NPC->enemy = NULL; newEnemy = NPC_CheckEnemy( NPCInfo->confusionTime < level.time, qfalse, qfalse ); NPC->enemy = sav_enemy; if ( newEnemy && newEnemy != sav_enemy ) {//picked up a new enemy! NPC->lastEnemy = NPC->enemy; G_SetEnemy( NPC, newEnemy ); //hold this one for at least 5-15 seconds TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); } else {//look again in 2-5 secs TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 2000, 5000 ) ); } } } Rancor_Combat(); } else { if ( TIMER_Done(NPC,"idlenoise") ) { G_Sound( NPC, CHAN_AUTO, G_SoundIndex( va("sound/chars/rancor/snort_%d.wav", Q_irand(1, 2))) ); TIMER_Set( NPC, "idlenoise", Q_irand( 2000, 4000 ) ); AddSoundEvent( NPC, NPC->r.currentOrigin, 384, AEL_DANGER, qfalse );//, qfalse ); } if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) { Rancor_Patrol(); } else { Rancor_Idle(); } } NPC_UpdateAngles( qtrue, qtrue ); }
/*QUAKED target_relay (1 1 0) (-8 -8 -8) (8 8 8) RED_ONLY BLUE_ONLY RANDOM NOKEY_ONLY TAKE_KEY NO_LOCKED_NOISE This doesn't perform any actions except fire its targets. The activator can be forced to be from a certain team. if RANDOM is checked, only one of the targets will be fired, not all of them "key" specifies an item you can be carrying that affects the operation of this relay this key is currently an int (1-16) which matches the id of a key entity (key_key1 = 1, etc) NOKEY_ONLY means "fire only if I do /not/ have the specified key" TAKE_KEY removes the key from the players inventory "lockednoise" specifies a .wav file to play if the relay is used and the player doesn't have the necessary key. By default this sound is "sound/movers/doors/default_door_locked.wav" NO_LOCKED_NOISE specifies that it will be silent if activated without proper key */ void target_relay_use(gentity_t *self, gentity_t *other, gentity_t *activator) { if ((self->spawnflags & 1) && activator && activator->client && activator->client->sess.sessionTeam != TEAM_AXIS) { return; } if ((self->spawnflags & 2) && activator && activator->client && activator->client->sess.sessionTeam != TEAM_ALLIES) { return; } if (self->spawnflags & 4) { gentity_t *ent; ent = G_PickTarget(self->target); if (ent && ent->use) { G_UseEntity(ent, self, activator); } return; } if (activator) { // activator can be NULL if called from script if (self->key) { // Gordon: removed keys // gitem_t *item; if (self->key == -1) { // relay permanently locked if (self->soundPos1) { G_Sound(self, self->soundPos1); //----(SA) added } return; } /* item = BG_FindItemForKey(self->key, 0); if(item) { if(activator->client->ps.stats[STAT_KEYS] & (1<<item->giTag)) // user has key { if (self->spawnflags & 8 ) { // relay is NOKEY_ONLY and player has key if (self->soundPos1) G_Sound( self, self->soundPos1); //----(SA) added return; } } else // user does not have key { if (!(self->spawnflags & 8) ) { if (self->soundPos1) G_Sound( self, self->soundPos1); //----(SA) added return; } } }*/ /* if(self->spawnflags & 16) { // (SA) take key activator->client->ps.stats[STAT_KEYS] &= ~(1<<item->giTag); // (SA) TODO: "took inventory item" sound }*/ } } G_UseTargets(self, activator); }
void hurt_touch( gentity_t *self, gentity_t *other, trace_t *trace ) { int dflags; int actualDmg = self->damage; if ( self->svFlags & SVF_INACTIVE ) {//set by target_deactivate return; } if ( !other->takedamage ) { return; } if( level.time < self->painDebounceTime + self->wait ) // normal 'wait' check { if( self->spawnflags & 2048 ) // MULTIPLE - allow multiple entities to touch this trigger in one frame { if ( self->painDebounceTime && level.time > self->painDebounceTime ) // if we haven't reached the next frame continue to let ents touch the trigger { return; } } else // only allowing one ent per frame to touch trigger { return; } } // if the player has already activated this trigger this frame if( other && !other->s.number && self->aimDebounceTime == level.time ) { return; } if ( self->spawnflags & 2 ) {//player only if ( other->s.number ) { return; } } if ( self->NPC_targetname && self->NPC_targetname[0] ) {//I am for you, Kirk if ( other->script_targetname && other->script_targetname[0] ) {//must have a name if ( Q_stricmp( self->NPC_targetname, other->script_targetname ) != 0 ) {//not the right guy to fire me off return; } } else {//no name? No trigger. return; } } // play sound if ( !(self->spawnflags & 4) ) { G_Sound( other, self->noise_index ); } if ( self->spawnflags & 8 ) { dflags = DAMAGE_NO_PROTECTION; } else { dflags = 0; } if ( self->delay ) {//Increase dmg over time if ( self->attackDebounceTime < self->delay ) {//FIXME: this is for the entire trigger, not per person, so if someone else jumped in after you were in it for 5 seconds, they'd get damaged faster actualDmg = floor( (double)(self->damage * self->attackDebounceTime / self->delay) ); } self->attackDebounceTime += FRAMETIME; self->e_ThinkFunc = thinkF_trigger_hurt_reset; self->nextthink = level.time + FRAMETIME*2; } if ( actualDmg ) { if (( self->spawnflags & 64 ) && other->client )//electrical damage { // zap effect other->s.powerups |= ( 1 << PW_SHOCKED ); other->client->ps.powerups[PW_SHOCKED] = level.time + 1000; } if ( self->spawnflags & 32 ) {//falling death G_Damage (other, self, self, NULL, NULL, actualDmg, dflags|DAMAGE_NO_ARMOR, MOD_FALLING); // G_Damage will free this ent, which makes it s.number 0, so we must check inuse... if ( !other->s.number && other->health <= 0 ) { if ( self->count ) { extern void CGCam_Fade( vec4_t source, vec4_t dest, float duration ); float src[4] = {0,0,0,0},dst[4]={0,0,0,1}; CGCam_Fade( src, dst, self->count ); } if ( self->spawnflags & 16 ) {//lock cam cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_CDP; cg.overrides.thirdPersonCameraDamp = 0; } if ( other->client ) { other->client->ps.pm_flags |= PMF_SLOW_MO_FALL; } //G_SoundOnEnt( other, CHAN_VOICE, "*falling1.wav" );//CHAN_VOICE_ATTEN? } } else { G_Damage (other, self, self, NULL, NULL, actualDmg, dflags, MOD_TRIGGER_HURT); } if( other && !other->s.number ) { self->aimDebounceTime = level.time; } if (( self->spawnflags & 64 ) && other->client && other->health <= 0 )//electrical damage {//just killed them, make the effect last longer since dead clients don't touch triggers other->client->ps.powerups[PW_SHOCKED] = level.time + 10000; } self->painDebounceTime = level.time; } if ( self->wait < 0 ) { self->e_TouchFunc = touchF_NULL; } }
/* ------------------------- NPC_Mark1_Pain - look at what was hit and see if it should be removed from the model. ------------------------- */ void NPC_Mark1_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) { int newBolt,i,chance; NPC_Pain( self, inflictor, other, point, damage, mod ); G_Sound( self, G_SoundIndex("sound/chars/mark1/misc/mark1_pain")); // Hit in the CHEST??? if (hitLoc==HL_CHEST) { chance = Q_irand( 1, 4); if ((chance == 1) && (damage > 5)) { NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } } // Hit in the left arm? else if ((hitLoc==HL_ARM_LT) && (self->locationDamage[HL_ARM_LT] > LEFT_ARM_HEALTH)) { if (self->locationDamage[hitLoc] >= LEFT_ARM_HEALTH) // Blow it up? { newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*flash3" ); if ( newBolt != -1 ) { NPC_Mark1_Part_Explode(self,newBolt); } gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "l_arm", TURN_OFF ); } } // Hit in the right arm? else if ((hitLoc==HL_ARM_RT) && (self->locationDamage[HL_ARM_RT] > RIGHT_ARM_HEALTH)) // Blow it up? { if (self->locationDamage[hitLoc] >= RIGHT_ARM_HEALTH) { newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*flash4" ); if ( newBolt != -1 ) { // G_PlayEffect( "small_chunks", self->playerModel, self->genericBolt2, self->s.number); NPC_Mark1_Part_Explode( self, newBolt ); } gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "r_arm", TURN_OFF ); } } // Check ammo pods else { for (i=0;i<6;i++) { if ((hitLoc==HL_GENERIC1+i) && (self->locationDamage[HL_GENERIC1+i] > AMMO_POD_HEALTH)) // Blow it up? { if (self->locationDamage[hitLoc] >= AMMO_POD_HEALTH) { newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], va("*torso_tube%d",(i+1)) ); if ( newBolt != -1 ) { NPC_Mark1_Part_Explode(self,newBolt); } gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], va("torso_tube%d",(i+1)), TURN_OFF ); NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); break; } } } } // Are both guns shot off? if ((gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "l_arm" )) && (gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "r_arm" ))) { G_Damage(self,NULL,NULL,NULL,NULL,self->health,0,MOD_UNKNOWN); } }
/* * ClientThink */ void ClientThink( edict_t *ent, usercmd_t *ucmd, int timeDelta ) { gclient_t *client; int i, j; static pmove_t pm; int delta, count; client = ent->r.client; client->ps.POVnum = ENTNUM( ent ); client->ps.playerNum = PLAYERNUM( ent ); // anti-lag if( ent->r.svflags & SVF_FAKECLIENT ) { client->timeDelta = 0; } else { int nudge; int fixedNudge = ( game.snapFrameTime ) * 0.5; // fixme: find where this nudge comes from. // add smoothing to timeDelta between the last few ucmds and a small fine-tuning nudge. nudge = fixedNudge + g_antilag_timenudge->integer; timeDelta += nudge; clamp( timeDelta, -g_antilag_maxtimedelta->integer, 0 ); // smooth using last valid deltas i = client->timeDeltasHead - 6; if( i < 0 ) i = 0; for( count = 0, delta = 0; i < client->timeDeltasHead; i++ ) { if( client->timeDeltas[i & G_MAX_TIME_DELTAS_MASK] < 0 ) { delta += client->timeDeltas[i & G_MAX_TIME_DELTAS_MASK]; count++; } } if( !count ) client->timeDelta = timeDelta; else { delta /= count; client->timeDelta = ( delta + timeDelta ) * 0.5; } client->timeDeltas[client->timeDeltasHead & G_MAX_TIME_DELTAS_MASK] = timeDelta; client->timeDeltasHead++; #ifdef UCMDTIMENUDGE client->timeDelta += client->pers.ucmdTimeNudge; #endif } clamp( client->timeDelta, -g_antilag_maxtimedelta->integer, 0 ); // update activity if he touched any controls if( ucmd->forwardmove != 0 || ucmd->sidemove != 0 || ucmd->upmove != 0 || ( ucmd->buttons & ~BUTTON_BUSYICON ) != 0 || client->ucmd.angles[PITCH] != ucmd->angles[PITCH] || client->ucmd.angles[YAW] != ucmd->angles[YAW] ) G_Client_UpdateActivity( client ); client->ucmd = *ucmd; // can exit intermission after two seconds, not counting postmatch if( GS_MatchState() == MATCH_STATE_WAITEXIT && ( ucmd->buttons & BUTTON_ATTACK ) && game.serverTime > GS_MatchStartTime() + 2000 ) level.exitNow = true; // (is this really needed?:only if not cared enough about ps in the rest of the code) // refresh player state position from the entity VectorCopy( ent->s.origin, client->ps.pmove.origin ); VectorCopy( ent->velocity, client->ps.pmove.velocity ); VectorCopy( ent->s.angles, client->ps.viewangles ); client->ps.pmove.gravity = level.gravity; if( GS_MatchState() >= MATCH_STATE_POSTMATCH || GS_MatchPaused() || ( ent->movetype != MOVETYPE_PLAYER && ent->movetype != MOVETYPE_NOCLIP ) ) client->ps.pmove.pm_type = PM_FREEZE; else if( ent->s.type == ET_GIB ) client->ps.pmove.pm_type = PM_GIB; else if( ent->movetype == MOVETYPE_NOCLIP || client->isTV ) client->ps.pmove.pm_type = PM_SPECTATOR; else client->ps.pmove.pm_type = PM_NORMAL; // set up for pmove memset( &pm, 0, sizeof( pmove_t ) ); pm.playerState = &client->ps; if( !client->isTV ) pm.cmd = *ucmd; if( memcmp( &client->old_pmove, &client->ps.pmove, sizeof( pmove_state_t ) ) ) pm.snapinitial = true; // perform a pmove Pmove( &pm ); // save results of pmove client->old_pmove = client->ps.pmove; // update the entity with the new position VectorCopy( client->ps.pmove.origin, ent->s.origin ); VectorCopy( client->ps.pmove.velocity, ent->velocity ); VectorCopy( client->ps.viewangles, ent->s.angles ); ent->viewheight = client->ps.viewheight; VectorCopy( pm.mins, ent->r.mins ); VectorCopy( pm.maxs, ent->r.maxs ); ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; if( pm.groundentity == -1 ) { ent->groundentity = NULL; } else { G_AwardResetPlayerComboStats( ent ); ent->groundentity = &game.edicts[pm.groundentity]; ent->groundentity_linkcount = ent->groundentity->linkcount; } GClip_LinkEntity( ent ); GS_AddLaserbeamPoint( &ent->r.client->resp.trail, &ent->r.client->ps, ucmd->serverTimeStamp ); // Regeneration if( ent->r.client->ps.inventory[POWERUP_REGEN] > 0 && ent->health < 200) { ent->health += ( game.frametime * 0.001f ) * 10.0f; // Regen expires if health reaches 200 if ( ent->health >= 199.0f ) ent->r.client->ps.inventory[POWERUP_REGEN]--; } // fire touch functions if( ent->movetype != MOVETYPE_NOCLIP ) { edict_t *other; // touch other objects for( i = 0; i < pm.numtouch; i++ ) { other = &game.edicts[pm.touchents[i]]; for( j = 0; j < i; j++ ) { if( &game.edicts[pm.touchents[j]] == other ) break; } if( j != i ) continue; // duplicated // player can't touch projectiles, only projectiles can touch the player G_CallTouch( other, ent, NULL, 0 ); } } ent->s.weapon = GS_ThinkPlayerWeapon( &client->ps, ucmd->buttons, ucmd->msec, client->timeDelta ); if( G_IsDead( ent ) ) { if( ent->deathTimeStamp + g_respawn_delay_min->integer <= level.time ) client->resp.snap.buttons |= ucmd->buttons; } else if( client->ps.pmove.stats[PM_STAT_NOUSERCONTROL] <= 0 ) client->resp.snap.buttons |= ucmd->buttons; // trigger the instashield if( GS_Instagib() && g_instashield->integer ) { if( client->ps.pmove.pm_type == PM_NORMAL && pm.cmd.upmove < 0 && client->resp.instashieldCharge == INSTA_SHIELD_MAX && client->ps.inventory[POWERUP_SHELL] == 0 ) { client->ps.inventory[POWERUP_SHELL] = client->resp.instashieldCharge; G_Sound( ent, CHAN_AUTO, trap_SoundIndex( GS_FindItemByTag( POWERUP_SHELL )->pickup_sound ), ATTN_NORM ); } } // if( client->ps.pmove.pm_type == PM_NORMAL ) client->level.stats.had_playtime = true; // generating plrkeys (optimized for net communication) ClientMakePlrkeys( client, ucmd ); }
/* ============= P_WorldEffects Check for lava / slime contents and drowning ============= */ void P_WorldEffects(gentity_t * ent) { int waterlevel; if (ent->client->noclip) { ent->client->airOutTime = level.time + 12000; // don't need air return; } waterlevel = ent->waterlevel; // // check for drowning // if (waterlevel == 3) { // if out of air, start drowning if (ent->client->airOutTime < level.time) { // drown! ent->client->airOutTime += 1000; if (ent->health > 0) { // take more damage the longer underwater ent->damage += 2; if (ent->damage > 15) ent->damage = 15; // play a gurp sound instead of a normal pain sound if (ent->health <= ent->damage) { G_Sound(ent, CHAN_VOICE, G_SoundIndex("*drown.wav")); } else if (rand() & 1) { G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp1.wav")); } else { G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp2.wav")); } // don't play a normal pain sound ent->pain_debounce_time = level.time + 200; G_Damage(ent, NULL, NULL, NULL, NULL, ent->damage, DAMAGE_NO_ARMOR, MOD_WATER); } } } else { ent->client->airOutTime = level.time + 12000; ent->damage = 2; } // // check for sizzle damage (move to pmove?) // if (waterlevel && (ent->watertype & (CONTENTS_LAVA | CONTENTS_SLIME))) { // Elder: changed around a bit -- using timestamp variable if (ent->health > 0 && level.time > ent->timestamp) { if (ent->watertype & CONTENTS_LAVA) { G_Damage(ent, NULL, NULL, NULL, NULL, 3 * waterlevel, 0, MOD_LAVA); } if (ent->watertype & CONTENTS_SLIME) { G_Damage(ent, NULL, NULL, NULL, NULL, waterlevel, 0, MOD_SLIME); } // Elder: added ent->timestamp = level.time + FRAMETIME; } } }
/* ============= P_WorldEffects Check for lava / slime contents and drowning ============= */ void P_WorldEffects( gentity_t *ent ) { qboolean envirosuit; int waterlevel; // TTimo: unused // static int lastflameburntime = 0; // JPW NERVE for slowing flamethrower burn intervals and doing more damage per interval if ( ent->client->noclip ) { ent->client->airOutTime = level.time + 12000; // don't need air return; } waterlevel = ent->waterlevel; envirosuit = ent->client->ps.powerups[PW_BATTLESUIT] > level.time; // G_Printf("breathe: %d invuln: %d nofatigue: %d\n", ent->client->ps.powerups[PW_BREATHER], level.time - ent->client->ps.powerups[PW_INVULNERABLE], ent->client->ps.powerups[PW_NOFATIGUE]); // // check for drowning // if ( waterlevel == 3 ) { // envirosuit give air if ( envirosuit ) { ent->client->airOutTime = level.time + 10000; } //----(SA) both these will end up being by virtue of having the 'breather' powerup if ( ent->client->ps.aiChar == AICHAR_FROGMAN ) { // let frogmen breathe forever ent->client->airOutTime = level.time + 10000; } if ( ent->client->ps.aiChar == AICHAR_SEALOPER ) { // ditto ent->client->airOutTime = level.time + 10000; } // if out of air, start drowning if ( ent->client->airOutTime < level.time ) { if ( ent->client->ps.powerups[PW_BREATHER] ) { // take air from the breather now that we need it ent->client->ps.powerups[PW_BREATHER] -= ( level.time - ent->client->airOutTime ); ent->client->airOutTime = level.time + ( level.time - ent->client->airOutTime ); } else { // drown! ent->client->airOutTime += 1000; if ( ent->health > 0 ) { // take more damage the longer underwater ent->damage += 2; if ( ent->damage > 15 ) { ent->damage = 15; } // play a gurp sound instead of a normal pain sound if ( ent->health <= ent->damage ) { G_Sound( ent, G_SoundIndex( "*drown.wav" ) ); } else if ( rand() & 1 ) { G_Sound( ent, G_SoundIndex( "sound/player/gurp1.wav" ) ); } else { G_Sound( ent, G_SoundIndex( "sound/player/gurp2.wav" ) ); } // don't play a normal pain sound ent->pain_debounce_time = level.time + 200; G_Damage( ent, NULL, NULL, NULL, NULL, ent->damage, DAMAGE_NO_ARMOR, MOD_WATER ); } } } } else { ent->client->airOutTime = level.time + 12000; ent->damage = 2; } // // check for sizzle damage (move to pmove?) // if ( waterlevel && ( ent->watertype & CONTENTS_LAVA ) ) { if ( ent->health > 0 && ent->pain_debounce_time <= level.time ) { if ( envirosuit ) { G_AddEvent( ent, EV_POWERUP_BATTLESUIT, 0 ); } else { if ( ent->watertype & CONTENTS_LAVA ) { G_Damage( ent, NULL, NULL, NULL, NULL, 30 * waterlevel, 0, MOD_LAVA ); } } } } // // check for burning from flamethrower // // JPW NERVE MP way if ( ent->s.onFireEnd && ent->client ) { if ( level.time - ent->client->lastBurnTime >= MIN_BURN_INTERVAL ) { // JPW NERVE server-side incremental damage routine / player damage/health is int (not float) // so I can't allocate 1.5 points per server tick, and 1 is too weak and 2 is too strong. // solution: allocate damage far less often (MIN_BURN_INTERVAL often) and do more damage. // That way minimum resolution (1 point) damage changes become less critical. ent->client->lastBurnTime = level.time; if ( ( ent->s.onFireEnd > level.time ) && ( ent->health > 0 ) ) { gentity_t *attacker; attacker = g_entities + ent->flameBurnEnt; G_Damage( ent, attacker->parent, attacker->parent, NULL, NULL, 5, DAMAGE_NO_KNOCKBACK, MOD_FLAMETHROWER ); // JPW NERVE was 7 } } } // jpw }
void eweb_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { if ( !eweb_can_be_used( self, other, activator ) ) { return; } int oldWeapon = activator->s.weapon; if ( oldWeapon == WP_SABER ) { self->alt_fire = activator->client->ps.SaberActive(); } // swap the users weapon with the emplaced gun and add the ammo the gun has to the player activator->client->ps.weapon = self->s.weapon; Add_Ammo( activator, WP_EMPLACED_GUN, self->count ); activator->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_EMPLACED_GUN ); // Allow us to point from one to the other activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it. self->activator = activator; G_RemoveWeaponModels( activator ); extern void ChangeWeapon( gentity_t *ent, int newWeapon ); if ( activator->NPC ) { ChangeWeapon( activator, WP_EMPLACED_GUN ); } else if ( activator->s.number == 0 ) { // we don't want for it to draw the weapon select stuff cg.weaponSelect = WP_EMPLACED_GUN; CG_CenterPrint( "@SP_INGAME_EXIT_VIEW", SCREEN_HEIGHT * 0.95 ); } VectorCopy( activator->currentOrigin, self->pos4 );//keep this around so we know when to make them play the strafe anim // the gun will track which weapon we used to have self->s.weapon = oldWeapon; // Lock the player activator->client->ps.eFlags |= EF_LOCKED_TO_WEAPON; activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it. self->activator = activator; self->delay = level.time; // can't disconnect from the thing for half a second // Let the gun be considered an enemy //Ugh, so much AI code seems to assume enemies are clients, maybe this shouldn't be on, but it's too late in the game to change it now without knowing what side-effects this will have self->svFlags |= SVF_NONNPC_ENEMY; self->noDamageTeam = activator->client->playerTeam; //FIXME: should really wait a bit after spawn and get this just once? self->waypoint = NAV::GetNearestNode(self); #ifdef _DEBUG if ( self->waypoint == -1 ) { gi.Printf( S_COLOR_RED"ERROR: no waypoint for emplaced_gun %s at %s\n", self->targetname, vtos(self->currentOrigin) ); } #endif G_Sound( self, G_SoundIndex( "sound/weapons/eweb/eweb_mount.mp3" )); #ifdef _IMMERSION G_Force( self, G_ForceIndex( "fffx/weapons/emplaced/emplaced_mount", FF_CHANNEL_TOUCH ) ); #endif // _IMMERSION if ( !(self->spawnflags&EMPLACED_PLAYERUSE) || activator->s.number == 0 ) {//player-only usescript or any usescript // Run use script G_ActivateBehavior( self, BSET_USE ); } }
//------------------------------------ void Seeker_Strafe( void ) { int side; vec3_t end, right, dir; trace_t tr; if ( random() > 0.7f || !NPC->enemy || !NPC->enemy->client ) { // Do a regular style strafe AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL ); // Pick a random strafe direction, then check to see if doing a strafe would be // reasonably valid side = ( rand() & 1 ) ? -1 : 1; VectorMA( NPC->currentOrigin, SEEKER_STRAFE_DIS * side, right, end ); gi.trace( &tr, NPC->currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID, G2_NOCOLLIDE, 0 ); // Close enough if ( tr.fraction > 0.9f ) { VectorMA( NPC->client->ps.velocity, SEEKER_STRAFE_VEL * side, right, NPC->client->ps.velocity ); G_Sound( NPC, G_SoundIndex( "sound/chars/seeker/misc/hiss" )); // Add a slight upward push NPC->client->ps.velocity[2] += SEEKER_UPWARD_PUSH; NPCInfo->standTime = level.time + 1000 + random() * 500; } } else { // Do a strafe to try and keep on the side of their enemy AngleVectors( NPC->enemy->client->renderInfo.eyeAngles, dir, right, NULL ); // Pick a random side side = ( rand() & 1 ) ? -1 : 1; VectorMA( NPC->enemy->currentOrigin, SEEKER_STRAFE_DIS * side, right, end ); // then add a very small bit of random in front of/behind the player action VectorMA( end, crandom() * 25, dir, end ); gi.trace( &tr, NPC->currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID, G2_NOCOLLIDE, 0 ); // Close enough if ( tr.fraction > 0.9f ) { VectorSubtract( tr.endpos, NPC->currentOrigin, dir ); dir[2] *= 0.25; // do less upward change float dis = VectorNormalize( dir ); // Try to move the desired enemy side VectorMA( NPC->client->ps.velocity, dis, dir, NPC->client->ps.velocity ); G_Sound( NPC, G_SoundIndex( "sound/chars/seeker/misc/hiss" )); // Add a slight upward push NPC->client->ps.velocity[2] += SEEKER_UPWARD_PUSH; NPCInfo->standTime = level.time + 2500 + random() * 500; } } }
void G_SetTauntAnim( gentity_t *ent, int taunt ) { if ( !ent || !ent->client ) { return; } if ( !ent->client->ps.torsoAnimTimer && !ent->client->ps.legsAnimTimer && !ent->client->ps.weaponTime && ent->client->ps.saberLockTime < level.time ) { int anim = -1; switch ( taunt ) { case TAUNT_TAUNT: if ( ent->client->ps.weapon != WP_SABER ) { anim = BOTH_ENGAGETAUNT; } else if ( ent->client->ps.saber[0].tauntAnim != -1 ) { anim = ent->client->ps.saber[0].tauntAnim; } else if ( ent->client->ps.dualSabers && ent->client->ps.saber[1].tauntAnim != -1 ) { anim = ent->client->ps.saber[1].tauntAnim; } else { switch ( ent->client->ps.saberAnimLevel ) { case SS_FAST: case SS_TAVION: if ( ent->client->ps.saber[1].Active() ) {//turn off second saber G_Sound( ent, ent->client->ps.saber[1].soundOff ); } else if ( ent->client->ps.saber[0].Active() ) {//turn off first G_Sound( ent, ent->client->ps.saber[0].soundOff ); } ent->client->ps.SaberDeactivate(); anim = BOTH_GESTURE1; break; case SS_MEDIUM: case SS_STRONG: case SS_DESANN: anim = BOTH_ENGAGETAUNT; break; case SS_DUAL: ent->client->ps.SaberActivate(); anim = BOTH_DUAL_TAUNT; break; case SS_STAFF: ent->client->ps.SaberActivate(); anim = BOTH_STAFF_TAUNT; break; } } break; case TAUNT_BOW: if ( ent->client->ps.saber[0].bowAnim != -1 ) { anim = ent->client->ps.saber[0].bowAnim; } else if ( ent->client->ps.dualSabers && ent->client->ps.saber[1].bowAnim != -1 ) { anim = ent->client->ps.saber[1].bowAnim; } else { anim = BOTH_BOW; } if ( ent->client->ps.saber[1].Active() ) {//turn off second saber G_Sound( ent, ent->client->ps.saber[1].soundOff ); } else if ( ent->client->ps.saber[0].Active() ) {//turn off first G_Sound( ent, ent->client->ps.saber[0].soundOff ); } ent->client->ps.SaberDeactivate(); break; case TAUNT_MEDITATE: if ( ent->client->ps.saber[0].meditateAnim != -1 ) { anim = ent->client->ps.saber[0].meditateAnim; } else if ( ent->client->ps.dualSabers && ent->client->ps.saber[1].meditateAnim != -1 ) { anim = ent->client->ps.saber[1].meditateAnim; } else { anim = BOTH_MEDITATE; } if ( ent->client->ps.saber[1].Active() ) {//turn off second saber G_Sound( ent, ent->client->ps.saber[1].soundOff ); } else if ( ent->client->ps.saber[0].Active() ) {//turn off first G_Sound( ent, ent->client->ps.saber[0].soundOff ); } ent->client->ps.SaberDeactivate(); break; case TAUNT_FLOURISH: if ( ent->client->ps.weapon == WP_SABER ) { ent->client->ps.SaberActivate(); if ( ent->client->ps.saber[0].flourishAnim != -1 ) { anim = ent->client->ps.saber[0].flourishAnim; } else if ( ent->client->ps.dualSabers && ent->client->ps.saber[1].flourishAnim != -1 ) { anim = ent->client->ps.saber[1].flourishAnim; } else { switch ( ent->client->ps.saberAnimLevel ) { case SS_FAST: case SS_TAVION: anim = BOTH_SHOWOFF_FAST; break; case SS_MEDIUM: anim = BOTH_SHOWOFF_MEDIUM; break; case SS_STRONG: case SS_DESANN: anim = BOTH_SHOWOFF_STRONG; break; case SS_DUAL: anim = BOTH_SHOWOFF_DUAL; break; case SS_STAFF: anim = BOTH_SHOWOFF_STAFF; break; } } } break; case TAUNT_GLOAT: if ( ent->client->ps.saber[0].gloatAnim != -1 ) { anim = ent->client->ps.saber[0].gloatAnim; } else if ( ent->client->ps.dualSabers && ent->client->ps.saber[1].gloatAnim != -1 ) { anim = ent->client->ps.saber[1].gloatAnim; } else { switch ( ent->client->ps.saberAnimLevel ) { case SS_FAST: case SS_TAVION: anim = BOTH_VICTORY_FAST; break; case SS_MEDIUM: anim = BOTH_VICTORY_MEDIUM; break; case SS_STRONG: case SS_DESANN: ent->client->ps.SaberActivate(); anim = BOTH_VICTORY_STRONG; break; case SS_DUAL: ent->client->ps.SaberActivate(); anim = BOTH_VICTORY_DUAL; break; case SS_STAFF: ent->client->ps.SaberActivate(); anim = BOTH_VICTORY_STAFF; break; } } break; } if ( anim != -1 ) { if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) { int parts = SETANIM_TORSO; if ( anim != BOTH_ENGAGETAUNT ) { parts = SETANIM_BOTH; VectorClear( ent->client->ps.velocity ); } NPC_SetAnim( ent, parts, anim, (SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD) ); } if ( taunt != TAUNT_MEDITATE && taunt != TAUNT_BOW ) {//no sound for meditate or bow G_TauntSound( ent, taunt ); } } } }
void SV_Physics_Step (edict_t *ent) { bool wasonground; bool hitsound = false; float *vel; float speed, newspeed, control; float friction; edict_t *groundentity; int mask; // airborn monsters should always check for ground if (!ent->groundentity) M_CheckGround (ent); groundentity = ent->groundentity; SV_CheckVelocity (ent); if (groundentity) wasonground = true; else wasonground = false; if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2]) SV_AddRotationalFriction (ent); // add gravity except: // flying monsters // swimming monsters who are in the water if (! wasonground) if (!(ent->flags & FL_FLY)) if (!((ent->flags & FL_SWIM) && (ent->waterlevel > 2))) { if (ent->velocity[2] < level.gravity*-0.1) hitsound = true; if (ent->waterlevel == 0) SV_AddGravity (ent); } // friction for flying monsters that have been given vertical velocity if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0)) { speed = fabs(ent->velocity[2]); control = speed < sv_stopspeed ? sv_stopspeed : speed; friction = sv_friction/3; newspeed = speed - (FRAMETIME * control * friction); if (newspeed < 0) newspeed = 0; newspeed /= speed; ent->velocity[2] *= newspeed; } // friction for flying monsters that have been given vertical velocity if ((ent->flags & FL_SWIM) && (ent->velocity[2] != 0)) { speed = fabs(ent->velocity[2]); control = speed < sv_stopspeed ? sv_stopspeed : speed; newspeed = speed - (FRAMETIME * control * sv_waterfriction * ent->waterlevel); if (newspeed < 0) newspeed = 0; newspeed /= speed; ent->velocity[2] *= newspeed; } if (ent->velocity[2] || ent->velocity[1] || ent->velocity[0]) { // apply friction // let dead monsters who aren't completely onground slide if ((wasonground) || (ent->flags & (FL_SWIM|FL_FLY))) if (!(ent->health <= 0.0 && !M_CheckBottom(ent))) { vel = ent->velocity; speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1]); if (speed) { friction = sv_friction; control = speed < sv_stopspeed ? sv_stopspeed : speed; newspeed = speed - FRAMETIME*control*friction; if (newspeed < 0) newspeed = 0; newspeed /= speed; vel[0] *= newspeed; vel[1] *= newspeed; } } if (ent->r.svflags & SVF_MONSTER) mask = MASK_MONSTERSOLID; else mask = MASK_SOLID; SV_FlyMove (ent, FRAMETIME, mask); GClip_LinkEntity (ent); GClip_TouchTriggers (ent); if (ent->groundentity) if (!wasonground) if (hitsound) G_Sound (ent, 0, trap_SoundIndex( S_LAND ), ATTN_NORM); } }
void NPC_Respond( gentity_t *self, int userNum ) { int event = -1; /* if ( Q_irand( 0, 1 ) ) { event = Q_irand(EV_RESPOND1, EV_RESPOND3); } else { event = Q_irand(EV_BUSY1, EV_BUSY3); } */ if ( !Q_irand( 0, 1 ) ) {//set looktarget to them for a second or two NPC_TempLookTarget( self, userNum, 1000, 3000 ); } //some last-minute hacked in responses switch ( self->client->NPC_class ) { case CLASS_JAN: if ( self->enemy ) { if ( !Q_irand( 0, 2 ) ) { event = Q_irand( EV_CHASE1, EV_CHASE3 ); } else if ( Q_irand( 0, 1 ) ) { event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); } else { event = Q_irand( EV_COVER1, EV_COVER5 ); } } else if ( !Q_irand( 0, 2 ) ) { event = EV_SUSPICIOUS4; } else if ( !Q_irand( 0, 1 ) ) { event = EV_SOUND1; } else { event = EV_CONFUSE1; } break; case CLASS_LANDO: if ( self->enemy ) { if ( !Q_irand( 0, 2 ) ) { event = Q_irand( EV_CHASE1, EV_CHASE3 ); } else if ( Q_irand( 0, 1 ) ) { event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); } else { event = Q_irand( EV_COVER1, EV_COVER5 ); } } else if ( !Q_irand( 0, 6 ) ) { event = EV_SIGHT2; } else if ( !Q_irand( 0, 5 ) ) { event = EV_GIVEUP4; } else if ( Q_irand( 0, 4 ) > 1 ) { event = Q_irand( EV_SOUND1, EV_SOUND3 ); } else { event = Q_irand( EV_JDETECTED1, EV_JDETECTED2 ); } break; case CLASS_LUKE: if ( self->enemy ) { event = EV_COVER1; } else { event = Q_irand( EV_SOUND1, EV_SOUND3 ); } break; case CLASS_JEDI: if ( !self->enemy ) { if ( !(self->svFlags&SVF_IGNORE_ENEMIES) && (self->NPC->scriptFlags&SCF_LOOK_FOR_ENEMIES) && self->client->enemyTeam == TEAM_ENEMY ) { event = Q_irand( EV_ANGER1, EV_ANGER3 ); } else { event = Q_irand( EV_TAUNT1, EV_TAUNT2 ); } } break; case CLASS_PRISONER: if ( self->enemy ) { if ( Q_irand( 0, 1 ) ) { event = Q_irand( EV_CHASE1, EV_CHASE3 ); } else { event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); } } else { event = Q_irand( EV_SOUND1, EV_SOUND3 ); } break; case CLASS_REBEL: if ( self->enemy ) { if ( !Q_irand( 0, 2 ) ) { event = Q_irand( EV_CHASE1, EV_CHASE3 ); } else { event = Q_irand( EV_DETECTED1, EV_DETECTED5 ); } } else { event = Q_irand( EV_SOUND1, EV_SOUND3 ); } break; case CLASS_BESPIN_COP: if ( !Q_stricmp( "bespincop", self->NPC_type ) ) {//variant 1 if ( self->enemy ) { if ( Q_irand( 0, 9 ) > 6 ) { event = Q_irand( EV_CHASE1, EV_CHASE3 ); } else if ( Q_irand( 0, 6 ) > 4 ) { event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); } else { event = Q_irand( EV_COVER1, EV_COVER5 ); } } else if ( !Q_irand( 0, 3 ) ) { event = Q_irand( EV_SIGHT2, EV_SIGHT3 ); } else if ( !Q_irand( 0, 1 ) ) { event = Q_irand( EV_SOUND1, EV_SOUND3 ); } else if ( !Q_irand( 0, 2 ) ) { event = EV_LOST1; } else if ( !Q_irand( 0, 1 ) ) { event = EV_ESCAPING2; } else { event = EV_GIVEUP4; } } else {//variant2 if ( self->enemy ) { if ( Q_irand( 0, 9 ) > 6 ) { event = Q_irand( EV_CHASE1, EV_CHASE3 ); } else if ( Q_irand( 0, 6 ) > 4 ) { event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); } else { event = Q_irand( EV_COVER1, EV_COVER5 ); } } else if ( !Q_irand( 0, 3 ) ) { event = Q_irand( EV_SIGHT1, EV_SIGHT2 ); } else if ( !Q_irand( 0, 1 ) ) { event = Q_irand( EV_SOUND1, EV_SOUND3 ); } else if ( !Q_irand( 0, 2 ) ) { event = EV_LOST1; } else if ( !Q_irand( 0, 1 ) ) { event = EV_GIVEUP3; } else { event = EV_CONFUSE1; } } break; case CLASS_R2D2: // droid G_Sound(self, G_SoundIndex(va("sound/chars/r2d2/misc/r2d2talk0%d.wav",Q_irand(1, 3)))); break; case CLASS_R5D2: // droid G_Sound(self, G_SoundIndex(va("sound/chars/r5d2/misc/r5talk%d.wav",Q_irand(1, 4)))); break; case CLASS_MOUSE: // droid G_Sound(self, G_SoundIndex(va("sound/chars/mouse/misc/mousego%d.wav",Q_irand(1, 3)))); break; case CLASS_GONK: // droid G_Sound(self, G_SoundIndex(va("sound/chars/gonk/misc/gonktalk%d.wav",Q_irand(1, 2)))); break; } if ( event != -1 ) { //hack here because we reuse some "combat" and "extra" sounds qboolean addFlag = (self->NPC->scriptFlags&SCF_NO_COMBAT_TALK); self->NPC->scriptFlags &= ~SCF_NO_COMBAT_TALK; G_AddVoiceEvent( self, event, 3000 ); if ( addFlag ) { self->NPC->scriptFlags |= SCF_NO_COMBAT_TALK; } } }
void misc_model_breakable_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc ) { int numChunks; float size = 0, scale; vec3_t dir, up, dis; //NOTE: Stop any scripts that are currently running (FLUSH)... ? //Turn off animation self->s.frame = self->startFrame = self->endFrame = 0; self->svFlags &= ~SVF_ANIMATING; self->health = 0; //Throw some chunks AngleVectors( self->s.apos.trBase, dir, NULL, NULL ); VectorNormalize( dir ); numChunks = random() * 6 + 20; VectorSubtract( self->absmax, self->absmin, dis ); // This formula really has no logical basis other than the fact that it seemed to be the closest to yielding the results that I wanted. // Volume is length * width * height...then break that volume down based on how many chunks we have scale = sqrt( sqrt( dis[0] * dis[1] * dis[2] )) * 1.75f; if ( scale > 48 ) { size = 2; } else if ( scale > 24 ) { size = 1; } scale = scale / numChunks; if ( self->radius > 0.0f ) { // designer wants to scale number of chunks, helpful because the above scale code is far from perfect // I do this after the scale calculation because it seems that the chunk size generally seems to be very close, it's just the number of chunks is a bit weak numChunks *= self->radius; } VectorAdd( self->absmax, self->absmin, dis ); VectorScale( dis, 0.5f, dis ); CG_Chunks( self->s.number, dis, dir, self->absmin, self->absmax, 300, numChunks, self->material, self->s.modelindex3, scale ); self->e_PainFunc = painF_NULL; self->e_DieFunc = dieF_NULL; // self->e_UseFunc = useF_NULL; self->takedamage = qfalse; if ( !(self->spawnflags & 4) ) {//We don't want to stay solid self->s.solid = 0; self->contents = 0; self->clipmask = 0; gi.linkentity(self); } VectorSet(up, 0, 0, 1); if(self->target) { G_UseTargets(self, attacker); } if(inflictor->client) { VectorSubtract( self->currentOrigin, inflictor->currentOrigin, dir ); VectorNormalize( dir ); } else { VectorCopy(up, dir); } if ( !(self->spawnflags & 2048) ) // NO_EXPLOSION { // Ok, we are allowed to explode, so do it now! if(self->splashDamage > 0 && self->splashRadius > 0) {//explode vec3_t org; AddSightEvent( attacker, self->currentOrigin, 256, AEL_DISCOVERED, 100 ); AddSoundEvent( attacker, self->currentOrigin, 128, AEL_DISCOVERED ); //FIXME: specify type of explosion? (barrel, electrical, etc.) Also, maybe just use the explosion effect below since it's // a bit better? // up the origin a little for the damage check, because several models have their origin on the ground, so they don't alwasy do damage, not the optimal solution... VectorCopy( self->currentOrigin, org ); if ( self->mins[2] > -4 ) {//origin is going to be below it or very very low in the model //center the origin org[2] = self->currentOrigin[2] + self->mins[2] + (self->maxs[2] - self->mins[2])/2.0f; } G_RadiusDamage( org, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN ); if ( self->model && Q_stricmp( "models/map_objects/ships/tie_fighter.md3", self->model ) == 0 ) {//TEMP HACK for Tie Fighters- they're HUGE G_PlayEffect( "fighter_explosion2", self->currentOrigin ); G_Sound( self, G_SoundIndex( "sound/weapons/tie_fighter/TIEexplode.wav" ) ); } else { CG_MiscModelExplosion( self->absmin, self->absmax, size, self->material ); G_Sound( self, G_SoundIndex("sound/weapons/explosions/cargoexplode.wav") ); } } else {//just break AddSightEvent( attacker, self->currentOrigin, 128, AEL_DISCOVERED ); AddSoundEvent( attacker, self->currentOrigin, 64, AEL_SUSPICIOUS ); // This is the default explosion CG_MiscModelExplosion( self->absmin, self->absmax, size, self->material ); G_Sound(self, G_SoundIndex("sound/weapons/explosions/cargoexplode.wav")); } } self->e_ThinkFunc = thinkF_NULL; self->nextthink = -1; if(self->s.modelindex2 != -1 && !(self->spawnflags & 8)) {//FIXME: modelindex doesn't get set to -1 if the damage model doesn't exist self->svFlags |= SVF_BROKEN; self->s.modelindex = self->s.modelindex2; G_ActivateBehavior( self, BSET_DEATH ); } else { G_FreeEntity( self ); } }