#include "precompiled.h" #pragma hdrstop #include "prey_local.h" const idEventDef AI_RemoveJetFx("removeJetFx"); const idEventDef AI_CreateJetFx("createJetFx"); const idEventDef AI_LaunchMine("launchMine", "d"); const idEventDef MA_IsEnemySniping("isEnemySniping", NULL, 'd'); const idEventDef MA_NumMines("numMines", NULL, 'f'); const idEventDef MA_DodgeProjectile("", "e"); const idEventDef MA_AppendHoverFx( "", "e" ); CLASS_DECLARATION( hhMonsterAI, hhJetpackHarvesterSimple ) EVENT( AI_RemoveJetFx, hhJetpackHarvesterSimple::Event_RemoveJetFx ) EVENT( EV_Activate, hhJetpackHarvesterSimple::Event_Activate) EVENT( AI_CreateJetFx, hhJetpackHarvesterSimple::Event_CreateJetFx ) EVENT( AI_LaunchMine, hhJetpackHarvesterSimple::Event_LaunchMine ) EVENT( EV_Broadcast_AppendFxToList, hhJetpackHarvesterSimple::Event_AppendToThrusterList ) EVENT( MA_OnProjectileLaunch, hhJetpackHarvesterSimple::Event_OnProjectileLaunch ) EVENT( MA_DodgeProjectile, hhJetpackHarvesterSimple::Event_DodgeProjectile ) EVENT( MA_IsEnemySniping, hhJetpackHarvesterSimple::Event_IsEnemySniping ) EVENT( MA_NumMines, hhJetpackHarvesterSimple::Event_NumMines ) EVENT( MA_AppendHoverFx, hhJetpackHarvesterSimple::Event_AppendHoverFx ) END_CLASS #ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build hhJetpackHarvesterSimple::~hhJetpackHarvesterSimple() { for ( int i=0;iGetPhysics()->GetAxis(); collision.endpos = mineList[i]->GetPhysics()->GetOrigin(); collision.c.point = mineList[i]->GetPhysics()->GetOrigin(); collision.c.normal.Set( 0, 0, 1 ); mineList[i]->Explode( &collision, idVec3(0,0,0), 0 ); } } } void hhJetpackHarvesterSimple::Spawn(void) { lastAntiProjectileAttack = gameLocal.GetTime(); for(int i=0;iGetJointWorldTransform( bones[i], bonePos, boneAxis ); fxInfo.SetEntity( this ); fxInfo.SetBindBone( bones[i] ); fxInfo.Toggle(); fxInfo.RemoveWhenDone( false ); if( fxThrusters[ThrustType_Idle][i] != NULL ) { gameLocal.Warning("Jetpack harvester already had flamejet made."); } BroadcastFxInfo( psystem, bonePos, boneAxis, &fxInfo, &EV_Broadcast_AppendFxToList ); } } psystem = spawnArgs.GetString( "fx_thruster_hover" ); if ( psystem && *psystem ) { for(int i=0;i* parmList ) { if( !parmList || parmList->Num() != 2 ) { return; } idVec3 jointPos; idMat3 jointAxis; const char* projectileDefName = (*parmList)[0].c_str(); const char* jointName = (*parmList)[1].c_str(); if( !projectileDefName || !projectileDefName[0] ) { return; } if( !GetJointWorldTransform(jointName, jointPos, jointAxis) ) { return; } hhHarvesterMine* mine = static_cast( gameLocal.SpawnObject(projectileDefName) ); mine->Create( this, jointPos, jointAxis ); mine->Launch( jointPos, jointAxis, physicsObj.GetPushedLinearVelocity() ); } void hhJetpackHarvesterSimple::Hide( void ) { hhMonsterAI::Hide(); for(int i=0;iNozzle(FALSE); } if(fxThrusters[ThrustType_Hover][i].GetEntity()) { fxThrusters[ThrustType_Hover][i]->Nozzle(FALSE); } } } void hhJetpackHarvesterSimple::Show( void ) { hhMonsterAI::Show(); for(int i=0;iNozzle(TRUE); } if(fxThrusters[ThrustType_Hover][i].GetEntity()) { fxThrusters[ThrustType_Hover][i]->Nozzle(FALSE); } } } idProjectile *hhJetpackHarvesterSimple::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { //HUMANHEAD mdc - added desiredProjectileDef for supporting multiple projs. idVec3 muzzle; idVec3 dir; idVec3 start; trace_t tr; idBounds projBounds; float distance; const idClipModel *projClip; float attack_accuracy; float attack_cone; float projectile_spread; float diff; float angle; float spin; idAngles ang; int num_projectiles; int i; idMat3 axis; idVec3 tmp; idProjectile *lastProjectile; //HUMANHEAD mdc - added to support multiple projectiles if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile int projIndex = FindProjectileInfo( desiredProjectileDef ); if( projIndex >= 0 ) { SetCurrentProjectile( projIndex ); } } //HUMANHEAD END if ( !projectileDef ) { gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); return NULL; } attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" ); attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); projectile_spread = desiredProjectileDef->GetFloat( "projectile_spread", "0" ); num_projectiles = desiredProjectileDef->GetInt( "num_projectiles", "1" ); idVec3 launch_velocity = vec3_zero; if ( desiredProjectileDef->GetFloat( "launch_y", "0" ) > 0.0f ) { if ( gameLocal.random.RandomFloat() < 0.5 ) { launch_velocity.y = -desiredProjectileDef->GetFloat( "launch_y", "0" ); } else { launch_velocity.y = desiredProjectileDef->GetFloat( "launch_y", "0" ); } launch_velocity *= viewAxis; } GetMuzzle( jointname, muzzle, axis ); if ( !projectile.GetEntity() ) { CreateProjectile( muzzle, axis[ 0 ] ); } lastProjectile = projectile.GetEntity(); if ( target != NULL ) { tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle; tmp.Normalize(); axis = tmp.ToMat3(); } else { axis = viewAxis; } // rotate it because the cone points up by default tmp = axis[2]; axis[2] = axis[0]; axis[0] = -tmp; // make sure the projectile starts inside the monster bounding box const idBounds &ownerBounds = physicsObj.GetAbsBounds(); projClip = lastProjectile->GetPhysics()->GetClipModel(); projBounds = projClip->GetBounds().Rotate( axis ); // check if the owner bounds is bigger than the projectile bounds if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) && ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) && ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) { if ( (ownerBounds - projBounds).RayIntersection( muzzle, viewAxis[ 0 ], distance ) ) { start = muzzle + distance * viewAxis[ 0 ]; } else { start = ownerBounds.GetCenter(); } } else { // projectile bounds bigger than the owner bounds, so just start it from the center start = ownerBounds.GetCenter(); } gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this ); muzzle = tr.endpos; // set aiming direction GetAimDir( muzzle, target, this, dir ); ang = dir.ToAngles(); // adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread. float t = MS2SEC( gameLocal.time + entityNumber * 497 ); ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy; if ( clampToAttackCone ) { // clamp the attack direction to be within monster's attack cone so he doesn't do // things like throw the missile backwards if you're behind him diff = idMath::AngleDelta( ang.yaw, current_yaw ); if ( diff > attack_cone ) { ang.yaw = current_yaw + attack_cone; } else if ( diff < -attack_cone ) { ang.yaw = current_yaw - attack_cone; } } axis = ang.ToMat3(); float spreadRad = DEG2RAD( projectile_spread ); for( i = 0; i < num_projectiles; i++ ) { // spread the projectiles out angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); //dir = axis[ 0 ] + axis[ 2 ] * ( 0 * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) ); dir = axis[ 0 ] - axis[ 1 ] * ( angle * idMath::Cos( spin ) ); dir.Normalize(); // launch the projectile if ( !projectile.GetEntity() ) { CreateProjectile( muzzle, dir ); } lastProjectile = projectile.GetEntity(); lastProjectile->Launch( muzzle, dir, launch_velocity ); projectile = NULL; if ( projectileDef->GetBool( "mine", "0" ) ) { idEntityPtr &entityPtr = mineList.Alloc(); entityPtr = lastProjectile; } } TriggerWeaponEffects( muzzle, axis ); lastAttackTime = gameLocal.time; //HUMANHEAD mdc - added to support multiple projectiles projectile = NULL; SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side //HUMANHEAD END return lastProjectile; } void hhJetpackHarvesterSimple::AnimMove( void ) { idVec3 goalPos; idVec3 delta; idVec3 goalDelta; float goalDist; monsterMoveResult_t moveResult; idVec3 newDest; idVec3 oldorigin = physicsObj.GetOrigin(); #ifdef HUMANHEAD //jsh wallwalk idMat3 oldaxis = GetGravViewAxis(); #else idMat3 oldaxis = viewAxis; #endif physicsObj.UseFlyMove( false ); AI_BLOCKED = false; if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){ move.lastMoveOrigin.Zero(); move.lastMoveTime = gameLocal.time; } move.obstacle = NULL; if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) { TurnToward( lastVisibleEnemyPos ); goalPos = oldorigin; } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); goalPos = oldorigin; } else if ( GetMovePos( goalPos ) ) { if ( move.moveCommand != MOVE_WANDER ) { CheckObstacleAvoidance( goalPos, newDest ); TurnToward( newDest ); } else { TurnToward( goalPos ); } } Turn(); if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) { if ( gameLocal.time < move.startTime + move.duration ) { goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time ); delta = goalPos - oldorigin; delta.z = 0.0f; } else { delta = move.moveDest - oldorigin; delta.z = 0.0f; StopMove( MOVE_STATUS_DONE ); } } else if ( allowMove ) { #ifdef HUMANHEAD //jsh wallwalk GetMoveDelta( oldaxis, GetGravViewAxis(), delta ); #else GetMoveDelta( oldaxis, viewAxis, delta ); #endif } else { delta.Zero(); } if ( move.moveCommand == MOVE_TO_POSITION ) { goalDelta = move.moveDest - oldorigin; goalDist = goalDelta.LengthFast(); if ( goalDist < delta.LengthFast() ) { delta = goalDelta; } } delta *= spawnArgs.GetFloat( "fly_scale", "1.0" ); physicsObj.SetDelta( delta ); physicsObj.ForceDeltaMove( disableGravity ); RunPhysics(); if ( ai_debugMove.GetBool() ) { // HUMANHEAD JRM - so we can see if grav is on or off if(disableGravity) { gameRenderWorld->DebugLine( colorRed, oldorigin, physicsObj.GetOrigin(), 5000 ); } else { gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 ); } } moveResult = physicsObj.GetMoveResult(); if ( !af_push_moveables && attack.Length() && TestMelee() ) { DirectDamage( attack, enemy.GetEntity() ); } else { idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) { KickObstacles( viewAxis[ 0 ], kickForce, blockEnt ); } } BlockedFailSafe(); AI_ONGROUND = physicsObj.OnGround(); idVec3 org = physicsObj.GetOrigin(); if ( oldorigin != org ) { TouchTriggers(); } if ( ai_debugMove.GetBool() ) { gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec ); gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec ); gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); DrawRoute(); } } void hhJetpackHarvesterSimple::Event_NumMines() { //we only want valid mines, so remove dead ones for ( int i=0;ihealth <= 0 ) { mineList.RemoveIndex(i); } } idThread::ReturnFloat( float(mineList.Num()) ); } void hhJetpackHarvesterSimple::Event_DodgeProjectile( hhProjectile *proj ) { if ( !proj || !enemy.IsValid() ) { return; } float min = spawnArgs.GetFloat( "dda_dodge_min", "0.3" ); float max = spawnArgs.GetFloat( "dda_dodge_max", "0.8" ); float dodgeChance = (min + (max-min)*gameLocal.GetDDAValue() ); if ( ai_debugBrain.GetBool() ) { gameLocal.Printf( "%s dodge chance %f\n", GetName(), dodgeChance ); } if ( gameLocal.random.RandomFloat() > dodgeChance ) { return; } idVec3 fw = viewAxis[0]; idVec3 projFw = proj->GetAxis()[0]; if(proj->GetOwner()) projFw = proj->GetOwner()->GetAxis()[0]; float dot = fw * projFw; if(dot > -.7f) return; const function_t *newstate = NULL; //determine which side to dodge to idVec3 povPos, targetPos; povPos = enemy->GetPhysics()->GetOrigin(); targetPos = GetPhysics()->GetOrigin(); idVec3 povToTarget = targetPos - povPos; povToTarget.z = 0.f; povToTarget.Normalize(); idVec3 povLeft, povUp; povToTarget.OrthogonalBasis(povLeft, povUp); povLeft.Normalize(); idVec3 projVel = proj->GetPhysics()->GetLinearVelocity(); projVel.Normalize(); dot = povLeft * projVel; if ( dot < 0 ) { newstate = GetScriptFunction( "state_DodgeRight" ); } else { newstate = GetScriptFunction( "state_DodgeLeft" ); } if ( newstate ) { lastAntiProjectileAttack = gameLocal.GetTime(); SetState( newstate ); UpdateScript(); } } void hhJetpackHarvesterSimple::Event_OnProjectileLaunch( hhProjectile *proj ) { if ( !proj || !spawnArgs.GetBool( "can_dodge", "1" ) || health <= 0 ) { return; } if ( gameLocal.GetTime() - lastAntiProjectileAttack < 1000 * int(spawnArgs.GetFloat( "dodge_freq", "2.0" )) ) { return; } if( proj->GetOwner() && !(ReactionTo(proj->GetOwner()) & (ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE)) ) { return; // The person who launched this projectile wasn't someone to worry about } if ( !enemy.IsValid() ) { return; } PostEventSec( &MA_DodgeProjectile, spawnArgs.GetFloat( "dodge_delay", "0.5" ), proj ); } bool hhJetpackHarvesterSimple::Collide( const trace_t &collision, const idVec3 &velocity ) { if ( af.IsActive() && !IsHidden() && !freezeDamage && !specialDamage ) { const char *fx = spawnArgs.GetString("fx_death"); if ( fx && *fx ) { hhFxInfo fxInfo; fxInfo.RemoveWhenDone(true); BroadcastFxInfo(fx, GetOrigin(), GetAxis(), &fxInfo); Hide(); PostEventMS(&EV_Remove, 1000); } } return false; } void hhJetpackHarvesterSimple::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { if (!frozen) { // Being tractored can mess up predeath fl.canBeTractored = false; } HandleNoGore(); if ( AI_DEAD ) { AI_PAIN = true; AI_DAMAGE = true; return; } //stop rocket fx and sounds StopSound( SND_CHANNEL_BODY3, false ); for(int i=0;iGetGravityNormal() * 800, MASK_MONSTERSOLID, this); if( TraceInfo.fraction < 1.0f ) { if ( HasPathTo( TraceInfo.endpos ) ) { Event_SetMoveType( MOVETYPE_ANIM ); health += 30; state = GetScriptFunction( "state_StartPreDeath" ); SetState( state ); SetWaitState( "" ); return; } } } hhMonsterAI::Killed( inflictor, attacker, damage, dir, location ); //explode creature's mines upon death for ( int i=0;iGetPhysics()->GetAxis(); collision.endpos = mineList[i]->GetPhysics()->GetOrigin(); collision.c.point = mineList[i]->GetPhysics()->GetOrigin(); collision.c.normal.Set( 0, 0, 1 ); mineList[i]->Explode( &collision, idVec3(0,0,0), 0 ); } } } void hhJetpackHarvesterSimple::Event_IsEnemySniping(void) { idThread::ReturnInt( false ); } /* ===================== hhJetpackHarvesterSimple::Save ===================== */ void hhJetpackHarvesterSimple::Save( idSaveGame *savefile ) const { int i; for ( i = 0; i < ThrustSide_Total; i++ ) { for ( int j = 0; j < ThrustType_Total; j++ ) { fxThrusters[i][j].Save( savefile ); } } savefile->WriteInt( lastAntiProjectileAttack ); savefile->WriteBool( allowPreDeath ); int num = mineList.Num(); savefile->WriteInt( num ); savefile->WriteBool( specialDamage ); for ( i = 0; i < num; i++ ) { mineList[i].Save( savefile ); } } /* ===================== hhJetpackHarvesterSimple::Restore ===================== */ void hhJetpackHarvesterSimple::Restore( idRestoreGame *savefile ) { int i; for ( i = 0; i < ThrustSide_Total; i++ ) { for ( int j = 0; j < ThrustType_Total; j++ ) { fxThrusters[i][j].Restore( savefile ); } } savefile->ReadInt( lastAntiProjectileAttack ); savefile->ReadBool( allowPreDeath ); int num; savefile->ReadInt( num ); savefile->ReadBool( specialDamage ); mineList.SetNum( num ); for ( i = 0; i < num; i++ ) { mineList[i].Restore( savefile ); } } void hhJetpackHarvesterSimple::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); if ( !AI_DEAD ) { if ( damageDef && damageDef->GetBool( "ice" ) && spawnArgs.GetBool( "can_freeze", "0" ) ) { freezeDamage = true; allowPreDeath = false; } else { freezeDamage = false; } if ( damageDef && damageDef->GetBool( "no_special_death" ) ) { specialDamage = true; allowPreDeath = false; } } hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); } #endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build