mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-12 08:11:10 +02:00
1778 lines
50 KiB
C++
1778 lines
50 KiB
C++
//*****************************************************************************
|
|
//**
|
|
//** 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("<perchTicker>", NULL);
|
|
const idEventDef EV_PerchChatter("perchChatter", NULL);
|
|
const idEventDef EV_PerchSquawk("<perchSquawk>", NULL);
|
|
|
|
const idEventDef EV_CheckForTarget( "<checkForTarget>", NULL );
|
|
const idEventDef EV_CheckForEnemy( "<checkForEnemy>", NULL );
|
|
|
|
const idEventDef EV_TalonAction("talonaction", "ed");
|
|
|
|
// Anim Events
|
|
const idEventDef EV_LandAnim("<landAnim>", NULL);
|
|
const idEventDef EV_PreLandAnim("<prelandAnim>", NULL);
|
|
const idEventDef EV_IdleAnim("<idleAnim>", NULL);
|
|
const idEventDef EV_TommyIdleAnim("<tommyIdleAnim>", NULL);
|
|
const idEventDef EV_FlyAnim("<flyAnim>", NULL);
|
|
const idEventDef EV_GlideAnim("<glideAnim>", NULL);
|
|
const idEventDef EV_TakeOffAnim("<takeOffAnim>", NULL);
|
|
const idEventDef EV_TakeOffAnimB("<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<idClass *&>(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<int &> (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<hhMonsterAI*>(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<hhMonsterAI *>( 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<hhMonsterAI*>(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; ix<num; ix++) {
|
|
if (targets[ix].IsValid()) {
|
|
targets[ix].GetEntity()->PostEventMS(&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; ix<num; ix++) {
|
|
if (targets[ix].IsValid()) {
|
|
targets[ix].GetEntity()->PostEventMS(&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<hhPlayer *>( 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<hhPlayer *>( 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;
|
|
}
|