//---------------------------------------------------------------- // rvMonsterConvoyHover.h // // Copyright 2002-2004 Raven Software //---------------------------------------------------------------- #include "../../idlib/precompiled.h" #pragma hdrstop #include "../Game_local.h" #ifndef __GAME_VEHICLEAI_H__ #include "VehicleAI.h" #endif #ifndef __GAME_PROJECTILE_H__ #include "../Projectile.h" #endif class rvMonsterConvoyHover : public rvVehicleMonster { public: CLASS_PROTOTYPE ( rvMonsterConvoyHover ); rvMonsterConvoyHover ( void ); ~rvMonsterConvoyHover ( void ); void Save ( idSaveGame *savefile ) const; void Restore ( idRestoreGame *savefile ); void InitSpawnArgsVariables ( void ); void Spawn ( void ); void Think ( void ); private: void Think_Random ( void ); void Think_Pathing ( void ); void AttackBlaster ( void ); void AttackBeam ( void ); //void AttackBomb ( void ); float angleYaw; // actually acos( angleYaw ) from player float minYaw; float maxYaw; float desiredHeight; // actually acos( desiredHeight ) from player float minHeight; float maxHeight; float distance; // distance to enemy float minDistance; float maxDistance; jointHandle_t jointGunRight; jointHandle_t jointGunLeft; int lastAttackTime; int attackStartTime; int blasterAttackDuration; int blasterAttackRate; int bombAttackDuration; int bombAttackRate; int shotCount; idPhysics_RigidBody physicsObj; static const int DAMPEN_ANGLE_SAMPLES = 8; idAngles hoverDampening[ DAMPEN_ANGLE_SAMPLES ]; void CalcDampening ( const idAngles & cur, idAngles & out ); stateResult_t State_Idle ( const stateParms_t& parms ); stateResult_t State_BlasterAttack ( const stateParms_t& parms ); stateResult_t State_BeamAttack ( const stateParms_t& parms ); //stateResult_t State_BombAttack ( const stateParms_t& parms ); struct { bool pathing:1; bool faceEnemy:1; bool dead:1; } myfl; virtual void OnDeath ( void ); CLASS_STATES_PROTOTYPE ( rvMonsterConvoyHover ); }; CLASS_DECLARATION ( rvVehicleMonster, rvMonsterConvoyHover ) END_CLASS /* ================ rvMonsterConvoyHover::rvMonsterConvoyHover ================ */ rvMonsterConvoyHover::rvMonsterConvoyHover ( void ) { } /* ================ rvMonsterConvoyHover::~rvMonsterConvoyHover ================ */ rvMonsterConvoyHover::~rvMonsterConvoyHover ( void ) { SetPhysics( NULL ); } /* ================ rvMonsterConvoyHover::Save ================ */ void rvMonsterConvoyHover::Save ( idSaveGame *savefile ) const { savefile->WriteFloat ( angleYaw ); savefile->WriteFloat ( desiredHeight ); savefile->WriteFloat ( distance ); savefile->WriteInt ( lastAttackTime ); savefile->WriteInt ( attackStartTime ); savefile->WriteInt ( shotCount ); savefile->WriteStaticObject ( physicsObj ); savefile->Write ( hoverDampening, sizeof ( idAngles ) * DAMPEN_ANGLE_SAMPLES ); savefile->Write( &myfl, sizeof(myfl) ); // cnicholson: Added unsaved var } /* ================ rvMonsterConvoyHover::Restore ================ */ void rvMonsterConvoyHover::Restore ( idRestoreGame *savefile ) { savefile->ReadFloat ( angleYaw ); savefile->ReadFloat ( desiredHeight ); savefile->ReadFloat ( distance ); savefile->ReadInt ( lastAttackTime ); savefile->ReadInt ( attackStartTime ); savefile->ReadInt ( shotCount ); physicsObj.SetSelf( this ); physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); savefile->ReadStaticObject ( physicsObj ); RestorePhysics ( &physicsObj ); physicsObj.EnableClip(); savefile->Read ( hoverDampening, sizeof ( idAngles ) * DAMPEN_ANGLE_SAMPLES ); savefile->Read( &myfl, sizeof(myfl) ); // cnicholson: Added unsaved var InitSpawnArgsVariables(); } void rvMonsterConvoyHover::InitSpawnArgsVariables ( void ) { jointGunRight = animator.GetJointHandle ( spawnArgs.GetString ( "joint_gun_right" ) ); jointGunLeft = animator.GetJointHandle ( spawnArgs.GetString ( "joint_gun_left" ) ); minYaw = spawnArgs.GetFloat( "minYaw", "0" ); maxYaw = spawnArgs.GetFloat( "maxYaw", "360" ); minHeight = spawnArgs.GetFloat( "minHeight", "10" ); maxHeight = spawnArgs.GetFloat( "maxHeight", "70" ); minDistance = spawnArgs.GetFloat( "minDistance", "100" ); maxDistance = spawnArgs.GetFloat( "maxDistance", "500" ); minDistance *= minDistance; maxDistance *= maxDistance; if ( minYaw == 0.0f && maxYaw == 0.0f ) { maxYaw = 360.0f; } blasterAttackDuration = SEC2MS ( spawnArgs.GetFloat ( "blasterAttackDuration", "1" ) ); blasterAttackRate = SEC2MS ( spawnArgs.GetFloat ( "blasterAttackRate", ".25" ) ); bombAttackDuration = SEC2MS ( spawnArgs.GetFloat ( "bombAttackDuration", "1" ) ); bombAttackRate = SEC2MS ( spawnArgs.GetFloat ( "bombAttackRate", ".25" ) ); } /* ================ rvMonsterConvoyHover::Spawn ================ */ void rvMonsterConvoyHover::Spawn ( void ) { physicsObj.SetSelf( this ); SetClipModel ( physicsObj ); physicsObj.SetOrigin( GetPhysics()->GetOrigin ( ) ); physicsObj.SetAxis ( GetPhysics()->GetAxis ( ) ); physicsObj.SetContents( CONTENTS_BODY ); physicsObj.SetClipMask( MASK_PLAYERSOLID|CONTENTS_VEHICLECLIP|CONTENTS_FLYCLIP ); physicsObj.SetFriction ( spawnArgs.GetFloat ( "friction_linear", "1" ), spawnArgs.GetFloat ( "friction_angular", "1" ), spawnArgs.GetFloat ( "friction_contact", "1" ) ); physicsObj.SetBouncyness ( spawnArgs.GetFloat ( "bouncyness", "0.6" ) ); physicsObj.SetGravity( vec3_origin ); SetPhysics( &physicsObj ); animator.CycleAnim ( ANIMCHANNEL_ALL, animator.GetAnim( spawnArgs.GetString( "anim", "idle" ) ), gameLocal.time, 0 ); BecomeActive( TH_THINK ); InitSpawnArgsVariables(); shotCount = 0; angleYaw = rvRandom::flrand( minYaw, maxYaw ); desiredHeight = 0.5f * ( maxHeight - minHeight ) + minHeight; distance = 0.5f * ( maxDistance - minDistance ) + minDistance; lastAttackTime = 0; SetState( "Idle" ); myfl.pathing = false; myfl.faceEnemy = true; myfl.dead = false; for ( int i = 0; i < DAMPEN_ANGLE_SAMPLES; i++ ) { hoverDampening[ i ].Zero(); } jointHandle_t joint; joint = GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_thruster", "tail_thrusters" ) ); if ( joint != INVALID_JOINT ) { PlayEffect ( "fx_exhaust", joint, true ); } StartSound ( "snd_flyloop", SND_CHANNEL_ANY, 0, false, NULL ); } /* ================ rvMonsterConvoyHover::Think ================ */ void rvMonsterConvoyHover::Think ( void ) { if ( !driver ) { rvVehicleMonster::Think(); return; } if ( !driver->IsDriving() ) { GetPhysics()->SetGravity( gameLocal.GetGravity() ); } trace_t trace; idVec3 dir = GetPhysics()->GetLinearVelocity(); dir.Normalize(); if ( gameLocal.TracePoint( this, trace, GetOrigin(), GetPhysics()->GetOrigin() + dir * 100.0f, MASK_SOLID|CONTENTS_FLYCLIP, 0 ) ) { if ( trace.c.contents == CONTENTS_FLYCLIP ) { float maxHeight = this->maxHeight * 2.0f; GetPhysics()->ApplyImpulse( 0, GetOrigin(), GetPhysics()->GetLinearVelocity() * 2.0f ); angleYaw = rvRandom::flrand( minYaw, maxYaw ); desiredHeight = idMath::ClampFloat( minHeight, maxHeight, desiredHeight + 5.0f ); distance = idMath::ClampFloat( minDistance, maxDistance, distance + 5.0f ); } } if ( !driver->CanSee( driver->enemy.ent, false ) ) { desiredHeight = idMath::ClampFloat( minHeight, maxHeight, desiredHeight + 5.0f ); distance = idMath::ClampFloat( minDistance, maxDistance, distance - 5.0f ); } if ( myfl.pathing ) { Think_Pathing( ); } else { Think_Random( ); } rvVehicleMonster::Think(); idVec3 vel = GetPhysics()->GetLinearVelocity(); // vel += idVec3( idMath::Sin( gameLocal.time + vel.x ), // idMath::Sin( gameLocal.time + vel.y ), // idMath::Sin( gameLocal.time + vel.z ) ); idAngles hover = idAngles( idMath::ClampFloat( -45.0f, 45.0f, vel.x / 20.0f ), 0.0f, idMath::ClampFloat( -25.0f, 25.0f, vel.z / 20.0f ) ); CalcDampening( hover, hover ); if ( myfl.faceEnemy ) { LookAtEntity( gameLocal.GetLocalPlayer(), 0 ); idAngles angles = GetPhysics()->GetAxis( ).ToAngles( ); angles.pitch *= 0.35f; GetPhysics()->SetAxis( angles.ToMat3( ) * hover.ToMat3() ); } else { GetPhysics()->SetAxis( GetPhysics()->GetAxis() * hover.ToMat3() ); } } /* ================ rvMonsterConvoyHover::Think_Random ================ */ void rvMonsterConvoyHover::Think_Random ( void ) { const idVec3 xAxis( 1.0f, 0.0f, 0.0f ); const idVec3 yAxis( 0.0f, 1.0f, 0.0f ); const idVec3 zAxis( 0.0f, 0.0f, 1.0f ); const float xSpeed = 16000.0f; const float ySpeed = 9000.0f; const float zSpeed = 9000.0f; idEntity * ent = driver->enemy.ent; if ( !ent ) { ent = gameLocal.GetLocalPlayer(); } idVec3 toEnemyYaw = ent->GetPhysics()->GetOrigin() - GetOrigin(); toEnemyYaw.z = 0.0f; toEnemyYaw.Normalize(); float yaw = toEnemyYaw.ToAngles().yaw; float height = GetOrigin().z - ent->GetPhysics()->GetOrigin().z; float dist = GetOrigin().Dist2XY( ent->GetPhysics()->GetOrigin() ); // yaw if ( idMath::Fabs( yaw - angleYaw ) < 10.0f ) { angleYaw = rvRandom::flrand( minYaw, maxYaw ); } else { idVec3 impulse = yAxis * ( ( angleYaw < yaw ) ? ySpeed : -ySpeed ); impulse *= GetPhysics()->GetAxis(); GetPhysics()->ApplyImpulse( 0, GetOrigin(), impulse ); // 5% chance of choosing a new angle if ( rvRandom::flrand() < 0.05f ) { angleYaw = rvRandom::flrand( minYaw, maxYaw ); } } // pitch (changed to height for convenience) if ( idMath::Fabs( height - desiredHeight ) < 5.0f ) { desiredHeight = rvRandom::flrand( minHeight, maxHeight ); } else if ( GetOrigin().z < ent->GetPhysics()->GetOrigin().z + desiredHeight ) { GetPhysics()->ApplyImpulse( 0, GetOrigin(), zAxis * zSpeed ); } else { GetPhysics()->ApplyImpulse( 0, GetOrigin(), zAxis * -zSpeed ); } // distance if ( idMath::Fabs( dist - distance ) > 20.0f ) { idVec3 impulse = xAxis * ( ( dist < distance ) ? -xSpeed : xSpeed ); idMat3 axis = GetPhysics()->GetAxis(); axis[ 1 ] = idVec3( 0.0f, 0.0f, 1.0f ); axis[ 2 ] = axis[ 1 ].Cross( axis[ 0 ] ); impulse *= axis; GetPhysics()->ApplyImpulse( 0, GetOrigin(), impulse ); } else { // just choose a random distance for now distance = rvRandom::flrand( minDistance, maxDistance ); } } /* ================ rvMonsterConvoyHover::Think_Pathing ================ */ void rvMonsterConvoyHover::Think_Pathing ( void ) { } /* ================ rvMonsterConvoyHover::AttackBlaster ================ */ void rvMonsterConvoyHover::AttackBlaster ( void ) { idVec3 offset; idMat3 axis; jointHandle_t joint; joint = ((shotCount++)&1) ? jointGunRight : jointGunLeft; if ( joint == INVALID_JOINT ) { return; } if ( !GetJointWorldTransform( joint, gameLocal.time, offset, axis ) ) { return; } PlayEffect ( "fx_muzzleflash", joint ); idProjectile* proj = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_attack_blaster") ); if( proj ) { idVec3 dir = GetVectorToEnemy(); if ( dir.Normalize() == 0.0f ) { dir = axis[ 0 ]; } proj->Create( this, offset, dir, NULL ); proj->Launch( offset, dir, GetPhysics()->GetPushedLinearVelocity() ); } } /* ================ rvMonsterConvoyHover::AttackBeam ================ */ void rvMonsterConvoyHover::AttackBeam ( void ) { idVec3 offset; idMat3 axis; jointHandle_t joint; joint = ((shotCount++)&1) ? jointGunRight : jointGunLeft; if ( joint == INVALID_JOINT ) { return; } if ( !GetJointWorldTransform( joint, gameLocal.time, offset, axis ) ) { return; } PlayEffect ( "fx_muzzleflash", joint ); idProjectile* proj = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_attack_blaster") ); if( proj ) { idVec3 dir = GetVectorToEnemy(); if ( dir.Normalize() == 0.0f ) { dir = axis[ 0 ]; } proj->Create( this, offset, dir, NULL ); proj->Launch( offset, dir, GetPhysics()->GetPushedLinearVelocity() ); } } /* ================ rvMonsterConvoyHover::AttackBomb ================ * void rvMonsterConvoyHover::AttackBomb ( void ) { jointHandle_t joint; joint = ((shotCount++)&1) ? jointGunRight : jointGunLeft; if ( joint == INVALID_JOINT ) { return; } if ( !GetJointWorldTransform( joint, gameLocal.time, offset, axis ) ) { return; } StartSound ( "snd_bombrun", SND_CHANNEL_ANY, 0, false, NULL ); PlayEffect ( "fx_bombflash", joint ); idProjectile* proj = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_attack_bomb") ); if( proj ) { proj->Create( this, offset, axis[0], NULL ); proj->Launch( offset, axis[0], GetPhysics()->GetPushedLinearVelocity() ); } } */ /* ================ rvMonsterConvoyHover::OnDeath ================ */ void rvMonsterConvoyHover::OnDeath ( void ) { myfl.dead = true; idVec3 angular = idVec3( rvRandom::flrand( 180.0f, 250.0f ), rvRandom::flrand( 180.0f, 250.0f ), rvRandom::flrand( 180.0f, 250.0f ) ); physicsObj.SetGravity( gameLocal.GetGravity() ); physicsObj.SetFriction( 0.0f, 0.0f, 0.0f ); GetPhysics()->SetAngularVelocity ( angular ); gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); } /* ================ rvMonsterConvoyHover::CalcDampening ================ */ void rvMonsterConvoyHover::CalcDampening ( const idAngles & cur, idAngles & out ) { idAngles current = cur; // just incase cur == out out = cur; for ( int i = 1; i < DAMPEN_ANGLE_SAMPLES; i ++ ) { hoverDampening[ i - 1 ] = hoverDampening[ i ]; out += hoverDampening[ i ]; } hoverDampening[ DAMPEN_ANGLE_SAMPLES - 1 ] = current; out *= ( 1.0f / DAMPEN_ANGLE_SAMPLES ); } /* =============================================================================== States =============================================================================== */ CLASS_STATES_DECLARATION ( rvMonsterConvoyHover ) STATE( "Idle", rvMonsterConvoyHover::State_Idle ) STATE( "BlasterAttack", rvMonsterConvoyHover::State_BlasterAttack ) STATE( "BeamAttack", rvMonsterConvoyHover::State_BeamAttack ) // STATE( "BombAttack", rvMonsterConvoyHover::State_BombAttack ) END_CLASS_STATES /* ================ rvMonsterConvoyHover::State_Idle ================ */ stateResult_t rvMonsterConvoyHover::State_Idle ( const stateParms_t& parms ) { if ( driver ) { if ( gameLocal.time - lastAttackTime > rvRandom::irand( 1000, 1200 ) && CanSee( driver->enemy.ent, false ) ) { // if ( rvRandom::irand( 0, 100 ) < 10 ) { // distance = 0; // PostState( "BeamAttack" ); // } else { PostState( "BlasterAttack" ); // } PostState( "Idle" ); return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_DONE; } /* ================ rvMonsterConvoyHover::State_BlasterAttack ================ */ stateResult_t rvMonsterConvoyHover::State_BlasterAttack ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_BLASTER, STAGE_BLASTERWAIT }; switch ( parms.stage ) { case STAGE_INIT: attackStartTime = gameLocal.time; return SRESULT_STAGE ( STAGE_BLASTER ); case STAGE_BLASTER: lastAttackTime = gameLocal.time; AttackBlaster ( ); return SRESULT_STAGE ( STAGE_BLASTERWAIT ); case STAGE_BLASTERWAIT: if ( gameLocal.time - attackStartTime > blasterAttackDuration ) { return SRESULT_DONE; } if ( gameLocal.time - lastAttackTime > blasterAttackRate ) { return SRESULT_STAGE ( STAGE_BLASTER ); } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvMonsterConvoyHover::State_BeamAttack ================ */ stateResult_t rvMonsterConvoyHover::State_BeamAttack ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_BEAM, STAGE_BEAMWAIT }; switch ( parms.stage ) { case STAGE_INIT: attackStartTime = gameLocal.time; return SRESULT_STAGE ( STAGE_BEAM ); case STAGE_BEAM: lastAttackTime = gameLocal.time; AttackBeam( ); return SRESULT_STAGE ( STAGE_BEAMWAIT ); case STAGE_BEAMWAIT: if ( gameLocal.time - attackStartTime > blasterAttackDuration ) { return SRESULT_DONE; } if ( gameLocal.time - lastAttackTime > blasterAttackRate ) { return SRESULT_STAGE ( STAGE_BEAM ); } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvMonsterConvoyHover::State_BombAttack ================ * stateResult_t rvMonsterConvoyHover::State_BombAttack ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_BOMB, STAGE_BOMBWAIT }; switch ( parms.stage ) { case STAGE_INIT: attackStartTime = gameLocal.time; return SRESULT_STAGE ( STAGE_BOMB ); case STAGE_BOMB: lastAttackTime = gameLocal.time; AttackBomb ( ); return SRESULT_STAGE ( STAGE_BOMBWAIT ); case STAGE_BOMBWAIT: if ( !enemy.fl.inFov || gameLocal.time - attackStartTime > bombAttackDuration ) { return SRESULT_DONE; } if ( gameLocal.time - lastAttackTime > bombAttackRate ) { return SRESULT_STAGE ( STAGE_BOMB ); } return SRESULT_WAIT; } return SRESULT_ERROR; } */