mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-12 08:11:10 +02:00
2141 lines
68 KiB
C++
2141 lines
68 KiB
C++
|
|
#include "precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "prey_local.h"
|
|
|
|
const idEventDef MA_LaserOn("laserOn", NULL);
|
|
const idEventDef MA_LaserOff("laserOff", NULL);
|
|
const idEventDef MA_EscapePortal( "escapePortal" );
|
|
const idEventDef MA_AssignSniperFx( "<assignSniperFx>", "e" );
|
|
const idEventDef MA_KickAngle( "kickAngle", "v" );
|
|
const idEventDef MA_AssignFlashLightFx( "<assignFlashLightFx>", "e" );
|
|
const idEventDef MA_FlashLightOn( "flashLightOn" );
|
|
const idEventDef MA_FlashLightOff( "flashLightOff" );
|
|
const idEventDef MA_EnemyCanSee( "enemyCanSee", NULL, 'd' );
|
|
const idEventDef MA_PrintAction( "printAction", "s" );
|
|
const idEventDef MA_GetAlly( "getAlly", NULL, 'e' );
|
|
const idEventDef MA_TriggerDelay( "triggerDelay", "Ef" );
|
|
const idEventDef MA_CallBackup( "callBackup", "f" );
|
|
const idEventDef MA_SaySound( "saySound", "s", 'f' );
|
|
const idEventDef MA_CheckRush( "<checkRush>" );
|
|
const idEventDef MA_CheckRetreat( "<checkRetreat>" );
|
|
const idEventDef MA_SayEnemyInfo( "sayEnemyInfo", NULL, 'd' );
|
|
const idEventDef MA_SetNextVoiceTime( "setNextVoiceTime", "d" );
|
|
const idEventDef MA_GetCoverNode( "getCoverNode", NULL, 'v' );
|
|
const idEventDef MA_GetCoverPoint( "getCoverPoint", NULL, 'v' );
|
|
const idEventDef MA_OnProjectileHit( "<onProjectileHit>", "e" );
|
|
const idEventDef MA_GetSightNode( "getSightNode", NULL, 'v' );
|
|
const idEventDef MA_GetNearSightPoint( "getNearSightPoint", NULL, 'v' );
|
|
const idEventDef MA_Blocked( "<blocked>" );
|
|
const idEventDef MA_EnemyPortal( "<enemyPortal>", "e" );
|
|
const idEventDef MA_GetEnemyPortal( "getEnemyPortal", NULL, 'e' );
|
|
const idEventDef MA_EnemyVehicleDocked( "enemyVehicleDocked", NULL, 'd' );
|
|
|
|
CLASS_DECLARATION(hhMonsterAI, hhHunterSimple)
|
|
EVENT( MA_OnProjectileLaunch, hhHunterSimple::Event_OnProjectileLaunch )
|
|
EVENT( MA_OnProjectileHit, hhHunterSimple::Event_OnProjectileHit )
|
|
EVENT( MA_LaserOn, hhHunterSimple::Event_LaserOn)
|
|
EVENT( MA_LaserOff, hhHunterSimple::Event_LaserOff)
|
|
EVENT( MA_EscapePortal, hhHunterSimple::Event_EscapePortal )
|
|
EVENT( MA_AssignSniperFx, hhHunterSimple::Event_AssignSniperFx )
|
|
EVENT( MA_KickAngle, hhHunterSimple::Event_KickAngle )
|
|
EVENT( MA_AssignFlashLightFx, hhHunterSimple::Event_AssignFlashLightFx )
|
|
EVENT( MA_FlashLightOn, hhHunterSimple::Event_FlashLightOn )
|
|
EVENT( MA_FlashLightOff, hhHunterSimple::Event_FlashLightOff )
|
|
EVENT( MA_EnemyCanSee, hhHunterSimple::Event_EnemyCanSee )
|
|
EVENT( MA_PrintAction, hhHunterSimple::Event_PrintAction )
|
|
EVENT( MA_GetAlly, hhHunterSimple::Event_GetAlly )
|
|
EVENT( MA_TriggerDelay, hhHunterSimple::Event_TriggerDelay )
|
|
EVENT( MA_CallBackup, hhHunterSimple::Event_CallBackup )
|
|
EVENT( MA_SaySound, hhHunterSimple::Event_SaySound )
|
|
EVENT( MA_CheckRush, hhHunterSimple::Event_CheckRush )
|
|
EVENT( MA_CheckRetreat, hhHunterSimple::Event_CheckRetreat )
|
|
EVENT( MA_SayEnemyInfo, hhHunterSimple::Event_SayEnemyInfo )
|
|
EVENT( MA_SetNextVoiceTime, hhHunterSimple::Event_SetNextVoiceTime )
|
|
EVENT( MA_GetCoverNode, hhHunterSimple::Event_GetCoverNode )
|
|
EVENT( MA_GetCoverPoint, hhHunterSimple::Event_GetCoverPoint )
|
|
EVENT( MA_GetSightNode, hhHunterSimple::Event_GetSightNode )
|
|
EVENT( MA_GetNearSightPoint, hhHunterSimple::Event_GetNearSightPoint )
|
|
EVENT( MA_Blocked, hhHunterSimple::Event_Blocked )
|
|
EVENT( MA_EnemyPortal, hhHunterSimple::Event_EnemyPortal )
|
|
EVENT( MA_GetEnemyPortal, hhHunterSimple::Event_GetEnemyPortal )
|
|
EVENT( MA_EnemyVehicleDocked, hhHunterSimple::Event_EnemyVehicleDocked )
|
|
END_CLASS
|
|
|
|
hhHunterSimple::hhHunterSimple() {
|
|
beamLaser = 0;
|
|
kickSpeed = ang_zero;
|
|
kickAngles = ang_zero;
|
|
alternateAccuracy = false;
|
|
bFlashLight = false;
|
|
nodeList.Clear();
|
|
ally.Clear();
|
|
nextEnemyCheck = 0;
|
|
enemyPortal = 0;
|
|
nextBlockCheckTime = 0;
|
|
lastMoveOrigin = vec3_zero;
|
|
nextSpiritCheck = 0; //HUMANHEAD jsh PCF 5/2/06 hunter combat fixes
|
|
}
|
|
|
|
void hhHunterSimple::Spawn() {
|
|
bFlashLight = false;
|
|
lastChargeTime = 0;
|
|
endSpeechTime = 0;
|
|
enemyRushCount = 0;
|
|
enemyRetreatCount = 0;
|
|
nextVoiceTime = 0;
|
|
currentAction.Clear();
|
|
currentSpeech.Clear();
|
|
flashlightLength = spawnArgs.GetFloat( "flashlightLength", "90" );
|
|
flashlightTime = gameLocal.time + SEC2MS( spawnArgs.GetInt( "flashlight_delay" ) );
|
|
beamLaser = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamLaser" ) );
|
|
initialOrigin = GetOrigin();
|
|
if( beamLaser.IsValid() ) {
|
|
beamLaser->Activate( false );
|
|
idMat3 boneAxis;
|
|
idVec3 boneOrigin;
|
|
GetJointWorldTransform( spawnArgs.GetString("laser_bone"), boneOrigin, boneAxis );
|
|
beamLaser->SetOrigin( boneOrigin );
|
|
beamLaser->BindToJoint( this, spawnArgs.GetString("laser_bone"), false );
|
|
}
|
|
PostEventSec( &MA_CheckRush, 0 );
|
|
PostEventSec( &MA_CheckRetreat, 0 );
|
|
}
|
|
|
|
void hhHunterSimple::Event_PostSpawn() {
|
|
hhMonsterAI::Event_PostSpawn();
|
|
|
|
//look for ainodes in targets list
|
|
for ( int i=0;i<targets.Num();i++ ) {
|
|
if ( targets[i].IsValid() && targets[i]->IsType( hhAINode::Type ) ) {
|
|
nodeList.Append( targets[i] );
|
|
}
|
|
}
|
|
if ( nodeList.Num() <= 0 ) {
|
|
//if targets list had none, find dynamically
|
|
idEntity *ent;
|
|
float distSq;
|
|
aasPath_t path;
|
|
int toAreaNum, areaNum;
|
|
for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
|
|
if ( !ent || !ent->spawnArgs.GetBool( "ainode", "0" ) ) {
|
|
continue;
|
|
}
|
|
distSq = (GetOrigin() - ent->GetOrigin()).LengthSqr();
|
|
if ( distSq > 2250000 ) { //less than 1500
|
|
continue;
|
|
}
|
|
//check reachability
|
|
toAreaNum = PointReachableAreaNum( ent->GetOrigin() );
|
|
areaNum = PointReachableAreaNum( physicsObj.GetOrigin() );
|
|
if ( !toAreaNum || !PathToGoal( path, areaNum, physicsObj.GetOrigin(), toAreaNum, ent->GetOrigin() ) ) {
|
|
continue;
|
|
}
|
|
nodeList.Append( ent );
|
|
}
|
|
}
|
|
}
|
|
|
|
void hhHunterSimple::Event_OnProjectileLaunch(hhProjectile *proj) {
|
|
if ( !enemy.IsValid() || !proj || AI_VEHICLE ) {
|
|
return;
|
|
}
|
|
if ( GetPhysics() && GetPhysics()->GetGravityNormal() != idVec3( 0,0,-1 ) ) {
|
|
return;
|
|
}
|
|
|
|
float min = spawnArgs.GetFloat( "dda_dodge_min", "0.3" );
|
|
float max = spawnArgs.GetFloat( "dda_dodge_max", "0.8" );
|
|
float dodgeChance = 0.6f;
|
|
|
|
dodgeChance = (min + (max-min)*gameLocal.GetDDAValue() );
|
|
|
|
if ( ai_debugBrain.GetBool() ) {
|
|
gameLocal.Printf( "%s dodge chance %f\n", GetName(), dodgeChance );
|
|
}
|
|
if ( proj->GetOwner() && proj->GetOwner() == enemy.GetEntity() ) {
|
|
float reactChance = proj->spawnArgs.GetFloat( "react_chance" );
|
|
if ( gameLocal.random.RandomFloat() < reactChance ) {
|
|
if ( ai_debugBrain.GetBool() ) {
|
|
gameLocal.Printf( "say %s\n", proj->spawnArgs.GetString( "react_sound" ) );
|
|
}
|
|
AI_ENEMY_RETREAT = true;
|
|
PostEventSec( &MA_SaySound, gameLocal.random.RandomFloat(), proj->spawnArgs.GetString( "react_sound" ) );
|
|
}
|
|
}
|
|
if ( gameLocal.random.RandomFloat() > dodgeChance ) {
|
|
return;
|
|
}
|
|
if ( proj->GetOwner() && proj->GetOwner()->IsType( hhHunterSimple::Type ) ) {
|
|
return;
|
|
}
|
|
|
|
//determine which side to dodge to
|
|
const function_t *newstate = NULL;
|
|
idVec3 povPos, targetPos;
|
|
povPos = enemy->GetPhysics()->GetOrigin();
|
|
targetPos = GetPhysics()->GetOrigin();
|
|
idVec3 povToTarget = targetPos - povPos;
|
|
povToTarget.z = 0.f;
|
|
povToTarget.Normalize();
|
|
idVec3 povLeft, povUp;
|
|
povToTarget.OrthogonalBasis(povLeft, povUp);
|
|
povLeft.Normalize();
|
|
|
|
idVec3 projVel = proj->GetPhysics()->GetLinearVelocity();
|
|
projVel.Normalize();
|
|
float dot = povLeft * projVel;
|
|
|
|
if ( dot < 0 ) {
|
|
newstate = GetScriptFunction( "state_DodgeRight" );
|
|
} else {
|
|
newstate = GetScriptFunction( "state_DodgeLeft" );
|
|
}
|
|
|
|
if ( newstate ) {
|
|
SetState( newstate );
|
|
UpdateScript();
|
|
}
|
|
}
|
|
|
|
void hhHunterSimple::Event_LaserOn() {
|
|
if ( beamLaser.IsValid() ) {
|
|
beamLaser->Activate( true );
|
|
}
|
|
}
|
|
|
|
void hhHunterSimple::Event_LaserOff() {
|
|
if ( beamLaser.IsValid() ) {
|
|
beamLaser->Activate( false );
|
|
}
|
|
}
|
|
|
|
void hhHunterSimple::Event_KickAngle( const idAngles &ang ) {
|
|
kickSpeed = ang;
|
|
}
|
|
|
|
void hhHunterSimple::Think() {
|
|
PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI);
|
|
if (ai_skipThink.GetBool()) {
|
|
return;
|
|
}
|
|
|
|
hhMonsterAI::Think();
|
|
|
|
trace_t trace;
|
|
if ( flashlightTime <= 0 || gameLocal.time > flashlightTime ) {
|
|
flashlightTime = 0;
|
|
if ( !AI_DEAD && !IsHidden() && !InVehicle() ) {
|
|
idMat3 boneAxis;
|
|
idVec3 boneOrigin;
|
|
GetJointWorldTransform( "fx_barrel", boneOrigin, boneAxis );
|
|
if ( ai_debugBrain.GetBool() ) {
|
|
gameRenderWorld->DebugArrow( colorRed, boneOrigin, boneOrigin + boneAxis[0] * flashlightLength, 10, 1 );
|
|
}
|
|
gameLocal.clip.TracePoint( trace, boneOrigin, boneOrigin + boneAxis[0] * flashlightLength, MASK_OPAQUE, this );
|
|
if ( trace.fraction < 1.0f ) {
|
|
Event_FlashLightOff();
|
|
} else {
|
|
Event_FlashLightOn();
|
|
}
|
|
} else if ( bFlashLight ) {
|
|
Event_FlashLightOff();
|
|
}
|
|
}
|
|
if ( enemy.IsValid() && beamLaser.IsValid() && beamLaser->IsActivated() ) {
|
|
memset(&trace, 0, sizeof(trace));
|
|
|
|
float frametime = (gameLocal.time - gameLocal.previousTime);
|
|
kickSpeed-=4*kickSpeed*frametime/1000;
|
|
kickSpeed-=30*kickAngles*frametime/1000;
|
|
kickAngles+=kickSpeed*frametime/1000;
|
|
|
|
idAngles ang = (lastVisibleEnemyPos + enemy->EyeOffset() - idVec3( 0,0,10 ) - beamLaser->GetOrigin()).ToAngles();
|
|
ang += kickAngles;
|
|
idVec3 forward, up, right;
|
|
ang.ToVectors( &forward, &right, &up );
|
|
gameLocal.clip.TracePoint( trace, beamLaser->GetOrigin(), beamLaser->GetOrigin() + forward * 4000, MASK_SHOT_BOUNDINGBOX, this );
|
|
if ( trace.fraction < 1.0f ) {
|
|
idEntity *hitEnt = gameLocal.GetTraceEntity( trace );
|
|
if ( hitEnt && hitEnt->IsType( idPlayer::Type ) ||
|
|
( hitEnt && hitEnt->IsType( hhVehicle::Type ) ) ) {
|
|
//if we hit the player, set the endpoint a little farther back
|
|
//to prevent the laser from stopping at our camera viewpoint
|
|
idVec3 offset = (trace.endpos - beamLaser->GetOrigin()).ToNormal() * 100;
|
|
beamLaser->SetTargetLocation( trace.endpos + offset );
|
|
} else {
|
|
beamLaser->SetTargetLocation( trace.endpos );
|
|
}
|
|
} else {
|
|
beamLaser->SetTargetLocation( beamLaser->GetOrigin() + forward * 5000 );
|
|
}
|
|
}
|
|
|
|
if ( ai_debugBrain.GetBool() && ally.IsValid() && ally->GetHealth() > 0 ) {
|
|
gameRenderWorld->DebugArrow( colorGreen, GetOrigin(), ally->GetOrigin(), 10, 1 );
|
|
}
|
|
|
|
if ( health > 0 && ai_debugActions.GetBool() && currentAction.Length() ) {
|
|
if ( AI_ENEMY_VISIBLE ) {
|
|
gameRenderWorld->DrawText("visible", GetEyePosition() + idVec3(0.0f, 0.0f, 30.0f), 0.4f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3());
|
|
}
|
|
if ( AI_ENEMY_SHOOTABLE ) {
|
|
gameRenderWorld->DrawText("shootable", GetEyePosition() + idVec3(0.0f, 0.0f, 20.0f), 0.4f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3());
|
|
}
|
|
gameRenderWorld->DrawText(va( "%s", currentAction.c_str() ), GetEyePosition() + idVec3(0.0f, 0.0f, 0.0f), 0.4f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3());
|
|
}
|
|
if ( health > 0 && ai_printSpeech.GetBool() && currentSpeech.Length() ) {
|
|
gameRenderWorld->DrawText(va( "%s", currentSpeech.c_str() ), GetEyePosition() + idVec3(0.0f, 0.0f, 10.0f), 0.4f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3());
|
|
if ( endSpeechTime > 0 && gameLocal.time > endSpeechTime ) {
|
|
currentSpeech.Clear();
|
|
endSpeechTime = 0;
|
|
}
|
|
}
|
|
|
|
if ( enemy.IsValid() ) {
|
|
//HUMANHEAD jsh PCF 5/2/06 hunter combat fixes
|
|
if ( nextSpiritCheck > 0 && gameLocal.time > nextSpiritCheck && enemy->IsType( hhSpiritProxy::Type ) ) {
|
|
nextSpiritCheck = 0;
|
|
SetEnemy( gameLocal.GetLocalPlayer() );
|
|
}
|
|
if ( AI_ENEMY_VISIBLE ) {
|
|
AI_ENEMY_LAST_SEEN = MS2SEC( gameLocal.realClientTime );
|
|
}
|
|
if ( enemy->GetHealth() < spawnArgs.GetInt( "health_warning", "25" ) ) {
|
|
AI_ENEMY_HEALTH_LOW = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
idProjectile *hhHunterSimple::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { //HUMANHEAD mdc - added desiredProjectileDef for supporting multiple projs.
|
|
//jsh overridden to allow per-projectile accuracy
|
|
idVec3 muzzle;
|
|
idVec3 dir;
|
|
idVec3 start;
|
|
trace_t tr;
|
|
idBounds projBounds;
|
|
float distance;
|
|
const idClipModel *projClip;
|
|
float attack_accuracy;
|
|
float attack_cone;
|
|
float projectile_spread;
|
|
float diff;
|
|
float angle;
|
|
float spin;
|
|
idAngles ang;
|
|
int num_projectiles;
|
|
int i;
|
|
idMat3 axis;
|
|
idVec3 tmp;
|
|
idProjectile *lastProjectile;
|
|
|
|
//HUMANHEAD mdc - added to support multiple projectiles
|
|
if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile
|
|
int projIndex = FindProjectileInfo( desiredProjectileDef );
|
|
if( projIndex >= 0 ) {
|
|
SetCurrentProjectile( projIndex );
|
|
}
|
|
}
|
|
//HUMANHEAD END
|
|
|
|
|
|
if ( !projectileDef || AI_DEAD ) {
|
|
gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() );
|
|
return NULL;
|
|
}
|
|
|
|
if ( projectileDef->GetBool( "snipe", "0" ) ) {
|
|
attack_accuracy = 0;
|
|
} else {
|
|
if ( projectileDef->GetFloat( "attack_accuracy" ) ) {
|
|
attack_accuracy = projectileDef->GetFloat( "attack_accuracy", "7" );
|
|
} else {
|
|
attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" );
|
|
}
|
|
}
|
|
|
|
attack_cone = spawnArgs.GetFloat( "attack_cone", "70" );
|
|
if ( projectileDef->GetFloat( "projectile_spread" ) ) {
|
|
projectile_spread = projectileDef->GetFloat( "projectile_spread", "0" );
|
|
} else {
|
|
projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" );
|
|
}
|
|
if ( projectileDef->GetFloat( "num_projectiles" ) ) {
|
|
num_projectiles = projectileDef->GetFloat( "num_projectiles", "1" );
|
|
} else {
|
|
num_projectiles = spawnArgs.GetFloat( "num_projectiles", "1" );
|
|
}
|
|
|
|
|
|
GetMuzzle( jointname, muzzle, axis );
|
|
|
|
if ( !projectile.GetEntity() ) {
|
|
CreateProjectile( muzzle, axis[ 0 ] );
|
|
}
|
|
|
|
lastProjectile = projectile.GetEntity();
|
|
|
|
if ( target != NULL ) {
|
|
tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle;
|
|
tmp.Normalize();
|
|
axis = tmp.ToMat3();
|
|
} else {
|
|
axis = viewAxis;
|
|
}
|
|
|
|
// rotate it because the cone points up by default
|
|
tmp = axis[2];
|
|
axis[2] = axis[0];
|
|
axis[0] = -tmp;
|
|
|
|
// make sure the projectile starts inside the monster bounding box
|
|
const idBounds &ownerBounds = physicsObj.GetAbsBounds();
|
|
projClip = lastProjectile->GetPhysics()->GetClipModel();
|
|
projBounds = projClip->GetBounds().Rotate( axis );
|
|
|
|
// check if the owner bounds is bigger than the projectile bounds
|
|
if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
|
|
( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
|
|
( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
|
|
if ( (ownerBounds - projBounds).RayIntersection( muzzle, viewAxis[ 0 ], distance ) ) {
|
|
start = muzzle + distance * viewAxis[ 0 ];
|
|
} else {
|
|
start = ownerBounds.GetCenter();
|
|
}
|
|
} else {
|
|
// projectile bounds bigger than the owner bounds, so just start it from the center
|
|
start = ownerBounds.GetCenter();
|
|
}
|
|
|
|
gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this );
|
|
muzzle = tr.endpos;
|
|
|
|
// set aiming direction
|
|
GetAimDir( muzzle, target, this, dir );
|
|
ang = dir.ToAngles();
|
|
|
|
// hunter inaccuracy is calculated instead of random spread based
|
|
float hitchance;
|
|
if ( !projectileDef->GetFloat( "hit_chance", "0.2", hitchance ) ) {
|
|
hitchance = spawnArgs.GetFloat( "hit_chance" );
|
|
}
|
|
float hitAccuracy;
|
|
if ( !projectileDef->GetFloat( "hit_accuracy", "0", hitAccuracy ) ) {
|
|
hitAccuracy = spawnArgs.GetFloat( "hit_accuracy" );
|
|
}
|
|
float missAccuracy;
|
|
if ( !projectileDef->GetFloat( "miss_accuracy", "0", missAccuracy ) ) {
|
|
missAccuracy = spawnArgs.GetFloat( "miss_accuracy" );
|
|
}
|
|
float t = MS2SEC( gameLocal.time + entityNumber * 497 );
|
|
alternateAccuracy = !alternateAccuracy;
|
|
if ( gameLocal.random.RandomFloat() < hitchance ) {
|
|
//rolled a hit so add very little inaccuracy
|
|
ang.pitch += idMath::Fabs( idMath::Sin16( t * 5.1 ) * hitAccuracy );
|
|
ang.yaw += idMath::Sin16( t * 6.7 ) * hitAccuracy;
|
|
} else {
|
|
float rand = gameLocal.random.RandomFloat();
|
|
if ( alternateAccuracy ) {
|
|
rand *= -1;
|
|
}
|
|
ang.pitch += rand * missAccuracy;
|
|
if ( !alternateAccuracy ) {
|
|
rand *= -1;
|
|
}
|
|
ang.yaw += rand * missAccuracy;
|
|
}
|
|
|
|
//quick hack that should be fine for now
|
|
bool oriented = GetPhysics()->GetGravityNormal() != idVec3( 0,0,-1 );
|
|
if ( !oriented && clampToAttackCone ) {
|
|
// clamp the attack direction to be within monster's attack cone so he doesn't do
|
|
// things like throw the missile backwards if you're behind him
|
|
diff = idMath::AngleDelta( ang.yaw, current_yaw );
|
|
if ( diff > attack_cone ) {
|
|
ang.yaw = current_yaw + attack_cone;
|
|
} else if ( diff < -attack_cone ) {
|
|
ang.yaw = current_yaw - attack_cone;
|
|
}
|
|
}
|
|
|
|
axis = ang.ToMat3();
|
|
|
|
float spreadRad = DEG2RAD( projectile_spread );
|
|
for( i = 0; i < num_projectiles; i++ ) {
|
|
// spread the projectiles out
|
|
angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() );
|
|
spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat();
|
|
dir = axis[ 0 ] + axis[ 2 ] * ( angle * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) );
|
|
dir.Normalize();
|
|
|
|
// launch the projectile
|
|
if ( !projectile.GetEntity() ) {
|
|
CreateProjectile( muzzle, dir );
|
|
}
|
|
lastProjectile = projectile.GetEntity();
|
|
lastProjectile->Launch( muzzle, dir, vec3_origin );
|
|
projectile = NULL;
|
|
}
|
|
|
|
TriggerWeaponEffects( muzzle, axis );
|
|
|
|
lastAttackTime = gameLocal.time;
|
|
|
|
//HUMANHEAD mdc - added to support multiple projectiles
|
|
projectile = NULL;
|
|
SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side
|
|
//HUMANHEAD END
|
|
|
|
return lastProjectile;
|
|
}
|
|
|
|
void hhHunterSimple::Event_EscapePortal() {
|
|
static const char * passPrefix = "portal_";
|
|
const char * portalDef;
|
|
idEntity * portal;
|
|
idDict portalArgs;
|
|
idList<idStr> xferKeys;
|
|
idList<idStr> xferValues;
|
|
const idKeyValue * buddyKV;
|
|
|
|
|
|
// Find out which portal def to spawn - If none specified, then exit;
|
|
buddyKV = spawnArgs.FindKey( "portal_buddy" );
|
|
if ( buddyKV && buddyKV->GetValue().Length() && gameLocal.FindEntity(buddyKV->GetValue().c_str()) ) {
|
|
// Case of a valid portal_buddy key, make a real portal
|
|
portalDef = spawnArgs.GetString( "def_portal" );
|
|
} else {
|
|
portalDef = spawnArgs.GetString( "def_fakeportal" );
|
|
}
|
|
|
|
if ( !portalDef || !portalDef[0] ) {
|
|
return;
|
|
}
|
|
|
|
// Set the origin of the portal to us.
|
|
//portalArgs.SetVector( "origin", GetOrigin() );
|
|
idVec3 escapePortalOffset = spawnArgs.GetVector( "escapePortalOffset", "40 0 0" );
|
|
portalArgs.SetVector( "origin", GetOrigin() + ( escapePortalOffset * GetAxis() ) );
|
|
portalArgs.SetFloat( "angle", GetAxis()[0].ToYaw() - 180 );
|
|
|
|
// Set the portal draw distance to zero (so the player cannot see through the creature portal)
|
|
portalArgs.Set( "shaderParm5", "0" );
|
|
|
|
// Pass along all 'portal_' keys to the portal's spawnArgs;
|
|
hhUtils::GetKeysAndValues( spawnArgs, passPrefix, xferKeys, xferValues );
|
|
for ( int i = 0; i < xferValues.Num(); ++i ) {
|
|
xferKeys[ i ].StripLeadingOnce( passPrefix );
|
|
portalArgs.Set( xferKeys[ i ].c_str(), xferValues[ i ].c_str() );
|
|
}
|
|
|
|
// Set the name of the associated game portal so it can be turned on and off
|
|
portalArgs.Set( "gamePortalName", GetName() );
|
|
|
|
// Spawn the portal
|
|
portal = gameLocal.SpawnObject( portalDef, &portalArgs );
|
|
if ( !portal ) {
|
|
return;
|
|
}
|
|
|
|
// Move the portal up some pre determinted amt, since its origin is in the middle of it
|
|
float offset = spawnArgs.GetFloat( "offset_portal", 0 );
|
|
portal->GetPhysics()->SetOrigin( portal->GetPhysics()->GetOrigin() + (portal->GetAxis()[2] * offset) );
|
|
|
|
// Update the camera stuff
|
|
portal->ProcessEvent( &EV_UpdateCameraTarget );
|
|
|
|
// Open the portal - Need to delay this, so that PostSpawn gets called/sets up the partner portal
|
|
//? Should we always pass in the player?
|
|
portal->PostEventSec( &EV_Activate, 0, gameLocal.GetLocalPlayer() );
|
|
|
|
// Maybe wait for it to finish?
|
|
|
|
|
|
}
|
|
|
|
void hhHunterSimple::Event_FindReaction( const char* effect ) {
|
|
idEntity* ent;
|
|
hhReaction* react;
|
|
hhReactionDesc::Effect react_effect;
|
|
idEntity* bestEnt = NULL;
|
|
int bestReactIndex = -1;
|
|
float bestDistance = -1;
|
|
float meToReact, enemyToReact;
|
|
int bestRank = -1;
|
|
|
|
react_effect = hhReactionDesc::StrToEffect( effect );
|
|
|
|
if( react_effect == hhReactionDesc::Effect_Invalid ) {
|
|
gameLocal.Warning( "unknown effect '%s' requested from FindReaction", effect );
|
|
idThread::ReturnEntity( NULL );
|
|
return;
|
|
}
|
|
|
|
for ( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
|
|
if( !ent || ent->fl.isDormant || !ent->fl.refreshReactions ) {
|
|
continue;
|
|
}
|
|
|
|
for( int j = 0; j < ent->GetNumReactions(); j++ ) {
|
|
react = ent->GetReaction( j );
|
|
if( !react || !react->IsActive() ) {
|
|
continue;
|
|
}
|
|
if( react_effect != react->desc->effect ) {
|
|
continue;
|
|
}
|
|
if( react->desc->flags & hhReactionDesc::flag_Exclusive ) { //check exclusiveness
|
|
if ( react->exclusiveOwner.IsValid() && react->exclusiveOwner->health <= 0 ) {
|
|
react->exclusiveOwner.Clear();
|
|
}
|
|
if( react->exclusiveOwner.GetEntity() && react->exclusiveOwner != this ) {
|
|
continue;
|
|
}
|
|
}
|
|
//Skip based on flag requirements
|
|
if( (react->desc->flags & hhReactionDesc::flagReq_RangeAttack) && !AI_HAS_RANGE_ATTACK ) {
|
|
continue;
|
|
}
|
|
// Skip monsters without melee attack
|
|
if( (react->desc->flags & hhReactionDesc::flagReq_MeleeAttack) && !AI_HAS_MELEE_ATTACK ) {
|
|
continue;
|
|
}
|
|
if( react->desc->flags & hhReactionDesc::flagReq_KeyValue ) {
|
|
idStr val;
|
|
if( spawnArgs.GetString(react->desc->key, "", val) ) {
|
|
if( val != react->desc->keyVal ) {
|
|
continue;
|
|
}
|
|
}
|
|
else {
|
|
continue;
|
|
}
|
|
}
|
|
// Skip monsters without specific animation?
|
|
if( react->desc->flags & hhReactionDesc::flagReq_Anim ) {
|
|
if( !GetAnimator()->HasAnim(react->desc->anim) ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Skip monsters in vehicles?
|
|
if( (react->desc->flags & hhReactionDesc::flagReq_NoVehicle) && InVehicle() ) {
|
|
continue;
|
|
}
|
|
|
|
float distToEnt, distToEnemy, distEnemyToEnt;
|
|
// Check actual specifics for reaction type
|
|
switch( react->desc->effect ) {
|
|
case hhReactionDesc::Effect_HaveFun:
|
|
case hhReactionDesc::Effect_Vehicle:
|
|
case hhReactionDesc::Effect_VehicleDock:
|
|
case hhReactionDesc::Effect_ProvideCover:
|
|
//only use reasonably reachable objects
|
|
if ( enemy.IsValid() ) {
|
|
distToEnt = TravelDistance( GetOrigin(), ent->GetOrigin() );
|
|
distToEnemy = (GetOrigin() - enemy->GetOrigin()).Length();
|
|
distEnemyToEnt = (enemy->GetOrigin() - ent->GetOrigin()).Length();
|
|
if ( distToEnt > 1.0 ) {
|
|
float ratio = distToEnt / distEnemyToEnt;
|
|
if ( ratio > 2.0f ) {
|
|
//skip if object is farther than enemy or if enemy is closer to object than i am
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
case hhReactionDesc::Effect_Heal:
|
|
if ( enemy.IsValid() ) {
|
|
distToEnt = TravelDistance( GetOrigin(), ent->GetOrigin() );
|
|
distToEnemy = (GetOrigin() - enemy->GetOrigin()).Length();
|
|
distEnemyToEnt = (enemy->GetOrigin() - ent->GetOrigin()).Length();
|
|
if ( distToEnt > 1.0 ) {
|
|
float ratio = distToEnt / distEnemyToEnt;
|
|
if ( ratio > 2.0f ) {
|
|
//skip if object is farther than enemy or if enemy is closer to object than i am
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
case hhReactionDesc::Effect_Climb:
|
|
case hhReactionDesc::Effect_Passageway:
|
|
case hhReactionDesc::Effect_CallBackup:
|
|
break;
|
|
case hhReactionDesc::Effect_DamageEnemy:
|
|
//check if enemy is close enough to matter
|
|
if ( enemy.IsValid() && react->desc->effectRadius > 0 ) {
|
|
enemyToReact = (enemy->GetOrigin() - react->causeEntity->GetOrigin()).LengthSqr();
|
|
if ( enemyToReact > react->desc->effectRadius * react->desc->effectRadius ) {
|
|
continue;
|
|
}
|
|
}
|
|
//check if i'm too close
|
|
meToReact = (GetOrigin() - react->causeEntity->GetOrigin()).LengthSqr();
|
|
if ( meToReact < react->desc->effectRadius * react->desc->effectRadius ) {
|
|
continue;
|
|
}
|
|
break;
|
|
case hhReactionDesc::Effect_Damage:
|
|
default:
|
|
continue;
|
|
}
|
|
// if we have actually gotten this far, do our intense calculations last..
|
|
int rank = EvaluateReaction( react );
|
|
if( rank == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
// We have a valid reaction...
|
|
float distSq = ( react->causeEntity->GetOrigin() - GetOrigin() ).LengthSqr();
|
|
if ( bestRank == -1 || bestRank <= rank ) { // Check the reaction rank against our best rank
|
|
if (rank == bestRank && distSq > bestDistance) { // If they are the same rank, but the current one is farther then ignore it.
|
|
continue;
|
|
}
|
|
bestEnt = ent;
|
|
bestReactIndex = j;
|
|
bestDistance = distSq;
|
|
bestRank = rank;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bestEnt ) {
|
|
targetReaction.entity = bestEnt;
|
|
targetReaction.reactionIndex = bestReactIndex;
|
|
hhReaction *reaction = targetReaction.GetReaction();
|
|
if( reaction && reaction->desc->flags & hhReactionDesc::flagReq_RangeAttack ) {
|
|
shootTarget = bestEnt;
|
|
} else {
|
|
shootTarget = NULL;
|
|
}
|
|
idThread::ReturnEntity( targetReaction.entity.GetEntity() );
|
|
} else {
|
|
idThread::ReturnEntity( NULL );
|
|
}
|
|
}
|
|
|
|
void hhHunterSimple::AnimMove( void ) {
|
|
//overridden to set AI_BLOCKED when blockEnt is found
|
|
idVec3 goalPos;
|
|
idVec3 delta;
|
|
idVec3 goalDelta;
|
|
float goalDist;
|
|
monsterMoveResult_t moveResult;
|
|
idVec3 newDest;
|
|
|
|
idVec3 oldorigin = physicsObj.GetOrigin();
|
|
#ifdef HUMANHEAD //jsh wallwalk
|
|
idMat3 oldaxis = GetGravViewAxis();
|
|
#else
|
|
idMat3 oldaxis = viewAxis;
|
|
#endif
|
|
|
|
AI_BLOCKED = false;
|
|
|
|
if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){
|
|
move.lastMoveOrigin.Zero();
|
|
move.lastMoveTime = gameLocal.time;
|
|
}
|
|
|
|
move.obstacle = NULL;
|
|
if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) {
|
|
TurnToward( lastVisibleEnemyPos );
|
|
goalPos = oldorigin;
|
|
} else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
|
|
TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
|
|
goalPos = oldorigin;
|
|
} else if ( GetMovePos( goalPos ) ) {
|
|
if ( move.moveCommand != MOVE_WANDER ) {
|
|
CheckObstacleAvoidance( goalPos, newDest );
|
|
nextMovePos = newDest;
|
|
TurnToward( newDest );
|
|
} else {
|
|
TurnToward( goalPos );
|
|
}
|
|
}
|
|
|
|
Turn();
|
|
|
|
if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) {
|
|
if ( gameLocal.time < move.startTime + move.duration ) {
|
|
goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time );
|
|
delta = goalPos - oldorigin;
|
|
delta.z = 0.0f;
|
|
} else {
|
|
delta = move.moveDest - oldorigin;
|
|
delta.z = 0.0f;
|
|
StopMove( MOVE_STATUS_DONE );
|
|
}
|
|
} else if ( allowMove ) {
|
|
#ifdef HUMANHEAD //jsh wallwalk
|
|
GetMoveDelta( oldaxis, GetGravViewAxis(), delta );
|
|
#else
|
|
GetMoveDelta( oldaxis, viewAxis, delta );
|
|
#endif
|
|
} else {
|
|
delta.Zero();
|
|
}
|
|
|
|
if ( ai_debugBrain.GetBool() ) {
|
|
gameRenderWorld->DebugArrow( colorRed, physicsObj.GetOrigin(), physicsObj.GetOrigin() + delta.ToNormal() * 100, 10 );
|
|
gameRenderWorld->DebugArrow( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + renderEntity.axis[1] * 100, 10 );
|
|
gameRenderWorld->DebugArrow( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + GetPhysics()->GetAxis()[2] * 100, 10 );
|
|
}
|
|
|
|
if ( move.moveCommand == MOVE_TO_POSITION ) {
|
|
goalDelta = move.moveDest - oldorigin;
|
|
goalDist = goalDelta.LengthFast();
|
|
if ( goalDist < delta.LengthFast() ) {
|
|
delta = goalDelta;
|
|
}
|
|
}
|
|
physicsObj.SetDelta( delta );
|
|
physicsObj.ForceDeltaMove( disableGravity );
|
|
|
|
RunPhysics();
|
|
|
|
moveResult = physicsObj.GetMoveResult();
|
|
if ( !af_push_moveables && attack.Length() && TestMelee() ) {
|
|
DirectDamage( attack, enemy.GetEntity() );
|
|
} else {
|
|
idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
|
|
if ( blockEnt ) {
|
|
AI_BLOCKED = true;
|
|
AI_NEXT_DIR_TIME = 0;
|
|
|
|
if ( blockEnt->IsType( idAI::Type ) ) {
|
|
StopMove( MOVE_STATUS_BLOCKED_BY_MONSTER );
|
|
}
|
|
}
|
|
if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
|
|
KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
|
|
}
|
|
}
|
|
|
|
BlockedFailSafe();
|
|
|
|
AI_ONGROUND = physicsObj.OnGround();
|
|
|
|
idVec3 org = physicsObj.GetOrigin();
|
|
if ( oldorigin != org ) {
|
|
TouchTriggers();
|
|
}
|
|
|
|
if ( ai_debugMove.GetBool() ) {
|
|
gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec );
|
|
gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec );
|
|
gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
|
|
DrawRoute();
|
|
}
|
|
}
|
|
|
|
void hhHunterSimple::Event_AssignSniperFx( hhEntityFx* fx ) {
|
|
sniperFx = fx;
|
|
}
|
|
|
|
void hhHunterSimple::Event_AssignFlashLightFx( hhEntityFx* fx ) {
|
|
flashLightFx = fx;
|
|
}
|
|
|
|
bool hhHunterSimple::UpdateAnimationControllers( void ) {
|
|
idVec3 local;
|
|
idVec3 focusPos;
|
|
idVec3 left;
|
|
idVec3 dir;
|
|
idVec3 orientationJointPos;
|
|
idVec3 localDir;
|
|
idAngles newLookAng;
|
|
idAngles diff;
|
|
idMat3 mat;
|
|
idMat3 axis;
|
|
idMat3 orientationJointAxis;
|
|
idAFAttachment *headEnt = head.GetEntity();
|
|
idVec3 eyepos;
|
|
idVec3 pos;
|
|
int i;
|
|
idAngles jointAng;
|
|
float orientationJointYaw;
|
|
|
|
if ( AI_DEAD ) {
|
|
return idActor::UpdateAnimationControllers();
|
|
}
|
|
|
|
if ( orientationJoint == INVALID_JOINT ) {
|
|
orientationJointAxis = viewAxis;
|
|
orientationJointPos = physicsObj.GetOrigin();
|
|
orientationJointYaw = current_yaw;
|
|
} else {
|
|
GetJointWorldTransform( orientationJoint, gameLocal.time, orientationJointPos, orientationJointAxis );
|
|
orientationJointYaw = orientationJointAxis[ 2 ].ToYaw();
|
|
orientationJointAxis = idAngles( 0.0f, orientationJointYaw, 0.0f ).ToMat3();
|
|
}
|
|
|
|
if ( ai_debugBrain.GetBool() ) {
|
|
gameRenderWorld->DebugArrow( colorCyan, orientationJointPos, orientationJointPos + orientationJointAxis[0] * 64.0, 10, 1 );
|
|
}
|
|
|
|
if ( focusJoint != INVALID_JOINT ) {
|
|
if ( headEnt ) {
|
|
headEnt->GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis );
|
|
} else {
|
|
// JRMMERGE_GRAVAXIS - What about GetGravAxis() are we still using/needing that?
|
|
GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis );
|
|
}
|
|
eyeOffset.z = eyepos.z - physicsObj.GetOrigin().z;
|
|
} else {
|
|
eyepos = GetEyePosition();
|
|
}
|
|
|
|
if ( headEnt ) {
|
|
CopyJointsFromBodyToHead();
|
|
}
|
|
|
|
// Update the IK after we've gotten all the joint positions we need, but before we set any joint positions.
|
|
// Getting the joint positions causes the joints to be updated. The IK gets joint positions itself (which
|
|
// are already up to date because of getting the joints in this function) and then sets their positions, which
|
|
// forces the heirarchy to be updated again next time we get a joint or present the model. If IK is enabled,
|
|
// or if we have a seperate head, we end up transforming the joints twice per frame. Characters with no
|
|
// head entity and no ik will only transform their joints once. Set g_debuganim to the current entity number
|
|
// in order to see how many times an entity transforms the joints per frame.
|
|
idActor::UpdateAnimationControllers();
|
|
|
|
idEntity *focusEnt = focusEntity.GetEntity();
|
|
//HUMANHEAD jsh allow eyefocus independent from allowJointMod
|
|
if ( ( !allowJointMod && !allowEyeFocus ) || ( gameLocal.time >= focusTime && focusTime != -1 ) ) {
|
|
focusPos = GetEyePosition() + orientationJointAxis[ 0 ] * 512.0f;
|
|
} else if ( focusEnt == NULL ) {
|
|
// keep looking at last position until focusTime is up
|
|
focusPos = currentFocusPos;
|
|
} else if ( focusEnt == enemy.GetEntity() ) {
|
|
if ( beamLaser.IsValid() && beamLaser->IsActivated() ) {
|
|
focusPos = beamLaser->GetTargetLocation();
|
|
} else {
|
|
focusPos = lastVisibleEnemyPos + lastVisibleEnemyEyeOffset - eyeVerticalOffset * enemy.GetEntity()->GetPhysics()->GetGravityNormal();
|
|
}
|
|
} else if ( focusEnt->IsType( idActor::Type ) ) {
|
|
focusPos = static_cast<idActor *>( focusEnt )->GetEyePosition() - eyeVerticalOffset * focusEnt->GetPhysics()->GetGravityNormal();
|
|
} else {
|
|
focusPos = focusEnt->GetPhysics()->GetOrigin();
|
|
}
|
|
|
|
currentFocusPos = currentFocusPos + ( focusPos - currentFocusPos ) * eyeFocusRate;
|
|
|
|
// determine yaw from origin instead of from focus joint since joint may be offset, which can cause us to bounce between two angles
|
|
dir = focusPos - orientationJointPos;
|
|
newLookAng.yaw = idMath::AngleNormalize180( dir.ToYaw() - orientationJointYaw );
|
|
newLookAng.roll = 0.0f;
|
|
newLookAng.pitch = 0.0f;
|
|
newLookAng += lookOffset;
|
|
|
|
// determine pitch from joint position
|
|
dir = focusPos - eyepos;
|
|
if ( ai_debugBrain.GetBool() ) {
|
|
gameRenderWorld->DebugArrow( colorYellow, eyepos, eyepos + dir, 10, 1 );
|
|
}
|
|
dir.NormalizeFast();
|
|
physicsObj.GetAxis().ProjectVector( dir, localDir );
|
|
newLookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ) + lookOffset.pitch;
|
|
newLookAng.roll = 0.0f;
|
|
diff = newLookAng - lookAng;
|
|
|
|
if ( eyeAng != diff ) {
|
|
eyeAng = diff;
|
|
eyeAng.Clamp( eyeMin, eyeMax );
|
|
idAngles angDelta = diff - eyeAng;
|
|
if ( !angDelta.Compare( ang_zero, 0.1f ) ) {
|
|
alignHeadTime = gameLocal.time;
|
|
} else {
|
|
alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime;
|
|
}
|
|
}
|
|
|
|
if ( idMath::Fabs( newLookAng.yaw ) < 0.1f ) {
|
|
alignHeadTime = gameLocal.time;
|
|
}
|
|
|
|
if ( ( gameLocal.time >= alignHeadTime ) || ( gameLocal.time < forceAlignHeadTime ) ) {
|
|
alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime;
|
|
destLookAng = newLookAng;
|
|
destLookAng.Clamp( lookMin, lookMax );
|
|
}
|
|
|
|
diff = destLookAng - lookAng;
|
|
if ( ( lookMin.pitch == -180.0f ) && ( lookMax.pitch == 180.0f ) ) {
|
|
if ( ( diff.pitch > 180.0f ) || ( diff.pitch <= -180.0f ) ) {
|
|
diff.pitch = 360.0f - diff.pitch;
|
|
}
|
|
}
|
|
if ( ( lookMin.yaw == -180.0f ) && ( lookMax.yaw == 180.0f ) ) {
|
|
if ( diff.yaw > 180.0f ) {
|
|
diff.yaw -= 360.0f;
|
|
} else if ( diff.yaw <= -180.0f ) {
|
|
diff.yaw += 360.0f;
|
|
}
|
|
}
|
|
lookAng = lookAng + diff * headFocusRate;
|
|
lookAng.Normalize180();
|
|
|
|
jointAng.roll = 0.0f;
|
|
if ( allowJointMod ) {
|
|
//quick hack. dont change yaw if in different gravity
|
|
bool oriented = GetPhysics()->GetGravityNormal() != idVec3( 0,0,-1 );
|
|
for( i = 0; i < lookJoints.Num(); i++ ) {
|
|
if ( oriented ) {
|
|
jointAng.yaw = 0;
|
|
} else {
|
|
jointAng.yaw = lookAng.yaw * lookJointAngles[ i ].yaw;
|
|
}
|
|
jointAng.pitch = lookAng.pitch * lookJointAngles[ i ].pitch;
|
|
animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() );
|
|
}
|
|
}
|
|
|
|
if ( move.moveType == MOVETYPE_FLY ) {
|
|
// lean into turns
|
|
AdjustFlyingAngles();
|
|
}
|
|
|
|
if ( headEnt ) {
|
|
idAnimator *headAnimator = headEnt->GetAnimator();
|
|
|
|
// HUMANHEAD pdm: Added support for look joints in head entities
|
|
if ( allowJointMod ) {
|
|
for( i = 0; i < headLookJoints.Num(); i++ ) {
|
|
jointAng.pitch = lookAng.pitch * headLookJointAngles[ i ].pitch;
|
|
jointAng.yaw = lookAng.yaw * headLookJointAngles[ i ].yaw;
|
|
headAnimator->SetJointAxis( headLookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() );
|
|
}
|
|
}
|
|
// HUMANHEAD END
|
|
|
|
if ( allowEyeFocus ) {
|
|
idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3(); idMat3 headTranspose = headEnt->GetPhysics()->GetAxis().Transpose();
|
|
axis = eyeAxis * orientationJointAxis;
|
|
left = axis[ 1 ] * eyeHorizontalOffset;
|
|
eyepos -= headEnt->GetPhysics()->GetOrigin();
|
|
headAnimator->SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose );
|
|
headAnimator->SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f - left ) * headTranspose );
|
|
|
|
//if ( ai_debugMove.GetBool() ) {
|
|
// gameRenderWorld->DebugLine( colorRed, orientationJointPos, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose, gameLocal.msec );
|
|
//}
|
|
} else {
|
|
headAnimator->ClearJoint( leftEyeJoint );
|
|
headAnimator->ClearJoint( rightEyeJoint );
|
|
}
|
|
} else {
|
|
if ( allowEyeFocus ) {
|
|
idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3();
|
|
axis = eyeAxis * orientationJointAxis;
|
|
left = axis[ 1 ] * eyeHorizontalOffset;
|
|
eyepos += axis[ 0 ] * 64.0f - physicsObj.GetOrigin();
|
|
animator.SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + left );
|
|
animator.SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos - left );
|
|
} else {
|
|
animator.ClearJoint( leftEyeJoint );
|
|
animator.ClearJoint( rightEyeJoint );
|
|
}
|
|
}
|
|
|
|
//HUMANHEAD pdm jawflap
|
|
hhAnimator *theAnimator;
|
|
if (head.IsValid()) {
|
|
theAnimator = head->GetAnimator();
|
|
}
|
|
else {
|
|
theAnimator = GetAnimator();
|
|
}
|
|
JawFlap(theAnimator);
|
|
//END HUMANHEAD
|
|
|
|
return true;
|
|
}
|
|
|
|
void hhHunterSimple::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
|
|
//overridden to remove muzzle fx on death
|
|
if ( AI_DEAD ) {
|
|
AI_DAMAGE = true;
|
|
return;
|
|
}
|
|
if ( enemy.IsValid() ) {
|
|
gameLocal.AlertAI( enemy.GetEntity(), spawnArgs.GetInt( "death_alert_radius", "800" ) );
|
|
}
|
|
Event_FlashLightOff();
|
|
if ( beamLaser.IsValid() ) {
|
|
beamLaser->Unbind();
|
|
SAFE_REMOVE( beamLaser );
|
|
}
|
|
hhMonsterAI::Killed( inflictor, attacker, damage, dir, location );
|
|
|
|
//taken from idEntity::RemoveBinds()
|
|
//remove any lingering fx on death
|
|
idEntity *ent;
|
|
idEntity *next;
|
|
for( ent = GetTeamChain(); ent != NULL; ent = next ) {
|
|
next = ent->GetTeamChain();
|
|
if ( ent->GetBindMaster() == this && ent->IsType( idEntityFx::Type ) ) {
|
|
ent->Unbind();
|
|
if( !ent->fl.noRemoveWhenUnbound ) {
|
|
ent->PostEventMS( &EV_Remove, 0 );
|
|
if (gameLocal.isClient) {
|
|
ent->Hide();
|
|
}
|
|
}
|
|
next = GetTeamChain();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void hhHunterSimple::Save( idSaveGame *savefile ) const {
|
|
beamLaser.Save( savefile );
|
|
savefile->WriteVec3( nextMovePos );
|
|
savefile->WriteAngles( kickAngles );
|
|
savefile->WriteAngles( kickSpeed );
|
|
savefile->WriteFloat( flashlightLength );
|
|
sniperFx.Save( savefile );
|
|
|
|
ally.Save( savefile );
|
|
muzzleFx.Save( savefile );
|
|
flashLightFx.Save( savefile );
|
|
savefile->WriteBool( bFlashLight );
|
|
savefile->WriteInt( endSpeechTime );
|
|
savefile->WriteInt( nextEnemyCheck );
|
|
savefile->WriteFloat( lastEnemyDistance );
|
|
savefile->WriteInt( lastChargeTime );
|
|
savefile->WriteInt( nextVoiceTime );
|
|
savefile->WriteVec3( initialOrigin );
|
|
savefile->WriteInt( flashlightTime );
|
|
enemyPortal.Save( savefile );
|
|
savefile->WriteVec3( lastMoveOrigin );
|
|
savefile->WriteInt( nextBlockCheckTime );
|
|
savefile->WriteInt( nextSpiritCheck ); //HUMANHEAD jsh PCF 5/2/06 hunter combat fixes
|
|
|
|
//HUMANHEAD jsh PCF 4/28/06 save nodelist
|
|
savefile->WriteInt(nodeList.Num());
|
|
for (int i = 0; i < nodeList.Num(); i++) {
|
|
nodeList[i].Save(savefile);
|
|
}
|
|
//END HUMANHEAD
|
|
};
|
|
|
|
void hhHunterSimple::Restore( idRestoreGame *savefile ) {
|
|
beamLaser.Restore( savefile );
|
|
savefile->ReadVec3( nextMovePos );
|
|
savefile->ReadAngles( kickAngles );
|
|
savefile->ReadAngles( kickSpeed );
|
|
savefile->ReadFloat( flashlightLength );
|
|
sniperFx.Restore( savefile );
|
|
|
|
ally.Restore( savefile );
|
|
muzzleFx.Restore( savefile );
|
|
flashLightFx.Restore( savefile );
|
|
savefile->ReadBool( bFlashLight );
|
|
savefile->ReadInt( endSpeechTime );
|
|
savefile->ReadInt( nextEnemyCheck );
|
|
savefile->ReadFloat( lastEnemyDistance );
|
|
savefile->ReadInt( lastChargeTime );
|
|
savefile->ReadInt( nextVoiceTime );
|
|
savefile->ReadVec3( initialOrigin );
|
|
savefile->ReadInt( flashlightTime );
|
|
enemyPortal.Restore( savefile );
|
|
savefile->ReadVec3( lastMoveOrigin );
|
|
savefile->ReadInt( nextBlockCheckTime );
|
|
savefile->ReadInt( nextSpiritCheck ); //HUMANHEAD jsh PCF 5/2/06 hunter combat fixes
|
|
|
|
//HUMANHEAD jsh PCF 4/28/06 save nodelist
|
|
int num;
|
|
savefile->ReadInt(num);
|
|
nodeList.SetNum(num);
|
|
for (int i = 0; i < num; i++) {
|
|
nodeList[i].Restore(savefile);
|
|
}
|
|
//END HUMANHEAD
|
|
|
|
alternateAccuracy = false;
|
|
enemyRushCount = 0;
|
|
enemyRetreatCount = 0;
|
|
enemyHiddenCount = 0;
|
|
currentAction.Clear();
|
|
currentSpeech.Clear();
|
|
};
|
|
|
|
void hhHunterSimple::Event_FlashLightOn() {
|
|
if ( !bFlashLight && gameLocal.time > flashlightTime && spawnArgs.GetBool( "can_flashlight" ) ) {
|
|
bFlashLight = true;
|
|
BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_flashLight"), "fx_barrel", NULL, &MA_AssignFlashLightFx, false );
|
|
}
|
|
}
|
|
|
|
void hhHunterSimple::Event_FlashLightOff() {
|
|
bFlashLight = false;
|
|
SAFE_REMOVE( flashLightFx );
|
|
}
|
|
|
|
void hhHunterSimple::PrintDebug() {
|
|
hhMonsterAI::PrintDebug();
|
|
gameLocal.Printf( " Ally: %s\n", ally.IsValid() ? ally->GetName() : "none" );
|
|
gameLocal.Printf( " Allow Movement: %s\n", allowMove ? "yes" : "no" );
|
|
gameLocal.Printf( " Turn rate : %f\n", turnRate );
|
|
if ( nodeList.Num() ) {
|
|
gameLocal.Printf( " Cover Points:\n" );
|
|
aasPath_t path;
|
|
int toAreaNum, areaNum;
|
|
for( int i=0;i<nodeList.Num();i++ ) {
|
|
if ( nodeList[i].IsValid() ) {
|
|
toAreaNum = PointReachableAreaNum( nodeList[i]->GetOrigin() );
|
|
areaNum = PointReachableAreaNum( physicsObj.GetOrigin() );
|
|
if ( !toAreaNum || !PathToGoal( path, areaNum, physicsObj.GetOrigin(), toAreaNum, nodeList[i]->GetOrigin() ) ) {
|
|
common->Printf( " %s: " S_COLOR_RED "NOT REACHABLE\n", nodeList[i]->GetName() );
|
|
} else {
|
|
common->Printf( " %s: " S_COLOR_GREEN " REACHABLE\n", nodeList[i]->GetName() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool hhHunterSimple::TurnToward( const idVec3 &pos ) {
|
|
idVec3 dir;
|
|
idVec3 local_dir;
|
|
float lengthSqr;
|
|
|
|
if (AI_VEHICLE && InVehicle()) {
|
|
GetVehicleInterfaceLocal()->OrientTowards( pos, 0.5 );
|
|
return true;
|
|
}
|
|
|
|
dir = pos - physicsObj.GetOrigin();
|
|
#ifdef HUMANHEAD //jsh wallwalk
|
|
physicsObj.GetAxis().ProjectVector( dir, local_dir );
|
|
#else
|
|
physicsObj.GetGravityAxis().ProjectVector( dir, local_dir );
|
|
#endif
|
|
local_dir.z = 0.0f;
|
|
lengthSqr = local_dir.LengthSqr();
|
|
if ( lengthSqr > Square( 2.0f ) || ( lengthSqr > Square( 0.1f ) && enemy.GetEntity() == NULL ) ) {
|
|
//HUMANHEAD jsh PCF 4/29/06 changed to use enemy rather than player for directional movement
|
|
if ( !AI_DIR_MOVEMENT || AI_PATHING || !enemy.IsValid() ) {
|
|
AI_DIR_MOVEMENT = false;
|
|
ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() );
|
|
AI_DIR = HUNTER_N;
|
|
} else {
|
|
float toEnemy = (enemy->GetOrigin() - GetOrigin()).ToAngles().yaw;
|
|
float toPos = (pos - GetOrigin()).ToAngles().yaw;
|
|
if ( gameLocal.time > AI_NEXT_DIR_TIME && lengthSqr > Square( 2.0f ) ) {
|
|
float ang = toEnemy - toPos;
|
|
ang = idMath::AngleNormalize360( ang );
|
|
AI_NEXT_DIR_TIME = gameLocal.time + SEC2MS(1);
|
|
if ( ang >= 135 && ang < 225 ) {
|
|
AI_DIR = HUNTER_S;
|
|
ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() + 180 );
|
|
} else if ( ang >= 45 && ang < 135 ) {
|
|
AI_DIR = HUNTER_W;
|
|
ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() + 90 );
|
|
} else if ( ang >= 225 && ang < 315 ) {
|
|
AI_DIR = HUNTER_E;
|
|
ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() + 270 );
|
|
} else {
|
|
AI_DIR = HUNTER_N;
|
|
ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool result = FacingIdeal();
|
|
return result;
|
|
}
|
|
|
|
void hhHunterSimple::LinkScriptVariables( void ) {
|
|
hhMonsterAI::LinkScriptVariables();
|
|
AI_DIR.LinkTo( scriptObject, "AI_DIR" );
|
|
AI_DIR_MOVEMENT.LinkTo( scriptObject, "AI_DIR_MOVEMENT" );
|
|
AI_LAST_DAMAGE_TIME.LinkTo( scriptObject, "AI_LAST_DAMAGE_TIME" );
|
|
AI_ENEMY_RUSH.LinkTo( scriptObject, "AI_ENEMY_RUSH" );
|
|
AI_ENEMY_RETREAT.LinkTo( scriptObject, "AI_ENEMY_RETREAT" );
|
|
AI_ENEMY_HEALTH_LOW.LinkTo( scriptObject, "AI_ENEMY_HEALTH_LOW" );
|
|
AI_ENEMY_RESURRECTED.LinkTo( scriptObject, "AI_ENEMY_RESURRECTED" );
|
|
AI_ALLOW_ORDERS.LinkTo( scriptObject, "AI_ALLOW_ORDERS" );
|
|
AI_ONGROUND.LinkTo( scriptObject, "AI_ONGROUND" );
|
|
AI_ENEMY_SHOOTABLE.LinkTo( scriptObject, "AI_ENEMY_SHOOTABLE" );
|
|
AI_ENEMY_LAST_SEEN.LinkTo( scriptObject, "AI_ENEMY_LAST_SEEN" );
|
|
AI_SHOTBLOCK.LinkTo( scriptObject, "AI_SHOTBLOCK" );
|
|
AI_NEXT_DIR_TIME.LinkTo( scriptObject, "AI_NEXT_DIR_TIME" );
|
|
AI_BLOCKED_FAILSAFE.LinkTo( scriptObject, "AI_BLOCKED_FAILSAFE" );
|
|
AI_KNOCKBACK.LinkTo( scriptObject, "AI_KNOCKBACK" );
|
|
}
|
|
|
|
void hhHunterSimple::Event_GetAlly() {
|
|
if ( ally.IsValid() ) {
|
|
idEntity *foo = ally.GetEntity();
|
|
if ( ally->GetHealth() > 0 ) {
|
|
idThread::ReturnEntity( ally.GetEntity() );
|
|
return;
|
|
} else {
|
|
ally.Clear();
|
|
}
|
|
}
|
|
|
|
idBounds bo( GetOrigin() );
|
|
bo.ExpandSelf( 2000 );
|
|
hhHunterSimple *entAI = NULL;
|
|
idLocationEntity *myLoc = NULL;
|
|
idLocationEntity *entLoc = NULL;
|
|
float bestDistSq = 9999999;
|
|
float distSq = 0;
|
|
entAI = static_cast<hhHunterSimple*>(gameLocal.FindEntity( spawnArgs.GetString( "ally" ) ));
|
|
if ( entAI && entAI->ally.IsValid() ) {
|
|
entAI = NULL;
|
|
}
|
|
if ( !entAI ) {
|
|
hhHunterSimple *hunter = NULL;
|
|
for ( int i=0;i<hhMonsterAI::allSimpleMonsters.Num();i++ ) {
|
|
hunter = static_cast<hhHunterSimple*>(hhMonsterAI::allSimpleMonsters[i]);
|
|
if ( !hunter || hunter == this || hunter->health <= 0 ) {
|
|
continue;
|
|
}
|
|
if ( !hunter->IsType( hhHunterSimple::Type ) || hunter->IsHidden() || hunter->ally.IsValid() ) {
|
|
continue;
|
|
}
|
|
if ( !hunter->GetEnemy() || hunter->GetEnemy() != GetEnemy() ) {
|
|
continue;
|
|
}
|
|
|
|
//if both locations are null, it likely means this locations haven't been set
|
|
//i.e. dev test map so just let the hunters to ally
|
|
myLoc = gameLocal.LocationForPoint( GetOrigin() );
|
|
entLoc = gameLocal.LocationForPoint( hunter->GetOrigin() );
|
|
if ( myLoc != entLoc ) {
|
|
continue;
|
|
}
|
|
|
|
distSq = (GetOrigin() - hunter->GetOrigin()).LengthSqr();
|
|
if ( distSq > 2250000 ) { //less than 1500
|
|
continue;
|
|
}
|
|
if ( distSq < bestDistSq ) {
|
|
entAI = hunter;
|
|
bestDistSq = distSq;
|
|
}
|
|
}
|
|
}
|
|
if ( entAI ) {
|
|
ally.Assign( entAI );
|
|
entAI->ally.Assign( this );
|
|
idThread::ReturnEntity( ally.GetEntity() );
|
|
return;
|
|
} else {
|
|
ally.Clear();
|
|
idThread::ReturnEntity( NULL );
|
|
return;
|
|
}
|
|
}
|
|
|
|
void hhHunterSimple::Event_PrintAction( const char *action ) {
|
|
currentAction = action;
|
|
}
|
|
|
|
void hhHunterSimple::Event_EnemyCanSee() {
|
|
trace_t tr;
|
|
if ( !enemy.IsValid() ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
// Check if a trace succeeds from the player to the entity
|
|
gameLocal.clip.TracePoint( tr, enemy->GetEyePosition(), GetEyePosition(), MASK_MONSTERSOLID, enemy.GetEntity() );
|
|
if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == this ) ) {
|
|
idThread::ReturnInt( true );
|
|
return;
|
|
}
|
|
idThread::ReturnInt( false );
|
|
}
|
|
|
|
void hhHunterSimple::Event_TriggerDelay( idEntity *ent, float delay ) {
|
|
if ( !ent ) {
|
|
return;
|
|
}
|
|
if ( delay < 0.0f ) {
|
|
delay = 0.0f;
|
|
}
|
|
ent->PostEventSec( &EV_Activate, delay, this );
|
|
}
|
|
|
|
void hhHunterSimple::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) {
|
|
const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName );
|
|
if ( damageDef && damageDef->GetBool( "pain_knockback", "0" ) ) {
|
|
AI_KNOCKBACK = true;
|
|
}
|
|
hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
|
|
AI_LAST_DAMAGE_TIME = MS2SEC(gameLocal.time);
|
|
}
|
|
|
|
void hhHunterSimple::Event_CallBackup( float delay ) {
|
|
idEntity *entAI = gameLocal.FindEntity( spawnArgs.GetString( "backup" ) );
|
|
if ( entAI && entAI->IsType( idAI::Type ) && entAI->spawnArgs.GetBool( "portal", "0" ) ) {
|
|
if ( delay < 0.0f ) {
|
|
delay = 0.0f;
|
|
}
|
|
entAI->PostEventSec( &EV_Activate, delay, this );
|
|
}
|
|
}
|
|
|
|
bool hhHunterSimple::CanSee( idEntity *ent, bool useFov ) {
|
|
trace_t tr;
|
|
idVec3 eye;
|
|
idVec3 toPos;
|
|
|
|
if ( ent->IsHidden() ) {
|
|
return false;
|
|
}
|
|
|
|
if ( ent->IsType( idActor::Type ) ) {
|
|
idActor *act = static_cast<idActor*>(ent);
|
|
|
|
// If this actor is in a vehicle, look at the vehicle, not the actor
|
|
if(act->InVehicle()) {
|
|
ent = act->GetVehicleInterface()->GetVehicle();
|
|
}
|
|
}
|
|
|
|
if ( ent->IsType( idActor::Type ) ) {
|
|
toPos = ( ( idActor * )ent )->GetEyePosition();
|
|
} else {
|
|
toPos = ent->GetPhysics()->GetOrigin();
|
|
}
|
|
|
|
if ( useFov && !CheckFOV( toPos ) ) {
|
|
return false;
|
|
}
|
|
|
|
eye = GetEyePosition();
|
|
|
|
if ( InVehicle() ) {
|
|
gameLocal.clip.TracePoint( tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, GetVehicleInterface()->GetVehicle() ); // HUMANHEAD JRM
|
|
if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == ent ) ) {
|
|
return true;
|
|
}
|
|
} else {
|
|
gameLocal.clip.TracePoint( tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, this ); // HUMANHEAD JRM
|
|
|
|
//if enemy is close to where the trace hit, he is shootable
|
|
if ( tr.fraction < 1.0f && enemy.IsValid() ) {
|
|
float hitDist = (GetEyePosition() - tr.endpos).LengthFast();
|
|
float fullDist = (enemy->GetEyePosition() - GetEyePosition()).LengthFast();
|
|
if ( fullDist > 0.0 ) {
|
|
if ( hitDist / fullDist > 0.75f ) {
|
|
AI_ENEMY_SHOOTABLE = true;
|
|
} else {
|
|
AI_ENEMY_SHOOTABLE = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == ent ) ) {
|
|
return true;
|
|
} else if ( bSeeThroughPortals && aas ) {
|
|
shootTarget = NULL;
|
|
#if 0 // jmarshall AI
|
|
int myArea = gameRenderWorld->PointInArea( GetOrigin() );
|
|
int numPortals = gameRenderWorld->NumGamePortalsInArea( myArea );
|
|
if ( numPortals > 0 ) {
|
|
int enemyArea = gameRenderWorld->PointInArea( ent->GetOrigin() );
|
|
for ( int i=0;i<numPortals;i++ ) {
|
|
//if portal's destination area is the same as monster's enemy's area
|
|
if ( gameRenderWorld->GetSoundPortal( myArea, i ).areas[0] == enemyArea ) {
|
|
//find the portal and set it as this monster's shoottarget
|
|
idEntity *spawnedEnt = NULL;
|
|
for( spawnedEnt = gameLocal.spawnedEntities.Next(); spawnedEnt != NULL; spawnedEnt = spawnedEnt->spawnNode.Next() ) {
|
|
if ( !spawnedEnt->IsType( hhPortal::Type ) ) {
|
|
continue;
|
|
}
|
|
if ( gameRenderWorld->PointInArea( spawnedEnt->GetOrigin() ) == myArea) {
|
|
shootTarget = spawnedEnt;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void hhHunterSimple::Event_SaySound( const char *soundName ) {
|
|
if ( AI_DEAD || ai_skipSpeech.GetBool() || gameLocal.time < nextVoiceTime ) {
|
|
return;
|
|
}
|
|
//if we've got an ally, clear out this sound so that he won't say it
|
|
if ( ally.IsValid() ) {
|
|
ally->spawnArgs.Set( soundName, "" );
|
|
}
|
|
int time;
|
|
StartSound( soundName, ( s_channelType )SND_CHANNEL_VOICE2, 0, 0, &time );
|
|
idThread::ReturnFloat( MS2SEC( time ) );
|
|
}
|
|
|
|
void hhHunterSimple::Event_OnProjectileLand(hhProjectile *proj) {
|
|
const function_t *newstate = NULL;
|
|
newstate = GetScriptFunction( "state_AvoidGrenade" );
|
|
shootTarget = proj;
|
|
if ( newstate ) {
|
|
SetState( newstate );
|
|
UpdateScript();
|
|
}
|
|
}
|
|
|
|
void hhHunterSimple::Event_CheckRush() {
|
|
if ( enemy.IsValid() ) {
|
|
float currentDist = (enemy->GetOrigin() - GetOrigin()).Length();
|
|
if ( currentDist < lastEnemyDistance ) {
|
|
//enemy is advancing
|
|
enemyRushCount++;
|
|
if ( enemyRushCount > spawnArgs.GetFloat( "charge_threshold", "3" ) ) {
|
|
if ( currentDist < spawnArgs.GetFloat( "rush_distance", "400" ) ) {
|
|
AI_ENEMY_RUSH = true;
|
|
}
|
|
}
|
|
} else {
|
|
enemyRushCount = 0;
|
|
AI_ENEMY_RUSH = false;
|
|
}
|
|
lastEnemyDistance = currentDist;
|
|
}
|
|
PostEventSec( &MA_CheckRush, spawnArgs.GetFloat( "rush_freq", "0.5" ) );
|
|
}
|
|
|
|
void hhHunterSimple::Event_SetNextVoiceTime( int nextTime ) {
|
|
nextVoiceTime = nextTime;
|
|
}
|
|
|
|
void hhHunterSimple::Event_CheckRetreat() {
|
|
if ( enemy.IsValid() ) {
|
|
float currentDist = (enemy->GetOrigin() - GetOrigin()).Length();
|
|
if ( currentDist > lastEnemyDistance ) {
|
|
//enemy is retreating
|
|
enemyRetreatCount++;
|
|
if ( enemyRetreatCount > spawnArgs.GetFloat( "retreat_threshold", "3" ) ) {
|
|
AI_ENEMY_RETREAT = true;
|
|
}
|
|
} else {
|
|
enemyRetreatCount = 0;
|
|
AI_ENEMY_RETREAT = false;
|
|
}
|
|
}
|
|
PostEventSec( &MA_CheckRetreat, spawnArgs.GetFloat( "retreat_freq", "0.5" ) );
|
|
}
|
|
|
|
void hhHunterSimple::Event_SayEnemyInfo() {
|
|
if ( AI_DEAD || !enemy.IsValid() || !ally.IsValid() || gameLocal.time < nextVoiceTime ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
hhHunterSimple *hunterAlly = static_cast<hhHunterSimple*>(ally.GetEntity());
|
|
if ( !hunterAlly ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
bool closerToEnemy = false;
|
|
idVec3 meToEnemy = enemy->GetOrigin() - GetOrigin();
|
|
idVec3 allyToEnemy = enemy->GetOrigin() - ally->GetOrigin();
|
|
float myDistToEnemy = meToEnemy.LengthFast();
|
|
float allyDistToEnemy = allyToEnemy.LengthFast();
|
|
float distToAlly = (ally->GetOrigin() - GetOrigin()).LengthFast();
|
|
if ( myDistToEnemy < allyDistToEnemy ) {
|
|
closerToEnemy = true;
|
|
}
|
|
if ( !CanSee( enemy.GetEntity(), false ) ) {
|
|
hunterAlly->SetNextVoiceTime( gameLocal.time + SEC2MS(1) );
|
|
Event_SaySound( "snd_behind_cover" );
|
|
idThread::ReturnInt( true );
|
|
return;
|
|
}
|
|
if ( closerToEnemy ) {
|
|
//check if enemy is underneath
|
|
if ( allyToEnemy.z < -spawnArgs.GetFloat( "z_down_dist", "75" ) ) {
|
|
if ( idMath::Fabs(meToEnemy.z) < spawnArgs.GetFloat( "z_equal_dist", "40" ) ) {
|
|
Event_SaySound( "snd_down_here" );
|
|
hunterAlly->SetNextVoiceTime( gameLocal.time + SEC2MS(1) );
|
|
idThread::ReturnInt( true );
|
|
return;
|
|
}
|
|
}
|
|
if ( myDistToEnemy < spawnArgs.GetFloat( "over_there_dist", "400" ) ) {
|
|
Event_SaySound( "snd_over_here" );
|
|
hunterAlly->SetNextVoiceTime( gameLocal.time + SEC2MS(1) );
|
|
idThread::ReturnInt( true );
|
|
return;
|
|
}
|
|
} else {
|
|
if ( myDistToEnemy > spawnArgs.GetFloat( "over_there_dist", "400" ) ) {
|
|
Event_SaySound( "snd_over_there" );
|
|
hunterAlly->SetNextVoiceTime( gameLocal.time + SEC2MS(1) );
|
|
idThread::ReturnInt( true );
|
|
return;
|
|
}
|
|
}
|
|
idThread::ReturnInt( false );
|
|
}
|
|
|
|
bool hhHunterSimple::StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) {
|
|
const idSoundShader *shader;
|
|
const char *sound;
|
|
|
|
if ( length ) {
|
|
*length = 0;
|
|
}
|
|
|
|
// we should ALWAYS be playing sounds from the def.
|
|
// hardcoded sounds MUST be avoided at all times because they won't get precached.
|
|
assert( idStr::Icmpn( soundName, "snd_", 4 ) == 0 );
|
|
|
|
if ( !spawnArgs.GetString( soundName, "", &sound ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( sound[0] == '\0' ) {
|
|
return false;
|
|
}
|
|
|
|
if ( !gameLocal.isNewFrame ) {
|
|
// don't play the sound, but don't report an error
|
|
return true;
|
|
}
|
|
|
|
// HUMANHEAD nla - Check if this sound should be played every X seconds
|
|
float minInterval;
|
|
int lastPlayed;
|
|
if ( spawnArgs.GetFloat( va( "min_%s", soundName ), "0", minInterval ) && minInterval > 0 ) {
|
|
if ( GetLastTimeSoundPlayed()->GetInt( soundName, "-1", lastPlayed ) ) {
|
|
if ( gameLocal.time - minInterval * 1000 < lastPlayed ) {
|
|
return( false );
|
|
}
|
|
}
|
|
GetLastTimeSoundPlayed()->SetInt( soundName, gameLocal.time );
|
|
} else if ( channel == SND_CHANNEL_VOICE2 ) {
|
|
//only play voice over sounds once for the hunter
|
|
if ( GetLastTimeSoundPlayed()->GetInt( soundName, "-1", lastPlayed ) ) {
|
|
if ( lastPlayed > 0 ) {
|
|
return( false );
|
|
}
|
|
}
|
|
GetLastTimeSoundPlayed()->SetInt( soundName, gameLocal.time );
|
|
}
|
|
// HUMANHEAD END
|
|
|
|
shader = declManager->FindSound( sound );
|
|
bool result;
|
|
result = StartSoundShader( shader, channel, soundShaderFlags, broadcast, length );
|
|
if ( result && shader && channel == SND_CHANNEL_VOICE2 ) {
|
|
//print debug info for voice overs
|
|
currentSpeech = shader->GetName();
|
|
endSpeechTime = gameLocal.time + SEC2MS(3);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void hhHunterSimple::Event_GetCoverNode() {
|
|
float dist, bestDist;
|
|
idEntity *bestNode = NULL;
|
|
idEntity *node = NULL;
|
|
if ( !enemy.IsValid() || !EnemyPositionValid() ) {
|
|
idThread::ReturnEntity( NULL );
|
|
return;
|
|
}
|
|
|
|
//find the closest attack node that can see our enemy
|
|
bestNode = NULL;
|
|
bestDist = 9999999.0f;
|
|
idLocationEntity *myLocation = gameLocal.LocationForPoint( physicsObj.GetOrigin() );
|
|
idLocationEntity *entLocation = NULL;
|
|
for( int i = 0; i < nodeList.Num(); i++ ) {
|
|
if ( !nodeList[i].IsValid() ) {
|
|
continue;
|
|
}
|
|
if ( !nodeList[i]->IsType( hhAINode::Type ) ) {
|
|
continue;
|
|
}
|
|
node = nodeList[ i ].GetEntity();
|
|
if ( !node ) {
|
|
continue;
|
|
}
|
|
|
|
//check if node is used
|
|
if ( node->IsType( hhAINode::Type ) ) {
|
|
hhAINode *aiNode = static_cast<hhAINode*>(node);
|
|
if ( aiNode && aiNode->user.IsValid() && !aiNode->user.IsEqualTo( this ) ) {
|
|
if ( !aiNode->user->IsHidden() && aiNode->user->GetHealth() > 0 ) {
|
|
continue;
|
|
} else {
|
|
aiNode->user.Clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
//skip if node is farther than enemy or enemy is closer to object than i am
|
|
float distToNode = TravelDistance( GetOrigin(), node->GetOrigin() );
|
|
float distEnemyToNode = (enemy->GetOrigin() - node->GetOrigin()).Length();
|
|
if ( distEnemyToNode < distToNode || distEnemyToNode < 200 ) {
|
|
continue;
|
|
}
|
|
//skip if object is farther than enemy or if enemy is closer to object than i am
|
|
if ( distToNode > 1.0 && distToNode / distEnemyToNode > 2.0f ) {
|
|
continue;
|
|
}
|
|
|
|
//skip if enemy can see the node
|
|
trace_t tr;
|
|
gameLocal.clip.TracePoint( tr, enemy->GetEyePosition(), node->GetOrigin(), MASK_SHOT_BOUNDINGBOX, this );
|
|
if ( tr.fraction >= 1.0f ) {
|
|
if ( ai_debugBrain.GetBool() ) {
|
|
gameRenderWorld->DebugArrow( colorMagenta, enemy->GetEyePosition(), node->GetOrigin(), 30, 5000 );
|
|
}
|
|
continue;
|
|
}
|
|
|
|
//find closest node
|
|
idVec3 org = node->GetPhysics()->GetOrigin();
|
|
dist = ( physicsObj.GetOrigin() - org ).LengthSqr();
|
|
if ( dist > bestDist ) {
|
|
continue;
|
|
}
|
|
|
|
//found a node that passed all tests
|
|
bestNode = node;
|
|
bestDist = dist;
|
|
}
|
|
if ( bestNode && bestNode->IsType( hhAINode::Type ) ) {
|
|
hhAINode *bestAINode = static_cast<hhAINode*>(bestNode);
|
|
if ( bestAINode ) {
|
|
bestAINode->user.Assign( this );
|
|
}
|
|
}
|
|
if ( bestNode ) {
|
|
if ( ai_debugBrain.GetBool() ) {
|
|
gameRenderWorld->DebugArrow( colorGreen, enemy->GetEyePosition(), bestNode->GetOrigin(), 30, 5000 );
|
|
}
|
|
idThread::ReturnVector( bestNode->GetOrigin() );
|
|
return;
|
|
}
|
|
idThread::ReturnVector( vec3_zero );
|
|
}
|
|
|
|
void hhHunterSimple::Event_GetCoverPoint() {
|
|
if ( !enemy.IsValid() ) {
|
|
idThread::ReturnVector( vec3_zero );
|
|
return;
|
|
}
|
|
//couldn't find a node. try sampling some nearby points
|
|
idVec3 testPoint;
|
|
float bestDist = 9999999.0f;
|
|
idVec3 finalPoint = vec3_zero;
|
|
int finalArea = -1;
|
|
bool clipped = false;
|
|
float yaw = (GetOrigin() - enemy->GetOrigin()).ToYaw();
|
|
int num;
|
|
idClipModel *cm;
|
|
idClipModel *clipModels[ MAX_GENTITIES ];
|
|
idBounds bounds;
|
|
trace_t tr;
|
|
float distance = spawnArgs.GetFloat( "cover_range", "200" );
|
|
float initialYaw = yaw;
|
|
for ( int i=0;i<16;i++ ) {
|
|
testPoint = GetOrigin() + distance * idAngles( 0, yaw, 0 ).ToForward();
|
|
testPoint.z += 35;
|
|
yaw += 22.5;
|
|
|
|
if( gameLocal.clip.Contents( testPoint, GetPhysics()->GetClipModel(), viewAxis, CONTENTS_SOLID, this ) != 0 ) {
|
|
continue;
|
|
}
|
|
|
|
//skip if node is farther than enemy or enemy is closer to object than i am
|
|
float distToNode = TravelDistance( GetOrigin(), testPoint );
|
|
float distEnemyToNode = (enemy->GetOrigin() - testPoint).Length();
|
|
if ( distEnemyToNode < distToNode || distEnemyToNode < 200 ) {
|
|
continue;
|
|
}
|
|
//skip if object is farther than enemy or if enemy is closer to object than i am
|
|
if ( distToNode > 1.0 && distToNode / distEnemyToNode > 2.0f ) {
|
|
continue;
|
|
}
|
|
|
|
//make sure it wont clip into anything at testPoint
|
|
clipped = false;
|
|
bounds.FromTransformedBounds( GetPhysics()->GetBounds(), testPoint, GetPhysics()->GetAxis() );
|
|
num = gameLocal.clip.ClipModelsTouchingBounds( bounds, MASK_MONSTERSOLID, clipModels, MAX_GENTITIES );
|
|
for ( int j = 0; j < num; j++ ) {
|
|
cm = clipModels[ j ];
|
|
// don't check render entities
|
|
if ( cm->IsRenderModel() ) {
|
|
continue;
|
|
}
|
|
idEntity *hit = cm->GetEntity();
|
|
if ( ( hit == this ) || !hit->fl.takedamage ) {
|
|
continue;
|
|
}
|
|
if ( physicsObj.ClipContents( cm ) ) {
|
|
clipped = true;
|
|
break;
|
|
}
|
|
}
|
|
if ( clipped ) {
|
|
if ( ai_debugBrain.GetBool() ) {
|
|
gameRenderWorld->DebugBounds( colorRed, bounds, vec3_origin, 10000 );
|
|
}
|
|
continue;
|
|
}
|
|
|
|
//make sure we can path there
|
|
aasPath_t path;
|
|
int toAreaNum = PointReachableAreaNum( testPoint );
|
|
int areaNum = PointReachableAreaNum( physicsObj.GetOrigin() );
|
|
if ( !toAreaNum || !PathToGoal( path, areaNum, physicsObj.GetOrigin(), toAreaNum, testPoint ) ) {
|
|
continue;
|
|
}
|
|
|
|
//make sure enemy cant see the point
|
|
gameLocal.clip.TracePoint( tr, enemy->GetEyePosition(), testPoint, MASK_SHOT_BOUNDINGBOX, this );
|
|
if ( tr.fraction >= 1.0f ) {
|
|
continue;
|
|
}
|
|
|
|
float distToPoint = (GetOrigin() - testPoint).Length();
|
|
if ( distToPoint < bestDist ) {
|
|
finalPoint = testPoint;
|
|
bestDist = distToPoint;
|
|
finalArea = toAreaNum;
|
|
}
|
|
}
|
|
|
|
if ( finalPoint != vec3_zero ) {
|
|
if ( aas ) {
|
|
aas->PushPointIntoAreaNum( finalArea, finalPoint );
|
|
}
|
|
idThread::ReturnVector( finalPoint );
|
|
return;
|
|
}
|
|
|
|
idThread::ReturnVector( vec3_zero );
|
|
}
|
|
|
|
void hhHunterSimple::Event_GetSightNode() {
|
|
if ( !enemy.IsValid() ) {
|
|
idThread::ReturnVector( vec3_zero );
|
|
return;
|
|
}
|
|
|
|
//find a good spot to attack from
|
|
trace_t tr;
|
|
float distance = spawnArgs.GetFloat( "attack_range", "500" );
|
|
idVec3 testPoint;
|
|
idVec3 finalPoint = vec3_zero;
|
|
bool clipped = false;
|
|
float yaw = (GetOrigin() - enemy->GetOrigin()).ToYaw();
|
|
float initialYaw = yaw;
|
|
idEntity *bestNode = NULL;
|
|
idEntity *node = NULL;
|
|
|
|
//find an attack node that can see our enemy
|
|
for( int i = 0; i < nodeList.Num(); i++ ) {
|
|
if ( !nodeList[i].IsValid() ) {
|
|
continue;
|
|
}
|
|
if ( !nodeList[i]->IsType( hhAINode::Type ) ) {
|
|
continue;
|
|
}
|
|
node = nodeList[ i ].GetEntity();
|
|
if ( !node ) {
|
|
continue;
|
|
}
|
|
|
|
//check if node is used
|
|
if ( node->IsType( hhAINode::Type ) ) {
|
|
hhAINode *aiNode = static_cast<hhAINode*>(node);
|
|
if ( aiNode && aiNode->user.IsValid() && !aiNode->user.IsEqualTo( this ) ) {
|
|
if ( !aiNode->user->IsHidden() && aiNode->user->GetHealth() > 0 ) {
|
|
continue;
|
|
} else {
|
|
aiNode->user.Clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
//check if we can see enemy from this node
|
|
idVec3 eye = testPoint + ( GetPhysics()->GetGravityNormal() * -eyeOffset.z );
|
|
//HUMANHEAD jsh PCF 4/27/06 changed trace to end at enemy origin instead of enemy eye position to fix slowdown
|
|
gameLocal.clip.TracePoint( tr, eye, enemy->GetOrigin(), MASK_SHOT_BOUNDINGBOX, enemy.GetEntity() );
|
|
if ( tr.fraction < 1.0f || ( gameLocal.GetTraceEntity( tr ) != enemy.GetEntity() ) ) {
|
|
continue;
|
|
}
|
|
|
|
//found a node that passed all tests
|
|
bestNode = node;
|
|
}
|
|
if ( bestNode && bestNode->IsType( hhAINode::Type ) ) {
|
|
if ( ai_debugBrain.GetBool() ) {
|
|
gameRenderWorld->DebugArrow( colorGreen, GetOrigin(), bestNode->GetOrigin(), 10, 5000 );
|
|
}
|
|
idThread::ReturnVector( bestNode->GetOrigin() );
|
|
return;
|
|
}
|
|
idThread::ReturnVector( vec3_zero );
|
|
}
|
|
|
|
void hhHunterSimple::Event_GetNearSightPoint() {
|
|
trace_t tr;
|
|
float distance = spawnArgs.GetFloat( "attack_range", "500" );
|
|
idVec3 testPoint;
|
|
idVec3 finalPoint = vec3_zero;
|
|
bool clipped = false;
|
|
float yaw = 0;
|
|
float initialYaw = 0;
|
|
int num, i, j;
|
|
idClipModel *cm;
|
|
idClipModel *clipModels[ MAX_GENTITIES ];
|
|
idBounds bounds;
|
|
int finalArea = -1;
|
|
idList<idVec3> listo;
|
|
|
|
if ( !enemy.IsValid() ) {
|
|
idThread::ReturnVector( vec3_zero );
|
|
return;
|
|
}
|
|
|
|
yaw = (GetOrigin() - enemy->GetOrigin()).ToYaw();
|
|
initialYaw = yaw;
|
|
//test 8 points around the monster, starting with one directly in front of it
|
|
for ( i=0;i<32;i++ ) {
|
|
testPoint = GetOrigin() + distance * idAngles( 0, yaw, 0 ).ToForward();
|
|
testPoint.z += spawnArgs.GetFloat( "attack_z", "300" );
|
|
yaw += 11.25;
|
|
|
|
//make sure it wont clip into anything at testPoint
|
|
clipped = false;
|
|
bounds.FromTransformedBounds( GetPhysics()->GetBounds(), testPoint, GetPhysics()->GetAxis() );
|
|
num = gameLocal.clip.ClipModelsTouchingBounds( bounds, MASK_MONSTERSOLID, clipModels, MAX_GENTITIES );
|
|
for ( j = 0; j < num; j++ ) {
|
|
cm = clipModels[ j ];
|
|
if ( cm->IsRenderModel() ) {
|
|
continue;
|
|
}
|
|
idEntity *hit = cm->GetEntity();
|
|
if ( ( hit == this ) || !hit->fl.takedamage ) {
|
|
continue;
|
|
}
|
|
if ( physicsObj.ClipContents( cm ) ) {
|
|
clipped = true;
|
|
break;
|
|
}
|
|
}
|
|
if ( clipped ) {
|
|
if ( ai_debugBrain.GetBool() ) {
|
|
gameRenderWorld->DebugBounds( colorRed, bounds, vec3_origin, 10000 );
|
|
}
|
|
continue;
|
|
}
|
|
|
|
//make sure we can path there
|
|
int toAreaNum = PointReachableAreaNum( testPoint );
|
|
int areaNum = PointReachableAreaNum( physicsObj.GetOrigin() );
|
|
aasPath_t path;
|
|
if ( !toAreaNum || !PathToGoal( path, areaNum, physicsObj.GetOrigin(), toAreaNum, testPoint ) ) {
|
|
if ( ai_debugBrain.GetBool() ) {
|
|
gameRenderWorld->DebugArrow( colorYellow, GetOrigin(), testPoint, 10, 5000 );
|
|
}
|
|
continue;
|
|
}
|
|
|
|
//make sure we can see enemy there
|
|
idVec3 eye = testPoint + ( GetPhysics()->GetGravityNormal() * -eyeOffset.z );
|
|
gameLocal.clip.TracePoint( tr, eye, enemy->GetEyePosition(), MASK_SHOT_BOUNDINGBOX, enemy.GetEntity() );
|
|
if ( tr.fraction < 1.0f ) {
|
|
if ( ai_debugBrain.GetBool() ) {
|
|
gameRenderWorld->DebugArrow( colorRed, eye, enemy->GetEyePosition(), 10, 5000 );
|
|
}
|
|
continue;
|
|
}
|
|
|
|
listo.Append( testPoint );
|
|
if ( ai_debugBrain.GetBool() ) {
|
|
gameRenderWorld->DebugArrow( colorGreen, GetOrigin(), testPoint, 10, 5000 );
|
|
}
|
|
finalArea = toAreaNum;
|
|
}
|
|
if ( listo.Num() ) {
|
|
finalPoint = listo[gameLocal.random.RandomInt(listo.Num())];
|
|
}
|
|
if ( finalPoint != vec3_zero ) {
|
|
if ( aas && finalArea != -1 ) {
|
|
aas->PushPointIntoAreaNum( finalArea, finalPoint );
|
|
}
|
|
idThread::ReturnVector( finalPoint );
|
|
return;
|
|
}
|
|
idThread::ReturnVector( vec3_zero );
|
|
}
|
|
|
|
void hhHunterSimple::Show() {
|
|
flashlightTime = gameLocal.time + SEC2MS( spawnArgs.GetInt( "flashlight_delay" ) );
|
|
hhMonsterAI::Show();
|
|
}
|
|
|
|
void hhHunterSimple::Event_EnemyPortal( idEntity *ent ) {
|
|
//HUMANHEAD jsh PCF 4/27/06 Added check for enemy and hiddenness
|
|
if ( !enemy.IsValid() || AI_PATHING || fl.hidden ) {
|
|
return;
|
|
}
|
|
enemyPortal = ent;
|
|
SetState( GetScriptFunction( "state_PortalAttack" ) );
|
|
}
|
|
|
|
void hhHunterSimple::Event_GetEnemyPortal() {
|
|
if ( enemyPortal.IsValid() ) {
|
|
idThread::ReturnEntity( enemyPortal.GetEntity() );
|
|
} else {
|
|
idThread::ReturnEntity( NULL );
|
|
}
|
|
}
|
|
|
|
void hhHunterSimple::Event_OnProjectileHit(hhProjectile *proj) {
|
|
if ( !enemy.IsValid() || !proj || AI_VEHICLE ) {
|
|
return;
|
|
}
|
|
if ( ( proj->GetOrigin().ToVec2() - GetOrigin().ToVec2() ).LengthFast() < 150 ) {
|
|
AI_SHOTBLOCK = true;
|
|
}
|
|
}
|
|
|
|
void hhHunterSimple::BlockedFailSafe() {
|
|
if ( move.moveCommand < NUM_NONMOVING_COMMANDS || !ai_blockedFailSafe.GetBool() || blockedRadius < 0.0f || !enemy.IsValid() || AI_PATHING ) {
|
|
return;
|
|
}
|
|
if ( gameLocal.time > nextBlockCheckTime ) {
|
|
if ( ( lastMoveOrigin - physicsObj.GetOrigin() ).Length() < blockedRadius ) {
|
|
AI_BLOCKED_FAILSAFE = true;
|
|
}
|
|
lastMoveOrigin = physicsObj.GetOrigin();
|
|
nextBlockCheckTime = gameLocal.time + blockedMoveTime;
|
|
}
|
|
}
|
|
void hhHunterSimple::UpdateEnemyPosition( void ) {
|
|
idActor *enemyEnt = enemy.GetEntity();
|
|
int enemyAreaNum;
|
|
int areaNum;
|
|
aasPath_t path;
|
|
predictedPath_t predictedPath;
|
|
idVec3 enemyPos;
|
|
bool onGround;
|
|
|
|
if ( !enemyEnt ) {
|
|
return;
|
|
}
|
|
|
|
const idVec3 &org = physicsObj.GetOrigin();
|
|
|
|
if ( move.moveType == MOVETYPE_FLY ) {
|
|
enemyPos = enemyEnt->GetPhysics()->GetOrigin();
|
|
onGround = true;
|
|
} else {
|
|
onGround = enemyEnt->GetFloorPos( 64.0f, enemyPos );
|
|
if ( enemyEnt->OnLadder() ) {
|
|
onGround = false;
|
|
}
|
|
}
|
|
|
|
if ( onGround ) {
|
|
// when we don't have an AAS, we can't tell if an enemy is reachable or not,
|
|
// so just assume that he is.
|
|
if ( !aas ) {
|
|
enemyAreaNum = 0;
|
|
lastReachableEnemyPos = enemyPos;
|
|
} else {
|
|
enemyAreaNum = PointReachableAreaNum( enemyPos, 1.0f );
|
|
if ( enemyAreaNum ) {
|
|
areaNum = PointReachableAreaNum( org );
|
|
if ( PathToGoal( path, areaNum, org, enemyAreaNum, enemyPos ) ) {
|
|
lastReachableEnemyPos = enemyPos;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
AI_ENEMY_IN_FOV = false;
|
|
AI_ENEMY_VISIBLE = false;
|
|
|
|
if ( CanSee( enemyEnt, false ) ) {
|
|
AI_ENEMY_VISIBLE = true;
|
|
if ( CheckFOV( enemyEnt->GetPhysics()->GetOrigin() ) ) {
|
|
AI_ENEMY_IN_FOV = true;
|
|
}
|
|
|
|
SetEnemyPosition();
|
|
}
|
|
|
|
if ( ai_debugMove.GetBool() ) {
|
|
gameRenderWorld->DebugBounds( colorLtGrey, enemyEnt->GetPhysics()->GetBounds(), lastReachableEnemyPos, gameLocal.msec );
|
|
gameRenderWorld->DebugBounds( colorWhite, enemyEnt->GetPhysics()->GetBounds(), lastVisibleReachableEnemyPos, gameLocal.msec );
|
|
}
|
|
}
|
|
|
|
//HUMANHEAD jsh PCF 4/27/06 increased the upper z zalue of the bounds to 90.0f
|
|
bool hhHunterSimple::ReachedPos( const idVec3 &pos, const moveCommand_t moveCommand ) const {
|
|
if ( move.moveType == MOVETYPE_SLIDE ) {
|
|
idBounds bnds( idVec3( -4, -4.0f, -8.0f ), idVec3( 4.0f, 4.0f, 64.0f ) );
|
|
bnds.TranslateSelf( physicsObj.GetOrigin() );
|
|
if ( bnds.ContainsPoint( pos ) ) {
|
|
return true;
|
|
}
|
|
} else {
|
|
if ( ( moveCommand == MOVE_TO_ENEMY ) || ( moveCommand == MOVE_TO_ENTITY ) ) {
|
|
if ( physicsObj.GetAbsBounds().IntersectsBounds( idBounds( pos ).Expand( 8.0f ) ) ) {
|
|
return true;
|
|
}
|
|
} else {
|
|
idBounds bnds( idVec3( -16.0, -16.0f, -8.0f ), idVec3( 16.0, 16.0f, 90.0f ) );
|
|
bnds.TranslateSelf( physicsObj.GetOrigin() );
|
|
if ( bnds.ContainsPoint( pos ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void hhHunterSimple::Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ) {
|
|
HH_ASSERT( player == enemy.GetEntity() );
|
|
//HUMANHEAD jsh PCF 4/29/06 stop looking and make enemy not visible for a frame
|
|
Event_LookAtEntity( NULL, 0.0f );
|
|
AI_ENEMY_VISIBLE = false;
|
|
enemy = proxy;
|
|
nextSpiritCheck = gameLocal.time + SEC2MS( spawnArgs.GetFloat( "spirit_check_delay", "5" ) );
|
|
}
|
|
|
|
void hhHunterSimple::Distracted( idActor *newEnemy ) {
|
|
if ( newEnemy && newEnemy->IsType( hhTalon::Type ) ) {
|
|
SetState( GetScriptFunction( "state_TalonCombat" ) );
|
|
} else {
|
|
SetState( GetScriptFunction( "state_Combat" ) );
|
|
}
|
|
|
|
SetEnemy( newEnemy );
|
|
}
|
|
|
|
void hhHunterSimple::Event_EnemyVehicleDocked() {
|
|
if ( enemy.IsValid() && enemy->InVehicle() && enemy->GetVehicleInterface() ) {
|
|
hhVehicle *vehicle = enemy->GetVehicleInterface()->GetVehicle();
|
|
if ( vehicle && vehicle->IsDocked() ) {
|
|
idThread::ReturnInt( true );
|
|
return;
|
|
}
|
|
}
|
|
idThread::ReturnInt( false );
|
|
}
|
|
|
|
void hhHunterSimple::Event_Blocked() {
|
|
AI_NEXT_DIR_TIME = 0;
|
|
StopMove( MOVE_STATUS_DONE );
|
|
} |