//***************************************************************************** //** //** GAME_TALON.CPP //** //** Game code for Tommy's sidekick, Talon the Hawk //** //***************************************************************************** // HEADER FILES --------------------------------------------------------------- #include "precompiled.h" #pragma hdrstop #include "prey_local.h" // MACROS --------------------------------------------------------------------- #define PERCH_ROTATION 3.5 #define ROTATION_SPEED 3.5 #define ROTATION_SPEED_FAST 5 #define TALON_BLEND 100 // TYPES ---------------------------------------------------------------------- // CLASS DECLARATIONS --------------------------------------------------------- const idEventDef EV_PerchTicker("", NULL); const idEventDef EV_PerchChatter("perchChatter", NULL); const idEventDef EV_PerchSquawk("", NULL); const idEventDef EV_CheckForTarget( "", NULL ); const idEventDef EV_CheckForEnemy( "", NULL ); const idEventDef EV_TalonAction("talonaction", "ed"); // Anim Events const idEventDef EV_LandAnim("", NULL); const idEventDef EV_PreLandAnim("", NULL); const idEventDef EV_IdleAnim("", NULL); const idEventDef EV_TommyIdleAnim("", NULL); const idEventDef EV_FlyAnim("", NULL); const idEventDef EV_GlideAnim("", NULL); const idEventDef EV_TakeOffAnim("", NULL); const idEventDef EV_TakeOffAnimB("", NULL); CLASS_DECLARATION( hhMonsterAI, hhTalon ) EVENT(EV_PerchChatter, hhTalon::Event_PerchChatter) EVENT(EV_PerchSquawk, hhTalon::Event_PerchSquawk) EVENT(EV_CheckForTarget, hhTalon::Event_CheckForTarget) EVENT(EV_CheckForEnemy, hhTalon::Event_CheckForEnemy) // Anims EVENT(EV_LandAnim, hhTalon::Event_LandAnim) EVENT(EV_PreLandAnim, hhTalon::Event_PreLandAnim) EVENT(EV_IdleAnim, hhTalon::Event_IdleAnim) EVENT(EV_TommyIdleAnim, hhTalon::Event_TommyIdleAnim) EVENT(EV_FlyAnim, hhTalon::Event_FlyAnim) EVENT(EV_GlideAnim, hhTalon::Event_GlideAnim) EVENT(EV_TakeOffAnim, hhTalon::Event_TakeOffAnim) EVENT(EV_TakeOffAnimB, hhTalon::Event_TakeOffAnimB) END_CLASS const idEventDef EV_CallTalon("callTalon", "fff" ); const idEventDef EV_ReleaseTalon("releaseTalon", NULL ); const idEventDef EV_SetPerchState("setPerchState", "f" ); CLASS_DECLARATION( idEntity, hhTalonTarget ) EVENT( EV_CallTalon, hhTalonTarget::Event_CallTalon ) EVENT( EV_ReleaseTalon, hhTalonTarget::Event_ReleaseTalon ) EVENT( EV_SetPerchState, hhTalonTarget::Event_SetPerchState ) END_CLASS // STATE DECLARATIONS --------------------------------------------------------- // EXTERNAL FUNCTION PROTOTYPES ----------------------------------------------- // PRIVATE FUNCTION PROTOTYPES ------------------------------------------------ // EXTERNAL DATA DECLARATIONS ------------------------------------------------- // PUBLIC DATA DEFINITIONS ---------------------------------------------------- // PRIVATE DATA DEFINITIONS --------------------------------------------------- // CODE ----------------------------------------------------------------------- //============================================================================= // // hhTalon::Spawn // //============================================================================= void hhTalon::Spawn(void) { flyAnim = GetAnimator()->GetAnim("fly"); glideAnim = GetAnimator()->GetAnim("glide"); prelandAnim = GetAnimator()->GetAnim("preland"); landAnim = GetAnimator()->GetAnim("land"); idleAnim = GetAnimator()->GetAnim("idle"); tommyIdleAnim = GetAnimator()->GetAnim("tommy_idle"); squawkAnim = GetAnimator()->GetAnim("squawk"); stepAnim = GetAnimator()->GetAnim("step"); takeOffAnim = GetAnimator()->GetAnim("takeoffA"); takeOffAnimB = GetAnimator()->GetAnim("takeoffB"); attackAnim = GetAnimator()->GetAnim("attack1"); preAttackAnim = GetAnimator()->GetAnim("preattack"); fl.neverDormant = true; fl.takedamage = false; fl.notarget = true; allowHiddenMovement = true; // Allow Talon to move while hidden health = 1; // Talon need some health for enemies to consider it alive //AOB GetAnimator()->RemoveOriginOffset( true ); // Register the wings skins openWingsSkin = declManager->FindSkin( spawnArgs.GetString("skin_openWings") ); closedWingsSkin = declManager->FindSkin( spawnArgs.GetString("skin_closeWings") ); Event_SetMoveType(MOVETYPE_FLY); GetPhysics()->SetContents( 0 ); // No collisions GetPhysics()->SetClipMask( 0 ); // No collisions Hide(); BecomeInactive(TH_THINK); } void hhTalon::Save(idSaveGame *savefile) const { owner.Save(savefile); savefile->WriteVec3(velocity); savefile->WriteVec3(acceleration); savefile->WriteObject(talonTarget); savefile->WriteVec3(talonTargetLoc); savefile->WriteMat3(talonTargetAxis); savefile->WriteVec3(lastCheckOrigin); savefile->WriteFloat(checkTraceTime); savefile->WriteFloat(checkFlyTime); savefile->WriteFloat(flyStraightTime); savefile->WriteBool(bLanding); savefile->WriteBool(bReturnToTommy); savefile->WriteBool( bForcedTarget ); savefile->WriteBool( bClawingAtEnemy ); savefile->WriteFloat( velocityFactor ); savefile->WriteFloat( rotationFactor ); savefile->WriteFloat( perchRotationFactor ); savefile->WriteInt(flyAnim); savefile->WriteInt(glideAnim); savefile->WriteInt(prelandAnim); savefile->WriteInt(landAnim); savefile->WriteInt(idleAnim); savefile->WriteInt(tommyIdleAnim); savefile->WriteInt(squawkAnim); savefile->WriteInt(stepAnim); savefile->WriteInt(takeOffAnim); savefile->WriteInt(takeOffAnimB); savefile->WriteInt(attackAnim); savefile->WriteInt(preAttackAnim); savefile->WriteSkin(openWingsSkin); savefile->WriteSkin(closedWingsSkin); enemy.Save( savefile ); trailFx.Save( savefile ); savefile->WriteInt( state ); } void hhTalon::Restore( idRestoreGame *savefile ) { owner.Restore(savefile); savefile->ReadVec3(velocity); savefile->ReadVec3(acceleration); savefile->ReadObject( reinterpret_cast(talonTarget) ); savefile->ReadVec3(talonTargetLoc); savefile->ReadMat3(talonTargetAxis); savefile->ReadVec3(lastCheckOrigin); savefile->ReadFloat(checkTraceTime); savefile->ReadFloat(checkFlyTime); savefile->ReadFloat(flyStraightTime); savefile->ReadBool(bLanding); savefile->ReadBool(bReturnToTommy); savefile->ReadBool( bForcedTarget ); savefile->ReadBool( bClawingAtEnemy ); savefile->ReadFloat( velocityFactor ); savefile->ReadFloat( rotationFactor ); savefile->ReadFloat( perchRotationFactor ); savefile->ReadInt(flyAnim); savefile->ReadInt(glideAnim); savefile->ReadInt(prelandAnim); savefile->ReadInt(landAnim); savefile->ReadInt(idleAnim); savefile->ReadInt(tommyIdleAnim); savefile->ReadInt(squawkAnim); savefile->ReadInt(stepAnim); savefile->ReadInt(takeOffAnim); savefile->ReadInt(takeOffAnimB); savefile->ReadInt(attackAnim); savefile->ReadInt(preAttackAnim); savefile->ReadSkin(openWingsSkin); savefile->ReadSkin(closedWingsSkin); enemy.Restore( savefile ); trailFx.Restore( savefile ); savefile->ReadInt( reinterpret_cast (state) ); } //============================================================================= // // hhTalon::hhTalon // //============================================================================= hhTalon::hhTalon() { owner = NULL; talonTarget = NULL; enemy.Clear(); trailFx.Clear(); } //============================================================================= // // hhTalon::~hhTalon // //============================================================================= hhTalon::~hhTalon() { if( owner.IsValid() ) { owner->talon = NULL; } if ( trailFx.IsValid() ) { trailFx->Nozzle( false ); SAFE_REMOVE( trailFx ); } } //============================================================================= // // hhTalon::SummonTalon // //============================================================================= void hhTalon::SummonTalon(void) { hhFxInfo fxInfo; state = StateNone; BecomeActive(TH_THINK); Show(); checkTraceTime = spawnArgs.GetFloat( "traceCheckPulse", "0.1" ); lastCheckOrigin = GetOrigin(); FindTalonTarget(NULL, NULL, true); CalculateTalonTargetLocation(); // Must recalculate the target location, as the target may have moved SetOrigin(talonTargetLoc); viewAxis = talonTargetAxis; bLanding = false; SetSkin( closedWingsSkin ); // The only time this is set in code, otherwise it's set by the land anim fl.neverDormant = true; SetForcedTarget( false ); velocityFactor = 1.0f; rotationFactor = 1.0f; perchRotationFactor = 1.0f; SetShaderParm( SHADERPARM_DIVERSITY, 0 ); EnterTommyState(); TommyTicker(); // Force Talon to tick once to adjust his orientation } //============================================================================= // // hhTalon::SetOwner // //============================================================================= void hhTalon::SetOwner( hhPlayer *newOwner ) { owner = newOwner; Event_SetOwner( newOwner ); } void hhTalon::OwnerEnteredVehicle( void ) { if ( talonTarget ) { talonTarget->Left( this ); } SummonTalon(); EnterVehicleState(); } void hhTalon::OwnerExitedVehicle( void ) { ExitVehicleState(); SummonTalon(); } //============================================================================= // // hhTalon::FindTalonTarget // //============================================================================= void hhTalon::FindTalonTarget( idEntity *skipEntity, hhTalonTarget *forceTarget, bool bForcePlayer ) { int i; int count; float dist; float bestDist; hhTalonTarget *ent; idVec3 dir; talonTarget = NULL; bestDist = spawnArgs.GetFloat( "perchDistance", "250" ); if ( owner->InVehicle() ) { bestDist *= 10; } if( bReturnToTommy) { bForcePlayer = true; } if ( forceTarget ) { talonTarget = forceTarget; } // Iterate through valid perch spots and find the best choice if( !bForcePlayer && !forceTarget ) { count = gameLocal.talonTargets.Num(); for(i = 0; i < count; i++) { ent = (hhTalonTarget *)gameLocal.talonTargets[i]; if ( !ent->bValidForTalon ) { // Not a valid target continue; } // ignore if there is no way we can see this perch spot if( ent == skipEntity || !gameLocal.InPlayerPVS( ent ) || ent->priority == -1) { continue; } if ( owner.IsValid() && owner->IsSpiritOrDeathwalking() && ent->bNotInSpiritWalk ) { // If the owner is spiritwalking, then don't try to fly to this target (useful for lifeforce pickups) continue; } // Calculate the distance from the player to the perch spot dist = ( owner->GetEyePosition() - ent->GetPhysics()->GetOrigin() ).Length(); // Adjust the distance based upon the priority (ranges from 0 - 2, -1 to completely skip). dist *= ent->priority; if(dist > bestDist) { // Farther than the nearest perch spot continue; } talonTarget = ent; bestDist = dist; } } if( !talonTarget && owner.IsValid() ) { // No perch spot, so use a spot close to the player owner->GetJointWorldTransform( "FX_bird", talonTargetLoc, talonTargetAxis ); talonTargetAxis = owner->viewAxis; } else { CalculateTalonTargetLocation(); } } //============================================================================= // // hhTalon::CalculateTalonTargetLocation // //============================================================================= void hhTalon::CalculateTalonTargetLocation() { trace_t tr; if ( !talonTarget ) { return; } // Trace down a bit to get the exact perch location if( gameLocal.clip.TracePoint( tr, talonTarget->GetPhysics()->GetOrigin() + idVec3(0, 0, 4), talonTarget->GetPhysics()->GetOrigin() - idVec3(0, 0, 16), MASK_SOLID, this )) { talonTargetLoc = tr.endpos - idVec3( 0, 0, 1 ); // End position, minus a tiny bit so Talon perches on railings } else { // No collision, just use the current floating spot talonTargetLoc = talonTarget->GetPhysics()->GetOrigin(); } talonTargetAxis = talonTarget->GetPhysics()->GetAxis(); } //============================================================================= // // CheckReachedTarget // //============================================================================= bool hhTalon::CheckReachedTarget( float distance ) { idVec3 vec; if(distance < spawnArgs.GetFloat( "distanceSlow", "80" ) ) { if(talonTarget) { velocity = (talonTargetLoc - GetOrigin()) * 0.05f; // Move more slowly when about to perch if( !GetAnimator()->IsAnimPlaying( GetAnimator()->GetAnim( prelandAnim ) ) ) { Event_PreLandAnim(); StartSound( "snd_preland", SND_CHANNEL_BODY ); } bLanding = true; } if(talonTarget) { if(distance < spawnArgs.GetFloat( "distancePerchEpsilon", "3" ) ) { // Perch on this spot Event_LandAnim(); StartSound( "snd_land", SND_CHANNEL_BODY ); talonTarget->Reached(this); EnterPerchState(); return true; // No need to continue the fly ticker } } else if(distance < spawnArgs.GetFloat( "distanceTommyEpsilon", "15.0" ) ) { // No perch spot, so perch on Tommy around this location Event_LandAnim(); EnterTommyState(); return true; // No need to continue the fly ticker } } else if(distance >= spawnArgs.GetFloat( "distanceTurbo", "2500" ) ) { // Distance is so far, we should kick Talon into turbo mode velocity *= 10; } return false; } //============================================================================= // // hhTalon::CheckCollisions // //============================================================================= void hhTalon::CheckCollisions(float deltaTime) { idVec3 oldOrigin; trace_t tr; checkTraceTime -= deltaTime; if(checkTraceTime > 0) { return; } checkTraceTime = spawnArgs.GetFloat( "traceCheckPulse", "0.1" ); idVec3 nextLocation = GetOrigin() + this->velocity * 0.5f; // Trace to determine if the hawk is just about to fly into a wall if ( gameLocal.clip.TracePoint( tr, GetOrigin(), nextLocation, MASK_SOLID, owner.GetEntity() ) ) { // Struck something solid // Glow SetShaderParm( SHADERPARM_TIMEOFFSET, MS2SEC( gameLocal.time ) ); } else if ( gameLocal.clip.TracePoint( tr, GetOrigin(), lastCheckOrigin, MASK_SOLID, owner.GetEntity() ) ) { // Exited something solid // Glow SetShaderParm( SHADERPARM_TIMEOFFSET, MS2SEC( gameLocal.time ) ); } lastCheckOrigin = GetOrigin(); } //============================================================================= // // hhTalon::Think // //============================================================================= void hhTalon::Think(void) { if ( owner->InGravityZone() ) { // don't perch in gravity zones, unless gravity is "normal" idVec3 gravity = owner->GetGravity(); gravity.Normalize(); float dot = gravity * idVec3( 0.0f, 0.0f, -1.0f ); if ( dot < 1.0f ) { if ( talonTarget ) { talonTarget->Left( this ); } ReturnToTommy(); } } // Shadow suppression based upon the player if ( g_showPlayerShadow.GetBool() ) { renderEntity.suppressShadowInViewID = 0; } else { renderEntity.suppressShadowInViewID = owner->entityNumber + 1; } hhMonsterAI::Think(); } //============================================================================= // // hhTalon::FlyMove // //============================================================================= void hhTalon::FlyMove( void ) { // Run the specific task Ticker switch( state ) { case StateTommy: TommyTicker(); break; case StateFly: FlyTicker(); break; case StateVehicle: VehicleTicker(); break; case StatePerch: PerchTicker(); break; case StateAttack: AttackTicker(); break; } // run the physics for this frame physicsObj.UseFlyMove( true ); physicsObj.UseVelocityMove( false ); physicsObj.SetDelta( vec3_zero ); physicsObj.ForceDeltaMove( disableGravity ); RunPhysics(); } //============================================================================= // // hhTalon::AdjustFlyingAngles // // No need to adjust flying angles on Talon //============================================================================= void hhTalon::AdjustFlyingAngles() { return; } //============================================================================= // // hhTalon::GetPhysicsToVisualTransform // //============================================================================= bool hhTalon::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { origin = modelOffset; if ( GetBindMaster() && bindJoint != INVALID_JOINT ) { idMat3 masterAxis; idVec3 masterOrigin; GetMasterPosition( masterOrigin, masterAxis ); origin = masterOrigin + physicsObj.GetLocalOrigin() * masterAxis; axis = GetBindMaster()->GetAxis(); } else { axis = viewAxis; } return true; } //============================================================================= // // hhTalon::CrashLand // // No need to check crash landing on Talon //============================================================================= void hhTalon::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) { return; } //============================================================================= // // hhTalon::Damage // // Talon cannot be damaged //============================================================================= void hhTalon::Damage( idEntity *inflictor, idEntity *attack, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { return; } //============================================================================= // // hhTalon::Killed // // Talon cannot be killed //============================================================================= void hhTalon::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { return; } //============================================================================= // // hhTalon::Portalled // //============================================================================= void hhTalon::Portalled( idEntity *portal ) { if ( state == StateTommy ) { // No need to portal if Talon is already on Tommy's shoulder return; } CancelEvents( &EV_GlideAnim ); // Could possibly have a glide anim queue CancelEvents( &EV_TakeOffAnimB ); // If Talon is currently on a gui, inform the gui he is leaving if ( talonTarget && ( state == StatePerch || state == StateVehicle ) ) { // CJR PCF 050306: Only call Left() if Talon is perching on a console talonTarget->Left( this ); } StopAttackFX(); // Force Talon to Tommy's shoulder after he portals talonTarget = NULL; bLanding = false; SetSkin( openWingsSkin ); // The only time this is set in code, otherwise it's set by the land anim Event_LandAnim(); SetForcedTarget( false ); EnterTommyState(); } //============================================================================= // // hhTalon::EnterFlyState // //============================================================================= void hhTalon::EnterFlyState(void) { EndState(); state = StateFly; checkFlyTime = spawnArgs.GetFloat( "checkFlyTime", "4.0" ); flyStraightTime = spawnArgs.GetFloat( "flyStraightTime", "0.5" ); bLanding = false; SetShaderParm( SHADERPARM_DIVERSITY, 0 ); // Talon's model should phase out if the player is too close } //============================================================================= // // hhTalon::FlyTicker // //============================================================================= void hhTalon::FlyTicker(void) { idAngles ang; float distance; float deltaTime = MS2SEC(gameLocal.msec); idVec3 oldVel; float distanceXY; float deltaZ; float rotSpeed; // If the bird is currently in fly mode, but hasn't finished the takeoff anim, then don't let the bird fly around if( GetAnimator()->IsAnimPlaying( GetAnimator()->GetAnim( takeOffAnim ) ) ) { return; } acceleration = talonTargetLoc - GetOrigin(); idVec2 vecXY = acceleration.ToVec2(); distance = acceleration.Normalize(); distanceXY = vecXY.Length(); deltaZ = talonTargetLoc.z - GetOrigin().z; float dp = acceleration * GetAxis()[0]; float side = acceleration * GetAxis()[1]; ang = GetAxis().ToAngles(); // Talon can either rotate towards the target, or fly straight. if( flyStraightTime > 0 ) { // Continue to fly straight flyStraightTime -= deltaTime; if ( flyStraightTime <= 0 && !bLanding ) { // No longer flying straight Event_FlyAnim(); } rotSpeed = 0; } else if( bLanding ) { // About to land, so turn slightly faster than normal rotSpeed = ROTATION_SPEED_FAST * rotationFactor; } else { rotSpeed = ROTATION_SPEED * rotationFactor; } rotSpeed *= (60.0f * USERCMD_ONE_OVER_HZ); // Determine whether Talon should rotate left or right to reach the target if(dp > 0) { if(dp >= 0.98f || ( side <= 0.1f && side >= 0.1f )) { // CJR PCF 04/28/06: don't turn if directly facing the target rotSpeed = 0; } else if (side < -0.1f) { rotSpeed *= -1.0f; } } else { if(side < 0) { rotSpeed *= -1.0f; } } // Apply rotation deltaViewAngles.yaw = rotSpeed; oldVel = velocity; float defaultVelocityXY = spawnArgs.GetFloat( "velocityXY", "7.0" ); float defaultVelocityZ = spawnArgs.GetFloat( "velocityZ", "4" ); velocity = viewAxis[0] * defaultVelocityXY * velocityFactor; // xy-velocity // Z-change based upon distance if(abs(deltaZ) > 0 && distanceXY > 0) { velocity.z = (deltaZ * defaultVelocityXY) / distanceXY; // Clamp z velocity if(velocity.z > defaultVelocityZ) { velocity.z = defaultVelocityZ; } } if ( CheckReachedTarget( distance ) ) { return; } // Apply velocity SetOrigin( GetOrigin() + velocity * (60.0f * USERCMD_ONE_OVER_HZ) ); // Check for colliding with world surfaces (only if not currently trying to land) if( !bLanding ) { CheckCollisions(deltaTime); } // Timeout for flying to perch spot... if too much time has passed, then fly straight for a bit checkFlyTime -= deltaTime; if( checkFlyTime <= 0 && !bLanding ) { flyStraightTime = spawnArgs.GetFloat( "flyStraightTime", "0.5" ); checkFlyTime = spawnArgs.GetFloat( "checkFlyTime", "4.0" ); PostEventMS( &EV_GlideAnim, 0 ); } // If no talonTarget (probably flying towards Tommy), then constantly look for a new target if( !bForcedTarget && !GetAnimator()->IsAnimPlaying( GetAnimator()->GetAnim( prelandAnim ) ) ) { FindTalonTarget( NULL, NULL, false ); } } //============================================================================= // // hhTalon::EnterTommyState // //============================================================================= void hhTalon::EnterTommyState(void) { idVec3 bindOrigin; idMat3 bindAxis; bReturnToTommy = false; EndState(); state = StateTommy; Hide(); UpdateVisuals(); // Bind the bird to Tommy's shoulder owner->GetJointWorldTransform( "fx_bird", bindOrigin, bindAxis ); SetOrigin( bindOrigin ); SetAxis( owner->viewAxis ); BindToJoint( owner.GetEntity(), "fx_bird", true ); // Start checking for enemies PostEventSec( &EV_CheckForEnemy, spawnArgs.GetFloat("checkEnemyTime", "4.0" ) ); // Start checking for nearby targets PostEventSec( &EV_CheckForTarget, spawnArgs.GetFloat( "checkTargetTime", "1.0" ) ); } //============================================================================= // // hhTalon::EndState // //============================================================================= void hhTalon::EndState(void) { Unbind(); int oldState = state; state = StateNone; if ( fl.hidden ) { // If the bird is hidden, show it when ending the state Show(); } // Zero out Talon's velocity when initially taking off, so it doesn't inherit any from the bind master velocity = vec3_zero; GetPhysics()->SetLinearVelocity( vec3_zero ); // Cancel any checks for enemies CancelEvents( &EV_CheckForEnemy ); CancelEvents( &EV_CheckForTarget ); StopAttackFX(); if( oldState == StateTommy ) { GetPhysics()->SetAxis( mat3_identity ); // Show the hawk in the player's view after it takes off Show(); } else if( oldState == StatePerch ) { CancelEvents( &EV_PerchSquawk ); } else if ( oldState == StateAttack ) { // Clear the enemy if ( enemy.IsValid() ) { (static_cast(enemy.GetEntity()))->Distracted( GetOwner() ); // Set the enemy's enemy back to Tommy enemy.Clear(); } } } //============================================================================= // // hhTalon::TommyTicker // //============================================================================= void hhTalon::TommyTicker(void) { idVec3 viewOrigin; idMat3 viewAxis; idMat3 ownerAxis; float deltaTime = MS2SEC(gameLocal.msec); bLanding = false; // Turn towards direction of Tommy idVec3 toFace = owner->GetAxis()[0]; toFace.z = 0; toFace.Normalize(); float dp = toFace * GetAxis()[0]; float side = toFace * GetAxis()[1]; if(side > 0.05 || dp < 0 ) { deltaViewAngles.yaw = PERCH_ROTATION * perchRotationFactor; UpdateVisuals(); } else if (side < -0.05) { deltaViewAngles.yaw = -PERCH_ROTATION * perchRotationFactor; UpdateVisuals(); } } //============================================================================= // // hhTalon::FindEnemy // //============================================================================= bool hhTalon::FindEnemy( void ) { idEntity *ent; hhMonsterAI *actor; hhMonsterAI *bestEnemy; float bestDist; float maxEnemyDist; float dist; idVec3 delta; int enemyCount; // Check if Talon already has a valid enemy if ( enemy.IsValid() && enemy->health > 0 ) { return true; } bestDist = 0; maxEnemyDist = 1024.0f * 1024.0f; // sqr bestEnemy = NULL; enemyCount = 0; for ( ent = gameLocal.activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { if ( ent->fl.hidden || ent->fl.isDormant || !ent->IsType( hhMonsterAI::Type ) ) { continue; } actor = static_cast( ent ); // Only attack living enemies, and only attack enemies that are targetting Tommy or Talon if ( (actor->health <= 0) || ( actor->GetEnemy() != GetOwner() && actor->GetEnemy() != this ) ) { continue; } if ( !gameLocal.InPlayerPVS( actor ) ) { continue; } // Check if Talon should avoid this enemy -- these enemies are also not included in the enemy count if ( !ent->spawnArgs.GetBool( "bTalonAttack", "0" ) ) { continue; } delta = GetOwner()->GetOrigin() - actor->GetPhysics()->GetOrigin(); // Find the farthest enemy (in the PVS) to Tommy dist = delta.LengthSqr(); if ( dist > bestDist && dist < maxEnemyDist && GetOwner()->CanSee( actor, false ) ) { bestDist = dist; bestEnemy = actor; } enemyCount++; } if ( bestEnemy && enemyCount >= 2 ) { // Only attack if more than one enemy is attacking the player enemy = bestEnemy; return true; } enemy.Clear(); return false; } //============================================================================= // // hhTalon::EnterPerchState // //============================================================================= void hhTalon::EnterPerchState(void) { EndState(); state = StatePerch; velocity = vec3_zero; if ( talonTarget ) { CalculateTalonTargetLocation(); // Must recalculate the target location, as the target may have moved SetOrigin( talonTargetLoc ); if ( talonTarget->IsBound() ) { // Only bind talon to this target, if it's bound to something Bind( talonTarget, true ); } GetPhysics()->SetLinearVelocity( vec3_zero ); } if ( talonTarget && talonTarget->bShouldSquawk ) { float frequency = talonTarget->spawnArgs.GetFloat( "squawkFrequency", "5.0" ); PostEventSec( &EV_PerchSquawk, frequency ); } SetShaderParm( SHADERPARM_DIVERSITY, 1 ); // Talon's model shouldn't phase out if the player is too close // Start checking for nearby enemies PostEventSec( &EV_CheckForEnemy, spawnArgs.GetFloat("checkEnemyTime", "4.0" ) ); // Start checking for nearby targets PostEventSec( &EV_CheckForTarget, spawnArgs.GetFloat( "checkTargetTime", "1.0" ) ); } //============================================================================= // // hhTalon::PerchTicker // //============================================================================= void hhTalon::PerchTicker(void) { float deltaTime = MS2SEC(gameLocal.msec); bLanding = false; // Turn towards direction of perch spot idVec3 toFace = talonTarget->GetPhysics()->GetAxis()[0]; toFace.z = 0; toFace.Normalize(); float dp = toFace * GetAxis()[0]; float side = toFace * GetAxis()[1]; if ( dp < 0 ) { if ( side >= 0 ) { deltaViewAngles.yaw = PERCH_ROTATION * perchRotationFactor; } else { deltaViewAngles.yaw = -PERCH_ROTATION * perchRotationFactor; } } else if (side > 0.05 ) { deltaViewAngles.yaw = PERCH_ROTATION * perchRotationFactor; } else if (side < -0.05) { deltaViewAngles.yaw = -PERCH_ROTATION * perchRotationFactor; } UpdateVisuals(); } //============================================================================= // // hhTalon::Event_PerchChatter // // Small chirping noise which is part of Talon's idle animation //============================================================================= void hhTalon::Event_PerchChatter(void) { if ( state != StateTommy ) { // Don't chatter when on Tommy's shoulder StartSound( "snd_chatter", SND_CHANNEL_VOICE ); } } //============================================================================= // // hhTalon::Event_PerchSquawk // // Louder, squawking noise, alerting the player to this location //============================================================================= void hhTalon::Event_PerchSquawk(void) { if ( !talonTarget ) { return; } if ( talonTarget->bShouldSquawk ) { // Always check, because it could have been turned off after Talon perched // Check if the player is outside of the squawk distance float squawkDistSqr = talonTarget->spawnArgs.GetFloat( "squawkDistance", "0" ); squawkDistSqr *= squawkDistSqr; if ( (GetOrigin() - owner->GetOrigin() ).LengthSqr() > squawkDistSqr ) { // Far enough away, so squawk GetAnimator()->ClearAllAnims( gameLocal.time, TALON_BLEND ); GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, squawkAnim, gameLocal.time, TALON_BLEND); PostEventMS( &EV_IdleAnim, GetAnimator()->GetAnim( squawkAnim )->Length() + TALON_BLEND ); StartSound( "snd_squawk", SND_CHANNEL_VOICE ); // Glow SetShaderParm( SHADERPARM_TIMEOFFSET, MS2SEC( gameLocal.time ) ); } // Post the next squawk attempt float frequency = talonTarget->spawnArgs.GetFloat( "squawkFrequency", "5.0" ); PostEventSec( &EV_PerchSquawk, frequency + frequency * gameLocal.random.RandomFloat() ); } } //============================================================================= // // hhTalon::Event_CheckForTarget // //============================================================================= void hhTalon::Event_CheckForTarget() { hhTalonTarget *oldTarget = talonTarget; // Determine if Talon should take off from his perch if ( !bForcedTarget ) { // do not check if Talon is forced this this point FindTalonTarget(NULL, NULL, false); if( oldTarget != talonTarget ) { Event_TakeOffAnim(); if( oldTarget ) { // Inform the perch spot that talon is leaving oldTarget->Left(this); } EnterFlyState(); return; } } PostEventSec( &EV_CheckForTarget, spawnArgs.GetFloat( "checkTargetTime", "1.0" ) ); } //============================================================================= // // hhTalon::Event_CheckForEnemy // //============================================================================= void hhTalon::Event_CheckForEnemy() { if ( !ai_talonAttack.GetBool() ) { return; } if ( !bForcedTarget ) { if ( FindEnemy() ) { Event_TakeOffAnim(); if( talonTarget ) { // Inform the perch spot that talon is leaving talonTarget->Left( this ); } EnterAttackState(); return; } } PostEventSec( &EV_CheckForEnemy, spawnArgs.GetFloat("checkEnemyTime", "4.0" ) ); } //============================================================================= // // hhTalon::EnterAttackState // //============================================================================= void hhTalon::EnterAttackState(void) { EndState(); state = StateAttack; checkFlyTime = spawnArgs.GetFloat( "attackTime", "8.0f" ); StartSound( "snd_attack", SND_CHANNEL_BODY ); bClawingAtEnemy = false; StartAttackFX(); } //============================================================================= // // hhTalon::AttackTicker // //============================================================================= void hhTalon::AttackTicker(void) { float deltaTime = MS2SEC(gameLocal.msec); if ( !FindEnemy() ) { ReturnToTommy(); StopAttackFX(); return; } idAngles ang; float distance; idVec3 oldVel; bool landing = false; float distanceXY; float deltaZ; float rotSpeed; float distToEnemy; idMat3 junkAxis; enemy->GetViewPos(talonTargetLoc, junkAxis); talonTargetLoc -= junkAxis[2] * 10.0f; distToEnemy = (talonTargetLoc - GetOrigin()).Length(); acceleration = talonTargetLoc - GetOrigin(); idVec2 vecXY = acceleration.ToVec2(); distance = acceleration.Normalize(); distanceXY = vecXY.Length(); deltaZ = talonTargetLoc.z - GetOrigin().z; float dp = acceleration * GetAxis()[0]; float side = acceleration * GetAxis()[1]; ang = GetAxis().ToAngles(); // Talon can either rotate towards the target, or fly straight. if( flyStraightTime > 0 ) { // Continue to fly straight flyStraightTime -= deltaTime; if ( flyStraightTime <= 0 ) { // Done gliding, play a flap animation Event_FlyAnim(); if ( bClawingAtEnemy ) { // Done attacking bClawingAtEnemy = false; flyStraightTime = 1.0f; } } rotSpeed = 0; } else { rotSpeed = ROTATION_SPEED * rotationFactor; } rotSpeed *= (60.0f * USERCMD_ONE_OVER_HZ); // CJR PCF 04/28/06: Adjust for 30Hz // Determine whether Talon should rotate left or right to reach the target if(dp > 0) { if(dp >= 0.98f || ( side <= 0.1f && side >= 0.1f )) { // CJR PCF 04/28/06: don't turn if directly facing the target rotSpeed = 0; } else if (side < -0.1f) { rotSpeed *= -1; } } else { if(side < 0) { rotSpeed *= -1; } } // Apply rotation deltaViewAngles.yaw = rotSpeed; float desiredRoll = -side * 20 * rotSpeed; oldVel = velocity; float defaultVelocityXY = spawnArgs.GetFloat( "velocityXY_attack", "8.5" ); float defaultVelocityZ = spawnArgs.GetFloat( "velocityZ", "4" ); // Adjust velocity based upon the attack if ( bClawingAtEnemy ) { defaultVelocityXY = 0.0f; idVec3 tempVec( vecXY.x, vecXY.y, 0.0f ); viewAxis = tempVec.ToMat3(); } else if ( flyStraightTime > 0 ) { // Flying away from the enemy float adjust = hhMath::ClampFloat( 0.0f, 1.0f, 2.0f - flyStraightTime * 2.0f ); defaultVelocityXY *= adjust; // Move more slowly when about to attack } else { // Flying around, check if the bird should slow down when nearing the enemy if ( distToEnemy < 150.0f && dp > 0 ) { // Close to the enemy and facing it if( !GetAnimator()->IsAnimPlaying( GetAnimator()->GetAnim( preAttackAnim ) ) ) { GetAnimator()->ClearAllAnims( gameLocal.time, TALON_BLEND ); GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, preAttackAnim, gameLocal.time, TALON_BLEND); CancelEvents( &EV_GlideAnim ); // Could possibly have a glide anim queue CancelEvents( &EV_TakeOffAnimB ); } float adjust = 1.0f - hhMath::ClampFloat( 0.0f, 0.9f, (100 - (distToEnemy-50) ) / 100.0f); // Decelerate Talon defaultVelocityXY *= adjust; } } velocity = viewAxis[0] * defaultVelocityXY * velocityFactor; // xy-velocity // Z-change based upon distance if(abs(deltaZ) > 0 && distanceXY > 0) { velocity.z = (deltaZ * defaultVelocityXY) / distanceXY; // Clamp z velocity if(velocity.z > defaultVelocityZ) { velocity.z = defaultVelocityZ; } } // Apply velocity SetOrigin( GetOrigin() + velocity ); UpdateVisuals(); // Timeout for flying to perch spot... if too much time has passed, then fly straight for a bit checkFlyTime -= deltaTime; if( checkFlyTime <= 0 && !bClawingAtEnemy && flyStraightTime <= 0 ) { ReturnToTommy(); StopAttackFX(); } // Check if Talon reached the target and attack! if ( flyStraightTime <= 0 && ( distToEnemy < spawnArgs.GetFloat( "distanceAttackEpsilon", "60" ) ) ) { // Damage the enemy StartSound( "snd_attack", SND_CHANNEL_BODY ); // Distract the enemy by setting Talon as its enemy -- random chance that this will succeed if ( enemy.IsValid() && gameLocal.random.RandomFloat() < spawnArgs.GetFloat( "attackChance", "0.5" ) ) { (static_cast(enemy.GetEntity()))->Distracted( this ); owner->TalonAttackComment(); } // Play an attack anim here GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, attackAnim, gameLocal.time, 0 ); flyStraightTime = MS2SEC( GetAnimator()->GetAnim( attackAnim )->Length() ); PostEventSec( &EV_FlyAnim, flyStraightTime ); // Play a fly anim once this attack animation is done bClawingAtEnemy = true; } } //============================================================================= // // hhTalon::StartAttackFX // //============================================================================= void hhTalon::StartAttackFX() { SetShaderParm( SHADERPARM_MODE, 1 ); // Fire glow attack if ( trailFx.IsValid() ) { // An effect already exists, turn it on trailFx->Nozzle( true ); } else { // Spawn a new effect const char *defName = spawnArgs.GetString( "fx_attackTrail" ); if (defName && defName[0]) { hhFxInfo fxInfo; fxInfo.SetNormal( -GetAxis()[0] ); fxInfo.SetEntity( this ); fxInfo.RemoveWhenDone( false ); trailFx = SpawnFxLocal( defName, GetOrigin(), GetAxis(), &fxInfo, gameLocal.isClient ); if (trailFx.IsValid()) { trailFx->fl.neverDormant = true; trailFx->fl.networkSync = false; trailFx->fl.clientEvents = true; } } } } //============================================================================= // // hhTalon::StopAttackFX // //============================================================================= void hhTalon::StopAttackFX() { SetShaderParm( SHADERPARM_MODE, 0 ); // Fire glow attack done if ( trailFx.IsValid() ) { trailFx->Nozzle( false ); } } //============================================================================= // // hhTalon::EnterVehicleState // //============================================================================= void hhTalon::EnterVehicleState(void) { EndState(); state = StateVehicle; Hide(); CancelEvents( &EV_GlideAnim ); // Could possibly have a glide anim queue CancelEvents( &EV_TakeOffAnimB ); } //============================================================================= // // hhTalon::ExitVehicleState // //============================================================================= void hhTalon::ExitVehicleState(void) { if ( talonTarget ) { talonTarget->Left( this ); } } //============================================================================= // // hhTalon::VehicleTicker // // How Talon acts when the player is in a vehicle: // - In the interest of speed, Talon warps from target to Target // - This allows for the giant translation GUIs to instantly translate as the player gets near //============================================================================= void hhTalon::VehicleTicker(void) { float deltaTime = MS2SEC(gameLocal.msec); hhTalonTarget *oldEnt = talonTarget; if( GetAnimator()->IsAnimPlaying( GetAnimator()->GetAnim( prelandAnim ) ) ) { return; } FindTalonTarget( NULL, NULL, bReturnToTommy ); if ( talonTarget == oldEnt ) { // No change to the target, so do nothing return; } if ( !talonTarget ) { // No Talon Target, so hide Talon Hide(); } else { // have a target, to show the bird Show(); } // Have a target, so spawn the bird at the target, if it isn't already there CalculateTalonTargetLocation(); // Must recalculate the target location, as the target may have moved SetOrigin( talonTargetLoc ); SetAxis( talonTargetAxis ); UpdateVisuals(); SetSkin( openWingsSkin ); Event_LandAnim(); // Trigger the target if ( talonTarget ) { talonTarget->Reached( this ); // Reached the spot, but fly through it } // Inform the last talonTarget that Talon has left if ( oldEnt ) { oldEnt->Left( this ); } } //============================================================================= // // hhTalon::UpdateAnimationControllers // Talon doesn't use any anim controllers //============================================================================= bool hhTalon::UpdateAnimationControllers() { idVec3 vel; float speed; float roll; speed = velocity.Length(); if ( speed < 3.0f || flyStraightTime > 0 ) { roll = 0.0f; } else { roll = acceleration * viewAxis[1] * -fly_roll_scale / fly_speed; if ( roll > fly_roll_max ) { roll = fly_roll_max; } else if ( roll < -fly_roll_max ) { roll = -fly_roll_max; } } fly_roll = fly_roll * 0.95f + roll * 0.05f; viewAxis = idAngles( 0, current_yaw, fly_roll ).ToMat3(); return false; } //============================================================================= // // hhTalon::ReturnToTommy // //============================================================================= void hhTalon::ReturnToTommy( void ) { if ( bReturnToTommy ) { // already returning return; } bReturnToTommy = true; bLanding = false; FindTalonTarget(NULL, NULL, true); SetSkin( openWingsSkin ); GetAnimator()->ClearAllAnims( gameLocal.time, TALON_BLEND ); GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, flyAnim, gameLocal.time, TALON_BLEND); EnterFlyState(); } // ANIM EVENTS ================================================================ //============================================================================= // // hhTalon::Event_LandAnim // //============================================================================= void hhTalon::Event_LandAnim(void) { GetAnimator()->ClearAllAnims( gameLocal.time, TALON_BLEND ); GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, landAnim, gameLocal.time, TALON_BLEND); if ( talonTarget ) { // Different animation if landing on Tommy or not PostEventMS( &EV_IdleAnim, GetAnimator()->GetAnim( landAnim )->Length() + TALON_BLEND ); } else { // No target, so the bird must be landing on Tommy PostEventMS( &EV_TommyIdleAnim, GetAnimator()->GetAnim( landAnim )->Length() + TALON_BLEND ); } CancelEvents( &EV_GlideAnim ); // Could possibly have a glide anim queue CancelEvents( &EV_TakeOffAnimB ); } //============================================================================= // // hhTalon::Event_PreLandAnim // //============================================================================= void hhTalon::Event_PreLandAnim(void) { GetAnimator()->ClearAllAnims( gameLocal.time, TALON_BLEND ); GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, prelandAnim, gameLocal.time, TALON_BLEND ); CancelEvents( &EV_GlideAnim ); // Could possibly have a glide anim queue CancelEvents( &EV_TakeOffAnimB ); } //============================================================================= // // hhTalon::Event_IdleAnim // //============================================================================= void hhTalon::Event_IdleAnim(void) { GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, idleAnim, gameLocal.time, TALON_BLEND); StartSound("snd_idle", SND_CHANNEL_BODY); } //============================================================================= // // hhTalon::Event_TommyIdleAnim // //============================================================================= void hhTalon::Event_TommyIdleAnim(void) { GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, tommyIdleAnim, gameLocal.time, TALON_BLEND); } //============================================================================= // // hhTalon::Event_FlyAnim // //============================================================================= void hhTalon::Event_FlyAnim(void) { GetAnimator()->ClearAllAnims( gameLocal.time, TALON_BLEND ); GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, flyAnim, gameLocal.time, TALON_BLEND); StartSound("snd_fly", SND_CHANNEL_BODY); } //============================================================================= // // hhTalon::Event_GlideAnim // //============================================================================= void hhTalon::Event_GlideAnim(void) { GetAnimator()->ClearAllAnims( gameLocal.time, TALON_BLEND ); GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, glideAnim, gameLocal.time, TALON_BLEND); StartSound("snd_glide", SND_CHANNEL_BODY); } //============================================================================= // // hhTalon::Event_TakeOffAnim // //============================================================================= void hhTalon::Event_TakeOffAnim(void) { GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); // No blend when clearing old anims while taking off GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, takeOffAnim, gameLocal.time, TALON_BLEND); PostEventMS( &EV_TakeOffAnimB, GetAnimator()->GetAnim( takeOffAnim )->Length() + TALON_BLEND ); StartSound("snd_takeoff", SND_CHANNEL_ANY); } //============================================================================= // // hhTalon::Event_TakeOffAnimB // //============================================================================= void hhTalon::Event_TakeOffAnimB(void) { GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, takeOffAnimB, gameLocal.time, 0); PostEventMS( &EV_GlideAnim, GetAnimator()->GetAnim( takeOffAnimB )->Length() + TALON_BLEND ); } //============================================================================= // // hhTalon::Show // // Special show needed to remove any collision information on Talon //============================================================================= void hhTalon::Show() { if ( state == StateTommy ) { return; } hhMonsterAI::Show(); // Needed after the show GetPhysics()->SetContents( 0 ); // No collisions GetPhysics()->SetClipMask( 0 ); // No collisions } // TALON PERCH SPOT =========================================================== //============================================================================= // // hhTalonTarget::Spawn // //============================================================================= void hhTalonTarget::Spawn(void) { priority = 2.0f - spawnArgs.GetInt("priority"); if ( priority < 0 ) { priority = 0; } else if ( priority > 2) { priority = 2; } bNotInSpiritWalk = spawnArgs.GetBool( "notInSpiritWalk" ); bShouldSquawk = spawnArgs.GetBool( "shouldSquawk" ); bValidForTalon = spawnArgs.GetBool( "valid", "1" ); gameLocal.RegisterTalonTarget(this); GetPhysics()->SetContents( 0 ); Hide(); } void hhTalonTarget::Save(idSaveGame *savefile) const { savefile->WriteInt( priority ); savefile->WriteBool( bNotInSpiritWalk ); savefile->WriteBool( bShouldSquawk ); savefile->WriteBool( bValidForTalon ); } void hhTalonTarget::Restore( idRestoreGame *savefile ) { savefile->ReadInt( priority ); savefile->ReadBool( bNotInSpiritWalk ); savefile->ReadBool( bShouldSquawk ); savefile->ReadBool( bValidForTalon ); } //============================================================================= // // hhTalonTarget::ShowTargets // //============================================================================= void hhTalonTarget::ShowTargets( void ) { int i; int count; idEntity *ent; count = gameLocal.talonTargets.Num(); for(i = 0; i < count; i++) { ent = (hhTalonTarget *)gameLocal.talonTargets[i]; ent->Show(); } } //============================================================================= // // hhTalonTarget::HideTargets // //============================================================================= void hhTalonTarget::HideTargets( void ) { int i; int count; idEntity *ent; count = gameLocal.talonTargets.Num(); for(i = 0; i < count; i++) { ent = (hhTalonTarget *)gameLocal.talonTargets[i]; ent->Hide(); } } //============================================================================= // // hhTalonTarget::~hhTalonTarget // //============================================================================= hhTalonTarget::~hhTalonTarget() { gameLocal.talonTargets.Remove( this ); } //============================================================================= // // hhTalonTarget::Reached // // Called when Talon reaches the target point //============================================================================= void hhTalonTarget::Reached( hhTalon *talon ) { if( !talon ) { return; } // Landing on a perch spot sends an action message to the spot's targets int num = targets.Num(); for (int ix=0; ixPostEventMS(&EV_TalonAction, 0, talon, true); } } } //============================================================================= // // hhTalonTarget::Left // // Called when Talon leaves the target point //============================================================================= void hhTalonTarget::Left( hhTalon *talon ) { if( !talon ) { return; } // Leaving on a perch spot sends an action message to the spot's targets int num = targets.Num(); for (int ix=0; ixPostEventMS(&EV_TalonAction, 0, talon, false); } } } //============================================================================= // // hhTalonTarget::Event_CallTalon // // Calls Talon to this point and forces him to stay on this point unless released // or called to another point //============================================================================= void hhTalonTarget::Event_CallTalon( float vel, float rot, float perch ) { hhPlayer *player; hhTalon *talon; // Find Talon player = static_cast( gameLocal.GetLocalPlayer() ); if ( !player->talon.IsValid() ) { return; } talon = player->talon.GetEntity(); talon->SetForcedTarget( true ); talon->SetForcedPhysicsFactors( vel, rot, perch ); talon->FindTalonTarget( NULL, this, false ); // Force this entity as the target talon->Event_TakeOffAnim(); talon->EnterFlyState(); } //============================================================================= // // hhTalonTarget::Event_ReleaseTalon // // Releases Talon from this perch spot and allows him to continue his normal scripting //============================================================================= void hhTalonTarget::Event_ReleaseTalon() { hhPlayer *player; hhTalon *talon; // Find Talon player = static_cast( gameLocal.GetLocalPlayer() ); if ( !player->talon.IsValid() ) { return; } talon = player->talon.GetEntity(); talon->SetForcedTarget( false ); talon->SetForcedPhysicsFactors( 1.0f, 1.0f, 1.0f ); } //============================================================================= // // hhTalonTarget::Event_SetPerchState // // Set the state if Talon can or cannot fly towards this target //============================================================================= void hhTalonTarget::Event_SetPerchState( float newState ) { bValidForTalon = ( newState > 0 ) ? true : false; }